Embedding git commit hash inside of a .net assembly

The need

The need is to easily identify which version of the code is deployed in an environment. Since the project in question wasn’t being build with TeamCity I needed a custom solution.

msbuild to the rescue

msbuild fires BeforeBuild and AfterBuild events during build process for .net application. We can use these events to modify (and reset) AssemblyInfo.cs file to include git commit hash as metadata. Problem with AfterBuild is that it doesn’t run if the build fails so after quite some research I found a PostBuildEvent target which does the job nicely.

The solution looks like this:

InΒ AssemblyInfo.cs file define an attribute to store git commit hash metadata

[assembly: AssemblyMetadata("GitCommitHash", "")]

Then in the csproj file inside some property group without any conditions set this property. Without it PostBuildEvent doesn’t fire on failed builds and you are left with a modified AssemblyInfo.cs which you don’t want.

<RunPostBuildEvent>Always</RunPostBuildEvent>

Then use this joy πŸ™‚

<UsingTask TaskName="SetGitCommitHash" TaskFactory="CodeTaskFactory" AssemblyFile="$(MSBuildToolsPath)\Microsoft.Build.Tasks.v4.0.dll">
  <ParameterGroup>
    <ProjectPath ParameterType="System.String" Required="True" Output="False" />
    <GitCommitHash ParameterType="System.String" Required="False" Output="False" />
  </ParameterGroup>
  <Task>
    <Using Namespace="System" />
    <Using Namespace="System.IO" />
    <Code Type="Fragment" Language="cs"><![CDATA[
var lines = File.ReadAllLines(ProjectPath + @"\Properties\AssemblyInfo.cs");
int hashIndex = Array.FindIndex(lines, l => l.StartsWith("[assembly: AssemblyMetadata(\"GitCommitHash\""));
lines[hashIndex] = "[assembly: AssemblyMetadata(\"GitCommitHash\", \"" + GitCommitHash + "\")]";
File.WriteAllLines(ProjectPath + @"\Properties\AssemblyInfo.cs", lines);
]]></Code>
  </Task>
</UsingTask>
<!-- To modify your build process, add your task inside one of the targets below and uncomment it. 
      Other similar extension points exist, see Microsoft.Common.targets.-->
<Target Name="BeforeBuild">
  <Exec Command="git rev-parse HEAD" ConsoleToMSBuild="true" ContinueOnError="True" Condition=" '$(GitCommitHash)' == '' ">
    <Output TaskParameter="ConsoleOutput" PropertyName="GitCommitHash" />
  </Exec>
  <SetGitCommitHash ProjectPath="$(MSBuildProjectDirectory)" GitCommitHash="$(GitCommitHash)" />
</Target>
<Target Name="PostBuildEvent">
  <SetGitCommitHash ProjectPath="$(MSBuildProjectDirectory)" GitCommitHash="" />
</Target>
<Target Name="AfterBuild" />

On line 1 we define a SetGitCommitHash task which finds the line with target attribute inside of AssemblyInfo.cs file and sets the hash value to whatever is passed to it.
On line 20 a git command is executed to retrieve current commit hash and then on line 21 it’s outputted to a GitCommitHash property. The condition is there for the cases when git command is not available and you are passing in this parameter to msbuild from the outside.
Finally lines 23 and 26 call the SetGitCommitHash to set the hash before build and clean it up after. This leaves no trace that AssemblyInfo.cs was ever modified.

Using the hash

I ended up with this solution. Extracting commit hash from assembly into a static variable during app start and then using it wherever necessary.

public class Global : HttpApplication
{
    public static string CommitHash = string.Empty;

    private void Application_Start(object sender, EventArgs e)
    {
        SetCommitHash();
    }

    private static void SetCommitHash()
    {
        string hash = typeof(Global).Assembly
            .GetCustomAttributes(typeof(System.Reflection.AssemblyMetadataAttribute), false)
            .Cast<System.Reflection.AssemblyMetadataAttribute>()
            .First(a => a.Key == "GitCommitHash").Value;

        CommitHash = hash;
    }
}

The caveat

One caveat to remember is that if the build is happening locally and and you xcopy Release folder output or Publish from VS, make sure that the latest changes are committed or otherwise the code in assemblies will not reflect the commit hash captured during build.

Leave a Comment

Your email address will not be published.