ref: 22383ae54cfeb178b150dc9417b0d773cc72c434
dir: /send.c/
#include <u.h>
#include <libc.h>
#include <pool.h>
#include "git.h"
typedef struct Objq Objq;
typedef struct Buf Buf;
typedef struct Compout Compout;
typedef struct Update Update;
struct Buf {
int off;
int sz;
uchar *data;
};
struct Compout {
int fd;
DigestState *st;
};
struct Objq {
Objq *next;
Object *obj;
};
struct Update {
char ref[128];
Hash theirs;
Hash ours;
};
int sendall;
int force;
int nbranch;
char **branch;
char *removed[128];
int nremoved;
static int
hwrite(int fd, void *buf, int nbuf, DigestState **st)
{
if(write(fd, buf, nbuf) != nbuf)
return -1;
*st = sha1(buf, nbuf, nil, *st);
return nbuf;
}
void
pack(Objset *send, Objset *skip, Object *o)
{
Dirent *e;
Object *s;
int i;
if(oshas(send, o) || oshas(skip, o))
return;
osadd(send, o);
switch(o->type){
case GCommit:
if((s = readobject(o->commit->tree)) == nil)
sysfatal("could not read tree %H: %r", o->hash);
pack(send, skip, s);
break;
case GTree:
for(i = 0; i < o->tree->nent; i++){
e = &o->tree->ent[i];
if ((s = readobject(e->h)) == nil)
sysfatal("could not read entry %H: %r", e->h);
pack(send, skip, s);
}
break;
default:
break;
}
}
int
compread(void *p, void *dst, int n)
{
Buf *b;
b = p;
if(n > b->sz - b->off)
n = b->sz - b->off;
memcpy(dst, b->data + b->off, n);
b->off += n;
return n;
}
int
compwrite(void *p, void *buf, int n)
{
Compout *o;
o = p;
o->st = sha1(buf, n, nil, o->st);
return write(o->fd, buf, n);
}
int
compress(int fd, void *buf, int sz, DigestState **st)
{
int r;
Buf b ={
.off=0,
.data=buf,
.sz=sz,
};
Compout o = {
.fd = fd,
.st = *st,
};
r = deflatezlib(&o, compwrite, &b, compread, 6, 0);
*st = o.st;
return r;
}
int
writeobject(int fd, Object *o, DigestState **st)
{
char hdr[8];
uvlong sz;
int i;
i = 1;
sz = o->size;
hdr[0] = o->type << 4;
hdr[0] |= sz & 0xf;
if(sz >= (1 << 4)){
hdr[0] |= 0x80;
sz >>= 4;
for(i = 1; i < sizeof(hdr); i++){
hdr[i] = sz & 0x7f;
if(sz <= 0x7f){
i++;
break;
}
hdr[i] |= 0x80;
sz >>= 7;
}
}
if(hwrite(fd, hdr, i, st) != i)
return -1;
if(compress(fd, o->data, o->size, st) == -1)
return -1;
return 0;
}
int
writepack(int fd, Update *upd, int nupd)
{
Objset send, skip;
Object *o, *p;
Objq *q, *n, *e;
DigestState *st;
Update *u;
char buf[4];
Hash h;
int i;
osinit(&send);
osinit(&skip);
for(i = 0; i < nupd; i++){
u = &upd[i];
if(hasheq(&u->theirs, &Zhash))
continue;
if((o = readobject(u->theirs)) == nil)
sysfatal("could not read %H", u->theirs);
pack(&skip, &skip, o);
}
q = nil;
e = nil;
for(i = 0; i < nupd; i++){
u = &upd[i];
if((o = readobject(u->ours)) == nil){
if(force)
continue;
else
sysfatal("could not read object %H", u->ours);
}
n = emalloc(sizeof(Objq));
n->obj = o;
if(!q){
q = n;
e = n;
}else{
e->next = n;
}
}
for(n = q; n; n = n->next)
e = n;
for(; q; q = n){
o = q->obj;
if(oshas(&skip, o) || oshas(&send, o))
goto iter;
pack(&send, &skip, o);
for(i = 0; i < o->commit->nparent; i++){
if((p = readobject(o->commit->parent[i])) == nil)
sysfatal("could not read parent of %H", o->hash);
e->next = emalloc(sizeof(Objq));
e->next->obj = p;
e = e->next;
}
iter:
n = q->next;
free(q);
}
st = nil;
PUTBE32(buf, send.nobj);
if(hwrite(fd, "PACK\0\0\0\02", 8, &st) != 8)
return -1;
if(hwrite(fd, buf, 4, &st) == -1)
return -1;
for(i = 0; i < send.sz; i++){
if(!send.obj[i])
continue;
if(writeobject(fd, send.obj[i], &st) == -1)
return -1;
}
sha1(nil, 0, h.h, st);
if(write(fd, h.h, sizeof(h.h)) == -1)
return -1;
return 0;
}
Update*
findref(Update *u, int nu, char *ref)
{
int i;
for(i = 0; i < nu; i++)
if(strcmp(u[i].ref, ref) == 0)
return &u[i];
return nil;
}
int
readours(Update **ret)
{
Update *u, *r;
int nu, i;
char *pfx;
Hash *h;
nu = 0;
u = emalloc((nremoved + nbranch)*sizeof(Update));
for(i = 0; i < nbranch; i++){
snprint(u[nu].ref, sizeof(u[nu].ref), "%s", branch[i]);
if(resolveref(&u[nu].ours, 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 = "";
snprint(u[nu].ref, sizeof(u[nu].ref), "%s%s", pfx, removed[i]);
h = &u[nu].ours;
if((r = findref(u, nu, u[nu].ref)) != nil)
h = &r->ours;
else
nu++;
memcpy(h, &Zhash, sizeof(Hash));
}
*ret = u;
return nu;
}
int
sendpack(int fd)
{
int i, n, r, nupd, nsp, send;
char buf[Pktmax], *sp[3];
Update *upd, *u;
Object *a, *b;
nupd = readours(&upd);
while(1){
n = readpkt(fd, buf, sizeof(buf));
if(n == -1)
return -1;
if(n == 0)
break;
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);
if((u = findref(upd, nupd, sp[1])) == nil)
continue;
if(hparse(&u->theirs, sp[0]) == -1)
sysfatal("invalid hash %s", sp[0]);
snprint(u->ref, sizeof(u->ref), sp[1]);
}
r = 0;
send = 0;
for(i = 0; i < nupd; i++){
u = &upd[i];
a = readobject(u->theirs);
b = readobject(u->ours);
if(!force && !hasheq(&u->theirs, &Zhash) && (a == nil || ancestor(a, b) != a)){
fprint(2, "remote has diverged\n");
werrstr("force needed");
send=0;
r = -1;
break;
}
if(hasheq(&u->ours, &Zhash)){
print("removed %s\n", u->ref);
continue;
}
if(hasheq(&u->theirs, &u->ours)){
print("uptodate %s\n", u->ref);
continue;
}
print("update %s %H %H\n", u->ref, u->theirs, u->ours);
n = snprint(buf, sizeof(buf), "%H %H %s", u->theirs, u->ours, u->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){
buf[n++] = '\0';
n += snprint(buf + n, sizeof(buf) - n, " report-status");
}
if(writepkt(fd, buf, n) == -1)
sysfatal("unable to send update pkt");
/*
* If we're rolling back with a force push, the other side already
* has our changes. There's no need to send a pack if that's the case.
*/
if(a == nil || b == nil || ancestor(b, a) != b)
send = 1;
}
flushpkt(fd);
if(!send)
print("nothing to send\n");
if(send){
if(chattygit)
fprint(2, "sending pack...\n");
if(writepack(fd, upd, nupd) == -1)
return -1;
/* We asked for a status report, may as well use it. */
while((n = readpkt(fd, 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;
r = -1;
}
}
return r;
}
void
usage(void)
{
fprint(2, "usage: %s remote [reponame]\n", argv0);
exits("usage");
}
void
main(int argc, char **argv)
{
char proto[Nproto], host[Nhost], port[Nport];
char repo[Nrepo], path[Npath];
char *br;
int fd;
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 '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();
fd = -1;
if(parseuri(argv[0], proto, host, port, path, repo) == -1)
sysfatal("bad uri %s", argv[0]);
if(strcmp(proto, "ssh") == 0 || strcmp(proto, "git+ssh") == 0)
fd = dialssh(host, port, path, "receive");
else if(strcmp(proto, "git") == 0)
fd = dialgit(host, port, path, "receive");
else if(strcmp(proto, "http") == 0 || strcmp(proto, "git+http") == 0)
sysfatal("http clone not implemented");
else
sysfatal("unknown protocol %s", proto);
if(fd == -1)
sysfatal("could not dial %s:%s: %r", proto, host);
if(sendpack(fd) == -1)
sysfatal("send failed: %r");
exits(nil);
}