Skip to content

Commit 433b8aa

Browse files
committed
Merge branch 'ds/sparse-checkout-harden'
Some rough edges in the sparse-checkout feature, especially around the cone mode, have been cleaned up. * ds/sparse-checkout-harden: sparse-checkout: fix cone mode behavior mismatch sparse-checkout: improve docs around 'set' in cone mode sparse-checkout: escape all glob characters on write sparse-checkout: use C-style quotes in 'list' subcommand sparse-checkout: unquote C-style strings over --stdin sparse-checkout: write escaped patterns in cone mode sparse-checkout: properly match escaped characters sparse-checkout: warn on globs in cone patterns sparse-checkout: detect short patterns sparse-checkout: cone mode does not recognize "**" sparse-checkout: fix documentation typo for core.sparseCheckoutCone clone: fix --sparse option with URLs sparse-checkout: create leading directories t1091: improve here-docs t1091: use check_files to reduce boilerplate
2 parents 4a77434 + f998a3f commit 433b8aa

File tree

6 files changed

+346
-156
lines changed

6 files changed

+346
-156
lines changed

Documentation/git-sparse-checkout.txt

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,14 @@ patterns (see 'CONE PATTERN SET' below).
5454
+
5555
When the `--stdin` option is provided, the patterns are read from
5656
standard in as a newline-delimited list instead of from the arguments.
57+
+
58+
When `core.sparseCheckoutCone` is enabled, the input list is considered a
59+
list of directories instead of sparse-checkout patterns. The command writes
60+
patterns to the sparse-checkout file to include all files contained in those
61+
directories (recursively) as well as files that are siblings of ancestor
62+
directories. The input format matches the output of `git ls-tree --name-only`.
63+
This includes interpreting pathnames that begin with a double quote (") as
64+
C-style quoted strings.
5765

5866
'disable'::
5967
Disable the `core.sparseCheckout` config setting, and restore the
@@ -110,7 +118,7 @@ The full pattern set allows for arbitrary pattern matches and complicated
110118
inclusion/exclusion rules. These can result in O(N*M) pattern matches when
111119
updating the index, where N is the number of patterns and M is the number
112120
of paths in the index. To combat this performance issue, a more restricted
113-
pattern set is allowed when `core.spareCheckoutCone` is enabled.
121+
pattern set is allowed when `core.sparseCheckoutCone` is enabled.
114122

115123
The accepted patterns in the cone pattern set are:
116124

@@ -132,9 +140,12 @@ the following patterns:
132140
----------------
133141

134142
This says "include everything in root, but nothing two levels below root."
135-
If we then add the folder `A/B/C` as a recursive pattern, the folders `A` and
136-
`A/B` are added as parent patterns. The resulting sparse-checkout file is
137-
now
143+
144+
When in cone mode, the `git sparse-checkout set` subcommand takes a list of
145+
directories instead of a list of sparse-checkout patterns. In this mode,
146+
the command `git sparse-checkout set A/B/C` sets the directory `A/B/C` as
147+
a recursive pattern, the directories `A` and `A/B` are added as parent
148+
patterns. The resulting sparse-checkout file is now
138149

139150
----------------
140151
/*

builtin/clone.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1129,7 +1129,7 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
11291129
if (option_required_reference.nr || option_optional_reference.nr)
11301130
setup_reference();
11311131

1132-
if (option_sparse_checkout && git_sparse_checkout_init(repo))
1132+
if (option_sparse_checkout && git_sparse_checkout_init(dir))
11331133
return 1;
11341134

11351135
remote = remote_get(option_origin);

builtin/sparse-checkout.c

Lines changed: 43 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
#include "resolve-undo.h"
1414
#include "unpack-trees.h"
1515
#include "wt-status.h"
16+
#include "quote.h"
1617

1718
static const char *empty_base = "";
1819

@@ -77,8 +78,10 @@ static int sparse_checkout_list(int argc, const char **argv)
7778

7879
string_list_sort(&sl);
7980

80-
for (i = 0; i < sl.nr; i++)
81-
printf("%s\n", sl.items[i].string);
81+
for (i = 0; i < sl.nr; i++) {
82+
quote_c_style(sl.items[i].string, NULL, stdout, 0);
83+
printf("\n");
84+
}
8285

8386
return 0;
8487
}
@@ -140,6 +143,22 @@ static int update_working_directory(struct pattern_list *pl)
140143
return result;
141144
}
142145

146+
static char *escaped_pattern(char *pattern)
147+
{
148+
char *p = pattern;
149+
struct strbuf final = STRBUF_INIT;
150+
151+
while (*p) {
152+
if (is_glob_special(*p))
153+
strbuf_addch(&final, '\\');
154+
155+
strbuf_addch(&final, *p);
156+
p++;
157+
}
158+
159+
return strbuf_detach(&final, NULL);
160+
}
161+
143162
static void write_cone_to_file(FILE *fp, struct pattern_list *pl)
144163
{
145164
int i;
@@ -164,10 +183,11 @@ static void write_cone_to_file(FILE *fp, struct pattern_list *pl)
164183
fprintf(fp, "/*\n!/*/\n");
165184

166185
for (i = 0; i < sl.nr; i++) {
167-
char *pattern = sl.items[i].string;
186+
char *pattern = escaped_pattern(sl.items[i].string);
168187

169188
if (strlen(pattern))
170189
fprintf(fp, "%s/\n!%s/*/\n", pattern, pattern);
190+
free(pattern);
171191
}
172192

173193
string_list_clear(&sl, 0);
@@ -185,8 +205,9 @@ static void write_cone_to_file(FILE *fp, struct pattern_list *pl)
185205
string_list_remove_duplicates(&sl, 0);
186206

187207
for (i = 0; i < sl.nr; i++) {
188-
char *pattern = sl.items[i].string;
208+
char *pattern = escaped_pattern(sl.items[i].string);
189209
fprintf(fp, "%s/\n", pattern);
210+
free(pattern);
190211
}
191212
}
192213

