You can run a validation task for every commit in a Git branch in order to verify the integrity of a Git branch's history.

Just like the Soviet Union, I occasionally prefer to rewrite history... although I limit myself to rewriting the history of one or more Git branches. When I do that, I'd like to verify that I didn't break anything after an interactive rebase. It actually turns out that this can easily happen. Some commits are left in a state where they either don't compile, or tests fail. Git is happy, but the code isn't. To deal with such situations, I wanted a tool that could verify each and every commit in a branch like this:

  1. Check out a commit.
  2. Compile the code.
  3. Run all tests.
  4. Repeat for the next commit, or stop if there's a failure.
The tool should do this from the first to the last commit in my current branch (e.g. master).

Motivation

If you don't care about my motivation for doing this, you can skip this section and move on to the solution. However, some people might like to point out to me that I shouldn't rewrite the history of my Git repositories - and particularly not of the master branch. I agree, for all repositories where I actually collaborate with other programmers.

However, I also use Git locally to produce teaching materials. As an example, if you download the example code for my Pluralsight courses, you'll see that it's all available as Git repositories. I think that's an added benefit for the student, because not only can you see the final result, but you can also see all the steps that lead me there. I even occasionally write commit messages longer than a single line, explaining my thinking as I check in code.

Like everyone else, I'm fallible, so often, when I prepare such materials, I end up taking some detours that I feel will confuse students more than it will help. Thus, I have a need to be able to edit my Git repositories. Right now, as an example, I'm working with a repository with 120 commits on the master branch, and I need to make some changes in the beginning of the history.

Solution

With much help from Darrell Hamilton, who ended up providing this gist, I was able to piece together this bash script:

#!/bin/sh
COMMITS=$(git --git-dir=BookingApi/.git --work-tree=BookingApi log --oneline  --reverse | cut -d " " -f 1)
CODE=0

git --git-dir=BookingApi/.git --work-tree=BookingApi reset --hard
git --git-dir=BookingApi/.git --work-tree=BookingApi clean -xdf
 
for COMMIT in $COMMITS
do
    git --git-dir=BookingApi/.git --work-tree=BookingApi checkout $COMMIT
    # run-tests          
    ./build.sh

    if [ $? -eq 0 ]
    then
        echo $COMMIT - passed
    else
        echo $COMMIT - failed
        exit
    fi

    git --git-dir=BookingApi/.git --work-tree=BookingApi reset --hard
    git --git-dir=BookingApi/.git --work-tree=BookingApi clean -xdf
done
 
git --git-dir=BookingApi/.git --work-tree=BookingApi checkout master

As you can tell, it follows the algorithm outlined above. First, it does a git log to get all the commits in the branch, and then it loops through all of them, one by one. For each commit, it compiles and runs all tests by calling out to a separate build.sh script. If the build and test step succeeds, it cleans up the working directory and moves on to the next step. If the verification step fails, it stops, so that I can examine the problem.

(The reason for the use of --git-dir and --work-tree is that I need to run the script from outside the Git repository itself; otherwise, the git clean -xdf step would delete the script files!)

This has turned out to work beautifully, and has already caught quite a number of commits that Git could happily rebase, but afterwards either couldn't compile, or had failing tests.

Running those tests in a tight loop has also finally provided some work for my multi-core processor:

Each run through those 120 commits takes about 20 minutes, so, even though today we have fast compilers, once again, we have an excuse for slacking off.


Comments

Steven King
Hi Mark,

A talk on some of Git's lesser known features that I recently saw suggested that there is a built-in mechanism for what you are trying to achieve. The git bisect command allows you to run a script for each of a number of commits. The exact details on how to perform this are provided in Pro Git, so I won't repeat them here.

Thanks,
Steven
2013-10-11 12:04 UTC

Hi Steven

Thank you for writing. As far as I can tell from the documentation, git bisect doesn't quite do what I'd like to do. The bisect feature basically performs a search through the repository to figure out which commit introduces a particular error. The search algorithm is attempting to be as efficient as possible, so it keeps bisecting the list of commits to figure out on which side the error occurs - hence the name. Thus, it very explicitly doesn't check each and every commit.

My scenario is different. While I'm not looking for any particular error, I just want to verify that every commit is OK.

2013-10-11 12:56 UTC

As of Git 1.7.12, the interactive rebase command can take an --exec <cmd> option, which will make it execute <cmd> after each commit. If the command fails, the interactive rebase will stop so you can fix the problem.

2014-04-12 09:36 UTC

I didn't know about the --exec option, but I'll try it out the next time I perform a rebase. Thank you for the tip.

2014-04-12 11:29 UTC


Wish to comment?

You can add a comment to this post by sending me a pull request. Alternatively, you can discuss this post on Twitter or Google Plus, or somewhere else with a permalink. Ping me with the link, and I may add it as a comment.

Published

Monday, 07 October 2013 08:38:00 UTC

Tags



"Our team wholeheartedly endorses Mark. His expert service provides tremendous value."
Hire me!