Disable git clean filter for checkout and stash pop

1k Views Asked by At

We use the auto-format tool uncrustify on our git repositories, they run in a git clean filter.

The problem is that users sometimes turn off their filters (don't ask). This leads to some bad formatting in some commits. Checking out these commits, while having the clean filter active, shows the bad formatting as diffs.

The filter is applied on git diff and others, and now users can't checkout another commit (warning about losing changes), so they turn off their filter, and create more problems since it's easy to forget to turn it on again.

One solution is to apply the auto-format on the server repository, I'm looking into it. I like seeing the auto-formatting on git diff, so I see what I'm going to commit.

The question is: can we prevent running the clean filter on given operations, such as checkout, stash pop, and such?

Any better suggestion?

2

There are 2 best solutions below

8
On

First, a note: git checkout should not run the clean filter at all, because it only does an index → work-tree copy.1 This runs the smudge filter instead. Only index ← work-tree copy operations use the clean filter.

Since git stash save / git stash push does an index ← work-tree operation, it will use the clean filter.

Stash's apply and pop invoke git merge-recursive, which is more complicated. While some merging happens in the index, this sometimes needs to do smudging and/or cleaning,2 so you will see some cases of clean filter invocation here too.

As ElpieKay suggested in the comment, a quick and dirty, but easy, way to disable a filter is to temporarily override its driver's configuration on the command line, with the front-end's -c option: git -c filter.name.type=noop for each applicable name (driver name in .gitattributes) and type (clean or smudge).


1This is true whether git checkout is doing a branch-switch (git switch operation in Git 2.23 or later) or a file-restore (git restore operation in Git 2.23 or later). In either case, checkout updates index copies of some file(s) if/as appropriate, then updates the work-tree copy from each updated or selected index copy.

That is, when using git checkout -- paths, the given paths get updated in the work-tree, from the copies already present in the index: this requires smudging. When using git checkout tree-ish -- paths, the given paths get copied from the given tree-ish into the index and then copied to the work-tree: this also requires smudging.

Or, when using git checkout commit or git checkout branch, the index entries that are different between the previous HEAD and the newly selected one are updated in the index, then copied to the work-tree, which requires smudging.

2git merge -X renormalize always runs both, doing a smudge first and then a clean, before merging, for every file in every commit, using the .gitattributes from the work-tree. Setting merge.renormalize in your configuration has the same effect, unless you override this with git merge -X no-renormalize. While git merge-recursive is lower level than git merge, I expect it does pretty similar things for the two commits that aren't represented by existing work-tree contents.

0
On

EDIT: I got a little over-enthusiastic. The answer below solves the filter running on every prompt, but not the filter introducing (actually not even real) changes to local files, thus preventing checkout.


The __git_ps1 function provided in git's contrib/completion/git-prompt.sh runs git diff, which in turn runs the relevant clean filters.

Although I'm not sure about all the steps involved in the undesired behavior, it seems that the changes created by the clean filter (probably triggered by the prompt) hindered checkout and stash pop, in order to protect from possible loss of uncommitted work.

A solution is to disable clean filters in the git diff operations of git-prompt.sh, using the trick from @ElpieKay. In this version of git-prompt.sh, it's on row 508 and 509.

- git diff --no-ext-diff --quiet || w="*"
- git diff --no-ext-diff --cached --quiet || i="+"
+ git diff --no-ext-diff --quiet -c filter.*.clean=""  || w="*"
+ git diff --no-ext-diff --cached --quiet  -c filter.*.clean="" || i="+"