Skip to content

Commit b652833

Browse files
committed
Merge branch 'ds/sparse-checkout-harden' into pu
Some rough edges in the sparse-checkout feature, especially around the cone mode, have been cleaned up. at v2. * ds/sparse-checkout-harden: sparse-checkout: improve docs around 'set' in cone mode sparse-checkout: use C-style quotes in 'list' subcommand sparse-checkout: write escaped patterns in cone mode sparse-checkout: properly match escaped characters sparse-checkout: warn on incorrect '*' in 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 d9a1c11 + 4fa635b commit b652833

File tree

6 files changed

+317
-155
lines changed

6 files changed

+317
-155
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
@@ -1128,7 +1128,7 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
11281128
if (option_required_reference.nr || option_optional_reference.nr)
11291129
setup_reference();
11301130

1131-
if (option_sparse_checkout && git_sparse_checkout_init(repo))
1131+
if (option_sparse_checkout && git_sparse_checkout_init(dir))
11321132
return 1;
11331133

11341134
remote = remote_get(option_origin);

builtin/sparse-checkout.c

Lines changed: 54 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 (*p == '*' || *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

@@ -333,7 +358,9 @@ static void insert_recursive_pattern(struct pattern_list *pl, struct strbuf *pat
333358
{
334359
struct pattern_entry *e = xmalloc(sizeof(*e));
335360
e->patternlen = path->len;
336-
e->pattern = strbuf_detach(path, NULL);
361+
e->pattern = dup_and_filter_pattern(path->buf);
362+
strbuf_release(path);
363+
337364
hashmap_entry_init(&e->ent,
338365
ignore_case ?
339366
strihash(e->pattern) :
@@ -365,13 +392,35 @@ static void insert_recursive_pattern(struct pattern_list *pl, struct strbuf *pat
365392

366393
static void strbuf_to_cone_pattern(struct strbuf *line, struct pattern_list *pl)
367394
{
395+
int i;
368396
strbuf_trim(line);
369397

370398
strbuf_trim_trailing_dir_sep(line);
371399

372400
if (!line->len)
373401
return;
374402

403+
for (i = 0; i < line->len; i++) {
404+
if (line->buf[i] == '*') {
405+
strbuf_insert(line, i, "\\", 1);
406+
i++;
407+
}
408+
409+
if (line->buf[i] == '\\') {
410+
if (i < line->len - 1 && line->buf[i + 1] == '\\')
411+
i++;
412+
else
413+
strbuf_insert(line, i, "\\", 1);
414+
415+
i++;
416+
}
417+
}
418+
419+
if (line->buf[0] == '"' && line->buf[line->len - 1] == '"') {
420+
strbuf_remove(line, 0, 1);
421+
strbuf_remove(line, line->len - 1, 1);
422+
}
423+
375424
if (line->buf[0] != '/')
376425
strbuf_insert(line, 0, "/", 1);
377426

dir.c

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

639+
char *dup_and_filter_pattern(const char *pattern)
640+
{
641+
char *set, *read;
642+
char *result = xstrdup(pattern);
643+
644+
set = result;
645+
read = result;
646+
647+
while (*read) {
648+
/* skip escape characters (once) */
649+
if (*read == '\\')
650+
read++;
651+
652+
*set = *read;
653+
654+
set++;
655+
read++;
656+
}
657+
*set = 0;
658+
659+
if (*(read - 2) == '/' && *(read - 1) == '*')
660+
*(read - 2) = 0;
661+
662+
return result;
663+
}
664+
639665
static void add_pattern_to_hashsets(struct pattern_list *pl, struct path_pattern *given)
640666
{
641667
struct pattern_entry *translated;
642668
char *truncated;
643669
char *data = NULL;
670+
const char *prev, *cur, *next;
644671

645672
if (!pl->use_cone_patterns)
646673
return;
@@ -657,17 +684,50 @@ static void add_pattern_to_hashsets(struct pattern_list *pl, struct path_pattern
657684
return;
658685
}
659686

687+
if (given->patternlen <= 2 ||
688+
*given->pattern == '*' ||
689+
strstr(given->pattern, "**")) {
690+
/* Not a cone pattern. */
691+
warning(_("unrecognized pattern: '%s'"), given->pattern);
692+
goto clear_hashmaps;
693+
}
694+
695+
prev = given->pattern;
696+
cur = given->pattern + 1;
697+
next = given->pattern + 2;
698+
699+
while (*cur) {
700+
/* We care about *cur == '*' */
701+
if (*cur != '*')
702+
goto increment;
703+
704+
/* But only if *prev != '\\' */
705+
if (*prev == '\\')
706+
goto increment;
707+
708+
/* But a trailing '/' then '*' is fine */
709+
if (*prev == '/' && *next == 0)
710+
goto increment;
711+
712+
/* Not a cone pattern. */
713+
warning(_("unrecognized pattern: '%s'"), given->pattern);
714+
goto clear_hashmaps;
715+
716+
increment:
717+
prev++;
718+
cur++;
719+
next++;
720+
}
721+
660722
if (given->patternlen > 2 &&
661723
!strcmp(given->pattern + given->patternlen - 2, "/*")) {
662724
if (!(given->flags & PATTERN_FLAG_NEGATIVE)) {
663725
/* Not a cone pattern. */
664-
pl->use_cone_patterns = 0;
665726
warning(_("unrecognized pattern: '%s'"), given->pattern);
666727
goto clear_hashmaps;
667728
}
668729

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

672732
translated = xmalloc(sizeof(struct pattern_entry));
673733
translated->pattern = truncated;
@@ -701,7 +761,7 @@ static void add_pattern_to_hashsets(struct pattern_list *pl, struct path_pattern
701761

702762
translated = xmalloc(sizeof(struct pattern_entry));
703763

704-
translated->pattern = xstrdup(given->pattern);
764+
translated->pattern = dup_and_filter_pattern(given->pattern);
705765
translated->patternlen = given->patternlen;
706766
hashmap_entry_init(&translated->ent,
707767
ignore_case ?

dir.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -413,6 +413,7 @@ int pl_hashmap_cmp(const void *unused_cmp_data,
413413
const struct hashmap_entry *a,
414414
const struct hashmap_entry *b,
415415
const void *key);
416+
char *dup_and_filter_pattern(const char *pattern);
416417
int hashmap_contains_parent(struct hashmap *map,
417418
const char *path,
418419
struct strbuf *buffer);

0 commit comments

Comments
 (0)