You are currently browsing the category archive for the ‘Git’ category.

New! Improved! Or how it should be done in the first place!
Previously I wrote an article about integrating Git and Xcode: Displaying Git details in XCode 4.2 projects. That was a good approach but after writing it I realized that I had only completed half the task I should have, and this article addresses how I should have done it.

What I was trying to do in that previous article was to automatically include versioning information from the Git repository into the iOS application. In doing so I could then explicitly match any version of the App in the wild to the actual code used to produce that App – which would aid greatly in diagnosing any reported bugs. The script I provided extracted the last commit date and hash as well as the last tag inserted into the repository. It then generated an include file that imported the data as Objective-C string literals that could be used directly by the code.

This process worked well, but it wasn’t until I went to upgrade an App in the App store that I saw what was missing from what I did. The problem was that even though I was including the version information in the code itself, the version information that Apple wanted needed to be included in Info.plist file for the App. So regardless of my smarts, I was still left with having to manually update the version and build information in XCode prior to submitting an Archive to Apple.

So this article addresses the deficiencies of the previous article and shows how to automatically inject information into the Info.plist file and achieve a “hands-off” approach to tagging your Apps with their build number. And while I am using Git to supply that data, this build number could come from any source you liked.

What needs to change
The version information that I ultimately want to automatically set are two values in the Info.plist file for the project. These are (with descriptions taken from Apple):

CFBundleVersion Specifies the build version number of the bundle, which identifies an iteration (released or unreleased) of the bundle. This is a monotonically increased string, comprised of one or more period-separated integers. This key is not localizable.
CFBundleShortVersionString  Specifies the release version number of the bundle, which identifies a released iteration of the app. The release version number is a string comprised of three period-separated integers. The first integer represents major revisions to the app, such as revisions that implement new features or major changes. The second integer denotes revisions that implement less prominent features. The third integer represents maintenance releases.

The value for this key differs from the value for “CFBundleVersion,” which identifies an iteration (released or unreleased) of the app. This key can be localized by including it in your InfoPlist.strings files.

Within an XCode project, CFBundleShortVersionString and CFBundleVersion can be found in these locations:

Info.plist field Summary Name Info name
CFBundleShortVersionString Version Bundle version string; short
CFBundleVersion Build Bundle version

 

But first a word from your sponsor
In scanning various websites I have seen a lot of pages that want to achieve the same outcome as what I want. However A lot of them are based on executing /usr/libexec/PlistBuddy in order to directly insert data into the Info.plist file. This works, but to me it suffers from some deficiencies:

  1. Directly rewriting a file in the project is a brute force approach.
  2. If the project is under version control, then the act of building the project changes the version history.
  3. Because the value is changed “behind the scenes”, when looking at the project in Xcode it isn’t obvious that the values in question are being changed.
  4. It ignores the mechanism that Apple already provides for updating the file.

So with that said I am going to charge my solution with:

  1. Using the Apple supplied method for changing the Info.plist file
  2. Raising the visibility of the data that is being automatically changed
  3. Not changing the project’s version history when simply building the project

The true path
The key to doing things the Apple way is to note two options in a project’s build settings:

  • Preprocess Info.plist File
  • Info.plist Preprocessor prefix file

Setting the first one to “yes” turns on the processing of the Info.plist file and perform the substitutions of symbols for values, while the second setting designates the file that contains the required #defines that link the symbols with their values.

In my case (as per my previous article) I set the prefix file to be “gitDataAutoGenerated.h”, so that all I need to do is to generate this file prior to the Info.plist preprocessing during the building of the project. Within that file I created a #define called AUTOVERSION which I matched up with my auto created version number extracted from the Git repository for the project. Then in the the appropriate spots on the projects Summary or Info Page, I set the value of the CFBundleVersion and/or CFBundleShortVersionString to be “AUTOVERSION” (with no quotes) and then everything magically gets linked up when I build the project.

And as “gitDataAutoGenerated.h” is not a part of the project, it can be changed with impunity without affecting the project’s version control history.

First things first
As per my previous article, I had created a “run script” build phase within the project that hosted the script that generated the “gitDataAutoGenerated.h” file. But this approach totally failed when it came to modifying the Info.plist file. After inspection of the build output messages, it was obvious that even though I had set up the script to run prior to any compilation, the preprocessing of the Info.plist file was actually being run before the script. So it was time for plan “B”

