3b8062865d6fd1570d750447195c73eb0cfcc294
[webgit] / src / object.c
1 /*
2     cehtehs git web frontend
3
4   Copyright (C)
5     2007,               Christian Thaeter <ct@pipapo.org>
6
7   This program is free software; you can redistribute it and/or
8   modify it under the terms of the GNU General Public License as
9   published by the Free Software Foundation; either version 2 of the
10   License, or (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 General Public License for more details.
16
17   You should have received a copy of the GNU General Public License
18   along with this program; if not, write to the Free Software
19   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
20 */
21
22 #include "object.h"
23 #include "repo.h"
24 #include "tag.h"
25
26 #define SHA1_HEADER <openssl/sha.h>
27 #include "git/tag.h"
28
29 Html
30 webgit_object_link (struct webgit_query* query,
31                     const char* repo,
32                     int repo_len,
33                     const char* object,
34                     int object_len,
35                     const char* path,
36                     const char* action,
37                     Html text)
38 {
39   return html (
40                html_tag ("a",
41                          html_attr ("href", html (
42                                                   html_fmt ("%s?repo=%.*s&object=%.*s",
43                                                             query->request->script_name,
44                                                             repo_len, repo,
45                                                             object_len, object),
46                                                   path ? html_fmt ("&path=%s", path) : html (),
47                                                   action ? html_fmt ("&action=%s", action) : html ()
48                                                   )
49                                     )
50                          ),
51                text
52                );
53 }
54
55
56
57 char*
58 webgit_commit_tree_parse (struct commit* commit)
59 {
60   char* tree = strstr (commit->buffer, "tree ");
61   if (!tree)
62     return NULL;
63   return tree + 5;
64 }
65
66 time_t
67 webgit_commit_author_date_parse (struct commit* commit, struct tm* tm)
68 {
69   struct tm tmp;
70   if (!tm)
71     tm = &tmp;
72
73   char* author = strstr (commit->buffer, "author ");
74   if (!author)
75     return (time_t)-1;
76
77   char* beg = strchr (author, '>');
78   if (!beg)
79     return (time_t)-1;
80
81   if (!strptime (beg + 2, "%s %Z", tm))
82     return (time_t)-1;
83
84   return mktime (tm);
85 }
86
87 Html
88 webgit_commit_author_name_parse (struct commit* commit)
89 {
90   char* author = strstr (commit->buffer, "author ");
91   if (!author)
92     return NULL;
93
94   char* end = strchr (author, '<');
95   if (!end)
96     return NULL;
97
98   return html_fmt ("%.*s", end-author-8, author+7);
99 }
100
101 Html
102 webgit_commit_author_email_parse (struct commit* commit)
103 {
104   char* author = strstr (commit->buffer, "author ");
105   if (!author)
106     return NULL;
107
108   char* beg = strchr (author, '<');
109   if (!beg)
110     return NULL;
111
112   char* end = strchr (beg, '>');
113   if (!end)
114     return NULL;
115
116
117   return html_fmt ("%.*s", end-beg-1, beg+1);
118 }
119
120 time_t
121 webgit_commit_committer_date_parse (struct commit* commit, struct tm* tm)
122 {
123   struct tm tmp;
124   if (!tm)
125     tm = &tmp;
126
127   char* committer = strstr (commit->buffer, "committer ");
128   if (!committer)
129     return (time_t)-1;
130
131   char* beg = strchr (committer, '>');
132   if (!beg)
133     return (time_t)-1;
134
135   if (!strptime (beg + 2, "%s %Z", tm))
136     return (time_t)-1;
137
138   return mktime (tm);
139 }
140
141 Html
142 webgit_commit_committer_name_parse (struct commit* commit)
143 {
144   char* committer = strstr (commit->buffer, "committer ");
145   if (!committer)
146     return NULL;
147
148   char* end = strchr (committer, '<');
149   if (!end)
150     return NULL;
151
152   return html_fmt ("%.*s", end-committer-11, committer+10);
153 }
154
155 Html
156 webgit_commit_committer_email_parse (struct commit* commit)
157 {
158   char* committer = strstr (commit->buffer, "committer ");
159   if (!committer)
160     return NULL;
161
162   char* beg = strchr (committer, '<');
163   if (!beg)
164     return NULL;
165
166   char* end = strchr (beg, '>');
167   if (!end)
168     return NULL;
169
170
171   return html_fmt ("%.*s", end-beg-1, beg+1);
172 }
173
174 Html
175 webgit_commit_header_parse (struct commit* commit)
176 {
177   char* header = strstr (commit->buffer, "\n\n");
178   if (!header)
179     return NULL;
180
181   char* end = strchr (header+2, '\n');
182   if (!end)
183     end = header + strlen (header+2);
184   else
185     --end;
186
187   return html_fmt ("%.*s", end-header, header+2);
188 }
189
190 Html
191 webgit_commit_message_parse (struct commit* commit)
192 {
193   char* header = strstr (commit->buffer, "\n\n");
194   if (!header)
195     return NULL;
196
197   header = strchr (header+2, '\n');
198   if (!header)
199     return html ();
200   else
201     ++header;
202
203   return html_fmt ("%.*s", strlen (header), header);
204 }
205
206
207
208
209 /*
210   Display commits
211 */
212
213 static Html
214 webgit_object_commit_menu_action (struct webgit_repo_info* repo, unsigned char* sha1, void* buf, unsigned long size)
215 {
216   (void) repo;
217   (void) sha1;
218   (void) buf;
219   (void) size;
220
221   return html ("TODO: commit-object sidebar");
222 }
223
224 static Html
225 webgit_object_commit_content_action (struct webgit_repo_info* repo, unsigned char* sha1, void* buf, unsigned long size)
226 {
227 /*
228   TODO pass commit objects instead buf/size, use parsers from above
229 */
230   (void) sha1;
231
232   Html tree = html_list ();
233   Html parents = html_list ();
234   Html author_text = html_list ();
235   Html author = html_list ();
236   Html committer = html_list ();
237
238   const char* author_beg = NULL;
239   const char* author_end = NULL;
240
241   const char* i = buf;
242   while (i && (void*)i < buf+size)
243     {
244       if (*i == '\n')
245         {
246           while (*i == '\n')
247             ++i;
248           break; /* message */
249         }
250       if (!strncmp (i, "tree ", 5))
251         {
252           html_list_append (tree,
253                             "Tree: ",
254                             webgit_object_link (repo->query,
255                                                 repo->name, strlen (repo->name),
256                                                 i+5, 40,
257                                                 NULL,
258                                                 NULL,
259                                                 html_fmt ("%.40s", i+5))
260                             );
261           if ((i = strchr (i, '\n')))
262             ++i;
263           continue;
264         }
265       else if (!strncmp (i, "parent ", 7))
266         {
267           html_list_append (parents, html (
268                                            "Parent: ",
269                                            webgit_object_link (repo->query,
270                                                                repo->name, strlen (repo->name),
271                                                                i+7, 40,
272                                                                NULL,
273                                                                NULL,
274                                                                html_fmt ("%.40s", i+7))
275                                            )
276                             );
277           if ((i = strchr (i, '\n')))
278             ++i;
279           continue;
280         }
281       else if (!strncmp (i, "author ", 7))
282         {
283           char* email_beg = strchr (i, '<');
284           char* email_end = strchr (email_beg, '>');
285
286           author_beg = i+7;
287           author_end = strchr (author_beg, '\n');
288
289           Html name = html_strndup (i+7, email_beg - i - 8);
290           Html email = html_strndup (email_beg + 1, email_end - email_beg - 1);
291
292           struct tm tm;
293           strptime (email_end + 2, "%s %Z", &tm);
294           char pretty_date[256];
295           strftime (pretty_date, 255, "%c", &tm);
296
297           html_list_append (author_text, "Author");
298
299           html_list_append (author, html (
300                                           html ( author_text ), /*BUG: libcwa bug, must be wraped in html()*/
301                                           webgit_email_link (name, email),
302                                           html_strndup (pretty_date, 255)
303                                           )
304                             );
305           if ((i = strchr (i, '\n')))
306             ++i;
307           continue;
308         }
309       else if (!strncmp (i, "committer ", 10))
310         {
311           char* email_beg = strchr (i, '<');
312           char* email_end = strchr (email_beg, '>');
313
314           if (author_beg && author_end && strncmp (i + 10, author_beg, author_end - author_beg))
315             {
316               Html name = html_strndup (i+10, email_beg - i - 11);
317               Html email = html_strndup (email_beg + 1, email_end - email_beg - 1);
318
319               struct tm tm;
320               strptime (email_end + 2, "%s %Z", &tm);
321               char pretty_date[256];
322               strftime (pretty_date, 255, "%c", &tm);
323
324               html_list_append (committer, html ("Committer: ",
325                                                  webgit_email_link (name, email),
326                                                  html_strndup (pretty_date, 255)
327                                                  )
328                                 );
329             }
330           else
331             html_list_append (author_text, " and Committer");
332
333           if ((i = strchr (i, '\n')))
334             ++i;
335           continue;
336         }
337       ++i;
338     }
339
340   html_list_append (author_text, ": ");
341
342   Html headline = html_list ();
343   Html message = html_list ();
344
345   if (i)
346     {
347       const char* message_beg = strchr (i, '\n');
348
349       if (message_beg)
350         {
351           html_list_append (headline,
352                             html_strndup (i, message_beg - i)
353                             );
354
355           while (*message_beg == '\n')
356             ++message_beg;
357
358           html_list_append (message,
359                             html_strndup (message_beg, SIZE_MAX)
360                             );
361         }
362       else
363         {
364           html_list_append (headline,
365                             html_strndup (i, SIZE_MAX)
366                             );
367           html_list_append (message,
368                             html_strndup (i, SIZE_MAX)
369                             );
370         }
371     }
372
373   /* TODO: diffstat */
374
375
376   return html (
377                html (html_tag ("h3"), headline),
378                html (html_tag ("div"), tree), html_nl (),
379                html (html_tag ("div"), parents), html_nl (),
380                html (html_tag ("div"), author), html_nl (),
381                html (html_tag ("div"), committer), html_nl (),
382                html (html_tag ("pre"), message)
383                );
384 }
385
386 Html
387 webgit_object_commit_action (struct webgit_repo_info* repo, unsigned char* sha1)
388 {
389   void* buf;
390   unsigned long size;
391
392   buf = read_object_with_reference (sha1, "commit", &size, NULL);
393
394   return html(
395               html(html_tag("div"), webgit_object_commit_menu_action (repo, sha1, buf, size)), html_nl (),
396               html(html_tag("div"), webgit_object_commit_content_action (repo, sha1, buf, size)), html_nl ()
397               );
398 }
399
400
401 /*
402   Display trees
403 */
404
405 static Html
406 webgit_object_tree_menu_action (struct webgit_repo_info* repo, unsigned char* sha1, struct tree *tree)
407 {
408   (void) repo;
409   (void) sha1;
410   (void) tree;
411   return html ("TODO: tree-object sidebar");
412 }
413
414 static const char*
415 pretty_mode (unsigned mode)
416 {
417   if (S_ISGITLINK (mode))
418     return "m---------";
419   else if (S_ISDIR (mode & S_IFMT))
420     return "drwxr-xr-x";
421   else if (S_ISLNK (mode))
422     return "lrwxrwxrwx";
423   else if (S_ISREG (mode))
424     {
425       if (mode & S_IXUSR)
426         return "-rwxr-xr-x";
427       else
428         return "-rw-r--r--";
429     }
430   else
431     return "----------";
432 }
433
434 /* callback has no user-data pointer! */
435 static struct webgit_query* query_in_flight = NULL;
436 static Html tree_in_flight = NULL;
437
438 static int
439 webgit_html_tree (const unsigned char *sha1, const char *base, int baselen,
440                   const char *name, unsigned mode, int stage)
441 {
442   (void) stage;
443   char pathname[PATH_MAX];
444
445   snprintf (pathname, PATH_MAX-1, "%.*s%s%s", baselen, base, baselen ? "/": "", name);
446
447   if (S_ISGITLINK(mode))
448     {
449       html_list_append (tree_in_flight, html (
450                                               html (
451                                                     html_tag ("tr"),
452                                                     html (html_tag ("td"), pretty_mode (mode)),
453                                                     html (html_tag ("td"),
454                                                           webgit_repo_link (query_in_flight,
455                                                                             query_in_flight->repo,
456                                                                             strlen (query_in_flight->repo),
457                                                                             name, strlen (pathname),
458                                                                             sha1_to_hex (sha1), 40,
459                                                                             "tree",
460                                                                             html_strndup (pathname, SIZE_MAX))
461                                                           ),
462                                                     html (html_tag ("td"), "history summary")
463                                                     ),
464                                               html_nl ()
465                                               )
466                         );
467     }
468   else if (S_ISDIR(mode))
469     {
470       html_list_append (tree_in_flight, html (
471                                               html (html_tag ("tr"),
472                                                     html (html_tag ("td"), pretty_mode (mode)),
473                                                     html (html_tag ("td"),
474                                                           webgit_object_link (query_in_flight,
475                                                                               query_in_flight->repo,
476                                                                               strlen (query_in_flight->repo),
477                                                                               sha1_to_hex (sha1), 40,
478                                                                               pathname,
479                                                                               NULL,
480                                                                               html_strndup (name, SIZE_MAX))
481                                                           ),
482                                                   html (html_tag ("td"), "history snap")
483                                                   ),
484                                              html_nl ()
485                                              )
486                         );
487     }
488   else
489     {
490       html_list_append (tree_in_flight, html (
491                                               html (html_tag ("tr"),
492                                                     html (html_tag ("td"), pretty_mode (mode)),
493                                                     html (html_tag ("td"),
494                                                           webgit_object_link (query_in_flight,
495                                                                               query_in_flight->repo,
496                                                                               strlen (query_in_flight->repo),
497                                                                               sha1_to_hex (sha1), 40,
498                                                                               pathname,
499                                                                               NULL,
500                                                                               html_strndup (name, SIZE_MAX))
501                                                           ),
502                                                     html (html_tag ("td"),
503                                                           "history ",
504                                                           webgit_object_link (query_in_flight,
505                                                                               query_in_flight->repo,
506                                                                               strlen (query_in_flight->repo),
507                                                                               sha1_to_hex (sha1), 40,
508                                                                               pathname,
509                                                                               "raw",
510                                                                               html ("raw")),
511                                                           " edit")
512                                                     ),
513                                               html_nl()
514                                               )
515                         );
516     }
517
518   return 0;
519 }
520
521
522 static Html
523 webgit_object_tree_content_action (struct webgit_repo_info* repo, unsigned char* sha1, struct tree *tree)
524 {
525   (void) sha1;
526   query_in_flight = repo->query;
527   tree_in_flight = html_list ();
528
529   read_tree_recursive (tree, repo->query->path,
530                        repo->query->path ? strlen (repo->query->path) : 0, 0, NULL, webgit_html_tree);
531
532   return html (html_tag ("table"), tree_in_flight);
533 }
534
535 Html
536 webgit_object_tree_action (struct webgit_repo_info* repo, unsigned char* sha1)
537 {
538   struct tree *tree;
539
540   tree = parse_tree_indirect (sha1);
541   if (!tree)
542     die("not a tree object");
543
544   return html(
545               html(html_tag("div"), webgit_object_tree_menu_action (repo, sha1, tree)), html_nl (),
546               html(html_tag("div"), webgit_object_tree_content_action (repo, sha1, tree)), html_nl ()
547               );
548 }
549
550
551 /*
552   Display blobs
553 */
554 static Html
555 webgit_object_blob_menu_action (struct webgit_repo_info* repo, unsigned char* sha1, void* buf, unsigned long size)
556 {
557   (void) repo;
558   (void) sha1;
559   (void) buf;
560   (void) size;
561   return html ("TODO: blob-object sidebar");
562 }
563
564 static Html
565 webgit_object_blob_content_action (struct webgit_repo_info* repo, unsigned char* sha1, void* buf, unsigned long size)
566 {
567   (void) sha1;
568
569   if (!memchr(buf, 0, size>8192 ? 8192 : size))
570     {
571       return html (html_tag ("pre"), html_strndup (buf, size));
572     }
573   else
574     {
575       struct webgit_query* query = repo->query;
576       const char* mimetype = webgit_mimetype (query->path);
577
578       Html ret;
579
580       if (mimetype && !strncmp(mimetype, "image/", 6))
581         {
582           /* inline image */
583           ret = html (
584                       html_tag ("img",
585                                 html_attr ("src",
586                                            html_fmt ("%s?repo=%s&action=raw&object=%s&path=%s",
587                                                      query->request->script_name,
588                                                      query->repo, query->object, query->path)
589                                            ),
590                                 html_attr ("alt", query->path ? query->path : query->object)
591                                 )
592                       );
593         }
594       else
595         {
596           /* link to raw data */
597           ret = html ("binary blob");
598         }
599       free ((char*)mimetype);
600
601       return ret;
602     }
603 }
604
605
606 Html
607 webgit_object_blob_action (struct webgit_repo_info* repo, unsigned char* sha1)
608 {
609   void* buf;
610   unsigned long size;
611
612   buf = read_object_with_reference (sha1, "blob", &size, NULL);
613
614   return html(
615               html(html_tag("div"), webgit_object_blob_menu_action (repo, sha1, buf, size)), html_nl (),
616               html(html_tag("div"), webgit_object_blob_content_action (repo, sha1, buf, size)), html_nl ()
617               );
618 }
619
620
621 /*
622   Display Tags
623 */
624 static Html
625 webgit_object_tag_menu_action (struct webgit_repo_info* repo, struct tag* tag, void *buffer, unsigned long size)
626 {
627   (void) repo;
628   (void) tag;
629   (void) buffer;
630   (void) size;
631
632   return html ("TODO: tag-object sidebar");
633 }
634
635 static Html
636 webgit_object_tag_content_action (struct webgit_repo_info* repo, struct tag* tag, void* buf, unsigned long size)
637 {
638   /*
639     NAME HEADER
640
641     object | tree
642
643     tagger: Christian ThaeterWed Jan 16 15:55:27 2008
644
645     message
646   */
647
648   struct object* derefed = deref_tag ((struct object*)tag, NULL, 0);
649
650   char type[16];
651
652   webgit_tag_type_parse (type, buf, size);
653
654   char* sha1hex = sha1_to_hex (derefed->sha1);
655
656   struct tm tm;
657   webgit_tag_date_parse (buf, size, &tm);
658   char pretty_date[256];
659   strftime (pretty_date, 255, "%c", &tm);
660
661   Html ret;
662
663   ret= html (
664              html (
665                    html_tag ("h1"),
666                    html_strndup (tag->tag, SIZE_MAX),
667                    " - ",
668                    webgit_tag_header_parse (buf, size)
669                    ),
670              html_fmt ("Type '%s': ", type),
671              webgit_object_link (repo->query,
672                                  repo->name, strlen(repo->name),
673                                  sha1hex, 40,
674                                  NULL,
675                                  NULL,
676                                  html (sha1hex)),
677              !strcmp (type, "commit") ?
678              html (
679                    " ",
680                    webgit_object_link (repo->query,
681                                        repo->name, strlen(repo->name),
682                                        sha1hex, 40,
683                                        NULL,
684                                        "tree",
685                                        html ("Tree"))
686                    ) : html (),
687              html (html_tag ("br")),
688              "Tagger: ",
689              webgit_email_link (
690                                 webgit_tag_name_parse (buf, size),
691                                 webgit_tag_email_parse (buf, size)
692                                 ),
693              html_strndup (pretty_date, 255),
694              html (html_tag ("br")),
695              html (html_tag ("pre"),
696                    webgit_tag_message_parse (buf, size)
697                    )
698              );
699
700
701
702
703
704   return ret;
705 }
706
707 Html
708 webgit_object_tag_action (struct webgit_repo_info* repo, unsigned char* sha1)
709 {
710   struct tag* tag = lookup_tag (sha1);
711   enum object_type type;
712   void *buffer;
713   unsigned long size;
714
715   buffer = read_sha1_file (tag->object.sha1, &type, &size);
716
717   if (!tag->object.parsed)
718     parse_tag_buffer (tag, buffer, size);
719
720   parse_tag_buffer (tag, buffer, size);
721
722   return html(
723               html(html_tag("div"), webgit_object_tag_menu_action (repo, tag, buffer, size)), html_nl (),
724               html(html_tag("div"), webgit_object_tag_content_action (repo, tag, buffer, size)), html_nl ()
725               );
726 }
727
728