config: add setting to ignore sparsity patterns in some cmds

When sparse checkout is enabled, some users expect the output of certain
commands (such as grep, diff, and log) to be also restricted within the
sparsity patterns. This would allow them to effectively work only on the
subset of files in which they are interested; and allow some commands to
possibly perform better, by not considering uninteresting paths. For
this reason, we taught grep to honor the sparsity patterns, in the
previous patch. But, on the other hand, allowing grep and the other
commands mentioned to optionally ignore the patterns also make for some
interesting use cases. E.g. using grep to search for a function
documentation that resides outside the sparse checkout.

In any case, there is no current way for users to configure the behavior
they want for these commands. Aiming to provide this flexibility, let's
introduce the sparse.restrictCmds setting (and the analogous
--[no]-restrict-to-sparse-paths global option). The default value is
true. For now, grep is the only one affected by this setting, but the
goal is to have support for more commands, in the future.

Helped-by: Elijah Newren <newren@gmail.com>
Signed-off-by: Matheus Tavares <matheus.bernardino@usp.br>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
This commit is contained in:
Matheus Tavares
2020-06-12 12:45:04 -03:00
committed by Junio C Hamano
parent e26e8bfcdb
commit 7506484113
12 changed files with 214 additions and 6 deletions

View File

@ -436,6 +436,8 @@ include::config/sequencer.txt[]
include::config/showbranch.txt[] include::config/showbranch.txt[]
include::config/sparse.txt[]
include::config/splitindex.txt[] include::config/splitindex.txt[]
include::config/ssh.txt[] include::config/ssh.txt[]

View File

@ -28,3 +28,11 @@ grep.fullName::
grep.fallbackToNoIndex:: grep.fallbackToNoIndex::
If set to true, fall back to git grep --no-index if git grep If set to true, fall back to git grep --no-index if git grep
is executed outside of a git repository. Defaults to false. is executed outside of a git repository. Defaults to false.
ifdef::git-grep[]
sparse.restrictCmds::
See base definition in linkgit:git-config[1]. grep honors
sparse.restrictCmds by limiting searches to the sparsity paths in three
cases: when searching the working tree, when searching the index with
--cached, and when searching a specified commit.
endif::git-grep[]

View File

@ -0,0 +1,20 @@
sparse.restrictCmds::
Only meaningful in conjunction with core.sparseCheckout. This option
extends sparse checkouts (which limit which paths are written to the
working tree), so that output and operations are also limited to the
sparsity paths where possible and implemented. The purpose of this
option is to (1) focus output for the user on the portion of the
repository that is of interest to them, and (2) enable potentially
dramatic performance improvements, especially in conjunction with
partial clones.
+
When this option is true (default), some git commands may limit their behavior
to the paths specified by the sparsity patterns, or to the intersection of
those paths and any (like `*.c`) that the user might also specify on the
command line. When false, the affected commands will work on full trees,
ignoring the sparsity patterns. For now, only git-grep honors this setting.
+
Note: commands which export, integrity check, or create history will always
operate on full trees (e.g. fast-export, format-patch, fsck, commit, etc.),
unaffected by any sparsity patterns. Also, writing commands such as
sparse-checkout and read-tree will not be affected by this configuration.

View File

@ -180,6 +180,10 @@ If you just want to run git as if it was started in `<path>` then use
Do not perform optional operations that require locks. This is Do not perform optional operations that require locks. This is
equivalent to setting the `GIT_OPTIONAL_LOCKS` to `0`. equivalent to setting the `GIT_OPTIONAL_LOCKS` to `0`.
--[no-]restrict-to-sparse-paths::
Overrides the sparse.restrictCmds configuration (see
linkgit:git-config[1]) for this execution.
--list-cmds=group[,group...]:: --list-cmds=group[,group...]::
List commands by group. This is an internal/experimental List commands by group. This is an internal/experimental
option and may change or be removed in the future. Supported option and may change or be removed in the future. Supported

View File

@ -986,6 +986,7 @@ LIB_OBJS += sha1-name.o
LIB_OBJS += shallow.o LIB_OBJS += shallow.o
LIB_OBJS += sideband.o LIB_OBJS += sideband.o
LIB_OBJS += sigchain.o LIB_OBJS += sigchain.o
LIB_OBJS += sparse-checkout.o
LIB_OBJS += split-index.o LIB_OBJS += split-index.o
LIB_OBJS += stable-qsort.o LIB_OBJS += stable-qsort.o
LIB_OBJS += strbuf.o LIB_OBJS += strbuf.o

