All of lore.kernel.org
 help / color / mirror / Atom feed
From: "ZheNing Hu via GitGitGadget" <gitgitgadget@gmail.com>
To: git@vger.kernel.org
Cc: "Christian Couder" <christian.couder@gmail.com>,
	"Ævar Arnfjörð Bjarmason" <avarab@gmail.com>,
	"Jeff King" <peff@peff.net>, "Junio C Hamano" <gitster@pobox.com>,
	"Derrick Stolee" <derrickstolee@github.com>,
	"Johannes Schindelin" <johannes.schindelin@gmx.de>,
	"Victoria Dye" <vdye@github.com>,
	"Elijah Newren" <newren@gmail.com>,
	"Emily Shaffer" <emilyshaffer@google.com>,
	"Matheus Tavares Bernardino" <matheus.bernardino@usp.br>,
	"Shaoxuan Yuan" <shaoxuan.yuan02@gmail.com>,
	"ZheNing Hu" <adlternative@gmail.com>,
	"ZheNing Hu" <adlternative@gmail.com>
Subject: [PATCH] [RFC] diff: introduce scope option
Date: Mon, 31 Oct 2022 04:11:52 +0000	[thread overview]
Message-ID: <pull.1398.git.1667189512579.gitgitgadget@gmail.com> (raw)

From: ZheNing Hu <adlternative@gmail.com>

When we use sparse-checkout, we often want the set of files
that some commands operate on to be restricted to the
sparse-checkout specification.

So introduce the `--scope` option to git diff, which have two
value: "sparse" and "all". "sparse" mean that diff is performed
restrict to paths which matching sparse-checkout specification,
"all" mean that diff is performed regardless of whether the path
meets the sparse-checkout specification. `--no-scope` is the default
option for now.

Add `diff.scope={sparse, all}` config, which can also have the same
capabilities as `--scope`, and it will be covered by `--scope` option.

Signed-off-by: ZheNing Hu <adlternative@gmail.com>
---
    [RFC] diff: introduce scope option
    
    In [1], we discovered that users working on different sparse-checkout
    specification may download unnecessary blobs from each other's
    specification in collaboration. In [2] Junio suggested that maybe we can
    restrict some git command's filespec in sparse-checkout specification to
    elegantly solve this problem above. In [3]: Newren and Derrick Stolee
    prefer to name the option --scope={sparse, all}.
    
    So this patch is attempt to do this thing on git diff:
    
    v1:
    
     1. add --restrict option to git diff, which restrict diff filespec in
        sparse-checkout specification. [4] v2.
     2. rename --restrict to --scope={sparse, all}, support --no-scope.
     3. add config: diff.scope={sparse,all}.
    
    Unresolved work:
    
     1. how to properly pass this --scope={sparse, all} to other commands
        like git log, git format-patch, etc.
     2. how to set the default value of scope for different diff commands.
    
    [1]:
    https://lore.kernel.org/git/CAOLTT8SHo66kGbvWr=+LQ9UVd1NHgqGGEYK2qq6==QgRCgLZqQ@mail.gmail.com/
    [2]: https://lore.kernel.org/git/xmqqzgeqw0sy.fsf@gitster.g/ [3]:
    https://lore.kernel.org/git/07a25d48-e364-0d9b-6ffa-41a5984eb5db@github.com/
    [4]:
    https://lore.kernel.org/git/pull.1368.git.1664036052741.gitgitgadget@gmail.com/

Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-1398%2Fadlternative%2Fzh%2Fdiff-scope-v1
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-1398/adlternative/zh/diff-scope-v1
Pull-Request: https://github.com/gitgitgadget/git/pull/1398

 Documentation/config/diff.txt         |  12 +
 Documentation/diff-options.txt        |  18 +
 builtin/diff.c                        |   4 +
 diff-lib.c                            |  36 +-
 diff-no-index.c                       |   4 +
 diff.c                                |  39 +++
 diff.h                                |  11 +
 t/t4070-diff-sparse-checkout-scope.sh | 469 ++++++++++++++++++++++++++
 tree-diff.c                           |   5 +
 9 files changed, 597 insertions(+), 1 deletion(-)
 create mode 100644 t/t4070-diff-sparse-checkout-scope.sh