@@ -199,6 +220,10 @@ static int write_patterns_and_update(struct pattern_list *pl)
199220
int result;
200221

201222
sparse_filename = get_sparse_checkout_filename();
223+
224+
if (safe_create_leading_directories(sparse_filename))
225+
die(_("failed to create directory for sparse-checkout file"));
226+
202227
fd = hold_lock_file_for_update(&lk, sparse_filename,
203228
LOCK_DIE_ON_ERROR);
204229

@@ -419,8 +444,21 @@ static int sparse_checkout_set(int argc, const char **argv, const char *prefix)
419444
pl.use_cone_patterns = 1;
420445

421446
if (set_opts.use_stdin) {
422-
while (!strbuf_getline(&line, stdin))
447+
struct strbuf unquoted = STRBUF_INIT;
448+
while (!strbuf_getline(&line, stdin)) {
449+
if (line.buf[0] == '"') {
450+
strbuf_reset(&unquoted);
451+
if (unquote_c_style(&unquoted, line.buf, NULL))
452+
die(_("unable to unquote C-style string '%s'"),
453+
line.buf);
454+
455+
strbuf_swap(&unquoted, &line);
456+
}
457+
423458
strbuf_to_cone_pattern(&line, &pl);
459+
}
460+
461+
strbuf_release(&unquoted);
424462
} else {
425463
for (i = 0; i < argc; i++) {
426464
strbuf_setlen(&line, 0);

dir.c

Lines changed: 75 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -636,11 +636,42 @@ int pl_hashmap_cmp(const void *unused_cmp_data,
636636
return strncmp(ee1->pattern, ee2->pattern, min_len);
637637
}
638638

639+
static char *dup_and_filter_pattern(const char *pattern)
640+
{
641+
char *set, *read;
642+
size_t count = 0;
643+
char *result = xstrdup(pattern);
644+
645+
set = result;
646+
read = result;
647+
648+
while (*read) {
649+
/* skip escape characters (once) */
650+
if (*read == '\\')
651+
read++;
652+
653+
*set = *read;
654+
655+
set++;
656+
read++;
657+
count++;
658+
}
659+
*set = 0;
660+
661+
if (count > 2 &&
662+
*(set - 1) == '*' &&
663+
*(set - 2) == '/')
664+
*(set - 2) = 0;
665+
666+
return result;
667+
}
668+
639669
static void add_pattern_to_hashsets(struct pattern_list *pl, struct path_pattern *given)
640670
{
641671
struct pattern_entry *translated;
642672
char *truncated;
643673
char *data = NULL;
674+
const char *prev, *cur, *next;
644675

645676
if (!pl->use_cone_patterns)
646677
return;
@@ -657,17 +688,57 @@ static void add_pattern_to_hashsets(struct pattern_list *pl, struct path_pattern
657688
return;
658689
}
659690

691+
if (given->patternlen <= 2 ||
692+
*given->pattern == '*' ||
693+
strstr(given->pattern, "**")) {
694+
/* Not a cone pattern. */
695+
warning(_("unrecognized pattern: '%s'"), given->pattern);
696+
goto clear_hashmaps;
697+
}
698+
699+
prev = given->pattern;
700+
cur = given->pattern + 1;
701+
next = given->pattern + 2;
702+
703+
while (*cur) {
704+
/* Watch for glob characters '*', '\', '[', '?' */
705+
if (!is_glob_special(*cur))
706+
goto increment;
707+
708+
/* But only if *prev != '\\' */
709+
if (*prev == '\\')
710+
goto increment;
711+
712+
/* But allow the initial '\' */
713+
if (*cur == '\\' &&
714+
is_glob_special(*next))
715+
goto increment;
716+
717+
/* But a trailing '/' then '*' is fine */
718+
if (*prev == '/' &&
719+
*cur == '*' &&
720+
*next == 0)
721+
goto increment;
722+
723+
/* Not a cone pattern. */
724+
warning(_("unrecognized pattern: '%s'"), given->pattern);
725+
goto clear_hashmaps;
726+
727+
increment:
728+
prev++;
729+
cur++;
730+
next++;
731+
}
732+
660733
if (given->patternlen > 2 &&
661734
!strcmp(given->pattern + given->patternlen - 2, "/*")) {
662735
if (!(given->flags & PATTERN_FLAG_NEGATIVE)) {
663736
/* Not a cone pattern. */
664-
pl->use_cone_patterns = 0;
665737
warning(_("unrecognized pattern: '%s'"), given->pattern);
666738
goto clear_hashmaps;
667739
}
668740

669-
truncated = xstrdup(given->pattern);
670-
truncated[given->patternlen - 2] = 0;
741+
truncated = dup_and_filter_pattern(given->pattern);
671742

672743
translated = xmalloc(sizeof(struct pattern_entry));
673744
translated->pattern = truncated;
@@ -701,7 +772,7 @@ static void add_pattern_to_hashsets(struct pattern_list *pl, struct path_pattern
701772

702773
translated = xmalloc(sizeof(struct pattern_entry));
703774

704-
translated->pattern = xstrdup(given->pattern);
775+
translated->pattern = dup_and_filter_pattern(given->pattern);
705776
translated->patternlen = given->patternlen;
706777
hashmap_entry_init(&translated->ent,
707778
ignore_case ?

0 commit comments

Comments
 (0)