Skip to content

Commit 00408ad

Browse files
williams-unitygitster
authored andcommitted
builtin/sparse-checkout: add check-rules command
There exists no direct way to interrogate git about which paths are matched by a given set of sparsity rules. It is possible to get this information from git, but it includes checking out the commit that contains the paths, applying the sparse checkout patterns and then using something like 'git ls-files -t' to check if the skip worktree bit is set. This works in some case, but there are cases where it is awkward or infeasible to generate a checkout for this purpose. Exposing the pattern matching of sparse checkout enables more tooling to be built and avoids a situation where tools that want to reason about sparse checkouts start containing parallel implementation of the rules. To accommodate this, add a 'check-rules' subcommand to the 'sparse-checkout' builtin along the lines of the 'git check-ignore' and 'git check-attr' commands. The new command accepts a list of paths on stdin and outputs just the ones the match the sparse checkout. To allow for use in a bare repository and to allow for interrogating about other patterns than the current ones, include a '--rules-file' option which allows the caller to explicitly pass sparse checkout rules in the format accepted by 'sparse-checkout set --stdin'. To allow for reuse of the handling of input patterns for the '--rules-file' flag, modify 'add_patterns_from_input()' to be able to read from a 'FILE' instead of just stdin. To allow for reuse of the logic which decides whether or not rules should be interpreted as cone-mode patterns, split that part out of 'update_modes()' such that can be called without modifying the config. An alternative could have been to create a new 'check-sparsity' command. However, placing it under 'sparse-checkout' allows for a) more easily re-using the sparse checkout pattern matching and cone/non-code mode handling, and b) keeps the documentation for the command next to the experimental warning and the cone-mode discussion. Signed-off-by: William Sprent <[email protected]> Signed-off-by: Junio C Hamano <[email protected]>
1 parent 24fc2cd commit 00408ad

File tree

3 files changed

+267
-23
lines changed

3 files changed

+267
-23
lines changed

Documentation/git-sparse-checkout.txt

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ git-sparse-checkout - Reduce your working tree to a subset of tracked files
99
SYNOPSIS
1010
--------
1111
[verse]
12-
'git sparse-checkout' (init | list | set | add | reapply | disable) [<options>]
12+
'git sparse-checkout' (init | list | set | add | reapply | disable | check-rules) [<options>]
1313

1414

1515
DESCRIPTION
@@ -135,6 +135,29 @@ paths to pass to a subsequent 'set' or 'add' command. However,
135135
the disable command, so the easy restore of calling a plain `init`
136136
decreased in utility.
137137

138+
'check-rules'::
139+
Check whether sparsity rules match one or more paths.
140+
+
141+
By default `check-rules` reads a list of paths from stdin and outputs only
142+
the ones that match the current sparsity rules. The input is expected to consist
143+
of one path per line, matching the output of `git ls-tree --name-only` including
144+
that pathnames that begin with a double quote (") are interpreted as C-style
145+
quoted strings.
146+
+
147+
When called with the `--rules-file <file>` flag the input files are matched
148+
against the sparse checkout rules found in `<file>` instead of the current ones.
149+
The rules in the files are expected to be in the same form as accepted by `git
150+
sparse-checkout set --stdin` (in particular, they must be newline-delimited).
151+
+
152+
By default, the rules passed to the `--rules-file` option are interpreted as
153+
cone mode directories. To pass non-cone mode patterns with `--rules-file`,
154+
combine the option with the `--no-cone` option.
155+
+
156+
When called with the `-z` flag, the format of the paths input on stdin as well
157+
as the output paths are \0 terminated and not quoted. Note that this does not
158+
apply to the format of the rules passed with the `--rules-file` option.
159+
160+
138161
EXAMPLES
139162
--------
140163
`git sparse-checkout set MY/DIR1 SUB/DIR2`::

builtin/sparse-checkout.c

Lines changed: 111 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
static const char *empty_base = "";
2121

2222
static char const * const builtin_sparse_checkout_usage[] = {
23-
N_("git sparse-checkout (init | list | set | add | reapply | disable) [<options>]"),
23+
N_("git sparse-checkout (init | list | set | add | reapply | disable | check-rules) [<options>]"),
2424
NULL
2525
};
2626

@@ -382,26 +382,29 @@ static int set_config(enum sparse_checkout_mode mode)
382382
return 0;
383383
}
384384

