From: Phillip Wood <phillip.w...@dunelm.org.uk>

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.w...@dunelm.org.uk>
---
 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

Reply via email to