add a re-edit link for files in worktree diverted from head
[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
98 static LList tree_in_flight = NULL;
99
100 LList
101 webgit_tree_parse (LList root, const unsigned char* sha1);
102
103
104 static int
105 webgit_treenode_cb (const unsigned char *sha1, const char *base, int baselen,
106                     const char *name, unsigned mode, int stage)
107 {
108   (void) base;
109   (void) baselen;
110   (void) stage;
111
112   treenode_addnew (tree_in_flight, mode, sha1, name);
113   return 0;
114 }
115
116
117 LList
118 webgit_tree_parse (LList root, const unsigned char* sha1)
119 {
120   struct tree *tree;
121
122   tree = parse_tree_indirect (sha1);
123   if (!tree)
124     die("not a tree object");
125
126   LList tree_back = tree_in_flight;
127   tree_in_flight = root;
128
129   read_tree_recursive (tree, "", 0, 0, NULL, webgit_treenode_cb);
130   tree_in_flight = tree_back;
131
132   return root;
133 }
134
135
136 int
137 webgit_worktree_getsha1 (struct webgit_query* query, unsigned char* sha1)
138 {
139   unsigned char sha1_tree[20];
140   if (get_sha1 (query->current_repo->worktree, sha1_tree))
141     return 0;
142
143   llist worktree;
144   llist_init (&worktree);
145   webgit_tree_parse (&worktree, sha1_tree);
146
147   int ret = 0;
148
149   const char* delim;
150   const char* name = query->path;
151   LList tree = &worktree;
152
153   do
154     {
155       delim = strchr (name, '/');
156       size_t len = delim ? (size_t)(delim - name - 1) : strlen (name);
157       LLIST_FOREACH (tree, n)
158         {
159           struct treenode* node = (struct treenode*)n;
160
161           if (strncmp (name, node->name, len))
162             continue;
163           else
164             {
165               if (delim)
166                 {
167                   name = delim + 1;
168                   tree = webgit_tree_parse (&node->subtree, node->sha1);
169                   break;
170                 }
171               else
172                 {
173                   hashcpy (sha1, node->sha1);
174                   ret = 1;
175                 }
176             }
177         }
178     } while (delim);
179
180   LLIST_WHILE_HEAD (&worktree, node)
181     treenode_free ((struct treenode*) node);
182
183   return ret;
184 }
185
186
187 static int
188 webgit_worktree_create (struct webgit_repo_info* repo,
189                         const char* ref,
190                         unsigned char* sha1_tree,
191                         unsigned char* sha1_parent,
192                         const char* message
193                         )
194 {
195   char* cbuf = cwa_buffer_provide (256 /* just a good guess */ +
196                                    2*(strlen (repo->query->name) + strlen (repo->query->email)) +
197                                    strlen (message));
198   sprintf (cbuf,
199            "tree %s\n"
200            "parent %s\n"
201            "author %s <%s> %lu +0000\n"
202            "committer %s <%s> %lu +0000\n\n"
203            "%s",
204            sha1_to_hex (sha1_tree),
205            sha1_to_hex (sha1_parent),
206            repo->query->name,
207            repo->query->email,
208            repo->query->now,
209            repo->query->name,
210            repo->query->email,
211            repo->query->now,
212            message
213            );
214
215   unsigned char sha1[20];
216
217   if (!!write_sha1_file (cbuf, strlen (cbuf), commit_type, sha1))
218     die("error writing worktree %s\n", sha1_to_hex (sha1));
219
220   struct ref_lock * lock = lock_ref_sha1 (ref, NULL);
221
222   lock->force_write = 1;
223   return write_ref_sha1 (lock, sha1, NULL);
224 }
225
226
227 void
228 webgit_worktree_setup (struct webgit_repo_info* repo)
229 {
230   char* worktree = cwa_buffer_provide (PATH_MAX);
231   unsigned char sha1_tree[20];
232   snprintf (worktree, PATH_MAX, "refs/worktrees/%s", repo->query->head);
233
234   unsigned char sha1_head[20];
235   get_sha1 (repo->query->head, sha1_head);
236
237   if (!read_ref (worktree, sha1_tree))
238     {
239       /* found worktree */
240       struct commit* commit = lookup_commit_reference (sha1_tree);
241       parse_commit (commit);
242
243       struct commit* treehead = pop_commit (&commit->parents);
244       parse_commit (treehead);
245
246       if (!!hashcmp (sha1_head, treehead->object.sha1))
247         {
248           /* head diverted from worktree */
249           if (!strncmp (webgit_object_commit_tree_parse (commit),
250                         webgit_object_commit_tree_parse (treehead), 40))
251             {
252               /* workree unchanged, forward it */
253               struct commit* newhead = lookup_commit_reference (sha1_head);
254               parse_commit (newhead);
255
256               get_sha1_hex (webgit_object_commit_tree_parse (newhead), sha1_tree);
257               webgit_worktree_create (repo, worktree+5, sha1_tree, sha1_head, "");
258             }
259           else
260             /* else uhm */
261             die ("TODO: uh ohh, head diverted from worktree, needs merge strategy");
262         }
263     }
264   else
265     {
266       /* create brand new worktree */
267       struct commit* parent = lookup_commit_reference (sha1_head);
268       parse_commit (parent);
269
270       get_sha1_hex (webgit_object_commit_tree_parse (parent), sha1_tree);
271       webgit_worktree_create (repo, worktree+5, sha1_tree, sha1_head, "");
272     }
273   repo->worktree = cwa_strndup (sha1_to_hex (sha1_tree), 40);
274 }
275
276
277 void
278 webgit_worktree_removefile (LList tree, const char* path)
279 {
280   const char* delim  = strchr (path, '/');
281   size_t len = delim ? (size_t)(delim - path - 1) : strlen (path);
282
283   LLIST_FOREACH (tree, n)
284     {
285       struct treenode* node = (struct treenode*)n;
286
287       if (!strncmp (node->name, path, len))
288         {
289           if (!delim)
290             /* remove blob */
291             treenode_free (node);
292           else
293             {
294               /*enter subtree*/
295               if (llist_is_empty (&node->subtree))
296                 webgit_tree_parse (&node->subtree, node->sha1);
297               node->dirty = 1;
298               webgit_worktree_removefile (&node->subtree, delim+1);
299             }
300           break;
301         }
302     }
303 }
304
305
306 void
307 webgit_worktree_addfile (LList tree, const char* path, unsigned char* sha1)
308 {
309   const char* delim  = strchr (path, '/');
310   size_t len = delim ? (size_t)(delim - path - 1) : strlen (path);
311
312   LLIST_FOREACH (tree, n)
313     {
314       struct treenode* node = (struct treenode*)n;
315
316       if (!delim)
317         {
318           treenode_addnew (tree, 0100644 /*TODO: ui for changing mode*/, sha1, path);
319           break;
320         }
321       else if (!strncmp (node->name, path, len))
322         {
323           /*enter subtree*/
324           if (llist_is_empty (&node->subtree))
325             webgit_tree_parse (&node->subtree, node->sha1);
326           node->dirty = 1;
327           webgit_worktree_addfile (&node->subtree, delim+1, sha1);
328           break;
329         }
330     }
331 }
332
333
334 void
335 webgit_worktree_fixup (LList tree, unsigned char* sha1)
336 {
337   size_t size = 0;
338   LLIST_FOREACH (tree, n)
339     {
340       struct treenode* node = (struct treenode*)n;
341       if (node->dirty)
342         {
343           if (llist_is_empty (&node->subtree))
344             {
345               /* remove subtree when it became empty */
346               n = llist_prev (n);
347               treenode_free (node);
348             }
349           else
350             {
351               /* otherwise recurse into subtree */
352               webgit_worktree_fixup (&node->subtree, node->sha1);
353             }
354         }
355       /* and count the size by the way */
356       size += 32 + node->len;
357     }
358
359   tree_sort (tree);
360
361   /* write it */
362   struct strbuf buf;
363
364   strbuf_init (&buf, size);
365
366   LLIST_FOREACH (tree, n)
367     {
368       struct treenode* node = (struct treenode*)n;
369
370       strbuf_addf (&buf, "%o %s%c", node->mode, node->name, '\0');
371       strbuf_add (&buf, node->sha1, 20);
372     }
373
374   write_sha1_file (buf.buf, buf.len, tree_type, sha1);
375
376   strbuf_release (&buf);
377 }
378
379 void
380 webgit_worktree_stage (struct webgit_repo_info* repo)
381 {
382   struct webgit_query* query = repo->query;
383
384   /* add blob/file to repo */
385   char* data;
386   size_t data_size;
387
388   if (query->file_size)
389     {
390       data = query->file;
391       data_size = query->file_size;
392     }
393   else if (query->blob_size)
394     {
395       data = query->blob;
396       data_size = query->blob_size;
397     }
398   else
399     die ("no data");
400
401   unsigned char sha1_blob[20];
402
403   if (!!write_sha1_file (data, data_size, "blob", sha1_blob))
404     die("error writing blob %s\n", sha1_to_hex (sha1_blob));
405
406   /* get original tree */
407   char* worktree = cwa_buffer_provide (PATH_MAX);
408   unsigned char sha1_tree[20];
409   snprintf (worktree, PATH_MAX, "refs/worktrees/%s", repo->query->head);
410   read_ref (worktree, sha1_tree);
411
412   struct commit* commit = lookup_commit_reference (sha1_tree);
413   commit->object.parsed = 0;
414   parse_commit (commit);
415
416   struct commit* treehead = pop_commit (&commit->parents);
417
418   parse_commit (treehead);
419   unsigned char sha1_parent[20];
420   hashcpy (sha1_parent, treehead->object.sha1);
421
422   llist tree;
423   llist_init (&tree);
424   webgit_tree_parse (&tree, commit->tree->object.sha1);
425
426   /* modify tree */
427   if (!strcmp (query->mode, "rename"))
428     {
429       webgit_worktree_removefile (&tree, query->oldpath);
430       webgit_worktree_addfile (&tree, query->path, sha1_blob);
431     }
432   else if (!strcmp (query->mode, "copy"))
433     {
434       die ("copy");
435       webgit_worktree_addfile (&tree, query->path, sha1_blob);
436     }
437   else if (!strcmp (query->mode, "delete"))
438     {
439       die ("delete");
440       webgit_worktree_removefile (&tree, query->oldpath);
441     }
442   else
443     die ("unknown mode");
444
445   webgit_worktree_fixup (&tree, sha1_tree);
446
447   LLIST_WHILE_HEAD (&tree, node)
448     treenode_free ((struct treenode*) node);
449
450   char* oldcomment = strstr (commit->buffer, "\n\n");
451   size_t comment_len = 0;
452   if (oldcomment)
453     comment_len = strlen (oldcomment+2);
454
455   if (query->comment)
456       comment_len += (sizeof (" * \n") + strlen (query->comment) - 1);
457
458   char* newcomment = cwa_buffer_provide (comment_len+1);
459
460   sprintf (newcomment, "%s * %s\n%c", oldcomment ? oldcomment+2 : "", query->comment, '\0');
461
462   webgit_worktree_create (repo,
463                           worktree + 5,
464                           sha1_tree,
465                           sha1_parent,
466                           newcomment
467                           );
468 }
469
470
471
472 int
473 webgit_worktree_blob_eq (struct webgit_repo_info* repo, const char* path)
474 {
475   /* find out if the current file in head and worktree are the same */
476
477   /* worktree */
478   unsigned char sha1_tree[20];
479   get_sha1 (repo->worktree, sha1_tree);
480
481   llist worktree;
482   llist_init (&worktree);
483   webgit_tree_parse (&worktree, sha1_tree);
484
485   const char* delim;
486   const char* name = path;
487   LList tree = &worktree;
488
489   unsigned char* worktree_file = NULL;
490
491   do
492     {
493       delim = strchr (name, '/');
494       size_t len = delim ? (size_t)(delim - name - 1) : strlen (name);
495       LLIST_FOREACH (tree, n)
496         {
497           struct treenode* node = (struct treenode*)n;
498
499           if (strncmp (name, node->name, len))
500             continue;
501           else
502             {
503               if (delim)
504                 {
505                   name = delim + 1;
506                   tree = webgit_tree_parse (&node->subtree, node->sha1);
507                   break;
508                 }
509               else
510                 worktree_file = node->sha1;
511             }
512         }
513     } while (delim);
514
515   /* head */
516   unsigned char sha1_head[20];
517   get_sha1 (repo->query->head, sha1_head);
518   struct commit* head_commit = lookup_commit (sha1_head);
519   parse_commit (head_commit);
520
521   llist headtree;
522   llist_init (&headtree);
523   webgit_tree_parse (&headtree, head_commit->tree->object.sha1);
524
525   name = path;
526   tree = &headtree;
527   unsigned char* headtree_file = NULL;
528
529   do
530     {
531       delim = strchr (name, '/');
532       size_t len = delim ? (size_t)(delim - name - 1) : strlen (name);
533       LLIST_FOREACH (tree, n)
534         {
535           struct treenode* node = (struct treenode*)n;
536           if (strncmp (name, node->name, len))
537             continue;
538           else
539             {
540               if (delim)
541                 {
542                   name = delim + 1;
543                   tree = webgit_tree_parse (&node->subtree, node->sha1);
544                   break;
545                 }
546               else
547                 headtree_file = node->sha1;
548             }
549         }
550     } while (delim);
551
552   int ret = 0;
553
554   if (worktree_file && headtree_file && !hashcmp(worktree_file, headtree_file))
555     ret = 1;
556
557   LLIST_WHILE_HEAD (&worktree, node)
558     treenode_free ((struct treenode*) node);
559   LLIST_WHILE_HEAD (&headtree, node)
560     treenode_free ((struct treenode*) node);
561
562   return ret;
563 }
564
565 /*
566 //      Local Variables:
567 //      mode: C
568 //      c-file-style: "gnu"
569 //      indent-tabs-mode: nil
570 //      End:
571 */