Chris Jefferson
Chris Jefferson

Reputation: 7157

Run git-clang-format on series of git commits

I have written a series of git commits, with awful code formatting.
Before I push them to github, I want to run git-clang-format on each commit, to get a nicely formatted code in my history.

Is there some combination of rebase and git-clang-format which will accomplish this?

Upvotes: 12

Views: 22993

Answers (3)

VonC
VonC

Reputation: 1328342

That looks like a job for git filter-branch, which can rewrite the commits you want. Since those commits are not yet pushed, changing their content (and, consequently their SHA1) is not a big deal.
And the effect is similar to what a rebase or cherry-picking would do, except you can run any command for each commit being replayed.

You can run a filter-branch over the last few commits:

See "Reformatting Your Codebase with git filter-branch", by Elliot Chance

git filter-branch --tree-filter 'git-clang-format' -- <SHA1>..HEAD

Considering the git-clang-format syntax, you can apply it only on the changed files in each commit.
For instance, for .cpp files:

git filter-branch --tree-filter 'git-clang-format $(\
  git diff-index --diff-filter=AM --name-only $GIT_COMMIT |\
    grep .cpp) || true' -- <SHA1>..HEAD

Update 2017, with Git 2.14.x/2.15 (Q4 2017) you have an illustration:

See commit 2118805, commit 6134de6 (14 Aug 2017) by Brandon Williams (mbrandonw).
(Merged by Junio C Hamano -- gitster -- in commit a36f631, 25 Sep 2017)

Makefile: add style build rule

Add the 'style' build rule which will run git-clang-format on the diff between HEAD and the current worktree.
The result is a diff of suggested changes.

.PHONY: style
style:
    git filter-branch --tree-filter 'git-clang-format || true' -- <SHA1>..HEAD

2022: as noted by thakis in the comments, D117414 adds return code to git-clang-format, solving llvm/llvm-project issue 53220

Upvotes: 18

rumpel
rumpel

Reputation: 8308

If you want to apply clang-format only to the changed lines in each commit do the following:

# The first commit you want to edit.
# You can use the following command if it’s a child of origin/master.
export FIRST_COMMIT=$(git rev-list --ancestry-path origin/master..HEAD | tail -n 1)

git filter-branch --tree-filter 'git-clang-format $FIRST_COMMIT^' -- $FIRST_COMMIT..HEAD

This will do the following for each commit:

  1. checkout the commit as it was in the original history
  2. compute the diff to FIRST_COMMIT’s parent (e.g. origin/master). (NOTE: If you only were only to compute the diff to the current commits parent you may undo clang-format changes done on other commits!)
  3. clang-format the affected lines
  4. create a copy of the commit on top of the previous one

Upvotes: 2

TML Winston
TML Winston

Reputation: 41

I do this manually right now so the format does not mess up anything. Example: header reorganization is a possibility which can cause compile failures.

Steps start from latest commit. If you start at HEAD~# then the changes almost never unless they are atomic and unrelated. git clang-format only changes the code you changed(and related code blocks) but not other non-touched code.

  1. git clang-format HEAD~1 :result will be files changed. Compile and verify.
  2. git commit these new files as a temp commit.
  3. git rebase -i HEAD~2
  4. Change the new commit to be a "fixup" and finish the rebase.

(you can do this manually on a command line but for some reason I still do this with the editing).

You then repeat the steps for HEAD~2, HEAD~3 until you have finished working your way up to the chain.

Some Notes on this. clang-format will change the same code over and over again in certain cases. It has gotten much rarer but have to ignore them at times.

Upvotes: 4

Related Questions