Skip to content

Commit 9671764

Browse files
committed
Merge branch 'en/sparse-checkout-fixes'
Further polishing of "git sparse-checkout". * en/sparse-checkout-fixes: sparse-checkout: reject arguments in cone-mode that look like patterns sparse-checkout: error or warn when given individual files sparse-checkout: pay attention to prefix for {set, add} sparse-checkout: correctly set non-cone mode when expected sparse-checkout: correct reapply's handling of options
2 parents b6c596f + 8dd7c47 commit 9671764

File tree

2 files changed

+161
-7
lines changed

2 files changed

+161
-7
lines changed

builtin/sparse-checkout.c

Lines changed: 73 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
#include "builtin.h"
2+
#include "cache.h"
23
#include "config.h"
34
#include "dir.h"
45
#include "parse-options.h"
@@ -401,6 +402,7 @@ static int update_modes(int *cone_mode, int *sparse_index)
401402
core_sparse_checkout_cone = 1;
402403
} else {
403404
mode = MODE_ALL_PATTERNS;
405+
core_sparse_checkout_cone = 0;
404406
}
405407
if (record_mode && set_config(mode))
406408
return 1;
@@ -681,18 +683,76 @@ static int modify_pattern_list(int argc, const char **argv, int use_stdin,
681683
return result;
682684
}
683685

686+
static void sanitize_paths(int argc, const char **argv,
687+
const char *prefix, int skip_checks)
688+
{
689+
int i;
690+
691+
if (!argc)
692+
return;
693+
694+
if (prefix && *prefix && core_sparse_checkout_cone) {
695+
/*
696+
* The args are not pathspecs, so unfortunately we
697+
* cannot imitate how cmd_add() uses parse_pathspec().
698+
*/
699+
int prefix_len = strlen(prefix);
700+
701+
for (i = 0; i < argc; i++)
702+
argv[i] = prefix_path(prefix, prefix_len, argv[i]);
703+
}
704+
705+
if (skip_checks)
706+
return;
707+
708+
if (prefix && *prefix && !core_sparse_checkout_cone)
709+
die(_("please run from the toplevel directory in non-cone mode"));
710+
711+
if (core_sparse_checkout_cone) {
712+
for (i = 0; i < argc; i++) {
713+
if (argv[i][0] == '/')
714+
die(_("specify directories rather than patterns (no leading slash)"));
715+
if (argv[i][0] == '!')
716+
die(_("specify directories rather than patterns. If your directory starts with a '!', pass --skip-checks"));
717+
if (strpbrk(argv[i], "*?[]"))
718+
die(_("specify directories rather than patterns. If your directory really has any of '*?[]\\' in it, pass --skip-checks"));
719+
}
720+
}
721+
722+
for (i = 0; i < argc; i++) {
723+
struct cache_entry *ce;
724+
struct index_state *index = the_repository->index;
725+
int pos = index_name_pos(index, argv[i], strlen(argv[i]));
726+
727+
if (pos < 0)
728+
continue;
729+
ce = index->cache[pos];
730+
if (S_ISSPARSEDIR(ce->ce_mode))
731+
continue;
732+
733+
if (core_sparse_checkout_cone)
734+
die(_("'%s' is not a directory; to treat it as a directory anyway, rerun with --skip-checks"), argv[i]);
735+
else
736+
warning(_("pass a leading slash before paths such as '%s' if you want a single file (see NON-CONE PROBLEMS in the git-sparse-checkout manual)."), argv[i]);
737+
}
738+
}
739+
684740
static char const * const builtin_sparse_checkout_add_usage[] = {
685-
N_("git sparse-checkout add (--stdin | <patterns>)"),
741+
N_("git sparse-checkout add [--skip-checks] (--stdin | <patterns>)"),
686742
NULL
687743
};
688744

689745
static struct sparse_checkout_add_opts {
746+
int skip_checks;
690747
int use_stdin;
691748
} add_opts;
692749

