Embedding Git Revision in projects, via Powershell

We have been working on an Azure cloud-services project, with a web interface. With constant updates it suddenly occurred to us that we had no system for determining exactly which revision was deployed. Embedding the revision identifier from your source control system—in our case git—is a standard approach, and this post documents our implementation and a couple of wrinkles encountered.1

Overview

Essentially, we need to do three things:

  1. Find the git SHA of the working copy;
  2. Embed this somewhere useful;
  3. Make sure it does this automatically before you deploy.

The last step is the easiest: you just run it as a pre-build hook.

How to display it

The closest description I could find of what we envisaged was this article.2 He outlines how to update the deployment label3 in the .azurepubXml profile, which was exactly what we were looking for. We eventually abandoned that aspect though because when you deploy directly from Visual Studio the profile is read before you hit “publish”, or in other words before your pre-build script has run and done anything useful.

Instead, we just write it to an HTML template that can be sourced anywhere: in a comment in every page, or on the administrators-only dashboard, or in the footer… it’s up to you.

The catch of course is you will now have a modified-file showing up in your git status every time you build. The solution to this is two-fold:

For the latter, you will need to edit it by hand:

<Content Include="Views\Shared\_GitRev.cshtml"
         Condition=" Exists('Views\Shared\_GitRev.cshtml') " />

As a bonus third step, you might want to think about how to gracefully fail if that template doesn’t exist for some reason. Assuming razor templates, an extension method is a useful approach:

public static MvcHtmlString PartialIfExists(this HtmlHelper html, string viewname)
{
    var controllerContext = html.ViewContext.Controller.ControllerContext;
    var result = ViewEngines.Engines.FindView(controllerContext, viewname, null);

    if (result.View != null)
    {
        return html.Partial(viewname);
    }
    else
        return MvcHtmlString.Empty;
}

Then your templates might contain something like

...
<!-- @Html.PartialIfExists("_GitRev") -->
...

Attempt 1: git.exe and powershell

The first version used the git executable to find the revision:

git rev-parse HEAD

A simple powershell script ties it together, and we’re in business:

if (Get-Command "git.exe" -ErrorAction SilentlyContinue)
{
    $revision = git rev-parse HEAD

    # make sure we don't include old versions if for some reason we
    # have one, but now can't find git.exe:
    $template = "$PSScriptRoot\..\Views\Shared\_GitRev.cshtml"
    if (Test-Path $template) {
        Remove-Item $template
    }

    Set-Content $template $revision
}

For completeness, it’s also worth pointing out here that it may be necessary to enable script execution for powershell permissions. I also include this line in my build hook:

powershell -Command "Set-ExecutionPolicy -Scope CurrentUser Unrestricted"

Attempt 2: Powershell-Native

That worked, but it introduces complications such as the need for git to be in your path, and generally feels a little fragile.

Luckily, I remembered libgit2, a portable standalone implementation of core git functionality. There are .NET bindings and nuget handles installation so this was much more promising:

try {
    # Note use of wildcard to ignore the version.
    Add-Type -Path $PSScriptRoot\..\..\packages\LibGit2Sharp*\lib\net35\LibGit2Sharp.dll

    $repo = New-Object -TypeName LibGit2Sharp.Repository -ArgumentList "$PSScriptRoot\..\..\..\.git"

    $template = "$PSScriptRoot\..\Views\Shared\_GitRev.cshtml"
    if (Test-Path $template) {
        Remove-Item $template
    }

    $revision = $repo.Head.Tip.Sha
    $dirty = ( $repo.Index.RetrieveStatus() | ? { $_.State -eq "Modified" } ).Count -gt 0

    if ($dirty) {
        $revision = "$revision*"
    }

    Set-Content $template $revision
    Write-Host "Labelled with $revision"
}
catch
{
    Write-Host "Exception during git-rev labelling; ignoring"
}

We use Add-Type to load the DLL (the hard-coded paths here feel slightly dirty, I admit, but we can use a wild-card to mitigate against version changes at least), then instantiate the Repository object. The SHA can then be accessed with $repo.Head.Tip.Sha. The next line also checks if any files are modified, a tweak which wasn’t present in the previous version.

Aftermath

This is the script now running as part of our builds. It works like a charm, but looking back it feels like more work than it should have been. I’m still relatively new to the Microsoft environment and tool-chain, so if there’s an easier way of doing this that I’m overlooking please let me know!

  1. It’s worth pointing out that what we’re about to do is a standard feature in subversion and others. Git doesn’t, and although it’s a pretty common question on stackoverflow, Linus is not a fan

  2. In between me finding that post and writing this, the author pulled it because he would do things differently now. He was kind enough to place it on github and let me link to it though. 

  3. In other words, instead of “MyApp - 26/05/2014 16:32:39” you now see “MyApp - 26/05/2014 16:32:39 - 01c5059500fa59c0f138d2f947f8493dd0a8914a” 

Mark Hepburn 26 May 2014
blog comments powered by Disqus