]> git.lizzy.rs Git - plan9front.git/blob - sys/src/cmd/git/save.c
git/save: leave submodules unmangled
[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(Dirent *e)
19 {
20         if(e->islink)
21                 return 0120000;
22         else if(e->ismod)
23                 return 0160000;
24         else if(e->mode & DMDIR)
25                 return 0040000;
26         else if(e->mode & 0111)
27                 return 0100755;
28         else
29                 return 0100644;
30 }
31
32 int
33 entcmp(void *pa, void *pb)
34 {
35         char abuf[256], bbuf[256], *ae, *be;
36         Dirent *a, *b;
37
38         a = pa;
39         b = pb;
40         /*
41          * If the files have the same name, they're equal.
42          * Otherwise, If they're trees, they sort as thoug
43          * there was a trailing slash.
44          *
45          * Wat.
46          */
47         if(strcmp(a->name, b->name) == 0)
48                 return 0;
49
50         ae = seprint(abuf, abuf + sizeof(abuf) - 1, a->name);
51         be = seprint(bbuf, bbuf + sizeof(bbuf) - 1, b->name);
52         if(a->mode & DMDIR)
53                 *ae = '/';
54         if(b->mode & DMDIR)
55                 *be = '/';
56         return strcmp(abuf, bbuf);
57 }
58
59 static int
60 bwrite(void *p, void *buf, int nbuf)
61 {
62         return Bwrite(p, buf, nbuf);
63 }
64
65 static int
66 objbytes(void *p, void *buf, int nbuf)
67 {
68         Objbuf *b;
69         int r, n, o;
70         char *s;
71
72         b = p;
73         n = 0;
74         if(b->off < b->nhdr){
75                 r = b->nhdr - b->off;
76                 r = (nbuf < r) ? nbuf : r;
77                 memcpy(buf, b->hdr, r);
78                 b->off += r;
79                 nbuf -= r;
80                 n += r;
81         }
82         if(b->off < b->ndat + b->nhdr){
83                 s = buf;
84                 o = b->off - b->nhdr;
85                 r = b->ndat - o;
86                 r = (nbuf < r) ? nbuf : r;
87                 memcpy(s + n, b->dat + o, r);
88                 b->off += r;
89                 n += r;
90         }
91         return n;
92 }
93
94 void
95 writeobj(Hash *h, char *hdr, int nhdr, char *dat, int ndat)
96 {
97         Objbuf b = {.off=0, .hdr=hdr, .nhdr=nhdr, .dat=dat, .ndat=ndat};
98         char s[64], o[256];
99         SHA1state *st;
100         Biobuf *f;
101         int fd;
102
103         st = sha1((uchar*)hdr, nhdr, nil, nil);
104         st = sha1((uchar*)dat, ndat, nil, st);
105         sha1(nil, 0, h->h, st);
106
107         snprint(s, sizeof(s), "%H", *h);
108         fd = create(".git/objects", OREAD, DMDIR|0755);
109         close(fd);
110         snprint(o, sizeof(o), ".git/objects/%c%c", s[0], s[1]);
111         fd = create(o, OREAD, DMDIR | 0755);
112         close(fd);
113         snprint(o, sizeof(o), ".git/objects/%c%c/%s", s[0], s[1], s + 2);
114         if(readobject(*h) == nil){
115                 if((f = Bopen(o, OWRITE)) == nil)
116                         sysfatal("could not open %s: %r", o);
117                 if(deflatezlib(f, bwrite, &b, objbytes, 9, 0) == -1)
118                         sysfatal("could not write %s: %r", o);
119                 Bterm(f);
120         }
121 }
122
123 int
124 writetree(Dirent *ent, int nent, Hash *h)
125 {
126         char *t, *txt, *etxt, hdr[128];
127         int nhdr, n;
128         Dirent *d, *p;
129
130         t = emalloc((16+256+20) * nent);
131         txt = t;
132         etxt = t + (16+256+20) * nent;
133
134         /* sqeeze out deleted entries */
135         n = 0;
136         p = ent;
137         for(d = ent; d != ent + nent; d++)
138                 if(d->name)
139                         p[n++] = *d;
140         nent = n;
141
142         qsort(ent, nent, sizeof(Dirent), entcmp);
143         for(d = ent; d != ent + nent; d++){
144                 if(strlen(d->name) >= 255)
145                         sysfatal("overly long filename: %s", d->name);
146                 t = seprint(t, etxt, "%o %s", gitmode(d), d->name) + 1;
147                 memcpy(t, d->h.h, sizeof(d->h.h));
148                 t += sizeof(d->h.h);
149         }
150         nhdr = snprint(hdr, sizeof(hdr), "%T %lld", GTree, (vlong)(t - txt)) + 1;
151         writeobj(h, hdr, nhdr, txt, t - txt);
152         free(txt);
153         return nent;
154 }
155
156 void
157 blobify(Dir *d, char *path, int *mode, Hash *bh)
158 {
159         char h[64], *buf;
160         int f, nh;
161
162         if((d->mode & DMDIR) != 0)
163                 sysfatal("not file: %s", path);
164         *mode = d->mode;
165         nh = snprint(h, sizeof(h), "%T %lld", GBlob, d->length) + 1;
166         if((f = open(path, OREAD)) == -1)
167                 sysfatal("could not open %s: %r", path);
168         buf = emalloc(d->length);
169         if(readn(f, buf, d->length) != d->length)
170                 sysfatal("could not read blob %s: %r", path);
171         writeobj(bh, h, nh, buf, d->length);
172         free(buf);
173         close(f);
174 }
175
176 int
177 tracked(char *path)
178 {
179         char ipath[256];
180         Dir *d;
181
182         /* Explicitly removed. */
183         snprint(ipath, sizeof(ipath), ".git/index9/removed/%s", path);
184         if(strstr(cleanname(ipath), ".git/index9/removed") != ipath)
185                 sysfatal("path %s leaves index", ipath);
186         d = dirstat(ipath);
187         if(d != nil && d->qid.type != QTDIR){
188                 free(d);
189                 return 0;
190         }
191
192         /* Explicitly added. */
193         snprint(ipath, sizeof(ipath), ".git/index9/tracked/%s", path);
194         if(strstr(cleanname(ipath), ".git/index9/tracked") != ipath)
195                 sysfatal("path %s leaves index", ipath);
196         if(access(ipath, AEXIST) == 0)
197                 return 1;
198
199         return 0;
200 }
201
202 int
203 pathelt(char *buf, int nbuf, char *p, int *isdir)
204 {
205         char *b;
206
207         b = buf;
208         if(*p == '/')
209                 p++;
210         while(*p && *p != '/' && b != buf + nbuf)
211                 *b++ = *p++;
212         *b = '\0';
213         *isdir = (*p == '/');
214         return b - buf;
215 }
216
217 Dirent*
218 dirent(Dirent **ent, int *nent, char *name)
219 {
220         Dirent *d;
221
222         for(d = *ent; d != *ent + *nent; d++)
223                 if(d->name && strcmp(d->name, name) == 0)
224                         return d;
225         *nent += 1;
226         *ent = erealloc(*ent, *nent * sizeof(Dirent));
227         d = *ent + (*nent - 1);
228         memset(d, 0, sizeof(*d));
229         d->name = estrdup(name);
230         return d;
231 }
232
233 int
234 treeify(Object *t, char **path, char **epath, int off, Hash *h)
235 {
236         int r, n, ne, nsub, nent, isdir;
237         char **p, **ep;
238         char elt[256];
239         Object **sub;
240         Dirent *e, *ent;
241         Dir *d;
242
243         r = -1;
244         nsub = 0;
245         nent = t->tree->nent;
246         ent = eamalloc(nent, sizeof(*ent));
247         sub = eamalloc((epath - path), sizeof(Object*));
248         memcpy(ent, t->tree->ent, nent*sizeof(*ent));
249         for(p = path; p != epath; p = ep){
250                 ne = pathelt(elt, sizeof(elt), *p + off, &isdir);
251                 for(ep = p; ep != epath; ep++){
252                         if(strncmp(elt, *ep + off, ne) != 0)
253                                 break;
254                         if((*ep)[off+ne] != '\0' && (*ep)[off+ne] != '/')
255                                 break;
256                 }
257                 e = dirent(&ent, &nent, elt);
258                 if(e->islink)
259                         sysfatal("symlinks may not be modified: %s", *path);
260                 if(e->ismod)
261                         sysfatal("submodules may not be modified: %s", *path);
262                 if(isdir){
263                         e->mode = DMDIR | 0755;
264                         sub[nsub] = readobject(e->h);
265                         if(sub[nsub] == nil || sub[nsub]->type != GTree)
266                                 sub[nsub] = emptydir();
267                         /*
268                          * if after processing deletions, a tree is empty,
269                          * mark it for removal from the parent.
270                          *
271                          * Note, it is still written to the object store,
272                          * but this is fine -- and ensures that an empty
273                          * repository will continue to work.
274                          */
275                         n = treeify(sub[nsub], p, ep, off + ne + 1, &e->h);
276                         if(n == 0)
277                                 e->name = nil;
278                         else if(n == -1)
279                                 goto err;
280                 }else{
281                         d = dirstat(*p);
282                         if(d != nil && tracked(*p))
283                                 blobify(d, *p, &e->mode, &e->h);
284                         else
285                                 e->name = nil;
286                         free(d);
287                 }
288         }
289         if(nent == 0){
290                 werrstr("%.*s: empty directory", off, *path);
291                 goto err;
292         }
293
294         r = writetree(ent, nent, h);
295 err:
296         free(sub);
297         return r;               
298 }
299
300
301 void
302 mkcommit(Hash *c, char *msg, char *name, char *email, vlong date, Hash *parents, int nparents, Hash tree)
303 {
304         char *s, h[64];
305         int ns, nh, i;
306         Fmt f;
307
308         fmtstrinit(&f);
309         fmtprint(&f, "tree %H\n", tree);
310         for(i = 0; i < nparents; i++)
311                 fmtprint(&f, "parent %H\n", parents[i]);
312         fmtprint(&f, "author %s <%s> %lld +0000\n", name, email, date);
313         fmtprint(&f, "committer %s <%s> %lld +0000\n", name, email, date);
314         fmtprint(&f, "\n");
315         fmtprint(&f, "%s", msg);
316         s = fmtstrflush(&f);
317
318         ns = strlen(s);
319         nh = snprint(h, sizeof(h), "%T %d", GCommit, ns) + 1;
320         writeobj(c, h, nh, s, ns);
321         free(s);
322 }
323
324 Object*
325 findroot(void)
326 {
327         Object *t, *c;
328         Hash h;
329
330         if(resolveref(&h, "HEAD") == -1)
331                 return emptydir();
332         if((c = readobject(h)) == nil || c->type != GCommit)
333                 sysfatal("could not read HEAD %H", h);
334         if((t = readobject(c->commit->tree)) == nil)
335                 sysfatal("could not read tree for commit %H", h);
336         return t;
337 }
338
339 void
340 usage(void)
341 {
342         fprint(2, "usage: %s -n name -e email -m message -d date [files...]\n", argv0);
343         exits("usage");
344 }
345
346 void
347 main(int argc, char **argv)
348 {
349         Hash th, ch, parents[Maxparents];
350         char *msg, *name, *email, *dstr, cwd[1024];
351         int i, r, ncwd, nparents;
352         vlong date;
353         Object *t;
354
355         gitinit();
356         if(access(".git", AEXIST) != 0)
357                 sysfatal("could not find git repo: %r");
358         if(getwd(cwd, sizeof(cwd)) == nil)
359                 sysfatal("getcwd: %r");
360         msg = nil;
361         name = nil;
362         email = nil;
363         dstr = nil;
364         date = time(nil);
365         nparents = 0;
366         ncwd = strlen(cwd);
367
368         ARGBEGIN{
369         case 'm':       msg = EARGF(usage());   break;
370         case 'n':       name = EARGF(usage());  break;
371         case 'e':       email = EARGF(usage()); break;
372         case 'd':       dstr = EARGF(usage());  break;
373         case 'p':
374                 if(nparents >= Maxparents)
375                         sysfatal("too many parents");
376                 if(resolveref(&parents[nparents++], EARGF(usage())) == -1)
377                         sysfatal("invalid parent: %r");
378                 break;
379         default:
380                 usage();
381         }ARGEND;
382
383         if(!msg)
384                 sysfatal("missing message");
385         if(!name)
386                 sysfatal("missing name");
387         if(!email)
388                 sysfatal("missing email");
389         if(dstr){
390                 date=strtoll(dstr, &dstr, 10);
391                 if(strlen(dstr) != 0)
392                         sysfatal("could not parse date %s", dstr);
393         }
394         if(msg == nil || name == nil)
395                 usage();
396         for(i = 0; i < argc; i++){
397                 cleanname(argv[i]);
398                 if(*argv[i] == '/' && strncmp(argv[i], cwd, ncwd) == 0)
399                         argv[i] += ncwd;
400                 while(*argv[i] == '/')
401                         argv[i]++;
402         }
403
404         t = findroot();
405         r = treeify(t, argv, argv + argc, 0, &th);
406         if(r == -1)
407                 sysfatal("could not commit: %r\n");
408         mkcommit(&ch, msg, name, email, date, parents, nparents, th);
409         print("%H\n", ch);
410         exits(nil);
411 }