Plan “B” is to create an additional target within the project that is dedicated to building the include file, and to make the App target dependent on the new script target. The process for doing this is:

  1. Add a new “Aggregate” target to the project called “VersionNumberScripts”
  2. Within the Build phases of this target, create a “Run script” phase and either copy over the the script from the equivalent phase from the App, or create the script from scratch. (If the script was copied, then the App’s run script phase can be deleted.)
  3. Within the build phases of the App, add the new VersionNumberScripts target as a Target Dependency

Once this is done the version number script will be run prior to the App target being built, thus ensuring that the correct information is ready for XCode to preprocess the Info.plist file in a timely manner.

Following the script
While the script from the previous article was fairly decent, I have updated it to be a little more robust by including default values for if/when the git commands fail to return any data. This new script is:

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

echo "Building file"

cd ${PROJECT_DIR}/${PROJECT_NAME}
gitDataFile="gitDataAutoGenerated.h"


echo "Get Information from system"

# Date and time that we are running this build
buildDate=`date "+%F %H:%M:%S"`

# Current branch in use
currentBranchTemp=`git rev-parse --abbrev-ref HEAD`
if [ -n "$currentBranchTemp" ]
 then
  currentBranch=$currentBranchTemp
 else
  currentBranch=""
fi

# Last hash from the current branch
lastCommitHashTemp=`git log --pretty=format:"%h" -1`
if [ -n "$lastCommitHashTemp" ]
 then
  lastCommitHash=$lastCommitHashTemp
 else
  lastCommitHash=""
fi

# Date and time of the last commit on this branch
lastCommitDateTemp=`git log --pretty=format:"%ad" --date=short -1`
if [ -n "$" ]
 then
  lastCommitDate=$lastCommitDateTemp
 else
  lastCommitDate=""
fi

# Comment from the last commit on this branch
lastCommitCommentTemp=`git log --pretty=format:"%s" -1`
if [ -n "$" ]
 then
  lastCommitComment=$lastCommitCommentTemp
 else
  lastCommitComment=""
fi

# Last tag applied to this branch
lastRepoTagTemp=`git describe --abbrev=0 --tags`
if [ -n "$lastRepoTagTemp" ]
 then
  lastRepoTag=$lastRepoTagTemp
 else
  lastRepoTag="0.0.0"
 fi

# Build the file with all the information in it
echo "Create header file"

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
echo -e "#define AUTOVERSION             $lastRepoTag" >> $gitDataFile


# Force the system to process the plist file
echo "touch plist"
touch ${PROJECT_DIR}/Info.plist

Note that this script does depend on the target name of the App being the same as that of the overall project.

An example of this scripts output is:

//-----------------------------------------
// Auto generated file
// Created 2012-10-24 15:57:47
//-----------------------------------------

#define BUILD_DATE              @"2012-10-24 15:57:47"
#define GIT_CURRENT_BRANCH      @"versionNumber"
#define GIT_LAST_COMMIT_HASH    @"2a92057"
#define GIT_LAST_COMMIT_DATE    @"2012-10-24"
#define GIT_LAST_COMMIT_COMMENT @"Fixed layout of About view controller"
#define GIT_LAST_REPO_TAG       @"1.1.0"
#define AUTOVERSION             1.1.0

Tag! You’re It
So you now have this great script that will extract tags from the project’s Git repository and insert them in the App as needed. You successfully build the App and look at the result .. but .. but ..where’s the tags?!?!??! why are they missing?!?!? I know that I tagged the release in Git.

And that’s what happened to me when I was building this system. I had tagged the repository, I had built the release, but there were no tags to be seen. The issue turned out to be where I was building the App.

At the same time I was creating this new build process, I was also setting up an additional computer for development, and I was testing the new build system on this new computer. In order to transfer the project’s Git repository to the new computer, I created a local Git server (well really a user named git on my main dev computer), pushed the project from the original system to the git server, and then cloned the project onto the new computer. Simple! Easy! Allows me to work on multiple computers! All sorts of possibilities now start to emerge.

What I hadn’t realized was that if you push a repository to another location the tags don’t get pushed. You have explicitly push the tags in a separate command. See 2.6 Git Basics – Tagging for a complete explanation on tagging.

So in addition to doing something like this:

git push origin master

I also have to do

git push origin --tags

It kinda sucks splitting the functionality up like this, but at least I learnt something new about git.

