Upgrade server-endpoint to also serve blobs in a packfile given their
hashes.  Reachability checks are performed before the packfile is sent -
both an absent blob and an unreachable blob are reported to the user in
the same way ("not our blob").

Due to a bug in "rev-list" in the absence of bitmaps (discussed here
[1]), the server repositories in tests all have bitmaps.

[1] <20170309003547.6930-1-jonathanta...@google.com>

Signed-off-by: Jonathan Tan <jonathanta...@google.com>
---
 server-endpoint.c          | 121 ++++++++++++++++++++++++++++++++++++++++++++-
 t/t5573-server-endpoint.sh |  60 ++++++++++++++++++++++
 2 files changed, 180 insertions(+), 1 deletion(-)
 create mode 100644 t/t5573-server-endpoint.sh

diff --git a/server-endpoint.c b/server-endpoint.c
index a9c0c7c94..870b853a6 100644
--- a/server-endpoint.c
+++ b/server-endpoint.c
@@ -192,6 +192,123 @@ static int fetch_ref(int stateless_rpc)
        return -1;
 }
 
+/*
+ * Returns 1 if all blobs are reachable. If not, returns 0 and stores the hash
+ * of one of the unreachable blobs in unreachable.
+ */
+static int are_all_reachable(const struct object_array *blobs, struct 
object_id *unreachable)
+{
+       struct child_process cmd = CHILD_PROCESS_INIT;
+       static const char *argv[] = {
+               "rev-list", "--objects", "--use-bitmap-index", "--stdin", 
"--not", "--all", "--not", NULL,
+       };
+       int i;
+       char buf[41] = {0};
+
+       cmd.argv = argv;
+       cmd.git_cmd = 1;
+       cmd.in = -1;
+       cmd.out = -1;
+
+       if (start_command(&cmd))
+               goto error;
+       
+       for (i = 0; i < blobs->nr; i++) {
+               write_in_full(cmd.in, 
sha1_to_hex(blobs->objects[i].item->oid.hash), 40);
+               write_in_full(cmd.in, "\n", 1);
+       }
+       close(cmd.in);
+       cmd.in = -1;
+
+       i = read_in_full(cmd.out, buf, 40);
+       close(cmd.out);
+       cmd.out = -1;
+
+       if (finish_command(&cmd))
+               goto error;
+
+       if (i) {
+               if (get_oid_hex(buf, unreachable))
+                       goto error;
+               return 0;
+       }
+
+       return 1;
+
+error:
+       if (cmd.out >= 0)
+               close(cmd.out);
+       die("problem with running rev-list");
+}
+
+static void send_blobs(const struct object_array *blobs)
+{
+       struct child_process cmd = CHILD_PROCESS_INIT;
+       static const char *argv[] = {
+               "pack-objects", "--stdout", NULL
+       };
+       int i;
+
+       cmd.argv = argv;
+       cmd.git_cmd = 1;
+       cmd.in = -1;
+       cmd.out = 0;
+
+       if (start_command(&cmd))
+               goto error;
+       
+       for (i = 0; i < blobs->nr; i++) {
+               write_in_full(cmd.in, 
sha1_to_hex(blobs->objects[i].item->oid.hash), 40);
+               write_in_full(cmd.in, "\n", 1);
+       }
+       close(cmd.in);
+       cmd.in = -1;
+
+       if (finish_command(&cmd))
+               goto error;
+
+       return;
+
+error:
+       die("problem with running pack-objects");
+}
+
+static int fetch_blob(void)
+{
+       char *line;
+
+       struct object_array wanted_blobs = OBJECT_ARRAY_INIT;
+       struct object_id unreachable;
+
+       while ((line = packet_read_line(0, NULL))) {
+               const char *arg;
+               if (skip_prefix(line, "want ", &arg)) {
+                       struct object_id oid;
+                       struct object *obj;
+                       if (get_oid_hex(arg, &oid)) {
+                               packet_write_fmt(1, "ERR invalid object ID 
<%s>", arg);
+                               return 0;
+                       }
+                       obj = parse_object(oid.hash);
+                       if (!obj || obj->type != OBJ_BLOB) {
+                               packet_write_fmt(1, "ERR not our blob <%s>", 
arg);
+                               return 0;
+                       }
+                       add_object_array(obj, NULL, &wanted_blobs);
+               }
+       }
+
+       if (!are_all_reachable(&wanted_blobs, &unreachable)) {
+               packet_write_fmt(1, "ERR not our blob <%s>", 
oid_to_hex(&unreachable));
+               return 0;
+       }
+
+       packet_write_fmt(1, "ACK\n");
+       send_blobs(&wanted_blobs);
+
+       return 0;
+}
+
 static int server_endpoint_config(const char *var, const char *value, void 
*unused)
 {
        return parse_hide_refs_config(var, value, "uploadpack");
@@ -224,5 +341,7 @@ int cmd_main(int argc, const char **argv)
        line = packet_read_line(0, NULL);
        if (!strcmp(line, "fetch-refs"))
                return fetch_ref(stateless_rpc);
-       die("only fetch-refs is supported");
+       if (!strcmp(line, "fetch-blobs"))
+               return fetch_blob();
+       die("only fetch-refs and fetch-blobs are supported");
 }
diff --git a/t/t5573-server-endpoint.sh b/t/t5573-server-endpoint.sh
new file mode 100644
index 000000000..48f052851
--- /dev/null
+++ b/t/t5573-server-endpoint.sh
@@ -0,0 +1,60 @@
+#!/bin/sh
+
+test_description='server-endpoint'
+
+. ./test-lib.sh
+
+test_expect_success 'fetch-blobs basic' '
+       rm -rf server client &&
+       git init server &&
+       (
+               cd server &&
+               test_commit 0 &&
+               test_commit 1 &&
+               git repack -a -d --write-bitmap-index
+       ) &&
+       BLOB0=$(git hash-object server/0.t) &&
+       BLOB1=$(git hash-object server/1.t) &&
+       printf "000ffetch-blobs0031want %s0031want %s0000" "$BLOB0" "$BLOB1" | 
git server-endpoint server >out &&
+
+       test "$(head -1 out)" = "0008ACK" &&
+
+       git init client &&
+       sed 1d out | git -C client unpack-objects &&
+       git -C client cat-file -e "$BLOB0" &&
+       git -C client cat-file -e "$BLOB1"
+'
+
+test_expect_success 'fetch-blobs no such object' '
+       rm -rf server client &&
+       git init server &&
+       (
+               cd server &&
+               test_commit 0 &&
+               git repack -a -d --write-bitmap-index
+       ) &&
+       BLOB0=$(git hash-object server/0.t) &&
+       echo myblob >myblob &&
+       MYBLOB=$(git hash-object myblob) &&
+       printf "000ffetch-blobs0031want %s0031want %s0000" "$BLOB0" "$MYBLOB" | 
git server-endpoint server >out &&
+
+       test_i18ngrep "$(printf "ERR not our blob.*%s" "$MYBLOB")" out
+'
+
+test_expect_success 'fetch-blobs unreachable' '
+       rm -rf server client &&
+       git init server &&
+       (
+               cd server &&
+               test_commit 0 &&
+               git repack -a -d --write-bitmap-index
+       ) &&
+       BLOB0=$(git hash-object server/0.t) &&
+       echo myblob >myblob &&
+       MYBLOB=$(git -C server hash-object -w ../myblob) &&
+       printf "000ffetch-blobs0031want %s0031want %s0000" "$BLOB0" "$MYBLOB" | 
git server-endpoint server >out &&
+
+       test_i18ngrep "$(printf "ERR not our blob.*%s" "$MYBLOB")" out
+'
+
+test_done
-- 
2.12.2.715.g7642488e1d-goog

Reply via email to