Make git automatically remove trailing whitespace before committing

0 votes
asked Feb 26, 2009 by mloughran

I'm using git with my team and would like to remove whitespace changes from my diffs, logs, merges, etc. I'm assuming that the easiest way to do this would be for git to automatically remove trailing whitespace (and other whitespace errors) from all commits as they are applied.

I have tried to add the following to by ~/.gitconfig file but it doesn't do anything when I commit. Maybe it's designed for something different. What's the solution?

[core]
    whitespace = trailing-space,space-before-tab
[apply]
    whitespace = fix

I'm using ruby in case anyone has any ruby specific ideas. Automatic code formatting before committing would be the next step, but that's a hard problem and not really causing a big problem.

14 Answers

0 votes
answered Feb 26, 2009 by bojo

This probably won't directly solve your problem, but you might want to set those via git-config in your actual project space, which edits ./.git/config as opposed to ~/.gitconfig. Nice to keep the settings consistent among all project members.

git config core.whitespace "trailing-space,space-before-tab"
git config apply.whitespace "trailing-space,space-before-tab"
0 votes
answered Feb 26, 2009 by vonc

Those settings (core.whitespace and apply.whitespace) are not there to remove trailing whitespace but to:

  • core.whitespace: detect them, and raise errors
  • apply.whitespace: and strip them, but only during patch, not "always automatically"

I believe the git hook pre-commit would do a better job for that (includes removing trailing whitespace)


Note that at any given time you can choose to not run the pre-commit hook:

  • temporarily: git commit --no-verify .
  • permanently: cd .git/hooks/ ; chmod -x pre-commit

Warning: by default, a pre-commit script (like this one), has not a "remove trailing" feature", but a "warning" feature like:

if (/\s$/) {
    bad_line("trailing whitespace", $_);
}

You could however build a better pre-commit hook, especially when you consider that:

Committing in Git with only some changes added to the staging area still results in an “atomic” revision that may never have existed as a working copy and may not work.


For instance, oldman proposes in another answer a pre-commit hook which detects and remove whitespace.
Since that hook get the file name of each file, I would recommend to be careful for certain type of files: you don't want to remove trailing whitespace in .md (markdown) files!

0 votes
answered Feb 26, 2009 by giacomo

I'd rather leave this task to your favorite editor.

Just set a command to remove trailing spaces when saving.

0 votes
answered Feb 18, 2010 by cmcginty

I found a git pre-commit hook that removes trailing whitespace.

#!/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"
   git add "$FILE"
done
exit
0 votes
answered Feb 4, 2011 by alexchaffee

On Mac OS (or, likely, any BSD), the sed command parameters have to be slightly different. Try this:

#!/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 -E 's/:[0-9]+:.*//' | uniq` ; do
    # Fix them!
    sed -i '' -E 's/[[:space:]]*$//' "$FILE"
    git add "$FILE"
done

Save this file as .git/hooks/pre-commit -- or look for the one that's already there, and paste the bottom chunk somewhere inside it. And remember to chmod a+x it too.

Or for global use (via Git commit hooks - global settings) you can put it in $GIT_PREFIX/git-core/templates/hooks (where GIT_PREFIX is /usr or /usr/local or /usr/share or /opt/local/share) and run git init inside your existing repos.

According to git help init:

Running git init in an existing repository is safe. It will not overwrite things that are already there. The primary reason for rerunning git init is to pick up newly added templates.

0 votes
answered Feb 7, 2011 by sdepold

Here is an ubuntu+mac os x compatible version:

#!/bin/sh
#

# A git hook script to find and fix trailing whitespace
# in your commits. Bypass it with the --no-verify option
# to git-commit
#

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]+:.*//' > /dev/null 2>&1 || sed -E 's/:[0-9]+:.*//') | uniq` ; do
  # Fix them!
  (sed -i 's/[[:space:]]*$//' "$FILE" > /dev/null 2>&1 || sed -i '' -E 's/[[:space:]]*$//' "$FILE")
  git add "$FILE"
done

# Now we can commit
exit

Have fun

0 votes
answered Feb 20, 2012 by grant-murphy

Was thinking about this today. This is all I ended up doing for a java project:

egrep -rl ' $' --include *.java *  | xargs sed -i 's/\s\+$//g'
0 votes
answered Feb 13, 2013 by ntc2

You can trick Git into fixing the whitespace for you, by tricking Git into treating your changes as a patch. In contrast to the "pre-commit hook" solutions, these solutions add whitespace-fixing commands to Git.

Yes, these are hacks.


Robust solutions

The following Git aliases are taken from my ~/.gitconfig.

By "robust" I mean that these aliases run without error, doing the right thing, regardless of whether the tree or index are dirty. However, they don't work if an interactive git rebase -i is already in progress; see my ~/.gitconfig for additional checks if you care about this corner case, where the git add -e trick described at the end should work.

If you want to run them directly in the shell, without creating a Git alias, just copy and paste everything between the double quotes (assuming your shell is Bash like).

Fix the index but not the tree

The following fixws Git alias fixes all whitespace errors in the index, if any, but doesn't touch the tree:

