2019-05-27 Programmatically checking for uncommitted changes

One problem that I encounter over and over again is checking if a Git repository is “clean”, i.e., if it doesn’t contain any uncommitted changes. This post serves as an easy way to remind myself how to accomplish that.

One problem with that is that it is not entirely obvious what does it mean for a repo to be “clean”. If a repo contains uncommitted changes in tracked files, it is of course “dirty” – but what if there are untracked files? Fortunately, there is an easy way to check both possibilities.

First of all, we must take care to use git status --porcelain, which is guaranteed not to change between Git versions (and not to include colors, which would be a nuisance when parsing by a script). Follow the manual to learn about the exact format this command outputs; for our purposes, it is enough to know that it displays all paths (files and directories) which differ between the HEAD and both the working directory and the index, and the way they differ is encoded as a two-character string at the beginning of each line. We are not really interested in whether the paths were added, deleted, modified or whatever – the only thing we want to know is whether there are any differences. In case we want to disregard untracked files, we need to exclude from its output all lines beginning with two question marks.

Now, checking if a command’s output was empty or not is easy in Bash scripts: the

[[ $(command) ]]

construct evaluates to true (i.e., zero) if command has empty output.

To sum it up, here is a handy Bash script, resulting in a non-zero exit code if the repo is dirty (allowing non-tracked files)

#!/bin/bash
if [[ $(git status --porcelain | grep -Pv '^\?\?') ]];
then
    exit 0
else
    exit 1
fi

And if we want to consider a repo with untracked files as dirty, we need to leave out the grep invocation.

Of course, all this is for writing Bash scripts. If we want to perform similar checks from Elisp, for example, we may want the shell-command-to-string function. It accepts a shell command and returns a string captured from its stdout (together with stderr if any). It is not extremely efficient to check if its output is an empty string, since under the hood it collects the output of the shell command in a temporary buffer and then converts it into a string, but I guess that the overhead of calling the shell is much, much larger, so this probably doesn’t matter at all.

CategoryEnglish, CategoryBlog, CategoryGit, CategoryEmacs