Implementation of fetch-blobs and fetch-refs
cover Implementation of fetch-blobs and fetch-refs
My previous proposal about a possible new server endpoint that serves blobs and refs [1] didn't seem to garner much interest, so I thought I'd provide a possible implementation of that proposal as a work in progress.
In particular, patch 1 demonstrates that a new server endpoint that serves refs without the initial ref advertisement can be done in 228 lines of code (according to "git diff --stat") while accounting for the various special cases that upload-pack imposes (stateless RPC, inclusion of an additional response when depth is requested, handling of "done" in request, sending a packfile directly after a response containing "ACK ready" without waiting for "done", and so on).
I'm hoping that these features would be of more interest to more people. To that end, I'm sending these patches in the hope of showing that these features are useful (omitting ref advertisements help greatly when serving large repos, as described in the commit message of patch 1, and serving blobs is useful for any fetch-blob-on-demand repo scheme) and that my proposed way of implementing them can be done in a relatively uncomplicated manner (as seen in these patches).
Patch 1-3 show what serving refs without the advertisement would look like from the server's and fetch-pack's points of view. Patch 4 is similar to some of the other blob-serving patches except that this contains reachability checks and that this bundles the resulting objects in a packfile.
[1] <[email protected]>
Jonathan Tan (4):
server-endpoint: serve refs without advertisement
fetch-pack: refactor "want" pkt-line generation
fetch-pack: support new server endpoint
server-endpoint: serve blobs by hash
.gitignore | 1 + Makefile | 3 + builtin/fetch-pack.c | 10 +- fetch-pack.c | 129 +++++++++++------ fetch-pack.h | 1 + server-endpoint.c | 347 +++++++++++++++++++++++++++++++++++++++++++++ t/helper/.gitignore | 1 + t/helper/test-un-pkt.c | 40 ++++++ t/t5573-server-endpoint.sh | 60 ++++++++ t/t9999-mytests.sh | 242 +++++++++++++++++++++++++++++++ 10 files changed, 790 insertions(+), 44 deletions(-) create mode 100644 server-endpoint.c create mode 100644 t/helper/test-un-pkt.c create mode 100644 t/t5573-server-endpoint.sh create mode 100644 t/t9999-mytests.sh
Signature · 1 line
2.12.2.715.g7642488e1d-goog
RFC 1/4 server-endpoint: serve refs without advertisement
Introduce a new binary that can act as an endpoint to serve refs without first sending the ref advertisement (a list of all ref names and associated hashes that the server contains). For very large repositories, including an internal Android repository with more than 700000 refs, this would save tens of megabytes of network bandwidth during each fetch.
This endpoint handles ref namespaces and "uploadpack.hiderefs" by itself, and handles other functionality by invoking upload-pack and acting as an intermediary (therefore having to know the relatively minute details of the fetch-pack/upload-pack protocol).
Note: There is still an issue with the handling of "deepen" lines. The documentation for the pack protocol states that "deepen 0" is the same as not specifying any depth, but upload-pack seems to not accept "deepen 0". I'm not sure if it's better to change the documentation or change the code - I generally prefer to change the code in such cases, but treating "deepen 0" (and similar things like "deepen 000") differently from other "deepen"s requires multiple components to know about this special case (upload-pack, fetch-pack, and now server-endpoint) so I'm inclined to just forbid it (like in the current code).
--- .gitignore | 1 + Makefile | 2 + server-endpoint.c | 228 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 231 insertions(+) create mode 100644 server-endpoint.c
.gitignore +1 -0
@@ -140,6 +140,7 @@ /git-rm /git-send-email /git-send-pack +/git-server-endpoint /git-sh-i18n /git-sh-i18n--envsubst /git-sh-setup
Makefile +2 -0
@@ -603,6 +603,7 @@ PROGRAM_OBJS += shell.o PROGRAM_OBJS += show-index.o PROGRAM_OBJS += upload-pack.o PROGRAM_OBJS += remote-testsvn.o +PROGRAM_OBJS += server-endpoint.o # Binary suffix, set to .exe for Windows builds X =
@@ -673,6 +674,7 @@ BINDIR_PROGRAMS_NEED_X += git-upload-pack BINDIR_PROGRAMS_NEED_X += git-receive-pack BINDIR_PROGRAMS_NEED_X += git-upload-archive BINDIR_PROGRAMS_NEED_X += git-shell +BINDIR_PROGRAMS_NEED_X += git-server-endpoint BINDIR_PROGRAMS_NO_X += git-cvsserver
server-endpoint.c new file +228 -0
228 lines not on this page · raw message
Signature · 1 line
2.12.2.715.g7642488e1d-goog
RFC 2/4 fetch-pack: refactor "want" pkt-line generation
In reply to Jonathan TanIn fetch-pack, refactor the generation of the initial request (containing the "want" lines) into its own function. This cleans up the code slightly in that the scopes of certain variables are reduced, but this commit mainly is in preparation for a subsequent one.
--- fetch-pack.c | 58 +++++++++++++++++++++++++++++++++------------------------- 1 file changed, 33 insertions(+), 25 deletions(-)
fetch-pack.c +33 -25
@@ -314,29 +314,14 @@ static int next_flush(struct fetch_pack_args *args, int count)return count; } -static int find_common (struct fetch_pack_args *args, -int fd[2 ],unsigned char *result_sha1, -struct ref *refs) +/* +* Generate the "want" lines to be sent. If there are no "want" lines to be +* sent, return NULL. +*/ +static char *get_wants (const struct fetch_pack_args *args,struct ref *refs) { -int fetching; -int count =0 , flushes =0 , flush_at =INITIAL_FLUSH , retval; -const unsigned char *sha1; -unsigned in_vain =0 ; -int got_continue =0 ; -int got_ready =0 ;struct strbuf req_buf =STRBUF_INIT ; -size_t state_len =0 ; -if (args->stateless_rpc && multi_ack ==1 ) -die (_ ("--stateless-rpc requires multi_ack_detailed" )); -if (marked) -for_each_ref (clear_marks,NULL ); - marked =1 ; - -for_each_ref (rev_list_insert_ref_oid,NULL ); -for_each_cached_alternate (insert_one_alternate_object); - - fetching =0 ;for ( ; refs ; refs = refs->next) {unsigned char *remote = refs->old_oid.hash;const char *remote_hex;
@@ -358,7 +343,7 @@ static int find_common(struct fetch_pack_args *args, } remote_hex =sha1_to_hex (remote); -if (!fetching) { +if (!req_buf.len) {struct strbuf c =STRBUF_INIT ;if (multi_ack ==2 )strbuf_addstr (&c," multi_ack_detailed" );if (multi_ack ==1 )strbuf_addstr (&c," multi_ack" );
@@ -378,14 +363,37 @@ static int find_common(struct fetch_pack_args *args,strbuf_release (&c); }else packet_buf_write (&req_buf,"want %s\n" , remote_hex); - fetching++; } -if (!fetching) { -strbuf_release (&req_buf); +return req_buf.len ?strbuf_detach (&req_buf,NULL ) :NULL ; +} + +static int find_common (struct fetch_pack_args *args, +int fd[2 ],unsigned char *result_sha1, +char *wants) +{ +int count =0 , flushes =0 , flush_at =INITIAL_FLUSH , retval; +const unsigned char *sha1; +unsigned in_vain =0 ; +int got_continue =0 ; +int got_ready =0 ; +struct strbuf req_buf =STRBUF_INIT ; +size_t state_len =0 ; + +if (args->stateless_rpc && multi_ack ==1 ) +die (_ ("--stateless-rpc requires multi_ack_detailed" )); +if (marked) +for_each_ref (clear_marks,NULL ); + marked =1 ; + +for_each_ref (rev_list_insert_ref_oid,NULL ); +for_each_cached_alternate (insert_one_alternate_object); + +if (!wants) {packet_flush (fd[1 ]);return 1 ; } +strbuf_attach (&req_buf, wants,strlen (wants),strlen (wants));if (is_repository_shallow ())write_shallow_commits (&req_buf,1 ,NULL );
@@ -943,7 +951,7 @@ static struct ref *do_fetch_pack(struct fetch_pack_args *args,packet_flush (fd[1 ]); goto all_done; } -if (find_common (args, fd, sha1, ref) <0 ) +if (find_common (args, fd, sha1,get_wants (args, ref)) <0 )if (!args->keep_pack)/* When cloning, it is not unusual to have * no common commit.
Signature · 1 line
2.12.2.715.g7642488e1d-goog
RFC 3/4 fetch-pack: support new server endpoint
In reply to Jonathan TanThis is a patch to demonstrate that fetch-pack only requires a small amount of changes to support the new server endpoint, and that things generally work in various situations, including both stateless RPC and non-stateless RPC (as can be seen from the tests).
Some minor issues remain:
- Names of refs should be treated as strings (when interfacing with the new endpoint is requested) instead of being parsed into struct ref unconditionally.
- Capability management could probably be done better instead of checking for "--new-way" everywhere.
- I'm not sure how to test the upload-pack code path that sends "ACK ready".
--- Makefile | 1 + builtin/fetch-pack.c | 10 +- fetch-pack.c | 75 +++++++++++---- fetch-pack.h | 1 + t/helper/.gitignore | 1 + t/helper/test-un-pkt.c | 40 ++++++++ t/t9999-mytests.sh | 242 +++++++++++++++++++++++++++++++++++++++++++++++++ 7 files changed, 349 insertions(+), 21 deletions(-) create mode 100644 t/helper/test-un-pkt.c create mode 100644 t/t9999-mytests.sh
Makefile +1 -0
@@ -641,6 +641,7 @@ TEST_PROGRAMS_NEED_X += test-string-list TEST_PROGRAMS_NEED_X += test-submodule-config TEST_PROGRAMS_NEED_X += test-subprocess TEST_PROGRAMS_NEED_X += test-svn-fe +TEST_PROGRAMS_NEED_X += test-un-pkt TEST_PROGRAMS_NEED_X += test-urlmatch-normalization TEST_PROGRAMS_NEED_X += test-wildmatch
builtin/fetch-pack.c +8 -2
@@ -143,6 +143,10 @@ int cmd_fetch_pack(int argc, const char **argv, const char *prefix) args.update_shallow =1 ;continue ; } +if (!strcmp ("--new-way" , arg)) { + args.new_way =1 ; +continue ; + }usage (fetch_pack_usage); }if (deepen_not.nr)
@@ -193,7 +197,8 @@ int cmd_fetch_pack(int argc, const char **argv, const char *prefix)if (!conn)return args.diag_url ?0 :1 ; } -get_remote_heads (fd[0 ],NULL ,0 , &ref,0 ,NULL , &shallow); +if (!args.new_way) +get_remote_heads (fd[0 ],NULL ,0 , &ref,0 ,NULL , &shallow); ref =fetch_pack (&args, fd, conn, ref, dest, sought, nr_sought, &shallow, pack_lockfile_ptr);
@@ -219,7 +224,8 @@ int cmd_fetch_pack(int argc, const char **argv, const char *prefix)* remote no-such-ref' would silently succeed without issuing * an error. */ - ret |=report_unmatched_refs (sought, nr_sought); +if (!args.new_way) + ret |=report_unmatched_refs (sought, nr_sought);while (ref) { printf("%s %s\n",
fetch-pack.c +56 -19
141 lines not on this page · raw message
fetch-pack.h +1 -0
@@ -29,6 +29,7 @@ struct fetch_pack_args { unsigned cloning:1; unsigned update_shallow:1; unsigned deepen:1; + unsigned new_way:1; };/*
t/helper/.gitignore +1 -0
@@ -29,5 +29,6 @@ /test-submodule-config /test-subprocess /test-svn-fe +/test-un-pkt /test-urlmatch-normalization /test-wildmatch
t/helper/test-un-pkt.c new file +40 -0
@@ -0,0 +1,40 @@ +/* +* This program takes payloads in pkt-line format (one or more pkt-lines +* followed by a flush pkt) and runs the given command once per payload. +*/ +#include "cache.h" +#include "pkt-line.h" +#include "run-command.h" + +int cmd_main (int argc,const char **argv) +{ +struct child_process *cmd =NULL ; +char buffer[LARGE_PACKET_MAX ]; +int size; + +while ((size =packet_read (0 ,NULL ,NULL , buffer,sizeof (buffer), +PACKET_READ_GENTLE_ON_EOF )) >=0 ) { +if (size >0 ) { +if (!cmd) { + cmd =xmalloc (sizeof (*cmd)); +child_process_init (cmd); + cmd->argv = argv +1 ; + cmd->git_cmd =1 ; + cmd->in =-1 ; + cmd->out =0 ; +if (start_command (cmd)) +die ("could not start command" ); + } +write_in_full (cmd->in, buffer, size); + }else if (cmd) { +close (cmd->in); +if (finish_command (cmd)) +die ("could not finish command" ); +free (cmd); + cmd =NULL ; +if (size <0 ) +break ; + } + } +return 0 ; +}
t/t9999-mytests.sh new file +242 -0
242 lines not on this page · raw message
Signature · 1 line
2.12.2.715.g7642488e1d-goog
RFC 4/4 server-endpoint: serve blobs by hash
In reply to Jonathan TanUpgrade 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] <[email protected]>
--- 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
server-endpoint.c +120 -1
@@ -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" ); }
t/t5573-server-endpoint.sh new file +60 -0
60 lines not on this page · raw message
Signature · 1 line
2.12.2.715.g7642488e1d-goog
Sorry for not paying attention to this patch set earlier. I missed (and might still
miss) the big picture(tm) here. This is because
http://public-inbox.org/git/[email protected]/
seemed to be specific for big binary files, but ...
Quoted 7 lines · Jonathan Tan
In particular, patch 1 demonstrates that a new server endpoint that serves refs without the initial ref advertisement can be done in 228 lines of code (according to "git diff --stat") while accounting for the various special cases that upload-pack imposes (stateless RPC, inclusion of an additional response when depth is requested, handling of "done" in request, sending a packfile directly after a response containing "ACK ready" without waiting for "done", and so on).
... from looking at the patches, this actually implements the idea that was thrown around on the mailing list as "client speaks first", as this essentially omits the refs advertisement, and then continues the conversation by running upload-pack as normal.
That seems very exciting to me!
Quoted 6 lines
To that end, I'm sending these patches in the hope of showing that these features are useful (omitting ref advertisements help greatly when serving large repos, as described in the commit message of patch 1, and serving blobs is useful for any fetch-blob-on-demand repo scheme) and that my proposed way of implementing them can be done in a relatively uncomplicated manner (as seen in these patches).
Yes, if we encapsulate v1 protocol on (both?) sides, we seem to need very little additional code, but get the benefit of using lots of well tested code.
Thanks,
Stefan