On Sun, Oct 01, 2023 at 04:03:29PM -0600, Jesse Hopkins wrote: > Thanks for the reply. Would you be able to point me to some > breadcrumbs for the "required protocol messages"? I might try to > tinker in some spare time. Try "git help protocol-pack" in a recent version of Git (this used to be in Documentation/technical/ of the repository, but much of that content was moved into manpages around the v2.38 timeframe). For a local or ssh connection, I think it is as simple as: # you somehow happen to know this commit exists on the server, # and what the current value of the ref is. If you don't know the # current value, you can pull it from receive-pack's ref # advertisement (I'll leave that as an exercise for the reader). old=1234abcd... new=5678cdef... ref=refs/heads/main # we'll use a local repository here, but you can replace receive-pack # invocation below with with "ssh $host git receive-pack $repo" repo=/path/to/repo.git { # git's pkt-line format is a 4-byte header with the ascii hex size of # the packet, followed by N-4 bytes of data. Each ref update is # in its own pkt, but we have just one. cmd="$old $new $ref" printf "%04x%s" $((${#cmd} + 4)) "$cmd" # An all-zero flush packet indicates the end of the list of updates. printf "0000" # the server insists that we send a valid packfile, even if it is # empty. This is from "git help format-pack" (the section on .pack # files), though you could also generate it with "git pack-objects # --stdout </dev/null". printf 'PACK' ;# packfile printf '\0\0\0\2' ;# version 2 printf '\0\0\0\0' ;# zero objects # checksum, which is the sha1 of the rest of the pack printf "\2\235\10\202\73\330\250\352\265\20\255\152\307\134\202\74\375\76\323\36" } | git receive-pack "$repo" You can get fancier by specifying capabilities (you might want "report-status", for example). That will work for local or ssh repos. For http, it gets a little more complicated. See the section "smart service git-receive-pack" of "git help protocol-http". All that said, I do think it might be reasonable for git-push to support this directly. It is basically: 1. Let the command run in a non-repo, skipping anything that requires it. This _might_ be a maintenance headache, but as a fallback you could always run from an empty local repository. 2. Tell it to always generate an empty pack (basically, a "trust me, the other side will be OK with it" option). The second part looks like something like this: diff --git a/send-pack.c b/send-pack.c index 89aca9d829..c54463c181 100644 --- a/send-pack.c +++ b/send-pack.c @@ -58,6 +58,9 @@ static void feed_object(const struct object_id *oid, FILE *fh, int negative) putc('\n', fh); } +/* obviously this should be passed down somehow in a real patch */ +#define SPECIAL_EMPTY_PACK_OPTION 1 + /* * Make a pack stream and spit it out into file descriptor fd */ @@ -103,17 +106,19 @@ static int pack_objects(int fd, struct ref *refs, struct oid_array *advertised, * parameters by writing to the pipe. */ po_in = xfdopen(po.in, "w"); - for (i = 0; i < advertised->nr; i++) - feed_object(&advertised->oid[i], po_in, 1); - for (i = 0; i < negotiated->nr; i++) - feed_object(&negotiated->oid[i], po_in, 1); - - while (refs) { - if (!is_null_oid(&refs->old_oid)) - feed_object(&refs->old_oid, po_in, 1); - if (!is_null_oid(&refs->new_oid)) - feed_object(&refs->new_oid, po_in, 0); - refs = refs->next; + if (!SPECIAL_EMPTY_PACK_OPTION) { + for (i = 0; i < advertised->nr; i++) + feed_object(&advertised->oid[i], po_in, 1); + for (i = 0; i < negotiated->nr; i++) + feed_object(&negotiated->oid[i], po_in, 1); + + while (refs) { + if (!is_null_oid(&refs->old_oid)) + feed_object(&refs->old_oid, po_in, 1); + if (!is_null_oid(&refs->new_oid)) + feed_object(&refs->new_oid, po_in, 0); + refs = refs->next; + } } fflush(po_in); Come to think of it, you could probably fake it by wrapping git-pack-objects with a script that throws away its input. Maybe hard to do because its a builtin (and we run it as "git pack-objects", which executes it directly in-process). -Peff