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,13 +1,13 @@
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#
diff --git a/cgit.c b/cgit.c
index 9452884..c263872 100644
--- a/cgit.c
+++ b/cgit.c
@@ -53,24 +53,26 @@ void repo_config(struct cgit_repo *repo, const char *name, const char *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) {
@@ -132,87 +134,91 @@ void config_cb(const char *name, const char *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);
@@ -241,24 +247,26 @@ static void querystring_cb(const char *name, const char *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
@@ -287,24 +295,25 @@ static void prepare_context(struct cgit_context *ctx)
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";
@@ -689,25 +698,25 @@ static int calc_ttl()
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
diff --git a/cgit.css b/cgit.css
index 28a2eeb..6e47eb3 100644
--- a/cgit.css
+++ b/cgit.css
@@ -522,25 +522,28 @@ a.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;
diff --git a/cgit.h b/cgit.h
index 1bdfbc6..e9e2718 100644
--- a/cgit.h
+++ b/cgit.h
@@ -64,24 +64,25 @@ struct cgit_repo {
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;
@@ -136,24 +137,25 @@ struct cgit_query {
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;
@@ -175,26 +177,28 @@ struct cgit_config {
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;
@@ -291,13 +295,15 @@ extern 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
@@ -106,24 +106,30 @@ enable-log-filecount::
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
@@ -157,24 +163,28 @@ local-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
@@ -312,24 +322,28 @@ repo.desc::
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
diff --git a/cmd.c b/cmd.c
index 605876b..6dc9f5e 100644
--- a/cmd.c
+++ b/cmd.c
@@ -24,25 +24,25 @@
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{
diff --git a/parsing.c b/parsing.c
index f3f3b15..f37c49d 100644
--- a/parsing.c
+++ b/parsing.c
@@ -181,24 +181,28 @@ struct commitinfo *cgit_parse_commit(struct commit *commit)
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;
diff --git a/scan-tree.c b/scan-tree.c
index dbca797..1e18f3c 100644
--- a/scan-tree.c
+++ b/scan-tree.c
@@ -47,24 +47,26 @@ static void repo_config(const char *name, const char *value)
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';
diff --git a/shared.c b/shared.c
index d0973ab..b42c2a2 100644
--- a/shared.c
+++ b/shared.c
@@ -50,24 +50,25 @@ struct cgit_repo *cgit_add_repo(const char *url)
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{
@@ -270,38 +271,46 @@ int cgit_diff_files(const unsigned char *old_sha1,
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);
@@ -419,12 +428,83 @@ int readfile(const char *path, char **buf, size_t *size)
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
@@ -76,25 +76,27 @@ void add_entry(struct commit *commit, char *host)
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;
diff --git a/ui-commit.c b/ui-commit.c
index 2d98ed9..a11bc5f 100644
--- a/ui-commit.c
+++ b/ui-commit.c
@@ -6,28 +6,28 @@
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));
@@ -77,27 +77,30 @@ void cgit_print_commit(char *hex, const char *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 }
diff --git a/ui-log.c b/ui-log.c
index 354ee08..ee93653 100644
--- a/ui-log.c
+++ b/ui-log.c
@@ -141,28 +141,31 @@ void cgit_print_log(const char *tip, int ofs, int cnt, char *grep, char *pattern
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);
diff --git a/ui-plain.c b/ui-plain.c
index 66cb19c..da76406 100644
--- a/ui-plain.c
+++ b/ui-plain.c
@@ -1,26 +1,25 @@
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);
@@ -44,51 +43,104 @@ static void print_object(const unsigned char *sha1, const char *path)
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
@@ -831,24 +831,25 @@ void cgit_print_pageheader(struct cgit_context *ctx)
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'>");