diff options
-rw-r--r-- | builtin/stash.c | 50 | ||||
-rwxr-xr-x | t/t7012-skip-worktree-writing.sh | 2 |
2 files changed, 49 insertions, 3 deletions
diff --git a/builtin/stash.c b/builtin/stash.c index 3c641cc5cd..9839835dae 100644 --- a/builtin/stash.c +++ b/builtin/stash.c @@ -363,12 +363,23 @@ static void unstage_changes_unless_new(struct object_id *orig_tree) * relevant trees, and the merge logic always stages whatever merges * cleanly. We want to unstage those changes, unless it corresponds * to a file that didn't exist as of orig_tree. + * + * However, if any SKIP_WORKTREE path is modified relative to + * orig_tree, then we want to clear the SKIP_WORKTREE bit and write + * it to the worktree before unstaging. */ + struct checkout state = CHECKOUT_INIT; struct diff_options diff_opts; struct lock_file lock = LOCK_INIT; int i; + /* If any entries have skip_worktree set, we'll have to check 'em out */ + state.force = 1; + state.quiet = 1; + state.refresh_cache = 1; + state.istate = &the_index; + /* * Step 1: get a difference between orig_tree (which corresponding * to the index before a merge was run) and the current index @@ -395,7 +406,42 @@ static void unstage_changes_unless_new(struct object_id *orig_tree) strlen(p->two->path)); /* - * Step 2: "unstage" changes, as long as they are still tracked + * Step 2: Place changes in the working tree + * + * Stash is about restoring changes *to the working tree*. + * So if the merge successfully got a new version of some + * path, but left it out of the working tree, then clear the + * SKIP_WORKTREE bit and write it to the working tree. + */ + if (pos >= 0 && ce_skip_worktree(active_cache[pos])) { + struct stat st; + + ce = active_cache[pos]; + if (!lstat(ce->name, &st)) { + /* Conflicting path present; relocate it */ + struct strbuf new_path = STRBUF_INIT; + int fd; + + strbuf_addf(&new_path, + "%s.stash.XXXXXX", ce->name); + fd = xmkstemp(new_path.buf); + close(fd); + printf(_("WARNING: Untracked file in way of " + "tracked file! Renaming\n " + " %s -> %s\n" + " to make room.\n"), + ce->name, new_path.buf); + if (rename(ce->name, new_path.buf)) + die("Failed to move %s to %s\n", + ce->name, new_path.buf); + strbuf_release(&new_path); + } + checkout_entry(ce, &state, NULL, NULL); + ce->ce_flags &= ~CE_SKIP_WORKTREE; + } + + /* + * Step 3: "unstage" changes, as long as they are still tracked */ if (p->one->oid_valid) { /* @@ -417,7 +463,7 @@ static void unstage_changes_unless_new(struct object_id *orig_tree) diff_flush(&diff_opts); /* - * Step 3: write the new index to disk + * Step 4: write the new index to disk */ repo_hold_locked_index(the_repository, &lock, LOCK_DIE_ON_ERROR); if (write_locked_index(&the_index, &lock, diff --git a/t/t7012-skip-worktree-writing.sh b/t/t7012-skip-worktree-writing.sh index a184ee97fb..e5c6a038fb 100755 --- a/t/t7012-skip-worktree-writing.sh +++ b/t/t7012-skip-worktree-writing.sh @@ -149,7 +149,7 @@ test_expect_success '--ignore-skip-worktree-entries leaves worktree alone' ' --diff-filter=D -- keep-me.t ' -test_expect_failure 'stash restore in sparse checkout' ' +test_expect_success 'stash restore in sparse checkout' ' test_create_repo stash-restore && ( cd stash-restore && |