View File

@ -25,6 +25,7 @@
#include "submodule-config.h" #include "submodule-config.h"
#include "object-store.h" #include "object-store.h"
#include "packfile.h" #include "packfile.h"
#include "sparse-checkout.h"
static char const * const grep_usage[] = { static char const * const grep_usage[] = {
N_("git grep [<options>] [-e] <pattern> [<rev>...] [[--] <path>...]"), N_("git grep [<options>] [-e] <pattern> [<rev>...] [[--] <path>...]"),
@ -498,6 +499,7 @@ static int grep_cache(struct grep_opt *opt,
int nr; int nr;
struct strbuf name = STRBUF_INIT; struct strbuf name = STRBUF_INIT;
int name_base_len = 0; int name_base_len = 0;
int sparse_paths_only = restrict_to_sparse_paths(repo);
if (repo->submodule_prefix) { if (repo->submodule_prefix) {
name_base_len = strlen(repo->submodule_prefix); name_base_len = strlen(repo->submodule_prefix);
strbuf_addstr(&name, repo->submodule_prefix); strbuf_addstr(&name, repo->submodule_prefix);
@ -509,7 +511,7 @@ static int grep_cache(struct grep_opt *opt,
for (nr = 0; nr < repo->index->cache_nr; nr++) { for (nr = 0; nr < repo->index->cache_nr; nr++) {
const struct cache_entry *ce = repo->index->cache[nr]; const struct cache_entry *ce = repo->index->cache[nr];
if (ce_skip_worktree(ce)) if (sparse_paths_only && ce_skip_worktree(ce))
continue; continue;
strbuf_setlen(&name, name_base_len); strbuf_setlen(&name, name_base_len);
@ -715,9 +717,10 @@ static int grep_tree(struct grep_opt *opt, const struct pathspec *pathspec,
int is_root_tree) int is_root_tree)
{ {
struct pattern_list *patterns = NULL; struct pattern_list *patterns = NULL;
int sparse_paths_only = restrict_to_sparse_paths(opt->repo);
int ret; int ret;
if (is_root_tree) if (is_root_tree && sparse_paths_only)
patterns = get_sparsity_patterns(opt->repo); patterns = get_sparsity_patterns(opt->repo);
ret = do_grep_tree(opt, pathspec, tree, base, tn_len, is_root_tree, ret = do_grep_tree(opt, pathspec, tree, base, tn_len, is_root_tree,
@ -1257,6 +1260,12 @@ int cmd_grep(int argc, const char **argv, const char *prefix)
if (!use_index || untracked) { if (!use_index || untracked) {
int use_exclude = (opt_exclude < 0) ? use_index : !!opt_exclude; int use_exclude = (opt_exclude < 0) ? use_index : !!opt_exclude;
if (opt_restrict_to_sparse_paths >= 0) {
die(_("--[no-]restrict-to-sparse-paths is incompatible"
" with --no-index and --untracked"));
}
hit = grep_directory(&opt, &pathspec, use_exclude, use_index); hit = grep_directory(&opt, &pathspec, use_exclude, use_index);
} else if (0 <= opt_exclude) { } else if (0 <= opt_exclude) {
die(_("--[no-]exclude-standard cannot be used for tracked contents")); die(_("--[no-]exclude-standard cannot be used for tracked contents"));

View File

@ -3207,6 +3207,8 @@ __git_main ()
--namespace= --namespace=
--no-replace-objects --no-replace-objects
--help --help
--restrict-to-sparse-paths
--no-restrict-to-sparse-paths
" "
;; ;;
*) *)

5
git.c
View File

@ -4,6 +4,7 @@
#include "help.h" #include "help.h"
#include "run-command.h" #include "run-command.h"
#include "alias.h" #include "alias.h"
#include "sparse-checkout.h"
#define RUN_SETUP (1<<0) #define RUN_SETUP (1<<0)
#define RUN_SETUP_GENTLY (1<<1) #define RUN_SETUP_GENTLY (1<<1)
@ -310,6 +311,10 @@ static int handle_options(const char ***argv, int *argc, int *envchanged)
} else { } else {
exit(list_cmds(cmd)); exit(list_cmds(cmd));
} }
} else if (!strcmp(cmd, "--restrict-to-sparse-paths")) {
opt_restrict_to_sparse_paths = 1;
} else if (!strcmp(cmd, "--no-restrict-to-sparse-paths")) {
opt_restrict_to_sparse_paths = 0;
} else { } else {
fprintf(stderr, _("unknown option: %s\n"), cmd); fprintf(stderr, _("unknown option: %s\n"), cmd);
usage(git_usage_string); usage(git_usage_string);

18
sparse-checkout.c Normal file
View File

@ -0,0 +1,18 @@
#include "cache.h"
#include "config.h"
#include "sparse-checkout.h"
int opt_restrict_to_sparse_paths = -1;
int restrict_to_sparse_paths(struct repository *repo)
{
int ret;
if (opt_restrict_to_sparse_paths >= 0)
return opt_restrict_to_sparse_paths;
if (repo_config_get_bool(repo, "sparse.restrictcmds", &ret))
ret = 1;
return ret;
}

11
sparse-checkout.h Normal file
View File

@ -0,0 +1,11 @@
#ifndef SPARSE_CHECKOUT_H
#define SPARSE_CHECKOUT_H
struct repository;
extern int opt_restrict_to_sparse_paths;
/* Whether or not cmds should restrict behavior on sparse paths, in this repo */
int restrict_to_sparse_paths(struct repository *repo);
#endif /* SPARSE_CHECKOUT_H */

View File

@ -80,10 +80,10 @@ test_expect_success 'setup' '
test_path_is_file sub2/a test_path_is_file sub2/a
' '
# The test below checks a special case: the sparsity patterns exclude '/b' # The two tests below check a special case: the sparsity patterns exclude '/b'
# and sparse checkout is enabled, but the path exists in the working tree (e.g. # and sparse checkout is enabled, but the path exists in the working tree (e.g.
# manually created after `git sparse-checkout init`). In this case, grep should # manually created after `git sparse-checkout init`). In this case, grep should
# skip it. # skip the file by default, but not with --no-restrict-to-sparse-paths.
test_expect_success 'grep in working tree should honor sparse checkout' ' test_expect_success 'grep in working tree should honor sparse checkout' '
cat >expect <<-EOF && cat >expect <<-EOF &&
a:text a:text
@ -93,6 +93,16 @@ test_expect_success 'grep in working tree should honor sparse checkout' '
git grep "text" >actual && git grep "text" >actual &&
test_cmp expect actual test_cmp expect actual
' '
test_expect_success 'grep w/ --no-restrict-to-sparse-paths for sparsely excluded but present paths' '
cat >expect <<-EOF &&
a:text
b:new-text
EOF
echo "new-text" >b &&
test_when_finished "rm b" &&
git --no-restrict-to-sparse-paths grep "text" >actual &&
test_cmp expect actual
'
test_expect_success 'grep unmerged file despite not matching sparsity patterns' ' test_expect_success 'grep unmerged file despite not matching sparsity patterns' '
cat >expect <<-EOF && cat >expect <<-EOF &&
@ -157,7 +167,7 @@ test_expect_success 'grep <tree-ish> should ignore sparsity patterns' '
' '
# Note that sub2/ is present in the worktree but it is excluded by the sparsity # Note that sub2/ is present in the worktree but it is excluded by the sparsity
# patterns, so grep should not recurse into it. # patterns, so grep should only recurse into it with --no-restrict-to-sparse-paths.
test_expect_success 'grep --recurse-submodules should honor sparse checkout in submodule' ' test_expect_success 'grep --recurse-submodules should honor sparse checkout in submodule' '
cat >expect <<-EOF && cat >expect <<-EOF &&
a:text a:text
@ -166,6 +176,15 @@ test_expect_success 'grep --recurse-submodules should honor sparse checkout in s
git grep --recurse-submodules "text" >actual && git grep --recurse-submodules "text" >actual &&
test_cmp expect actual test_cmp expect actual
' '
test_expect_success 'grep --recurse-submodules should search in excluded submodules w/ --no-restrict-to-sparse-paths' '
cat >expect <<-EOF &&
a:text
sub/B/b:text
sub2/a:text
EOF
git --no-restrict-to-sparse-paths grep --recurse-submodules "text" >actual &&
test_cmp expect actual
'
test_expect_success 'grep --recurse-submodules --cached should honor sparse checkout in submodule' ' test_expect_success 'grep --recurse-submodules --cached should honor sparse checkout in submodule' '
cat >expect <<-EOF && cat >expect <<-EOF &&
@ -192,4 +211,111 @@ test_expect_success 'grep --recurse-submodules <commit-ish> should honor sparse
test_cmp expect_tag-to-commit actual_tag-to-commit test_cmp expect_tag-to-commit actual_tag-to-commit
' '
for cmd in 'git --no-restrict-to-sparse-paths grep' \
'git -c sparse.restrictCmds=false grep' \
'git -c sparse.restrictCmds=true --no-restrict-to-sparse-paths grep'
do
test_expect_success "$cmd --cached should ignore sparsity patterns" '
cat >expect <<-EOF &&
a:text
b:text
dir/c:text
EOF
$cmd --cached "text" >actual &&
test_cmp expect actual
'
test_expect_success "$cmd <commit-ish> should ignore sparsity patterns" '
commit=$(git rev-parse HEAD) &&
cat >expect_commit <<-EOF &&
$commit:a:text
$commit:b:text
$commit:dir/c:text
EOF
cat >expect_tag-to-commit <<-EOF &&
tag-to-commit:a:text
tag-to-commit:b:text
tag-to-commit:dir/c:text
EOF
$cmd "text" $commit >actual_commit &&
test_cmp expect_commit actual_commit &&
$cmd "text" tag-to-commit >actual_tag-to-commit &&
test_cmp expect_tag-to-commit actual_tag-to-commit
'
done
test_expect_success 'grep --recurse-submodules --cached w/ --no-restrict-to-sparse-paths' '
cat >expect <<-EOF &&
a:text
b:text
dir/c:text
sub/A/a:text
sub/B/b:text
sub2/a:text
EOF
git --no-restrict-to-sparse-paths grep --recurse-submodules --cached \
"text" >actual &&
test_cmp expect actual
'
test_expect_success 'grep --recurse-submodules <commit-ish> w/ --no-restrict-to-sparse-paths' '
commit=$(git rev-parse HEAD) &&
cat >expect_commit <<-EOF &&
$commit:a:text
$commit:b:text
$commit:dir/c:text
$commit:sub/A/a:text
$commit:sub/B/b:text
$commit:sub2/a:text
EOF
cat >expect_tag-to-commit <<-EOF &&
tag-to-commit:a:text
tag-to-commit:b:text
tag-to-commit:dir/c:text
tag-to-commit:sub/A/a:text
tag-to-commit:sub/B/b:text
tag-to-commit:sub2/a:text
EOF
git --no-restrict-to-sparse-paths grep --recurse-submodules "text" \
$commit >actual_commit &&
test_cmp expect_commit actual_commit &&
git --no-restrict-to-sparse-paths grep --recurse-submodules "text" \
tag-to-commit >actual_tag-to-commit &&
test_cmp expect_tag-to-commit actual_tag-to-commit
'
test_expect_success 'should respect the sparse.restrictCmds values from submodules' '
cat >expect <<-EOF &&
a:text
sub/A/a:text
sub/B/b:text
EOF
test_config -C sub sparse.restrictCmds false &&
git grep --cached --recurse-submodules "text" >actual &&
test_cmp expect actual
'
test_expect_success 'should propagate --[no]-restrict-to-sparse-paths to submodules' '
cat >expect <<-EOF &&
a:text
b:text
dir/c:text
sub/A/a:text
sub/B/b:text
sub2/a:text
EOF
test_config -C sub sparse.restrictCmds true &&
git --no-restrict-to-sparse-paths grep --cached --recurse-submodules "text" >actual &&
test_cmp expect actual
'
for opt in '--untracked' '--no-index'
do
test_expect_success "--[no]-restrict-to-sparse-paths and $opt are incompatible" "
test_must_fail git --restrict-to-sparse-paths grep $opt . 2>actual &&
test_i18ngrep 'restrict-to-sparse-paths is incompatible with' actual
"
done
test_done test_done

View File

@ -1473,6 +1473,8 @@ test_expect_success 'double dash "git" itself' '
--namespace= --namespace=
--no-replace-objects Z --no-replace-objects Z
--help Z --help Z
--restrict-to-sparse-paths Z
--no-restrict-to-sparse-paths Z
EOF EOF
' '
@ -1515,7 +1517,7 @@ test_expect_success 'general options' '
test_completion "git --nam" "--namespace=" && test_completion "git --nam" "--namespace=" &&
test_completion "git --bar" "--bare " && test_completion "git --bar" "--bare " &&
test_completion "git --inf" "--info-path " && test_completion "git --inf" "--info-path " &&
test_completion "git --no-r" "--no-replace-objects " test_completion "git --no-rep" "--no-replace-objects "
' '
test_expect_success 'general options plus command' ' test_expect_success 'general options plus command' '