diff --git a/Documentation/config/diff.txt b/Documentation/config/diff.txt
index 35a7bf86d77..52707e1b2d6 100644
--- a/Documentation/config/diff.txt
+++ b/Documentation/config/diff.txt
@@ -201,6 +201,18 @@ diff.algorithm::
 --
 +
 
+diff.scope::
+	Choose diff scope. The variants are as follows:
++
+--
+`sparse`;;
+	Restrict diff paths to those matching sparse-checkout specification.
+`all`;;
+	Without restriction, diff is performed regardless of whether the path
+	meets the sparse-checkout specification.
+--
++
+
 diff.wsErrorHighlight::
 	Highlight whitespace errors in the `context`, `old` or `new`
 	lines of the diff.  Multiple values are separated by comma,
diff --git a/Documentation/diff-options.txt b/Documentation/diff-options.txt
index 3674ac48e92..04bf83e8be1 100644
--- a/Documentation/diff-options.txt
+++ b/Documentation/diff-options.txt
@@ -195,6 +195,24 @@ For instance, if you configured the `diff.algorithm` variable to a
 non-default value and want to use the default one, then you
 have to use `--diff-algorithm=default` option.
 
+ifndef::git-format-patch[]
+ifndef::git-log[]
+
+--scope={sparse|all}::
+	Choose diff scope. The variants are as follows:
++
+--
+`--sparse`;;
+	Restrict diff paths to those matching sparse-checkout specification.
+`--all`;;
+	Without restriction, diff is performed regardless of whether the path
+	meets the sparse-checkout specification.
+--
++
+
+endif::git-log[]
+endif::git-format-patch[]
+
 --stat[=<width>[,<name-width>[,<count>]]]::
 	Generate a diffstat. By default, as much space as necessary
 	will be used for the filename part, and the rest for the graph
diff --git a/builtin/diff.c b/builtin/diff.c
index 854d2c5a5c4..6b450f7184c 100644
--- a/builtin/diff.c
+++ b/builtin/diff.c
@@ -54,6 +54,10 @@ static void stuff_change(struct diff_options *opt,
 	    oideq(old_oid, new_oid) && (old_mode == new_mode))
 		return;
 
+	if (opt->scope == DIFF_SCOPE_SPARSE &&
+	    !diff_paths_in_sparse_checkout(old_path, new_path))
+		return;
+
 	if (opt->flags.reverse_diff) {
 		SWAP(old_mode, new_mode);
 		SWAP(old_oid, new_oid);
diff --git a/diff-lib.c b/diff-lib.c
index 2edea41a234..a3381f2e0ff 100644
--- a/diff-lib.c
+++ b/diff-lib.c
@@ -88,6 +88,22 @@ static int match_stat_with_submodule(struct diff_options *diffopt,
 	return changed;
 }
 
+int diff_path_in_sparse_checkout(const char *path) {
+	if (core_sparse_checkout_cone)
+		return path_in_cone_mode_sparse_checkout(path, the_repository->index);
+	else
+		return path_in_sparse_checkout(path, the_repository->index);
+}
+
+int diff_paths_in_sparse_checkout(const char *one, const char*two) {
+	if (one == two || !strcmp(one, two))
+		return diff_path_in_sparse_checkout(one);
+	else
+		return diff_path_in_sparse_checkout(one) &&
+		       diff_path_in_sparse_checkout(two);
+}
+
+
 int run_diff_files(struct rev_info *revs, unsigned int option)
 {
 	int entries, i;
@@ -113,6 +129,9 @@ int run_diff_files(struct rev_info *revs, unsigned int option)
 
 		if (diff_can_quit_early(&revs->diffopt))
 			break;
+		if (revs->diffopt.scope == DIFF_SCOPE_SPARSE &&
+		    !diff_path_in_sparse_checkout(ce->name))
+			continue;
 
 		if (!ce_path_match(istate, ce, &revs->prune_data, NULL))
 			continue;
@@ -202,7 +221,8 @@ int run_diff_files(struct rev_info *revs, unsigned int option)
 				continue;
 		}
 
-		if (ce_uptodate(ce) || ce_skip_worktree(ce))
+		if (ce_uptodate(ce) ||
+		    (revs->diffopt.scope != DIFF_SCOPE_ALL && ce_skip_worktree(ce)))
 			continue;
 
 		/*
@@ -439,6 +459,20 @@ static void do_oneway_diff(struct unpack_trees_options *o,
 			return;	/* nothing to diff.. */
 	}
 
