]> git.ipfire.org Git - thirdparty/git.git/commitdiff
sparse-checkout: error or warn when given individual files
authorElijah Newren <newren@gmail.com>
Sat, 19 Feb 2022 16:44:44 +0000 (16:44 +0000)
committerJunio C Hamano <gitster@pobox.com>
Sun, 20 Feb 2022 08:01:15 +0000 (00:01 -0800)
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 <derrickstolee@github.com>
Signed-off-by: Elijah Newren <newren@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
builtin/sparse-checkout.c
t/t1091-sparse-checkout-builtin.sh

index 3f828feb1f056dadebe7b4e3dad5ff72d26d71d7..38ac37d9c61dfafb8eec60afd1aa49f3b385bb6f 100644 (file)
@@ -1,4 +1,5 @@
 #include "builtin.h"
+#include "cache.h"
 #include "config.h"
 #include "dir.h"
 #include "parse-options.h"
@@ -684,8 +685,11 @@ 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)
+static void sanitize_paths(int argc, const char **argv,
+                          const char *prefix, int skip_checks)
 {
+       int i;
+
        if (!argc)
                return;
 
@@ -694,30 +698,52 @@ static void sanitize_paths(int argc, const char **argv, const char *prefix)
                 * The args are not pathspecs, so unfortunately we
                 * cannot imitate how cmd_add() uses parse_pathspec().
                 */
-               int i;
                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"));
 
+       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(),
@@ -733,19 +759,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);
+       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;
 
@@ -759,6 +786,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),
@@ -787,7 +817,7 @@ static int sparse_checkout_set(int argc, const char **argv, const char *prefix)
                argv = default_patterns;
                argc = default_patterns_nr;
        } else {
-               sanitize_paths(argc, argv, prefix);
+               sanitize_paths(argc, argv, prefix, set_opts.skip_checks);
        }
 
        return modify_pattern_list(argc, argv, set_opts.use_stdin, REPLACE);
index c1f86b0e02e2d45a77b03fb43b7ca62db25f6307..3b39329266b7fc67e678edb745ac8831df8259b7 100755 (executable)
@@ -562,7 +562,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 &&
        /*
        !/*/
@@ -839,4 +839,18 @@ test_expect_success 'set from subdir in non-cone mode throws an 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