How do I properly git stash/pop in pre-commit hooks to get a clean working tree for tests?

Viewed 10379

I'm trying to do a pre-commit hook with a bare run of unit tests and I want to make sure my working directory is clean. Compiling takes a long time so I want to take advantage of reusing compiled binaries whenever possible. My script follows examples I've seen online:

# Stash changes
git stash -q --keep-index

# Run tests
...

# Restore changes
git stash pop -q

This causes problems though. Here's the repro:

  1. Add // Step 1 to a.java
  2. git add .
  3. Add // Step 2 to a.java
  4. git commit
    1. git stash -q --keep-index # Stash changes
    2. Run tests
    3. git stash pop -q # Restore changes

At this point I hit the problem. The git stash pop -q apparently has a conflict and in a.java I have

// Step 1
<<<<<<< Updated upstream
=======
// Step 2
>>>>>>> Stashed changes

Is there a way to get this to pop cleanly?

4 Answers

It seems that most answers here, at this time, are at least 5 years old. git stash has been re-written in C, there were some new bugs, and I don't really know how reliable it is.

I've used this a few times and it seems to be working - I wrote it from scratch, just using the docs as a guide. Has not been battle tested.

# We stash and un-stash changes ourselves.
#  - If any pre-commit/lint-staged checks fail, any auto-fixes will be lost.

# Create stash
#   index is the "staging area", so --keep-index means that anything you have already staged will be un-touched.
# NOTE: we always create a stash - possibly even a totally empty one.
git stash --keep-index --include-untracked --message="pre-commit auto-stash"
uncoloredStashedStat=$(git stash show --include-untracked stash@{0})
[[ $uncoloredStashedStat ]] && {
  echo "Stashed:"
  git diff --stat --staged stash@{0}
}

lintStagedStatus='failed'

yarn lint-staged --no-stash --concurrent $pre_commit_concurrency --shell "/bin/bash"  && {
  lintStagedStatus='passed'
}

outputSuppressed=$(git add --intent-to-add "**/*.snap")
diff=$(git diff)
[[ $diff ]] && {
  echo "staging modifications from pre-commit scripts:"
  git diff
  git add .
}

# Pop stash
#   We always create a stash - so we will always pop it.
#   Popped stash should generally not cause merge conflicts,
#   if your editor is formatting+autofixing code on save.
[[ $uncoloredStashedStat ]] && echo "restoring stash..."
git stash pop

if test "$lintStagedStatus" != 'passed'; then
  exit 1;
fi
Related