shithub: git9

Download patch

ref: 8da1ef146aa867ec8021aa4acae54b030701ee6e
parent: e33368493eae299479a49cf528b4b6060e069c67
author: Ori Bernstein <ori@eigenstate.org>
date: Sun Jun 4 13:29:51 EDT 2023

git/serve: support better error reports

--- a/serve.c
+++ b/serve.c
@@ -7,12 +7,28 @@
 
 char	*pathpfx = nil;
 int	allowwrite;
+int	report;
 
 int
+parsecaps(char *caps)
+{
+	char *p, *n;
+
+	for(p = caps; p != nil; p = n){
+		if((n = strchr(p, ' ')) != nil)
+			*n++ = 0;
+		if(strcmp(p, "report-status") == 0)
+			report = 1;
+	}
+	return 0;
+}
+
+int
 showrefs(Conn *c)
 {
 	int i, ret, nrefs;
 	Hash head, *refs;
+	char *p, *e, pkt[Pktmax];
 	char **names;
 
 	ret = -1;
@@ -28,7 +44,14 @@
 	for(i = 0; i < nrefs; i++){
 		if(strncmp(names[i], "heads/", strlen("heads/")) != 0)
 			continue;
-		if(fmtpkt(c, "%H refs/%s\n", refs[i], names[i]) == -1)
+		p = pkt;
+		e = pkt+sizeof(pkt);
+		p = seprint(p, e, "%H refs/%s\n", refs[i], names[i]);
+		if(i == 0){
+			*p++ = 0;
+			p = seprint(p, e, "report-status");
+		}
+		if(writepkt(c, pkt, p-pkt) == -1)
 			goto error;
 	}
 	if(flushpkt(c) == -1)
@@ -45,8 +68,8 @@
 int
 servnegotiate(Conn *c, Hash **head, int *nhead, Hash **tail, int *ntail)
 {
-	char pkt[Pktmax];
-	int n, acked;
+	char *sp[3], pkt[Pktmax];
+	int n, nsp, acked;
 	Object *o;
 	Hash h;
 
@@ -62,14 +85,22 @@
 			goto error;
 		if(n == 0)
 			break;
-		if(strncmp(pkt, "want ", 5) != 0){
+		if((nsp = getfields(pkt, sp, nelem(sp), 1, " \t")) < 2){
+			werrstr("protocol garble %s", pkt);
+			goto error;
+		}
+		if(strcmp(sp[0], "want") != 0){
 			werrstr(" protocol garble %s", pkt);
 			goto error;
 		}
-		if(hparse(&h, &pkt[5]) == -1){
+		if(hparse(&h, sp[1]) == -1){
 			werrstr(" garbled want");
 			goto error;
 		}
+		if(nsp > 2 && parsecaps(sp[2]) == -1){
+			werrstr("garbled caps %s", sp[2]);
+			goto error;
+		}
 		if((o = readobject(h)) == nil){
 			werrstr("requested nonexistent object");
 			goto error;
@@ -151,7 +182,7 @@
 {
 	char pkt[Pktmax], *sp[4];
 	Hash old, new;
-	int n, i;
+	int l, n, i;
 
 	if(showrefs(c) == -1)
 		return -1;
@@ -164,6 +195,11 @@
 			goto error;
 		if(n == 0)
 			break;
+		l = strlen(pkt);
+		if(n > l+1 && parsecaps(pkt+l+1) == -1){
+			fmtpkt(c, "ERR  protocol garble %s\n", pkt);
+			goto error;
+		}
 		if(getfields(pkt, sp, nelem(sp), 1, " \t\n\r") != 3){
 			fmtpkt(c, "ERR  protocol garble %s\n", pkt);
 			goto error;
@@ -314,21 +350,26 @@
 		packsz += n;
 	}
 	if(checkhash(pfd, packsz, &h) == -1){
-		dprint(1, "hash mismatch\n");
+		werrstr("hash mismatch\n");
 		goto error1;
 	}
 	if(indexpack(packtmp, idxtmp, h) == -1){
-		dprint(1, "indexing failed: %r\n");
+		werrstr("indexing failed: %r\n");
 		goto error1;
 	}
 	if(rename(packtmp, idxtmp, h) == -1){
-		dprint(1, "rename failed: %r\n");
+		werrstr("rename failed: %r\n");
 		goto error2;
 	}
+	if(report)
+		fmtpkt(c, "unpack ok");
 	return 0;
 
 error2:	remove(idxtmp);
 error1:	remove(packtmp);
+	dprint(1, "update pack: %r");
+	if(report)
+		fmtpkt(c, "unpack %r");
 	return -1;
 }	
 
@@ -348,12 +389,13 @@
 int
 updaterefs(Conn *c, Hash *cur, Hash *upd, char **ref, int nupd)
 {
-	char refpath[512], buf[128];
-	int i, newidx, hadref, fd, ret, lockfd;
+	char refpath[512];
+	int i, j, newidx, hadref, fd, ret, lockfd;
 	vlong newtm;
 	Object *o;
 	Hash h;
 
+	i = 0;
 	ret = -1;
 	hadref = 0;
 	newidx = -1;
@@ -364,20 +406,20 @@
 	 */
 	newtm = -23811206400;	
 	if((lockfd = lockrepo()) == -1){
-		snprint(buf, sizeof(buf), "repo locked\n");
-		return -1;
+		werrstr("repo locked\n");
+		goto out;
 	}
 	for(i = 0; i < nupd; i++){
 		if(resolveref(&h, ref[i]) == 0){
 			hadref = 1;
 			if(!hasheq(&h, &cur[i])){
-				snprint(buf, sizeof(buf), "old ref changed: %s", ref[i]);
-				goto error;
+				werrstr("old ref changed: %s", ref[i]);
+				goto out;
 			}
 		}
 		if(snprint(refpath, sizeof(refpath), ".git/%s", ref[i]) == sizeof(refpath)){
-			snprint(buf, sizeof(buf), "ref path too long: %s", ref[i]);
-			goto error;
+			werrstr("ref path too long: %s", ref[i]);
+			goto out;
 		}
 		if(hasheq(&upd[i], &Zhash)){
 			remove(refpath);
@@ -384,12 +426,12 @@
 			continue;
 		}
 		if((o = readobject(upd[i])) == nil){
-			snprint(buf, sizeof(buf), "update to nonexistent hash %H", upd[i]);
-			goto error;
+			werrstr("update to nonexistent hash %H", upd[i]);
+			goto out;
 		}
 		if(o->type != GCommit){
-			snprint(buf, sizeof(buf), "not commit: %H", upd[i]);
-			goto error;
+			werrstr("not commit: %H", upd[i]);
+			goto out;
 		}
 		if(o->commit->mtime > newtm){
 			newtm = o->commit->mtime;
@@ -397,13 +439,13 @@
 		}
 		unref(o);
 		if((fd = create(refpath, OWRITE|OTRUNC, 0644)) == -1){
-			snprint(buf, sizeof(buf), "open ref: %r");
-			goto error;
+			werrstr("open ref: %r");
+			goto out;
 		}
 		if(fprint(fd, "%H", upd[i]) == -1){
-			snprint(buf, sizeof(buf), "upate ref: %r");
+			werrstr("upate ref: %r");
 			close(fd);
-			goto error;
+			goto out;
 		}
 		close(fd);
 	}
@@ -420,22 +462,30 @@
 	 * use. This should make us pick a useful default in
 	 * those cases, instead of silently failing.
 	 */
+	i = 0;
 	if(resolveref(&h, "HEAD") == -1 && hadref == 0 && newidx != -1){
 		if((fd = create(".git/HEAD", OWRITE|OTRUNC, 0644)) == -1){
-			snprint(buf, sizeof(buf), "open HEAD: %r");
-			goto error;
+			werrstr("open HEAD: %r");
+			goto out;
 		}
-		if(fprint(fd, "ref: %s", ref[0]) == -1){
-			snprint(buf, sizeof(buf), "write HEAD ref: %r");
-			goto error;
+		if(fprint(fd, "ref: %s", ref[i]) == -1){
+			werrstr("write HEAD ref: %r");
+			goto out;
 		}
 		close(fd);
 	}
 	ret = 0;
-error:
-	fmtpkt(c, "ERR %s", buf);
-	close(lockfd);
-	werrstr(buf);
+out:
+	if(report){
+		for(j = 0; j < nupd; j++){
+			if(i != j || ret == 0)
+				fmtpkt(c, "ok %s", ref[i]);
+			else
+				fmtpkt(c, "ng %s %r\n", ref[i]);
+		}
+	}
+	if(lockfd != -1)
+		close(lockfd);
 	return ret;
 }