4

I'm writing a pre-commit hook to run my Python Tests and everything is working wonderfully... until I get to a merge conflict. In this merge conflict, some documentation files are introduced that have trailing whitespace and every time I try to commit, I get these messages:

<stdin>:28: trailing whitespace.
Ticket Link: 
<stdin>:54: trailing whitespace.
Then visit `http://app.dev` 
<stdin>:13528: trailing whitespace.
                            //most be provided for ALL resource updates, and 
<stdin>:13531: trailing whitespace.
    "deleted": false,  //indicates if this action resulted in a resource delete; 

warning: squelched 415 whitespace errors
warning: 420 lines add whitespace errors.
fatal: could not open '.git/MERGE_HEAD' for reading: No such file or directory

And then when I open up my editor to write the commit message, it's totally empty.

My pre-commit script is:

#!/bin/sh

RED='\033[0;31m'
NC='\033[0m'

proper_pop() {
    git reset --hard -q 
    git stash apply -q --index && git stash drop -q
}

exit_and_pop() {
    proper_pop

    if [ "$1" -ne 0 ]; then
        echo "${RED}Your code failed the pre-commit hook! Please examine the output and fix your issues!${NC}"
    fi

    exit $1
}

run_and_bail() {
    bash -c "$1";
    ret=$?;

    if [ "${ret}" -ne 0 ]; then
        exit_and_pop "${ret}"
    fi
}

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

old_stash=$(git rev-parse -q --verify refs/stash)
git stash -q --keep-index
new_stash=$(git rev-parse -q --verify refs/stash)

if [ "$old_stash" = "$new_stash" ]; then
    echo "pre-commit script: No changes to test. Not running."
    sleep 1  # HACK: Editor may erase message if done too quickly, make the programmer read
    exit 0
fi

# If you want to allow non-ASCII filenames set this variable to true.
allownonascii=$(git config --bool hooks.allownonascii)

# Redirect output to stderr.
exec 1>&2

# Cross platform projects tend to avoid non-ASCII filenames; prevent
# them from being added to the repository. We exploit the fact that the
# printable range starts at the space character and ends with tilde.
if [ "$allownonascii" != "true" ] &&
    # Note that the use of brackets around a tr range is ok here, (it's
    # even required, for portability to Solaris 10's /usr/bin/tr), since
    # the square bracket bytes happen to fall in the designated range.
    test $(git diff --cached --name-only --diff-filter=A -z $against |
      LC_ALL=C tr -d '[ -~]\0' | wc -c) != 0
then
    cat <<\EOF
Error: Attempt to add a non-ASCII file name.
This can cause problems if you want to work with people on other platforms.
To be portable it is advisable to rename the file.
If you know what you are doing you can disable this check using:
  git config hooks.allownonascii true
EOF
    exit_and_pop 1
fi

if ! [ -z "$(which git-pylint-commit-hook)" ]; then
    run_and_bail "git-pylint-commit-hook"
fi

if ! [ -z "$(which pep8)" ]; then
    run_and_bail "python hooks/pep8-hook-check.py"
fi

proper_pop

Through my debugging, I have produced the minimal script down to reproduce this error as:

#!/bin/sh

RED='\033[0;31m'
NC='\033[0m'

proper_pop() {
    git reset --hard -q 
    git stash apply -q --index && git stash drop -q
}

exit_and_pop() {
    proper_pop

    if [ "$1" -ne 0 ]; then
        echo "${RED}Your code failed the pre-commit hook! Please examine the output and fix your issues!${NC}"
    fi

    exit $1
}

run_and_bail() {
    bash -c "$1";
    ret=$?;

    if [ "${ret}" -ne 0 ]; then
        exit_and_pop "${ret}"
    fi
}

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

old_stash=$(git rev-parse -q --verify refs/stash)
git stash -q --keep-index
new_stash=$(git rev-parse -q --verify refs/stash)

if [ "$old_stash" = "$new_stash" ]; then
    echo "pre-commit script: No changes to test. Not running."
    sleep 1  # HACK: Editor may erase message if done too quickly, make the programmer read
    exit 0
fi

proper_pop

Through this, I have learned that the line: git reset --hard -q in proper_pop is the sole culprit (removing it removes the error). There are no other hooks installed, the git version is: 1.8.1.2, but I've also run this on version 2.5.0 and the same issue happens. Does anyone have any idea what's happening?

I tried piping stdout and stderr to /dev/null for that one command, just to see if it worked, and the errors were still printed...

