all repos — cgit @ f3c1a187fe2bc33f8423cd535d5045899699995b

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 "html.h"
 11
 12const 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
 16static 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
 29void cgit_print_error(char *msg)
 30{
 31	html("<div class='error'>");
 32	html_txt(msg);
 33	html("</div>\n");
 34}
 35
 36char *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
 44char *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
 53char *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
 73char *cgit_pageurl(const char *reponame, const char *pagename,
 74		   const char *query)
 75{
 76	return cgit_fileurl(reponame,pagename,0,query);
 77}
 78
 79const 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
104char *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
116static 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
169static 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
185void 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
191void 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
222void 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
234void 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
240void 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
246void 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
268void 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
274void 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
301void 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
313void 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
356void 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
371void 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
389void cgit_print_docend()
390{
391	html("</td>\n</tr>\n</table>\n</body>\n</html>\n");
392}
393
394int 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
402int 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
441void 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
468void 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
561void 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/* vim:set sw=8: */