Fixed staging
[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 (!query->current_repo->worktree || 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   TRACE (webgit);
196   if (!repo->query->name || !repo->query->email)
197     return 0;
198
199   char* cbuf = cwa_buffer_provide (256 /* just a good guess */ +
200                                    2*(strlen (repo->query->name) + strlen (repo->query->email)) +
201                                    (message ? strlen (message) : 0));
202
203   sprintf (cbuf,
204            "tree %s\n"
205            "parent %s\n"
206            "author %s <%s> %lu +0000\n"
207            "committer %s <%s> %lu +0000\n\n"
208            "%s",
209            sha1_to_hex (sha1_tree),
210            sha1_to_hex (sha1_parent),
211            repo->query->name,
212            repo->query->email,
213            repo->query->now,
214            repo->query->name,
215            repo->query->email,
216            repo->query->now,
217            message
218            );
219
220   unsigned char sha1[20];
221
222   if (!!write_sha1_file (cbuf, strlen (cbuf), commit_type, sha1))
223     die("error writing worktree %s\n", sha1_to_hex (sha1));
224
225   struct ref_lock * lock = lock_ref_sha1 (ref, NULL);
226
227   lock->force_write = 1;
228   return write_ref_sha1 (lock, sha1, NULL);
229 }
230
231
232 void
233 webgit_worktree_setup (struct webgit_repo_info* repo)
234 {
235   TRACE (webgit);
236   char* worktree = cwa_buffer_provide (PATH_MAX);
237   unsigned char sha1_tree[20];
238   snprintf (worktree, PATH_MAX, "refs/worktrees/%s", repo->query->head);
239
240   unsigned char sha1_head[20];
241   get_sha1 (repo->query->head, sha1_head);
242
243   if (!read_ref (worktree, sha1_tree))
244     {
245       /* found worktree */
246       struct commit* commit = lookup_commit_reference (sha1_tree);
247       parse_commit (commit);
248
249       struct commit* treehead = pop_commit (&commit->parents);
250       parse_commit (treehead);
251
252       if (!!hashcmp (sha1_head, treehead->object.sha1))
253         {
254           /* head diverted from worktree */
255           if (!strncmp (webgit_object_commit_tree_parse (commit),
256                         webgit_object_commit_tree_parse (treehead), 40))
257             {
258               /* workree unchanged, forward it */
259               struct commit* newhead = lookup_commit_reference (sha1_head);
260               parse_commit (newhead);
261
262               get_sha1_hex (webgit_object_commit_tree_parse (newhead), sha1_tree);
263               webgit_worktree_create (repo, worktree+5, sha1_tree, sha1_head, "");
264             }
265           else
266             /* else uhm */
267             die ("TODO: uh ohh, head diverted from worktree, needs merge strategy");
268         }
269     }
270   else
271     {
272       /* create brand new worktree */
273       struct commit* parent = lookup_commit_reference (sha1_head);
274       parse_commit (parent);
275
276       get_sha1_hex (webgit_object_commit_tree_parse (parent), sha1_tree);
277       webgit_worktree_create (repo, worktree+5, sha1_tree, sha1_head, "");
278     }
279   repo->worktree = cwa_strndup (sha1_to_hex (sha1_tree), 40);
280 }
281
282
283 void
284 webgit_worktree_removefile (LList tree, const char* path)
285 {
286   TRACE (webgit);
287   const char* delim  = strchr (path, '/');
288   size_t len = delim ? (size_t)(delim - path - 1) : strlen (path);
289
290   LLIST_FOREACH (tree, n)
291     {
292       struct treenode* node = (struct treenode*)n;
293
294       if (!strncmp (node->name, path, len))
295         {
296           if (!delim)
297             /* remove blob */
298             treenode_free (node);
299           else
300             {
301               /*enter subtree*/
302               if (llist_is_empty (&node->subtree))
303                 webgit_tree_parse (&node->subtree, node->sha1);
304               node->dirty = 1;
305               webgit_worktree_removefile (&node->subtree, delim+1);
306             }
307           break;
308         }
309     }
310 }
311
312
313 void
314 webgit_worktree_addfile (LList tree, const char* path, unsigned char* sha1)
315 {
316   TRACE (webgit);
317   const char* delim  = strchr (path, '/');
318   size_t len = delim ? (size_t)(delim - path - 1) : strlen (path);
319
320   LLIST_FOREACH (tree, n)
321     {
322       struct treenode* node = (struct treenode*)n;
323
324       if (!delim)
325         {
326           treenode_addnew (tree, 0100644 /*TODO: ui for changing mode*/, sha1, path);
327           break;
328         }
329       else if (!strncmp (node->name, path, len))
330         {
331           /*enter subtree*/
332           if (llist_is_empty (&node->subtree))
333             webgit_tree_parse (&node->subtree, node->sha1);
334           node->dirty = 1;
335           webgit_worktree_addfile (&node->subtree, delim+1, sha1);
336           break;
337         }
338     }
339 }
340
341
342 static int
343 webgit_worktree_isunix (LList tree, const char* path)
344 {
345   TRACE (webgit);
346   const char* delim  = strchr (path, '/');
347   size_t len = delim ? (size_t)(delim - path - 1) : strlen (path);
348
349   LLIST_FOREACH (tree, n)
350     {
351       struct treenode* node = (struct treenode*)n;
352
353       if (!strncmp (node->name, path, len))
354         {
355           if (!delim)
356             {
357               size_t size;
358               char* buf = read_object_with_reference (node->sha1, "blob", &size, NULL);
359               if (!memchr(buf, 0, size) && !memchr (buf, '\r', size))
360                 return 1;
361               return 0;
362             }
363           else
364             {
365               /*enter subtree*/
366               if (llist_is_empty (&node->subtree))
367                 webgit_tree_parse (&node->subtree, node->sha1);
368               return webgit_worktree_isunix (&node->subtree, delim+1);
369             }
370         }
371     }
372   NOTREACHED;
373   return -1;
374 }
375
376
377 void
378 webgit_worktree_fixup (LList tree, unsigned char* sha1)
379 {
380   TRACE (webgit);
381   size_t size = 0;
382   LLIST_FOREACH (tree, n)
383     {
384       struct treenode* node = (struct treenode*)n;
385       if (node->dirty)
386         {
387           if (llist_is_empty (&node->subtree))
388             {
389               /* remove subtree when it became empty */
390               n = llist_prev (n);
391               treenode_free (node);
392             }
393           else
394             {
395               /* otherwise recurse into subtree */
396               webgit_worktree_fixup (&node->subtree, node->sha1);
397             }
398         }
399       /* and count the size by the way */
400       size += 32 + node->len;
401     }
402
403   tree_sort (tree);
404
405   /* write it */
406   struct strbuf buf;
407
408   strbuf_init (&buf, size);
409
410   LLIST_FOREACH (tree, n)
411     {
412       struct treenode* node = (struct treenode*)n;
413
414       strbuf_addf (&buf, "%o %s%c", node->mode, node->name, '\0');
415       strbuf_add (&buf, node->sha1, 20);
416     }
417
418   write_sha1_file (buf.buf, buf.len, tree_type, sha1);
419
420   strbuf_release (&buf);
421 }
422
423 void
424 webgit_worktree_stage (struct webgit_repo_info* repo)
425 {
426   TRACE (webgit);
427   struct webgit_query* query = repo->query;
428
429   /* add blob/file to repo */
430   char* data;
431   size_t data_size;
432
433   if (query->file_size)
434     {
435       data = query->file;
436       data_size = query->file_size;
437     }
438   else if (query->blob_size)
439     {
440       data = query->blob;
441       data_size = query->blob_size;
442     }
443   else
444     /* no data */
445     return;
446
447   /* get original tree */
448   char* worktree = cwa_buffer_provide (PATH_MAX);
449   unsigned char sha1_tree[20];
450   snprintf (worktree, PATH_MAX, "refs/worktrees/%s", repo->query->head);
451   read_ref (worktree, sha1_tree);
452
453   struct commit* commit = lookup_commit_reference (sha1_tree);
454   commit->object.parsed = 0;
455   parse_commit (commit);
456
457   struct commit* treehead = pop_commit (&commit->parents);
458
459   parse_commit (treehead);
460   unsigned char sha1_parent[20];
461   hashcpy (sha1_parent, treehead->object.sha1);
462
463   llist tree;
464   llist_init (&tree);
465   webgit_tree_parse (&tree, commit->tree->object.sha1);
466
467   /* convert blob to to unix lineendings if the old blob used them too */
468   if (webgit_worktree_isunix (&tree, query->oldpath))
469     data_size = cwa_httptounix (data, data_size);
470
471   /* save new blob */
472   unsigned char sha1_blob[20];
473
474   if (!!write_sha1_file (data, data_size, "blob", sha1_blob))
475     die("error writing blob %s\n", sha1_to_hex (sha1_blob));
476
477   /* modify tree */
478   if (!strcmp (query->mode, "rename"))
479     {
480       webgit_worktree_removefile (&tree, query->oldpath);
481       webgit_worktree_addfile (&tree, query->path, sha1_blob);
482     }
483   else if (!strcmp (query->mode, "copy"))
484     {
485       webgit_worktree_addfile (&tree, query->path, sha1_blob);
486     }
487   else if (!strcmp (query->mode, "delete"))
488     {
489       webgit_worktree_removefile (&tree, query->oldpath);
490     }
491   else
492     die ("unknown mode");
493
494   webgit_worktree_fixup (&tree, sha1_tree);
495
496   LLIST_WHILE_HEAD (&tree, node)
497     treenode_free ((struct treenode*) node);
498
499   char* oldcomment = strstr (commit->buffer, "\n\n");
500   size_t comment_len = 0;
501   if (oldcomment)
502     {
503       oldcomment += 2;
504       comment_len = strlen (oldcomment);
505     }
506
507   if (query->comment)
508       comment_len += (sizeof (" * \n") + strlen (query->comment) - 1);
509
510   char* newcomment;
511
512   if (query->comment)
513     {
514       newcomment = cwa_buffer_provide (comment_len+1);
515       sprintf (newcomment, "%s * %s\n%c", oldcomment ? oldcomment : "", query->comment, '\0');
516     }
517   else
518     newcomment = oldcomment;
519
520   webgit_worktree_create (repo,
521                           worktree + 5,
522                           sha1_tree,
523                           sha1_parent,
524                           newcomment
525                           );
526 }
527
528
529
530 int
531 webgit_worktree_blob_eq (struct webgit_repo_info* repo, const char* path)
532 {
533   TRACE (webgit);
534   /* find out if the current file in head and worktree are the same */
535
536   /* worktree */
537   unsigned char sha1_tree[20];
538   get_sha1 (repo->worktree, sha1_tree);
539
540   llist worktree;
541   llist_init (&worktree);
542   webgit_tree_parse (&worktree, sha1_tree);
543
544   const char* delim;
545   const char* name = path;
546   LList tree = &worktree;
547
548   unsigned char* worktree_file = NULL;
549
550   do
551     {
552       delim = strchr (name, '/');
553       size_t len = delim ? (size_t)(delim - name - 1) : strlen (name);
554       LLIST_FOREACH (tree, n)
555         {
556           struct treenode* node = (struct treenode*)n;
557
558           if (strncmp (name, node->name, len))
559             continue;
560           else
561             {
562               if (delim)
563                 {
564                   name = delim + 1;
565                   tree = webgit_tree_parse (&node->subtree, node->sha1);
566                   break;
567                 }
568               else
569                 worktree_file = node->sha1;
570             }
571         }
572     } while (delim);
573
574   /* head */
575   unsigned char sha1_head[20];
576   get_sha1 (repo->query->head, sha1_head);
577   struct commit* head_commit = lookup_commit (sha1_head);
578   parse_commit (head_commit);
579
580   llist headtree;
581   llist_init (&headtree);
582   webgit_tree_parse (&headtree, head_commit->tree->object.sha1);
583
584   name = path;
585   tree = &headtree;
586   unsigned char* headtree_file = NULL;
587
588   do
589     {
590       delim = strchr (name, '/');
591       size_t len = delim ? (size_t)(delim - name - 1) : strlen (name);
592       LLIST_FOREACH (tree, n)
593         {
594           struct treenode* node = (struct treenode*)n;
595           if (strncmp (name, node->name, len))
596             continue;
597           else
598             {
599               if (delim)
600                 {
601                   name = delim + 1;
602                   tree = webgit_tree_parse (&node->subtree, node->sha1);
603                   break;
604                 }
605               else
606                 headtree_file = node->sha1;
607             }
608         }
609     } while (delim);
610
611   int ret = 0;
612
613   if (worktree_file && headtree_file && !hashcmp(worktree_file, headtree_file))
614     ret = 1;
615
616   LLIST_WHILE_HEAD (&worktree, node)
617     treenode_free ((struct treenode*) node);
618   LLIST_WHILE_HEAD (&headtree, node)
619     treenode_free ((struct treenode*) node);
620
621   return ret;
622 }
623
624 /*
625 //      Local Variables:
626 //      mode: C
627 //      c-file-style: "gnu"
628 //      indent-tabs-mode: nil
629 //      End:
630 */