Git-aware PowerShell prompt

Today a colleague sent around an interesting link on how to put your git status in your bash prompt. I thought I’d spend a few minutes trying to get a similar effect in PowerShell (v2 CTP3). It’s not particularly useful, but I found it an interesting exercise. Here’s what I came up with, saved in GitAwarePrompt.ps1. Disclaimer: I know nothing about PowerShell so use anything from this post at your own risk. :)

function Get-GitBranchNameWithStatusIndicator {
  $statusOutput = Invoke-Expression 'git status 2>$null'  #1
  if (!$statusOutput) { return } #2
  $branch = $statusOutput[0] #3
  if ($branch -eq "# Not currently on any branch.") {
    $branch = "No branch"
  } else {
    $branch =  $branch.SubString("# On branch ".Length) 
  $statusSummary = $statusOutput[-1] #4
  if ($statusSummary -eq "nothing to commit (working directory clean)") { #5
    $statusIndicator = "" 
  } else {
    $statusIndicator = "*"
  return $branch + $statusIndicator

function prompt { #6
  $gitStatus = Get-GitBranchNameWithStatusIndicator
  Write-Host ("PS " + $(get-location)) -nonewline
  if ($gitStatus) {
    Write-Host (" [" + $gitStatus +"]") -nonewline -foregroundcolor Gray
  Write-Host (">") -nonewline
  return " "

I’ve put some numbered comments on a couple of lines so we can go through the main parts of the script, or you can skip to the next heading if you just want to try out the script.

Line #1 uses the Invoke-Expression commandlet to run the git status command and store the output in the local $statusOutput variable. We are using 2>$null to drop any output written to standard error, otherwise whenever we try and write a prompt in a non-git directory we’ll get a git error message appearing. You’ll also notice we have the git status 2>$null command surrounded by single quotes (‘), rather than double quotes ("). The reason is that PowerShell automatically performs variable substitution within double quoted strings. So if we used double quotes the $null would be replaced by with nothing (it’s current value), which will cause us no end of problems (go on, ask me how I know! :)).

If the git status command errors out (say, if the current directory is not in a git repo), then $gitStatus will be null. Line #2 checks for this and returns void if $statusOutput is undefined. Otherwise $statusOutput will an array of objects, with an item for each line of the command output. Line #3 grabs the first line of output, which contains a string which contains our branch name, and stores it in a variable. The if/else that follow checks first to make sure we are actually on a branch, and parses the branch name from the line.

Line #4 was a nice surprise for me – PowerShell supports wrapped array indexing! Using an index of -1 grabs the last item in the array. Very pythonesque :). Line #5 then compares this with the output git status gives when there are no changes, and the rest of the if/else block sets the $statusIndicator based on this result. (No built in ternary operator apparently.)

Finally, line #6 defines a prompt function, which PowerShell uses to write it’s prompt.

Using the script

If we just run this script from within PowerShell it will do absolutely nothing. The reason is because any functions or variables defined will be cleaned up when the script exits. To change the scope and affect the current environment we need to dot-source the script, like this (depending on where you saved the script):

PS > . $HOME/Documents/WindowsPowerShell/GitAwarePrompt.ps1

Without the dot and space, you get nothing. With the dot-sourced script, we have created a prompt function for PowerShell to call, and we get the status of our Git working directory from our command line:

PS C:\Development\git\HelloWorld [helloGit]> "Modify hello.txt" > hello.txt
PS C:\Development\git\HelloWorld [helloGit*]> git commit -a -m "Commit change"
[helloGit]: created 8a7947c: "Commit change"
 1 files changed, 0 insertions(+), 0 deletions(-)
PS C:\Development\git\HelloWorld [helloGit]> git checkout master
Switched to branch "master"
PS C:\Development\git\HelloWorld [master]> "Change hello.txt again" > hello.txt
PS C:\Development\git\HelloWorld [master*]>
PS C:\Development\git\HelloWorld [master*]> cd ..
PS C:\Development\Git>

Setting the default prompt

You can also more permanently modify your PowerShell prompt by putting the script in your PowerShell profile, which is set to something like ~\Documents\WindowsPowerShell\Microsoft.PowerShell_profile.ps1 by default (if it doesn’t exist, you can create it from PowerShell using New-Item $PROFILE -ItemType file -Force). This can be something like the Git-aware prompt shown here, or something more interesting. Be careful you don’t slow down your shell too much though: prompt is obviously called on every new line!