Merge branch 'en/removing-untracked-fixes'
Various fixes in code paths that move untracked files away to make room. * en/removing-untracked-fixes: Documentation: call out commands that nuke untracked files/directories Comment important codepaths regarding nuking untracked files/dirs unpack-trees: avoid nuking untracked dir in way of locally deleted file unpack-trees: avoid nuking untracked dir in way of unmerged file Change unpack_trees' 'reset' flag into an enum Remove ignored files by default when they are in the way unpack-trees: make dir an internal-only struct unpack-trees: introduce preserve_ignored to unpack_trees_options read-tree, merge-recursive: overwrite ignored files by default checkout, read-tree: fix leak of unpack_trees_options.dir t2500: add various tests for nuking untracked files
This commit is contained in:
commit
a7c2daa06d
@ -118,8 +118,9 @@ OPTIONS
|
||||
-f::
|
||||
--force::
|
||||
When switching branches, proceed even if the index or the
|
||||
working tree differs from `HEAD`. This is used to throw away
|
||||
local changes.
|
||||
working tree differs from `HEAD`, and even if there are untracked
|
||||
files in the way. This is used to throw away local changes and
|
||||
any untracked files or directories that are in the way.
|
||||
+
|
||||
When checking out paths from the index, do not fail upon unmerged
|
||||
entries; instead, unmerged entries are ignored.
|
||||
|
@ -10,8 +10,7 @@ SYNOPSIS
|
||||
--------
|
||||
[verse]
|
||||
'git read-tree' [[-m [--trivial] [--aggressive] | --reset | --prefix=<prefix>]
|
||||
[-u [--exclude-per-directory=<gitignore>] | -i]]
|
||||
[--index-output=<file>] [--no-sparse-checkout]
|
||||
[-u | -i]] [--index-output=<file>] [--no-sparse-checkout]
|
||||
(--empty | <tree-ish1> [<tree-ish2> [<tree-ish3>]])
|
||||
|
||||
|
||||
@ -39,8 +38,9 @@ OPTIONS
|
||||
|
||||
--reset::
|
||||
Same as -m, except that unmerged entries are discarded instead
|
||||
of failing. When used with `-u`, updates leading to loss of
|
||||
working tree changes will not abort the operation.
|
||||
of failing. When used with `-u`, updates leading to loss of
|
||||
working tree changes or untracked files or directories will not
|
||||
abort the operation.
|
||||
|
||||
-u::
|
||||
After a successful merge, update the files in the work
|
||||
@ -88,21 +88,6 @@ OPTIONS
|
||||
The command will refuse to overwrite entries that already
|
||||
existed in the original index file.
|
||||
|
||||
--exclude-per-directory=<gitignore>::
|
||||
When running the command with `-u` and `-m` options, the
|
||||
merge result may need to overwrite paths that are not
|
||||
tracked in the current branch. The command usually
|
||||
refuses to proceed with the merge to avoid losing such a
|
||||
path. However this safety valve sometimes gets in the
|
||||
way. For example, it often happens that the other
|
||||
branch added a file that used to be a generated file in
|
||||
your branch, and the safety valve triggers when you try
|
||||
to switch to that branch after you ran `make` but before
|
||||
running `make clean` to remove the generated file. This
|
||||
option tells the command to read per-directory exclude
|
||||
file (usually '.gitignore') and allows such an untracked
|
||||
but explicitly ignored file to be overwritten.
|
||||
|
||||
--index-output=<file>::
|
||||
Instead of writing the results out to `$GIT_INDEX_FILE`,
|
||||
write the resulting index in the named file. While the
|
||||
|
@ -69,7 +69,8 @@ linkgit:git-add[1]).
|
||||
|
||||
--hard::
|
||||
Resets the index and working tree. Any changes to tracked files in the
|
||||
working tree since `<commit>` are discarded.
|
||||
working tree since `<commit>` are discarded. Any untracked files or
|
||||
directories in the way of writing any tracked files are simply deleted.
|
||||
|
||||
--merge::
|
||||
Resets the index and updates the files in the working tree that are
|
||||
|
@ -1917,7 +1917,8 @@ static int fast_forward_to(struct tree *head, struct tree *remote, int reset)
|
||||
opts.dst_index = &the_index;
|
||||
opts.update = 1;
|
||||
opts.merge = 1;
|
||||
opts.reset = reset;
|
||||
opts.reset = reset ? UNPACK_RESET_PROTECT_UNTRACKED : 0;
|
||||
opts.preserve_ignored = 0; /* FIXME: !overwrite_ignore */
|
||||
opts.fn = twoway_merge;
|
||||
init_tree_desc(&t[0], head->buffer, head->size);
|
||||
init_tree_desc(&t[1], remote->buffer, remote->size);
|
||||
|
@ -646,7 +646,9 @@ static int reset_tree(struct tree *tree, const struct checkout_opts *o,
|
||||
opts.head_idx = -1;
|
||||
opts.update = worktree;
|
||||
opts.skip_unmerged = !worktree;
|
||||
opts.reset = 1;
|
||||
opts.reset = o->force ? UNPACK_RESET_OVERWRITE_UNTRACKED :
|
||||
UNPACK_RESET_PROTECT_UNTRACKED;
|
||||
opts.preserve_ignored = (!o->force && !o->overwrite_ignore);
|
||||
opts.merge = 1;
|
||||
opts.fn = oneway_merge;
|
||||
opts.verbose_update = o->show_progress;
|
||||
@ -746,11 +748,7 @@ static int merge_working_tree(const struct checkout_opts *opts,
|
||||
new_branch_info->commit ?
|
||||
&new_branch_info->commit->object.oid :
|
||||
&new_branch_info->oid, NULL);
|
||||
if (opts->overwrite_ignore) {
|
||||
topts.dir = xcalloc(1, sizeof(*topts.dir));
|
||||
topts.dir->flags |= DIR_SHOW_IGNORED;
|
||||
setup_standard_excludes(topts.dir);
|
||||
}
|
||||
topts.preserve_ignored = !opts->overwrite_ignore;
|
||||
tree = parse_tree_indirect(old_branch_info->commit ?
|
||||
&old_branch_info->commit->object.oid :
|
||||
the_hash_algo->empty_tree);
|
||||
|
@ -687,6 +687,7 @@ static int checkout(int submodule_progress)
|
||||
opts.update = 1;
|
||||
opts.merge = 1;
|
||||
opts.clone = 1;
|
||||
opts.preserve_ignored = 0;
|
||||
opts.fn = oneway_merge;
|
||||
opts.verbose_update = (option_verbosity >= 0);
|
||||
opts.src_index = &the_index;
|
||||
|
@ -680,6 +680,7 @@ static int read_tree_trivial(struct object_id *common, struct object_id *head,
|
||||
opts.verbose_update = 1;
|
||||
opts.trivial_merges_only = 1;
|
||||
opts.merge = 1;
|
||||
opts.preserve_ignored = 0; /* FIXME: !overwrite_ignore */
|
||||
trees[nr_trees] = parse_tree_indirect(common);
|
||||
if (!trees[nr_trees++])
|
||||
return -1;
|
||||
|
@ -38,7 +38,7 @@ static int list_tree(struct object_id *oid)
|
||||
}
|
||||
|
||||
static const char * const read_tree_usage[] = {
|
||||
N_("git read-tree [(-m [--trivial] [--aggressive] | --reset | --prefix=<prefix>) [-u [--exclude-per-directory=<gitignore>] | -i]] [--no-sparse-checkout] [--index-output=<file>] (--empty | <tree-ish1> [<tree-ish2> [<tree-ish3>]])"),
|
||||
N_("git read-tree [(-m [--trivial] [--aggressive] | --reset | --prefix=<prefix>) [-u | -i]] [--no-sparse-checkout] [--index-output=<file>] (--empty | <tree-ish1> [<tree-ish2> [<tree-ish3>]])"),
|
||||
NULL
|
||||
};
|
||||
|
||||
@ -53,24 +53,16 @@ static int index_output_cb(const struct option *opt, const char *arg,
|
||||
static int exclude_per_directory_cb(const struct option *opt, const char *arg,
|
||||
int unset)
|
||||
{
|
||||
struct dir_struct *dir;
|
||||
struct unpack_trees_options *opts;
|
||||
|
||||
BUG_ON_OPT_NEG(unset);
|
||||
|
||||
opts = (struct unpack_trees_options *)opt->value;
|
||||
|
||||
if (opts->dir)
|
||||
die("more than one --exclude-per-directory given.");
|
||||
|
||||
dir = xcalloc(1, sizeof(*opts->dir));
|
||||
dir->flags |= DIR_SHOW_IGNORED;
|
||||
dir->exclude_per_dir = arg;
|
||||
opts->dir = dir;
|
||||
/* We do not need to nor want to do read-directory
|
||||
* here; we are merely interested in reusing the
|
||||
* per directory ignore stack mechanism.
|
||||
*/
|
||||
if (!opts->update)
|
||||
die("--exclude-per-directory is meaningless unless -u");
|
||||
if (strcmp(arg, ".gitignore"))
|
||||
die("--exclude-per-directory argument must be .gitignore");
|
||||
return 0;
|
||||
}
|
||||
|
||||
@ -174,6 +166,9 @@ int cmd_read_tree(int argc, const char **argv, const char *cmd_prefix)
|
||||
if (1 < opts.merge + opts.reset + prefix_set)
|
||||
die("Which one? -m, --reset, or --prefix?");
|
||||
|
||||
if (opts.reset)
|
||||
opts.reset = UNPACK_RESET_OVERWRITE_UNTRACKED;
|
||||
|
||||
/*
|
||||
* NEEDSWORK
|
||||
*
|
||||
@ -209,8 +204,9 @@ int cmd_read_tree(int argc, const char **argv, const char *cmd_prefix)
|
||||
if ((opts.update || opts.index_only) && !opts.merge)
|
||||
die("%s is meaningless without -m, --reset, or --prefix",
|
||||
opts.update ? "-u" : "-i");
|
||||
if ((opts.dir && !opts.update))
|
||||
die("--exclude-per-directory is meaningless unless -u");
|
||||
if (opts.update && !opts.reset)
|
||||
opts.preserve_ignored = 0;
|
||||
/* otherwise, opts.preserve_ignored is irrelevant */
|
||||
if (opts.merge && !opts.index_only)
|
||||
setup_work_tree();
|
||||
|
||||
|
@ -67,12 +67,18 @@ static int reset_index(const char *ref, const struct object_id *oid, int reset_t
|
||||
case KEEP:
|
||||
case MERGE:
|
||||
opts.update = 1;
|
||||
opts.preserve_ignored = 0; /* FIXME: !overwrite_ignore */
|
||||
break;
|
||||
case HARD:
|
||||
opts.update = 1;
|
||||
/* fallthrough */
|
||||
opts.reset = UNPACK_RESET_OVERWRITE_UNTRACKED;
|
||||
break;
|
||||
case MIXED:
|
||||
opts.reset = UNPACK_RESET_PROTECT_UNTRACKED;
|
||||
/* but opts.update=0, so working tree not updated */
|
||||
break;
|
||||
default:
|
||||
opts.reset = 1;
|
||||
BUG("invalid reset_type passed to reset_index");
|
||||
}
|
||||
|
||||
read_cache_unmerged();
|
||||
|
@ -256,8 +256,10 @@ static int reset_tree(struct object_id *i_tree, int update, int reset)
|
||||
opts.src_index = &the_index;
|
||||
opts.dst_index = &the_index;
|
||||
opts.merge = 1;
|
||||
opts.reset = reset;
|
||||
opts.reset = reset ? UNPACK_RESET_PROTECT_UNTRACKED : 0;
|
||||
opts.update = update;
|
||||
if (update)
|
||||
opts.preserve_ignored = 0; /* FIXME: !overwrite_ignore */
|
||||
opts.fn = oneway_merge;
|
||||
|
||||
if (unpack_trees(nr_trees, t, &opts))
|
||||
@ -1533,6 +1535,7 @@ static int do_push_stash(const struct pathspec *ps, const char *stash_msg, int q
|
||||
} else {
|
||||
struct child_process cp = CHILD_PROCESS_INIT;
|
||||
cp.git_cmd = 1;
|
||||
/* BUG: this nukes untracked files in the way */
|
||||
strvec_pushl(&cp.args, "reset", "--hard", "-q",
|
||||
"--no-recurse-submodules", NULL);
|
||||
if (run_command(&cp)) {
|
||||
|
@ -3086,6 +3086,10 @@ static int add_submodule(const struct add_data *add_data)
|
||||
prepare_submodule_repo_env(&cp.env_array);
|
||||
cp.git_cmd = 1;
|
||||
cp.dir = add_data->sm_path;
|
||||
/*
|
||||
* NOTE: we only get here if add_data->force is true, so
|
||||
* passing --force to checkout is reasonable.
|
||||
*/
|
||||
strvec_pushl(&cp.args, "checkout", "-f", "-q", NULL);
|
||||
|
||||
if (add_data->branch) {
|
||||
|
@ -91,7 +91,7 @@ do
|
||||
git checkout -q $commit -- .
|
||||
git rerere
|
||||
fi
|
||||
git reset -q --hard
|
||||
git reset -q --hard # Might nuke untracked files...
|
||||
done
|
||||
|
||||
if test -z "$branch"
|
||||
|
@ -4062,11 +4062,7 @@ static int checkout(struct merge_options *opt,
|
||||
unpack_opts.quiet = 0; /* FIXME: sequencer might want quiet? */
|
||||
unpack_opts.verbose_update = (opt->verbosity > 2);
|
||||
unpack_opts.fn = twoway_merge;
|
||||
if (1/* FIXME: opts->overwrite_ignore*/) {
|
||||
CALLOC_ARRAY(unpack_opts.dir, 1);
|
||||
unpack_opts.dir->flags |= DIR_SHOW_IGNORED;
|
||||
setup_standard_excludes(unpack_opts.dir);
|
||||
}
|
||||
unpack_opts.preserve_ignored = 0; /* FIXME: !opts->overwrite_ignore */
|
||||
parse_tree(prev);
|
||||
init_tree_desc(&trees[0], prev->buffer, prev->size);
|
||||
parse_tree(next);
|
||||
@ -4074,8 +4070,6 @@ static int checkout(struct merge_options *opt,
|
||||
|
||||
ret = unpack_trees(2, trees, &unpack_opts);
|
||||
clear_unpack_trees_porcelain(&unpack_opts);
|
||||
dir_clear(unpack_opts.dir);
|
||||
FREE_AND_NULL(unpack_opts.dir);
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
@ -409,8 +409,11 @@ static int unpack_trees_start(struct merge_options *opt,
|
||||
memset(&opt->priv->unpack_opts, 0, sizeof(opt->priv->unpack_opts));
|
||||
if (opt->priv->call_depth)
|
||||
opt->priv->unpack_opts.index_only = 1;
|
||||
else
|
||||
else {
|
||||
opt->priv->unpack_opts.update = 1;
|
||||
/* FIXME: should only do this if !overwrite_ignore */
|
||||
opt->priv->unpack_opts.preserve_ignored = 0;
|
||||
}
|
||||
opt->priv->unpack_opts.merge = 1;
|
||||
opt->priv->unpack_opts.head_idx = 2;
|
||||
opt->priv->unpack_opts.fn = threeway_merge;
|
||||
|
8
merge.c
8
merge.c
@ -53,7 +53,6 @@ int checkout_fast_forward(struct repository *r,
|
||||
struct unpack_trees_options opts;
|
||||
struct tree_desc t[MAX_UNPACK_TREES];
|
||||
int i, nr_trees = 0;
|
||||
struct dir_struct dir = DIR_INIT;
|
||||
struct lock_file lock_file = LOCK_INIT;
|
||||
|
||||
refresh_index(r->index, REFRESH_QUIET, NULL, NULL, NULL);
|
||||
@ -80,11 +79,7 @@ int checkout_fast_forward(struct repository *r,
|
||||
}
|
||||
|
||||
memset(&opts, 0, sizeof(opts));
|
||||
if (overwrite_ignore) {
|
||||
dir.flags |= DIR_SHOW_IGNORED;
|
||||
setup_standard_excludes(&dir);
|
||||
opts.dir = &dir;
|
||||
}
|
||||
opts.preserve_ignored = !overwrite_ignore;
|
||||
|
||||
opts.head_idx = 1;
|
||||
opts.src_index = r->index;
|
||||
@ -101,7 +96,6 @@ int checkout_fast_forward(struct repository *r,
|
||||
clear_unpack_trees_porcelain(&opts);
|
||||
return -1;
|
||||
}
|
||||
dir_clear(&dir);
|
||||
clear_unpack_trees_porcelain(&opts);
|
||||
|
||||
if (write_locked_index(r->index, &lock_file, COMMIT_LOCK))
|
||||
|
3
reset.c
3
reset.c
@ -56,9 +56,10 @@ int reset_head(struct repository *r, struct object_id *oid, const char *action,
|
||||
unpack_tree_opts.fn = reset_hard ? oneway_merge : twoway_merge;
|
||||
unpack_tree_opts.update = 1;
|
||||
unpack_tree_opts.merge = 1;
|
||||
unpack_tree_opts.preserve_ignored = 0; /* FIXME: !overwrite_ignore */
|
||||
init_checkout_metadata(&unpack_tree_opts.meta, switch_to_branch, oid, NULL);
|
||||
if (!detach_head)
|
||||
unpack_tree_opts.reset = 1;
|
||||
unpack_tree_opts.reset = UNPACK_RESET_PROTECT_UNTRACKED;
|
||||
|
||||
if (repo_read_index_unmerged(r) < 0) {
|
||||
ret = error(_("could not read index"));
|
||||
|
@ -3693,6 +3693,7 @@ static int do_reset(struct repository *r,
|
||||
unpack_tree_opts.fn = oneway_merge;
|
||||
unpack_tree_opts.merge = 1;
|
||||
unpack_tree_opts.update = 1;
|
||||
unpack_tree_opts.preserve_ignored = 0; /* FIXME: !overwrite_ignore */
|
||||
init_checkout_metadata(&unpack_tree_opts.meta, name, &oid, NULL);
|
||||
|
||||
if (repo_read_index_unmerged(r)) {
|
||||
|
@ -1896,6 +1896,7 @@ static void submodule_reset_index(const char *path)
|
||||
|
||||
strvec_pushf(&cp.args, "--super-prefix=%s%s/",
|
||||
get_super_prefix_or_empty(), path);
|
||||
/* TODO: determine if this might overwright untracked files */
|
||||
strvec_pushl(&cp.args, "read-tree", "-u", "--reset", NULL);
|
||||
|
||||
strvec_push(&cp.args, empty_tree_oid_hex());
|
||||
|
@ -6,7 +6,6 @@ test_description='read-tree can handle submodules'
|
||||
. "$TEST_DIRECTORY"/lib-submodule-update.sh
|
||||
|
||||
KNOWN_FAILURE_DIRECTORY_SUBMODULE_CONFLICTS=1
|
||||
KNOWN_FAILURE_SUBMODULE_OVERWRITE_IGNORED_UNTRACKED=1
|
||||
|
||||
test_submodule_switch_recursing_with_args "read-tree -u -m"
|
||||
|
||||
|
244
t/t2500-untracked-overwriting.sh
Executable file
244
t/t2500-untracked-overwriting.sh
Executable file
@ -0,0 +1,244 @@
|
||||
#!/bin/sh
|
||||
|
||||
test_description='Test handling of overwriting untracked files'
|
||||
|
||||
. ./test-lib.sh
|
||||
|
||||
test_setup_reset () {
|
||||
git init reset_$1 &&
|
||||
(
|
||||
cd reset_$1 &&
|
||||
test_commit init &&
|
||||
|
||||
git branch stable &&
|
||||
git branch work &&
|
||||
|
||||
git checkout work &&
|
||||
test_commit foo &&
|
||||
|
||||
git checkout stable
|
||||
)
|
||||
}
|
||||
|
||||
test_expect_success 'reset --hard will nuke untracked files/dirs' '
|
||||
test_setup_reset hard &&
|
||||
(
|
||||
cd reset_hard &&
|
||||
git ls-tree -r stable &&
|
||||
git log --all --name-status --oneline &&
|
||||
git ls-tree -r work &&
|
||||
|
||||
mkdir foo.t &&
|
||||
echo precious >foo.t/file &&
|
||||
echo foo >expect &&
|
||||
|
||||
git reset --hard work &&
|
||||
|
||||
# check that untracked directory foo.t/ was nuked
|
||||
test_path_is_file foo.t &&
|
||||
test_cmp expect foo.t
|
||||
)
|
||||
'
|
||||
|
||||
test_expect_success 'reset --merge will preserve untracked files/dirs' '
|
||||
test_setup_reset merge &&
|
||||
(
|
||||
cd reset_merge &&
|
||||
|
||||
mkdir foo.t &&
|
||||
echo precious >foo.t/file &&
|
||||
cp foo.t/file expect &&
|
||||
|
||||
test_must_fail git reset --merge work 2>error &&
|
||||
test_cmp expect foo.t/file &&
|
||||
grep "Updating .foo.t. would lose untracked files" error
|
||||
)
|
||||
'
|
||||
|
||||
test_expect_success 'reset --keep will preserve untracked files/dirs' '
|
||||
test_setup_reset keep &&
|
||||
(
|
||||
cd reset_keep &&
|
||||
|
||||
mkdir foo.t &&
|
||||
echo precious >foo.t/file &&
|
||||
cp foo.t/file expect &&
|
||||
|
||||
test_must_fail git reset --merge work 2>error &&
|
||||
test_cmp expect foo.t/file &&
|
||||
grep "Updating.*foo.t.*would lose untracked files" error
|
||||
)
|
||||
'
|
||||
|
||||
test_setup_checkout_m () {
|
||||
git init checkout &&
|
||||
(
|
||||
cd checkout &&
|
||||
test_commit init &&
|
||||
|
||||
test_write_lines file has some >filler &&
|
||||
git add filler &&
|
||||
git commit -m filler &&
|
||||
|
||||
git branch stable &&
|
||||
|
||||
git switch -c work &&
|
||||
echo stuff >notes.txt &&
|
||||
test_write_lines file has some words >filler &&
|
||||
git add notes.txt filler &&
|
||||
git commit -m filler &&
|
||||
|
||||
git checkout stable
|
||||
)
|
||||
}
|
||||
|
||||
test_expect_success 'checkout -m does not nuke untracked file' '
|
||||
test_setup_checkout_m &&
|
||||
(
|
||||
cd checkout &&
|
||||
|
||||
# Tweak filler
|
||||
test_write_lines this file has some >filler &&
|
||||
# Make an untracked file, save its contents in "expect"
|
||||
echo precious >notes.txt &&
|
||||
cp notes.txt expect &&
|
||||
|
||||
test_must_fail git checkout -m work &&
|
||||
test_cmp expect notes.txt
|
||||
)
|
||||
'
|
||||
|
||||
test_setup_sequencing () {
|
||||
git init sequencing_$1 &&
|
||||
(
|
||||
cd sequencing_$1 &&
|
||||
test_commit init &&
|
||||
|
||||
test_write_lines this file has some words >filler &&
|
||||
git add filler &&
|
||||
git commit -m filler &&
|
||||
|
||||
mkdir -p foo/bar &&
|
||||
test_commit foo/bar/baz &&
|
||||
|
||||
git branch simple &&
|
||||
git branch fooey &&
|
||||
|
||||
git checkout fooey &&
|
||||
git rm foo/bar/baz.t &&
|
||||
echo stuff >>filler &&
|
||||
git add -u &&
|
||||
git commit -m "changes" &&
|
||||
|
||||
git checkout simple &&
|
||||
echo items >>filler &&
|
||||
echo newstuff >>newfile &&
|
||||
git add filler newfile &&
|
||||
git commit -m another
|
||||
)
|
||||
}
|
||||
|
||||
test_expect_success 'git rebase --abort and untracked files' '
|
||||
test_setup_sequencing rebase_abort_and_untracked &&
|
||||
(
|
||||
cd sequencing_rebase_abort_and_untracked &&
|
||||
git checkout fooey &&
|
||||
test_must_fail git rebase simple &&
|
||||
|
||||
cat init.t &&
|
||||
git rm init.t &&
|
||||
echo precious >init.t &&
|
||||
cp init.t expect &&
|
||||
git status --porcelain &&
|
||||
test_must_fail git rebase --abort &&
|
||||
test_cmp expect init.t
|
||||
)
|
||||
'
|
||||
|
||||
test_expect_success 'git rebase fast forwarding and untracked files' '
|
||||
test_setup_sequencing rebase_fast_forward_and_untracked &&
|
||||
(
|
||||
cd sequencing_rebase_fast_forward_and_untracked &&
|
||||
git checkout init &&
|
||||
echo precious >filler &&
|
||||
cp filler expect &&
|
||||
test_must_fail git rebase init simple &&
|
||||
test_cmp expect filler
|
||||
)
|
||||
'
|
||||
|
||||
test_expect_failure 'git rebase --autostash and untracked files' '
|
||||
test_setup_sequencing rebase_autostash_and_untracked &&
|
||||
(
|
||||
cd sequencing_rebase_autostash_and_untracked &&
|
||||
git checkout simple &&
|
||||
git rm filler &&
|
||||
mkdir filler &&
|
||||
echo precious >filler/file &&
|
||||
cp filler/file expect &&
|
||||
git rebase --autostash init &&
|
||||
test_path_is_file filler/file
|
||||
)
|
||||
'
|
||||
|
||||
test_expect_failure 'git stash and untracked files' '
|
||||
test_setup_sequencing stash_and_untracked_files &&
|
||||
(
|
||||
cd sequencing_stash_and_untracked_files &&
|
||||
git checkout simple &&
|
||||
git rm filler &&
|
||||
mkdir filler &&
|
||||
echo precious >filler/file &&
|
||||
cp filler/file expect &&
|
||||
git status --porcelain &&
|
||||
git stash push &&
|
||||
git status --porcelain &&
|
||||
test_path_is_file filler/file
|
||||
)
|
||||
'
|
||||
|
||||
test_expect_success 'git am --abort and untracked dir vs. unmerged file' '
|
||||
test_setup_sequencing am_abort_and_untracked &&
|
||||
(
|
||||
cd sequencing_am_abort_and_untracked &&
|
||||
git format-patch -1 --stdout fooey >changes.mbox &&
|
||||
test_must_fail git am --3way changes.mbox &&
|
||||
|
||||
# Delete the conflicted file; we will stage and commit it later
|
||||
rm filler &&
|
||||
|
||||
# Put an unrelated untracked directory there
|
||||
mkdir filler &&
|
||||
echo foo >filler/file1 &&
|
||||
echo bar >filler/file2 &&
|
||||
|
||||
test_must_fail git am --abort 2>errors &&
|
||||
test_path_is_dir filler &&
|
||||
grep "Updating .filler. would lose untracked files in it" errors
|
||||
)
|
||||
'
|
||||
|
||||
test_expect_success 'git am --skip and untracked dir vs deleted file' '
|
||||
test_setup_sequencing am_skip_and_untracked &&
|
||||
(
|
||||
cd sequencing_am_skip_and_untracked &&
|
||||
git checkout fooey &&
|
||||
git format-patch -1 --stdout simple >changes.mbox &&
|
||||
test_must_fail git am --3way changes.mbox &&
|
||||
|
||||
# Delete newfile
|
||||
rm newfile &&
|
||||
|
||||
# Put an unrelated untracked directory there
|
||||
mkdir newfile &&
|
||||
echo foo >newfile/file1 &&
|
||||
echo bar >newfile/file2 &&
|
||||
|
||||
# Change our mind about resolutions, just skip this patch
|
||||
test_must_fail git am --skip 2>errors &&
|
||||
test_path_is_dir newfile &&
|
||||
grep "Updating .newfile. would lose untracked files in it" errors
|
||||
)
|
||||
'
|
||||
|
||||
test_done
|
@ -6,7 +6,6 @@ test_description='reset can handle submodules'
|
||||
. "$TEST_DIRECTORY"/lib-submodule-update.sh
|
||||
|
||||
KNOWN_FAILURE_DIRECTORY_SUBMODULE_CONFLICTS=1
|
||||
KNOWN_FAILURE_SUBMODULE_OVERWRITE_IGNORED_UNTRACKED=1
|
||||
|
||||
test_submodule_switch_recursing_with_args "reset --keep"
|
||||
|
||||
|
@ -1694,9 +1694,15 @@ int unpack_trees(unsigned len, struct tree_desc *t, struct unpack_trees_options
|
||||
static struct cache_entry *dfc;
|
||||
struct pattern_list pl;
|
||||
int free_pattern_list = 0;
|
||||
struct dir_struct dir = DIR_INIT;
|
||||
|
||||
if (o->reset == UNPACK_RESET_INVALID)
|
||||
BUG("o->reset had a value of 1; should be UNPACK_TREES_*_UNTRACKED");
|
||||
|
||||
if (len > MAX_UNPACK_TREES)
|
||||
die("unpack_trees takes at most %d trees", MAX_UNPACK_TREES);
|
||||
if (o->dir)
|
||||
BUG("o->dir is for internal use only");
|
||||
|
||||
trace_performance_enter();
|
||||
trace2_region_enter("unpack_trees", "unpack_trees", the_repository);
|
||||
@ -1707,6 +1713,16 @@ int unpack_trees(unsigned len, struct tree_desc *t, struct unpack_trees_options
|
||||
ensure_full_index(o->dst_index);
|
||||
}
|
||||
|
||||
if (o->reset == UNPACK_RESET_OVERWRITE_UNTRACKED &&
|
||||
o->preserve_ignored)
|
||||
BUG("UNPACK_RESET_OVERWRITE_UNTRACKED incompatible with preserved ignored files");
|
||||
|
||||
if (!o->preserve_ignored) {
|
||||
o->dir = &dir;
|
||||
o->dir->flags |= DIR_SHOW_IGNORED;
|
||||
setup_standard_excludes(o->dir);
|
||||
}
|
||||
|
||||
if (!core_apply_sparse_checkout || !o->update)
|
||||
o->skip_sparse_checkout = 1;
|
||||
if (!o->skip_sparse_checkout && !o->pl) {
|
||||
@ -1868,6 +1884,10 @@ int unpack_trees(unsigned len, struct tree_desc *t, struct unpack_trees_options
|
||||
done:
|
||||
if (free_pattern_list)
|
||||
clear_pattern_list(&pl);
|
||||
if (o->dir) {
|
||||
dir_clear(o->dir);
|
||||
o->dir = NULL;
|
||||
}
|
||||
trace2_region_leave("unpack_trees", "unpack_trees", the_repository);
|
||||
trace_performance_leave("unpack_trees");
|
||||
return ret;
|
||||
@ -2158,9 +2178,15 @@ static int icase_exists(struct unpack_trees_options *o, const char *name, int le
|
||||
return src && !ie_match_stat(o->src_index, src, st, CE_MATCH_IGNORE_VALID|CE_MATCH_IGNORE_SKIP_WORKTREE);
|
||||
}
|
||||
|
||||
enum absent_checking_type {
|
||||
COMPLETELY_ABSENT,
|
||||
ABSENT_ANY_DIRECTORY
|
||||
};
|
||||
|
||||
static int check_ok_to_remove(const char *name, int len, int dtype,
|
||||
const struct cache_entry *ce, struct stat *st,
|
||||
enum unpack_trees_error_types error_type,
|
||||
enum absent_checking_type absent_type,
|
||||
struct unpack_trees_options *o)
|
||||
{
|
||||
const struct cache_entry *result;
|
||||
@ -2195,6 +2221,10 @@ static int check_ok_to_remove(const char *name, int len, int dtype,
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* If we only care about directories, then we can remove */
|
||||
if (absent_type == ABSENT_ANY_DIRECTORY)
|
||||
return 0;
|
||||
|
||||
/*
|
||||
* The previous round may already have decided to
|
||||
* delete this path, which is in a subdirectory that
|
||||
@ -2215,12 +2245,14 @@ static int check_ok_to_remove(const char *name, int len, int dtype,
|
||||
*/
|
||||
static int verify_absent_1(const struct cache_entry *ce,
|
||||
enum unpack_trees_error_types error_type,
|
||||
enum absent_checking_type absent_type,
|
||||
struct unpack_trees_options *o)
|
||||
{
|
||||
int len;
|
||||
struct stat st;
|
||||
|
||||
if (o->index_only || o->reset || !o->update)
|
||||
if (o->index_only || !o->update ||
|
||||
o->reset == UNPACK_RESET_OVERWRITE_UNTRACKED)
|
||||
return 0;
|
||||
|
||||
len = check_leading_path(ce->name, ce_namelen(ce), 0);
|
||||
@ -2240,7 +2272,8 @@ static int verify_absent_1(const struct cache_entry *ce,
|
||||
NULL, o);
|
||||
else
|
||||
ret = check_ok_to_remove(path, len, DT_UNKNOWN, NULL,
|
||||
&st, error_type, o);
|
||||
&st, error_type,
|
||||
absent_type, o);
|
||||
}
|
||||
free(path);
|
||||
return ret;
|
||||
@ -2255,7 +2288,7 @@ static int verify_absent_1(const struct cache_entry *ce,
|
||||
|
||||
return check_ok_to_remove(ce->name, ce_namelen(ce),
|
||||
ce_to_dtype(ce), ce, &st,
|
||||
error_type, o);
|
||||
error_type, absent_type, o);
|
||||
}
|
||||
}
|
||||
|
||||
@ -2265,14 +2298,23 @@ static int verify_absent(const struct cache_entry *ce,
|
||||
{
|
||||
if (!o->skip_sparse_checkout && (ce->ce_flags & CE_NEW_SKIP_WORKTREE))
|
||||
return 0;
|
||||
return verify_absent_1(ce, error_type, o);
|
||||
return verify_absent_1(ce, error_type, COMPLETELY_ABSENT, o);
|
||||
}
|
||||
|
||||
static int verify_absent_if_directory(const struct cache_entry *ce,
|
||||
enum unpack_trees_error_types error_type,
|
||||
struct unpack_trees_options *o)
|
||||
{
|
||||
if (!o->skip_sparse_checkout && (ce->ce_flags & CE_NEW_SKIP_WORKTREE))
|
||||
return 0;
|
||||
return verify_absent_1(ce, error_type, ABSENT_ANY_DIRECTORY, o);
|
||||
}
|
||||
|
||||
static int verify_absent_sparse(const struct cache_entry *ce,
|
||||
enum unpack_trees_error_types error_type,
|
||||
struct unpack_trees_options *o)
|
||||
{
|
||||
return verify_absent_1(ce, error_type, o);
|
||||
return verify_absent_1(ce, error_type, COMPLETELY_ABSENT, o);
|
||||
}
|
||||
|
||||
static int merged_entry(const struct cache_entry *ce,
|
||||
@ -2346,6 +2388,12 @@ static int merged_entry(const struct cache_entry *ce,
|
||||
* Previously unmerged entry left as an existence
|
||||
* marker by read_index_unmerged();
|
||||
*/
|
||||
if (verify_absent_if_directory(merge,
|
||||
ERROR_WOULD_LOSE_UNTRACKED_OVERWRITTEN, o)) {
|
||||
discard_cache_entry(merge);
|
||||
return -1;
|
||||
}
|
||||
|
||||
invalidate_ce_path(old, o);
|
||||
}
|
||||
|
||||
@ -2363,7 +2411,10 @@ static int deleted_entry(const struct cache_entry *ce,
|
||||
if (verify_absent(ce, ERROR_WOULD_LOSE_UNTRACKED_REMOVED, o))
|
||||
return -1;
|
||||
return 0;
|
||||
} else if (verify_absent_if_directory(ce, ERROR_WOULD_LOSE_UNTRACKED_REMOVED, o)) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (!(old->ce_flags & CE_CONFLICTED) && verify_uptodate(old, o))
|
||||
return -1;
|
||||
add_entry(o, ce, CE_REMOVE, 0);
|
||||
|
@ -45,10 +45,17 @@ void setup_unpack_trees_porcelain(struct unpack_trees_options *opts,
|
||||
*/
|
||||
void clear_unpack_trees_porcelain(struct unpack_trees_options *opts);
|
||||
|
||||
enum unpack_trees_reset_type {
|
||||
UNPACK_RESET_NONE = 0, /* traditional "false" value; still valid */
|
||||
UNPACK_RESET_INVALID = 1, /* "true" no longer valid; use below values */
|
||||
UNPACK_RESET_PROTECT_UNTRACKED,
|
||||
UNPACK_RESET_OVERWRITE_UNTRACKED
|
||||
};
|
||||
|
||||
struct unpack_trees_options {
|
||||
unsigned int reset,
|
||||
merge,
|
||||
unsigned int merge,
|
||||
update,
|
||||
preserve_ignored,
|
||||
clone,
|
||||
index_only,
|
||||
nontrivial_merge,
|
||||
@ -64,9 +71,9 @@ struct unpack_trees_options {
|
||||
exiting_early,
|
||||
show_all_errors,
|
||||
dry_run;
|
||||
enum unpack_trees_reset_type reset;
|
||||
const char *prefix;
|
||||
int cache_bottom;
|
||||
struct dir_struct *dir;
|
||||
struct pathspec *pathspec;
|
||||
merge_fn_t fn;
|
||||
const char *msgs[NB_UNPACK_TREES_WARNING_TYPES];
|
||||
@ -88,6 +95,7 @@ struct unpack_trees_options {
|
||||
struct index_state result;
|
||||
|
||||
struct pattern_list *pl; /* for internal use */
|
||||
struct dir_struct *dir; /* for internal use only */
|
||||
struct checkout_metadata meta;
|
||||
};
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user