Skip to content

Instantly share code, notes, and snippets.

@pvspain
Last active September 21, 2020 23:20
Show Gist options
  • Save pvspain/aa5a249385b1c47613512308bcbdf263 to your computer and use it in GitHub Desktop.
Save pvspain/aa5a249385b1c47613512308bcbdf263 to your computer and use it in GitHub Desktop.
powershell profile incorporating git status
#! /usr/bin/env pwsh
function Prompt {
$promptColor,
$infoColor,
$warnColor,
$errorColor,
$defaultColor =
'Cyan',
'Green',
'Yellow',
'Red',
'White'
$version = $PSVersionTable.PSVersion.ToString()
# Substitute shortened form of $HOME if present
# We must double-up Windows path-separator characters '\' (backslash) before passing off for regex evaluation.
# Regex library is C-based and '\' (backslash) is the special-quoted-character-prefix. A literal backslash is represented as '\\'
$myHome = if ($IsLinux -or $IsMacOS) { $HOME } else {$HOME -replace '\\', '\\' }
$location = (Get-Location).Path -replace $myHome, '~'
Write-Host "PS v$version $location " -NoNewline -ForegroundColor $promptColor
# Get details about current repository
# We can't append "--abrev-ref HEAD" as it gets interpreted as additional arguments to "--short"
$gitDir,
$insideGitDir,
$isBareRepo,
$insideWorkTree,
$localRev,
$shortSha =
(& git rev-parse `
--git-dir `
--is-inside-git-dir `
--is-bare-repository `
--is-inside-work-tree `
'@' `
--short HEAD 2> $null)
# Are we in a git repository?
# $gitDir is relative path to '.git' dir for local repository, empty for non-repository
if ($gitDir) {
# Separator before git info
Write-Host '| ' -NoNewline -ForegroundColor $promptColor
function Test-DetachedHead {
$head = Get-Content "$gitDir\HEAD"
-not ($head -match 'ref: ')
}
# Initialise local vars
$fgColor, $next, $local, $remote, $status = $infoColor, @(), @(), @(), @()
## Process local repo 'dirty' status...
# Check for staged changes
$staged = [bool](& git diff --no-ext-diff --staged)
# Check for unstaged changes
$unstaged = [bool](& git diff --no-ext-diff)
# Required actions to migrate workspace state to local repository
if ($unstaged) { $next += , 'add' }
if ($staged) { $next += , 'commit' }
if ($next) { $fgColor = $warnColor }
## Process 'local' repo status...
if (Test-DetachedHead) {
$local = "local:$shortSha (detached)"
# Add any tags for current revision...
if ($localTags = (&git tag --points-at '@')) {
$local += " tags:$localTags"
}
$fgColor = $errorColor
}
else {
$localBranch = $(& git symbolic-ref --short HEAD)
$local = "local:$localBranch"
}
## Process 'remote' repo status...
# Remote status is irrelevant for detached local repo
if (-not (Test-DetachedHead)) {
$remoteBranch = (& git rev-parse --abbrev-ref '@{u}')
$remote = "remote:$remoteBranch"
# NOTE: Relies on a recent 'git fetch' operation for fresh remote information
$remoteRev = (& git rev-parse '@{u}')
$lastCommonRev = (& git merge-base '@' '@{u}')
# Check for valid values
if (($remoteRev -ne '@u') -and $lastCommonRev) {
# NOTE: We are not modifying $fgColor for remote state
if ($localRev -eq $remoteRev) {
# Local and remote repo committed revisions match.
}
elseif ($localRev -eq $lastCommonRev) {
# Remote repo ahead. Pull required.
# NOTE: Leading (,) below converts RHS to an array
# - and stops conversion of $next to scalar string
$next += , 'pull'
}
elseif ($remoteRev -eq $lastCommonRev) {
# Local repo ahead. Push required.
$next += , 'push'
}
else {
# Local and origin repos have diverged - pull then push.
$next += 'pull', 'push'
}
}
}
## Build displayed 'status'...
if ($next) { $status += "next:$($next -join ',')" }
if ($local) { $status += $local } # $local will always have content
if ($remote) { $status += $remote }
Write-Host "git $status" -NoNewline -ForegroundColor $fgColor
}
Write-Host "`n`$" -NoNewline -ForegroundColor $defaultColor
return " "
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment