Switch to edit worktree after staging an edit
[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 worktree */
448   char* worktree = cwa_buffer_provide (PATH_MAX);
449   unsigned char sha1_tree[20];
450   snprintf (worktree, PATH_MAX, "refs/%s%s",
451             strncmp ("worktrees/",
452                      repo->query->head, 10) ? "worktrees/" : "",
453             repo->query->head);
454   read_ref (worktree, sha1_tree);
455
456   struct commit* commit = lookup_commit_reference (sha1_tree);
457   commit->object.parsed = 0;
458   parse_commit (commit);
459
460   struct commit* treehead = pop_commit (&commit->parents);
461
462   parse_commit (treehead);
463   unsigned char sha1_parent[20];
464   hashcpy (sha1_parent, treehead->object.sha1);
465
466   llist tree;
467   llist_init (&tree);
468   webgit_tree_parse (&tree, commit->tree->object.sha1);
469
470   /* convert blob to to unix lineendings if the old blob used them too */
471   if (webgit_worktree_isunix (&tree, query->oldpath))
472     data_size = cwa_httptounix (data, data_size);
473
474   /* save new blob */
475   unsigned char sha1_blob[20];
476
477   if (!!write_sha1_file (data, data_size, "blob", sha1_blob))
478     die("error writing blob %s\n", sha1_to_hex (sha1_blob));
479
480   /* modify tree */
481   if (!strcmp (query->mode, "rename"))
482     {
483       webgit_worktree_removefile (&tree, query->oldpath);
484       webgit_worktree_addfile (&tree, query->path, sha1_blob);
485     }
486   else if (!strcmp (query->mode, "copy"))
487     {
488       webgit_worktree_addfile (&tree, query->path, sha1_blob);
489     }
490   else if (!strcmp (query->mode, "delete"))
491     {
492       webgit_worktree_removefile (&tree, query->oldpath);
493     }
494   else
495     die ("unknown mode");
496
497   webgit_worktree_fixup (&tree, sha1_tree);
498
499   LLIST_WHILE_HEAD (&tree, node)
500     treenode_free ((struct treenode*) node);
501
502   char* oldcomment = strstr (commit->buffer, "\n\n");
503   size_t comment_len = 0;
504   if (oldcomment)
505     {
506       oldcomment += 2;
507       comment_len = strlen (oldcomment);
508     }
509
510   if (query->comment)
511       comment_len += (sizeof (" * \n") + strlen (query->comment) - 1);
512
513   char* newcomment;
514
515   if (query->comment)
516     {
517       newcomment = cwa_buffer_provide (comment_len+1);
518       sprintf (newcomment, "%s * %s\n%c", oldcomment ? oldcomment : "", query->comment, '\0');
519     }
520   else
521     newcomment = oldcomment;
522
523   webgit_worktree_create (repo,
524                           worktree + 5,
525                           sha1_tree,
526                           sha1_parent,
527                           newcomment
528                           );
529 }
530
531
532
533 int
534 webgit_worktree_blob_eq (struct webgit_repo_info* repo, const char* path)
535 {
536   TRACE (webgit);
537   /* find out if the current file in head and worktree are the same */
538
539   /* worktree */
540   unsigned char sha1_tree[20];
541   get_sha1 (repo->worktree, sha1_tree);
542
543   llist worktree;
544   llist_init (&worktree);
545   webgit_tree_parse (&worktree, sha1_tree);
546
547   const char* delim;
548   const char* name = path;
549   LList tree = &worktree;
550
551   unsigned char* worktree_file = NULL;
552
553   do
554     {
555       delim = strchr (name, '/');
556       size_t len = delim ? (size_t)(delim - name - 1) : strlen (name);
557       LLIST_FOREACH (tree, n)
558         {
559           struct treenode* node = (struct treenode*)n;
560
561           if (strncmp (name, node->name, len))
562             continue;
563           else
564             {
565               if (delim)
566                 {
567                   name = delim + 1;
568                   tree = webgit_tree_parse (&node->subtree, node->sha1);
569                   break;
570                 }
571               else
572                 worktree_file = node->sha1;
573             }
574         }
575     } while (delim);
576
577   /* head */
578   unsigned char sha1_head[20];
579   get_sha1 (repo->query->head, sha1_head);
580   struct commit* head_commit = lookup_commit (sha1_head);
581   parse_commit (head_commit);
582
583   llist headtree;
584   llist_init (&headtree);
585   webgit_tree_parse (&headtree, head_commit->tree->object.sha1);
586
587   name = path;
588   tree = &headtree;
589   unsigned char* headtree_file = NULL;
590
591   do
592     {
593       delim = strchr (name, '/');
594       size_t len = delim ? (size_t)(delim - name - 1) : strlen (name);
595       LLIST_FOREACH (tree, n)
596         {
597           struct treenode* node = (struct treenode*)n;
598           if (strncmp (name, node->name, len))
599             continue;
600           else
601             {
602               if (delim)
603                 {
604                   name = delim + 1;
605                   tree = webgit_tree_parse (&node->subtree, node->sha1);
606                   break;
607                 }
608               else
609                 headtree_file = node->sha1;
610             }
611         }
612     } while (delim);
613
614   int ret = 0;
615
616   if (worktree_file && headtree_file && !hashcmp(worktree_file, headtree_file))
617     ret = 1;
618
619   LLIST_WHILE_HEAD (&worktree, node)
620     treenode_free ((struct treenode*) node);
621   LLIST_WHILE_HEAD (&headtree, node)
622     treenode_free ((struct treenode*) node);
623
624   return ret;
625 }
626
627 /*
628 //      Local Variables:
629 //      mode: C
630 //      c-file-style: "gnu"
631 //      indent-tabs-mode: nil
632 //      End:
633 */