57e9b9384662024cf8591cdc434c773bedda57f6
[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
25
26 Html
27 webgit_object_link (struct webgit_query* query,
28                     const char* repo,
29                     int repo_len,
30                     const char* object,
31                     int object_len,
32                     const char* path,
33                     const char* action,
34                     Html text)
35 {
36   return html (
37                html_tag ("a",
38                          html_attr ("href", html (
39                                                   html_fmt ("%s?repo=%.*s&object=%.*s",
40                                                             query->request->script_name,
41                                                             repo_len, repo,
42                                                             object_len, object),
43                                                   path ? html_fmt ("&path=%s", path) : html (),
44                                                   action ? html_fmt ("&action=%s", action) : html ()
45                                                   )
46                                     )
47                          ),
48                text
49                );
50 }
51
52
53
54 char*
55 webgit_commit_tree_parse (struct commit* commit)
56 {
57   char* tree = strstr (commit->buffer, "tree ");
58   if (!tree)
59     return NULL;
60   return tree + 5;
61 }
62
63 time_t
64 webgit_commit_author_date_parse (struct commit* commit, struct tm* tm)
65 {
66   struct tm tmp;
67   if (!tm)
68     tm = &tmp;
69
70   char* author = strstr (commit->buffer, "author ");
71   if (!author)
72     return (time_t)-1;
73
74   char* beg = strchr (author, '>');
75   if (!beg)
76     return (time_t)-1;
77
78   if (!strptime (beg + 2, "%s %Z", tm))
79     return (time_t)-1;
80
81   return mktime (tm);
82 }
83
84 Html
85 webgit_commit_author_name_parse (struct commit* commit)
86 {
87   char* author = strstr (commit->buffer, "author ");
88   if (!author)
89     return NULL;
90
91   char* end = strchr (author, '<');
92   if (!end)
93     return NULL;
94
95   return html_fmt ("%.*s", end-author-8, author+7);
96 }
97
98 Html
99 webgit_commit_author_email_parse (struct commit* commit)
100 {
101   char* author = strstr (commit->buffer, "author ");
102   if (!author)
103     return NULL;
104
105   char* beg = strchr (author, '<');
106   if (!beg)
107     return NULL;
108
109   char* end = strchr (beg, '>');
110   if (!end)
111     return NULL;
112
113
114   return html_fmt ("%.*s", end-beg-1, beg+1);
115 }
116
117 time_t
118 webgit_commit_committer_date_parse (struct commit* commit, struct tm* tm)
119 {
120   struct tm tmp;
121   if (!tm)
122     tm = &tmp;
123
124   char* committer = strstr (commit->buffer, "committer ");
125   if (!committer)
126     return (time_t)-1;
127
128   char* beg = strchr (committer, '>');
129   if (!beg)
130     return (time_t)-1;
131
132   if (!strptime (beg + 2, "%s %Z", tm))
133     return (time_t)-1;
134
135   return mktime (tm);
136 }
137
138 Html
139 webgit_commit_committer_name_parse (struct commit* commit)
140 {
141   char* committer = strstr (commit->buffer, "committer ");
142   if (!committer)
143     return NULL;
144
145   char* end = strchr (committer, '<');
146   if (!end)
147     return NULL;
148
149   return html_fmt ("%.*s", end-committer-11, committer+10);
150 }
151
152 Html
153 webgit_commit_committer_email_parse (struct commit* commit)
154 {
155   char* committer = strstr (commit->buffer, "committer ");
156   if (!committer)
157     return NULL;
158
159   char* beg = strchr (committer, '<');
160   if (!beg)
161     return NULL;
162
163   char* end = strchr (beg, '>');
164   if (!end)
165     return NULL;
166
167
168   return html_fmt ("%.*s", end-beg-1, beg+1);
169 }
170
171 Html
172 webgit_commit_header_parse (struct commit* commit)
173 {
174   char* header = strstr (commit->buffer, "\n\n");
175   if (!header)
176     return NULL;
177
178   char* end = strchr (header+2, '\n');
179   if (!end)
180     end = header + strlen (header+2);
181   else
182     --end;
183
184   return html_fmt ("%.*s", end-header, header+2);
185 }
186
187 Html
188 webgit_commit_message_parse (struct commit* commit)
189 {
190   char* header = strstr (commit->buffer, "\n\n");
191   if (!header)
192     return NULL;
193
194   header = strchr (header+2, '\n');
195   if (!header)
196     return html ();
197   else
198     ++header;
199
200   return html_fmt ("%.*s", strlen (header), header);
201 }
202
203
204
205
206 /*
207   Display commits
208 */
209
210 static Html
211 webgit_object_commit_menu_action (struct webgit_query* query, unsigned char* sha1, void* buf, unsigned long size)
212 {
213   (void) query;
214   (void) sha1;
215   (void) buf;
216   (void) size;
217
218   return html ("TODO: commit-object sidebar");
219 }
220
221 static Html
222 webgit_object_commit_content_action (struct webgit_query* query, unsigned char* sha1, void* buf, unsigned long size)
223 {
224 /*
225   TODO pass commit objects instead buf/size, use parsers from above
226 */
227   (void) sha1;
228
229   Html tree = html_list ();
230   Html parents = html_list ();
231   Html author_text = html_list ();
232   Html author = html_list ();
233   Html committer = html_list ();
234
235   const char* author_beg = NULL;
236   const char* author_end = NULL;
237
238   const char* i = buf;
239   while (i && (void*)i < buf+size)
240     {
241       if (*i == '\n')
242         {
243           while (*i == '\n')
244             ++i;
245           break; /* message */
246         }
247       if (!strncmp (i, "tree ", 5))
248         {
249           html_list_append (tree,
250                             "Tree: ",
251                             webgit_object_link (query,
252                                                 query->repo, strlen (query->repo),
253                                                 i+5, 40,
254                                                 NULL,
255                                                 NULL,
256                                                 html_fmt ("%.40s", i+5))
257                             );
258           if ((i = strchr (i, '\n')))
259             ++i;
260           continue;
261         }
262       else if (!strncmp (i, "parent ", 7))
263         {
264           html_list_append (parents, html (
265                                            "Parent: ",
266                                            webgit_object_link (query,
267                                                                query->repo, strlen (query->repo),
268                                                                i+7, 40,
269                                                                NULL,
270                                                                NULL,
271                                                                html_fmt ("%.40s", i+7))
272                                            )
273                             );
274           if ((i = strchr (i, '\n')))
275             ++i;
276           continue;
277         }
278       else if (!strncmp (i, "author ", 7))
279         {
280           char* email_beg = strchr (i, '<');
281           char* email_end = strchr (email_beg, '>');
282
283           author_beg = i+7;
284           author_end = strchr (author_beg, '\n');
285
286           Html name = html_strndup (i+7, email_beg - i - 8);
287           Html email = html_strndup (email_beg + 1, email_end - email_beg - 1);
288
289           struct tm tm;
290           strptime (email_end + 2, "%s %Z", &tm);
291           char pretty_date[256];
292           strftime (pretty_date, 255, "%c", &tm);
293
294           html_list_append (author_text, "Author");
295
296           html_list_append (author, html (
297                                           html ( author_text ), /*BUG: libcwa bug, must be wraped in html()*/
298                                           webgit_email_link (name, email),
299                                           html_strndup (pretty_date, 255)
300                                           )
301                             );
302           if ((i = strchr (i, '\n')))
303             ++i;
304           continue;
305         }
306       else if (!strncmp (i, "committer ", 10))
307         {
308           char* email_beg = strchr (i, '<');
309           char* email_end = strchr (email_beg, '>');
310
311           if (author_beg && author_end && strncmp (i + 10, author_beg, author_end - author_beg))
312             {
313               Html name = html_strndup (i+10, email_beg - i - 11);
314               Html email = html_strndup (email_beg + 1, email_end - email_beg - 1);
315
316               struct tm tm;
317               strptime (email_end + 2, "%s %Z", &tm);
318               char pretty_date[256];
319               strftime (pretty_date, 255, "%c", &tm);
320
321               html_list_append (committer, html ("Committer: ",
322                                                  webgit_email_link (name, email),
323                                                  html_strndup (pretty_date, 255)
324                                                  )
325                                 );
326             }
327           else
328             html_list_append (author_text, " and Committer");
329
330           if ((i = strchr (i, '\n')))
331             ++i;
332           continue;
333         }
334       ++i;
335     }
336
337   html_list_append (author_text, ": ");
338
339   Html headline = html_list ();
340   Html message = html_list ();
341
342   if (i)
343     {
344       const char* message_beg = strchr (i, '\n');
345
346       if (message_beg)
347         {
348           html_list_append (headline,
349                             html_strndup (i, message_beg - i)
350                             );
351
352           while (*message_beg == '\n')
353             ++message_beg;
354
355           html_list_append (message,
356                             html_strndup (message_beg, SIZE_MAX)
357                             );
358         }
359       else
360         {
361           html_list_append (headline,
362                             html_strndup (i, SIZE_MAX)
363                             );
364           html_list_append (message,
365                             html_strndup (i, SIZE_MAX)
366                             );
367         }
368     }
369
370   /* TODO: diffstat */
371
372
373   return html (
374                html (html_tag ("h3"), headline),
375                html (html_tag ("div"), tree), html_nl (),
376                html (html_tag ("div"), parents), html_nl (),
377                html (html_tag ("div"), author), html_nl (),
378                html (html_tag ("div"), committer), html_nl (),
379                html (html_tag ("pre"), message)
380                );
381 }
382
383 Html
384 webgit_object_commit_action (struct webgit_query* query, unsigned char* sha1)
385 {
386   void* buf;
387   unsigned long size;
388
389   buf = read_object_with_reference (sha1, "commit", &size, NULL);
390
391   return html(
392               html(html_tag("div"), webgit_object_commit_menu_action (query, sha1, buf, size)), html_nl (),
393               html(html_tag("div"), webgit_object_commit_content_action (query, sha1, buf, size)), html_nl ()
394               );
395 }
396
397
398 /*
399   Display trees
400 */
401
402 static Html
403 webgit_object_tree_menu_action (struct webgit_query* query, unsigned char* sha1, struct tree *tree)
404 {
405   (void) query;
406   (void) sha1;
407   (void) tree;
408   return html ("TODO: tree-object sidebar");
409 }
410
411 static const char*
412 pretty_mode (unsigned mode)
413 {
414   if (S_ISGITLINK (mode))
415     return "m---------";
416   else if (S_ISDIR (mode & S_IFMT))
417     return "drwxr-xr-x";
418   else if (S_ISLNK (mode))
419     return "lrwxrwxrwx";
420   else if (S_ISREG (mode))
421     {
422       if (mode & S_IXUSR)
423         return "-rwxr-xr-x";
424       else
425         return "-rw-r--r--";
426     }
427   else
428     return "----------";
429 }
430
431 /* callback has no user-data pointer! */
432 static struct webgit_query* query_in_flight = NULL;
433 static Html tree_in_flight = NULL;
434
435 static int
436 webgit_html_tree (const unsigned char *sha1, const char *base, int baselen,
437                   const char *name, unsigned mode, int stage)
438 {
439   (void) stage;
440   char pathname[PATH_MAX];
441
442   snprintf (pathname, PATH_MAX-1, "%.*s%s%s", baselen, base, baselen ? "/": "", name);
443
444   if (S_ISGITLINK(mode))
445     {
446       html_list_append (tree_in_flight, html (
447                                               html (
448                                                     html_tag ("tr"),
449                                                     html (html_tag ("td"), pretty_mode (mode)),
450                                                     html (html_tag ("td"),
451                                                           webgit_repo_link (query_in_flight,
452                                                                             query_in_flight->repo,
453                                                                             strlen (query_in_flight->repo),
454                                                                             name, strlen (pathname),
455                                                                             sha1_to_hex (sha1), 40,
456                                                                             "tree",
457                                                                             html_strndup (pathname, SIZE_MAX))
458                                                           ),
459                                                     html (html_tag ("td"), "history summary")
460                                                     ),
461                                               html_nl ()
462                                               )
463                         );
464     }
465   else if (S_ISDIR(mode))
466     {
467       html_list_append (tree_in_flight, html (
468                                               html (html_tag ("tr"),
469                                                     html (html_tag ("td"), pretty_mode (mode)),
470                                                     html (html_tag ("td"),
471                                                           webgit_object_link (query_in_flight,
472                                                                               query_in_flight->repo,
473                                                                               strlen (query_in_flight->repo),
474                                                                               sha1_to_hex (sha1), 40,
475                                                                               pathname,
476                                                                               NULL,
477                                                                               html_strndup (name, SIZE_MAX))
478                                                           ),
479                                                   html (html_tag ("td"), "history snap")
480                                                   ),
481                                              html_nl ()
482                                              )
483                         );
484     }
485   else
486     {
487       html_list_append (tree_in_flight, html (
488                                               html (html_tag ("tr"),
489                                                     html (html_tag ("td"), pretty_mode (mode)),
490                                                     html (html_tag ("td"),
491                                                           webgit_object_link (query_in_flight,
492                                                                               query_in_flight->repo,
493                                                                               strlen (query_in_flight->repo),
494                                                                               sha1_to_hex (sha1), 40,
495                                                                               pathname,
496                                                                               NULL,
497                                                                               html_strndup (name, SIZE_MAX))
498                                                           ),
499                                                     html (html_tag ("td"),
500                                                           "history ",
501                                                           webgit_object_link (query_in_flight,
502                                                                               query_in_flight->repo,
503                                                                               strlen (query_in_flight->repo),
504                                                                               sha1_to_hex (sha1), 40,
505                                                                               pathname,
506                                                                               "raw",
507                                                                               html ("raw")),
508                                                           " edit")
509                                                     ),
510                                               html_nl()
511                                               )
512                         );
513     }
514
515   return 0;
516 }
517
518
519 static Html
520 webgit_object_tree_content_action (struct webgit_query* query, unsigned char* sha1, struct tree *tree)
521 {
522   (void) sha1;
523   query_in_flight = query;
524   tree_in_flight = html_list ();
525
526   read_tree_recursive (tree, query->path, query->path ? strlen (query->path): 0, 0, NULL, webgit_html_tree);
527
528   return html (html_tag ("table"), tree_in_flight);
529 }
530
531 Html
532 webgit_object_tree_action (struct webgit_query* query, unsigned char* sha1)
533 {
534   struct tree *tree;
535
536   tree = parse_tree_indirect (sha1);
537   if (!tree)
538     die("not a tree object");
539
540   return html(
541               html(html_tag("div"), webgit_object_tree_menu_action (query, sha1, tree)), html_nl (),
542               html(html_tag("div"), webgit_object_tree_content_action (query, sha1, tree)), html_nl ()
543               );
544 }
545
546
547 /*
548   Display blobs
549 */
550
551 static Html
552 webgit_object_blob_menu_action (struct webgit_query* query, unsigned char* sha1, void* buf, unsigned long size)
553 {
554   (void) query;
555   (void) sha1;
556   (void) buf;
557   (void) size;
558   return html ("TODO: blob-object sidebar");
559 }
560
561 static Html
562 webgit_object_blob_content_action (struct webgit_query* query, unsigned char* sha1, void* buf, unsigned long size)
563 {
564   (void) sha1;
565
566   if (!memchr(buf, 0, size>8192 ? 8192 : size))
567     {
568       return html (html_tag ("pre"), html_strndup (buf, size));
569     }
570   else
571     {
572       const char* mimetype = webgit_mimetype (query->path);
573
574       Html ret;
575
576       if (mimetype && !strncmp(mimetype, "image/", 6))
577         {
578           /* inline image */
579           ret = html (
580                       html_tag ("img",
581                                 html_attr ("src",
582                                            html_fmt ("%s?repo=%s&action=raw&object=%s&path=%s",
583                                                             query->request->script_name,
584                                                             query->repo, query->object, query->path)
585                                            ),
586                                 html_attr ("alt", query->path? query->path : query->object)
587                                 )
588                       );
589         }
590       else
591         {
592           /* link to raw data */
593           ret = html ("binary blob");
594         }
595       free ((char*)mimetype);
596
597       return ret;
598     }
599 }
600
601
602 Html
603 webgit_object_blob_action (struct webgit_query* query, unsigned char* sha1)
604 {
605   void* buf;
606   unsigned long size;
607
608   buf = read_object_with_reference (sha1, "blob", &size, NULL);
609
610   return html(
611               html(html_tag("div"), webgit_object_blob_menu_action (query, sha1, buf, size)), html_nl (),
612               html(html_tag("div"), webgit_object_blob_content_action (query, sha1, buf, size)), html_nl ()
613               );
614 }
615
616
617
618
619 /*pretty printer for sourcecode*/
620 //Html
621 //webgit_object_blob_dispatch (struct webgit_query* query, const char *sha1);