Re: Possible to update-ref remote repository?
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