{"thread":{"id":"52780","subject":"[PATCH 0/4] Sparse-checkout: Add subcommand and Windows paths","startedAt":"2020-02-11T15:02:30Z","lastAt":"2020-02-11T19:48:08Z","messageCount":9,"participants":["Derrick Stolee via GitGitGadget","Junio C Hamano","Jeff King"],"isPatch":true,"patchVersion":1,"patchTotal":4},"messages":[{"id":"391529","messageId":"pull.546.git.1581433344.gitgitgadget@gmail.com","threadId":"52780","inReplyTo":null,"subject":"[PATCH 0/4] Sparse-checkout: Add subcommand and Windows paths","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2020-02-11T15:02:20Z","receivedAt":"2020-02-11T15:02:30Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"This is based on ds/sparse-checkout-harden.\n\nThe sparse-checkout builtin currently lets users modify their\nsparse-checkout file with the all-or-nothing \"set\" subcommand. It may be\neasier for a user to expand their sparse cone using a \"git sparse-checkout\nadd <pattern/path> ...\" subcommand. To achieve this while reusing as much\ncode as possible from the \"set\" subcommand, the first two patches extract\nmethods from sparse_checkout_set().\n\nAs part of this \"add\" subcommand, I created a modify_pattern_list() method\nthat serves as the core interaction with the sparse-checkout file and\nworking directory. It changes how it constructs the in-memory pattern list\nbased on an \"enum modify_type\" that only has two options: REPLACE and ADD.\nThis could be extended with REMOVE in a later update. I started building the\nREMOVE logic, but it was more complicated than ADD, and I didn't have time\nto finish it right now due to other obligations.\n\nFinally, a Windows user contacted me about using Windows-style paths when in\ncone mode to add a nested directory. This case is fixed and tested in the\nfinal patch.\n\nThanks, -Stolee\n\nDerrick Stolee (4):\n  sparse-checkout: extract add_patterns_from_input()\n  sparse-checkout: extract pattern update from 'set' subcommand\n  sparse-checkout: create 'add' subcommand\n  sparse-checkout: work with Windows paths\n\n Documentation/git-sparse-checkout.txt |   7 ++\n builtin/sparse-checkout.c             | 141 ++++++++++++++++++++------\n t/t1091-sparse-checkout-builtin.sh    |  73 +++++++++++++\n 3 files changed, 189 insertions(+), 32 deletions(-)\n\n\nbase-commit: f998a3f1e588d73ed7285cb14ac4839f63f6dc82\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-546%2Fderrickstolee%2Fsparse-add-v1\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-546/derrickstolee/sparse-add-v1\nPull-Request: https://github.com/gitgitgadget/git/pull/546\n-- \ngitgitgadget\n"},{"id":"391530","messageId":"7990916ef05c618c3246053b3e5845b316f67668.1581433344.git.gitgitgadget@gmail.com","threadId":"52780","inReplyTo":"pull.546.git.1581433344.gitgitgadget@gmail.com","subject":"[PATCH 2/4] sparse-checkout: extract pattern update from 'set' subcommand","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2020-02-11T15:02:22Z","receivedAt":"2020-02-11T15:02:30Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"From: Derrick Stolee <dstolee@microsoft.com>\n\nIn anticipation of adding \"add\" and \"remove\" subcommands to the\nsparse-checkout builtin, extract a modify_pattern_list() method from the\nsparse_checkout_set() method. This command will read input from the\ncommand-line or stdin to construct a set of patterns, then modify the\nexisting sparse-checkout patterns after a successful update of the\nworking directory.\n\nCurrently, the only way to modify the patterns is to replace all of the\npatterns. This will be extended in a later update.\n\nSigned-off-by: Derrick Stolee <dstolee@microsoft.com>\n---\n builtin/sparse-checkout.c | 44 +++++++++++++++++++++++----------------\n 1 file changed, 26 insertions(+), 18 deletions(-)\n\ndiff --git a/builtin/sparse-checkout.c b/builtin/sparse-checkout.c\nindex 41d8aaf9a2..03915dd729 100644\n--- a/builtin/sparse-checkout.c\n+++ b/builtin/sparse-checkout.c\n@@ -462,29 +462,17 @@ static void add_patterns_from_input(struct pattern_list *pl,\n \t}\n }\n \n-static int sparse_checkout_set(int argc, const char **argv, const char *prefix)\n+enum modify_type {\n+\tREPLACE,\n+};\n+\n+static int modify_pattern_list(int argc, const char **argv, enum modify_type m)\n {\n-\tstruct pattern_list pl;\n \tint result;\n \tint changed_config = 0;\n-\n-\tstatic struct option builtin_sparse_checkout_set_options[] = {\n-\t\tOPT_BOOL(0, \"stdin\", &set_opts.use_stdin,\n-\t\t\t N_(\"read patterns from standard in\")),\n-\t\tOPT_END(),\n-\t};\n-\n-\trepo_read_index(the_repository);\n-\trequire_clean_work_tree(the_repository,\n-\t\t\t\tN_(\"set sparse-checkout patterns\"), NULL, 1, 0);\n-\n+\tstruct pattern_list pl;\n \tmemset(&pl, 0, sizeof(pl));\n \n-\targc = parse_options(argc, argv, prefix,\n-\t\t\t     builtin_sparse_checkout_set_options,\n-\t\t\t     builtin_sparse_checkout_set_usage,\n-\t\t\t     PARSE_OPT_KEEP_UNKNOWN);\n-\n \tadd_patterns_from_input(&pl, argc, argv);\n \n \tif (!core_apply_sparse_checkout) {\n@@ -502,6 +490,26 @@ static int sparse_checkout_set(int argc, const char **argv, const char *prefix)\n \treturn result;\n }\n \n+static int sparse_checkout_set(int argc, const char **argv, const char *prefix)\n+{\n+\tstatic struct option builtin_sparse_checkout_set_options[] = {\n+\t\tOPT_BOOL(0, \"stdin\", &set_opts.use_stdin,\n+\t\t\t N_(\"read patterns from standard in\")),\n+\t\tOPT_END(),\n+\t};\n+\n+\trepo_read_index(the_repository);\n+\trequire_clean_work_tree(the_repository,\n+\t\t\t\tN_(\"set sparse-checkout patterns\"), NULL, 1, 0);\n+\n+\targc = parse_options(argc, argv, prefix,\n+\t\t\t     builtin_sparse_checkout_set_options,\n+\t\t\t     builtin_sparse_checkout_set_usage,\n+\t\t\t     PARSE_OPT_KEEP_UNKNOWN);\n+\n+\treturn modify_pattern_list(argc, argv, REPLACE);\n+}\n+\n static int sparse_checkout_disable(int argc, const char **argv)\n {\n \tstruct pattern_list pl;\n-- \ngitgitgadget\n\n"},{"id":"391531","messageId":"663c40a6909ffac6650a3ee6883b3299f6b2bd90.1581433344.git.gitgitgadget@gmail.com","threadId":"52780","inReplyTo":"pull.546.git.1581433344.gitgitgadget@gmail.com","subject":"[PATCH 3/4] sparse-checkout: create 'add' subcommand","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2020-02-11T15:02:23Z","receivedAt":"2020-02-11T15:02:33Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"From: Derrick Stolee <dstolee@microsoft.com>\n\nWhen using the sparse-checkout feature, a user may want to incrementally\ngrow their sparse-checkout pattern set. Allow adding patterns using a\nnew 'add' subcommand. This is not much different from the 'set'\nsubcommand, because we still want to allow the '--stdin' option and\ninterpret inputs as directories when in cone mode and patterns\notherwise.\n\nWhen in cone mode, we are growing the cone. This may actually reduce the\nset of patterns when adding directory A when A/B is already a directory\nin the cone. Test the different cases: siblings, parents, ancestors.\n\nWhen not in cone mode, we can only assume the patterns should be\nappended to the sparse-checkout file.\n\nSigned-off-by: Derrick Stolee <dstolee@microsoft.com>\n---\n Documentation/git-sparse-checkout.txt |  7 +++\n builtin/sparse-checkout.c             | 72 ++++++++++++++++++++++++---\n t/t1091-sparse-checkout-builtin.sh    | 59 ++++++++++++++++++++++\n 3 files changed, 132 insertions(+), 6 deletions(-)\n\ndiff --git a/Documentation/git-sparse-checkout.txt b/Documentation/git-sparse-checkout.txt\nindex 0914619881..746f920d71 100644\n--- a/Documentation/git-sparse-checkout.txt\n+++ b/Documentation/git-sparse-checkout.txt\n@@ -59,6 +59,13 @@ directories. The input format matches the output of `git ls-tree --name-only`.\n This includes interpreting pathnames that begin with a double quote (\") as\n C-style quoted strings.\n \n+'add'::\n+\tUpdate the sparse-checkout file to include additional patterns.\n+\tBy default, these patterns are read from the command-line arguments,\n+\tbut they can be read from stdin using the `--stdin` option. When\n+\t`core.sparseCheckoutCone` is enabled, the given patterns are interpreted\n+\tas directory names as in the 'set' subcommand.\n+\n 'disable'::\n \tDisable the `core.sparseCheckout` config setting, and restore the\n \tworking directory to include all files. Leaves the sparse-checkout\ndiff --git a/builtin/sparse-checkout.c b/builtin/sparse-checkout.c\nindex 03915dd729..af9e3e5123 100644\n--- a/builtin/sparse-checkout.c\n+++ b/builtin/sparse-checkout.c\n@@ -18,7 +18,7 @@\n static const char *empty_base = \"\";\n \n static char const * const builtin_sparse_checkout_usage[] = {\n-\tN_(\"git sparse-checkout (init|list|set|disable) <options>\"),\n+\tN_(\"git sparse-checkout (init|list|set|add|disable) <options>\"),\n \tNULL\n };\n \n@@ -404,7 +404,7 @@ static void strbuf_to_cone_pattern(struct strbuf *line, struct pattern_list *pl)\n }\n \n static char const * const builtin_sparse_checkout_set_usage[] = {\n-\tN_(\"git sparse-checkout set (--stdin | <patterns>)\"),\n+\tN_(\"git sparse-checkout (set|add) (--stdin | <patterns>)\"),\n \tNULL\n };\n \n@@ -464,8 +464,54 @@ static void add_patterns_from_input(struct pattern_list *pl,\n \n enum modify_type {\n \tREPLACE,\n+\tADD,\n };\n \n+static void add_patterns_cone_mode(int argc, const char **argv,\n+\t\t\t\t   struct pattern_list *pl)\n+{\n+\tstruct strbuf buffer = STRBUF_INIT;\n+\tstruct pattern_entry *pe;\n+\tstruct hashmap_iter iter;\n+\tstruct pattern_list existing;\n+\tchar *sparse_filename = get_sparse_checkout_filename();\n+\n+\tadd_patterns_from_input(pl, argc, argv);\n+\n+\tmemset(&existing, 0, sizeof(existing));\n+\texisting.use_cone_patterns = core_sparse_checkout_cone;\n+\n+\tif (add_patterns_from_file_to_list(sparse_filename, \"\", 0,\n+\t\t\t\t\t   &existing, NULL))\n+\t\tdie(_(\"unable to load existing sparse-checkout patterns\"));\n+\tfree(sparse_filename);\n+\n+\thashmap_for_each_entry(&existing.recursive_hashmap, &iter, pe, ent) {\n+\t\tif (!hashmap_contains_parent(&pl->recursive_hashmap,\n+\t\t\t\t\tpe->pattern, &buffer) ||\n+\t\t    !hashmap_contains_parent(&pl->parent_hashmap,\n+\t\t\t\t\tpe->pattern, &buffer)) {\n+\t\t\tstrbuf_reset(&buffer);\n+\t\t\tstrbuf_addstr(&buffer, pe->pattern);\n+\t\t\tinsert_recursive_pattern(pl, &buffer);\n+\t\t}\n+\t}\n+\n+\tclear_pattern_list(&existing);\n+\tstrbuf_release(&buffer);\n+}\n+\n+static void add_patterns_literal(int argc, const char **argv,\n+\t\t\t\t struct pattern_list *pl)\n+{\n+\tchar *sparse_filename = get_sparse_checkout_filename();\n+\tif (add_patterns_from_file_to_list(sparse_filename, \"\", 0,\n+\t\t\t\t\t   pl, NULL))\n+\t\tdie(_(\"unable to load existing sparse-checkout patterns\"));\n+\tfree(sparse_filename);\n+\tadd_patterns_from_input(pl, argc, argv);\n+}\n+\n static int modify_pattern_list(int argc, const char **argv, enum modify_type m)\n {\n \tint result;\n@@ -473,7 +519,18 @@ static int modify_pattern_list(int argc, const char **argv, enum modify_type m)\n \tstruct pattern_list pl;\n \tmemset(&pl, 0, sizeof(pl));\n \n-\tadd_patterns_from_input(&pl, argc, argv);\n+\tswitch (m) {\n+\tcase ADD:\n+\t\tif (core_sparse_checkout_cone)\n+\t\t\tadd_patterns_cone_mode(argc, argv, &pl);\n+\t\telse\n+\t\t\tadd_patterns_literal(argc, argv, &pl);\n+\t\tbreak;\n+\n+\tcase REPLACE:\n+\t\tadd_patterns_from_input(&pl, argc, argv);\n+\t\tbreak;\n+\t}\n \n \tif (!core_apply_sparse_checkout) {\n \t\tset_config(MODE_ALL_PATTERNS);\n@@ -490,7 +547,8 @@ static int modify_pattern_list(int argc, const char **argv, enum modify_type m)\n \treturn result;\n }\n \n-static int sparse_checkout_set(int argc, const char **argv, const char *prefix)\n+static int sparse_checkout_set(int argc, const char **argv, const char *prefix,\n+\t\t\t       enum modify_type m)\n {\n \tstatic struct option builtin_sparse_checkout_set_options[] = {\n \t\tOPT_BOOL(0, \"stdin\", &set_opts.use_stdin,\n@@ -507,7 +565,7 @@ static int sparse_checkout_set(int argc, const char **argv, const char *prefix)\n \t\t\t     builtin_sparse_checkout_set_usage,\n \t\t\t     PARSE_OPT_KEEP_UNKNOWN);\n \n-\treturn modify_pattern_list(argc, argv, REPLACE);\n+\treturn modify_pattern_list(argc, argv, m);\n }\n \n static int sparse_checkout_disable(int argc, const char **argv)\n@@ -558,7 +616,9 @@ int cmd_sparse_checkout(int argc, const char **argv, const char *prefix)\n \t\tif (!strcmp(argv[0], \"init\"))\n \t\t\treturn sparse_checkout_init(argc, argv);\n \t\tif (!strcmp(argv[0], \"set\"))\n-\t\t\treturn sparse_checkout_set(argc, argv, prefix);\n+\t\t\treturn sparse_checkout_set(argc, argv, prefix, REPLACE);\n+\t\tif (!strcmp(argv[0], \"add\"))\n+\t\t\treturn sparse_checkout_set(argc, argv, prefix, ADD);\n \t\tif (!strcmp(argv[0], \"disable\"))\n \t\t\treturn sparse_checkout_disable(argc, argv);\n \t}\ndiff --git a/t/t1091-sparse-checkout-builtin.sh b/t/t1091-sparse-checkout-builtin.sh\nindex 7d982096fb..f9265de5e8 100755\n--- a/t/t1091-sparse-checkout-builtin.sh\n+++ b/t/t1091-sparse-checkout-builtin.sh\n@@ -141,6 +141,21 @@ test_expect_success 'set sparse-checkout using --stdin' '\n \tcheck_files repo \"a folder1 folder2\"\n '\n \n+test_expect_success 'add to sparse-checkout' '\n+\tcat repo/.git/info/sparse-checkout >expect &&\n+\tcat >add <<-\\EOF &&\n+\tpattern1\n+\t/folder1/\n+\tpattern2\n+\tEOF\n+\tcat add >>expect &&\n+\tgit -C repo sparse-checkout add --stdin <add &&\n+\tgit -C repo sparse-checkout list >actual &&\n+\ttest_cmp expect actual &&\n+\ttest_cmp expect repo/.git/info/sparse-checkout &&\n+\tcheck_files repo \"a folder1 folder2\"\n+'\n+\n test_expect_success 'cone mode: match patterns' '\n \tgit -C repo config --worktree core.sparseCheckoutCone true &&\n \trm -rf repo/a repo/folder1 repo/folder2 &&\n@@ -219,8 +234,52 @@ test_expect_success 'cone mode: set with nested folders' '\n \ttest_cmp repo/.git/info/sparse-checkout expect\n '\n \n+test_expect_success 'cone mode: add independent path' '\n+\tgit -C repo sparse-checkout set deep/deeper1 &&\n+\tgit -C repo sparse-checkout add folder1 &&\n+\tcat >expect <<-\\EOF &&\n+\t/*\n+\t!/*/\n+\t/deep/\n+\t!/deep/*/\n+\t/deep/deeper1/\n+\t/folder1/\n+\tEOF\n+\ttest_cmp expect repo/.git/info/sparse-checkout &&\n+\tcheck_files repo a deep folder1\n+'\n+\n+test_expect_success 'cone mode: add sibling path' '\n+\tgit -C repo sparse-checkout set deep/deeper1 &&\n+\tgit -C repo sparse-checkout add deep/deeper2 &&\n+\tcat >expect <<-\\EOF &&\n+\t/*\n+\t!/*/\n+\t/deep/\n+\t!/deep/*/\n+\t/deep/deeper1/\n+\t/deep/deeper2/\n+\tEOF\n+\ttest_cmp expect repo/.git/info/sparse-checkout &&\n+\tcheck_files repo a deep\n+'\n+\n+test_expect_success 'cone mode: add parent path' '\n+\tgit -C repo sparse-checkout set deep/deeper1 folder1 &&\n+\tgit -C repo sparse-checkout add deep &&\n+\tcat >expect <<-\\EOF &&\n+\t/*\n+\t!/*/\n+\t/deep/\n+\t/folder1/\n+\tEOF\n+\ttest_cmp expect repo/.git/info/sparse-checkout &&\n+\tcheck_files repo a deep folder1\n+'\n+\n test_expect_success 'revert to old sparse-checkout on bad update' '\n \ttest_when_finished git -C repo reset --hard &&\n+\tgit -C repo sparse-checkout set deep &&\n \techo update >repo/deep/deeper2/a &&\n \tcp repo/.git/info/sparse-checkout expect &&\n \ttest_must_fail git -C repo sparse-checkout set deep/deeper1 2>err &&\n-- \ngitgitgadget\n\n"},{"id":"391532","messageId":"75ee62caa940e7232e0edb50788302f36a08b5b9.1581433344.git.gitgitgadget@gmail.com","threadId":"52780","inReplyTo":"pull.546.git.1581433344.gitgitgadget@gmail.com","subject":"[PATCH 1/4] sparse-checkout: extract add_patterns_from_input()","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2020-02-11T15:02:21Z","receivedAt":"2020-02-11T15:02:33Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"From: Derrick Stolee <dstolee@microsoft.com>\n\nIn anticipation of extending the sparse-checkout builtin with \"add\"\nand \"remove\" subcommands, extract the code that fills a pattern list\nbased on the input values. The input changes depending on the\npresence of \"--stdin\" or the value of core.sparseCheckoutCone.\n\nSigned-off-by: Derrick Stolee <dstolee@microsoft.com>\n---\n builtin/sparse-checkout.c | 64 +++++++++++++++++++++------------------\n 1 file changed, 35 insertions(+), 29 deletions(-)\n\ndiff --git a/builtin/sparse-checkout.c b/builtin/sparse-checkout.c\nindex 7aeb384362..41d8aaf9a2 100644\n--- a/builtin/sparse-checkout.c\n+++ b/builtin/sparse-checkout.c\n@@ -412,36 +412,16 @@ static struct sparse_checkout_set_opts {\n \tint use_stdin;\n } set_opts;\n \n-static int sparse_checkout_set(int argc, const char **argv, const char *prefix)\n+static void add_patterns_from_input(struct pattern_list *pl,\n+\t\t\t\t    int argc, const char **argv)\n {\n \tint i;\n-\tstruct pattern_list pl;\n-\tint result;\n-\tint changed_config = 0;\n-\n-\tstatic struct option builtin_sparse_checkout_set_options[] = {\n-\t\tOPT_BOOL(0, \"stdin\", &set_opts.use_stdin,\n-\t\t\t N_(\"read patterns from standard in\")),\n-\t\tOPT_END(),\n-\t};\n-\n-\trepo_read_index(the_repository);\n-\trequire_clean_work_tree(the_repository,\n-\t\t\t\tN_(\"set sparse-checkout patterns\"), NULL, 1, 0);\n-\n-\tmemset(&pl, 0, sizeof(pl));\n-\n-\targc = parse_options(argc, argv, prefix,\n-\t\t\t     builtin_sparse_checkout_set_options,\n-\t\t\t     builtin_sparse_checkout_set_usage,\n-\t\t\t     PARSE_OPT_KEEP_UNKNOWN);\n-\n \tif (core_sparse_checkout_cone) {\n \t\tstruct strbuf line = STRBUF_INIT;\n \n-\t\thashmap_init(&pl.recursive_hashmap, pl_hashmap_cmp, NULL, 0);\n-\t\thashmap_init(&pl.parent_hashmap, pl_hashmap_cmp, NULL, 0);\n-\t\tpl.use_cone_patterns = 1;\n+\t\thashmap_init(&pl->recursive_hashmap, pl_hashmap_cmp, NULL, 0);\n+\t\thashmap_init(&pl->parent_hashmap, pl_hashmap_cmp, NULL, 0);\n+\t\tpl->use_cone_patterns = 1;\n \n \t\tif (set_opts.use_stdin) {\n \t\t\tstruct strbuf unquoted = STRBUF_INIT;\n@@ -455,7 +435,7 @@ static int sparse_checkout_set(int argc, const char **argv, const char *prefix)\n \t\t\t\t\tstrbuf_swap(&unquoted, &line);\n \t\t\t\t}\n \n-\t\t\t\tstrbuf_to_cone_pattern(&line, &pl);\n+\t\t\t\tstrbuf_to_cone_pattern(&line, pl);\n \t\t\t}\n \n \t\t\tstrbuf_release(&unquoted);\n@@ -463,7 +443,7 @@ static int sparse_checkout_set(int argc, const char **argv, const char *prefix)\n \t\t\tfor (i = 0; i < argc; i++) {\n \t\t\t\tstrbuf_setlen(&line, 0);\n \t\t\t\tstrbuf_addstr(&line, argv[i]);\n-\t\t\t\tstrbuf_to_cone_pattern(&line, &pl);\n+\t\t\t\tstrbuf_to_cone_pattern(&line, pl);\n \t\t\t}\n \t\t}\n \t} else {\n@@ -473,13 +453,39 @@ static int sparse_checkout_set(int argc, const char **argv, const char *prefix)\n \t\t\twhile (!strbuf_getline(&line, stdin)) {\n \t\t\t\tsize_t len;\n \t\t\t\tchar *buf = strbuf_detach(&line, &len);\n-\t\t\t\tadd_pattern(buf, empty_base, 0, &pl, 0);\n+\t\t\t\tadd_pattern(buf, empty_base, 0, pl, 0);\n \t\t\t}\n \t\t} else {\n \t\t\tfor (i = 0; i < argc; i++)\n-\t\t\t\tadd_pattern(argv[i], empty_base, 0, &pl, 0);\n+\t\t\t\tadd_pattern(argv[i], empty_base, 0, pl, 0);\n \t\t}\n \t}\n+}\n+\n+static int sparse_checkout_set(int argc, const char **argv, const char *prefix)\n+{\n+\tstruct pattern_list pl;\n+\tint result;\n+\tint changed_config = 0;\n+\n+\tstatic struct option builtin_sparse_checkout_set_options[] = {\n+\t\tOPT_BOOL(0, \"stdin\", &set_opts.use_stdin,\n+\t\t\t N_(\"read patterns from standard in\")),\n+\t\tOPT_END(),\n+\t};\n+\n+\trepo_read_index(the_repository);\n+\trequire_clean_work_tree(the_repository,\n+\t\t\t\tN_(\"set sparse-checkout patterns\"), NULL, 1, 0);\n+\n+\tmemset(&pl, 0, sizeof(pl));\n+\n+\targc = parse_options(argc, argv, prefix,\n+\t\t\t     builtin_sparse_checkout_set_options,\n+\t\t\t     builtin_sparse_checkout_set_usage,\n+\t\t\t     PARSE_OPT_KEEP_UNKNOWN);\n+\n+\tadd_patterns_from_input(&pl, argc, argv);\n \n \tif (!core_apply_sparse_checkout) {\n \t\tset_config(MODE_ALL_PATTERNS);\n-- \ngitgitgadget\n\n"},{"id":"391533","messageId":"f770df489849fa8379a9fa18c023bceb788fd96b.1581433344.git.gitgitgadget@gmail.com","threadId":"52780","inReplyTo":"pull.546.git.1581433344.gitgitgadget@gmail.com","subject":"[PATCH 4/4] sparse-checkout: work with Windows paths","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2020-02-11T15:02:24Z","receivedAt":"2020-02-11T15:02:34Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"From: Derrick Stolee <dstolee@microsoft.com>\n\nWhen using Windows, a user may run 'git sparse-checkout set A\\B\\C'\nto add the Unix-style path A/B/C to their sparse-checkout patterns.\nNormalizing the input path converts the backslashes to slashes before we\nadd the string 'A/B/C' to the recursive hashset.\n\nSigned-off-by: Derrick Stolee <dstolee@microsoft.com>\n---\n builtin/sparse-checkout.c          |  3 +++\n t/t1091-sparse-checkout-builtin.sh | 14 ++++++++++++++\n 2 files changed, 17 insertions(+)\n\ndiff --git a/builtin/sparse-checkout.c b/builtin/sparse-checkout.c\nindex af9e3e5123..3e314e3358 100644\n--- a/builtin/sparse-checkout.c\n+++ b/builtin/sparse-checkout.c\n@@ -394,6 +394,9 @@ static void strbuf_to_cone_pattern(struct strbuf *line, struct pattern_list *pl)\n \n \tstrbuf_trim_trailing_dir_sep(line);\n \n+\tif (strbuf_normalize_path(line))\n+\t\tdie(_(\"could not normalize path %s\"), line->buf);\n+\n \tif (!line->len)\n \t\treturn;\n \ndiff --git a/t/t1091-sparse-checkout-builtin.sh b/t/t1091-sparse-checkout-builtin.sh\nindex f9265de5e8..c35cbdef45 100755\n--- a/t/t1091-sparse-checkout-builtin.sh\n+++ b/t/t1091-sparse-checkout-builtin.sh\n@@ -497,4 +497,18 @@ test_expect_success BSLASHPSPEC 'pattern-checks: escaped characters' '\n \ttest_cmp list-expect list-actual\n '\n \n+test_expect_success MINGW 'cone mode replaces backslashes with slashes' '\n+\tgit -C repo sparse-checkout set deep\\\\deeper1 &&\n+\tcat >expect <<-\\EOF &&\n+\t/*\n+\t!/*/\n+\t/deep/\n+\t!/deep/*/\n+\t/deep/deeper1/\n+\tEOF\n+\ttest_cmp expect repo/.git/info/sparse-checkout &&\n+\tcheck_files repo a deep &&\n+\tcheck_files repo/deep a deeper1\n+'\n+\n test_done\n-- \ngitgitgadget\n"},{"id":"391540","messageId":"xmqqo8u5vz9d.fsf@gitster-ct.c.googlers.com","threadId":"52780","inReplyTo":"75ee62caa940e7232e0edb50788302f36a08b5b9.1581433344.git.gitgitgadget@gmail.com","subject":"Re: [PATCH 1/4] sparse-checkout: extract add_patterns_from_input()","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2020-02-11T16:56:30Z","receivedAt":"2020-02-11T16:56:41Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"Derrick Stolee via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n\n> -static int sparse_checkout_set(int argc, const char **argv, const char *prefix)\n> +static void add_patterns_from_input(struct pattern_list *pl,\n> +\t\t\t\t    int argc, const char **argv)\n\nSeparating out what happens _after_ parse_options into its own\nhelper function makes sense.\n\nEverything, not limited to this function, works on its input, so the\nname suffix does not add much value to the readers.  IOW, I do not\nthink I, as a new reader of this code, would be confused if this\nfunction were called add_patterns().\n\nIf we wanted to add more words to the name, perhaps I'd use them to\ndescribe the shape of the input (e.g. \"add_patterns_from_acav\") but\nthat is obvious from the list of input parameter, so...\n\nI haven't read the rest of the series, but will we ever call this\nhelper function with an array we manufacture ourselves?  If the\ninput array is known to be NULL terminated (and at this step in the\nseries, it certainly is, as we are using the ac/av supplied by the C\nruntime entry point), perhaps we can omit argc from the parameter to\nsimplify the calling convention a bit?\n\n>  {\n>  \tint i;\n> -\tstruct pattern_list pl;\n> -\tint result;\n> -\tint changed_config = 0;\n> -\n> -\tstatic struct option builtin_sparse_checkout_set_options[] = {\n> -\t\tOPT_BOOL(0, \"stdin\", &set_opts.use_stdin,\n> -\t\t\t N_(\"read patterns from standard in\")),\n> -\t\tOPT_END(),\n> -\t};\n> -\n> -\trepo_read_index(the_repository);\n> -\trequire_clean_work_tree(the_repository,\n> -\t\t\t\tN_(\"set sparse-checkout patterns\"), NULL, 1, 0);\n> -\n> -\tmemset(&pl, 0, sizeof(pl));\n> -\n> -\targc = parse_options(argc, argv, prefix,\n> -\t\t\t     builtin_sparse_checkout_set_options,\n> -\t\t\t     builtin_sparse_checkout_set_usage,\n> -\t\t\t     PARSE_OPT_KEEP_UNKNOWN);\n> -\n>  \tif (core_sparse_checkout_cone) {\n>  \t\tstruct strbuf line = STRBUF_INIT;\n>  \n> -\t\thashmap_init(&pl.recursive_hashmap, pl_hashmap_cmp, NULL, 0);\n> -\t\thashmap_init(&pl.parent_hashmap, pl_hashmap_cmp, NULL, 0);\n> -\t\tpl.use_cone_patterns = 1;\n> +\t\thashmap_init(&pl->recursive_hashmap, pl_hashmap_cmp, NULL, 0);\n> +\t\thashmap_init(&pl->parent_hashmap, pl_hashmap_cmp, NULL, 0);\n> +\t\tpl->use_cone_patterns = 1;\n> ...\n\n"},{"id":"391543","messageId":"xmqqk14tvytp.fsf@gitster-ct.c.googlers.com","threadId":"52780","inReplyTo":"663c40a6909ffac6650a3ee6883b3299f6b2bd90.1581433344.git.gitgitgadget@gmail.com","subject":"Re: [PATCH 3/4] sparse-checkout: create 'add' subcommand","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2020-02-11T17:05:54Z","receivedAt":"2020-02-11T17:06:00Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"Derrick Stolee via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n\n> +\tswitch (m) {\n> +\tcase ADD:\n> +\t\tif (core_sparse_checkout_cone)\n> +\t\t\tadd_patterns_cone_mode(argc, argv, &pl);\n> +\t\telse\n> +\t\t\tadd_patterns_literal(argc, argv, &pl);\n> +\t\tbreak;\n> +\n> +\tcase REPLACE:\n> +\t\tadd_patterns_from_input(&pl, argc, argv);\n> +\t\tbreak;\n> +\t}\n\nIs it just me or do readers find it irritating to see the order of\nthe arguments seem a bit random?  Those who like Pseudo-OO-in-C\nprobably would want to consistently see &pl as the first parameter,\nwhich lets them pretend various flavours of add_patterns_*() are\nall \"methods\" to the pattern list object.\n\n> +static void add_patterns_literal(int argc, const char **argv,\n> +\t\t\t\t struct pattern_list *pl)\n> +{\n> +\tchar *sparse_filename = get_sparse_checkout_filename();\n> +\tif (add_patterns_from_file_to_list(sparse_filename, \"\", 0,\n> +\t\t\t\t\t   pl, NULL))\n> +\t\tdie(_(\"unable to load existing sparse-checkout patterns\"));\n> +\tfree(sparse_filename);\n> +\tadd_patterns_from_input(pl, argc, argv);\n> +}\n\nIt may make it easier to read the caller if this helper were\nreplaced with one that does not add anything but just read from the\nexisting file, i.e.\n\n\t\tif (core_sparse_checkout_cone)\n\t\t\tadd_patterns_cone_mode(argc, argv, &pl);\n\t\telse {\n\t\t\tread_existing_patterns(&pl);\n\t\t\tadd_patterns_from_input(&pl, argc, argv);\n\t\t}\n\n"},{"id":"391544","messageId":"xmqqimkdvysg.fsf@gitster-ct.c.googlers.com","threadId":"52780","inReplyTo":"pull.546.git.1581433344.gitgitgadget@gmail.com","subject":"Re: [PATCH 0/4] Sparse-checkout: Add subcommand and Windows paths","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2020-02-11T17:06:39Z","receivedAt":"2020-02-11T17:06:43Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"Derrick Stolee via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n\n> This is based on ds/sparse-checkout-harden.\n>\n> The sparse-checkout builtin currently lets users modify their\n> sparse-checkout file with the all-or-nothing \"set\" subcommand. It may be\n> easier for a user to expand their sparse cone using a \"git sparse-checkout\n> add <pattern/path> ...\" subcommand.\n\nMakes sense.  \n\nI suspect that people may get greedy and start saying \"now we can\nadd a new directory (say, Documentation/) without repeating what we\nalready configured, I want a way to add a new directory without some\nof the paths in that directory (say, Documentation/ but not\nDocumentation/technical/)\", but that won't happen until people can\nuse \"add\", so let's take small incremental steps, and addition of\n\"add\" is such a good first step.\n\nWill queue.  Thanks.\n"},{"id":"391569","messageId":"20200211194806.GB2127797@coredump.intra.peff.net","threadId":"52780","inReplyTo":"pull.546.git.1581433344.gitgitgadget@gmail.com","subject":"Re: [PATCH 0/4] Sparse-checkout: Add subcommand and Windows paths","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2020-02-11T19:48:06Z","receivedAt":"2020-02-11T19:48:08Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Tue, Feb 11, 2020 at 03:02:20PM +0000, Derrick Stolee via GitGitGadget wrote:\n\n> This is based on ds/sparse-checkout-harden.\n> \n> The sparse-checkout builtin currently lets users modify their\n> sparse-checkout file with the all-or-nothing \"set\" subcommand. It may be\n> easier for a user to expand their sparse cone using a \"git sparse-checkout\n> add <pattern/path> ...\" subcommand. To achieve this while reusing as much\n> code as possible from the \"set\" subcommand, the first two patches extract\n> methods from sparse_checkout_set().\n\nFWIW, this makes perfect sense to me. I actually expected \"set\" to\nbehave like \"add\" the first time I tried it out.\n\n-Peff\n"}]}