99

Is there an way that you can run clang-format in a mode where it reports if the file meets the specified format? A kind of dry-run mode where it reports if a change is needed, but doesn't make the change. Ideally I'd like clang-format to just return a non-zero exit code if the file needs changes. Or, even more ideally, a non-zero exit code and a list of the files that need changes on standard output.

I'm trying to keep the question generic, so that more people can answer, but what I am trying to do is write a git pre-commit hook that will reject any commits that don't match the expected .clang-format . It's easy to run clang-format on the list of files in the index. But it's hard to know if clang-format actually changed anything.

I have one potential solution based on -output-replacements-xml (that I will post as an answer), but it's a hack and I feel like this should be more straightforward. Comments/suggestions, edits, different answers/approaches are all welcome.

David Ogren
  • 4,396
  • 1
  • 19
  • 29

9 Answers9

55

Starting with clang-format-10, you can use the --dry-run and -Werror command line options. They will cause ClangFormat to output any formatting violations to stdout and return a non-zero exit status if any input file was not correctly formatted.

$ clang-format --dry-run --Werror foo.cpp
foo.cpp:129:23: error: code should be clang-formatted [-Wclang-format-violations]
        if (rc <= 0) {
$ echo $?
1

Originally from my website here: https://rigtorp.se/notes/clang-format/

Andrew Ring
  • 3,185
  • 1
  • 23
  • 28
Erik
  • 770
  • 7
  • 4
  • 9
    For future readers, the `--dry-run` flag was added in clang-format-10. If you have an older version, this flag won't work. If you're curious, check out commit [6a1f7d6c9ff8228328d0e65b8678a9c6dff49837](https://github.com/llvm/llvm-project/commit/6a1f7d6c9ff8228328d0e65b8678a9c6dff49837). – bitoffdev Dec 11 '20 at 18:14
  • 2
    Update (6/2): Changing this to the official answer since clang-format (6 years later) now has this as an official flag and this question still gets a lot of traffic. – David Ogren Jun 02 '21 at 20:06
50

One of the reasons I feel like this should be easier than it is because -output-replacements-xml essentially gives me the answer that I want, it just doesn't give it to me in an easy to consume way. However, since the output if no replacements are needed is very predictable, parsing the output isn't too hard.

What I have right now is

clang-format -style=file -output-replacements-xml | grep -c "<replacement " >/dev/null

This actually returns the inverse of the exit code I want, since grep returns 0 if something matches, 1 if nothing does. But that is easy enough to deal with.

So the relevant bit of my git pre-commit hook would be

git diff --cached --name-only --diff-filter=ACMRT |
  grep "\.[cmh]$" |
  xargs -n1 clang-format -style=file -output-replacements-xml |
  grep "<replacement " >/dev/null
if [ $? -ne 1 ]; then 
    echo "Commit did not match clang-format"
    exit 1
fi
  1. Get the full filenames of the files in the index (excluding files that are being deleted and other unusual cases where I might not want to process the file)
  2. Only keep the filenames of things I want to check the formatting of (in my case just c,m, and h files)
  3. Run the results through xargs to essentially "for each" the next command
  4. Run clang-format with the -output-replacements-xml option on all of the files
  5. Search for replacement (as opposed to replacements) that indicates that clang-format has found a replacement that it wants to make. (Discarding all output as the XML won't be meaningful to the user.)
  6. The last command exits 1 (grep says we found nothing) we are done and things are fine.
  7. If not, display a message and exit 1, which cancels the commit. Unfortunately we don't have an easy way to tell the user which file was the problem, but they can run clang-format themselves and see.
Jens
  • 69,818
  • 15
  • 125
  • 179
David Ogren
  • 4,396
  • 1
  • 19
  • 29
  • I'm shocked this isn't a more popular question/answer. I am looking to do exactly what you are doing. Could you post the entire git hook to do this? – David Doria Feb 04 '16 at 19:29
  • 1
    The above code snippet should be everything you need for the hook, other than `#!/bin/bash` at the top. (I'd put the whole hook on gist but it had other project-specific stuff in it.) The only difference in my hook was comments that explain the same things as above and a full-qualfication of the clang-format path. – David Ogren Feb 04 '16 at 19:52
  • 32
    My two cents: `diff -u <(cat src/*) <(clang-format src/*)` instead of the XML – phs Jun 22 '16 at 22:39
  • 4
    @phs that's the best answer here! – pattivacek Jul 25 '17 at 11:42
  • 1
    grep -c "" | grep 0 should also invert the return type since if the first grep finds something then grep 0 will not find a 0 – Bomaz Aug 27 '18 at 15:04
  • 2
    It seems that `clang-format -output-replacements-xml` outputs some replacements even if `clang-format -i` has just been run on the files and there are no more replacements to be made (the results are just identity replacements). I'm not sure under which circumstances that happens, but if anyone finds this wondering why the answer doesn't work for them, I recommend @phs's suggestion – oarfish May 21 '19 at 15:13
  • It would be better to grep `replacement offset` and invert the status code. Works at least with `clang-format-6.0`. Replacements are outputted even if everything is fine, but actual offsets are not. – juzzlin Oct 02 '19 at 13:41
  • At the time this was written, offset didn't exist. Nor did the run-clang-format above. Note that I've now changed the officially accepted answer to run-clang-format rather than this original approach. – David Ogren Oct 03 '19 at 19:53
  • @DavidOgren I need approach as yours, I am trying to set clang-format as build breaker in jenkins pipeline. In pipeline stage should give true or false. If source code is formated is true, otherwise false, But i couldn't integrated. Any idea? –  Aug 03 '20 at 15:10
  • @full_steak_developer See the accepted answer above. I changed the accepted answer a couple of years ago because run-clang-format seems to be designed to solve this exact problem. (It didn't exist when I wrote the original question, but now looks to be the best approach.) – David Ogren Aug 05 '20 at 03:59
  • I don't want to use external script in my CI, I've simply compared git diff, if any diff then error;) –  Aug 07 '20 at 23:26
12

run-clang-format is a simple wrapper around clang-format designed precisely to be used as a hook or as a continuous integration script: it outputs a diff and exits with a sensible status.

The example given on the home page speaks for itself:

run-clang-format example

Matthieu Moy
  • 15,151
  • 5
  • 38
  • 65
4

I am not entirely sure what your use case is, but check out git-clang-format (https://llvm.org/svn/llvm-project/cfe/trunk/tools/clang-format/git-clang-format). It basically provides a clang-format integration for git and maybe that is what you are looking for.

djasper
  • 2,234
  • 1
  • 14
  • 8
  • 1
    I appreciate the answer, I'll look into it. However, it doesn't really answer the question or solve my use case. It's seems to just be just running clang-format on the changed files. Essentially I am looking for a safeguard that ensure that the current style (as defined in clang-format) is met, but that doesn't arbitrarily run clang-format. (I want at least a chance to review any formatting changes that clang-format would make: it doesn't always break lines the way I would want it to.) – David Ogren Jun 03 '14 at 19:10
  • 19
    So, how about: `clang-format | diff -`? clang-format (without the `-i` option) won't make any changes to your file, just print it to stdout. – djasper Jun 04 '14 at 07:44
4

Command Line

You can use git diff along with clang-format-diff:

$ git diff -U0 --no-color --staged HEAD | clang-format-diff -p1

Note:

  • --staged is used to run clang-format-diff only on the staged changes
  • If you want to run this command on specific directories, you can rewrite it as:
$ git diff -U0 --no-color --staged HEAD -- $PWD/dir1 $PWD/dir2 $PWD/dir3  | clang-format-diff -p1

..and the pre-commit

Now you pre-commit can be this one:

#!/bin/bash

dir_list="$PWD"  # Add the directories you want here
cmd="git diff -U0 --no-color --staged HEAD -- $dir_list | clang-format-diff -p1"

echo ""
echo "Running clang-format on this commit"
echo ""

# Execute the format command
diff=$(eval "$cmd")
if [[ $? -ne 0 ]]
then
    echo "Command failed to execute."
    exit 1
fi

# Print the outcome
if [[ -z "$diff" ]]
then
    echo "Everything is clean"
    exit 0
else
    echo "$diff"
    echo ""
    echo "Commit aborted due to code format inconsistencies."
    exit 1
fi

gon1332
  • 1,930
  • 1
  • 24
  • 30
2

I slightly adjusted the comment from phs in this post to come up with:

find embedded/ -regex '.*\.\(ino\|cpp\|hpp\|cc\|cxx\|h\)' -exec cat {} \; | diff -u <(find embedded/ -regex '.*\.\(ino\|cpp\|hpp\|cc\|cxx\|h\)' -exec clang-format-3.9 -style=file {} \;) -

that is..

  1. cat all cpp-ish files and pipe that to diff (diff will accept stdin because I specify - at the end)
  2. use process substitution (the <( .. ) syntax) to run clang-format on those same files. Don't use in-place formatting here. This is the other half that's sent to diff
  3. if diff exits with no output, success! You can also check the exit code via $? -- it should be zero.

I have my CI service (travis) run this line in a bash script to make sure things are formatted properly. I have another script for actually running the formatter in-place. This reminds me of a caveat: you must use a shell that can do process sub (the posix shell does not).

Matt
  • 4,815
  • 5
  • 39
  • 40
1

I use git-clang-format and a pre-commit script from Mike Rhodes' blog:

#!/bin/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)

The script has a small error in that it doesn't work when there are no commits (trying to check against HEAD which doesn't exist yet). To bypass this, use the -n or --no-verify option.

Using -n to skip the pre-commit script can also be helpful when you what to bypass the check because it can take a long time for a large codebase.

The original post is here: http://www.dx13.co.uk/articles/2015/4/3/Setting-up-git-clang-format.html

Daniel
  • 3,243
  • 2
  • 32
  • 31
1

After I got inspired by David Ogren's post I made a pre-commit hook that is able to work on the staged changes. This will ensure that the pre-commit hook will work on the code that will actual make up the content of the commit and can't be fooled by a clang-format run that didn't get staged.

#!/bin/bash

files=()
for file in `git diff --cached --name-only --diff-filter=ACMRT | grep -E "\.(cpp|hpp)$"`; do
  if ! cmp -s <(git show :${file}) <(git show :${file}|clang-format); then
    files+=("${file}")
  fi
done

if [ -n "${files}" ]; then
echo Format error within the following files:
printf "%s\n" "${files[@]}"
exit 1
fi
Martin
  • 10,738
  • 14
  • 59
  • 67
0

You can simply use the -n option

clang-format -n <file>
MyDeveloperDay
  • 2,485
  • 1
  • 17
  • 20
  • Duplicate of Erik's answer – David Ogren Jun 02 '21 at 20:04
  • 1
    Actually that is not true (I should know as I added these options to clang-format in the first place), --dry-run == -n (-n being the short version), -Werror turns it into an error so you can use the return code (if that is what you need) -n leaves it just as a warning so the error text will be different `test1.cpp:7:29: warning: code should be clang-formatted [-Wclang-format-violations]`, of course -n is just so much simpler to type too! – MyDeveloperDay Jun 04 '21 at 14:11
  • Hey @MyDeveloperDay, could you (guys) please also add a message specifically referring to the actual rule that was violated? You know, like normal compiler errors that tell what's wrong, not just f* off. :) It would be immensely helpful e.g. when quickly looking at GH lint action failure logs with supposedly compliant (but still apparently problematic) lines etc. – Sz. Feb 24 '23 at 16:54
  • @Sz this I'm afraid is highlight unlikely, it would effectively require tracking of every space and likely character and what cause it to move, which could be multiple rules. – MyDeveloperDay Mar 09 '23 at 11:30