]> git.lizzy.rs Git - plan9front.git/blob - sys/src/cmd/git/save.c
a9f0f51621645e78989304b111c2c633955e063a
[plan9front.git] / sys / src / cmd / git / save.c
1 #include <u.h>
2 #include <libc.h>
3 #include "git.h"
4
5 typedef struct Objbuf Objbuf;
6 struct Objbuf {
7         int off;
8         char *hdr;
9         int nhdr;
10         char *dat;
11         int ndat;
12 };
13 enum {
14         Maxparents = 16,
15 };
16
17 int
18 gitmode(int m)
19 {
20         if(m & DMDIR)           /* directory */
21                 return 0040000;
22         else if(m & 0111)       /* executable */
23                 return 0100755;
24         else if(m != 0)         /* regular */
25                 return 0100644;
26         else                    /* symlink */
27                 return 0120000;
28 }
29
30 int
31 entcmp(void *pa, void *pb)
32 {
33         char abuf[256], bbuf[256], *ae, *be;
34         Dirent *a, *b;
35
36         a = pa;
37         b = pb;
38         /*
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.
42          *
43          * Wat.
44          */
45         if(strcmp(a->name, b->name) == 0)
46                 return 0;
47
48         ae = seprint(abuf, abuf + sizeof(abuf) - 1, a->name);
49         be = seprint(bbuf, bbuf + sizeof(bbuf) - 1, b->name);
50         if(a->mode & DMDIR)
51                 *ae = '/';
52         if(b->mode & DMDIR)
53                 *be = '/';
54         return strcmp(abuf, bbuf);
55 }
56
57 static int
58 bwrite(void *p, void *buf, int nbuf)
59 {
60         return Bwrite(p, buf, nbuf);
61 }
62
63 static int
64 objbytes(void *p, void *buf, int nbuf)
65 {
66         Objbuf *b;
67         int r, n, o;
68         char *s;
69
70         b = p;
71         n = 0;
72         if(b->off < b->nhdr){
73                 r = b->nhdr - b->off;
74                 r = (nbuf < r) ? nbuf : r;
75                 memcpy(buf, b->hdr, r);
76                 b->off += r;
77                 nbuf -= r;
78                 n += r;
79         }
80         if(b->off < b->ndat + b->nhdr){
81                 s = buf;
82                 o = b->off - b->nhdr;
83                 r = b->ndat - o;
84                 r = (nbuf < r) ? nbuf : r;
85                 memcpy(s + n, b->dat + o, r);
86                 b->off += r;
87                 n += r;
88         }
89         return n;
90 }
91
92 void
93 writeobj(Hash *h, char *hdr, int nhdr, char *dat, int ndat)
94 {
95         Objbuf b = {.off=0, .hdr=hdr, .nhdr=nhdr, .dat=dat, .ndat=ndat};
96         char s[64], o[256];
97         SHA1state *st;
98         Biobuf *f;
99         int fd;
100
101         st = sha1((uchar*)hdr, nhdr, nil, nil);
102         st = sha1((uchar*)dat, ndat, nil, st);
103         sha1(nil, 0, h->h, st);
104
105         snprint(s, sizeof(s), "%H", *h);
106         fd = create(".git/objects", OREAD, DMDIR|0755);
107         close(fd);
108         snprint(o, sizeof(o), ".git/objects/%c%c", s[0], s[1]);
109         fd = create(o, OREAD, DMDIR | 0755);
110         close(fd);
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);
117                 Bterm(f);
118         }
119 }
120
121 int
122 writetree(Dirent *ent, int nent, Hash *h)
123 {
124         char *t, *txt, *etxt, hdr[128];
125         int nhdr, n;
126         Dirent *d, *p;
127
128         t = emalloc((16+256+20) * nent);
129         txt = t;
130         etxt = t + (16+256+20) * nent;
131
132         /* sqeeze out deleted entries */
133         n = 0;
134         p = ent;
135         for(d = ent; d != ent + nent; d++)
136                 if(d->name)
137                         p[n++] = *d;
138         nent = n;
139
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));
146                 t += sizeof(d->h.h);
147         }
148         nhdr = snprint(hdr, sizeof(hdr), "%T %lld", GTree, (vlong)(t - txt)) + 1;
149         writeobj(h, hdr, nhdr, txt, t - txt);
150         free(txt);
151         return nent;
152 }
153
154 void
155 blobify(Dir *d, char *path, int *mode, Hash *bh)
156 {
157         char h[64], *buf;
158         int f, nh;
159
160         if((d->mode & DMDIR) != 0)
161                 sysfatal("not file: %s", path);
162         *mode = d->mode;
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);
170         free(buf);
171         close(f);
172 }
173
174 int
175 tracked(char *path)
176 {
177         char ipath[256];
178         Dir *d;
179
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);
184         d = dirstat(ipath);
185         if(d != nil && d->qid.type != QTDIR){
186                 free(d);
187                 return 0;
188         }
189
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)
195                 return 1;
196
197         return 0;
198 }
199
200 int
201 pathelt(char *buf, int nbuf, char *p, int *isdir)
202 {
203         char *b;
204
205         b = buf;
206         if(*p == '/')
207                 p++;
208         while(*p && *p != '/' && b != buf + nbuf)
209                 *b++ = *p++;
210         *b = '\0';
211         *isdir = (*p == '/');
212         return b - buf;
213 }
214
215 Dirent*
216 dirent(Dirent **ent, int *nent, char *name)
217 {
218         Dirent *d;
219
220         for(d = *ent; d != *ent + *nent; d++)
221                 if(d->name && strcmp(d->name, name) == 0)
222                         return d;
223         *nent += 1;
224         *ent = erealloc(*ent, *nent * sizeof(Dirent));
225         d = *ent + (*nent - 1);
226         memset(d, 0, sizeof(*d));
227         d->name = estrdup(name);
228         return d;
229 }
230
231 int
232 treeify(Object *t, char **path, char **epath, int off, Hash *h)
233 {
234         int r, n, ne, nsub, nent, isdir;
235         char **p, **ep;
236         char elt[256];
237         Object **sub;
238         Dirent *e, *ent;
239         Dir *d;
240
241         r = -1;
242         nsub = 0;
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)
251                                 break;
252                         if((*ep)[off+ne] != '\0' && (*ep)[off+ne] != '/')
253                                 break;
254                 }
255                 e = dirent(&ent, &nent, elt);
256                 if(e->islink)
257                         sysfatal("symlinks may not be modified: %s", *path);
258                 if(e->ismod)
259                         sysfatal("submodules may not be modified: %s", *path);
260                 if(isdir){
261                         e->mode = DMDIR | 0755;
262                         sub[nsub] = readobject(e->h);
263                         if(sub[nsub] == nil || sub[nsub]->type != GTree)
264                                 sub[nsub] = emptydir();
265                         /*
266                          * if after processing deletions, a tree is empty,
267                          * mark it for removal from the parent.
268                          *
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.
272                          */
273                         n = treeify(sub[nsub], p, ep, off + ne + 1, &e->h);
274                         if(n == 0)
275                                 e->name = nil;
276                         else if(n == -1)
277                                 goto err;
278                 }else{
279                         d = dirstat(*p);
280                         if(d != nil && tracked(*p))
281                                 blobify(d, *p, &e->mode, &e->h);
282                         else
283                                 e->name = nil;
284                         free(d);
285                 }
286         }
287         if(nent == 0){
288                 werrstr("%.*s: empty directory", off, *path);
289                 goto err;
290         }
291
292         r = writetree(ent, nent, h);
293 err:
294         free(sub);
295         return r;               
296 }
297
298
299 void
300 mkcommit(Hash *c, char *msg, char *name, char *email, vlong date, Hash *parents, int nparents, Hash tree)
301 {
302         char *s, h[64];
303         int ns, nh, i;
304         Fmt f;
305
306         fmtstrinit(&f);
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);
312         fmtprint(&f, "\n");
313         fmtprint(&f, "%s", msg);
314         s = fmtstrflush(&f);
315
316         ns = strlen(s);
317         nh = snprint(h, sizeof(h), "%T %d", GCommit, ns) + 1;
318         writeobj(c, h, nh, s, ns);
319         free(s);
320 }
321
322 Object*
323 findroot(void)
324 {
325         Object *t, *c;
326         Hash h;
327
328         if(resolveref(&h, "HEAD") == -1)
329                 return emptydir();
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);
334         return t;
335 }
336
337 void
338 usage(void)
339 {
340         fprint(2, "usage: %s -n name -e email -m message -d date [files...]\n", argv0);
341         exits("usage");
342 }
343
344 void
345 main(int argc, char **argv)
346 {
347         Hash th, ch, parents[Maxparents];
348         char *msg, *name, *email, *dstr, cwd[1024];
349         int i, r, ncwd, nparents;
350         vlong date;
351         Object *t;
352
353         gitinit();
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");
358         msg = nil;
359         name = nil;
360         email = nil;
361         dstr = nil;
362         date = time(nil);
363         nparents = 0;
364         ncwd = strlen(cwd);
365
366         ARGBEGIN{
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;
371         case 'p':
372                 if(nparents >= Maxparents)
373                         sysfatal("too many parents");
374                 if(resolveref(&parents[nparents++], EARGF(usage())) == -1)
375                         sysfatal("invalid parent: %r");
376                 break;
377         default:
378                 usage();
379         }ARGEND;
380
381         if(!msg)
382                 sysfatal("missing message");
383         if(!name)
384                 sysfatal("missing name");
385         if(!email)
386                 sysfatal("missing email");
387         if(dstr){
388                 date=strtoll(dstr, &dstr, 10);
389                 if(strlen(dstr) != 0)
390                         sysfatal("could not parse date %s", dstr);
391         }
392         if(msg == nil || name == nil)
393                 usage();
394         for(i = 0; i < argc; i++){
395                 cleanname(argv[i]);
396                 if(*argv[i] == '/' && strncmp(argv[i], cwd, ncwd) == 0)
397                         argv[i] += ncwd;
398                 while(*argv[i] == '/')
399                         argv[i]++;
400         }
401
402         t = findroot();
403         r = treeify(t, argv, argv + argc, 0, &th);
404         if(r == -1)
405                 sysfatal("could not commit: %r\n");
406         mkcommit(&ch, msg, name, email, date, parents, nparents, th);
407         print("%H\n", ch);
408         exits(nil);
409 }