Mark Ashley Bell

15 Feb 2021

Check the status of multiple Git working copies with Powershell

Background

I work with several folders containing multiple Git working copies, so I often find myself getting distracted and forgetting to commit or push changes, particularly when quickly switching between projects.

A couple of years ago I wrote a command line utility called gitinsync, which would give me back the status of every working copy in a given folder, so that I could periodically check if there were changes I hadn't committed. gitinsync uses LibGit2Sharp to query the origin repository and compare its state with the local working copy.

However, this tool has a number of flaws; the main one being that it doesn't work with repositories cloned over SSH; LibGit2Sharp has no current or planned support for SSH authentication, for valid reasons. It also feels rather overcomplicated...

So I decided to try and reproduce the behaviour of gitinsync, using only Git itself (instant 100% support for the whole Git feature set!) and a bit of Powershell.

The script

In summary: we loop over all child folders containing a .git folder, then run git status --porcelain in each, transform the results and display a nice, easy-to-scan table.

# Escape and colour codes for use in virtual terminal escape sequences
$esc = [char]27
$red = '31'
$green = '32'

# Get child folders of the current folder which contain a '.git' folder
Get-ChildItem -Path . -Attributes Directory+Hidden -Recurse -Filter '.git' | 
ForEach-Object { 
    # Assume the parent folder of this .git folder is the working copy
    $workingCopy = $_.Parent
    $repositoryName = $workingCopy.Name

    # Change the working folder to the working copy
    Push-Location $workingCopy.FullName

    # Update progress, as using -AutoSize on Format-Table
    # stops anything being written to the terminal until 
    # *all* processing is finished
    Write-Progress `
        -Activity 'Check For Local Changes' `
        -Status 'Checking:' `
        -CurrentOperation $repositoryName

    # Get a list of untracked/uncommitted changes
    [Array]$gitStatus = $(git status --porcelain) | 
        ForEach-Object { $_.Trim() }

    # Status includes VT escape sequences for coloured text
    $status = ($gitStatus) `
        ? "$esc[$($red)mCHECK$esc[0m" `
        : "$esc[$($green)mOK$esc[0m"

    # For some reason, the git status --porcelain output returns 
    # two '?' chars for untracked changes, when all other statuses 
    # are one character... this just cleans it up so that it's 
    # nicer to scan visually in the terminal
    $details = ($gitStatus -replace '\?\?', '?' | Out-String).TrimEnd()

    # Change back to the original directory
    Pop-Location

    # Return a simple 'row' object containing all the info
    [PSCustomObject]@{ 
        Status = $status
        'Working Copy' = $repositoryName
        Details = $details
    }
} |
Format-Table -Wrap -AutoSize

There are a couple of things going on here which warrant a little more explanation.

Git plumbing and porcelain

If you haven't seen git status --porcelain before, it's just a way of getting consistent, machine-parsable output from the git status command.

Confusingly, in the context of the git status command, the --porcelain switch actually means... not porcelain—but I'm sure we're all used to the “quirks” of the Git CLI by now.

Virtual Terminal escape sequences

These are sequences of characters which tell the terminal to transform the display or cursor in some way.

In this case, we're using them to make the text in the status column red or green depending on whether there are changes or not (hat tip to this Q&A for showing me how to use these).


Note: this is Powershell 7, so if you're using an earlier version, you'll need to rewrite the ternary conditional assigning to $status as a standard if/else.

Questions or comments? Get in touch @markeebee, or email [Turn On Javascript To View Email Address].

More articles

© Mark Ashley Bell 2023