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:
Junio C Hamano 2021-10-13 15:15:57 -07:00
commit a7c2daa06d
23 changed files with 366 additions and 74 deletions

View File

@ -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.

View File

@ -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

View File

@ -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

View File

@ -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);

View File

@ -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);

View File

@ -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;

View File

@ -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;

View File

@ -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();

View File

@ -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();

View File

@ -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)) {

View File

@ -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) {

View File

@ -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"

View File

@ -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;
}

View File

@ -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;

View File

@ -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))

View File

@ -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"));

View File

@ -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)) {

View File

@ -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());

View File

@ -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
View 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

View File

@ -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"

View File

@ -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);

View File

@ -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;
};