Add only non-whitespace changes

GitWhitespace

Git Problem Overview


I have my text editor to automatically trim trailing whitespace upon saving a file, and I am contributing to an open source project that has severe problems with trailing whitespace.

Every time I try to submit a patch I must first ignore all whitespace-only changes by hand, to choose only the relevant information. Not only that, but when I run git rebase I usually run into several problems because of them.

As such I would like to be able to add to index only non-whitespace changes, in a way similar that git add -p does, but without having to pick all the changes myself.

Does anyone know how to do this?

EDIT: I cannot change the way the project works, and they have decided, after discussing it on the mailing list, to ignore this.

Git Solutions


Solution 1 - Git

@Frew solution wasn't quite what I needed, so this is the alias I made for the exact same problem:

alias.addnw=!sh -c 'git diff -U0 -w --no-color "$@" | git apply --cached --ignore-whitespace --unidiff-zero -'

Or you can simply run:

git diff -U0 -w --no-color | git apply --cached --ignore-whitespace --unidiff-zero -
Update

Added options -U0, and --unidiff-zero respectively to workaround context matching issues, according to this comment.

Basically it applies the patch which would be applied with add without whitespace changes. You will notice that after a git addnw your/file there will still be unstaged changes, it's the whitespaces left.

The --no-color isn't required but as I have colors set to always, I have to use it. Anyway, better safe than sorry.

Warning

While this trick works as-is, if you try to use it to drop blank line changes with --ignore-blank-lines then things get complicated. With this option, git diff will just drop some chunks, making the resulting patch bogus since the line numbers in the destination file are going to be off.

Solution 2 - Git

Create a patch file containing only the real changes (excluding lines with only whitespace changes), then clean your workspace and apply that patch file: > git diff > backup
> git diff -w > changes
> git reset --hard
> patch < changes

Review the remaining differences, then add and commit as normal.

The equivalent for Mercurial is to do this: > hg diff > backup
> hg diff -w > changes
> hg revert --all
> hg import --no-commit changes

Solution 3 - Git

This works for me:

If you want to keep a stash around, this works

git stash && git stash apply && git diff -w > foo.patch && git checkout . && git apply foo.patch && rm foo.patch

I don't like the stashes, but I have run into a bug in git + cygwin where I lose changes, so to make sure that stuff went to the reflog at least I set up the following:

git add . && git commit -am 'tmp' && git reset HEAD^ && git diff -w > foo.patch && git checkout . && git apply foo.patch && rm foo.patch

Basically we create a diff that doesn't include the space changes, revert all of our changes, and then apply the diff.

Solution 4 - Git

Top-voted answer does not work in all cases, due to whitespace in the patch context according to users in the comments.

I revised the command as follows:

$ git diff -U0 -w --no-color | git apply --cached --ignore-whitespace --unidiff-zero

This generates a patch with no context. Shouldn't be a problem since the patch is short-lived.

Corresponding alias, again a revision of what was already provided by other users:

addw = !sh -c 'git diff -U0 -w --no-color "$@" | git apply --cached --ignore-whitespace --unidiff-zero' -

Solution 5 - Git

Add the following to your .gitconfig:

anw = !git diff -U0 -w --no-color -- \"$@\" | git apply --cached --ignore-whitespace --unidiff-zero "#"

Thanks to @Colin Herbert's answer for the inspiration.

Syntax Explanation

The final # must be quoted so it's not treated it as a comment inside the .gitconfig, but instead gets passed through and is treated as a comment inside the shell - it is inserted between the end of the git apply and the user-supplied arguments that git automatically places at the end of the command line. These arguments aren't wanted here - we don't want git apply to consume them, hence the preceding comment character. You may want to run this command as GIT_TRACE=1 git anw to see this in action.

The -- signals end of arguments and allows for the case that you have a file named -w or something that would look like a switch to git diff.

Escaped double-quotes around $@ are required to preserve any user-supplied quoted arguments. If the " character is not escaped, it will be consumed by the .gitconfig parser and not reach the shell.

