-rw-r--r-- | Makefile | 2 | ||||
-rw-r--r-- | cgit.c | 2 | ||||
-rw-r--r-- | cgit.css | 50 | ||||
-rw-r--r-- | cgit.h | 5 | ||||
-rw-r--r-- | parsing.c | 1 | ||||
-rw-r--r-- | ui-commit.c | 80 | ||||
-rw-r--r-- | ui-log.c | 11 | ||||
-rw-r--r-- | ui-tree.c | 6 | ||||
-rw-r--r-- | ui-view.c | 2 |
9 files changed, 140 insertions, 19 deletions
@@ -1,30 +1,30 @@ CGIT_VERSION = 0.1-pre INSTALL_BIN = /var/www/htdocs/cgit.cgi INSTALL_CSS = /var/www/htdocs/cgit.css CACHE_ROOT = /var/cache/cgit 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.c ui-tree.c + ui-summary.o ui-log.o ui-view.c ui-tree.c ui-commit.c CFLAGS += -Wall all: cgit install: all clean-cache install cgit $(INSTALL_BIN) install cgit.css $(INSTALL_CSS) cgit: cgit.c cgit.h git.h $(OBJECTS) $(CC) $(CFLAGS) -DCGIT_VERSION='"$(CGIT_VERSION)"' cgit.c -o cgit \ $(OBJECTS) $(EXTLIBS) $(OBJECTS): cgit.h git.h .PHONY: clean clean: rm -f cgit *.o clean-cache: rm -rf $(CACHE_ROOT)/* @@ -1,82 +1,84 @@ /* 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 void cgit_print_repo_page(struct cacheitem *item) { if (chdir(fmt("%s/%s", cgit_root, cgit_query_repo)) || cgit_read_config("info/cgit", cgit_repo_config_cb)) { char *title = fmt("%s - %s", cgit_root_title, "Bad request"); cgit_print_docstart(title, item); cgit_print_pageheader(title); cgit_print_error(fmt("Unable to scan repository: %s", strerror(errno))); cgit_print_docend(); return; } setenv("GIT_DIR", fmt("%s/%s", cgit_root, cgit_query_repo), 1); char *title = fmt("%s - %s", cgit_repo_name, cgit_repo_desc); cgit_print_docstart(title, item); cgit_print_pageheader(title); if (!cgit_query_page) { cgit_print_summary(); } else if (!strcmp(cgit_query_page, "log")) { cgit_print_log(cgit_query_head, cgit_query_ofs, 100); } else if (!strcmp(cgit_query_page, "tree")) { cgit_print_tree(cgit_query_sha1); + } else if (!strcmp(cgit_query_page, "commit")) { + cgit_print_commit(cgit_query_sha1); } else if (!strcmp(cgit_query_page, "view")) { cgit_print_view(cgit_query_sha1); } cgit_print_docend(); } static void cgit_fill_cache(struct cacheitem *item) { htmlfd = item->fd; item->st.st_mtime = time(NULL); if (cgit_query_repo) cgit_print_repo_page(item); else cgit_print_repolist(item); } static void cgit_check_cache(struct cacheitem *item) { int i = 0; cache_prepare(item); 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); cache_unlock(item); } else { cache_cancel_lock(item); } } else if (cache_expired(item) && cache_lock(item)) { if (cache_expired(item)) { cgit_fill_cache(item); cache_unlock(item); } else { cache_cancel_lock(item); } } } static void cgit_print_cache(struct cacheitem *item) @@ -1,78 +1,120 @@ 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; + padding: 0.1em 0.5em 0.1em 0.5em; vertical-align: baseline; } table.list td { border: none; - padding: 0.1em 1em 0.1em 0.5em; + padding: 0.1em 0.5em 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#blob { + border: solid 1px black; +} + div.error { color: red; font-weight: bold; margin: 1em 2em; } div.ls-dir a { font-weight: bold; } th.filesize, td.filesize { text-align: right; } th.filemode, td.filemode { text-align: center; } td.blob { white-space: pre; font-family: courier; font-size: 100%; -}
\ No newline at end of file +} + +table.log td { + white-space: nowrap; +} + +table.commit-info { + border-collapse: collapse; + margin-top: 1em; + +} +table.commit-info th { + text-align: left; + font-weight: normal; + padding: 0.1em 1em 0.1em 0.1em; +} +table.commit-info td { + font-weight: normal; + padding: 0.1em 1em 0.1em 0.1em; +} +div.commit-subject { + font-weight: bold; + font-size: 110%; + margin: 1em 0em 1em; +} +div.commit-msg { + white-space: pre; + font-family: courier; + font-size: 100%; +} +.sha1 { + font-family: courier; + font-size: 90%; +} +.left { + text-align: left; +} +.right { + text-align: right; +} @@ -47,52 +47,53 @@ extern char *cgit_repo_owner; 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_head; extern char *cgit_query_sha1; extern int cgit_query_ofs; extern int htmlfd; 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 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); 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 void cache_prepare(struct cacheitem *item); 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_docstart(char *title, struct cacheitem *item); extern void cgit_print_docend(); extern void cgit_print_pageheader(char *title); 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); -extern void cgit_print_view(char *hex); -extern void cgit_print_tree(const char *sha1); +extern void cgit_print_view(const char *hex); +extern void cgit_print_tree(const char *hex); +extern void cgit_print_commit(const char *hex); #endif /* CGIT_H */ @@ -105,55 +105,56 @@ int cgit_parse_query(char *txt, configfn fn) return 0; } 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; 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 = strchr(p, '\n') + 1; } if (!strncmp(p, "committer ", 9)) { p += 9; t = strchr(p, '<') - 1; ret->committer = substr(p, t); p = strchr(p, '\n') + 1; } while (*p == '\n') p = strchr(p, '\n') + 1; t = strchr(p, '\n'); ret->subject = substr(p, t); + p = t + 1; while (*p == '\n') p = strchr(p, '\n') + 1; ret->msg = p; return ret; } diff --git a/ui-commit.c b/ui-commit.c new file mode 100644 index 0000000..1c0e7e5 --- a/dev/null +++ b/ui-commit.c @@ -0,0 +1,80 @@ +#include "cgit.h" + +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_commit(const char *hex) +{ + struct commit *commit; + struct commitinfo *info; + struct commit_list *p; + unsigned long size; + char type[20]; + char *buf; + + unsigned char sha1[20]; + + if (get_sha1(hex, sha1)) { + cgit_print_error(fmt("Bad object id: %s", hex)); + return; + } + + buf = read_sha1_file(sha1, type, &size); + if (!buf) { + cgit_print_error(fmt("Bad object reference: %s", hex)); + return; + } + + commit = lookup_commit(sha1); + if (!commit) { + cgit_print_error(fmt("Bad commit reference: %s", hex)); + return; + } + + commit->buffer = buf; + if (parse_commit_buffer(commit, buf, size)) { + cgit_print_error(fmt("Malformed commit buffer: %s", hex)); + return; + } + + info = cgit_parse_commit(commit); + + html("<table class='commit-info'>\n"); + html("<tr><th>author</th><td colspan='2'>"); + html_txt(info->author); + html("</td></tr>\n"); + html("<tr><th>committer</th><td>"); + html_txt(info->committer); + html("</td><td class='right'>"); + cgit_print_date(commit->date); + html("</td></tr>\n"); + html("<tr><th>tree</th><td colspan='2' class='sha1'><a href='"); + html_attr(cgit_pageurl(cgit_query_repo, "tree", fmt("id=%s", sha1_to_hex(commit->tree->object.sha1)))); + htmlf("'>%s</a></td></tr>\n", sha1_to_hex(commit->tree->object.sha1)); + + for (p = commit->parents; p ; p = p->next) { + html("<tr><th>parent</th><td colspan='2' class='sha1'><a href='"); + html_attr(cgit_pageurl(cgit_query_repo, "commit", fmt("id=%s", sha1_to_hex(p->item->object.sha1)))); + htmlf("'>%s</a></td></tr>\n", + sha1_to_hex(p->item->object.sha1)); + } + html("</table>\n"); + html("<div class='commit-subject'>"); + html_txt(info->subject); + html("</div>"); + html("<div class='commit-msg'>"); + html_txt(info->msg); + html("</div>"); + free(info->author); + free(info->committer); + free(info->subject); + free(info); +} @@ -1,97 +1,92 @@ /* ui-log.c: functions for log output * * Copyright (C) 2006 Lars Hjemli * * Licensed under GNU General Public License v2 * (see COPYING for full license text) */ #include "cgit.h" void print_commit(struct commit *commit) { char buf[32]; struct commitinfo *info; struct tm *time; info = cgit_parse_commit(commit); time = gmtime(&commit->date); html("<tr><td>"); strftime(buf, sizeof(buf), "%Y-%m-%d %H:%M:%S", time); html_txt(buf); html("</td><td>"); char *qry = fmt("id=%s", sha1_to_hex(commit->object.sha1)); - char *url = cgit_pageurl(cgit_query_repo, "view", qry); + char *url = cgit_pageurl(cgit_query_repo, "commit", qry); html_link_open(url, NULL, NULL); html_txt(info->subject); html_link_close(); html("</td><td>"); html_txt(info->author); - html("</td><td><a href='"); - html_attr(cgit_pageurl(cgit_query_repo, "tree", - fmt("id=%s", - sha1_to_hex(commit->tree->object.sha1)))); - html("'>tree</a>"); html("</td></tr>\n"); free(info->author); free(info->committer); free(info->subject); free(info); } 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 i; 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><th>Link</th></tr>\n"); + html("<table class='list log'>"); + html("<tr><th class='left'>Date</th><th class='left'>Message</th><th class='left'>Author</th></tr>\n"); if (ofs<0) ofs = 0; for (i = 0; i < ofs && (commit = get_revision(&rev)) != NULL; i++) { free(commit->buffer); commit->buffer = NULL; free_commit_list(commit->parents); commit->parents = NULL; } for (i = 0; i < cnt && (commit = get_revision(&rev)) != NULL; i++) { print_commit(commit); free(commit->buffer); commit->buffer = NULL; free_commit_list(commit->parents); commit->parents = NULL; } html("</table>\n"); html("<div class='pager'>"); if (ofs > 0) { html(" <a href='"); html(cgit_pageurl(cgit_query_repo, cgit_query_page, fmt("h=%s&ofs=%d", tip, ofs-cnt))); html("'>[prev]</a> "); } if ((commit = get_revision(&rev)) != NULL) { html(" <a href='"); html(cgit_pageurl(cgit_query_repo, "log", fmt("h=%s&ofs=%d", tip, ofs+cnt))); html("'>[next]</a> "); } html("</div>"); } @@ -17,54 +17,54 @@ static int print_entry(const unsigned char *sha1, const char *base, char type[20]; unsigned long size; if (sha1_object_info(sha1, type, &size)) { cgit_print_error(fmt("Bad object name: %s", sha1_to_hex(sha1))); return 0; } name = xstrdup(pathname); html("<tr><td>"); if (S_ISDIR(mode)) { html("<div class='ls-dir'><a href='"); html_attr(cgit_pageurl(cgit_query_repo, "tree", fmt("id=%s", sha1_to_hex(sha1)))); } else { html("<div class='ls-blob'><a href='"); html_attr(cgit_pageurl(cgit_query_repo, "view", fmt("id=%s", sha1_to_hex(sha1)))); } html("'>"); html_txt(name); if (S_ISDIR(mode)) html("/"); html("</a></div></td>"); htmlf("<td class='filesize'>%li</td>", size); htmlf("<td class='filemode'>%06o</td>", mode); html("</tr>\n"); free(name); return 0; } void cgit_print_tree(const char *hex) { struct tree *tree; unsigned char sha1[20]; if (get_sha1_hex(hex, sha1)) { cgit_print_error(fmt("Invalid object id: %s", hex)); return; } tree = parse_tree_indirect(sha1); if (!tree) { cgit_print_error(fmt("Not a tree object: %s", hex)); return; } html("<h2>Tree content</h2>\n"); html("<table class='list'>\n"); - html("<tr><th>Name</th>"); - html("<th class='filesize'>Size</th>"); - html("<th class='filemode'>Mode</th></tr>\n"); + html("<tr><th class='left'>Name</th>"); + html("<th class='right'>Size</th>"); + html("<th class='right'>Mode</th></tr>\n"); read_tree_recursive(tree, "", 0, 1, NULL, print_entry); html("</table>\n"); } @@ -1,42 +1,42 @@ /* ui-view.c: functions to output _any_ object, given it's sha1 * * Copyright (C) 2006 Lars Hjemli * * Licensed under GNU General Public License v2 * (see COPYING for full license text) */ #include "cgit.h" -void cgit_print_view(char *hex) +void cgit_print_view(const char *hex) { unsigned char sha1[20]; char type[20]; unsigned char *buf; unsigned long size; if (get_sha1_hex(hex, sha1)){ cgit_print_error(fmt("Bad hex value: %s", hex)); return; } if (sha1_object_info(sha1, type, &size)){ cgit_print_error("Bad object name"); return; } buf = read_sha1_file(sha1, type, &size); if (!buf) { cgit_print_error("Error reading object"); return; } buf[size] = '\0'; html("<h2>Object content</h2>\n"); html("<table class='list'>\n"); htmlf("<tr><th>%s %s, %li bytes</th></tr>\n", type, hex, size); html("<tr><td class='blob'>\n"); html_txt(buf); html("\n</td></tr>\n"); html("</table>\n"); } |