88070a1fc0a7cee391ed7f0401b7501b916b19d6
[webgit] / src / worktree.c
1 /*
2     cehtehs git web frontend
3
4   Copyright (C)
5     2008,               Christian Thaeter <ct@pipapo.org>
6
7   This program is free software: you can redistribute it and/or modify
8   it under the terms of the GNU Affero General Public License as published by
9   the Free Software Foundation, either version 3 of the License, or
10   (at your option) any later version.
11
12   This program is distributed in the hope that it will be useful,
13   but WITHOUT ANY WARRANTY; without even the implied warranty of
14   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15   GNU Affero General Public License for more details.
16
17   You should have received a copy of the GNU Affero General Public License
18   along with this program.  If not, see <http://www.gnu.org/licenses/>.
19 */
20
21 #include "worktree.h"
22 #include "object.h"
23
24 #include "llist.h"
25
26 #define SHA1_HEADER <openssl/sha.h>
27 #include "git/cache.h"
28 #include "git/commit.h"
29 #include "git/refs.h"
30 #include "git/tree.h"
31 #include "git/strbuf.h"
32
33 struct treenode
34 {
35   llist node;
36   llist subtree;
37   int dirty;
38   unsigned mode;
39   unsigned char sha1[20];
40   size_t len;
41   char name[];
42 };
43
44 struct treenode*
45 treenode_new (unsigned mode, const unsigned char* sha1, const char* name)
46 {
47   size_t len = strlen (name);
48   struct treenode* self = cwa_malloc (sizeof (*self) + len + 1);
49
50   llist_init (&self->node);
51   llist_init (&self->subtree);
52   self->mode = mode;
53   hashcpy (self->sha1, sha1);
54   self->len = len;
55   strcpy (self->name, name);
56   self->name[len] = '\0';
57   self->dirty = 0;
58
59   return self;
60 }
61
62 struct treenode*
63 treenode_addnew (LList tree, unsigned mode, const unsigned char* sha1, const char* name)
64 {
65   struct treenode* self = treenode_new (mode, sha1, name);
66   llist_insert_tail (tree, &self->node);
67   return self;
68 }
69
70 void
71 treenode_free (struct treenode* self)
72 {
73   llist_unlink (&self->node);
74   LLIST_WHILE_HEAD (&self->subtree, node)
75     treenode_free ((struct treenode*) node);
76   free (self);
77 }
78
79
80 static int
81 treenode_cmp (LList la, LList lb)
82 {
83   struct treenode* a = LLIST_TO_STRUCTP (la, struct treenode, node);
84   struct treenode* b = LLIST_TO_STRUCTP (lb, struct treenode, node);
85
86   return base_name_compare (a->name, a->len, a->mode, b->name, b->len, b->mode);
87 }
88
89
90 LList
91 tree_sort (LList tree)
92 {
93   llist_sort (tree, treenode_cmp);
94   return tree;
95 }
96
97 static LList tree_in_flight = NULL;
98
99 LList
100 webgit_tree_parse (LList root, const unsigned char* sha1);
101
102
103 static int
104 webgit_treenode_cb (const unsigned char *sha1, const char *base, int baselen,
105                     const char *name, unsigned mode, int stage)
106 {
107   (void) base;
108   (void) baselen;
109   (void) stage;
110
111   treenode_addnew (tree_in_flight, mode, sha1, name);
112   return 0;
113 }
114
115
116 LList
117 webgit_tree_parse (LList root, const unsigned char* sha1)
118 {
119   struct tree *tree;
120
121   tree = parse_tree_indirect (sha1);
122   if (!tree)
123     die("not a tree object");
124
125   LList tree_back = tree_in_flight;
126   tree_in_flight = root;
127
128   read_tree_recursive (tree, "", 0, 0, NULL, webgit_treenode_cb);
129   tree_in_flight = tree_back;
130
131   return root;
132 }
133
134 static int
135 webgit_worktree_create (struct webgit_repo_info* repo,
136                         const char* ref,
137                         unsigned char* sha1_tree,
138                         unsigned char* sha1_parent,
139                         const char* message
140                         )
141 {
142   char* cbuf = cwa_buffer_provide (256 /* just a good guess */ +
143                                    2*(strlen (repo->query->name) + strlen (repo->query->email)) +
144                                    strlen (message));
145   sprintf (cbuf,
146            "tree %s\n"
147            "parent %s\n"
148            "author %s <%s> %lu +0000\n"
149            "committer %s <%s> %lu +0000\n\n"
150            "%s",
151            sha1_to_hex (sha1_tree),
152            sha1_to_hex (sha1_parent),
153            repo->query->name,
154            repo->query->email,
155            repo->query->now,
156            repo->query->name,
157            repo->query->email,
158            repo->query->now,
159            message
160            );
161
162   unsigned char sha1[20];
163
164   if (!!write_sha1_file (cbuf, strlen (cbuf), commit_type, sha1))
165     die("error writing worktree %s\n", sha1_to_hex (sha1));
166
167   struct ref_lock * lock = lock_ref_sha1 (ref, NULL);
168
169   lock->force_write = 1;
170   return write_ref_sha1 (lock, sha1, NULL);
171 }
172
173
174 void
175 webgit_worktree_setup (struct webgit_repo_info* repo)
176 {
177   char* worktree = cwa_buffer_provide (PATH_MAX);
178   unsigned char sha1_tree[20];
179   snprintf (worktree, PATH_MAX, "refs/worktrees/%s", repo->query->head);
180
181   unsigned char sha1_head[20];
182   get_sha1 (repo->query->head, sha1_head);
183
184   if (!read_ref (worktree, sha1_tree))
185     {
186       /* found worktree */
187       struct commit* commit = lookup_commit_reference (sha1_tree);
188       parse_commit (commit);
189
190       struct commit* treehead = pop_commit (&commit->parents);
191       parse_commit (treehead);
192
193       if (!!hashcmp (sha1_head, treehead->object.sha1))
194         {
195           /* head diverted from worktree */
196           if (!strncmp (webgit_object_commit_tree_parse (commit),
197                         webgit_object_commit_tree_parse (treehead), 40))
198             {
199               /* workree unchanged, forward it */
200               struct commit* newhead = lookup_commit_reference (sha1_head);
201               parse_commit (newhead);
202
203               get_sha1_hex (webgit_object_commit_tree_parse (newhead), sha1_tree);
204               webgit_worktree_create (repo, worktree+5, sha1_tree, sha1_head, "");
205             }
206           else
207             /* else uhm */
208             die ("TODO: uh ohh, head diverted from worktree, needs merge strategy");
209         }
210     }
211   else
212     {
213       /* create brand new worktree */
214       struct commit* parent = lookup_commit_reference (sha1_head);
215       parse_commit (parent);
216
217       get_sha1_hex (webgit_object_commit_tree_parse (parent), sha1_tree);
218       webgit_worktree_create (repo, worktree+5, sha1_tree, sha1_head, "");
219     }
220   repo->worktree = cwa_strndup (sha1_to_hex (sha1_tree), 40);
221 }
222
223
224 void
225 webgit_worktree_removefile (LList tree, const char* path)
226 {
227   const char* delim  = strchr (path, '/');
228   size_t len = delim ? (size_t)(delim - path - 1) : strlen (path);
229
230   LLIST_FOREACH (tree, n)
231     {
232       struct treenode* node = (struct treenode*)n;
233
234       if (!strncmp (node->name, path, len))
235         {
236           if (!delim)
237             /* remove blob */
238             treenode_free (node);
239           else
240             {
241               /*enter subtree*/
242               if (llist_is_empty (&node->subtree))
243                 webgit_tree_parse (&node->subtree, node->sha1);
244               node->dirty = 1;
245               webgit_worktree_removefile (&node->subtree, delim+1);
246             }
247           break;
248         }
249     }
250 }
251
252
253 void
254 webgit_worktree_addfile (LList tree, const char* path, unsigned char* sha1)
255 {
256   const char* delim  = strchr (path, '/');
257   size_t len = delim ? (size_t)(delim - path - 1) : strlen (path);
258
259   LLIST_FOREACH (tree, n)
260     {
261       struct treenode* node = (struct treenode*)n;
262
263       if (!delim)
264         {
265           treenode_addnew (tree, 0100644 /*TODO: ui for changing mode*/, sha1, path);
266           break;
267         }
268       else if (!strncmp (node->name, path, len))
269         {
270           /*enter subtree*/
271           if (llist_is_empty (&node->subtree))
272             webgit_tree_parse (&node->subtree, node->sha1);
273           node->dirty = 1;
274           webgit_worktree_addfile (&node->subtree, delim+1, sha1);
275           break;
276         }
277     }
278 }
279
280
281 void
282 webgit_worktree_fixup (LList tree, unsigned char* sha1)
283 {
284   size_t size = 0;
285   LLIST_FOREACH (tree, n)
286     {
287       struct treenode* node = (struct treenode*)n;
288       if (node->dirty)
289         {
290           if (llist_is_empty (&node->subtree))
291             {
292               /* remove subtree when it became empty */
293               n = llist_prev (n);
294               treenode_free (node);
295             }
296           else
297             {
298               /* otherwise recurse into subtree */
299               webgit_worktree_fixup (&node->subtree, node->sha1);
300             }
301         }
302       /* and count the size by the way */
303       size += 32 + node->len;
304     }
305
306   tree_sort (tree);
307
308   /* write it */
309   struct strbuf buf;
310
311   strbuf_init (&buf, size);
312
313   LLIST_FOREACH (tree, n)
314     {
315       struct treenode* node = (struct treenode*)n;
316
317       strbuf_addf (&buf, "%o %s%c", node->mode, node->name, '\0');
318       strbuf_add (&buf, node->sha1, 20);
319     }
320
321   write_sha1_file (buf.buf, buf.len, tree_type, sha1);
322
323   strbuf_release (&buf);
324 }
325
326 void
327 webgit_worktree_stage (struct webgit_repo_info* repo)
328 {
329   struct webgit_query* query = repo->query;
330
331   /* add blob/file to repo */
332   char* data;
333   size_t data_size;
334
335   if (query->file_size)
336     {
337       data = query->file;
338       data_size = query->file_size;
339     }
340   else if (query->blob_size)
341     {
342       data = query->blob;
343       data_size = query->blob_size;
344     }
345   else
346     die ("no data");
347
348   unsigned char sha1_blob[20];
349
350   if (!!write_sha1_file (data, data_size, "blob", sha1_blob))
351     die("error writing blob %s\n", sha1_to_hex (sha1_blob));
352
353   /* get original tree */
354   char* worktree = cwa_buffer_provide (PATH_MAX);
355   unsigned char sha1_tree[20];
356   snprintf (worktree, PATH_MAX, "refs/worktrees/%s", repo->query->head);
357   read_ref (worktree, sha1_tree);
358
359   struct commit* commit = lookup_commit_reference (sha1_tree);
360   commit->object.parsed = 0;
361   parse_commit (commit);
362
363   struct commit* treehead = pop_commit (&commit->parents);
364
365   parse_commit (treehead);
366   unsigned char sha1_parent[20];
367   hashcpy (sha1_parent, treehead->object.sha1);
368
369   llist tree;
370   llist_init (&tree);
371   webgit_tree_parse (&tree, commit->tree->object.sha1);
372
373   /* modify tree */
374   if (!strcmp (query->mode, "rename"))
375     {
376       webgit_worktree_removefile (&tree, query->oldpath);
377       webgit_worktree_addfile (&tree, query->path, sha1_blob);
378     }
379   else if (!strcmp (query->mode, "copy"))
380     {
381       die ("copy");
382       webgit_worktree_addfile (&tree, query->path, sha1_blob);
383     }
384   else if (!strcmp (query->mode, "delete"))
385     {
386       die ("delete");
387       webgit_worktree_removefile (&tree, query->oldpath);
388     }
389   else
390     die ("unknown mode");
391
392   webgit_worktree_fixup (&tree, sha1_tree);
393
394   LLIST_WHILE_HEAD (&tree, node)
395     treenode_free ((struct treenode*) node);
396
397   char* oldcomment = strstr (commit->buffer, "\n\n");
398   size_t comment_len = 0;
399   if (oldcomment)
400     comment_len = strlen (oldcomment+2);
401
402   if (query->comment)
403       comment_len += (sizeof (" * \n") + strlen (query->comment) - 1);
404
405   char* newcomment = cwa_buffer_provide (comment_len+1);
406
407   sprintf (newcomment, "%s * %s\n%c", oldcomment ? oldcomment+2 : "", query->comment, '\0');
408
409   webgit_worktree_create (repo,
410                           worktree + 5,
411                           sha1_tree,
412                           sha1_parent,
413                           newcomment
414                           );
415 }
416
417
418
419 int
420 webgit_worktree_blob_eq (struct webgit_repo_info* repo, const char* path)
421 {
422   /* find out if the current file in head and worktree are the same */
423
424   /* worktree */
425   unsigned char sha1_tree[20];
426   get_sha1 (repo->worktree, sha1_tree);
427
428   llist worktree;
429   llist_init (&worktree);
430   webgit_tree_parse (&worktree, sha1_tree);
431
432   const char* delim;
433   const char* name = path;
434   LList tree = &worktree;
435
436   unsigned char* worktree_file = NULL;
437
438   do
439     {
440       delim = strchr (name, '/');
441       size_t len = delim ? (size_t)(delim - name - 1) : strlen (name);
442       LLIST_FOREACH (tree, n)
443         {
444           struct treenode* node = (struct treenode*)n;
445
446           if (strncmp (name, node->name, len))
447             continue;
448           else
449             {
450               if (delim)
451                 {
452                   name = delim + 1;
453                   tree = webgit_tree_parse (&node->subtree, node->sha1);
454                   break;
455                 }
456               else
457                 worktree_file = node->sha1;
458             }
459         }
460     } while (delim);
461
462   /* head */
463   unsigned char sha1_head[20];
464   get_sha1 (repo->query->head, sha1_head);
465   struct commit* head_commit = lookup_commit (sha1_head);
466   parse_commit (head_commit);
467
468   llist headtree;
469   llist_init (&headtree);
470   webgit_tree_parse (&headtree, head_commit->tree->object.sha1);
471
472   name = path;
473   tree = &headtree;
474   unsigned char* headtree_file = NULL;
475
476   do
477     {
478       delim = strchr (name, '/');
479       size_t len = delim ? (size_t)(delim - name - 1) : strlen (name);
480       LLIST_FOREACH (tree, n)
481         {
482           struct treenode* node = (struct treenode*)n;
483           if (strncmp (name, node->name, len))
484             continue;
485           else
486             {
487               if (delim)
488                 {
489                   name = delim + 1;
490                   tree = webgit_tree_parse (&node->subtree, node->sha1);
491                   break;
492                 }
493               else
494                 headtree_file = node->sha1;
495             }
496         }
497     } while (delim);
498
499   int ret = 0;
500
501   if (worktree_file && headtree_file && !hashcmp(worktree_file, headtree_file))
502     ret = 1;
503
504   LLIST_WHILE_HEAD (&worktree, node)
505     treenode_free ((struct treenode*) node);
506   LLIST_WHILE_HEAD (&headtree, node)
507     treenode_free ((struct treenode*) node);
508
509   return ret;
510 }
511
512 /*
513 //      Local Variables:
514 //      mode: C
515 //      c-file-style: "gnu"
516 //      indent-tabs-mode: nil
517 //      End:
518 */