b410f4cf5170149bba9686999dd2d72911271bc1
[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   free (self);
75 }
76
77
78 static int
79 treenode_cmp (LList la, LList lb)
80 {
81   struct treenode* a = LLIST_TO_STRUCTP (la, struct treenode, node);
82   struct treenode* b = LLIST_TO_STRUCTP (lb, struct treenode, node);
83
84   return base_name_compare (a->name, a->len, a->mode, b->name, b->len, b->mode);
85 }
86
87
88 LList
89 tree_sort (LList tree)
90 {
91   llist_sort (tree, treenode_cmp);
92  return tree;
93 }
94
95 static LList tree_in_flight = NULL;
96
97 LList
98 webgit_tree_parse (LList root, const unsigned char* sha1);
99
100
101 static int
102 webgit_treenode_cb (const unsigned char *sha1, const char *base, int baselen,
103                     const char *name, unsigned mode, int stage)
104 {
105   (void) base;
106   (void) baselen;
107   (void) stage;
108
109   treenode_addnew (tree_in_flight, mode, sha1, name);
110   return 0;
111 }
112
113
114 LList
115 webgit_tree_parse (LList root, const unsigned char* sha1)
116 {
117   struct tree *tree;
118
119   tree = parse_tree_indirect (sha1);
120   if (!tree)
121     die("not a tree object");
122
123   LList tree_back = tree_in_flight;
124   tree_in_flight = root;
125
126   read_tree_recursive (tree, "", 0, 0, NULL, webgit_treenode_cb);
127   tree_in_flight = tree_back;
128
129   return root;
130 }
131
132 static int
133 webgit_worktree_create (struct webgit_repo_info* repo,
134                         const char* ref,
135                         unsigned char* sha1_tree,
136                         unsigned char* sha1_parent,
137                         const char* message
138                         )
139 {
140   char* cbuf = cwa_buffer_provide (256 /* just a good guess */ +
141                                    2*(strlen (repo->query->name) + strlen (repo->query->email)) +
142                                    strlen (message));
143   sprintf (cbuf,
144            "tree %s\n"
145            "parent %s\n"
146            "author %s <%s> %lu +0000\n"
147            "committer %s <%s> %lu +0000\n\n"
148            "%s",
149            sha1_to_hex (sha1_tree),
150            sha1_to_hex (sha1_parent),
151            repo->query->name,
152            repo->query->email,
153            repo->query->now,
154            repo->query->name,
155            repo->query->email,
156            repo->query->now,
157            message
158            );
159
160   unsigned char sha1[20];
161
162   if (!!write_sha1_file (cbuf, strlen (cbuf), commit_type, sha1))
163     die("error writing worktree %s\n", sha1_to_hex (sha1));
164
165   struct ref_lock * lock = lock_ref_sha1 (ref, NULL);
166
167   lock->force_write = 1;
168   return write_ref_sha1 (lock, sha1, NULL);
169 }
170
171
172 void
173 webgit_worktree_setup (struct webgit_repo_info* repo)
174 {
175   char* worktree = cwa_buffer_provide (PATH_MAX);
176   unsigned char sha1_tree[20];
177   snprintf (worktree, PATH_MAX, "refs/worktrees/%s", repo->query->head);
178
179   unsigned char sha1_head[20];
180   get_sha1 (repo->query->head, sha1_head);
181
182   if (!read_ref (worktree, sha1_tree))
183     {
184       /* found worktree */
185       struct commit* commit = lookup_commit_reference (sha1_tree);
186       parse_commit (commit);
187
188       struct commit* treehead = pop_commit (&commit->parents);
189       parse_commit (treehead);
190
191       if (!!hashcmp (sha1_head, treehead->object.sha1))
192         {
193           /* head diverted from worktree */
194           if (!strncmp (webgit_object_commit_tree_parse (commit),
195                         webgit_object_commit_tree_parse (treehead), 40))
196             {
197               /* workree unchanged, forward it */
198               struct commit* newhead = lookup_commit_reference (sha1_head);
199               parse_commit (newhead);
200
201               get_sha1_hex (webgit_object_commit_tree_parse (newhead), sha1_tree);
202               webgit_worktree_create (repo, worktree+5, sha1_tree, sha1_head, "");
203             }
204           else
205             /* else uhm */
206             die ("TODO: uh ohh, head diverted from worktree, needs merge strategy");
207         }
208     }
209   else
210     {
211       /* create brand new worktree */
212       struct commit* parent = lookup_commit_reference (sha1_head);
213       parse_commit (parent);
214
215       get_sha1_hex (webgit_object_commit_tree_parse (parent), sha1_tree);
216       webgit_worktree_create (repo, worktree+5, sha1_tree, sha1_head, "");
217     }
218   repo->worktree = cwa_strndup (sha1_to_hex (sha1_tree), 40);
219 }
220
221
222 void
223 webgit_worktree_removefile (LList tree, const char* path)
224 {
225   const char* delim  = strchr (path, '/');
226   size_t len = delim ? (size_t)(delim - path - 1) : strlen (path);
227
228   LLIST_FOREACH (tree, n)
229     {
230       struct treenode* node = (struct treenode*)n;
231
232       if (!strncmp (node->name, path, len))
233         {
234           if (!delim)
235             /* remove blob */
236             treenode_free (node);
237           else
238             {
239               /*enter subtree*/
240               if (llist_is_empty (&node->subtree))
241                 webgit_tree_parse (&node->subtree, node->sha1);
242               node->dirty = 1;
243               webgit_worktree_removefile (&node->subtree, delim+1);
244             }
245           break;
246         }
247     }
248 }
249
250
251 void
252 webgit_worktree_addfile (LList tree, const char* path, unsigned char* sha1)
253 {
254   const char* delim  = strchr (path, '/');
255   size_t len = delim ? (size_t)(delim - path - 1) : strlen (path);
256
257   LLIST_FOREACH (tree, n)
258     {
259       struct treenode* node = (struct treenode*)n;
260
261       if (!delim)
262         {
263           treenode_addnew (tree, 0100644 /*TODO: ui for changing mode*/, sha1, path);
264           break;
265         }
266       else if (!strncmp (node->name, path, len))
267         {
268           /*enter subtree*/
269           if (llist_is_empty (&node->subtree))
270             webgit_tree_parse (&node->subtree, node->sha1);
271           node->dirty = 1;
272           webgit_worktree_addfile (&node->subtree, delim+1, sha1);
273           break;
274         }
275     }
276 }
277
278
279 void
280 webgit_worktree_fixup (LList tree, unsigned char* sha1)
281 {
282   size_t size = 0;
283   LLIST_FOREACH (tree, n)
284     {
285       struct treenode* node = (struct treenode*)n;
286       if (node->dirty)
287         {
288           if (llist_is_empty (&node->subtree))
289             {
290               /* remove subtree when it became empty */
291               n = llist_prev (n);
292               treenode_free (node);
293             }
294           else
295             {
296               /* otherwise recurse into subtree */
297               webgit_worktree_fixup (&node->subtree, node->sha1);
298             }
299         }
300       /* and count the size by the way */
301       size += 32 + node->len;
302     }
303
304   tree_sort (tree);
305
306   /* write it */
307   struct strbuf buf;
308
309   strbuf_init (&buf, size);
310
311   LLIST_FOREACH (tree, n)
312     {
313       struct treenode* node = (struct treenode*)n;
314
315       strbuf_addf (&buf, "%o %s%c", node->mode, node->name, '\0');
316       strbuf_add (&buf, node->sha1, 20);
317     }
318
319   write_sha1_file (buf.buf, buf.len, tree_type, sha1);
320
321   strbuf_release (&buf);
322
323   webgit_warn ("written tree %s", sha1_to_hex(sha1));
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   webgit_warn ("parent %p parsed %d\n", treehead, treehead->object.parsed);
365
366   parse_commit (treehead);
367   unsigned char sha1_parent[20];
368   hashcpy (sha1_parent, treehead->object.sha1);
369
370   llist tree;
371   llist_init (&tree);
372   webgit_tree_parse (&tree, commit->tree->object.sha1);
373
374   /* modify tree */
375   if (!strcmp (query->mode, "rename"))
376     {
377       webgit_worktree_removefile (&tree, query->oldpath);
378       webgit_worktree_addfile (&tree, query->path, sha1_blob);
379     }
380   else if (!strcmp (query->mode, "copy"))
381     {
382       die ("copy");
383       webgit_worktree_addfile (&tree, query->path, sha1_blob);
384     }
385   else if (!strcmp (query->mode, "delete"))
386     {
387       die ("delete");
388       webgit_worktree_removefile (&tree, query->oldpath);
389     }
390   else
391     die ("unknown mode");
392
393   webgit_worktree_fixup (&tree, sha1_tree);
394
395   char* oldcomment = strstr (commit->buffer, "\n\n");
396   size_t comment_len = 0;
397   if (oldcomment)
398     comment_len = strlen (oldcomment+2);
399
400   if (query->comment)
401       comment_len += (sizeof (" * \n") + strlen (query->comment) - 1);
402
403   char* newcomment = cwa_buffer_provide (comment_len+1);
404
405   sprintf (newcomment, "%s * %s\n%c", oldcomment ? oldcomment+2 : "", query->comment, '\0');
406
407   webgit_worktree_create (repo,
408                           worktree + 5,
409                           sha1_tree,
410                           sha1_parent,
411                           newcomment
412                           );
413 }
414
415
416 #if 0
417 int
418 webgit_worktree_same_blob (struct webgit_repo_info* repo)
419 {
420
421   return 0;
422 }
423 #endif
424
425 /*
426 //      Local Variables:
427 //      mode: C
428 //      c-file-style: "gnu"
429 //      indent-tabs-mode: nil
430 //      End:
431 */