Skip to content

Commit

Permalink
Merge branch 'en/sparse-checkout-fixes' into jch
Browse files Browse the repository at this point in the history
Further polishing of "git sparse-checkout".

* en/sparse-checkout-fixes:
  sparse-checkout: reject arguments in cone-mode that look like patterns
  sparse-checkout: error or warn when given individual files
  sparse-checkout: pay attention to prefix for {set, add}
  sparse-checkout: correctly set non-cone mode when expected
  sparse-checkout: correct reapply's handling of options
  • Loading branch information
gitster committed Feb 25, 2022
2 parents 91a83ab + 8dd7c47 commit bdf35d8
Show file tree
Hide file tree
Showing 2 changed files with 161 additions and 7 deletions.
78 changes: 73 additions & 5 deletions builtin/sparse-checkout.c
@@ -1,4 +1,5 @@
#include "builtin.h"
#include "cache.h"
#include "config.h"
#include "dir.h"
#include "parse-options.h"
Expand Down Expand Up @@ -401,6 +402,7 @@ static int update_modes(int *cone_mode, int *sparse_index)
core_sparse_checkout_cone = 1;
} else {
mode = MODE_ALL_PATTERNS;
core_sparse_checkout_cone = 0;
}
if (record_mode && set_config(mode))
return 1;
Expand Down Expand Up @@ -681,18 +683,76 @@ static int modify_pattern_list(int argc, const char **argv, int use_stdin,
return result;
}

static void sanitize_paths(int argc, const char **argv,
const char *prefix, int skip_checks)
{
int i;

if (!argc)
return;

if (prefix && *prefix && core_sparse_checkout_cone) {
/*
* The args are not pathspecs, so unfortunately we
* cannot imitate how cmd_add() uses parse_pathspec().
*/
int prefix_len = strlen(prefix);

for (i = 0; i < argc; i++)
argv[i] = prefix_path(prefix, prefix_len, argv[i]);
}

if (skip_checks)
return;

if (prefix && *prefix && !core_sparse_checkout_cone)
die(_("please run from the toplevel directory in non-cone mode"));

if (core_sparse_checkout_cone) {
for (i = 0; i < argc; i++) {
if (argv[i][0] == '/')
die(_("specify directories rather than patterns (no leading slash)"));
if (argv[i][0] == '!')
die(_("specify directories rather than patterns. If your directory starts with a '!', pass --skip-checks"));
if (strpbrk(argv[i], "*?[]"))
die(_("specify directories rather than patterns. If your directory really has any of '*?[]\\' in it, pass --skip-checks"));
}
}

for (i = 0; i < argc; i++) {
struct cache_entry *ce;
struct index_state *index = the_repository->index;
int pos = index_name_pos(index, argv[i], strlen(argv[i]));

if (pos < 0)
continue;
ce = index->cache[pos];
if (S_ISSPARSEDIR(ce->ce_mode))
continue;

if (core_sparse_checkout_cone)
die(_("'%s' is not a directory; to treat it as a directory anyway, rerun with --skip-checks"), argv[i]);
else
warning(_("pass a leading slash before paths such as '%s' if you want a single file (see NON-CONE PROBLEMS in the git-sparse-checkout manual)."), argv[i]);
}
}

static char const * const builtin_sparse_checkout_add_usage[] = {
N_("git sparse-checkout add (--stdin | <patterns>)"),
N_("git sparse-checkout add [--skip-checks] (--stdin | <patterns>)"),
NULL
};

static struct sparse_checkout_add_opts {
int skip_checks;
int use_stdin;
} add_opts;

static int sparse_checkout_add(int argc, const char **argv, const char *prefix)
{
static struct option builtin_sparse_checkout_add_options[] = {
OPT_BOOL_F(0, "skip-checks", &add_opts.skip_checks,
N_("skip some sanity checks on the given paths that might give false positives"),
PARSE_OPT_NONEG),
OPT_BOOL(0, "stdin", &add_opts.use_stdin,
N_("read patterns from standard in")),
OPT_END(),
Expand All @@ -708,17 +768,20 @@ static int sparse_checkout_add(int argc, const char **argv, const char *prefix)
builtin_sparse_checkout_add_usage,
PARSE_OPT_KEEP_UNKNOWN);

sanitize_paths(argc, argv, prefix, add_opts.skip_checks);

return modify_pattern_list(argc, argv, add_opts.use_stdin, ADD);
}

static char const * const builtin_sparse_checkout_set_usage[] = {
N_("git sparse-checkout set [--[no-]cone] [--[no-]sparse-index] (--stdin | <patterns>)"),
N_("git sparse-checkout set [--[no-]cone] [--[no-]sparse-index] [--skip-checks] (--stdin | <patterns>)"),
NULL
};

static struct sparse_checkout_set_opts {
int cone_mode;
int sparse_index;
int skip_checks;
int use_stdin;
} set_opts;

Expand All @@ -732,6 +795,9 @@ static int sparse_checkout_set(int argc, const char **argv, const char *prefix)
N_("initialize the sparse-checkout in cone mode")),
OPT_BOOL(0, "sparse-index", &set_opts.sparse_index,
N_("toggle the use of a sparse index")),
OPT_BOOL_F(0, "skip-checks", &set_opts.skip_checks,
N_("skip some sanity checks on the given paths that might give false positives"),
PARSE_OPT_NONEG),
OPT_BOOL_F(0, "stdin", &set_opts.use_stdin,
N_("read patterns from standard in"),
PARSE_OPT_NONEG),
Expand Down Expand Up @@ -759,6 +825,8 @@ static int sparse_checkout_set(int argc, const char **argv, const char *prefix)
if (!core_sparse_checkout_cone && argc == 0) {
argv = default_patterns;
argc = default_patterns_nr;
} else {
sanitize_paths(argc, argv, prefix, set_opts.skip_checks);
}