693750
static int sparse_checkout_add(int argc, const char **argv, const char *prefix)
694751
{
695752
static struct option builtin_sparse_checkout_add_options[] = {
753+
OPT_BOOL_F(0, "skip-checks", &add_opts.skip_checks,
754+
N_("skip some sanity checks on the given paths that might give false positives"),
755+
PARSE_OPT_NONEG),
696756
OPT_BOOL(0, "stdin", &add_opts.use_stdin,
697757
N_("read patterns from standard in")),
698758
OPT_END(),
@@ -708,17 +768,20 @@ static int sparse_checkout_add(int argc, const char **argv, const char *prefix)
708768
builtin_sparse_checkout_add_usage,
709769
PARSE_OPT_KEEP_UNKNOWN);
710770

771+
sanitize_paths(argc, argv, prefix, add_opts.skip_checks);
772+
711773
return modify_pattern_list(argc, argv, add_opts.use_stdin, ADD);
712774
}
713775

714776
static char const * const builtin_sparse_checkout_set_usage[] = {
715-
N_("git sparse-checkout set [--[no-]cone] [--[no-]sparse-index] (--stdin | <patterns>)"),
777+
N_("git sparse-checkout set [--[no-]cone] [--[no-]sparse-index] [--skip-checks] (--stdin | <patterns>)"),
716778
NULL
717779
};
718780

719781
static struct sparse_checkout_set_opts {
720782
int cone_mode;
721783
int sparse_index;
784+
int skip_checks;
722785
int use_stdin;
723786
} set_opts;
724787

@@ -732,6 +795,9 @@ static int sparse_checkout_set(int argc, const char **argv, const char *prefix)
732795
N_("initialize the sparse-checkout in cone mode")),
733796
OPT_BOOL(0, "sparse-index", &set_opts.sparse_index,
734797
N_("toggle the use of a sparse index")),
798+
OPT_BOOL_F(0, "skip-checks", &set_opts.skip_checks,
799+
N_("skip some sanity checks on the given paths that might give false positives"),
800+
PARSE_OPT_NONEG),
735801
OPT_BOOL_F(0, "stdin", &set_opts.use_stdin,
736802
N_("read patterns from standard in"),
737803
PARSE_OPT_NONEG),
@@ -759,6 +825,8 @@ static int sparse_checkout_set(int argc, const char **argv, const char *prefix)
759825
if (!core_sparse_checkout_cone && argc == 0) {
760826
argv = default_patterns;
761827
argc = default_patterns_nr;
828+
} else {
829+
sanitize_paths(argc, argv, prefix, set_opts.skip_checks);
762830
}
763831

764832
return modify_pattern_list(argc, argv, set_opts.use_stdin, REPLACE);
@@ -787,15 +855,15 @@ static int sparse_checkout_reapply(int argc, const char **argv)
787855
if (!core_apply_sparse_checkout)
788856
die(_("must be in a sparse-checkout to reapply sparsity patterns"));
789857

858+
reapply_opts.cone_mode = -1;
859+
reapply_opts.sparse_index = -1;
860+
790861
argc = parse_options(argc, argv, NULL,
791862
builtin_sparse_checkout_reapply_options,
792863
builtin_sparse_checkout_reapply_usage, 0);
793864

794865
repo_read_index(the_repository);
795866

796-
reapply_opts.cone_mode = -1;
797-
reapply_opts.sparse_index = -1;
798-
799867
if (update_modes(&reapply_opts.cone_mode, &reapply_opts.sparse_index))
800868
return 1;
801869

t/t1091-sparse-checkout-builtin.sh

Lines changed: 88 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -510,6 +510,37 @@ test_expect_failure 'sparse-checkout reapply' '
510510
git -C tweak sparse-checkout disable
511511
'
512512

513+
test_expect_success 'reapply can handle config options' '
514+
git -C repo sparse-checkout init --cone --no-sparse-index &&
515+
git -C repo config --worktree --list >actual &&
516+
cat >expect <<-\EOF &&
517+
core.sparsecheckout=true
518+
core.sparsecheckoutcone=true
519+
index.sparse=false
520+
EOF
521+
test_cmp expect actual &&
522+
523+
git -C repo sparse-checkout reapply --no-cone --no-sparse-index &&
524+
git -C repo config --worktree --list >actual &&
525+
cat >expect <<-\EOF &&
526+
core.sparsecheckout=true
527+
core.sparsecheckoutcone=false
528+
index.sparse=false
529+
EOF
530+
test_cmp expect actual &&
531+
532+
git -C repo sparse-checkout reapply --cone --sparse-index &&
533+
git -C repo config --worktree --list >actual &&
534+
cat >expect <<-\EOF &&
535+
core.sparsecheckout=true
536+
core.sparsecheckoutcone=true
537+
index.sparse=true
538+
EOF
539+
test_cmp expect actual &&
540+
541+
git -C repo sparse-checkout disable
542+
'
543+
513544
test_expect_success 'cone mode: set with core.ignoreCase=true' '
514545
rm repo/.git/info/sparse-checkout &&
515546
git -C repo sparse-checkout init --cone &&
@@ -549,7 +580,7 @@ test_expect_success 'different sparse-checkouts with worktrees' '
549580
'
550581

