Merge branch 'mt/grep-sparse-checkout' into pu

"git grep" has been tweaked to be limited to the sparse checkout
paths.

Review needed on 4/6; otherwise looking sane.
cf. <CABPp-BGdEyEeajYZj_rdxp=MyEQdszuyjVTax=hhYj3fOtRQUQ@mail.gmail.com>

* mt/grep-sparse-checkout:
  config: add setting to ignore sparsity patterns in some cmds
  grep: honor sparse checkout patterns
  config: correctly read worktree configs in submodules
  t/helper/test-config: facilitate addition of new cli options
  t/helper/test-config: return exit codes consistently
  doc: grep: unify info on configuration variables
This commit is contained in:
Junio C Hamano
2020-06-19 14:52:43 -07:00
17 changed files with 735 additions and 166 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

@ -16,9 +16,23 @@ grep.extendedRegexp::
other than 'default'. other than 'default'.
grep.threads:: grep.threads::
Number of grep worker threads to use. Number of grep worker threads to use. See `--threads`
See `grep.threads` in linkgit:git-grep[1] for more information. ifndef::git-grep[]
in linkgit:git-grep[1]
endif::git-grep[]
for more information.
grep.fullName::
If set to true, enable `--full-name` option by default.
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

@ -41,34 +41,8 @@ characters. An empty string as search expression matches all lines.
CONFIGURATION CONFIGURATION
------------- -------------
grep.lineNumber:: :git-grep: 1
If set to true, enable `-n` option by default. include::config/grep.txt[]
grep.column::
If set to true, enable the `--column` option by default.
grep.patternType::
Set the default matching behavior. Using a value of 'basic', 'extended',
'fixed', or 'perl' will enable the `--basic-regexp`, `--extended-regexp`,
`--fixed-strings`, or `--perl-regexp` option accordingly, while the
value 'default' will return to the default matching behavior.
grep.extendedRegexp::
If set to true, enable `--extended-regexp` option by default. This
option is ignored when the `grep.patternType` option is set to a value
other than 'default'.
grep.threads::
Number of grep worker threads to use. If unset (or set to 0), Git will
use as many threads as the number of logical cores available.
grep.fullName::
If set to true, enable `--full-name` option by default.
grep.fallbackToNoIndex::
If set to true, fall back to git grep --no-index if git grep
is executed outside of a git repository. Defaults to false.
OPTIONS OPTIONS
------- -------
@ -269,8 +243,10 @@ providing this option will cause it to die.
found. found.
--threads <num>:: --threads <num>::
Number of grep worker threads to use. Number of grep worker threads to use. If not provided (or set to
See `grep.threads` in 'CONFIGURATION' for more information. 0), Git will use as many worker threads as the number of logical
cores available. The default value can also be set with the
`grep.threads` configuration.
-f <file>:: -f <file>::
Read patterns from <file>, one per line. Read patterns from <file>, one per line.

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