# Logic:
#
# The 'git stash save' fails if the tree is clean (instead of
# creating an empty stash :P). So, we only 'stash' and 'pop' if
# the tree is dirty.
#
# The 'git rebase --whitespace=fix HEAD~' throws away the commit
# if it's empty, and adding '--keep-empty' prevents the whitespace
# from being fixed. So, we first check that the index is dirty.
#
# Also:
# - '(! git diff-index --quiet --cached HEAD)' is true (zero) if
#   the index is dirty
# - '(! git diff-files --quiet .)' is true if the tree is dirty
#
# The 'rebase --whitespace=fix' trick is from here:
# https://stackoverflow.com/a/19156679/470844
fixws = !"\
  if (! git diff-files --quiet .) && \
     (! git diff-index --quiet --cached HEAD) ; then \
    git commit -m FIXWS_SAVE_INDEX && \
    git stash save FIXWS_SAVE_TREE && \
    git rebase --whitespace=fix HEAD~ && \
    git stash pop && \
    git reset --soft HEAD~ ; \
  elif (! git diff-index --quiet --cached HEAD) ; then \
    git commit -m FIXWS_SAVE_INDEX && \
    git rebase --whitespace=fix HEAD~ && \
    git reset --soft HEAD~ ; \
  fi"

The idea is to run git fixws before git commit if you have whitespace errors in the index.

Fix the index and the tree

The following fixws-global-tree-and-index Git alias fixes all whitespace errors in the index and the tree, if any:

# The different cases are:
# - dirty tree and dirty index
# - dirty tree and clean index
# - clean tree and dirty index
#
# We have to consider separate cases because the 'git rebase
# --whitespace=fix' is not compatible with empty commits (adding
# '--keep-empty' makes Git not fix the whitespace :P).
fixws-global-tree-and-index = !"\
  if (! git diff-files --quiet .) && \
     (! git diff-index --quiet --cached HEAD) ; then \
    git commit -m FIXWS_SAVE_INDEX && \
    git add -u :/ && \
    git commit -m FIXWS_SAVE_TREE && \
    git rebase --whitespace=fix HEAD~2 && \
    git reset HEAD~ && \
    git reset --soft HEAD~ ; \
  elif (! git diff-files --quiet .) ; then \
    git add -u :/ && \
    git commit -m FIXWS_SAVE_TREE && \
    git rebase --whitespace=fix HEAD~ && \
    git reset HEAD~ ; \
  elif (! git diff-index --quiet --cached HEAD) ; then \
    git commit -m FIXWS_SAVE_INDEX && \
    git rebase --whitespace=fix HEAD~ && \
    git reset --soft HEAD~ ; \
  fi"

To also fix whitespace in unversioned files, do

git add --intent-to-add <unversioned files> && git fixws-global-tree-and-index

Simple but not robust solutions

These versions are easier to copy and paste, but they don't do the right thing if their side conditions are not met.

Fix the sub-tree rooted at the current directory (but resets the index if it's not empty)

Using git add -e to "edit" the patches with the identity editor ::

(export GIT_EDITOR=: && git -c apply.whitespace=fix add -ue .) && git checkout . && git reset

Fix and preserve the index (but fails if the tree is dirty or the index is empty)

git commit -m TEMP && git rebase --whitespace=fix HEAD~ && git reset --soft HEAD~

Fix the tree and the index (but resets the index if it's not empty)

git add -u :/ && git commit -m TEMP && git rebase --whitespace=fix HEAD~ && git reset HEAD~

Explanation of the export GIT_EDITOR=: && git -c apply.whitespace=fix add -ue . trick

Before I learned about the git rebase --whitespace=fix trick from this answer I was using the more complicated git add trick everywhere.

If we did it manually:

  1. Set apply.whitespace to fix (you only have to do this once):

    git config apply.whitespace fix
    

    This tells Git to fix whitespace in patches.

  2. Convince Git to treat your changes as a patch:

    git add -up .
    

    Hit a+enterto select all changes for each file. You'll get a warning about Git fixing your whitespace errors.
    (git -c color.ui=auto diff at this point reveals that your non-indexed changes are exactly the whitespace errors).

  3. Remove the whitespace errors from your working copy:

    git checkout .
    
  4. Bring back your changes (if you aren't ready to commit them):

    git reset
    

The GIT_EDITOR=: means to use : as the editor, and as a command : is the identity.

0 votes
answered Feb 14, 2013 by urandom

I wrote this pre-commit hook, which only removes the trailing white-space from the lines which you've changed/added, since the previous suggestions tend to create unreadable commits if the target files have too much trailing white-space.

#!/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

IFS='
'

files=$(git diff-index --check --cached $against -- | sed '/^[+-]/d' | perl -pe 's/:[0-9]+:.*//' | uniq)
for file in $files ; do
    diff=$(git diff --cached $file)
    if test "$(git config diff.noprefix)" = "true"; then
        prefix=0
    else
        prefix=1
    fi
    echo "$diff" | patch -R -p$prefix
    diff=$(echo "$diff" | perl -pe 's/[ \t]+$// if m{^\+}')
    out=$(echo "$diff" | patch -p$prefix -f -s -t -o -)
    if [ $? -eq 0 ]; then
        echo "$diff" | patch -p$prefix -f -t -s
    fi
    git add $file
done
0 votes
answered Feb 2, 2014 by nat

To delete trailing whitespace at end of line in a file portably, use ed:

test -s file &&
   printf '%s\n' H ',g/[[:space:]]*$/s///' 'wq' | ed -s file
Welcome to Q&A, where you can ask questions and receive answers from other members of the community.
Website Online Counter

...