+	if (revs->diffopt.scope == DIFF_SCOPE_SPARSE) {
+		if (idx && tree) {
+			if (!diff_paths_in_sparse_checkout(idx->name, tree->name))
+				return;
+		} else if (idx) {
+			if (!diff_path_in_sparse_checkout(idx->name))
+				return;
+		} else if (tree) {
+			if (!diff_path_in_sparse_checkout(tree->name))
+				return;
+		} else
+			return;
+	}
+
 	/* if the entry is not checked out, don't examine work tree */
 	cached = o->index_only ||
 		(idx && ((idx->ce_flags & CE_VALID) || ce_skip_worktree(idx)));
diff --git a/diff-no-index.c b/diff-no-index.c
index 18edbdf4b59..ea94a104ea4 100644
--- a/diff-no-index.c
+++ b/diff-no-index.c
@@ -281,6 +281,10 @@ int diff_no_index(struct rev_info *revs,
 
 	fixup_paths(paths, &replacement);
 
+	if (revs->diffopt.scope == DIFF_SCOPE_SPARSE &&
+	    !diff_paths_in_sparse_checkout(paths[0], paths[1]))
+		goto out;
+
 	revs->diffopt.skip_stat_unmatch = 1;
 	if (!revs->diffopt.output_format)
 		revs->diffopt.output_format = DIFF_FORMAT_PATCH;
diff --git a/diff.c b/diff.c
index 285d6e2d575..9de4044ae05 100644
--- a/diff.c
+++ b/diff.c
@@ -48,6 +48,7 @@ static int diff_interhunk_context_default;
 static const char *diff_word_regex_cfg;
 static const char *external_diff_cmd_cfg;
 static const char *diff_order_file_cfg;
+static const char *external_diff_scope_cfg;
 int diff_auto_refresh_index = 1;
 static int diff_mnemonic_prefix;
 static int diff_no_prefix;
@@ -57,6 +58,7 @@ static int diff_dirstat_permille_default = 30;
 static struct diff_options default_diff_options;
 static long diff_algorithm;
 static unsigned ws_error_highlight_default = WSEH_NEW;
+static enum diff_scope external_diff_scope;
 
 static char diff_colors[][COLOR_MAXLEN] = {
 	GIT_COLOR_RESET,
@@ -423,6 +425,16 @@ int git_diff_ui_config(const char *var, const char *value, void *cb)
 		return 0;
 	}
 
+	if (!strcmp(var, "diff.scope")) {
+		git_config_string(&external_diff_scope_cfg, var, value);
+		if (!strcmp(value, "all"))
+			external_diff_scope = DIFF_SCOPE_ALL;
+		else if (!strcmp(value, "sparse"))
+			external_diff_scope = DIFF_SCOPE_SPARSE;
+		else
+			return -1;
+	}
+
 	if (git_color_config(var, value, cb) < 0)
 		return -1;
 
@@ -4663,6 +4675,7 @@ void repo_diff_setup(struct repository *r, struct diff_options *options)
 
 	options->color_moved = diff_color_moved_default;
 	options->color_moved_ws_handling = diff_color_moved_ws_default;
+	options->scope = external_diff_scope;
 
 	prep_parse_options(options);
 }
@@ -4914,6 +4927,29 @@ static int parse_dirstat_opt(struct diff_options *options, const char *params)
 	return 1;
 }
 
