diff options
Diffstat (limited to '')
| -rw-r--r-- | Makefile | 24 | ||||
| -rw-r--r-- | cgit.c | 410 | ||||
| -rw-r--r-- | cgit.css | 63 | ||||
| -rw-r--r-- | cgit.h | 21 | ||||
| -rw-r--r-- | config.c | 73 | ||||
| -rw-r--r-- | git.h | 399 | ||||
| -rw-r--r-- | html.c | 100 | 
7 files changed, 1090 insertions, 0 deletions
| diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..1470c0a --- /dev/null +++ b/Makefile @@ -0,0 +1,24 @@ + +INSTALL_BIN = /var/www/htdocs/cgit.cgi +INSTALL_CSS = /var/www/htdocs/cgit.css + +EXTLIBS = ../git/libgit.a ../git/xdiff/lib.a -lz -lcrypto +OBJECTS = cgit.o config.o html.o + +all: cgit + +install: all +	install cgit $(INSTALL_BIN) +	install cgit.css $(INSTALL_CSS) + +clean: +	rm -f cgit *.o + +cgit: $(OBJECTS) +	$(CC) -o cgit $(OBJECTS) $(EXTLIBS) + +cgit.o: cgit.h git.h config.o html.o + +config.o: cgit.h git.h html.c + +html.o: cgit.h git.h html.c @@ -0,0 +1,410 @@ +#include "cgit.h" + +static 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 const char cgit_error[] = +"<div class='error'>%s</div>"; + +static const char cgit_lib_error[] = +"<div class='error'>%s: %s</div>"; + + +char *cgit_root         = "/var/git"; +char *cgit_root_title   = "Git repository browser"; +char *cgit_css          = "/cgit.css"; +char *cgit_logo         = "/git-logo.png"; +char *cgit_logo_link    = "http://www.kernel.org/pub/software/scm/git/docs/"; +char *cgit_virtual_root = NULL; + +char *cgit_repo_name    = NULL; +char *cgit_repo_desc    = NULL; +char *cgit_repo_owner   = NULL; + +char *cgit_query_repo   = NULL; +char *cgit_query_page   = NULL; +char *cgit_query_head   = NULL; + +int cgit_parse_query(char *txt, configfn fn) +{ +	char *t = txt, *value = NULL, c; + +	if (!txt) +		return 0; + +	while((c=*t) != '\0') { +		if (c=='=') { +			*t = '\0'; +			value = t+1; +		} else if (c=='&') { +			*t = '\0'; +			(*fn)(txt, value); +			txt = t+1; +			value = NULL; +		} +		t++; +	} +	if (t!=txt) +		(*fn)(txt, value); +	return 0; +} + +void cgit_global_config_cb(const char *name, const char *value) +{ +	if (!strcmp(name, "root")) +		cgit_root = xstrdup(value); +	else if (!strcmp(name, "root-title")) +		cgit_root_title = xstrdup(value); +	else if (!strcmp(name, "css")) +		cgit_css = xstrdup(value); +	else if (!strcmp(name, "logo")) +		cgit_logo = xstrdup(value); +	else if (!strcmp(name, "logo-link")) +		cgit_logo_link = xstrdup(value); +	else if (!strcmp(name, "virtual-root")) +		cgit_virtual_root = xstrdup(value); +} + +void cgit_repo_config_cb(const char *name, const char *value) +{ +	if (!strcmp(name, "name")) +		cgit_repo_name = xstrdup(value); +	else if (!strcmp(name, "desc")) +		cgit_repo_desc = xstrdup(value); +	else if (!strcmp(name, "owner")) +		cgit_repo_owner = xstrdup(value); +} + +void cgit_querystring_cb(const char *name, const char *value) +{ +	if (!strcmp(name,"r")) +		cgit_query_repo = xstrdup(value); +	else if (!strcmp(name, "p")) +		cgit_query_page = xstrdup(value); +	else if (!strcmp(name, "h")) +		cgit_query_head = xstrdup(value); +} + +char *cgit_repourl(const char *reponame) +{ +	if (cgit_virtual_root) { +		return fmt("%s/%s/", cgit_virtual_root, reponame); +	} else { +		return fmt("?r=%s", reponame); +	} +} + +char *cgit_pageurl(const char *reponame, const char *pagename,  +		   const char *query) +{ +	if (cgit_virtual_root) { +		return fmt("%s/%s/%s/?%s", cgit_virtual_root, reponame,  +			   pagename, query); +	} else { +		return fmt("?r=%s&p=%s&%s", reponame, pagename, query); +	} +} + +static int cgit_print_branch_cb(const char *refname, const unsigned char *sha1, +				int flags, void *cb_data) +{ +	struct commit *commit; +	char buf[256], *url; + +	commit = lookup_commit(sha1); +	if (commit && !parse_commit(commit)){ +		html("<tr><td>"); +		url = cgit_pageurl(cgit_query_repo, "log",  +				   fmt("h=%s", refname)); +		html_link_open(url, NULL, NULL); +		strncpy(buf, refname, sizeof(buf)); +		html_txt(buf); +		html_link_close(); +		html("</td><td>"); +		pretty_print_commit(CMIT_FMT_ONELINE, commit, ~0, buf, +				    sizeof(buf), 0, NULL, NULL, 0); +		html_txt(buf); +		html("</td></tr>\n"); +	} else { +		html("<tr><td>"); +		html_txt(buf); +		html("</td><td>"); +		htmlf("*** bad ref %s", sha1_to_hex(sha1)); +		html("</td></tr>\n"); +	} +	return 0; +} + +static void cgit_print_docstart(char *title) +{ +	html("Content-Type: text/html; charset=utf-8\n"); +	html("\n"); +	html(cgit_doctype); +	html("<html>\n"); +	html("<head>\n"); +	html("<title>"); +	html_txt(title); +	html("</title>\n"); +	html("<link rel='stylesheet' type='text/css' href='"); +	html_attr(cgit_css); +	html("'/>\n"); +	html("</head>\n"); +	html("<body>\n"); +} + +static void cgit_print_docend() +{ +	html("</body>\n</html>\n"); +} + +static void cgit_print_pageheader(char *title) +{ +	html("<div id='header'>"); +	htmlf("<a href='%s'>", cgit_logo_link); +	htmlf("<img id='logo' src='%s'/>\n", cgit_logo); +	htmlf("</a>"); +	html_txt(title); +	html("</div>"); +} + +static void cgit_print_repolist() +{ +	DIR *d; +	struct dirent *de; +	struct stat st; +	char *name; + +	cgit_print_docstart(cgit_root_title); +	cgit_print_pageheader(cgit_root_title); + +	if (!(d = opendir("."))) { +		htmlf(cgit_lib_error, "Unable to scan repository directory", +		      strerror(errno)); +		cgit_print_docend(); +		return; +	} + +	html("<h2>Repositories</h2>\n"); +	html("<table class='list'>"); +	html("<tr><th>Name</th><th>Description</th><th>Owner</th></tr>\n"); +	while ((de = readdir(d)) != NULL) { +		if (de->d_name[0] == '.') +			continue; +		if (stat(de->d_name, &st) < 0) +			continue; +		if (!S_ISDIR(st.st_mode)) +			continue; + +		cgit_repo_name = cgit_repo_desc = cgit_repo_owner = NULL; +		name = fmt("%s/.git/info/cgit", de->d_name); +		if (cgit_read_config(name, cgit_repo_config_cb)) +			continue; + +		html("<tr><td>"); +		html_link_open(cgit_repourl(de->d_name), NULL, NULL); +		html_txt(cgit_repo_name); +		html_link_close(); +		html("</td><td>"); +		html_txt(cgit_repo_desc); +		html("</td><td>"); +		html_txt(cgit_repo_owner); +		html("</td></tr>\n"); +	} +	closedir(d); +	html("</table>"); +	cgit_print_docend(); +} + +static void cgit_print_branches() +{ +	html("<table class='list'>"); +	html("<tr><th>Branch name</th><th>Head commit</th></tr>\n"); +	for_each_branch_ref(cgit_print_branch_cb, NULL); +	html("</table>"); +} + +static int get_one_line(char *txt) +{ +	char *t; + +	for(t=txt; *t != '\n' && t != '\0'; t++) +		; +	*t = '\0'; +	return t-txt-1; +} + +static void cgit_print_commit_shortlog(struct commit *commit) +{ +	char *h, *t, *p;  +	char *tree = NULL, *author = NULL, *subject = NULL; +	int len; +	time_t sec; +	struct tm *time; +	char buf[32]; + +	h = t = commit->buffer; +	 +	if (strncmp(h, "tree ", 5)) +		die("Bad commit format: %s",  +		    sha1_to_hex(commit->object.sha1)); +	 +	len = get_one_line(h); +	tree = h+5; +	h += len + 2; + +	while (!strncmp(h, "parent ", 7)) +		h += get_one_line(h) + 2; +	 +	if (!strncmp(h, "author ", 7)) { +		author = h+7; +		h += get_one_line(h) + 2; +		t = author; +		while(t!=h && *t!='<')  +			t++; +		*t='\0'; +		p = t; +		while(--t!=author && *t==' ') +			*t='\0'; +		while(++p!=h && *p!='>') +			; +		while(++p!=h && !isdigit(*p)) +			; + +		t = p; +		while(++p && isdigit(*p)) +			; +		*p = '\0'; +		sec = atoi(t); +		time = gmtime(&sec); +	} + +	while((len = get_one_line(h)) > 0) +		h += len+2; + +	h++; +	len = get_one_line(h); + +	subject = h; + +	html("<tr><td>"); +	strftime(buf, sizeof(buf), "%Y-%m-%d %H:%M:%S", time); +	html_txt(buf); +	html("</td><td>"); +	char *qry = fmt("h=%s", sha1_to_hex(commit->object.sha1)); +	char *url = cgit_pageurl(cgit_query_repo, "view", qry); +	html_link_open(url, NULL, NULL); +	html_txt(subject); +	html_link_close(); +	html("</td><td>"); +	html_txt(author); +	html("</td></tr>\n"); +} + +static void cgit_print_log(const char *tip, int ofs, int cnt) +{ +	struct rev_info rev; +	struct commit *commit; +	const char *argv[2] = {NULL, tip}; +	int n = 0; +	 +	init_revisions(&rev, NULL); +	rev.abbrev = DEFAULT_ABBREV; +	rev.commit_format = CMIT_FMT_DEFAULT; +	rev.verbose_header = 1; +	rev.show_root_diff = 0; +	setup_revisions(2, argv, &rev, NULL); +	prepare_revision_walk(&rev); + +	html("<h2>Log</h2>"); +	html("<table class='list'>"); +	html("<tr><th>Date</th><th>Message</th><th>Author</th></tr>\n"); +	while ((commit = get_revision(&rev)) != NULL && n++ < 100) { +		cgit_print_commit_shortlog(commit); +		free(commit->buffer); +		commit->buffer = NULL; +		free_commit_list(commit->parents); +		commit->parents = NULL; +	} +	html("</table>\n"); +} + +static void cgit_print_repo_summary() +{ +	html("<h2>"); +	html_txt("Repo summary page"); +	html("</h2>"); +	cgit_print_branches(); +} + +static void cgit_print_object(char *hex) +{ +	unsigned char sha1[20]; +	//struct object *object; +	char type[20]; +	unsigned char *buf; +	unsigned long size; + +	if (get_sha1_hex(hex, sha1)){ +		htmlf(cgit_error, "Bad hex value"); +	        return; +	} + +	if (sha1_object_info(sha1, type, NULL)){ +		htmlf(cgit_error, "Bad object name"); +		return; +	} + +	buf = read_sha1_file(sha1, type, &size); +	if (!buf) { +		htmlf(cgit_error, "Error reading object"); +		return; +	} + +	buf[size] = '\0'; +	html("<h2>Object view</h2>"); +	htmlf("sha1=%s<br/>type=%s<br/>size=%i<br/>", hex, type, size); +	html("<pre>"); +	html_txt(buf); +	html("</pre>"); +} + +static void cgit_print_repo_page() +{ +	if (chdir(cgit_query_repo) ||  +	    cgit_read_config(".git/info/cgit", cgit_repo_config_cb)) { +		char *title = fmt("%s - %s", cgit_root_title, "Bad request"); +		cgit_print_docstart(title); +		cgit_print_pageheader(title); +		htmlf(cgit_lib_error, "Unable to scan repository", +		      strerror(errno)); +		cgit_print_docend(); +		return; +	} +	 +	char *title = fmt("%s - %s", cgit_repo_name, cgit_repo_desc); +	cgit_print_docstart(title); +	cgit_print_pageheader(title); +	if (!cgit_query_page) +		cgit_print_repo_summary(); +	else if (!strcmp(cgit_query_page, "log")) { +		cgit_print_log(cgit_query_head, 0, 100); +	} else if (!strcmp(cgit_query_page, "view")) { +		cgit_print_object(cgit_query_head); +	} +	cgit_print_docend(); +} + +int main(int argc, const char **argv) +{ +	if (cgit_read_config("/etc/cgitrc", cgit_global_config_cb)) +		die("Error reading config: %d %s", errno, strerror(errno)); + +	chdir(cgit_root); +	cgit_parse_query(getenv("QUERY_STRING"), cgit_querystring_cb); +	if (cgit_query_repo) +		cgit_print_repo_page(); +	else +		cgit_print_repolist(); +	return 0; +} diff --git a/cgit.css b/cgit.css new file mode 100644 index 0000000..3ed0c22 --- /dev/null +++ b/cgit.css @@ -0,0 +1,63 @@ +body { +	font-family: arial; +	font-size: normal; +	background: white; +	padding: 0em; +	margin: 0.5em; +} + + +h2 { +	font-size: normal; +	font-weight: bold; +	margin-bottom: 0.1em; +} + + +table.list { +	border: solid 1px black; +	border-collapse: collapse; +	border: solid 1px #aaa; +} + +table.list th { +	text-align: left; +	font-weight: bold; +	background: #ddd; +	border-bottom: solid 1px #aaa; +	padding: 0.1em 0.5em 0.1em; +	vertical-align: baseline; +} +table.list td { +	border: none; +	padding: 0.1em 0.5em; +	background: white; +} + +img { +	border: none; +} + + +div#header { +	background-color: #ddd; +	padding: 0.25em 0.25em 0.25em 0.5em; +	font-size: 150%; +	font-weight: bold; +	border: solid 1px #aaa; +	vertical-align: middle; +} + +div#header img#logo { +	float: right; +} + +div#content { +	margin: 0.5em 0.5em; +} + +div.error { +	color: red; +	font-weight: bold; +	margin: 1em 2em; +}
\ No newline at end of file @@ -0,0 +1,21 @@ +#ifndef CGIT_H +#define CGIT_H + +#include "git.h" +#include <openssl/sha.h> + +extern char *fmt(const char *format,...); + +extern void html(const char *txt); +extern void htmlf(const char *format,...); +extern void html_txt(char *txt); +extern void html_attr(char *txt); + +extern void html_link_open(char *url, char *title, char *class); +extern void html_link_close(void); + +typedef void (*configfn)(const char *name, const char *value); + +extern int cgit_read_config(const char *filename, configfn fn); + +#endif /* CGIT_H */ diff --git a/config.c b/config.c new file mode 100644 index 0000000..858ab69 --- /dev/null +++ b/config.c @@ -0,0 +1,73 @@ +#include "cgit.h" + +int next_char(FILE *f) +{ +	int c = fgetc(f); +	if (c=='\r') { +		c = fgetc(f); +		if (c!='\n') { +			ungetc(c, f); +			c = '\r'; +		} +	} +	return c; +} + +void skip_line(FILE *f) +{ +	int c; + +	while((c=next_char(f)) && c!='\n' && c!=EOF) +		; +} + +int read_config_line(FILE *f, char *line, const char **value, int bufsize) +{ +	int i = 0, isname = 0; + +	*value = NULL; +	while(i<bufsize-1) { +		int c = next_char(f); +		if (!isname && (c=='#' || c==';')) { +			skip_line(f); +			continue; +		} +		if (!isname && isblank(c)) +			continue; + +		if (c=='=' && !*value) { +			line[i] = 0; +			*value = &line[i+1]; +		} else if (c=='\n' && !isname) { +			i = 0; +			continue; +		} else if (c=='\n' || c==EOF) { +			line[i] = 0; +			break; +		} else { +			line[i]=c; +		} +		isname = 1; +		i++; +	} +	line[i+1] = 0; +	return i; +} + +int cgit_read_config(const char *filename, configfn fn) +{ +	int ret = 0, len; +	char line[256]; +	const char *value; +	FILE *f = fopen(filename, "r"); + +	if (!f) +		return -1; + +	while(len = read_config_line(f, line, &value, sizeof(line))) +		(*fn)(line, value); + +	fclose(f); +	return ret; +} + @@ -0,0 +1,399 @@ +#ifndef GIT_H +#define GIT_H + + +/* + * from git:git-compat-util.h  + */ + + +#ifndef FLEX_ARRAY +#if defined(__GNUC__) && (__GNUC__ < 3) +#define FLEX_ARRAY 0 +#else +#define FLEX_ARRAY /* empty */ +#endif +#endif + + +#include <unistd.h> +#include <stdio.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <stddef.h> +#include <stdlib.h> +#include <stdarg.h> +#include <string.h> +#include <errno.h> +#include <limits.h> +#include <sys/param.h> +#include <netinet/in.h> +#include <sys/types.h> +#include <dirent.h> +#include <time.h> + + +static inline char* xstrdup(const char *str) +{ +	char *ret = strdup(str); +	if (!ret) +		die("Out of memory, strdup failed"); +	return ret; +} + +static inline void *xmalloc(size_t size) +{ +	void *ret = malloc(size); +	if (!ret && !size) +		ret = malloc(1); +	if (!ret) +		die("Out of memory, malloc failed"); +#ifdef XMALLOC_POISON +	memset(ret, 0xA5, size); +#endif +	return ret; +} + +static inline void *xrealloc(void *ptr, size_t size) +{ +	void *ret = realloc(ptr, size); +	if (!ret && !size) +		ret = realloc(ptr, 1); +	if (!ret) +		die("Out of memory, realloc failed"); +	return ret; +} + +static inline void *xcalloc(size_t nmemb, size_t size) +{ +	void *ret = calloc(nmemb, size); +	if (!ret && (!nmemb || !size)) +		ret = calloc(1, 1); +	if (!ret) +		die("Out of memory, calloc failed"); +	return ret; +} + +static inline ssize_t xread(int fd, void *buf, size_t len) +{ +	ssize_t nr; +	while (1) { +		nr = read(fd, buf, len); +		if ((nr < 0) && (errno == EAGAIN || errno == EINTR)) +			continue; +		return nr; +	} +} + +static inline ssize_t xwrite(int fd, const void *buf, size_t len) +{ +	ssize_t nr; +	while (1) { +		nr = write(fd, buf, len); +		if ((nr < 0) && (errno == EAGAIN || errno == EINTR)) +			continue; +		return nr; +	} +} + + + + +/* + * from git:cache.h + */ + + +/* Convert to/from hex/sha1 representation */ +#define MINIMUM_ABBREV 4 +#define DEFAULT_ABBREV 7 + + +extern void * read_sha1_file(const unsigned char *sha1, char *type, unsigned long *size); + + + + +/* + * from git:object.h  + */ + +struct object_list { +	struct object *item; +	struct object_list *next; +}; + +struct object_refs { +	unsigned count; +	struct object *base; +	struct object *ref[FLEX_ARRAY]; /* more */ +}; + +struct object_array { +	unsigned int nr; +	unsigned int alloc; +	struct object_array_entry { +		struct object *item; +		const char *name; +	} *objects; +}; + +#define TYPE_BITS   3 +#define FLAG_BITS  27 + +/* + * The object type is stored in 3 bits. + */ +struct object { +	unsigned parsed : 1; +	unsigned used : 1; +	unsigned type : TYPE_BITS; +	unsigned flags : FLAG_BITS; +	unsigned char sha1[20]; +}; + + +/* + * from git:tree.h + */ + +struct tree { +	struct object object; +	void *buffer; +	unsigned long size; +}; + + + + +/* from git:commit.h */ + +struct commit_list { +	struct commit *item; +	struct commit_list *next; +}; + +struct commit { +	struct object object; +	void *util; +	unsigned long date; +	struct commit_list *parents; +	struct tree *tree; +	char *buffer; +}; + + +/* Commit formats */ +enum cmit_fmt { +	CMIT_FMT_RAW, +	CMIT_FMT_MEDIUM, +	CMIT_FMT_DEFAULT = CMIT_FMT_MEDIUM, +	CMIT_FMT_SHORT, +	CMIT_FMT_FULL, +	CMIT_FMT_FULLER, +	CMIT_FMT_ONELINE, +	CMIT_FMT_EMAIL, + +	CMIT_FMT_UNSPECIFIED, +}; + + + +struct commit *lookup_commit(const unsigned char *sha1); +struct commit *lookup_commit_reference(const unsigned char *sha1); +struct commit *lookup_commit_reference_gently(const unsigned char *sha1, +					      int quiet); + +typedef void (*topo_sort_set_fn_t)(struct commit*, void *data); +typedef void* (*topo_sort_get_fn_t)(struct commit*); + + + + +/* + *  from git:diff.h + */ + + +struct rev_info; +struct diff_options; +struct diff_queue_struct; + +typedef void (*change_fn_t)(struct diff_options *options, +		 unsigned old_mode, unsigned new_mode, +		 const unsigned char *old_sha1, +		 const unsigned char *new_sha1, +		 const char *base, const char *path); + +typedef void (*add_remove_fn_t)(struct diff_options *options, +		    int addremove, unsigned mode, +		    const unsigned char *sha1, +		    const char *base, const char *path); + +typedef void (*diff_format_fn_t)(struct diff_queue_struct *q, +		struct diff_options *options, void *data); + +#define DIFF_FORMAT_RAW		0x0001 +#define DIFF_FORMAT_DIFFSTAT	0x0002 +#define DIFF_FORMAT_NUMSTAT	0x0004 +#define DIFF_FORMAT_SUMMARY	0x0008 +#define DIFF_FORMAT_PATCH	0x0010 + +/* These override all above */ +#define DIFF_FORMAT_NAME	0x0100 +#define DIFF_FORMAT_NAME_STATUS	0x0200 +#define DIFF_FORMAT_CHECKDIFF	0x0400 + +/* Same as output_format = 0 but we know that -s flag was given + * and we should not give default value to output_format. + */ +#define DIFF_FORMAT_NO_OUTPUT	0x0800 + +#define DIFF_FORMAT_CALLBACK	0x1000 + +struct diff_options { +	const char *filter; +	const char *orderfile; +	const char *pickaxe; +	const char *single_follow; +	unsigned recursive:1, +		 tree_in_recursive:1, +		 binary:1, +		 text:1, +		 full_index:1, +		 silent_on_remove:1, +		 find_copies_harder:1, +		 color_diff:1, +		 color_diff_words:1; +	int context; +	int break_opt; +	int detect_rename; +	int line_termination; +	int output_format; +	int pickaxe_opts; +	int rename_score; +	int reverse_diff; +	int rename_limit; +	int setup; +	int abbrev; +	const char *msg_sep; +	const char *stat_sep; +	long xdl_opts; + +	int stat_width; +	int stat_name_width; + +	int nr_paths; +	const char **paths; +	int *pathlens; +	change_fn_t change; +	add_remove_fn_t add_remove; +	diff_format_fn_t format_callback; +	void *format_callback_data; +}; + +enum color_diff { +	DIFF_RESET = 0, +	DIFF_PLAIN = 1, +	DIFF_METAINFO = 2, +	DIFF_FRAGINFO = 3, +	DIFF_FILE_OLD = 4, +	DIFF_FILE_NEW = 5, +	DIFF_COMMIT = 6, +	DIFF_WHITESPACE = 7, +}; + + + + + + + +/* + * from git:revision.h + */ + +struct rev_info; +struct log_info; + +typedef void (prune_fn_t)(struct rev_info *revs, struct commit *commit); + +struct rev_info { +	/* Starting list */ +	struct commit_list *commits; +	struct object_array pending; + +	/* Basic information */ +	const char *prefix; +	void *prune_data; +	prune_fn_t *prune_fn; + +	/* Traversal flags */ +	unsigned int	dense:1, +			no_merges:1, +			no_walk:1, +			remove_empty_trees:1, +			simplify_history:1, +			lifo:1, +			topo_order:1, +			tag_objects:1, +			tree_objects:1, +			blob_objects:1, +			edge_hint:1, +			limited:1, +			unpacked:1, /* see also ignore_packed below */ +			boundary:1, +			parents:1; + +	/* Diff flags */ +	unsigned int	diff:1, +			full_diff:1, +			show_root_diff:1, +			no_commit_id:1, +			verbose_header:1, +			ignore_merges:1, +			combine_merges:1, +			dense_combined_merges:1, +			always_show_header:1; + +	/* Format info */ +	unsigned int	shown_one:1, +			abbrev_commit:1, +			relative_date:1; + +	const char **ignore_packed; /* pretend objects in these are unpacked */ +	int num_ignore_packed; + +	unsigned int	abbrev; +	enum cmit_fmt	commit_format; +	struct log_info *loginfo; +	int		nr, total; +	const char	*mime_boundary; +	const char	*message_id; +	const char	*ref_message_id; +	const char	*add_signoff; +	const char	*extra_headers; + +	/* Filter by commit log message */ +	struct grep_opt	*grep_filter; + +	/* special limits */ +	int max_count; +	unsigned long max_age; +	unsigned long min_age; + +	/* diff info for patches and for paths limiting */ +	struct diff_options diffopt; +	struct diff_options pruning; + +	topo_sort_set_fn_t topo_setter; +	topo_sort_get_fn_t topo_getter; +}; + + +extern struct commit *get_revision(struct rev_info *revs); + + + + +#endif /* GIT_H */ @@ -0,0 +1,100 @@ +#include "cgit.h" + +char *fmt(const char *format, ...) +{ +	static char buf[8][1024]; +	static int bufidx; +	int len; +	va_list args; + +	bufidx++; +	bufidx &= 7; + +	va_start(args, format); +	len = vsnprintf(buf[bufidx], sizeof(buf[bufidx]), format, args); +	va_end(args); +	if (len>sizeof(buf[bufidx])) +		die("[html.c] string truncated: %s", format); +	return buf[bufidx]; +} + +void html(const char *txt) +{ +	fputs(txt, stdout); +} + +void htmlf(const char *format, ...) +{ +	va_list args; + +	va_start(args, format); +	vprintf(format, args); +	va_end(args); +} + +void html_txt(char *txt) +{ +	char *t = txt; +	while(*t){ +		int c = *t; +		if (c=='<' || c=='>' || c=='&') { +			*t = '\0'; +			html(txt); +			*t = c; +			if (c=='>') +				html(">"); +			else if (c=='<') +				html("<"); +			else if (c=='&') +				html("&"); +			txt = t+1; +		} +		t++; +	} +	if (t!=txt) +		html(txt); +} + + +void html_attr(char *txt) +{ +	char *t = txt; +	while(*t){ +		int c = *t; +		if (c=='<' || c=='>' || c=='\'') { +			*t = '\0'; +			html(txt); +			*t = c; +			if (c=='>') +				html(">"); +			else if (c=='<') +				html("<"); +			else if (c=='\'') +				html(""e;"); +			txt = t+1; +		} +		t++; +	} +	if (t!=txt) +		html(txt); +} + +void html_link_open(char *url, char *title, char *class) +{ +	html("<a href='"); +	html_attr(url); +	if (title) { +		html("' title='"); +		html_attr(title); +	} +	if (class) { +		html("' class='"); +		html_attr(class); +	} +	html("'>"); +} + +void html_link_close(void) +{ +	html("</a>"); +} | 
