plan9fox/sys/src/cmd/git/send.c
Ori Bernstein 08447e5d64 git/send: fill in 'theirs' object, even if we miss it
When pushing, git/send would sometimes decide we had all the
objects that we'd need to update the remote, and would try
to pack and send the entire history of the repository. This
is because we only set the 'theirs' ref when we had the object.

If we didn't have the object, we would set a zero hash,
then when deciding if we needed to force, we would think
that we were updating a new branch and send everything,
which would fail to update the remote.
2022-04-17 01:19:10 +00:00

314 lines
6.4 KiB
C

#include <u.h>
#include <libc.h>
#include "git.h"
typedef struct Capset Capset;
typedef struct Map Map;
struct Capset {
int sideband;
int sideband64k;
int report;
};
struct Map {
char *ref;
Hash ours;
Hash theirs;
};
int sendall;
int force;
int nbranch;
char **branch;
char *removed[128];
int nremoved;
int npacked;
int nsent;
int
findref(char **r, int nr, char *ref)
{
int i;
for(i = 0; i < nr; i++)
if(strcmp(r[i], ref) == 0)
return i;
return -1;
}
int
findkey(Map *m, int nm, char *ref)
{
int i;
for(i = 0; i < nm; i++)
if(strcmp(m[i].ref, ref) == 0)
return i;
return -1;
}
int
readours(Hash **tailp, char ***refp)
{
int nu, i, idx;
char *r, *pfx, **ref;
Hash *tail;
if(sendall)
return listrefs(tailp, refp);
nu = 0;
tail = eamalloc((nremoved + nbranch), sizeof(Hash));
ref = eamalloc((nremoved + nbranch), sizeof(char*));
for(i = 0; i < nbranch; i++){
ref[nu] = estrdup(branch[i]);
if(resolveref(&tail[nu], branch[i]) == -1)
sysfatal("broken branch %s", branch[i]);
nu++;
}
for(i = 0; i < nremoved; i++){
pfx = "refs/heads/";
if(strstr(removed[i], "heads/") == removed[i])
pfx = "refs/";
if(strstr(removed[i], "refs/heads/") == removed[i])
pfx = "";
if((r = smprint("%s%s", pfx, removed[i])) == nil)
sysfatal("smprint: %r");
if((idx = findref(ref, nu, r)) == -1)
idx = nu++;
assert(idx < nremoved + nbranch);
memcpy(&tail[idx], &Zhash, sizeof(Hash));
free(r);
}
dprint(1, "nu: %d\n", nu);
for(i = 0; i < nu; i++)
dprint(1, "update: %H %s\n", tail[i], ref[i]);
*tailp = tail;
*refp = ref;
return nu;
}
char *
matchcap(char *s, char *cap, int full)
{
if(strncmp(s, cap, strlen(cap)) == 0)
if(!full || strlen(s) == strlen(cap))
return s + strlen(cap);
return nil;
}
void
parsecaps(char *caps, Capset *cs)
{
char *p, *n;
for(p = caps; p != nil; p = n){
n = strchr(p, ' ');
if(n != nil)
*n++ = 0;
if(matchcap(p, "report-status", 1) != nil)
cs->report = 1;
if(matchcap(p, "side-band", 1) != nil)
cs->sideband = 1;
if(matchcap(p, "side-band-64k", 1) != nil)
cs->sideband64k = 1;
}
}
int
sendpack(Conn *c)
{
int i, n, idx, nsp, send, first;
int nours, ntheirs, nmap;
char buf[Pktmax], *sp[3];
Hash h, *theirs, *ours;
Object *a, *b, *p, *o;
char **refs;
Capset cs;
Map *map, *m;
first = 1;
memset(&cs, 0, sizeof(Capset));
nours = readours(&ours, &refs);
theirs = nil;
ntheirs = 0;
nmap = nours;
map = eamalloc(nmap, sizeof(Map));
for(i = 0; i < nmap; i++){
map[i].theirs = Zhash;
map[i].ours = ours[i];
map[i].ref = refs[i];
}
while(1){
n = readpkt(c, buf, sizeof(buf));
if(n == -1)
return -1;
if(n == 0)
break;
if(first && n > strlen(buf))
parsecaps(buf + strlen(buf) + 1, &cs);
first = 0;
if(strncmp(buf, "ERR ", 4) == 0)
sysfatal("%s", buf + 4);
if(getfields(buf, sp, nelem(sp), 1, " \t\r\n") != 2)
sysfatal("invalid ref line %.*s", utfnlen(buf, n), buf);
theirs = earealloc(theirs, ntheirs+1, sizeof(Hash));
if(hparse(&theirs[ntheirs], sp[0]) == -1)
sysfatal("invalid hash %s", sp[0]);
if((idx = findkey(map, nmap, sp[1])) != -1)
map[idx].theirs = theirs[ntheirs];
/*
* we only keep their ref if we can read the object to add it
* to our reachability; otherwise, discard it; we only care
* that we don't have it, so we can tell whether we need to
* bail out of pushing.
*/
if((o = readobject(theirs[ntheirs])) != nil){
ntheirs++;
unref(o);
}
}
if(writephase(c) == -1)
return -1;
send = 0;
if(force)
send=1;
for(i = 0; i < nmap; i++){
m = &map[i];
a = readobject(m->theirs);
if(hasheq(&m->ours, &Zhash))
b = nil;
else
b = readobject(m->ours);
p = nil;
if(a != nil && b != nil)
p = ancestor(a, b);
if(!force && !hasheq(&m->theirs, &Zhash) && (a == nil || p != a)){
fprint(2, "remote has diverged\n");
werrstr("remote diverged");
flushpkt(c);
return -1;
}
unref(a);
unref(b);
unref(p);
if(hasheq(&m->theirs, &m->ours)){
print("uptodate %s\n", m->ref);
continue;
}
print("update %s %H %H\n", m->ref, m->theirs, m->ours);
n = snprint(buf, sizeof(buf), "%H %H %s", m->theirs, m->ours, m->ref);
/*
* Workaround for github.
*
* Github will accept the pack but fail to update the references
* if we don't have capabilities advertised. Report-status seems
* harmless to add, so we add it.
*
* Github doesn't advertise any capabilities, so we can't check
* for compatibility. We just need to add it blindly.
*/
if(i == 0 && cs.report){
buf[n++] = '\0';
n += snprint(buf + n, sizeof(buf) - n, " report-status");
}
if(writepkt(c, buf, n) == -1)
sysfatal("unable to send update pkt");
send = 1;
}
flushpkt(c);
if(!send){
fprint(2, "nothing to send\n");
return 0;
}
if(writepack(c->wfd, ours, nours, theirs, ntheirs, &h) == -1)
return -1;
if(!cs.report)
return 0;
if(readphase(c) == -1)
return -1;
/* We asked for a status report, may as well use it. */
while((n = readpkt(c, buf, sizeof(buf))) > 0){
buf[n] = 0;
if(chattygit)
fprint(2, "done sending pack, status %s\n", buf);
nsp = getfields(buf, sp, nelem(sp), 1, " \t\n\r");
if(nsp < 2)
continue;
if(nsp < 3)
sp[2] = "";
/*
* Only report errors; successes will be reported by
* surrounding scripts.
*/
if(strcmp(sp[0], "unpack") == 0 && strcmp(sp[1], "ok") != 0)
fprint(2, "unpack %s\n", sp[1]);
else if(strcmp(sp[0], "ng") == 0)
fprint(2, "failed update: %s\n", sp[1]);
else
continue;
return -1;
}
return 0;
}
void
usage(void)
{
fprint(2, "usage: %s remote [reponame]\n", argv0);
exits("usage");
}
void
main(int argc, char **argv)
{
char *br;
Conn c;
ARGBEGIN{
default:
usage();
break;
case 'd':
chattygit++;
break;
case 'f':
force++;
break;
case 'r':
if(nremoved == nelem(removed))
sysfatal("too many deleted branches");
removed[nremoved++] = EARGF(usage());
break;
case 'a':
sendall++;
break;
case 'b':
br = EARGF(usage());
if(strncmp(br, "refs/heads/", strlen("refs/heads/")) == 0)
br = smprint("%s", br);
else if(strncmp(br, "heads/", strlen("heads/")) == 0)
br = smprint("refs/%s", br);
else
br = smprint("refs/heads/%s", br);
branch = erealloc(branch, (nbranch + 1)*sizeof(char*));
branch[nbranch] = br;
nbranch++;
break;
}ARGEND;
gitinit();
if(argc != 1)
usage();
if(gitconnect(&c, argv[0], "receive") == -1)
sysfatal("git connect: %s: %r", argv[0]);
if(sendpack(&c) == -1)
sysfatal("send failed: %r");
closeconn(&c);
exits(nil);
}