ref: 29bce68c99b7d73cf9b7956a797e8f2e737d8684
parent: b41a69d4bd25da331cb771f743a4227b0f68e5c0
author: Ori Bernstein <ori@eigenstate.org>
date: Sun Aug 30 20:10:25 EDT 2020
git/serve: implement push support
--- a/ref.c
+++ b/ref.c
@@ -263,7 +263,7 @@
osinit(&drop);
for(i = 0; i < nhead; i++){
if((o = readobject(head[i])) == nil)
- sysfatal("read %H: %r", head[i]);
+ sysfatal("read head %H: %r", head[i]);
dprint(1, "twixt init: keep %H\n", o->hash);
e = emalloc(sizeof(Objq));
e->o = o;
@@ -274,7 +274,7 @@
}
for(i = 0; i < ntail; i++){
if((o = readobject(tail[i])) == nil)
- sysfatal("read %H: %r", tail[i]);
+ sysfatal("read tail %H: %r", tail[i]);
dprint(1, "init: drop %H\n", o->hash);
e = emalloc(sizeof(Objq));
e->o = o;
@@ -284,6 +284,7 @@
unref(o);
}
+ dprint(1, "finding twixt commits\n");
while(q != nil){
if(oshas(&drop, q->o->hash))
goto next;
--- a/serve.c
+++ b/serve.c
@@ -1,9 +1,13 @@
#include <u.h>
#include <libc.h>
#include <pool.h>
+#include <ctype.h>
#include "git.h"
+#define Packtmp ".git/objects/pack/recv.pack.tmp"
+#define Idxtmp ".git/objects/pack/recv.idx.tmp"
+
char *pathpfx = "";
int allowwrite;
@@ -51,11 +55,12 @@
}
int
-negotiate(Conn *c, Hash **head, int *nhead, Hash **tail, int *ntail)
+servnegotiate(Conn *c, Hash **head, int *nhead, Hash **tail, int *ntail)
{
char pkt[Pktmax];
int n, acked;
Object *o;
+ Hash h;
if(showrefs(c) == -1)
return -1;
@@ -64,39 +69,61 @@
*tail = nil;
*nhead = 0;
*ntail = 0;
- acked = 0;
while(1){
if((n = readpkt(c, pkt, sizeof(pkt))) == -1)
goto error;
if(n == 0)
break;
- if(strncmp(pkt, "want ", 5) == 0){
- *head = erealloc(*head, (*nhead + 1)*sizeof(Hash));
- if(hparse(&(*head)[*nhead], &pkt[5]) == -1){
- fmtpkt(c, "ERR: garbled want\n");
- goto error;
- }
- *nhead += 1;
+ if(strncmp(pkt, "want ", 5) != 0){
+ fmtpkt(c, "ERR protocol garble %s\n", pkt);
+ goto error;
}
-
- if(strncmp(pkt, "have ", 5) == 0){
- *tail = erealloc(*tail, (*ntail + 1)*sizeof(Hash));
- if(hparse(&(*tail)[*ntail], &pkt[5]) == -1){
- fmtpkt(c, "ERR: garbled have\n");
- goto error;
- }
- if((o = readobject((*tail)[*ntail])) == nil)
- continue;
- if(!acked)
- if(fmtpkt(c, "ACK %H\r\n", o->hash) == -1)
+ if(hparse(&h, &pkt[5]) == -1){
+ fmtpkt(c, "ERR garbled want\n");
+ goto error;
+ }
+ if((o = readobject(h)) == nil){
+ fmtpkt(c, "ERR requested nonexistent object");
+ goto error;
+ }
+ unref(o);
+ *head = erealloc(*head, (*nhead + 1)*sizeof(Hash));
+ (*head)[*nhead] = h;
+ *nhead += 1;
+ }
+
+ acked = 0;
+ while(1){
+ if((n = readpkt(c, pkt, sizeof(pkt))) == -1)
+ goto error;
+ if(strcmp(pkt, "done") == 0 || strcmp(pkt, "done\n") == 0)
+ break;
+ if(n == 0){
+ if(!acked && fmtpkt(c, "NAK\n") == -1)
goto error;
- unref(o);
+ }
+ if(strncmp(pkt, "have ", 5) != 0){
+ fmtpkt(c, "ERR protocol garble %s\n", pkt);
+ goto error;
+ }
+ if(hparse(&h, &pkt[5]) == -1){
+ fmtpkt(c, "ERR garbled have\n");
+ goto error;
+ }
+ if((o = readobject(h)) == nil)
+ continue;
+ if(!acked){
+ if(fmtpkt(c, "ACK %H\n", h) == -1)
+ goto error;
acked = 1;
- *ntail += 1;
}
+ unref(o);
+ *tail = erealloc(*tail, (*ntail + 1)*sizeof(Hash));
+ (*tail)[*ntail] = h;
+ *ntail += 1;
}
- if(!acked)
- fmtpkt(c, "NAK\n");
+ if(!acked && fmtpkt(c, "NAK\n") == -1)
+ goto error;
return 0;
error:
free(*head);
@@ -111,25 +138,251 @@
Object **obj;
int nhead, ntail, nobj;
- if(negotiate(c, &head, &nhead, &tail, &ntail) == -1)
+ dprint(1, "negotiating pack\n");
+ if(servnegotiate(c, &head, &nhead, &tail, &ntail) == -1)
sysfatal("negotiate: %r");
dprint(1, "finding twixt\n");
if(findtwixt(head, nhead, tail, ntail, &obj, &nobj) == -1)
sysfatal("twixt: %r");
+ fprint(2, "DPRINT!\n");
dprint(1, "writing pack\n");
- if(writepack(c->wfd, obj, nobj, &h) == -1)
+ if(nobj > 0 && writepack(c->wfd, obj, nobj, &h) == -1)
sysfatal("send: %r");
return 0;
}
int
-recvpack(Conn *c)
+validref(char *s)
{
- USED(c);
- sysfatal("recvpack: noimpl");
+ if(strncmp(s, "refs/", 5) != 0)
+ return 0;
+ for(; *s != '\0'; s++)
+ if(!isalnum(*s) && strchr("/-_.", *s) == nil)
+ return 0;
+ return 1;
+}
+
+int
+recvnegotiate(Conn *c, Hash **cur, Hash **upd, char ***ref, int *nupd)
+{
+ char pkt[Pktmax], *sp[4];
+ Hash old, new;
+ int n, i;
+
+ if(showrefs(c) == -1)
+ return -1;
+ *cur = nil;
+ *upd = nil;
+ *ref = nil;
+ *nupd = 0;
+ while(1){
+ if((n = readpkt(c, pkt, sizeof(pkt))) == -1)
+ goto error;
+ if(n == 0)
+ break;
+ if(getfields(pkt, sp, nelem(sp), 1, " \t\n\r") != 3){
+ fmtpkt(c, "ERR protocol garble %s\n", pkt);
+ goto error;
+ }
+ if(hparse(&old, sp[0]) == -1){
+ fmtpkt(c, "ERR bad old hash %s\n", sp[0]);
+ goto error;
+ }
+ if(hparse(&new, sp[1]) == -1){
+ fmtpkt(c, "ERR bad new hash %s\n", sp[1]);
+ goto error;
+ }
+ if(!validref(sp[2])){
+ fmtpkt(c, "ERR invalid ref %s\n", sp[2]);
+ goto error;
+ }
+ *cur = erealloc(*cur, (*nupd + 1)*sizeof(Hash));
+ *upd = erealloc(*upd, (*nupd + 1)*sizeof(Hash));
+ *ref = erealloc(*ref, (*nupd + 1)*sizeof(Hash));
+ (*cur)[*nupd] = old;
+ (*upd)[*nupd] = new;
+ (*ref)[*nupd] = estrdup(sp[2]);
+ *nupd += 1;
+ }
+ return 0;
+error:
+ free(*cur);
+ free(*upd);
+ for(i = 0; i < *nupd; i++)
+ free((*ref)[i]);
+ free(*ref);
return -1;
}
+int
+rename(char *pack, char *idx, Hash h)
+{
+ char name[128], path[196];
+ Dir st;
+
+ nulldir(&st);
+ st.name = name;
+ snprint(name, sizeof(name), "%H.pack", h);
+ snprint(path, sizeof(path), ".git/objects/pack/%s", name);
+ if(access(path, AEXIST) == 0)
+ fprint(2, "warning, pack %s already pushed\n", name);
+ else if(dirwstat(pack, &st) == -1)
+ return -1;
+ snprint(name, sizeof(name), "%H.idx", h);
+ snprint(path, sizeof(path), ".git/objects/pack/%s", name);
+ if(access(path, AEXIST) == 0)
+ fprint(2, "warning, pack %s already indexed\n", name);
+ else if(dirwstat(idx, &st) == -1)
+ return -1;
+ return 0;
+}
+
+int
+checkhash(int fd, vlong sz, Hash *hcomp)
+{
+ DigestState *st;
+ Hash hexpect;
+ char buf[Pktmax];
+ vlong n, r;
+ int nr;
+
+ if(sz < 28){
+ werrstr("undersize packfile");
+ return -1;
+ }
+
+ st = nil;
+ n = 0;
+ if(seek(fd, 0, 0) == -1)
+ sysfatal("packfile seek: %r");
+ while(n != sz - 20){
+ nr = sizeof(buf);
+ if(sz - n - 20 < sizeof(buf))
+ nr = sz - n - 20;
+ r = readn(fd, buf, nr);
+ if(r != nr){
+ werrstr("short read");
+ return -1;
+ }
+ st = sha1((uchar*)buf, nr, nil, st);
+ n += r;
+ }
+ sha1(nil, 0, hcomp->h, st);
+ if(readn(fd, hexpect.h, sizeof(hexpect.h)) != sizeof(hexpect.h))
+ sysfatal("truncated packfile");
+ if(!hasheq(hcomp, &hexpect)){
+ werrstr("bad hash: %H != %H", *hcomp, hexpect);
+ return -1;
+ }
+ return 0;
+}
+
+int
+updatepack(Conn *c)
+{
+ char buf[Pktmax];
+ int n, pfd, packsz;
+ Hash h;
+
+ if((pfd = create(Packtmp, ORDWR, 0644)) == -1)
+ return -1;
+ packsz = 0;
+ while(1){
+ n = read(c->rfd, buf, sizeof(buf));
+ if(n == 0)
+ break;
+ if(n == -1 || write(pfd, buf, n) != n)
+ return -1;
+ packsz += n;
+ }
+ if(checkhash(pfd, packsz, &h) == -1){
+ dprint(1, "hash mismatch\n");
+ goto error1;
+ }
+ if(indexpack(Packtmp, Idxtmp, h) == -1){
+ dprint(1, "indexing failed\n");
+ goto error1;
+ }
+ if(rename(Packtmp, Idxtmp, h) == -1){
+ dprint(1, "rename failed: %r\n");
+ goto error2;
+ }
+ return 0;
+
+error2:// remove(Idxtmp);
+error1:// remove(Packtmp);
+ return -1;
+}
+
+int
+lockrepo(void)
+{
+ int fd, i;
+
+ for(i = 0; i < 10; i++) {
+ if((fd = create(".git/index9/lock", ORDWR|OEXCL, 0644))!= -1)
+ return fd;
+ sleep(250);
+ }
+ return -1;
+}
+
+int
+updaterefs(Conn *c, Hash *cur, Hash *upd, char **ref, int nupd)
+{
+ char refpath[512];
+ int i, fd, ret, lockfd;
+ Hash h;
+
+ ret = -1;
+ if((lockfd = lockrepo()) == -1){
+ fmtpkt(c, "ERR repo locked\n");
+ return -1;
+ }
+ for(i = 0; i < nupd; i++){
+ if(resolveref(&h, ref[i]) == 0 && !hasheq(&h, &cur[i])){
+ fmtpkt(c, "ERR old ref changed: %s", ref[i]);
+ goto error;
+ }
+ if(snprint(refpath, sizeof(refpath), ".git/%s", ref[i]) == sizeof(refpath)){
+ fmtpkt(c, "ERR ref path too long: %s", ref[i]);
+ goto error;
+ }
+ if((fd = create(refpath, OWRITE, 0644)) == -1){
+ fmtpkt(c, "ERR open ref: %r");
+ goto error;
+ }
+ if(fprint(fd, "%H", upd[i]) == -1){
+ close(fd);
+ fmtpkt(c, "ERR upate ref: %r");
+ goto error;
+ }
+ close(fd);
+ }
+
+ ret = 0;
+error:
+ close(lockfd);
+ remove(".git/index9/lock");
+ return ret;
+}
+
+int
+recvpack(Conn *c)
+{
+ Hash *cur, *upd;
+ char **ref;
+ int nupd;
+
+ if(recvnegotiate(c, &cur, &upd, &ref, &nupd) == -1)
+ sysfatal("negotiate refs: %r");
+ if(nupd != 0 && updatepack(c) == 0)
+ sysfatal("update pack: %r");
+ if(nupd != 0 && updaterefs(c, cur, upd, ref, nupd) == -1)
+ sysfatal("update refs: %r");
+ return 0;
+}
+
char*
parsecmd(char *buf, char *cmd, int ncmd)
{
@@ -191,7 +444,7 @@
sysfatal("cd %s: %r", p);
if(access(".git", AREAD) == -1)
sysfatal("no git repository");
- if(strcmp(cmd, "git-fetch-pack") == 0 && allowwrite)
+ if(strcmp(cmd, "git-receive-pack") == 0 && allowwrite)
recvpack(&c);
else if(strcmp(cmd, "git-upload-pack") == 0)
servpack(&c);
--- a/util.c
+++ b/util.c
@@ -248,11 +248,10 @@
dprint(int v, char *fmt, ...)
{
va_list ap;
- int n;
- if(v >= debug)
+ if(debug < v)
return;
va_start(ap, fmt);
- n = vfprint(2, fmt, ap);
+ vfprint(2, fmt, ap);
va_end(ap);
}