summaryrefslogtreecommitdiffabout
Unidiff
Diffstat (more/less context) (show whitespace changes)
-rw-r--r--Makefile2
-rw-r--r--cgit.c4
-rw-r--r--cgit.css15
-rw-r--r--cgit.h3
-rw-r--r--cmd.c3
m---------git0
-rw-r--r--shared.c5
-rw-r--r--ui-log.c35
-rw-r--r--ui-refs.c36
-rw-r--r--ui-repolist.c137
-rw-r--r--ui-shared.c29
-rw-r--r--ui-shared.h2
-rw-r--r--ui-snapshot.c92
-rw-r--r--ui-snapshot.h3
-rw-r--r--ui-tree.c2
15 files changed, 282 insertions, 86 deletions
diff --git a/Makefile b/Makefile
index 2e51c31..3c7ec07 100644
--- a/Makefile
+++ b/Makefile
@@ -1,31 +1,31 @@
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_CONFIG = /etc/cgitrc 4CGIT_CONFIG = /etc/cgitrc
5CACHE_ROOT = /var/cache/cgit 5CACHE_ROOT = /var/cache/cgit
6SHA1_HEADER = <openssl/sha.h> 6SHA1_HEADER = <openssl/sha.h>
7GIT_VER = 1.6.0.3 7GIT_VER = 1.6.1
8GIT_URL = http://www.kernel.org/pub/software/scm/git/git-$(GIT_VER).tar.bz2 8GIT_URL = http://www.kernel.org/pub/software/scm/git/git-$(GIT_VER).tar.bz2
9 9
10# Define NO_STRCASESTR if you don't have strcasestr. 10# Define NO_STRCASESTR if you don't have strcasestr.
11# 11#
12# Define NEEDS_LIBICONV if linking with libc is not enough (eg. Darwin). 12# Define NEEDS_LIBICONV if linking with libc is not enough (eg. Darwin).
13# 13#
14 14
15#-include config.mak 15#-include config.mak
16 16
17# 17#
18# Platform specific tweaks 18# Platform specific tweaks
19# 19#
20 20
21uname_S := $(shell sh -c 'uname -s 2>/dev/null || echo not') 21uname_S := $(shell sh -c 'uname -s 2>/dev/null || echo not')
22uname_O := $(shell sh -c 'uname -o 2>/dev/null || echo not') 22uname_O := $(shell sh -c 'uname -o 2>/dev/null || echo not')
23uname_R := $(shell sh -c 'uname -r 2>/dev/null || echo not') 23uname_R := $(shell sh -c 'uname -r 2>/dev/null || echo not')
24 24
25ifeq ($(uname_O),Cygwin) 25ifeq ($(uname_O),Cygwin)
26 NO_STRCASESTR = YesPlease 26 NO_STRCASESTR = YesPlease
27 NEEDS_LIBICONV = YesPlease 27 NEEDS_LIBICONV = YesPlease
28endif 28endif
29 29
30# 30#
31# Let the user override the above settings. 31# Let the user override the above settings.
diff --git a/cgit.c b/cgit.c
index c82587b..166fbc6 100644
--- a/cgit.c
+++ b/cgit.c
@@ -133,48 +133,52 @@ static void querystring_cb(const char *name, const char *value)
133 } else if (!strcmp(name, "url")) { 133 } else if (!strcmp(name, "url")) {
134 ctx.qry.url = xstrdup(value); 134 ctx.qry.url = xstrdup(value);
135 cgit_parse_url(value); 135 cgit_parse_url(value);
136 } else if (!strcmp(name, "qt")) { 136 } else if (!strcmp(name, "qt")) {
137 ctx.qry.grep = xstrdup(value); 137 ctx.qry.grep = xstrdup(value);
138 } else if (!strcmp(name, "q")) { 138 } else if (!strcmp(name, "q")) {
139 ctx.qry.search = xstrdup(value); 139 ctx.qry.search = xstrdup(value);
140 } else if (!strcmp(name, "h")) { 140 } else if (!strcmp(name, "h")) {
141 ctx.qry.head = xstrdup(value); 141 ctx.qry.head = xstrdup(value);
142 ctx.qry.has_symref = 1; 142 ctx.qry.has_symref = 1;
143 } else if (!strcmp(name, "id")) { 143 } else if (!strcmp(name, "id")) {
144 ctx.qry.sha1 = xstrdup(value); 144 ctx.qry.sha1 = xstrdup(value);
145 ctx.qry.has_sha1 = 1; 145 ctx.qry.has_sha1 = 1;
146 } else if (!strcmp(name, "id2")) { 146 } else if (!strcmp(name, "id2")) {
147 ctx.qry.sha2 = xstrdup(value); 147 ctx.qry.sha2 = xstrdup(value);
148 ctx.qry.has_sha1 = 1; 148 ctx.qry.has_sha1 = 1;
149 } else if (!strcmp(name, "ofs")) { 149 } else if (!strcmp(name, "ofs")) {
150 ctx.qry.ofs = atoi(value); 150 ctx.qry.ofs = atoi(value);
151 } else if (!strcmp(name, "path")) { 151 } else if (!strcmp(name, "path")) {
152 ctx.qry.path = trim_end(value, '/'); 152 ctx.qry.path = trim_end(value, '/');
153 } else if (!strcmp(name, "name")) { 153 } else if (!strcmp(name, "name")) {
154 ctx.qry.name = xstrdup(value); 154 ctx.qry.name = xstrdup(value);
155 } else if (!strcmp(name, "mimetype")) { 155 } else if (!strcmp(name, "mimetype")) {
156 ctx.qry.mimetype = xstrdup(value); 156 ctx.qry.mimetype = xstrdup(value);
157 } else if (!strcmp(name, "s")){
158 ctx.qry.sort = xstrdup(value);
159 } else if (!strcmp(name, "showmsg")) {
160 ctx.qry.showmsg = atoi(value);
157 } 161 }
158} 162}
159 163
160static void prepare_context(struct cgit_context *ctx) 164static void prepare_context(struct cgit_context *ctx)
161{ 165{
162 memset(ctx, 0, sizeof(ctx)); 166 memset(ctx, 0, sizeof(ctx));
163 ctx->cfg.agefile = "info/web/last-modified"; 167 ctx->cfg.agefile = "info/web/last-modified";
164 ctx->cfg.nocache = 0; 168 ctx->cfg.nocache = 0;
165 ctx->cfg.cache_size = 0; 169 ctx->cfg.cache_size = 0;
166 ctx->cfg.cache_dynamic_ttl = 5; 170 ctx->cfg.cache_dynamic_ttl = 5;
167 ctx->cfg.cache_max_create_time = 5; 171 ctx->cfg.cache_max_create_time = 5;
168 ctx->cfg.cache_repo_ttl = 5; 172 ctx->cfg.cache_repo_ttl = 5;
169 ctx->cfg.cache_root = CGIT_CACHE_ROOT; 173 ctx->cfg.cache_root = CGIT_CACHE_ROOT;
170 ctx->cfg.cache_root_ttl = 5; 174 ctx->cfg.cache_root_ttl = 5;
171 ctx->cfg.cache_static_ttl = -1; 175 ctx->cfg.cache_static_ttl = -1;
172 ctx->cfg.css = "/cgit.css"; 176 ctx->cfg.css = "/cgit.css";
173 ctx->cfg.logo = "/git-logo.png"; 177 ctx->cfg.logo = "/git-logo.png";
174 ctx->cfg.local_time = 0; 178 ctx->cfg.local_time = 0;
175 ctx->cfg.max_repo_count = 50; 179 ctx->cfg.max_repo_count = 50;
176 ctx->cfg.max_commit_count = 50; 180 ctx->cfg.max_commit_count = 50;
177 ctx->cfg.max_lock_attempts = 5; 181 ctx->cfg.max_lock_attempts = 5;
178 ctx->cfg.max_msg_len = 80; 182 ctx->cfg.max_msg_len = 80;
179 ctx->cfg.max_repodesc_len = 80; 183 ctx->cfg.max_repodesc_len = 80;
180 ctx->cfg.module_link = "./?repo=%s&page=commit&id=%s"; 184 ctx->cfg.module_link = "./?repo=%s&page=commit&id=%s";
diff --git a/cgit.css b/cgit.css
index a37d218..7928c2f 100644
--- a/cgit.css
+++ b/cgit.css
@@ -99,71 +99,86 @@ table.tabs td.form form {
99 99
100table.tabs td.form input, 100table.tabs td.form input,
101table.tabs td.form select { 101table.tabs td.form select {
102 font-size: 90%; 102 font-size: 90%;
103} 103}
104 104
105div.content { 105div.content {
106 margin: 0px; 106 margin: 0px;
107 padding: 2em; 107 padding: 2em;
108 border-top: solid 3px #ccc; 108 border-top: solid 3px #ccc;
109 border-bottom: solid 3px #ccc; 109 border-bottom: solid 3px #ccc;
110} 110}
111 111
112 112
113table.list { 113table.list {
114 width: 100%; 114 width: 100%;
115 border: none; 115 border: none;
116 border-collapse: collapse; 116 border-collapse: collapse;
117} 117}
118 118
119table.list tr { 119table.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}
126 130
127table.list tr.nohover:hover { 131table.list tr.nohover:hover {
128 background: white; 132 background: white;
129} 133}
130 134
131table.list th { 135table.list th {
132 font-weight: bold; 136 font-weight: bold;
133 /* color: #888; 137 /* color: #888;
134 border-top: dashed 1px #888; 138 border-top: dashed 1px #888;
135 border-bottom: dashed 1px #888; 139 border-bottom: dashed 1px #888;
136 */ 140 */
137 padding: 0.1em 0.5em 0.05em 0.5em; 141 padding: 0.1em 0.5em 0.05em 0.5em;
138 vertical-align: baseline; 142 vertical-align: baseline;
139} 143}
140 144
141table.list td { 145table.list td {
142 border: none; 146 border: none;
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}
149 164
150table.list td a:hover { 165table.list td a:hover {
151 color: #00f; 166 color: #00f;
152} 167}
153 168
154img { 169img {
155 border: none; 170 border: none;
156} 171}
157 172
158input#switch-btn { 173input#switch-btn {
159 margin: 2px 0px 0px 0px; 174 margin: 2px 0px 0px 0px;
160} 175}
161 176
162td#sidebar input.txt { 177td#sidebar input.txt {
163 width: 100%; 178 width: 100%;
164 margin: 2px 0px 0px 0px; 179 margin: 2px 0px 0px 0px;
165} 180}
166 181
167table#grid { 182table#grid {
168 margin: 0px; 183 margin: 0px;
169} 184}
diff --git a/cgit.h b/cgit.h
index 92f0c5a..cb2f176 100644
--- a/cgit.h
+++ b/cgit.h
@@ -40,48 +40,49 @@
40 40
41/* 41/*
42 * Default encoding 42 * Default encoding
43 */ 43 */
44#define PAGE_ENCODING "UTF-8" 44#define PAGE_ENCODING "UTF-8"
45 45
46typedef void (*configfn)(const char *name, const char *value); 46typedef void (*configfn)(const char *name, const char *value);
47typedef void (*filepair_fn)(struct diff_filepair *pair); 47typedef void (*filepair_fn)(struct diff_filepair *pair);
48typedef void (*linediff_fn)(char *line, int len); 48typedef void (*linediff_fn)(char *line, int len);
49 49
50struct cgit_repo { 50struct cgit_repo {
51 char *url; 51 char *url;
52 char *name; 52 char *name;
53 char *path; 53 char *path;
54 char *desc; 54 char *desc;
55 char *owner; 55 char *owner;
56 char *defbranch; 56 char *defbranch;
57 char *group; 57 char *group;
58 char *module_link; 58 char *module_link;
59 char *readme; 59 char *readme;
60 char *clone_url; 60 char *clone_url;
61 int snapshots; 61 int snapshots;
62 int enable_log_filecount; 62 int enable_log_filecount;
63 int enable_log_linecount; 63 int enable_log_linecount;
64 time_t mtime;
64}; 65};
65 66
66struct cgit_repolist { 67struct cgit_repolist {
67 int length; 68 int length;
68 int count; 69 int count;
69 struct cgit_repo *repos; 70 struct cgit_repo *repos;
70}; 71};
71 72
72struct commitinfo { 73struct commitinfo {
73 struct commit *commit; 74 struct commit *commit;
74 char *author; 75 char *author;
75 char *author_email; 76 char *author_email;
76 unsigned long author_date; 77 unsigned long author_date;
77 char *committer; 78 char *committer;
78 char *committer_email; 79 char *committer_email;
79 unsigned long committer_date; 80 unsigned long committer_date;
80 char *subject; 81 char *subject;
81 char *msg; 82 char *msg;
82 char *msg_encoding; 83 char *msg_encoding;
83}; 84};
84 85
85struct taginfo { 86struct taginfo {
86 char *tagger; 87 char *tagger;
87 char *tagger_email; 88 char *tagger_email;
@@ -100,48 +101,50 @@ struct refinfo {
100 101
101struct reflist { 102struct reflist {
102 struct refinfo **refs; 103 struct refinfo **refs;
103 int alloc; 104 int alloc;
104 int count; 105 int count;
105}; 106};
106 107
107struct cgit_query { 108struct cgit_query {
108 int has_symref; 109 int has_symref;
109 int has_sha1; 110 int has_sha1;
110 char *raw; 111 char *raw;
111 char *repo; 112 char *repo;
112 char *page; 113 char *page;
113 char *search; 114 char *search;
114 char *grep; 115 char *grep;
115 char *head; 116 char *head;
116 char *sha1; 117 char *sha1;
117 char *sha2; 118 char *sha2;
118 char *path; 119 char *path;
119 char *name; 120 char *name;
120 char *mimetype; 121 char *mimetype;
121 char *url; 122 char *url;
122 int ofs; 123 int ofs;
123 int nohead; 124 int nohead;
125 char *sort;
126 int showmsg;
124}; 127};
125 128
126struct cgit_config { 129struct cgit_config {
127 char *agefile; 130 char *agefile;
128 char *cache_root; 131 char *cache_root;
129 char *clone_prefix; 132 char *clone_prefix;
130 char *css; 133 char *css;
131 char *favicon; 134 char *favicon;
132 char *footer; 135 char *footer;
133 char *index_header; 136 char *index_header;
134 char *index_info; 137 char *index_info;
135 char *logo; 138 char *logo;
136 char *logo_link; 139 char *logo_link;
137 char *module_link; 140 char *module_link;
138 char *repo_group; 141 char *repo_group;
139 char *robots; 142 char *robots;
140 char *root_title; 143 char *root_title;
141 char *root_desc; 144 char *root_desc;
142 char *root_readme; 145 char *root_readme;
143 char *script_name; 146 char *script_name;
144 char *virtual_root; 147 char *virtual_root;
145 int cache_size; 148 int cache_size;
146 int cache_dynamic_ttl; 149 int cache_dynamic_ttl;
147 int cache_max_create_time; 150 int cache_max_create_time;
diff --git a/cmd.c b/cmd.c
index 5b3c14c..8914fa5 100644
--- a/cmd.c
+++ b/cmd.c
@@ -83,50 +83,49 @@ static void objects_fn(struct cgit_context *ctx)
83} 83}
84 84
85static void repolist_fn(struct cgit_context *ctx) 85static void repolist_fn(struct cgit_context *ctx)
86{ 86{
87 cgit_print_repolist(); 87 cgit_print_repolist();
88} 88}
89 89
90static void patch_fn(struct cgit_context *ctx) 90static void patch_fn(struct cgit_context *ctx)
91{ 91{
92 cgit_print_patch(ctx->qry.sha1); 92 cgit_print_patch(ctx->qry.sha1);
93} 93}
94 94
95static void plain_fn(struct cgit_context *ctx) 95static void plain_fn(struct cgit_context *ctx)
96{ 96{
97 cgit_print_plain(ctx); 97 cgit_print_plain(ctx);
98} 98}
99 99
100static void refs_fn(struct cgit_context *ctx) 100static void refs_fn(struct cgit_context *ctx)
101{ 101{
102 cgit_print_refs(); 102 cgit_print_refs();
103} 103}
104 104
105static void snapshot_fn(struct cgit_context *ctx) 105static void snapshot_fn(struct cgit_context *ctx)
106{ 106{
107 cgit_print_snapshot(ctx->qry.head, ctx->qry.sha1, 107 cgit_print_snapshot(ctx->qry.head, ctx->qry.sha1, ctx->qry.path,
108 cgit_repobasename(ctx->repo->url), ctx->qry.path,
109 ctx->repo->snapshots, ctx->qry.nohead); 108 ctx->repo->snapshots, ctx->qry.nohead);
110} 109}
111 110
112static void summary_fn(struct cgit_context *ctx) 111static void summary_fn(struct cgit_context *ctx)
113{ 112{
114 cgit_print_summary(); 113 cgit_print_summary();
115} 114}
116 115
117static void tag_fn(struct cgit_context *ctx) 116static void tag_fn(struct cgit_context *ctx)
118{ 117{
119 cgit_print_tag(ctx->qry.sha1); 118 cgit_print_tag(ctx->qry.sha1);
120} 119}
121 120
122static void tree_fn(struct cgit_context *ctx) 121static void tree_fn(struct cgit_context *ctx)
123{ 122{
124 cgit_print_tree(ctx->qry.sha1, ctx->qry.path); 123 cgit_print_tree(ctx->qry.sha1, ctx->qry.path);
125} 124}
126 125
127#define def_cmd(name, want_repo, want_layout) \ 126#define def_cmd(name, want_repo, want_layout) \
128 {#name, name##_fn, want_repo, want_layout} 127 {#name, name##_fn, want_repo, want_layout}
129 128
130struct cgit_cmd *cgit_get_cmd(struct cgit_context *ctx) 129struct cgit_cmd *cgit_get_cmd(struct cgit_context *ctx)
131{ 130{
132 static struct cgit_cmd cmds[] = { 131 static struct cgit_cmd cmds[] = {
diff --git a/git b/git
Subproject 031e6c898f61db1ae0c0be641eac6532c1000d5 Subproject 8104ebfe8276657ee803cca7eb8665a78cf3ef8
diff --git a/shared.c b/shared.c
index f5875e4..a764c4d 100644
--- a/shared.c
+++ b/shared.c
@@ -39,48 +39,49 @@ struct cgit_repo *cgit_add_repo(const char *url)
39 39
40 if (++cgit_repolist.count > cgit_repolist.length) { 40 if (++cgit_repolist.count > cgit_repolist.length) {
41 if (cgit_repolist.length == 0) 41 if (cgit_repolist.length == 0)
42 cgit_repolist.length = 8; 42 cgit_repolist.length = 8;
43 else 43 else
44 cgit_repolist.length *= 2; 44 cgit_repolist.length *= 2;
45 cgit_repolist.repos = xrealloc(cgit_repolist.repos, 45 cgit_repolist.repos = xrealloc(cgit_repolist.repos,
46 cgit_repolist.length * 46 cgit_repolist.length *
47 sizeof(struct cgit_repo)); 47 sizeof(struct cgit_repo));
48 } 48 }
49 49
50 ret = &cgit_repolist.repos[cgit_repolist.count-1]; 50 ret = &cgit_repolist.repos[cgit_repolist.count-1];
51 ret->url = trim_end(url, '/'); 51 ret->url = trim_end(url, '/');
52 ret->name = ret->url; 52 ret->name = ret->url;
53 ret->path = NULL; 53 ret->path = NULL;
54 ret->desc = "[no description]"; 54 ret->desc = "[no description]";
55 ret->owner = NULL; 55 ret->owner = NULL;
56 ret->group = ctx.cfg.repo_group; 56 ret->group = ctx.cfg.repo_group;
57 ret->defbranch = "master"; 57 ret->defbranch = "master";
58 ret->snapshots = ctx.cfg.snapshots; 58 ret->snapshots = ctx.cfg.snapshots;
59 ret->enable_log_filecount = ctx.cfg.enable_log_filecount; 59 ret->enable_log_filecount = ctx.cfg.enable_log_filecount;
60 ret->enable_log_linecount = ctx.cfg.enable_log_linecount; 60 ret->enable_log_linecount = ctx.cfg.enable_log_linecount;
61 ret->module_link = ctx.cfg.module_link; 61 ret->module_link = ctx.cfg.module_link;
62 ret->readme = NULL; 62 ret->readme = NULL;
63 ret->mtime = -1;
63 return ret; 64 return ret;
64} 65}
65 66
66struct cgit_repo *cgit_get_repoinfo(const char *url) 67struct cgit_repo *cgit_get_repoinfo(const char *url)
67{ 68{
68 int i; 69 int i;
69 struct cgit_repo *repo; 70 struct cgit_repo *repo;
70 71
71 for (i=0; i<cgit_repolist.count; i++) { 72 for (i=0; i<cgit_repolist.count; i++) {
72 repo = &cgit_repolist.repos[i]; 73 repo = &cgit_repolist.repos[i];
73 if (!strcmp(repo->url, url)) 74 if (!strcmp(repo->url, url))
74 return repo; 75 return repo;
75 } 76 }
76 return NULL; 77 return NULL;
77} 78}
78 79
79void *cgit_free_commitinfo(struct commitinfo *info) 80void *cgit_free_commitinfo(struct commitinfo *info)
80{ 81{
81 free(info->author); 82 free(info->author);
82 free(info->author_email); 83 free(info->author_email);
83 free(info->committer); 84 free(info->committer);
84 free(info->committer_email); 85 free(info->committer_email);
85 free(info->subject); 86 free(info->subject);
86 free(info->msg); 87 free(info->msg);
@@ -245,52 +246,54 @@ int filediff_cb(void *priv, mmbuffer_t *mb, int nbuf)
245 diffbuf = NULL; 246 diffbuf = NULL;
246 buflen = 0; 247 buflen = 0;
247 } 248 }
248 if (diffbuf) { 249 if (diffbuf) {
249 ((linediff_fn)priv)(diffbuf, buflen); 250 ((linediff_fn)priv)(diffbuf, buflen);
250 free(diffbuf); 251 free(diffbuf);
251 diffbuf = NULL; 252 diffbuf = NULL;
252 buflen = 0; 253 buflen = 0;
253 } 254 }
254 return 0; 255 return 0;
255} 256}
256 257
257int cgit_diff_files(const unsigned char *old_sha1, 258int cgit_diff_files(const unsigned char *old_sha1,
258 const unsigned char *new_sha1, 259 const unsigned char *new_sha1,
259 linediff_fn fn) 260 linediff_fn fn)
260{ 261{
261 mmfile_t file1, file2; 262 mmfile_t file1, file2;
262 xpparam_t diff_params; 263 xpparam_t diff_params;
263 xdemitconf_t emit_params; 264 xdemitconf_t emit_params;
264 xdemitcb_t emit_cb; 265 xdemitcb_t emit_cb;
265 266
266 if (!load_mmfile(&file1, old_sha1) || !load_mmfile(&file2, new_sha1)) 267 if (!load_mmfile(&file1, old_sha1) || !load_mmfile(&file2, new_sha1))
267 return 1; 268 return 1;
268 269
270 memset(&diff_params, 0, sizeof(diff_params));
271 memset(&emit_params, 0, sizeof(emit_params));
272 memset(&emit_cb, 0, sizeof(emit_cb));
269 diff_params.flags = XDF_NEED_MINIMAL; 273 diff_params.flags = XDF_NEED_MINIMAL;
270 emit_params.ctxlen = 3; 274 emit_params.ctxlen = 3;
271 emit_params.flags = XDL_EMIT_FUNCNAMES; 275 emit_params.flags = XDL_EMIT_FUNCNAMES;
272 emit_params.find_func = NULL;
273 emit_cb.outf = filediff_cb; 276 emit_cb.outf = filediff_cb;
274 emit_cb.priv = fn; 277 emit_cb.priv = fn;
275 xdl_diff(&file1, &file2, &diff_params, &emit_params, &emit_cb); 278 xdl_diff(&file1, &file2, &diff_params, &emit_params, &emit_cb);
276 return 0; 279 return 0;
277} 280}
278 281
279void cgit_diff_tree(const unsigned char *old_sha1, 282void cgit_diff_tree(const unsigned char *old_sha1,
280 const unsigned char *new_sha1, 283 const unsigned char *new_sha1,
281 filepair_fn fn, const char *prefix) 284 filepair_fn fn, const char *prefix)
282{ 285{
283 struct diff_options opt; 286 struct diff_options opt;
284 int ret; 287 int ret;
285 int prefixlen; 288 int prefixlen;
286 289
287 diff_setup(&opt); 290 diff_setup(&opt);
288 opt.output_format = DIFF_FORMAT_CALLBACK; 291 opt.output_format = DIFF_FORMAT_CALLBACK;
289 opt.detect_rename = 1; 292 opt.detect_rename = 1;
290 opt.rename_limit = ctx.cfg.renamelimit; 293 opt.rename_limit = ctx.cfg.renamelimit;
291 DIFF_OPT_SET(&opt, RECURSIVE); 294 DIFF_OPT_SET(&opt, RECURSIVE);
292 opt.format_callback = cgit_diff_tree_cb; 295 opt.format_callback = cgit_diff_tree_cb;
293 opt.format_callback_data = fn; 296 opt.format_callback_data = fn;
294 if (prefix) { 297 if (prefix) {
295 opt.nr_paths = 1; 298 opt.nr_paths = 1;
296 opt.paths = &prefix; 299 opt.paths = &prefix;
diff --git a/ui-log.c b/ui-log.c
index d212984..2f90778 100644
--- a/ui-log.c
+++ b/ui-log.c
@@ -14,74 +14,88 @@ int files, add_lines, rem_lines;
14 14
15void count_lines(char *line, int size) 15void count_lines(char *line, int size)
16{ 16{
17 if (size <= 0) 17 if (size <= 0)
18 return; 18 return;
19 19
20 if (line[0] == '+') 20 if (line[0] == '+')
21 add_lines++; 21 add_lines++;
22 22
23 else if (line[0] == '-') 23 else if (line[0] == '-')
24 rem_lines++; 24 rem_lines++;
25} 25}
26 26
27void inspect_files(struct diff_filepair *pair) 27void inspect_files(struct diff_filepair *pair)
28{ 28{
29 files++; 29 files++;
30 if (ctx.repo->enable_log_linecount) 30 if (ctx.repo->enable_log_linecount)
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 print_commit(struct commit *commit) 34void print_commit(struct commit *commit)
35{ 35{
36 struct commitinfo *info; 36 struct commitinfo *info;
37 char *tmp; 37 char *tmp;
38 int cols = 2;
38 39
39 info = cgit_parse_commit(commit); 40 info = cgit_parse_commit(commit);
40 html("<tr><td>"); 41 htmlf("<tr%s><td>",
42 ctx.qry.showmsg ? " class='logheader'" : "");
41 tmp = fmt("id=%s", sha1_to_hex(commit->object.sha1)); 43 tmp = fmt("id=%s", sha1_to_hex(commit->object.sha1));
42 tmp = cgit_pageurl(ctx.repo->url, "commit", tmp); 44 tmp = cgit_pageurl(ctx.repo->url, "commit", tmp);
43 html_link_open(tmp, NULL, NULL); 45 html_link_open(tmp, NULL, NULL);
44 cgit_print_age(commit->date, TM_WEEK * 2, FMT_SHORTDATE); 46 cgit_print_age(commit->date, TM_WEEK * 2, FMT_SHORTDATE);
45 html_link_close(); 47 html_link_close();
46 html("</td><td>"); 48 htmlf("</td><td%s>",
49 ctx.qry.showmsg ? " class='logsubject'" : "");
47 cgit_commit_link(info->subject, NULL, NULL, ctx.qry.head, 50 cgit_commit_link(info->subject, NULL, NULL, ctx.qry.head,
48 sha1_to_hex(commit->object.sha1)); 51 sha1_to_hex(commit->object.sha1));
49 html("</td><td>"); 52 html("</td><td>");
50 html_txt(info->author); 53 html_txt(info->author);
51 if (ctx.repo->enable_log_filecount) { 54 if (ctx.repo->enable_log_filecount) {
52 files = 0; 55 files = 0;
53 add_lines = 0; 56 add_lines = 0;
54 rem_lines = 0; 57 rem_lines = 0;
55 cgit_diff_commit(commit, inspect_files); 58 cgit_diff_commit(commit, inspect_files);
56 html("</td><td>"); 59 html("</td><td>");
57 htmlf("%d", files); 60 htmlf("%d", files);
58 if (ctx.repo->enable_log_linecount) { 61 if (ctx.repo->enable_log_linecount) {
59 html("</td><td>"); 62 html("</td><td>");
60 htmlf("-%d/+%d", rem_lines, add_lines); 63 htmlf("-%d/+%d", rem_lines, add_lines);
61 } 64 }
62 } 65 }
63 html("</td></tr>\n"); 66 html("</td></tr>\n");
67 if (ctx.qry.showmsg) {
68 if (ctx.repo->enable_log_filecount) {
69 cols++;
70 if (ctx.repo->enable_log_linecount)
71 cols++;
72 }
73 htmlf("<tr class='nohover'><td/><td colspan='%d' class='logmsg'>",
74 cols);
75 html_txt(info->msg);
76 html("</td></tr>\n");
77 }
64 cgit_free_commitinfo(info); 78 cgit_free_commitinfo(info);
65} 79}
66 80
67static const char *disambiguate_ref(const char *ref) 81static const char *disambiguate_ref(const char *ref)
68{ 82{
69 unsigned char sha1[20]; 83 unsigned char sha1[20];
70 const char *longref; 84 const char *longref;
71 85
72 longref = fmt("refs/heads/%s", ref); 86 longref = fmt("refs/heads/%s", ref);
73 if (get_sha1(longref, sha1) == 0) 87 if (get_sha1(longref, sha1) == 0)
74 return longref; 88 return longref;
75 89
76 return ref; 90 return ref;
77} 91}
78 92
79void cgit_print_log(const char *tip, int ofs, int cnt, char *grep, char *pattern, 93void cgit_print_log(const char *tip, int ofs, int cnt, char *grep, char *pattern,
80 char *path, int pager) 94 char *path, int pager)
81{ 95{
82 struct rev_info rev; 96 struct rev_info rev;
83 struct commit *commit; 97 struct commit *commit;
84 const char *argv[] = {NULL, NULL, NULL, NULL, NULL}; 98 const char *argv[] = {NULL, NULL, NULL, NULL, NULL};
85 int argc = 2; 99 int argc = 2;
86 int i, columns = 3; 100 int i, columns = 3;
87 101
@@ -92,77 +106,84 @@ void cgit_print_log(const char *tip, int ofs, int cnt, char *grep, char *pattern
92 106
93 if (grep && pattern && (!strcmp(grep, "grep") || 107 if (grep && pattern && (!strcmp(grep, "grep") ||
94 !strcmp(grep, "author") || 108 !strcmp(grep, "author") ||
95 !strcmp(grep, "committer"))) 109 !strcmp(grep, "committer")))
96 argv[argc++] = fmt("--%s=%s", grep, pattern); 110 argv[argc++] = fmt("--%s=%s", grep, pattern);
97 111
98 if (path) { 112 if (path) {
99 argv[argc++] = "--"; 113 argv[argc++] = "--";
100 argv[argc++] = path; 114 argv[argc++] = path;
101 } 115 }
102 init_revisions(&rev, NULL); 116 init_revisions(&rev, NULL);
103 rev.abbrev = DEFAULT_ABBREV; 117 rev.abbrev = DEFAULT_ABBREV;
104 rev.commit_format = CMIT_FMT_DEFAULT; 118 rev.commit_format = CMIT_FMT_DEFAULT;
105 rev.verbose_header = 1; 119 rev.verbose_header = 1;
106 rev.show_root_diff = 0; 120 rev.show_root_diff = 0;
107 setup_revisions(argc, argv, &rev, NULL); 121 setup_revisions(argc, argv, &rev, NULL);
108 rev.grep_filter.regflags |= REG_ICASE; 122 rev.grep_filter.regflags |= REG_ICASE;
109 compile_grep_patterns(&rev.grep_filter); 123 compile_grep_patterns(&rev.grep_filter);
110 prepare_revision_walk(&rev); 124 prepare_revision_walk(&rev);
111 125
112 if (pager) 126 if (pager)
113 html("<table class='list nowrap'>"); 127 html("<table class='list nowrap'>");
114 128
115 html("<tr class='nohover'><th class='left'>Age</th>" 129 html("<tr class='nohover'><th class='left'>Age</th>"
116 "<th class='left'>Commit message</th>" 130 "<th class='left'>Commit message");
117 "<th class='left'>Author</th>"); 131 if (pager) {
132 html(" (");
133 cgit_log_link("toggle", NULL, NULL, ctx.qry.head, ctx.qry.sha1,
134 ctx.qry.path, ctx.qry.ofs, ctx.qry.grep,
135 ctx.qry.search, ctx.qry.showmsg ? 0 : 1);
136 html(")");
137 }
138 html("</th><th class='left'>Author</th>");
118 if (ctx.repo->enable_log_filecount) { 139 if (ctx.repo->enable_log_filecount) {
119 html("<th class='left'>Files</th>"); 140 html("<th class='left'>Files</th>");
120 columns++; 141 columns++;
121 if (ctx.repo->enable_log_linecount) { 142 if (ctx.repo->enable_log_linecount) {
122 html("<th class='left'>Lines</th>"); 143 html("<th class='left'>Lines</th>");
123 columns++; 144 columns++;
124 } 145 }
125 } 146 }
126 html("</tr>\n"); 147 html("</tr>\n");
127 148
128 if (ofs<0) 149 if (ofs<0)
129 ofs = 0; 150 ofs = 0;
130 151
131 for (i = 0; i < ofs && (commit = get_revision(&rev)) != NULL; i++) { 152 for (i = 0; i < ofs && (commit = get_revision(&rev)) != NULL; i++) {
132 free(commit->buffer); 153 free(commit->buffer);
133 commit->buffer = NULL; 154 commit->buffer = NULL;
134 free_commit_list(commit->parents); 155 free_commit_list(commit->parents);
135 commit->parents = NULL; 156 commit->parents = NULL;
136 } 157 }
137 158
138 for (i = 0; i < cnt && (commit = get_revision(&rev)) != NULL; i++) { 159 for (i = 0; i < cnt && (commit = get_revision(&rev)) != NULL; i++) {
139 print_commit(commit); 160 print_commit(commit);
140 free(commit->buffer); 161 free(commit->buffer);
141 commit->buffer = NULL; 162 commit->buffer = NULL;
142 free_commit_list(commit->parents); 163 free_commit_list(commit->parents);
143 commit->parents = NULL; 164 commit->parents = NULL;
144 } 165 }
145 if (pager) { 166 if (pager) {
146 htmlf("</table><div class='pager'>", 167 htmlf("</table><div class='pager'>",
147 columns); 168 columns);
148 if (ofs > 0) { 169 if (ofs > 0) {
149 cgit_log_link("[prev]", NULL, NULL, ctx.qry.head, 170 cgit_log_link("[prev]", NULL, NULL, ctx.qry.head,
150 ctx.qry.sha1, ctx.qry.path, 171 ctx.qry.sha1, ctx.qry.path,
151 ofs - cnt, ctx.qry.grep, 172 ofs - cnt, ctx.qry.grep,
152 ctx.qry.search); 173 ctx.qry.search, ctx.qry.showmsg);
153 html("&nbsp;"); 174 html("&nbsp;");
154 } 175 }
155 if ((commit = get_revision(&rev)) != NULL) { 176 if ((commit = get_revision(&rev)) != NULL) {
156 cgit_log_link("[next]", NULL, NULL, ctx.qry.head, 177 cgit_log_link("[next]", NULL, NULL, ctx.qry.head,
157 ctx.qry.sha1, ctx.qry.path, 178 ctx.qry.sha1, ctx.qry.path,
158 ofs + cnt, ctx.qry.grep, 179 ofs + cnt, ctx.qry.grep,
159 ctx.qry.search); 180 ctx.qry.search, ctx.qry.showmsg);
160 } 181 }
161 html("</div>"); 182 html("</div>");
162 } else if ((commit = get_revision(&rev)) != NULL) { 183 } else if ((commit = get_revision(&rev)) != NULL) {
163 html("<tr class='nohover'><td colspan='3'>"); 184 html("<tr class='nohover'><td colspan='3'>");
164 cgit_log_link("[...]", NULL, NULL, ctx.qry.head, NULL, NULL, 0, 185 cgit_log_link("[...]", NULL, NULL, ctx.qry.head, NULL, NULL, 0,
165 NULL, NULL); 186 NULL, NULL, ctx.qry.showmsg);
166 html("</td></tr>\n"); 187 html("</td></tr>\n");
167 } 188 }
168} 189}
diff --git a/ui-refs.c b/ui-refs.c
index 32e0429..d61ee7c 100644
--- a/ui-refs.c
+++ b/ui-refs.c
@@ -37,102 +37,134 @@ static int cmp_ref_name(const void *a, const void *b)
37static int cmp_branch_age(const void *a, const void *b) 37static int cmp_branch_age(const void *a, const void *b)
38{ 38{
39 struct refinfo *r1 = *(struct refinfo **)a; 39 struct refinfo *r1 = *(struct refinfo **)a;
40 struct refinfo *r2 = *(struct refinfo **)b; 40 struct refinfo *r2 = *(struct refinfo **)b;
41 41
42 return cmp_age(r1->commit->committer_date, r2->commit->committer_date); 42 return cmp_age(r1->commit->committer_date, r2->commit->committer_date);
43} 43}
44 44
45static int cmp_tag_age(const void *a, const void *b) 45static int cmp_tag_age(const void *a, const void *b)
46{ 46{
47 struct refinfo *r1 = *(struct refinfo **)a; 47 struct refinfo *r1 = *(struct refinfo **)a;
48 struct refinfo *r2 = *(struct refinfo **)b; 48 struct refinfo *r2 = *(struct refinfo **)b;
49 49
50 return cmp_age(r1->tag->tagger_date, r2->tag->tagger_date); 50 return cmp_age(r1->tag->tagger_date, r2->tag->tagger_date);
51} 51}
52 52
53static int print_branch(struct refinfo *ref) 53static int print_branch(struct refinfo *ref)
54{ 54{
55 struct commitinfo *info = ref->commit; 55 struct commitinfo *info = ref->commit;
56 char *name = (char *)ref->refname; 56 char *name = (char *)ref->refname;
57 57
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) {
65 cgit_commit_link(info->subject, NULL, NULL, name, NULL); 66 cgit_commit_link(info->subject, NULL, NULL, name, NULL);
66 html("</td><td>"); 67 html("</td><td>");
67 html_txt(info->author); 68 html_txt(info->author);
68 html("</td><td colspan='2'>"); 69 html("</td><td colspan='2'>");
69 cgit_print_age(info->commit->date, -1, NULL); 70 cgit_print_age(info->commit->date, -1, NULL);
70 } else { 71 } else {
71 html("</td><td></td><td>"); 72 html("</td><td></td><td>");
72 cgit_object_link(ref->object); 73 cgit_object_link(ref->object);
73 } 74 }
74 html("</td></tr>\n"); 75 html("</td></tr>\n");
75 return 0; 76 return 0;
76} 77}
77 78
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;
90 struct taginfo *info; 116 struct taginfo *info;
91 char *name = (char *)ref->refname; 117 char *name = (char *)ref->refname;
92 118
93 if (ref->object->type == OBJ_TAG) { 119 if (ref->object->type == OBJ_TAG) {
94 tag = (struct tag *)ref->object; 120 tag = (struct tag *)ref->object;
95 info = ref->tag; 121 info = ref->tag;
96 if (!tag || !info) 122 if (!tag || !info)
97 return 1; 123 return 1;
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>");
127 if (ctx.repo->snapshots && (tag->tagged->type == OBJ_COMMIT))
128 print_tag_downloads(ctx.repo, name);
129 else
101 cgit_object_link(tag->tagged); 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);
105 html("</td><td colspan='2'>"); 134 html("</td><td colspan='2'>");
106 if (info->tagger_date > 0) 135 if (info->tagger_date > 0)
107 cgit_print_age(info->tagger_date, -1, NULL); 136 cgit_print_age(info->tagger_date, -1, NULL);
108 html("</td></tr>\n"); 137 html("</td></tr>\n");
109 } else { 138 } else {
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 html_txt(name);
114 html("</td><td>"); 143 html("</td><td>");
144 if (ctx.repo->snapshots && (tag->tagged->type == OBJ_COMMIT))
145 print_tag_downloads(ctx.repo, name);
146 else
115 cgit_object_link(ref->object); 147 cgit_object_link(ref->object);
116 html("</td></tr>\n"); 148 html("</td></tr>\n");
117 } 149 }
118 return 0; 150 return 0;
119} 151}
120 152
121static void print_refs_link(char *path) 153static void print_refs_link(char *path)
122{ 154{
123 html("<tr class='nohover'><td colspan='4'>"); 155 html("<tr class='nohover'><td colspan='4'>");
124 cgit_refs_link("[...]", NULL, NULL, ctx.qry.head, NULL, path); 156 cgit_refs_link("[...]", NULL, NULL, ctx.qry.head, NULL, path);
125 html("</td></tr>"); 157 html("</td></tr>");
126} 158}
127 159
128void cgit_print_branches(int maxcount) 160void cgit_print_branches(int maxcount)
129{ 161{
130 struct reflist list; 162 struct reflist list;
131 int i; 163 int i;
132 164
133 html("<tr class='nohover'><th class='left'>Branch</th>" 165 html("<tr class='nohover'><th class='left'>Branch</th>"
134 "<th class='left'>Commit message</th>" 166 "<th class='left'>Commit message</th>"
135 "<th class='left'>Author</th>" 167 "<th class='left'>Author</th>"
136 "<th class='left' colspan='2'>Age</th></tr>\n"); 168 "<th class='left' colspan='2'>Age</th></tr>\n");
137 169
138 list.refs = NULL; 170 list.refs = NULL;
diff --git a/ui-repolist.c b/ui-repolist.c
index 2324273..2c13d50 100644
--- a/ui-repolist.c
+++ b/ui-repolist.c
@@ -11,161 +11,268 @@
11#include <string.h> 11#include <string.h>
12 12
13#include <time.h> 13#include <time.h>
14 14
15#include "cgit.h" 15#include "cgit.h"
16#include "html.h" 16#include "html.h"
17#include "ui-shared.h" 17#include "ui-shared.h"
18 18
19time_t read_agefile(char *path) 19time_t read_agefile(char *path)
20{ 20{
21 FILE *f; 21 FILE *f;
22 static char buf[64], buf2[64]; 22 static char buf[64], buf2[64];
23 23
24 if (!(f = fopen(path, "r"))) 24 if (!(f = fopen(path, "r")))
25 return -1; 25 return -1;
26 if (fgets(buf, sizeof(buf), f) == NULL) 26 if (fgets(buf, sizeof(buf), f) == NULL)
27 return -1; 27 return -1;
28 fclose(f); 28 fclose(f);
29 if (parse_date(buf, buf2, sizeof(buf2))) 29 if (parse_date(buf, buf2, sizeof(buf2)))
30 return strtoul(buf2, NULL, 10); 30 return strtoul(buf2, NULL, 10);
31 else 31 else
32 return 0; 32 return 0;
33} 33}
34 34
35static void print_modtime(struct cgit_repo *repo) 35static int get_repo_modtime(const struct cgit_repo *repo, time_t *mtime)
36{ 36{
37 char *path; 37 char *path;
38 struct stat s; 38 struct stat s;
39 struct cgit_repo *r = (struct cgit_repo *)repo;
39 40
41 if (repo->mtime != -1) {
42 *mtime = repo->mtime;
43 return 1;
44 }
40 path = fmt("%s/%s", repo->path, ctx.cfg.agefile); 45 path = fmt("%s/%s", repo->path, ctx.cfg.agefile);
41 if (stat(path, &s) == 0) { 46 if (stat(path, &s) == 0) {
42 cgit_print_age(read_agefile(path), -1, NULL); 47 *mtime = read_agefile(path);
43 return; 48 r->mtime = *mtime;
49 return 1;
44 } 50 }
45 51
46 path = fmt("%s/refs/heads/%s", repo->path, repo->defbranch); 52 path = fmt("%s/refs/heads/%s", repo->path, repo->defbranch);
47 if (stat(path, &s) != 0) 53 if (stat(path, &s) == 0)
48 return; 54 *mtime = s.st_mtime;
49 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);
50} 67}
51 68
52int is_match(struct cgit_repo *repo) 69int is_match(struct cgit_repo *repo)
53{ 70{
54 if (!ctx.qry.search) 71 if (!ctx.qry.search)
55 return 1; 72 return 1;
56 if (repo->url && strcasestr(repo->url, ctx.qry.search)) 73 if (repo->url && strcasestr(repo->url, ctx.qry.search))
57 return 1; 74 return 1;
58 if (repo->name && strcasestr(repo->name, ctx.qry.search)) 75 if (repo->name && strcasestr(repo->name, ctx.qry.search))
59 return 1; 76 return 1;
60 if (repo->desc && strcasestr(repo->desc, ctx.qry.search)) 77 if (repo->desc && strcasestr(repo->desc, ctx.qry.search))
61 return 1; 78 return 1;
62 if (repo->owner && strcasestr(repo->owner, ctx.qry.search)) 79 if (repo->owner && strcasestr(repo->owner, ctx.qry.search))
63 return 1; 80 return 1;
64 return 0; 81 return 0;
65} 82}
66 83
67int is_in_url(struct cgit_repo *repo) 84int is_in_url(struct cgit_repo *repo)
68{ 85{
69 if (!ctx.qry.url) 86 if (!ctx.qry.url)
70 return 1; 87 return 1;
71 if (repo->url && !prefixcmp(repo->url, ctx.qry.url)) 88 if (repo->url && !prefixcmp(repo->url, ctx.qry.url))
72 return 1; 89 return 1;
73 return 0; 90 return 0;
74} 91}
75 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
76void print_header(int columns) 103void print_header(int columns)
77{ 104{
78 html("<tr class='nohover'>" 105 html("<tr class='nohover'>");
79 "<th class='left'>Name</th>" 106 print_sort_header("Name", "name");
80 "<th class='left'>Description</th>" 107 print_sort_header("Description", "desc");
81 "<th class='left'>Owner</th>" 108 print_sort_header("Owner", "owner");
82 "<th class='left'>Idle</th>"); 109 print_sort_header("Idle", "idle");
83 if (ctx.cfg.enable_index_links) 110 if (ctx.cfg.enable_index_links)
84 html("<th class='left'>Links</th>"); 111 html("<th class='left'>Links</th>");
85 html("</tr>\n"); 112 html("</tr>\n");
86} 113}
87 114
88 115
89void print_pager(int items, int pagelen, char *search) 116void print_pager(int items, int pagelen, char *search)
90{ 117{
91 int i; 118 int i;
92 html("<div class='pager'>"); 119 html("<div class='pager'>");
93 for(i = 0; i * pagelen < items; i++) 120 for(i = 0; i * pagelen < items; i++)
94 cgit_index_link(fmt("[%d]", i+1), fmt("Page %d", i+1), NULL, 121 cgit_index_link(fmt("[%d]", i+1), fmt("Page %d", i+1), NULL,
95 search, i * pagelen); 122 search, i * pagelen);
96 html("</div>"); 123 html("</div>");
97} 124}
98 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
99void cgit_print_repolist() 201void cgit_print_repolist()
100{ 202{
101 int i, columns = 4, hits = 0, header = 0; 203 int i, columns = 4, hits = 0, header = 0;
102 char *last_group = NULL; 204 char *last_group = NULL;
205 int sorted = 0;
103 206
104 if (ctx.cfg.enable_index_links) 207 if (ctx.cfg.enable_index_links)
105 columns++; 208 columns++;
106 209
107 ctx.page.title = ctx.cfg.root_title; 210 ctx.page.title = ctx.cfg.root_title;
108 cgit_print_http_headers(&ctx); 211 cgit_print_http_headers(&ctx);
109 cgit_print_docstart(&ctx); 212 cgit_print_docstart(&ctx);
110 cgit_print_pageheader(&ctx); 213 cgit_print_pageheader(&ctx);
111 214
112 if (ctx.cfg.index_header) 215 if (ctx.cfg.index_header)
113 html_include(ctx.cfg.index_header); 216 html_include(ctx.cfg.index_header);
114 217
218 if(ctx.qry.sort)
219 sorted = sort_repolist(ctx.qry.sort);
220
115 html("<table summary='repository list' class='list nowrap'>"); 221 html("<table summary='repository list' class='list nowrap'>");
116 for (i=0; i<cgit_repolist.count; i++) { 222 for (i=0; i<cgit_repolist.count; i++) {
117 ctx.repo = &cgit_repolist.repos[i]; 223 ctx.repo = &cgit_repolist.repos[i];
118 if (!(is_match(ctx.repo) && is_in_url(ctx.repo))) 224 if (!(is_match(ctx.repo) && is_in_url(ctx.repo)))
119 continue; 225 continue;
120 hits++; 226 hits++;
121 if (hits <= ctx.qry.ofs) 227 if (hits <= ctx.qry.ofs)
122 continue; 228 continue;
123 if (hits > ctx.qry.ofs + ctx.cfg.max_repo_count) 229 if (hits > ctx.qry.ofs + ctx.cfg.max_repo_count)
124 continue; 230 continue;
125 if (!header++) 231 if (!header++)
126 print_header(columns); 232 print_header(columns);
127 if ((last_group == NULL && ctx.repo->group != NULL) || 233 if (!sorted &&
234 ((last_group == NULL && ctx.repo->group != NULL) ||
128 (last_group != NULL && ctx.repo->group == NULL) || 235 (last_group != NULL && ctx.repo->group == NULL) ||
129 (last_group != NULL && ctx.repo->group != NULL && 236 (last_group != NULL && ctx.repo->group != NULL &&
130 strcmp(ctx.repo->group, last_group))) { 237 strcmp(ctx.repo->group, last_group)))) {
131 htmlf("<tr class='nohover'><td colspan='%d' class='repogroup'>", 238 htmlf("<tr class='nohover'><td colspan='%d' class='repogroup'>",
132 columns); 239 columns);
133 html_txt(ctx.repo->group); 240 html_txt(ctx.repo->group);
134 html("</td></tr>"); 241 html("</td></tr>");
135 last_group = ctx.repo->group; 242 last_group = ctx.repo->group;
136 } 243 }
137 htmlf("<tr><td class='%s'>", 244 htmlf("<tr><td class='%s'>",
138 ctx.repo->group ? "sublevel-repo" : "toplevel-repo"); 245 !sorted && ctx.repo->group ? "sublevel-repo" : "toplevel-repo");
139 cgit_summary_link(ctx.repo->name, ctx.repo->name, NULL, NULL); 246 cgit_summary_link(ctx.repo->name, ctx.repo->name, NULL, NULL);
140 html("</td><td>"); 247 html("</td><td>");
141 html_link_open(cgit_repourl(ctx.repo->url), NULL, NULL); 248 html_link_open(cgit_repourl(ctx.repo->url), NULL, NULL);
142 html_ntxt(ctx.cfg.max_repodesc_len, ctx.repo->desc); 249 html_ntxt(ctx.cfg.max_repodesc_len, ctx.repo->desc);
143 html_link_close(); 250 html_link_close();
144 html("</td><td>"); 251 html("</td><td>");
145 html_txt(ctx.repo->owner); 252 html_txt(ctx.repo->owner);
146 html("</td><td>"); 253 html("</td><td>");
147 print_modtime(ctx.repo); 254 print_modtime(ctx.repo);
148 html("</td>"); 255 html("</td>");
149 if (ctx.cfg.enable_index_links) { 256 if (ctx.cfg.enable_index_links) {
150 html("<td>"); 257 html("<td>");
151 cgit_summary_link("summary", NULL, "button", NULL); 258 cgit_summary_link("summary", NULL, "button", NULL);
152 cgit_log_link("log", NULL, "button", NULL, NULL, NULL, 259 cgit_log_link("log", NULL, "button", NULL, NULL, NULL,
153 0, NULL, NULL); 260 0, NULL, NULL, ctx.qry.showmsg);
154 cgit_tree_link("tree", NULL, "button", NULL, NULL, NULL); 261 cgit_tree_link("tree", NULL, "button", NULL, NULL, NULL);
155 html("</td>"); 262 html("</td>");
156 } 263 }
157 html("</tr>\n"); 264 html("</tr>\n");
158 } 265 }
159 html("</table>"); 266 html("</table>");
160 if (!hits) 267 if (!hits)
161 cgit_print_error("No repositories found"); 268 cgit_print_error("No repositories found");
162 else if (hits > ctx.cfg.max_repo_count) 269 else if (hits > ctx.cfg.max_repo_count)
163 print_pager(hits, ctx.cfg.max_repo_count, ctx.qry.search); 270 print_pager(hits, ctx.cfg.max_repo_count, ctx.qry.search);
164 cgit_print_docend(); 271 cgit_print_docend();
165} 272}
166 273
167void cgit_print_site_readme() 274void cgit_print_site_readme()
168{ 275{
169 if (ctx.cfg.root_readme) 276 if (ctx.cfg.root_readme)
170 html_include(ctx.cfg.root_readme); 277 html_include(ctx.cfg.root_readme);
171} 278}
diff --git a/ui-shared.c b/ui-shared.c
index 224e5f3..95dfeb4 100644
--- a/ui-shared.c
+++ b/ui-shared.c
@@ -260,72 +260,78 @@ static void reporevlink(char *page, char *name, char *title, char *class,
260void cgit_summary_link(char *name, char *title, char *class, char *head) 260void cgit_summary_link(char *name, char *title, char *class, char *head)
261{ 261{
262 reporevlink(NULL, name, title, class, head, NULL, NULL); 262 reporevlink(NULL, name, title, class, head, NULL, NULL);
263} 263}
264 264
265void cgit_tag_link(char *name, char *title, char *class, char *head, 265void cgit_tag_link(char *name, char *title, char *class, char *head,
266 char *rev) 266 char *rev)
267{ 267{
268 reporevlink("tag", name, title, class, head, rev, NULL); 268 reporevlink("tag", name, title, class, head, rev, NULL);
269} 269}
270 270
271void cgit_tree_link(char *name, char *title, char *class, char *head, 271void cgit_tree_link(char *name, char *title, char *class, char *head,
272 char *rev, char *path) 272 char *rev, char *path)
273{ 273{
274 reporevlink("tree", name, title, class, head, rev, path); 274 reporevlink("tree", name, title, class, head, rev, path);
275} 275}
276 276
277void cgit_plain_link(char *name, char *title, char *class, char *head, 277void cgit_plain_link(char *name, char *title, char *class, char *head,
278 char *rev, char *path) 278 char *rev, char *path)
279{ 279{
280 reporevlink("plain", name, title, class, head, rev, path); 280 reporevlink("plain", name, title, class, head, rev, path);
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
288 delim = repolink(title, class, "log", head, path); 289 delim = repolink(title, class, "log", head, path);
289 if (rev && strcmp(rev, ctx.qry.head)) { 290 if (rev && strcmp(rev, ctx.qry.head)) {
290 html(delim); 291 html(delim);
291 html("id="); 292 html("id=");
292 html_url_arg(rev); 293 html_url_arg(rev);
293 delim = "&"; 294 delim = "&";
294 } 295 }
295 if (grep && pattern) { 296 if (grep && pattern) {
296 html(delim); 297 html(delim);
297 html("qt="); 298 html("qt=");
298 html_url_arg(grep); 299 html_url_arg(grep);
299 delim = "&"; 300 delim = "&";
300 html(delim); 301 html(delim);
301 html("q="); 302 html("q=");
302 html_url_arg(pattern); 303 html_url_arg(pattern);
303 } 304 }
304 if (ofs > 0) { 305 if (ofs > 0) {
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);
311 html("</a>"); 317 html("</a>");
312} 318}
313 319
314void cgit_commit_link(char *name, char *title, char *class, char *head, 320void cgit_commit_link(char *name, char *title, char *class, char *head,
315 char *rev) 321 char *rev)
316{ 322{
317 if (strlen(name) > ctx.cfg.max_msg_len && ctx.cfg.max_msg_len >= 15) { 323 if (strlen(name) > ctx.cfg.max_msg_len && ctx.cfg.max_msg_len >= 15) {
318 name[ctx.cfg.max_msg_len] = '\0'; 324 name[ctx.cfg.max_msg_len] = '\0';
319 name[ctx.cfg.max_msg_len - 1] = '.'; 325 name[ctx.cfg.max_msg_len - 1] = '.';
320 name[ctx.cfg.max_msg_len - 2] = '.'; 326 name[ctx.cfg.max_msg_len - 2] = '.';
321 name[ctx.cfg.max_msg_len - 3] = '.'; 327 name[ctx.cfg.max_msg_len - 3] = '.';
322 } 328 }
323 reporevlink("commit", name, title, class, head, rev, NULL); 329 reporevlink("commit", name, title, class, head, rev, NULL);
324} 330}
325 331
326void cgit_refs_link(char *name, char *title, char *class, char *head, 332void cgit_refs_link(char *name, char *title, char *class, char *head,
327 char *rev, char *path) 333 char *rev, char *path)
328{ 334{
329 reporevlink("refs", name, title, class, head, rev, path); 335 reporevlink("refs", name, title, class, head, rev, path);
330} 336}
331 337
@@ -344,63 +350,65 @@ void cgit_diff_link(char *name, char *title, char *class, char *head,
344 if (new_rev && strcmp(new_rev, ctx.qry.head)) { 350 if (new_rev && strcmp(new_rev, ctx.qry.head)) {
345 html(delim); 351 html(delim);
346 html("id="); 352 html("id=");
347 html_url_arg(new_rev); 353 html_url_arg(new_rev);
348 delim = "&amp;"; 354 delim = "&amp;";
349 } 355 }
350 if (old_rev) { 356 if (old_rev) {
351 html(delim); 357 html(delim);
352 html("id2="); 358 html("id2=");
353 html_url_arg(old_rev); 359 html_url_arg(old_rev);
354 } 360 }
355 html("'>"); 361 html("'>");
356 html_txt(name); 362 html_txt(name);
357 html("</a>"); 363 html("</a>");
358} 364}
359 365
360void cgit_patch_link(char *name, char *title, char *class, char *head, 366void cgit_patch_link(char *name, char *title, char *class, char *head,
361 char *rev) 367 char *rev)
362{ 368{
363 reporevlink("patch", name, title, class, head, rev, NULL); 369 reporevlink("patch", name, title, class, head, rev, NULL);
364} 370}
365 371
366void cgit_object_link(struct object *obj) 372void cgit_object_link(struct object *obj)
367{ 373{
368 char *page, *rev, *name; 374 char *page, *shortrev, *fullrev, *name;
369 375
376 fullrev = sha1_to_hex(obj->sha1);
377 shortrev = xstrdup(fullrev);
378 shortrev[10] = '\0';
370 if (obj->type == OBJ_COMMIT) { 379 if (obj->type == OBJ_COMMIT) {
371 cgit_commit_link(fmt("commit %s", sha1_to_hex(obj->sha1)), NULL, NULL, 380 cgit_commit_link(fmt("commit %s...", shortrev), NULL, NULL,
372 ctx.qry.head, sha1_to_hex(obj->sha1)); 381 ctx.qry.head, fullrev);
373 return; 382 return;
374 } else if (obj->type == OBJ_TREE) 383 } else if (obj->type == OBJ_TREE)
375 page = "tree"; 384 page = "tree";
376 else if (obj->type == OBJ_TAG) 385 else if (obj->type == OBJ_TAG)
377 page = "tag"; 386 page = "tag";
378 else 387 else
379 page = "blob"; 388 page = "blob";
380 rev = sha1_to_hex(obj->sha1); 389 name = fmt("%s %s...", typename(obj->type), shortrev);
381 name = fmt("%s %s", typename(obj->type), rev); 390 reporevlink(page, name, NULL, NULL, ctx.qry.head, fullrev, NULL);
382 reporevlink(page, name, NULL, NULL, ctx.qry.head, rev, NULL);
383} 391}
384 392
385void cgit_print_date(time_t secs, char *format, int local_time) 393void cgit_print_date(time_t secs, char *format, int local_time)
386{ 394{
387 char buf[64]; 395 char buf[64];
388 struct tm *time; 396 struct tm *time;
389 397
390 if (!secs) 398 if (!secs)
391 return; 399 return;
392 if(local_time) 400 if(local_time)
393 time = localtime(&secs); 401 time = localtime(&secs);
394 else 402 else
395 time = gmtime(&secs); 403 time = gmtime(&secs);
396 strftime(buf, sizeof(buf)-1, format, time); 404 strftime(buf, sizeof(buf)-1, format, time);
397 html_txt(buf); 405 html_txt(buf);
398} 406}
399 407
400void cgit_print_age(time_t t, time_t max_relative, char *format) 408void cgit_print_age(time_t t, time_t max_relative, char *format)
401{ 409{
402 time_t now, secs; 410 time_t now, secs;
403 411
404 if (!t) 412 if (!t)
405 return; 413 return;
406 time(&now); 414 time(&now);
@@ -547,48 +555,50 @@ int print_archive_ref(const char *refname, const unsigned char *sha1,
547 html_txt(strlpart(buf, 20)); 555 html_txt(strlpart(buf, 20));
548 html_link_close(); 556 html_link_close();
549 return 0; 557 return 0;
550} 558}
551 559
552void add_hidden_formfields(int incl_head, int incl_search, char *page) 560void add_hidden_formfields(int incl_head, int incl_search, char *page)
553{ 561{
554 char *url; 562 char *url;
555 563
556 if (!ctx.cfg.virtual_root) { 564 if (!ctx.cfg.virtual_root) {
557 url = fmt("%s/%s", ctx.qry.repo, page); 565 url = fmt("%s/%s", ctx.qry.repo, page);
558 if (ctx.qry.path) 566 if (ctx.qry.path)
559 url = fmt("%s/%s", url, ctx.qry.path); 567 url = fmt("%s/%s", url, ctx.qry.path);
560 html_hidden("url", url); 568 html_hidden("url", url);
561 } 569 }
562 570
563 if (incl_head && ctx.qry.head && ctx.repo->defbranch && 571 if (incl_head && ctx.qry.head && ctx.repo->defbranch &&
564 strcmp(ctx.qry.head, ctx.repo->defbranch)) 572 strcmp(ctx.qry.head, ctx.repo->defbranch))
565 html_hidden("h", ctx.qry.head); 573 html_hidden("h", ctx.qry.head);
566 574
567 if (ctx.qry.sha1) 575 if (ctx.qry.sha1)
568 html_hidden("id", ctx.qry.sha1); 576 html_hidden("id", ctx.qry.sha1);
569 if (ctx.qry.sha2) 577 if (ctx.qry.sha2)
570 html_hidden("id2", ctx.qry.sha2); 578 html_hidden("id2", ctx.qry.sha2);
579 if (ctx.qry.showmsg)
580 html_hidden("showmsg", "1");
571 581
572 if (incl_search) { 582 if (incl_search) {
573 if (ctx.qry.grep) 583 if (ctx.qry.grep)
574 html_hidden("qt", ctx.qry.grep); 584 html_hidden("qt", ctx.qry.grep);
575 if (ctx.qry.search) 585 if (ctx.qry.search)
576 html_hidden("q", ctx.qry.search); 586 html_hidden("q", ctx.qry.search);
577 } 587 }
578} 588}
579 589
580char *hc(struct cgit_cmd *cmd, const char *page) 590char *hc(struct cgit_cmd *cmd, const char *page)
581{ 591{
582 return (strcmp(cmd->name, page) ? NULL : "active"); 592 return (strcmp(cmd->name, page) ? NULL : "active");
583} 593}
584 594
585void cgit_print_pageheader(struct cgit_context *ctx) 595void cgit_print_pageheader(struct cgit_context *ctx)
586{ 596{
587 struct cgit_cmd *cmd = cgit_get_cmd(ctx); 597 struct cgit_cmd *cmd = cgit_get_cmd(ctx);
588 598
589 html("<table id='header'>\n"); 599 html("<table id='header'>\n");
590 html("<tr>\n"); 600 html("<tr>\n");
591 html("<td class='logo' rowspan='2'><a href='"); 601 html("<td class='logo' rowspan='2'><a href='");
592 if (ctx->cfg.logo_link) 602 if (ctx->cfg.logo_link)
593 html_attr(ctx->cfg.logo_link); 603 html_attr(ctx->cfg.logo_link);
594 else 604 else
@@ -613,49 +623,49 @@ void cgit_print_pageheader(struct cgit_context *ctx)
613 } else 623 } else
614 html_txt(ctx->cfg.root_title); 624 html_txt(ctx->cfg.root_title);
615 html("</td></tr>\n"); 625 html("</td></tr>\n");
616 626
617 html("<tr><td class='sub'>"); 627 html("<tr><td class='sub'>");
618 if (ctx->repo) { 628 if (ctx->repo) {
619 html_txt(ctx->repo->desc); 629 html_txt(ctx->repo->desc);
620 html("</td><td class='sub right'>"); 630 html("</td><td class='sub right'>");
621 html_txt(ctx->repo->owner); 631 html_txt(ctx->repo->owner);
622 } else { 632 } else {
623 if (ctx->cfg.root_desc) 633 if (ctx->cfg.root_desc)
624 html_txt(ctx->cfg.root_desc); 634 html_txt(ctx->cfg.root_desc);
625 else if (ctx->cfg.index_info) 635 else if (ctx->cfg.index_info)
626 html_include(ctx->cfg.index_info); 636 html_include(ctx->cfg.index_info);
627 } 637 }
628 html("</td></tr></table>\n"); 638 html("</td></tr></table>\n");
629 639
630 html("<table class='tabs'><tr><td>\n"); 640 html("<table class='tabs'><tr><td>\n");
631 if (ctx->repo) { 641 if (ctx->repo) {
632 cgit_summary_link("summary", NULL, hc(cmd, "summary"), 642 cgit_summary_link("summary", NULL, hc(cmd, "summary"),
633 ctx->qry.head); 643 ctx->qry.head);
634 cgit_refs_link("refs", NULL, hc(cmd, "refs"), ctx->qry.head, 644 cgit_refs_link("refs", NULL, hc(cmd, "refs"), ctx->qry.head,
635 ctx->qry.sha1, NULL); 645 ctx->qry.sha1, NULL);
636 cgit_log_link("log", NULL, hc(cmd, "log"), ctx->qry.head, 646 cgit_log_link("log", NULL, hc(cmd, "log"), ctx->qry.head,
637 NULL, NULL, 0, NULL, NULL); 647 NULL, NULL, 0, NULL, NULL, ctx->qry.showmsg);
638 cgit_tree_link("tree", NULL, hc(cmd, "tree"), ctx->qry.head, 648 cgit_tree_link("tree", NULL, hc(cmd, "tree"), ctx->qry.head,
639 ctx->qry.sha1, NULL); 649 ctx->qry.sha1, NULL);
640 cgit_commit_link("commit", NULL, hc(cmd, "commit"), 650 cgit_commit_link("commit", NULL, hc(cmd, "commit"),
641 ctx->qry.head, ctx->qry.sha1); 651 ctx->qry.head, ctx->qry.sha1);
642 cgit_diff_link("diff", NULL, hc(cmd, "diff"), ctx->qry.head, 652 cgit_diff_link("diff", NULL, hc(cmd, "diff"), ctx->qry.head,
643 ctx->qry.sha1, ctx->qry.sha2, NULL); 653 ctx->qry.sha1, ctx->qry.sha2, NULL);
644 if (ctx->repo->readme) 654 if (ctx->repo->readme)
645 reporevlink("about", "about", NULL, 655 reporevlink("about", "about", NULL,
646 hc(cmd, "about"), ctx->qry.head, NULL, 656 hc(cmd, "about"), ctx->qry.head, NULL,
647 NULL); 657 NULL);
648 html("</td><td class='form'>"); 658 html("</td><td class='form'>");
649 html("<form class='right' method='get' action='"); 659 html("<form class='right' method='get' action='");
650 if (ctx->cfg.virtual_root) 660 if (ctx->cfg.virtual_root)
651 html_url_path(cgit_fileurl(ctx->qry.repo, "log", 661 html_url_path(cgit_fileurl(ctx->qry.repo, "log",
652 ctx->qry.path, NULL)); 662 ctx->qry.path, NULL));
653 html("'>\n"); 663 html("'>\n");
654 add_hidden_formfields(1, 0, "log"); 664 add_hidden_formfields(1, 0, "log");
655 html("<select name='qt'>\n"); 665 html("<select name='qt'>\n");
656 html_option("grep", "log msg", ctx->qry.grep); 666 html_option("grep", "log msg", ctx->qry.grep);
657 html_option("author", "author", ctx->qry.grep); 667 html_option("author", "author", ctx->qry.grep);
658 html_option("committer", "committer", ctx->qry.grep); 668 html_option("committer", "committer", ctx->qry.grep);
659 html("</select>\n"); 669 html("</select>\n");
660 html("<input class='txt' type='text' size='10' name='q' value='"); 670 html("<input class='txt' type='text' size='10' name='q' value='");
661 html_attr(ctx->qry.search); 671 html_attr(ctx->qry.search);
@@ -686,29 +696,28 @@ void cgit_print_filemode(unsigned short mode)
686 if (S_ISDIR(mode)) 696 if (S_ISDIR(mode))
687 html("d"); 697 html("d");
688 else if (S_ISLNK(mode)) 698 else if (S_ISLNK(mode))
689 html("l"); 699 html("l");
690 else if (S_ISGITLINK(mode)) 700 else if (S_ISGITLINK(mode))
691 html("m"); 701 html("m");
692 else 702 else
693 html("-"); 703 html("-");
694 html_fileperm(mode >> 6); 704 html_fileperm(mode >> 6);
695 html_fileperm(mode >> 3); 705 html_fileperm(mode >> 3);
696 html_fileperm(mode); 706 html_fileperm(mode);
697} 707}
698 708
699void cgit_print_snapshot_links(const char *repo, const char *head, 709void cgit_print_snapshot_links(const char *repo, const char *head,
700 const char *hex, int snapshots) 710 const char *hex, int snapshots)
701{ 711{
702 const struct cgit_snapshot_format* f; 712 const struct cgit_snapshot_format* f;
703 char *filename; 713 char *filename;
704 714
705 for (f = cgit_snapshot_formats; f->suffix; f++) { 715 for (f = cgit_snapshot_formats; f->suffix; f++) {
706 if (!(snapshots & f->bit)) 716 if (!(snapshots & f->bit))
707 continue; 717 continue;
708 filename = fmt("%s-%s%s", cgit_repobasename(repo), hex, 718 filename = fmt("%s-%s%s", cgit_repobasename(repo), hex,
709 f->suffix); 719 f->suffix);
710 cgit_snapshot_link(filename, NULL, NULL, (char *)head, 720 cgit_snapshot_link(filename, NULL, NULL, NULL, NULL, filename);
711 (char *)hex, filename);
712 html("<br/>"); 721 html("<br/>");
713 } 722 }
714} 723}
diff --git a/ui-shared.h b/ui-shared.h
index 3c8a6d0..2ab53ae 100644
--- a/ui-shared.h
+++ b/ui-shared.h
@@ -1,46 +1,46 @@
1#ifndef UI_SHARED_H 1#ifndef UI_SHARED_H
2#define UI_SHARED_H 2#define UI_SHARED_H
3 3
4extern char *cgit_hosturl(); 4extern char *cgit_hosturl();
5extern char *cgit_repourl(const char *reponame); 5extern char *cgit_repourl(const char *reponame);
6extern char *cgit_fileurl(const char *reponame, const char *pagename, 6extern char *cgit_fileurl(const char *reponame, const char *pagename,
7 const char *filename, const char *query); 7 const char *filename, const char *query);
8extern char *cgit_pageurl(const char *reponame, const char *pagename, 8extern char *cgit_pageurl(const char *reponame, const char *pagename,
9 const char *query); 9 const char *query);
10 10
11extern void cgit_index_link(char *name, char *title, char *class, 11extern void cgit_index_link(char *name, char *title, char *class,
12 char *pattern, int ofs); 12 char *pattern, int ofs);
13extern void cgit_summary_link(char *name, char *title, char *class, char *head); 13extern void cgit_summary_link(char *name, char *title, char *class, char *head);
14extern void cgit_tag_link(char *name, char *title, char *class, char *head, 14extern void cgit_tag_link(char *name, char *title, char *class, char *head,
15 char *rev); 15 char *rev);
16extern void cgit_tree_link(char *name, char *title, char *class, char *head, 16extern void cgit_tree_link(char *name, char *title, char *class, char *head,
17 char *rev, char *path); 17 char *rev, char *path);
18extern void cgit_plain_link(char *name, char *title, char *class, char *head, 18extern 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,
26 char *rev); 26 char *rev);
27extern void cgit_refs_link(char *name, char *title, char *class, char *head, 27extern void cgit_refs_link(char *name, char *title, char *class, char *head,
28 char *rev, char *path); 28 char *rev, char *path);
29extern void cgit_snapshot_link(char *name, char *title, char *class, 29extern void cgit_snapshot_link(char *name, char *title, char *class,
30 char *head, char *rev, char *archivename); 30 char *head, char *rev, char *archivename);
31extern void cgit_diff_link(char *name, char *title, char *class, char *head, 31extern void cgit_diff_link(char *name, char *title, char *class, char *head,
32 char *new_rev, char *old_rev, char *path); 32 char *new_rev, char *old_rev, char *path);
33extern void cgit_object_link(struct object *obj); 33extern void cgit_object_link(struct object *obj);
34 34
35extern void cgit_print_error(char *msg); 35extern void cgit_print_error(char *msg);
36extern void cgit_print_date(time_t secs, char *format, int local_time); 36extern void cgit_print_date(time_t secs, char *format, int local_time);
37extern void cgit_print_age(time_t t, time_t max_relative, char *format); 37extern void cgit_print_age(time_t t, time_t max_relative, char *format);
38extern void cgit_print_http_headers(struct cgit_context *ctx); 38extern void cgit_print_http_headers(struct cgit_context *ctx);
39extern void cgit_print_docstart(struct cgit_context *ctx); 39extern void cgit_print_docstart(struct cgit_context *ctx);
40extern void cgit_print_docend(); 40extern void cgit_print_docend();
41extern void cgit_print_pageheader(struct cgit_context *ctx); 41extern void cgit_print_pageheader(struct cgit_context *ctx);
42extern void cgit_print_filemode(unsigned short mode); 42extern void cgit_print_filemode(unsigned short mode);
43extern void cgit_print_snapshot_links(const char *repo, const char *head, 43extern void cgit_print_snapshot_links(const char *repo, const char *head,
44 const char *hex, int snapshots); 44 const char *hex, int snapshots);
45 45
46#endif /* UI_SHARED_H */ 46#endif /* UI_SHARED_H */
diff --git a/ui-snapshot.c b/ui-snapshot.c
index 9c4d086..6f09151 100644
--- a/ui-snapshot.c
+++ b/ui-snapshot.c
@@ -93,96 +93,100 @@ static int make_snapshot(const struct cgit_snapshot_format *format,
93 return 1; 93 return 1;
94 } 94 }
95 commit = lookup_commit_reference(sha1); 95 commit = lookup_commit_reference(sha1);
96 if(!commit) { 96 if(!commit) {
97 cgit_print_error(fmt("Not a commit reference: %s", hex)); 97 cgit_print_error(fmt("Not a commit reference: %s", hex));
98 return 1; 98 return 1;
99 } 99 }
100 memset(&args, 0, sizeof(args)); 100 memset(&args, 0, sizeof(args));
101 if (prefix) { 101 if (prefix) {
102 args.base = fmt("%s/", prefix); 102 args.base = fmt("%s/", prefix);
103 args.baselen = strlen(prefix) + 1; 103 args.baselen = strlen(prefix) + 1;
104 } else { 104 } else {
105 args.base = ""; 105 args.base = "";
106 args.baselen = 0; 106 args.baselen = 0;
107 } 107 }
108 args.tree = commit->tree; 108 args.tree = commit->tree;
109 args.time = commit->date; 109 args.time = commit->date;
110 ctx.page.mimetype = xstrdup(format->mimetype); 110 ctx.page.mimetype = xstrdup(format->mimetype);
111 ctx.page.filename = xstrdup(filename); 111 ctx.page.filename = xstrdup(filename);
112 cgit_print_http_headers(&ctx); 112 cgit_print_http_headers(&ctx);
113 format->write_func(&args); 113 format->write_func(&args);
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;
133
134 snapshot = xstrdup(filename);
135 snapshot[strlen(snapshot) - strlen(format->suffix)] = '\0';
136 fprintf(stderr, "snapshot=%s\n", snapshot);
137
138 if (get_sha1(snapshot, sha1) == 0)
139 return snapshot;
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
155 return NULL; 156 return NULL;
156 filename += strlen(reponame);
157 while (filename && (*filename == '-' || *filename == '_'))
158 filename++;
159 dwim_filename = xstrdup(filename);
160 dwim_filename[strlen(filename) - strlen(fmt->suffix)] = '\0';
161 for_each_tag_ref(ref_cb, NULL);
162 return dwim_refname;
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) {
172 ctx.page.mimetype = "text/html"; 167 ctx.page.mimetype = "text/html";
173 cgit_print_http_headers(&ctx); 168 cgit_print_http_headers(&ctx);
174 cgit_print_docstart(&ctx); 169 cgit_print_docstart(&ctx);
175 cgit_print_pageheader(&ctx); 170 cgit_print_pageheader(&ctx);
176 cgit_print_error(fmt("Unsupported snapshot format: %s", filename)); 171 cgit_print_error(fmt("Unsupported snapshot format: %s", filename));
177 cgit_print_docend(); 172 cgit_print_docend();
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 prefix = xstrdup(filename);
180 prefix[strlen(filename) - strlen(f->suffix)] = '\0';
181 }
182 }
183 183
184 if (!hex) 184 if (!hex)
185 hex = head; 185 hex = head;
186 186
187 if (!prefix)
188 prefix = xstrdup(cgit_repobasename(ctx.repo->url));
189
187 make_snapshot(f, hex, prefix, filename); 190 make_snapshot(f, hex, prefix, filename);
191 free(prefix);
188} 192}
diff --git a/ui-snapshot.h b/ui-snapshot.h
index 3540303..b6ede52 100644
--- a/ui-snapshot.h
+++ b/ui-snapshot.h
@@ -1,8 +1,7 @@
1#ifndef UI_SNAPSHOT_H 1#ifndef UI_SNAPSHOT_H
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-tree.c b/ui-tree.c
index 79332fc..051db7c 100644
--- a/ui-tree.c
+++ b/ui-tree.c
@@ -85,49 +85,49 @@ static int ls_item(const unsigned char *sha1, const char *base, int baselen,
85 } 85 }
86 86
87 html("<tr><td class='ls-mode'>"); 87 html("<tr><td class='ls-mode'>");
88 cgit_print_filemode(mode); 88 cgit_print_filemode(mode);
89 html("</td><td>"); 89 html("</td><td>");
90 if (S_ISGITLINK(mode)) { 90 if (S_ISGITLINK(mode)) {
91 htmlf("<a class='ls-mod' href='"); 91 htmlf("<a class='ls-mod' href='");
92 html_attr(fmt(ctx.repo->module_link, 92 html_attr(fmt(ctx.repo->module_link,
93 name, 93 name,
94 sha1_to_hex(sha1))); 94 sha1_to_hex(sha1)));
95 html("'>"); 95 html("'>");
96 html_txt(name); 96 html_txt(name);
97 html("</a>"); 97 html("</a>");
98 } else if (S_ISDIR(mode)) { 98 } else if (S_ISDIR(mode)) {
99 cgit_tree_link(name, NULL, "ls-dir", ctx.qry.head, 99 cgit_tree_link(name, NULL, "ls-dir", ctx.qry.head,
100 curr_rev, fullpath); 100 curr_rev, fullpath);
101 } else { 101 } else {
102 cgit_tree_link(name, NULL, "ls-blob", ctx.qry.head, 102 cgit_tree_link(name, NULL, "ls-blob", ctx.qry.head,
103 curr_rev, fullpath); 103 curr_rev, fullpath);
104 } 104 }
105 htmlf("</td><td class='ls-size'>%li</td>", size); 105 htmlf("</td><td class='ls-size'>%li</td>", size);
106 106
107 html("<td>"); 107 html("<td>");
108 cgit_log_link("log", NULL, "button", ctx.qry.head, curr_rev, 108 cgit_log_link("log", NULL, "button", ctx.qry.head, curr_rev,
109 fullpath, 0, NULL, NULL); 109 fullpath, 0, NULL, NULL, ctx.qry.showmsg);
110 html("</td></tr>\n"); 110 html("</td></tr>\n");
111 free(name); 111 free(name);
112 return 0; 112 return 0;
113} 113}
114 114
115static void ls_head() 115static void ls_head()
116{ 116{
117 html("<table summary='tree listing' class='list'>\n"); 117 html("<table summary='tree listing' class='list'>\n");
118 html("<tr class='nohover'>"); 118 html("<tr class='nohover'>");
119 html("<th class='left'>Mode</th>"); 119 html("<th class='left'>Mode</th>");
120 html("<th class='left'>Name</th>"); 120 html("<th class='left'>Name</th>");
121 html("<th class='right'>Size</th>"); 121 html("<th class='right'>Size</th>");
122 html("<th/>"); 122 html("<th/>");
123 html("</tr>\n"); 123 html("</tr>\n");
124 header = 1; 124 header = 1;
125} 125}
126 126
127static void ls_tail() 127static void ls_tail()
128{ 128{
129 if (!header) 129 if (!header)
130 return; 130 return;
131 html("</table>\n"); 131 html("</table>\n");
132 header = 0; 132 header = 0;
133} 133}