all repos — cgit @ ed5bd30ebe6921dd22948a3f33a314283f043606

a hyperfast web frontend for git written in c

ui-shared.c (view raw)

  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 "ui-shared.h"
 11#include "cmd.h"
 12#include "html.h"
 13
 14const char cgit_doctype[] =
 15"<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\"\n"
 16"  \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n";
 17
 18static char *http_date(time_t t)
 19{
 20	static char day[][4] =
 21		{"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"};
 22	static char month[][4] =
 23		{"Jan", "Feb", "Mar", "Apr", "May", "Jun",
 24		 "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"};
 25	struct tm *tm = gmtime(&t);
 26	return fmt("%s, %02d %s %04d %02d:%02d:%02d GMT", day[tm->tm_wday],
 27		   tm->tm_mday, month[tm->tm_mon], 1900 + tm->tm_year,
 28		   tm->tm_hour, tm->tm_min, tm->tm_sec);
 29}
 30
 31void cgit_print_error(const char *fmt, ...)
 32{
 33	va_list ap;
 34	va_start(ap, fmt);
 35	cgit_vprint_error(fmt, ap);
 36	va_end(ap);
 37}
 38
 39void cgit_vprint_error(const char *fmt, va_list ap)
 40{
 41	va_list cp;
 42	html("<div class='error'>");
 43	va_copy(cp, ap);
 44	html_vtxtf(fmt, cp);
 45	va_end(cp);
 46	html("</div>\n");
 47}
 48
 49const char *cgit_httpscheme()
 50{
 51	if (ctx.env.https && !strcmp(ctx.env.https, "on"))
 52		return "https://";
 53	else
 54		return "http://";
 55}
 56
 57const char *cgit_hosturl()
 58{
 59	if (ctx.env.http_host)
 60		return ctx.env.http_host;
 61	if (!ctx.env.server_name)
 62		return NULL;
 63	if (!ctx.env.server_port || atoi(ctx.env.server_port) == 80)
 64		return ctx.env.server_name;
 65	return xstrdup(fmt("%s:%s", ctx.env.server_name, ctx.env.server_port));
 66}
 67
 68const char *cgit_rooturl()
 69{
 70	if (ctx.cfg.virtual_root)
 71		return ctx.cfg.virtual_root;
 72	else
 73		return ctx.cfg.script_name;
 74}
 75
 76char *cgit_repourl(const char *reponame)
 77{
 78	if (ctx.cfg.virtual_root) {
 79		return fmt("%s%s/", ctx.cfg.virtual_root, reponame);
 80	} else {
 81		return fmt("?r=%s", reponame);
 82	}
 83}
 84
 85char *cgit_fileurl(const char *reponame, const char *pagename,
 86		   const char *filename, const char *query)
 87{
 88	char *tmp;
 89	char *delim;
 90
 91	if (ctx.cfg.virtual_root) {
 92		tmp = fmt("%s%s/%s/%s", ctx.cfg.virtual_root, reponame,
 93			  pagename, (filename ? filename:""));
 94		delim = "?";
 95	} else {
 96		tmp = fmt("?url=%s/%s/%s", reponame, pagename,
 97			  (filename ? filename : ""));
 98		delim = "&amp;";
 99	}
100	if (query)
101		tmp = fmt("%s%s%s", tmp, delim, query);
102	return tmp;
103}
104
105char *cgit_pageurl(const char *reponame, const char *pagename,
106		   const char *query)
107{
108	return cgit_fileurl(reponame, pagename, 0, query);
109}
110
111const char *cgit_repobasename(const char *reponame)
112{
113	/* I assume we don't need to store more than one repo basename */
114	static char rvbuf[1024];
115	int p;
116	const char *rv;
117	strncpy(rvbuf, reponame, sizeof(rvbuf));
118	if (rvbuf[sizeof(rvbuf)-1])
119		die("cgit_repobasename: truncated repository name '%s'", reponame);
120	p = strlen(rvbuf)-1;
121	/* strip trailing slashes */
122	while (p && rvbuf[p] == '/') rvbuf[p--] = 0;
123	/* strip trailing .git */
124	if (p >= 3 && !strncmp(&rvbuf[p-3], ".git", 4)) {
125		p -= 3; rvbuf[p--] = 0;
126	}
127	/* strip more trailing slashes if any */
128	while ( p && rvbuf[p] == '/') rvbuf[p--] = 0;
129	/* find last slash in the remaining string */
130	rv = strrchr(rvbuf,'/');
131	if (rv)
132		return ++rv;
133	return rvbuf;
134}
135
136static void site_url(const char *page, const char *search, const char *sort, int ofs)
137{
138	char *delim = "?";
139
140	if (ctx.cfg.virtual_root)
141		html_attr(ctx.cfg.virtual_root);
142	else
143		html(ctx.cfg.script_name);
144
145	if (page) {
146		htmlf("?p=%s", page);
147		delim = "&amp;";
148	}
149	if (search) {
150		html(delim);
151		html("q=");
152		html_attr(search);
153		delim = "&amp;";
154	}
155	if (sort) {
156		html(delim);
157		html("s=");
158		html_attr(sort);
159		delim = "&amp;";
160	}
161	if (ofs) {
162		html(delim);
163		htmlf("ofs=%d", ofs);
164	}
165}
166
167static void site_link(const char *page, const char *name, const char *title,
168		      const char *class, const char *search, const char *sort, int ofs)
169{
170	html("<a");
171	if (title) {
172		html(" title='");
173		html_attr(title);
174		html("'");
175	}
176	if (class) {
177		html(" class='");
178		html_attr(class);
179		html("'");
180	}
181	html(" href='");
182	site_url(page, search, sort, ofs);
183	html("'>");
184	html_txt(name);
185	html("</a>");
186}
187
188void cgit_index_link(const char *name, const char *title, const char *class,
189		     const char *pattern, const char *sort, int ofs)
190{
191	site_link(NULL, name, title, class, pattern, sort, ofs);
192}
193
194static char *repolink(const char *title, const char *class, const char *page,
195		      const char *head, const char *path)
196{
197	char *delim = "?";
198
199	html("<a");
200	if (title) {
201		html(" title='");
202		html_attr(title);
203		html("'");
204	}
205	if (class) {
206		html(" class='");
207		html_attr(class);
208		html("'");
209	}
210	html(" href='");
211	if (ctx.cfg.virtual_root) {
212		html_url_path(ctx.cfg.virtual_root);
213		html_url_path(ctx.repo->url);
214		if (ctx.repo->url[strlen(ctx.repo->url) - 1] != '/')
215			html("/");
216		if (page) {
217			html_url_path(page);
218			html("/");
219			if (path)
220				html_url_path(path);
221		}
222	} else {
223		html(ctx.cfg.script_name);
224		html("?url=");
225		html_url_arg(ctx.repo->url);
226		if (ctx.repo->url[strlen(ctx.repo->url) - 1] != '/')
227			html("/");
228		if (page) {
229			html_url_arg(page);
230			html("/");
231			if (path)
232				html_url_arg(path);
233		}
234		delim = "&amp;";
235	}
236	if (head && strcmp(head, ctx.repo->defbranch)) {
237		html(delim);
238		html("h=");
239		html_url_arg(head);
240		delim = "&amp;";
241	}
242	return fmt("%s", delim);
243}
244
245static void reporevlink(const char *page, const char *name, const char *title,
246			const char *class, const char *head, const char *rev,
247			const char *path)
248{
249	char *delim;
250
251	delim = repolink(title, class, page, head, path);
252	if (rev && ctx.qry.head != NULL && strcmp(rev, ctx.qry.head)) {
253		html(delim);
254		html("id=");
255		html_url_arg(rev);
256	}
257	html("'>");
258	html_txt(name);
259	html("</a>");
260}
261
262void cgit_summary_link(const char *name, const char *title, const char *class,
263		       const char *head)
264{
265	reporevlink(NULL, name, title, class, head, NULL, NULL);
266}
267
268void cgit_tag_link(const char *name, const char *title, const char *class,
269		   const char *head, const char *rev)
270{
271	reporevlink("tag", name, title, class, head, rev, NULL);
272}
273
274void cgit_tree_link(const char *name, const char *title, const char *class,
275		    const char *head, const char *rev, const char *path)
276{
277	reporevlink("tree", name, title, class, head, rev, path);
278}
279
280void cgit_plain_link(const char *name, const char *title, const char *class,
281		     const char *head, const char *rev, const char *path)
282{
283	reporevlink("plain", name, title, class, head, rev, path);
284}
285
286void cgit_log_link(const char *name, const char *title, const char *class,
287		   const char *head, const char *rev, const char *path,
288		   int ofs, const char *grep, const char *pattern, int showmsg)
289{
290	char *delim;
291
292	delim = repolink(title, class, "log", head, path);
293	if (rev && ctx.qry.head && strcmp(rev, ctx.qry.head)) {
294		html(delim);
295		html("id=");
296		html_url_arg(rev);
297		delim = "&amp;";
298	}
299	if (grep && pattern) {
300		html(delim);
301		html("qt=");
302		html_url_arg(grep);
303		delim = "&amp;";
304		html(delim);
305		html("q=");
306		html_url_arg(pattern);
307	}
308	if (ofs > 0) {
309		html(delim);
310		html("ofs=");
311		htmlf("%d", ofs);
312		delim = "&amp;";
313	}
314	if (showmsg) {
315		html(delim);
316		html("showmsg=1");
317	}
318	html("'>");
319	html_txt(name);
320	html("</a>");
321}
322
323void cgit_commit_link(char *name, const char *title, const char *class,
324		      const char *head, const char *rev, const char *path,
325		      int toggle_ssdiff)
326{
327	if (strlen(name) > ctx.cfg.max_msg_len && ctx.cfg.max_msg_len >= 15) {
328		name[ctx.cfg.max_msg_len] = '\0';
329		name[ctx.cfg.max_msg_len - 1] = '.';
330		name[ctx.cfg.max_msg_len - 2] = '.';
331		name[ctx.cfg.max_msg_len - 3] = '.';
332	}
333
334	char *delim;
335
336	delim = repolink(title, class, "commit", head, path);
337	if (rev && ctx.qry.head && strcmp(rev, ctx.qry.head)) {
338		html(delim);
339		html("id=");
340		html_url_arg(rev);
341		delim = "&amp;";
342	}
343	if ((ctx.qry.ssdiff && !toggle_ssdiff) || (!ctx.qry.ssdiff && toggle_ssdiff)) {
344		html(delim);
345		html("ss=1");
346		delim = "&amp;";
347	}
348	if (ctx.qry.context > 0 && ctx.qry.context != 3) {
349		html(delim);
350		html("context=");
351		htmlf("%d", ctx.qry.context);
352		delim = "&amp;";
353	}
354	if (ctx.qry.ignorews) {
355		html(delim);
356		html("ignorews=1");
357		delim = "&amp;";
358	}
359	html("'>");
360	if (name[0] != '\0')
361		html_txt(name);
362	else
363		html_txt("(no commit message)");
364	html("</a>");
365}
366
367void cgit_refs_link(const char *name, const char *title, const char *class,
368		    const char *head, const char *rev, const char *path)
369{
370	reporevlink("refs", name, title, class, head, rev, path);
371}
372
373void cgit_snapshot_link(const char *name, const char *title, const char *class,
374			const char *head, const char *rev,
375			const char *archivename)
376{
377	reporevlink("snapshot", name, title, class, head, rev, archivename);
378}
379
380void cgit_diff_link(const char *name, const char *title, const char *class,
381		    const char *head, const char *new_rev, const char *old_rev,
382		    const char *path, int toggle_ssdiff)
383{
384	char *delim;
385
386	delim = repolink(title, class, "diff", head, path);
387	if (new_rev && ctx.qry.head != NULL && strcmp(new_rev, ctx.qry.head)) {
388		html(delim);
389		html("id=");
390		html_url_arg(new_rev);
391		delim = "&amp;";
392	}
393	if (old_rev) {
394		html(delim);
395		html("id2=");
396		html_url_arg(old_rev);
397		delim = "&amp;";
398	}
399	if ((ctx.qry.ssdiff && !toggle_ssdiff) || (!ctx.qry.ssdiff && toggle_ssdiff)) {
400		html(delim);
401		html("ss=1");
402		delim = "&amp;";
403	}
404	if (ctx.qry.context > 0 && ctx.qry.context != 3) {
405		html(delim);
406		html("context=");
407		htmlf("%d", ctx.qry.context);
408		delim = "&amp;";
409	}
410	if (ctx.qry.ignorews) {
411		html(delim);
412		html("ignorews=1");
413		delim = "&amp;";
414	}
415	html("'>");
416	html_txt(name);
417	html("</a>");
418}
419
420void cgit_patch_link(const char *name, const char *title, const char *class,
421		     const char *head, const char *rev, const char *path)
422{
423	reporevlink("patch", name, title, class, head, rev, path);
424}
425
426void cgit_stats_link(const char *name, const char *title, const char *class,
427		     const char *head, const char *path)
428{
429	reporevlink("stats", name, title, class, head, NULL, path);
430}
431
432static void cgit_self_link(char *name, const char *title, const char *class,
433			   struct cgit_context *ctx)
434{
435	if (!strcmp(ctx->qry.page, "repolist"))
436		cgit_index_link(name, title, class, ctx->qry.search, ctx->qry.sort,
437				ctx->qry.ofs);
438	else if (!strcmp(ctx->qry.page, "summary"))
439		cgit_summary_link(name, title, class, ctx->qry.head);
440	else if (!strcmp(ctx->qry.page, "tag"))
441		cgit_tag_link(name, title, class, ctx->qry.head,
442			      ctx->qry.has_sha1 ? ctx->qry.sha1 : NULL);
443	else if (!strcmp(ctx->qry.page, "tree"))
444		cgit_tree_link(name, title, class, ctx->qry.head,
445			       ctx->qry.has_sha1 ? ctx->qry.sha1 : NULL,
446			       ctx->qry.path);
447	else if (!strcmp(ctx->qry.page, "plain"))
448		cgit_plain_link(name, title, class, ctx->qry.head,
449				ctx->qry.has_sha1 ? ctx->qry.sha1 : NULL,
450				ctx->qry.path);
451	else if (!strcmp(ctx->qry.page, "log"))
452		cgit_log_link(name, title, class, ctx->qry.head,
453			      ctx->qry.has_sha1 ? ctx->qry.sha1 : NULL,
454			      ctx->qry.path, ctx->qry.ofs,
455			      ctx->qry.grep, ctx->qry.search,
456			      ctx->qry.showmsg);
457	else if (!strcmp(ctx->qry.page, "commit"))
458		cgit_commit_link(name, title, class, ctx->qry.head,
459				 ctx->qry.has_sha1 ? ctx->qry.sha1 : NULL,
460				 ctx->qry.path, 0);
461	else if (!strcmp(ctx->qry.page, "patch"))
462		cgit_patch_link(name, title, class, ctx->qry.head,
463				ctx->qry.has_sha1 ? ctx->qry.sha1 : NULL,
464				ctx->qry.path);
465	else if (!strcmp(ctx->qry.page, "refs"))
466		cgit_refs_link(name, title, class, ctx->qry.head,
467			       ctx->qry.has_sha1 ? ctx->qry.sha1 : NULL,
468			       ctx->qry.path);
469	else if (!strcmp(ctx->qry.page, "snapshot"))
470		cgit_snapshot_link(name, title, class, ctx->qry.head,
471				   ctx->qry.has_sha1 ? ctx->qry.sha1 : NULL,
472				   ctx->qry.path);
473	else if (!strcmp(ctx->qry.page, "diff"))
474		cgit_diff_link(name, title, class, ctx->qry.head,
475			       ctx->qry.sha1, ctx->qry.sha2,
476			       ctx->qry.path, 0);
477	else if (!strcmp(ctx->qry.page, "stats"))
478		cgit_stats_link(name, title, class, ctx->qry.head,
479				ctx->qry.path);
480	else {
481		/* Don't known how to make link for this page */
482		repolink(title, class, ctx->qry.page, ctx->qry.head, ctx->qry.path);
483		html("><!-- cgit_self_link() doesn't know how to make link for page '");
484		html_txt(ctx->qry.page);
485		html("' -->");
486		html_txt(name);
487		html("</a>");
488	}
489}
490
491void cgit_object_link(struct object *obj)
492{
493	char *page, *shortrev, *fullrev, *name;
494
495	fullrev = sha1_to_hex(obj->sha1);
496	shortrev = xstrdup(fullrev);
497	shortrev[10] = '\0';
498	if (obj->type == OBJ_COMMIT) {
499		cgit_commit_link(fmt("commit %s...", shortrev), NULL, NULL,
500				 ctx.qry.head, fullrev, NULL, 0);
501		return;
502	} else if (obj->type == OBJ_TREE)
503		page = "tree";
504	else if (obj->type == OBJ_TAG)
505		page = "tag";
506	else
507		page = "blob";
508	name = fmt("%s %s...", typename(obj->type), shortrev);
509	reporevlink(page, name, NULL, NULL, ctx.qry.head, fullrev, NULL);
510}
511
512static struct string_list_item *lookup_path(struct string_list *list,
513					    const char *path)
514{
515	struct string_list_item *item;
516
517	while (path && path[0]) {
518		if ((item = string_list_lookup(list, path)))
519			return item;
520		if (!(path = strchr(path, '/')))
521			break;
522		path++;
523	}
524	return NULL;
525}
526
527void cgit_submodule_link(const char *class, char *path, const char *rev)
528{
529	struct string_list *list;
530	struct string_list_item *item;
531	char tail, *dir;
532	size_t len;
533
534	len = 0;
535	tail = 0;
536	list = &ctx.repo->submodules;
537	item = lookup_path(list, path);
538	if (!item) {
539		len = strlen(path);
540		tail = path[len - 1];
541		if (tail == '/') {
542			path[len - 1] = 0;
543			item = lookup_path(list, path);
544		}
545	}
546	html("<a ");
547	if (class)
548		htmlf("class='%s' ", class);
549	html("href='");
550	if (item) {
551		html_attr(fmt(item->util, rev));
552	} else if (ctx.repo->module_link) {
553		dir = strrchr(path, '/');
554		if (dir)
555			dir++;
556		else
557			dir = path;
558		html_attr(fmt(ctx.repo->module_link, dir, rev));
559	} else {
560		html("#");
561	}
562	html("'>");
563	html_txt(path);
564	html("</a>");
565	html_txt(fmt(" @ %.7s", rev));
566	if (item && tail)
567		path[len - 1] = tail;
568}
569
570void cgit_print_date(time_t secs, const char *format, int local_time)
571{
572	char buf[64];
573	struct tm *time;
574
575	if (!secs)
576		return;
577	if (local_time)
578		time = localtime(&secs);
579	else
580		time = gmtime(&secs);
581	strftime(buf, sizeof(buf)-1, format, time);
582	html_txt(buf);
583}
584
585void cgit_print_age(time_t t, time_t max_relative, const char *format)
586{
587	time_t now, secs;
588
589	if (!t)
590		return;
591	time(&now);
592	secs = now - t;
593
594	if (secs > max_relative && max_relative >= 0) {
595		cgit_print_date(t, format, ctx.cfg.local_time);
596		return;
597	}
598
599	if (secs < TM_HOUR * 2) {
600		htmlf("<span class='age-mins'>%.0f min.</span>",
601		      secs * 1.0 / TM_MIN);
602		return;
603	}
604	if (secs < TM_DAY * 2) {
605		htmlf("<span class='age-hours'>%.0f hours</span>",
606		      secs * 1.0 / TM_HOUR);
607		return;
608	}
609	if (secs < TM_WEEK * 2) {
610		htmlf("<span class='age-days'>%.0f days</span>",
611		      secs * 1.0 / TM_DAY);
612		return;
613	}
614	if (secs < TM_MONTH * 2) {
615		htmlf("<span class='age-weeks'>%.0f weeks</span>",
616		      secs * 1.0 / TM_WEEK);
617		return;
618	}
619	if (secs < TM_YEAR * 2) {
620		htmlf("<span class='age-months'>%.0f months</span>",
621		      secs * 1.0 / TM_MONTH);
622		return;
623	}
624	htmlf("<span class='age-years'>%.0f years</span>",
625	      secs * 1.0 / TM_YEAR);
626}
627
628void cgit_print_http_headers(struct cgit_context *ctx)
629{
630	if (ctx->env.no_http && !strcmp(ctx->env.no_http, "1"))
631		return;
632
633	if (ctx->page.status)
634		htmlf("Status: %d %s\n", ctx->page.status, ctx->page.statusmsg);
635	if (ctx->page.mimetype && ctx->page.charset)
636		htmlf("Content-Type: %s; charset=%s\n", ctx->page.mimetype,
637		      ctx->page.charset);
638	else if (ctx->page.mimetype)
639		htmlf("Content-Type: %s\n", ctx->page.mimetype);
640	if (ctx->page.size)
641		htmlf("Content-Length: %zd\n", ctx->page.size);
642	if (ctx->page.filename)
643		htmlf("Content-Disposition: inline; filename=\"%s\"\n",
644		      ctx->page.filename);
645	htmlf("Last-Modified: %s\n", http_date(ctx->page.modified));
646	htmlf("Expires: %s\n", http_date(ctx->page.expires));
647	if (ctx->page.etag)
648		htmlf("ETag: \"%s\"\n", ctx->page.etag);
649	html("\n");
650	if (ctx->env.request_method && !strcmp(ctx->env.request_method, "HEAD"))
651		exit(0);
652}
653
654void cgit_print_docstart(struct cgit_context *ctx)
655{
656	if (ctx->cfg.embedded) {
657		if (ctx->cfg.header)
658			html_include(ctx->cfg.header);
659		return;
660	}
661
662	const char *host = cgit_hosturl();
663	html(cgit_doctype);
664	html("<html xmlns='http://www.w3.org/1999/xhtml' xml:lang='en' lang='en'>\n");
665	html("<head>\n");
666	html("<title>");
667	html_txt(ctx->page.title);
668	html("</title>\n");
669	htmlf("<meta name='generator' content='cgit %s'/>\n", cgit_version);
670	if (ctx->cfg.robots && *ctx->cfg.robots)
671		htmlf("<meta name='robots' content='%s'/>\n", ctx->cfg.robots);
672	html("<link rel='stylesheet' type='text/css' href='");
673	html_attr(ctx->cfg.css);
674	html("'/>\n");
675	if (ctx->cfg.favicon) {
676		html("<link rel='shortcut icon' href='");
677		html_attr(ctx->cfg.favicon);
678		html("'/>\n");
679	}
680	if (host && ctx->repo && ctx->qry.head) {
681		html("<link rel='alternate' title='Atom feed' href='");
682		html(cgit_httpscheme());
683		html_attr(cgit_hosturl());
684		html_attr(cgit_fileurl(ctx->repo->url, "atom", ctx->qry.vpath,
685				       fmt("h=%s", ctx->qry.head)));
686		html("' type='application/atom+xml'/>\n");
687	}
688	if (ctx->cfg.head_include)
689		html_include(ctx->cfg.head_include);
690	html("</head>\n");
691	html("<body>\n");
692	if (ctx->cfg.header)
693		html_include(ctx->cfg.header);
694}
695
696void cgit_print_docend()
697{
698	html("</div> <!-- class=content -->\n");
699	if (ctx.cfg.embedded) {
700		html("</div> <!-- id=cgit -->\n");
701		if (ctx.cfg.footer)
702			html_include(ctx.cfg.footer);
703		return;
704	}
705	if (ctx.cfg.footer)
706		html_include(ctx.cfg.footer);
707	else {
708		htmlf("<div class='footer'>generated  by cgit %s at ",
709			cgit_version);
710		cgit_print_date(time(NULL), FMT_LONGDATE, ctx.cfg.local_time);
711		html("</div>\n");
712	}
713	html("</div> <!-- id=cgit -->\n");
714	html("</body>\n</html>\n");
715}
716
717static int print_branch_option(const char *refname, const unsigned char *sha1,
718			       int flags, void *cb_data)
719{
720	char *name = (char *)refname;
721	html_option(name, name, ctx.qry.head);
722	return 0;
723}
724
725void cgit_add_hidden_formfields(int incl_head, int incl_search,
726				const char *page)
727{
728	char *url;
729
730	if (!ctx.cfg.virtual_root) {
731		url = fmt("%s/%s", ctx.qry.repo, page);
732		if (ctx.qry.vpath)
733			url = fmt("%s/%s", url, ctx.qry.vpath);
734		html_hidden("url", url);
735	}
736
737	if (incl_head && ctx.qry.head && ctx.repo->defbranch &&
738	    strcmp(ctx.qry.head, ctx.repo->defbranch))
739		html_hidden("h", ctx.qry.head);
740
741	if (ctx.qry.sha1)
742		html_hidden("id", ctx.qry.sha1);
743	if (ctx.qry.sha2)
744		html_hidden("id2", ctx.qry.sha2);
745	if (ctx.qry.showmsg)
746		html_hidden("showmsg", "1");
747
748	if (incl_search) {
749		if (ctx.qry.grep)
750			html_hidden("qt", ctx.qry.grep);
751		if (ctx.qry.search)
752			html_hidden("q", ctx.qry.search);
753	}
754}
755
756static const char *hc(struct cgit_context *ctx, const char *page)
757{
758	return strcmp(ctx->qry.page, page) ? NULL : "active";
759}
760
761static void cgit_print_path_crumbs(struct cgit_context *ctx, char *path)
762{
763	char *old_path = ctx->qry.path;
764	char *p = path, *q, *end = path + strlen(path);
765
766	ctx->qry.path = NULL;
767	cgit_self_link("root", NULL, NULL, ctx);
768	ctx->qry.path = p = path;
769	while (p < end) {
770		if (!(q = strchr(p, '/')))
771			q = end;
772		*q = '\0';
773		html_txt("/");
774		cgit_self_link(p, NULL, NULL, ctx);
775		if (q < end)
776			*q = '/';
777		p = q + 1;
778	}
779	ctx->qry.path = old_path;
780}
781
782static void print_header(struct cgit_context *ctx)
783{
784	char *logo = NULL, *logo_link = NULL;
785
786	html("<table id='header'>\n");
787	html("<tr>\n");
788
789	if (ctx->repo && ctx->repo->logo && *ctx->repo->logo)
790		logo = ctx->repo->logo;
791	else
792		logo = ctx->cfg.logo;
793	if (ctx->repo && ctx->repo->logo_link && *ctx->repo->logo_link)
794		logo_link = ctx->repo->logo_link;
795	else
796		logo_link = ctx->cfg.logo_link;
797	if (logo && *logo) {
798		html("<td class='logo' rowspan='2'><a href='");
799		if (logo_link && *logo_link)
800			html_attr(logo_link);
801		else
802			html_attr(cgit_rooturl());
803		html("'><img src='");
804		html_attr(logo);
805		html("' alt='cgit logo'/></a></td>\n");
806	}
807
808	html("<td class='main'>");
809	if (ctx->repo) {
810		cgit_index_link("index", NULL, NULL, NULL, NULL, 0);
811		html(" : ");
812		cgit_summary_link(ctx->repo->name, ctx->repo->name, NULL, NULL);
813		html("</td><td class='form'>");
814		html("<form method='get' action=''>\n");
815		cgit_add_hidden_formfields(0, 1, ctx->qry.page);
816		html("<select name='h' onchange='this.form.submit();'>\n");
817		for_each_branch_ref(print_branch_option, ctx->qry.head);
818		html("</select> ");
819		html("<input type='submit' name='' value='switch'/>");
820		html("</form>");
821	} else
822		html_txt(ctx->cfg.root_title);
823	html("</td></tr>\n");
824
825	html("<tr><td class='sub'>");
826	if (ctx->repo) {
827		html_txt(ctx->repo->desc);
828		html("</td><td class='sub right'>");
829		html_txt(ctx->repo->owner);
830	} else {
831		if (ctx->cfg.root_desc)
832			html_txt(ctx->cfg.root_desc);
833		else if (ctx->cfg.index_info)
834			html_include(ctx->cfg.index_info);
835	}
836	html("</td></tr></table>\n");
837}
838
839void cgit_print_pageheader(struct cgit_context *ctx)
840{
841	html("<div id='cgit'>");
842	if (!ctx->cfg.noheader)
843		print_header(ctx);
844
845	html("<table class='tabs'><tr><td>\n");
846	if (ctx->repo) {
847		cgit_summary_link("summary", NULL, hc(ctx, "summary"),
848				  ctx->qry.head);
849		cgit_refs_link("refs", NULL, hc(ctx, "refs"), ctx->qry.head,
850			       ctx->qry.sha1, NULL);
851		cgit_log_link("log", NULL, hc(ctx, "log"), ctx->qry.head,
852			      NULL, ctx->qry.vpath, 0, NULL, NULL,
853			      ctx->qry.showmsg);
854		cgit_tree_link("tree", NULL, hc(ctx, "tree"), ctx->qry.head,
855			       ctx->qry.sha1, ctx->qry.vpath);
856		cgit_commit_link("commit", NULL, hc(ctx, "commit"),
857				 ctx->qry.head, ctx->qry.sha1, ctx->qry.vpath, 0);
858		cgit_diff_link("diff", NULL, hc(ctx, "diff"), ctx->qry.head,
859			       ctx->qry.sha1, ctx->qry.sha2, ctx->qry.vpath, 0);
860		if (ctx->repo->max_stats)
861			cgit_stats_link("stats", NULL, hc(ctx, "stats"),
862					ctx->qry.head, ctx->qry.vpath);
863		if (ctx->repo->readme)
864			reporevlink("about", "about", NULL,
865				    hc(ctx, "about"), ctx->qry.head, NULL,
866				    NULL);
867		html("</td><td class='form'>");
868		html("<form class='right' method='get' action='");
869		if (ctx->cfg.virtual_root)
870			html_url_path(cgit_fileurl(ctx->qry.repo, "log",
871						   ctx->qry.vpath, NULL));
872		html("'>\n");
873		cgit_add_hidden_formfields(1, 0, "log");
874		html("<select name='qt'>\n");
875		html_option("grep", "log msg", ctx->qry.grep);
876		html_option("author", "author", ctx->qry.grep);
877		html_option("committer", "committer", ctx->qry.grep);
878		html_option("range", "range", ctx->qry.grep);
879		html("</select>\n");
880		html("<input class='txt' type='text' size='10' name='q' value='");
881		html_attr(ctx->qry.search);
882		html("'/>\n");
883		html("<input type='submit' value='search'/>\n");
884		html("</form>\n");
885	} else {
886		site_link(NULL, "index", NULL, hc(ctx, "repolist"), NULL, NULL, 0);
887		if (ctx->cfg.root_readme)
888			site_link("about", "about", NULL, hc(ctx, "about"),
889				  NULL, NULL, 0);
890		html("</td><td class='form'>");
891		html("<form method='get' action='");
892		html_attr(cgit_rooturl());
893		html("'>\n");
894		html("<input type='text' name='q' size='10' value='");
895		html_attr(ctx->qry.search);
896		html("'/>\n");
897		html("<input type='submit' value='search'/>\n");
898		html("</form>");
899	}
900	html("</td></tr></table>\n");
901	if (ctx->qry.vpath) {
902		html("<div class='path'>");
903		html("path: ");
904		cgit_print_path_crumbs(ctx, ctx->qry.vpath);
905		html("</div>");
906	}
907	html("<div class='content'>");
908}
909
910void cgit_print_filemode(unsigned short mode)
911{
912	if (S_ISDIR(mode))
913		html("d");
914	else if (S_ISLNK(mode))
915		html("l");
916	else if (S_ISGITLINK(mode))
917		html("m");
918	else
919		html("-");
920	html_fileperm(mode >> 6);
921	html_fileperm(mode >> 3);
922	html_fileperm(mode);
923}
924
925void cgit_print_snapshot_links(const char *repo, const char *head,
926			       const char *hex, int snapshots)
927{
928	const struct cgit_snapshot_format* f;
929	char *prefix;
930	char *filename;
931	unsigned char sha1[20];
932
933	if (get_sha1(fmt("refs/tags/%s", hex), sha1) == 0 &&
934	    (hex[0] == 'v' || hex[0] == 'V') && isdigit(hex[1]))
935		hex++;
936	prefix = xstrdup(fmt("%s-%s", cgit_repobasename(repo), hex));
937	for (f = cgit_snapshot_formats; f->suffix; f++) {
938		if (!(snapshots & f->bit))
939			continue;
940		filename = fmt("%s%s", prefix, f->suffix);
941		cgit_snapshot_link(filename, NULL, NULL, NULL, NULL, filename);
942		html("<br/>");
943	}
944	free(prefix);
945}