Last things last
Well that’s a wrap on my new build system. Given how easy it was to set things up the right way I’m kicking myself for not doing this the first time around. Now I have this nailed down I’m in the process of updating my NotchFlow App to run under iOS 6 (and with the new 4″ retina screen resolution). So look forward to update on that App soon.

Finally I have to put in a plug for 89Paint in Richmond. They are sponsoring the local Gangplank group and I have been working out of their offices this week – it sure beats sitting at home by myself talking to the cat!

Not happy Jan
While the integration of Git into XCode is a good step forward, for me it doesn’t go far enough. As I have been using XCode and Git I have been finding many use cases which you just can’t do within XCode, and that force you to drop back to the command line in order to get things. This includes things like seeing the true state of things, creating branches from previous commits and helping XCode/Git sort out the state of a project after it got a little confused.

So this week I am going to keep on the subject of Git point out a couple useful command line basics. This won’t be an extensive Git tutorial, so if you are looking for a whole lot more, then you should head on over to somewhere like Top 10 Git Tutorials for Beginners where you’ll find an extensive list of dedicated tutorials.

Before you can use any of these commands, you’ll need to open a Terminal window and navigate to the directory that your project is located in.

So show me!
The most basic Git command you can use is simply:

git

Which will respond with a summary of all the Git sub-commands

usage: git [--version] [--exec-path[=]] [--html-path]
           [-p|--paginate|--no-pager] [--no-replace-objects]
           [--bare] [--git-dir=] [--work-tree=]
           [-c name=value] [--help]
            <command> [<args>]

The most commonly used git commands are:
   add        Add file contents to the index
   bisect     Find by binary search the change that introduced a bug
   branch     List, create, or delete branches
   checkout   Checkout a branch or paths to the working tree
   clone      Clone a repository into a new directory
   commit     Record changes to the repository
   diff       Show changes between commits, commit and working tree, etc
   fetch      Download objects and refs from another repository
   grep       Print lines matching a pattern
   init       Create an empty git repository or reinitialize an existing one
   log        Show commit logs
   merge      Join two or more development histories together
   mv         Move or rename a file, a directory, or a symlink
   pull       Fetch from and merge with another repository or a local branch
   push       Update remote refs along with associated objects
   rebase     Forward-port local commits to the updated upstream head
   reset      Reset current HEAD to the specified state
   rm         Remove files from the working tree and from the index
   show       Show various types of objects
   status     Show the working tree status
   tag        Create, list, delete or verify a tag object signed with GPG

See 'git help ' for more information on a specific command.

Of these the one I use the most is:

git status

As it shows what the current state of my repository is. For example, on a project I am currently working on, running this command results in:

165-156-000-000:NotchFlow joalah$ git status

# On branch master
# Changes not staged for commit:
#   (use "git add <file>..." to update what will be committed)
#   (use "git checkout -- <file>..." to discard changes in working directory)
#
#	modified:   NotchFlow/CalcViewController.m
#
no changes added to commit (use "git add" and/or "git commit -a")

Which tells me that I am working on the “master” branch and there is one file that has been modified but not yet committed to the repository. While I knew from the XCode IDE that I had modified a file, the IDE itself doesn’t tell you what branch you are currently working on. For that you need to either open the XCode Organizer, or drop into the command line.

Another important command for seeing what is what, is:

git branch

By itself it will list out the branches that exist in the repository, and mark the one that is currently checked out:

n165-156-000-000:NotchFlow joalah$ git branch
  CleanupMVC
  LastWorking
  NewKeyboardLayout
  SmallerPreferences
* master

And finally the ultimate list of information from the repository can be seen in:

git log

This lists everything that has been done, tracking each change with the hash of each commit, and listing them all the way to the creation of the repository. For a project that has been under development for a while this can be a surprisingly long list of commits. Thus the log command comes with a slew of options with which to limit, arrange and order its results. The simplest of these options are things like:

-n Limits the output to the previous ‘n’ commits
-pretty=oneline Summarizes the commit hash and comment all on one line
--abbrev-commit Displays a partial commit hash

An example of using this command is:

n165-156-000-000:NotchFlow joalah$ git log --pretty=oneline -5 --abbrev-commit
adb209e Merged in cleanup of main view controller
55f771a Removed unused file
0624d7c Finished ceasing up locations
705aef0 ignoring autogenerated files
7c03745 Cleaned up location of text

One benefit of the abbreviated commit hash is that you can use it anywhere that git wants to see a commit hash. Thus you don’t have to key in all 40 characters of the full commit hash all the time.

