From: Derrick Stolee <dsto...@microsoft.com>

To make the cone pattern set easy to use, update the behavior of
'git sparse-checkout [init|add]'.

Add '--cone' flag to 'git sparse-checkout init' to set the config
option 'core.sparseCheckout=cone'.

When running 'git sparse-checkout add' in cone mode, a user only
needs to supply a list of recursive folder matches. Git will
automatically add the necessary parent matches for the leading
directories.

Signed-off-by: Derrick Stolee <dsto...@microsoft.com>
---
 builtin/sparse-checkout.c          | 134 +++++++++++++++++++++++++++--
 t/t1091-sparse-checkout-builtin.sh |  35 ++++++++
 2 files changed, 164 insertions(+), 5 deletions(-)

diff --git a/builtin/sparse-checkout.c b/builtin/sparse-checkout.c
index 77e5235720..0a4e101ddd 100644
--- a/builtin/sparse-checkout.c
+++ b/builtin/sparse-checkout.c
@@ -6,15 +6,22 @@
 #include "repository.h"
 #include "run-command.h"
 #include "strbuf.h"
+#include "string-list.h"
 
 static char const * const builtin_sparse_checkout_usage[] = {
        N_("git sparse-checkout [init|add|list|disable]"),
        NULL
 };
 
+static const char * const builtin_sparse_checkout_init_usage[] = {
+       N_("git sparse-checkout init [--cone]"),
+       NULL
+};
+
 struct opts_sparse_checkout {
        const char *subcommand;
        int read_stdin;
+       int cone;
 } opts;
 
 static char *get_sparse_checkout_filename(void)
@@ -41,6 +48,60 @@ static void write_excludes_to_file(FILE *fp, struct 
exclude_list *el)
        }
 }
 
+static void write_cone_to_file(FILE *fp, struct exclude_list *el)
+{
+       int i;
+       struct exclude_entry *entry;
+       struct hashmap_iter iter;
+       struct string_list sl = STRING_LIST_INIT_DUP;
+
+       hashmap_iter_init(&el->parent_hashmap, &iter);
+       while ((entry = hashmap_iter_next(&iter))) {
+               char *pattern = xstrdup(entry->pattern);
+               char *converted = pattern;
+               if (pattern[0] == '/')
+                       converted++;
+               if (pattern[entry->patternlen - 1] == '/')
+                       pattern[entry->patternlen - 1] = 0;
+               string_list_insert(&sl, converted);
+               free(pattern);
+       }
+
+       string_list_sort(&sl);
+       string_list_remove_duplicates(&sl, 0);
+
+       for (i = 0; i < sl.nr; i++) {
+               char *pattern = sl.items[i].string;
+
+               if (!strcmp(pattern, ""))
+                       fprintf(fp, "/*\n!/*/*\n");
+               else
+                       fprintf(fp, "/%s/*\n!/%s/*/*\n", pattern, pattern);
+       }
+
+       string_list_clear(&sl, 0);
+
+       hashmap_iter_init(&el->recursive_hashmap, &iter);
+       while ((entry = hashmap_iter_next(&iter))) {
+               char *pattern = xstrdup(entry->pattern);
+               char *converted = pattern;
+               if (pattern[0] == '/')
+                       converted++;
+               if (pattern[entry->patternlen - 1] == '/')
+                       pattern[entry->patternlen - 1] = 0;
+               string_list_insert(&sl, converted);
+               free(pattern);
+       }
+
+       string_list_sort(&sl);
+       string_list_remove_duplicates(&sl, 0);
+
+       for (i = 0; i < sl.nr; i++) {
+               char *pattern = sl.items[i].string;
+               fprintf(fp, "/%s/*\n", pattern);
+       }
+}
+
 static int sparse_checkout_list(int argc, const char **argv)
 {
        struct exclude_list el;
@@ -141,8 +202,21 @@ static int sparse_checkout_init(int argc, const char 
**argv)
        char *sparse_filename;
        FILE *fp;
        int res;
+       enum sparse_checkout_mode mode;
 
-       if (sc_set_config(SPARSE_CHECKOUT_FULL))
+       static struct option builtin_sparse_checkout_init_options[] = {
+               OPT_BOOL(0, "cone", &opts.cone,
+                        N_("initialize the sparse-checkout in cone mode")),
+               OPT_END(),
+       };
+
+       argc = parse_options(argc, argv, NULL,
+                            builtin_sparse_checkout_init_options,
+                            builtin_sparse_checkout_init_usage, 0);
+
+       mode = opts.cone ? SPARSE_CHECKOUT_CONE : SPARSE_CHECKOUT_FULL;
+
+       if (sc_set_config(mode))
                return 1;
 
        memset(&el, 0, sizeof(el));
@@ -183,6 +257,34 @@ static int sparse_checkout_init(int argc, const char 
**argv)
        return sc_read_tree();
 }
 
