Adding git commit information to your assemblies

The problem

It is a fairly common practice of .NET projects hosted in Subversion to include the repository Revision number as part of the AssemblyVersion. For example, MyWidgets.dll might have a version of 1.2.0.5309, which means it was built from the snapshot of source code at revision 5309. This can be very valuable, as it makes it very easy to trace a binary in the wild back to its original source.

Many projects are now moving towards distributed version control systems (DVCS),  like git, which do not use a simple counter to describe a commit. In git, each commit is represented by a SHA1 hash, which looks something like e5f08d4a9e15943a64014329bd6fd2f348a89b15. Assembly versions in .NET are made up of four 16-bit integers, so it is obvious we cannot include the SHA1 of a commit. We need another way to get a unique number.

A solution

The solution I’ve been using is to take advantage of the “number of commits since the most recent tag”, as made available through the git describe command. In order for this to work, you must have at least one tag in your repository. Every time you change the major or minor components of your version, you should create a new tag for that commit. I’ll start by marking commit e5f08d4a9e15943a64014329bd6fd2f348a89b15 as version 1.2.0 (notice I leave off the last version component, as that is the number we will generate):

$ git tag v1.2.0 e5f08d

(Note: tags are not pushed to remotes, by default. Whenever you create a tag that you wish to make public, you must use the –tags flag when you push. See the Sharing Tags section in Pro Git)

I can now use the describe command like this:

$ git describe --tags --long

which will output:

v1.2.0-<strong>115</strong>-g4642f26

The –tags flag allows me to use lightweight tags, and the –long flag makes sure the output is always in the same format. In this example, the number 115 is what we are looking for. That means there have been 115 commits since the most recent tag, which is v1.2.0. The characters after the “g” make up the short version of the current commit’s SHA1. Now its just a simple matter of parsing the describe output so that we can use the number in our assembly version.

An example

You can see a real world example in FubuMVC build script. It is a rake (ruby) script that uses the assemblyinfo task from Albacore to generate a CommonAssemblyInfo.cs file at build time, which is referenced by all of the projects in the solution. The relevant section is:

desc "Update the version information for the build"
assemblyinfo :version do |asm|
  asm_version = BUILD_NUMBER_BASE + ".0"
  
  begin
	gittag = `git describe --long`.chomp 	# looks something like v0.1.0-63-g92228f4
    gitnumberpart = /-(d+)-/.match(gittag)
    gitnumber = gitnumberpart.nil? ? '0' : gitnumberpart[1]
    commit = (ENV["BUILD_VCS_NUMBER"].nil? ? `git log -1 --pretty=format:%H` : ENV["BUILD_VCS_NUMBER"])
  rescue
    commit = "git unavailable"
    gitnumber = "0"
  end
  build_number = "#{BUILD_NUMBER_BASE}.#{gitnumber}"
  tc_build_number = ENV["BUILD_NUMBER"]
  puts "##teamcity[buildNumber '#{build_number}-#{tc_build_number}']" unless tc_build_number.nil?
  asm.trademark = commit
  asm.product_name = "#{PRODUCT} #{gittag}"
  asm.description = build_number
  asm.version = asm_version
  asm.file_version = build_number
  asm.custom_attributes :AssemblyInformationalVersion => asm_version
  asm.copyright = COPYRIGHT
  asm.output_file = COMMON_ASSEMBLY_INFO
end
  • Lines 26-27: run the git describe the command, parse the output with a regular expression, and store it in the gitnumberpart variable
  • Line 29: store the full git commit SHA1 number in the commit variable (either using an environment variable available on the build server, or from the git log command)
  • Lines 31-32: just in case the machine running the build does not have the git client installed
  • Line 34: create the full version number by concatenating the BUILD_NUMBER_BASE variable with the gitnumber from describe. BUILD_NUMBER_BASE can either be hardcoded in the rake script, or read from a VERSION file. It contains the first 3 components of the version (ex: 1.2.0)
  • Lines 35-36: We rewrite our TeamCity build number to be the assembly version number followed by the sequence number provided by TeamCity (ex: #1.2.0.115-#330, where 330 is the sequence number from TC)
  • Line 37: We store the full commit SHA1 in the AssemblyTrademark attribute. We use this within the FubuMVC diagnostics pages to provide a link to the github page for the currently executing version of the framework.
  • Line 38: We append the full output of the describe command to the product name and store in AssemblyProduct (ex: FubuMVC v1.2.0-115g4642f26)
  • Lines 40-42: We write the various version attributes. Notice the AssemblyVersion attribute always uses zero for the last component of the version, instead of the ever increasing “commits since tag” number. This is on purpose, since AssemblyVersion is used as part of the assembly name, and should not change unless there are breaking changes. We only adjust the version on every build for AssemblyFileVersion, which is not used by .NET at all, but is visible within the File Properties dialog in Windows Explorer.

Related Articles:

This entry was posted in git, teamcity. Bookmark the permalink. Follow any comments here with the RSS feed for this post.
  • Łukasz Podolak

    Afaik, the default git plugin for teamcity doesn’t support agent-side checkout – only server-side is currently supported. What that means, is that the .git folder is not included in the downloaded files to the agent. So how do you run your git commands under teamcity build process ? Do you use custom git plugin for TeamCity ? If so, which one ?
    cheers

  • http://www.lostechies.com/members/jflanagan/default.aspx Joshua Flanagan

    You are correct. One of the changes I had to make on our TeamCity build server was to make sure we were using the community Git plugin, not the JetBrains Git plugin (both are available on teamcity.codebetter.com). I do not know which “community Git plugin” it is, as I do not manage that server.
    My best guess is that it is this one:
    http://github.com/chrisortman/git-teamcity
    based on reading this post which refers to it: http://teamcitydev.blogspot.com/2009/04/teamcity-git-support-from-jetbrains.html

  • http://www.lostechies.com/members/jflanagan/default.aspx Joshua Flanagan

    Regarding my last comment – according to the TeamCity 5.1 release notes, the JetBrains plugin should now support this.

  • http://andrewmyhre.myopenid.com/ Andrew

    Hi,

    Nice tutorial. I’m doing a similar thing by executing a git rev-parse command from within my msbuild script. Trouble is, it fails, saying ‘not a git repository’ – which makes sense because TeamCity just checks out the files without creating a local repo (as far as I know). I was wondering if you encountered this and if so how you worked around it?

    Thanks

    • Anonymous

      It sounds like you are using an old version of the jetbrains git plugin.
      Either upgrade to the latest teamcity, or seek out the “community” git
      plugin. If you use the community plugin, you have to select the option to
      check out files on the agent, which is not the default. Best option is to
      upgrade.

  • Pingback: Recording the git commit hash in your assembly with TeamCity | Richard Dingwall