Why does `git stash -p` sometimes fail?

2019-01-31 05:27发布

问题:

I ♥ git stash -p. But sometimes, after a satisfying session of y, n, and s, I get this:

Saved working directory and index state WIP on foo: 9794c1a lorum ipsum
error: patch failed: spec/models/thing_spec.rb:65
error: spec/models/thing_spec.rb: patch does not apply
Cannot remove worktree changes

Why?

回答1:

git stash -p should fail less with Git 2.17 (Q2 2018).
Before that, "git add -p" (which shares logic with git stash) has been lazy in coalescing split patches before passing the result to underlying "git apply", leading to corner case bugs; the logic to prepare the patch to be applied after hunk selections has been tightened.

See commit 3a8522f, commit b3e0fcf, commit 2b8ea7f (05 Mar 2018), commit fecc6f3, commit 23fea4c, commit 902f414 (01 Mar 2018), and commit 11489a6, commit e4d671c, commit 492e60c (19 Feb 2018) by Phillip Wood (phillipwood).
(Merged by Junio C Hamano -- gitster -- in commit 436d18f, 14 Mar 2018)

add -p: adjust offsets of subsequent hunks when one is skipped

(add, but again, can be applied to stash)

Since commit 8cbd431 ("git-add--interactive: replace hunk recounting with apply --recount", 2008-7-2, Git v1.6.0-rc0) if a hunk is skipped then we rely on the context lines to apply subsequent hunks in the right place.

While this works most of the time it is possible for hunks to end up being applied in the wrong place.

To fix this adjust the offset of subsequent hunks to correct for any change in the number of insertions or deletions due to the skipped hunk. The change in offset due to edited hunks that have the number of insertions or deletions changed is ignored here, it will be fixed in the next commit.

You can see some tests here.


Git 2.19 improves git add -p: when user edits the patch in "git add -p" and the user's editor is set to strip trailing whitespaces indiscriminately, an empty line that is unchanged in the patch would become completely empty (instead of a line with a sole SP on it).
The code introduced in Git 2.17 timeframe failed to parse such a patch, but now it learned to notice the situation and cope with it.

See commit f4d35a6 (11 Jun 2018) by Phillip Wood (phillipwood).
(Merged by Junio C Hamano -- gitster -- in commit 5eb8da8, 28 Jun 2018)

add -p: fix counting empty context lines in edited patches

recount_edited_hunk() introduced in commit 2b8ea7f ("add -p: calculate offset delta for edited patches", 2018-03-05, Git v2.17.0) required all context lines to start with a space, empty lines are not counted.
This was intended to avoid any recounting problems if the user had introduced empty lines at the end when editing the patch.

However this introduced a regression into 'git add -p' as it seems it is common for editors to strip the trailing whitespace from empty context lines when patches are edited thereby introducing empty lines that should be counted.
'git apply' knows how to deal with such empty lines and POSIX states that whether or not there is an space on an empty context line is implementation defined (see diff command).

Fix the regression by counting lines that consist solely of a newline as well as lines starting with a space as context lines and add a test to prevent future regressions.



回答2:

This happens for me any time I try to split a hunk into smaller hunks that are too close together (less than 3 lines between changes). The short explanation is that the patch has context lines in it that conflict with your local changes. More complete explanation below.


Suppose I have a git repo with these uncommitted changes:

--- a/pangram
+++ b/pangram
@@ -1,8 +1,8 @@
 The
-quick
+relatively quick
 brown
 fox
-jumps
+walks
 over
 the
 lazy

If I stash the first change, I get:

--- a/pangram
+++ b/pangram
@@ -1,5 +1,5 @@
 The
-quick
+relatively quick
 brown
 fox
 jumps

The git stash command actually does succeed in saving the patch (check git stash list), but then git uses that patch in reverse to remove the stashed changes from my working dir. The context after the hunk has "jumps", which doesn't match the "walks" still in my working dir. So git bails out with

error: patch failed: pangram:1
error: pangram: patch does not apply
Cannot remove worktree changes

and leaves all the changes in my working dir, and the stash becomes pretty much worthless.


I would call this a bug in git's hunk splitting support. If it knows it's splitting the changes too close, it could shave off a few lines of context from the patch, or jimmy the patch to have the modified context lines instead of the pristine ones. Alternatively, if splitting hunks this close is officially unsupported, it should actually refuse to split hunks that close.



回答3:

After just having a git stash -p fail in this same way, I had luck with this workaround (git 2.0.2):

  • git add -p, splitting the exact same hunks but with inverse answers ("y" to add "keeps" changes, "n" to stash keeps changes.)
  • git stash -k to keep the index and stash everything else
  • git reset to continue working on my files

I'm not sure why git add -p didn't fail in the same way that git stash -p did. I guess because adding works with the index rather than creating a patch file?



回答4:

The accepted answer at the moment can still unfortunately fail, even in Git 2.17.

If, like me, you spent a lot of effort constructing the perfect stash and don't want to throw that effort away, it is still possible to mostly get what you want with:

git stash show -p | patch -p1 -R

This will fail with rejects, but odds are good most of the hunks will apply correctly and at least save you the time of reviewing all the files again.



回答5:

Applying the state can fail with conflicts; in this case, it is not removed from the stash list. You need to resolve the conflicts by hand and call git stash drop manually afterwards



标签: git git-stash