зеркало из https://github.com/microsoft/git.git
Merge branch 'en/stash-apply-sparse-checkout' into maint
"git stash" did not work well in a sparsely checked out working tree. * en/stash-apply-sparse-checkout: stash: fix stash application in sparse-checkouts stash: remove unnecessary process forking t7012: add a testcase demonstrating stash apply bugs in sparse checkouts
This commit is contained in:
Коммит
a4031f6dc0
165
builtin/stash.c
165
builtin/stash.c
|
@ -325,35 +325,6 @@ static void add_diff_to_buf(struct diff_queue_struct *q,
|
|||
}
|
||||
}
|
||||
|
||||
static int get_newly_staged(struct strbuf *out, struct object_id *c_tree)
|
||||
{
|
||||
struct child_process cp = CHILD_PROCESS_INIT;
|
||||
const char *c_tree_hex = oid_to_hex(c_tree);
|
||||
|
||||
/*
|
||||
* diff-index is very similar to diff-tree above, and should be
|
||||
* converted together with update_index.
|
||||
*/
|
||||
cp.git_cmd = 1;
|
||||
strvec_pushl(&cp.args, "diff-index", "--cached", "--name-only",
|
||||
"--diff-filter=A", NULL);
|
||||
strvec_push(&cp.args, c_tree_hex);
|
||||
return pipe_command(&cp, NULL, 0, out, 0, NULL, 0);
|
||||
}
|
||||
|
||||
static int update_index(struct strbuf *out)
|
||||
{
|
||||
struct child_process cp = CHILD_PROCESS_INIT;
|
||||
|
||||
/*
|
||||
* Update-index is very complicated and may need to have a public
|
||||
* function exposed in order to remove this forking.
|
||||
*/
|
||||
cp.git_cmd = 1;
|
||||
strvec_pushl(&cp.args, "update-index", "--add", "--stdin", NULL);
|
||||
return pipe_command(&cp, out->buf, out->len, NULL, 0, NULL, 0);
|
||||
}
|
||||
|
||||
static int restore_untracked(struct object_id *u_tree)
|
||||
{
|
||||
int res;
|
||||
|
@ -385,6 +356,121 @@ static int restore_untracked(struct object_id *u_tree)
|
|||
return res;
|
||||
}
|
||||
|
||||
static void unstage_changes_unless_new(struct object_id *orig_tree)
|
||||
{
|
||||
/*
|
||||
* When we enter this function, there has been a clean merge of
|
||||
* 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
|
||||
* (reflecting the changes brought in by the merge).
|
||||
*/
|
||||
diff_setup(&diff_opts);
|
||||
diff_opts.flags.recursive = 1;
|
||||
diff_opts.detect_rename = 0;
|
||||
diff_opts.output_format = DIFF_FORMAT_NO_OUTPUT;
|
||||
diff_setup_done(&diff_opts);
|
||||
|
||||
do_diff_cache(orig_tree, &diff_opts);
|
||||
diffcore_std(&diff_opts);
|
||||
|
||||
/* Iterate over the paths that changed due to the merge... */
|
||||
for (i = 0; i < diff_queued_diff.nr; i++) {
|
||||
struct diff_filepair *p;
|
||||
struct cache_entry *ce;
|
||||
int pos;
|
||||
|
||||
/* Look up the path's position in the current index. */
|
||||
p = diff_queued_diff.queue[i];
|
||||
pos = index_name_pos(&the_index, p->two->path,
|
||||
strlen(p->two->path));
|
||||
|
||||
/*
|
||||
* 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) {
|
||||
/*
|
||||
* Path existed in orig_tree; restore index entry
|
||||
* from that tree in order to "unstage" the changes.
|
||||
*/
|
||||
int option = ADD_CACHE_OK_TO_REPLACE;
|
||||
if (pos < 0)
|
||||
option = ADD_CACHE_OK_TO_ADD;
|
||||
|
||||
ce = make_cache_entry(&the_index,
|
||||
p->one->mode,
|
||||
&p->one->oid,
|
||||
p->one->path,
|
||||
0, 0);
|
||||
add_index_entry(&the_index, ce, option);
|
||||
}
|
||||
}
|
||||
diff_flush(&diff_opts);
|
||||
|
||||
/*
|
||||
* 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,
|
||||
COMMIT_LOCK | SKIP_IF_UNCHANGED))
|
||||
die(_("Unable to write index."));
|
||||
}
|
||||
|
||||
static int do_apply_stash(const char *prefix, struct stash_info *info,
|
||||
int index, int quiet)
|
||||
{
|
||||
|
@ -467,26 +553,7 @@ static int do_apply_stash(const char *prefix, struct stash_info *info,
|
|||
if (reset_tree(&index_tree, 0, 0))
|
||||
return -1;
|
||||
} else {
|
||||
struct strbuf out = STRBUF_INIT;
|
||||
|
||||
if (get_newly_staged(&out, &c_tree)) {
|
||||
strbuf_release(&out);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (reset_tree(&c_tree, 0, 1)) {
|
||||
strbuf_release(&out);
|
||||
return -1;
|
||||
}
|
||||
|
||||
ret = update_index(&out);
|
||||
strbuf_release(&out);
|
||||
if (ret)
|
||||
return -1;
|
||||
|
||||
/* read back the result of update_index() back from the disk */
|
||||
discard_cache();
|
||||
read_cache();
|
||||
unstage_changes_unless_new(&c_tree);
|
||||
}
|
||||
|
||||
if (!quiet) {
|
||||
|
|
|
@ -316,14 +316,7 @@ test_submodule_switch_common () {
|
|||
command="$1"
|
||||
######################### Appearing submodule #########################
|
||||
# Switching to a commit letting a submodule appear creates empty dir ...
|
||||
if test "$KNOWN_FAILURE_STASH_DOES_IGNORE_SUBMODULE_CHANGES" = 1
|
||||
then
|
||||
# Restoring stash fails to restore submodule index entry
|
||||
RESULT="failure"
|
||||
else
|
||||
RESULT="success"
|
||||
fi
|
||||
test_expect_$RESULT "$command: added submodule creates empty directory" '
|
||||
test_expect_success "$command: added submodule creates empty directory" '
|
||||
prolog &&
|
||||
reset_work_tree_to no_submodule &&
|
||||
(
|
||||
|
@ -337,6 +330,13 @@ test_submodule_switch_common () {
|
|||
)
|
||||
'
|
||||
# ... and doesn't care if it already exists.
|
||||
if test "$KNOWN_FAILURE_STASH_DOES_IGNORE_SUBMODULE_CHANGES" = 1
|
||||
then
|
||||
# Restoring stash fails to restore submodule index entry
|
||||
RESULT="failure"
|
||||
else
|
||||
RESULT="success"
|
||||
fi
|
||||
test_expect_$RESULT "$command: added submodule leaves existing empty directory alone" '
|
||||
prolog &&
|
||||
reset_work_tree_to no_submodule &&
|
||||
|
|
|
@ -149,6 +149,94 @@ test_expect_success '--ignore-skip-worktree-entries leaves worktree alone' '
|
|||
--diff-filter=D -- keep-me.t
|
||||
'
|
||||
|
||||
test_expect_success 'stash restore in sparse checkout' '
|
||||
test_create_repo stash-restore &&
|
||||
(
|
||||
cd stash-restore &&
|
||||
|
||||
mkdir subdir &&
|
||||
echo A >subdir/A &&
|
||||
echo untouched >untouched &&
|
||||
echo removeme >removeme &&
|
||||
echo modified >modified &&
|
||||
git add . &&
|
||||
git commit -m Initial &&
|
||||
|
||||
echo AA >>subdir/A &&
|
||||
echo addme >addme &&
|
||||
echo tweaked >>modified &&
|
||||
rm removeme &&
|
||||
git add addme &&
|
||||
|
||||
git stash push &&
|
||||
|
||||
git sparse-checkout set subdir &&
|
||||
|
||||
# Ensure after sparse-checkout we only have expected files
|
||||
cat >expect <<-EOF &&
|
||||
S modified
|
||||
S removeme
|
||||
H subdir/A
|
||||
S untouched
|
||||
EOF
|
||||
git ls-files -t >actual &&
|
||||
test_cmp expect actual &&
|
||||
|
||||
test_path_is_missing addme &&
|
||||
test_path_is_missing modified &&
|
||||
test_path_is_missing removeme &&
|
||||
test_path_is_file subdir/A &&
|
||||
test_path_is_missing untouched &&
|
||||
|
||||
# Put a file in the working directory in the way
|
||||
echo in the way >modified &&
|
||||
git stash apply &&
|
||||
|
||||
# Ensure stash vivifies modifies paths...
|
||||
cat >expect <<-EOF &&
|
||||
H addme
|
||||
H modified
|
||||
H removeme
|
||||
H subdir/A
|
||||
S untouched
|
||||
EOF
|
||||
git ls-files -t >actual &&
|
||||
test_cmp expect actual &&
|
||||
|
||||
# ...and that the paths show up in status as changed...
|
||||
cat >expect <<-EOF &&
|
||||
A addme
|
||||
M modified
|
||||
D removeme
|
||||
M subdir/A
|
||||
?? actual
|
||||
?? expect
|
||||
?? modified.stash.XXXXXX
|
||||
EOF
|
||||
git status --porcelain | \
|
||||
sed -e s/stash......./stash.XXXXXX/ >actual &&
|
||||
test_cmp expect actual &&
|
||||
|
||||
# ...and that working directory reflects the files correctly
|
||||
test_path_is_file addme &&
|
||||
test_path_is_file modified &&
|
||||
test_path_is_missing removeme &&
|
||||
test_path_is_file subdir/A &&
|
||||
test_path_is_missing untouched &&
|
||||
|
||||
# ...including that we have the expected "modified" file...
|
||||
cat >expect <<-EOF &&
|
||||
modified
|
||||
tweaked
|
||||
EOF
|
||||
test_cmp expect modified &&
|
||||
|
||||
# ...and that the other "modified" file is still present...
|
||||
echo in the way >expect &&
|
||||
test_cmp expect modified.stash.*
|
||||
)
|
||||
'
|
||||
|
||||
#TODO test_expect_failure 'git-apply adds file' false
|
||||
#TODO test_expect_failure 'git-apply updates file' false
|
||||
#TODO test_expect_failure 'git-apply removes file' false
|
||||
|
|
Загрузка…
Ссылка в новой задаче