385-
static int update_modes(int *cone_mode, int *sparse_index)
386-
{
387-
int mode, record_mode;
388-
389-
/* Determine if we need to record the mode; ensure sparse checkout on */
390-
record_mode = (*cone_mode != -1) || !core_apply_sparse_checkout;
391-
385+
static enum sparse_checkout_mode update_cone_mode(int *cone_mode) {
392386
/* If not specified, use previous definition of cone mode */
393387
if (*cone_mode == -1 && core_apply_sparse_checkout)
394388
*cone_mode = core_sparse_checkout_cone;
395389

396390
/* Set cone/non-cone mode appropriately */
397391
core_apply_sparse_checkout = 1;
398392
if (*cone_mode == 1 || *cone_mode == -1) {
399-
mode = MODE_CONE_PATTERNS;
400393
core_sparse_checkout_cone = 1;
401-
} else {
402-
mode = MODE_ALL_PATTERNS;
403-
core_sparse_checkout_cone = 0;
394+
return MODE_CONE_PATTERNS;
404395
}
396+
core_sparse_checkout_cone = 0;
397+
return MODE_ALL_PATTERNS;
398+
}
399+
400+
static int update_modes(int *cone_mode, int *sparse_index)
401+
{
402+
int mode, record_mode;
403+
404+
/* Determine if we need to record the mode; ensure sparse checkout on */
405+
record_mode = (*cone_mode != -1) || !core_apply_sparse_checkout;
406+
407+
mode = update_cone_mode(cone_mode);
405408
if (record_mode && set_config(mode))
406409
return 1;
407410

@@ -545,7 +548,7 @@ static void strbuf_to_cone_pattern(struct strbuf *line, struct pattern_list *pl)
545548

546549
static void add_patterns_from_input(struct pattern_list *pl,
547550
int argc, const char **argv,
548-
int use_stdin)
551+
FILE *file)
549552
{
550553
int i;
551554
if (core_sparse_checkout_cone) {
@@ -555,9 +558,9 @@ static void add_patterns_from_input(struct pattern_list *pl,
555558
hashmap_init(&pl->parent_hashmap, pl_hashmap_cmp, NULL, 0);
556559
pl->use_cone_patterns = 1;
557560

558-
if (use_stdin) {
561+
if (file) {
559562
struct strbuf unquoted = STRBUF_INIT;
560-
while (!strbuf_getline(&line, stdin)) {
563+
while (!strbuf_getline(&line, file)) {
561564
if (line.buf[0] == '"') {
562565
strbuf_reset(&unquoted);
563566
if (unquote_c_style(&unquoted, line.buf, NULL))
@@ -579,10 +582,10 @@ static void add_patterns_from_input(struct pattern_list *pl,
579582
}
580583
}
581584
} else {
582-
if (use_stdin) {
585+
if (file) {
583586
struct strbuf line = STRBUF_INIT;
584587

585-
while (!strbuf_getline(&line, stdin)) {
588+
while (!strbuf_getline(&line, file)) {
586589
size_t len;
587590
char *buf = strbuf_detach(&line, &len);
588591
add_pattern(buf, empty_base, 0, pl, 0);
@@ -609,7 +612,8 @@ static void add_patterns_cone_mode(int argc, const char **argv,
609612
struct pattern_list existing;
610613
char *sparse_filename = get_sparse_checkout_filename();
611614

612-
add_patterns_from_input(pl, argc, argv, use_stdin);
615+
add_patterns_from_input(pl, argc, argv,
616+
use_stdin ? stdin : NULL);
613617

614618
memset(&existing, 0, sizeof(existing));
615619
existing.use_cone_patterns = core_sparse_checkout_cone;
@@ -646,7 +650,7 @@ static void add_patterns_literal(int argc, const char **argv,
646650
pl, NULL, 0))
647651
die(_("unable to load existing sparse-checkout patterns"));
648652
free(sparse_filename);
649-
add_patterns_from_input(pl, argc, argv, use_stdin);
653+
add_patterns_from_input(pl, argc, argv, use_stdin ? stdin : NULL);
650654
}
651655

652656
static int modify_pattern_list(int argc, const char **argv, int use_stdin,
@@ -665,7 +669,8 @@ static int modify_pattern_list(int argc, const char **argv, int use_stdin,
665669
break;
666670

667671
case REPLACE:
668-
add_patterns_from_input(pl, argc, argv, use_stdin);
672+
add_patterns_from_input(pl, argc, argv,
673+
use_stdin ? stdin : NULL);
669674
break;
670675
}
671676

@@ -927,6 +932,91 @@ static int sparse_checkout_disable(int argc, const char **argv,
927932
return set_config(MODE_NO_PATTERNS);
928933
}
929934

935+
static char const * const builtin_sparse_checkout_check_rules_usage[] = {
936+
N_("git sparse-checkout check-rules [-z] [--skip-checks]"
937+
"[--[no-]cone] [--rules-file <file>]"),
938+
NULL
939+
};
940+
941+
static struct sparse_checkout_check_rules_opts {
942+
int cone_mode;
943+
int null_termination;
944+
char *rules_file;
945+
} check_rules_opts;
946+
947+
static int check_rules(struct pattern_list *pl, int null_terminated) {
948+
struct strbuf line = STRBUF_INIT;
949+
struct strbuf unquoted = STRBUF_INIT;
950+
char *path;
951+
int line_terminator = null_terminated ? 0 : '\n';
952+
strbuf_getline_fn getline_fn = null_terminated ? strbuf_getline_nul
953+
: strbuf_getline;
954+
the_repository->index->sparse_checkout_patterns = pl;
955+
while (!getline_fn(&line, stdin)) {
956+
path = line.buf;
957+
if (!null_terminated && line.buf[0] == '"') {
958+
strbuf_reset(&unquoted);
959+
if (unquote_c_style(&unquoted, line.buf, NULL))
960+
die(_("unable to unquote C-style string '%s'"),
961+
line.buf);
962+
963+
path = unquoted.buf;
964+
}
965+
966+
if (path_in_sparse_checkout(path, the_repository->index))
967+
write_name_quoted(path, stdout, line_terminator);
968+
}
969+
strbuf_release(&line);
970+
strbuf_release(&unquoted);
971+
972+
return 0;
973+
}
974+
975+
static int sparse_checkout_check_rules(int argc, const char **argv, const char *prefix)
976+
{
977+
static struct option builtin_sparse_checkout_check_rules_options[] = {
978+
OPT_BOOL('z', NULL, &check_rules_opts.null_termination,
979+
N_("terminate input and output files by a NUL character")),
980+
OPT_BOOL(0, "cone", &check_rules_opts.cone_mode,
981+
N_("when used with --rules-file interpret patterns as cone mode patterns")),
982+
OPT_FILENAME(0, "rules-file", &check_rules_opts.rules_file,
983+
N_("use patterns in <file> instead of the current ones.")),
984+
OPT_END(),
985+
};
986+
987+
FILE *fp;
988+
int ret;
989+
struct pattern_list pl = {0};
990+
char *sparse_filename;
991+
check_rules_opts.cone_mode = -1;
992+
993+
argc = parse_options(argc, argv, prefix,
994+
builtin_sparse_checkout_check_rules_options,
995+
builtin_sparse_checkout_check_rules_usage,
996+
PARSE_OPT_KEEP_UNKNOWN_OPT);
997+
998+
if (check_rules_opts.rules_file && check_rules_opts.cone_mode < 0)
999+
check_rules_opts.cone_mode = 1;
1000+
1001+
update_cone_mode(&check_rules_opts.cone_mode);
1002+
pl.use_cone_patterns = core_sparse_checkout_cone;
1003+
if (check_rules_opts.rules_file) {
1004+
fp = xfopen(check_rules_opts.rules_file, "r");
1005+
add_patterns_from_input(&pl, argc, argv, fp);
1006+
fclose(fp);
1007+
} else {
1008+
sparse_filename = get_sparse_checkout_filename();
1009+
if (add_patterns_from_file_to_list(sparse_filename, "", 0, &pl,
1010+
NULL, 0))
1011+
die(_("unable to load existing sparse-checkout patterns"));
1012+
free(sparse_filename);
1013+
}
1014+
1015+
ret = check_rules(&pl, check_rules_opts.null_termination);
1016+
clear_pattern_list(&pl);
1017+
return ret;
1018+
}
1019+
9301020
int cmd_sparse_checkout(int argc, const char **argv, const char *prefix)
9311021
{
9321022
parse_opt_subcommand_fn *fn = NULL;
@@ -937,6 +1027,7 @@ int cmd_sparse_checkout(int argc, const char **argv, const char *prefix)
9371027
OPT_SUBCOMMAND("add", &fn, sparse_checkout_add),
9381028
OPT_SUBCOMMAND("reapply", &fn, sparse_checkout_reapply),
9391029
OPT_SUBCOMMAND("disable", &fn, sparse_checkout_disable),
1030+
OPT_SUBCOMMAND("check-rules", &fn, sparse_checkout_check_rules),
9401031
OPT_END(),
9411032
};
9421033

0 commit comments

Comments
 (0)