Note that I have revised these ideas in Displaying Git details in XCode projects revisited
 

Cruising along
Source or Version control systems (VCS) are great for organizing the journey of a program from its initial inception through to the latest release. Through judicious use of committing and tagging your files, you can easily retrieve, rebuild or debug any version of code from anywhere in your programs life history. (In fact proper use of a source control system is something that all professional software writers should master, no matter what flavour.)

With XCode 4, Apple chose to integrate Git directly into the editor, allowing it to offer to build repositories each time you create a new project. As a VCS, Git is one of the more modern varieties, built on a premise of distributed software development, and easily creating and merging multiple branches of code in parallel. But while not everyone works with a development team distributed around the world, everyone should know how to create and merge branches in XCode.

The ease of branching in Git helps to keep the main branch of your code pollution free. Instead of trying a new feature in the release version, you branch off the entire codebase to a test branch, and try new things, or build new features in isolation. Then when you are happy with the result, you just merge your changes back into the main branch.

Rocks ahead!
But what if you have multiple versions/branches of your program out in the world and a user reports a bug. How do you know what code was used to build that particular instance? Somehow you need to reconcile the bug report with the code in your VCS, so you know what code to pull for testing. If only the bug report was tagged with the codes version and you could look up that tag in the repository!

A naive way of doing this would be to place a string or numeric constant in your code that described the current version information and then manually update it when you decided on a version change. This could then be displayed on a label somewhere in your program.

NSString* version = @"Version 1.1";

...

label.text = version;

The problem with this type of system is two-fold:

  1. The version is never guaranteed to be correct, as you will make changes to the program, but forget to update the string.
  2. The VCS doesn’t know about the string, so you can’t ask it to return that particular version

Charting a better course
As the VCS knows about the what versions of code are stored in it, it is better to get the VCS to tell the code what version it is. And if you make this a part of the projects build system, then this will happen automatically and seamlessly – thus saving you from a manual step.

In the case of Git, there are several items that can be extracted from the repository that can uniquely identify what version of code was used to build a particular application’s release:

  • The current branch name
  • The last commit hash
  • The last commit date
  • The last commit comment
  • The last repository tag

All of these items can easily be obtained by running the command line Git tool like so:

Current branch name: git rev-parse –abbrev-ref HEAD
Last commit hash: git log –pretty=format:”%h” -1
Last commit date: git log –pretty=format:”%ad” –date=short -1
Last commit comment: git log –pretty=format:”%s” -1
Last repository tag: git describe –abbrev=0 –tags

(For a description of these commands, please see the Git website)

All that is needed now is to integrate this into your projects build system!

Building the build system
When I researched how to get the Git details into a project, there were several solutions that involved running a script, creating a file and modifying plists and then doing a few more other things. To me they were overcomplicating things a bit. All I wanted was to create a bunch of #defines in a simple .h file and include that where it was needed. Doing this in XCode 4 turned out to be very very easy. All that I did was:

  1. Select the target I want to include the Git data in
  2. Selected the Build Phases of the target
  3. Created a new “Run Script”
  4. Entered OSX shell script commands to create the .h file
  5. Imported the .h file into the source as needed
  6. Added the .h file to my git .ignore file

Visually this is:

Select the Target in the project</td
Go to the Build Phases</td
Create a new “Run Script” phase</td
Move the “Run Script” to be before the “Compile Sources” phase</td
Edit the “Run Script”</td

All is revealed
My script file that I inserted into the Run Scripts is:

# build data file that is included in the source
# so we can automatically report Git repo information
# in the application

cd ${PROJECT_DIR}/${TARGETNAME}
gitDataFile="gitDataAutoGenerated.h"
buildDate=`date "+%F %H:%M:%S"`
currentBranch=`git rev-parse --abbrev-ref HEAD`
lastCommitHash=`git log --pretty=format:"%h" -1`
lastCommitDate=`git log --pretty=format:"%ad" --date=short -1`
lastCommitComment=`git log --pretty=format:"%s" -1`
lastRepoTag=`git describe --abbrev=0 --tags`

echo -e "//-----------------------------------------" > $gitDataFile
echo -e "// Auto generated file" >> $gitDataFile
echo -e "// Created $buildDate" >> $gitDataFile
echo -e "//-----------------------------------------" >> $gitDataFile
echo -e "" >> $gitDataFile
echo -e "#define BUILD_DATE              @\"$buildDate\"" >> $gitDataFile
echo -e "#define GIT_CURRENT_BRANCH      @\"$currentBranch\"" >> $gitDataFile
echo -e "#define GIT_LAST_COMMIT_HASH    @\"$lastCommitHash\"" >> $gitDataFile
echo -e "#define GIT_LAST_COMMIT_DATE    @\"$lastCommitDate\"" >> $gitDataFile
echo -e "#define GIT_LAST_COMMIT_COMMENT @\"$lastCommitComment\"" >> $gitDataFile
echo -e "#define GIT_LAST_REPO_TAG       @\"$lastRepoTag\"" >> $gitDataFile

And for the current state of my project, this produces the gitDataAutoGenerated.h file with the following contents:

//-----------------------------------------
// Auto generated file
// Created 2012-03-04 18:41:15
//-----------------------------------------

#define BUILD_DATE              @"2012-03-04 18:41:15"
#define GIT_CURRENT_BRANCH      @"master"
#define GIT_LAST_COMMIT_HASH    @"8988fe8"
#define GIT_LAST_COMMIT_DATE    @"2012-03-04"
#define GIT_LAST_COMMIT_COMMENT @"Toned down size of elements in preferences"
#define GIT_LAST_REPO_TAG       @"v0.01"

Which I include in my program as:

#import "gitDataAutoGenerated.h"
...
programBuildDate.text = BUILD_DATE;
programCreateDate.text = GIT_LAST_COMMIT_DATE;
programVersion.text = [NSString stringWithFormat:@"%@%@%@", GIT_LAST_REPO_TAG,([GIT_LAST_REPO_TAG length]>0?@".":@"") , [GIT_LAST_COMMIT_HASH uppercaseString]];

And I display this on an information page as:

So now if ever I get a bug report from this program, I can use the Git commit hash of 8988fe8 to go straight to the source and re-create exactly the program version that the user has. Of course if I was being really retentive I would have archived the software binary along with the source code! But thats a story for a different day.

Hope you all found this useful.
Peter

About these ads