551582
test_expect_success 'set using filename keeps file on-disk' '
552-
git -C repo sparse-checkout set a deep &&
583+
git -C repo sparse-checkout set --skip-checks a deep &&
553584
cat >expect <<-\EOF &&
554585
/*
555586
!/*/
@@ -660,7 +691,7 @@ test_expect_success BSLASHPSPEC 'pattern-checks: escaped characters' '
660691
git -C escaped reset --hard $COMMIT &&
661692
check_files escaped "a deep folder1 folder2 zbad\\dir zdoes*exist" zglob[!a]? &&
662693
git -C escaped sparse-checkout init --cone &&
663-
git -C escaped sparse-checkout set zbad\\dir/bogus "zdoes*not*exist" "zdoes*exist" "zglob[!a]?" &&
694+
git -C escaped sparse-checkout set --skip-checks zbad\\dir/bogus "zdoes*not*exist" "zdoes*exist" "zglob[!a]?" &&
664695
cat >expect <<-\EOF &&
665696
/*
666697
!/*/
@@ -785,4 +816,59 @@ test_expect_success 'malformed cone-mode patterns' '
785816
grep "warning: disabling cone pattern matching" err
786817
'
787818

819+
test_expect_success 'set from subdir pays attention to prefix' '
820+
git -C repo sparse-checkout disable &&
821+
git -C repo/deep sparse-checkout set --cone deeper2 ../folder1 &&
822+
823+
git -C repo sparse-checkout list >actual &&
824+
825+
cat >expect <<-\EOF &&
826+
deep/deeper2
827+
folder1
828+
EOF
829+
test_cmp expect actual
830+
'
831+
832+
test_expect_success 'add from subdir pays attention to prefix' '
833+
git -C repo sparse-checkout set --cone deep/deeper2 &&
834+
git -C repo/deep sparse-checkout add deeper1/deepest ../folder1 &&
835+
836+
git -C repo sparse-checkout list >actual &&
837+
838+
cat >expect <<-\EOF &&
839+
deep/deeper1/deepest
840+
deep/deeper2
841+
folder1
842+
EOF
843+
test_cmp expect actual
844+
'
845+
846+
test_expect_success 'set from subdir in non-cone mode throws an error' '
847+
git -C repo sparse-checkout disable &&
848+
test_must_fail git -C repo/deep sparse-checkout set --no-cone deeper2 ../folder1 2>error &&
849+
850+
grep "run from the toplevel directory in non-cone mode" error
851+
'
852+
853+
test_expect_success 'set from subdir in non-cone mode throws an error' '
854+
git -C repo sparse-checkout set --no-cone deep/deeper2 &&
855+
test_must_fail git -C repo/deep sparse-checkout add deeper1/deepest ../folder1 2>error &&
856+
857+
grep "run from the toplevel directory in non-cone mode" error
858+
'
859+
860+
test_expect_success 'by default, cone mode will error out when passed files' '
861+
git -C repo sparse-checkout reapply --cone &&
862+
test_must_fail git -C repo sparse-checkout add .gitignore 2>error &&
863+
864+
grep ".gitignore.*is not a directory" error
865+
'
866+
867+
test_expect_success 'by default, non-cone mode will warn on individual files' '
868+
git -C repo sparse-checkout reapply --no-cone &&
869+
git -C repo sparse-checkout add .gitignore 2>warning &&
870+
871+
grep "pass a leading slash before paths.*if you want a single file" warning
872+
'
873+
788874
test_done

0 commit comments

Comments
 (0)