summaryrefslogtreecommitdiffabout
Unidiff
Diffstat (more/less context) (ignore whitespace changes)
-rw-r--r--Makefile44
-rw-r--r--cache.c4
-rw-r--r--cgit.c46
-rw-r--r--cgit.css40
-rw-r--r--cgit.h9
-rw-r--r--cgitrc.5.txt4
-rw-r--r--cmd.c3
m---------git0
-rw-r--r--parsing.c4
-rw-r--r--shared.c5
-rwxr-xr-xtests/setup.sh8
-rwxr-xr-xtests/t0010-validate-html.sh9
-rwxr-xr-xtests/t0104-tree.sh2
-rwxr-xr-xtests/t0107-snapshot.sh22
-rw-r--r--ui-log.c88
-rw-r--r--ui-patch.c4
-rw-r--r--ui-plain.c2
-rw-r--r--ui-refs.c42
-rw-r--r--ui-repolist.c144
-rw-r--r--ui-shared.c36
-rw-r--r--ui-shared.h2
-rw-r--r--ui-snapshot.c100
-rw-r--r--ui-snapshot.h3
-rw-r--r--ui-tag.c16
-rw-r--r--ui-tree.c8
25 files changed, 485 insertions, 160 deletions
diff --git a/Makefile b/Makefile
index f426f98..a52285e 100644
--- a/Makefile
+++ b/Makefile
@@ -1,11 +1,33 @@
1CGIT_VERSION = v0.8.1 1CGIT_VERSION = v0.8.1
2CGIT_SCRIPT_NAME = cgit.cgi 2CGIT_SCRIPT_NAME = cgit.cgi
3CGIT_SCRIPT_PATH = /var/www/htdocs/cgit 3CGIT_SCRIPT_PATH = /var/www/htdocs/cgit
4CGIT_DATA_PATH = $(CGIT_SCRIPT_PATH)
4CGIT_CONFIG = /etc/cgitrc 5CGIT_CONFIG = /etc/cgitrc
5CACHE_ROOT = /var/cache/cgit 6CACHE_ROOT = /var/cache/cgit
6SHA1_HEADER = <openssl/sha.h> 7SHA1_HEADER = <openssl/sha.h>
7GIT_VER = 1.6.0.2 8GIT_VER = 1.6.1
8GIT_URL = http://www.kernel.org/pub/software/scm/git/git-$(GIT_VER).tar.bz2 9GIT_URL = http://www.kernel.org/pub/software/scm/git/git-$(GIT_VER).tar.bz2
10INSTALL = install
11
12# Define NO_STRCASESTR if you don't have strcasestr.
13#
14# Define NEEDS_LIBICONV if linking with libc is not enough (eg. Darwin).
15#
16
17#-include config.mak
18
19#
20# Platform specific tweaks
21#
22
23uname_S := $(shell sh -c 'uname -s 2>/dev/null || echo not')
24uname_O := $(shell sh -c 'uname -o 2>/dev/null || echo not')
25uname_R := $(shell sh -c 'uname -r 2>/dev/null || echo not')
26
27ifeq ($(uname_O),Cygwin)
28 NO_STRCASESTR = YesPlease
29 NEEDS_LIBICONV = YesPlease
30endif
9 31
10# 32#
11# Let the user override the above settings. 33# Let the user override the above settings.
@@ -97,6 +119,9 @@ CFLAGS += -DCGIT_CACHE_ROOT='"$(CACHE_ROOT)"'
97ifdef NO_ICONV 119ifdef NO_ICONV
98 CFLAGS += -DNO_ICONV 120 CFLAGS += -DNO_ICONV
99endif 121endif
122ifdef NO_STRCASESTR
123 CFLAGS += -DNO_STRCASESTR
124endif
100 125
101cgit: $(OBJECTS) libgit 126cgit: $(OBJECTS) libgit
102 $(QUIET_CC)$(CC) $(CFLAGS) $(LDFLAGS) -o cgit $(OBJECTS) $(EXTLIBS) 127 $(QUIET_CC)$(CC) $(CFLAGS) $(LDFLAGS) -o cgit $(OBJECTS) $(EXTLIBS)
@@ -106,22 +131,23 @@ cgit.o: VERSION
106-include $(OBJECTS:.o=.d) 131-include $(OBJECTS:.o=.d)
107 132
108libgit: 133libgit:
109 $(QUIET_SUBDIR0)git $(QUIET_SUBDIR1) libgit.a 134 $(QUIET_SUBDIR0)git $(QUIET_SUBDIR1) NO_CURL=1 libgit.a
110 $(QUIET_SUBDIR0)git $(QUIET_SUBDIR1) xdiff/lib.a 135 $(QUIET_SUBDIR0)git $(QUIET_SUBDIR1) NO_CURL=1 xdiff/lib.a
111 136
112test: all 137test: all
113 $(QUIET_SUBDIR0)tests $(QUIET_SUBDIR1) all 138 $(QUIET_SUBDIR0)tests $(QUIET_SUBDIR1) all
114 139
115install: all 140install: all
116 mkdir -p $(DESTDIR)$(CGIT_SCRIPT_PATH) 141 $(INSTALL) -m 0755 -d $(DESTDIR)$(CGIT_SCRIPT_PATH)
117 install cgit $(DESTDIR)$(CGIT_SCRIPT_PATH)/$(CGIT_SCRIPT_NAME) 142 $(INSTALL) -m 0755 cgit $(DESTDIR)$(CGIT_SCRIPT_PATH)/$(CGIT_SCRIPT_NAME)
118 install cgit.css $(DESTDIR)$(CGIT_SCRIPT_PATH)/cgit.css 143 $(INSTALL) -m 0755 -d $(DESTDIR)$(CGIT_DATA_PATH)
119 install cgit.png $(DESTDIR)$(CGIT_SCRIPT_PATH)/cgit.png 144 $(INSTALL) -m 0644 cgit.css $(DESTDIR)$(CGIT_DATA_PATH)/cgit.css
145 $(INSTALL) -m 0644 cgit.png $(DESTDIR)$(CGIT_DATA_PATH)/cgit.png
120 146
121uninstall: 147uninstall:
122 rm -f $(CGIT_SCRIPT_PATH)/$(CGIT_SCRIPT_NAME) 148 rm -f $(CGIT_SCRIPT_PATH)/$(CGIT_SCRIPT_NAME)
123 rm -f $(CGIT_SCRIPT_PATH)/cgit.css 149 rm -f $(CGIT_DATA_PATH)/cgit.css
124 rm -f $(CGIT_SCRIPT_PATH)/cgit.png 150 rm -f $(CGIT_DATA_PATH)/cgit.png
125 151
126clean: 152clean:
127 rm -f cgit VERSION *.o *.d 153 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)
416 fullname, strerror(err), err); 416 fullname, strerror(err), err);
417 continue; 417 continue;
418 } 418 }
419 printf("%s %s %10zd %s\n", 419 printf("%s %s %10"PRIuMAX" %s\n",
420 name, 420 name,
421 sprintftime("%Y-%m-%d %H:%M:%S", 421 sprintftime("%Y-%m-%d %H:%M:%S",
422 slot.cache_st.st_mtime), 422 slot.cache_st.st_mtime),
423 slot.cache_st.st_size, 423 (uintmax_t)slot.cache_st.st_size,
424 slot.buf); 424 slot.buf);
425 close_slot(&slot); 425 close_slot(&slot);
426 } 426 }
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)
159 ctx.qry.name = xstrdup(value); 159 ctx.qry.name = xstrdup(value);
160 } else if (!strcmp(name, "mimetype")) { 160 } else if (!strcmp(name, "mimetype")) {
161 ctx.qry.mimetype = xstrdup(value); 161 ctx.qry.mimetype = xstrdup(value);
162 } else if (!strcmp(name, "s")){
163 ctx.qry.sort = xstrdup(value);
164 } else if (!strcmp(name, "showmsg")) {
165 ctx.qry.showmsg = atoi(value);
162 } else if (!strcmp(name, "period")) { 166 } else if (!strcmp(name, "period")) {
163 ctx.qry.period = xstrdup(value); 167 ctx.qry.period = xstrdup(value);
164 } 168 }
@@ -297,7 +301,6 @@ static void process_request(void *cbdata)
297 cmd = cgit_get_cmd(ctx); 301 cmd = cgit_get_cmd(ctx);
298 if (!cmd) { 302 if (!cmd) {
299 ctx->page.title = "cgit error"; 303 ctx->page.title = "cgit error";
300 ctx->repo = NULL;
301 cgit_print_http_headers(ctx); 304 cgit_print_http_headers(ctx);
302 cgit_print_docstart(ctx); 305 cgit_print_docstart(ctx);
303 cgit_print_pageheader(ctx); 306 cgit_print_pageheader(ctx);
@@ -443,28 +446,29 @@ int main(int argc, const char **argv)
443 ctx.repo = NULL; 446 ctx.repo = NULL;
444 http_parse_querystring(ctx.qry.raw, querystring_cb); 447 http_parse_querystring(ctx.qry.raw, querystring_cb);
445 448
446 /* If virtual-root isn't specified in cgitrc and no url 449 /* If virtual-root isn't specified in cgitrc, lets pretend
447 * parameter is specified on the querystring, lets pretend 450 * that virtual-root equals SCRIPT_NAME.
448 * that virtualroot equals SCRIPT_NAME and use PATH_INFO as
449 * url. This allows cgit to work with virtual urls without
450 * the need for rewriterules in the webserver (as long as
451 * PATH_INFO is included in the cache lookup key).
452 */ 451 */
453 if (!ctx.cfg.virtual_root && !ctx.qry.url) { 452 if (!ctx.cfg.virtual_root)
454 ctx.cfg.virtual_root = ctx.cfg.script_name; 453 ctx.cfg.virtual_root = ctx.cfg.script_name;
455 path = getenv("PATH_INFO"); 454
456 if (path) { 455 /* If no url parameter is specified on the querystring, lets
457 if (path[0] == '/') 456 * use PATH_INFO as url. This allows cgit to work with virtual
458 path++; 457 * urls without the need for rewriterules in the webserver (as
459 ctx.qry.url = xstrdup(path); 458 * long as PATH_INFO is included in the cache lookup key).
460 if (ctx.qry.raw) { 459 */
461 qry = ctx.qry.raw; 460 path = getenv("PATH_INFO");
462 ctx.qry.raw = xstrdup(fmt("%s?%s", path, qry)); 461 if (!ctx.qry.url && path) {
463 free(qry); 462 if (path[0] == '/')
464 } else 463 path++;
465 ctx.qry.raw = ctx.qry.url; 464 ctx.qry.url = xstrdup(path);
466 cgit_parse_url(ctx.qry.url); 465 if (ctx.qry.raw) {
467 } 466 qry = ctx.qry.raw;
467 ctx.qry.raw = xstrdup(fmt("%s?%s", path, qry));
468 free(qry);
469 } else
470 ctx.qry.raw = ctx.qry.url;
471 cgit_parse_url(ctx.qry.url);
468 } 472 }
469 473
470 ttl = calc_ttl(); 474 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 {
120 background: white; 120 background: white;
121} 121}
122 122
123table.list tr.logheader {
124 background: #eee;
125}
126
123table.list tr:hover { 127table.list tr:hover {
124 background: #eee; 128 background: #eee;
125} 129}
@@ -143,6 +147,17 @@ table.list td {
143 padding: 0.1em 0.5em 0.1em 0.5em; 147 padding: 0.1em 0.5em 0.1em 0.5em;
144} 148}
145 149
150table.list td.logsubject {
151 font-family: monospace;
152 font-weight: bold;
153}
154
155table.list td.logmsg {
156 font-family: monospace;
157 white-space: pre;
158 padding: 1em 0em 2em 0em;
159}
160
146table.list td a { 161table.list td a {
147 color: black; 162 color: black;
148} 163}
@@ -456,6 +471,30 @@ div.footer {
456 font-size: 80%; 471 font-size: 80%;
457 color: #ccc; 472 color: #ccc;
458} 473}
474a.branch-deco {
475 margin: 0px 0.5em;
476 padding: 0px 0.25em;
477 background-color: #88ff88;
478 border: solid 1px #007700;
479}
480a.tag-deco {
481 margin: 0px 0.5em;
482 padding: 0px 0.25em;
483 background-color: #ffff88;
484 border: solid 1px #777700;
485}
486a.remote-deco {
487 margin: 0px 0.5em;
488 padding: 0px 0.25em;
489 background-color: #ccccff;
490 border: solid 1px #000077;
491}
492a.deco {
493 margin: 0px 0.5em;
494 padding: 0px 0.25em;
495 background-color: #ff8888;
496 border: solid 1px #770000;
497}
459table.stats { 498table.stats {
460 border: solid 1px black; 499 border: solid 1px black;
461 border-collapse: collapse; 500 border-collapse: collapse;
@@ -532,4 +571,3 @@ table.hgraph div.bar {
532 background-color: #eee; 571 background-color: #eee;
533 height: 1em; 572 height: 1em;
534} 573}
535
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 {
62 int enable_log_filecount; 62 int enable_log_filecount;
63 int enable_log_linecount; 63 int enable_log_linecount;
64 int max_stats; 64 int max_stats;
65 time_t mtime;
65}; 66};
66 67
67struct cgit_repolist { 68struct cgit_repolist {
@@ -123,6 +124,8 @@ struct cgit_query {
123 char *period; 124 char *period;
124 int ofs; 125 int ofs;
125 int nohead; 126 int nohead;
127 char *sort;
128 int showmsg;
126}; 129};
127 130
128struct cgit_config { 131struct cgit_config {
@@ -236,11 +239,5 @@ extern const char *cgit_repobasename(const char *reponame);
236 239
237extern int cgit_parse_snapshots_mask(const char *str); 240extern int cgit_parse_snapshots_mask(const char *str);
238 241
239/* libgit.a either links against or compiles its own implementation of
240 * strcasestr(), and we'd like to reuse it. Simply re-declaring it
241 * seems to do the trick.
242 */
243extern char *strcasestr(const char *haystack, const char *needle);
244
245 242
246#endif /* CGIT_H */ 243#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
302root-readme=/var/www/htdocs/about.html 302root-readme=/var/www/htdocs/about.html
303 303
304 304
305# Allow download of tar.gz, tar.bz and zip-files 305# Allow download of tar.gz, tar.bz2 and zip-files
306snapshots=tar.gz tar.bz zip 306snapshots=tar.gz tar.bz2 zip
307 307
308 308
309## 309##
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)
105 105
106static void snapshot_fn(struct cgit_context *ctx) 106static void snapshot_fn(struct cgit_context *ctx)
107{ 107{
108 cgit_print_snapshot(ctx->qry.head, ctx->qry.sha1, 108 cgit_print_snapshot(ctx->qry.head, ctx->qry.sha1, ctx->qry.path,
109 cgit_repobasename(ctx->repo->url), ctx->qry.path,
110 ctx->repo->snapshots, ctx->qry.nohead); 109 ctx->repo->snapshots, ctx->qry.nohead);
111} 110}
112 111
diff --git a/git b/git
Subproject 97a7a82f199f165f85fe39a3c318b18c621e633 Subproject 8104ebfe8276657ee803cca7eb8665a78cf3ef8
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)
96 return p; 96 return p;
97} 97}
98 98
99#ifdef NO_ICONV
100#define reencode(a, b, c)
101#else
99const char *reencode(char **txt, const char *src_enc, const char *dst_enc) 102const char *reencode(char **txt, const char *src_enc, const char *dst_enc)
100{ 103{
101 char *tmp; 104 char *tmp;
@@ -110,6 +113,7 @@ const char *reencode(char **txt, const char *src_enc, const char *dst_enc)
110 } 113 }
111 return *txt; 114 return *txt;
112} 115}
116#endif
113 117
114struct commitinfo *cgit_parse_commit(struct commit *commit) 118struct commitinfo *cgit_parse_commit(struct commit *commit)
115{ 119{
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)
61 ret->max_stats = ctx.cfg.max_stats; 61 ret->max_stats = ctx.cfg.max_stats;
62 ret->module_link = ctx.cfg.module_link; 62 ret->module_link = ctx.cfg.module_link;
63 ret->readme = NULL; 63 ret->readme = NULL;
64 ret->mtime = -1;
64 return ret; 65 return ret;
65} 66}
66 67
@@ -267,10 +268,12 @@ int cgit_diff_files(const unsigned char *old_sha1,
267 if (!load_mmfile(&file1, old_sha1) || !load_mmfile(&file2, new_sha1)) 268 if (!load_mmfile(&file1, old_sha1) || !load_mmfile(&file2, new_sha1))
268 return 1; 269 return 1;
269 270
271 memset(&diff_params, 0, sizeof(diff_params));
272 memset(&emit_params, 0, sizeof(emit_params));
273 memset(&emit_cb, 0, sizeof(emit_cb));
270 diff_params.flags = XDF_NEED_MINIMAL; 274 diff_params.flags = XDF_NEED_MINIMAL;
271 emit_params.ctxlen = 3; 275 emit_params.ctxlen = 3;
272 emit_params.flags = XDL_EMIT_FUNCNAMES; 276 emit_params.flags = XDL_EMIT_FUNCNAMES;
273 emit_params.find_func = NULL;
274 emit_cb.outf = filediff_cb; 277 emit_cb.outf = filediff_cb;
275 emit_cb.priv = fn; 278 emit_cb.priv = fn;
276 xdl_diff(&file1, &file2, &diff_params, &emit_params, &emit_cb); 279 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() {
25 mkdir -p $name 25 mkdir -p $name
26 cd $name 26 cd $name
27 git init 27 git init
28 for ((n=1; n<=count; n++)) 28 n=1
29 while test $n -le $count
29 do 30 do
30 echo $n >file-$n 31 echo $n >file-$n
31 git add file-$n 32 git add file-$n
32 git commit -m "commit $n" 33 git commit -m "commit $n"
34 n=$(expr $n + 1)
33 done 35 done
34 if test "$3" = "testplus" 36 if test "$3" = "testplus"
35 then 37 then
@@ -101,7 +103,7 @@ run_test()
101{ 103{
102 desc=$1 104 desc=$1
103 script=$2 105 script=$2
104 ((test_count++)) 106 test_count=$(expr $test_count + 1)
105 printf "\ntest %d: name='%s'\n" $test_count "$desc" >>test-output.log 107 printf "\ntest %d: name='%s'\n" $test_count "$desc" >>test-output.log
106 printf "test %d: eval='%s'\n" $test_count "$2" >>test-output.log 108 printf "test %d: eval='%s'\n" $test_count "$2" >>test-output.log
107 eval "$2" >>test-output.log 2>>test-output.log 109 eval "$2" >>test-output.log 2>>test-output.log
@@ -111,7 +113,7 @@ run_test()
111 then 113 then
112 printf " %2d) %-60s [ok]\n" $test_count "$desc" 114 printf " %2d) %-60s [ok]\n" $test_count "$desc"
113 else 115 else
114 ((test_failed++)) 116 test_failed=$(expr $test_failed + 1)
115 printf " %2d) %-60s [failed]\n" $test_count "$desc" 117 printf " %2d) %-60s [failed]\n" $test_count "$desc"
116 fi 118 fi
117} 119}
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()
9 test -z "$NO_TIDY_WARNINGS" || tidy_opt+=" --show-warnings no" 9 test -z "$NO_TIDY_WARNINGS" || tidy_opt+=" --show-warnings no"
10 cgit_url "$1" >trash/tidy-$test_count || return 10 cgit_url "$1" >trash/tidy-$test_count || return
11 sed -ie "1,4d" trash/tidy-$test_count || return 11 sed -ie "1,4d" trash/tidy-$test_count || return
12 tidy $tidy_opt trash/tidy-$test_count 12 "$tidy" $tidy_opt trash/tidy-$test_count
13 rc=$? 13 rc=$?
14 14
15 # tidy returns with exitcode 1 on warnings, 2 on error 15 # tidy returns with exitcode 1 on warnings, 2 on error
@@ -23,6 +23,13 @@ test_url()
23 23
24prepare_tests 'Validate html with tidy' 24prepare_tests 'Validate html with tidy'
25 25
26tidy=`which tidy`
27test -n "$tidy" || {
28 echo "Skipping tests: tidy not found"
29 tests_done
30 exit
31}
32
26run_test 'index page' 'test_url ""' 33run_test 'index page' 'test_url ""'
27run_test 'foo' 'test_url "foo"' 34run_test 'foo' 'test_url "foo"'
28run_test 'foo/log' 'test_url "foo/log"' 35run_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' '
15' 15'
16 16
17run_test 'no line 2' ' 17run_test 'no line 2' '
18 grep -e "<a id=.n2. name=.n2. href=.#n2.>2</a>" trash/tmp 18 ! grep -e "<a id=.n2. name=.n2. href=.#n2.>2</a>" trash/tmp
19' 19'
20 20
21run_test 'generate foo+bar/tree' 'cgit_url "foo%2bbar/tree" >trash/tmp' 21run_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 @@
4 4
5prepare_tests "Verify snapshot" 5prepare_tests "Verify snapshot"
6 6
7run_test 'get foo/snapshot/test.tar.gz' ' 7run_test 'get foo/snapshot/master.tar.gz' '
8 cgit_url "foo/snapshot/test.tar.gz" >trash/tmp 8 cgit_url "foo/snapshot/master.tar.gz" >trash/tmp
9' 9'
10 10
11run_test 'check html headers' ' 11run_test 'check html headers' '
12 head -n 1 trash/tmp | 12 head -n 1 trash/tmp |
13 grep -e "Content-Type: application/x-tar" && 13 grep -e "Content-Type: application/x-gzip" &&
14 14
15 head -n 2 trash/tmp | 15 head -n 2 trash/tmp |
16 grep -e "Content-Disposition: inline; filename=.test.tar.gz." 16 grep -e "Content-Disposition: inline; filename=.master.tar.gz."
17' 17'
18 18
19run_test 'strip off the header lines' ' 19run_test 'strip off the header lines' '
20 tail -n +6 trash/tmp > trash/test.tar.gz 20 tail -n +6 trash/tmp > trash/master.tar.gz
21' 21'
22 22
23run_test 'verify gzip format' 'gunzip --test trash/test.tar.gz' 23run_test 'verify gzip format' 'gunzip --test trash/master.tar.gz'
24run_test 'untar' ' 24run_test 'untar' '
25 rm -rf trash/foo && 25 rm -rf trash/master &&
26 tar -xf trash/test.tar.gz -C trash 26 tar -xf trash/master.tar.gz -C trash
27' 27'
28 28
29run_test 'count files' ' 29run_test 'count files' '
30 c=$(ls -1 trash/foo/ | wc -l) && 30 c=$(ls -1 trash/master/ | wc -l) &&
31 test $c = 5 31 test $c = 5
32' 32'
33 33
34run_test 'verify untarred file-5' ' 34run_test 'verify untarred file-5' '
35 grep -e "^5$" trash/foo/file-5 && 35 grep -e "^5$" trash/master/file-5 &&
36 test $(cat trash/foo/file-5 | wc -l) = 1 36 test $(cat trash/master/file-5 | wc -l) = 1
37' 37'
38 38
39tests_done 39tests_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)
31 cgit_diff_files(pair->one->sha1, pair->two->sha1, count_lines); 31 cgit_diff_files(pair->one->sha1, pair->two->sha1, count_lines);
32} 32}
33 33
34void show_commit_decorations(struct commit *commit)
35{
36 struct name_decoration *deco;
37 static char buf[1024];
38
39 buf[sizeof(buf) - 1] = 0;
40 deco = lookup_decoration(&name_decoration, &commit->object);
41 while (deco) {
42 if (!prefixcmp(deco->name, "refs/heads/")) {
43 strncpy(buf, deco->name + 11, sizeof(buf) - 1);
44 cgit_log_link(buf, NULL, "branch-deco", buf, NULL, NULL,
45 0, NULL, NULL, ctx.qry.showmsg);
46 }
47 else if (!prefixcmp(deco->name, "tag: refs/tags/")) {
48 strncpy(buf, deco->name + 15, sizeof(buf) - 1);
49 cgit_tag_link(buf, NULL, "tag-deco", ctx.qry.head, buf);
50 }
51 else if (!prefixcmp(deco->name, "refs/remotes/")) {
52 strncpy(buf, deco->name + 13, sizeof(buf) - 1);
53 cgit_log_link(buf, NULL, "remote-deco", NULL,
54 sha1_to_hex(commit->object.sha1), NULL,
55 0, NULL, NULL, ctx.qry.showmsg);
56 }
57 else {
58 strncpy(buf, deco->name, sizeof(buf) - 1);
59 cgit_commit_link(buf, NULL, "deco", ctx.qry.head,
60 sha1_to_hex(commit->object.sha1));
61 }
62 deco = deco->next;
63 }
64}
65
34void print_commit(struct commit *commit) 66void print_commit(struct commit *commit)
35{ 67{
36 struct commitinfo *info; 68 struct commitinfo *info;
37 char *tmp; 69 char *tmp;
70 int cols = 2;
38 71
39 info = cgit_parse_commit(commit); 72 info = cgit_parse_commit(commit);
40 html("<tr><td>"); 73 htmlf("<tr%s><td>",
74 ctx.qry.showmsg ? " class='logheader'" : "");
41 tmp = fmt("id=%s", sha1_to_hex(commit->object.sha1)); 75 tmp = fmt("id=%s", sha1_to_hex(commit->object.sha1));
42 tmp = cgit_pageurl(ctx.repo->url, "commit", tmp); 76 tmp = cgit_pageurl(ctx.repo->url, "commit", tmp);
43 html_link_open(tmp, NULL, NULL); 77 html_link_open(tmp, NULL, NULL);
44 cgit_print_age(commit->date, TM_WEEK * 2, FMT_SHORTDATE); 78 cgit_print_age(commit->date, TM_WEEK * 2, FMT_SHORTDATE);
45 html_link_close(); 79 html_link_close();
46 html("</td><td>"); 80 htmlf("</td><td%s>",
81 ctx.qry.showmsg ? " class='logsubject'" : "");
47 cgit_commit_link(info->subject, NULL, NULL, ctx.qry.head, 82 cgit_commit_link(info->subject, NULL, NULL, ctx.qry.head,
48 sha1_to_hex(commit->object.sha1)); 83 sha1_to_hex(commit->object.sha1));
84 show_commit_decorations(commit);
49 html("</td><td>"); 85 html("</td><td>");
50 html_txt(info->author); 86 html_txt(info->author);
51 if (ctx.repo->enable_log_filecount) { 87 if (ctx.repo->enable_log_filecount) {
@@ -61,21 +97,45 @@ void print_commit(struct commit *commit)
61 } 97 }
62 } 98 }
63 html("</td></tr>\n"); 99 html("</td></tr>\n");
100 if (ctx.qry.showmsg) {
101 if (ctx.repo->enable_log_filecount) {
102 cols++;
103 if (ctx.repo->enable_log_linecount)
104 cols++;
105 }
106 htmlf("<tr class='nohover'><td/><td colspan='%d' class='logmsg'>",
107 cols);
108 html_txt(info->msg);
109 html("</td></tr>\n");
110 }
64 cgit_free_commitinfo(info); 111 cgit_free_commitinfo(info);
65} 112}
66 113
114static const char *disambiguate_ref(const char *ref)
115{
116 unsigned char sha1[20];
117 const char *longref;
118
119 longref = fmt("refs/heads/%s", ref);
120 if (get_sha1(longref, sha1) == 0)
121 return longref;
122
123 return ref;
124}
67 125
68void cgit_print_log(const char *tip, int ofs, int cnt, char *grep, char *pattern, 126void cgit_print_log(const char *tip, int ofs, int cnt, char *grep, char *pattern,
69 char *path, int pager) 127 char *path, int pager)
70{ 128{
71 struct rev_info rev; 129 struct rev_info rev;
72 struct commit *commit; 130 struct commit *commit;
73 const char *argv[] = {NULL, tip, NULL, NULL, NULL}; 131 const char *argv[] = {NULL, NULL, NULL, NULL, NULL};
74 int argc = 2; 132 int argc = 2;
75 int i, columns = 3; 133 int i, columns = 3;
76 134
77 if (!tip) 135 if (!tip)
78 argv[1] = ctx.qry.head; 136 tip = ctx.qry.head;
137
138 argv[1] = disambiguate_ref(tip);
79 139
80 if (grep && pattern && (!strcmp(grep, "grep") || 140 if (grep && pattern && (!strcmp(grep, "grep") ||
81 !strcmp(grep, "author") || 141 !strcmp(grep, "author") ||
@@ -92,6 +152,8 @@ void cgit_print_log(const char *tip, int ofs, int cnt, char *grep, char *pattern
92 rev.verbose_header = 1; 152 rev.verbose_header = 1;
93 rev.show_root_diff = 0; 153 rev.show_root_diff = 0;
94 setup_revisions(argc, argv, &rev, NULL); 154 setup_revisions(argc, argv, &rev, NULL);
155 load_ref_decorations();
156 rev.show_decorations = 1;
95 rev.grep_filter.regflags |= REG_ICASE; 157 rev.grep_filter.regflags |= REG_ICASE;
96 compile_grep_patterns(&rev.grep_filter); 158 compile_grep_patterns(&rev.grep_filter);
97 prepare_revision_walk(&rev); 159 prepare_revision_walk(&rev);
@@ -100,8 +162,16 @@ void cgit_print_log(const char *tip, int ofs, int cnt, char *grep, char *pattern
100 html("<table class='list nowrap'>"); 162 html("<table class='list nowrap'>");
101 163
102 html("<tr class='nohover'><th class='left'>Age</th>" 164 html("<tr class='nohover'><th class='left'>Age</th>"
103 "<th class='left'>Commit message</th>" 165 "<th class='left'>Commit message");
104 "<th class='left'>Author</th>"); 166 if (pager) {
167 html(" (");
168 cgit_log_link(ctx.qry.showmsg ? "Collapse" : "Expand", NULL,
169 NULL, ctx.qry.head, ctx.qry.sha1,
170 ctx.qry.path, ctx.qry.ofs, ctx.qry.grep,
171 ctx.qry.search, ctx.qry.showmsg ? 0 : 1);
172 html(")");
173 }
174 html("</th><th class='left'>Author</th>");
105 if (ctx.repo->enable_log_filecount) { 175 if (ctx.repo->enable_log_filecount) {
106 html("<th class='left'>Files</th>"); 176 html("<th class='left'>Files</th>");
107 columns++; 177 columns++;
@@ -136,20 +206,20 @@ void cgit_print_log(const char *tip, int ofs, int cnt, char *grep, char *pattern
136 cgit_log_link("[prev]", NULL, NULL, ctx.qry.head, 206 cgit_log_link("[prev]", NULL, NULL, ctx.qry.head,
137 ctx.qry.sha1, ctx.qry.path, 207 ctx.qry.sha1, ctx.qry.path,
138 ofs - cnt, ctx.qry.grep, 208 ofs - cnt, ctx.qry.grep,
139 ctx.qry.search); 209 ctx.qry.search, ctx.qry.showmsg);
140 html("&nbsp;"); 210 html("&nbsp;");
141 } 211 }
142 if ((commit = get_revision(&rev)) != NULL) { 212 if ((commit = get_revision(&rev)) != NULL) {
143 cgit_log_link("[next]", NULL, NULL, ctx.qry.head, 213 cgit_log_link("[next]", NULL, NULL, ctx.qry.head,
144 ctx.qry.sha1, ctx.qry.path, 214 ctx.qry.sha1, ctx.qry.path,
145 ofs + cnt, ctx.qry.grep, 215 ofs + cnt, ctx.qry.grep,
146 ctx.qry.search); 216 ctx.qry.search, ctx.qry.showmsg);
147 } 217 }
148 html("</div>"); 218 html("</div>");
149 } else if ((commit = get_revision(&rev)) != NULL) { 219 } else if ((commit = get_revision(&rev)) != NULL) {
150 html("<tr class='nohover'><td colspan='3'>"); 220 html("<tr class='nohover'><td colspan='3'>");
151 cgit_log_link("[...]", NULL, NULL, ctx.qry.head, NULL, NULL, 0, 221 cgit_log_link("[...]", NULL, NULL, ctx.qry.head, NULL, NULL, 0,
152 NULL, NULL); 222 NULL, NULL, ctx.qry.showmsg);
153 html("</td></tr>\n"); 223 html("</td></tr>\n");
154 } 224 }
155} 225}
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)
101 ctx.page.filename = patchname; 101 ctx.page.filename = patchname;
102 cgit_print_http_headers(&ctx); 102 cgit_print_http_headers(&ctx);
103 htmlf("From %s Mon Sep 17 00:00:00 2001\n", sha1_to_hex(sha1)); 103 htmlf("From %s Mon Sep 17 00:00:00 2001\n", sha1_to_hex(sha1));
104 htmlf("From: %s%s\n", info->author, info->author_email); 104 htmlf("From: %s %s\n", info->author, info->author_email);
105 html("Date: "); 105 html("Date: ");
106 cgit_print_date(info->author_date, "%a, %d %b %Y %H:%M:%S %z%n", ctx.cfg.local_time); 106 cgit_print_date(info->author_date, "%a, %d %b %Y %H:%M:%S %z%n", ctx.cfg.local_time);
107 htmlf("Subject: %s\n\n", info->subject); 107 htmlf("Subject: %s\n\n", info->subject);
108 if (info->msg && *info->msg) { 108 if (info->msg && *info->msg) {
109 htmlf("%s", info->msg); 109 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)
18{ 18{
19 enum object_type type; 19 enum object_type type;
20 char *buf; 20 char *buf;
21 size_t size; 21 unsigned long size;
22 22
23 type = sha1_object_info(sha1, &size); 23 type = sha1_object_info(sha1, &size);
24 if (type == OBJ_BAD) { 24 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)
58 if (!info) 58 if (!info)
59 return 1; 59 return 1;
60 html("<tr><td>"); 60 html("<tr><td>");
61 cgit_log_link(name, NULL, NULL, name, NULL, NULL, 0, NULL, NULL); 61 cgit_log_link(name, NULL, NULL, name, NULL, NULL, 0, NULL, NULL,
62 ctx.qry.showmsg);
62 html("</td><td>"); 63 html("</td><td>");
63 64
64 if (ref->object->type == OBJ_COMMIT) { 65 if (ref->object->type == OBJ_COMMIT) {
@@ -78,12 +79,37 @@ static int print_branch(struct refinfo *ref)
78static void print_tag_header() 79static void print_tag_header()
79{ 80{
80 html("<tr class='nohover'><th class='left'>Tag</th>" 81 html("<tr class='nohover'><th class='left'>Tag</th>"
81 "<th class='left'>Reference</th>" 82 "<th class='left'>Download</th>"
82 "<th class='left'>Author</th>" 83 "<th class='left'>Author</th>"
83 "<th class='left' colspan='2'>Age</th></tr>\n"); 84 "<th class='left' colspan='2'>Age</th></tr>\n");
84 header = 1; 85 header = 1;
85} 86}
86 87
88static void print_tag_downloads(const struct cgit_repo *repo, const char *ref)
89{
90 const struct cgit_snapshot_format* f;
91 char *filename;
92 const char *basename;
93
94 if (!ref || strlen(ref) < 2)
95 return;
96
97 basename = cgit_repobasename(repo->url);
98 if (prefixcmp(ref, basename) != 0) {
99 if ((ref[0] == 'v' || ref[0] == 'V') && isdigit(ref[1]))
100 ref++;
101 if (isdigit(ref[0]))
102 ref = xstrdup(fmt("%s-%s", basename, ref));
103 }
104
105 for (f = cgit_snapshot_formats; f->suffix; f++) {
106 if (!(repo->snapshots & f->bit))
107 continue;
108 filename = fmt("%s%s", ref, f->suffix);
109 cgit_snapshot_link(filename, NULL, NULL, NULL, NULL, filename);
110 html("&nbsp;&nbsp;");
111 }
112}
87static int print_tag(struct refinfo *ref) 113static int print_tag(struct refinfo *ref)
88{ 114{
89 struct tag *tag; 115 struct tag *tag;
@@ -98,7 +124,10 @@ static int print_tag(struct refinfo *ref)
98 html("<tr><td>"); 124 html("<tr><td>");
99 cgit_tag_link(name, NULL, NULL, ctx.qry.head, name); 125 cgit_tag_link(name, NULL, NULL, ctx.qry.head, name);
100 html("</td><td>"); 126 html("</td><td>");
101 cgit_object_link(tag->tagged); 127 if (ctx.repo->snapshots && (tag->tagged->type == OBJ_COMMIT))
128 print_tag_downloads(ctx.repo, name);
129 else
130 cgit_object_link(tag->tagged);
102 html("</td><td>"); 131 html("</td><td>");
103 if (info->tagger) 132 if (info->tagger)
104 html(info->tagger); 133 html(info->tagger);
@@ -110,9 +139,12 @@ static int print_tag(struct refinfo *ref)
110 if (!header) 139 if (!header)
111 print_tag_header(); 140 print_tag_header();
112 html("<tr><td>"); 141 html("<tr><td>");
113 html_txt(name); 142 cgit_tag_link(name, NULL, NULL, ctx.qry.head, name);
114 html("</td><td>"); 143 html("</td><td>");
115 cgit_object_link(ref->object); 144 if (ctx.repo->snapshots && (ref->object->type == OBJ_COMMIT))
145 print_tag_downloads(ctx.repo, name);
146 else
147 cgit_object_link(ref->object);
116 html("</td></tr>\n"); 148 html("</td></tr>\n");
117 } 149 }
118 return 0; 150 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 @@
6 * (see COPYING for full license text) 6 * (see COPYING for full license text)
7 */ 7 */
8 8
9/* This is needed for strcasestr to be defined by <string.h> */
10#define _GNU_SOURCE 1
11#include <string.h>
12
9#include <time.h> 13#include <time.h>
10 14
11#include "cgit.h" 15#include "cgit.h"
@@ -19,7 +23,8 @@ time_t read_agefile(char *path)
19 23
20 if (!(f = fopen(path, "r"))) 24 if (!(f = fopen(path, "r")))
21 return -1; 25 return -1;
22 fgets(buf, sizeof(buf), f); 26 if (fgets(buf, sizeof(buf), f) == NULL)
27 return -1;
23 fclose(f); 28 fclose(f);
24 if (parse_date(buf, buf2, sizeof(buf2))) 29 if (parse_date(buf, buf2, sizeof(buf2)))
25 return strtoul(buf2, NULL, 10); 30 return strtoul(buf2, NULL, 10);
@@ -27,21 +32,38 @@ time_t read_agefile(char *path)
27 return 0; 32 return 0;
28} 33}
29 34
30static void print_modtime(struct cgit_repo *repo) 35static int get_repo_modtime(const struct cgit_repo *repo, time_t *mtime)
31{ 36{
32 char *path; 37 char *path;
33 struct stat s; 38 struct stat s;
39 struct cgit_repo *r = (struct cgit_repo *)repo;
34 40
41 if (repo->mtime != -1) {
42 *mtime = repo->mtime;
43 return 1;
44 }
35 path = fmt("%s/%s", repo->path, ctx.cfg.agefile); 45 path = fmt("%s/%s", repo->path, ctx.cfg.agefile);
36 if (stat(path, &s) == 0) { 46 if (stat(path, &s) == 0) {
37 cgit_print_age(read_agefile(path), -1, NULL); 47 *mtime = read_agefile(path);
38 return; 48 r->mtime = *mtime;
49 return 1;
39 } 50 }
40 51
41 path = fmt("%s/refs/heads/%s", repo->path, repo->defbranch); 52 path = fmt("%s/refs/heads/%s", repo->path, repo->defbranch);
42 if (stat(path, &s) != 0) 53 if (stat(path, &s) == 0)
43 return; 54 *mtime = s.st_mtime;
44 cgit_print_age(s.st_mtime, -1, NULL); 55 else
56 *mtime = 0;
57
58 r->mtime = *mtime;
59 return (r->mtime != 0);
60}
61
62static void print_modtime(struct cgit_repo *repo)
63{
64 time_t t;
65 if (get_repo_modtime(repo, &t))
66 cgit_print_age(t, -1, NULL);
45} 67}
46 68
47int is_match(struct cgit_repo *repo) 69int is_match(struct cgit_repo *repo)
@@ -68,13 +90,23 @@ int is_in_url(struct cgit_repo *repo)
68 return 0; 90 return 0;
69} 91}
70 92
93void print_sort_header(const char *title, const char *sort)
94{
95 htmlf("<th class='left'><a href='./?s=%s", sort);
96 if (ctx.qry.search) {
97 html("&q=");
98 html_url_arg(ctx.qry.search);
99 }
100 htmlf("'>%s</a></th>", title);
101}
102
71void print_header(int columns) 103void print_header(int columns)
72{ 104{
73 html("<tr class='nohover'>" 105 html("<tr class='nohover'>");
74 "<th class='left'>Name</th>" 106 print_sort_header("Name", "name");
75 "<th class='left'>Description</th>" 107 print_sort_header("Description", "desc");
76 "<th class='left'>Owner</th>" 108 print_sort_header("Owner", "owner");
77 "<th class='left'>Idle</th>"); 109 print_sort_header("Idle", "idle");
78 if (ctx.cfg.enable_index_links) 110 if (ctx.cfg.enable_index_links)
79 html("<th class='left'>Links</th>"); 111 html("<th class='left'>Links</th>");
80 html("</tr>\n"); 112 html("</tr>\n");
@@ -91,10 +123,86 @@ void print_pager(int items, int pagelen, char *search)
91 html("</div>"); 123 html("</div>");
92} 124}
93 125
126static int cmp(const char *s1, const char *s2)
127{
128 if (s1 && s2)
129 return strcmp(s1, s2);
130 if (s1 && !s2)
131 return -1;
132 if (s2 && !s1)
133 return 1;
134 return 0;
135}
136
137static int sort_name(const void *a, const void *b)
138{
139 const struct cgit_repo *r1 = a;
140 const struct cgit_repo *r2 = b;
141
142 return cmp(r1->name, r2->name);
143}
144
145static int sort_desc(const void *a, const void *b)
146{
147 const struct cgit_repo *r1 = a;
148 const struct cgit_repo *r2 = b;
149
150 return cmp(r1->desc, r2->desc);
151}
152
153static int sort_owner(const void *a, const void *b)
154{
155 const struct cgit_repo *r1 = a;
156 const struct cgit_repo *r2 = b;
157
158 return cmp(r1->owner, r2->owner);
159}
160
161static int sort_idle(const void *a, const void *b)
162{
163 const struct cgit_repo *r1 = a;
164 const struct cgit_repo *r2 = b;
165 time_t t1, t2;
166
167 t1 = t2 = 0;
168 get_repo_modtime(r1, &t1);
169 get_repo_modtime(r2, &t2);
170 return t2 - t1;
171}
172
173struct sortcolumn {
174 const char *name;
175 int (*fn)(const void *a, const void *b);
176};
177
178struct sortcolumn sortcolumn[] = {
179 {"name", sort_name},
180 {"desc", sort_desc},
181 {"owner", sort_owner},
182 {"idle", sort_idle},
183 {NULL, NULL}
184};
185
186int sort_repolist(char *field)
187{
188 struct sortcolumn *column;
189
190 for (column = &sortcolumn[0]; column->name; column++) {
191 if (strcmp(field, column->name))
192 continue;
193 qsort(cgit_repolist.repos, cgit_repolist.count,
194 sizeof(struct cgit_repo), column->fn);
195 return 1;
196 }
197 return 0;
198}
199
200
94void cgit_print_repolist() 201void cgit_print_repolist()
95{ 202{
96 int i, columns = 4, hits = 0, header = 0; 203 int i, columns = 4, hits = 0, header = 0;
97 char *last_group = NULL; 204 char *last_group = NULL;
205 int sorted = 0;
98 206
99 if (ctx.cfg.enable_index_links) 207 if (ctx.cfg.enable_index_links)
100 columns++; 208 columns++;
@@ -107,6 +215,9 @@ void cgit_print_repolist()
107 if (ctx.cfg.index_header) 215 if (ctx.cfg.index_header)
108 html_include(ctx.cfg.index_header); 216 html_include(ctx.cfg.index_header);
109 217
218 if(ctx.qry.sort)
219 sorted = sort_repolist(ctx.qry.sort);
220
110 html("<table summary='repository list' class='list nowrap'>"); 221 html("<table summary='repository list' class='list nowrap'>");
111 for (i=0; i<cgit_repolist.count; i++) { 222 for (i=0; i<cgit_repolist.count; i++) {
112 ctx.repo = &cgit_repolist.repos[i]; 223 ctx.repo = &cgit_repolist.repos[i];
@@ -119,10 +230,11 @@ void cgit_print_repolist()
119 continue; 230 continue;
120 if (!header++) 231 if (!header++)
121 print_header(columns); 232 print_header(columns);
122 if ((last_group == NULL && ctx.repo->group != NULL) || 233 if (!sorted &&
234 ((last_group == NULL && ctx.repo->group != NULL) ||
123 (last_group != NULL && ctx.repo->group == NULL) || 235 (last_group != NULL && ctx.repo->group == NULL) ||
124 (last_group != NULL && ctx.repo->group != NULL && 236 (last_group != NULL && ctx.repo->group != NULL &&
125 strcmp(ctx.repo->group, last_group))) { 237 strcmp(ctx.repo->group, last_group)))) {
126 htmlf("<tr class='nohover'><td colspan='%d' class='repogroup'>", 238 htmlf("<tr class='nohover'><td colspan='%d' class='repogroup'>",
127 columns); 239 columns);
128 html_txt(ctx.repo->group); 240 html_txt(ctx.repo->group);
@@ -130,7 +242,7 @@ void cgit_print_repolist()
130 last_group = ctx.repo->group; 242 last_group = ctx.repo->group;
131 } 243 }
132 htmlf("<tr><td class='%s'>", 244 htmlf("<tr><td class='%s'>",
133 ctx.repo->group ? "sublevel-repo" : "toplevel-repo"); 245 !sorted && ctx.repo->group ? "sublevel-repo" : "toplevel-repo");
134 cgit_summary_link(ctx.repo->name, ctx.repo->name, NULL, NULL); 246 cgit_summary_link(ctx.repo->name, ctx.repo->name, NULL, NULL);
135 html("</td><td>"); 247 html("</td><td>");
136 html_link_open(cgit_repourl(ctx.repo->url), NULL, NULL); 248 html_link_open(cgit_repourl(ctx.repo->url), NULL, NULL);
@@ -145,7 +257,7 @@ void cgit_print_repolist()
145 html("<td>"); 257 html("<td>");
146 cgit_summary_link("summary", NULL, "button", NULL); 258 cgit_summary_link("summary", NULL, "button", NULL);
147 cgit_log_link("log", NULL, "button", NULL, NULL, NULL, 259 cgit_log_link("log", NULL, "button", NULL, NULL, NULL,
148 0, NULL, NULL); 260 0, NULL, NULL, ctx.qry.showmsg);
149 cgit_tree_link("tree", NULL, "button", NULL, NULL, NULL); 261 cgit_tree_link("tree", NULL, "button", NULL, NULL, NULL);
150 html("</td>"); 262 html("</td>");
151 } 263 }
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,
281} 281}
282 282
283void cgit_log_link(char *name, char *title, char *class, char *head, 283void cgit_log_link(char *name, char *title, char *class, char *head,
284 char *rev, char *path, int ofs, char *grep, char *pattern) 284 char *rev, char *path, int ofs, char *grep, char *pattern,
285 int showmsg)
285{ 286{
286 char *delim; 287 char *delim;
287 288
@@ -305,6 +306,11 @@ void cgit_log_link(char *name, char *title, char *class, char *head,
305 html(delim); 306 html(delim);
306 html("ofs="); 307 html("ofs=");
307 htmlf("%d", ofs); 308 htmlf("%d", ofs);
309 delim = "&";
310 }
311 if (showmsg) {
312 html(delim);
313 html("showmsg=1");
308 } 314 }
309 html("'>"); 315 html("'>");
310 html_txt(name); 316 html_txt(name);
@@ -371,11 +377,14 @@ void cgit_stats_link(char *name, char *title, char *class, char *head,
371 377
372void cgit_object_link(struct object *obj) 378void cgit_object_link(struct object *obj)
373{ 379{
374 char *page, *rev, *name; 380 char *page, *shortrev, *fullrev, *name;
375 381
382 fullrev = sha1_to_hex(obj->sha1);
383 shortrev = xstrdup(fullrev);
384 shortrev[10] = '\0';
376 if (obj->type == OBJ_COMMIT) { 385 if (obj->type == OBJ_COMMIT) {
377 cgit_commit_link(fmt("commit %s", sha1_to_hex(obj->sha1)), NULL, NULL, 386 cgit_commit_link(fmt("commit %s...", shortrev), NULL, NULL,
378 ctx.qry.head, sha1_to_hex(obj->sha1)); 387 ctx.qry.head, fullrev);
379 return; 388 return;
380 } else if (obj->type == OBJ_TREE) 389 } else if (obj->type == OBJ_TREE)
381 page = "tree"; 390 page = "tree";
@@ -383,9 +392,8 @@ void cgit_object_link(struct object *obj)
383 page = "tag"; 392 page = "tag";
384 else 393 else
385 page = "blob"; 394 page = "blob";
386 rev = sha1_to_hex(obj->sha1); 395 name = fmt("%s %s...", typename(obj->type), shortrev);
387 name = fmt("%s %s", typename(obj->type), rev); 396 reporevlink(page, name, NULL, NULL, ctx.qry.head, fullrev, NULL);
388 reporevlink(page, name, NULL, NULL, ctx.qry.head, rev, NULL);
389} 397}
390 398
391void cgit_print_date(time_t secs, char *format, int local_time) 399void 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)
574 html_hidden("id", ctx.qry.sha1); 582 html_hidden("id", ctx.qry.sha1);
575 if (ctx.qry.sha2) 583 if (ctx.qry.sha2)
576 html_hidden("id2", ctx.qry.sha2); 584 html_hidden("id2", ctx.qry.sha2);
585 if (ctx.qry.showmsg)
586 html_hidden("showmsg", "1");
577 587
578 if (incl_search) { 588 if (incl_search) {
579 if (ctx.qry.grep) 589 if (ctx.qry.grep)
@@ -583,15 +593,20 @@ void cgit_add_hidden_formfields(int incl_head, int incl_search, char *page)
583 } 593 }
584} 594}
585 595
596const char *fallback_cmd = "repolist";
597
586char *hc(struct cgit_cmd *cmd, const char *page) 598char *hc(struct cgit_cmd *cmd, const char *page)
587{ 599{
588 return (strcmp(cmd->name, page) ? NULL : "active"); 600 return (strcmp(cmd ? cmd->name : fallback_cmd, page) ? NULL : "active");
589} 601}
590 602
591void cgit_print_pageheader(struct cgit_context *ctx) 603void cgit_print_pageheader(struct cgit_context *ctx)
592{ 604{
593 struct cgit_cmd *cmd = cgit_get_cmd(ctx); 605 struct cgit_cmd *cmd = cgit_get_cmd(ctx);
594 606
607 if (!cmd && ctx->repo)
608 fallback_cmd = "summary";
609
595 html("<table id='header'>\n"); 610 html("<table id='header'>\n");
596 html("<tr>\n"); 611 html("<tr>\n");
597 html("<td class='logo' rowspan='2'><a href='"); 612 html("<td class='logo' rowspan='2'><a href='");
@@ -640,7 +655,7 @@ void cgit_print_pageheader(struct cgit_context *ctx)
640 cgit_refs_link("refs", NULL, hc(cmd, "refs"), ctx->qry.head, 655 cgit_refs_link("refs", NULL, hc(cmd, "refs"), ctx->qry.head,
641 ctx->qry.sha1, NULL); 656 ctx->qry.sha1, NULL);
642 cgit_log_link("log", NULL, hc(cmd, "log"), ctx->qry.head, 657 cgit_log_link("log", NULL, hc(cmd, "log"), ctx->qry.head,
643 NULL, NULL, 0, NULL, NULL); 658 NULL, NULL, 0, NULL, NULL, ctx->qry.showmsg);
644 cgit_tree_link("tree", NULL, hc(cmd, "tree"), ctx->qry.head, 659 cgit_tree_link("tree", NULL, hc(cmd, "tree"), ctx->qry.head,
645 ctx->qry.sha1, NULL); 660 ctx->qry.sha1, NULL);
646 cgit_commit_link("commit", NULL, hc(cmd, "commit"), 661 cgit_commit_link("commit", NULL, hc(cmd, "commit"),
@@ -716,8 +731,7 @@ void cgit_print_snapshot_links(const char *repo, const char *head,
716 continue; 731 continue;
717 filename = fmt("%s-%s%s", cgit_repobasename(repo), hex, 732 filename = fmt("%s-%s%s", cgit_repobasename(repo), hex,
718 f->suffix); 733 f->suffix);
719 cgit_snapshot_link(filename, NULL, NULL, (char *)head, 734 cgit_snapshot_link(filename, NULL, NULL, NULL, NULL, filename);
720 (char *)hex, filename);
721 html("<br/>"); 735 html("<br/>");
722 } 736 }
723} 737}
diff --git a/ui-shared.h b/ui-shared.h
index 1524dc3..5a3821f 100644
--- a/ui-shared.h
+++ b/ui-shared.h
@@ -19,7 +19,7 @@ extern void cgit_plain_link(char *name, char *title, char *class, char *head,
19 char *rev, char *path); 19 char *rev, char *path);
20extern void cgit_log_link(char *name, char *title, char *class, char *head, 20extern void cgit_log_link(char *name, char *title, char *class, char *head,
21 char *rev, char *path, int ofs, char *grep, 21 char *rev, char *path, int ofs, char *grep,
22 char *pattern); 22 char *pattern, int showmsg);
23extern void cgit_commit_link(char *name, char *title, char *class, char *head, 23extern void cgit_commit_link(char *name, char *title, char *class, char *head,
24 char *rev); 24 char *rev);
25extern void cgit_patch_link(char *name, char *title, char *class, char *head, 25extern void cgit_patch_link(char *name, char *title, char *class, char *head,
diff --git a/ui-snapshot.c b/ui-snapshot.c
index 9c4d086..f25613e 100644
--- a/ui-snapshot.c
+++ b/ui-snapshot.c
@@ -58,8 +58,8 @@ static int write_tar_bzip2_archive(struct archiver_args *args)
58 58
59const struct cgit_snapshot_format cgit_snapshot_formats[] = { 59const struct cgit_snapshot_format cgit_snapshot_formats[] = {
60 { ".zip", "application/x-zip", write_zip_archive, 0x1 }, 60 { ".zip", "application/x-zip", write_zip_archive, 0x1 },
61 { ".tar.gz", "application/x-tar", write_tar_gzip_archive, 0x2 }, 61 { ".tar.gz", "application/x-gzip", write_tar_gzip_archive, 0x2 },
62 { ".tar.bz2", "application/x-tar", write_tar_bzip2_archive, 0x4 }, 62 { ".tar.bz2", "application/x-bzip2", write_tar_bzip2_archive, 0x4 },
63 { ".tar", "application/x-tar", write_tar_archive, 0x8 }, 63 { ".tar", "application/x-tar", write_tar_archive, 0x8 },
64 {} 64 {}
65}; 65};
@@ -114,58 +114,53 @@ static int make_snapshot(const struct cgit_snapshot_format *format,
114 return 0; 114 return 0;
115} 115}
116 116
117char *dwim_filename = NULL; 117/* Try to guess the requested revision from the requested snapshot name.
118const char *dwim_refname = NULL; 118 * First the format extension is stripped, e.g. "cgit-0.7.2.tar.gz" become
119 119 * "cgit-0.7.2". If this is a valid commit object name we've got a winner.
120static int ref_cb(const char *refname, const unsigned char *sha1, int flags, 120 * Otherwise, if the snapshot name has a prefix matching the result from
121 void *cb_data) 121 * repo_basename(), we strip the basename and any following '-' and '_'
122{ 122 * characters ("cgit-0.7.2" -> "0.7.2") and check the resulting name once
123 const char *r = refname; 123 * more. If this still isn't a valid commit object name, we check if pre-
124 while (r && *r) { 124 * pending a 'v' to the remaining snapshot name ("0.7.2" -> "v0.7.2") gives
125 fprintf(stderr, " cmp %s with %s:", dwim_filename, r); 125 * us something valid.
126 if (!strcmp(dwim_filename, r)) {
127 fprintf(stderr, "MATCH!\n");
128 dwim_refname = refname;
129 return 1;
130 }
131 fprintf(stderr, "no match\n");
132 if (isdigit(*r))
133 break;
134 r++;
135 }
136 return 0;
137}
138
139/* Try to guess the requested revision by combining repo name and tag name
140 * and comparing this to the requested snapshot name. E.g. the requested
141 * snapshot is "cgit-0.7.2.tar.gz" while repo name is "cgit" and tag name
142 * is "v0.7.2". First, the reponame is stripped off, leaving "-0.7.2.tar.gz".
143 * Next, any '-' and '_' characters are stripped, leaving "0.7.2.tar.gz".
144 * Finally, the requested format suffix is removed and we end up with "0.7.2".
145 * Then we test each tag against this dwimmed filename, and for each tag
146 * we even try to remove any leading characters which are non-digits. I.e.
147 * we first compare with "v0.7.2", then with "0.7.2" and we've got a match.
148 */ 126 */
149static const char *get_ref_from_filename(const char *url, const char *filename, 127static const char *get_ref_from_filename(const char *url, const char *filename,
150 const struct cgit_snapshot_format *fmt) 128 const struct cgit_snapshot_format *format)
151{ 129{
152 const char *reponame = cgit_repobasename(url); 130 const char *reponame;
153 fprintf(stderr, "reponame=%s, filename=%s\n", reponame, filename); 131 unsigned char sha1[20];
154 if (prefixcmp(filename, reponame)) 132 char *snapshot;
155 return NULL; 133
156 filename += strlen(reponame); 134 snapshot = xstrdup(filename);
157 while (filename && (*filename == '-' || *filename == '_')) 135 snapshot[strlen(snapshot) - strlen(format->suffix)] = '\0';
158 filename++; 136 fprintf(stderr, "snapshot=%s\n", snapshot);
159 dwim_filename = xstrdup(filename); 137
160 dwim_filename[strlen(filename) - strlen(fmt->suffix)] = '\0'; 138 if (get_sha1(snapshot, sha1) == 0)
161 for_each_tag_ref(ref_cb, NULL); 139 return snapshot;
162 return dwim_refname; 140
141 reponame = cgit_repobasename(url);
142 fprintf(stderr, "reponame=%s\n", reponame);
143 if (prefixcmp(snapshot, reponame) == 0) {
144 snapshot += strlen(reponame);
145 while (snapshot && (*snapshot == '-' || *snapshot == '_'))
146 snapshot++;
147 }
148
149 if (get_sha1(snapshot, sha1) == 0)
150 return snapshot;
151
152 snapshot = fmt("v%s", snapshot);
153 if (get_sha1(snapshot, sha1) == 0)
154 return snapshot;
155
156 return NULL;
163} 157}
164 158
165void cgit_print_snapshot(const char *head, const char *hex, const char *prefix, 159void cgit_print_snapshot(const char *head, const char *hex,
166 const char *filename, int snapshots, int dwim) 160 const char *filename, int snapshots, int dwim)
167{ 161{
168 const struct cgit_snapshot_format* f; 162 const struct cgit_snapshot_format* f;
163 char *prefix = NULL;
169 164
170 f = get_format(filename); 165 f = get_format(filename);
171 if (!f) { 166 if (!f) {
@@ -178,11 +173,22 @@ void cgit_print_snapshot(const char *head, const char *hex, const char *prefix,
178 return; 173 return;
179 } 174 }
180 175
181 if (!hex && dwim) 176 if (!hex && dwim) {
182 hex = get_ref_from_filename(ctx.repo->url, filename, f); 177 hex = get_ref_from_filename(ctx.repo->url, filename, f);
178 if (hex == NULL) {
179 html_status(404, "Not found", 0);
180 return;
181 }
182 prefix = xstrdup(filename);
183 prefix[strlen(filename) - strlen(f->suffix)] = '\0';
184 }
183 185
184 if (!hex) 186 if (!hex)
185 hex = head; 187 hex = head;
186 188
189 if (!prefix)
190 prefix = xstrdup(cgit_repobasename(ctx.repo->url));
191
187 make_snapshot(f, hex, prefix, filename); 192 make_snapshot(f, hex, prefix, filename);
193 free(prefix);
188} 194}
diff --git a/ui-snapshot.h b/ui-snapshot.h
index 3540303..b6ede52 100644
--- a/ui-snapshot.h
+++ b/ui-snapshot.h
@@ -2,7 +2,6 @@
2#define UI_SNAPSHOT_H 2#define UI_SNAPSHOT_H
3 3
4extern void cgit_print_snapshot(const char *head, const char *hex, 4extern void cgit_print_snapshot(const char *head, const char *hex,
5 const char *prefix, const char *filename, 5 const char *filename, int snapshot, int dwim);
6 int snapshot, int dwim);
7 6
8#endif /* UI_SNAPSHOT_H */ 7#endif /* UI_SNAPSHOT_H */
diff --git a/ui-tag.c b/ui-tag.c
index 3aea87d..0e056e0 100644
--- a/ui-tag.c
+++ b/ui-tag.c
@@ -53,8 +53,9 @@ void cgit_print_tag(char *revname)
53 return; 53 return;
54 } 54 }
55 html("<table class='commit-info'>\n"); 55 html("<table class='commit-info'>\n");
56 htmlf("<tr><td>Tag name</td><td>%s (%s)</td></tr>\n", 56 htmlf("<tr><td>Tag name</td><td>");
57 revname, sha1_to_hex(sha1)); 57 html_txt(revname);
58 htmlf(" (%s)</td></tr>\n", sha1_to_hex(sha1));
58 if (info->tagger_date > 0) { 59 if (info->tagger_date > 0) {
59 html("<tr><td>Tag date</td><td>"); 60 html("<tr><td>Tag date</td><td>");
60 cgit_print_date(info->tagger_date, FMT_LONGDATE, ctx.cfg.local_time); 61 cgit_print_date(info->tagger_date, FMT_LONGDATE, ctx.cfg.local_time);
@@ -74,6 +75,15 @@ void cgit_print_tag(char *revname)
74 html("</td></tr>\n"); 75 html("</td></tr>\n");
75 html("</table>\n"); 76 html("</table>\n");
76 print_tag_content(info->msg); 77 print_tag_content(info->msg);
77 } 78 } else {
79 html("<table class='commit-info'>\n");
80 htmlf("<tr><td>Tag name</td><td>");
81 html_txt(revname);
82 html("</td></tr>\n");
83 html("<tr><td>Tagged object</td><td>");
84 cgit_object_link(obj);
85 html("</td></tr>\n");
86 html("</table>\n");
87 }
78 return; 88 return;
79} 89}
diff --git a/ui-tree.c b/ui-tree.c
index e27e796..4b8e7a0 100644
--- a/ui-tree.c
+++ b/ui-tree.c
@@ -54,8 +54,10 @@ static void print_object(const unsigned char *sha1, char *path)
54 } 54 }
55 idx++; 55 idx++;
56 } 56 }
57 htmlf(linefmt, ++lineno); 57 if (start < idx) {
58 html_txt(buf + start); 58 htmlf(linefmt, ++lineno);
59 html_txt(buf + start);
60 }
59 html("</td></tr>\n"); 61 html("</td></tr>\n");
60 html("</table>\n"); 62 html("</table>\n");
61} 63}
@@ -106,7 +108,7 @@ static int ls_item(const unsigned char *sha1, const char *base, int baselen,
106 108
107 html("<td>"); 109 html("<td>");
108 cgit_log_link("log", NULL, "button", ctx.qry.head, curr_rev, 110 cgit_log_link("log", NULL, "button", ctx.qry.head, curr_rev,
109 fullpath, 0, NULL, NULL); 111 fullpath, 0, NULL, NULL, ctx.qry.showmsg);
110 if (ctx.repo->max_stats) 112 if (ctx.repo->max_stats)
111 cgit_stats_link("stats", NULL, "button", ctx.qry.head, 113 cgit_stats_link("stats", NULL, "button", ctx.qry.head,
112 fullpath); 114 fullpath);