]> git.ipfire.org Git - thirdparty/git.git/commitdiff
builtin/clone.c: add --reject-shallow option
authorLi Linchao <lilinchao@oschina.cn>
Thu, 1 Apr 2021 10:46:59 +0000 (10:46 +0000)
committerJunio C Hamano <gitster@pobox.com>
Thu, 1 Apr 2021 19:58:58 +0000 (12:58 -0700)
In some scenarios, users may want more history than the repository
offered for cloning, which happens to be a shallow repository, can
give them. But because users don't know it is a shallow repository
until they download it to local, we may want to refuse to clone
this kind of repository, without creating any unnecessary files.

The '--depth=x' option cannot be used as a solution; the source may
be deep enough to give us 'x' commits when cloned, but the user may
later need to deepen the history to arbitrary depth.

Teach '--reject-shallow' option to "git clone" to abort as soon as
we find out that we are cloning from a shallow repository.

Signed-off-by: Li Linchao <lilinchao@oschina.cn>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
Documentation/config/clone.txt
Documentation/git-clone.txt
builtin/clone.c
fetch-pack.c
fetch-pack.h
t/t5601-clone.sh
t/t5606-clone-options.sh
t/t5611-clone-config.sh
transport.c
transport.h

index 47de36a5fedfbe8b4e0f76efcf65b59534063435..7bcfbd18a52a7a2ddfada41a245bb80d882a1bce 100644 (file)
@@ -2,3 +2,7 @@ clone.defaultRemoteName::
        The name of the remote to create when cloning a repository.  Defaults to
        `origin`, and can be overridden by passing the `--origin` command-line
        option to linkgit:git-clone[1].
+
+clone.rejectShallow::
+       Reject to clone a repository if it is a shallow one, can be overridden by
+       passing option `--reject-shallow` in command line. See linkgit:git-clone[1]
index 02d9c19cec7531db4ac9b592030a602d752bedcd..3fe3810f1ce11f9e3f963df4d17449763296d116 100644 (file)
@@ -15,7 +15,7 @@ SYNOPSIS
          [--dissociate] [--separate-git-dir <git dir>]
          [--depth <depth>] [--[no-]single-branch] [--no-tags]
          [--recurse-submodules[=<pathspec>]] [--[no-]shallow-submodules]
-         [--[no-]remote-submodules] [--jobs <n>] [--sparse]
+         [--[no-]remote-submodules] [--jobs <n>] [--sparse] [--[no-]reject-shallow]
          [--filter=<filter>] [--] <repository>
          [<directory>]
 
@@ -149,6 +149,11 @@ objects from the source repository into a pack in the cloned repository.
 --no-checkout::
        No checkout of HEAD is performed after the clone is complete.
 
+--[no-]reject-shallow::
+       Fail if the source repository is a shallow repository.
+       The 'clone.rejectShallow' configuration variable can be used to
+       specify the default.
+
 --bare::
        Make a 'bare' Git repository.  That is, instead of
        creating `<directory>` and placing the administrative
index 51e844a2de0a236f3ecd0305b3a5726aed80f5e6..2a5485b72499658ca76d4a65b29efb6123931db2 100644 (file)
@@ -50,6 +50,8 @@ static int option_no_checkout, option_bare, option_mirror, option_single_branch
 static int option_local = -1, option_no_hardlinks, option_shared;
 static int option_no_tags;
 static int option_shallow_submodules;
+static int option_reject_shallow = -1;    /* unspecified */
+static int config_reject_shallow = -1;    /* unspecified */
 static int deepen;
 static char *option_template, *option_depth, *option_since;
 static char *option_origin = NULL;
@@ -90,6 +92,8 @@ static struct option builtin_clone_options[] = {
        OPT__VERBOSITY(&option_verbosity),
        OPT_BOOL(0, "progress", &option_progress,
                 N_("force progress reporting")),
+       OPT_BOOL(0, "reject-shallow", &option_reject_shallow,
+                N_("don't clone shallow repository")),
        OPT_BOOL('n', "no-checkout", &option_no_checkout,
                 N_("don't create a checkout")),
        OPT_BOOL(0, "bare", &option_bare, N_("create a bare repository")),
@@ -858,6 +862,9 @@ static int git_clone_config(const char *k, const char *v, void *cb)
                free(remote_name);
                remote_name = xstrdup(v);
        }
+       if (!strcmp(k, "clone.rejectshallow"))
+               config_reject_shallow = git_config_bool(k, v);
+
        return git_default_config(k, v, cb);
 }
 
