sparse-checkout: init and set in cone mode

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

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

When running 'git sparse-checkout set' 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.

When testing 'git sparse-checkout set' in cone mode, check the
error stream to ensure we do not see any errors. Specifically,
we want to avoid the warning that the patterns do not match
the cone-mode patterns.

Helped-by: Eric Wong <e@80x24.org>
Helped-by: Johannes Schindelin <Johannes.Schindelin@gmx.de>
Signed-off-by: Derrick Stolee <dstolee@microsoft.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
This commit is contained in:
Derrick Stolee 2019-11-21 22:04:42 +00:00 committed by Junio C Hamano
parent 96cc8ab531
commit af09ce24a9
4 changed files with 206 additions and 20 deletions

View File

@ -6,6 +6,7 @@
#include "repository.h" #include "repository.h"
#include "run-command.h" #include "run-command.h"
#include "strbuf.h" #include "strbuf.h"
#include "string-list.h"
static char const * const builtin_sparse_checkout_usage[] = { static char const * const builtin_sparse_checkout_usage[] = {
N_("git sparse-checkout (init|list|set|disable) <options>"), N_("git sparse-checkout (init|list|set|disable) <options>"),
@ -74,9 +75,65 @@ static int update_working_directory(void)
return result; return result;
} }
static void write_cone_to_file(FILE *fp, struct pattern_list *pl)
{
int i;
struct pattern_entry *pe;
struct hashmap_iter iter;
struct string_list sl = STRING_LIST_INIT_DUP;
hashmap_for_each_entry(&pl->parent_hashmap, &iter, pe, ent)
string_list_insert(&sl, pe->pattern);
string_list_sort(&sl);
string_list_remove_duplicates(&sl, 0);
fprintf(fp, "/*\n!/*/\n");
for (i = 0; i < sl.nr; i++) {
char *pattern = sl.items[i].string;
if (strlen(pattern))
fprintf(fp, "%s/\n!%s/*/\n", pattern, pattern);
}
string_list_clear(&sl, 0);
hashmap_for_each_entry(&pl->recursive_hashmap, &iter, pe, ent)
string_list_insert(&sl, pe->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 write_patterns_and_update(struct pattern_list *pl)
{
char *sparse_filename;
FILE *fp;
sparse_filename = get_sparse_checkout_filename();
fp = fopen(sparse_filename, "w");
if (core_sparse_checkout_cone)
write_cone_to_file(fp, pl);
else
write_patterns_to_file(fp, pl);
fclose(fp);
free(sparse_filename);
return update_working_directory();
}
enum sparse_checkout_mode { enum sparse_checkout_mode {
MODE_NO_PATTERNS = 0, MODE_NO_PATTERNS = 0,
MODE_ALL_PATTERNS = 1, MODE_ALL_PATTERNS = 1,
MODE_CONE_PATTERNS = 2,
}; };
static int set_config(enum sparse_checkout_mode mode) static int set_config(enum sparse_checkout_mode mode)
@ -93,9 +150,22 @@ static int set_config(enum sparse_checkout_mode mode)
"core.sparseCheckout", "core.sparseCheckout",
mode ? "true" : NULL); mode ? "true" : NULL);
git_config_set_in_file_gently(config_path,
"core.sparseCheckoutCone",
mode == MODE_CONE_PATTERNS ? "true" : NULL);
return 0; return 0;
} }
static char const * const builtin_sparse_checkout_init_usage[] = {
N_("git sparse-checkout init [--cone]"),
NULL
};
static struct sparse_checkout_init_opts {
int cone_mode;
} init_opts;
static int sparse_checkout_init(int argc, const char **argv) static int sparse_checkout_init(int argc, const char **argv)
{ {
struct pattern_list pl; struct pattern_list pl;
@ -103,8 +173,21 @@ static int sparse_checkout_init(int argc, const char **argv)
FILE *fp; FILE *fp;
int res; int res;
struct object_id oid; struct object_id oid;
int mode;
if (set_config(MODE_ALL_PATTERNS)) static struct option builtin_sparse_checkout_init_options[] = {
OPT_BOOL(0, "cone", &init_opts.cone_mode,
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 = init_opts.cone_mode ? MODE_CONE_PATTERNS : MODE_ALL_PATTERNS;
if (set_config(mode))
return 1; return 1;
memset(&pl, 0, sizeof(pl)); memset(&pl, 0, sizeof(pl));
@ -136,18 +219,47 @@ reset_dir:
return update_working_directory(); return update_working_directory();
} }
static int write_patterns_and_update(struct pattern_list *pl) static void insert_recursive_pattern(struct pattern_list *pl, struct strbuf *path)
{ {
char *sparse_filename; struct pattern_entry *e = xmalloc(sizeof(*e));
FILE *fp; e->patternlen = path->len;
e->pattern = strbuf_detach(path, NULL);
hashmap_entry_init(&e->ent, memhash(e->pattern, e->patternlen));
sparse_filename = get_sparse_checkout_filename(); hashmap_add(&pl->recursive_hashmap, &e->ent);
fp = fopen(sparse_filename, "w");
write_patterns_to_file(fp, pl);
fclose(fp);
free(sparse_filename);
return update_working_directory(); while (e->patternlen) {
char *slash = strrchr(e->pattern, '/');
char *oldpattern = e->pattern;
size_t newlen;
if (slash == e->pattern)
break;
newlen = slash - e->pattern;
e = xmalloc(sizeof(struct pattern_entry));
e->patternlen = newlen;
e->pattern = xstrndup(oldpattern, newlen);
hashmap_entry_init(&e->ent, memhash(e->pattern, e->patternlen));
if (!hashmap_get_entry(&pl->parent_hashmap, e, ent, NULL))
hashmap_add(&pl->parent_hashmap, &e->ent);
}
}
static void strbuf_to_cone_pattern(struct strbuf *line, struct pattern_list *pl)
{
strbuf_trim(line);
strbuf_trim_trailing_dir_sep(line);
if (!line->len)
return;
if (line->buf[0] != '/')
strbuf_insert(line, 0, "/", 1);
insert_recursive_pattern(pl, line);
} }
static char const * const builtin_sparse_checkout_set_usage[] = { static char const * const builtin_sparse_checkout_set_usage[] = {
@ -180,17 +292,36 @@ static int sparse_checkout_set(int argc, const char **argv, const char *prefix)
builtin_sparse_checkout_set_usage, builtin_sparse_checkout_set_usage,
PARSE_OPT_KEEP_UNKNOWN); 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);
if (set_opts.use_stdin) {
while (!strbuf_getline(&line, stdin))
strbuf_to_cone_pattern(&line, &pl);
} else {
for (i = 0; i < argc; i++) {
strbuf_setlen(&line, 0);
strbuf_addstr(&line, argv[i]);
strbuf_to_cone_pattern(&line, &pl);
}
}
} else {
if (set_opts.use_stdin) { if (set_opts.use_stdin) {
struct strbuf line = STRBUF_INIT; struct strbuf line = STRBUF_INIT;
while (!strbuf_getline(&line, stdin)) { while (!strbuf_getline(&line, stdin)) {
char *buf = strbuf_detach(&line, NULL); 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 { } else {
for (i = 0; i < argc; i++) for (i = 0; i < argc; i++)
add_pattern(argv[i], empty_base, 0, &pl, 0); add_pattern(argv[i], empty_base, 0, &pl, 0);
} }
}
if (!core_apply_sparse_checkout) { if (!core_apply_sparse_checkout) {
set_config(MODE_ALL_PATTERNS); set_config(MODE_ALL_PATTERNS);

2
dir.c
View File

@ -611,7 +611,7 @@ void parse_path_pattern(const char **pattern,
*patternlen = len; *patternlen = len;
} }
static int pl_hashmap_cmp(const void *unused_cmp_data, int pl_hashmap_cmp(const void *unused_cmp_data,
const struct hashmap_entry *a, const struct hashmap_entry *a,
const struct hashmap_entry *b, const struct hashmap_entry *b,
const void *key) const void *key)

4
dir.h
View File

@ -299,6 +299,10 @@ int is_excluded(struct dir_struct *dir,
struct index_state *istate, struct index_state *istate,
const char *name, int *dtype); const char *name, int *dtype);
int pl_hashmap_cmp(const void *unused_cmp_data,
const struct hashmap_entry *a,
const struct hashmap_entry *b,
const void *key);
int hashmap_contains_parent(struct hashmap *map, int hashmap_contains_parent(struct hashmap *map,
const char *path, const char *path,
struct strbuf *buffer); struct strbuf *buffer);

View File

@ -186,4 +186,55 @@ test_expect_success 'sparse-checkout disable' '
test_cmp expect dir test_cmp expect dir
' '
test_expect_success 'cone mode: init and set' '
git -C repo sparse-checkout init --cone &&
git -C repo config --list >config &&
test_i18ngrep "core.sparsecheckoutcone=true" config &&
ls repo >dir &&
echo a >expect &&
test_cmp expect dir &&
git -C repo sparse-checkout set deep/deeper1/deepest/ 2>err &&
test_must_be_empty err &&
ls repo >dir &&
cat >expect <<-EOF &&
a
deep
EOF
test_cmp expect dir &&
ls repo/deep >dir &&
cat >expect <<-EOF &&
a
deeper1
EOF
test_cmp expect dir &&
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 &&
git -C repo sparse-checkout set --stdin 2>err <<-EOF &&
folder1
folder2
EOF
test_must_be_empty err &&
cat >expect <<-EOF &&
a
folder1
folder2
EOF
ls repo >dir &&
test_cmp expect dir
'
test_done test_done