Separate whitespace problems from real change in git with vim
Whitespace changes can be annoying, especially when we have colleagues that do not care so much about them. If we care about whitespace correctness, we probably have whitespace indicators and trimmers configured in vim:
" Removes trailing spaces function! TrimWhiteSpace() " Only strip if the b:noStripWhitespace variable isn't set if exists('b:noStripWhitespace') return endif %s/\s*$// '' endfunction " Control characters expansion set list listchars=tab:»-,trail:.,eol:↲,extends:»,precedes:«,nbsp:% au FileType diff let b:noStripWhitespace=1 au FileWritePre * call TrimWhiteSpace() au FileAppendPre * call TrimWhiteSpace() au FilterWritePre * call TrimWhiteSpace() au BufWritePre * call TrimWhiteSpace()
Then our fixer will fix the whitespace errors our colleagues introduced as we work. Great, but things break down quickly when we try to use the
--patch option of
git-checkout(1), or similar utilities to selectively and interactively manipulate our changes, either for staging changes for commit, or partially dropping changes: whitespace corrections spam the interactive sessions, making it extremely difficult to navigate through. Well, with a bit of experience in editting
diffs, this situation can be neatly handled using vim's pipe read and write functionality.
Reading in a clean diff without whitespace changes
What we want to do here is to get the real changes free of whitespace corrections for editting. The basic Ex command is:
:set ft=diff | r !git diff -U0 -w
Piece by piece:
set ft=diffsets the current file type to diff, triggering any
autocmds there may be in our local config (e.g. to disable the space trimmer). This also gets us syntax highlighting.
rreads the output of
git-diff(1)into the current (anonymous) buffer for editting.
-U0makes git generate 0 lines of context for the resulting patch. This is important! See the explanation below.
-wignores all whitespace changes in our diff, leaving them for process at a later time.
Refer to the manual page for detailed explanations of options. Note that the 0 line context option is important because as we're ignoring whitespace changes, the context may be out of sync with HEAD. By dropping the contexts we make the patch applicable.
We can now proceed to edit the patches as normal, just like what we'd do using the
e action during
To stage a subset of changes (add to cache)
We want to delete all currently unwanted changes in the patch. Use the following Ex command to stage the changes:
:w !git apply --unidiff-zero --ignore-whitespace --cached
Piece by piece:
wwrites the buffer to the input of
--unidiff-zeromakes git accept the
-U0patch generated earlier.
--ignore-whitespacemakes git ignore whitespaces in context lines.
--cachedmakes git apply into the staging area, keeping the tree untouched. This is essentially the
To drop changes from working tree
We want to delete everything other than the changes we want to drop. The resulting patch is then applied in reverse with the following:
:w !git apply -R --unidiff-zero --ignore-whitespace
Piece by piece:
-Rapplies the patch in reverse, essentially reverting the changes.
--cachedoption means that we operate on the working tree.
It is recommended to stash the working tree beforehand to avoid any unexpected loss of work.