@ -983,6 +983,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>...]"),
@ -410,7 +411,7 @@ static int grep_cache(struct grep_opt *opt,
const struct pathspec *pathspec, int cached); const struct pathspec *pathspec, int cached);
static int grep_tree(struct grep_opt *opt, const struct pathspec *pathspec, static int grep_tree(struct grep_opt *opt, const struct pathspec *pathspec,
struct tree_desc *tree, struct strbuf *base, int tn_len, struct tree_desc *tree, struct strbuf *base, int tn_len,
int check_attr); int is_root_tree);
static int grep_submodule(struct grep_opt *opt, static int grep_submodule(struct grep_opt *opt,
const struct pathspec *pathspec, const struct pathspec *pathspec,
@ -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);
@ -508,6 +510,10 @@ 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 (sparse_paths_only && ce_skip_worktree(ce))
continue;
strbuf_setlen(&name, name_base_len); strbuf_setlen(&name, name_base_len);
strbuf_addstr(&name, ce->name); strbuf_addstr(&name, ce->name);
@ -520,8 +526,7 @@ static int grep_cache(struct grep_opt *opt,
* cache entry are identical, even if worktree file has * cache entry are identical, even if worktree file has
* been modified, so use cache version instead * been modified, so use cache version instead
*/ */
if (cached || (ce->ce_flags & CE_VALID) || if (cached || (ce->ce_flags & CE_VALID)) {
ce_skip_worktree(ce)) {
if (ce_stage(ce) || ce_intent_to_add(ce)) if (ce_stage(ce) || ce_intent_to_add(ce))
continue; continue;
hit |= grep_oid(opt, &ce->oid, name.buf, hit |= grep_oid(opt, &ce->oid, name.buf,
@ -552,9 +557,76 @@ static int grep_cache(struct grep_opt *opt,
return hit; return hit;
} }
static int grep_tree(struct grep_opt *opt, const struct pathspec *pathspec, static struct pattern_list *get_sparsity_patterns(struct repository *repo)
struct tree_desc *tree, struct strbuf *base, int tn_len, {
int check_attr) struct pattern_list *patterns;
char *sparse_file;
int sparse_config, cone_config;
if (repo_config_get_bool(repo, "core.sparsecheckout", &sparse_config) ||
!sparse_config) {
return NULL;
}
sparse_file = repo_git_path(repo, "info/sparse-checkout");
patterns = xcalloc(1, sizeof(*patterns));
if (repo_config_get_bool(repo, "core.sparsecheckoutcone", &cone_config))
cone_config = 0;
patterns->use_cone_patterns = cone_config;
if (add_patterns_from_file_to_list(sparse_file, "", 0, patterns, NULL)) {
if (file_exists(sparse_file)) {
warning(_("failed to load sparse-checkout file: '%s'"),
sparse_file);
}
free(sparse_file);
free(patterns);
return NULL;
}
free(sparse_file);
return patterns;
}
static int path_in_sparse_checkout(struct strbuf *path, int prefix_len,
unsigned int entry_mode,
struct index_state *istate,
struct pattern_list *sparsity,
enum pattern_match_result parent_match,
enum pattern_match_result *match)
{
int dtype = DT_UNKNOWN;
int is_dir = S_ISDIR(entry_mode);
if (parent_match == MATCHED_RECURSIVE) {
*match = parent_match;
return 1;
}
if (is_dir && !is_dir_sep(path->buf[path->len - 1]))
strbuf_addch(path, '/');
*match = path_matches_pattern_list(path->buf, path->len,
path->buf + prefix_len, &dtype,
sparsity, istate);
if (*match == UNDECIDED)
*match = parent_match;
if (is_dir)
strbuf_trim_trailing_dir_sep(path);
if (*match == NOT_MATCHED &&
(!is_dir || (is_dir && sparsity->use_cone_patterns)))
return 0;
return 1;
}
static int do_grep_tree(struct grep_opt *opt, const struct pathspec *pathspec,
struct tree_desc *tree, struct strbuf *base, int tn_len,
int check_attr, struct pattern_list *sparsity,
enum pattern_match_result default_sparsity_match)
{ {
struct repository *repo = opt->repo; struct repository *repo = opt->repo;
int hit = 0; int hit = 0;
@ -570,6 +642,7 @@ static int grep_tree(struct grep_opt *opt, const struct pathspec *pathspec,
while (tree_entry(tree, &entry)) { while (tree_entry(tree, &entry)) {
int te_len = tree_entry_len(&entry); int te_len = tree_entry_len(&entry);
enum pattern_match_result sparsity_match = 0;
if (match != all_entries_interesting) { if (match != all_entries_interesting) {
strbuf_addstr(&name, base->buf + tn_len); strbuf_addstr(&name, base->buf + tn_len);
@ -586,6 +659,19 @@ static int grep_tree(struct grep_opt *opt, const struct pathspec *pathspec,
strbuf_add(base, entry.path, te_len); strbuf_add(base, entry.path, te_len);
if (sparsity) {
struct strbuf path = STRBUF_INIT;
strbuf_addstr(&path, base->buf + tn_len);
if (!path_in_sparse_checkout(&path, old_baselen - tn_len,
entry.mode, repo->index,
sparsity, default_sparsity_match,
&sparsity_match)) {
strbuf_setlen(base, old_baselen);
continue;
}
}
if (S_ISREG(entry.mode)) { if (S_ISREG(entry.mode)) {
hit |= grep_oid(opt, &entry.oid, base->buf, tn_len, hit |= grep_oid(opt, &entry.oid, base->buf, tn_len,
check_attr ? base->buf + tn_len : NULL); check_attr ? base->buf + tn_len : NULL);
@ -602,8 +688,8 @@ static int grep_tree(struct grep_opt *opt, const struct pathspec *pathspec,
strbuf_addch(base, '/'); strbuf_addch(base, '/');
init_tree_desc(&sub, data, size); init_tree_desc(&sub, data, size);
hit |= grep_tree(opt, pathspec, &sub, base, tn_len, hit |= do_grep_tree(opt, pathspec, &sub, base, tn_len,
check_attr); check_attr, sparsity, sparsity_match);
free(data); free(data);
} else if (recurse_submodules && S_ISGITLINK(entry.mode)) { } else if (recurse_submodules && S_ISGITLINK(entry.mode)) {
hit |= grep_submodule(opt, pathspec, &entry.oid, hit |= grep_submodule(opt, pathspec, &entry.oid,
@ -621,6 +707,32 @@ static int grep_tree(struct grep_opt *opt, const struct pathspec *pathspec,
return hit; return hit;
} }
/*
* Note: sparsity patterns and paths' attributes will only be considered if
* is_root_tree has true value. (Otherwise, we cannot properly perform pattern
* matching on paths.)
*/
static int grep_tree(struct grep_opt *opt, const struct pathspec *pathspec,
struct tree_desc *tree, struct strbuf *base, int tn_len,
int is_root_tree)
{
struct pattern_list *patterns = NULL;
int sparse_paths_only = restrict_to_sparse_paths(opt->repo);
int ret;
if (is_root_tree && sparse_paths_only)
patterns = get_sparsity_patterns(opt->repo);
ret = do_grep_tree(opt, pathspec, tree, base, tn_len, is_root_tree,
patterns, 0);
if (patterns) {
clear_pattern_list(patterns);
free(patterns);
}
return ret;
}
static int grep_object(struct grep_opt *opt, const struct pathspec *pathspec, static int grep_object(struct grep_opt *opt, const struct pathspec *pathspec,
struct object *obj, const char *name, const char *path) struct object *obj, const char *name, const char *path)
{ {
@ -1148,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

@ -1747,11 +1747,22 @@ static int do_git_config_sequence(const struct config_options *opts,
ret += git_config_from_file(fn, repo_config, data); ret += git_config_from_file(fn, repo_config, data);
current_parsing_scope = CONFIG_SCOPE_WORKTREE; current_parsing_scope = CONFIG_SCOPE_WORKTREE;
if (!opts->ignore_worktree && repository_format_worktree_config) { if (!opts->ignore_worktree && repo_config && opts->git_dir) {
char *path = git_pathdup("config.worktree"); struct repository_format repo_fmt = REPOSITORY_FORMAT_INIT;
if (!access_or_die(path, R_OK, 0)) struct strbuf buf = STRBUF_INIT;
ret += git_config_from_file(fn, path, data);
free(path); read_repository_format(&repo_fmt, repo_config);
if (!verify_repository_format(&repo_fmt, &buf) &&
repo_fmt.worktree_config) {
char *path = mkpathdup("%s/config.worktree", opts->git_dir);
if (!access_or_die(path, R_OK, 0))
ret += git_config_from_file(fn, path, data);
free(path);
}
strbuf_release(&buf);
clear_repository_format(&repo_fmt);
} }
current_parsing_scope = CONFIG_SCOPE_COMMAND; current_parsing_scope = CONFIG_SCOPE_COMMAND;

View File

@ -3382,6 +3382,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

@ -5,6 +5,7 @@
#include "run-command.h" #include "run-command.h"
#include "alias.h" #include "alias.h"
#include "shallow.h" #include "shallow.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)
@ -311,6 +312,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

@ -2,12 +2,19 @@
#include "cache.h" #include "cache.h"
#include "config.h" #include "config.h"
#include "string-list.h" #include "string-list.h"
#include "submodule-config.h"
/* /*
* This program exposes the C API of the configuration mechanism * This program exposes the C API of the configuration mechanism
* as a set of simple commands in order to facilitate testing. * as a set of simple commands in order to facilitate testing.
* *
* Reads stdin and prints result of command to stdout: * Usage: test-tool config [--submodule=<path>] <cmd> [<args>]
*
* If --submodule=<path> is given, <cmd> will operate on the submodule at the
* given <path>. This option is not valid for the commands: read_early_config,
* configset_get_value and configset_get_value_multi.
*
* Possible cmds are:
* *
* get_value -> prints the value with highest priority for the entered key * get_value -> prints the value with highest priority for the entered key
* *
@ -30,6 +37,14 @@
* iterate -> iterate over all values using git_config(), and print some * iterate -> iterate over all values using git_config(), and print some
* data for each * data for each
* *
* Exit codes:
* 0: success
* 1: value not found for the given config key
* 2: config file path given as argument is inaccessible or doesn't exist
* 129: test-config usage error
*
* Note: tests may also expect 128 for die() calls in the config machinery.
*
* Examples: * Examples:
* *
* To print the value with highest priority for key "foo.bAr Baz.rock": * To print the value with highest priority for key "foo.bAr Baz.rock":
@ -64,131 +79,173 @@ static int early_config_cb(const char *var, const char *value, void *vdata)
return 0; return 0;
} }
enum test_config_exit_code {
TC_SUCCESS = 0,
TC_VALUE_NOT_FOUND = 1,
TC_CONFIG_FILE_ERROR = 2,
TC_USAGE_ERROR = 129,
};
int cmd__config(int argc, const char **argv) int cmd__config(int argc, const char **argv)
{ {
int i, val; int i, val;
const char *v; const char *v;
const struct string_list *strptr; const struct string_list *strptr;
struct config_set cs; struct config_set cs = { .hash_initialized = 0 };
enum test_config_exit_code ret = TC_SUCCESS;
struct repository *repo = the_repository;
const char *subrepo_path = NULL;
if (argc == 3 && !strcmp(argv[1], "read_early_config")) { argc--; /* skip over "config" */
read_early_config(early_config_cb, (void *)argv[2]); argv++;
return 0;
if (argc == 0)
goto print_usage_error;
if (skip_prefix(*argv, "--submodule=", &subrepo_path)) {
argc--;
argv++;
if (argc == 0)
goto print_usage_error;
}
if (argc == 2 && !strcmp(argv[0], "read_early_config")) {
if (subrepo_path) {
fprintf(stderr, "Cannot use --submodule with read_early_config\n");
return TC_USAGE_ERROR;
}
read_early_config(early_config_cb, (void *)argv[1]);
return TC_SUCCESS;
} }
setup_git_directory(); setup_git_directory();
git_configset_init(&cs); git_configset_init(&cs);
if (argc < 2) { if (subrepo_path) {
fprintf(stderr, "Please, provide a command name on the command-line\n"); const struct submodule *sub;
goto exit1; struct repository *subrepo = xcalloc(1, sizeof(*repo));
} else if (argc == 3 && !strcmp(argv[1], "get_value")) {
if (!git_config_get_value(argv[2], &v)) { sub = submodule_from_path(the_repository, &null_oid, subrepo_path);
if (!v) if (!sub || repo_submodule_init(subrepo, the_repository, sub)) {
printf("(NULL)\n"); fprintf(stderr, "Invalid argument to --submodule: '%s'\n",
else subrepo_path);
printf("%s\n", v); free(subrepo);
goto exit0; ret = TC_USAGE_ERROR;
} else { goto out;
printf("Value not found for \"%s\"\n", argv[2]);
goto exit1;
} }
} else if (argc == 3 && !strcmp(argv[1], "get_value_multi")) { repo = subrepo;
strptr = git_config_get_value_multi(argv[2]);
if (strptr) {
for (i = 0; i < strptr->nr; i++) {
v = strptr->items[i].string;
if (!v)
printf("(NULL)\n");
else
printf("%s\n", v);
}
goto exit0;
} else {
printf("Value not found for \"%s\"\n", argv[2]);
goto exit1;
}
} else if (argc == 3 && !strcmp(argv[1], "get_int")) {
if (!git_config_get_int(argv[2], &val)) {
printf("%d\n", val);
goto exit0;
} else {
printf("Value not found for \"%s\"\n", argv[2]);
goto exit1;
}
} else if (argc == 3 && !strcmp(argv[1], "get_bool")) {
if (!git_config_get_bool(argv[2], &val)) {
printf("%d\n", val);
goto exit0;
} else {
printf("Value not found for \"%s\"\n", argv[2]);
goto exit1;
}
} else if (argc == 3 && !strcmp(argv[1], "get_string")) {
if (!git_config_get_string_const(argv[2], &v)) {
printf("%s\n", v);
goto exit0;
} else {
printf("Value not found for \"%s\"\n", argv[2]);
goto exit1;
}
} else if (!strcmp(argv[1], "configset_get_value")) {
for (i = 3; i < argc; i++) {
int err;
if ((err = git_configset_add_file(&cs, argv[i]))) {
fprintf(stderr, "Error (%d) reading configuration file %s.\n", err, argv[i]);
goto exit2;
}
}
if (!git_configset_get_value(&cs, argv[2], &v)) {
if (!v)
printf("(NULL)\n");
else
printf("%s\n", v);
goto exit0;
} else {
printf("Value not found for \"%s\"\n", argv[2]);
goto exit1;
}
} else if (!strcmp(argv[1], "configset_get_value_multi")) {
for (i = 3; i < argc; i++) {
int err;
if ((err = git_configset_add_file(&cs, argv[i]))) {
fprintf(stderr, "Error (%d) reading configuration file %s.\n", err, argv[i]);
goto exit2;
}
}
strptr = git_configset_get_value_multi(&cs, argv[2]);
if (strptr) {
for (i = 0; i < strptr->nr; i++) {
v = strptr->items[i].string;
if (!v)
printf("(NULL)\n");
else
printf("%s\n", v);
}
goto exit0;
} else {
printf("Value not found for \"%s\"\n", argv[2]);
goto exit1;
}
} else if (!strcmp(argv[1], "iterate")) {
git_config(iterate_cb, NULL);
goto exit0;
} }
die("%s: Please check the syntax and the function name", argv[0]); if (argc == 2 && !strcmp(argv[0], "get_value")) {
if (!repo_config_get_value(repo, argv[1], &v)) {
if (!v)
printf("(NULL)\n");
else
printf("%s\n", v);
} else {
printf("Value not found for \"%s\"\n", argv[1]);
ret = TC_VALUE_NOT_FOUND;
}
} else if (argc == 2 && !strcmp(argv[0], "get_value_multi")) {
strptr = repo_config_get_value_multi(repo, argv[1]);
if (strptr) {
for (i = 0; i < strptr->nr; i++) {
v = strptr->items[i].string;
if (!v)
printf("(NULL)\n");
else
printf("%s\n", v);
}
} else {
printf("Value not found for \"%s\"\n", argv[1]);
ret = TC_VALUE_NOT_FOUND;
}
} else if (argc == 2 && !strcmp(argv[0], "get_int")) {
if (!repo_config_get_int(repo, argv[1], &val)) {
printf("%d\n", val);
} else {
printf("Value not found for \"%s\"\n", argv[1]);
ret = TC_VALUE_NOT_FOUND;
}
} else if (argc == 2 && !strcmp(argv[0], "get_bool")) {
if (!repo_config_get_bool(repo, argv[1], &val)) {
printf("%d\n", val);
} else {
exit0: printf("Value not found for \"%s\"\n", argv[1]);
git_configset_clear(&cs); ret = TC_VALUE_NOT_FOUND;
return 0; }
} else if (argc == 2 && !strcmp(argv[0], "get_string")) {
if (!repo_config_get_string_const(repo, argv[1], &v)) {
printf("%s\n", v);
} else {
printf("Value not found for \"%s\"\n", argv[1]);
ret = TC_VALUE_NOT_FOUND;
}
} else if (argc >= 2 && !strcmp(argv[0], "configset_get_value")) {
if (subrepo_path) {
fprintf(stderr, "Cannot use --submodule with configset_get_value\n");
ret = TC_USAGE_ERROR;
goto out;
}
for (i = 2; i < argc; i++) {
int err;
if ((err = git_configset_add_file(&cs, argv[i]))) {
fprintf(stderr, "Error (%d) reading configuration file %s.\n", err, argv[i]);
ret = TC_CONFIG_FILE_ERROR;
goto out;
}
}
if (!git_configset_get_value(&cs, argv[1], &v)) {
if (!v)
printf("(NULL)\n");
else
printf("%s\n", v);
} else {
printf("Value not found for \"%s\"\n", argv[1]);
ret = TC_VALUE_NOT_FOUND;
}
} else if (argc >= 2 && !strcmp(argv[0], "configset_get_value_multi")) {
if (subrepo_path) {
fprintf(stderr, "Cannot use --submodule with configset_get_value_multi\n");
ret = TC_USAGE_ERROR;
goto out;
}
for (i = 2; i < argc; i++) {
int err;
if ((err = git_configset_add_file(&cs, argv[i]))) {
fprintf(stderr, "Error (%d) reading configuration file %s.\n", err, argv[i]);
ret = TC_CONFIG_FILE_ERROR;
goto out;
}
}
strptr = git_configset_get_value_multi(&cs, argv[1]);
if (strptr) {
for (i = 0; i < strptr->nr; i++) {
v = strptr->items[i].string;
if (!v)
printf("(NULL)\n");
else
printf("%s\n", v);
}
} else {
printf("Value not found for \"%s\"\n", argv[1]);
ret = TC_VALUE_NOT_FOUND;
}
} else if (!strcmp(argv[0], "iterate")) {
repo_config(repo, iterate_cb, NULL);
} else {
print_usage_error:
fprintf(stderr, "Invalid syntax. Usage: test-tool config"
" [--submodule=<path>] <cmd> [args]\n");
ret = TC_USAGE_ERROR;
}
exit1: out:
git_configset_clear(&cs); git_configset_clear(&cs);
return 1; if (repo != the_repository) {
repo_clear(repo);
exit2: free(repo);
git_configset_clear(&cs); }
return 2; return ret;
} }

View File

@ -78,4 +78,20 @@ test_expect_success 'config.worktree no longer read without extension' '
test_cmp_config -C wt2 shared this.is test_cmp_config -C wt2 shared this.is
' '
test_expect_success 'correctly read config.worktree from submodules' '
test_unconfig extensions.worktreeConfig &&
git init sub &&
(
cd sub &&
test_commit A &&
git config extensions.worktreeConfig true &&
git config --worktree wtconfig.sub test-value
) &&
git submodule add ./sub &&
git commit -m "add sub" &&
echo test-value >expect &&
test-tool config --submodule=sub get_value wtconfig.sub >actual &&
test_cmp expect actual
'
test_done test_done

View File

@ -109,15 +109,6 @@ test_expect_success 'ls-files --modified' '
test -z "$(git ls-files -m)" test -z "$(git ls-files -m)"
' '
test_expect_success 'grep with skip-worktree file' '
git update-index --no-skip-worktree 1 &&
echo test > 1 &&
git update-index 1 &&
git update-index --skip-worktree 1 &&
rm 1 &&
test "$(git grep --no-ext-grep test)" = "1:test"
'
echo ":000000 100644 $ZERO_OID $EMPTY_BLOB A 1" > expected echo ":000000 100644 $ZERO_OID $EMPTY_BLOB A 1" > expected
test_expect_success 'diff-index does not examine skip-worktree absent entries' ' test_expect_success 'diff-index does not examine skip-worktree absent entries' '
setup_absent && setup_absent &&

321
t/t7817-grep-sparse-checkout.sh Executable file
View File

@ -0,0 +1,321 @@
#!/bin/sh
test_description='grep in sparse checkout
This test creates a repo with the following structure:
.
|-- a
|-- b
|-- dir
| `-- c
|-- sub
| |-- A
| | `-- a
| `-- B
| `-- b
`-- sub2
`-- a
Where the outer repository has non-cone mode sparsity patterns, sub is a
submodule with cone mode sparsity patterns and sub2 is a submodule that is
excluded by the superproject sparsity patterns. The resulting sparse checkout
should leave the following structure in the working tree:
.
|-- a
|-- sub
| `-- B
| `-- b
`-- sub2
`-- a
But note that sub2 should have the SKIP_WORKTREE bit set.
'
. ./test-lib.sh
test_expect_success 'setup' '
echo "text" >a &&
echo "text" >b &&
mkdir dir &&
echo "text" >dir/c &&
git init sub &&
(
cd sub &&
mkdir A B &&
echo "text" >A/a &&
echo "text" >B/b &&
git add A B &&
git commit -m sub &&
git sparse-checkout init --cone &&
git sparse-checkout set B
) &&
git init sub2 &&
(
cd sub2 &&
echo "text" >a &&
git add a &&
git commit -m sub2
) &&
git submodule add ./sub &&
git submodule add ./sub2 &&
git add a b dir &&
git commit -m super &&
git sparse-checkout init --no-cone &&
git sparse-checkout set "/*" "!b" "!/*/" "sub" &&
git tag -am tag-to-commit tag-to-commit HEAD &&
tree=$(git rev-parse HEAD^{tree}) &&
git tag -am tag-to-tree tag-to-tree $tree &&
test_path_is_missing b &&
test_path_is_missing dir &&
test_path_is_missing sub/A &&
test_path_is_file a &&
test_path_is_file sub/B/b &&
test_path_is_file sub2/a
'
# 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.
# manually created after `git sparse-checkout init`). In this case, grep should
# skip the file by default, but not with --no-restrict-to-sparse-paths.
test_expect_success 'grep in working tree should honor sparse checkout' '
cat >expect <<-EOF &&
a:text
EOF
echo "new-text" >b &&
test_when_finished "rm b" &&
git grep "text" >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' '
cat >expect <<-EOF &&
b:modified-b-in-branchX
b:modified-b-in-branchY
EOF
test_when_finished "test_might_fail git merge --abort && \
git checkout master" &&
git sparse-checkout disable &&
git checkout -b branchY master &&
test_commit modified-b-in-branchY b &&
git checkout -b branchX master &&
test_commit modified-b-in-branchX b &&
git sparse-checkout init &&
test_path_is_missing b &&
test_must_fail git merge branchY &&
git grep "modified-b" >actual &&
test_cmp expect actual
'
test_expect_success 'grep --cached should honor sparse checkout' '
cat >expect <<-EOF &&
a:text
EOF
git grep --cached "text" >actual &&
test_cmp expect actual
'
test_expect_success 'grep <commit-ish> should honor sparse checkout' '
commit=$(git rev-parse HEAD) &&
cat >expect_commit <<-EOF &&
$commit:a:text
EOF
cat >expect_tag-to-commit <<-EOF &&
tag-to-commit:a:text
EOF
git grep "text" $commit >actual_commit &&
test_cmp expect_commit actual_commit &&
git grep "text" tag-to-commit >actual_tag-to-commit &&
test_cmp expect_tag-to-commit actual_tag-to-commit
'
test_expect_success 'grep <tree-ish> should ignore sparsity patterns' '
commit=$(git rev-parse HEAD) &&
tree=$(git rev-parse HEAD^{tree}) &&
cat >expect_tree <<-EOF &&
$tree:a:text
$tree:b:text
$tree:dir/c:text
EOF
cat >expect_tag-to-tree <<-EOF &&
tag-to-tree:a:text
tag-to-tree:b:text
tag-to-tree:dir/c:text
EOF
git grep "text" $tree >actual_tree &&
test_cmp expect_tree actual_tree &&
git grep "text" tag-to-tree >actual_tag-to-tree &&
test_cmp expect_tag-to-tree actual_tag-to-tree
'
# Note that sub2/ is present in the worktree but it is excluded by the sparsity
# 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' '
cat >expect <<-EOF &&
a:text
sub/B/b:text
EOF
git grep --recurse-submodules "text" >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' '
cat >expect <<-EOF &&
a:text
sub/B/b:text
EOF
git grep --recurse-submodules --cached "text" >actual &&
test_cmp expect actual
'
test_expect_success 'grep --recurse-submodules <commit-ish> should honor sparse checkout in submodule' '
commit=$(git rev-parse HEAD) &&
cat >expect_commit <<-EOF &&
$commit:a:text
$commit:sub/B/b:text
EOF
cat >expect_tag-to-commit <<-EOF &&
tag-to-commit:a:text
tag-to-commit:sub/B/b:text
EOF
git grep --recurse-submodules "text" $commit >actual_commit &&
test_cmp expect_commit actual_commit &&
git grep --recurse-submodules "text" 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

View File

@ -1928,6 +1928,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
' '
@ -1970,7 +1972,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' '