return modify_pattern_list(argc, argv, set_opts.use_stdin, REPLACE);
Expand Down Expand Up @@ -787,15 +855,15 @@ static int sparse_checkout_reapply(int argc, const char **argv)
if (!core_apply_sparse_checkout)
die(_("must be in a sparse-checkout to reapply sparsity patterns"));

reapply_opts.cone_mode = -1;
reapply_opts.sparse_index = -1;

argc = parse_options(argc, argv, NULL,
builtin_sparse_checkout_reapply_options,
builtin_sparse_checkout_reapply_usage, 0);

repo_read_index(the_repository);

reapply_opts.cone_mode = -1;
reapply_opts.sparse_index = -1;

if (update_modes(&reapply_opts.cone_mode, &reapply_opts.sparse_index))
return 1;

Expand Down
90 changes: 88 additions & 2 deletions t/t1091-sparse-checkout-builtin.sh
Expand Up @@ -510,6 +510,37 @@ test_expect_failure 'sparse-checkout reapply' '
git -C tweak sparse-checkout disable
'

test_expect_success 'reapply can handle config options' '
git -C repo sparse-checkout init --cone --no-sparse-index &&
git -C repo config --worktree --list >actual &&
cat >expect <<-\EOF &&
core.sparsecheckout=true
core.sparsecheckoutcone=true
index.sparse=false
EOF
test_cmp expect actual &&
git -C repo sparse-checkout reapply --no-cone --no-sparse-index &&
git -C repo config --worktree --list >actual &&
cat >expect <<-\EOF &&
core.sparsecheckout=true
core.sparsecheckoutcone=false
index.sparse=false
EOF
test_cmp expect actual &&
git -C repo sparse-checkout reapply --cone --sparse-index &&
git -C repo config --worktree --list >actual &&
cat >expect <<-\EOF &&
core.sparsecheckout=true
core.sparsecheckoutcone=true
index.sparse=true
EOF
test_cmp expect actual &&
git -C repo sparse-checkout disable
'

test_expect_success 'cone mode: set with core.ignoreCase=true' '
rm repo/.git/info/sparse-checkout &&
git -C repo sparse-checkout init --cone &&
Expand Down Expand Up @@ -549,7 +580,7 @@ test_expect_success 'different sparse-checkouts with worktrees' '
'

test_expect_success 'set using filename keeps file on-disk' '
git -C repo sparse-checkout set a deep &&
git -C repo sparse-checkout set --skip-checks a deep &&
cat >expect <<-\EOF &&
/*
!/*/
Expand Down Expand Up @@ -660,7 +691,7 @@ test_expect_success BSLASHPSPEC 'pattern-checks: escaped characters' '
git -C escaped reset --hard $COMMIT &&
check_files escaped "a deep folder1 folder2 zbad\\dir zdoes*exist" zglob[!a]? &&
git -C escaped sparse-checkout init --cone &&
git -C escaped sparse-checkout set zbad\\dir/bogus "zdoes*not*exist" "zdoes*exist" "zglob[!a]?" &&
git -C escaped sparse-checkout set --skip-checks zbad\\dir/bogus "zdoes*not*exist" "zdoes*exist" "zglob[!a]?" &&
cat >expect <<-\EOF &&
/*
!/*/
Expand Down Expand Up @@ -785,4 +816,59 @@ test_expect_success 'malformed cone-mode patterns' '
grep "warning: disabling cone pattern matching" err
'

test_expect_success 'set from subdir pays attention to prefix' '
git -C repo sparse-checkout disable &&
git -C repo/deep sparse-checkout set --cone deeper2 ../folder1 &&
git -C repo sparse-checkout list >actual &&
cat >expect <<-\EOF &&
deep/deeper2
folder1
EOF
test_cmp expect actual
'

test_expect_success 'add from subdir pays attention to prefix' '
git -C repo sparse-checkout set --cone deep/deeper2 &&
git -C repo/deep sparse-checkout add deeper1/deepest ../folder1 &&
git -C repo sparse-checkout list >actual &&
cat >expect <<-\EOF &&
deep/deeper1/deepest
deep/deeper2
folder1
EOF
test_cmp expect actual
'

test_expect_success 'set from subdir in non-cone mode throws an error' '
git -C repo sparse-checkout disable &&
test_must_fail git -C repo/deep sparse-checkout set --no-cone deeper2 ../folder1 2>error &&
grep "run from the toplevel directory in non-cone mode" error
'

test_expect_success 'set from subdir in non-cone mode throws an error' '
git -C repo sparse-checkout set --no-cone deep/deeper2 &&
test_must_fail git -C repo/deep sparse-checkout add deeper1/deepest ../folder1 2>error &&
grep "run from the toplevel directory in non-cone mode" error
'

test_expect_success 'by default, cone mode will error out when passed files' '
git -C repo sparse-checkout reapply --cone &&
test_must_fail git -C repo sparse-checkout add .gitignore 2>error &&
grep ".gitignore.*is not a directory" error
'

test_expect_success 'by default, non-cone mode will warn on individual files' '
git -C repo sparse-checkout reapply --no-cone &&
git -C repo sparse-checkout add .gitignore 2>warning &&
grep "pass a leading slash before paths.*if you want a single file" warning
'

test_done

0 comments on commit bdf35d8

Please sign in to comment.