It's completely a problem I can work around, and it really doesn't cause any issues, but I don't want a less technical co-worker to see this error and for it to throw them off (and I'm not sure if this will be printed every time we're merging in something with trailing whitespaces), so I'd love to know what's happening and how to fix this (or tell git reset to ignore trailing whitespace errors).

EDIT:

Here is a full working repo that demonstrates this problem: https://github.com/hjc1710/so-git-hook-question, merely follow the steps in the README.md and you'll get the error, regardless of whether you're in a merge or not.

My git config (for both my workstation, which is on 1.8.x, and my laptop, which is on 2.5.x) can be found here. Sensitive information has been stripped, but none of it should have been relevant.

hjc1710
  • 576
  • 4
  • 17
  • 1
    Running a reset --hard with stashing seems like a recipe for disaster in a pre-commit. Your loosing the staging and changing the HEAD all while in the middle of git attempting to do the same. Also if you do a git reset in the middle of a merge you've essentially aborted the merge. Maybe not using a pre-commit but instead a staging branch. Then with a workflow of a CI system that tests staging and merges to master for you. – Sukima Feb 23 '16 at 23:37
  • I'm trying to see where stdin is piped into diff, because that is what the complaints are coming from (`:28`), but the syntax doesn't seem to be there. Have you confirmed that the pre-commit hook causes the same problem on a clean machine with a fresh install of git? Running your script produced no error for me, but those errors do look reminiscent of the warning for things like `git rebase --whitespace=warn`. I would be interested in seeing your repo, global, and system .gitconfigs as well (`git config -l`, `git config --global -l`, `git config --system -l`). – Bujiraso Feb 28 '16 at 17:16
  • @Bujiraso I have edited my original question with both my git configs (my 2.5.x on my laptop and my 1.8.x on my workstation), and a sample repo that demonstrates this exact problem using a stripped down hook and a single text file. The error happens whether you're in a merge or not, and the hook simply stashes with an index, and properly unstashes without the index. I don't have any easy machine with a fresh install, but those instructions in the repo should trigger an error 100% of the time, if you want to play with them. I have **no** idea where this is getting piped to git diff. Thanks! – hjc1710 Feb 29 '16 at 21:03
  • 1
    @Sukima - stashing changes in a pre-commit hook isn't the crazy thing in the world. A ton of example scripts I've seen do that, that way you're only linting and testing what's about to get committed, and not what's just sitting there changed. You also have to `reset --hard` to properly undo what stash does, otherwise `git stash pop` will give you merge conflicts. I see what you're saying about merges though, and I might disable this for merges by seeing if `MERGE_MSG` exists when the hook runs. However, this also happens when not in a merge, so... I'm confused! – hjc1710 Feb 29 '16 at 21:06
  • @hjc1710 - yep -- now I'm seeing your error. Will investigate further. – Bujiraso Feb 29 '16 at 21:39
  • Awesome! Thanks so much @Bujiraso! – hjc1710 Feb 29 '16 at 21:40
  • @hjc1710 - Does the problem occur for you if you merely run `git stash` `git stash apply -q --index`? It does for me. That's without any committing or merging -- just running it from the shell. (Also I sent you a PR on github for a smaller pre-commit hook) – Bujiraso Feb 29 '16 at 22:08
  • @Bujiraso - yes it does! It still happens if I run the commands directly, and in the modified commit hook that you submitted. I just ran a small simple edit, committed, and pushed it up, which still produced the error. Any further thoughts on this? – hjc1710 Feb 29 '16 at 22:16
  • I'm not sure if I interpreted your question correctly. My answer addresses the question `Why can't I suppress the whitespace errors?` rather than `Why does git detect whitespace errors in the first place?`. Please clarify if I misunderstood. – merlin2011 Feb 29 '16 at 22:47
  • @hjc1710 -- I added an answer with my understandings on the matter. – Bujiraso Feb 29 '16 at 23:08
  • Read, accepted, and appreciated! Thanks @Bujiraso! Loved your answer and was able to tailor it to solve my problem! You just had a slight error in your `.git/config`. – hjc1710 Mar 01 '16 at 01:07
  • @merlin2011 You actually managed to answer both of my questions! I now know how to fix it, and what causes it. Unfortunately, your fix uses an outside package and Bujiraso's does not, so I'll be accepting his. Thanks for your time and effort though! – hjc1710 Mar 01 '16 at 01:07

2 Answers2

3

As the second footnote in this answer points out, git stash apply merely calls git diff ... | git apply --index. The warnings are coming from these commands.

This happens when you have trailing whitespace, stash the files containing trailing whitespace, and then apply the stash. More accurately it happens any time you try to git apply trailing newlines to a body of code

If you create a git patch with trailing whitespace and try to apply it via cat a.patch | git diff --index, the same error results.

As for solving this issue, it would stand to reason that setting core.whitespace=nowarn in the local, global, or system gitconfig would silence it, but this does not seem to be true for me, but YMMV. (Those settings are not being read, it seems). run git config core.whitespace -blank-at-eol, as pointed out in the comments.

The only solution I see, other than the above, is to find the offending line in the git-stash bash script and add the --whitespace=nowarn argument to git apply, yourself. Which I should mention is a fairly non-standard, non-portable solution. You'd have to have everyone do this, most likely every time git updates, too. But if they bother you to no-end -- that's what you can do.

On the bright side, as you mention, it can be worked around, but it might be worthwhile to open a bug on this on git, itself, if I haven't made a mess of things and it is ignoring even the system gitconfig. I would expect it should honour at least that.

Community
  • 1
  • 1
Bujiraso
  • 289
  • 2
  • 13
  • And if you want a command to replace just that line, the following worked for me `sudo sed -ie '/git diff.*git apply/s/apply --cached/apply --cached --whitespace=nowarn/' [location of git-stash script]`. For me it was located in `/usr/lib/git-core/git-stash`. – Bujiraso Feb 29 '16 at 22:54
  • 1
    Can confirm that if you open `/usr/lib/git-core/git-stash` and update line `422` to be: `git diff-tree --binary $s^2^..$s^2 | git apply --whitespace=nowarn --cached`, that the **entire issue** goes away. However, you are 100% right, that is **not** portable and is a **terrible** thing to do, so it's not what I'm going to do. However, I think you're doing your git config slightly wrong. If I edit my local git config (`.git/config`) and add `core.whitespace=nowarn` it doesn't work, and looking through the docs, that isn't a supported command, but adding `core.whitespace=-blank-at-eol` works! – hjc1710 Mar 01 '16 at 01:02
  • 1
    I think I'll open an issue on git, saying that `git stash apply` should also respect `--whitespace=nowarn`, like `git apply` does, but I don't think this is a bug larger than that. Thanks so much for your time and effort man! You really did a number in working to hunt this one down, figure out what was wrong and propose solutions (which I'm going to end up using). You earned that bounty and the correct answer! – hjc1710 Mar 01 '16 at 01:02
  • @hjc1710 I can confirm that that change to line `422` fixes the problem. Glad I could help and thanks! – Bujiraso Mar 01 '16 at 01:22
  • 1
    Awesome! Thanks for confirming @Bujiraso. [Here](https://gist.github.com/hjc1710/a77e1c5b319bda5e0e40) is the final gitconfig I used to get rid of the error once and for all! Luckily, we have a setup script that everyone runs, so adding this to a local git config wasn't an issue. – hjc1710 Mar 01 '16 at 01:24
  • @hjc1710 glad to hear you found a clean way to fix it! – Bujiraso Mar 01 '16 at 01:25
2

Kudos to you for making this so easy to reproduce!

The problem appears to be that the git command inside the hook is writing the whitespace errors to the current tty instead of stdout or stderr. Then, the hook runner is picking up the errors and then incorrectly claiming that it is stdout.

The fix comes from the answer to one of my previous questions.

#!/bin/sh
git stash > /dev/null
unbuffer sh -c 'git stash apply -q --index  && git stash drop -q ' >/dev/null 2>&1

Edit: If you want the unbuffer program on OSX, you can get it using the instructions here.

brew tap homebrew/dupes/expect
brew install homebrew/dupes/expect
Community
  • 1
  • 1
merlin2011
  • 71,677
  • 44
  • 195
  • 329
  • My pleasure for making this so easy to test! This was a tough problem to explain, so I figured the repo was easier. Also, thanks so much for your answer and response! Unfortunately, `unbuffer` is not installed by default and I have **no idea** if that works on OSX or what the OSX analog is (I have about 8 OSX guys), and I'd really rather not bring in an outside package, so I'm not going to accept your answer. You did an awesome job of telling me **why** this goes around ` >/dev/null 2>&1` and I thank you for that, but I like the answer of editing the local `gitconfig` better! – hjc1710 Mar 01 '16 at 01:06
  • No need to justify not accepting an answer. I'm glad you learned something from it. :) – merlin2011 Mar 01 '16 at 01:14
  • Thanks! I really do appreciate your time, and I always love learning about TTY's and pseudo-TTY's, they're absolutely **fascinating**. – hjc1710 Mar 01 '16 at 01:22