clone: --dissociate option to mark that reference is only temporary

While use of the --reference option to borrow objects from an existing local repository of the same project is an effective way to reduce traffic when cloning a project over the network, it makes the resulting "borrowing" repository dependent on the "borrowed" repository. After running git clone --reference=P $URL Q the resulting repository Q will be broken if the borrowed repository P disappears. The way to allow the borrowed repository to be removed is to repack the borrowing repository (i.e. run "git repack -a -d" in Q); while power users may know it very well, it is not easily discoverable. Teach a new "--dissociate" option to "git clone" to run this repacking for the user. Helped-by: Johannes Sixt <j6t@kdbg.org> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Junio C Hamano committed Oct 14, 2014 at 12:38 UTC fb1d6dabce69bce3f28a7f442da990ef8df872ac
3 files changed +46 -2
Documentation/git-clone.txt
+9 -2
@@ -12,7 +12,7 @@ SYNOPSIS
12 'git clone' [--template=<template_directory>]
13 [-l] [-s] [--no-hardlinks] [-q] [-n] [--bare] [--mirror]
14 [-o <name>] [-b <name>] [-u <upload-pack>] [--reference <repository>]
15 - [--separate-git-dir <git dir>]
15 + [--dissociate] [--separate-git-dir <git dir>]
16 [--depth <depth>] [--[no-]single-branch]
17 [--recursive | --recurse-submodules] [--] <repository>
18 [<directory>]
@@ -98,7 +98,14 @@ objects from the source repository into a pack in the cloned repository.
98 require fewer objects to be copied from the repository
99 being cloned, reducing network and local storage costs.
100 +
101 -*NOTE*: see the NOTE for the `--shared` option.
101 +*NOTE*: see the NOTE for the `--shared` option, and also the
102 +`--dissociate` option.
103 +
104 +--dissociate::
105 + Borrow the objects from reference repositories specified
106 + with the `--reference` options only to reduce network
107 + transfer and stop borrowing from them after a clone is made
108 + by making necessary local copies of borrowed objects.
109
110 --quiet::
111 -q::
builtin/clone.c
+20
@@ -48,6 +48,7 @@ static int option_verbosity;
48 static int option_progress = -1;
49 static struct string_list option_config;
50 static struct string_list option_reference;
51 +static int option_dissociate;
52
53 static int opt_parse_reference(const struct option *opt, const char *arg, int unset)
54 {
@@ -93,6 +94,8 @@ static struct option builtin_clone_options[] = {
94 N_("create a shallow clone of that depth")),
95 OPT_BOOL(0, "single-branch", &option_single_branch,
96 N_("clone only one branch, HEAD or --branch")),
97 + OPT_BOOL(0, "dissociate", &option_dissociate,
98 + N_("use --reference only while cloning")),
99 OPT_STRING(0, "separate-git-dir", &real_git_dir, N_("gitdir"),
100 N_("separate git dir from working tree")),
101 OPT_STRING_LIST('c', "config", &option_config, N_("key=value"),
@@ -736,6 +739,16 @@ static void write_refspec_config(const char* src_ref_prefix,
739 strbuf_release(&value);
740 }
741
742 +static void dissociate_from_references(void)
743 +{
744 + static const char* argv[] = { "repack", "-a", "-d", NULL };
745 +
746 + if (run_command_v_opt(argv, RUN_GIT_CMD|RUN_COMMAND_NO_STDIN))
747 + die(_("cannot repack to clean up"));
748 + if (unlink(git_path("objects/info/alternates")) && errno != ENOENT)
749 + die_errno(_("cannot unlink temporary alternates file"));
750 +}
751 +
752 int cmd_clone(int argc, const char **argv, const char *prefix)
753 {
754 int is_bundle = 0, is_local;
@@ -883,6 +896,10 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
896
897 if (option_reference.nr)
898 setup_reference();
899 + else if (option_dissociate) {
900 + warning(_("--dissociate given, but there is no --reference"));
901 + option_dissociate = 0;
902 + }
903
904 fetch_pattern = value.buf;
905 refspec = parse_fetch_refspec(1, &fetch_pattern);
@@ -996,6 +1013,9 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
1013 transport_unlock_pack(transport);
1014 transport_disconnect(transport);
1015
1016 + if (option_dissociate)
1017 + dissociate_from_references();
1018 +
1019 junk_mode = JUNK_LEAVE_REPO;
1020 err = checkout();
1021
t/t5700-clone-reference.sh
+17
@@ -198,4 +198,21 @@ test_expect_success 'clone using repo pointed at by gitfile as reference' '
198 test_cmp expected "$base_dir/O/.git/objects/info/alternates"
199 '
200
201 +test_expect_success 'clone and dissociate from reference' '
202 + git init P &&
203 + (
204 + cd P && test_commit one
205 + ) &&
206 + git clone P Q &&
207 + (
208 + cd Q && test_commit two
209 + ) &&
210 + git clone --no-local --reference=P Q R &&
211 + git clone --no-local --reference=P --dissociate Q S &&
212 + # removing the reference P would corrupt R but not S
213 + rm -fr P &&
214 + test_must_fail git -C R fsck &&
215 + git -C S fsck
216 +'
217 +
218 test_done