Skip to content

Commit

Permalink
Merge branch 'en/removing-untracked-fixes' into next
Browse files Browse the repository at this point in the history
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
  • Loading branch information
gitster committed Oct 6, 2021
2 parents 1950944 + 0e29222 commit fc4e387
Show file tree
Hide file tree
Showing 23 changed files with 366 additions and 74 deletions.
5 changes: 3 additions & 2 deletions Documentation/git-checkout.txt
Expand Up @@ -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.
Expand Down
23 changes: 4 additions & 19 deletions Documentation/git-read-tree.txt
Expand Up @@ -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>]])


Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
3 changes: 2 additions & 1 deletion Documentation/git-reset.txt
Expand Up @@ -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
Expand Down
3 changes: 2 additions & 1 deletion builtin/am.c
Expand Up @@ -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);
Expand Down
10 changes: 4 additions & 6 deletions builtin/checkout.c
Expand Up @@ -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;
Expand Down Expand Up @@ -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);
Expand Down
1 change: 1 addition & 0 deletions builtin/clone.c
Expand Up @@ -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;
Expand Down
1 change: 1 addition & 0 deletions builtin/merge.c
Expand Up @@ -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;
Expand Down
26 changes: 11 additions & 15 deletions builtin/read-tree.c
Expand Up @@ -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
};

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

Expand Down Expand Up @@ -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
*
Expand Down Expand Up @@ -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();

Expand Down
10 changes: 8 additions & 2 deletions builtin/reset.c
Expand Up @@ -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();
Expand Down
5 changes: 4 additions & 1 deletion builtin/stash.c
Expand Up @@ -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))
Expand Down Expand Up @@ -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)) {
Expand Down
4 changes: 4 additions & 0 deletions builtin/submodule--helper.c
Expand Up @@ -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) {
Expand Down
2 changes: 1 addition & 1 deletion contrib/rerere-train.sh
Expand Up @@ -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"
Expand Down
8 changes: 1 addition & 7 deletions merge-ort.c
Expand Up @@ -4062,20 +4062,14 @@ 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);
init_tree_desc(&trees[1], next->buffer, next->size);

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

Expand Down
5 changes: 4 additions & 1 deletion merge-recursive.c
Expand Up @@ -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;
Expand Down
8 changes: 1 addition & 7 deletions merge.c
Expand Up @@ -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);
Expand All @@ -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;
Expand All @@ -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))
Expand Down
3 changes: 2 additions & 1 deletion reset.c
Expand Up @@ -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"));
Expand Down
1 change: 1 addition & 0 deletions sequencer.c
Expand Up @@ -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)) {
Expand Down
1 change: 1 addition & 0 deletions submodule.c
Expand Up @@ -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());
Expand Down
1 change: 0 additions & 1 deletion t/t1013-read-tree-submodule.sh
Expand Up @@ -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"

Expand Down

0 comments on commit fc4e387

Please sign in to comment.