+static int diff_opt_diff_scope(const struct option *option,
+				const char *optarg, int unset)
+{
+	struct diff_options *opt = option->value;
+
+	if (unset) {
+		opt->scope = DIFF_SCOPE_NONE;
+	} else if (optarg) {
+		if (!strcmp(optarg, "all")) {
+			if (core_apply_sparse_checkout) {
+				opt->scope = DIFF_SCOPE_ALL;
+			}
+		} else if (!strcmp(optarg, "sparse")) {
+			if (core_apply_sparse_checkout) {
+				opt->scope = DIFF_SCOPE_SPARSE;
+			}
+		} else
+			return error(_("invalid --scope value: %s"), optarg);
+	}
+
+	return 0;
+}
+
 static int diff_opt_diff_filter(const struct option *option,
 				const char *optarg, int unset)
 {
@@ -5683,6 +5719,9 @@ static void prep_parse_options(struct diff_options *options)
 		OPT_CALLBACK_F(0, "diff-filter", options, N_("[(A|C|D|M|R|T|U|X|B)...[*]]"),
 			       N_("select files by diff type"),
 			       PARSE_OPT_NONEG, diff_opt_diff_filter),
+		OPT_CALLBACK_F(0, "scope", options, N_("[sparse|all]"),
+			       N_("choose diff scope"),
+			       PARSE_OPT_OPTARG, diff_opt_diff_scope),
 		{ OPTION_CALLBACK, 0, "output", options, N_("<file>"),
 		  N_("output to a specific file"),
 		  PARSE_OPT_NONEG, NULL, 0, diff_opt_output },
diff --git a/diff.h b/diff.h
index 8ae18e5ab1e..90f7512034c 100644
--- a/diff.h
+++ b/diff.h
@@ -230,6 +230,12 @@ enum diff_submodule_format {
 	DIFF_SUBMODULE_INLINE_DIFF
 };
 
+enum diff_scope {
+	DIFF_SCOPE_NONE = 0,
+	DIFF_SCOPE_ALL,
+	DIFF_SCOPE_SPARSE,
+};
+
 /**
  * the set of options the calling program wants to affect the operation of
  * diffcore library with.
@@ -285,6 +291,9 @@ struct diff_options {
 	/* diff-filter bits */
 	unsigned int filter, filter_not;
 
+	/* diff sparse-checkout scope */
+	enum diff_scope scope;
+
 	int use_color;
 
 	/* Number of context lines to generate in patch output. */
@@ -696,4 +705,6 @@ void print_stat_summary(FILE *fp, int files,
 			int insertions, int deletions);
 void setup_diff_pager(struct diff_options *);
 
+int diff_path_in_sparse_checkout(const char *path);
+int diff_paths_in_sparse_checkout(const char *one, const char *two);
 #endif /* DIFF_H */
diff --git a/t/t4070-diff-sparse-checkout-scope.sh b/t/t4070-diff-sparse-checkout-scope.sh
new file mode 100644
index 00000000000..dca75a3308b
--- /dev/null
+++ b/t/t4070-diff-sparse-checkout-scope.sh
@@ -0,0 +1,469 @@
+#!/bin/sh
+
+test_description='diff sparse-checkout scope'
+
+GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main
+export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME
+
+. ./test-lib.sh
+
+
+test_expect_success 'setup' '
+	git init temp &&
+	(
+		cd temp &&
+		mkdir sub1 &&
+		mkdir sub2 &&
+		echo sub1/file1 >sub1/file1 &&
+		echo sub2/file2 >sub2/file2 &&
+		echo file1 >file1 &&
+		echo file2 >file2 &&
+		git add --all &&
+		git commit -m init &&
+		echo sub1/file1 >>sub1/file1 &&
+		echo sub1/file2 >>sub1/file2 &&
+		echo sub2/file1 >>sub2/file1 &&
+		echo sub2/file2 >>sub2/file2 &&
+		echo file1 >>file1 &&
+		echo file2 >>file2 &&
+		git add --all &&
+		git commit -m change1 &&
+		echo sub1/file1 >>sub1/file1 &&
+		echo sub1/file2 >>sub1/file2 &&
+		echo sub2/file1 >>sub2/file1 &&
+		echo sub2/file2 >>sub2/file2 &&
+		echo file1 >>file1 &&
+		echo file2 >>file2 &&
+		git add --all &&
+		git commit -m change2
+	)
+'
+
+reset_repo () {
+	rm -rf repo &&
+	git clone --no-checkout temp repo
+}
+
+reset_with_sparse_checkout() {
+	reset_repo &&
+	git -C repo sparse-checkout set $1 sub1 &&
+	git -C repo checkout
+}
+
+change_worktree_and_index() {
+	(
+		cd repo &&
+		mkdir sub2 sub3 &&
+		echo sub1/file3 >sub1/file3 &&
+		echo sub2/file3 >sub2/file3 &&
+		echo sub3/file3 >sub3/file3 &&
+		echo file3 >file3 &&
+		git add --all --sparse &&
+		echo sub1/file3 >>sub1/file3 &&
+		echo sub2/file3 >>sub2/file3 &&
+		echo sub3/file3 >>sub3/file3 &&
+		echo file3 >>file3
+	)
+}
+
+diff_scope() {
+	title=$1
+	need_change_worktree_and_index=$2
+	sparse_checkout_option=$3
+	scope_option=$4
+	expect=$5
+	shift 5
+	args=("$@")
+
+	test_expect_success "$title $sparse_checkout_option $scope_option" "
+		reset_with_sparse_checkout $sparse_checkout_option &&
+		if test \"$need_change_worktree_and_index\" = \"true\" ; then
+			change_worktree_and_index
+		fi &&
+		git -C repo diff $scope_option ${args[*]} >actual &&
+		if test -z \"$expect\" ; then
+			>expect
+		else
+			cat > expect <<-EOF
+$expect
+			EOF
+		fi &&
+		test_cmp expect actual
+	"
+}
+
+args=("--name-only" "HEAD" "HEAD~")
+diff_scope builtin_diff_tree false "--no-cone" "--scope=sparse" \
+"sub1/file1
+sub1/file2" "${args[@]}"
+
+diff_scope builtin_diff_tree false "--no-cone" "--scope=all" \
+"file1
+file2
+sub1/file1
+sub1/file2
+sub2/file1
+sub2/file2" "${args[@]}"
+
+diff_scope builtin_diff_tree false "--no-cone" "--no-scope" \
+"file1
+file2
+sub1/file1
+sub1/file2
+sub2/file1
+sub2/file2" "${args[@]}"
+
+diff_scope builtin_diff_tree false "--cone" "--scope=sparse" \
+"file1
+file2
+sub1/file1
+sub1/file2" "${args[@]}"
+
+diff_scope builtin_diff_tree false "--cone" "--scope=all" \
+"file1
+file2
+sub1/file1
+sub1/file2
+sub2/file1
+sub2/file2" "${args[@]}"
+
+diff_scope builtin_diff_tree false "--cone" "--no-scope" \
+"file1
+file2
+sub1/file1
+sub1/file2
+sub2/file1
+sub2/file2" "${args[@]}"
+
+args=("--name-only" "HEAD~")
+diff_scope builtin_diff_index true "--no-cone" "--scope=sparse" \
+"sub1/file1
+sub1/file2
+sub1/file3" "${args[@]}"
+
+diff_scope builtin_diff_index true "--no-cone" "--scope=all" \
+"file1
+file2
+file3
+sub1/file1
+sub1/file2
+sub1/file3
+sub2/file1
+sub2/file2
+sub2/file3
+sub3/file3" "${args[@]}"
+
+diff_scope builtin_diff_index true "--no-cone" "--no-scope" \
+"file1
+file2
+file3
+sub1/file1
+sub1/file2
+sub1/file3
+sub2/file1
+sub2/file2
+sub2/file3
+sub3/file3" "${args[@]}"
+
+diff_scope builtin_diff_index true "--cone" "--scope=sparse" \
+"file1
+file2
+file3
+sub1/file1
+sub1/file2
+sub1/file3" "${args[@]}"
+
+diff_scope builtin_diff_index true "--cone" "--scope=all" \
+"file1
+file2
+file3
+sub1/file1
+sub1/file2
+sub1/file3
+sub2/file1
+sub2/file2
+sub2/file3
+sub3/file3" "${args[@]}"
+
+diff_scope builtin_diff_index true "--cone" "--no-scope" \
+"file1
+file2
+file3
+sub1/file1
+sub1/file2
+sub1/file3
+sub2/file1
+sub2/file2
+sub2/file3
+sub3/file3" "${args[@]}"
+
+args=("--name-only" "file3" "sub1/" "sub2/")
+
+diff_scope builtin_diff_files true "--no-cone" "--scope=sparse" \
+"sub1/file3" "${args[@]}"
+
+diff_scope builtin_diff_files true "--no-cone" "--scope=all" \
+"file3
+sub1/file3
+sub2/file1
+sub2/file2
+sub2/file3" "${args[@]}"
+
+diff_scope builtin_diff_files true "--no-cone" "--no-scope" \
+"file3
+sub1/file3
+sub2/file3" "${args[@]}"
+
+diff_scope builtin_diff_files true "--cone" "--scope=sparse" \
+"file3
+sub1/file3" "${args[@]}"
+
+diff_scope builtin_diff_files true "--cone" "--scope=all" \
+"file3
+sub1/file3
+sub2/file1
+sub2/file2
+sub2/file3" "${args[@]}"
+
+diff_scope builtin_diff_files true "--cone" "--no-scope" \
+"file3
+sub1/file3
+sub2/file3" "${args[@]}"
+
+
+args=("--name-only" "HEAD~:sub2/file2" "sub1/file2")
+
+diff_scope builtin_diff_b_f true "--no-cone" "--scope=sparse" \
+"" "${args[@]}"
+
+diff_scope builtin_diff_b_f true "--no-cone" "--scope=all" \
+"sub1/file2" "${args[@]}"
+
+diff_scope builtin_diff_b_f true "--no-cone" "--no-scope" \
+"sub1/file2" "${args[@]}"
+
+args=("--name-only" "HEAD~:sub1/file1" "file3")
+
+diff_scope builtin_diff_b_f true "--cone" "--scope=sparse" \
+"file3" "${args[@]}"
+
+diff_scope builtin_diff_b_f true "--cone" "--scope=all" \
+"file3" "${args[@]}"
+
+diff_scope builtin_diff_b_f true "--cone" "--no-scope" \
+"file3" "${args[@]}"
+
+args=("--name-only" HEAD~:sub2/file2 HEAD:sub1/file2)
+
+diff_scope builtin_diff_blobs true "--no-cone" "--scope=sparse" \
+"" "${args[@]}"
+
+diff_scope builtin_diff_blobs true "--no-cone" "--scope=all" \
+"sub1/file2" "${args[@]}"
+
+diff_scope builtin_diff_blobs true "--no-cone" "--no-scope" \
+"sub1/file2" "${args[@]}"
+
+args=("--name-only" HEAD~:sub1/file1 HEAD:file2)
+
+diff_scope builtin_diff_blobs false "--cone" "--scope=sparse" \
+"file2" "${args[@]}"
+
+diff_scope builtin_diff_blobs false "--cone" "--scope=all" \
+"file2" "${args[@]}"
+
+diff_scope builtin_diff_blobs false "--cone" "--no-scope" \
+"file2" "${args[@]}"
+
+args=("--name-only" HEAD~2 HEAD~ HEAD)
+
+diff_scope builtin_diff_combined false "--no-cone" "--scope=sparse" \
+"sub1/file1
+sub1/file2" "${args[@]}"
+
+diff_scope builtin_diff_combined false "--no-cone" "--scope=all" \
+"file1
+file2
+sub1/file1
+sub1/file2
+sub2/file1
+sub2/file2" "${args[@]}"
+
+diff_scope builtin_diff_combined false "--no-cone" "--no-scope" \
+"file1
+file2
+sub1/file1
+sub1/file2
+sub2/file1
+sub2/file2" "${args[@]}"
+
+diff_scope builtin_diff_combined false "--cone" "--scope=sparse" \
+"file1
+file2
+sub1/file1
+sub1/file2" "${args[@]}"
+
+diff_scope builtin_diff_combined false "--cone" "--scope=all" \
+"file1
+file2
+sub1/file1
+sub1/file2
+sub2/file1
+sub2/file2" "${args[@]}"
+
+diff_scope builtin_diff_combined false "--cone" "--no-scope" \
+"file1
+file2
+sub1/file1
+sub1/file2
+sub2/file1
+sub2/file2" "${args[@]}"
+
+test_expect_success 'diff_no_index --no-cone, --scope=sparse' '
+	reset_with_sparse_checkout --no-cone &&
+	(
+		cd repo &&
+		mkdir sub3 &&
+		echo sub3/file3 >sub3/file3
+	) &&
+	test_expect_code 1 git -C repo diff --no-index --name-only --scope=sparse sub1/file1 sub1/file2 >actual &&
+	cat > expect <<-EOF &&
+sub1/file2
+	EOF
+	test_expect_code 1 git -C repo diff --no-index --name-only --scope=sparse sub1/file1 sub3/file3 >actual &&
+	>expect &&
+	test_cmp expect actual
+'
+
+test_expect_success 'diff_no_index --no-cone, --scope=all' '
+	reset_with_sparse_checkout --no-cone &&
+	(
+		cd repo &&
+		mkdir sub3 &&
+		echo sub3/file3 >sub3/file3
+	) &&
+	test_expect_code 1 git -C repo diff --no-index --name-only --scope=all sub1/file1 sub1/file2 >actual &&
+	cat > expect <<-EOF &&
+sub1/file2
+	EOF
+	test_expect_code 1 git -C repo diff --no-index --name-only --scope=all sub1/file1 sub3/file3 >actual &&
+	cat > expect <<-EOF &&
+sub3/file3
+	EOF
+	test_cmp expect actual
+'
+
+test_expect_success 'diff_no_index --no-cone, --no-scope' '
+	reset_with_sparse_checkout --no-cone &&
+	(
+		cd repo &&
+		mkdir sub3 &&
+		echo sub3/file3 >sub3/file3
+	) &&
+	test_expect_code 1 git -C repo diff --no-index --name-only --no-scope sub1/file1 sub1/file2 >actual &&
+	cat > expect <<-EOF &&
+sub1/file2
+	EOF
+	test_expect_code 1 git -C repo diff --no-index --name-only --no-scope sub1/file1 sub3/file3 >actual &&
+	cat > expect <<-EOF &&
+sub3/file3
+	EOF
+	test_cmp expect actual
+'
+
+test_expect_success 'diff_no_index --cone, --scope=sparse' '
+	reset_with_sparse_checkout --cone &&
+	(
+		cd repo &&
+		echo file3 >file3 &&
+		mkdir sub3 &&
+		echo sub3/file3 >sub3/file3
+	) &&
+	test_expect_code 1 git -C repo diff --no-index --name-only --scope=sparse sub1/file1 file3 >actual &&
+	cat > expect <<-EOF &&
+file3
+	EOF
+	test_expect_code 1 git -C repo diff --no-index --name-only --scope=sparse sub1/file1 sub3/file3 >actual &&
+	>expect &&
+	test_cmp expect actual
+'
+
+test_expect_success 'diff_no_index --cone, --scope=all' '
+	reset_with_sparse_checkout --cone &&
+	(
+		cd repo &&
+		echo file3 >file3 &&
+		mkdir sub3 &&
+		echo sub3/file3 >sub3/file3
+	) &&
+	test_expect_code 1 git -C repo diff --no-index --name-only --scope=all sub1/file1 file3 >actual &&
+	cat > expect <<-EOF &&
+file3
+	EOF
+	test_expect_code 1 git -C repo diff --no-index --name-only --scope=all sub1/file1 sub3/file3 >actual &&
+	cat > expect <<-EOF &&
+sub3/file3
+	EOF
+	test_cmp expect actual
+'
+
+test_expect_success 'diff_no_index --cone, --no-scope' '
+	reset_with_sparse_checkout --cone &&
+	(
+		cd repo &&
+		echo file3 >file3 &&
+		mkdir sub3 &&
+		echo sub3/file3 >sub3/file3
+	) &&
+	test_expect_code 1 git -C repo diff --no-index --name-only --no-scope sub1/file1 file3 >actual &&
+	cat > expect <<-EOF &&
+file3
+	EOF
+	test_expect_code 1 git -C repo diff --no-index --name-only --no-scope sub1/file1 sub3/file3 >actual &&
+	cat > expect <<-EOF &&
+sub3/file3
+	EOF
+	test_cmp expect actual
+'
+
+test_expect_success 'diff scope config sparse' '
+	reset_with_sparse_checkout --cone &&
+	git -C repo -c diff.scope=sparse diff --name-only HEAD~ >actual &&
+	cat > expect <<-EOF &&
+file1
+file2
+sub1/file1
+sub1/file2
+	EOF
+	test_cmp expect actual
+'
+
+test_expect_success 'diff scope config all' '
+	reset_with_sparse_checkout --cone &&
+	git -C repo -c diff.scope=all diff --name-only HEAD~ >actual &&
+	cat > expect <<-EOF &&
+file1
+file2
+sub1/file1
+sub1/file2
+sub2/file1
+sub2/file2
+	EOF
+	test_cmp expect actual
+'
+
+test_expect_success 'diff scope config override by option' '
+	reset_with_sparse_checkout --cone &&
+	git -C repo -c diff.scope=sparse diff --name-only --scope=all HEAD~ >actual &&
+	cat > expect <<-EOF &&
+file1
+file2
+sub1/file1
+sub1/file2
+sub2/file1
+sub2/file2
+	EOF
+	test_cmp expect actual
+'
+
+test_done
diff --git a/tree-diff.c b/tree-diff.c
index 69031d7cbae..67f99c8e4df 100644
--- a/tree-diff.c
+++ b/tree-diff.c
@@ -76,6 +76,11 @@ static int tree_entry_pathcmp(struct tree_desc *t1, struct tree_desc *t2)
 static int emit_diff_first_parent_only(struct diff_options *opt, struct combine_diff_path *p)
 {
 	struct combine_diff_parent *p0 = &p->parent[0];
+
+	if (opt->scope == DIFF_SCOPE_SPARSE &&
+	    !diff_path_in_sparse_checkout(p->path))
+		return 0;
+
 	if (p->mode && p0->mode) {
 		opt->change(opt, p0->mode, p->mode, &p0->oid, &p->oid,
 			1, 1, p->path, 0, 0);

base-commit: 63bba4fdd86d80ef061c449daa97a981a9be0792
-- 
gitgitgadget

             reply	other threads:[~2022-10-31  4:12 UTC|newest]

Thread overview: 11+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2022-10-31  4:11 ZheNing Hu via GitGitGadget [this message]
2022-11-01  1:34 ` [PATCH] [RFC] diff: introduce scope option Taylor Blau
2022-11-01  2:13   ` ZheNing Hu
2022-11-01  5:18 ` Elijah Newren
2022-11-06  2:11   ` ZheNing Hu
2022-11-06  6:58     ` Elijah Newren
2022-11-14  9:08       ` ZheNing Hu
2022-11-25  2:45 ` [PATCH v2] [RFC] diff: introduce --scope option ZheNing Hu via GitGitGadget
2022-11-29 12:00   ` [PATCH v3 0/2] [RFC] diff: introduce scope option ZheNing Hu via GitGitGadget
2022-11-29 12:00     ` [PATCH v3 1/2] [RFC] diff: introduce --scope option ZheNing Hu via GitGitGadget
2022-11-29 12:00     ` [PATCH v3 2/2] [RPC] grep: " ZheNing Hu via GitGitGadget

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=pull.1398.git.1667189512579.gitgitgadget@gmail.com \
    --to=gitgitgadget@gmail.com \
    --cc=adlternative@gmail.com \
    --cc=avarab@gmail.com \
    --cc=christian.couder@gmail.com \
    --cc=derrickstolee@github.com \
    --cc=emilyshaffer@google.com \
    --cc=git@vger.kernel.org \
    --cc=gitster@pobox.com \
    --cc=johannes.schindelin@gmx.de \
    --cc=matheus.bernardino@usp.br \
    --cc=newren@gmail.com \
    --cc=peff@peff.net \
    --cc=shaoxuan.yuan02@gmail.com \
    --cc=vdye@github.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.