#include #include #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); }