]> gitweb.ps.run Git - ps-cgit/blob - ui-shared.c
Merge branch 'lh/cleanup'
[ps-cgit] / ui-shared.c
1 /* ui-shared.c: common web output functions
2  *
3  * Copyright (C) 2006 Lars Hjemli
4  *
5  * Licensed under GNU General Public License v2
6  *   (see COPYING for full license text)
7  */
8
9 #include "cgit.h"
10 #include "html.h"
11
12 const char cgit_doctype[] =
13 "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\"\n"
14 "  \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n";
15
16 static char *http_date(time_t t)
17 {
18         static char day[][4] =
19                 {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"};
20         static char month[][4] =
21                 {"Jan", "Feb", "Mar", "Apr", "May", "Jun",
22                  "Jul", "Aug", "Sep", "Oct", "Now", "Dec"};
23         struct tm *tm = gmtime(&t);
24         return fmt("%s, %02d %s %04d %02d:%02d:%02d GMT", day[tm->tm_wday],
25                    tm->tm_mday, month[tm->tm_mon], 1900+tm->tm_year,
26                    tm->tm_hour, tm->tm_min, tm->tm_sec);
27 }
28
29 void cgit_print_error(char *msg)
30 {
31         html("<div class='error'>");
32         html_txt(msg);
33         html("</div>\n");
34 }
35
36 char *cgit_rooturl()
37 {
38         if (ctx.cfg.virtual_root)
39                 return fmt("%s/", ctx.cfg.virtual_root);
40         else
41                 return ctx.cfg.script_name;
42 }
43
44 char *cgit_repourl(const char *reponame)
45 {
46         if (ctx.cfg.virtual_root) {
47                 return fmt("%s/%s/", ctx.cfg.virtual_root, reponame);
48         } else {
49                 return fmt("?r=%s", reponame);
50         }
51 }
52
53 char *cgit_fileurl(const char *reponame, const char *pagename,
54                    const char *filename, const char *query)
55 {
56         char *tmp;
57         char *delim;
58
59         if (ctx.cfg.virtual_root) {
60                 tmp = fmt("%s/%s/%s/%s", ctx.cfg.virtual_root, reponame,
61                           pagename, (filename ? filename:""));
62                 delim = "?";
63         } else {
64                 tmp = fmt("?url=%s/%s/%s", reponame, pagename,
65                           (filename ? filename : ""));
66                 delim = "&";
67         }
68         if (query)
69                 tmp = fmt("%s%s%s", tmp, delim, query);
70         return tmp;
71 }
72
73 char *cgit_pageurl(const char *reponame, const char *pagename,
74                    const char *query)
75 {
76         return cgit_fileurl(reponame,pagename,0,query);
77 }
78
79 const char *cgit_repobasename(const char *reponame)
80 {
81         /* I assume we don't need to store more than one repo basename */
82         static char rvbuf[1024];
83         int p;
84         const char *rv;
85         strncpy(rvbuf,reponame,sizeof(rvbuf));
86         if(rvbuf[sizeof(rvbuf)-1])
87                 die("cgit_repobasename: truncated repository name '%s'", reponame);
88         p = strlen(rvbuf)-1;
89         /* strip trailing slashes */
90         while(p && rvbuf[p]=='/') rvbuf[p--]=0;
91         /* strip trailing .git */
92         if(p>=3 && !strncmp(&rvbuf[p-3],".git",4)) {
93                 p -= 3; rvbuf[p--] = 0;
94         }
95         /* strip more trailing slashes if any */
96         while( p && rvbuf[p]=='/') rvbuf[p--]=0;
97         /* find last slash in the remaining string */
98         rv = strrchr(rvbuf,'/');
99         if(rv)
100                 return ++rv;
101         return rvbuf;
102 }
103
104 char *cgit_currurl()
105 {
106         if (!ctx.cfg.virtual_root)
107                 return ctx.cfg.script_name;
108         else if (ctx.qry.page)
109                 return fmt("%s/%s/%s/", ctx.cfg.virtual_root, ctx.qry.repo, ctx.qry.page);
110         else if (ctx.qry.repo)
111                 return fmt("%s/%s/", ctx.cfg.virtual_root, ctx.qry.repo);
112         else
113                 return fmt("%s/", ctx.cfg.virtual_root);
114 }
115
116 static char *repolink(char *title, char *class, char *page, char *head,
117                       char *path)
118 {
119         char *delim = "?";
120
121         html("<a");
122         if (title) {
123                 html(" title='");
124                 html_attr(title);
125                 html("'");
126         }
127         if (class) {
128                 html(" class='");
129                 html_attr(class);
130                 html("'");
131         }
132         html(" href='");
133         if (ctx.cfg.virtual_root) {
134                 html_attr(ctx.cfg.virtual_root);
135                 if (ctx.cfg.virtual_root[strlen(ctx.cfg.virtual_root) - 1] != '/')
136                         html("/");
137                 html_attr(ctx.repo->url);
138                 if (ctx.repo->url[strlen(ctx.repo->url) - 1] != '/')
139                         html("/");
140                 if (page) {
141                         html(page);
142                         html("/");
143                         if (path)
144                                 html_attr(path);
145                 }
146         } else {
147                 html(ctx.cfg.script_name);
148                 html("?url=");
149                 html_attr(ctx.repo->url);
150                 if (ctx.repo->url[strlen(ctx.repo->url) - 1] != '/')
151                         html("/");
152                 if (page) {
153                         html(page);
154                         html("/");
155                         if (path)
156                                 html_attr(path);
157                 }
158                 delim = "&amp;";
159         }
160         if (head && strcmp(head, ctx.repo->defbranch)) {
161                 html(delim);
162                 html("h=");
163                 html_attr(head);
164                 delim = "&amp;";
165         }
166         return fmt("%s", delim);
167 }
168
169 static void reporevlink(char *page, char *name, char *title, char *class,
170                         char *head, char *rev, char *path)
171 {
172         char *delim;
173
174         delim = repolink(title, class, page, head, path);
175         if (rev && strcmp(rev, ctx.qry.head)) {
176                 html(delim);
177                 html("id=");
178                 html_attr(rev);
179         }
180         html("'>");
181         html_txt(name);
182         html("</a>");
183 }
184
185 void cgit_tree_link(char *name, char *title, char *class, char *head,
186                     char *rev, char *path)
187 {
188         reporevlink("tree", name, title, class, head, rev, path);
189 }
190
191 void cgit_log_link(char *name, char *title, char *class, char *head,
192                    char *rev, char *path, int ofs, char *grep, char *pattern)
193 {
194         char *delim;
195
196         delim = repolink(title, class, "log", head, path);
197         if (rev && strcmp(rev, ctx.qry.head)) {
198                 html(delim);
199                 html("id=");
200                 html_attr(rev);
201                 delim = "&";
202         }
203         if (grep && pattern) {
204                 html(delim);
205                 html("qt=");
206                 html_attr(grep);
207                 delim = "&";
208                 html(delim);
209                 html("q=");
210                 html_attr(pattern);
211         }
212         if (ofs > 0) {
213                 html(delim);
214                 html("ofs=");
215                 htmlf("%d", ofs);
216         }
217         html("'>");
218         html_txt(name);
219         html("</a>");
220 }
221
222 void cgit_commit_link(char *name, char *title, char *class, char *head,
223                       char *rev)
224 {
225         if (strlen(name) > ctx.cfg.max_msg_len && ctx.cfg.max_msg_len >= 15) {
226                 name[ctx.cfg.max_msg_len] = '\0';
227                 name[ctx.cfg.max_msg_len - 1] = '.';
228                 name[ctx.cfg.max_msg_len - 2] = '.';
229                 name[ctx.cfg.max_msg_len - 3] = '.';
230         }
231         reporevlink("commit", name, title, class, head, rev, NULL);
232 }
233
234 void cgit_refs_link(char *name, char *title, char *class, char *head,
235                     char *rev, char *path)
236 {
237         reporevlink("refs", name, title, class, head, rev, path);
238 }
239
240 void cgit_snapshot_link(char *name, char *title, char *class, char *head,
241                         char *rev, char *archivename)
242 {
243         reporevlink("snapshot", name, title, class, head, rev, archivename);
244 }
245
246 void cgit_diff_link(char *name, char *title, char *class, char *head,
247                     char *new_rev, char *old_rev, char *path)
248 {
249         char *delim;
250
251         delim = repolink(title, class, "diff", head, path);
252         if (new_rev && strcmp(new_rev, ctx.qry.head)) {
253                 html(delim);
254                 html("id=");
255                 html_attr(new_rev);
256                 delim = "&amp;";
257         }
258         if (old_rev) {
259                 html(delim);
260                 html("id2=");
261                 html_attr(old_rev);
262         }
263         html("'>");
264         html_txt(name);
265         html("</a>");
266 }
267
268 void cgit_patch_link(char *name, char *title, char *class, char *head,
269                      char *rev)
270 {
271         reporevlink("patch", name, title, class, head, rev, NULL);
272 }
273
274 void cgit_object_link(struct object *obj)
275 {
276         char *page, *arg, *url;
277
278         if (obj->type == OBJ_COMMIT) {
279                 cgit_commit_link(fmt("commit %s", sha1_to_hex(obj->sha1)), NULL, NULL,
280                                  ctx.qry.head, sha1_to_hex(obj->sha1));
281                 return;
282         } else if (obj->type == OBJ_TREE) {
283                 page = "tree";
284                 arg = "id";
285         } else if (obj->type == OBJ_TAG) {
286                 page = "tag";
287                 arg = "id";
288         } else {
289                 page = "blob";
290                 arg = "id";
291         }
292
293         url = cgit_pageurl(ctx.qry.repo, page,
294                            fmt("%s=%s", arg, sha1_to_hex(obj->sha1)));
295         html_link_open(url, NULL, NULL);
296         htmlf("%s %s", typename(obj->type),
297               sha1_to_hex(obj->sha1));
298         html_link_close();
299 }
300
301 void cgit_print_date(time_t secs, char *format)
302 {
303         char buf[64];
304         struct tm *time;
305
306         if (!secs)
307                 return;
308         time = gmtime(&secs);
309         strftime(buf, sizeof(buf)-1, format, time);
310         html_txt(buf);
311 }
312
313 void cgit_print_age(time_t t, time_t max_relative, char *format)
314 {
315         time_t now, secs;
316
317         if (!t)
318                 return;
319         time(&now);
320         secs = now - t;
321
322         if (secs > max_relative && max_relative >= 0) {
323                 cgit_print_date(t, format);
324                 return;
325         }
326
327         if (secs < TM_HOUR * 2) {
328                 htmlf("<span class='age-mins'>%.0f min.</span>",
329                       secs * 1.0 / TM_MIN);
330                 return;
331         }
332         if (secs < TM_DAY * 2) {
333                 htmlf("<span class='age-hours'>%.0f hours</span>",
334                       secs * 1.0 / TM_HOUR);
335                 return;
336         }
337         if (secs < TM_WEEK * 2) {
338                 htmlf("<span class='age-days'>%.0f days</span>",
339                       secs * 1.0 / TM_DAY);
340                 return;
341         }
342         if (secs < TM_MONTH * 2) {
343                 htmlf("<span class='age-weeks'>%.0f weeks</span>",
344                       secs * 1.0 / TM_WEEK);
345                 return;
346         }
347         if (secs < TM_YEAR * 2) {
348                 htmlf("<span class='age-months'>%.0f months</span>",
349                       secs * 1.0 / TM_MONTH);
350                 return;
351         }
352         htmlf("<span class='age-years'>%.0f years</span>",
353               secs * 1.0 / TM_YEAR);
354 }
355
356 void cgit_print_http_headers(struct cgit_context *ctx)
357 {
358         if (ctx->page.mimetype && ctx->page.charset)
359                 htmlf("Content-Type: %s; charset=%s\n", ctx->page.mimetype,
360                       ctx->page.charset);
361         else if (ctx->page.mimetype)
362                 htmlf("Content-Type: %s\n", ctx->page.mimetype);
363         if (ctx->page.filename)
364                 htmlf("Content-Disposition: inline; filename=\"%s\"\n",
365                       ctx->page.filename);
366         htmlf("Last-Modified: %s\n", http_date(ctx->page.modified));
367         htmlf("Expires: %s\n", http_date(ctx->page.expires));
368         html("\n");
369 }
370
371 void cgit_print_docstart(struct cgit_context *ctx)
372 {
373         html(cgit_doctype);
374         html("<html xmlns='http://www.w3.org/1999/xhtml' xml:lang='en' lang='en'>\n");
375         html("<head>\n");
376         html("<title>");
377         html_txt(ctx->page.title);
378         html("</title>\n");
379         htmlf("<meta name='generator' content='cgit %s'/>\n", cgit_version);
380         if (ctx->cfg.robots && *ctx->cfg.robots)
381                 htmlf("<meta name='robots' content='%s'/>\n", ctx->cfg.robots);
382         html("<link rel='stylesheet' type='text/css' href='");
383         html_attr(ctx->cfg.css);
384         html("'/>\n");
385         html("</head>\n");
386         html("<body>\n");
387 }
388
389 void cgit_print_docend()
390 {
391         html("</td>\n</tr>\n</table>\n</body>\n</html>\n");
392 }
393
394 int print_branch_option(const char *refname, const unsigned char *sha1,
395                         int flags, void *cb_data)
396 {
397         char *name = (char *)refname;
398         html_option(name, name, ctx.qry.head);
399         return 0;
400 }
401
402 int print_archive_ref(const char *refname, const unsigned char *sha1,
403                        int flags, void *cb_data)
404 {
405         struct tag *tag;
406         struct taginfo *info;
407         struct object *obj;
408         char buf[256], *url;
409         unsigned char fileid[20];
410         int *header = (int *)cb_data;
411
412         if (prefixcmp(refname, "refs/archives"))
413                 return 0;
414         strncpy(buf, refname+14, sizeof(buf));
415         obj = parse_object(sha1);
416         if (!obj)
417                 return 1;
418         if (obj->type == OBJ_TAG) {
419                 tag = lookup_tag(sha1);
420                 if (!tag || parse_tag(tag) || !(info = cgit_parse_tag(tag)))
421                         return 0;
422                 hashcpy(fileid, tag->tagged->sha1);
423         } else if (obj->type != OBJ_BLOB) {
424                 return 0;
425         } else {
426                 hashcpy(fileid, sha1);
427         }
428         if (!*header) {
429                 html("<h1>download</h1>\n");
430                 *header = 1;
431         }
432         url = cgit_pageurl(ctx.qry.repo, "blob",
433                            fmt("id=%s&amp;path=%s", sha1_to_hex(fileid),
434                                buf));
435         html_link_open(url, NULL, "menu");
436         html_txt(strlpart(buf, 20));
437         html_link_close();
438         return 0;
439 }
440
441 void add_hidden_formfields(int incl_head, int incl_search, char *page)
442 {
443         char *url;
444
445         if (!ctx.cfg.virtual_root) {
446                 url = fmt("%s/%s", ctx.qry.repo, page);
447                 if (ctx.qry.path)
448                         url = fmt("%s/%s", url, ctx.qry.path);
449                 html_hidden("url", url);
450         }
451
452         if (incl_head && strcmp(ctx.qry.head, ctx.repo->defbranch))
453                 html_hidden("h", ctx.qry.head);
454
455         if (ctx.qry.sha1)
456                 html_hidden("id", ctx.qry.sha1);
457         if (ctx.qry.sha2)
458                 html_hidden("id2", ctx.qry.sha2);
459
460         if (incl_search) {
461                 if (ctx.qry.grep)
462                         html_hidden("qt", ctx.qry.grep);
463                 if (ctx.qry.search)
464                         html_hidden("q", ctx.qry.search);
465         }
466 }
467
468 void cgit_print_pageheader(struct cgit_context *ctx)
469 {
470         static const char *default_info = "This is cgit, a fast webinterface for git repositories";
471         int header = 0;
472         char *url;
473
474         html("<table id='layout' summary=''>\n");
475         html("<tr><td id='sidebar'>\n");
476         html("<table class='sidebar' cellspacing='0' summary=''>\n");
477         html("<tr><td class='sidebar'>\n<a href='");
478         html_attr(cgit_rooturl());
479         htmlf("'><img src='%s' alt='cgit'/></a>\n",
480               ctx->cfg.logo);
481         html("</td></tr>\n<tr><td class='sidebar'>\n");
482         if (ctx->repo) {
483                 html("<h1 class='first'>");
484                 html_txt(strrpart(ctx->repo->name, 20));
485                 html("</h1>\n");
486                 html_txt(ctx->repo->desc);
487                 if (ctx->repo->owner) {
488                         html("<h1>owner</h1>\n");
489                         html_txt(ctx->repo->owner);
490                 }
491                 html("<h1>navigate</h1>\n");
492                 reporevlink(NULL, "summary", NULL, "menu", ctx->qry.head,
493                             NULL, NULL);
494                 cgit_log_link("log", NULL, "menu", ctx->qry.head, NULL, NULL,
495                               0, NULL, NULL);
496                 cgit_tree_link("tree", NULL, "menu", ctx->qry.head,
497                                ctx->qry.sha1, NULL);
498                 cgit_commit_link("commit", NULL, "menu", ctx->qry.head,
499                               ctx->qry.sha1);
500                 cgit_diff_link("diff", NULL, "menu", ctx->qry.head,
501                                ctx->qry.sha1, ctx->qry.sha2, NULL);
502                 cgit_patch_link("patch", NULL, "menu", ctx->qry.head,
503                                 ctx->qry.sha1);
504
505                 for_each_ref(print_archive_ref, &header);
506
507                 if (ctx->repo->clone_url || ctx->cfg.clone_prefix) {
508                         html("<h1>clone</h1>\n");
509                         if (ctx->repo->clone_url)
510                                 url = ctx->repo->clone_url;
511                         else
512                                 url = fmt("%s%s", ctx->cfg.clone_prefix,
513                                           ctx->repo->url);
514                         html("<a class='menu' href='");
515                         html_attr(url);
516                         html("' title='");
517                         html_attr(url);
518                         html("'>\n");
519                         html_txt(strrpart(url, 20));
520                         html("</a>\n");
521                 }
522
523                 html("<h1>branch</h1>\n");
524                 html("<form method='get' action=''>\n");
525                 add_hidden_formfields(0, 1, ctx->qry.page);
526 //              html("<table summary='branch selector' class='grid'><tr><td id='branch-dropdown-cell'>");
527                 html("<select name='h' onchange='this.form.submit();'>\n");
528                 for_each_branch_ref(print_branch_option, ctx->qry.head);
529                 html("</select>\n");
530 //              html("</td><td>");
531                 html("<noscript><input type='submit' id='switch-btn' value='switch'/></noscript>\n");
532 //              html("</td></tr></table>");
533                 html("</form>\n");
534
535                 html("<h1>search</h1>\n");
536                 html("<form method='get' action='");
537                 if (ctx->cfg.virtual_root)
538                         html_attr(cgit_fileurl(ctx->qry.repo, "log",
539                                                ctx->qry.path, NULL));
540                 html("'>\n");
541                 add_hidden_formfields(1, 0, "log");
542                 html("<select name='qt'>\n");
543                 html_option("grep", "log msg", ctx->qry.grep);
544                 html_option("author", "author", ctx->qry.grep);
545                 html_option("committer", "committer", ctx->qry.grep);
546                 html("</select>\n");
547                 html("<input class='txt' type='text' name='q' value='");
548                 html_attr(ctx->qry.search);
549                 html("'/>\n");
550                 html("</form>\n");
551         } else {
552                 if (!ctx->cfg.index_info || html_include(ctx->cfg.index_info))
553                         html(default_info);
554         }
555
556         html("</td></tr></table></td>\n");
557
558         html("<td id='content'>\n");
559 }
560
561 void cgit_print_filemode(unsigned short mode)
562 {
563         if (S_ISDIR(mode))
564                 html("d");
565         else if (S_ISLNK(mode))
566                 html("l");
567         else if (S_ISGITLINK(mode))
568                 html("m");
569         else
570                 html("-");
571         html_fileperm(mode >> 6);
572         html_fileperm(mode >> 3);
573         html_fileperm(mode);
574 }
575
576 void cgit_print_snapshot_links(const char *repo, const char *head,
577                                const char *hex, int snapshots)
578 {
579         const struct cgit_snapshot_format* f;
580         char *filename;
581
582         for (f = cgit_snapshot_formats; f->suffix; f++) {
583                 if (!(snapshots & f->bit))
584                         continue;
585                 filename = fmt("%s-%s%s", cgit_repobasename(repo), hex,
586                                f->suffix);
587                 cgit_snapshot_link(filename, NULL, NULL, (char *)head,
588                                    (char *)hex, filename);
589                 html("<br/>");
590         }
591 }