From: Lars Hjemli Date: Tue, 27 Jan 2009 19:16:37 +0000 (+0100) Subject: Merge branch 'lh/stats' X-Git-Url: https://gitweb.ps.run/ps-cgit/commitdiff_plain/7710178e45dee61e85ea77c4221309ce8c086f95?hp=b54ef9749c083afd86573112fad3b3ed8ee2d0e4 Merge branch 'lh/stats' Conflicts: cgit.c cgit.css cgit.h ui-tree.c Signed-off-by: Lars Hjemli --- diff --git a/Makefile b/Makefile index f426f98..a52285e 100644 --- a/Makefile +++ b/Makefile @@ -1,11 +1,33 @@ CGIT_VERSION = v0.8.1 CGIT_SCRIPT_NAME = cgit.cgi CGIT_SCRIPT_PATH = /var/www/htdocs/cgit +CGIT_DATA_PATH = $(CGIT_SCRIPT_PATH) CGIT_CONFIG = /etc/cgitrc CACHE_ROOT = /var/cache/cgit SHA1_HEADER = -GIT_VER = 1.6.0.2 +GIT_VER = 1.6.1 GIT_URL = http://www.kernel.org/pub/software/scm/git/git-$(GIT_VER).tar.bz2 +INSTALL = install + +# Define NO_STRCASESTR if you don't have strcasestr. +# +# Define NEEDS_LIBICONV if linking with libc is not enough (eg. Darwin). +# + +#-include config.mak + +# +# Platform specific tweaks +# + +uname_S := $(shell sh -c 'uname -s 2>/dev/null || echo not') +uname_O := $(shell sh -c 'uname -o 2>/dev/null || echo not') +uname_R := $(shell sh -c 'uname -r 2>/dev/null || echo not') + +ifeq ($(uname_O),Cygwin) + NO_STRCASESTR = YesPlease + NEEDS_LIBICONV = YesPlease +endif # # Let the user override the above settings. @@ -97,6 +119,9 @@ CFLAGS += -DCGIT_CACHE_ROOT='"$(CACHE_ROOT)"' ifdef NO_ICONV CFLAGS += -DNO_ICONV endif +ifdef NO_STRCASESTR + CFLAGS += -DNO_STRCASESTR +endif cgit: $(OBJECTS) libgit $(QUIET_CC)$(CC) $(CFLAGS) $(LDFLAGS) -o cgit $(OBJECTS) $(EXTLIBS) @@ -106,22 +131,23 @@ cgit.o: VERSION -include $(OBJECTS:.o=.d) libgit: - $(QUIET_SUBDIR0)git $(QUIET_SUBDIR1) libgit.a - $(QUIET_SUBDIR0)git $(QUIET_SUBDIR1) xdiff/lib.a + $(QUIET_SUBDIR0)git $(QUIET_SUBDIR1) NO_CURL=1 libgit.a + $(QUIET_SUBDIR0)git $(QUIET_SUBDIR1) NO_CURL=1 xdiff/lib.a test: all $(QUIET_SUBDIR0)tests $(QUIET_SUBDIR1) all install: all - mkdir -p $(DESTDIR)$(CGIT_SCRIPT_PATH) - install cgit $(DESTDIR)$(CGIT_SCRIPT_PATH)/$(CGIT_SCRIPT_NAME) - install cgit.css $(DESTDIR)$(CGIT_SCRIPT_PATH)/cgit.css - install cgit.png $(DESTDIR)$(CGIT_SCRIPT_PATH)/cgit.png + $(INSTALL) -m 0755 -d $(DESTDIR)$(CGIT_SCRIPT_PATH) + $(INSTALL) -m 0755 cgit $(DESTDIR)$(CGIT_SCRIPT_PATH)/$(CGIT_SCRIPT_NAME) + $(INSTALL) -m 0755 -d $(DESTDIR)$(CGIT_DATA_PATH) + $(INSTALL) -m 0644 cgit.css $(DESTDIR)$(CGIT_DATA_PATH)/cgit.css + $(INSTALL) -m 0644 cgit.png $(DESTDIR)$(CGIT_DATA_PATH)/cgit.png uninstall: rm -f $(CGIT_SCRIPT_PATH)/$(CGIT_SCRIPT_NAME) - rm -f $(CGIT_SCRIPT_PATH)/cgit.css - rm -f $(CGIT_SCRIPT_PATH)/cgit.png + rm -f $(CGIT_DATA_PATH)/cgit.css + rm -f $(CGIT_DATA_PATH)/cgit.png clean: rm -f cgit VERSION *.o *.d diff --git a/cache.c b/cache.c index 57068a1..d7a8d5a 100644 --- a/cache.c +++ b/cache.c @@ -416,11 +416,11 @@ int cache_ls(const char *path) fullname, strerror(err), err); continue; } - printf("%s %s %10zd %s\n", + printf("%s %s %10"PRIuMAX" %s\n", name, sprintftime("%Y-%m-%d %H:%M:%S", slot.cache_st.st_mtime), - slot.cache_st.st_size, + (uintmax_t)slot.cache_st.st_size, slot.buf); close_slot(&slot); } diff --git a/cgit.c b/cgit.c index 57e11cd..608cab6 100644 --- a/cgit.c +++ b/cgit.c @@ -159,6 +159,10 @@ static void querystring_cb(const char *name, const char *value) ctx.qry.name = xstrdup(value); } else if (!strcmp(name, "mimetype")) { ctx.qry.mimetype = xstrdup(value); + } else if (!strcmp(name, "s")){ + ctx.qry.sort = xstrdup(value); + } else if (!strcmp(name, "showmsg")) { + ctx.qry.showmsg = atoi(value); } else if (!strcmp(name, "period")) { ctx.qry.period = xstrdup(value); } @@ -297,7 +301,6 @@ static void process_request(void *cbdata) cmd = cgit_get_cmd(ctx); if (!cmd) { ctx->page.title = "cgit error"; - ctx->repo = NULL; cgit_print_http_headers(ctx); cgit_print_docstart(ctx); cgit_print_pageheader(ctx); @@ -443,28 +446,29 @@ int main(int argc, const char **argv) ctx.repo = NULL; http_parse_querystring(ctx.qry.raw, querystring_cb); - /* If virtual-root isn't specified in cgitrc and no url - * parameter is specified on the querystring, lets pretend - * that virtualroot equals SCRIPT_NAME and use PATH_INFO as - * url. This allows cgit to work with virtual urls without - * the need for rewriterules in the webserver (as long as - * PATH_INFO is included in the cache lookup key). + /* If virtual-root isn't specified in cgitrc, lets pretend + * that virtual-root equals SCRIPT_NAME. */ - if (!ctx.cfg.virtual_root && !ctx.qry.url) { + if (!ctx.cfg.virtual_root) ctx.cfg.virtual_root = ctx.cfg.script_name; - path = getenv("PATH_INFO"); - if (path) { - if (path[0] == '/') - path++; - ctx.qry.url = xstrdup(path); - if (ctx.qry.raw) { - qry = ctx.qry.raw; - ctx.qry.raw = xstrdup(fmt("%s?%s", path, qry)); - free(qry); - } else - ctx.qry.raw = ctx.qry.url; - cgit_parse_url(ctx.qry.url); - } + + /* If no url parameter is specified on the querystring, lets + * use PATH_INFO as url. This allows cgit to work with virtual + * urls without the need for rewriterules in the webserver (as + * long as PATH_INFO is included in the cache lookup key). + */ + path = getenv("PATH_INFO"); + if (!ctx.qry.url && path) { + if (path[0] == '/') + path++; + ctx.qry.url = xstrdup(path); + if (ctx.qry.raw) { + qry = ctx.qry.raw; + ctx.qry.raw = xstrdup(fmt("%s?%s", path, qry)); + free(qry); + } else + ctx.qry.raw = ctx.qry.url; + cgit_parse_url(ctx.qry.url); } ttl = calc_ttl(); diff --git a/cgit.css b/cgit.css index ef30fbf..e8214de 100644 --- a/cgit.css +++ b/cgit.css @@ -120,6 +120,10 @@ table.list tr { background: white; } +table.list tr.logheader { + background: #eee; +} + table.list tr:hover { background: #eee; } @@ -143,6 +147,17 @@ table.list td { padding: 0.1em 0.5em 0.1em 0.5em; } +table.list td.logsubject { + font-family: monospace; + font-weight: bold; +} + +table.list td.logmsg { + font-family: monospace; + white-space: pre; + padding: 1em 0em 2em 0em; +} + table.list td a { color: black; } @@ -456,6 +471,30 @@ div.footer { font-size: 80%; color: #ccc; } +a.branch-deco { + margin: 0px 0.5em; + padding: 0px 0.25em; + background-color: #88ff88; + border: solid 1px #007700; +} +a.tag-deco { + margin: 0px 0.5em; + padding: 0px 0.25em; + background-color: #ffff88; + border: solid 1px #777700; +} +a.remote-deco { + margin: 0px 0.5em; + padding: 0px 0.25em; + background-color: #ccccff; + border: solid 1px #000077; +} +a.deco { + margin: 0px 0.5em; + padding: 0px 0.25em; + background-color: #ff8888; + border: solid 1px #770000; +} table.stats { border: solid 1px black; border-collapse: collapse; @@ -532,4 +571,3 @@ table.hgraph div.bar { background-color: #eee; height: 1em; } - diff --git a/cgit.h b/cgit.h index f2cb671..4fe94c6 100644 --- a/cgit.h +++ b/cgit.h @@ -62,6 +62,7 @@ struct cgit_repo { int enable_log_filecount; int enable_log_linecount; int max_stats; + time_t mtime; }; struct cgit_repolist { @@ -123,6 +124,8 @@ struct cgit_query { char *period; int ofs; int nohead; + char *sort; + int showmsg; }; struct cgit_config { @@ -236,11 +239,5 @@ extern const char *cgit_repobasename(const char *reponame); extern int cgit_parse_snapshots_mask(const char *str); -/* libgit.a either links against or compiles its own implementation of - * strcasestr(), and we'd like to reuse it. Simply re-declaring it - * seems to do the trick. - */ -extern char *strcasestr(const char *haystack, const char *needle); - #endif /* CGIT_H */ diff --git a/cgitrc.5.txt b/cgitrc.5.txt index 0bbbea3..09f56a6 100644 --- a/cgitrc.5.txt +++ b/cgitrc.5.txt @@ -302,8 +302,8 @@ root-desc=tracking the foobar development root-readme=/var/www/htdocs/about.html -# Allow download of tar.gz, tar.bz and zip-files -snapshots=tar.gz tar.bz zip +# Allow download of tar.gz, tar.bz2 and zip-files +snapshots=tar.gz tar.bz2 zip ## diff --git a/cmd.c b/cmd.c index 763a558..cf97da7 100644 --- a/cmd.c +++ b/cmd.c @@ -105,8 +105,7 @@ static void refs_fn(struct cgit_context *ctx) static void snapshot_fn(struct cgit_context *ctx) { - cgit_print_snapshot(ctx->qry.head, ctx->qry.sha1, - cgit_repobasename(ctx->repo->url), ctx->qry.path, + cgit_print_snapshot(ctx->qry.head, ctx->qry.sha1, ctx->qry.path, ctx->repo->snapshots, ctx->qry.nohead); } diff --git a/git b/git index 97a7a82..8104ebf 160000 --- a/git +++ b/git @@ -1 +1 @@ -Subproject commit 97a7a82f199f165f85fe39a3c318b18c621e6335 +Subproject commit 8104ebfe8276657ee803cca7eb8665a78cf3ef83 diff --git a/parsing.c b/parsing.c index c8f3048..f3f3b15 100644 --- a/parsing.c +++ b/parsing.c @@ -96,6 +96,9 @@ char *parse_user(char *t, char **name, char **email, unsigned long *date) return p; } +#ifdef NO_ICONV +#define reencode(a, b, c) +#else const char *reencode(char **txt, const char *src_enc, const char *dst_enc) { char *tmp; @@ -110,6 +113,7 @@ const char *reencode(char **txt, const char *src_enc, const char *dst_enc) } return *txt; } +#endif struct commitinfo *cgit_parse_commit(struct commit *commit) { diff --git a/shared.c b/shared.c index 7382609..578a544 100644 --- a/shared.c +++ b/shared.c @@ -61,6 +61,7 @@ struct cgit_repo *cgit_add_repo(const char *url) ret->max_stats = ctx.cfg.max_stats; ret->module_link = ctx.cfg.module_link; ret->readme = NULL; + ret->mtime = -1; return ret; } @@ -267,10 +268,12 @@ int cgit_diff_files(const unsigned char *old_sha1, if (!load_mmfile(&file1, old_sha1) || !load_mmfile(&file2, new_sha1)) return 1; + memset(&diff_params, 0, sizeof(diff_params)); + memset(&emit_params, 0, sizeof(emit_params)); + memset(&emit_cb, 0, sizeof(emit_cb)); diff_params.flags = XDF_NEED_MINIMAL; emit_params.ctxlen = 3; emit_params.flags = XDL_EMIT_FUNCNAMES; - emit_params.find_func = NULL; emit_cb.outf = filediff_cb; emit_cb.priv = fn; xdl_diff(&file1, &file2, &diff_params, &emit_params, &emit_cb); diff --git a/tests/setup.sh b/tests/setup.sh index 1457dd5..30f90d5 100755 --- a/tests/setup.sh +++ b/tests/setup.sh @@ -25,11 +25,13 @@ mkrepo() { mkdir -p $name cd $name git init - for ((n=1; n<=count; n++)) + n=1 + while test $n -le $count do echo $n >file-$n git add file-$n git commit -m "commit $n" + n=$(expr $n + 1) done if test "$3" = "testplus" then @@ -101,7 +103,7 @@ run_test() { desc=$1 script=$2 - ((test_count++)) + test_count=$(expr $test_count + 1) printf "\ntest %d: name='%s'\n" $test_count "$desc" >>test-output.log printf "test %d: eval='%s'\n" $test_count "$2" >>test-output.log eval "$2" >>test-output.log 2>>test-output.log @@ -111,7 +113,7 @@ run_test() then printf " %2d) %-60s [ok]\n" $test_count "$desc" else - ((test_failed++)) + test_failed=$(expr $test_failed + 1) printf " %2d) %-60s [failed]\n" $test_count "$desc" fi } diff --git a/tests/t0010-validate-html.sh b/tests/t0010-validate-html.sh index 94aa52b..3fe4800 100755 --- a/tests/t0010-validate-html.sh +++ b/tests/t0010-validate-html.sh @@ -9,7 +9,7 @@ test_url() test -z "$NO_TIDY_WARNINGS" || tidy_opt+=" --show-warnings no" cgit_url "$1" >trash/tidy-$test_count || return sed -ie "1,4d" trash/tidy-$test_count || return - tidy $tidy_opt trash/tidy-$test_count + "$tidy" $tidy_opt trash/tidy-$test_count rc=$? # tidy returns with exitcode 1 on warnings, 2 on error @@ -23,6 +23,13 @@ test_url() prepare_tests 'Validate html with tidy' +tidy=`which tidy` +test -n "$tidy" || { + echo "Skipping tests: tidy not found" + tests_done + exit +} + run_test 'index page' 'test_url ""' run_test 'foo' 'test_url "foo"' run_test 'foo/log' 'test_url "foo/log"' diff --git a/tests/t0104-tree.sh b/tests/t0104-tree.sh index 0d62cc8..33f4eb0 100755 --- a/tests/t0104-tree.sh +++ b/tests/t0104-tree.sh @@ -15,7 +15,7 @@ run_test 'find line 1' ' ' run_test 'no line 2' ' - grep -e "2" trash/tmp + ! grep -e "2" trash/tmp ' run_test 'generate foo+bar/tree' 'cgit_url "foo%2bbar/tree" >trash/tmp' diff --git a/tests/t0107-snapshot.sh b/tests/t0107-snapshot.sh index d97c465..8ab4912 100755 --- a/tests/t0107-snapshot.sh +++ b/tests/t0107-snapshot.sh @@ -4,36 +4,36 @@ prepare_tests "Verify snapshot" -run_test 'get foo/snapshot/test.tar.gz' ' - cgit_url "foo/snapshot/test.tar.gz" >trash/tmp +run_test 'get foo/snapshot/master.tar.gz' ' + cgit_url "foo/snapshot/master.tar.gz" >trash/tmp ' run_test 'check html headers' ' head -n 1 trash/tmp | - grep -e "Content-Type: application/x-tar" && + grep -e "Content-Type: application/x-gzip" && head -n 2 trash/tmp | - grep -e "Content-Disposition: inline; filename=.test.tar.gz." + grep -e "Content-Disposition: inline; filename=.master.tar.gz." ' run_test 'strip off the header lines' ' - tail -n +6 trash/tmp > trash/test.tar.gz + tail -n +6 trash/tmp > trash/master.tar.gz ' -run_test 'verify gzip format' 'gunzip --test trash/test.tar.gz' +run_test 'verify gzip format' 'gunzip --test trash/master.tar.gz' run_test 'untar' ' - rm -rf trash/foo && - tar -xf trash/test.tar.gz -C trash + rm -rf trash/master && + tar -xf trash/master.tar.gz -C trash ' run_test 'count files' ' - c=$(ls -1 trash/foo/ | wc -l) && + c=$(ls -1 trash/master/ | wc -l) && test $c = 5 ' run_test 'verify untarred file-5' ' - grep -e "^5$" trash/foo/file-5 && - test $(cat trash/foo/file-5 | wc -l) = 1 + grep -e "^5$" trash/master/file-5 && + test $(cat trash/master/file-5 | wc -l) = 1 ' tests_done diff --git a/ui-log.c b/ui-log.c index 8dd8b89..3202848 100644 --- a/ui-log.c +++ b/ui-log.c @@ -31,21 +31,57 @@ void inspect_files(struct diff_filepair *pair) cgit_diff_files(pair->one->sha1, pair->two->sha1, count_lines); } +void show_commit_decorations(struct commit *commit) +{ + struct name_decoration *deco; + static char buf[1024]; + + buf[sizeof(buf) - 1] = 0; + deco = lookup_decoration(&name_decoration, &commit->object); + while (deco) { + if (!prefixcmp(deco->name, "refs/heads/")) { + strncpy(buf, deco->name + 11, sizeof(buf) - 1); + cgit_log_link(buf, NULL, "branch-deco", buf, NULL, NULL, + 0, NULL, NULL, ctx.qry.showmsg); + } + else if (!prefixcmp(deco->name, "tag: refs/tags/")) { + strncpy(buf, deco->name + 15, sizeof(buf) - 1); + cgit_tag_link(buf, NULL, "tag-deco", ctx.qry.head, buf); + } + else if (!prefixcmp(deco->name, "refs/remotes/")) { + strncpy(buf, deco->name + 13, sizeof(buf) - 1); + cgit_log_link(buf, NULL, "remote-deco", NULL, + sha1_to_hex(commit->object.sha1), NULL, + 0, NULL, NULL, ctx.qry.showmsg); + } + else { + strncpy(buf, deco->name, sizeof(buf) - 1); + cgit_commit_link(buf, NULL, "deco", ctx.qry.head, + sha1_to_hex(commit->object.sha1)); + } + deco = deco->next; + } +} + void print_commit(struct commit *commit) { struct commitinfo *info; char *tmp; + int cols = 2; info = cgit_parse_commit(commit); - html(""); + htmlf("", + ctx.qry.showmsg ? " class='logheader'" : ""); tmp = fmt("id=%s", sha1_to_hex(commit->object.sha1)); tmp = cgit_pageurl(ctx.repo->url, "commit", tmp); html_link_open(tmp, NULL, NULL); cgit_print_age(commit->date, TM_WEEK * 2, FMT_SHORTDATE); html_link_close(); - html(""); + htmlf("", + ctx.qry.showmsg ? " class='logsubject'" : ""); cgit_commit_link(info->subject, NULL, NULL, ctx.qry.head, sha1_to_hex(commit->object.sha1)); + show_commit_decorations(commit); html(""); html_txt(info->author); if (ctx.repo->enable_log_filecount) { @@ -61,21 +97,45 @@ void print_commit(struct commit *commit) } } html("\n"); + if (ctx.qry.showmsg) { + if (ctx.repo->enable_log_filecount) { + cols++; + if (ctx.repo->enable_log_linecount) + cols++; + } + htmlf("", + cols); + html_txt(info->msg); + html("\n"); + } cgit_free_commitinfo(info); } +static const char *disambiguate_ref(const char *ref) +{ + unsigned char sha1[20]; + const char *longref; + + longref = fmt("refs/heads/%s", ref); + if (get_sha1(longref, sha1) == 0) + return longref; + + return ref; +} void cgit_print_log(const char *tip, int ofs, int cnt, char *grep, char *pattern, char *path, int pager) { struct rev_info rev; struct commit *commit; - const char *argv[] = {NULL, tip, NULL, NULL, NULL}; + const char *argv[] = {NULL, NULL, NULL, NULL, NULL}; int argc = 2; int i, columns = 3; if (!tip) - argv[1] = ctx.qry.head; + tip = ctx.qry.head; + + argv[1] = disambiguate_ref(tip); if (grep && pattern && (!strcmp(grep, "grep") || !strcmp(grep, "author") || @@ -92,6 +152,8 @@ void cgit_print_log(const char *tip, int ofs, int cnt, char *grep, char *pattern rev.verbose_header = 1; rev.show_root_diff = 0; setup_revisions(argc, argv, &rev, NULL); + load_ref_decorations(); + rev.show_decorations = 1; rev.grep_filter.regflags |= REG_ICASE; compile_grep_patterns(&rev.grep_filter); prepare_revision_walk(&rev); @@ -100,8 +162,16 @@ void cgit_print_log(const char *tip, int ofs, int cnt, char *grep, char *pattern html(""); html("" - "" - ""); + ""); if (ctx.repo->enable_log_filecount) { html(""); columns++; @@ -136,20 +206,20 @@ void cgit_print_log(const char *tip, int ofs, int cnt, char *grep, char *pattern cgit_log_link("[prev]", NULL, NULL, ctx.qry.head, ctx.qry.sha1, ctx.qry.path, ofs - cnt, ctx.qry.grep, - ctx.qry.search); + ctx.qry.search, ctx.qry.showmsg); html(" "); } if ((commit = get_revision(&rev)) != NULL) { cgit_log_link("[next]", NULL, NULL, ctx.qry.head, ctx.qry.sha1, ctx.qry.path, ofs + cnt, ctx.qry.grep, - ctx.qry.search); + ctx.qry.search, ctx.qry.showmsg); } html(""); } else if ((commit = get_revision(&rev)) != NULL) { html("\n"); } } diff --git a/ui-patch.c b/ui-patch.c index e60877d..1d77336 100644 --- a/ui-patch.c +++ b/ui-patch.c @@ -101,9 +101,9 @@ void cgit_print_patch(char *hex) ctx.page.filename = patchname; cgit_print_http_headers(&ctx); htmlf("From %s Mon Sep 17 00:00:00 2001\n", sha1_to_hex(sha1)); - htmlf("From: %s%s\n", info->author, info->author_email); + htmlf("From: %s %s\n", info->author, info->author_email); html("Date: "); - cgit_print_date(info->author_date, "%a, %d %b %Y %H:%M:%S %z%n", ctx.cfg.local_time); + cgit_print_date(info->author_date, "%a, %d %b %Y %H:%M:%S %z%n", ctx.cfg.local_time); htmlf("Subject: %s\n\n", info->subject); if (info->msg && *info->msg) { htmlf("%s", info->msg); diff --git a/ui-plain.c b/ui-plain.c index be559e0..5addd9e 100644 --- a/ui-plain.c +++ b/ui-plain.c @@ -18,7 +18,7 @@ static void print_object(const unsigned char *sha1, const char *path) { enum object_type type; char *buf; - size_t size; + unsigned long size; type = sha1_object_info(sha1, &size); if (type == OBJ_BAD) { diff --git a/ui-refs.c b/ui-refs.c index 32e0429..25da00a 100644 --- a/ui-refs.c +++ b/ui-refs.c @@ -58,7 +58,8 @@ static int print_branch(struct refinfo *ref) if (!info) return 1; html("" - "" + "" "" "\n"); header = 1; } +static void print_tag_downloads(const struct cgit_repo *repo, const char *ref) +{ + const struct cgit_snapshot_format* f; + char *filename; + const char *basename; + + if (!ref || strlen(ref) < 2) + return; + + basename = cgit_repobasename(repo->url); + if (prefixcmp(ref, basename) != 0) { + if ((ref[0] == 'v' || ref[0] == 'V') && isdigit(ref[1])) + ref++; + if (isdigit(ref[0])) + ref = xstrdup(fmt("%s-%s", basename, ref)); + } + + for (f = cgit_snapshot_formats; f->suffix; f++) { + if (!(repo->snapshots & f->bit)) + continue; + filename = fmt("%s%s", ref, f->suffix); + cgit_snapshot_link(filename, NULL, NULL, NULL, NULL, filename); + html("  "); + } +} static int print_tag(struct refinfo *ref) { struct tag *tag; @@ -98,7 +124,10 @@ static int print_tag(struct refinfo *ref) html("\n"); } return 0; diff --git a/ui-repolist.c b/ui-repolist.c index ab050c7..2c13d50 100644 --- a/ui-repolist.c +++ b/ui-repolist.c @@ -6,6 +6,10 @@ * (see COPYING for full license text) */ +/* This is needed for strcasestr to be defined by */ +#define _GNU_SOURCE 1 +#include + #include #include "cgit.h" @@ -19,7 +23,8 @@ time_t read_agefile(char *path) if (!(f = fopen(path, "r"))) return -1; - fgets(buf, sizeof(buf), f); + if (fgets(buf, sizeof(buf), f) == NULL) + return -1; fclose(f); if (parse_date(buf, buf2, sizeof(buf2))) return strtoul(buf2, NULL, 10); @@ -27,21 +32,38 @@ time_t read_agefile(char *path) return 0; } -static void print_modtime(struct cgit_repo *repo) +static int get_repo_modtime(const struct cgit_repo *repo, time_t *mtime) { char *path; struct stat s; + struct cgit_repo *r = (struct cgit_repo *)repo; + if (repo->mtime != -1) { + *mtime = repo->mtime; + return 1; + } path = fmt("%s/%s", repo->path, ctx.cfg.agefile); if (stat(path, &s) == 0) { - cgit_print_age(read_agefile(path), -1, NULL); - return; + *mtime = read_agefile(path); + r->mtime = *mtime; + return 1; } path = fmt("%s/refs/heads/%s", repo->path, repo->defbranch); - if (stat(path, &s) != 0) - return; - cgit_print_age(s.st_mtime, -1, NULL); + if (stat(path, &s) == 0) + *mtime = s.st_mtime; + else + *mtime = 0; + + r->mtime = *mtime; + return (r->mtime != 0); +} + +static void print_modtime(struct cgit_repo *repo) +{ + time_t t; + if (get_repo_modtime(repo, &t)) + cgit_print_age(t, -1, NULL); } int is_match(struct cgit_repo *repo) @@ -68,13 +90,23 @@ int is_in_url(struct cgit_repo *repo) return 0; } +void print_sort_header(const char *title, const char *sort) +{ + htmlf("", title); +} + void print_header(int columns) { - html("" - "" - "" - "" - ""); + html(""); + print_sort_header("Name", "name"); + print_sort_header("Description", "desc"); + print_sort_header("Owner", "owner"); + print_sort_header("Idle", "idle"); if (ctx.cfg.enable_index_links) html(""); html("\n"); @@ -91,10 +123,86 @@ void print_pager(int items, int pagelen, char *search) html(""); } +static int cmp(const char *s1, const char *s2) +{ + if (s1 && s2) + return strcmp(s1, s2); + if (s1 && !s2) + return -1; + if (s2 && !s1) + return 1; + return 0; +} + +static int sort_name(const void *a, const void *b) +{ + const struct cgit_repo *r1 = a; + const struct cgit_repo *r2 = b; + + return cmp(r1->name, r2->name); +} + +static int sort_desc(const void *a, const void *b) +{ + const struct cgit_repo *r1 = a; + const struct cgit_repo *r2 = b; + + return cmp(r1->desc, r2->desc); +} + +static int sort_owner(const void *a, const void *b) +{ + const struct cgit_repo *r1 = a; + const struct cgit_repo *r2 = b; + + return cmp(r1->owner, r2->owner); +} + +static int sort_idle(const void *a, const void *b) +{ + const struct cgit_repo *r1 = a; + const struct cgit_repo *r2 = b; + time_t t1, t2; + + t1 = t2 = 0; + get_repo_modtime(r1, &t1); + get_repo_modtime(r2, &t2); + return t2 - t1; +} + +struct sortcolumn { + const char *name; + int (*fn)(const void *a, const void *b); +}; + +struct sortcolumn sortcolumn[] = { + {"name", sort_name}, + {"desc", sort_desc}, + {"owner", sort_owner}, + {"idle", sort_idle}, + {NULL, NULL} +}; + +int sort_repolist(char *field) +{ + struct sortcolumn *column; + + for (column = &sortcolumn[0]; column->name; column++) { + if (strcmp(field, column->name)) + continue; + qsort(cgit_repolist.repos, cgit_repolist.count, + sizeof(struct cgit_repo), column->fn); + return 1; + } + return 0; +} + + void cgit_print_repolist() { int i, columns = 4, hits = 0, header = 0; char *last_group = NULL; + int sorted = 0; if (ctx.cfg.enable_index_links) columns++; @@ -107,6 +215,9 @@ void cgit_print_repolist() if (ctx.cfg.index_header) html_include(ctx.cfg.index_header); + if(ctx.qry.sort) + sorted = sort_repolist(ctx.qry.sort); + html("
AgeCommit messageAuthorCommit message"); + if (pager) { + html(" ("); + cgit_log_link(ctx.qry.showmsg ? "Collapse" : "Expand", NULL, + NULL, ctx.qry.head, ctx.qry.sha1, + ctx.qry.path, ctx.qry.ofs, ctx.qry.grep, + ctx.qry.search, ctx.qry.showmsg ? 0 : 1); + html(")"); + } + html("AuthorFiles
"); cgit_log_link("[...]", NULL, NULL, ctx.qry.head, NULL, NULL, 0, - NULL, NULL); + NULL, NULL, ctx.qry.showmsg); html("
"); - cgit_log_link(name, NULL, NULL, name, NULL, NULL, 0, NULL, NULL); + cgit_log_link(name, NULL, NULL, name, NULL, NULL, 0, NULL, NULL, + ctx.qry.showmsg); html(""); if (ref->object->type == OBJ_COMMIT) { @@ -78,12 +79,37 @@ static int print_branch(struct refinfo *ref) static void print_tag_header() { html("
TagReferenceDownloadAuthorAge
"); cgit_tag_link(name, NULL, NULL, ctx.qry.head, name); html(""); - cgit_object_link(tag->tagged); + if (ctx.repo->snapshots && (tag->tagged->type == OBJ_COMMIT)) + print_tag_downloads(ctx.repo, name); + else + cgit_object_link(tag->tagged); html(""); if (info->tagger) html(info->tagger); @@ -110,9 +139,12 @@ static int print_tag(struct refinfo *ref) if (!header) print_tag_header(); html("
"); - html_txt(name); + cgit_tag_link(name, NULL, NULL, ctx.qry.head, name); html(""); - cgit_object_link(ref->object); + if (ctx.repo->snapshots && (ref->object->type == OBJ_COMMIT)) + print_tag_downloads(ctx.repo, name); + else + cgit_object_link(ref->object); html("
%s
NameDescriptionOwnerIdle
Links
"); for (i=0; igroup != NULL) || + if (!sorted && + ((last_group == NULL && ctx.repo->group != NULL) || (last_group != NULL && ctx.repo->group == NULL) || (last_group != NULL && ctx.repo->group != NULL && - strcmp(ctx.repo->group, last_group))) { + strcmp(ctx.repo->group, last_group)))) { htmlf(""); } diff --git a/ui-shared.c b/ui-shared.c index 1bb30c2..4f28512 100644 --- a/ui-shared.c +++ b/ui-shared.c @@ -281,7 +281,8 @@ void cgit_plain_link(char *name, char *title, char *class, char *head, } void cgit_log_link(char *name, char *title, char *class, char *head, - char *rev, char *path, int ofs, char *grep, char *pattern) + char *rev, char *path, int ofs, char *grep, char *pattern, + int showmsg) { char *delim; @@ -305,6 +306,11 @@ void cgit_log_link(char *name, char *title, char *class, char *head, html(delim); html("ofs="); htmlf("%d", ofs); + delim = "&"; + } + if (showmsg) { + html(delim); + html("showmsg=1"); } html("'>"); html_txt(name); @@ -371,11 +377,14 @@ void cgit_stats_link(char *name, char *title, char *class, char *head, void cgit_object_link(struct object *obj) { - char *page, *rev, *name; + char *page, *shortrev, *fullrev, *name; + fullrev = sha1_to_hex(obj->sha1); + shortrev = xstrdup(fullrev); + shortrev[10] = '\0'; if (obj->type == OBJ_COMMIT) { - cgit_commit_link(fmt("commit %s", sha1_to_hex(obj->sha1)), NULL, NULL, - ctx.qry.head, sha1_to_hex(obj->sha1)); + cgit_commit_link(fmt("commit %s...", shortrev), NULL, NULL, + ctx.qry.head, fullrev); return; } else if (obj->type == OBJ_TREE) page = "tree"; @@ -383,9 +392,8 @@ void cgit_object_link(struct object *obj) page = "tag"; else page = "blob"; - rev = sha1_to_hex(obj->sha1); - name = fmt("%s %s", typename(obj->type), rev); - reporevlink(page, name, NULL, NULL, ctx.qry.head, rev, NULL); + name = fmt("%s %s...", typename(obj->type), shortrev); + reporevlink(page, name, NULL, NULL, ctx.qry.head, fullrev, NULL); } void cgit_print_date(time_t secs, char *format, int local_time) @@ -574,6 +582,8 @@ void cgit_add_hidden_formfields(int incl_head, int incl_search, char *page) html_hidden("id", ctx.qry.sha1); if (ctx.qry.sha2) html_hidden("id2", ctx.qry.sha2); + if (ctx.qry.showmsg) + html_hidden("showmsg", "1"); if (incl_search) { if (ctx.qry.grep) @@ -583,15 +593,20 @@ void cgit_add_hidden_formfields(int incl_head, int incl_search, char *page) } } +const char *fallback_cmd = "repolist"; + char *hc(struct cgit_cmd *cmd, const char *page) { - return (strcmp(cmd->name, page) ? NULL : "active"); + return (strcmp(cmd ? cmd->name : fallback_cmd, page) ? NULL : "active"); } void cgit_print_pageheader(struct cgit_context *ctx) { struct cgit_cmd *cmd = cgit_get_cmd(ctx); + if (!cmd && ctx->repo) + fallback_cmd = "summary"; + html("
", columns); html_txt(ctx.repo->group); @@ -130,7 +242,7 @@ void cgit_print_repolist() last_group = ctx.repo->group; } htmlf("
", - ctx.repo->group ? "sublevel-repo" : "toplevel-repo"); + !sorted && ctx.repo->group ? "sublevel-repo" : "toplevel-repo"); cgit_summary_link(ctx.repo->name, ctx.repo->name, NULL, NULL); html(""); html_link_open(cgit_repourl(ctx.repo->url), NULL, NULL); @@ -145,7 +257,7 @@ void cgit_print_repolist() html(""); cgit_summary_link("summary", NULL, "button", NULL); cgit_log_link("log", NULL, "button", NULL, NULL, NULL, - 0, NULL, NULL); + 0, NULL, NULL, ctx.qry.showmsg); cgit_tree_link("tree", NULL, "button", NULL, NULL, NULL); html("
\n"); html("\n"); html("\n"); html("\n"); } @@ -106,7 +108,7 @@ static int ls_item(const unsigned char *sha1, const char *base, int baselen, html(""); cgit_log_link("log", NULL, "button", ctx.qry.head, curr_rev, - fullpath, 0, NULL, NULL); + fullpath, 0, NULL, NULL, ctx.qry.showmsg); if (ctx.repo->max_stats) cgit_stats_link("stats", NULL, "button", ctx.qry.head, fullpath);