Some useful Git commands for CI

I spend a lot of time writing build scripts for interacting with Git repos. But Git’s documentation is notoriously opaque, and a lot of my Git knowledge comes from word-of-mouth rather than reading the docs. In that spirit of sharing, these are few of the Git commands I find useful when writing build scripts.


$ git rev-parse --show-toplevel
/Users/alexwlchan/repos/alexwlchan.net

This command gives you an absolute path to the top-level of your repository.

Often scripts expect to be run from the root of a repo, so assume that $(pwd) gives you that path – but that’s not always true. Using this command always gives you the top of your repo.

Some examples of how I use this:


$ git rev-parse --show-prefix
_posts/2017/

While reading the git-rev-parse docs for this post, I stumbled upon this similar command. This tells you where you are, relative to the top-level of the repo.

I don’t have a use for this yet, but I feel like it’s bound to come in handy.


$ git diff --exit-code
$ echo $?
0

$ echo "Hello world" >> README.md
$ git diff --exit-code
diff --git a/README.md b/README.md
index a562e4a..a20d083 100644
--- a/README.md
+++ b/README.md
@@ -27,3 +27,4 @@ $ make serve

 A local copy of the site will be served on <http://localhost:5757>.
 If you make changes to the source files, this version will automatically update.
+Hello world

$ echo $?
1

Running git diff will show you any unstaged changes. Adding --exit-code will cause it to have a non-zero exit code when you have a non-empty diff.

I use this to check that automated tasks have run correctly:

This is a pattern I originally read in the Hypothesis Makefile.

(If you just want an exit code and no printed diff, add --quiet.)


$ git diff --name-only
Gemfile
Gemfile.lock
Makefile
README.md

$ git diff --name-only e7791a0 82eb01c
_plugins/static_file_generator.rb
install_jekyll.sh

Adding the --name-only flag to git diff gives you a list of files with changes.

In larger projects, it can be useful to skip some of the tests/builds if there aren’t any changes to relevant files. Output from this command can be input in a decision whether to run certain parts of the build.

A few examples:

On its own, this command isn’t very useful – it’s when you wrap your own logic around it that it becomes really powerful.


$ git merge-base --is-ancestor master my-branch; echo $?
0

$ git merge-base --is-ancestor other-branch my-branch; echo $?
1

This final command can tell you if one commit is the ancestor (directly precedes) another commit. It exits with code 0 for yes, code 1 for no.

The example is based on the following branch structure:

There are three commits up to master (marked in blue). There are then additional two commits atop master to my-branch (red), and three commits in a different branch from master, named other-branch (green).

Most of the time, I’m comparing against master – more specifically, the shared master that’s been pushed to GitHub. Checking that shared master is an ancestor of my current branch is a good way to ensure I don’t inadvertently blat changes in master.

Let’s look at an example.

Now master has advanced another two commits (blue), while other-branch (green) hasn’t moved.

Consider the two commits in master but not in other-branch. Suppose they contain important changes, and we’ve deployed them to a staging server. If we do a deployment from other-branch, we’ll deploy our changes and revert the already-deployed changes from master. This opens the door for mistakes and confusion – so we can use --is-ancestor to check we’re only building on top of current master, not some earlier version of it.

In this case, a deployment from other-branch would be rejected until we’d rebased against master.


I use all of these commands on a regular basis, but I bet there are others I’m missing. What Git commands do you find indispensable? What do you wish more people knew about? I’d love to know what you find useful – write your own blog post, or tell me on Twitter.