summaryrefslogtreecommitdiffstatshomepage
path: root/ui-shared.c
blob: 9b120ae0ff6e74b1814d6eecc9bfa2a41d76b94e (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
pre { line-height: 125%; }
td.linenos .normal { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; }
span.linenos { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; }
td.linenos .special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; }
span.linenos.special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; }
.highlight .hll { background-color: #ffffcc }
.highlight .c { color: #888888 } /* Comment */
.highlight .err { color: #a61717; background-color: #e3d2d2 } /* Error */
.highlight .k { color: #008800; font-weight: bold } /* Keyword */
.highlight .ch { color: #888888 } /* Comment.Hashbang */
.highlight .cm { color: #888888 } /* Comment.Multiline */
.highlight .cp { color: #cc0000; font-weight: bold } /* Comment.Preproc */
.highlight .cpf { color: #888888 } /* Comment.PreprocFile */
.highlight .c1 { color: #888888 } /* Comment.Single */
.highlight .cs { color: #cc0000; font-weight: bold; background-color: #fff0f0 } /* Comment.Special */
.highlight .gd { color: #000000; background-color: #ffdddd } /* Generic.Deleted */
.highlight .ge { font-style: italic } /* Generic.Emph */
.highlight .gr { color: #aa0000 } /* Generic.Error */
.highlight .gh { color: #333333 } /* Generic.Heading */
.highlight .gi { color: #000000; background-color: #ddffdd } /* Generic.Inserted */
.highlight .go { color: #888888 } /* Generic.Output */
.highlight .gp { color: #555555 } /* Generic.Prompt */
.highlight .gs { font-weight: bold } /* Generic.Strong */
.highlight .gu { color: #666666 } /* Generic.Subheading */
.highlight .gt { color: #aa0000 } /* Generic.Traceback */
.highlight .kc { color: #008800; font-weight: bold } /* Keyword.Constant */
.highlight .kd { color: #008800; font-weight: bold } /* Keyword.Declaration */
.highlight .kn { color: #008800; font-weight: bold } /* Keyword.Namespace */
.highlight .kp { color: #008800 } /* Keyword.Pseudo */
.highlight .kr { color: #008800; font-weight: bold } /* Keyword.Reserved */
.highlight .kt { color: #888888; font-weight: bold } /* Keyword.Type */
.highlight .m { color: #0000DD; font-weight: bold } /* Literal.Number */
.highlight .s { color: #dd2200; background-color: #fff0f0 } /* Literal.String */
.highlight .na { color: #336699 } /* Name.Attribute */
.highlight .nb { color: #003388 } /* Name.Builtin */
.highlight .nc { color: #bb0066; font-weight: bold } /* Name.Class */
.highlight .no { color: #003366; font-weight: bold } /* Name.Constant */
.highlight .nd { color: #555555 } /* Name.Decorator */
.highlight .ne { color: #bb0066; font-weight: bold } /* Name.Exception */
.highlight .nf { color: #0066bb; font-weight: bold } /* Name.Function */
.highlight .nl { color: #336699; font-style: italic } /* Name.Label */
.highlight .nn { color: #bb0066; font-weight: bold } /* Name.Namespace */
.highlight .py { color: #336699; font-weight: bold } /* Name.Property */
.highlight .nt { color: #bb0066; font-weight: bold } /* Name.Tag */
.highlight .nv { color: #336699 } /* Name.Variable */
.highlight .ow { color: #008800 } /* Operator.Word */
.highlight .w { color: #bbbbbb } /* Text.Whitespace */
.highlight .mb { color: #0000DD; font-weight: bold } /* Literal.Number.Bin */
.highlight .mf { color: #0000DD; font-weight: bold } /* Literal.Number.Float */
.highlight .mh { color: #0000DD; font-weight: bold } /* Literal.Number.Hex */
.highlight .mi { color: #0000DD; font-weight: bold } /* Literal.Number.Integer */
.highlight .mo { color: #0000DD; font-weight: bold } /* Literal.Number.Oct */
.highlight .sa { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Affix */
.highlight .sb { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Backtick */
.highlight .sc { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Char */
.highlight .dl { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Delimiter */
.highlight .sd { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Doc */
.highlight .s2 { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Double */
.highlight .se { color: #0044dd; background-color: #fff0f0 } /* Literal.String.Escape */
.highlight .sh { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Heredoc */
.highlight .si { color: #3333bb; background-color: #fff0f0 } /* Literal.String.Interpol */
.highlight .sx { color: #22bb22; background-color: #f0fff0 } /* Literal.String.Other */
.highlight .sr { color: #008800; background-color: #fff0ff } /* Literal.String.Regex */
.highlight .s1 { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Single */
.highlight .ss { color: #aa6600; background-color: #fff0f0 } /* Literal.String.Symbol */
.highlight .bp { color: #003388 } /* Name.Builtin.Pseudo */
.highlight .fm { color: #0066bb; font-weight: bold } /* Name.Function.Magic */
.highlight .vc { color: #336699 } /* Name.Variable.Class */
.highlight .vg { color: #dd7700 } /* Name.Variable.Global */
.highlight .vi { color: #3333bb } /* Name.Variable.Instance */
.highlight .vm { color: #336699 } /* Name.Variable.Magic */
.highlight .il { color: #0000DD; font-weight: bold } /* Literal.Number.Integer.Long */
/* ui-log.c: functions for log output
 *
 * Copyright (C) 2006 Lars Hjemli
 *
 * Licensed under GNU General Public License v2
 *   (see COPYING for full license text)
 */

#include "cgit.h"
#include "html.h"
#include "ui-shared.h"

int files, add_lines, rem_lines;

void count_lines(char *line, int size)
{
	if (size <= 0)
		return;

	if (line[0] == '+')
		add_lines++;

	else if (line[0] == '-')
		rem_lines++;
}

void inspect_files(struct diff_filepair *pair)
{
	unsigned long old_size = 0;
	unsigned long new_size = 0;
	int binary = 0;

	files++;
	if (ctx.repo->enable_log_linecount)
		cgit_diff_files(pair->one->sha1, pair->two->sha1, &old_size,
				&new_size, &binary, count_lines);
}

void show_commit_decorations(struct commit *commit)
{
	struct name_decoration *deco;
	static char buf[1024];

	buf[sizeof(buf) - 1] = 0;
	deco = lookup_decoration(&name_decoration, &commit->object);
	while (deco) {
		if (!prefixcmp(deco->name, "refs/heads/")) {
			strncpy(buf, deco->name + 11, sizeof(buf) - 1);
			cgit_log_link(buf, NULL, "branch-deco", buf, NULL, NULL,
				0, NULL, NULL, ctx.qry.showmsg);
		}
		else if (!prefixcmp(deco->name, "tag: refs/tags/")) {
			strncpy(buf, deco->name + 15, sizeof(buf) - 1);
			cgit_tag_link(buf, NULL, "tag-deco", ctx.qry.head, buf);
		}
		else if (!prefixcmp(deco->name, "refs/remotes/")) {
			strncpy(buf, decopre { line-height: 125%; }
td.linenos .normal { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; }
span.linenos { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; }
td.linenos .special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; }
span.linenos.special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; }
.highlight .hll { background-color: #ffffcc }
.highlight .c { color: #888888 } /* Comment */
.highlight .err { color: #a61717; background-color: #e3d2d2 } /* Error */
.highlight .k { color: #008800; font-weight: bold } /* Keyword */
.highlight .ch { color: #888888 } /* Comment.Hashbang */
.highlight .cm { color: #888888 } /* Comment.Multiline */
.highlight .cp { color: #cc0000; font-weight: bold } /* Comment.Preproc */
.highlight .cpf { color: #888888 } /* Comment.PreprocFile */
.highlight .c1 { color: #888888 } /* Comment.Single */
.highlight .cs { color: #cc0000; font-weight: bold; background-color: #fff0f0 } /* Comment.Special */
.highlight .gd { color: #000000; background-color: #ffdddd } /* Generic.Deleted */
.highlight .ge { font-style: italic } /* Generic.Emph */
.highlight .gr { color: #aa0000 } /* Generic.Error */
.highlight .gh { color: #333333 } /* Generic.Heading */
.highlight .gi { color: #000000; background-color: #ddffdd } /* Generic.Inserted */
.highlight .go { color: #888888 } /* Generic.Output */
.highlight .gp { color: #555555 } /* Generic.Prompt */
.highlight .gs { font-weight: bold } /* Generic.Strong */
.highlight .gu { color: #666666 } /* Generic.Subheading */
.highlight .gt { color: #aa0000 } /* Generic.Traceback */
.highlight .kc { color: #008800; font-weight: bold } /* Keyword.Constant */
.highlight .kd { color: #008800; font-weight: bold } /* Keyword.Declaration */
.highlight .kn { color: #008800; font-weight: bold } /* Keyword.Namespace */
.highlight .kp { color: #008800 } /* Keyword.Pseudo */
.highlight .kr { color: #008800; font-weight: bold } /* Keyword.Reserved */
.highlight .kt { color: #888888; font-weight: bold } /* Keyword.Type */
.highlight .m { color: #0000DD; font-weight: bold } /* Literal.Number */
.highlight .s { color: #dd2200; background-color: #fff0f0 } /* Literal.String */
.highlight .na { color: #336699 } /* Name.Attribute */
.highlight .nb { color: #003388 } /* Name.Builtin */
.highlight .nc { color: #bb0066; font-weight: bold } /* Name.Class */
.highlight .no { color: #003366; font-weight: bold } /* Name.Constant */
.highlight .nd { color: #555555 } /* Name.Decorator */
.highlight .ne { color: #bb0066; font-weight: bold } /* Name.Exception */
.highlight .nf { color: #0066bb; font-weight: bold } /* Name.Function */
.highlight .nl { color: #336699; font-style: italic } /* Name.Label */
.highlight .nn { color: #bb0066; font-weight: bold } /* Name.Namespace */
.highlight .py { color: #336699; font-weight: bold } /* Name.Property */
.highlight .nt { color: #bb0066; font-weight: bold } /* Name.Tag */
.highlight .nv { color: #336699 } /* Name.Variable */
.highlight .ow { color: #008800 } /* Operator.Word */
.highlight .w { color: #bbbbbb } /* Text.Whitespace */
.highlight .mb { color: #0000DD; font-weight: bold } /* Literal.Number.Bin */
.highlight .mf { color: #0000DD; font-weight: bold } /* Literal.Number.Float */
.highlight .mh { color: #0000DD; font-weight: bold } /* Literal.Number.Hex */
.highlight .mi { color: #0000DD; font-weight: bold } /* Literal.Number.Integer */
.highlight .mo { color: #0000DD; font-weight: bold } /* Literal.Number.Oct */
.highlight .sa { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Affix */
.highlight .sb { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Backtick */
.highlight .sc { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Char */
.highlight .dl { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Delimiter */
.highlight .sd { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Doc */
.highlight .s2 { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Double */
.highlight .se { color: #0044dd; background-color: #fff0f0 } /* Literal.String.Escape */
.highlight .sh { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Heredoc */
.highlight .si { color: #3333bb; background-color: #fff0f0 } /* Literal.String.Interpol */
.highlight .sx { color: #22bb22; background-color: #f0fff0 } /* Literal.String.Other */
.highlight .sr { color: #008800; background-color: #fff0ff } /* Literal.String.Regex */
.highlight .s1 { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Single */
.highlight .ss { color: #aa6600; background-color: #fff0f0 } /* Literal.String.Symbol */
.highlight .bp { color: #003388 } /* Name.Builtin.Pseudo */
.highlight .fm { color: #0066bb; font-weight: bold } /* Name.Function.Magic */
.highlight .vc { color: #336699 } /* Name.Variable.Class */
.highlight .vg { color: #dd7700 } /* Name.Variable.Global */
.highlight .vi { color: #3333bb } /* Name.Variable.Instance */
.highlight .vm { color: #336699 } /* Name.Variable.Magic */
.highlight .il { color: #0000DD; font-weight: bold } /* Literal.Number.Integer.Long */
/* ui-shared.c: common web output functions
 *
 * Copyright (C) 2006 Lars Hjemli
 *
 * Licensed under GNU General Public License v2
 *   (see COPYING for full license text)
 */

#include "cgit.h"
#include "cmd.h"
#include "html.h"

const char cgit_doctype[] =
"<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\"\n"
"  \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n";

static char *http_date(time_t t)
{
	static char day[][4] =
		{"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"};
	static char month[][4] =
		{"Jan", "Feb", "Mar", "Apr", "May", "Jun",
		 "Jul", "Aug", "Sep", "Oct", "Now", "Dec"};
	struct tm *tm = gmtime(&t);
	return fmt("%s, %02d %s %04d %02d:%02d:%02d GMT", day[tm->tm_wday],
		   tm->tm_mday, month[tm->tm_mon], 1900+tm->tm_year,
		   tm->tm_hour, tm->tm_min, tm->tm_sec);
}

void cgit_print_error(char *msg)
{
	html("<div class='error'>");
	html_txt(msg);
	html("</div>\n");
}

char *cgit_hosturl()
{
	char *host, *port;

	host = getenv("HTTP_HOST");
	if (host) {
		host = xstrdup(host);
	} else {
		host = getenv("SERVER_NAME");
		if (!host)
			return NULL;
		port = getenv("SERVER_PORT");
		if (port && atoi(port) != 80)
			host = xstrdup(fmt("%s:%d", host, atoi(port)));
		else
			host = xstrdup(host);
	}
	return host;
}

char *cgit_rooturl()
{
	if (ctx.cfg.virtual_root)
		return fmt("%s/", ctx.cfg.virtual_root);
	else
		return ctx.cfg.script_name;
}

char *cgit_repourl(const char *reponame)
{
	if (ctx.cfg.virtual_root) {
		return fmt("%s/%s/", ctx.cfg.virtual_root, reponame);
	} else {
		return fmt("?r=%s", reponame);
	}
}

char *cgit_fileurl(const char *reponame, const char *pagename,
		   const char *filename, const char *query)
{
	char *tmp;
	char *delim;

	if (ctx.cfg.virtual_root) {
		tmp = fmt("%s/%s/%s/%s", ctx.cfg.virtual_root, reponame,
			  pagename, (filename ? filename:""));
		delim = "?";
	} else {
		tmp = fmt("?url=%s/%s/%s", reponame, pagename,
			  (filename ? filename : ""));
		delim = "&";
	}
	if (query)
		tmp = fmt("%s%s%s", tmp, delim, query);
	return tmp;
}

char *cgit_pageurl(const char *reponame, const char *pagename,
		   const char *query)
{
	return cgit_fileurl(reponame,pagename,0,query);
}

const char *cgit_repobasename(const char *reponame)
{
	/* I assume we don't need to store more than one repo basename */
	static char rvbuf[1024];
	int p;
	const char *rv;
	strncpy(rvbuf,reponame,sizeof(rvbuf));
	if(rvbuf[sizeof(rvbuf)-1])
		die("cgit_repobasename: truncated repository name '%s'", reponame);
	p = strlen(rvbuf)-1;
	/* strip trailing slashes */
	while(p && rvbuf[p]=='/') rvbuf[p--]=0;
	/* strip trailing .git */
	if(p>=3 && !strncmp(&rvbuf[p-3],".git",4)) {
		p -= 3; rvbuf[p--] = 0;
	}
	/* strip more trailing slashes if any */
	while( p && rvbuf[p]=='/') rvbuf[p--]=0;
	/* find last slash in the remaining string */
	rv = strrchr(rvbuf,'/');
	if(rv)
		return ++rv;
	return rvbuf;
}

char *cgit_currurl()
{
	if (!ctx.cfg.virtual_root)
		return ctx.cfg.script_name;
	else if (ctx.qry.page)
		return fmt("%s/%s/%s/", ctx.cfg.virtual_root, ctx.qry.repo, ctx.qry.page);
	else if (ctx.qry.repo)
		return fmt("%s/%s/", ctx.cfg.virtual_root, ctx.qry.repo);
	else
		return fmt("%s/", ctx.cfg.virtual_root);
}

static void site_url(char *page, char *search, int ofs)
{
	char *delim = "?";

	if (ctx.cfg.virtual_root) {
		html_attr(ctx.cfg.virtual_root);
		if (ctx.cfg.virtual_root[strlen(ctx.cfg.virtual_root) - 1] != '/')
			html("/");
	} else
		html(ctx.cfg.script_name);

	if (page) {
		htmlf("?p=%s", page);
		delim = "&";
	}
	if (search) {
		html(delim);
		html("q=");
		html_attr(search);
		delim = "&";
	}
	if (ofs) {
		html(delim);
		htmlf("ofs=%d", ofs);
	}
}

static void site_link(char *page, char *name, char *title, char *class,
		      char *search, int ofs)
{
	html("<a");
	if (title) {
		html(" title='");
		html_attr(title);
		html("'");
	}
	if (class) {
		html(" class='");
		html_attr(class);
		html("'");
	}
	html(" href='");
	site_url(page, search, ofs);
	html("'>");
	html_txt(name);
	html("</a>");
}

void cgit_index_link(char *name, char *title, char *class, char *pattern,
		     int ofs)
{
	site_link(NULL, name, title, class, pattern, ofs);
}

static char *repolink(char *title, char *class, char *page, char *head,
		      char *path)
{
	char *delim = "?";

	html("<a");
	if (title) {
		html(" title='");
		html_attr(title);
		html("'");
	}
	if (class) {
		html(" class='");
		html_attr(class);
		html("'");
	}
	html(" href='");
	if (ctx.cfg.virtual_root) {
		html_url_path(ctx.cfg.virtual_root);
		if (ctx.cfg.virtual_root[strlen(ctx.cfg.virtual_root) - 1] != '/')
			html("/");
		html_url_path(ctx.repo->url);
		if (ctx.repo->url[strlen(ctx.repo->url) - 1] != '/')
			html("/");
		if (page) {
			html_url_path(page);
			html("/");
			if (path)
				html_url_path(path);
		}
	} else {
		html(ctx.cfg.script_name);
		html("?url=");
		html_url_arg(ctx.repo->url);
		if (ctx.repo->url[strlen(ctx.repo->url) - 1] != '/')
			html("/");
		if (page) {
			html_url_arg(page);
			html("/");
			if (path)
				html_url_arg(path);
		}
		delim = "&amp;";
	}
	if (head && strcmp(head, ctx.repo->defbranch)) {
		html(delim);
		html("h=");
		html_url_arg(head);
		delim = "&amp;";
	}
	return fmt("%s", delim);
}

static void reporevlink(char *page, char *name, char *title, char *class,
			char *head, char *rev, char *path)
{
	char *delim;

	delim = repolink(title, class, page, head, path);
	if (rev && strcmp(rev, ctx.qry.head)) {
		html(delim);
		html("id=");
		html_url_arg(rev);
	}
	html("'>");
	html_txt(name);
	html("</a>");
}

void cgit_summary_link(char *name, char *title, char *class, char *head)
{
	reporevlink(NULL, name, title, class, head, NULL, NULL);
}

void cgit_tag_link(char *name, char *title, char *class, char *head,
		   char *rev)
{
	reporevlink("tag", name, title, class, head, rev, NULL);
}

void cgit_tree_link(char *name, char *title, char *class, char *head,
		    char *rev, char *path)
{
	reporevlink("tree", name, title, class, head, rev, path);
}

void cgit_plain_link(char *name, char *title, char *class, char *head,
		     char *rev, char *path)
{
	reporevlink("plain", name, title, class, head, rev, path);
}

void cgit_log_link(char *name, char *title, char *class, char *head,
		   char *rev, char *path, int ofs, char *grep, char *pattern)
{
	char *delim;

	delim = repolink(title, class, "log", head, path);
	if (rev && strcmp(rev, ctx.qry.head)) {
		html(delim);
		html("id=");
		html_url_arg(rev);
		delim = "&";
	}
	if (grep && pattern) {
		html(delim);
		html("qt=");
		html_url_arg(grep);
		delim = "&";
		html(delim);
		html("q=");
		html_url_arg(pattern);
	}
	if (ofs > 0) {
		html(delim);
		html("ofs=");
		htmlf("%d", ofs);
	}
	html("'>");
	html_txt(name);
	html("</a>");
}

void cgit_commit_link(char *name, char *title, char *class, char *head,
		      char *rev)
{
	if (strlen(name) > ctx.cfg.max_msg_len && ctx.cfg.max_msg_len >= 15) {
		name[ctx.cfg.max_msg_len] = '\0';
		name[ctx.cfg.max_msg_len - 1] = '.';
		name[ctx.cfg.max_msg_len - 2] = '.';
		name[ctx.cfg.max_msg_len - 3] = '.';
	}
	reporevlink("commit", name, title, class, head, rev, NULL);
}

void cgit_refs_link(char *name, char *title, char *class, char *head,
		    char *rev, char *path)
{
	reporevlink("refs", name, title, class, head, rev, path);
}

void cgit_snapshot_link(char *name, char *title, char *class, char *head,
			char *rev, char *archivename)
{
	reporevlink("snapshot", name, title, class, head, rev, archivename);
}

void cgit_diff_link(char *name, char *title, char *class, char *head,
		    char *new_rev, char *old_rev, char *path)
{
	char *delim;

	delim = repolink(title, class, "diff", head, path);
	if (new_rev && strcmp(new_rev, ctx.qry.head)) {
		html(delim);
		html("id=");
		html_url_arg(new_rev);
		delim = "&amp;";
	}
	if (old_rev) {
		html(delim);
		html("id2=");
		html_url_arg(old_rev);
	}
	html("'>");
	html_txt(name);
	html("</a>");
}

void cgit_patch_link(char *name, char *title, char *class, char *head,
		     char *rev)
{
	reporevlink("patch", name, title, class, head, rev, NULL);
}

void cgit_object_link(struct object *obj)
{
	char *page, *rev, *name;

	if (obj->type == OBJ_COMMIT) {
                cgit_commit_link(fmt("commit %s", sha1_to_hex(obj->sha1)), NULL, NULL,
				 ctx.qry.head, sha1_to_hex(obj->sha1));
		return;
	} else if (obj->type == OBJ_TREE)
		page = "tree";
	else if (obj->type == OBJ_TAG)
		page = "tag";
	else
		page = "blob";
	rev = sha1_to_hex(obj->sha1);
	name = fmt("%s %s", typename(obj->type), rev);
	reporevlink(page, name, NULL, NULL, ctx.qry.head, rev, NULL);
}

void cgit_print_date(time_t secs, char *format, int local_time)
{
	char buf[64];
	struct tm *time;

	if (!secs)
		return;
	if(local_time)
		time = localtime(&secs);
	else
		time = gmtime(&secs);
	strftime(buf, sizeof(buf)-1, format, time);
	html_txt(buf);
}

void cgit_print_age(time_t t, time_t max_relative, char *format)
{
	time_t now, secs;

	if (!t)
		return;
	time(&now);
	secs = now - t;

	if (secs > max_relative && max_relative >= 0) {
		cgit_print_date(t, format, ctx.cfg.local_time);
		return;
	}

	if (secs < TM_HOUR * 2) {
		htmlf("<span class='age-mins'>%.0f min.</span>",
		      secs * 1.0 / TM_MIN);
		return;
	}
	if (secs < TM_DAY * 2) {
		htmlf("<span class='age-hours'>%.0f hours</span>",
		      secs * 1.0 / TM_HOUR);
		return;
	}
	if (secs < TM_WEEK * 2) {
		htmlf("<span class='age-days'>%.0f days</span>",
		      secs * 1.0 / TM_DAY);
		return;
	}
	if (secs < TM_MONTH * 2) {
		htmlf("<span class='age-weeks'>%.0f weeks</span>",
		      secs * 1.0 / TM_WEEK);
		return;
	}
	if (secs < TM_YEAR * 2) {
		htmlf("<span class='age-months'>%.0f months</span>",
		      secs * 1.0 / TM_MONTH);
		return;
	}
	htmlf("<span class='age-years'>%.0f years</span>",
	      secs * 1.0 / TM_YEAR);
}

void cgit_print_http_headers(struct cgit_context *ctx)
{
	if (ctx->page.mimetype && ctx->page.charset)
		htmlf("Content-Type: %s; charset=%s\n", ctx->page.mimetype,
		      ctx->page.charset);
	else if (ctx->page.mimetype)
		htmlf("Content-Type: %s\n", ctx->page.mimetype);
	if (ctx->page.size)
		htmlf("Content-Length: %ld\n", ctx->page.size);
	if (ctx->page.filename)
		htmlf("Content-Disposition: inline; filename=\"%s\"\n",
		      ctx->page.filename);
	htmlf("Last-Modified: %s\n", http_date(ctx->page.modified));
	htmlf("Expires: %s\n", http_date(ctx->page.expires));
	html("\n");
}

void cgit_print_docstart(struct cgit_context *ctx)
{
	char *host = cgit_hosturl();
	html(cgit_doctype);
	html("<html xmlns='http://www.w3.org/1999/xhtml' xml:lang='en' lang='en'>\n");
	html("<head>\n");
	html("<title>");
	html_txt(ctx->page.title);
	html("</title>\n");
	htmlf("<meta name='generator' content='cgit %s'/>\n", cgit_version);
	if (ctx->cfg.robots && *ctx->cfg.robots)
		htmlf("<meta name='robots' content='%s'/>\n", ctx->cfg.robots);
	html("<link rel='stylesheet' type='text/css' href='");
	html_attr(ctx->cfg.css);
	html("'/>\n");
	if (ctx->cfg.favicon) {
		html("<link rel='shortcut icon' href='");
		html_attr(ctx->cfg.favicon);
		html("'/>\n");
	}
	if (host && ctx->repo) {
		html("<link rel='alternate' title='Atom feed' href='http://");
		html_attr(cgit_hosturl());
		html_attr(cgit_fileurl(ctx->repo->url, "atom", ctx->qry.path,
				       fmt("h=%s", ctx->qry.head)));
		html("' type='application/atom+xml'/>");
	}
	html("</head>\n");
	html("<body>\n");
}

void cgit_print_docend()
{
	html("</div>");
	if (ctx.cfg.footer)
		html_include(ctx.cfg.footer);
	else {
		htmlf("<div class='footer'>generated  by cgit %s at ",
			cgit_version);
		cgit_print_date(time(NULL), FMT_LONGDATE, ctx.cfg.local_time);
		html("</div>\n");
	}
	html("</body>\n</html>\n");
}

int print_branch_option(const char *refname, const unsigned char *sha1,
			int flags, void *cb_data)
{
	char *name = (char *)refname;
	html_option(name, name, ctx.qry.head);
	return 0;
}

int print_archive_ref(const char *refname, const unsigned char *sha1,
		       int flags, void *cb_data)
{
	struct tag *tag;
	struct taginfo *info;
	struct object *obj;
	char buf[256], *url;
	unsigned char fileid[20];
	int *header = (int *)cb_data;

	if (prefixcmp(refname, "refs/archives"))
		return 0;
	strncpy(buf, refname+14, sizeof(buf));
	obj = parse_object(sha1);
	if (!obj)
		return 1;
	if (obj->type == OBJ_TAG) {
		tag = lookup_tag(sha1);
		if (!tag || parse_tag(tag) || !(info = cgit_parse_tag(tag)))
			return 0;
		hashcpy(fileid, tag->tagged->sha1);
	} else if (obj->type != OBJ_BLOB) {
		return 0;
	} else {
		hashcpy(fileid, sha1);
	}
	if (!*header) {
		html("<h1>download</h1>\n");
		*header = 1;
	}
	url = cgit_pageurl(ctx.qry.repo, "blob",
			   fmt("id=%s&amp;path=%s", sha1_to_hex(fileid),
			       buf));
	html_link_open(url, NULL, "menu");
	html_txt(strlpart(buf, 20));
	html_link_close();
	return 0;
}

void add_hidden_formfields(int incl_head, int incl_search, char *page)
{
	char *url;

	if (!ctx.cfg.virtual_root) {
		url = fmt("%s/%s", ctx.qry.repo, page);
		if (ctx.qry.path)
			url = fmt("%s/%s", url, ctx.qry.path);
		html_hidden("url", url);
	}

	if (incl_head && ctx.qry.head && ctx.repo->defbranch &&
	    strcmp(ctx.qry.head, ctx.repo->defbranch))
		html_hidden("h", ctx.qry.head);

	if (ctx.qry.sha1)
		html_hidden("id", ctx.qry.sha1);
	if (ctx.qry.sha2)
		html_hidden("id2", ctx.qry.sha2);

	if (incl_search) {
		if (ctx.qry.grep)
			html_hidden("qt", ctx.qry.grep);
		if (ctx.qry.search)
			html_hidden("q", ctx.qry.search);
	}
}

char *hc(struct cgit_cmd *cmd, const char *page)
{
	return (strcmp(cmd->name, page) ? NULL : "active");
}

void cgit_print_pageheader(struct cgit_context *ctx)
{
	struct cgit_cmd *cmd = cgit_get_cmd(ctx);

	html("<table id='header'>\n");
	html("<tr>\n");
	html("<td class='logo' rowspan='2'><a href='");
	if (ctx->cfg.logo_link)
		html_attr(ctx->cfg.logo_link);
	else
		html_attr(cgit_rooturl());
	html("'><img src='");
	html_attr(ctx->cfg.logo);
	html("' alt='cgit logo'/></a></td>\n");

	html("<td class='main'>");
	if (ctx->repo) {
		cgit_index_link("index", NULL, NULL, NULL, 0);
		html(" : ");
		cgit_summary_link(ctx->repo->name, ctx->repo->name, NULL, NULL);
		html("</td><td class='form'>");
		html("<form method='get' action=''>\n");
		add_hidden_formfields(0, 1, ctx->qry.page);
		html("<select name='h' onchange='this.form.submit();'>\n");
		for_each_branch_ref(print_branch_option, ctx->qry.head);
		html("</select> ");
		html("<input type='submit' name='' value='switch'/>");
		html("</form>");
	} else
		html_txt(ctx->cfg.root_title);
	html("</td></tr>\n");

	html("<tr><td class='sub'>");
	if (ctx->repo) {
		html_txt(ctx->repo->desc);
		html("</td><td class='sub right'>");
		html_txt(ctx->repo->owner);
	} else {
		if (ctx->cfg.root_desc)
			html_txt(ctx->cfg.root_desc);
		else if (ctx->cfg.index_info)
			html_include(ctx->cfg.index_info);
	}
	html("</td></tr></table>\n");

	html("<table class='tabs'><tr><td>\n");
	if (ctx->repo) {
		cgit_summary_link("summary", NULL, NULL, ctx->qry.head);
		cgit_refs_link("refs", NULL, hc(cmd, "refs"), ctx->qry.head,
			       ctx->qry.sha1, NULL);
		cgit_log_link("log", NULL, hc(cmd, "log"), ctx->qry.head,
			      NULL, NULL, 0, NULL, NULL);
		cgit_tree_link("tree", NULL, hc(cmd, "tree"), ctx->qry.head,
			       ctx->qry.sha1, NULL);
		cgit_commit_link("commit", NULL, hc(cmd, "commit"),
				 ctx->qry.head, ctx->qry.sha1);
		cgit_diff_link("diff", NULL, hc(cmd, "diff"), ctx->qry.head,
			       ctx->qry.sha1, ctx->qry.sha2, NULL);
		if (ctx->repo->readme)
			reporevlink("about", "about", NULL,
				    hc(cmd, "about"), ctx->qry.head, NULL,
				    NULL);
		html("</td><td class='form'>");
		html("<form class='right' method='get' action='");
		if (ctx->cfg.virtual_root)
			html_url_path(cgit_fileurl(ctx->qry.repo, "log",
						   ctx->qry.path, NULL));
		html("'>\n");
		add_hidden_formfields(1, 0, "log");
		html("<select name='qt'>\n");
		html_option("grep", "log msg", ctx->qry.grep);
		html_option("author", "author", ctx->qry.grep);
		html_option("committer", "committer", ctx->qry.grep);
		html("</select>\n");
		html("<input class='txt' type='text' size='10' name='q' value='");
		html_attr(ctx->qry.search);
		html("'/>\n");
		html("<input type='submit' value='search'/>\n");
		html("</form>\n");
	} else {
		site_link(NULL, "index", NULL, hc(cmd, "repolist"), NULL, 0);
		if (ctx->cfg.root_readme)
			site_link("about", "about", NULL, hc(cmd, "about"),
				  NULL, 0);
		html("</td><td class='form'>");
		html("<form method='get' action='");
		html_attr(cgit_rooturl());
		html("'>\n");
		html("<input type='text' name='q' size='10' value='");
		html_attr(ctx->qry.search);
		html("'/>\n");
		html("<input type='submit' value='search'/>\n");
		html("</form>");
	}
	html("</td></tr></table>\n");
	html("<div class='content'>");
}

void cgit_print_filemode(unsigned short mode)
{
	if (S_ISDIR(mode))
		html("d");
	else if (S_ISLNK(mode))
		html("l");
	else if (S_ISGITLINK(mode))
		html("m");
	else
		html("-");
	html_fileperm(mode >> 6);
	html_fileperm(mode >> 3);
	html_fileperm(mode);
}

void cgit_print_snapshot_links(const char *repo, const char *head,
			       const char *hex, int snapshots)
{
	const struct cgit_snapshot_format* f;
    	char *filename;

	for (f = cgit_snapshot_formats; f->suffix; f++) {
		if (!(snapshots & f->bit))
			continue;
		filename = fmt("%s-%s%s", cgit_repobasename(repo), hex,
			       f->suffix);
		cgit_snapshot_link(filename, NULL, NULL, (char *)head,
				   (char *)hex, filename);
		html("<br/>");
	}
}