397

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.

Lii
  • 11,553
  • 8
  • 64
  • 88
Edu Felipe
  • 10,197
  • 13
  • 44
  • 41

12 Answers12

455

@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 --src-prefix=a/ --dst-prefix=b/ "$@" | git apply --cached --ignore-whitespace --unidiff-zero -'

Or you can simply run:

git diff -U0 -w --no-color --src-prefix=a/ --dst-prefix=b/ | 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.

cweiske
  • 30,033
  • 14
  • 133
  • 194
Colin Hebert
  • 91,525
  • 15
  • 160
  • 151
  • 1
    I only added the stash because a commenter asked for it. Also, --no-color shouldn't be needed as git dtrt when piping. – Frew Schmidt Aug 24 '11 at 21:55
  • @Frew, I know for the --no-colors (see the last line) Regarding the stash as I said, it works but it's unnecessary. If your script/command works, no need to stash anything, and as your script is correct I must disagree with Paŭlo on the necessity of a stash. Beside all that, I still think your solution is somewhat messy. You lose whitespace changes in the process (but it's okay because the original content is kept in the stash) and you end up with a patch file in your repository. – Colin Hebert Aug 24 '11 at 22:49
  • 1
    And another advantage of this solution is that you can select which file you'll add to your index (as the add command usually do) – Colin Hebert Aug 24 '11 at 22:51
  • 1
    i dont think your solution works.. steps what i did.. (1) added a tab(whitespace) to my source, (2) git diff now shows ^M as a change in my file. (3) then tried your fix "git diff -w --no-color "$@" | git apply --cached" (which basically does a git add) (4) now git diff --cached output matches exactly with diff at step(2) so.. i think this fix doesnot work?? any clues? – ashishsony Jan 10 '12 at 13:06
  • What happen when you just do a git diff -w ? – Colin Hebert Jan 10 '12 at 15:26
  • Okay, the problem seems to be a CRLF used in a LF environment (windows line feed vs UNIX line feed). So it isn't actually due to the solution here but to the fact that they're somehow not recognised as white spaces. http://stackoverflow.com/questions/1889559/git-diff-to-ignore-m – Colin Hebert Jan 11 '12 at 10:16
  • @ColinHebert well thanks for the link,but i dont understand one thing,all configs like cr-at-eol,trailing spaces etc,which i tried did remove the colored markings from git diff output,but git still considers lines ending in a trailing whitespace or ending in a ^M as a modification.so what do these configs fix if git still considers them as a valid change.or if i am getting things wrong please correct me.what i understand is that after these configs,git should not treat those lines as valid change and not show them in git diff at all..? – ashishsony Jan 12 '12 at 06:49
  • These configurations are not about considering a whitespace change as a "non-change". They're about avoiding conflicts between Windows linefeeds (CRLF or \r\n) and Unix linefeeds (LF or \n) – Colin Hebert Jan 12 '12 at 16:53
  • 7
    This worked well for me, however I had to use ``git apply --ignore-whitespace`` otherwise the patch would not apply for obvious reasons. – jupp0r Oct 10 '12 at 13:46
  • This is close to what I've been doing. The only problem is that it loses the commit message, etc. – Edward Falk Jan 31 '14 at 00:24
  • 129
    There should really be an option to git add, like `git add -w` that did this. – Jarl Apr 18 '14 at 15:40
  • @ColinHebert: Nice solution. Re: "These configurations are not about considering a whitespace change as a "non-change". [...]" Yes, but shouldn't it (always) imply that naturally? Any example where CR/LF conversion is on, but their diffs are still relevant? (If it's to keep git simple: more shaky implicit decision-making and heuristics are already built-in, like rename detection, perhaps auto-merge etc. This one seems simple, practical, common, cheap... Well, but (as usual in apparently trivial cases): I assume I just don't know enough about it. That's why I'm asking. Thanks!) – Sz. May 10 '14 at 16:36
  • 1
    For those on Windows using MSysGit, this is what I had to use to get it to work: `git config alias.addnw '!sh -c "git diff -w --no-color | git apply --cached --ignore-whitespace"'` – Carl Jun 03 '14 at 22:04
  • 1
    I use bash aliases rather than [git aliases](http://git-scm.com/book/en/v2/Git-Basics-Git-Aliases). Here's what I added to ~/.bashrc: `alias gadw='sh -c '\''git diff -w --no-color "$@" | git apply --cached --ignore-whitespace'\'' - '` – EoghanM Feb 03 '15 at 11:20
  • 7
    This gives me problems with `patch does not apply` and `error while searching for`... Any ideas? – DTI-Matt Jul 27 '15 at 14:17
  • 18
    didn't work for me. Got a `patch does not apply` error. – Jerry Saravia Sep 15 '15 at 16:57
  • This doesn't seem to work when renaming files (old file is marked deleted, "new" file is untracked). Any solutions for that? – monkey0506 Feb 01 '16 at 12:17
  • 1
    It doesn't work when there's whitespace anywhere in the patch context. I think this is the reason for the `patch does not apply` error. This technique might work on slightly screwed up projects, but it won't work on projects were every damn line includes screwy whitespace. fml. – bronson Mar 21 '16 at 22:11
  • Corollary: after running `git add -u && git diff --cached -w --no-color | git apply -R --cached --ignore-whitespace && git commit -m 'only whitespace changes'`, it is much easier to continue using `git add -p`. – mkrieger1 Mar 22 '16 at 10:18
  • 15
    If you're getting 'patch failed' due to whitespace in context as @bronson points out, this revised command works (It generates a patch with no context): `git diff -U0 -w --no-color | git apply --cached --ignore-whitespace --unidiff-zero`. This isn't risky because the index is already as up-to-date as can be, so it's a reliable base for the patch. – void.pointer May 12 '16 at 15:34
  • 1
    IMHO the alias snippet is redundant. Just give the command to keep the answer as concise as possible. – tokland Jul 07 '16 at 16:54
  • 4
    **Two issues with this**: 1) This will add *all* tracked files, ignoring any user-specified files/directories. 2) Filenames with whitespace or `;rm -rf --no-preserve-root /;` will cause issues. 3) Unnecessary sub-shell created. See [my answer](http://stackoverflow.com/a/39487648/5353461) for the resolution. – Tom Hale Sep 14 '16 at 10:04
  • I needed to add `--binary` to the diff to make this work, because git was convinced that one of my text files was binary. – Marcus Downing Feb 21 '19 at 16:20
  • If you have `diff.noprefix` config enabled, remember to add `-p0` to the apply options. – cmbuckley Mar 14 '19 at 12:08
  • 2
    This command adds *all* files in repo. To specify which files you need to add: `git diff -U0 -w --no-color -- PATH\TO\FILE.HERE | git apply --cached --ignore-whitespace --unidiff-zero -`. – sdlins May 06 '19 at 18:21
  • The command `git add -w -p` would be so great! – Felipe Dec 18 '20 at 04:07
  • The command ignores its first argument because bash fills arguments following `-c` starting from `$0`, not `$1`. It is also executed from the repo root. Switch to the current directory and provide any string as first argument: `!sh -c 'cd "./$GIT_PREFIX" && git diff -U0 -w --no-color "$@" | git apply --cached --ignore-whitespace --unidiff-zero -' addnw` – Roland W Aug 17 '21 at 18:23
  • @jarl that's what my hack does. https://stackoverflow.com/a/68863986/8354652 – Omid Aug 20 '21 at 16:34
44

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

Steve Pitchers
  • 7,088
  • 5
  • 41
  • 41
  • [What is a “protected” question?](http://meta.stackexchange.com/q/52764) and *Me Too* answers. I don't think this even qualifies as a me too answer because it appears the question was pulled out of thin air... – jww Jul 23 '15 at 19:29
  • 5
    @jww The core of the original poster's question is "how to avoid committing white-space only changes to source control". The OP happens to be using Git, but this also applies to every source control system I've ever used. This answer shows the correct procedure if someone happens to be using Mercurial. I could imagine someone else might also contribute a solution for people using Sublesion, etc. – Steve Pitchers Jul 24 '15 at 10:30
  • 4
    @jww and @ pagid: I edited my answer to specifically address Git, using the same approach as my solution for Mercurial. In my view, StackOverflow is [more than just another Q+A forum](http://meta.stackexchange.com/questions/92107/is-stack-overflow-a-forum) - it also has a role as a repository of knowledge. People other than the original poster might benefit from the answers given, and their circumstances vary. That's why I believe answers conveying a general principle are valid, rather than targeting only a single specific situation. – Steve Pitchers Jul 24 '15 at 18:28
  • @Steve - *"I edited my answer to specifically address Git..."* - why didn't you ask a new question in the context of mercurial, and then add your own answer to the new question??? – jww Jul 24 '15 at 23:59
  • @jww It's a good suggestion. A Mercurial related query led me here. The same problem applies to many version control systems; this solution concept is works for all the ones I know. – Steve Pitchers Jul 27 '15 at 08:41
  • Possible duplicate: http://stackoverflow.com/questions/5583693/ignore-whitespace-when-doing-a-merge-in-mercurial – Steve Pitchers Jul 27 '15 at 08:42
  • 13
    This is actually the cleanest, most understandable, and most unbreakable of the approaches that I have seen. – Kzqai Jun 30 '16 at 11:38
  • What is this patch < changes ? At line:1 char:7 + patch < changes + ~ The '<' operator is reserved for future use. + CategoryInfo : ParserError: (:) [], ParentContainsErrorRecordException + FullyQualifiedErrorId : RedirectionNotSupported – Gangnus Jan 23 '18 at 19:46
  • 1
    @Gangnus It is intended to feed the contents from the file "changes" into the "patch" command. Is it possible you have a deficient shell that does not support this kind of [redirection](https://www.tldp.org/LDP/abs/html/io-redirection.html)? – Steve Pitchers Jan 24 '18 at 21:02
  • @Gangnus Just a guess, but... If you are using Powershell rather than Bash, you will have to adapt the syntax of the commands to the Powershell syntax for input redirection, whatever that is. – Steve Pitchers Jan 24 '18 at 21:08
  • @Gangus In Powershell you might try `Get-Content changes | patch`. But I can't test that right now, so be careful! – Steve Pitchers Jan 24 '18 at 21:18
  • @StevePitchers Thank you. Sorry. Really, I tried that in Windows, for neither in question tags or in your answer I haven't seen the OS limitations. – Gangnus Jan 25 '18 at 07:42
  • 1
    @Gangnus If you download [Git Bash](http://gitforwindows.org/) or [Cygwin Bash](https://www.cygwin.com/) the script will run on Windows. Or if you are on Windows 10, it could be even easier with the [new subsystem](https://www.howtogeek.com/249966/how-to-install-and-use-the-linux-bash-shell-on-windows-10/)! – Steve Pitchers Jan 26 '18 at 09:15
  • The patch created by "git diff > backup" has broken file names. – JaM Apr 14 '23 at 14:04
  • @JaM Broken in what way? What filenames do you see and how do they differ from what you expected? What were the circumstances / which platform, which shell are you running on? – Steve Pitchers Apr 20 '23 at 16:15
40

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.

Frew Schmidt
  • 9,364
  • 16
  • 64
  • 86
  • 1
    +1. You might want to do a `git stash` instead of checkout, to have a backup of your changes, at least until it is tested. – Paŭlo Ebermann Jun 10 '11 at 19:59
  • 1
    You'll end up with a lot of stashes and basically you don't really need to do all of this. It works but I think it's a bit messy – Colin Hebert Aug 22 '11 at 15:16
  • 3
    I agree with Colin. If the script works, then there should be no need to create a stash. What might be good to consider though would be to run stash, then stash pop. Popped stashes can be recovered if necessary, but you won't end up with a lot of stashes otherwise. This also leaves an extra file lying around – Casebash Feb 07 '12 at 00:51
  • 1
    How about skipping binary files? When trying to apply the above snippet, I get errors that the patch cannot be applied without the full index line! What beats me is that I didnt even touch these files/binaries in the first place! – tver3305 Apr 13 '12 at 12:21
  • 1
    I think at the end of the first command "git rm foo.patch" should just be "rm foo.patch". Otherwise very helpful thanks. – Jack Casey Jul 02 '12 at 05:19
  • I have autocrlf set to input, but all files are in dos format. I don't know how relevant that is, but my problem is that after I do git apply foo.patch, to me it says "patch does not apply" and rejects the patch I made with diff -w – Attila Szeremi Sep 27 '12 at 14:05
  • @Szerémi yeah, you might want to use one of the other less automatic solutions in that case. At my place of work we've all stopped using autocrlf as it's generally frustrating instead of helpful. – Frew Schmidt Sep 27 '12 at 17:10
  • Unfortunately, since I am working with people using weird editors that change the line endings for files, autocrlf=input has been extremely helpful in that I never get complete file content contents due to differences in line endings. It just seems as though in this case with autocrlf set to input AND the file happening to be CRLF that the trick in this post just so happens to not work :/ ah well, no running away from the problems caused by Microsoft. That's life. – Attila Szeremi Sep 28 '12 at 12:02
  • Brilliant, thank you! I had to change the stash apply part to `git apply --ignore-space-change --ignore-whitespace foo.patch` otherwise I would get "patch does not apply." – Nateowami Jun 01 '17 at 13:34
17

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' -
void.pointer
  • 24,859
  • 31
  • 132
  • 243
  • To only ignore indentation changes I had to use ```--ignore-space-change``` instead of ```-w```. ```git diff -U0 --ignore-space-change --no-color | git apply --cached --unidiff-zero``` – Andrew Jun 19 '16 at 15:39
  • 1
    A word of warning not to use this lovely no context trick with `--ignore-blank-lines` else you will find diff chunks being patched at **incorrect** offsets if some of the 'white space' changes you're looking to ignore are empty line removals/additions. – elbeardmorez Jul 23 '18 at 07:13
13

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 '\"$@\"' -
Community
  • 1
  • 1
Tom Hale
  • 40,825
  • 36
  • 187
  • 242
  • Excellent. This allowed me to add one file at a time. Other than adding a root directory for an argument, is there a way to make this work like 'git add -A'? – Chucky Jan 23 '17 at 10:47
7

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.

karmakaze
  • 34,689
  • 1
  • 30
  • 32
2

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.

MattArmstrong
  • 349
  • 3
  • 9
aleclarson
  • 18,087
  • 14
  • 64
  • 91
0

It works for me :

git config apply.whitespace fix

Before each commit use command :

git add -up .
Tomasz
  • 884
  • 8
  • 12
0

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,

Omid
  • 57
  • 7
0

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...

Jlam
  • 1,932
  • 1
  • 21
  • 26
-1

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
cmcginty
  • 113,384
  • 42
  • 163
  • 163
  • 4
    This question is asking how to preserve trailing whitespace. – Douglas Aug 18 '10 at 20:50
  • @Douglas: one could probably use this answer to create a commit on a temporary branch, commit the real patch there and cherry-pick the diff only into the working branch somehow... – Tobias Kienzler Feb 23 '11 at 14:51
-2

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.

Kevin Vermeer
  • 2,736
  • 2
  • 27
  • 38
  • 20
    It's not intentional, but I cannot change the way 100+ people who contribute the project think. They don't mind it, and won't accept patches with 1000+ changes what only deal with trailing whitespace. They know about the problem and have decided to ignore it. This discussion already happened in the list and was closed. In this case, it's me who needs to adapt to them. – Edu Felipe Aug 18 '10 at 19:13
  • 21
    Then configure your editor such that it doesn't trim trailing whitespace when working on this project's code. – jamessan Aug 18 '10 at 19:17