Note: .gitconfig alias parsing doesn't recognise single-quotes as anything special - its only special characters are ", \, \n, and ; (outside of a "-quoted string). This is why a " must always be escaped, even if it looks like it's inside a single-quoted string (which git is completely agnostic about).

This is important, eg. if you have a handy alias to execute a bash command in the working tree's root. The incorrect formulation is:

sh = !bash -c '"$@"' -

While the correct one is:

sh = !bash -c '\"$@\"' -

Solution 6 - Git

How about the following:

git add `git diff -w --ignore-submodules |grep "^[+][+][+]" |cut -c7-`

The command inside backquotes gets the names of files which have non-whitespace changes.

Solution 7 - Git

It works for me :

git config apply.whitespace fix

Before each commit use command :

git add -up .

Solution 8 - Git

This is my hack.

git diff -w | grep "diff --git a/*" | sed -r 's#diff --git a/(.*) b(.*)#\1#g' | xargs git add 

git diff -w only shows files with non whitespace changes,

Solution 9 - Git

> I have my text editor to automatically trim trailing whitespace upon saving a file

Doesn't your editor have proj / dir specific settings? Could just disable your whitespace preferences for this project. Seems like a much easier solve...

Solution 10 - Git

Similar to @void.pointer's answer, but for fixing the most recent commit.

git reset --mixed head^
git diff -U0 -w --no-color | git apply --cached --ignore-whitespace --unidiff-zero -

This leaves the whitespace changes unstaged and the rest staged.

Solution 11 - Git

I found a git pre-commit hook that removes trailing whitespace. However, if you can't get others to use this, then it might not be a valid solution.

  #!/bin/sh

  if git-rev-parse --verify HEAD >/dev/null 2>&1 ; then
     against=HEAD
  else
     # Initial commit: diff against an empty tree object
     against=4b825dc642cb6eb9a060e54bf8d69288fbee4904
  fi
  # Find files with trailing whitespace
  for FILE in `exec git diff-index --check --cached $against -- | sed '/^[+-]/d' | sed -r 's/:[0-9]+:.*//' | uniq` ; do
     # Fix them!
     sed -i 's/[[:space:]]*$//' "$FILE"
  done
  exit

Solution 12 - Git

You should first consider if the trailing whitespace is intentional. Many projects, including the Linux kernel, Mozilla, Drupal, and Kerberos (to name a few from the Wikipedia page on style) prohibit trailing whitespace. From the Linux kernel documentation:

> Get a decent editor and don't leave > whitespace at the end of lines.

In your case, the problem is the other way around: previous commits (and maybe current ones) did not follow this guideline.

I'd wager that no one really wants the trailing whitespace, and fixing the problem might be a welcome change. Other users might also be experiencing the same problem you are. It's also likely that the contributor(s) who are adding trailing whitespace are unaware that they are doing so.

Rather than trying to reconfigure git to ignore the problem, or disabling the otherwise desirable functionality in your editor, I'd start off with a post to the project mailing list explaining the problem. Many editors (and git itself) can be configured to deal with trailing whitespace.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionEdu FelipeView Question on Stackoverflow
Solution 1 - GitColin HebertView Answer on Stackoverflow
Solution 2 - GitSteve PitchersView Answer on Stackoverflow
Solution 3 - GitFrew SchmidtView Answer on Stackoverflow
Solution 4 - Gitvoid.pointerView Answer on Stackoverflow
Solution 5 - GitTom HaleView Answer on Stackoverflow
Solution 6 - GitkarmakazeView Answer on Stackoverflow
Solution 7 - GitTomaszView Answer on Stackoverflow
Solution 8 - GitMoses SView Answer on Stackoverflow
Solution 9 - GitJlamView Answer on Stackoverflow
Solution 10 - GitaleclarsonView Answer on Stackoverflow
Solution 11 - GitcmcgintyView Answer on Stackoverflow
Solution 12 - GitKevin VermeerView Answer on Stackoverflow