Skip to content

Commit 4ce5043

Browse files
newrengitster
authored andcommitted
sparse-checkout: error or warn when given individual files
The set and add subcommands accept multiple positional arguments. The meaning of these arguments differs slightly in the two modes: Cone mode only accepts directories. If given a file, it would previously treat it as a directory, causing not just the file itself to be included but all sibling files as well -- likely against users' expectations. Throw an error if the specified path is a file in the index. Provide a --skip-checks argument to allow users to override (e.g. for the case when the given path IS a directory on another branch). Non-cone mode accepts general gitignore patterns. There are many reasons to avoid this mode, but one possible reason to use it instead of cone mode: to be able to select individual files within a directory. However, if a file is passed to set/add in non-cone mode, you won't be selecting a single file, you'll be selecting a file with the same name in any directory. Thus users will likely want to prefix any paths they specify with a leading '/' character; warn users if the patterns they specify exactly name a file because it means they are likely missing such a leading slash. Reviewed-by: Derrick Stolee <[email protected]> Signed-off-by: Elijah Newren <[email protected]> Signed-off-by: Junio C Hamano <[email protected]>
1 parent bb8b5e9 commit 4ce5043

File tree

2 files changed

+51
-7
lines changed

2 files changed

+51
-7
lines changed

builtin/sparse-checkout.c

Lines changed: 36 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
#include "builtin.h"
2+
#include "cache.h"
23
#include "config.h"
34
#include "dir.h"
45
#include "parse-options.h"
@@ -684,8 +685,11 @@ static int modify_pattern_list(int argc, const char **argv, int use_stdin,
684685
return result;
685686
}
686687

687-
static void sanitize_paths(int argc, const char **argv, const char *prefix)
688+
static void sanitize_paths(int argc, const char **argv,
689+
const char *prefix, int skip_checks)
688690
{
691+
int i;
692+
689693
if (!argc)
690694
return;
691695

@@ -694,30 +698,52 @@ static void sanitize_paths(int argc, const char **argv, const char *prefix)
694698
* The args are not pathspecs, so unfortunately we
695699
* cannot imitate how cmd_add() uses parse_pathspec().
696700
*/
697-
int i;
698701
int prefix_len = strlen(prefix);
699702

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

707+
if (skip_checks)
708+
return;
709+
704710
if (prefix && *prefix && !core_sparse_checkout_cone)
705711
die(_("please run from the toplevel directory in non-cone mode"));
706712

713+
for (i = 0; i < argc; i++) {
714+
struct cache_entry *ce;
715+
struct index_state *index = the_repository->index;
716+
int pos = index_name_pos(index, argv[i], strlen(argv[i]));
717+
718+
if (pos < 0)
719+
continue;
720+
ce = index->cache[pos];
721+
if (S_ISSPARSEDIR(ce->ce_mode))
722+
continue;
723+
724+
if (core_sparse_checkout_cone)
725+
die(_("'%s' is not a directory; to treat it as a directory anyway, rerun with --skip-checks"), argv[i]);
726+
else
727+
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]);
728+
}
707729
}
708730

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

714736
static struct sparse_checkout_add_opts {
737+
int skip_checks;
715738
int use_stdin;
716739
} add_opts;
717740

718741
static int sparse_checkout_add(int argc, const char **argv, const char *prefix)
719742
{
720743
static struct option builtin_sparse_checkout_add_options[] = {
744+
OPT_BOOL_F(0, "skip-checks", &add_opts.skip_checks,
745+
N_("skip some sanity checks on the given paths that might give false positives"),
746+
PARSE_OPT_NONEG),
721747
OPT_BOOL(0, "stdin", &add_opts.use_stdin,
722748
N_("read patterns from standard in")),
723749
OPT_END(),
@@ -733,19 +759,20 @@ static int sparse_checkout_add(int argc, const char **argv, const char *prefix)
733759
builtin_sparse_checkout_add_usage,
734760
PARSE_OPT_KEEP_UNKNOWN);
735761

736-
sanitize_paths(argc, argv, prefix);
762+
sanitize_paths(argc, argv, prefix, add_opts.skip_checks);
737763

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

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

746772
static struct sparse_checkout_set_opts {
747773
int cone_mode;
748774
int sparse_index;
775+
int skip_checks;
749776
int use_stdin;
750777
} set_opts;
751778

@@ -759,6 +786,9 @@ static int sparse_checkout_set(int argc, const char **argv, const char *prefix)
759786
N_("initialize the sparse-checkout in cone mode")),
760787
OPT_BOOL(0, "sparse-index", &set_opts.sparse_index,
761788
N_("toggle the use of a sparse index")),
789+
OPT_BOOL_F(0, "skip-checks", &set_opts.skip_checks,
790+
N_("skip some sanity checks on the given paths that might give false positives"),
791+
PARSE_OPT_NONEG),
762792
OPT_BOOL_F(0, "stdin", &set_opts.use_stdin,
763793
N_("read patterns from standard in"),
764794
PARSE_OPT_NONEG),
@@ -787,7 +817,7 @@ static int sparse_checkout_set(int argc, const char **argv, const char *prefix)
787817
argv = default_patterns;
788818
argc = default_patterns_nr;
789819
} else {
790-
sanitize_paths(argc, argv, prefix);
820+
sanitize_paths(argc, argv, prefix, set_opts.skip_checks);
791821
}
792822

793823
return modify_pattern_list(argc, argv, set_opts.use_stdin, REPLACE);

t/t1091-sparse-checkout-builtin.sh

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -562,7 +562,7 @@ test_expect_success 'different sparse-checkouts with worktrees' '
562562
'
563563

564564
test_expect_success 'set using filename keeps file on-disk' '
565-
git -C repo sparse-checkout set a deep &&
565+
git -C repo sparse-checkout set --skip-checks a deep &&
566566
cat >expect <<-\EOF &&
567567
/*
568568
!/*/
@@ -839,4 +839,18 @@ test_expect_success 'set from subdir in non-cone mode throws an error' '
839839
grep "run from the toplevel directory in non-cone mode" error
840840
'
841841

842+
test_expect_success 'by default, cone mode will error out when passed files' '
843+
git -C repo sparse-checkout reapply --cone &&
844+
test_must_fail git -C repo sparse-checkout add .gitignore 2>error &&
845+
846+
grep ".gitignore.*is not a directory" error
847+
'
848+
849+
test_expect_success 'by default, non-cone mode will warn on individual files' '
850+
git -C repo sparse-checkout reapply --no-cone &&
851+
git -C repo sparse-checkout add .gitignore 2>warning &&
852+
853+
grep "pass a leading slash before paths.*if you want a single file" warning
854+
'
855+
842856
test_done

0 commit comments

Comments
 (0)