-rw-r--r-- | Makefile | 8 | ||||
-rw-r--r-- | cache.c | 22 | ||||
-rw-r--r-- | cgit.c | 74 | ||||
-rw-r--r-- | cgit.h | 17 | ||||
m--------- | git | 0 | ||||
-rw-r--r-- | html.c | 14 | ||||
-rw-r--r-- | parsing.c | 44 | ||||
-rw-r--r-- | shared.c | 33 | ||||
-rw-r--r-- | ui-repolist.c | 5 | ||||
-rw-r--r-- | ui-shared.c | 5 |
10 files changed, 166 insertions, 56 deletions
@@ -1,89 +1,87 @@ CGIT_VERSION = 0.4 prefix = /var/www/htdocs/cgit SHA1_HEADER = <openssl/sha.h> CACHE_ROOT = /var/cache/cgit CGIT_CONFIG = /etc/cgitrc CGIT_SCRIPT_NAME = cgit.cgi # # Let the user override the above settings. # -include cgit.conf EXTLIBS = git/libgit.a git/xdiff/lib.a -lz -lcrypto OBJECTS = shared.o cache.o parsing.o html.o ui-shared.o ui-repolist.o \ ui-summary.o ui-log.o ui-view.o ui-tree.o ui-commit.o ui-diff.o \ ui-snapshot.o ui-blob.o CFLAGS += -Wall ifdef DEBUG CFLAGS += -g endif CFLAGS += -Igit CFLAGS += -DSHA1_HEADER='$(SHA1_HEADER)' CFLAGS += -DCGIT_VERSION='"$(CGIT_VERSION)"' CFLAGS += -DCGIT_CONFIG='"$(CGIT_CONFIG)"' CFLAGS += -DCGIT_SCRIPT_NAME='"$(CGIT_SCRIPT_NAME)"' # -# If make is run on a nongit platform, we need to get the git sources as a tarball. -# But there is currently no recent enough tarball available on kernel.org, so download -# a zipfile from hjemli.net instead +# If make is run on a nongit platform, get the git sources as a tarball. # GITVER = $(shell git version 2>/dev/null || echo nogit) ifeq ($(GITVER),nogit) -GITURL = http://hjemli.net/git/git/snapshot/?id=v1.5.2-rc2 -INITGIT = test -e git/git.c || (curl "$(GITURL)" > tmp.zip && unzip tmp.zip) +GITURL = http://www.kernel.org/pub/software/scm/git/git-1.5.2.tar.bz2 +INITGIT = test -e git/git.c || ((curl "$(GITURL)" | tar -xj) && mv git-1.5.2 git) else INITGIT = ./submodules.sh -i endif # # basic build rules # all: cgit cgit: cgit.c cgit.h $(OBJECTS) $(CC) $(CFLAGS) cgit.c -o cgit $(OBJECTS) $(EXTLIBS) $(OBJECTS): cgit.h git/libgit.a git/libgit.a: $(INITGIT) $(MAKE) -C git # # phony targets # install: all clean-cache mkdir -p $(prefix) install cgit $(prefix)/$(CGIT_SCRIPT_NAME) install cgit.css $(prefix)/cgit.css clean-cgit: rm -f cgit *.o distclean-cgit: clean-cgit git clean -d -x clean-sub: $(MAKE) -C git clean distclean-sub: clean-sub $(shell cd git && git clean -d -x) clean-cache: rm -rf $(CACHE_ROOT)/* clean: clean-cgit clean-sub distclean: distclean-cgit distclean-sub .PHONY: all install clean clean-cgit clean-sub clean-cache \ distclean distclean-cgit distclean-sub @@ -1,111 +1,119 @@ /* cache.c: cache management * * Copyright (C) 2006 Lars Hjemli * * Licensed under GNU General Public License v2 * (see COPYING for full license text) */ #include "cgit.h" const int NOLOCK = -1; char *cache_safe_filename(const char *unsafe) { - static char buf[PATH_MAX]; - char *s = buf; + static char buf[4][PATH_MAX]; + static int bufidx; + char *s; char c; + bufidx++; + bufidx &= 3; + s = buf[bufidx]; + while(unsafe && (c = *unsafe++) != 0) { - if (c == '/' || c == ' ' || c == '&' || c == '|' || + if (c == '/' || c == ' ' || c == '&' || c == '|' || c == '>' || c == '<' || c == '.') c = '_'; *s++ = c; } *s = '\0'; - return buf; + return buf[bufidx]; } int cache_exist(struct cacheitem *item) { if (stat(item->name, &item->st)) { item->st.st_mtime = 0; return 0; } return 1; } int cache_create_dirs() { char *path; path = fmt("%s", cgit_cache_root); if (mkdir(path, S_IRWXU) && errno!=EEXIST) return 0; - if (!cgit_query_repo) + if (!cgit_repo) return 0; - path = fmt("%s/%s", cgit_cache_root, cgit_query_repo); + path = fmt("%s/%s", cgit_cache_root, + cache_safe_filename(cgit_repo->url)); + if (mkdir(path, S_IRWXU) && errno!=EEXIST) return 0; if (cgit_query_page) { - path = fmt("%s/%s/%s", cgit_cache_root, cgit_query_repo, + path = fmt("%s/%s/%s", cgit_cache_root, + cache_safe_filename(cgit_repo->url), cgit_query_page); if (mkdir(path, S_IRWXU) && errno!=EEXIST) return 0; } return 1; } int cache_refill_overdue(const char *lockfile) { struct stat st; if (stat(lockfile, &st)) return 0; else return (time(NULL) - st.st_mtime > cgit_cache_max_create_time); } int cache_lock(struct cacheitem *item) { int i = 0; char *lockfile = xstrdup(fmt("%s.lock", item->name)); top: if (++i > cgit_max_lock_attempts) die("cache_lock: unable to lock %s: %s", item->name, strerror(errno)); item->fd = open(lockfile, O_WRONLY|O_CREAT|O_EXCL, S_IRUSR|S_IWUSR); if (item->fd == NOLOCK && errno == ENOENT && cache_create_dirs()) goto top; if (item->fd == NOLOCK && errno == EEXIST && cache_refill_overdue(lockfile) && !unlink(lockfile)) goto top; free(lockfile); return (item->fd > 0); } int cache_unlock(struct cacheitem *item) { close(item->fd); return (rename(fmt("%s.lock", item->name), item->name) == 0); } int cache_cancel_lock(struct cacheitem *item) { return (unlink(fmt("%s.lock", item->name)) == 0); } int cache_expired(struct cacheitem *item) { if (item->ttl < 0) return 0; return item->st.st_mtime + item->ttl * 60 < time(NULL); } @@ -1,266 +1,254 @@ /* cgit.c: cgi for the git scm * * Copyright (C) 2006 Lars Hjemli * * Licensed under GNU General Public License v2 * (see COPYING for full license text) */ #include "cgit.h" const char cgit_version[] = CGIT_VERSION; -static struct repoinfo *cgit_get_repoinfo(char *url) -{ - int i; - struct repoinfo *repo; - - for (i=0; i<cgit_repolist.count; i++) { - repo = &cgit_repolist.repos[i]; - if (!strcmp(repo->url, url)) - return repo; - } - return NULL; -} - - static int cgit_prepare_cache(struct cacheitem *item) { - if (!cgit_query_repo) { - item->name = xstrdup(fmt("%s/index.html", cgit_cache_root)); - item->ttl = cgit_cache_root_ttl; - return 1; - } - cgit_repo = cgit_get_repoinfo(cgit_query_repo); - if (!cgit_repo) { + if (!cgit_repo && cgit_query_repo) { char *title = fmt("%s - %s", cgit_root_title, "Bad request"); cgit_print_docstart(title, item); cgit_print_pageheader(title, 0); cgit_print_error(fmt("Unknown repo: %s", cgit_query_repo)); cgit_print_docend(); return 0; } - if (!cgit_query_page) { + if (!cgit_repo) { + item->name = xstrdup(fmt("%s/index.html", cgit_cache_root)); + item->ttl = cgit_cache_root_ttl; + return 1; + } + + if (!cgit_cmd) { item->name = xstrdup(fmt("%s/%s/index.html", cgit_cache_root, - cgit_repo->url)); + cache_safe_filename(cgit_repo->url))); item->ttl = cgit_cache_repo_ttl; } else { item->name = xstrdup(fmt("%s/%s/%s/%s.html", cgit_cache_root, - cgit_repo->url, cgit_query_page, + cache_safe_filename(cgit_repo->url), cgit_query_page, cache_safe_filename(cgit_querystring))); if (cgit_query_has_symref) item->ttl = cgit_cache_dynamic_ttl; else if (cgit_query_has_sha1) item->ttl = cgit_cache_static_ttl; else item->ttl = cgit_cache_repo_ttl; } return 1; } static void cgit_print_repo_page(struct cacheitem *item) { char *title; int show_search; if (!cgit_query_head) cgit_query_head = cgit_repo->defbranch; if (chdir(cgit_repo->path)) { title = fmt("%s - %s", cgit_root_title, "Bad request"); cgit_print_docstart(title, item); cgit_print_pageheader(title, 0); cgit_print_error(fmt("Unable to scan repository: %s", strerror(errno))); cgit_print_docend(); return; } title = fmt("%s - %s", cgit_repo->name, cgit_repo->desc); show_search = 0; setenv("GIT_DIR", cgit_repo->path, 1); - if (cgit_query_page) { - if (cgit_repo->snapshots && !strcmp(cgit_query_page, "snapshot")) { + if ((cgit_cmd == CMD_SNAPSHOT) && cgit_repo->snapshots) { cgit_print_snapshot(item, cgit_query_sha1, "zip", cgit_repo->url, cgit_query_name); return; - } - if (!strcmp(cgit_query_page, "blob")) { - cgit_print_blob(item, cgit_query_sha1, cgit_query_path); - return; - } } - if (cgit_query_page && !strcmp(cgit_query_page, "log")) - show_search = 1; + if (cgit_cmd == CMD_BLOB) { + cgit_print_blob(item, cgit_query_sha1, cgit_query_path); + return; + } + show_search = (cgit_cmd == CMD_LOG); cgit_print_docstart(title, item); - - - if (!cgit_query_page) { + if (!cgit_cmd) { cgit_print_pageheader("summary", show_search); cgit_print_summary(); cgit_print_docend(); return; } cgit_print_pageheader(cgit_query_page, show_search); - if (!strcmp(cgit_query_page, "log")) { + switch(cgit_cmd) { + case CMD_LOG: cgit_print_log(cgit_query_head, cgit_query_ofs, cgit_max_commit_count, cgit_query_search, cgit_query_path); - } else if (!strcmp(cgit_query_page, "tree")) { + break; + case CMD_TREE: cgit_print_tree(cgit_query_head, cgit_query_sha1, cgit_query_path); - } else if (!strcmp(cgit_query_page, "commit")) { + break; + case CMD_COMMIT: cgit_print_commit(cgit_query_head); - } else if (!strcmp(cgit_query_page, "view")) { + break; + case CMD_VIEW: cgit_print_view(cgit_query_sha1, cgit_query_path); - } else if (!strcmp(cgit_query_page, "diff")) { + break; + case CMD_DIFF: cgit_print_diff(cgit_query_head, cgit_query_sha1, cgit_query_sha2, cgit_query_path); - } else { + break; + default: cgit_print_error("Invalid request"); } cgit_print_docend(); } static void cgit_fill_cache(struct cacheitem *item, int use_cache) { static char buf[PATH_MAX]; int stdout2; getcwd(buf, sizeof(buf)); item->st.st_mtime = time(NULL); if (use_cache) { stdout2 = chk_positive(dup(STDOUT_FILENO), "Preserving STDOUT"); chk_zero(close(STDOUT_FILENO), "Closing STDOUT"); chk_positive(dup2(item->fd, STDOUT_FILENO), "Dup2(cachefile)"); } - if (cgit_query_repo) + if (cgit_repo) cgit_print_repo_page(item); else cgit_print_repolist(item); if (use_cache) { chk_zero(close(STDOUT_FILENO), "Close redirected STDOUT"); chk_positive(dup2(stdout2, STDOUT_FILENO), "Restoring original STDOUT"); chk_zero(close(stdout2), "Closing temporary STDOUT"); } chdir(buf); } static void cgit_check_cache(struct cacheitem *item) { int i = 0; top: if (++i > cgit_max_lock_attempts) { die("cgit_refresh_cache: unable to lock %s: %s", item->name, strerror(errno)); } if (!cache_exist(item)) { if (!cache_lock(item)) { sleep(1); goto top; } if (!cache_exist(item)) { cgit_fill_cache(item, 1); cache_unlock(item); } else { cache_cancel_lock(item); } } else if (cache_expired(item) && cache_lock(item)) { if (cache_expired(item)) { cgit_fill_cache(item, 1); cache_unlock(item); } else { cache_cancel_lock(item); } } } static void cgit_print_cache(struct cacheitem *item) { static char buf[4096]; ssize_t i; int fd = open(item->name, O_RDONLY); if (fd<0) die("Unable to open cached file %s", item->name); while((i=read(fd, buf, sizeof(buf))) > 0) write(STDOUT_FILENO, buf, i); close(fd); } static void cgit_parse_args(int argc, const char **argv) { int i; for (i = 1; i < argc; i++) { if (!strncmp(argv[i], "--cache=", 8)) { cgit_cache_root = xstrdup(argv[i]+8); } if (!strcmp(argv[i], "--nocache")) { cgit_nocache = 1; } if (!strncmp(argv[i], "--query=", 8)) { cgit_querystring = xstrdup(argv[i]+8); } if (!strncmp(argv[i], "--repo=", 7)) { cgit_query_repo = xstrdup(argv[i]+7); } if (!strncmp(argv[i], "--page=", 7)) { cgit_query_page = xstrdup(argv[i]+7); } if (!strncmp(argv[i], "--head=", 7)) { cgit_query_head = xstrdup(argv[i]+7); cgit_query_has_symref = 1; } if (!strncmp(argv[i], "--sha1=", 7)) { cgit_query_sha1 = xstrdup(argv[i]+7); cgit_query_has_sha1 = 1; } if (!strncmp(argv[i], "--ofs=", 6)) { cgit_query_ofs = atoi(argv[i]+6); } } } int main(int argc, const char **argv) { struct cacheitem item; htmlfd = STDOUT_FILENO; item.st.st_mtime = time(NULL); cgit_repolist.length = 0; cgit_repolist.count = 0; cgit_repolist.repos = NULL; cgit_read_config(CGIT_CONFIG, cgit_global_config_cb); + cgit_repo = NULL; if (getenv("SCRIPT_NAME")) cgit_script_name = xstrdup(getenv("SCRIPT_NAME")); if (getenv("QUERY_STRING")) cgit_querystring = xstrdup(getenv("QUERY_STRING")); cgit_parse_args(argc, argv); cgit_parse_query(cgit_querystring, cgit_querystring_cb); if (!cgit_prepare_cache(&item)) return 0; if (cgit_nocache) { cgit_fill_cache(&item, 0); } else { cgit_check_cache(&item); cgit_print_cache(&item); } return 0; } @@ -1,188 +1,205 @@ #ifndef CGIT_H #define CGIT_H #include <git-compat-util.h> #include <cache.h> #include <grep.h> #include <object.h> #include <tree.h> #include <commit.h> #include <tag.h> #include <diff.h> #include <diffcore.h> #include <refs.h> #include <revision.h> #include <log-tree.h> #include <archive.h> #include <xdiff/xdiff.h> +/* + * The valid cgit repo-commands + */ +#define CMD_LOG 1 +#define CMD_COMMIT 2 +#define CMD_DIFF 3 +#define CMD_TREE 4 +#define CMD_VIEW 5 +#define CMD_BLOB 6 +#define CMD_SNAPSHOT 7 + typedef void (*configfn)(const char *name, const char *value); typedef void (*filepair_fn)(struct diff_filepair *pair); typedef void (*linediff_fn)(char *line, int len); struct cacheitem { char *name; struct stat st; int ttl; int fd; }; struct repoinfo { char *url; char *name; char *path; char *desc; char *owner; char *defbranch; char *group; char *module_link; int snapshots; int enable_log_filecount; int enable_log_linecount; }; struct repolist { int length; int count; struct repoinfo *repos; }; struct commitinfo { struct commit *commit; char *author; char *author_email; unsigned long author_date; char *committer; char *committer_email; unsigned long committer_date; char *subject; char *msg; }; struct taginfo { char *tagger; char *tagger_email; int tagger_date; char *msg; }; extern const char cgit_version[]; extern struct repolist cgit_repolist; extern struct repoinfo *cgit_repo; +extern int cgit_cmd; extern char *cgit_root_title; extern char *cgit_css; extern char *cgit_logo; +extern char *cgit_index_header; extern char *cgit_logo_link; extern char *cgit_module_link; extern char *cgit_virtual_root; extern char *cgit_script_name; extern char *cgit_cache_root; extern char *cgit_repo_group; extern int cgit_nocache; extern int cgit_snapshots; extern int cgit_enable_log_filecount; extern int cgit_enable_log_linecount; extern int cgit_max_lock_attempts; extern int cgit_cache_root_ttl; extern int cgit_cache_repo_ttl; extern int cgit_cache_dynamic_ttl; extern int cgit_cache_static_ttl; extern int cgit_cache_max_create_time; extern int cgit_max_msg_len; extern int cgit_max_repodesc_len; extern int cgit_max_commit_count; extern int cgit_query_has_symref; extern int cgit_query_has_sha1; extern char *cgit_querystring; extern char *cgit_query_repo; extern char *cgit_query_page; extern char *cgit_query_search; extern char *cgit_query_head; extern char *cgit_query_sha1; extern char *cgit_query_sha2; extern char *cgit_query_path; extern char *cgit_query_name; extern int cgit_query_ofs; extern int htmlfd; +extern int cgit_get_cmd_index(const char *cmd); +extern struct repoinfo *cgit_get_repoinfo(const char *url); extern void cgit_global_config_cb(const char *name, const char *value); extern void cgit_repo_config_cb(const char *name, const char *value); extern void cgit_querystring_cb(const char *name, const char *value); extern int chk_zero(int result, char *msg); extern int chk_positive(int result, char *msg); extern int hextoint(char c); extern void *cgit_free_commitinfo(struct commitinfo *info); extern int cgit_diff_files(const unsigned char *old_sha1, const unsigned char *new_sha1, linediff_fn fn); extern void cgit_diff_tree(const unsigned char *old_sha1, const unsigned char *new_sha1, filepair_fn fn); extern void cgit_diff_commit(struct commit *commit, filepair_fn fn); 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_ntxt(int len, char *txt); extern void html_attr(char *txt); extern void html_hidden(char *name, char *value); extern void html_link_open(char *url, char *title, char *class); extern void html_link_close(void); extern void html_filemode(unsigned short mode); +extern int html_include(const char *filename); extern int cgit_read_config(const char *filename, configfn fn); extern int cgit_parse_query(char *txt, configfn fn); extern struct commitinfo *cgit_parse_commit(struct commit *commit); extern struct taginfo *cgit_parse_tag(struct tag *tag); +extern void cgit_parse_url(const char *url); extern char *cache_safe_filename(const char *unsafe); extern int cache_lock(struct cacheitem *item); extern int cache_unlock(struct cacheitem *item); extern int cache_cancel_lock(struct cacheitem *item); extern int cache_exist(struct cacheitem *item); extern int cache_expired(struct cacheitem *item); extern char *cgit_repourl(const char *reponame); extern char *cgit_pageurl(const char *reponame, const char *pagename, const char *query); extern void cgit_print_error(char *msg); extern void cgit_print_date(unsigned long secs); extern void cgit_print_docstart(char *title, struct cacheitem *item); extern void cgit_print_docend(); extern void cgit_print_pageheader(char *title, int show_search); extern void cgit_print_snapshot_start(const char *mimetype, const char *filename, struct cacheitem *item); extern void cgit_print_repolist(struct cacheitem *item); extern void cgit_print_summary(); extern void cgit_print_log(const char *tip, int ofs, int cnt, char *grep, char *path); extern void cgit_print_view(const char *hex, char *path); extern void cgit_print_blob(struct cacheitem *item, const char *hex, char *path); extern void cgit_print_tree(const char *rev, const char *hex, char *path); extern void cgit_print_commit(const char *hex); extern void cgit_print_diff(const char *head, const char *old_hex, const char *new_hex, char *path); extern void cgit_print_snapshot(struct cacheitem *item, const char *hex, const char *format, const char *prefix, const char *filename); #endif /* CGIT_H */ diff --git a/git b/git -Subproject 9159afbfce955db86373dab95b5f8e31fb763da +Subproject aba170cdb4874b72dd619e6f7bbc13c33295f83 @@ -1,168 +1,182 @@ /* html.c: helper functions for html output * * Copyright (C) 2006 Lars Hjemli * * Licensed under GNU General Public License v2 * (see COPYING for full license text) */ #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) { write(htmlfd, txt, strlen(txt)); } void htmlf(const char *format, ...) { static char buf[65536]; va_list args; va_start(args, format); vsnprintf(buf, sizeof(buf), format, args); va_end(args); html(buf); } void html_txt(char *txt) { char *t = txt; while(t && *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_ntxt(int len, char *txt) { char *t = txt; while(t && *t && len--){ 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) { char c = *t; *t = '\0'; html(txt); *t = c; } if (len<0) html("..."); } void html_attr(char *txt) { char *t = txt; while(t && *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_hidden(char *name, char *value) { html("<input type='hidden' name='"); html_attr(name); html("' value='"); html_attr(value); html("'/>"); } 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>"); } void html_fileperm(unsigned short mode) { htmlf("%c%c%c", (mode & 4 ? 'r' : '-'), (mode & 2 ? 'w' : '-'), (mode & 1 ? 'x' : '-')); } void html_filemode(unsigned short mode) { if (S_ISDIR(mode)) html("d"); else if (S_ISLNK(mode)) html("l"); else if (S_ISDIRLNK(mode)) html("m"); else html("-"); html_fileperm(mode >> 6); html_fileperm(mode >> 3); html_fileperm(mode); } + +int html_include(const char *filename) +{ + FILE *f; + char buf[4096]; + size_t len; + + if (!(f = fopen(filename, "r"))) + return -1; + while((len = fread(buf, 1, 4096, f)) > 0) + write(htmlfd, buf, len); + fclose(f); + return 0; +} @@ -1,249 +1,293 @@ /* config.c: parsing of config files * * Copyright (C) 2006 Lars Hjemli * * Licensed under GNU General Public License v2 * (see COPYING for full license text) */ #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 && isspace(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) { static int nesting; int len; char line[256]; const char *value; FILE *f; /* cancel deeply nested include-commands */ if (nesting > 8) return -1; if (!(f = fopen(filename, "r"))) return -1; nesting++; while((len = read_config_line(f, line, &value, sizeof(line))) > 0) (*fn)(line, value); nesting--; fclose(f); return 0; } char *convert_query_hexchar(char *txt) { int d1, d2; if (strlen(txt) < 3) { *txt = '\0'; return txt-1; } d1 = hextoint(*(txt+1)); d2 = hextoint(*(txt+2)); if (d1<0 || d2<0) { strcpy(txt, txt+3); return txt-1; } else { *txt = d1 * 16 + d2; strcpy(txt+1, txt+3); return txt; } } int cgit_parse_query(char *txt, configfn fn) { char *t, *value = NULL, c; if (!txt) return 0; t = txt = xstrdup(txt); while((c=*t) != '\0') { if (c=='=') { *t = '\0'; value = t+1; } else if (c=='+') { *t = ' '; } else if (c=='%') { t = convert_query_hexchar(t); } else if (c=='&') { *t = '\0'; (*fn)(txt, value); txt = t+1; value = NULL; } t++; } if (t!=txt) (*fn)(txt, value); return 0; } +/* + * url syntax: [repo ['/' cmd [ '/' path]]] + * repo: any valid repo url, may contain '/' + * cmd: log | commit | diff | tree | view | blob | snapshot + * path: any valid path, may contain '/' + * + */ +void cgit_parse_url(const char *url) +{ + char *cmd, *p; + + cgit_repo = NULL; + if (!url || url[0] == '\0') + return; + + cgit_repo = cgit_get_repoinfo(url); + if (cgit_repo) { + cgit_query_repo = cgit_repo->url; + return; + } + + cmd = strchr(url, '/'); + while (!cgit_repo && cmd) { + cmd[0] = '\0'; + cgit_repo = cgit_get_repoinfo(url); + if (cgit_repo == NULL) { + cmd[0] = '/'; + cmd = strchr(cmd + 1, '/'); + continue; + } + + cgit_query_repo = cgit_repo->url; + p = strchr(cmd + 1, '/'); + if (p) { + p[0] = '\0'; + if (p[1]) + cgit_query_path = xstrdup(p + 1); + } + cgit_cmd = cgit_get_cmd_index(cmd + 1); + cgit_query_page = xstrdup(cmd + 1); + return; + } +} + char *substr(const char *head, const char *tail) { char *buf; buf = xmalloc(tail - head + 1); strncpy(buf, head, tail - head); buf[tail - head] = '\0'; return buf; } struct commitinfo *cgit_parse_commit(struct commit *commit) { struct commitinfo *ret; char *p = commit->buffer, *t = commit->buffer; ret = xmalloc(sizeof(*ret)); ret->commit = commit; ret->author = NULL; ret->author_email = NULL; ret->committer = NULL; ret->committer_email = NULL; ret->subject = NULL; ret->msg = NULL; if (strncmp(p, "tree ", 5)) die("Bad commit: %s", sha1_to_hex(commit->object.sha1)); else p += 46; // "tree " + hex[40] + "\n" while (!strncmp(p, "parent ", 7)) p += 48; // "parent " + hex[40] + "\n" if (!strncmp(p, "author ", 7)) { p += 7; t = strchr(p, '<') - 1; ret->author = substr(p, t); p = t; t = strchr(t, '>') + 1; ret->author_email = substr(p, t); ret->author_date = atol(++t); p = strchr(t, '\n') + 1; } if (!strncmp(p, "committer ", 9)) { p += 9; t = strchr(p, '<') - 1; ret->committer = substr(p, t); p = t; t = strchr(t, '>') + 1; ret->committer_email = substr(p, t); ret->committer_date = atol(++t); p = strchr(t, '\n') + 1; } while (*p == '\n') p = strchr(p, '\n') + 1; t = strchr(p, '\n'); if (t && *t) { ret->subject = substr(p, t); p = t + 1; while (*p == '\n') p = strchr(p, '\n') + 1; ret->msg = p; } return ret; } struct taginfo *cgit_parse_tag(struct tag *tag) { void *data; enum object_type type; unsigned long size; char *p, *t; struct taginfo *ret; data = read_sha1_file(tag->object.sha1, &type, &size); if (!data || type != OBJ_TAG) { free(data); return 0; } ret = xmalloc(sizeof(*ret)); ret->tagger = NULL; ret->tagger_email = NULL; ret->tagger_date = 0; ret->msg = NULL; p = data; while (p && *p) { if (*p == '\n') break; if (!strncmp(p, "tagger ", 7)) { p += 7; t = strchr(p, '<') - 1; ret->tagger = substr(p, t); p = t; t = strchr(t, '>') + 1; ret->tagger_email = substr(p, t); ret->tagger_date = atol(++t); } p = strchr(p, '\n') + 1; } while (p && (*p == '\n')) p = strchr(p, '\n') + 1; if (p && *p) ret->msg = xstrdup(p); free(data); return ret; } @@ -1,332 +1,365 @@ /* shared.c: global vars + some callback functions * * Copyright (C) 2006 Lars Hjemli * * Licensed under GNU General Public License v2 * (see COPYING for full license text) */ #include "cgit.h" struct repolist cgit_repolist; struct repoinfo *cgit_repo; +int cgit_cmd; char *cgit_root_title = "Git repository browser"; char *cgit_css = "/cgit.css"; char *cgit_logo = "/git-logo.png"; +char *cgit_index_header = NULL; char *cgit_logo_link = "http://www.kernel.org/pub/software/scm/git/docs/"; char *cgit_module_link = "./?repo=%s&page=commit&id=%s"; char *cgit_virtual_root = NULL; char *cgit_script_name = CGIT_SCRIPT_NAME; char *cgit_cache_root = "/var/cache/cgit"; char *cgit_repo_group = NULL; int cgit_nocache = 0; int cgit_snapshots = 0; int cgit_enable_log_filecount = 0; int cgit_enable_log_linecount = 0; int cgit_max_lock_attempts = 5; int cgit_cache_root_ttl = 5; int cgit_cache_repo_ttl = 5; int cgit_cache_dynamic_ttl = 5; int cgit_cache_static_ttl = -1; int cgit_cache_max_create_time = 5; int cgit_max_msg_len = 60; int cgit_max_repodesc_len = 60; int cgit_max_commit_count = 50; int cgit_query_has_symref = 0; int cgit_query_has_sha1 = 0; char *cgit_querystring = NULL; char *cgit_query_repo = NULL; char *cgit_query_page = NULL; char *cgit_query_head = NULL; char *cgit_query_search = NULL; char *cgit_query_sha1 = NULL; char *cgit_query_sha2 = NULL; char *cgit_query_path = NULL; char *cgit_query_name = NULL; int cgit_query_ofs = 0; int htmlfd = 0; + +int cgit_get_cmd_index(const char *cmd) +{ + static char *cmds[] = {"log", "commit", "diff", "tree", "view", "blob", "snapshot", NULL}; + int i; + + for(i = 0; cmds[i]; i++) + if (!strcmp(cmd, cmds[i])) + return i + 1; + return 0; +} + int chk_zero(int result, char *msg) { if (result != 0) die("%s: %s", msg, strerror(errno)); return result; } int chk_positive(int result, char *msg) { if (result <= 0) die("%s: %s", msg, strerror(errno)); return result; } struct repoinfo *add_repo(const char *url) { struct repoinfo *ret; if (++cgit_repolist.count > cgit_repolist.length) { if (cgit_repolist.length == 0) cgit_repolist.length = 8; else cgit_repolist.length *= 2; cgit_repolist.repos = xrealloc(cgit_repolist.repos, cgit_repolist.length * sizeof(struct repoinfo)); } ret = &cgit_repolist.repos[cgit_repolist.count-1]; ret->url = xstrdup(url); ret->name = ret->url; ret->path = NULL; ret->desc = NULL; ret->owner = NULL; ret->group = cgit_repo_group; ret->defbranch = "master"; ret->snapshots = cgit_snapshots; ret->enable_log_filecount = cgit_enable_log_filecount; ret->enable_log_linecount = cgit_enable_log_linecount; ret->module_link = cgit_module_link; return ret; } +struct repoinfo *cgit_get_repoinfo(const char *url) +{ + int i; + struct repoinfo *repo; + + for (i=0; i<cgit_repolist.count; i++) { + repo = &cgit_repolist.repos[i]; + if (!strcmp(repo->url, url)) + return repo; + } + return NULL; +} + void cgit_global_config_cb(const char *name, const char *value) { 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, "index-header")) + cgit_index_header = xstrdup(value); else if (!strcmp(name, "logo-link")) cgit_logo_link = xstrdup(value); else if (!strcmp(name, "module-link")) cgit_module_link = xstrdup(value); else if (!strcmp(name, "virtual-root")) cgit_virtual_root = xstrdup(value); else if (!strcmp(name, "nocache")) cgit_nocache = atoi(value); else if (!strcmp(name, "snapshots")) cgit_snapshots = atoi(value); else if (!strcmp(name, "enable-log-filecount")) cgit_enable_log_filecount = atoi(value); else if (!strcmp(name, "enable-log-linecount")) cgit_enable_log_linecount = atoi(value); else if (!strcmp(name, "cache-root")) cgit_cache_root = xstrdup(value); else if (!strcmp(name, "cache-root-ttl")) cgit_cache_root_ttl = atoi(value); else if (!strcmp(name, "cache-repo-ttl")) cgit_cache_repo_ttl = atoi(value); else if (!strcmp(name, "cache-static-ttl")) cgit_cache_static_ttl = atoi(value); else if (!strcmp(name, "cache-dynamic-ttl")) cgit_cache_dynamic_ttl = atoi(value); else if (!strcmp(name, "max-message-length")) cgit_max_msg_len = atoi(value); else if (!strcmp(name, "max-repodesc-length")) cgit_max_repodesc_len = atoi(value); else if (!strcmp(name, "max-commit-count")) cgit_max_commit_count = atoi(value); else if (!strcmp(name, "repo.group")) cgit_repo_group = xstrdup(value); else if (!strcmp(name, "repo.url")) cgit_repo = add_repo(value); else if (!strcmp(name, "repo.name")) cgit_repo->name = xstrdup(value); else if (cgit_repo && !strcmp(name, "repo.path")) cgit_repo->path = xstrdup(value); else if (cgit_repo && !strcmp(name, "repo.desc")) cgit_repo->desc = xstrdup(value); else if (cgit_repo && !strcmp(name, "repo.owner")) cgit_repo->owner = xstrdup(value); else if (cgit_repo && !strcmp(name, "repo.defbranch")) cgit_repo->defbranch = xstrdup(value); else if (cgit_repo && !strcmp(name, "repo.snapshots")) cgit_repo->snapshots = cgit_snapshots * atoi(value); else if (cgit_repo && !strcmp(name, "repo.enable-log-filecount")) cgit_repo->enable_log_filecount = cgit_enable_log_filecount * atoi(value); else if (cgit_repo && !strcmp(name, "repo.enable-log-linecount")) cgit_repo->enable_log_linecount = cgit_enable_log_linecount * atoi(value); else if (cgit_repo && !strcmp(name, "repo.module-link")) cgit_repo->module_link= xstrdup(value); else if (!strcmp(name, "include")) cgit_read_config(value, cgit_global_config_cb); } void cgit_querystring_cb(const char *name, const char *value) { if (!strcmp(name,"r")) { cgit_query_repo = xstrdup(value); + cgit_repo = cgit_get_repoinfo(value); } else if (!strcmp(name, "p")) { cgit_query_page = xstrdup(value); + cgit_cmd = cgit_get_cmd_index(value); + } else if (!strcmp(name, "url")) { + cgit_parse_url(value); } else if (!strcmp(name, "q")) { cgit_query_search = xstrdup(value); } else if (!strcmp(name, "h")) { cgit_query_head = xstrdup(value); cgit_query_has_symref = 1; } else if (!strcmp(name, "id")) { cgit_query_sha1 = xstrdup(value); cgit_query_has_sha1 = 1; } else if (!strcmp(name, "id2")) { cgit_query_sha2 = xstrdup(value); cgit_query_has_sha1 = 1; } else if (!strcmp(name, "ofs")) { cgit_query_ofs = atoi(value); } else if (!strcmp(name, "path")) { cgit_query_path = xstrdup(value); } else if (!strcmp(name, "name")) { cgit_query_name = xstrdup(value); } } void *cgit_free_commitinfo(struct commitinfo *info) { free(info->author); free(info->author_email); free(info->committer); free(info->committer_email); free(info->subject); free(info); return NULL; } int hextoint(char c) { if (c >= 'a' && c <= 'f') return 10 + c - 'a'; else if (c >= 'A' && c <= 'F') return 10 + c - 'A'; else if (c >= '0' && c <= '9') return c - '0'; else return -1; } void cgit_diff_tree_cb(struct diff_queue_struct *q, struct diff_options *options, void *data) { int i; for (i = 0; i < q->nr; i++) { if (q->queue[i]->status == 'U') continue; ((filepair_fn)data)(q->queue[i]); } } static int load_mmfile(mmfile_t *file, const unsigned char *sha1) { enum object_type type; if (is_null_sha1(sha1)) { file->ptr = (char *)""; file->size = 0; } else { file->ptr = read_sha1_file(sha1, &type, &file->size); } return 1; } /* * Receive diff-buffers from xdiff and concatenate them as * needed across multiple callbacks. * * This is basically a copy of xdiff-interface.c/xdiff_outf(), * ripped from git and modified to use globals instead of * a special callback-struct. */ char *diffbuf = NULL; int buflen = 0; int filediff_cb(void *priv, mmbuffer_t *mb, int nbuf) { int i; for (i = 0; i < nbuf; i++) { if (mb[i].ptr[mb[i].size-1] != '\n') { /* Incomplete line */ diffbuf = xrealloc(diffbuf, buflen + mb[i].size); memcpy(diffbuf + buflen, mb[i].ptr, mb[i].size); buflen += mb[i].size; continue; } /* we have a complete line */ if (!diffbuf) { ((linediff_fn)priv)(mb[i].ptr, mb[i].size); continue; } diffbuf = xrealloc(diffbuf, buflen + mb[i].size); memcpy(diffbuf + buflen, mb[i].ptr, mb[i].size); ((linediff_fn)priv)(diffbuf, buflen + mb[i].size); free(diffbuf); diffbuf = NULL; buflen = 0; } if (diffbuf) { ((linediff_fn)priv)(diffbuf, buflen); free(diffbuf); diffbuf = NULL; buflen = 0; } return 0; } int cgit_diff_files(const unsigned char *old_sha1, const unsigned char *new_sha1, linediff_fn fn) { mmfile_t file1, file2; xpparam_t diff_params; xdemitconf_t emit_params; xdemitcb_t emit_cb; if (!load_mmfile(&file1, old_sha1) || !load_mmfile(&file2, new_sha1)) return 1; diff_params.flags = XDF_NEED_MINIMAL; emit_params.ctxlen = 3; emit_params.flags = XDL_EMIT_FUNCNAMES; emit_cb.outf = filediff_cb; emit_cb.priv = fn; xdl_diff(&file1, &file2, &diff_params, &emit_params, &emit_cb); return 0; } void cgit_diff_tree(const unsigned char *old_sha1, const unsigned char *new_sha1, filepair_fn fn) { struct diff_options opt; int ret; diff_setup(&opt); opt.output_format = DIFF_FORMAT_CALLBACK; opt.detect_rename = 1; opt.recursive = 1; opt.format_callback = cgit_diff_tree_cb; opt.format_callback_data = fn; diff_setup_done(&opt); if (old_sha1) ret = diff_tree_sha1(old_sha1, new_sha1, "", &opt); else ret = diff_root_tree_sha1(new_sha1, "", &opt); diffcore_std(&opt); diff_flush(&opt); } void cgit_diff_commit(struct commit *commit, filepair_fn fn) { unsigned char *old_sha1 = NULL; if (commit->parents) old_sha1 = commit->parents->item->object.sha1; cgit_diff_tree(old_sha1, commit->object.sha1, fn); } diff --git a/ui-repolist.c b/ui-repolist.c index 8724589..33e3e7f 100644 --- a/ui-repolist.c +++ b/ui-repolist.c @@ -1,63 +1,68 @@ /* ui-repolist.c: functions for generating the repolist page * * Copyright (C) 2006 Lars Hjemli * * Licensed under GNU General Public License v2 * (see COPYING for full license text) */ #include "cgit.h" void cgit_print_repolist(struct cacheitem *item) { struct repoinfo *repo; int i; char *last_group = NULL; cgit_print_docstart(cgit_root_title, item); cgit_print_pageheader(cgit_root_title, 0); html("<table class='list nowrap'>"); + if (cgit_index_header) { + html("<tr class='nohover'><td colspan='4' class='include-block'>"); + html_include(cgit_index_header); + html("</td></tr>"); + } html("<tr class='nohover'>" "<th class='left'>Name</th>" "<th class='left'>Description</th>" "<th class='left'>Owner</th>" "<th class='left'>Links</th></tr>\n"); for (i=0; i<cgit_repolist.count; i++) { repo = &cgit_repolist.repos[i]; if ((last_group == NULL && repo->group != NULL) || (last_group != NULL && repo->group == NULL) || (last_group != NULL && repo->group!= NULL && strcmp(repo->group, last_group))) { html("<tr class='nohover'><td colspan='4' class='repogroup'>"); html_txt(repo->group); html("</td></tr>"); last_group = repo->group; } html("<tr><td>"); html_link_open(cgit_repourl(repo->url), NULL, NULL); html_txt(repo->name); html_link_close(); html("</td><td>"); html_ntxt(cgit_max_repodesc_len, repo->desc); html("</td><td>"); html_txt(repo->owner); html("</td><td>"); html_link_open(cgit_pageurl(repo->name, "commit", NULL), "Commit: display last commit", NULL); html("C</a> "); html_link_open(cgit_pageurl(repo->name, "diff", NULL), "Diff: see changes introduced by last commit", NULL); html("D</a> "); html_link_open(cgit_pageurl(repo->name, "log", NULL), "Log: show history of the main branch", NULL); html("L</a> "); html_link_open(cgit_pageurl(repo->name, "tree", NULL), "Tree: browse the files in the main branch", NULL); html("T</a>"); html("</td></tr>\n"); } html("</table>"); cgit_print_docend(); } diff --git a/ui-shared.c b/ui-shared.c index 6211056..c7fbc5e 100644 --- a/ui-shared.c +++ b/ui-shared.c @@ -1,177 +1,180 @@ /* 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" 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); } static long ttl_seconds(long ttl) { if (ttl<0) return 60 * 60 * 24 * 365; else return ttl * 60; } void cgit_print_error(char *msg) { html("<div class='error'>"); html_txt(msg); html("</div>\n"); } char *cgit_rooturl() { if (cgit_virtual_root) return fmt("%s/", cgit_virtual_root); else return cgit_script_name; } 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) { if (query) return fmt("%s/%s/%s/?%s", cgit_virtual_root, reponame, pagename, query); else return fmt("%s/%s/%s/", cgit_virtual_root, reponame, pagename); } else { - return fmt("?r=%s&p=%s&%s", reponame, pagename, query); + if (query) + return fmt("?r=%s&p=%s&%s", reponame, pagename, query); + else + return fmt("?r=%s&p=%s", reponame, pagename); } } char *cgit_currurl() { if (!cgit_virtual_root) return cgit_script_name; else if (cgit_query_page) return fmt("%s/%s/%s/", cgit_virtual_root, cgit_query_repo, cgit_query_page); else if (cgit_query_repo) return fmt("%s/%s/", cgit_virtual_root, cgit_query_repo); else return fmt("%s/", cgit_virtual_root); } void cgit_print_date(unsigned long secs) { char buf[32]; struct tm *time; time = gmtime(&secs); strftime(buf, sizeof(buf), "%Y-%m-%d %H:%M:%S", time); html_txt(buf); } void cgit_print_docstart(char *title, struct cacheitem *item) { html("Content-Type: text/html; charset=utf-8\n"); htmlf("Last-Modified: %s\n", http_date(item->st.st_mtime)); htmlf("Expires: %s\n", http_date(item->st.st_mtime + ttl_seconds(item->ttl))); html("\n"); html(cgit_doctype); html("<html>\n"); html("<head>\n"); html("<title>"); html_txt(title); html("</title>\n"); htmlf("<meta name='generator' content='cgit v%s'/>\n", cgit_version); html("<link rel='stylesheet' type='text/css' href='"); html_attr(cgit_css); html("'/>\n"); html("</head>\n"); html("<body>\n"); } void cgit_print_docend() { html("</td></tr></table>"); html("</body>\n</html>\n"); } void cgit_print_pageheader(char *title, int show_search) { html("<table id='layout'>"); html("<tr><td id='header'>"); html(cgit_root_title); html("</td><td id='logo'>"); html("<a href='"); html_attr(cgit_logo_link); htmlf("'><img src='%s'/></a>", cgit_logo); html("</td></tr>"); html("<tr><td id='crumb'>"); htmlf("<a href='%s'>root</a>", cgit_rooturl()); if (cgit_query_repo) { htmlf(" : <a href='%s'>", cgit_repourl(cgit_repo->url)); html_txt(cgit_repo->name); htmlf("</a> : %s", title); } html("</td>"); html("<td id='search'>"); if (show_search) { html("<form method='get' href='"); html_attr(cgit_currurl()); html("'>"); if (!cgit_virtual_root) { if (cgit_query_repo) html_hidden("r", cgit_query_repo); if (cgit_query_page) html_hidden("p", cgit_query_page); } if (cgit_query_head) html_hidden("h", cgit_query_head); if (cgit_query_sha1) html_hidden("id", cgit_query_sha1); if (cgit_query_sha2) html_hidden("id2", cgit_query_sha2); html("<input type='text' name='q' value='"); html_attr(cgit_query_search); html("'/></form>"); } html("</td></tr>"); html("<tr><td id='content' colspan='2'>"); } void cgit_print_snapshot_start(const char *mimetype, const char *filename, struct cacheitem *item) { htmlf("Content-Type: %s\n", mimetype); htmlf("Content-Disposition: inline; filename=\"%s\"\n", filename); htmlf("Last-Modified: %s\n", http_date(item->st.st_mtime)); htmlf("Expires: %s\n", http_date(item->st.st_mtime + ttl_seconds(item->ttl))); html("\n"); } |