Skip to content

Commit 3fc2e37

Browse files
committed
Merge branch 'ds/sparse-add' into jch
"git sparse-checkout" learned a new "add" subcommand. * ds/sparse-add: sparse-checkout: allow one-character directories in cone mode sparse-checkout: work with Windows paths sparse-checkout: create 'add' subcommand sparse-checkout: extract pattern update from 'set' subcommand sparse-checkout: extract add_patterns_from_input()
2 parents 2d2118b + 6c11c6a commit 3fc2e37

File tree

4 files changed

+201
-34
lines changed

4 files changed

+201
-34
lines changed

Documentation/git-sparse-checkout.txt

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,13 @@ directories. The input format matches the output of `git ls-tree --name-only`.
6363
This includes interpreting pathnames that begin with a double quote (") as
6464
C-style quoted strings.
6565

66+
'add'::
67+
Update the sparse-checkout file to include additional patterns.
68+
By default, these patterns are read from the command-line arguments,
69+
but they can be read from stdin using the `--stdin` option. When
70+
`core.sparseCheckoutCone` is enabled, the given patterns are interpreted
71+
as directory names as in the 'set' subcommand.
72+
6673
'disable'::
6774
Disable the `core.sparseCheckout` config setting, and restore the
6875
working directory to include all files. Leaves the sparse-checkout

builtin/sparse-checkout.c

Lines changed: 109 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
static const char *empty_base = "";
1919

2020
static char const * const builtin_sparse_checkout_usage[] = {
21-
N_("git sparse-checkout (init|list|set|disable) <options>"),
21+
N_("git sparse-checkout (init|list|set|add|disable) <options>"),
2222
NULL
2323
};
2424

@@ -394,6 +394,9 @@ static void strbuf_to_cone_pattern(struct strbuf *line, struct pattern_list *pl)
394394

395395
strbuf_trim_trailing_dir_sep(line);
396396

397+
if (strbuf_normalize_path(line))
398+
die(_("could not normalize path %s"), line->buf);
399+
397400
if (!line->len)
398401
return;
399402

@@ -404,44 +407,24 @@ static void strbuf_to_cone_pattern(struct strbuf *line, struct pattern_list *pl)
404407
}
405408

406409
static char const * const builtin_sparse_checkout_set_usage[] = {
407-
N_("git sparse-checkout set (--stdin | <patterns>)"),
410+
N_("git sparse-checkout (set|add) (--stdin | <patterns>)"),
408411
NULL
409412
};
410413

411414
static struct sparse_checkout_set_opts {
412415
int use_stdin;
413416
} set_opts;
414417

