all repos — cgit @ 7640d90b73c01b16bb04ce4c541f52cbaae5f82a

a hyperfast web frontend for git written in c

cgit.c (view raw)

  1/* cgit.c: cgi for the git scm
  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
 11static const char cgit_doctype[] =
 12"<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\"\n"
 13"  \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n";
 14
 15static const char cgit_error[] =
 16"<div class='error'>%s</div>";
 17
 18static const char cgit_lib_error[] =
 19"<div class='error'>%s: %s</div>";
 20
 21int htmlfd = 0;
 22
 23char *cgit_root         = "/usr/src/git";
 24char *cgit_root_title   = "Git repository browser";
 25char *cgit_css          = "/cgit.css";
 26char *cgit_logo         = "/git-logo.png";
 27char *cgit_logo_link    = "http://www.kernel.org/pub/software/scm/git/docs/";
 28char *cgit_virtual_root = NULL;
 29
 30char *cgit_cache_root   = "/var/cache/cgit";
 31
 32int cgit_cache_root_ttl        =  5;
 33int cgit_cache_repo_ttl        =  5;
 34int cgit_cache_dynamic_ttl     =  5;
 35int cgit_cache_static_ttl      = -1;
 36int cgit_cache_max_create_time =  5;
 37
 38char *cgit_repo_name    = NULL;
 39char *cgit_repo_desc    = NULL;
 40char *cgit_repo_owner   = NULL;
 41
 42int cgit_query_has_symref = 0;
 43int cgit_query_has_sha1   = 0;
 44
 45char *cgit_querystring  = NULL;
 46char *cgit_query_repo   = NULL;
 47char *cgit_query_page   = NULL;
 48char *cgit_query_head   = NULL;
 49char *cgit_query_sha1   = NULL;
 50
 51struct cacheitem cacheitem;
 52
 53int cgit_parse_query(char *txt, configfn fn)
 54{
 55	char *t, *value = NULL, c;
 56
 57	if (!txt)
 58		return 0;
 59
 60	t = txt = xstrdup(txt);
 61 
 62	while((c=*t) != '\0') {
 63		if (c=='=') {
 64			*t = '\0';
 65			value = t+1;
 66		} else if (c=='&') {
 67			*t = '\0';
 68			(*fn)(txt, value);
 69			txt = t+1;
 70			value = NULL;
 71		}
 72		t++;
 73	}
 74	if (t!=txt)
 75		(*fn)(txt, value);
 76	return 0;
 77}
 78
 79void cgit_global_config_cb(const char *name, const char *value)
 80{
 81	if (!strcmp(name, "root"))
 82		cgit_root = xstrdup(value);
 83	else if (!strcmp(name, "root-title"))
 84		cgit_root_title = xstrdup(value);
 85	else if (!strcmp(name, "css"))
 86		cgit_css = xstrdup(value);
 87	else if (!strcmp(name, "logo"))
 88		cgit_logo = xstrdup(value);
 89	else if (!strcmp(name, "logo-link"))
 90		cgit_logo_link = xstrdup(value);
 91	else if (!strcmp(name, "virtual-root"))
 92		cgit_virtual_root = xstrdup(value);
 93}
 94
 95void cgit_repo_config_cb(const char *name, const char *value)
 96{
 97	if (!strcmp(name, "name"))
 98		cgit_repo_name = xstrdup(value);
 99	else if (!strcmp(name, "desc"))
100		cgit_repo_desc = xstrdup(value);
101	else if (!strcmp(name, "owner"))
102		cgit_repo_owner = xstrdup(value);
103}
104
105void cgit_querystring_cb(const char *name, const char *value)
106{
107	if (!strcmp(name,"r"))
108		cgit_query_repo = xstrdup(value);
109	else if (!strcmp(name, "p"))
110		cgit_query_page = xstrdup(value);
111	else if (!strcmp(name, "h")) {
112		cgit_query_head = xstrdup(value);
113		cgit_query_has_symref = 1;
114	} else if (!strcmp(name, "id")) {
115		cgit_query_sha1 = xstrdup(value);
116		cgit_query_has_sha1 = 1;
117	}
118}
119
120char *cgit_repourl(const char *reponame)
121{
122	if (cgit_virtual_root) {
123		return fmt("%s/%s/", cgit_virtual_root, reponame);
124	} else {
125		return fmt("?r=%s", reponame);
126	}
127}
128
129char *cgit_pageurl(const char *reponame, const char *pagename, 
130		   const char *query)
131{
132	if (cgit_virtual_root) {
133		return fmt("%s/%s/%s/?%s", cgit_virtual_root, reponame, 
134			   pagename, query);
135	} else {
136		return fmt("?r=%s&p=%s&%s", reponame, pagename, query);
137	}
138}
139
140static int cgit_print_branch_cb(const char *refname, const unsigned char *sha1,
141				int flags, void *cb_data)
142{
143	struct commit *commit;
144	char buf[256], *url;
145
146	commit = lookup_commit(sha1);
147	if (commit && !parse_commit(commit)){
148		html("<tr><td>");
149		url = cgit_pageurl(cgit_query_repo, "log", 
150				   fmt("h=%s", refname));
151		html_link_open(url, NULL, NULL);
152		strncpy(buf, refname, sizeof(buf));
153		html_txt(buf);
154		html_link_close();
155		html("</td><td>");
156		pretty_print_commit(CMIT_FMT_ONELINE, commit, ~0, buf,
157				    sizeof(buf), 0, NULL, NULL, 0);
158		html_txt(buf);
159		html("</td></tr>\n");
160	} else {
161		html("<tr><td>");
162		html_txt(buf);
163		html("</td><td>");
164		htmlf("*** bad ref %s", sha1_to_hex(sha1));
165		html("</td></tr>\n");
166	}
167	return 0;
168}
169
170/* Sun, 06 Nov 1994 08:49:37 GMT */
171static char *http_date(time_t t)
172{
173	static char day[][4] = {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"};
174	static char month[][4] = {"Jan", "Feb", "Mar", "Apr", "May", "Jun",
175				  "Jul", "Aug", "Sep", "Oct", "Now", "Dec"};
176	struct tm *tm = gmtime(&t);
177	return fmt("%s, %02d %s %04d %02d:%02d:%02d GMT", day[tm->tm_wday],
178		   tm->tm_mday, month[tm->tm_mon], 1900+tm->tm_year,
179		   tm->tm_hour, tm->tm_min, tm->tm_sec);
180}
181
182static int ttl_seconds(int ttl)
183{
184	if (ttl<0)
185		return 60 * 60 * 24 * 365;
186	else 
187		return ttl * 60;
188}
189
190static void cgit_print_docstart(char *title)
191{
192	html("Content-Type: text/html; charset=utf-8\n");
193	htmlf("Last-Modified: %s\n", http_date(cacheitem.st.st_mtime));
194	htmlf("Expires: %s\n", http_date(cacheitem.st.st_mtime + 
195					 ttl_seconds(cacheitem.ttl)));
196	html("\n");
197	html(cgit_doctype);
198	html("<html>\n");
199	html("<head>\n");
200	html("<title>");
201	html_txt(title);
202	html("</title>\n");
203	html("<link rel='stylesheet' type='text/css' href='");
204	html_attr(cgit_css);
205	html("'/>\n");
206	html("</head>\n");
207	html("<body>\n");
208}
209
210static void cgit_print_docend()
211{
212	html("</body>\n</html>\n");
213}
214
215static void cgit_print_pageheader(char *title)
216{
217	html("<div id='header'>");
218	htmlf("<a href='%s'>", cgit_logo_link);
219	htmlf("<img id='logo' src='%s'/>\n", cgit_logo);
220	htmlf("</a>");
221	html_txt(title);
222	html("</div>");
223}
224
225static void cgit_print_repolist()
226{
227	DIR *d;
228	struct dirent *de;
229	struct stat st;
230	char *name;
231
232	chdir(cgit_root);
233	cgit_print_docstart(cgit_root_title);
234	cgit_print_pageheader(cgit_root_title);
235
236	if (!(d = opendir("."))) {
237		htmlf(cgit_lib_error, "Unable to scan repository directory",
238		      strerror(errno));
239		cgit_print_docend();
240		return;
241	}
242
243	html("<h2>Repositories</h2>\n");
244	html("<table class='list'>");
245	html("<tr><th>Name</th><th>Description</th><th>Owner</th></tr>\n");
246	while ((de = readdir(d)) != NULL) {
247		if (de->d_name[0] == '.')
248			continue;
249		if (stat(de->d_name, &st) < 0)
250			continue;
251		if (!S_ISDIR(st.st_mode))
252			continue;
253
254		cgit_repo_name = cgit_repo_desc = cgit_repo_owner = NULL;
255		name = fmt("%s/info/cgit", de->d_name);
256		if (cgit_read_config(name, cgit_repo_config_cb))
257			continue;
258
259		html("<tr><td>");
260		html_link_open(cgit_repourl(de->d_name), NULL, NULL);
261		html_txt(cgit_repo_name);
262		html_link_close();
263		html("</td><td>");
264		html_txt(cgit_repo_desc);
265		html("</td><td>");
266		html_txt(cgit_repo_owner);
267		html("</td></tr>\n");
268	}
269	closedir(d);
270	html("</table>");
271	cgit_print_docend();
272}
273
274static void cgit_print_branches()
275{
276	html("<table class='list'>");
277	html("<tr><th>Branch name</th><th>Head commit</th></tr>\n");
278	for_each_branch_ref(cgit_print_branch_cb, NULL);
279	html("</table>");
280}
281
282static int get_one_line(char *txt)
283{
284	char *t;
285
286	for(t=txt; *t != '\n' && t != '\0'; t++)
287		;
288	*t = '\0';
289	return t-txt-1;
290}
291
292static void cgit_print_commit_shortlog(struct commit *commit)
293{
294	char *h, *t, *p; 
295	char *tree = NULL, *author = NULL, *subject = NULL;
296	int len;
297	time_t sec;
298	struct tm *time;
299	char buf[32];
300
301	h = t = commit->buffer;
302	
303	if (strncmp(h, "tree ", 5))
304		die("Bad commit format: %s", 
305		    sha1_to_hex(commit->object.sha1));
306	
307	len = get_one_line(h);
308	tree = h+5;
309	h += len + 2;
310
311	while (!strncmp(h, "parent ", 7))
312		h += get_one_line(h) + 2;
313	
314	if (!strncmp(h, "author ", 7)) {
315		author = h+7;
316		h += get_one_line(h) + 2;
317		t = author;
318		while(t!=h && *t!='<') 
319			t++;
320		*t='\0';
321		p = t;
322		while(--t!=author && *t==' ')
323			*t='\0';
324		while(++p!=h && *p!='>')
325			;
326		while(++p!=h && !isdigit(*p))
327			;
328
329		t = p;
330		while(++p && isdigit(*p))
331			;
332		*p = '\0';
333		sec = atoi(t);
334		time = gmtime(&sec);
335	}
336
337	while((len = get_one_line(h)) > 0)
338		h += len+2;
339
340	h++;
341	len = get_one_line(h);
342
343	subject = h;
344
345	html("<tr><td>");
346	strftime(buf, sizeof(buf), "%Y-%m-%d %H:%M:%S", time);
347	html_txt(buf);
348	html("</td><td>");
349	char *qry = fmt("id=%s", sha1_to_hex(commit->object.sha1));
350	char *url = cgit_pageurl(cgit_query_repo, "view", qry);
351	html_link_open(url, NULL, NULL);
352	html_txt(subject);
353	html_link_close();
354	html("</td><td>");
355	html_txt(author);
356	html("</td></tr>\n");
357}
358
359static void cgit_print_log(const char *tip, int ofs, int cnt)
360{
361	struct rev_info rev;
362	struct commit *commit;
363	const char *argv[2] = {NULL, tip};
364	int n = 0;
365	
366	init_revisions(&rev, NULL);
367	rev.abbrev = DEFAULT_ABBREV;
368	rev.commit_format = CMIT_FMT_DEFAULT;
369	rev.verbose_header = 1;
370	rev.show_root_diff = 0;
371	setup_revisions(2, argv, &rev, NULL);
372	prepare_revision_walk(&rev);
373
374	html("<h2>Log</h2>");
375	html("<table class='list'>");
376	html("<tr><th>Date</th><th>Message</th><th>Author</th></tr>\n");
377	while ((commit = get_revision(&rev)) != NULL && n++ < 100) {
378		cgit_print_commit_shortlog(commit);
379		free(commit->buffer);
380		commit->buffer = NULL;
381		free_commit_list(commit->parents);
382		commit->parents = NULL;
383	}
384	html("</table>\n");
385}
386
387static void cgit_print_repo_summary()
388{
389	html("<h2>");
390	html_txt("Repo summary page");
391	html("</h2>");
392	cgit_print_branches();
393}
394
395static void cgit_print_object(char *hex)
396{
397	unsigned char sha1[20];
398	//struct object *object;
399	char type[20];
400	unsigned char *buf;
401	unsigned long size;
402
403	if (get_sha1_hex(hex, sha1)){
404		htmlf(cgit_error, "Bad hex value");
405	        return;
406	}
407
408	if (sha1_object_info(sha1, type, NULL)){
409		htmlf(cgit_error, "Bad object name");
410		return;
411	}
412
413	buf = read_sha1_file(sha1, type, &size);
414	if (!buf) {
415		htmlf(cgit_error, "Error reading object");
416		return;
417	}
418
419	buf[size] = '\0';
420	html("<h2>Object view</h2>");
421	htmlf("sha1=%s<br/>type=%s<br/>size=%i<br/>", hex, type, size);
422	html("<pre>");
423	html_txt(buf);
424	html("</pre>");
425}
426
427static void cgit_print_repo_page()
428{
429	if (chdir(fmt("%s/%s", cgit_root, cgit_query_repo)) || 
430	    cgit_read_config("info/cgit", cgit_repo_config_cb)) {
431		char *title = fmt("%s - %s", cgit_root_title, "Bad request");
432		cgit_print_docstart(title);
433		cgit_print_pageheader(title);
434		htmlf(cgit_lib_error, "Unable to scan repository",
435		      strerror(errno));
436		cgit_print_docend();
437		return;
438	}
439	setenv("GIT_DIR", fmt("%s/%s", cgit_root, cgit_query_repo), 1);
440	char *title = fmt("%s - %s", cgit_repo_name, cgit_repo_desc);
441	cgit_print_docstart(title);
442	cgit_print_pageheader(title);
443	if (!cgit_query_page)
444		cgit_print_repo_summary();
445	else if (!strcmp(cgit_query_page, "log")) {
446		cgit_print_log(cgit_query_head, 0, 100);
447	} else if (!strcmp(cgit_query_page, "view")) {
448		cgit_print_object(cgit_query_sha1);
449	}
450	cgit_print_docend();
451}
452
453static void cgit_fill_cache(struct cacheitem *item)
454{
455	htmlfd = item->fd;
456	item->st.st_mtime = time(NULL);
457	if (cgit_query_repo)
458		cgit_print_repo_page();
459	else
460		cgit_print_repolist();
461}
462
463static void cgit_refresh_cache(struct cacheitem *item)
464{
465 top:
466	if (!cache_lookup(item)) {
467		if (cache_lock(item)) {
468			cgit_fill_cache(item);
469			cache_unlock(item);
470		} else {
471			sched_yield();
472			goto top;
473		}
474	} else if (cache_expired(item)) {
475		if (cache_lock(item)) {
476			cgit_fill_cache(item);
477			cache_unlock(item);
478		}
479	}
480}
481
482static void cgit_print_cache(struct cacheitem *item)
483{
484	static char buf[4096];
485	ssize_t i;
486
487	int fd = open(item->name, O_RDONLY);
488	if (fd<0)
489		die("Unable to open cached file %s", item->name);
490
491	while((i=read(fd, buf, sizeof(buf))) > 0)
492		write(STDOUT_FILENO, buf, i);
493
494	close(fd);
495}
496
497int main(int argc, const char **argv)
498{
499	cgit_read_config("/etc/cgitrc", cgit_global_config_cb);
500	cgit_querystring = xstrdup(getenv("QUERY_STRING"));
501	cgit_parse_query(cgit_querystring, cgit_querystring_cb);
502	cgit_refresh_cache(&cacheitem);
503	cgit_print_cache(&cacheitem);
504	return 0;
505}