summaryrefslogtreecommitdiffabout
Unidiff
Diffstat (more/less context) (ignore whitespace changes)
-rw-r--r--Makefile2
-rw-r--r--cgit.c19
-rw-r--r--cgit.css5
-rw-r--r--cgit.h6
-rw-r--r--cgitrc.5.txt14
-rw-r--r--cmd.c2
-rw-r--r--parsing.c4
-rw-r--r--scan-tree.c2
-rw-r--r--shared.c80
-rw-r--r--ui-atom.c4
-rw-r--r--ui-commit.c13
-rw-r--r--ui-log.c11
-rw-r--r--ui-plain.c68
-rw-r--r--ui-shared.c1
14 files changed, 205 insertions, 26 deletions
diff --git a/Makefile b/Makefile
index 0a5055b..3a4d974 100644
--- a/Makefile
+++ b/Makefile
@@ -1,25 +1,25 @@
1CGIT_VERSION = v0.8.3.1 1CGIT_VERSION = v0.8.3.2
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_DATA_PATH = $(CGIT_SCRIPT_PATH)
5CGIT_CONFIG = /etc/cgitrc 5CGIT_CONFIG = /etc/cgitrc
6CACHE_ROOT = /var/cache/cgit 6CACHE_ROOT = /var/cache/cgit
7SHA1_HEADER = <openssl/sha.h> 7SHA1_HEADER = <openssl/sha.h>
8GIT_VER = 1.7.0 8GIT_VER = 1.7.0
9GIT_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 10INSTALL = install
11 11
12# Define NO_STRCASESTR if you don't have strcasestr. 12# Define NO_STRCASESTR if you don't have strcasestr.
13# 13#
14# Define NO_OPENSSL to disable linking with OpenSSL and use bundled SHA1 14# Define NO_OPENSSL to disable linking with OpenSSL and use bundled SHA1
15# implementation (slower). 15# implementation (slower).
16# 16#
17# Define NEEDS_LIBICONV if linking with libc is not enough (eg. Darwin). 17# Define NEEDS_LIBICONV if linking with libc is not enough (eg. Darwin).
18# 18#
19 19
20#-include config.mak 20#-include config.mak
21 21
22# 22#
23# Platform specific tweaks 23# Platform specific tweaks
24# 24#
25 25
diff --git a/cgit.c b/cgit.c
index 9452884..c263872 100644
--- a/cgit.c
+++ b/cgit.c
@@ -41,48 +41,50 @@ struct cgit_filter *new_filter(const char *cmd, int extra_args)
41} 41}
42 42
43static void process_cached_repolist(const char *path); 43static void process_cached_repolist(const char *path);
44 44
45void repo_config(struct cgit_repo *repo, const char *name, const char *value) 45void repo_config(struct cgit_repo *repo, const char *name, const char *value)
46{ 46{
47 if (!strcmp(name, "name")) 47 if (!strcmp(name, "name"))
48 repo->name = xstrdup(value); 48 repo->name = xstrdup(value);
49 else if (!strcmp(name, "clone-url")) 49 else if (!strcmp(name, "clone-url"))
50 repo->clone_url = xstrdup(value); 50 repo->clone_url = xstrdup(value);
51 else if (!strcmp(name, "desc")) 51 else if (!strcmp(name, "desc"))
52 repo->desc = xstrdup(value); 52 repo->desc = xstrdup(value);
53 else if (!strcmp(name, "owner")) 53 else if (!strcmp(name, "owner"))
54 repo->owner = xstrdup(value); 54 repo->owner = xstrdup(value);
55 else if (!strcmp(name, "defbranch")) 55 else if (!strcmp(name, "defbranch"))
56 repo->defbranch = xstrdup(value); 56 repo->defbranch = xstrdup(value);
57 else if (!strcmp(name, "snapshots")) 57 else if (!strcmp(name, "snapshots"))
58 repo->snapshots = ctx.cfg.snapshots & cgit_parse_snapshots_mask(value); 58 repo->snapshots = ctx.cfg.snapshots & cgit_parse_snapshots_mask(value);
59 else if (!strcmp(name, "enable-log-filecount")) 59 else if (!strcmp(name, "enable-log-filecount"))
60 repo->enable_log_filecount = ctx.cfg.enable_log_filecount * atoi(value); 60 repo->enable_log_filecount = ctx.cfg.enable_log_filecount * atoi(value);
61 else if (!strcmp(name, "enable-log-linecount")) 61 else if (!strcmp(name, "enable-log-linecount"))
62 repo->enable_log_linecount = ctx.cfg.enable_log_linecount * atoi(value); 62 repo->enable_log_linecount = ctx.cfg.enable_log_linecount * atoi(value);
63 else if (!strcmp(name, "enable-remote-branches")) 63 else if (!strcmp(name, "enable-remote-branches"))
64 repo->enable_remote_branches = atoi(value); 64 repo->enable_remote_branches = atoi(value);
65 else if (!strcmp(name, "enable-subject-links"))
66 repo->enable_subject_links = atoi(value);
65 else if (!strcmp(name, "max-stats")) 67 else if (!strcmp(name, "max-stats"))
66 repo->max_stats = cgit_find_stats_period(value, NULL); 68 repo->max_stats = cgit_find_stats_period(value, NULL);
67 else if (!strcmp(name, "module-link")) 69 else if (!strcmp(name, "module-link"))
68 repo->module_link= xstrdup(value); 70 repo->module_link= xstrdup(value);
69 else if (!strcmp(name, "section")) 71 else if (!strcmp(name, "section"))
70 repo->section = xstrdup(value); 72 repo->section = xstrdup(value);
71 else if (!strcmp(name, "readme") && value != NULL) { 73 else if (!strcmp(name, "readme") && value != NULL) {
72 if (*value == '/') 74 if (*value == '/')
73 repo->readme = xstrdup(value); 75 repo->readme = xstrdup(value);
74 else 76 else
75 repo->readme = xstrdup(fmt("%s/%s", repo->path, value)); 77 repo->readme = xstrdup(fmt("%s/%s", repo->path, value));
76 } else if (ctx.cfg.enable_filter_overrides) { 78 } else if (ctx.cfg.enable_filter_overrides) {
77 if (!strcmp(name, "about-filter")) 79 if (!strcmp(name, "about-filter"))
78 repo->about_filter = new_filter(value, 0); 80 repo->about_filter = new_filter(value, 0);
79 else if (!strcmp(name, "commit-filter")) 81 else if (!strcmp(name, "commit-filter"))
80 repo->commit_filter = new_filter(value, 0); 82 repo->commit_filter = new_filter(value, 0);
81 else if (!strcmp(name, "source-filter")) 83 else if (!strcmp(name, "source-filter"))
82 repo->source_filter = new_filter(value, 1); 84 repo->source_filter = new_filter(value, 1);
83 } 85 }
84} 86}
85 87
86void config_cb(const char *name, const char *value) 88void config_cb(const char *name, const char *value)
87{ 89{
88 if (!strcmp(name, "section") || !strcmp(name, "repo.group")) 90 if (!strcmp(name, "section") || !strcmp(name, "repo.group"))
@@ -120,203 +122,210 @@ void config_cb(const char *name, const char *value)
120 else if (!strcmp(name, "module-link")) 122 else if (!strcmp(name, "module-link"))
121 ctx.cfg.module_link = xstrdup(value); 123 ctx.cfg.module_link = xstrdup(value);
122 else if (!strcmp(name, "virtual-root")) { 124 else if (!strcmp(name, "virtual-root")) {
123 ctx.cfg.virtual_root = trim_end(value, '/'); 125 ctx.cfg.virtual_root = trim_end(value, '/');
124 if (!ctx.cfg.virtual_root && (!strcmp(value, "/"))) 126 if (!ctx.cfg.virtual_root && (!strcmp(value, "/")))
125 ctx.cfg.virtual_root = ""; 127 ctx.cfg.virtual_root = "";
126 } else if (!strcmp(name, "nocache")) 128 } else if (!strcmp(name, "nocache"))
127 ctx.cfg.nocache = atoi(value); 129 ctx.cfg.nocache = atoi(value);
128 else if (!strcmp(name, "noplainemail")) 130 else if (!strcmp(name, "noplainemail"))
129 ctx.cfg.noplainemail = atoi(value); 131 ctx.cfg.noplainemail = atoi(value);
130 else if (!strcmp(name, "noheader")) 132 else if (!strcmp(name, "noheader"))
131 ctx.cfg.noheader = atoi(value); 133 ctx.cfg.noheader = atoi(value);
132 else if (!strcmp(name, "snapshots")) 134 else if (!strcmp(name, "snapshots"))
133 ctx.cfg.snapshots = cgit_parse_snapshots_mask(value); 135 ctx.cfg.snapshots = cgit_parse_snapshots_mask(value);
134 else if (!strcmp(name, "enable-filter-overrides")) 136 else if (!strcmp(name, "enable-filter-overrides"))
135 ctx.cfg.enable_filter_overrides = atoi(value); 137 ctx.cfg.enable_filter_overrides = atoi(value);
136 else if (!strcmp(name, "enable-index-links")) 138 else if (!strcmp(name, "enable-index-links"))
137 ctx.cfg.enable_index_links = atoi(value); 139 ctx.cfg.enable_index_links = atoi(value);
138 else if (!strcmp(name, "enable-log-filecount")) 140 else if (!strcmp(name, "enable-log-filecount"))
139 ctx.cfg.enable_log_filecount = atoi(value); 141 ctx.cfg.enable_log_filecount = atoi(value);
140 else if (!strcmp(name, "enable-log-linecount")) 142 else if (!strcmp(name, "enable-log-linecount"))
141 ctx.cfg.enable_log_linecount = atoi(value); 143 ctx.cfg.enable_log_linecount = atoi(value);
142 else if (!strcmp(name, "enable-remote-branches")) 144 else if (!strcmp(name, "enable-remote-branches"))
143 ctx.cfg.enable_remote_branches = atoi(value); 145 ctx.cfg.enable_remote_branches = atoi(value);
146 else if (!strcmp(name, "enable-subject-links"))
147 ctx.cfg.enable_subject_links = atoi(value);
144 else if (!strcmp(name, "enable-tree-linenumbers")) 148 else if (!strcmp(name, "enable-tree-linenumbers"))
145 ctx.cfg.enable_tree_linenumbers = atoi(value); 149 ctx.cfg.enable_tree_linenumbers = atoi(value);
146 else if (!strcmp(name, "max-stats")) 150 else if (!strcmp(name, "max-stats"))
147 ctx.cfg.max_stats = cgit_find_stats_period(value, NULL); 151 ctx.cfg.max_stats = cgit_find_stats_period(value, NULL);
148 else if (!strcmp(name, "cache-size")) 152 else if (!strcmp(name, "cache-size"))
149 ctx.cfg.cache_size = atoi(value); 153 ctx.cfg.cache_size = atoi(value);
150 else if (!strcmp(name, "cache-root")) 154 else if (!strcmp(name, "cache-root"))
151 ctx.cfg.cache_root = xstrdup(value); 155 ctx.cfg.cache_root = xstrdup(expand_macros(value));
152 else if (!strcmp(name, "cache-root-ttl")) 156 else if (!strcmp(name, "cache-root-ttl"))
153 ctx.cfg.cache_root_ttl = atoi(value); 157 ctx.cfg.cache_root_ttl = atoi(value);
154 else if (!strcmp(name, "cache-repo-ttl")) 158 else if (!strcmp(name, "cache-repo-ttl"))
155 ctx.cfg.cache_repo_ttl = atoi(value); 159 ctx.cfg.cache_repo_ttl = atoi(value);
156 else if (!strcmp(name, "cache-scanrc-ttl")) 160 else if (!strcmp(name, "cache-scanrc-ttl"))
157 ctx.cfg.cache_scanrc_ttl = atoi(value); 161 ctx.cfg.cache_scanrc_ttl = atoi(value);
158 else if (!strcmp(name, "cache-static-ttl")) 162 else if (!strcmp(name, "cache-static-ttl"))
159 ctx.cfg.cache_static_ttl = atoi(value); 163 ctx.cfg.cache_static_ttl = atoi(value);
160 else if (!strcmp(name, "cache-dynamic-ttl")) 164 else if (!strcmp(name, "cache-dynamic-ttl"))
161 ctx.cfg.cache_dynamic_ttl = atoi(value); 165 ctx.cfg.cache_dynamic_ttl = atoi(value);
162 else if (!strcmp(name, "about-filter")) 166 else if (!strcmp(name, "about-filter"))
163 ctx.cfg.about_filter = new_filter(value, 0); 167 ctx.cfg.about_filter = new_filter(value, 0);
164 else if (!strcmp(name, "commit-filter")) 168 else if (!strcmp(name, "commit-filter"))
165 ctx.cfg.commit_filter = new_filter(value, 0); 169 ctx.cfg.commit_filter = new_filter(value, 0);
166 else if (!strcmp(name, "embedded")) 170 else if (!strcmp(name, "embedded"))
167 ctx.cfg.embedded = atoi(value); 171 ctx.cfg.embedded = atoi(value);
172 else if (!strcmp(name, "max-atom-items"))
173 ctx.cfg.max_atom_items = atoi(value);
168 else if (!strcmp(name, "max-message-length")) 174 else if (!strcmp(name, "max-message-length"))
169 ctx.cfg.max_msg_len = atoi(value); 175 ctx.cfg.max_msg_len = atoi(value);
170 else if (!strcmp(name, "max-repodesc-length")) 176 else if (!strcmp(name, "max-repodesc-length"))
171 ctx.cfg.max_repodesc_len = atoi(value); 177 ctx.cfg.max_repodesc_len = atoi(value);
172 else if (!strcmp(name, "max-blob-size")) 178 else if (!strcmp(name, "max-blob-size"))
173 ctx.cfg.max_blob_size = atoi(value); 179 ctx.cfg.max_blob_size = atoi(value);
174 else if (!strcmp(name, "max-repo-count")) 180 else if (!strcmp(name, "max-repo-count"))
175 ctx.cfg.max_repo_count = atoi(value); 181 ctx.cfg.max_repo_count = atoi(value);
176 else if (!strcmp(name, "max-commit-count")) 182 else if (!strcmp(name, "max-commit-count"))
177 ctx.cfg.max_commit_count = atoi(value); 183 ctx.cfg.max_commit_count = atoi(value);
178 else if (!strcmp(name, "scan-path")) 184 else if (!strcmp(name, "scan-path"))
179 if (!ctx.cfg.nocache && ctx.cfg.cache_size) 185 if (!ctx.cfg.nocache && ctx.cfg.cache_size)
180 process_cached_repolist(value); 186 process_cached_repolist(expand_macros(value));
181 else 187 else
182 scan_tree(value, repo_config); 188 scan_tree(expand_macros(value), repo_config);
183 else if (!strcmp(name, "source-filter")) 189 else if (!strcmp(name, "source-filter"))
184 ctx.cfg.source_filter = new_filter(value, 1); 190 ctx.cfg.source_filter = new_filter(value, 1);
185 else if (!strcmp(name, "summary-log")) 191 else if (!strcmp(name, "summary-log"))
186 ctx.cfg.summary_log = atoi(value); 192 ctx.cfg.summary_log = atoi(value);
187 else if (!strcmp(name, "summary-branches")) 193 else if (!strcmp(name, "summary-branches"))
188 ctx.cfg.summary_branches = atoi(value); 194 ctx.cfg.summary_branches = atoi(value);
189 else if (!strcmp(name, "summary-tags")) 195 else if (!strcmp(name, "summary-tags"))
190 ctx.cfg.summary_tags = atoi(value); 196 ctx.cfg.summary_tags = atoi(value);
191 else if (!strcmp(name, "side-by-side-diffs")) 197 else if (!strcmp(name, "side-by-side-diffs"))
192 ctx.cfg.ssdiff = atoi(value); 198 ctx.cfg.ssdiff = atoi(value);
193 else if (!strcmp(name, "agefile")) 199 else if (!strcmp(name, "agefile"))
194 ctx.cfg.agefile = xstrdup(value); 200 ctx.cfg.agefile = xstrdup(value);
195 else if (!strcmp(name, "renamelimit")) 201 else if (!strcmp(name, "renamelimit"))
196 ctx.cfg.renamelimit = atoi(value); 202 ctx.cfg.renamelimit = atoi(value);
197 else if (!strcmp(name, "robots")) 203 else if (!strcmp(name, "robots"))
198 ctx.cfg.robots = xstrdup(value); 204 ctx.cfg.robots = xstrdup(value);
199 else if (!strcmp(name, "clone-prefix")) 205 else if (!strcmp(name, "clone-prefix"))
200 ctx.cfg.clone_prefix = xstrdup(value); 206 ctx.cfg.clone_prefix = xstrdup(value);
201 else if (!strcmp(name, "local-time")) 207 else if (!strcmp(name, "local-time"))
202 ctx.cfg.local_time = atoi(value); 208 ctx.cfg.local_time = atoi(value);
203 else if (!prefixcmp(name, "mimetype.")) 209 else if (!prefixcmp(name, "mimetype."))
204 add_mimetype(name + 9, value); 210 add_mimetype(name + 9, value);
205 else if (!strcmp(name, "include")) 211 else if (!strcmp(name, "include"))
206 parse_configfile(value, config_cb); 212 parse_configfile(expand_macros(value), config_cb);
207} 213}
208 214
209static void querystring_cb(const char *name, const char *value) 215static void querystring_cb(const char *name, const char *value)
210{ 216{
211 if (!value) 217 if (!value)
212 value = ""; 218 value = "";
213 219
214 if (!strcmp(name,"r")) { 220 if (!strcmp(name,"r")) {
215 ctx.qry.repo = xstrdup(value); 221 ctx.qry.repo = xstrdup(value);
216 ctx.repo = cgit_get_repoinfo(value); 222 ctx.repo = cgit_get_repoinfo(value);
217 } else if (!strcmp(name, "p")) { 223 } else if (!strcmp(name, "p")) {
218 ctx.qry.page = xstrdup(value); 224 ctx.qry.page = xstrdup(value);
219 } else if (!strcmp(name, "url")) { 225 } else if (!strcmp(name, "url")) {
220 if (*value == '/') 226 if (*value == '/')
221 value++; 227 value++;
222 ctx.qry.url = xstrdup(value); 228 ctx.qry.url = xstrdup(value);
223 cgit_parse_url(value); 229 cgit_parse_url(value);
224 } else if (!strcmp(name, "qt")) { 230 } else if (!strcmp(name, "qt")) {
225 ctx.qry.grep = xstrdup(value); 231 ctx.qry.grep = xstrdup(value);
226 } else if (!strcmp(name, "q")) { 232 } else if (!strcmp(name, "q")) {
227 ctx.qry.search = xstrdup(value); 233 ctx.qry.search = xstrdup(value);
228 } else if (!strcmp(name, "h")) { 234 } else if (!strcmp(name, "h")) {
229 ctx.qry.head = xstrdup(value); 235 ctx.qry.head = xstrdup(value);
230 ctx.qry.has_symref = 1; 236 ctx.qry.has_symref = 1;
231 } else if (!strcmp(name, "id")) { 237 } else if (!strcmp(name, "id")) {
232 ctx.qry.sha1 = xstrdup(value); 238 ctx.qry.sha1 = xstrdup(value);
233 ctx.qry.has_sha1 = 1; 239 ctx.qry.has_sha1 = 1;
234 } else if (!strcmp(name, "id2")) { 240 } else if (!strcmp(name, "id2")) {
235 ctx.qry.sha2 = xstrdup(value); 241 ctx.qry.sha2 = xstrdup(value);
236 ctx.qry.has_sha1 = 1; 242 ctx.qry.has_sha1 = 1;
237 } else if (!strcmp(name, "ofs")) { 243 } else if (!strcmp(name, "ofs")) {
238 ctx.qry.ofs = atoi(value); 244 ctx.qry.ofs = atoi(value);
239 } else if (!strcmp(name, "path")) { 245 } else if (!strcmp(name, "path")) {
240 ctx.qry.path = trim_end(value, '/'); 246 ctx.qry.path = trim_end(value, '/');
241 } else if (!strcmp(name, "name")) { 247 } else if (!strcmp(name, "name")) {
242 ctx.qry.name = xstrdup(value); 248 ctx.qry.name = xstrdup(value);
243 } else if (!strcmp(name, "mimetype")) { 249 } else if (!strcmp(name, "mimetype")) {
244 ctx.qry.mimetype = xstrdup(value); 250 ctx.qry.mimetype = xstrdup(value);
245 } else if (!strcmp(name, "s")){ 251 } else if (!strcmp(name, "s")){
246 ctx.qry.sort = xstrdup(value); 252 ctx.qry.sort = xstrdup(value);
247 } else if (!strcmp(name, "showmsg")) { 253 } else if (!strcmp(name, "showmsg")) {
248 ctx.qry.showmsg = atoi(value); 254 ctx.qry.showmsg = atoi(value);
249 } else if (!strcmp(name, "period")) { 255 } else if (!strcmp(name, "period")) {
250 ctx.qry.period = xstrdup(value); 256 ctx.qry.period = xstrdup(value);
251 } else if (!strcmp(name, "ss")) { 257 } else if (!strcmp(name, "ss")) {
252 ctx.qry.ssdiff = atoi(value); 258 ctx.qry.ssdiff = atoi(value);
259 } else if (!strcmp(name, "all")) {
260 ctx.qry.show_all = atoi(value);
253 } else if (!strcmp(name, "context")) { 261 } else if (!strcmp(name, "context")) {
254 ctx.qry.context = atoi(value); 262 ctx.qry.context = atoi(value);
255 } else if (!strcmp(name, "ignorews")) { 263 } else if (!strcmp(name, "ignorews")) {
256 ctx.qry.ignorews = atoi(value); 264 ctx.qry.ignorews = atoi(value);
257 } 265 }
258} 266}
259 267
260char *xstrdupn(const char *str) 268char *xstrdupn(const char *str)
261{ 269{
262 return (str ? xstrdup(str) : NULL); 270 return (str ? xstrdup(str) : NULL);
263} 271}
264 272
265static void prepare_context(struct cgit_context *ctx) 273static void prepare_context(struct cgit_context *ctx)
266{ 274{
267 memset(ctx, 0, sizeof(*ctx)); 275 memset(ctx, 0, sizeof(*ctx));
268 ctx->cfg.agefile = "info/web/last-modified"; 276 ctx->cfg.agefile = "info/web/last-modified";
269 ctx->cfg.nocache = 0; 277 ctx->cfg.nocache = 0;
270 ctx->cfg.cache_size = 0; 278 ctx->cfg.cache_size = 0;
271 ctx->cfg.cache_dynamic_ttl = 5; 279 ctx->cfg.cache_dynamic_ttl = 5;
272 ctx->cfg.cache_max_create_time = 5; 280 ctx->cfg.cache_max_create_time = 5;
273 ctx->cfg.cache_repo_ttl = 5; 281 ctx->cfg.cache_repo_ttl = 5;
274 ctx->cfg.cache_root = CGIT_CACHE_ROOT; 282 ctx->cfg.cache_root = CGIT_CACHE_ROOT;
275 ctx->cfg.cache_root_ttl = 5; 283 ctx->cfg.cache_root_ttl = 5;
276 ctx->cfg.cache_scanrc_ttl = 15; 284 ctx->cfg.cache_scanrc_ttl = 15;
277 ctx->cfg.cache_static_ttl = -1; 285 ctx->cfg.cache_static_ttl = -1;
278 ctx->cfg.css = "/cgit.css"; 286 ctx->cfg.css = "/cgit.css";
279 ctx->cfg.logo = "/cgit.png"; 287 ctx->cfg.logo = "/cgit.png";
280 ctx->cfg.local_time = 0; 288 ctx->cfg.local_time = 0;
281 ctx->cfg.enable_tree_linenumbers = 1; 289 ctx->cfg.enable_tree_linenumbers = 1;
282 ctx->cfg.max_repo_count = 50; 290 ctx->cfg.max_repo_count = 50;
283 ctx->cfg.max_commit_count = 50; 291 ctx->cfg.max_commit_count = 50;
284 ctx->cfg.max_lock_attempts = 5; 292 ctx->cfg.max_lock_attempts = 5;
285 ctx->cfg.max_msg_len = 80; 293 ctx->cfg.max_msg_len = 80;
286 ctx->cfg.max_repodesc_len = 80; 294 ctx->cfg.max_repodesc_len = 80;
287 ctx->cfg.max_blob_size = 0; 295 ctx->cfg.max_blob_size = 0;
288 ctx->cfg.max_stats = 0; 296 ctx->cfg.max_stats = 0;
289 ctx->cfg.module_link = "./?repo=%s&page=commit&id=%s"; 297 ctx->cfg.module_link = "./?repo=%s&page=commit&id=%s";
290 ctx->cfg.renamelimit = -1; 298 ctx->cfg.renamelimit = -1;
291 ctx->cfg.robots = "index, nofollow"; 299 ctx->cfg.robots = "index, nofollow";
292 ctx->cfg.root_title = "Git repository browser"; 300 ctx->cfg.root_title = "Git repository browser";
293 ctx->cfg.root_desc = "a fast webinterface for the git dscm"; 301 ctx->cfg.root_desc = "a fast webinterface for the git dscm";
294 ctx->cfg.script_name = CGIT_SCRIPT_NAME; 302 ctx->cfg.script_name = CGIT_SCRIPT_NAME;
295 ctx->cfg.section = ""; 303 ctx->cfg.section = "";
296 ctx->cfg.summary_branches = 10; 304 ctx->cfg.summary_branches = 10;
297 ctx->cfg.summary_log = 10; 305 ctx->cfg.summary_log = 10;
298 ctx->cfg.summary_tags = 10; 306 ctx->cfg.summary_tags = 10;
307 ctx->cfg.max_atom_items = 10;
299 ctx->cfg.ssdiff = 0; 308 ctx->cfg.ssdiff = 0;
300 ctx->env.cgit_config = xstrdupn(getenv("CGIT_CONFIG")); 309 ctx->env.cgit_config = xstrdupn(getenv("CGIT_CONFIG"));
301 ctx->env.http_host = xstrdupn(getenv("HTTP_HOST")); 310 ctx->env.http_host = xstrdupn(getenv("HTTP_HOST"));
302 ctx->env.https = xstrdupn(getenv("HTTPS")); 311 ctx->env.https = xstrdupn(getenv("HTTPS"));
303 ctx->env.no_http = xstrdupn(getenv("NO_HTTP")); 312 ctx->env.no_http = xstrdupn(getenv("NO_HTTP"));
304 ctx->env.path_info = xstrdupn(getenv("PATH_INFO")); 313 ctx->env.path_info = xstrdupn(getenv("PATH_INFO"));
305 ctx->env.query_string = xstrdupn(getenv("QUERY_STRING")); 314 ctx->env.query_string = xstrdupn(getenv("QUERY_STRING"));
306 ctx->env.request_method = xstrdupn(getenv("REQUEST_METHOD")); 315 ctx->env.request_method = xstrdupn(getenv("REQUEST_METHOD"));
307 ctx->env.script_name = xstrdupn(getenv("SCRIPT_NAME")); 316 ctx->env.script_name = xstrdupn(getenv("SCRIPT_NAME"));
308 ctx->env.server_name = xstrdupn(getenv("SERVER_NAME")); 317 ctx->env.server_name = xstrdupn(getenv("SERVER_NAME"));
309 ctx->env.server_port = xstrdupn(getenv("SERVER_PORT")); 318 ctx->env.server_port = xstrdupn(getenv("SERVER_PORT"));
310 ctx->page.mimetype = "text/html"; 319 ctx->page.mimetype = "text/html";
311 ctx->page.charset = PAGE_ENCODING; 320 ctx->page.charset = PAGE_ENCODING;
312 ctx->page.filename = NULL; 321 ctx->page.filename = NULL;
313 ctx->page.size = 0; 322 ctx->page.size = 0;
314 ctx->page.modified = time(NULL); 323 ctx->page.modified = time(NULL);
315 ctx->page.expires = ctx->page.modified; 324 ctx->page.expires = ctx->page.modified;
316 ctx->page.etag = NULL; 325 ctx->page.etag = NULL;
317 memset(&ctx->cfg.mimetypes, 0, sizeof(struct string_list)); 326 memset(&ctx->cfg.mimetypes, 0, sizeof(struct string_list));
318 if (ctx->env.script_name) 327 if (ctx->env.script_name)
319 ctx->cfg.script_name = ctx->env.script_name; 328 ctx->cfg.script_name = ctx->env.script_name;
320 if (ctx->env.query_string) 329 if (ctx->env.query_string)
321 ctx->qry.raw = ctx->env.query_string; 330 ctx->qry.raw = ctx->env.query_string;
322 if (!ctx->env.cgit_config) 331 if (!ctx->env.cgit_config)
@@ -677,49 +686,49 @@ static int calc_ttl()
677 if (!ctx.qry.page) 686 if (!ctx.qry.page)
678 return ctx.cfg.cache_repo_ttl; 687 return ctx.cfg.cache_repo_ttl;
679 688
680 if (ctx.qry.has_symref) 689 if (ctx.qry.has_symref)
681 return ctx.cfg.cache_dynamic_ttl; 690 return ctx.cfg.cache_dynamic_ttl;
682 691
683 if (ctx.qry.has_sha1) 692 if (ctx.qry.has_sha1)
684 return ctx.cfg.cache_static_ttl; 693 return ctx.cfg.cache_static_ttl;
685 694
686 return ctx.cfg.cache_repo_ttl; 695 return ctx.cfg.cache_repo_ttl;
687} 696}
688 697
689int main(int argc, const char **argv) 698int main(int argc, const char **argv)
690{ 699{
691 const char *path; 700 const char *path;
692 char *qry; 701 char *qry;
693 int err, ttl; 702 int err, ttl;
694 703
695 prepare_context(&ctx); 704 prepare_context(&ctx);
696 cgit_repolist.length = 0; 705 cgit_repolist.length = 0;
697 cgit_repolist.count = 0; 706 cgit_repolist.count = 0;
698 cgit_repolist.repos = NULL; 707 cgit_repolist.repos = NULL;
699 708
700 cgit_parse_args(argc, argv); 709 cgit_parse_args(argc, argv);
701 parse_configfile(ctx.env.cgit_config, config_cb); 710 parse_configfile(expand_macros(ctx.env.cgit_config), config_cb);
702 ctx.repo = NULL; 711 ctx.repo = NULL;
703 http_parse_querystring(ctx.qry.raw, querystring_cb); 712 http_parse_querystring(ctx.qry.raw, querystring_cb);
704 713
705 /* If virtual-root isn't specified in cgitrc, lets pretend 714 /* If virtual-root isn't specified in cgitrc, lets pretend
706 * that virtual-root equals SCRIPT_NAME. 715 * that virtual-root equals SCRIPT_NAME.
707 */ 716 */
708 if (!ctx.cfg.virtual_root) 717 if (!ctx.cfg.virtual_root)
709 ctx.cfg.virtual_root = ctx.cfg.script_name; 718 ctx.cfg.virtual_root = ctx.cfg.script_name;
710 719
711 /* If no url parameter is specified on the querystring, lets 720 /* If no url parameter is specified on the querystring, lets
712 * use PATH_INFO as url. This allows cgit to work with virtual 721 * use PATH_INFO as url. This allows cgit to work with virtual
713 * urls without the need for rewriterules in the webserver (as 722 * urls without the need for rewriterules in the webserver (as
714 * long as PATH_INFO is included in the cache lookup key). 723 * long as PATH_INFO is included in the cache lookup key).
715 */ 724 */
716 path = ctx.env.path_info; 725 path = ctx.env.path_info;
717 if (!ctx.qry.url && path) { 726 if (!ctx.qry.url && path) {
718 if (path[0] == '/') 727 if (path[0] == '/')
719 path++; 728 path++;
720 ctx.qry.url = xstrdup(path); 729 ctx.qry.url = xstrdup(path);
721 if (ctx.qry.raw) { 730 if (ctx.qry.raw) {
722 qry = ctx.qry.raw; 731 qry = ctx.qry.raw;
723 ctx.qry.raw = xstrdup(fmt("%s?%s", path, qry)); 732 ctx.qry.raw = xstrdup(fmt("%s?%s", path, qry));
724 free(qry); 733 free(qry);
725 } else 734 } else
diff --git a/cgit.css b/cgit.css
index 28a2eeb..6e47eb3 100644
--- a/cgit.css
+++ b/cgit.css
@@ -510,49 +510,52 @@ a.branch-deco {
510 margin: 0px 0.5em; 510 margin: 0px 0.5em;
511 padding: 0px 0.25em; 511 padding: 0px 0.25em;
512 background-color: #88ff88; 512 background-color: #88ff88;
513 border: solid 1px #007700; 513 border: solid 1px #007700;
514} 514}
515a.tag-deco { 515a.tag-deco {
516 margin: 0px 0.5em; 516 margin: 0px 0.5em;
517 padding: 0px 0.25em; 517 padding: 0px 0.25em;
518 background-color: #ffff88; 518 background-color: #ffff88;
519 border: solid 1px #777700; 519 border: solid 1px #777700;
520} 520}
521a.remote-deco { 521a.remote-deco {
522 margin: 0px 0.5em; 522 margin: 0px 0.5em;
523 padding: 0px 0.25em; 523 padding: 0px 0.25em;
524 background-color: #ccccff; 524 background-color: #ccccff;
525 border: solid 1px #000077; 525 border: solid 1px #000077;
526} 526}
527a.deco { 527a.deco {
528 margin: 0px 0.5em; 528 margin: 0px 0.5em;
529 padding: 0px 0.25em; 529 padding: 0px 0.25em;
530 background-color: #ff8888; 530 background-color: #ff8888;
531 border: solid 1px #770000; 531 border: solid 1px #770000;
532} 532}
533 533
534div.commit-subject a { 534div.commit-subject a.branch-deco,
535div.commit-subject a.tag-deco,
536div.commit-subject a.remote-deco,
537div.commit-subject a.deco {
535 margin-left: 1em; 538 margin-left: 1em;
536 font-size: 75%; 539 font-size: 75%;
537} 540}
538 541
539table.stats { 542table.stats {
540 border: solid 1px black; 543 border: solid 1px black;
541 border-collapse: collapse; 544 border-collapse: collapse;
542} 545}
543 546
544table.stats th { 547table.stats th {
545 text-align: left; 548 text-align: left;
546 padding: 1px 0.5em; 549 padding: 1px 0.5em;
547 background-color: #eee; 550 background-color: #eee;
548 border: solid 1px black; 551 border: solid 1px black;
549} 552}
550 553
551table.stats td { 554table.stats td {
552 text-align: right; 555 text-align: right;
553 padding: 1px 0.5em; 556 padding: 1px 0.5em;
554 border: solid 1px black; 557 border: solid 1px black;
555} 558}
556 559
557table.stats td.total { 560table.stats td.total {
558 font-weight: bold; 561 font-weight: bold;
diff --git a/cgit.h b/cgit.h
index 1bdfbc6..e9e2718 100644
--- a/cgit.h
+++ b/cgit.h
@@ -52,48 +52,49 @@ typedef void (*linediff_fn)(char *line, int len);
52struct cgit_filter { 52struct cgit_filter {
53 char *cmd; 53 char *cmd;
54 char **argv; 54 char **argv;
55 int old_stdout; 55 int old_stdout;
56 int pipe_fh[2]; 56 int pipe_fh[2];
57 int pid; 57 int pid;
58 int exitstatus; 58 int exitstatus;
59}; 59};
60 60
61struct cgit_repo { 61struct cgit_repo {
62 char *url; 62 char *url;
63 char *name; 63 char *name;
64 char *path; 64 char *path;
65 char *desc; 65 char *desc;
66 char *owner; 66 char *owner;
67 char *defbranch; 67 char *defbranch;
68 char *module_link; 68 char *module_link;
69 char *readme; 69 char *readme;
70 char *section; 70 char *section;
71 char *clone_url; 71 char *clone_url;
72 int snapshots; 72 int snapshots;
73 int enable_log_filecount; 73 int enable_log_filecount;
74 int enable_log_linecount; 74 int enable_log_linecount;
75 int enable_remote_branches; 75 int enable_remote_branches;
76 int enable_subject_links;
76 int max_stats; 77 int max_stats;
77 time_t mtime; 78 time_t mtime;
78 struct cgit_filter *about_filter; 79 struct cgit_filter *about_filter;
79 struct cgit_filter *commit_filter; 80 struct cgit_filter *commit_filter;
80 struct cgit_filter *source_filter; 81 struct cgit_filter *source_filter;
81}; 82};
82 83
83typedef void (*repo_config_fn)(struct cgit_repo *repo, const char *name, 84typedef void (*repo_config_fn)(struct cgit_repo *repo, const char *name,
84 const char *value); 85 const char *value);
85 86
86struct cgit_repolist { 87struct cgit_repolist {
87 int length; 88 int length;
88 int count; 89 int count;
89 struct cgit_repo *repos; 90 struct cgit_repo *repos;
90}; 91};
91 92
92struct commitinfo { 93struct commitinfo {
93 struct commit *commit; 94 struct commit *commit;
94 char *author; 95 char *author;
95 char *author_email; 96 char *author_email;
96 unsigned long author_date; 97 unsigned long author_date;
97 char *committer; 98 char *committer;
98 char *committer_email; 99 char *committer_email;
99 unsigned long committer_date; 100 unsigned long committer_date;
@@ -124,89 +125,92 @@ struct reflist {
124 int count; 125 int count;
125}; 126};
126 127
127struct cgit_query { 128struct cgit_query {
128 int has_symref; 129 int has_symref;
129 int has_sha1; 130 int has_sha1;
130 char *raw; 131 char *raw;
131 char *repo; 132 char *repo;
132 char *page; 133 char *page;
133 char *search; 134 char *search;
134 char *grep; 135 char *grep;
135 char *head; 136 char *head;
136 char *sha1; 137 char *sha1;
137 char *sha2; 138 char *sha2;
138 char *path; 139 char *path;
139 char *name; 140 char *name;
140 char *mimetype; 141 char *mimetype;
141 char *url; 142 char *url;
142 char *period; 143 char *period;
143 int ofs; 144 int ofs;
144 int nohead; 145 int nohead;
145 char *sort; 146 char *sort;
146 int showmsg; 147 int showmsg;
147 int ssdiff; 148 int ssdiff;
149 int show_all;
148 int context; 150 int context;
149 int ignorews; 151 int ignorews;
150 char *vpath; 152 char *vpath;
151}; 153};
152 154
153struct cgit_config { 155struct cgit_config {
154 char *agefile; 156 char *agefile;
155 char *cache_root; 157 char *cache_root;
156 char *clone_prefix; 158 char *clone_prefix;
157 char *css; 159 char *css;
158 char *favicon; 160 char *favicon;
159 char *footer; 161 char *footer;
160 char *head_include; 162 char *head_include;
161 char *header; 163 char *header;
162 char *index_header; 164 char *index_header;
163 char *index_info; 165 char *index_info;
164 char *logo; 166 char *logo;
165 char *logo_link; 167 char *logo_link;
166 char *module_link; 168 char *module_link;
167 char *robots; 169 char *robots;
168 char *root_title; 170 char *root_title;
169 char *root_desc; 171 char *root_desc;
170 char *root_readme; 172 char *root_readme;
171 char *script_name; 173 char *script_name;
172 char *section; 174 char *section;
173 char *virtual_root; 175 char *virtual_root;
174 int cache_size; 176 int cache_size;
175 int cache_dynamic_ttl; 177 int cache_dynamic_ttl;
176 int cache_max_create_time; 178 int cache_max_create_time;
177 int cache_repo_ttl; 179 int cache_repo_ttl;
178 int cache_root_ttl; 180 int cache_root_ttl;
179 int cache_scanrc_ttl; 181 int cache_scanrc_ttl;
180 int cache_static_ttl; 182 int cache_static_ttl;
181 int embedded; 183 int embedded;
182 int enable_filter_overrides; 184 int enable_filter_overrides;
183 int enable_index_links; 185 int enable_index_links;
184 int enable_log_filecount; 186 int enable_log_filecount;
185 int enable_log_linecount; 187 int enable_log_linecount;
186 int enable_remote_branches; 188 int enable_remote_branches;
189 int enable_subject_links;
187 int enable_tree_linenumbers; 190 int enable_tree_linenumbers;
188 int local_time; 191 int local_time;
192 int max_atom_items;
189 int max_repo_count; 193 int max_repo_count;
190 int max_commit_count; 194 int max_commit_count;
191 int max_lock_attempts; 195 int max_lock_attempts;
192 int max_msg_len; 196 int max_msg_len;
193 int max_repodesc_len; 197 int max_repodesc_len;
194 int max_blob_size; 198 int max_blob_size;
195 int max_stats; 199 int max_stats;
196 int nocache; 200 int nocache;
197 int noplainemail; 201 int noplainemail;
198 int noheader; 202 int noheader;
199 int renamelimit; 203 int renamelimit;
200 int snapshots; 204 int snapshots;
201 int summary_branches; 205 int summary_branches;
202 int summary_log; 206 int summary_log;
203 int summary_tags; 207 int summary_tags;
204 int ssdiff; 208 int ssdiff;
205 struct string_list mimetypes; 209 struct string_list mimetypes;
206 struct cgit_filter *about_filter; 210 struct cgit_filter *about_filter;
207 struct cgit_filter *commit_filter; 211 struct cgit_filter *commit_filter;
208 struct cgit_filter *source_filter; 212 struct cgit_filter *source_filter;
209}; 213};
210 214
211struct cgit_page { 215struct cgit_page {
212 time_t modified; 216 time_t modified;
@@ -279,25 +283,27 @@ extern int cgit_diff_files(const unsigned char *old_sha1,
279 int *binary, int context, int ignorews, 283 int *binary, int context, int ignorews,
280 linediff_fn fn); 284 linediff_fn fn);
281 285
282extern void cgit_diff_tree(const unsigned char *old_sha1, 286extern void cgit_diff_tree(const unsigned char *old_sha1,
283 const unsigned char *new_sha1, 287 const unsigned char *new_sha1,
284 filepair_fn fn, const char *prefix, int ignorews); 288 filepair_fn fn, const char *prefix, int ignorews);
285 289
286extern void cgit_diff_commit(struct commit *commit, filepair_fn fn); 290extern void cgit_diff_commit(struct commit *commit, filepair_fn fn);
287 291
288extern char *fmt(const char *format,...); 292extern char *fmt(const char *format,...);
289 293
290extern struct commitinfo *cgit_parse_commit(struct commit *commit); 294extern struct commitinfo *cgit_parse_commit(struct commit *commit);
291extern struct taginfo *cgit_parse_tag(struct tag *tag); 295extern struct taginfo *cgit_parse_tag(struct tag *tag);
292extern void cgit_parse_url(const char *url); 296extern void cgit_parse_url(const char *url);
293 297
294extern const char *cgit_repobasename(const char *reponame); 298extern const char *cgit_repobasename(const char *reponame);
295 299
296extern int cgit_parse_snapshots_mask(const char *str); 300extern int cgit_parse_snapshots_mask(const char *str);
297 301
298extern int cgit_open_filter(struct cgit_filter *filter); 302extern int cgit_open_filter(struct cgit_filter *filter);
299extern int cgit_close_filter(struct cgit_filter *filter); 303extern int cgit_close_filter(struct cgit_filter *filter);
300 304
301extern int readfile(const char *path, char **buf, size_t *size); 305extern int readfile(const char *path, char **buf, size_t *size);
302 306
307extern char *expand_macros(const char *txt);
308
303#endif /* CGIT_H */ 309#endif /* CGIT_H */
diff --git a/cgitrc.5.txt b/cgitrc.5.txt
index 1f7ac1e..a853522 100644
--- a/cgitrc.5.txt
+++ b/cgitrc.5.txt
@@ -94,48 +94,54 @@ embedded::
94enable-filter-overrides:: 94enable-filter-overrides::
95 Flag which, when set to "1", allows all filter settings to be 95 Flag which, when set to "1", allows all filter settings to be
96 overridden in repository-specific cgitrc files. Default value: none. 96 overridden in repository-specific cgitrc files. Default value: none.
97 97
98enable-index-links:: 98enable-index-links::
99 Flag which, when set to "1", will make cgit generate extra links for 99 Flag which, when set to "1", will make cgit generate extra links for
100 each repo in the repository index (specifically, to the "summary", 100 each repo in the repository index (specifically, to the "summary",
101 "commit" and "tree" pages). Default value: "0". 101 "commit" and "tree" pages). Default value: "0".
102 102
103enable-log-filecount:: 103enable-log-filecount::
104 Flag which, when set to "1", will make cgit print the number of 104 Flag which, when set to "1", will make cgit print the number of
105 modified files for each commit on the repository log page. Default 105 modified files for each commit on the repository log page. Default
106 value: "0". 106 value: "0".
107 107
108enable-log-linecount:: 108enable-log-linecount::
109 Flag which, when set to "1", will make cgit print the number of added 109 Flag which, when set to "1", will make cgit print the number of added
110 and removed lines for each commit on the repository log page. Default 110 and removed lines for each commit on the repository log page. Default
111 value: "0". 111 value: "0".
112 112
113enable-remote-branches:: 113enable-remote-branches::
114 Flag which, when set to "1", will make cgit display remote branches 114 Flag which, when set to "1", will make cgit display remote branches
115 in the summary and refs views. Default value: "0". See also: 115 in the summary and refs views. Default value: "0". See also:
116 "repo.enable-remote-branches". 116 "repo.enable-remote-branches".
117 117
118enable-subject-links::
119 Flag which, when set to "1", will make cgit use the subject of the
120 parent commit as link text when generating links to parent commits
121 in commit view. Default value: "0". See also:
122 "repo.enable-subject-links".
123
118enable-tree-linenumbers:: 124enable-tree-linenumbers::
119 Flag which, when set to "1", will make cgit generate linenumber links 125 Flag which, when set to "1", will make cgit generate linenumber links
120 for plaintext blobs printed in the tree view. Default value: "1". 126 for plaintext blobs printed in the tree view. Default value: "1".
121 127
122favicon:: 128favicon::
123 Url used as link to a shortcut icon for cgit. If specified, it is 129 Url used as link to a shortcut icon for cgit. If specified, it is
124 suggested to use the value "/favicon.ico" since certain browsers will 130 suggested to use the value "/favicon.ico" since certain browsers will
125 ignore other values. Default value: none. 131 ignore other values. Default value: none.
126 132
127footer:: 133footer::
128 The content of the file specified with this option will be included 134 The content of the file specified with this option will be included
129 verbatim at the bottom of all pages (i.e. it replaces the standard 135 verbatim at the bottom of all pages (i.e. it replaces the standard
130 "generated by..." message. Default value: none. 136 "generated by..." message. Default value: none.
131 137
132head-include:: 138head-include::
133 The content of the file specified with this option will be included 139 The content of the file specified with this option will be included
134 verbatim in the html HEAD section on all pages. Default value: none. 140 verbatim in the html HEAD section on all pages. Default value: none.
135 141
136header:: 142header::
137 The content of the file specified with this option will be included 143 The content of the file specified with this option will be included
138 verbatim at the top of all pages. Default value: none. 144 verbatim at the top of all pages. Default value: none.
139 145
140include:: 146include::
141 Name of a configfile to include before the rest of the current config- 147 Name of a configfile to include before the rest of the current config-
@@ -145,48 +151,52 @@ index-header::
145 The content of the file specified with this option will be included 151 The content of the file specified with this option will be included
146 verbatim above the repository index. This setting is deprecated, and 152 verbatim above the repository index. This setting is deprecated, and
147 will not be supported by cgit-1.0 (use root-readme instead). Default 153 will not be supported by cgit-1.0 (use root-readme instead). Default
148 value: none. 154 value: none.
149 155
150index-info:: 156index-info::
151 The content of the file specified with this option will be included 157 The content of the file specified with this option will be included
152 verbatim below the heading on the repository index page. This setting 158 verbatim below the heading on the repository index page. This setting
153 is deprecated, and will not be supported by cgit-1.0 (use root-desc 159 is deprecated, and will not be supported by cgit-1.0 (use root-desc
154 instead). Default value: none. 160 instead). Default value: none.
155 161
156local-time:: 162local-time::
157 Flag which, if set to "1", makes cgit print commit and tag times in the 163 Flag which, if set to "1", makes cgit print commit and tag times in the
158 servers timezone. Default value: "0". 164 servers timezone. Default value: "0".
159 165
160logo:: 166logo::
161 Url which specifies the source of an image which will be used as a logo 167 Url which specifies the source of an image which will be used as a logo
162 on all cgit pages. Default value: "/cgit.png". 168 on all cgit pages. Default value: "/cgit.png".
163 169
164logo-link:: 170logo-link::
165 Url loaded when clicking on the cgit logo image. If unspecified the 171 Url loaded when clicking on the cgit logo image. If unspecified the
166 calculated url of the repository index page will be used. Default 172 calculated url of the repository index page will be used. Default
167 value: none. 173 value: none.
168 174
175max-atom-items::
176 Specifies the number of items to display in atom feeds view. Default
177 value: "10".
178
169max-commit-count:: 179max-commit-count::
170 Specifies the number of entries to list per page in "log" view. Default 180 Specifies the number of entries to list per page in "log" view. Default
171 value: "50". 181 value: "50".
172 182
173max-message-length:: 183max-message-length::
174 Specifies the maximum number of commit message characters to display in 184 Specifies the maximum number of commit message characters to display in
175 "log" view. Default value: "80". 185 "log" view. Default value: "80".
176 186
177max-repo-count:: 187max-repo-count::
178 Specifies the number of entries to list per page on therepository 188 Specifies the number of entries to list per page on therepository
179 index page. Default value: "50". 189 index page. Default value: "50".
180 190
181max-repodesc-length:: 191max-repodesc-length::
182 Specifies the maximum number of repo description characters to display 192 Specifies the maximum number of repo description characters to display
183 on the repository index page. Default value: "80". 193 on the repository index page. Default value: "80".
184 194
185max-blob-size:: 195max-blob-size::
186 Specifies the maximum size of a blob to display HTML for in KBytes. 196 Specifies the maximum size of a blob to display HTML for in KBytes.
187 Default value: "0" (limit disabled). 197 Default value: "0" (limit disabled).
188 198
189max-stats:: 199max-stats::
190 Set the default maximum statistics period. Valid values are "week", 200 Set the default maximum statistics period. Valid values are "week",
191 "month", "quarter" and "year". If unspecified, statistics are 201 "month", "quarter" and "year". If unspecified, statistics are
192 disabled. Default value: none. See also: "repo.max-stats". 202 disabled. Default value: none. See also: "repo.max-stats".
@@ -300,48 +310,52 @@ repo.clone-url::
300repo.commit-filter:: 310repo.commit-filter::
301 Override the default commit-filter. Default value: none. See also: 311 Override the default commit-filter. Default value: none. See also:
302 "enable-filter-overrides". 312 "enable-filter-overrides".
303 313
304repo.defbranch:: 314repo.defbranch::
305 The name of the default branch for this repository. If no such branch 315 The name of the default branch for this repository. If no such branch
306 exists in the repository, the first branch name (when sorted) is used 316 exists in the repository, the first branch name (when sorted) is used
307 as default instead. Default value: "master". 317 as default instead. Default value: "master".
308 318
309repo.desc:: 319repo.desc::
310 The value to show as repository description. Default value: none. 320 The value to show as repository description. Default value: none.
311 321
312repo.enable-log-filecount:: 322repo.enable-log-filecount::
313 A flag which can be used to disable the global setting 323 A flag which can be used to disable the global setting
314 `enable-log-filecount'. Default value: none. 324 `enable-log-filecount'. Default value: none.
315 325
316repo.enable-log-linecount:: 326repo.enable-log-linecount::
317 A flag which can be used to disable the global setting 327 A flag which can be used to disable the global setting
318 `enable-log-linecount'. Default value: none. 328 `enable-log-linecount'. Default value: none.
319 329
320repo.enable-remote-branches:: 330repo.enable-remote-branches::
321 Flag which, when set to "1", will make cgit display remote branches 331 Flag which, when set to "1", will make cgit display remote branches
322 in the summary and refs views. Default value: <enable-remote-branches>. 332 in the summary and refs views. Default value: <enable-remote-branches>.
323 333
334repo.enable-subject-links::
335 A flag which can be used to override the global setting
336 `enable-subject-links'. Default value: none.
337
324repo.max-stats:: 338repo.max-stats::
325 Override the default maximum statistics period. Valid values are equal 339 Override the default maximum statistics period. Valid values are equal
326 to the values specified for the global "max-stats" setting. Default 340 to the values specified for the global "max-stats" setting. Default
327 value: none. 341 value: none.
328 342
329repo.name:: 343repo.name::
330 The value to show as repository name. Default value: <repo.url>. 344 The value to show as repository name. Default value: <repo.url>.
331 345
332repo.owner:: 346repo.owner::
333 A value used to identify the owner of the repository. Default value: 347 A value used to identify the owner of the repository. Default value:
334 none. 348 none.
335 349
336repo.path:: 350repo.path::
337 An absolute path to the repository directory. For non-bare repositories 351 An absolute path to the repository directory. For non-bare repositories
338 this is the .git-directory. Default value: none. 352 this is the .git-directory. Default value: none.
339 353
340repo.readme:: 354repo.readme::
341 A path (relative to <repo.path>) which specifies a file to include 355 A path (relative to <repo.path>) which specifies a file to include
342 verbatim as the "About" page for this repo. Default value: none. 356 verbatim as the "About" page for this repo. Default value: none.
343 357
344repo.snapshots:: 358repo.snapshots::
345 A mask of allowed snapshot-formats for this repo, restricted by the 359 A mask of allowed snapshot-formats for this repo, restricted by the
346 "snapshots" global setting. Default value: <snapshots>. 360 "snapshots" global setting. Default value: <snapshots>.
347 361
diff --git a/cmd.c b/cmd.c
index 605876b..6dc9f5e 100644
--- a/cmd.c
+++ b/cmd.c
@@ -12,49 +12,49 @@
12#include "ui-shared.h" 12#include "ui-shared.h"
13#include "ui-atom.h" 13#include "ui-atom.h"
14#include "ui-blob.h" 14#include "ui-blob.h"
15#include "ui-clone.h" 15#include "ui-clone.h"
16#include "ui-commit.h" 16#include "ui-commit.h"
17#include "ui-diff.h" 17#include "ui-diff.h"
18#include "ui-log.h" 18#include "ui-log.h"
19#include "ui-patch.h" 19#include "ui-patch.h"
20#include "ui-plain.h" 20#include "ui-plain.h"
21#include "ui-refs.h" 21#include "ui-refs.h"
22#include "ui-repolist.h" 22#include "ui-repolist.h"
23#include "ui-snapshot.h" 23#include "ui-snapshot.h"
24#include "ui-stats.h" 24#include "ui-stats.h"
25#include "ui-summary.h" 25#include "ui-summary.h"
26#include "ui-tag.h" 26#include "ui-tag.h"
27#include "ui-tree.h" 27#include "ui-tree.h"
28 28
29static void HEAD_fn(struct cgit_context *ctx) 29static void HEAD_fn(struct cgit_context *ctx)
30{ 30{
31 cgit_clone_head(ctx); 31 cgit_clone_head(ctx);
32} 32}
33 33
34static void atom_fn(struct cgit_context *ctx) 34static void atom_fn(struct cgit_context *ctx)
35{ 35{
36 cgit_print_atom(ctx->qry.head, ctx->qry.path, 10); 36 cgit_print_atom(ctx->qry.head, ctx->qry.path, ctx->cfg.max_atom_items);
37} 37}
38 38
39static void about_fn(struct cgit_context *ctx) 39static void about_fn(struct cgit_context *ctx)
40{ 40{
41 if (ctx->repo) 41 if (ctx->repo)
42 cgit_print_repo_readme(ctx->qry.path); 42 cgit_print_repo_readme(ctx->qry.path);
43 else 43 else
44 cgit_print_site_readme(); 44 cgit_print_site_readme();
45} 45}
46 46
47static void blob_fn(struct cgit_context *ctx) 47static void blob_fn(struct cgit_context *ctx)
48{ 48{
49 cgit_print_blob(ctx->qry.sha1, ctx->qry.path, ctx->qry.head); 49 cgit_print_blob(ctx->qry.sha1, ctx->qry.path, ctx->qry.head);
50} 50}
51 51
52static void commit_fn(struct cgit_context *ctx) 52static void commit_fn(struct cgit_context *ctx)
53{ 53{
54 cgit_print_commit(ctx->qry.sha1, ctx->qry.path); 54 cgit_print_commit(ctx->qry.sha1, ctx->qry.path);
55} 55}
56 56
57static void diff_fn(struct cgit_context *ctx) 57static void diff_fn(struct cgit_context *ctx)
58{ 58{
59 cgit_print_diff(ctx->qry.sha1, ctx->qry.sha2, ctx->qry.path); 59 cgit_print_diff(ctx->qry.sha1, ctx->qry.sha2, ctx->qry.path);
60} 60}
diff --git a/parsing.c b/parsing.c
index f3f3b15..f37c49d 100644
--- a/parsing.c
+++ b/parsing.c
@@ -169,48 +169,52 @@ struct commitinfo *cgit_parse_commit(struct commit *commit)
169 169
170 // skip empty lines between headers and message 170 // skip empty lines between headers and message
171 while (p && *p == '\n') 171 while (p && *p == '\n')
172 p++; 172 p++;
173 173
174 if (!p) 174 if (!p)
175 return ret; 175 return ret;
176 176
177 t = strchr(p, '\n'); 177 t = strchr(p, '\n');
178 if (t) { 178 if (t) {
179 ret->subject = substr(p, t); 179 ret->subject = substr(p, t);
180 p = t + 1; 180 p = t + 1;
181 181
182 while (p && *p == '\n') { 182 while (p && *p == '\n') {
183 p = strchr(p, '\n'); 183 p = strchr(p, '\n');
184 if (p) 184 if (p)
185 p++; 185 p++;
186 } 186 }
187 if (p) 187 if (p)
188 ret->msg = xstrdup(p); 188 ret->msg = xstrdup(p);
189 } else 189 } else
190 ret->subject = xstrdup(p); 190 ret->subject = xstrdup(p);
191 191
192 if (ret->msg_encoding) { 192 if (ret->msg_encoding) {
193 reencode(&ret->author, PAGE_ENCODING, ret->msg_encoding);
194 reencode(&ret->author_email, PAGE_ENCODING, ret->msg_encoding);
195 reencode(&ret->committer, PAGE_ENCODING, ret->msg_encoding);
196 reencode(&ret->committer_email, PAGE_ENCODING, ret->msg_encoding);
193 reencode(&ret->subject, PAGE_ENCODING, ret->msg_encoding); 197 reencode(&ret->subject, PAGE_ENCODING, ret->msg_encoding);
194 reencode(&ret->msg, PAGE_ENCODING, ret->msg_encoding); 198 reencode(&ret->msg, PAGE_ENCODING, ret->msg_encoding);
195 } 199 }
196 200
197 return ret; 201 return ret;
198} 202}
199 203
200 204
201struct taginfo *cgit_parse_tag(struct tag *tag) 205struct taginfo *cgit_parse_tag(struct tag *tag)
202{ 206{
203 void *data; 207 void *data;
204 enum object_type type; 208 enum object_type type;
205 unsigned long size; 209 unsigned long size;
206 char *p; 210 char *p;
207 struct taginfo *ret; 211 struct taginfo *ret;
208 212
209 data = read_sha1_file(tag->object.sha1, &type, &size); 213 data = read_sha1_file(tag->object.sha1, &type, &size);
210 if (!data || type != OBJ_TAG) { 214 if (!data || type != OBJ_TAG) {
211 free(data); 215 free(data);
212 return 0; 216 return 0;
213 } 217 }
214 218
215 ret = xmalloc(sizeof(*ret)); 219 ret = xmalloc(sizeof(*ret));
216 ret->tagger = NULL; 220 ret->tagger = NULL;
diff --git a/scan-tree.c b/scan-tree.c
index dbca797..1e18f3c 100644
--- a/scan-tree.c
+++ b/scan-tree.c
@@ -35,48 +35,50 @@ static int is_git_dir(const char *path)
35 35
36 return 1; 36 return 1;
37} 37}
38 38
39struct cgit_repo *repo; 39struct cgit_repo *repo;
40repo_config_fn config_fn; 40repo_config_fn config_fn;
41 41
42static void repo_config(const char *name, const char *value) 42static void repo_config(const char *name, const char *value)
43{ 43{
44 config_fn(repo, name, value); 44 config_fn(repo, name, value);
45} 45}
46 46
47static void add_repo(const char *base, const char *path, repo_config_fn fn) 47static void add_repo(const char *base, const char *path, repo_config_fn fn)
48{ 48{
49 struct stat st; 49 struct stat st;
50 struct passwd *pwd; 50 struct passwd *pwd;
51 char *p; 51 char *p;
52 size_t size; 52 size_t size;
53 53
54 if (stat(path, &st)) { 54 if (stat(path, &st)) {
55 fprintf(stderr, "Error accessing %s: %s (%d)\n", 55 fprintf(stderr, "Error accessing %s: %s (%d)\n",
56 path, strerror(errno), errno); 56 path, strerror(errno), errno);
57 return; 57 return;
58 } 58 }
59 if (!stat(fmt("%s/noweb", path), &st))
60 return;
59 if ((pwd = getpwuid(st.st_uid)) == NULL) { 61 if ((pwd = getpwuid(st.st_uid)) == NULL) {
60 fprintf(stderr, "Error reading owner-info for %s: %s (%d)\n", 62 fprintf(stderr, "Error reading owner-info for %s: %s (%d)\n",
61 path, strerror(errno), errno); 63 path, strerror(errno), errno);
62 return; 64 return;
63 } 65 }
64 if (base == path) 66 if (base == path)
65 p = fmt("%s", path); 67 p = fmt("%s", path);
66 else 68 else
67 p = fmt("%s", path + strlen(base) + 1); 69 p = fmt("%s", path + strlen(base) + 1);
68 70
69 if (!strcmp(p + strlen(p) - 5, "/.git")) 71 if (!strcmp(p + strlen(p) - 5, "/.git"))
70 p[strlen(p) - 5] = '\0'; 72 p[strlen(p) - 5] = '\0';
71 73
72 repo = cgit_add_repo(xstrdup(p)); 74 repo = cgit_add_repo(xstrdup(p));
73 repo->name = repo->url; 75 repo->name = repo->url;
74 repo->path = xstrdup(path); 76 repo->path = xstrdup(path);
75 p = (pwd && pwd->pw_gecos) ? strchr(pwd->pw_gecos, ',') : NULL; 77 p = (pwd && pwd->pw_gecos) ? strchr(pwd->pw_gecos, ',') : NULL;
76 if (p) 78 if (p)
77 *p = '\0'; 79 *p = '\0';
78 repo->owner = (pwd ? xstrdup(pwd->pw_gecos ? pwd->pw_gecos : pwd->pw_name) : ""); 80 repo->owner = (pwd ? xstrdup(pwd->pw_gecos ? pwd->pw_gecos : pwd->pw_name) : "");
79 81
80 p = fmt("%s/description", path); 82 p = fmt("%s/description", path);
81 if (!stat(p, &st)) 83 if (!stat(p, &st))
82 readfile(p, &repo->desc, &size); 84 readfile(p, &repo->desc, &size);
diff --git a/shared.c b/shared.c
index d0973ab..b42c2a2 100644
--- a/shared.c
+++ b/shared.c
@@ -38,48 +38,49 @@ struct cgit_repo *cgit_add_repo(const char *url)
38 38
39 if (++cgit_repolist.count > cgit_repolist.length) { 39 if (++cgit_repolist.count > cgit_repolist.length) {
40 if (cgit_repolist.length == 0) 40 if (cgit_repolist.length == 0)
41 cgit_repolist.length = 8; 41 cgit_repolist.length = 8;
42 else 42 else
43 cgit_repolist.length *= 2; 43 cgit_repolist.length *= 2;
44 cgit_repolist.repos = xrealloc(cgit_repolist.repos, 44 cgit_repolist.repos = xrealloc(cgit_repolist.repos,
45 cgit_repolist.length * 45 cgit_repolist.length *
46 sizeof(struct cgit_repo)); 46 sizeof(struct cgit_repo));
47 } 47 }
48 48
49 ret = &cgit_repolist.repos[cgit_repolist.count-1]; 49 ret = &cgit_repolist.repos[cgit_repolist.count-1];
50 memset(ret, 0, sizeof(struct cgit_repo)); 50 memset(ret, 0, sizeof(struct cgit_repo));
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->section = ctx.cfg.section; 56 ret->section = ctx.cfg.section;
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->enable_remote_branches = ctx.cfg.enable_remote_branches; 61 ret->enable_remote_branches = ctx.cfg.enable_remote_branches;
62 ret->enable_subject_links = ctx.cfg.enable_subject_links;
62 ret->max_stats = ctx.cfg.max_stats; 63 ret->max_stats = ctx.cfg.max_stats;
63 ret->module_link = ctx.cfg.module_link; 64 ret->module_link = ctx.cfg.module_link;
64 ret->readme = NULL; 65 ret->readme = NULL;
65 ret->mtime = -1; 66 ret->mtime = -1;
66 ret->about_filter = ctx.cfg.about_filter; 67 ret->about_filter = ctx.cfg.about_filter;
67 ret->commit_filter = ctx.cfg.commit_filter; 68 ret->commit_filter = ctx.cfg.commit_filter;
68 ret->source_filter = ctx.cfg.source_filter; 69 ret->source_filter = ctx.cfg.source_filter;
69 return ret; 70 return ret;
70} 71}
71 72
72struct cgit_repo *cgit_get_repoinfo(const char *url) 73struct cgit_repo *cgit_get_repoinfo(const char *url)
73{ 74{
74 int i; 75 int i;
75 struct cgit_repo *repo; 76 struct cgit_repo *repo;
76 77
77 for (i=0; i<cgit_repolist.count; i++) { 78 for (i=0; i<cgit_repolist.count; i++) {
78 repo = &cgit_repolist.repos[i]; 79 repo = &cgit_repolist.repos[i];
79 if (!strcmp(repo->url, url)) 80 if (!strcmp(repo->url, url))
80 return repo; 81 return repo;
81 } 82 }
82 return NULL; 83 return NULL;
83} 84}
84 85
85void *cgit_free_commitinfo(struct commitinfo *info) 86void *cgit_free_commitinfo(struct commitinfo *info)
@@ -258,62 +259,70 @@ int filediff_cb(void *priv, mmbuffer_t *mb, int nbuf)
258 buflen = 0; 259 buflen = 0;
259 } 260 }
260 return 0; 261 return 0;
261} 262}
262 263
263int cgit_diff_files(const unsigned char *old_sha1, 264int cgit_diff_files(const unsigned char *old_sha1,
264 const unsigned char *new_sha1, unsigned long *old_size, 265 const unsigned char *new_sha1, unsigned long *old_size,
265 unsigned long *new_size, int *binary, int context, 266 unsigned long *new_size, int *binary, int context,
266 int ignorews, linediff_fn fn) 267 int ignorews, linediff_fn fn)
267{ 268{
268 mmfile_t file1, file2; 269 mmfile_t file1, file2;
269 xpparam_t diff_params; 270 xpparam_t diff_params;
270 xdemitconf_t emit_params; 271 xdemitconf_t emit_params;
271 xdemitcb_t emit_cb; 272 xdemitcb_t emit_cb;
272 273
273 if (!load_mmfile(&file1, old_sha1) || !load_mmfile(&file2, new_sha1)) 274 if (!load_mmfile(&file1, old_sha1) || !load_mmfile(&file2, new_sha1))
274 return 1; 275 return 1;
275 276
276 *old_size = file1.size; 277 *old_size = file1.size;
277 *new_size = file2.size; 278 *new_size = file2.size;
278 279
279 if ((file1.ptr && buffer_is_binary(file1.ptr, file1.size)) || 280 if ((file1.ptr && buffer_is_binary(file1.ptr, file1.size)) ||
280 (file2.ptr && buffer_is_binary(file2.ptr, file2.size))) { 281 (file2.ptr && buffer_is_binary(file2.ptr, file2.size))) {
281 *binary = 1; 282 *binary = 1;
283 if (file1.size)
284 free(file1.ptr);
285 if (file2.size)
286 free(file2.ptr);
282 return 0; 287 return 0;
283 } 288 }
284 289
285 memset(&diff_params, 0, sizeof(diff_params)); 290 memset(&diff_params, 0, sizeof(diff_params));
286 memset(&emit_params, 0, sizeof(emit_params)); 291 memset(&emit_params, 0, sizeof(emit_params));
287 memset(&emit_cb, 0, sizeof(emit_cb)); 292 memset(&emit_cb, 0, sizeof(emit_cb));
288 diff_params.flags = XDF_NEED_MINIMAL; 293 diff_params.flags = XDF_NEED_MINIMAL;
289 if (ignorews) 294 if (ignorews)
290 diff_params.flags |= XDF_IGNORE_WHITESPACE; 295 diff_params.flags |= XDF_IGNORE_WHITESPACE;
291 emit_params.ctxlen = context > 0 ? context : 3; 296 emit_params.ctxlen = context > 0 ? context : 3;
292 emit_params.flags = XDL_EMIT_FUNCNAMES; 297 emit_params.flags = XDL_EMIT_FUNCNAMES;
293 emit_cb.outf = filediff_cb; 298 emit_cb.outf = filediff_cb;
294 emit_cb.priv = fn; 299 emit_cb.priv = fn;
295 xdl_diff(&file1, &file2, &diff_params, &emit_params, &emit_cb); 300 xdl_diff(&file1, &file2, &diff_params, &emit_params, &emit_cb);
301 if (file1.size)
302 free(file1.ptr);
303 if (file2.size)
304 free(file2.ptr);
296 return 0; 305 return 0;
297} 306}
298 307
299void cgit_diff_tree(const unsigned char *old_sha1, 308void cgit_diff_tree(const unsigned char *old_sha1,
300 const unsigned char *new_sha1, 309 const unsigned char *new_sha1,
301 filepair_fn fn, const char *prefix, int ignorews) 310 filepair_fn fn, const char *prefix, int ignorews)
302{ 311{
303 struct diff_options opt; 312 struct diff_options opt;
304 int ret; 313 int ret;
305 int prefixlen; 314 int prefixlen;
306 315
307 diff_setup(&opt); 316 diff_setup(&opt);
308 opt.output_format = DIFF_FORMAT_CALLBACK; 317 opt.output_format = DIFF_FORMAT_CALLBACK;
309 opt.detect_rename = 1; 318 opt.detect_rename = 1;
310 opt.rename_limit = ctx.cfg.renamelimit; 319 opt.rename_limit = ctx.cfg.renamelimit;
311 DIFF_OPT_SET(&opt, RECURSIVE); 320 DIFF_OPT_SET(&opt, RECURSIVE);
312 if (ignorews) 321 if (ignorews)
313 DIFF_XDL_SET(&opt, IGNORE_WHITESPACE); 322 DIFF_XDL_SET(&opt, IGNORE_WHITESPACE);
314 opt.format_callback = cgit_diff_tree_cb; 323 opt.format_callback = cgit_diff_tree_cb;
315 opt.format_callback_data = fn; 324 opt.format_callback_data = fn;
316 if (prefix) { 325 if (prefix) {
317 opt.nr_paths = 1; 326 opt.nr_paths = 1;
318 opt.paths = &prefix; 327 opt.paths = &prefix;
319 prefixlen = strlen(prefix); 328 prefixlen = strlen(prefix);
@@ -407,24 +416,95 @@ int cgit_close_filter(struct cgit_filter *filter)
407int readfile(const char *path, char **buf, size_t *size) 416int readfile(const char *path, char **buf, size_t *size)
408{ 417{
409 int fd, e; 418 int fd, e;
410 struct stat st; 419 struct stat st;
411 420
412 fd = open(path, O_RDONLY); 421 fd = open(path, O_RDONLY);
413 if (fd == -1) 422 if (fd == -1)
414 return errno; 423 return errno;
415 if (fstat(fd, &st)) { 424 if (fstat(fd, &st)) {
416 e = errno; 425 e = errno;
417 close(fd); 426 close(fd);
418 return e; 427 return e;
419 } 428 }
420 if (!S_ISREG(st.st_mode)) { 429 if (!S_ISREG(st.st_mode)) {
421 close(fd); 430 close(fd);
422 return EISDIR; 431 return EISDIR;
423 } 432 }
424 *buf = xmalloc(st.st_size + 1); 433 *buf = xmalloc(st.st_size + 1);
425 *size = read_in_full(fd, *buf, st.st_size); 434 *size = read_in_full(fd, *buf, st.st_size);
426 e = errno; 435 e = errno;
427 (*buf)[*size] = '\0'; 436 (*buf)[*size] = '\0';
428 close(fd); 437 close(fd);
429 return (*size == st.st_size ? 0 : e); 438 return (*size == st.st_size ? 0 : e);
430} 439}
440
441int is_token_char(char c)
442{
443 return isalnum(c) || c == '_';
444}
445
446/* Replace name with getenv(name), return pointer to zero-terminating char
447 */
448char *expand_macro(char *name, int maxlength)
449{
450 char *value;
451 int len;
452
453 len = 0;
454 value = getenv(name);
455 if (value) {
456 len = strlen(value);
457 if (len > maxlength)
458 len = maxlength;
459 strncpy(name, value, len);
460 }
461 return name + len;
462}
463
464#define EXPBUFSIZE (1024 * 8)
465
466/* Replace all tokens prefixed by '$' in the specified text with the
467 * value of the named environment variable.
468 * NB: the return value is a static buffer, i.e. it must be strdup'd
469 * by the caller.
470 */
471char *expand_macros(const char *txt)
472{
473 static char result[EXPBUFSIZE];
474 char *p, *start;
475 int len;
476
477 p = result;
478 start = NULL;
479 while (p < result + EXPBUFSIZE - 1 && txt && *txt) {
480 *p = *txt;
481 if (start) {
482 if (!is_token_char(*txt)) {
483 if (p - start > 0) {
484 *p = '\0';
485 len = result + EXPBUFSIZE - start - 1;
486 p = expand_macro(start, len) - 1;
487 }
488 start = NULL;
489 txt--;
490 }
491 p++;
492 txt++;
493 continue;
494 }
495 if (*txt == '$') {
496 start = p;
497 txt++;
498 continue;
499 }
500 p++;
501 txt++;
502 }
503 *p = '\0';
504 if (start && p - start > 0) {
505 len = result + EXPBUFSIZE - start - 1;
506 p = expand_macro(start, len);
507 *p = '\0';
508 }
509 return result;
510}
diff --git a/ui-atom.c b/ui-atom.c
index 808b2d0..9f049ae 100644
--- a/ui-atom.c
+++ b/ui-atom.c
@@ -64,49 +64,51 @@ void add_entry(struct commit *commit, char *host)
64 htmlf("<id>%s</id>\n", hex); 64 htmlf("<id>%s</id>\n", hex);
65 html("<content type='text'>\n"); 65 html("<content type='text'>\n");
66 html_txt(info->msg); 66 html_txt(info->msg);
67 html("</content>\n"); 67 html("</content>\n");
68 html("<content type='xhtml'>\n"); 68 html("<content type='xhtml'>\n");
69 html("<div xmlns='http://www.w3.org/1999/xhtml'>\n"); 69 html("<div xmlns='http://www.w3.org/1999/xhtml'>\n");
70 html("<pre>\n"); 70 html("<pre>\n");
71 html_txt(info->msg); 71 html_txt(info->msg);
72 html("</pre>\n"); 72 html("</pre>\n");
73 html("</div>\n"); 73 html("</div>\n");
74 html("</content>\n"); 74 html("</content>\n");
75 html("</entry>\n"); 75 html("</entry>\n");
76 cgit_free_commitinfo(info); 76 cgit_free_commitinfo(info);
77} 77}
78 78
79 79
80void cgit_print_atom(char *tip, char *path, int max_count) 80void cgit_print_atom(char *tip, char *path, int max_count)
81{ 81{
82 char *host; 82 char *host;
83 const char *argv[] = {NULL, tip, NULL, NULL, NULL}; 83 const char *argv[] = {NULL, tip, NULL, NULL, NULL};
84 struct commit *commit; 84 struct commit *commit;
85 struct rev_info rev; 85 struct rev_info rev;
86 int argc = 2; 86 int argc = 2;
87 87
88 if (!tip) 88 if (ctx.qry.show_all)
89 argv[1] = "--all";
90 else if (!tip)
89 argv[1] = ctx.qry.head; 91 argv[1] = ctx.qry.head;
90 92
91 if (path) { 93 if (path) {
92 argv[argc++] = "--"; 94 argv[argc++] = "--";
93 argv[argc++] = path; 95 argv[argc++] = path;
94 } 96 }
95 97
96 init_revisions(&rev, NULL); 98 init_revisions(&rev, NULL);
97 rev.abbrev = DEFAULT_ABBREV; 99 rev.abbrev = DEFAULT_ABBREV;
98 rev.commit_format = CMIT_FMT_DEFAULT; 100 rev.commit_format = CMIT_FMT_DEFAULT;
99 rev.verbose_header = 1; 101 rev.verbose_header = 1;
100 rev.show_root_diff = 0; 102 rev.show_root_diff = 0;
101 rev.max_count = max_count; 103 rev.max_count = max_count;
102 setup_revisions(argc, argv, &rev, NULL); 104 setup_revisions(argc, argv, &rev, NULL);
103 prepare_revision_walk(&rev); 105 prepare_revision_walk(&rev);
104 106
105 host = cgit_hosturl(); 107 host = cgit_hosturl();
106 ctx.page.mimetype = "text/xml"; 108 ctx.page.mimetype = "text/xml";
107 ctx.page.charset = "utf-8"; 109 ctx.page.charset = "utf-8";
108 cgit_print_http_headers(&ctx); 110 cgit_print_http_headers(&ctx);
109 html("<feed xmlns='http://www.w3.org/2005/Atom'>\n"); 111 html("<feed xmlns='http://www.w3.org/2005/Atom'>\n");
110 html("<title>"); 112 html("<title>");
111 html_txt(ctx.repo->name); 113 html_txt(ctx.repo->name);
112 html("</title>\n"); 114 html("</title>\n");
diff --git a/ui-commit.c b/ui-commit.c
index 2d98ed9..a11bc5f 100644
--- a/ui-commit.c
+++ b/ui-commit.c
@@ -1,45 +1,45 @@
1/* ui-commit.c: generate commit view 1/* ui-commit.c: generate commit view
2 * 2 *
3 * Copyright (C) 2006 Lars Hjemli 3 * Copyright (C) 2006 Lars Hjemli
4 * 4 *
5 * Licensed under GNU General Public License v2 5 * Licensed under GNU General Public License v2
6 * (see COPYING for full license text) 6 * (see COPYING for full license text)
7 */ 7 */
8 8
9#include "cgit.h" 9#include "cgit.h"
10#include "html.h" 10#include "html.h"
11#include "ui-shared.h" 11#include "ui-shared.h"
12#include "ui-diff.h" 12#include "ui-diff.h"
13#include "ui-log.h" 13#include "ui-log.h"
14 14
15void cgit_print_commit(char *hex, const char *prefix) 15void cgit_print_commit(char *hex, const char *prefix)
16{ 16{
17 struct commit *commit, *parent; 17 struct commit *commit, *parent;
18 struct commitinfo *info; 18 struct commitinfo *info, *parent_info;
19 struct commit_list *p; 19 struct commit_list *p;
20 unsigned char sha1[20]; 20 unsigned char sha1[20];
21 char *tmp; 21 char *tmp, *tmp2;
22 int parents = 0; 22 int parents = 0;
23 23
24 if (!hex) 24 if (!hex)
25 hex = ctx.qry.head; 25 hex = ctx.qry.head;
26 26
27 if (get_sha1(hex, sha1)) { 27 if (get_sha1(hex, sha1)) {
28 cgit_print_error(fmt("Bad object id: %s", hex)); 28 cgit_print_error(fmt("Bad object id: %s", hex));
29 return; 29 return;
30 } 30 }
31 commit = lookup_commit_reference(sha1); 31 commit = lookup_commit_reference(sha1);
32 if (!commit) { 32 if (!commit) {
33 cgit_print_error(fmt("Bad commit reference: %s", hex)); 33 cgit_print_error(fmt("Bad commit reference: %s", hex));
34 return; 34 return;
35 } 35 }
36 info = cgit_parse_commit(commit); 36 info = cgit_parse_commit(commit);
37 37
38 load_ref_decorations(DECORATE_FULL_REFS); 38 load_ref_decorations(DECORATE_FULL_REFS);
39 39
40 html("<table summary='commit info' class='commit-info'>\n"); 40 html("<table summary='commit info' class='commit-info'>\n");
41 html("<tr><th>author</th><td>"); 41 html("<tr><th>author</th><td>");
42 html_txt(info->author); 42 html_txt(info->author);
43 if (!ctx.cfg.noplainemail) { 43 if (!ctx.cfg.noplainemail) {
44 html(" "); 44 html(" ");
45 html_txt(info->author_email); 45 html_txt(info->author_email);
@@ -65,51 +65,54 @@ void cgit_print_commit(char *hex, const char *prefix)
65 if ((ctx.qry.ssdiff && !ctx.cfg.ssdiff) || (!ctx.qry.ssdiff && ctx.cfg.ssdiff)) 65 if ((ctx.qry.ssdiff && !ctx.cfg.ssdiff) || (!ctx.qry.ssdiff && ctx.cfg.ssdiff))
66 cgit_commit_link("unidiff", NULL, NULL, ctx.qry.head, tmp, prefix, 1); 66 cgit_commit_link("unidiff", NULL, NULL, ctx.qry.head, tmp, prefix, 1);
67 else 67 else
68 cgit_commit_link("side-by-side diff", NULL, NULL, ctx.qry.head, tmp, prefix, 1); 68 cgit_commit_link("side-by-side diff", NULL, NULL, ctx.qry.head, tmp, prefix, 1);
69 html(")</td></tr>\n"); 69 html(")</td></tr>\n");
70 html("<tr><th>tree</th><td colspan='2' class='sha1'>"); 70 html("<tr><th>tree</th><td colspan='2' class='sha1'>");
71 tmp = xstrdup(hex); 71 tmp = xstrdup(hex);
72 cgit_tree_link(sha1_to_hex(commit->tree->object.sha1), NULL, NULL, 72 cgit_tree_link(sha1_to_hex(commit->tree->object.sha1), NULL, NULL,
73 ctx.qry.head, tmp, NULL); 73 ctx.qry.head, tmp, NULL);
74 if (prefix) { 74 if (prefix) {
75 html(" /"); 75 html(" /");
76 cgit_tree_link(prefix, NULL, NULL, ctx.qry.head, tmp, prefix); 76 cgit_tree_link(prefix, NULL, NULL, ctx.qry.head, tmp, prefix);
77 } 77 }
78 html("</td></tr>\n"); 78 html("</td></tr>\n");
79 for (p = commit->parents; p ; p = p->next) { 79 for (p = commit->parents; p ; p = p->next) {
80 parent = lookup_commit_reference(p->item->object.sha1); 80 parent = lookup_commit_reference(p->item->object.sha1);
81 if (!parent) { 81 if (!parent) {
82 html("<tr><td colspan='3'>"); 82 html("<tr><td colspan='3'>");
83 cgit_print_error("Error reading parent commit"); 83 cgit_print_error("Error reading parent commit");
84 html("</td></tr>"); 84 html("</td></tr>");
85 continue; 85 continue;
86 } 86 }
87 html("<tr><th>parent</th>" 87 html("<tr><th>parent</th>"
88 "<td colspan='2' class='sha1'>"); 88 "<td colspan='2' class='sha1'>");
89 cgit_commit_link(sha1_to_hex(p->item->object.sha1), NULL, NULL, 89 tmp = tmp2 = sha1_to_hex(p->item->object.sha1);
90 ctx.qry.head, 90 if (ctx.repo->enable_subject_links) {
91 sha1_to_hex(p->item->object.sha1), prefix, 0); 91 parent_info = cgit_parse_commit(parent);
92 tmp2 = parent_info->subject;
93 }
94 cgit_commit_link(tmp2, NULL, NULL, ctx.qry.head, tmp, prefix, 0);
92 html(" ("); 95 html(" (");
93 cgit_diff_link("diff", NULL, NULL, ctx.qry.head, hex, 96 cgit_diff_link("diff", NULL, NULL, ctx.qry.head, hex,
94 sha1_to_hex(p->item->object.sha1), prefix, 0); 97 sha1_to_hex(p->item->object.sha1), prefix, 0);
95 html(")</td></tr>"); 98 html(")</td></tr>");
96 parents++; 99 parents++;
97 } 100 }
98 if (ctx.repo->snapshots) { 101 if (ctx.repo->snapshots) {
99 html("<tr><th>download</th><td colspan='2' class='sha1'>"); 102 html("<tr><th>download</th><td colspan='2' class='sha1'>");
100 cgit_print_snapshot_links(ctx.qry.repo, ctx.qry.head, 103 cgit_print_snapshot_links(ctx.qry.repo, ctx.qry.head,
101 hex, ctx.repo->snapshots); 104 hex, ctx.repo->snapshots);
102 html("</td></tr>"); 105 html("</td></tr>");
103 } 106 }
104 html("</table>\n"); 107 html("</table>\n");
105 html("<div class='commit-subject'>"); 108 html("<div class='commit-subject'>");
106 if (ctx.repo->commit_filter) 109 if (ctx.repo->commit_filter)
107 cgit_open_filter(ctx.repo->commit_filter); 110 cgit_open_filter(ctx.repo->commit_filter);
108 html_txt(info->subject); 111 html_txt(info->subject);
109 if (ctx.repo->commit_filter) 112 if (ctx.repo->commit_filter)
110 cgit_close_filter(ctx.repo->commit_filter); 113 cgit_close_filter(ctx.repo->commit_filter);
111 show_commit_decorations(commit); 114 show_commit_decorations(commit);
112 html("</div>"); 115 html("</div>");
113 html("<div class='commit-msg'>"); 116 html("<div class='commit-msg'>");
114 if (ctx.repo->commit_filter) 117 if (ctx.repo->commit_filter)
115 cgit_open_filter(ctx.repo->commit_filter); 118 cgit_open_filter(ctx.repo->commit_filter);
diff --git a/ui-log.c b/ui-log.c
index 354ee08..ee93653 100644
--- a/ui-log.c
+++ b/ui-log.c
@@ -129,52 +129,55 @@ static const char *disambiguate_ref(const char *ref)
129 unsigned char sha1[20]; 129 unsigned char sha1[20];
130 const char *longref; 130 const char *longref;
131 131
132 longref = fmt("refs/heads/%s", ref); 132 longref = fmt("refs/heads/%s", ref);
133 if (get_sha1(longref, sha1) == 0) 133 if (get_sha1(longref, sha1) == 0)
134 return longref; 134 return longref;
135 135
136 return ref; 136 return ref;
137} 137}
138 138
139void cgit_print_log(const char *tip, int ofs, int cnt, char *grep, char *pattern, 139void cgit_print_log(const char *tip, int ofs, int cnt, char *grep, char *pattern,
140 char *path, int pager) 140 char *path, int pager)
141{ 141{
142 struct rev_info rev; 142 struct rev_info rev;
143 struct commit *commit; 143 struct commit *commit;
144 const char *argv[] = {NULL, NULL, NULL, NULL, NULL}; 144 const char *argv[] = {NULL, NULL, NULL, NULL, NULL};
145 int argc = 2; 145 int argc = 2;
146 int i, columns = 3; 146 int i, columns = 3;
147 147
148 if (!tip) 148 if (!tip)
149 tip = ctx.qry.head; 149 tip = ctx.qry.head;
150 150
151 argv[1] = disambiguate_ref(tip); 151 argv[1] = disambiguate_ref(tip);
152 152
153 if (grep && pattern && (!strcmp(grep, "grep") || 153 if (grep && pattern) {
154 !strcmp(grep, "author") || 154 if (!strcmp(grep, "grep") || !strcmp(grep, "author") ||
155 !strcmp(grep, "committer"))) 155 !strcmp(grep, "committer"))
156 argv[argc++] = fmt("--%s=%s", grep, pattern); 156 argv[argc++] = fmt("--%s=%s", grep, pattern);
157 if (!strcmp(grep, "range"))
158 argv[1] = pattern;
159 }
157 160
158 if (path) { 161 if (path) {
159 argv[argc++] = "--"; 162 argv[argc++] = "--";
160 argv[argc++] = path; 163 argv[argc++] = path;
161 } 164 }
162 init_revisions(&rev, NULL); 165 init_revisions(&rev, NULL);
163 rev.abbrev = DEFAULT_ABBREV; 166 rev.abbrev = DEFAULT_ABBREV;
164 rev.commit_format = CMIT_FMT_DEFAULT; 167 rev.commit_format = CMIT_FMT_DEFAULT;
165 rev.verbose_header = 1; 168 rev.verbose_header = 1;
166 rev.show_root_diff = 0; 169 rev.show_root_diff = 0;
167 setup_revisions(argc, argv, &rev, NULL); 170 setup_revisions(argc, argv, &rev, NULL);
168 load_ref_decorations(DECORATE_FULL_REFS); 171 load_ref_decorations(DECORATE_FULL_REFS);
169 rev.show_decorations = 1; 172 rev.show_decorations = 1;
170 rev.grep_filter.regflags |= REG_ICASE; 173 rev.grep_filter.regflags |= REG_ICASE;
171 compile_grep_patterns(&rev.grep_filter); 174 compile_grep_patterns(&rev.grep_filter);
172 prepare_revision_walk(&rev); 175 prepare_revision_walk(&rev);
173 176
174 if (pager) 177 if (pager)
175 html("<table class='list nowrap'>"); 178 html("<table class='list nowrap'>");
176 179
177 html("<tr class='nohover'><th class='left'>Age</th>" 180 html("<tr class='nohover'><th class='left'>Age</th>"
178 "<th class='left'>Commit message"); 181 "<th class='left'>Commit message");
179 if (pager) { 182 if (pager) {
180 html(" ("); 183 html(" (");
diff --git a/ui-plain.c b/ui-plain.c
index 66cb19c..da76406 100644
--- a/ui-plain.c
+++ b/ui-plain.c
@@ -1,94 +1,146 @@
1/* ui-plain.c: functions for output of plain blobs by path 1/* ui-plain.c: functions for output of plain blobs by path
2 * 2 *
3 * Copyright (C) 2008 Lars Hjemli 3 * Copyright (C) 2008 Lars Hjemli
4 * 4 *
5 * Licensed under GNU General Public License v2 5 * Licensed under GNU General Public License v2
6 * (see COPYING for full license text) 6 * (see COPYING for full license text)
7 */ 7 */
8 8
9#include "cgit.h" 9#include "cgit.h"
10#include "html.h" 10#include "html.h"
11#include "ui-shared.h" 11#include "ui-shared.h"
12 12
13char *curr_rev; 13int match_baselen;
14char *match_path;
15int match; 14int match;
16 15
17static void print_object(const unsigned char *sha1, const char *path) 16static void print_object(const unsigned char *sha1, const char *path)
18{ 17{
19 enum object_type type; 18 enum object_type type;
20 char *buf, *ext; 19 char *buf, *ext;
21 unsigned long size; 20 unsigned long size;
22 struct string_list_item *mime; 21 struct string_list_item *mime;
23 22
24 type = sha1_object_info(sha1, &size); 23 type = sha1_object_info(sha1, &size);
25 if (type == OBJ_BAD) { 24 if (type == OBJ_BAD) {
26 html_status(404, "Not found", 0); 25 html_status(404, "Not found", 0);
27 return; 26 return;
28 } 27 }
29 28
30 buf = read_sha1_file(sha1, &type, &size); 29 buf = read_sha1_file(sha1, &type, &size);
31 if (!buf) { 30 if (!buf) {
32 html_status(404, "Not found", 0); 31 html_status(404, "Not found", 0);
33 return; 32 return;
34 } 33 }
35 ctx.page.mimetype = NULL; 34 ctx.page.mimetype = NULL;
36 ext = strrchr(path, '.'); 35 ext = strrchr(path, '.');
37 if (ext && *(++ext)) { 36 if (ext && *(++ext)) {
38 mime = string_list_lookup(ext, &ctx.cfg.mimetypes); 37 mime = string_list_lookup(ext, &ctx.cfg.mimetypes);
39 if (mime) 38 if (mime)
40 ctx.page.mimetype = (char *)mime->util; 39 ctx.page.mimetype = (char *)mime->util;
41 } 40 }
42 if (!ctx.page.mimetype) { 41 if (!ctx.page.mimetype) {
43 if (buffer_is_binary(buf, size)) 42 if (buffer_is_binary(buf, size))
44 ctx.page.mimetype = "application/octet-stream"; 43 ctx.page.mimetype = "application/octet-stream";
45 else 44 else
46 ctx.page.mimetype = "text/plain"; 45 ctx.page.mimetype = "text/plain";
47 } 46 }
48 ctx.page.filename = fmt("%s", path); 47 ctx.page.filename = fmt("%s", path);
49 ctx.page.size = size; 48 ctx.page.size = size;
50 ctx.page.etag = sha1_to_hex(sha1); 49 ctx.page.etag = sha1_to_hex(sha1);
51 cgit_print_http_headers(&ctx); 50 cgit_print_http_headers(&ctx);
52 html_raw(buf, size); 51 html_raw(buf, size);
53 match = 1; 52 match = 1;
54} 53}
55 54
55static void print_dir(const unsigned char *sha1, const char *path,
56 const char *base)
57{
58 char *fullpath;
59 if (path[0] || base[0])
60 fullpath = fmt("/%s%s/", base, path);
61 else
62 fullpath = "/";
63 ctx.page.etag = sha1_to_hex(sha1);
64 cgit_print_http_headers(&ctx);
65 htmlf("<html><head><title>%s</title></head>\n<body>\n"
66 " <h2>%s</h2>\n <ul>\n", fullpath, fullpath);
67 if (path[0] || base[0])
68 html(" <li><a href=\"../\">../</a></li>\n");
69 match = 2;
70}
71
72static void print_dir_entry(const unsigned char *sha1, const char *path,
73 unsigned mode)
74{
75 const char *sep = "";
76 if (S_ISDIR(mode))
77 sep = "/";
78 htmlf(" <li><a href=\"%s%s\">%s%s</a></li>\n", path, sep, path, sep);
79 match = 2;
80}
81
82static void print_dir_tail(void)
83{
84 html(" </ul>\n</body></html>\n");
85}
86
56static int walk_tree(const unsigned char *sha1, const char *base, int baselen, 87static int walk_tree(const unsigned char *sha1, const char *base, int baselen,
57 const char *pathname, unsigned mode, int stage, 88 const char *pathname, unsigned mode, int stage,
58 void *cbdata) 89 void *cbdata)
59{ 90{
60 if (S_ISDIR(mode)) 91 if (baselen == match_baselen) {
92 if (S_ISREG(mode))
93 print_object(sha1, pathname);
94 else if (S_ISDIR(mode)) {
95 print_dir(sha1, pathname, base);
96 return READ_TREE_RECURSIVE;
97 }
98 }
99 else if (baselen > match_baselen)
100 print_dir_entry(sha1, pathname, mode);
101 else if (S_ISDIR(mode))
61 return READ_TREE_RECURSIVE; 102 return READ_TREE_RECURSIVE;
62 103
63 if (S_ISREG(mode) && !strncmp(base, match_path, baselen) && 104 return 0;
64 !strcmp(pathname, match_path + baselen)) 105}
65 print_object(sha1, pathname);
66 106
107static int basedir_len(const char *path)
108{
109 char *p = strrchr(path, '/');
110 if (p)
111 return p - path + 1;
67 return 0; 112 return 0;
68} 113}
69 114
70void cgit_print_plain(struct cgit_context *ctx) 115void cgit_print_plain(struct cgit_context *ctx)
71{ 116{
72 const char *rev = ctx->qry.sha1; 117 const char *rev = ctx->qry.sha1;
73 unsigned char sha1[20]; 118 unsigned char sha1[20];
74 struct commit *commit; 119 struct commit *commit;
75 const char *paths[] = {ctx->qry.path, NULL}; 120 const char *paths[] = {ctx->qry.path, NULL};
76 121
77 if (!rev) 122 if (!rev)
78 rev = ctx->qry.head; 123 rev = ctx->qry.head;
79 124
80 curr_rev = xstrdup(rev);
81 if (get_sha1(rev, sha1)) { 125 if (get_sha1(rev, sha1)) {
82 html_status(404, "Not found", 0); 126 html_status(404, "Not found", 0);
83 return; 127 return;
84 } 128 }
85 commit = lookup_commit_reference(sha1); 129 commit = lookup_commit_reference(sha1);
86 if (!commit || parse_commit(commit)) { 130 if (!commit || parse_commit(commit)) {
87 html_status(404, "Not found", 0); 131 html_status(404, "Not found", 0);
88 return; 132 return;
89 } 133 }
90 match_path = ctx->qry.path; 134 if (!paths[0]) {
135 paths[0] = "";
136 match_baselen = -1;
137 print_dir(commit->tree->object.sha1, "", "");
138 }
139 else
140 match_baselen = basedir_len(paths[0]);
91 read_tree_recursive(commit->tree, "", 0, 0, paths, walk_tree, NULL); 141 read_tree_recursive(commit->tree, "", 0, 0, paths, walk_tree, NULL);
92 if (!match) 142 if (!match)
93 html_status(404, "Not found", 0); 143 html_status(404, "Not found", 0);
144 else if (match == 2)
145 print_dir_tail();
94} 146}
diff --git a/ui-shared.c b/ui-shared.c
index f46c935..ae29615 100644
--- a/ui-shared.c
+++ b/ui-shared.c
@@ -819,48 +819,49 @@ void cgit_print_pageheader(struct cgit_context *ctx)
819 cgit_tree_link("tree", NULL, hc(ctx, "tree"), ctx->qry.head, 819 cgit_tree_link("tree", NULL, hc(ctx, "tree"), ctx->qry.head,
820 ctx->qry.sha1, ctx->qry.vpath); 820 ctx->qry.sha1, ctx->qry.vpath);
821 cgit_commit_link("commit", NULL, hc(ctx, "commit"), 821 cgit_commit_link("commit", NULL, hc(ctx, "commit"),
822 ctx->qry.head, ctx->qry.sha1, ctx->qry.vpath, 0); 822 ctx->qry.head, ctx->qry.sha1, ctx->qry.vpath, 0);
823 cgit_diff_link("diff", NULL, hc(ctx, "diff"), ctx->qry.head, 823 cgit_diff_link("diff", NULL, hc(ctx, "diff"), ctx->qry.head,
824 ctx->qry.sha1, ctx->qry.sha2, ctx->qry.vpath, 0); 824 ctx->qry.sha1, ctx->qry.sha2, ctx->qry.vpath, 0);
825 if (ctx->repo->max_stats) 825 if (ctx->repo->max_stats)
826 cgit_stats_link("stats", NULL, hc(ctx, "stats"), 826 cgit_stats_link("stats", NULL, hc(ctx, "stats"),
827 ctx->qry.head, ctx->qry.vpath); 827 ctx->qry.head, ctx->qry.vpath);
828 if (ctx->repo->readme) 828 if (ctx->repo->readme)
829 reporevlink("about", "about", NULL, 829 reporevlink("about", "about", NULL,
830 hc(ctx, "about"), ctx->qry.head, NULL, 830 hc(ctx, "about"), ctx->qry.head, NULL,
831 NULL); 831 NULL);
832 html("</td><td class='form'>"); 832 html("</td><td class='form'>");
833 html("<form class='right' method='get' action='"); 833 html("<form class='right' method='get' action='");
834 if (ctx->cfg.virtual_root) 834 if (ctx->cfg.virtual_root)
835 html_url_path(cgit_fileurl(ctx->qry.repo, "log", 835 html_url_path(cgit_fileurl(ctx->qry.repo, "log",
836 ctx->qry.vpath, NULL)); 836 ctx->qry.vpath, NULL));
837 html("'>\n"); 837 html("'>\n");
838 cgit_add_hidden_formfields(1, 0, "log"); 838 cgit_add_hidden_formfields(1, 0, "log");
839 html("<select name='qt'>\n"); 839 html("<select name='qt'>\n");
840 html_option("grep", "log msg", ctx->qry.grep); 840 html_option("grep", "log msg", ctx->qry.grep);
841 html_option("author", "author", ctx->qry.grep); 841 html_option("author", "author", ctx->qry.grep);
842 html_option("committer", "committer", ctx->qry.grep); 842 html_option("committer", "committer", ctx->qry.grep);
843 html_option("range", "range", ctx->qry.grep);
843 html("</select>\n"); 844 html("</select>\n");
844 html("<input class='txt' type='text' size='10' name='q' value='"); 845 html("<input class='txt' type='text' size='10' name='q' value='");
845 html_attr(ctx->qry.search); 846 html_attr(ctx->qry.search);
846 html("'/>\n"); 847 html("'/>\n");
847 html("<input type='submit' value='search'/>\n"); 848 html("<input type='submit' value='search'/>\n");
848 html("</form>\n"); 849 html("</form>\n");
849 } else { 850 } else {
850 site_link(NULL, "index", NULL, hc(ctx, "repolist"), NULL, 0); 851 site_link(NULL, "index", NULL, hc(ctx, "repolist"), NULL, 0);
851 if (ctx->cfg.root_readme) 852 if (ctx->cfg.root_readme)
852 site_link("about", "about", NULL, hc(ctx, "about"), 853 site_link("about", "about", NULL, hc(ctx, "about"),
853 NULL, 0); 854 NULL, 0);
854 html("</td><td class='form'>"); 855 html("</td><td class='form'>");
855 html("<form method='get' action='"); 856 html("<form method='get' action='");
856 html_attr(cgit_rooturl()); 857 html_attr(cgit_rooturl());
857 html("'>\n"); 858 html("'>\n");
858 html("<input type='text' name='q' size='10' value='"); 859 html("<input type='text' name='q' size='10' value='");
859 html_attr(ctx->qry.search); 860 html_attr(ctx->qry.search);
860 html("'/>\n"); 861 html("'/>\n");
861 html("<input type='submit' value='search'/>\n"); 862 html("<input type='submit' value='search'/>\n");
862 html("</form>"); 863 html("</form>");
863 } 864 }
864 html("</td></tr></table>\n"); 865 html("</td></tr></table>\n");
865 if (ctx->qry.vpath) { 866 if (ctx->qry.vpath) {
866 html("<div class='path'>"); 867 html("<div class='path'>");