From: Phillip Wood <phillip.wood@xxxxxxxxxxxxx> Currently there is no way to get read-tree to respect .git/info/exclude or core.excludesFile so scripts using `read-tree -u` have subtly different behavior to porcelain commands like checkout even when they use --exclude-per-directory. This new option is copied from ls-tree's --exclude-standard option to setup the standard excludes. The new option is also used to fix a known submodule test failure. Note that KNOWN_FAILURE_SUBMODULE_OVERWRITE_IGNORED_UNTRACKED is still used by t7112-reset-submodule.sh as it is not removed (apparently reset does not call setup_standard_excludes()). Signed-off-by: Phillip Wood <phillip.wood@xxxxxxxxxxxxx> --- Documentation/git-read-tree.txt | 9 +++++- builtin/read-tree.c | 55 ++++++++++++++++++++++++++++++--- t/t1005-read-tree-reset.sh | 36 ++++++++++++++++++--- t/t1013-read-tree-submodule.sh | 3 +- 4 files changed, 90 insertions(+), 13 deletions(-) diff --git a/Documentation/git-read-tree.txt b/Documentation/git-read-tree.txt index 67864c6bbc..a2b8b73a99 100644 --- a/Documentation/git-read-tree.txt +++ b/Documentation/git-read-tree.txt @@ -107,7 +107,14 @@ OPTIONS 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. + but explicitly ignored file to be overwritten. Incompatible + with `--exclude-standard`. + +--exclude-standard:: + When updating the worktree use the standard Git exclusions: + .git/info/exclude, .gitignore in each directory, and the user's global + exclusion file when deciding if it is safe to overwrite a file. + Incompatible with `--exclude-per-directory`. --index-output=<file>:: Instead of writing the results out to `$GIT_INDEX_FILE`, diff --git a/builtin/read-tree.c b/builtin/read-tree.c index 23735adde9..5df493c4a7 100644 --- a/builtin/read-tree.c +++ b/builtin/read-tree.c @@ -50,6 +50,40 @@ static int index_output_cb(const struct option *opt, const char *arg, return 0; } +enum exclude_type { + EXCLUDE_NONE, + EXCLUDE_PER_DIRECTORY, + EXCLUDE_STANDARD +} exclude_opt = EXCLUDE_NONE; + +static int exclude_error(enum exclude_type exclude) +{ + if (exclude == exclude_opt) + return error("more than one --exclude-per-directory given"); + else + return error("cannot combine --exclude-per-directory and " + "--exclude-standard"); +} + +static int option_parse_exclude_standard(const struct option *opt, + const char *arg, int unset) +{ + struct unpack_trees_options *opts; + + BUG_ON_OPT_NEG(unset); + BUG_ON_OPT_ARG(arg); + + if (exclude_opt == EXCLUDE_PER_DIRECTORY) + return exclude_error(EXCLUDE_STANDARD); + + opts = (struct unpack_trees_options *)opt->value; + opts->dir = xcalloc(1, sizeof(*opts->dir)); + setup_standard_excludes(opts->dir); + exclude_opt = EXCLUDE_STANDARD; + + return 0; +} + static int exclude_per_directory_cb(const struct option *opt, const char *arg, int unset) { @@ -61,12 +95,13 @@ static int exclude_per_directory_cb(const struct option *opt, const char *arg, opts = (struct unpack_trees_options *)opt->value; if (opts->dir) - die("more than one --exclude-per-directory given."); + return exclude_error(EXCLUDE_PER_DIRECTORY); dir = xcalloc(1, sizeof(*opts->dir)); dir->flags |= DIR_SHOW_IGNORED; dir->exclude_per_dir = arg; opts->dir = dir; + exclude_opt = EXCLUDE_PER_DIRECTORY; /* We do not need to nor want to do read-directory * here; we are merely interested in reusing the * per directory ignore stack mechanism. @@ -147,6 +182,10 @@ int cmd_read_tree(int argc, const char **argv, const char *unused_prefix) N_("gitignore"), N_("allow explicitly ignored files to be overwritten"), PARSE_OPT_NONEG, exclude_per_directory_cb }, + { OPTION_CALLBACK, 0, "exclude-standard", &opts, NULL, + N_("add the standard git exclusions"), + PARSE_OPT_NOARG | PARSE_OPT_NONEG, + option_parse_exclude_standard }, OPT_BOOL('i', NULL, &opts.index_only, N_("don't check the working tree after merging")), OPT__DRY_RUN(&opts.dry_run, N_("don't update the index or the work tree")), @@ -219,10 +258,16 @@ int cmd_read_tree(int argc, const char **argv, const char *unused_prefix) opts.reset = UNPACK_RESET_PROTECT_UNTRACKED; } if ((opts.dir && !opts.update)) - die("--exclude-per-directory is meaningless unless -u"); - if (opts.dir && opts.reset == UNPACK_RESET_OVERWRITE_UNTRACKED) - warning("--exclude-per-directory without --preserve-untracked " - "has no effect"); + die("%s requires -u", exclude_opt == EXCLUDE_STANDARD ? + "--exclude-standard" :" --exclude-per-directory"); + if (opts.dir && opts.reset == UNPACK_RESET_OVERWRITE_UNTRACKED) { + if (exclude_opt == EXCLUDE_STANDARD) + die("--reset with --exclude-standard requires " + "--protect-untracked"); + else + warning("--exclude-per-directory without " + "--preserve-untracked has no effect"); + } if (opts.merge && !opts.index_only) setup_work_tree(); diff --git a/t/t1005-read-tree-reset.sh b/t/t1005-read-tree-reset.sh index 6c9dd6805b..2e2a6a0c69 100755 --- a/t/t1005-read-tree-reset.sh +++ b/t/t1005-read-tree-reset.sh @@ -30,6 +30,20 @@ test_expect_success '--protect-untracked option sanity checks' ' read_tree_u_must_fail -m -u --no-protect-untracked ' +test_expect_success 'exclude option sanity checks' ' + read_tree_u_must_fail --reset -u --exclude-standard HEAD && + read_tree_u_must_fail --reset --protect-untracked --exclude-standard && + read_tree_u_must_fail --reset -u --protect-untracked \ + --exclude-standard \ + --exclude-per-directory=.gitignore HEAD && + read_tree_u_must_fail --reset -u --protect-untracked \ + --exclude-per-directory=gitignore \ + --exclude-per-directory=.gitignore HEAD && + read_tree_u_must_fail --reset --exclude-per-directory=.gitignore HEAD && + read_tree_u_must_succeed --reset -u --exclude-per-directory=.gitignore \ + HEAD +' + test_expect_success 'reset should reset worktree' ' echo changed >df && read_tree_u_must_succeed -u --reset HEAD^ && @@ -53,12 +67,24 @@ test_expect_success 'reset --protect-untracked protects untracked directory' ' test_cmp expected-err actual-err ' -test_expect_success 'reset --protect-untracked resets' ' - rm -rf new && +test_expect_success 'reset --protect-untracked --exclude-standard overwrites ignored path' ' + test_when_finished "rm .git/info/exclude" && + echo missing >.git/info/exclude && + read_tree_u_must_fail -u --reset --protect-untracked \ + --exclude-standard HEAD && + echo new >.git/info/exclude && echo changed >df/file && - read_tree_u_must_succeed -u --reset --protect-untracked HEAD && - git ls-files >actual-two && - test_cmp expect-two actual-two + read_tree_u_must_succeed -u --reset --protect-untracked \ + --exclude-standard HEAD && + git ls-files >actual && + test_cmp expect-two actual +' + +test_expect_success 'reset --protect-untracked resets' ' + echo changed >df && + read_tree_u_must_succeed -u --reset --protect-untracked HEAD^ && + git ls-files >actual && + test_cmp expect actual ' test_expect_success 'reset should remove remnants from a failed merge' ' diff --git a/t/t1013-read-tree-submodule.sh b/t/t1013-read-tree-submodule.sh index 91a6fafcb4..728280d40d 100755 --- a/t/t1013-read-tree-submodule.sh +++ b/t/t1013-read-tree-submodule.sh @@ -6,9 +6,8 @@ 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" +test_submodule_switch_recursing_with_args "read-tree -u -m --exclude-standard" test_submodule_forced_switch_recursing_with_args "read-tree -u --reset" -- 2.21.0