+static void insert_recursive_pattern(struct exclude_list *el, struct strbuf 
*path)
+{
+       struct exclude_entry *e = xmalloc(sizeof(struct exclude_entry));
+       e->patternlen = path->len;
+       e->pattern = strbuf_detach(path, NULL);
+       hashmap_entry_init(e, memhash(e->pattern, e->patternlen));
+
+       hashmap_add(&el->recursive_hashmap, e);
+
+       while (e->patternlen) {
+               char *slash = strrchr(e->pattern, '/');
+               char *oldpattern = e->pattern;
+               size_t newlen;
+
+               if (!slash)
+                       break;
+
+               newlen = slash - e->pattern;
+               e = xmalloc(sizeof(struct exclude_entry));
+               e->patternlen = newlen;
+               e->pattern = xstrndup(oldpattern, newlen);
+               hashmap_entry_init(e, memhash(e->pattern, e->patternlen));
+
+               if (!hashmap_get(&el->parent_hashmap, e, NULL))
+                       hashmap_add(&el->parent_hashmap, e);
+       }
+}
+
 static int sparse_checkout_add(int argc, const char **argv)
 {
        struct exclude_list el;
@@ -196,11 +298,33 @@ static int sparse_checkout_add(int argc, const char 
**argv)
        add_excludes_from_file_to_list(sparse_filename, "", 0, &el, NULL);
 
        fp = fopen(sparse_filename, "w");
-       write_excludes_to_file(fp, &el);
 
-       while (!strbuf_getline(&line, stdin)) {
-               strbuf_trim(&line);
-               fprintf(fp, "%s\n", line.buf);
+       if (core_sparse_checkout == SPARSE_CHECKOUT_FULL) {
+               write_excludes_to_file(fp, &el);
+
+               while (!strbuf_getline(&line, stdin)) {
+                       strbuf_trim(&line);
+                       fprintf(fp, "%s\n", line.buf);
+               }
+       } else if (core_sparse_checkout == SPARSE_CHECKOUT_CONE) {
+               while (!strbuf_getline(&line, stdin)) {
+                       strbuf_trim(&line);
+
+                       strbuf_trim_trailing_dir_sep(&line);
+
+                       if (!line.len)
+                               continue;
+
+                       if (line.buf[0] == '/')
+                               strbuf_remove(&line, 0, 1);
+
+                       if (!line.len)
+                               continue;
+
+                       insert_recursive_pattern(&el, &line);
+               }
+
+               write_cone_to_file(fp, &el);
        }
 
        fclose(fp);
diff --git a/t/t1091-sparse-checkout-builtin.sh 
b/t/t1091-sparse-checkout-builtin.sh
index 60f10864a1..3412bafdff 100755
--- a/t/t1091-sparse-checkout-builtin.sh
+++ b/t/t1091-sparse-checkout-builtin.sh
@@ -157,4 +157,39 @@ test_expect_success 'sparse-checkout disable' '
        test_cmp expect dir
 '
 
+test_expect_success 'cone mode: init and add' '
+       git -C repo sparse-checkout init --cone &&
+       git -C repo config --list >config &&
+       test_i18ngrep "core.sparsecheckout=cone" config &&
+       ls repo >dir  &&
+       echo a >expect &&
+       test_cmp expect dir &&
+       echo deep/deeper1/deepest | git -C repo sparse-checkout add &&
+       ls repo >dir  &&
+       cat >expect <<-EOF &&
+               a
+               deep
+       EOF
+       ls repo/deep >dir  &&
+       cat >expect <<-EOF &&
+               a
+               deeper1
+       EOF
+       ls repo/deep/deeper1 >dir  &&
+       cat >expect <<-EOF &&
+               a
+               deepest
+       EOF
+       test_cmp expect dir &&
+       cat >expect <<-EOF &&
+               /*
+               !/*/*
+               /deep/*
+               !/deep/*/*
+               /deep/deeper1/*
+               !/deep/deeper1/*/*
+               /deep/deeper1/deepest/*
+       EOF
+       test_cmp expect repo/.git/info/sparse-checkout
+'
 test_done
\ No newline at end of file
-- 
gitgitgadget

Reply via email to