Skip to content

Commit b85a6f8

Browse files
committed
stash -p: (partially) fix bug concerning split hunks
When trying to stash part of the worktree changes by splitting a hunk and then only partially accepting the split bits and pieces, the user is presented with a rather cryptic error: error: patch failed: <file>:<line> error: test: patch does not apply Cannot remove worktree changes and the command would fail to stash the desired parts of the worktree changes (even if the `stash` ref was actually updated correctly). We even have a test case demonstrating that failure, carrying it for four years already. The explanation: when splitting a hunk, the changed lines are no longer separated by more than 3 lines (which is the amount of context lines Git's diffs use by default), but less than that. So when staging only part of the diff hunk for stashing, the resulting diff that we want to apply to the worktree in reverse will contain those changes to be dropped surrounded by three context lines, but since the diff is relative to HEAD rather than to the worktree, these context lines will not match. Example time. Let's assume that the file README contains these lines: We the people and the worktree added some lines so that it contains these lines instead: We are the kind people and the user tries to stash the line containing "are", then the command will internally stage this line to a temporary index file and try to revert the diff between HEAD and that index file. The diff hunk that `git stash` tries to revert will look somewhat like this: @@ -1776,3 +1776,4 We +are the people It is obvious, now, that the trailing context lines overlap with the part of the original diff hunk that the user did *not* want to stash. Keeping in mind that context lines in diffs serve the primary purpose of finding the exact location when the diff does not apply precisely (but when the exact line number in the file to be patched differs from the line number indicated in the diff), we work around this by reducing the amount of context lines: the diff was just generated. Note: this is not a *full* fix for the issue. Just as demonstrated in t3701's 'add -p works with pathological context lines' test case, there are ambiguities in the diff format. It is very rare in practice, of course, to encounter such repeated lines. The full solution for such cases would be to replace the approach of generating a diff from the stash and then applying it in reverse by emulating `git revert` (i.e. doing a 3-way merge). However, in `git stash -p` it would not apply to `HEAD` but instead to the worktree, which makes this non-trivial to implement as long as we also maintain a scripted version of `add -i`. Signed-off-by: Johannes Schindelin <[email protected]>
1 parent 9c4ed70 commit b85a6f8

File tree

3 files changed

+3
-3
lines changed

3 files changed

+3
-3
lines changed

builtin/stash.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1019,7 +1019,7 @@ static int stash_patch(struct stash_info *info, struct pathspec ps,
10191019
}
10201020

10211021
cp_diff_tree.git_cmd = 1;
1022-
argv_array_pushl(&cp_diff_tree.args, "diff-tree", "-p", "HEAD",
1022+
argv_array_pushl(&cp_diff_tree.args, "diff-tree", "-p", "-U1", "HEAD",
10231023
oid_to_hex(&info->w_tree), "--", NULL);
10241024
if (pipe_command(&cp_diff_tree, NULL, 0, out_patch, 0, NULL, 0)) {
10251025
ret = -1;

git-legacy-stash.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -212,7 +212,7 @@ create_stash () {
212212
w_tree=$(GIT_INDEX_FILE="$TMP-index" git write-tree) ||
213213
die "$(gettext "Cannot save the current worktree state")"
214214

215-
git diff-tree -p HEAD $w_tree -- >"$TMP-patch" &&
215+
git diff-tree -p -U1 HEAD $w_tree -- >"$TMP-patch" &&
216216
test -s "$TMP-patch" ||
217217
die "$(gettext "No changes selected")"
218218

t/t3904-stash-patch.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@ test_expect_success 'none of this moved HEAD' '
8989
verify_saved_head
9090
'
9191

92-
test_expect_failure 'stash -p with split hunk' '
92+
test_expect_success 'stash -p with split hunk' '
9393
git reset --hard &&
9494
cat >test <<-\EOF &&
9595
aaa

0 commit comments

Comments
 (0)