Setting up git-clang-format

I am draconian when it comes to code formatting. Combined with an “I’ll know it when I see it” attitude, this can become irritating. In exasperation, a colleague suggested using clang-format to automatically format our Objective C codebase. Their proposal:

I’ll get the tool’s formatting specification as close to what you like as possible. In return, you agree that you’ll accept what the tool produces.

So, quite some time ago, we introduced a clang-formatting file to our repository.

Huge, horrible whitespace monsters

Originally we suggested using a plugin for Xcode to format code. This is a pretty great tool, but we had a problem: it was hard to format just the lines that had been changed, leading to frustration picking apart “real” changes from white space changes.

The other day I came across a script to solve this problem, from the LLVM team themselves, git-clang-format. It’s a python script that hooks into git to give you a git clang-format command, which runs clang-format over the changes staged for commit.

Surprisingly — to me at least — it’s really easy to install a git extension:

  1. Call the script something starting with git-.
  2. Put it in your path.
  3. Make it executable.

So for git-clang-format, the process runs something like this:

  1. Install the standalone clang-format tool: brew install clang-format.
  2. Download git-clang-format from here. Read it to check for nastiness.
  3. Move the script somewhere in your path, for me mv git-clang-format ~/bin/git-clang-format.
  4. Make the script executable: chmod +x ~/bin/git-clang-format.
  5. Check it’s been picked up by git: git clang-format -h.
  6. Try it out with: git clang-format --diff.

You can now run clang-format, which will modify your working copy, rather than your staged files. This allows for easy backing out of the changes it makes.

> git status
On branch 45845-reusable-fetcher
Your branch is up-to-date with 'origin/45845-reusable-fetcher'.

Changes to be committed:
  (use "git reset HEAD ..." to unstage)

	modified:   Classes/common/CDTFetchChanges.h

> git clang-format
changed files:
    Classes/common/CDTFetchChanges.h

Running on every commit

The final step is to make sure clang-format is run before each commit. Some may want to just run the tool on every commit, but I’m happier with a simple reminder.

Following some instructions from Atlassian, I wrote a simple git pre-commit hook which runs git clang-format --diff, aborting the commit if something other than no modified files to format or clang-format did not modify any files is returned. Rather fragile, but as long as I don’t update my git-clang-format script, it should continue working.

#!/usr/bin/env python

import subprocess
output = subprocess.check_output(["git", "clang-format", "--diff"])

if output not in ['no modified files to format\n', 'clang-format did not modify any files\n']:
    print "Run git clang-format, then commit.\n"
    exit(1)
else:
    exit(0)

Git stores commit hooks on a per-repository basis, inside the .git folder of the repository. So we need to add this script there, and make it executable:

  1. cp check_formatting.sh .git/hooks/pre-commit
  2. chmod -x .git/hooks/pre-commit

Once all this is in place, if you try to make a commit with which clang-format finds an issue the commit will be aborted. Of course, you could be more or less terse in your message:

> git commit -m "Documentation updates"
Run git clang-format, then commit.

Given how lax I am at keeping to my own standards, this is invaluable.

← Older
Facebook's presumably a more successful AOL
→ Newer
One to Watch update submitted to Apple