From 6fb705abcb6044f07954b486d71c05151262b6b6 Mon Sep 17 00:00:00 2001 From: Derrick Stolee Date: Tue, 11 Feb 2020 15:02:21 +0000 Subject: sparse-checkout: extract add_patterns_from_input() In anticipation of extending the sparse-checkout builtin with "add" and "remove" subcommands, extract the code that fills a pattern list based on the input values. The input changes depending on the presence of "--stdin" or the value of core.sparseCheckoutCone. Signed-off-by: Derrick Stolee Signed-off-by: Junio C Hamano diff --git a/builtin/sparse-checkout.c b/builtin/sparse-checkout.c index 7aeb384..41d8aaf 100644 --- a/builtin/sparse-checkout.c +++ b/builtin/sparse-checkout.c @@ -412,36 +412,16 @@ static struct sparse_checkout_set_opts { int use_stdin; } set_opts; -static int sparse_checkout_set(int argc, const char **argv, const char *prefix) +static void add_patterns_from_input(struct pattern_list *pl, + int argc, const char **argv) { int i; - struct pattern_list pl; - int result; - int changed_config = 0; - - static struct option builtin_sparse_checkout_set_options[] = { - OPT_BOOL(0, "stdin", &set_opts.use_stdin, - N_("read patterns from standard in")), - OPT_END(), - }; - - repo_read_index(the_repository); - require_clean_work_tree(the_repository, - N_("set sparse-checkout patterns"), NULL, 1, 0); - - memset(&pl, 0, sizeof(pl)); - - argc = parse_options(argc, argv, prefix, - builtin_sparse_checkout_set_options, - builtin_sparse_checkout_set_usage, - PARSE_OPT_KEEP_UNKNOWN); - if (core_sparse_checkout_cone) { struct strbuf line = STRBUF_INIT; - hashmap_init(&pl.recursive_hashmap, pl_hashmap_cmp, NULL, 0); - hashmap_init(&pl.parent_hashmap, pl_hashmap_cmp, NULL, 0); - pl.use_cone_patterns = 1; + hashmap_init(&pl->recursive_hashmap, pl_hashmap_cmp, NULL, 0); + hashmap_init(&pl->parent_hashmap, pl_hashmap_cmp, NULL, 0); + pl->use_cone_patterns = 1; if (set_opts.use_stdin) { struct strbuf unquoted = STRBUF_INIT; @@ -455,7 +435,7 @@ static int sparse_checkout_set(int argc, const char **argv, const char *prefix) strbuf_swap(&unquoted, &line); } - strbuf_to_cone_pattern(&line, &pl); + strbuf_to_cone_pattern(&line, pl); } strbuf_release(&unquoted); @@ -463,7 +443,7 @@ static int sparse_checkout_set(int argc, const char **argv, const char *prefix) for (i = 0; i < argc; i++) { strbuf_setlen(&line, 0); strbuf_addstr(&line, argv[i]); - strbuf_to_cone_pattern(&line, &pl); + strbuf_to_cone_pattern(&line, pl); } } } else { @@ -473,13 +453,39 @@ static int sparse_checkout_set(int argc, const char **argv, const char *prefix) while (!strbuf_getline(&line, stdin)) { size_t len; char *buf = strbuf_detach(&line, &len); - add_pattern(buf, empty_base, 0, &pl, 0); + add_pattern(buf, empty_base, 0, pl, 0); } } else { for (i = 0; i < argc; i++) - add_pattern(argv[i], empty_base, 0, &pl, 0); + add_pattern(argv[i], empty_base, 0, pl, 0); } } +} + +static int sparse_checkout_set(int argc, const char **argv, const char *prefix) +{ + struct pattern_list pl; + int result; + int changed_config = 0; + + static struct option builtin_sparse_checkout_set_options[] = { + OPT_BOOL(0, "stdin", &set_opts.use_stdin, + N_("read patterns from standard in")), + OPT_END(), + }; + + repo_read_index(the_repository); + require_clean_work_tree(the_repository, + N_("set sparse-checkout patterns"), NULL, 1, 0); + + memset(&pl, 0, sizeof(pl)); + + argc = parse_options(argc, argv, prefix, + builtin_sparse_checkout_set_options, + builtin_sparse_checkout_set_usage, + PARSE_OPT_KEEP_UNKNOWN); + + add_patterns_from_input(&pl, argc, argv); if (!core_apply_sparse_checkout) { set_config(MODE_ALL_PATTERNS); -- cgit v0.10.2-6-g49f6 From 4bf0c06c7169da61de489544207a7659ef31029f Mon Sep 17 00:00:00 2001 From: Derrick Stolee Date: Tue, 11 Feb 2020 15:02:22 +0000 Subject: sparse-checkout: extract pattern update from 'set' subcommand In anticipation of adding "add" and "remove" subcommands to the sparse-checkout builtin, extract a modify_pattern_list() method from the sparse_checkout_set() method. This command will read input from the command-line or stdin to construct a set of patterns, then modify the existing sparse-checkout patterns after a successful update of the working directory. Currently, the only way to modify the patterns is to replace all of the patterns. This will be extended in a later update. Signed-off-by: Derrick Stolee Signed-off-by: Junio C Hamano diff --git a/builtin/sparse-checkout.c b/builtin/sparse-checkout.c index 41d8aaf..03915dd 100644 --- a/builtin/sparse-checkout.c +++ b/builtin/sparse-checkout.c @@ -462,29 +462,17 @@ static void add_patterns_from_input(struct pattern_list *pl, } } -static int sparse_checkout_set(int argc, const char **argv, const char *prefix) +enum modify_type { + REPLACE, +}; + +static int modify_pattern_list(int argc, const char **argv, enum modify_type m) { - struct pattern_list pl; int result; int changed_config = 0; - - static struct option builtin_sparse_checkout_set_options[] = { - OPT_BOOL(0, "stdin", &set_opts.use_stdin, - N_("read patterns from standard in")), - OPT_END(), - }; - - repo_read_index(the_repository); - require_clean_work_tree(the_repository, - N_("set sparse-checkout patterns"), NULL, 1, 0); - + struct pattern_list pl; memset(&pl, 0, sizeof(pl)); - argc = parse_options(argc, argv, prefix, - builtin_sparse_checkout_set_options, - builtin_sparse_checkout_set_usage, - PARSE_OPT_KEEP_UNKNOWN); - add_patterns_from_input(&pl, argc, argv); if (!core_apply_sparse_checkout) { @@ -502,6 +490,26 @@ static int sparse_checkout_set(int argc, const char **argv, const char *prefix) return result; } +static int sparse_checkout_set(int argc, const char **argv, const char *prefix) +{ + static struct option builtin_sparse_checkout_set_options[] = { + OPT_BOOL(0, "stdin", &set_opts.use_stdin, + N_("read patterns from standard in")), + OPT_END(), + }; + + repo_read_index(the_repository); + require_clean_work_tree(the_repository, + N_("set sparse-checkout patterns"), NULL, 1, 0); + + argc = parse_options(argc, argv, prefix, + builtin_sparse_checkout_set_options, + builtin_sparse_checkout_set_usage, + PARSE_OPT_KEEP_UNKNOWN); + + return modify_pattern_list(argc, argv, REPLACE); +} + static int sparse_checkout_disable(int argc, const char **argv) { struct pattern_list pl; -- cgit v0.10.2-6-g49f6 From 2631dc879d59aa08095bc4fb5bc9bcc491a787e9 Mon Sep 17 00:00:00 2001 From: Derrick Stolee Date: Tue, 11 Feb 2020 15:02:23 +0000 Subject: sparse-checkout: create 'add' subcommand When using the sparse-checkout feature, a user may want to incrementally grow their sparse-checkout pattern set. Allow adding patterns using a new 'add' subcommand. This is not much different from the 'set' subcommand, because we still want to allow the '--stdin' option and interpret inputs as directories when in cone mode and patterns otherwise. When in cone mode, we are growing the cone. This may actually reduce the set of patterns when adding directory A when A/B is already a directory in the cone. Test the different cases: siblings, parents, ancestors. When not in cone mode, we can only assume the patterns should be appended to the sparse-checkout file. Signed-off-by: Derrick Stolee Signed-off-by: Junio C Hamano diff --git a/Documentation/git-sparse-checkout.txt b/Documentation/git-sparse-checkout.txt index 0914619..746f920 100644 --- a/Documentation/git-sparse-checkout.txt +++ b/Documentation/git-sparse-checkout.txt @@ -59,6 +59,13 @@ directories. The input format matches the output of `git ls-tree --name-only`. This includes interpreting pathnames that begin with a double quote (") as C-style quoted strings. +'add':: + Update the sparse-checkout file to include additional patterns. + By default, these patterns are read from the command-line arguments, + but they can be read from stdin using the `--stdin` option. When + `core.sparseCheckoutCone` is enabled, the given patterns are interpreted + as directory names as in the 'set' subcommand. + 'disable':: Disable the `core.sparseCheckout` config setting, and restore the working directory to include all files. Leaves the sparse-checkout diff --git a/builtin/sparse-checkout.c b/builtin/sparse-checkout.c index 03915dd..af9e3e5 100644 --- a/builtin/sparse-checkout.c +++ b/builtin/sparse-checkout.c @@ -18,7 +18,7 @@ static const char *empty_base = ""; static char const * const builtin_sparse_checkout_usage[] = { - N_("git sparse-checkout (init|list|set|disable) "), + N_("git sparse-checkout (init|list|set|add|disable) "), NULL }; @@ -404,7 +404,7 @@ static void strbuf_to_cone_pattern(struct strbuf *line, struct pattern_list *pl) } static char const * const builtin_sparse_checkout_set_usage[] = { - N_("git sparse-checkout set (--stdin | )"), + N_("git sparse-checkout (set|add) (--stdin | )"), NULL }; @@ -464,8 +464,54 @@ static void add_patterns_from_input(struct pattern_list *pl, enum modify_type { REPLACE, + ADD, }; +static void add_patterns_cone_mode(int argc, const char **argv, + struct pattern_list *pl) +{ + struct strbuf buffer = STRBUF_INIT; + struct pattern_entry *pe; + struct hashmap_iter iter; + struct pattern_list existing; + char *sparse_filename = get_sparse_checkout_filename(); + + add_patterns_from_input(pl, argc, argv); + + memset(&existing, 0, sizeof(existing)); + existing.use_cone_patterns = core_sparse_checkout_cone; + + if (add_patterns_from_file_to_list(sparse_filename, "", 0, + &existing, NULL)) + die(_("unable to load existing sparse-checkout patterns")); + free(sparse_filename); + + hashmap_for_each_entry(&existing.recursive_hashmap, &iter, pe, ent) { + if (!hashmap_contains_parent(&pl->recursive_hashmap, + pe->pattern, &buffer) || + !hashmap_contains_parent(&pl->parent_hashmap, + pe->pattern, &buffer)) { + strbuf_reset(&buffer); + strbuf_addstr(&buffer, pe->pattern); + insert_recursive_pattern(pl, &buffer); + } + } + + clear_pattern_list(&existing); + strbuf_release(&buffer); +} + +static void add_patterns_literal(int argc, const char **argv, + struct pattern_list *pl) +{ + char *sparse_filename = get_sparse_checkout_filename(); + if (add_patterns_from_file_to_list(sparse_filename, "", 0, + pl, NULL)) + die(_("unable to load existing sparse-checkout patterns")); + free(sparse_filename); + add_patterns_from_input(pl, argc, argv); +} + static int modify_pattern_list(int argc, const char **argv, enum modify_type m) { int result; @@ -473,7 +519,18 @@ static int modify_pattern_list(int argc, const char **argv, enum modify_type m) struct pattern_list pl; memset(&pl, 0, sizeof(pl)); - add_patterns_from_input(&pl, argc, argv); + switch (m) { + case ADD: + if (core_sparse_checkout_cone) + add_patterns_cone_mode(argc, argv, &pl); + else + add_patterns_literal(argc, argv, &pl); + break; + + case REPLACE: + add_patterns_from_input(&pl, argc, argv); + break; + } if (!core_apply_sparse_checkout) { set_config(MODE_ALL_PATTERNS); @@ -490,7 +547,8 @@ static int modify_pattern_list(int argc, const char **argv, enum modify_type m) return result; } -static int sparse_checkout_set(int argc, const char **argv, const char *prefix) +static int sparse_checkout_set(int argc, const char **argv, const char *prefix, + enum modify_type m) { static struct option builtin_sparse_checkout_set_options[] = { OPT_BOOL(0, "stdin", &set_opts.use_stdin, @@ -507,7 +565,7 @@ static int sparse_checkout_set(int argc, const char **argv, const char *prefix) builtin_sparse_checkout_set_usage, PARSE_OPT_KEEP_UNKNOWN); - return modify_pattern_list(argc, argv, REPLACE); + return modify_pattern_list(argc, argv, m); } static int sparse_checkout_disable(int argc, const char **argv) @@ -558,7 +616,9 @@ int cmd_sparse_checkout(int argc, const char **argv, const char *prefix) if (!strcmp(argv[0], "init")) return sparse_checkout_init(argc, argv); if (!strcmp(argv[0], "set")) - return sparse_checkout_set(argc, argv, prefix); + return sparse_checkout_set(argc, argv, prefix, REPLACE); + if (!strcmp(argv[0], "add")) + return sparse_checkout_set(argc, argv, prefix, ADD); if (!strcmp(argv[0], "disable")) return sparse_checkout_disable(argc, argv); } diff --git a/t/t1091-sparse-checkout-builtin.sh b/t/t1091-sparse-checkout-builtin.sh index 7d98209..f9265de 100755 --- a/t/t1091-sparse-checkout-builtin.sh +++ b/t/t1091-sparse-checkout-builtin.sh @@ -141,6 +141,21 @@ test_expect_success 'set sparse-checkout using --stdin' ' check_files repo "a folder1 folder2" ' +test_expect_success 'add to sparse-checkout' ' + cat repo/.git/info/sparse-checkout >expect && + cat >add <<-\EOF && + pattern1 + /folder1/ + pattern2 + EOF + cat add >>expect && + git -C repo sparse-checkout add --stdin actual && + test_cmp expect actual && + test_cmp expect repo/.git/info/sparse-checkout && + check_files repo "a folder1 folder2" +' + test_expect_success 'cone mode: match patterns' ' git -C repo config --worktree core.sparseCheckoutCone true && rm -rf repo/a repo/folder1 repo/folder2 && @@ -219,8 +234,52 @@ test_expect_success 'cone mode: set with nested folders' ' test_cmp repo/.git/info/sparse-checkout expect ' +test_expect_success 'cone mode: add independent path' ' + git -C repo sparse-checkout set deep/deeper1 && + git -C repo sparse-checkout add folder1 && + cat >expect <<-\EOF && + /* + !/*/ + /deep/ + !/deep/*/ + /deep/deeper1/ + /folder1/ + EOF + test_cmp expect repo/.git/info/sparse-checkout && + check_files repo a deep folder1 +' + +test_expect_success 'cone mode: add sibling path' ' + git -C repo sparse-checkout set deep/deeper1 && + git -C repo sparse-checkout add deep/deeper2 && + cat >expect <<-\EOF && + /* + !/*/ + /deep/ + !/deep/*/ + /deep/deeper1/ + /deep/deeper2/ + EOF + test_cmp expect repo/.git/info/sparse-checkout && + check_files repo a deep +' + +test_expect_success 'cone mode: add parent path' ' + git -C repo sparse-checkout set deep/deeper1 folder1 && + git -C repo sparse-checkout add deep && + cat >expect <<-\EOF && + /* + !/*/ + /deep/ + /folder1/ + EOF + test_cmp expect repo/.git/info/sparse-checkout && + check_files repo a deep folder1 +' + test_expect_success 'revert to old sparse-checkout on bad update' ' test_when_finished git -C repo reset --hard && + git -C repo sparse-checkout set deep && echo update >repo/deep/deeper2/a && cp repo/.git/info/sparse-checkout expect && test_must_fail git -C repo sparse-checkout set deep/deeper1 2>err && -- cgit v0.10.2-6-g49f6 From ef07659926f64d70e8cb41025c3d7456eecb962e Mon Sep 17 00:00:00 2001 From: Derrick Stolee Date: Tue, 11 Feb 2020 15:02:24 +0000 Subject: sparse-checkout: work with Windows paths When using Windows, a user may run 'git sparse-checkout set A\B\C' to add the Unix-style path A/B/C to their sparse-checkout patterns. Normalizing the input path converts the backslashes to slashes before we add the string 'A/B/C' to the recursive hashset. Signed-off-by: Derrick Stolee Signed-off-by: Junio C Hamano diff --git a/builtin/sparse-checkout.c b/builtin/sparse-checkout.c index af9e3e5..3e314e3 100644 --- a/builtin/sparse-checkout.c +++ b/builtin/sparse-checkout.c @@ -394,6 +394,9 @@ static void strbuf_to_cone_pattern(struct strbuf *line, struct pattern_list *pl) strbuf_trim_trailing_dir_sep(line); + if (strbuf_normalize_path(line)) + die(_("could not normalize path %s"), line->buf); + if (!line->len) return; diff --git a/t/t1091-sparse-checkout-builtin.sh b/t/t1091-sparse-checkout-builtin.sh index f9265de..c35cbde 100755 --- a/t/t1091-sparse-checkout-builtin.sh +++ b/t/t1091-sparse-checkout-builtin.sh @@ -497,4 +497,18 @@ test_expect_success BSLASHPSPEC 'pattern-checks: escaped characters' ' test_cmp list-expect list-actual ' +test_expect_success MINGW 'cone mode replaces backslashes with slashes' ' + git -C repo sparse-checkout set deep\\deeper1 && + cat >expect <<-\EOF && + /* + !/*/ + /deep/ + !/deep/*/ + /deep/deeper1/ + EOF + test_cmp expect repo/.git/info/sparse-checkout && + check_files repo a deep && + check_files repo/deep a deeper1 +' + test_done -- cgit v0.10.2-6-g49f6 From 6c11c6a124a0175f4d1b94f0fa077ed1c098339b Mon Sep 17 00:00:00 2001 From: Derrick Stolee Date: Thu, 20 Feb 2020 20:07:06 +0000 Subject: sparse-checkout: allow one-character directories in cone mode In 9e6d3e64 (sparse-checkout: detect short patterns, 2020-01-24), a condition on the minimum length of a cone-mode pattern was introduced. However, this condition was off-by-one. If we have a directory with a single character, say "b", then the command git sparse-checkout set b will correctly add the pattern "/b/" to the sparse-checkout file. When this is interpeted in dir.c, the pattern is "/b" with the PATTERN_FLAG_MUSTBEDIR flag. This string has length two, which satisfies our inclusive inequality (<= 2). The reason for this inequality is that we will start to read the pattern string character-by-character using three char pointers: prev, cur, next. In particular, next is set to the current pattern plus two. The mistake was that next will still be a valid pointer when the pattern length is two, since the string is null-terminated. Make this inequality strict so these patterns work. Signed-off-by: Derrick Stolee Signed-off-by: Junio C Hamano diff --git a/dir.c b/dir.c index 7ac0920..a87900d 100644 --- a/dir.c +++ b/dir.c @@ -682,7 +682,7 @@ static void add_pattern_to_hashsets(struct pattern_list *pl, struct path_pattern return; } - if (given->patternlen <= 2 || + if (given->patternlen < 2 || *given->pattern == '*' || strstr(given->pattern, "**")) { /* Not a cone pattern. */ diff --git a/t/t1091-sparse-checkout-builtin.sh b/t/t1091-sparse-checkout-builtin.sh index c35cbde..b4c9c32 100755 --- a/t/t1091-sparse-checkout-builtin.sh +++ b/t/t1091-sparse-checkout-builtin.sh @@ -417,10 +417,20 @@ test_expect_success 'pattern-checks: too short' ' cat >repo/.git/info/sparse-checkout <<-\EOF && /* !/*/ - /a + / EOF check_read_tree_errors repo "a" "disabling cone pattern matching" ' +test_expect_success 'pattern-checks: not too short' ' + cat >repo/.git/info/sparse-checkout <<-\EOF && + /* + !/*/ + /b/ + EOF + git -C repo read-tree -mu HEAD 2>err && + test_must_be_empty err && + check_files repo a +' test_expect_success 'pattern-checks: trailing "*"' ' cat >repo/.git/info/sparse-checkout <<-\EOF && -- cgit v0.10.2-6-g49f6