415-
static int sparse_checkout_set(int argc, const char **argv, const char *prefix)
418+
static void add_patterns_from_input(struct pattern_list *pl,
419+
int argc, const char **argv)
416420
{
417421
int i;
418-
struct pattern_list pl;
419-
int result;
420-
int changed_config = 0;
421-
422-
static struct option builtin_sparse_checkout_set_options[] = {
423-
OPT_BOOL(0, "stdin", &set_opts.use_stdin,
424-
N_("read patterns from standard in")),
425-
OPT_END(),
426-
};
427-
428-
repo_read_index(the_repository);
429-
require_clean_work_tree(the_repository,
430-
N_("set sparse-checkout patterns"), NULL, 1, 0);
431-
432-
memset(&pl, 0, sizeof(pl));
433-
434-
argc = parse_options(argc, argv, prefix,
435-
builtin_sparse_checkout_set_options,
436-
builtin_sparse_checkout_set_usage,
437-
PARSE_OPT_KEEP_UNKNOWN);
438-
439422
if (core_sparse_checkout_cone) {
440423
struct strbuf line = STRBUF_INIT;
441424

442-
hashmap_init(&pl.recursive_hashmap, pl_hashmap_cmp, NULL, 0);
443-
hashmap_init(&pl.parent_hashmap, pl_hashmap_cmp, NULL, 0);
444-
pl.use_cone_patterns = 1;
425+
hashmap_init(&pl->recursive_hashmap, pl_hashmap_cmp, NULL, 0);
426+
hashmap_init(&pl->parent_hashmap, pl_hashmap_cmp, NULL, 0);
427+
pl->use_cone_patterns = 1;
445428

446429
if (set_opts.use_stdin) {
447430
struct strbuf unquoted = STRBUF_INIT;
@@ -455,15 +438,15 @@ static int sparse_checkout_set(int argc, const char **argv, const char *prefix)
455438
strbuf_swap(&unquoted, &line);
456439
}
457440

458-
strbuf_to_cone_pattern(&line, &pl);
441+
strbuf_to_cone_pattern(&line, pl);
459442
}
460443

461444
strbuf_release(&unquoted);
462445
} else {
463446
for (i = 0; i < argc; i++) {
464447
strbuf_setlen(&line, 0);
465448
strbuf_addstr(&line, argv[i]);
466-
strbuf_to_cone_pattern(&line, &pl);
449+
strbuf_to_cone_pattern(&line, pl);
467450
}
468451
}
469452
} else {
@@ -473,13 +456,84 @@ static int sparse_checkout_set(int argc, const char **argv, const char *prefix)
473456
while (!strbuf_getline(&line, stdin)) {
474457
size_t len;
475458
char *buf = strbuf_detach(&line, &len);
476-
add_pattern(buf, empty_base, 0, &pl, 0);
459+
add_pattern(buf, empty_base, 0, pl, 0);
477460
}
478461
} else {
479462
for (i = 0; i < argc; i++)
480-
add_pattern(argv[i], empty_base, 0, &pl, 0);
463+
add_pattern(argv[i], empty_base, 0, pl, 0);
481464
}
482465
}
466+
}
467+
468+
enum modify_type {
469+
REPLACE,
470+
ADD,
471+
};
472+
473+
static void add_patterns_cone_mode(int argc, const char **argv,
474+
struct pattern_list *pl)
475+
{
476+
struct strbuf buffer = STRBUF_INIT;
477+
struct pattern_entry *pe;
478+
struct hashmap_iter iter;
479+
struct pattern_list existing;
480+
char *sparse_filename = get_sparse_checkout_filename();
481+
482+
add_patterns_from_input(pl, argc, argv);
483+
484+
memset(&existing, 0, sizeof(existing));
485+
existing.use_cone_patterns = core_sparse_checkout_cone;
486+
487+
if (add_patterns_from_file_to_list(sparse_filename, "", 0,
488+
&existing, NULL))
489+
die(_("unable to load existing sparse-checkout patterns"));
490+
free(sparse_filename);
491+
492+
hashmap_for_each_entry(&existing.recursive_hashmap, &iter, pe, ent) {
493+
if (!hashmap_contains_parent(&pl->recursive_hashmap,
494+
pe->pattern, &buffer) ||
495+
!hashmap_contains_parent(&pl->parent_hashmap,
496+
pe->pattern, &buffer)) {
497+
strbuf_reset(&buffer);
498+
strbuf_addstr(&buffer, pe->pattern);
499+
insert_recursive_pattern(pl, &buffer);
500+
}
501+
}
502+
503+
clear_pattern_list(&existing);
504+
strbuf_release(&buffer);
505+
}
506+
507+
static void add_patterns_literal(int argc, const char **argv,
508+
struct pattern_list *pl)
509+
{
510+
char *sparse_filename = get_sparse_checkout_filename();
511+
if (add_patterns_from_file_to_list(sparse_filename, "", 0,
512+
pl, NULL))
513+
die(_("unable to load existing sparse-checkout patterns"));
514+
free(sparse_filename);
515+
add_patterns_from_input(pl, argc, argv);
516+
}
517+
518+
static int modify_pattern_list(int argc, const char **argv, enum modify_type m)
519+
{
520+
int result;
521+
int changed_config = 0;
522+
struct pattern_list pl;
523+
memset(&pl, 0, sizeof(pl));
524+
525+
switch (m) {
526+
case ADD:
527+
if (core_sparse_checkout_cone)
528+
add_patterns_cone_mode(argc, argv, &pl);
529+
else
530+
add_patterns_literal(argc, argv, &pl);
531+
break;
532+
533+
case REPLACE:
534+
add_patterns_from_input(&pl, argc, argv);
535+
break;
536+
}
483537

484538
if (!core_apply_sparse_checkout) {
485539
set_config(MODE_ALL_PATTERNS);
@@ -496,6 +550,27 @@ static int sparse_checkout_set(int argc, const char **argv, const char *prefix)
496550
return result;
497551
}
498552

553+
static int sparse_checkout_set(int argc, const char **argv, const char *prefix,
554+
enum modify_type m)
555+
{
556+
static struct option builtin_sparse_checkout_set_options[] = {
557+
OPT_BOOL(0, "stdin", &set_opts.use_stdin,
558+
N_("read patterns from standard in")),
559+
OPT_END(),
560+
};
561+
562+
repo_read_index(the_repository);
563+
require_clean_work_tree(the_repository,
564+
N_("set sparse-checkout patterns"), NULL, 1, 0);
565+
566+
argc = parse_options(argc, argv, prefix,
567+
builtin_sparse_checkout_set_options,
568+
builtin_sparse_checkout_set_usage,
569+
PARSE_OPT_KEEP_UNKNOWN);
570+
571+
return modify_pattern_list(argc, argv, m);
572+
}
573+
499574
static int sparse_checkout_disable(int argc, const char **argv)
500575
{
501576
struct pattern_list pl;
@@ -544,7 +619,9 @@ int cmd_sparse_checkout(int argc, const char **argv, const char *prefix)
544619
if (!strcmp(argv[0], "init"))
545620
return sparse_checkout_init(argc, argv);
546621
if (!strcmp(argv[0], "set"))
547-
return sparse_checkout_set(argc, argv, prefix);
622+
return sparse_checkout_set(argc, argv, prefix, REPLACE);
623+
if (!strcmp(argv[0], "add"))
624+
return sparse_checkout_set(argc, argv, prefix, ADD);
548625
if (!strcmp(argv[0], "disable"))
549626
return sparse_checkout_disable(argc, argv);
550627
}

dir.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -688,7 +688,7 @@ static void add_pattern_to_hashsets(struct pattern_list *pl, struct path_pattern
688688
return;
689689
}
690690

691-
if (given->patternlen <= 2 ||
691+
if (given->patternlen < 2 ||
692692
*given->pattern == '*' ||
693693
strstr(given->pattern, "**")) {
694694
/* Not a cone pattern. */

t/t1091-sparse-checkout-builtin.sh

Lines changed: 84 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,21 @@ test_expect_success 'set sparse-checkout using --stdin' '
141141
check_files repo "a folder1 folder2"
142142
'
143143

144+
test_expect_success 'add to sparse-checkout' '
145+
cat repo/.git/info/sparse-checkout >expect &&
146+
cat >add <<-\EOF &&
147+
pattern1
148+
/folder1/
149+
pattern2
150+
EOF
151+
cat add >>expect &&
152+
git -C repo sparse-checkout add --stdin <add &&
153+
git -C repo sparse-checkout list >actual &&
154+
test_cmp expect actual &&
155+
test_cmp expect repo/.git/info/sparse-checkout &&
156+
check_files repo "a folder1 folder2"
157+
'
158+
144159
test_expect_success 'cone mode: match patterns' '
145160
git -C repo config --worktree core.sparseCheckoutCone true &&
146161
rm -rf repo/a repo/folder1 repo/folder2 &&
@@ -219,8 +234,52 @@ test_expect_success 'cone mode: set with nested folders' '
219234
test_cmp repo/.git/info/sparse-checkout expect
220235
'
221236

237+
test_expect_success 'cone mode: add independent path' '
238+
git -C repo sparse-checkout set deep/deeper1 &&
239+
git -C repo sparse-checkout add folder1 &&
240+
cat >expect <<-\EOF &&
241+
/*
242+
!/*/
243+
/deep/
244+
!/deep/*/
245+
/deep/deeper1/
246+
/folder1/
247+
EOF
248+
test_cmp expect repo/.git/info/sparse-checkout &&
249+
check_files repo a deep folder1
250+
'
251+
252+
test_expect_success 'cone mode: add sibling path' '
253+
git -C repo sparse-checkout set deep/deeper1 &&
254+
git -C repo sparse-checkout add deep/deeper2 &&
255+
cat >expect <<-\EOF &&
256+
/*
257+
!/*/
258+
/deep/
259+
!/deep/*/
260+
/deep/deeper1/
261+
/deep/deeper2/
262+
EOF
263+
test_cmp expect repo/.git/info/sparse-checkout &&
264+
check_files repo a deep
265+
'
266+
267+
test_expect_success 'cone mode: add parent path' '
268+
git -C repo sparse-checkout set deep/deeper1 folder1 &&
269+
git -C repo sparse-checkout add deep &&
270+
cat >expect <<-\EOF &&
271+
/*
272+
!/*/
273+
/deep/
274+
/folder1/
275+
EOF
276+
test_cmp expect repo/.git/info/sparse-checkout &&
277+
check_files repo a deep folder1
278+
'
279+
222280
test_expect_success 'revert to old sparse-checkout on bad update' '
223281
test_when_finished git -C repo reset --hard &&
282+
git -C repo sparse-checkout set deep &&
224283
echo update >repo/deep/deeper2/a &&
225284
cp repo/.git/info/sparse-checkout expect &&
226285
test_must_fail git -C repo sparse-checkout set deep/deeper1 2>err &&
@@ -358,10 +417,20 @@ test_expect_success 'pattern-checks: too short' '
358417
cat >repo/.git/info/sparse-checkout <<-\EOF &&
359418
/*
360419
!/*/
361-
/a
420+
/
362421
EOF
363422
check_read_tree_errors repo "a" "disabling cone pattern matching"
364423
'
424+
test_expect_success 'pattern-checks: not too short' '
425+
cat >repo/.git/info/sparse-checkout <<-\EOF &&
426+
/*
427+
!/*/
428+
/b/
429+
EOF
430+
git -C repo read-tree -mu HEAD 2>err &&
431+
test_must_be_empty err &&
432+
check_files repo a
433+
'
365434

366435
test_expect_success 'pattern-checks: trailing "*"' '
367436
cat >repo/.git/info/sparse-checkout <<-\EOF &&
@@ -438,4 +507,18 @@ test_expect_success BSLASHPSPEC 'pattern-checks: escaped characters' '
438507
test_cmp list-expect list-actual
439508
'
440509

510+
test_expect_success MINGW 'cone mode replaces backslashes with slashes' '
511+
git -C repo sparse-checkout set deep\\deeper1 &&
512+
cat >expect <<-\EOF &&
513+
/*
514+
!/*/
515+
/deep/
516+
!/deep/*/
517+
/deep/deeper1/
518+
EOF
519+
test_cmp expect repo/.git/info/sparse-checkout &&
520+
check_files repo a deep &&
521+
check_files repo/deep a deeper1
522+
'
523+
441524
test_done

0 commit comments

Comments
 (0)