GitUtils.ps1
# Inspired by Mark Embling # http://www.markembling.info/view/my-ideal-powershell-prompt-with-git-integration function Get-GitDirectory { if ($Env:GIT_DIR) { $Env:GIT_DIR } else { Get-LocalOrParentPath .git } } function Get-GitBranch($gitDir = $(Get-GitDirectory), [Diagnostics.Stopwatch]$sw) { if ($gitDir) { dbg 'Finding branch' $sw $r = ''; $b = ''; $c = '' if (Test-Path $gitDir\rebase-merge\interactive) { dbg 'Found rebase-merge\interactive' $sw $r = '|REBASE-i' $b = "$(Get-Content $gitDir\rebase-merge\head-name)" } elseif (Test-Path $gitDir\rebase-merge) { dbg 'Found rebase-merge' $sw $r = '|REBASE-m' $b = "$(Get-Content $gitDir\rebase-merge\head-name)" } else { if (Test-Path $gitDir\rebase-apply) { dbg 'Found rebase-apply' $sw if (Test-Path $gitDir\rebase-apply\rebasing) { dbg 'Found rebase-apply\rebasing' $sw $r = '|REBASE' } elseif (Test-Path $gitDir\rebase-apply\applying) { dbg 'Found rebase-apply\applying' $sw $r = '|AM' } else { dbg 'Found rebase-apply' $sw $r = '|AM/REBASE' } } elseif (Test-Path $gitDir\MERGE_HEAD) { dbg 'Found MERGE_HEAD' $sw $r = '|MERGING' } elseif (Test-Path $gitDir\CHERRY_PICK_HEAD) { dbg 'Found CHERRY_PICK_HEAD' $sw $r = '|CHERRY-PICKING' } elseif (Test-Path $gitDir\BISECT_LOG) { dbg 'Found BISECT_LOG' $sw $r = '|BISECTING' } $b = Invoke-NullCoalescing ` { dbg 'Trying symbolic-ref' $sw; git symbolic-ref HEAD 2>$null } ` { '({0})' -f (Invoke-NullCoalescing ` { dbg 'Trying describe' $sw switch ($Global:GitPromptSettings.DescribeStyle) { 'contains' { git describe --contains HEAD 2>$null } 'branch' { git describe --contains --all HEAD 2>$null } 'describe' { git describe HEAD 2>$null } default { git describe --tags --exact-match HEAD 2>$null } } } ` { dbg 'Falling back on parsing HEAD' $sw $ref = $null if (Test-Path $gitDir\HEAD) { dbg 'Reading from .git\HEAD' $sw $ref = Get-Content $gitDir\HEAD 2>$null } else { dbg 'Trying rev-parse' $sw $ref = git rev-parse HEAD 2>$null } if ($ref -match 'ref: (?<ref>.+)') { return $Matches['ref'] } elseif ($ref -and $ref.Length -ge 7) { return $ref.Substring(0,7)+'...' } else { return 'unknown' } } ) } } dbg 'Inside git directory?' $sw if ('true' -eq $(git rev-parse --is-inside-git-dir 2>$null)) { dbg 'Inside git directory' $sw if ('true' -eq $(git rev-parse --is-bare-repository 2>$null)) { $c = 'BARE:' } else { $b = 'GIT_DIR!' } } "$c$($b -replace 'refs/heads/','')$r" } } function Get-GitStatus($gitDir = (Get-GitDirectory)) { $settings = $Global:GitPromptSettings $enabled = (-not $settings) -or $settings.EnablePromptStatus if ($enabled -and $gitDir) { if($settings.Debug) { $sw = [Diagnostics.Stopwatch]::StartNew(); Write-Host '' } else { $sw = $null } $branch = $null $aheadBy = 0 $behindBy = 0 $indexAdded = @() $indexModified = @() $indexDeleted = @() $indexUnmerged = @() $filesAdded = @() $filesModified = @() $filesDeleted = @() $filesUnmerged = @() $stashCount = 0 if($settings.EnableFileStatus -and !$(InDisabledRepository)) { dbg 'Getting status' $sw $status = git -c color.status=false status --short --branch 2>$null if($settings.EnableStashStatus) { dbg 'Getting stash count' $sw $stashCount = $null | git stash list 2>$null | measure-object | select -expand Count } } else { $status = @() } dbg 'Parsing status' $sw $status | foreach { dbg "Status: $_" $sw if($_) { switch -regex ($_) { '^(?<index>[^#])(?<working>.) (?<path1>.*?)(?: -> (?<path2>.*))?$' { switch ($matches['index']) { 'A' { $indexAdded += $matches['path1'] } 'M' { $indexModified += $matches['path1'] } 'R' { $indexModified += $matches['path1'] } 'C' { $indexModified += $matches['path1'] } 'D' { $indexDeleted += $matches['path1'] } 'U' { $indexUnmerged += $matches['path1'] } } switch ($matches['working']) { '?' { $filesAdded += $matches['path1'] } 'A' { $filesAdded += $matches['path1'] } 'M' { $filesModified += $matches['path1'] } 'D' { $filesDeleted += $matches['path1'] } 'U' { $filesUnmerged += $matches['path1'] } } } '^## (?<branch>\S+?)(?:\.\.\.(?<upstream>\S+))?(?: \[(?:ahead (?<ahead>\d+))?(?:, )?(?:behind (?<behind>\d+))?\])?$' { $branch = $matches['branch'] $upstream = $matches['upstream'] $aheadBy = [int]$matches['ahead'] $behindBy = [int]$matches['behind'] } '^## Initial commit on (?<branch>\S+)$' { $branch = $matches['branch'] } } } } if(!$branch) { $branch = Get-GitBranch $gitDir $sw } dbg 'Building status object' $sw $indexPaths = $indexAdded + $indexModified + $indexDeleted + $indexUnmerged $workingPaths = $filesAdded + $filesModified + $filesDeleted + $filesUnmerged $index = New-Object PSObject @(,@($indexPaths | ?{ $_ } | Select -Unique)) | Add-Member -PassThru NoteProperty Added $indexAdded | Add-Member -PassThru NoteProperty Modified $indexModified | Add-Member -PassThru NoteProperty Deleted $indexDeleted | Add-Member -PassThru NoteProperty Unmerged $indexUnmerged $working = New-Object PSObject @(,@($workingPaths | ?{ $_ } | Select -Unique)) | Add-Member -PassThru NoteProperty Added $filesAdded | Add-Member -PassThru NoteProperty Modified $filesModified | Add-Member -PassThru NoteProperty Deleted $filesDeleted | Add-Member -PassThru NoteProperty Unmerged $filesUnmerged $result = New-Object PSObject -Property @{ GitDir = $gitDir Branch = $branch AheadBy = $aheadBy BehindBy = $behindBy Upstream = $upstream HasIndex = [bool]$index Index = $index HasWorking = [bool]$working Working = $working HasUntracked = [bool]$filesAdded StashCount = $stashCount } dbg 'Finished' $sw if($sw) { $sw.Stop() } return $result } } function InDisabledRepository { $currentLocation = Get-Location foreach ($repo in $Global:GitPromptSettings.RepositoriesInWhichToDisableFileStatus) { if ($currentLocation -like "$repo*") { return $true } } return $false } function Enable-GitColors { Write-Warning 'Enable-GitColors is Obsolete and will be removed in a future version of posh-git.' } function Get-AliasPattern($exe) { $aliases = @($exe) + @(Get-Alias | where { $_.Definition -eq $exe } | select -Exp Name) "($($aliases -join '|'))" } function setenv($key, $value) { [void][Environment]::SetEnvironmentVariable($key, $value, [EnvironmentVariableTarget]::Process) Set-TempEnv $key $value } function Get-TempEnv($key) { $path = Join-Path ($Env:TEMP) ".ssh\$key.env" if (Test-Path $path) { $value = Get-Content $path [void][Environment]::SetEnvironmentVariable($key, $value, [EnvironmentVariableTarget]::Process) } } function Set-TempEnv($key, $value) { $path = Join-Path ($Env:TEMP) ".ssh\$key.env" if ($value -eq $null) { if (Test-Path $path) { Remove-Item $path } } else { New-Item $path -Force -ItemType File > $null $value | Out-File -FilePath $path -Encoding ascii -Force } } # Retrieve the current SSH agent PID (or zero). Can be used to determine if there # is a running agent. function Get-SshAgent() { if ($env:GIT_SSH -imatch 'plink') { $pageantPid = Get-Process | Where-Object { $_.Name -eq 'pageant' } | Select -ExpandProperty Id -First 1 if ($null -ne $pageantPid) { return $pageantPid } } else { $agentPid = $Env:SSH_AGENT_PID if ($agentPid) { $sshAgentProcess = Get-Process | Where-Object { $_.Id -eq $agentPid -and $_.Name -eq 'ssh-agent' } if ($null -ne $sshAgentProcess) { return $agentPid } else { setenv 'SSH_AGENT_PID', $null setenv 'SSH_AUTH_SOCK', $null } } } return 0 } # Attempt to guess Pageant's location function Find-Pageant() { Write-Verbose "Pageant not in path. Trying to guess location." $gitSsh = $env:GIT_SSH if ($gitSsh -and (test-path $gitSsh)) { $pageant = join-path (split-path $gitSsh) pageant } if (!(get-command $pageant -Erroraction SilentlyContinue)) { return } # Guessing failed. else { return $pageant } } # Attempt to guess $program's location. For ssh-agent/ssh-add. function Find-Ssh($program = 'ssh-agent') { Write-Verbose "$program not in path. Trying to guess location." $gitItem = Get-Command git -Erroraction SilentlyContinue | Get-Item if ($gitItem -eq $null) { Write-Warning 'git not in path'; return } $sshLocation = join-path $gitItem.directory.parent.fullname bin/$program if (get-command $sshLocation -Erroraction SilentlyContinue) { return $sshLocation } $sshLocation = join-path $gitItem.directory.parent.fullname usr/bin/$program if (get-command $sshLocation -Erroraction SilentlyContinue) { return $sshLocation } } # Loosely based on bash script from http://help.github.com/ssh-key-passphrases/ function Start-SshAgent([switch]$Quiet) { [int]$agentPid = Get-SshAgent if ($agentPid -gt 0) { if (!$Quiet) { $agentName = Get-Process -Id $agentPid | Select -ExpandProperty Name if (!$agentName) { $agentName = "SSH Agent" } Write-Host "$agentName is already running (pid $($agentPid))" } return } if ($env:GIT_SSH -imatch 'plink') { Write-Host "GIT_SSH set to $($env:GIT_SSH), using Pageant as SSH agent." $pageant = Get-Command pageant -TotalCount 1 -Erroraction SilentlyContinue $pageant = if ($pageant) {$pageant} else {Find-Pageant} if (!$pageant) { Write-Warning "Could not find Pageant."; return } Start-Process -NoNewWindow $pageant } else { $sshAgent = Get-Command ssh-agent -TotalCount 1 -ErrorAction SilentlyContinue $sshAgent = if ($sshAgent) {$sshAgent} else {Find-Ssh('ssh-agent')} if (!$sshAgent) { Write-Warning 'Could not find ssh-agent'; return } & $sshAgent | foreach { if($_ -match '(?<key>[^=]+)=(?<value>[^;]+);') { setenv $Matches['key'] $Matches['value'] } } } Add-SshKey } function Get-SshPath($File = 'id_rsa') { $home = Resolve-Path (Invoke-NullCoalescing $Env:HOME ~) Resolve-Path (Join-Path $home ".ssh\$File") -ErrorAction SilentlyContinue 2> $null } # Add a key to the SSH agent function Add-SshKey() { if ($env:GIT_SSH -imatch 'plink') { $pageant = Get-Command pageant -Erroraction SilentlyContinue | Select -First 1 -ExpandProperty Name $pageant = if ($pageant) {$pageant} else {Find-Pageant} if (!$pageant) { Write-Warning 'Could not find Pageant'; return } if ($args.Count -eq 0) { $keystring = "" $keyPath = Join-Path $Env:HOME ".ssh" $keys = Get-ChildItem $keyPath/"*.ppk" | Select -ExpandProperty Name foreach ( $key in $keys ) { $keystring += "`"$keyPath\$key`" " } if ( $keystring ) { & $pageant "$keystring" } } else { foreach ($value in $args) { & $pageant $value } } } else { $sshAdd = Get-Command ssh-add -TotalCount 1 -ErrorAction SilentlyContinue $sshAdd = if ($sshAdd) {$sshAdd} else {Find-Ssh('ssh-add')} if (!$sshAdd) { Write-Warning 'Could not find ssh-add'; return } if ($args.Count -eq 0) { & $sshAdd } else { foreach ($value in $args) { & $sshAdd $value } } } } # Stop a running SSH agent function Stop-SshAgent() { [int]$agentPid = Get-SshAgent if ($agentPid -gt 0) { # Stop agent process $proc = Get-Process -Id $agentPid -ErrorAction SilentlyContinue if ($proc -ne $null) { Stop-Process $agentPid } setenv 'SSH_AGENT_PID', $null setenv 'SSH_AUTH_SOCK', $null } } function Update-AllBranches($Upstream = 'master', [switch]$Quiet) { $head = git rev-parse --abbrev-ref HEAD git checkout -q $Upstream $branches = (git branch --no-color --no-merged) | where { $_ -notmatch '^\* ' } foreach ($line in $branches) { $branch = $line.SubString(2) if (!$Quiet) { Write-Host "Rebasing $branch onto $Upstream..." } git rebase -q $Upstream $branch > $null 2> $null if ($LASTEXITCODE) { git rebase --abort Write-Warning "Rebase failed for $branch" } } git checkout -q $head } |