diff --git a/Documentation/config.txt b/Documentation/config.txt index ef0768b91a..fd74b80302 100644 --- a/Documentation/config.txt +++ b/Documentation/config.txt @@ -436,6 +436,8 @@ include::config/sequencer.txt[] include::config/showbranch.txt[] +include::config/sparse.txt[] + include::config/splitindex.txt[] include::config/ssh.txt[] diff --git a/Documentation/config/grep.txt b/Documentation/config/grep.txt index 44abe45a7c..a3275ab4b7 100644 --- a/Documentation/config/grep.txt +++ b/Documentation/config/grep.txt @@ -16,9 +16,23 @@ grep.extendedRegexp:: other than 'default'. grep.threads:: - Number of grep worker threads to use. - See `grep.threads` in linkgit:git-grep[1] for more information. + Number of grep worker threads to use. See `--threads` +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:: If set to true, fall back to git grep --no-index if git grep 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[] diff --git a/Documentation/config/sparse.txt b/Documentation/config/sparse.txt new file mode 100644 index 0000000000..494761526e --- /dev/null +++ b/Documentation/config/sparse.txt @@ -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. diff --git a/Documentation/git-grep.txt b/Documentation/git-grep.txt index a7f9bc99ea..9bdf807584 100644 --- a/Documentation/git-grep.txt +++ b/Documentation/git-grep.txt @@ -41,34 +41,8 @@ characters. An empty string as search expression matches all lines. CONFIGURATION ------------- -grep.lineNumber:: - If set to true, enable `-n` option by default. - -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. - +:git-grep: 1 +include::config/grep.txt[] OPTIONS ------- @@ -269,8 +243,10 @@ providing this option will cause it to die. found. --threads :: - Number of grep worker threads to use. - See `grep.threads` in 'CONFIGURATION' for more information. + Number of grep worker threads to use. If not provided (or set to + 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 :: Read patterns from , one per line. diff --git a/Documentation/git.txt b/Documentation/git.txt index 3e50065198..c42bbc6098 100644 --- a/Documentation/git.txt +++ b/Documentation/git.txt @@ -180,6 +180,10 @@ If you just want to run git as if it was started in `` then use Do not perform optional operations that require locks. This is 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 commands by group. This is an internal/experimental option and may change or be removed in the future. Supported diff --git a/Makefile b/Makefile index 372139f1f2..9c8a6f19cd 100644 --- a/Makefile +++ b/Makefile @@ -983,6 +983,7 @@ LIB_OBJS += sha1-name.o LIB_OBJS += shallow.o LIB_OBJS += sideband.o LIB_OBJS += sigchain.o +LIB_OBJS += sparse-checkout.o LIB_OBJS += split-index.o LIB_OBJS += stable-qsort.o LIB_OBJS += strbuf.o diff --git a/builtin/grep.c b/builtin/grep.c index a5056f395a..7f485ea732 100644 --- a/builtin/grep.c +++ b/builtin/grep.c @@ -25,6 +25,7 @@ #include "submodule-config.h" #include "object-store.h" #include "packfile.h" +#include "sparse-checkout.h" static char const * const grep_usage[] = { N_("git grep [] [-e] [...] [[--] ...]"), @@ -410,7 +411,7 @@ static int grep_cache(struct grep_opt *opt, const struct pathspec *pathspec, int cached); static int grep_tree(struct grep_opt *opt, const struct pathspec *pathspec, 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, const struct pathspec *pathspec, @@ -498,6 +499,7 @@ static int grep_cache(struct grep_opt *opt, int nr; struct strbuf name = STRBUF_INIT; int name_base_len = 0; + int sparse_paths_only = restrict_to_sparse_paths(repo); if (repo->submodule_prefix) { name_base_len = strlen(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++) { 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_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 * been modified, so use cache version instead */ - if (cached || (ce->ce_flags & CE_VALID) || - ce_skip_worktree(ce)) { + if (cached || (ce->ce_flags & CE_VALID)) { if (ce_stage(ce) || ce_intent_to_add(ce)) continue; hit |= grep_oid(opt, &ce->oid, name.buf, @@ -552,9 +557,76 @@ static int grep_cache(struct grep_opt *opt, return hit; } -static int grep_tree(struct grep_opt *opt, const struct pathspec *pathspec, - struct tree_desc *tree, struct strbuf *base, int tn_len, - int check_attr) +static struct pattern_list *get_sparsity_patterns(struct repository *repo) +{ + 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; int hit = 0; @@ -570,6 +642,7 @@ static int grep_tree(struct grep_opt *opt, const struct pathspec *pathspec, while (tree_entry(tree, &entry)) { int te_len = tree_entry_len(&entry); + enum pattern_match_result sparsity_match = 0; if (match != all_entries_interesting) { 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); + 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)) { hit |= grep_oid(opt, &entry.oid, base->buf, tn_len, 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, '/'); init_tree_desc(&sub, data, size); - hit |= grep_tree(opt, pathspec, &sub, base, tn_len, - check_attr); + hit |= do_grep_tree(opt, pathspec, &sub, base, tn_len, + check_attr, sparsity, sparsity_match); free(data); } else if (recurse_submodules && S_ISGITLINK(entry.mode)) { 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; } +/* + * 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, 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) { 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); } else if (0 <= opt_exclude) { die(_("--[no-]exclude-standard cannot be used for tracked contents")); diff --git a/config.c b/config.c index 8db9c77098..c2d56309dc 100644 --- a/config.c +++ b/config.c @@ -1747,11 +1747,22 @@ static int do_git_config_sequence(const struct config_options *opts, ret += git_config_from_file(fn, repo_config, data); current_parsing_scope = CONFIG_SCOPE_WORKTREE; - if (!opts->ignore_worktree && repository_format_worktree_config) { - char *path = git_pathdup("config.worktree"); - if (!access_or_die(path, R_OK, 0)) - ret += git_config_from_file(fn, path, data); - free(path); + if (!opts->ignore_worktree && repo_config && opts->git_dir) { + struct repository_format repo_fmt = REPOSITORY_FORMAT_INIT; + struct strbuf buf = STRBUF_INIT; + + 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; diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash index de5d0fbbd1..302cc6c6f8 100644 --- a/contrib/completion/git-completion.bash +++ b/contrib/completion/git-completion.bash @@ -3382,6 +3382,8 @@ __git_main () --namespace= --no-replace-objects --help + --restrict-to-sparse-paths + --no-restrict-to-sparse-paths " ;; *) diff --git a/git.c b/git.c index 2f021b97f3..b078c75405 100644 --- a/git.c +++ b/git.c @@ -5,6 +5,7 @@ #include "run-command.h" #include "alias.h" #include "shallow.h" +#include "sparse-checkout.h" #define RUN_SETUP (1<<0) #define RUN_SETUP_GENTLY (1<<1) @@ -311,6 +312,10 @@ static int handle_options(const char ***argv, int *argc, int *envchanged) } else { 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 { fprintf(stderr, _("unknown option: %s\n"), cmd); usage(git_usage_string); diff --git a/sparse-checkout.c b/sparse-checkout.c new file mode 100644 index 0000000000..96c5ed5446 --- /dev/null +++ b/sparse-checkout.c @@ -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; +} diff --git a/sparse-checkout.h b/sparse-checkout.h new file mode 100644 index 0000000000..a4805e443a --- /dev/null +++ b/sparse-checkout.h @@ -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 */ diff --git a/t/helper/test-config.c b/t/helper/test-config.c index 234c722b48..284f83a921 100644 --- a/t/helper/test-config.c +++ b/t/helper/test-config.c @@ -2,12 +2,19 @@ #include "cache.h" #include "config.h" #include "string-list.h" +#include "submodule-config.h" /* * This program exposes the C API of the configuration mechanism * 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=] [] + * + * If --submodule= is given, will operate on the submodule at the + * given . 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 * @@ -30,6 +37,14 @@ * iterate -> iterate over all values using git_config(), and print some * 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: * * 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; } +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 i, val; const char *v; 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")) { - read_early_config(early_config_cb, (void *)argv[2]); - return 0; + argc--; /* skip over "config" */ + argv++; + + 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(); - git_configset_init(&cs); - if (argc < 2) { - fprintf(stderr, "Please, provide a command name on the command-line\n"); - goto exit1; - } else if (argc == 3 && !strcmp(argv[1], "get_value")) { - if (!git_config_get_value(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; + if (subrepo_path) { + const struct submodule *sub; + struct repository *subrepo = xcalloc(1, sizeof(*repo)); + + sub = submodule_from_path(the_repository, &null_oid, subrepo_path); + if (!sub || repo_submodule_init(subrepo, the_repository, sub)) { + fprintf(stderr, "Invalid argument to --submodule: '%s'\n", + subrepo_path); + free(subrepo); + ret = TC_USAGE_ERROR; + goto out; } - } else if (argc == 3 && !strcmp(argv[1], "get_value_multi")) { - 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; + repo = subrepo; } - 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: - git_configset_clear(&cs); - return 0; + printf("Value not found for \"%s\"\n", argv[1]); + ret = TC_VALUE_NOT_FOUND; + } + } 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=] [args]\n"); + ret = TC_USAGE_ERROR; + } -exit1: +out: git_configset_clear(&cs); - return 1; - -exit2: - git_configset_clear(&cs); - return 2; + if (repo != the_repository) { + repo_clear(repo); + free(repo); + } + return ret; } diff --git a/t/t2404-worktree-config.sh b/t/t2404-worktree-config.sh index 9536d10919..1e32c93735 100755 --- a/t/t2404-worktree-config.sh +++ b/t/t2404-worktree-config.sh @@ -78,4 +78,20 @@ test_expect_success 'config.worktree no longer read without extension' ' 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 diff --git a/t/t7011-skip-worktree-reading.sh b/t/t7011-skip-worktree-reading.sh index 37525cae3a..26852586ac 100755 --- a/t/t7011-skip-worktree-reading.sh +++ b/t/t7011-skip-worktree-reading.sh @@ -109,15 +109,6 @@ test_expect_success 'ls-files --modified' ' 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 test_expect_success 'diff-index does not examine skip-worktree absent entries' ' setup_absent && diff --git a/t/t7817-grep-sparse-checkout.sh b/t/t7817-grep-sparse-checkout.sh new file mode 100755 index 0000000000..f93a4f71d1 --- /dev/null +++ b/t/t7817-grep-sparse-checkout.sh @@ -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 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 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 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 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 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 diff --git a/t/t9902-completion.sh b/t/t9902-completion.sh index 8f434a0931..aff5ef3d76 100755 --- a/t/t9902-completion.sh +++ b/t/t9902-completion.sh @@ -1928,6 +1928,8 @@ test_expect_success 'double dash "git" itself' ' --namespace= --no-replace-objects Z --help Z + --restrict-to-sparse-paths Z + --no-restrict-to-sparse-paths Z EOF ' @@ -1970,7 +1972,7 @@ test_expect_success 'general options' ' test_completion "git --nam" "--namespace=" && test_completion "git --bar" "--bare " && 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' '