31

Is there a way to add all hunks in a file matching a regex?

I know I can search for a given hunk with /, but that only finds the first. I want to add all matching.

Martin Hansen
  • 5,154
  • 3
  • 32
  • 53

3 Answers3

15

The Solution

The following one-liner worked for me where <regex> is a POSIX Extended Regular Expression. This requires grepdiff but it was already included in my Ubuntu distribution.

git diff -U1 -G<regex> --pickaxe-regex \
| grepdiff -E <regex> --output-matching=hunk \
| git apply --cached

How it works

The first line shows all differences whose patch text contains added/removed lines that match <regex>. The problem is that this works on a file-level. That means that any changes in a file with at least one matching hunk will be included.

Thus we need line two. From all the hunks it receives grepdiff will only print those that include a match for <regex>. the problem here is, that it will not only match the lines that have actually been changed, but also the diff context. That's why the diff context is minimized¹ by passing -U1 to git diff.

Finally line three takes the filtered diff and applies it to the index, e.g. stages it.

Conclusion

All in all this means that there are edge cases where this will add more than desired. But those can probably avoided by tweaking the regex a bit.

Thanks to @VonC for providing the link to that other SO question. It had all the pieces, they just had to be put together appropriately.


¹: You can also pass -U0 to omit the context altogether. While that completely removes the need for the pre-filtering in line one, the resulting patch won't apply anymore in line three.

raphinesse
  • 19,068
  • 6
  • 39
  • 48
  • 3
    You can use `git apply --cached --unidiff-zero` to apply the patch with zero lines of context and can also leave out the `-G --pickaxe-regex`, but since grepdiff doesn't distinguish between additions and deletions it's probably safer to use Python's difflib or unidiff library. – Johannes Riecken Apr 03 '19 at 10:35
  • 2
    For those who don't already have `grepdiff` installed, it's part of the [patchutils](https://github.com/twaugh/patchutils) package. – willkil Jun 18 '20 at 21:29
  • 1
    I'm trying to come up with a way to make this an interactive patch rather than just assuming your regex is perfect :D ... Any ideas? The best way I've come up with so far is to assume the regex is perfect then do a `git restore -p` to go through and remove the things I didn't want included. Ideally this would act like `git add -p ` and do an interactive add but only cycling through a list of hunks with the regex match. – topher217 Sep 10 '21 at 07:53
  • I'm doing ``git diff -U1 | wsl grepdiff -E 'Enter\(\)\|Leave\(\)\|AutoCriticalSection' --output-matching=hunk > patch.diff`` and then ``cat .\patch.diff | git apply --cached`` but the second line always generated ``patch does not apply`` for all files. – bradgonesurfing Aug 22 '23 at 07:47
13

Sadly, the patch in July 2011 went nowhere for now.
It would have introduced a git add --hunks=magic option.

For now, you will have to do with:

Quite a cumbersome process.

Community
  • 1
  • 1
VonC
  • 1,262,500
  • 529
  • 4,410
  • 5,250
9

Adding to the answer of @raphinesse and comments by @rubystallion, in addition to tips on how to format it from here, I made a git function to do this with the regex as the only input. Just enter this into your gitconfig (I used my global gitconfig) and use it with git regexadd <regex>.

[alias]
        regexadd = "!f() { git diff -U0 \
                | grepdiff -E $1 --output-matching=hunk \
                | git apply --cached --unidiff-zero; }; f"

topher217
  • 1,188
  • 12
  • 35
  • Yeah, I find it enormously helpful in cleaning up and making my commits much more concise. I might add to this at some point, giving it other args for the context size, and weather to apply or just check the grep before applying it, but this might get too messy in the gitconfig so might opt for a standard bash script at that point. – topher217 Aug 26 '20 at 09:50