Web lists-archives.com

[PATCH 2/2] read-tree: add --exclude-standard




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