[PATCH v3 1/3] Support multiple virtual repositories with a single object store and refs
From: Jamey Sharp <hidden>
Date: 2016-06-15 22:51:18
From: Josh Triplett <josh@joshtriplett.org>
Given many repositories with copies of the same objects (such as branches of
the same source), sharing a common object store will avoid duplication.
Alternates provide a single baseline, but don't handle ongoing activity in the
various repositories. Furthermore, operations such as git-gc need to know
about all of the refs.
Git supports storing multiple virtual repositories within the object store and
references of a single underlying repository. The underlying repository
stores the objects for all of the virtual repositories, and includes all the
refs and heads of the virtual repositories using prefixed names.
git-upload-pack and git-receive-pack rewrite the names of refs and heads as
specified by the --ref-prefix and --head options. For instance,
--ref-prefix=virtual/reponame/ will use refs/virtual/reponame/heads/* and
refs/virtual/reponame/tags/*. git-upload-pack and git-receive-pack will
ignore any references that do not match the specified prefix.
These options implement the underlying mechanism for virtual
repositories; the higher-level protocol handler (such as http-backend or
a custom server) can pass these options when invoking upload-pack or
receive-pack, providing values based on components of the repository
path. For a simple local test, git-remote-ext works:
git clone ext::'git %s --ref-prefix=virtual/reponame/ --head=virtual-HEAD/reponame storage.git'
Commit by Josh Triplett and Jamey Sharp.
Signed-off-by: Josh Triplett <josh@joshtriplett.org>
Signed-off-by: Jamey Sharp <redacted>
---
v2: remove accidentally-included debug message, and add patch 2/2 for
git-http-backend.
v3: add patch 3/3 with documentation for virtual repositories, and
incorporated feedback from Jeff King and Junio C Hamano.
builtin/receive-pack.c | 36 +++++++++++++++++++++++++++---------
upload-pack.c | 34 +++++++++++++++++++++++++++-------
2 files changed, 54 insertions(+), 16 deletions(-)
diff --git a/builtin/receive-pack.c b/builtin/receive-pack.c
index e1ba4dc..76dacd0 100644
--- a/builtin/receive-pack.c
+++ b/builtin/receive-pack.c@@ -34,6 +34,8 @@ static int prefer_ofs_delta = 1; static int auto_update_server_info; static int auto_gc = 1; static const char *head_name; +static const char *head_path = "HEAD"; +static const char *ref_prefix = "refs/"; static int sent_capabilities; static enum deny_action parse_deny_action(const char *var, const char *value)
@@ -108,11 +110,12 @@ static int receive_pack_config(const char *var, const char *value, void *cb) static int show_ref(const char *path, const unsigned char *sha1, int flag, void *cb_data) { + const char *refnameprefix = cb_data; if (sent_capabilities) - packet_write(1, "%s %s\n", sha1_to_hex(sha1), path); + packet_write(1, "%s %s%s\n", sha1_to_hex(sha1), refnameprefix, path); else - packet_write(1, "%s %s%c%s%s\n", - sha1_to_hex(sha1), path, 0, + packet_write(1, "%s %s%s%c%s%s\n", + sha1_to_hex(sha1), refnameprefix, path, 0, " report-status delete-refs side-band-64k", prefer_ofs_delta ? " ofs-delta" : ""); sent_capabilities = 1;
@@ -121,9 +124,9 @@ static int show_ref(const char *path, const unsigned char *sha1, int flag, void static void write_head_info(void) { - for_each_ref(show_ref, NULL); + for_each_ref_in(ref_prefix, show_ref, "refs/"); if (!sent_capabilities) - show_ref("capabilities^{}", null_sha1, 0, NULL); + show_ref("capabilities^{}", null_sha1, 0, ""); }
@@ -332,6 +335,8 @@ static void refuse_unconfigured_deny_delete_current(void) static const char *update(struct command *cmd) { const char *name = cmd->ref_name; + struct strbuf prefixed_name_buf = STRBUF_INIT; + const char *prefixed_name; unsigned char *old_sha1 = cmd->old_sha1; unsigned char *new_sha1 = cmd->new_sha1; struct ref_lock *lock;
@@ -342,7 +347,10 @@ static const char *update(struct command *cmd) return "funny refname"; } - if (is_ref_checked_out(name)) { + strbuf_addf(&prefixed_name_buf, "%s%s", ref_prefix, name + 5); + prefixed_name = strbuf_detach(&prefixed_name_buf, NULL); + + if (is_ref_checked_out(prefixed_name)) { switch (deny_current_branch) { case DENY_IGNORE: break;
@@ -370,7 +378,7 @@ static const char *update(struct command *cmd) return "deletion prohibited"; } - if (!strcmp(name, head_name)) { + if (!strcmp(prefixed_name, head_name)) { switch (deny_delete_current) { case DENY_IGNORE: break;
@@ -426,14 +434,14 @@ static const char *update(struct command *cmd) rp_warning("Allowing deletion of corrupt ref."); old_sha1 = NULL; } - if (delete_ref(name, old_sha1, 0)) { + if (delete_ref(prefixed_name, old_sha1, 0)) { rp_error("failed to delete %s", name); return "failed to delete"; } return NULL; /* good */ } else { - lock = lock_any_ref_for_update(name, old_sha1, 0); + lock = lock_any_ref_for_update(prefixed_name, old_sha1, 0); if (!lock) { rp_error("failed to lock %s", name); return "failed to lock";
@@ -760,6 +768,16 @@ int cmd_receive_pack(int argc, const char **argv, const char *prefix) advertise_refs = 1; continue; } + if (!prefixcmp(arg, "--head=")) { + head_path = arg+7; + continue; + } + if (!prefixcmp(arg, "--ref-prefix=")) { + struct strbuf prefixbuf = STRBUF_INIT; + strbuf_addf(&prefixbuf, "refs/%s", arg+13); + ref_prefix = strbuf_detach(&prefixbuf, NULL); + continue; + } if (!strcmp(arg, "--stateless-rpc")) { stateless_rpc = 1; continue;
diff --git a/upload-pack.c b/upload-pack.c
index ce5cbbe..a1e495f 100644
--- a/upload-pack.c
+++ b/upload-pack.c@@ -34,6 +34,8 @@ static int shallow_nr; static struct object_array have_obj; static struct object_array want_obj; static struct object_array extra_edge_obj; +static const char *head_path = "HEAD"; +static const char *ref_prefix = ""; static unsigned int timeout; /* 0 for no sideband, * otherwise maximum packet size (up to 65520 bytes).
@@ -640,17 +642,18 @@ static int send_ref(const char *refname, const unsigned char *sha1, int flag, vo static const char *capabilities = "multi_ack thin-pack side-band" " side-band-64k ofs-delta shallow no-progress" " include-tag multi_ack_detailed"; + const char *refnameprefix = cb_data; struct object *o = parse_object(sha1); if (!o) die("git upload-pack: cannot find object %s:", sha1_to_hex(sha1)); if (capabilities) - packet_write(1, "%s %s%c%s%s\n", sha1_to_hex(sha1), refname, + packet_write(1, "%s %s%s%c%s%s\n", sha1_to_hex(sha1), refnameprefix, refname, 0, capabilities, stateless_rpc ? " no-done" : ""); else - packet_write(1, "%s %s\n", sha1_to_hex(sha1), refname); + packet_write(1, "%s %s%s\n", sha1_to_hex(sha1), refnameprefix, refname); capabilities = NULL; if (!(o->flags & OUR_REF)) { o->flags |= OUR_REF;
@@ -659,7 +662,7 @@ static int send_ref(const char *refname, const unsigned char *sha1, int flag, vo if (o->type == OBJ_TAG) { o = deref_tag(o, refname, 0); if (o) - packet_write(1, "%s %s^{}\n", sha1_to_hex(o->sha1), refname); + packet_write(1, "%s %s%s^{}\n", sha1_to_hex(o->sha1), refnameprefix, refname); } return 0; }
@@ -678,15 +681,24 @@ static int mark_our_ref(const char *refname, const unsigned char *sha1, int flag static void upload_pack(void) { + struct strbuf prefix = STRBUF_INIT; + unsigned char sha1[20]; + int flag; + + strbuf_addf(&prefix, "refs/%s", ref_prefix); if (advertise_refs || !stateless_rpc) { reset_timeout(); - head_ref(send_ref, NULL); - for_each_ref(send_ref, NULL); + if (resolve_ref(head_path, sha1, 1, &flag)) + send_ref("HEAD", sha1, flag, ""); + for_each_ref_in(prefix.buf, send_ref, "refs/"); packet_flush(1); } else { - head_ref(mark_our_ref, NULL); - for_each_ref(mark_our_ref, NULL); + if (resolve_ref(head_path, sha1, 1, &flag)) + mark_our_ref("HEAD", sha1, flag, NULL); + for_each_ref_in(prefix.buf, mark_our_ref, NULL); } + strbuf_release(&prefix); + if (advertise_refs) return;
@@ -716,6 +728,14 @@ int main(int argc, char **argv) advertise_refs = 1; continue; } + if (!prefixcmp(arg, "--head=")) { + head_path = arg+7; + continue; + } + if (!prefixcmp(arg, "--ref-prefix=")) { + ref_prefix = arg+13; + continue; + } if (!strcmp(arg, "--stateless-rpc")) { stateless_rpc = 1; continue;
--
1.7.5.1