all repos — cgit @ 25105d7ecaba474d4b7c364ebb586aac3dfc5abb

a hyperfast web frontend for git written in c

cgit.c (view raw)

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