Jumping from branch to branch
Once you know what branches are available, you can easily switch between them with

git checkout <branchname>

For example:

n165-156-000-000:NotchFlow joalah$ git branch
  CleanupMVC
  LastWorking
  NewKeyboardLayout
  SmallerPreferences
* master

n165-156-000-000:NotchFlow joalah$ git checkout CleanupMVC
M	NotchFlow/CalcViewController.m
Switched to branch 'CleanupMVC'

n165-156-000-000:NotchFlow joalah$ git branch
* CleanupMVC
  LastWorking
  NewKeyboardLayout
  SmallerPreferences
  master

Splitting branches
Like from within the XCode Organizer tool you can create and delete branches using the git command line. But differing from the Organizer you can also do so with a lot more options than just creating a new branch from the current state of an existing branch. The git command for branching:

git branch

Has some useful variations such as:

-m [<oldbranch>] <newbranch> Move or rename an existing branch.
-d <branchname> Delete an existing branch.
<branchname> [<start-point>] Create a new branch.
 
 

To me the ‘Create’ variant is the most useful, as it allows you to create a new branch from an arbitrary starting point in the repository. The starting point can be one of:

<branchname> The name of an existing branch
<commit-id> An arbitrary commit id from anywhere in the repository
<tag> An arbitrary tag from anywhere in the repository
 

If it is not specified, the the HEAD of the currently checked out branch will be used.

The following is an example of creating a new branch from a pre-existing commit, and then checking out the new branch. Note that the starting point was specified with only the abbreviate commit hash:

n165-156-000-000:NotchFlow joalah$ git log -5 --pretty=oneline -5 --abbrev-commit
55f771a Removed unused file
0624d7c Finished ceasing up locations
7c03745 Cleaned up location of text
e0bb05d Working with new messaging system
e70b543 Back together again .. I think

n165-156-000-000:NotchFlow joalah$ git branch WorkingAgain e0bb05d

n165-156-000-000:NotchFlow joalah$ git branch
* CleanupMVC
  LastWorking
  NewKeyboardLayout
  SmallerPreferences
  WorkingAgain
  master

n165-156-000-000:NotchFlow joalah$ git checkout WorkingAgain
M	NotchFlow/CalcViewController.m
Switched to branch 'WorkingAgain'

n165-156-000-000:NotchFlow joalah$ git log -5 --pretty=oneline -5 --abbrev-commit
e0bb05d Working with new messaging system
e70b543 Back together again .. I think
2c98537 New Display sort of broken
50b9427 Mostly works
e7d9596 Sort of working with new keyboard
n165-156-000-000:NotchFlow joalah$ 

Marking the trail
Finally in order to indicate a significant milestone in the life of a project, you can mark a commit with a tag. A tag is simply a text string that carries some meaning for the person who created it. Typically you would tag a commit to indicate something like the completion of a specific release version. The tags also include a description part which you can use to explain why a commit has been tagged.

With the git command:

git tag

You can list, add and delete tags through the commands options. Useful combinations of these options are:

-l [-n[<num>]] List all the existing tags, with ‘num’ lines of their descriptions. Note that if not specified, ‘num’ defaults to zero!
<tagname> -m <msg> [<commit>] Create a new tag with a a description <msg> for either the last or an arbitrarily specified commit hash
-d <tagname> Delete a tag
 

As an example:

n165-156-000-000:NotchFlow joalah$ git tag -l -n1
v0.01           Technical Feature Complete

n165-156-000-000:NotchFlow joalah$ git tag v1.0 -m 'testing tags'

n165-156-000-000:NotchFlow joalah$ git tag -l -n1
v0.01           Technical Feature Complete
v1.0            testing tags

n165-156-000-000:NotchFlow joalah$ git tag -d v1.0
Deleted tag 'v1.0' (was 3e9e48e)

n165-156-000-000:NotchFlow joalah$ git tag -l -n1
v0.01           Technical Feature Complete

Given a git tag, you can see the associate hash from this command:

git show <tag>

By default this produces a rather verbose output, but it can be trimmed down with a format option as in this example:

n165-156-000-000:NotchFlow joalah$ git show V1.0 -s --format="%h, %cd, %cr, %s"
tag v1.0
Tagger: Peter Milway <peter@JoalahDesigns.com>

testing tags
e41ee4a, Wed Mar 14 15:51:28 2012 -0400, 68 minutes ago, Added changes

Once again its been a pleasure
Peter

 
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