transport.c: introduce core.alternateRefsCommand
authorTaylor Blau <me@ttaylorr.com>
Mon, 8 Oct 2018 18:09:28 +0000 (11:09 -0700)
committerJunio C Hamano <gitster@pobox.com>
Tue, 9 Oct 2018 05:30:03 +0000 (14:30 +0900)
When in a repository containing one or more alternates, Git would
sometimes like to list references from those alternates. For example,
'git receive-pack' lists the "tips" pointed to by references in those
alternates as special ".have" references.

Listing ".have" references is designed to make pushing changes from
upstream to a fork a lightweight operation, by advertising to the pusher
that the fork already has the objects (via its alternate). Thus, the
client can avoid sending them.

However, when the alternate (upstream, in the previous example) has a
pathologically large number of references, the initial advertisement is
too expensive. In fact, it can dominate any such optimization where the
pusher avoids sending certain objects.

Introduce "core.alternateRefsCommand" in order to provide a facility to
limit or filter alternate references. This can be used, for example, to
filter out references the alternate does not wish to send (for space
concerns, or otherwise) during the initial advertisement.

Let the repository that has alternates configure this command to avoid
trusting the alternate to provide us a safe command to run in the shell.
To find the alternate, pass its absolute path as the first argument.

Signed-off-by: Taylor Blau <me@ttaylorr.com>
Acked-by: Jeff King <peff@peff.net>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
Documentation/config.txt
t/t5410-receive-pack-alternates.sh [new file with mode: 0755]
transport.c
index eb66a119753726b0260acd456f5728351dc05ba3..00224dea61b5c83248e5a5785f8e0a80f6b80031 100644 (file)
@@ -616,6 +616,17 @@ core.preferSymlinkRefs::
        This is sometimes needed to work with old scripts that
        expect HEAD to be a symbolic link.
 
+core.alternateRefsCommand::
+       When advertising tips of available history from an alternate, use the shell to
+       execute the specified command instead of linkgit:git-for-each-ref[1]. The
+       first argument is the absolute path of the alternate. Output must contain one
+       hex object id per line (i.e., the same as produce by `git for-each-ref
+       --format='%(objectname)'`).
++
+Note that you cannot generally put `git for-each-ref` directly into the config
+value, as it does not take a repository path as an argument (but you can wrap
+the command above in a shell script).
+
 core.bare::
        If true this repository is assumed to be 'bare' and has no
        working directory associated with it.  If this is the case a
diff --git a/t/t5410-receive-pack-alternates.sh b/t/t5410-receive-pack-alternates.sh
new file mode 100755 (executable)
index 0000000..49d0fe4
--- /dev/null
@@ -0,0 +1,33 @@
+#!/bin/sh
+
+test_description='git receive-pack with alternate ref filtering'
+
+. ./test-lib.sh
+
+test_expect_success 'setup' '
+       test_commit base &&
+       git clone -s --bare . fork &&
+       git checkout -b public/branch master &&
+       test_commit public &&
+       git checkout -b private/branch master &&
+       test_commit private
+'
+
+extract_haves () {
+       depacketize | perl -lne '/^(\S+) \.have/ and print $1'
+}
+
+test_expect_success 'with core.alternateRefsCommand' '
+       write_script fork/alternate-refs <<-\EOF &&
+               git --git-dir="$1" for-each-ref \
+                       --format="%(objectname)" \
+                       refs/heads/public/
+       EOF
+       test_config -C fork core.alternateRefsCommand alternate-refs &&
+       git rev-parse public/branch >expect &&
+       printf "0000" | git receive-pack fork >actual &&
+       extract_haves <actual >actual.haves &&
+       test_cmp expect actual.haves
+'
+
+test_done
index 25d8ea7fe5cd5c7048c4e6549e19b8336d877ac0..19baec7942d2c865adb30fcf06c0d08ddb2dc63e 100644 (file)
@@ -1328,10 +1328,21 @@ char *transport_anonymize_url(const char *url)
 static void fill_alternate_refs_command(struct child_process *cmd,
                                        const char *repo_path)
 {
-       cmd->git_cmd = 1;
-       argv_array_pushf(&cmd->args, "--git-dir=%s", repo_path);
-       argv_array_push(&cmd->args, "for-each-ref");
-       argv_array_push(&cmd->args, "--format=%(objectname)");
+       const char *value;
+
+       if (!git_config_get_value("core.alternateRefsCommand", &value)) {
+               cmd->use_shell = 1;
+
+               argv_array_push(&cmd->args, value);
+               argv_array_push(&cmd->args, repo_path);
+       } else {
+               cmd->git_cmd = 1;
+
+               argv_array_pushf(&cmd->args, "--git-dir=%s", repo_path);
+               argv_array_push(&cmd->args, "for-each-ref");
+               argv_array_push(&cmd->args, "--format=%(objectname)");
+       }
+
        cmd->env = local_repo_env;
        cmd->out = -1;
 }