@@ -963,6 +970,7 @@ static int path_exists(const char *path)
 int cmd_clone(int argc, const char **argv, const char *prefix)
 {
        int is_bundle = 0, is_local;
+       int reject_shallow = 0;
        const char *repo_name, *repo, *work_tree, *git_dir;
        char *path, *dir, *display_repo = NULL;
        int dest_exists, real_dest_exists = 0;
@@ -1156,6 +1164,15 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
         */
        git_config(git_clone_config, NULL);
 
+       /*
+        * If option_reject_shallow is specified from CLI option,
+        * ignore config_reject_shallow from git_clone_config.
+        */
+       if (config_reject_shallow != -1)
+               reject_shallow = config_reject_shallow;
+       if (option_reject_shallow != -1)
+               reject_shallow = option_reject_shallow;
+
        /*
         * apply the remote name provided by --origin only after this second
         * call to git_config, to ensure it overrides all config-based values.
@@ -1216,6 +1233,8 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
                if (filter_options.choice)
                        warning(_("--filter is ignored in local clones; use file:// instead."));
                if (!access(mkpath("%s/shallow", path), F_OK)) {
+                       if (reject_shallow)
+                               die(_("source repository is shallow, reject to clone."));
                        if (option_local > 0)
                                warning(_("source repository is shallow, ignoring --local"));
                        is_local = 0;
@@ -1227,6 +1246,8 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
 
        transport_set_option(transport, TRANS_OPT_KEEP, "yes");
 
+       if (reject_shallow)
+               transport_set_option(transport, TRANS_OPT_REJECT_SHALLOW, "1");
        if (option_depth)
                transport_set_option(transport, TRANS_OPT_DEPTH,
                                     option_depth);
index fb04a76ca263042b145b40adf1577a81c6d7e12d..40392692ad07b2e530152c409b248f4357cbd0a8 100644 (file)
@@ -1129,9 +1129,11 @@ static struct ref *do_fetch_pack(struct fetch_pack_args *args,
        if (args->deepen)
                setup_alternate_shallow(&shallow_lock, &alternate_shallow_file,
                                        NULL);
-       else if (si->nr_ours || si->nr_theirs)
+       else if (si->nr_ours || si->nr_theirs) {
+               if (args->reject_shallow_remote)
+                       die(_("source repository is shallow, reject to clone."));
                alternate_shallow_file = setup_temporary_shallow(si->shallow);
-       else
+       else
                alternate_shallow_file = NULL;
        if (get_pack(args, fd, pack_lockfiles, NULL, sought, nr_sought,
                     &gitmodules_oids))
@@ -1498,10 +1500,12 @@ static void receive_shallow_info(struct fetch_pack_args *args,
                 * rejected (unless --update-shallow is set); do the same.
                 */
                prepare_shallow_info(si, shallows);
-               if (si->nr_ours || si->nr_theirs)
+               if (si->nr_ours || si->nr_theirs) {
+                       if (args->reject_shallow_remote)
+                               die(_("source repository is shallow, reject to clone."));
                        alternate_shallow_file =
                                setup_temporary_shallow(si->shallow);
-               else
+               else
                        alternate_shallow_file = NULL;
        } else {
                alternate_shallow_file = NULL;
index 736a3dae467ac08a402e6b45e305bab0e7911a37..f114d7277567529d9145b89eee8b32d1f3c65a15 100644 (file)
@@ -39,6 +39,7 @@ struct fetch_pack_args {
        unsigned self_contained_and_connected:1;
        unsigned cloning:1;
        unsigned update_shallow:1;
+       unsigned reject_shallow_remote:1;
        unsigned deepen:1;
 
        /*
index e7e6c089554c5d2b0ba7cd51b79a53f6880f0ae1..329ae599fd3c629b3ff354d6058fbb22b219c9d7 100755 (executable)
@@ -759,6 +759,15 @@ test_expect_success 'partial clone using HTTP' '
        partial_clone "$HTTPD_DOCUMENT_ROOT_PATH/server" "$HTTPD_URL/smart/server"
 '
 
+test_expect_success 'reject cloning shallow repository using HTTP' '
+       test_when_finished "rm -rf repo" &&
+       git clone --bare --no-local --depth=1 src "$HTTPD_DOCUMENT_ROOT_PATH/repo.git" &&
+       test_must_fail git clone --reject-shallow $HTTPD_URL/smart/repo.git repo 2>err &&
+       test_i18ngrep -e "source repository is shallow, reject to clone." err &&
+
+       git clone --no-reject-shallow $HTTPD_URL/smart/repo.git repo
+'
+
 # DO NOT add non-httpd-specific tests here, because the last part of this
 # test script is only executed when httpd is available and enabled.
 
index 428b0aac93fabd0b947294a9929dcab317fd72bd..3a595c0f82c7043a44887bdd8499b490355d675d 100755 (executable)
@@ -11,7 +11,8 @@ test_expect_success 'setup' '
        mkdir parent &&
        (cd parent && git init &&
         echo one >file && git add file &&
-        git commit -m one)
+        git commit -m one) &&
+       git clone --depth=1 --no-local parent shallow-repo
 
 '
 
@@ -45,6 +46,30 @@ test_expect_success 'disallows --bare with --separate-git-dir' '
 
 '
 
+test_expect_success 'reject cloning shallow repository' '
+       test_when_finished "rm -rf repo" &&
+       test_must_fail git clone --reject-shallow shallow-repo out 2>err &&
+       test_i18ngrep -e "source repository is shallow, reject to clone." err &&
+
+       git clone --no-reject-shallow shallow-repo repo
+'
+
+test_expect_success 'reject cloning non-local shallow repository' '
+       test_when_finished "rm -rf repo" &&
+       test_must_fail git clone --reject-shallow --no-local shallow-repo out 2>err &&
+       test_i18ngrep -e "source repository is shallow, reject to clone." err &&
+
+       git clone --no-reject-shallow --no-local shallow-repo repo
+'
+
+test_expect_success 'succeed cloning normal repository' '
+       test_when_finished "rm -rf chilad1 child2 child3 child4 " &&
+       git clone --reject-shallow parent child1 &&
+       git clone --reject-shallow --no-local parent child2 &&
+       git clone --no-reject-shallow parent child3 &&
+       git clone --no-reject-shallow --no-local parent child4
+'
+
 test_expect_success 'uses "origin" for default remote name' '
 
        git clone parent clone-default-origin &&
index 9f555b87ecdf4c09e2031d1ab485c33bf63a9f5f..f8625f915821b5db03a5667b548ed055d26ccca3 100755 (executable)
@@ -95,6 +95,31 @@ test_expect_success 'clone -c remote.<remote>.fetch=<refspec> --origin=<name>' '
        test_cmp expect actual
 '
 
+test_expect_success 'set up shallow repository' '
+       git clone --depth=1 --no-local . shallow-repo
+'
+
+test_expect_success 'clone.rejectshallow=true should reject cloning shallow repo' '
+       test_when_finished "rm -rf out" &&
+       test_must_fail git -c clone.rejectshallow=true clone --no-local shallow-repo out 2>err &&
+       test_i18ngrep -e "source repository is shallow, reject to clone." err &&
+
+       git -c clone.rejectshallow=false clone --no-local shallow-repo out
+'
+
+test_expect_success 'option --[no-]reject-shallow override clone.rejectshallow config' '
+       test_when_finished "rm -rf out" &&
+       test_must_fail git -c clone.rejectshallow=false clone --reject-shallow --no-local shallow-repo out 2>err &&
+       test_i18ngrep -e "source repository is shallow, reject to clone." err &&
+
+       git -c clone.rejectshallow=true clone --no-reject-shallow --no-local shallow-repo out
+'
+
+test_expect_success 'clone.rejectshallow=true should succeed cloning normal repo' '
+       test_when_finished "rm -rf out" &&
+       git -c clone.rejectshallow=true clone --no-local . out
+'
+
 test_expect_success MINGW 'clone -c core.hideDotFiles' '
        test_commit attributes .gitattributes "" &&
        rm -rf child &&
index 1c4ab676d1b148835431106d99766545c31da721..b231894f90395aade755aee408d92dd3be566e2b 100644 (file)
@@ -236,6 +236,9 @@ static int set_git_option(struct git_transport_options *opts,
                list_objects_filter_die_if_populated(&opts->filter_options);
                parse_list_objects_filter(&opts->filter_options, value);
                return 0;
+       } else if (!strcmp(name, TRANS_OPT_REJECT_SHALLOW)) {
+               opts->reject_shallow = !!value;
+               return 0;
        }
        return 1;
 }
@@ -370,6 +373,7 @@ static int fetch_refs_via_pack(struct transport *transport,
        args.stateless_rpc = transport->stateless_rpc;
        args.server_options = transport->server_options;
        args.negotiation_tips = data->options.negotiation_tips;
+       args.reject_shallow_remote = transport->smart_options->reject_shallow;
 
        if (!data->got_remote_heads) {
                int i;
index 24e15799e714aeb53f725b161a14172202cbae7e..4d5db0a7f22b366db870f9b421e41d1bba5a339c 100644 (file)
@@ -14,6 +14,7 @@ struct git_transport_options {
        unsigned check_self_contained_and_connected : 1;
        unsigned self_contained_and_connected : 1;
        unsigned update_shallow : 1;
+       unsigned reject_shallow : 1;
        unsigned deepen_relative : 1;
 
        /* see documentation of corresponding flag in fetch-pack.h */
@@ -194,6 +195,9 @@ void transport_check_allowed(const char *type);
 /* Aggressively fetch annotated tags if possible */
 #define TRANS_OPT_FOLLOWTAGS "followtags"
 
+/* Reject shallow repo transport */
+#define TRANS_OPT_REJECT_SHALLOW "rejectshallow"
+
 /* Accept refs that may update .git/shallow without --depth */
 #define TRANS_OPT_UPDATE_SHALLOW "updateshallow"