diff --git a/builtin/sparse-checkout.c b/builtin/sparse-checkout.c index 601cf9dc278d18..61bc430f761adb 100644 --- a/builtin/sparse-checkout.c +++ b/builtin/sparse-checkout.c @@ -428,14 +428,26 @@ static int sparse_checkout_init(int argc, const char **argv) } else mode = MODE_ALL_PATTERNS; - if (set_config(mode)) - return 1; - memset(&pl, 0, sizeof(pl)); + pl.use_cone_patterns = core_sparse_checkout_cone; sparse_filename = get_sparse_checkout_filename(); res = add_patterns_from_file_to_list(sparse_filename, "", 0, &pl, NULL, 0); + /* + * If res >= 0, file already exists. If in cone mode init, verify that the + * patterns are cone mode-compatible (if applicable). Otherwise, fall back + * on non-cone mode sparse checkout. + */ + if (res >= 0 && core_sparse_checkout_cone && !pl.use_cone_patterns) { + warning(_("unable to initialize from existing patterns; disabling cone mode")); + mode = MODE_ALL_PATTERNS; + core_sparse_checkout_cone = 0; + } + + if (set_config(mode)) + return 1; + if (init_opts.sparse_index >= 0) { if (set_sparse_index_config(the_repository, init_opts.sparse_index) < 0) die(_("failed to modify sparse-index config")); @@ -601,13 +613,21 @@ static void add_patterns_cone_mode(int argc, const char **argv, add_patterns_from_input(pl, argc, argv); memset(&existing, 0, sizeof(existing)); - existing.use_cone_patterns = core_sparse_checkout_cone; + existing.use_cone_patterns = 1; if (add_patterns_from_file_to_list(sparse_filename, "", 0, &existing, NULL, 0)) die(_("unable to load existing sparse-checkout patterns")); free(sparse_filename); + /* + * If use_cone_patterns has been disabled, at least one of the existing + * patterns is invalid for cone mode. In that case, the hashmap does not + * correctly reflect patterns, so we must exit early. + */ + if (existing.use_cone_patterns == 0) + die(_("unable to use existing sparse-checkout patterns in cone mode")); + hashmap_for_each_entry(&existing.recursive_hashmap, &iter, pe, ent) { if (!hashmap_contains_parent(&pl->recursive_hashmap, pe->pattern, &buffer) || diff --git a/dir.c b/dir.c index ed3b06da64eed9..fffefd669c9153 100644 --- a/dir.c +++ b/dir.c @@ -715,6 +715,11 @@ static void add_pattern_to_hashsets(struct pattern_list *pl, struct path_pattern if (!pl->use_cone_patterns) return; + if (*given->pattern != '/') { + warning(_("unrecognized pattern: '%s'"), given->pattern); + goto clear_hashmaps; + } + if (given->flags & PATTERN_FLAG_NEGATIVE && given->flags & PATTERN_FLAG_MUSTBEDIR && !strcmp(given->pattern, "/*")) { diff --git a/t/t1091-sparse-checkout-builtin.sh b/t/t1091-sparse-checkout-builtin.sh index 8f54e69502e2be..2789b21c18d385 100755 --- a/t/t1091-sparse-checkout-builtin.sh +++ b/t/t1091-sparse-checkout-builtin.sh @@ -191,7 +191,7 @@ test_expect_success 'cone mode: match patterns' ' test_expect_success 'cone mode: warn on bad pattern' ' test_when_finished mv sparse-checkout repo/.git/info/ && cp repo/.git/info/sparse-checkout . && - echo "!/deep/deeper/*" >>repo/.git/info/sparse-checkout && + echo "!/deep/deeper/*" >repo/.git/info/sparse-checkout && git -C repo read-tree -mu HEAD 2>err && test_i18ngrep "unrecognized negative pattern" err ' @@ -573,7 +573,7 @@ test_expect_success 'pattern-checks: starting "*"' ' cat >repo/.git/info/sparse-checkout <<-\EOF && /* !/*/ - *eep/ + /*eep/ EOF check_read_tree_errors repo "a deep" "disabling cone pattern matching" ' @@ -584,12 +584,21 @@ test_expect_success 'pattern-checks: contained glob characters' ' cat >repo/.git/info/sparse-checkout <<-EOF && /* !/*/ - something$c-else/ + /something$c-else/ EOF check_read_tree_errors repo "a" "disabling cone pattern matching" done ' +test_expect_success 'pattern-checks: starting "/"' ' + cat >repo/.git/info/sparse-checkout <<-\EOF && + /* + !/*/ + deep/ + EOF + check_read_tree_errors repo "a deep" "disabling cone pattern matching" +' + test_expect_success BSLASHPSPEC 'pattern-checks: escaped characters' ' git clone repo escaped && TREEOID=$(git -C escaped rev-parse HEAD:folder1) && @@ -712,4 +721,61 @@ test_expect_success 'cone mode clears ignored subdirectories' ' test_cmp expect out ' +test_expect_success 'init with cone mode verifies existing cone patterns' ' + rm -f repo/.git/info/sparse-checkout && + + # Set non-cone mode pattern + git -C repo sparse-checkout init && + git -C repo sparse-checkout set deep/deeper*/ && + git -C repo sparse-checkout disable && + + git -C repo sparse-checkout init --cone 2>err && + test_i18ngrep "disabling cone mode" err && + test_must_fail git -C repo config core.sparsecheckoutcone +' + +test_expect_success 'add with cone mode verifies existing cone patterns' ' + rm -f repo/.git/info/sparse-checkout && + + git -C repo sparse-checkout init --cone && + cat >repo/.git/info/sparse-checkout <<-\EOF && + /* + !/*/ + /deep/deeper*/ + EOF + + test_must_fail git -C repo sparse-checkout add folder1 2>err && + test_i18ngrep "unable to use existing sparse-checkout patterns in cone mode" err +' + +# NEEDSWORK: in the case of directory patterns like `deep/`, it might be worth trying +# to "correct" the patterns to match a cone mode style. However, that may be more difficult +# for nested directories (like `deep/deeper1/`) in which multiple individual patterns +# would be mapped from the original (`/deep/`, `!/deep/*/`, `/deep/deeper1/`). +test_expect_success 'add cone pattern disallowed with existing non-cone directory pattern' ' + rm -f repo/.git/info/sparse-checkout && + + git -C repo sparse-checkout init --cone && + + # Manually set the sparse checkout pattern to a directory pattern + # without preceding slash + cat >repo/.git/info/sparse-checkout <<-\EOF && + deep/ + EOF + + # `add` fails because `deep/` is not a valid cone pattern. + test_must_fail git -C repo sparse-checkout add folder1/ 2>err && + test_i18ngrep "unable to use existing sparse-checkout patterns in cone mode" err && + + # `set` succeeds with same patterns set properly for cone mode. + git -C repo sparse-checkout set deep/ folder1/ && + cat >expect <<-\EOF && + /* + !/*/ + /deep/ + /folder1/ + EOF + test_cmp expect repo/.git/info/sparse-checkout +' + test_done