280 lines · 5.8 KB · C
RawDownload
#include <u.h>#include <libc.h> #include "git.h" typedef struct Map	Map; 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; intfindref(char **r, int nr, char *ref){	int i;	for(i = 0; i < nr; i++)		if(strcmp(r[i], ref) == 0)			return i;	return -1;} intfindkey(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;} intreadours(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++;		else			free(ref[idx]);		assert(idx < nremoved + nbranch);		memcpy(&tail[idx], &Zhash, sizeof(Hash));		ref[idx] = 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;	} intsendpack(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;	Map *map, *m; 	first = 1;	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, c);		first = 0; 		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)		&& !hasheq(&m->ours, &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 && c->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(!c->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)			werrstr("unpack %s", sp[1]);		else if(strcmp(sp[0], "ng") == 0)			werrstr("failed update: %s %s", sp[1], sp[2]);		else			continue;		return -1;	}	return 0;} voidusage(void){	fprint(2, "usage: %s remote [reponame]\n", argv0);	exits("usage");} voidmain(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(nil, 0, nil);	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);}