Clang-format 能告诉我是否需要更改格式吗?

有没有一种方法可以运行 clang-format的模式,如果它报告的文件符合指定的格式?一种模拟运行模式,它在需要更改时报告,但不进行更改。理想情况下,如果文件需要更改,我希望 clang-format 只返回一个非零退出代码。或者,更理想的情况是,一个非零退出代码和一个需要对标准输出进行更改的文件列表。

我试图保持这个问题的通用性,以便更多的人可以回答,但我要做的是写一个 git 提前提交钩子,将拒绝任何不符合预期的提交。Clang 格式。对索引中的文件列表运行 clang-format 很容易。但是很难知道 clang 格式是否真的改变了什么。

我有一个潜在的解决方案基于 -output-replacements-xml(我将作为一个答案) ,但它是一个黑客,我觉得这应该更直接。欢迎评论/建议、编辑、不同的答案/方法。

39454 次浏览

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.

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.

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

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

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).

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

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/

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


You can simply use the -n option

clang-format -n <file>