39

How can I do git add with patch mode but ignoring whitespace changes.

The use case is for when you've reformatted a file and also made changes to it. I want to commit the real code changes separately first (as shown by git diff -w path) and then commit the reformatting as a separate commit.

Sam
  • 14,642
  • 6
  • 27
  • 39
  • 1
    The 'g' option to git add --patch will help; whitespace changes will appear empty in the list of hunks to choose. – William Pursell Jul 04 '11 at 12:58
  • Which version of git is the g option added in? – Sam Jul 04 '11 at 13:00
  • 1
    It was in 1.6.1-rc1-37, so the first official release with it would have been 1.6.2. (It is not actually an 'option', but a navigation menu selection that is used when a hunk is displayed.) – William Pursell Jul 04 '11 at 13:03
  • Note that g will not do what I was thinking: I interpreted "whitespace changes" to mean "inserting or deleting blank lines". 'g' will show blank headers for those types of changes, but not for insertion/deletion of whitespace. – William Pursell Jul 04 '11 at 15:14
  • 1
    Possible duplicate of [Add only non-whitespace changes](http://stackoverflow.com/questions/3515597/add-only-non-whitespace-changes) – George Hilliard Jul 08 '16 at 15:36

4 Answers4

25

Here's an adaptation from a related question.

git diff -w --no-color | git apply --cached --ignore-whitespace

It has the benefit that you don't need to use stash, temporary files, or perform a reset --hard on your working folders.

Addendum

The solution above only stages changes except whitespace-only edits. This did not address patch, though using --patch to stage isn't straight forward in this situation.

Patch Option 1: Edit the diff in a text editor

There are many ways to implement this using a text editor. Vim is especially suited to this.

In the root directory of your repository, start Vim.

In normal mode, load the diff into an empty buffer with...

:r !git diff -w --no-color
:set ft=diff  # if you want syntax highlighting

Edit the diff and remove the parts you don't want to stage.

To stage the contents of the vim buffer, run the vim ex command...

:w !git apply --cached --ignore-whitespace

If you're a Vim afficionado, you could use visual mode to stage, too!

:<',>'w !git apply --cached --ignore-whitespace

You can commit the staged changes with the ex command...

:!git commit -m "message"
# or
:!git commit

Clear the buffer, read the unstaged changes, and repeat

:bd! | set ft=diff | r !git diff -w --no-color

Eventually, you'll be left with only whitespace changes to commit.

If you don't use Vim, you could also dump git diff into a file, edit the file, save, then feed the file into git apply. Commit and repeat until done. It's a bit tedious, but functional.

Patch Option 2: Patch reset

It's backwards from git add --patch, but once you've staged non-whitespace changes with...

git diff -w --no-color | git apply --cached --ignore-whitespace

...you can unstage chunks in patch mode with...

git reset --patch .

Keep in mind you're removing the changes that you want to keep staged. Repeat and commit as necessary until you only have whitespace changes left.

Justin C
  • 640
  • 7
  • 11
  • 4
    Well that's just fabulous. Very useful after Visual Studio poops all over web.config. – kenchilada Mar 20 '13 at 23:38
  • How does have so many upvotes. It completely fails to include git add --patch, allowing the user to go through all of their changes in hunks, but ignoring whitespace changes? – Chucky Jun 27 '17 at 16:54
  • I completely overlooked "patch", Chucky. Updated with some alternatives to your solution. Thanks! – Justin C Jun 28 '17 at 03:09
  • This can fail in some complicated cases when the context has whitespace errors that are ignored by git diff. The unidiff-zero options on diff and apply can help by removing all contexts: https://stackoverflow.com/a/63904526/5520728 – Johnson Steward Aug 09 '21 at 16:17
3

If you want to do git add --patch but ignore all whitespace like the asker is asking, you can do this in one command:

git diff -w --no-color | git apply --cached --ignore-whitespace && git checkout -- . && git reset && git add -p

git diff -w --no-color creates a diff

git apply --cached --ignore-whitespace applies the diff ignoring whitepace, and indexes it

git checkout -- . removes the unindexed “whitespace” changes

git reset resets the index to just the non-whitespace changes

git add -p adds the non-whitespace changes in patch mode

Wrap this up in an alias, like so:

alias gwap=“git diff -U0 -w --no-color | git apply --cached --ignore-whitespace --unidiff-zero && git checkout -- . && git reset && git add -p”

Or if you're on a unix based system like I am:

gwap= !git diff -U0 -w --no-color | git apply --cached --ignore-whitespace --unidiff-zero && git checkout -- . && git reset && git add -p 

(Notice I added options -U0, and --unidiff-zero respectively to workaround context matching issues, according to this comment.)

Source: https://til.hashrocket.com/posts/696df00135-remove-whitespace-changes-then-git-add-p

Chucky
  • 1,701
  • 7
  • 28
  • 62
  • It might also be useful to stash the whitespace changes and then pop them after this command, just for safety. I wasn't sure how to do this without stashing everything in the index. Does anyone have any idea? – Chucky Jun 27 '17 at 17:04
  • I don't know of anyway to selectively stash, and I've looked extensively for that ability. However, this solution works great if your whitespace-only changes are caused by a formatting tool (e.g. `gofmt` or `rustfmt`). I just rerun the formatting tool after committing to restore the whitespace changes. – Justin C Jun 28 '17 at 03:12
  • After `git diff | git apply`, maybe `git stash --keep-index` will work to save the whitespace changes. It'll stash all changes, but leave the index alone. `git reset && git add -p`, commit until done, then `git stash pop` should restore the whitespace changes. Inspired by [this answer](https://stackoverflow.com/questions/52704/how-do-i-discard-unstaged-changes-in-git). – Justin C Jun 28 '17 at 03:25
  • I gave that a try, Justin. But merge conflicts occur when popping the stash. – Chucky Jun 28 '17 at 08:46
  • I feared as much. :/ I guess a temp file will have to suffice. Before `git checkout -- .`, `git diff -U0 --no-color > some-temp-file`, then at the end, ` – Justin C Jun 28 '17 at 14:54
1

A more robust and versatile version of @"Justin C"s answer is:

anw = !git diff -U0 -w --no-color -- \"$@\" | git apply --cached --ignore-whitespace --unidiff-zero "#"
  • With no argument - adds all tracked files' non-whitespace changes
  • Given files/directories - only adds non-whitespace changes in those locations

See this answer for more.

Community
  • 1
  • 1
Tom Hale
  • 40,825
  • 36
  • 187
  • 242
1

Note: This answer is old. 6 years down the road, the other answer by Justin is much better. Prefer to use git apply --cached

I suggest simply roundtripping a diff

Idea:

git diff --ignore-all-space | (git reset --hard && git apply)

Warning: this is fraught with danger because of the git reset there (it will not preserve changes to binary files as written). Perhaps you'd want a bash function similar to

function cleanup_patch()
{
    if [ $# -lt 1 ]; then 
        echo 'Must provide explicit paths (wildcards allowed)'; 
    else
        git diff --ignore-all-space -- "$@" |
            (git checkout HEAD -- "$@" &&
             git apply)
    fi
}

Afaict the seemingly useful --binary option to diff doesn't honour the whitespace ignore flags

sehe
  • 374,641
  • 47
  • 450
  • 633
  • I did it by redirecting diff's output to a file. Then using checkout path and finally apply with --ignore-whitespace. If you want to edit your answer I can accept it. – Sam Jul 04 '11 at 13:16
  • @Sam: if my answer helped you, that's enough. Why is using a temporary file substantially better? What you describe appears to be exactly what the `cleanup_patch` function does, only with added tedium. I might be missing something – sehe Jul 04 '11 at 13:33
  • Well outputting to a file is just safety wheels for when you are not using diff & patch that often. You can check that the patch looks sane and if you screw after reseting/checkouting you can at least manually apply the patches changes. Iow me being a n00b. Anyway don't you at least think checking out a specific path is safer than reseting the whole working dir? Oh now I see your shell function actually does that X_X I'll continue thinking out loud. Shouldn't you use the --ignore-whitespace flag with apply? – Sam Jul 04 '11 at 14:09
  • @Sam: ignoring whitespace on apply is redundant. Glad you noticed the `checkout` in the bash function. By all means, use a temp file if you want. My preference would be to `commit`/`reset HEAD^` or use `git stash` if I were doubting it. However, I usually cleanup patches manually anyway – sehe Jul 04 '11 at 14:22
  • So why does apply have that flag? – Sam Jul 05 '11 at 05:56
  • @Sam: it is there in case you _do_ need it, like all other options really. In this case we're feeding it a diff that ignores all space changes, so I'd consider it a bug if apply could ignore further white space – sehe Jul 05 '11 at 07:01
  • `git reset --hard ` will remove your current changes – Ankur Loriya Aug 24 '18 at 09:08
  • 1
    @AnkurLoriya that's on purpose but I agree it makes the one liner approach to risky. The other answer should be the accepted answer. Will add a note at the top in a few minutes – sehe Aug 24 '18 at 09:11