5 typedef struct Objbuf Objbuf;
20 if(m & DMDIR) /* directory */
22 else if(m & 0111) /* executable */
24 else if(m != 0) /* regular */
31 entcmp(void *pa, void *pb)
33 char abuf[256], bbuf[256], *ae, *be;
39 * If the files have the same name, they're equal.
40 * Otherwise, If they're trees, they sort as thoug
41 * there was a trailing slash.
45 if(strcmp(a->name, b->name) == 0)
48 ae = seprint(abuf, abuf + sizeof(abuf) - 1, a->name);
49 be = seprint(bbuf, bbuf + sizeof(bbuf) - 1, b->name);
54 return strcmp(abuf, bbuf);
58 bwrite(void *p, void *buf, int nbuf)
60 return Bwrite(p, buf, nbuf);
64 objbytes(void *p, void *buf, int nbuf)
74 r = (nbuf < r) ? nbuf : r;
75 memcpy(buf, b->hdr, r);
80 if(b->off < b->ndat + b->nhdr){
84 r = (nbuf < r) ? nbuf : r;
85 memcpy(s + n, b->dat + o, r);
93 writeobj(Hash *h, char *hdr, int nhdr, char *dat, int ndat)
95 Objbuf b = {.off=0, .hdr=hdr, .nhdr=nhdr, .dat=dat, .ndat=ndat};
101 st = sha1((uchar*)hdr, nhdr, nil, nil);
102 st = sha1((uchar*)dat, ndat, nil, st);
103 sha1(nil, 0, h->h, st);
105 snprint(s, sizeof(s), "%H", *h);
106 fd = create(".git/objects", OREAD, DMDIR|0755);
108 snprint(o, sizeof(o), ".git/objects/%c%c", s[0], s[1]);
109 fd = create(o, OREAD, DMDIR | 0755);
111 snprint(o, sizeof(o), ".git/objects/%c%c/%s", s[0], s[1], s + 2);
112 if(readobject(*h) == nil){
113 if((f = Bopen(o, OWRITE)) == nil)
114 sysfatal("could not open %s: %r", o);
115 if(deflatezlib(f, bwrite, &b, objbytes, 9, 0) == -1)
116 sysfatal("could not write %s: %r", o);
122 writetree(Dirent *ent, int nent, Hash *h)
124 char *t, *txt, *etxt, hdr[128];
128 t = emalloc((16+256+20) * nent);
130 etxt = t + (16+256+20) * nent;
132 /* sqeeze out deleted entries */
135 for(d = ent; d != ent + nent; d++)
140 qsort(ent, nent, sizeof(Dirent), entcmp);
141 for(d = ent; d != ent + nent; d++){
142 if(strlen(d->name) >= 255)
143 sysfatal("overly long filename: %s", d->name);
144 t = seprint(t, etxt, "%o %s", gitmode(d->mode), d->name) + 1;
145 memcpy(t, d->h.h, sizeof(d->h.h));
148 nhdr = snprint(hdr, sizeof(hdr), "%T %lld", GTree, (vlong)(t - txt)) + 1;
149 writeobj(h, hdr, nhdr, txt, t - txt);
155 blobify(Dir *d, char *path, int *mode, Hash *bh)
160 if((d->mode & DMDIR) != 0)
161 sysfatal("not file: %s", path);
163 nh = snprint(h, sizeof(h), "%T %lld", GBlob, d->length) + 1;
164 if((f = open(path, OREAD)) == -1)
165 sysfatal("could not open %s: %r", path);
166 buf = emalloc(d->length);
167 if(readn(f, buf, d->length) != d->length)
168 sysfatal("could not read blob %s: %r", path);
169 writeobj(bh, h, nh, buf, d->length);
180 /* Explicitly removed. */
181 snprint(ipath, sizeof(ipath), ".git/index9/removed/%s", path);
182 if(strstr(cleanname(ipath), ".git/index9/removed") != ipath)
183 sysfatal("path %s leaves index", ipath);
185 if(d != nil && d->qid.type != QTDIR){
190 /* Explicitly added. */
191 snprint(ipath, sizeof(ipath), ".git/index9/tracked/%s", path);
192 if(strstr(cleanname(ipath), ".git/index9/tracked") != ipath)
193 sysfatal("path %s leaves index", ipath);
194 if(access(ipath, AEXIST) == 0)
201 pathelt(char *buf, int nbuf, char *p, int *isdir)
208 while(*p && *p != '/' && b != buf + nbuf)
211 *isdir = (*p == '/');
216 dirent(Dirent **ent, int *nent, char *name)
220 for(d = *ent; d != *ent + *nent; d++)
221 if(d->name && strcmp(d->name, name) == 0)
224 *ent = erealloc(*ent, *nent * sizeof(Dirent));
225 d = *ent + (*nent - 1);
226 memset(d, 0, sizeof(*d));
227 d->name = estrdup(name);
232 treeify(Object *t, char **path, char **epath, int off, Hash *h)
234 int r, n, ne, nsub, nent, isdir;
243 nent = t->tree->nent;
244 ent = eamalloc(nent, sizeof(*ent));
245 sub = eamalloc((epath - path), sizeof(Object*));
246 memcpy(ent, t->tree->ent, nent*sizeof(*ent));
247 for(p = path; p != epath; p = ep){
248 ne = pathelt(elt, sizeof(elt), *p + off, &isdir);
249 for(ep = p; ep != epath; ep++){
250 if(strncmp(elt, *ep + off, ne) != 0)
252 if((*ep)[off+ne] != '\0' && (*ep)[off+ne] != '/')
255 e = dirent(&ent, &nent, elt);
257 sysfatal("symlinks may not be modified: %s", *path);
259 sysfatal("submodules may not be modified: %s", *path);
261 e->mode = DMDIR | 0755;
262 sub[nsub] = readobject(e->h);
263 if(sub[nsub] == nil || sub[nsub]->type != GTree)
264 sub[nsub] = emptydir();
266 * if after processing deletions, a tree is empty,
267 * mark it for removal from the parent.
269 * Note, it is still written to the object store,
270 * but this is fine -- and ensures that an empty
271 * repository will continue to work.
273 n = treeify(sub[nsub], p, ep, off + ne + 1, &e->h);
280 if(d != nil && tracked(*p))
281 blobify(d, *p, &e->mode, &e->h);
288 werrstr("%.*s: empty directory", off, *path);
292 r = writetree(ent, nent, h);
300 mkcommit(Hash *c, char *msg, char *name, char *email, vlong date, Hash *parents, int nparents, Hash tree)
307 fmtprint(&f, "tree %H\n", tree);
308 for(i = 0; i < nparents; i++)
309 fmtprint(&f, "parent %H\n", parents[i]);
310 fmtprint(&f, "author %s <%s> %lld +0000\n", name, email, date);
311 fmtprint(&f, "committer %s <%s> %lld +0000\n", name, email, date);
313 fmtprint(&f, "%s", msg);
317 nh = snprint(h, sizeof(h), "%T %d", GCommit, ns) + 1;
318 writeobj(c, h, nh, s, ns);
328 if(resolveref(&h, "HEAD") == -1)
330 if((c = readobject(h)) == nil || c->type != GCommit)
331 sysfatal("could not read HEAD %H", h);
332 if((t = readobject(c->commit->tree)) == nil)
333 sysfatal("could not read tree for commit %H", h);
340 fprint(2, "usage: %s -n name -e email -m message -d date [files...]\n", argv0);
345 main(int argc, char **argv)
347 Hash th, ch, parents[Maxparents];
348 char *msg, *name, *email, *dstr, cwd[1024];
349 int i, r, ncwd, nparents;
354 if(access(".git", AEXIST) != 0)
355 sysfatal("could not find git repo: %r");
356 if(getwd(cwd, sizeof(cwd)) == nil)
357 sysfatal("getcwd: %r");
367 case 'm': msg = EARGF(usage()); break;
368 case 'n': name = EARGF(usage()); break;
369 case 'e': email = EARGF(usage()); break;
370 case 'd': dstr = EARGF(usage()); break;
372 if(nparents >= Maxparents)
373 sysfatal("too many parents");
374 if(resolveref(&parents[nparents++], EARGF(usage())) == -1)
375 sysfatal("invalid parent: %r");
382 sysfatal("missing message");
384 sysfatal("missing name");
386 sysfatal("missing email");
388 date=strtoll(dstr, &dstr, 10);
389 if(strlen(dstr) != 0)
390 sysfatal("could not parse date %s", dstr);
392 if(msg == nil || name == nil)
394 for(i = 0; i < argc; i++){
396 if(*argv[i] == '/' && strncmp(argv[i], cwd, ncwd) == 0)
398 while(*argv[i] == '/')
403 r = treeify(t, argv, argv + argc, 0, &th);
405 sysfatal("could not commit: %r\n");
406 mkcommit(&ch, msg, name, email, date, parents, nparents, th);