summaryrefslogtreecommitdiffabout
authorLars Hjemli <hjemli@gmail.com>2009-12-08 18:34:30 (UTC)
committer Lars Hjemli <hjemli@gmail.com>2009-12-08 18:34:30 (UTC)
commit4e75d7c9b93010663963d362099777b77a67c446 (patch) (unidiff)
treeb36c95b774e31bd4bb3bcde0123d6522719b7a5a
parentf4f6210b4f9c214dc247567bbd06b1135c9ecd06 (diff)
parent41934a3222cd3e5a5f214e4275929519c70d311d (diff)
downloadcgit-4e75d7c9b93010663963d362099777b77a67c446.zip
cgit-4e75d7c9b93010663963d362099777b77a67c446.tar.gz
cgit-4e75d7c9b93010663963d362099777b77a67c446.tar.bz2
Merge branch 'lh/remote-branches'
Diffstat (more/less context) (ignore whitespace changes)
-rw-r--r--cgit.c4
-rw-r--r--cgit.h2
-rw-r--r--cgitrc.5.txt9
-rw-r--r--shared.c1
-rw-r--r--ui-refs.c2
5 files changed, 18 insertions, 0 deletions
diff --git a/cgit.c b/cgit.c
index 4f68a4b..e46c00a 100644
--- a/cgit.c
+++ b/cgit.c
@@ -1,728 +1,732 @@
1/* cgit.c: cgi for the git scm 1/* cgit.c: cgi for the git scm
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 "cache.h" 10#include "cache.h"
11#include "cmd.h" 11#include "cmd.h"
12#include "configfile.h" 12#include "configfile.h"
13#include "html.h" 13#include "html.h"
14#include "ui-shared.h" 14#include "ui-shared.h"
15#include "ui-stats.h" 15#include "ui-stats.h"
16#include "scan-tree.h" 16#include "scan-tree.h"
17 17
18const char *cgit_version = CGIT_VERSION; 18const char *cgit_version = CGIT_VERSION;
19 19
20void add_mimetype(const char *name, const char *value) 20void add_mimetype(const char *name, const char *value)
21{ 21{
22 struct string_list_item *item; 22 struct string_list_item *item;
23 23
24 item = string_list_insert(xstrdup(name), &ctx.cfg.mimetypes); 24 item = string_list_insert(xstrdup(name), &ctx.cfg.mimetypes);
25 item->util = xstrdup(value); 25 item->util = xstrdup(value);
26} 26}
27 27
28struct cgit_filter *new_filter(const char *cmd, int extra_args) 28struct cgit_filter *new_filter(const char *cmd, int extra_args)
29{ 29{
30 struct cgit_filter *f; 30 struct cgit_filter *f;
31 31
32 if (!cmd || !cmd[0]) 32 if (!cmd || !cmd[0])
33 return NULL; 33 return NULL;
34 34
35 f = xmalloc(sizeof(struct cgit_filter)); 35 f = xmalloc(sizeof(struct cgit_filter));
36 f->cmd = xstrdup(cmd); 36 f->cmd = xstrdup(cmd);
37 f->argv = xmalloc((2 + extra_args) * sizeof(char *)); 37 f->argv = xmalloc((2 + extra_args) * sizeof(char *));
38 f->argv[0] = f->cmd; 38 f->argv[0] = f->cmd;
39 f->argv[1] = NULL; 39 f->argv[1] = NULL;
40 return f; 40 return f;
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"))
64 repo->enable_remote_branches = atoi(value);
63 else if (!strcmp(name, "max-stats")) 65 else if (!strcmp(name, "max-stats"))
64 repo->max_stats = cgit_find_stats_period(value, NULL); 66 repo->max_stats = cgit_find_stats_period(value, NULL);
65 else if (!strcmp(name, "module-link")) 67 else if (!strcmp(name, "module-link"))
66 repo->module_link= xstrdup(value); 68 repo->module_link= xstrdup(value);
67 else if (!strcmp(name, "section")) 69 else if (!strcmp(name, "section"))
68 repo->section = xstrdup(value); 70 repo->section = xstrdup(value);
69 else if (!strcmp(name, "readme") && value != NULL) { 71 else if (!strcmp(name, "readme") && value != NULL) {
70 if (*value == '/') 72 if (*value == '/')
71 repo->readme = xstrdup(value); 73 repo->readme = xstrdup(value);
72 else 74 else
73 repo->readme = xstrdup(fmt("%s/%s", repo->path, value)); 75 repo->readme = xstrdup(fmt("%s/%s", repo->path, value));
74 } else if (ctx.cfg.enable_filter_overrides) { 76 } else if (ctx.cfg.enable_filter_overrides) {
75 if (!strcmp(name, "about-filter")) 77 if (!strcmp(name, "about-filter"))
76 repo->about_filter = new_filter(value, 0); 78 repo->about_filter = new_filter(value, 0);
77 else if (!strcmp(name, "commit-filter")) 79 else if (!strcmp(name, "commit-filter"))
78 repo->commit_filter = new_filter(value, 0); 80 repo->commit_filter = new_filter(value, 0);
79 else if (!strcmp(name, "source-filter")) 81 else if (!strcmp(name, "source-filter"))
80 repo->source_filter = new_filter(value, 1); 82 repo->source_filter = new_filter(value, 1);
81 } 83 }
82} 84}
83 85
84void config_cb(const char *name, const char *value) 86void config_cb(const char *name, const char *value)
85{ 87{
86 if (!strcmp(name, "section") || !strcmp(name, "repo.group")) 88 if (!strcmp(name, "section") || !strcmp(name, "repo.group"))
87 ctx.cfg.section = xstrdup(value); 89 ctx.cfg.section = xstrdup(value);
88 else if (!strcmp(name, "repo.url")) 90 else if (!strcmp(name, "repo.url"))
89 ctx.repo = cgit_add_repo(value); 91 ctx.repo = cgit_add_repo(value);
90 else if (ctx.repo && !strcmp(name, "repo.path")) 92 else if (ctx.repo && !strcmp(name, "repo.path"))
91 ctx.repo->path = trim_end(value, '/'); 93 ctx.repo->path = trim_end(value, '/');
92 else if (ctx.repo && !prefixcmp(name, "repo.")) 94 else if (ctx.repo && !prefixcmp(name, "repo."))
93 repo_config(ctx.repo, name + 5, value); 95 repo_config(ctx.repo, name + 5, value);
94 else if (!strcmp(name, "root-title")) 96 else if (!strcmp(name, "root-title"))
95 ctx.cfg.root_title = xstrdup(value); 97 ctx.cfg.root_title = xstrdup(value);
96 else if (!strcmp(name, "root-desc")) 98 else if (!strcmp(name, "root-desc"))
97 ctx.cfg.root_desc = xstrdup(value); 99 ctx.cfg.root_desc = xstrdup(value);
98 else if (!strcmp(name, "root-readme")) 100 else if (!strcmp(name, "root-readme"))
99 ctx.cfg.root_readme = xstrdup(value); 101 ctx.cfg.root_readme = xstrdup(value);
100 else if (!strcmp(name, "css")) 102 else if (!strcmp(name, "css"))
101 ctx.cfg.css = xstrdup(value); 103 ctx.cfg.css = xstrdup(value);
102 else if (!strcmp(name, "favicon")) 104 else if (!strcmp(name, "favicon"))
103 ctx.cfg.favicon = xstrdup(value); 105 ctx.cfg.favicon = xstrdup(value);
104 else if (!strcmp(name, "footer")) 106 else if (!strcmp(name, "footer"))
105 ctx.cfg.footer = xstrdup(value); 107 ctx.cfg.footer = xstrdup(value);
106 else if (!strcmp(name, "head-include")) 108 else if (!strcmp(name, "head-include"))
107 ctx.cfg.head_include = xstrdup(value); 109 ctx.cfg.head_include = xstrdup(value);
108 else if (!strcmp(name, "header")) 110 else if (!strcmp(name, "header"))
109 ctx.cfg.header = xstrdup(value); 111 ctx.cfg.header = xstrdup(value);
110 else if (!strcmp(name, "logo")) 112 else if (!strcmp(name, "logo"))
111 ctx.cfg.logo = xstrdup(value); 113 ctx.cfg.logo = xstrdup(value);
112 else if (!strcmp(name, "index-header")) 114 else if (!strcmp(name, "index-header"))
113 ctx.cfg.index_header = xstrdup(value); 115 ctx.cfg.index_header = xstrdup(value);
114 else if (!strcmp(name, "index-info")) 116 else if (!strcmp(name, "index-info"))
115 ctx.cfg.index_info = xstrdup(value); 117 ctx.cfg.index_info = xstrdup(value);
116 else if (!strcmp(name, "logo-link")) 118 else if (!strcmp(name, "logo-link"))
117 ctx.cfg.logo_link = xstrdup(value); 119 ctx.cfg.logo_link = xstrdup(value);
118 else if (!strcmp(name, "module-link")) 120 else if (!strcmp(name, "module-link"))
119 ctx.cfg.module_link = xstrdup(value); 121 ctx.cfg.module_link = xstrdup(value);
120 else if (!strcmp(name, "virtual-root")) { 122 else if (!strcmp(name, "virtual-root")) {
121 ctx.cfg.virtual_root = trim_end(value, '/'); 123 ctx.cfg.virtual_root = trim_end(value, '/');
122 if (!ctx.cfg.virtual_root && (!strcmp(value, "/"))) 124 if (!ctx.cfg.virtual_root && (!strcmp(value, "/")))
123 ctx.cfg.virtual_root = ""; 125 ctx.cfg.virtual_root = "";
124 } else if (!strcmp(name, "nocache")) 126 } else if (!strcmp(name, "nocache"))
125 ctx.cfg.nocache = atoi(value); 127 ctx.cfg.nocache = atoi(value);
126 else if (!strcmp(name, "noplainemail")) 128 else if (!strcmp(name, "noplainemail"))
127 ctx.cfg.noplainemail = atoi(value); 129 ctx.cfg.noplainemail = atoi(value);
128 else if (!strcmp(name, "noheader")) 130 else if (!strcmp(name, "noheader"))
129 ctx.cfg.noheader = atoi(value); 131 ctx.cfg.noheader = atoi(value);
130 else if (!strcmp(name, "snapshots")) 132 else if (!strcmp(name, "snapshots"))
131 ctx.cfg.snapshots = cgit_parse_snapshots_mask(value); 133 ctx.cfg.snapshots = cgit_parse_snapshots_mask(value);
132 else if (!strcmp(name, "enable-filter-overrides")) 134 else if (!strcmp(name, "enable-filter-overrides"))
133 ctx.cfg.enable_filter_overrides = atoi(value); 135 ctx.cfg.enable_filter_overrides = atoi(value);
134 else if (!strcmp(name, "enable-index-links")) 136 else if (!strcmp(name, "enable-index-links"))
135 ctx.cfg.enable_index_links = atoi(value); 137 ctx.cfg.enable_index_links = atoi(value);
136 else if (!strcmp(name, "enable-log-filecount")) 138 else if (!strcmp(name, "enable-log-filecount"))
137 ctx.cfg.enable_log_filecount = atoi(value); 139 ctx.cfg.enable_log_filecount = atoi(value);
138 else if (!strcmp(name, "enable-log-linecount")) 140 else if (!strcmp(name, "enable-log-linecount"))
139 ctx.cfg.enable_log_linecount = atoi(value); 141 ctx.cfg.enable_log_linecount = atoi(value);
142 else if (!strcmp(name, "enable-remote-branches"))
143 ctx.cfg.enable_remote_branches = atoi(value);
140 else if (!strcmp(name, "enable-tree-linenumbers")) 144 else if (!strcmp(name, "enable-tree-linenumbers"))
141 ctx.cfg.enable_tree_linenumbers = atoi(value); 145 ctx.cfg.enable_tree_linenumbers = atoi(value);
142 else if (!strcmp(name, "max-stats")) 146 else if (!strcmp(name, "max-stats"))
143 ctx.cfg.max_stats = cgit_find_stats_period(value, NULL); 147 ctx.cfg.max_stats = cgit_find_stats_period(value, NULL);
144 else if (!strcmp(name, "cache-size")) 148 else if (!strcmp(name, "cache-size"))
145 ctx.cfg.cache_size = atoi(value); 149 ctx.cfg.cache_size = atoi(value);
146 else if (!strcmp(name, "cache-root")) 150 else if (!strcmp(name, "cache-root"))
147 ctx.cfg.cache_root = xstrdup(value); 151 ctx.cfg.cache_root = xstrdup(value);
148 else if (!strcmp(name, "cache-root-ttl")) 152 else if (!strcmp(name, "cache-root-ttl"))
149 ctx.cfg.cache_root_ttl = atoi(value); 153 ctx.cfg.cache_root_ttl = atoi(value);
150 else if (!strcmp(name, "cache-repo-ttl")) 154 else if (!strcmp(name, "cache-repo-ttl"))
151 ctx.cfg.cache_repo_ttl = atoi(value); 155 ctx.cfg.cache_repo_ttl = atoi(value);
152 else if (!strcmp(name, "cache-scanrc-ttl")) 156 else if (!strcmp(name, "cache-scanrc-ttl"))
153 ctx.cfg.cache_scanrc_ttl = atoi(value); 157 ctx.cfg.cache_scanrc_ttl = atoi(value);
154 else if (!strcmp(name, "cache-static-ttl")) 158 else if (!strcmp(name, "cache-static-ttl"))
155 ctx.cfg.cache_static_ttl = atoi(value); 159 ctx.cfg.cache_static_ttl = atoi(value);
156 else if (!strcmp(name, "cache-dynamic-ttl")) 160 else if (!strcmp(name, "cache-dynamic-ttl"))
157 ctx.cfg.cache_dynamic_ttl = atoi(value); 161 ctx.cfg.cache_dynamic_ttl = atoi(value);
158 else if (!strcmp(name, "about-filter")) 162 else if (!strcmp(name, "about-filter"))
159 ctx.cfg.about_filter = new_filter(value, 0); 163 ctx.cfg.about_filter = new_filter(value, 0);
160 else if (!strcmp(name, "commit-filter")) 164 else if (!strcmp(name, "commit-filter"))
161 ctx.cfg.commit_filter = new_filter(value, 0); 165 ctx.cfg.commit_filter = new_filter(value, 0);
162 else if (!strcmp(name, "embedded")) 166 else if (!strcmp(name, "embedded"))
163 ctx.cfg.embedded = atoi(value); 167 ctx.cfg.embedded = atoi(value);
164 else if (!strcmp(name, "max-message-length")) 168 else if (!strcmp(name, "max-message-length"))
165 ctx.cfg.max_msg_len = atoi(value); 169 ctx.cfg.max_msg_len = atoi(value);
166 else if (!strcmp(name, "max-repodesc-length")) 170 else if (!strcmp(name, "max-repodesc-length"))
167 ctx.cfg.max_repodesc_len = atoi(value); 171 ctx.cfg.max_repodesc_len = atoi(value);
168 else if (!strcmp(name, "max-blob-size")) 172 else if (!strcmp(name, "max-blob-size"))
169 ctx.cfg.max_blob_size = atoi(value); 173 ctx.cfg.max_blob_size = atoi(value);
170 else if (!strcmp(name, "max-repo-count")) 174 else if (!strcmp(name, "max-repo-count"))
171 ctx.cfg.max_repo_count = atoi(value); 175 ctx.cfg.max_repo_count = atoi(value);
172 else if (!strcmp(name, "max-commit-count")) 176 else if (!strcmp(name, "max-commit-count"))
173 ctx.cfg.max_commit_count = atoi(value); 177 ctx.cfg.max_commit_count = atoi(value);
174 else if (!strcmp(name, "scan-path")) 178 else if (!strcmp(name, "scan-path"))
175 if (!ctx.cfg.nocache && ctx.cfg.cache_size) 179 if (!ctx.cfg.nocache && ctx.cfg.cache_size)
176 process_cached_repolist(value); 180 process_cached_repolist(value);
177 else 181 else
178 scan_tree(value, repo_config); 182 scan_tree(value, repo_config);
179 else if (!strcmp(name, "source-filter")) 183 else if (!strcmp(name, "source-filter"))
180 ctx.cfg.source_filter = new_filter(value, 1); 184 ctx.cfg.source_filter = new_filter(value, 1);
181 else if (!strcmp(name, "summary-log")) 185 else if (!strcmp(name, "summary-log"))
182 ctx.cfg.summary_log = atoi(value); 186 ctx.cfg.summary_log = atoi(value);
183 else if (!strcmp(name, "summary-branches")) 187 else if (!strcmp(name, "summary-branches"))
184 ctx.cfg.summary_branches = atoi(value); 188 ctx.cfg.summary_branches = atoi(value);
185 else if (!strcmp(name, "summary-tags")) 189 else if (!strcmp(name, "summary-tags"))
186 ctx.cfg.summary_tags = atoi(value); 190 ctx.cfg.summary_tags = atoi(value);
187 else if (!strcmp(name, "side-by-side-diffs")) 191 else if (!strcmp(name, "side-by-side-diffs"))
188 ctx.cfg.ssdiff = atoi(value); 192 ctx.cfg.ssdiff = atoi(value);
189 else if (!strcmp(name, "agefile")) 193 else if (!strcmp(name, "agefile"))
190 ctx.cfg.agefile = xstrdup(value); 194 ctx.cfg.agefile = xstrdup(value);
191 else if (!strcmp(name, "renamelimit")) 195 else if (!strcmp(name, "renamelimit"))
192 ctx.cfg.renamelimit = atoi(value); 196 ctx.cfg.renamelimit = atoi(value);
193 else if (!strcmp(name, "robots")) 197 else if (!strcmp(name, "robots"))
194 ctx.cfg.robots = xstrdup(value); 198 ctx.cfg.robots = xstrdup(value);
195 else if (!strcmp(name, "clone-prefix")) 199 else if (!strcmp(name, "clone-prefix"))
196 ctx.cfg.clone_prefix = xstrdup(value); 200 ctx.cfg.clone_prefix = xstrdup(value);
197 else if (!strcmp(name, "local-time")) 201 else if (!strcmp(name, "local-time"))
198 ctx.cfg.local_time = atoi(value); 202 ctx.cfg.local_time = atoi(value);
199 else if (!prefixcmp(name, "mimetype.")) 203 else if (!prefixcmp(name, "mimetype."))
200 add_mimetype(name + 9, value); 204 add_mimetype(name + 9, value);
201 else if (!strcmp(name, "include")) 205 else if (!strcmp(name, "include"))
202 parse_configfile(value, config_cb); 206 parse_configfile(value, config_cb);
203} 207}
204 208
205static void querystring_cb(const char *name, const char *value) 209static void querystring_cb(const char *name, const char *value)
206{ 210{
207 if (!value) 211 if (!value)
208 value = ""; 212 value = "";
209 213
210 if (!strcmp(name,"r")) { 214 if (!strcmp(name,"r")) {
211 ctx.qry.repo = xstrdup(value); 215 ctx.qry.repo = xstrdup(value);
212 ctx.repo = cgit_get_repoinfo(value); 216 ctx.repo = cgit_get_repoinfo(value);
213 } else if (!strcmp(name, "p")) { 217 } else if (!strcmp(name, "p")) {
214 ctx.qry.page = xstrdup(value); 218 ctx.qry.page = xstrdup(value);
215 } else if (!strcmp(name, "url")) { 219 } else if (!strcmp(name, "url")) {
216 if (*value == '/') 220 if (*value == '/')
217 value++; 221 value++;
218 ctx.qry.url = xstrdup(value); 222 ctx.qry.url = xstrdup(value);
219 cgit_parse_url(value); 223 cgit_parse_url(value);
220 } else if (!strcmp(name, "qt")) { 224 } else if (!strcmp(name, "qt")) {
221 ctx.qry.grep = xstrdup(value); 225 ctx.qry.grep = xstrdup(value);
222 } else if (!strcmp(name, "q")) { 226 } else if (!strcmp(name, "q")) {
223 ctx.qry.search = xstrdup(value); 227 ctx.qry.search = xstrdup(value);
224 } else if (!strcmp(name, "h")) { 228 } else if (!strcmp(name, "h")) {
225 ctx.qry.head = xstrdup(value); 229 ctx.qry.head = xstrdup(value);
226 ctx.qry.has_symref = 1; 230 ctx.qry.has_symref = 1;
227 } else if (!strcmp(name, "id")) { 231 } else if (!strcmp(name, "id")) {
228 ctx.qry.sha1 = xstrdup(value); 232 ctx.qry.sha1 = xstrdup(value);
229 ctx.qry.has_sha1 = 1; 233 ctx.qry.has_sha1 = 1;
230 } else if (!strcmp(name, "id2")) { 234 } else if (!strcmp(name, "id2")) {
231 ctx.qry.sha2 = xstrdup(value); 235 ctx.qry.sha2 = xstrdup(value);
232 ctx.qry.has_sha1 = 1; 236 ctx.qry.has_sha1 = 1;
233 } else if (!strcmp(name, "ofs")) { 237 } else if (!strcmp(name, "ofs")) {
234 ctx.qry.ofs = atoi(value); 238 ctx.qry.ofs = atoi(value);
235 } else if (!strcmp(name, "path")) { 239 } else if (!strcmp(name, "path")) {
236 ctx.qry.path = trim_end(value, '/'); 240 ctx.qry.path = trim_end(value, '/');
237 } else if (!strcmp(name, "name")) { 241 } else if (!strcmp(name, "name")) {
238 ctx.qry.name = xstrdup(value); 242 ctx.qry.name = xstrdup(value);
239 } else if (!strcmp(name, "mimetype")) { 243 } else if (!strcmp(name, "mimetype")) {
240 ctx.qry.mimetype = xstrdup(value); 244 ctx.qry.mimetype = xstrdup(value);
241 } else if (!strcmp(name, "s")){ 245 } else if (!strcmp(name, "s")){
242 ctx.qry.sort = xstrdup(value); 246 ctx.qry.sort = xstrdup(value);
243 } else if (!strcmp(name, "showmsg")) { 247 } else if (!strcmp(name, "showmsg")) {
244 ctx.qry.showmsg = atoi(value); 248 ctx.qry.showmsg = atoi(value);
245 } else if (!strcmp(name, "period")) { 249 } else if (!strcmp(name, "period")) {
246 ctx.qry.period = xstrdup(value); 250 ctx.qry.period = xstrdup(value);
247 } else if (!strcmp(name, "ss")) { 251 } else if (!strcmp(name, "ss")) {
248 ctx.qry.ssdiff = atoi(value); 252 ctx.qry.ssdiff = atoi(value);
249 } 253 }
250} 254}
251 255
252char *xstrdupn(const char *str) 256char *xstrdupn(const char *str)
253{ 257{
254 return (str ? xstrdup(str) : NULL); 258 return (str ? xstrdup(str) : NULL);
255} 259}
256 260
257static void prepare_context(struct cgit_context *ctx) 261static void prepare_context(struct cgit_context *ctx)
258{ 262{
259 memset(ctx, 0, sizeof(ctx)); 263 memset(ctx, 0, sizeof(ctx));
260 ctx->cfg.agefile = "info/web/last-modified"; 264 ctx->cfg.agefile = "info/web/last-modified";
261 ctx->cfg.nocache = 0; 265 ctx->cfg.nocache = 0;
262 ctx->cfg.cache_size = 0; 266 ctx->cfg.cache_size = 0;
263 ctx->cfg.cache_dynamic_ttl = 5; 267 ctx->cfg.cache_dynamic_ttl = 5;
264 ctx->cfg.cache_max_create_time = 5; 268 ctx->cfg.cache_max_create_time = 5;
265 ctx->cfg.cache_repo_ttl = 5; 269 ctx->cfg.cache_repo_ttl = 5;
266 ctx->cfg.cache_root = CGIT_CACHE_ROOT; 270 ctx->cfg.cache_root = CGIT_CACHE_ROOT;
267 ctx->cfg.cache_root_ttl = 5; 271 ctx->cfg.cache_root_ttl = 5;
268 ctx->cfg.cache_scanrc_ttl = 15; 272 ctx->cfg.cache_scanrc_ttl = 15;
269 ctx->cfg.cache_static_ttl = -1; 273 ctx->cfg.cache_static_ttl = -1;
270 ctx->cfg.css = "/cgit.css"; 274 ctx->cfg.css = "/cgit.css";
271 ctx->cfg.logo = "/cgit.png"; 275 ctx->cfg.logo = "/cgit.png";
272 ctx->cfg.local_time = 0; 276 ctx->cfg.local_time = 0;
273 ctx->cfg.enable_tree_linenumbers = 1; 277 ctx->cfg.enable_tree_linenumbers = 1;
274 ctx->cfg.max_repo_count = 50; 278 ctx->cfg.max_repo_count = 50;
275 ctx->cfg.max_commit_count = 50; 279 ctx->cfg.max_commit_count = 50;
276 ctx->cfg.max_lock_attempts = 5; 280 ctx->cfg.max_lock_attempts = 5;
277 ctx->cfg.max_msg_len = 80; 281 ctx->cfg.max_msg_len = 80;
278 ctx->cfg.max_repodesc_len = 80; 282 ctx->cfg.max_repodesc_len = 80;
279 ctx->cfg.max_blob_size = 0; 283 ctx->cfg.max_blob_size = 0;
280 ctx->cfg.max_stats = 0; 284 ctx->cfg.max_stats = 0;
281 ctx->cfg.module_link = "./?repo=%s&page=commit&id=%s"; 285 ctx->cfg.module_link = "./?repo=%s&page=commit&id=%s";
282 ctx->cfg.renamelimit = -1; 286 ctx->cfg.renamelimit = -1;
283 ctx->cfg.robots = "index, nofollow"; 287 ctx->cfg.robots = "index, nofollow";
284 ctx->cfg.root_title = "Git repository browser"; 288 ctx->cfg.root_title = "Git repository browser";
285 ctx->cfg.root_desc = "a fast webinterface for the git dscm"; 289 ctx->cfg.root_desc = "a fast webinterface for the git dscm";
286 ctx->cfg.script_name = CGIT_SCRIPT_NAME; 290 ctx->cfg.script_name = CGIT_SCRIPT_NAME;
287 ctx->cfg.section = ""; 291 ctx->cfg.section = "";
288 ctx->cfg.summary_branches = 10; 292 ctx->cfg.summary_branches = 10;
289 ctx->cfg.summary_log = 10; 293 ctx->cfg.summary_log = 10;
290 ctx->cfg.summary_tags = 10; 294 ctx->cfg.summary_tags = 10;
291 ctx->cfg.ssdiff = 0; 295 ctx->cfg.ssdiff = 0;
292 ctx->env.cgit_config = xstrdupn(getenv("CGIT_CONFIG")); 296 ctx->env.cgit_config = xstrdupn(getenv("CGIT_CONFIG"));
293 ctx->env.http_host = xstrdupn(getenv("HTTP_HOST")); 297 ctx->env.http_host = xstrdupn(getenv("HTTP_HOST"));
294 ctx->env.https = xstrdupn(getenv("HTTPS")); 298 ctx->env.https = xstrdupn(getenv("HTTPS"));
295 ctx->env.no_http = xstrdupn(getenv("NO_HTTP")); 299 ctx->env.no_http = xstrdupn(getenv("NO_HTTP"));
296 ctx->env.path_info = xstrdupn(getenv("PATH_INFO")); 300 ctx->env.path_info = xstrdupn(getenv("PATH_INFO"));
297 ctx->env.query_string = xstrdupn(getenv("QUERY_STRING")); 301 ctx->env.query_string = xstrdupn(getenv("QUERY_STRING"));
298 ctx->env.request_method = xstrdupn(getenv("REQUEST_METHOD")); 302 ctx->env.request_method = xstrdupn(getenv("REQUEST_METHOD"));
299 ctx->env.script_name = xstrdupn(getenv("SCRIPT_NAME")); 303 ctx->env.script_name = xstrdupn(getenv("SCRIPT_NAME"));
300 ctx->env.server_name = xstrdupn(getenv("SERVER_NAME")); 304 ctx->env.server_name = xstrdupn(getenv("SERVER_NAME"));
301 ctx->env.server_port = xstrdupn(getenv("SERVER_PORT")); 305 ctx->env.server_port = xstrdupn(getenv("SERVER_PORT"));
302 ctx->page.mimetype = "text/html"; 306 ctx->page.mimetype = "text/html";
303 ctx->page.charset = PAGE_ENCODING; 307 ctx->page.charset = PAGE_ENCODING;
304 ctx->page.filename = NULL; 308 ctx->page.filename = NULL;
305 ctx->page.size = 0; 309 ctx->page.size = 0;
306 ctx->page.modified = time(NULL); 310 ctx->page.modified = time(NULL);
307 ctx->page.expires = ctx->page.modified; 311 ctx->page.expires = ctx->page.modified;
308 ctx->page.etag = NULL; 312 ctx->page.etag = NULL;
309 memset(&ctx->cfg.mimetypes, 0, sizeof(struct string_list)); 313 memset(&ctx->cfg.mimetypes, 0, sizeof(struct string_list));
310 if (ctx->env.script_name) 314 if (ctx->env.script_name)
311 ctx->cfg.script_name = ctx->env.script_name; 315 ctx->cfg.script_name = ctx->env.script_name;
312 if (ctx->env.query_string) 316 if (ctx->env.query_string)
313 ctx->qry.raw = ctx->env.query_string; 317 ctx->qry.raw = ctx->env.query_string;
314 if (!ctx->env.cgit_config) 318 if (!ctx->env.cgit_config)
315 ctx->env.cgit_config = CGIT_CONFIG; 319 ctx->env.cgit_config = CGIT_CONFIG;
316} 320}
317 321
318struct refmatch { 322struct refmatch {
319 char *req_ref; 323 char *req_ref;
320 char *first_ref; 324 char *first_ref;
321 int match; 325 int match;
322}; 326};
323 327
324int find_current_ref(const char *refname, const unsigned char *sha1, 328int find_current_ref(const char *refname, const unsigned char *sha1,
325 int flags, void *cb_data) 329 int flags, void *cb_data)
326{ 330{
327 struct refmatch *info; 331 struct refmatch *info;
328 332
329 info = (struct refmatch *)cb_data; 333 info = (struct refmatch *)cb_data;
330 if (!strcmp(refname, info->req_ref)) 334 if (!strcmp(refname, info->req_ref))
331 info->match = 1; 335 info->match = 1;
332 if (!info->first_ref) 336 if (!info->first_ref)
333 info->first_ref = xstrdup(refname); 337 info->first_ref = xstrdup(refname);
334 return info->match; 338 return info->match;
335} 339}
336 340
337char *find_default_branch(struct cgit_repo *repo) 341char *find_default_branch(struct cgit_repo *repo)
338{ 342{
339 struct refmatch info; 343 struct refmatch info;
340 char *ref; 344 char *ref;
341 345
342 info.req_ref = repo->defbranch; 346 info.req_ref = repo->defbranch;
343 info.first_ref = NULL; 347 info.first_ref = NULL;
344 info.match = 0; 348 info.match = 0;
345 for_each_branch_ref(find_current_ref, &info); 349 for_each_branch_ref(find_current_ref, &info);
346 if (info.match) 350 if (info.match)
347 ref = info.req_ref; 351 ref = info.req_ref;
348 else 352 else
349 ref = info.first_ref; 353 ref = info.first_ref;
350 if (ref) 354 if (ref)
351 ref = xstrdup(ref); 355 ref = xstrdup(ref);
352 return ref; 356 return ref;
353} 357}
354 358
355static int prepare_repo_cmd(struct cgit_context *ctx) 359static int prepare_repo_cmd(struct cgit_context *ctx)
356{ 360{
357 char *tmp; 361 char *tmp;
358 unsigned char sha1[20]; 362 unsigned char sha1[20];
359 int nongit = 0; 363 int nongit = 0;
360 364
361 setenv("GIT_DIR", ctx->repo->path, 1); 365 setenv("GIT_DIR", ctx->repo->path, 1);
362 setup_git_directory_gently(&nongit); 366 setup_git_directory_gently(&nongit);
363 if (nongit) { 367 if (nongit) {
364 ctx->page.title = fmt("%s - %s", ctx->cfg.root_title, 368 ctx->page.title = fmt("%s - %s", ctx->cfg.root_title,
365 "config error"); 369 "config error");
366 tmp = fmt("Not a git repository: '%s'", ctx->repo->path); 370 tmp = fmt("Not a git repository: '%s'", ctx->repo->path);
367 ctx->repo = NULL; 371 ctx->repo = NULL;
368 cgit_print_http_headers(ctx); 372 cgit_print_http_headers(ctx);
369 cgit_print_docstart(ctx); 373 cgit_print_docstart(ctx);
370 cgit_print_pageheader(ctx); 374 cgit_print_pageheader(ctx);
371 cgit_print_error(tmp); 375 cgit_print_error(tmp);
372 cgit_print_docend(); 376 cgit_print_docend();
373 return 1; 377 return 1;
374 } 378 }
375 ctx->page.title = fmt("%s - %s", ctx->repo->name, ctx->repo->desc); 379 ctx->page.title = fmt("%s - %s", ctx->repo->name, ctx->repo->desc);
376 380
377 if (!ctx->qry.head) { 381 if (!ctx->qry.head) {
378 ctx->qry.nohead = 1; 382 ctx->qry.nohead = 1;
379 ctx->qry.head = find_default_branch(ctx->repo); 383 ctx->qry.head = find_default_branch(ctx->repo);
380 ctx->repo->defbranch = ctx->qry.head; 384 ctx->repo->defbranch = ctx->qry.head;
381 } 385 }
382 386
383 if (!ctx->qry.head) { 387 if (!ctx->qry.head) {
384 cgit_print_http_headers(ctx); 388 cgit_print_http_headers(ctx);
385 cgit_print_docstart(ctx); 389 cgit_print_docstart(ctx);
386 cgit_print_pageheader(ctx); 390 cgit_print_pageheader(ctx);
387 cgit_print_error("Repository seems to be empty"); 391 cgit_print_error("Repository seems to be empty");
388 cgit_print_docend(); 392 cgit_print_docend();
389 return 1; 393 return 1;
390 } 394 }
391 395
392 if (get_sha1(ctx->qry.head, sha1)) { 396 if (get_sha1(ctx->qry.head, sha1)) {
393 tmp = xstrdup(ctx->qry.head); 397 tmp = xstrdup(ctx->qry.head);
394 ctx->qry.head = ctx->repo->defbranch; 398 ctx->qry.head = ctx->repo->defbranch;
395 ctx->page.status = 404; 399 ctx->page.status = 404;
396 ctx->page.statusmsg = "not found"; 400 ctx->page.statusmsg = "not found";
397 cgit_print_http_headers(ctx); 401 cgit_print_http_headers(ctx);
398 cgit_print_docstart(ctx); 402 cgit_print_docstart(ctx);
399 cgit_print_pageheader(ctx); 403 cgit_print_pageheader(ctx);
400 cgit_print_error(fmt("Invalid branch: %s", tmp)); 404 cgit_print_error(fmt("Invalid branch: %s", tmp));
401 cgit_print_docend(); 405 cgit_print_docend();
402 return 1; 406 return 1;
403 } 407 }
404 return 0; 408 return 0;
405} 409}
406 410
407static void process_request(void *cbdata) 411static void process_request(void *cbdata)
408{ 412{
409 struct cgit_context *ctx = cbdata; 413 struct cgit_context *ctx = cbdata;
410 struct cgit_cmd *cmd; 414 struct cgit_cmd *cmd;
411 415
412 cmd = cgit_get_cmd(ctx); 416 cmd = cgit_get_cmd(ctx);
413 if (!cmd) { 417 if (!cmd) {
414 ctx->page.title = "cgit error"; 418 ctx->page.title = "cgit error";
415 cgit_print_http_headers(ctx); 419 cgit_print_http_headers(ctx);
416 cgit_print_docstart(ctx); 420 cgit_print_docstart(ctx);
417 cgit_print_pageheader(ctx); 421 cgit_print_pageheader(ctx);
418 cgit_print_error("Invalid request"); 422 cgit_print_error("Invalid request");
419 cgit_print_docend(); 423 cgit_print_docend();
420 return; 424 return;
421 } 425 }
422 426
423 if (cmd->want_repo && !ctx->repo) { 427 if (cmd->want_repo && !ctx->repo) {
424 cgit_print_http_headers(ctx); 428 cgit_print_http_headers(ctx);
425 cgit_print_docstart(ctx); 429 cgit_print_docstart(ctx);
426 cgit_print_pageheader(ctx); 430 cgit_print_pageheader(ctx);
427 cgit_print_error(fmt("No repository selected")); 431 cgit_print_error(fmt("No repository selected"));
428 cgit_print_docend(); 432 cgit_print_docend();
429 return; 433 return;
430 } 434 }
431 435
432 if (ctx->repo && prepare_repo_cmd(ctx)) 436 if (ctx->repo && prepare_repo_cmd(ctx))
433 return; 437 return;
434 438
435 if (cmd->want_layout) { 439 if (cmd->want_layout) {
436 cgit_print_http_headers(ctx); 440 cgit_print_http_headers(ctx);
437 cgit_print_docstart(ctx); 441 cgit_print_docstart(ctx);
438 cgit_print_pageheader(ctx); 442 cgit_print_pageheader(ctx);
439 } 443 }
440 444
441 cmd->fn(ctx); 445 cmd->fn(ctx);
442 446
443 if (cmd->want_layout) 447 if (cmd->want_layout)
444 cgit_print_docend(); 448 cgit_print_docend();
445} 449}
446 450
447int cmp_repos(const void *a, const void *b) 451int cmp_repos(const void *a, const void *b)
448{ 452{
449 const struct cgit_repo *ra = a, *rb = b; 453 const struct cgit_repo *ra = a, *rb = b;
450 return strcmp(ra->url, rb->url); 454 return strcmp(ra->url, rb->url);
451} 455}
452 456
453char *build_snapshot_setting(int bitmap) 457char *build_snapshot_setting(int bitmap)
454{ 458{
455 const struct cgit_snapshot_format *f; 459 const struct cgit_snapshot_format *f;
456 char *result = xstrdup(""); 460 char *result = xstrdup("");
457 char *tmp; 461 char *tmp;
458 int len; 462 int len;
459 463
460 for (f = cgit_snapshot_formats; f->suffix; f++) { 464 for (f = cgit_snapshot_formats; f->suffix; f++) {
461 if (f->bit & bitmap) { 465 if (f->bit & bitmap) {
462 tmp = result; 466 tmp = result;
463 result = xstrdup(fmt("%s%s ", tmp, f->suffix)); 467 result = xstrdup(fmt("%s%s ", tmp, f->suffix));
464 free(tmp); 468 free(tmp);
465 } 469 }
466 } 470 }
467 len = strlen(result); 471 len = strlen(result);
468 if (len) 472 if (len)
469 result[len - 1] = '\0'; 473 result[len - 1] = '\0';
470 return result; 474 return result;
471} 475}
472 476
473char *get_first_line(char *txt) 477char *get_first_line(char *txt)
474{ 478{
475 char *t = xstrdup(txt); 479 char *t = xstrdup(txt);
476 char *p = strchr(t, '\n'); 480 char *p = strchr(t, '\n');
477 if (p) 481 if (p)
478 *p = '\0'; 482 *p = '\0';
479 return t; 483 return t;
480} 484}
481 485
482void print_repo(FILE *f, struct cgit_repo *repo) 486void print_repo(FILE *f, struct cgit_repo *repo)
483{ 487{
484 fprintf(f, "repo.url=%s\n", repo->url); 488 fprintf(f, "repo.url=%s\n", repo->url);
485 fprintf(f, "repo.name=%s\n", repo->name); 489 fprintf(f, "repo.name=%s\n", repo->name);
486 fprintf(f, "repo.path=%s\n", repo->path); 490 fprintf(f, "repo.path=%s\n", repo->path);
487 if (repo->owner) 491 if (repo->owner)
488 fprintf(f, "repo.owner=%s\n", repo->owner); 492 fprintf(f, "repo.owner=%s\n", repo->owner);
489 if (repo->desc) { 493 if (repo->desc) {
490 char *tmp = get_first_line(repo->desc); 494 char *tmp = get_first_line(repo->desc);
491 fprintf(f, "repo.desc=%s\n", tmp); 495 fprintf(f, "repo.desc=%s\n", tmp);
492 free(tmp); 496 free(tmp);
493 } 497 }
494 if (repo->readme) 498 if (repo->readme)
495 fprintf(f, "repo.readme=%s\n", repo->readme); 499 fprintf(f, "repo.readme=%s\n", repo->readme);
496 if (repo->defbranch) 500 if (repo->defbranch)
497 fprintf(f, "repo.defbranch=%s\n", repo->defbranch); 501 fprintf(f, "repo.defbranch=%s\n", repo->defbranch);
498 if (repo->module_link) 502 if (repo->module_link)
499 fprintf(f, "repo.module-link=%s\n", repo->module_link); 503 fprintf(f, "repo.module-link=%s\n", repo->module_link);
500 if (repo->section) 504 if (repo->section)
501 fprintf(f, "repo.section=%s\n", repo->section); 505 fprintf(f, "repo.section=%s\n", repo->section);
502 if (repo->clone_url) 506 if (repo->clone_url)
503 fprintf(f, "repo.clone-url=%s\n", repo->clone_url); 507 fprintf(f, "repo.clone-url=%s\n", repo->clone_url);
504 fprintf(f, "repo.enable-log-filecount=%d\n", 508 fprintf(f, "repo.enable-log-filecount=%d\n",
505 repo->enable_log_filecount); 509 repo->enable_log_filecount);
506 fprintf(f, "repo.enable-log-linecount=%d\n", 510 fprintf(f, "repo.enable-log-linecount=%d\n",
507 repo->enable_log_linecount); 511 repo->enable_log_linecount);
508 if (repo->about_filter && repo->about_filter != ctx.cfg.about_filter) 512 if (repo->about_filter && repo->about_filter != ctx.cfg.about_filter)
509 fprintf(f, "repo.about-filter=%s\n", repo->about_filter->cmd); 513 fprintf(f, "repo.about-filter=%s\n", repo->about_filter->cmd);
510 if (repo->commit_filter && repo->commit_filter != ctx.cfg.commit_filter) 514 if (repo->commit_filter && repo->commit_filter != ctx.cfg.commit_filter)
511 fprintf(f, "repo.commit-filter=%s\n", repo->commit_filter->cmd); 515 fprintf(f, "repo.commit-filter=%s\n", repo->commit_filter->cmd);
512 if (repo->source_filter && repo->source_filter != ctx.cfg.source_filter) 516 if (repo->source_filter && repo->source_filter != ctx.cfg.source_filter)
513 fprintf(f, "repo.source-filter=%s\n", repo->source_filter->cmd); 517 fprintf(f, "repo.source-filter=%s\n", repo->source_filter->cmd);
514 if (repo->snapshots != ctx.cfg.snapshots) { 518 if (repo->snapshots != ctx.cfg.snapshots) {
515 char *tmp = build_snapshot_setting(repo->snapshots); 519 char *tmp = build_snapshot_setting(repo->snapshots);
516 fprintf(f, "repo.snapshots=%s\n", tmp); 520 fprintf(f, "repo.snapshots=%s\n", tmp);
517 free(tmp); 521 free(tmp);
518 } 522 }
519 if (repo->max_stats != ctx.cfg.max_stats) 523 if (repo->max_stats != ctx.cfg.max_stats)
520 fprintf(f, "repo.max-stats=%s\n", 524 fprintf(f, "repo.max-stats=%s\n",
521 cgit_find_stats_periodname(repo->max_stats)); 525 cgit_find_stats_periodname(repo->max_stats));
522 fprintf(f, "\n"); 526 fprintf(f, "\n");
523} 527}
524 528
525void print_repolist(FILE *f, struct cgit_repolist *list, int start) 529void print_repolist(FILE *f, struct cgit_repolist *list, int start)
526{ 530{
527 int i; 531 int i;
528 532
529 for(i = start; i < list->count; i++) 533 for(i = start; i < list->count; i++)
530 print_repo(f, &list->repos[i]); 534 print_repo(f, &list->repos[i]);
531} 535}
532 536
533/* Scan 'path' for git repositories, save the resulting repolist in 'cached_rc' 537/* Scan 'path' for git repositories, save the resulting repolist in 'cached_rc'
534 * and return 0 on success. 538 * and return 0 on success.
535 */ 539 */
536static int generate_cached_repolist(const char *path, const char *cached_rc) 540static int generate_cached_repolist(const char *path, const char *cached_rc)
537{ 541{
538 char *locked_rc; 542 char *locked_rc;
539 int idx; 543 int idx;
540 FILE *f; 544 FILE *f;
541 545
542 locked_rc = xstrdup(fmt("%s.lock", cached_rc)); 546 locked_rc = xstrdup(fmt("%s.lock", cached_rc));
543 f = fopen(locked_rc, "wx"); 547 f = fopen(locked_rc, "wx");
544 if (!f) { 548 if (!f) {
545 /* Inform about the error unless the lockfile already existed, 549 /* Inform about the error unless the lockfile already existed,
546 * since that only means we've got concurrent requests. 550 * since that only means we've got concurrent requests.
547 */ 551 */
548 if (errno != EEXIST) 552 if (errno != EEXIST)
549 fprintf(stderr, "[cgit] Error opening %s: %s (%d)\n", 553 fprintf(stderr, "[cgit] Error opening %s: %s (%d)\n",
550 locked_rc, strerror(errno), errno); 554 locked_rc, strerror(errno), errno);
551 return errno; 555 return errno;
552 } 556 }
553 idx = cgit_repolist.count; 557 idx = cgit_repolist.count;
554 scan_tree(path, repo_config); 558 scan_tree(path, repo_config);
555 print_repolist(f, &cgit_repolist, idx); 559 print_repolist(f, &cgit_repolist, idx);
556 if (rename(locked_rc, cached_rc)) 560 if (rename(locked_rc, cached_rc))
557 fprintf(stderr, "[cgit] Error renaming %s to %s: %s (%d)\n", 561 fprintf(stderr, "[cgit] Error renaming %s to %s: %s (%d)\n",
558 locked_rc, cached_rc, strerror(errno), errno); 562 locked_rc, cached_rc, strerror(errno), errno);
559 fclose(f); 563 fclose(f);
560 return 0; 564 return 0;
561} 565}
562 566
563static void process_cached_repolist(const char *path) 567static void process_cached_repolist(const char *path)
564{ 568{
565 struct stat st; 569 struct stat st;
566 char *cached_rc; 570 char *cached_rc;
567 time_t age; 571 time_t age;
568 572
569 cached_rc = xstrdup(fmt("%s/rc-%8x", ctx.cfg.cache_root, 573 cached_rc = xstrdup(fmt("%s/rc-%8x", ctx.cfg.cache_root,
570 hash_str(path))); 574 hash_str(path)));
571 575
572 if (stat(cached_rc, &st)) { 576 if (stat(cached_rc, &st)) {
573 /* Nothing is cached, we need to scan without forking. And 577 /* Nothing is cached, we need to scan without forking. And
574 * if we fail to generate a cached repolist, we need to 578 * if we fail to generate a cached repolist, we need to
575 * invoke scan_tree manually. 579 * invoke scan_tree manually.
576 */ 580 */
577 if (generate_cached_repolist(path, cached_rc)) 581 if (generate_cached_repolist(path, cached_rc))
578 scan_tree(path, repo_config); 582 scan_tree(path, repo_config);
579 return; 583 return;
580 } 584 }
581 585
582 parse_configfile(cached_rc, config_cb); 586 parse_configfile(cached_rc, config_cb);
583 587
584 /* If the cached configfile hasn't expired, lets exit now */ 588 /* If the cached configfile hasn't expired, lets exit now */
585 age = time(NULL) - st.st_mtime; 589 age = time(NULL) - st.st_mtime;
586 if (age <= (ctx.cfg.cache_scanrc_ttl * 60)) 590 if (age <= (ctx.cfg.cache_scanrc_ttl * 60))
587 return; 591 return;
588 592
589 /* The cached repolist has been parsed, but it was old. So lets 593 /* The cached repolist has been parsed, but it was old. So lets
590 * rescan the specified path and generate a new cached repolist 594 * rescan the specified path and generate a new cached repolist
591 * in a child-process to avoid latency for the current request. 595 * in a child-process to avoid latency for the current request.
592 */ 596 */
593 if (fork()) 597 if (fork())
594 return; 598 return;
595 599
596 exit(generate_cached_repolist(path, cached_rc)); 600 exit(generate_cached_repolist(path, cached_rc));
597} 601}
598 602
599static void cgit_parse_args(int argc, const char **argv) 603static void cgit_parse_args(int argc, const char **argv)
600{ 604{
601 int i; 605 int i;
602 int scan = 0; 606 int scan = 0;
603 607
604 for (i = 1; i < argc; i++) { 608 for (i = 1; i < argc; i++) {
605 if (!strncmp(argv[i], "--cache=", 8)) { 609 if (!strncmp(argv[i], "--cache=", 8)) {
606 ctx.cfg.cache_root = xstrdup(argv[i]+8); 610 ctx.cfg.cache_root = xstrdup(argv[i]+8);
607 } 611 }
608 if (!strcmp(argv[i], "--nocache")) { 612 if (!strcmp(argv[i], "--nocache")) {
609 ctx.cfg.nocache = 1; 613 ctx.cfg.nocache = 1;
610 } 614 }
611 if (!strcmp(argv[i], "--nohttp")) { 615 if (!strcmp(argv[i], "--nohttp")) {
612 ctx.env.no_http = "1"; 616 ctx.env.no_http = "1";
613 } 617 }
614 if (!strncmp(argv[i], "--query=", 8)) { 618 if (!strncmp(argv[i], "--query=", 8)) {
615 ctx.qry.raw = xstrdup(argv[i]+8); 619 ctx.qry.raw = xstrdup(argv[i]+8);
616 } 620 }
617 if (!strncmp(argv[i], "--repo=", 7)) { 621 if (!strncmp(argv[i], "--repo=", 7)) {
618 ctx.qry.repo = xstrdup(argv[i]+7); 622 ctx.qry.repo = xstrdup(argv[i]+7);
619 } 623 }
620 if (!strncmp(argv[i], "--page=", 7)) { 624 if (!strncmp(argv[i], "--page=", 7)) {
621 ctx.qry.page = xstrdup(argv[i]+7); 625 ctx.qry.page = xstrdup(argv[i]+7);
622 } 626 }
623 if (!strncmp(argv[i], "--head=", 7)) { 627 if (!strncmp(argv[i], "--head=", 7)) {
624 ctx.qry.head = xstrdup(argv[i]+7); 628 ctx.qry.head = xstrdup(argv[i]+7);
625 ctx.qry.has_symref = 1; 629 ctx.qry.has_symref = 1;
626 } 630 }
627 if (!strncmp(argv[i], "--sha1=", 7)) { 631 if (!strncmp(argv[i], "--sha1=", 7)) {
628 ctx.qry.sha1 = xstrdup(argv[i]+7); 632 ctx.qry.sha1 = xstrdup(argv[i]+7);
629 ctx.qry.has_sha1 = 1; 633 ctx.qry.has_sha1 = 1;
630 } 634 }
631 if (!strncmp(argv[i], "--ofs=", 6)) { 635 if (!strncmp(argv[i], "--ofs=", 6)) {
632 ctx.qry.ofs = atoi(argv[i]+6); 636 ctx.qry.ofs = atoi(argv[i]+6);
633 } 637 }
634 if (!strncmp(argv[i], "--scan-tree=", 12) || 638 if (!strncmp(argv[i], "--scan-tree=", 12) ||
635 !strncmp(argv[i], "--scan-path=", 12)) { 639 !strncmp(argv[i], "--scan-path=", 12)) {
636 /* HACK: the global snapshot bitmask defines the 640 /* HACK: the global snapshot bitmask defines the
637 * set of allowed snapshot formats, but the config 641 * set of allowed snapshot formats, but the config
638 * file hasn't been parsed yet so the mask is 642 * file hasn't been parsed yet so the mask is
639 * currently 0. By setting all bits high before 643 * currently 0. By setting all bits high before
640 * scanning we make sure that any in-repo cgitrc 644 * scanning we make sure that any in-repo cgitrc
641 * snapshot setting is respected by scan_tree(). 645 * snapshot setting is respected by scan_tree().
642 * BTW: we assume that there'll never be more than 646 * BTW: we assume that there'll never be more than
643 * 255 different snapshot formats supported by cgit... 647 * 255 different snapshot formats supported by cgit...
644 */ 648 */
645 ctx.cfg.snapshots = 0xFF; 649 ctx.cfg.snapshots = 0xFF;
646 scan++; 650 scan++;
647 scan_tree(argv[i] + 12, repo_config); 651 scan_tree(argv[i] + 12, repo_config);
648 } 652 }
649 } 653 }
650 if (scan) { 654 if (scan) {
651 qsort(cgit_repolist.repos, cgit_repolist.count, 655 qsort(cgit_repolist.repos, cgit_repolist.count,
652 sizeof(struct cgit_repo), cmp_repos); 656 sizeof(struct cgit_repo), cmp_repos);
653 print_repolist(stdout, &cgit_repolist, 0); 657 print_repolist(stdout, &cgit_repolist, 0);
654 exit(0); 658 exit(0);
655 } 659 }
656} 660}
657 661
658static int calc_ttl() 662static int calc_ttl()
659{ 663{
660 if (!ctx.repo) 664 if (!ctx.repo)
661 return ctx.cfg.cache_root_ttl; 665 return ctx.cfg.cache_root_ttl;
662 666
663 if (!ctx.qry.page) 667 if (!ctx.qry.page)
664 return ctx.cfg.cache_repo_ttl; 668 return ctx.cfg.cache_repo_ttl;
665 669
666 if (ctx.qry.has_symref) 670 if (ctx.qry.has_symref)
667 return ctx.cfg.cache_dynamic_ttl; 671 return ctx.cfg.cache_dynamic_ttl;
668 672
669 if (ctx.qry.has_sha1) 673 if (ctx.qry.has_sha1)
670 return ctx.cfg.cache_static_ttl; 674 return ctx.cfg.cache_static_ttl;
671 675
672 return ctx.cfg.cache_repo_ttl; 676 return ctx.cfg.cache_repo_ttl;
673} 677}
674 678
675int main(int argc, const char **argv) 679int main(int argc, const char **argv)
676{ 680{
677 const char *path; 681 const char *path;
678 char *qry; 682 char *qry;
679 int err, ttl; 683 int err, ttl;
680 684
681 prepare_context(&ctx); 685 prepare_context(&ctx);
682 cgit_repolist.length = 0; 686 cgit_repolist.length = 0;
683 cgit_repolist.count = 0; 687 cgit_repolist.count = 0;
684 cgit_repolist.repos = NULL; 688 cgit_repolist.repos = NULL;
685 689
686 cgit_parse_args(argc, argv); 690 cgit_parse_args(argc, argv);
687 parse_configfile(ctx.env.cgit_config, config_cb); 691 parse_configfile(ctx.env.cgit_config, config_cb);
688 ctx.repo = NULL; 692 ctx.repo = NULL;
689 http_parse_querystring(ctx.qry.raw, querystring_cb); 693 http_parse_querystring(ctx.qry.raw, querystring_cb);
690 694
691 /* If virtual-root isn't specified in cgitrc, lets pretend 695 /* If virtual-root isn't specified in cgitrc, lets pretend
692 * that virtual-root equals SCRIPT_NAME. 696 * that virtual-root equals SCRIPT_NAME.
693 */ 697 */
694 if (!ctx.cfg.virtual_root) 698 if (!ctx.cfg.virtual_root)
695 ctx.cfg.virtual_root = ctx.cfg.script_name; 699 ctx.cfg.virtual_root = ctx.cfg.script_name;
696 700
697 /* If no url parameter is specified on the querystring, lets 701 /* If no url parameter is specified on the querystring, lets
698 * use PATH_INFO as url. This allows cgit to work with virtual 702 * use PATH_INFO as url. This allows cgit to work with virtual
699 * urls without the need for rewriterules in the webserver (as 703 * urls without the need for rewriterules in the webserver (as
700 * long as PATH_INFO is included in the cache lookup key). 704 * long as PATH_INFO is included in the cache lookup key).
701 */ 705 */
702 path = ctx.env.path_info; 706 path = ctx.env.path_info;
703 if (!ctx.qry.url && path) { 707 if (!ctx.qry.url && path) {
704 if (path[0] == '/') 708 if (path[0] == '/')
705 path++; 709 path++;
706 ctx.qry.url = xstrdup(path); 710 ctx.qry.url = xstrdup(path);
707 if (ctx.qry.raw) { 711 if (ctx.qry.raw) {
708 qry = ctx.qry.raw; 712 qry = ctx.qry.raw;
709 ctx.qry.raw = xstrdup(fmt("%s?%s", path, qry)); 713 ctx.qry.raw = xstrdup(fmt("%s?%s", path, qry));
710 free(qry); 714 free(qry);
711 } else 715 } else
712 ctx.qry.raw = xstrdup(ctx.qry.url); 716 ctx.qry.raw = xstrdup(ctx.qry.url);
713 cgit_parse_url(ctx.qry.url); 717 cgit_parse_url(ctx.qry.url);
714 } 718 }
715 719
716 ttl = calc_ttl(); 720 ttl = calc_ttl();
717 ctx.page.expires += ttl*60; 721 ctx.page.expires += ttl*60;
718 if (ctx.env.request_method && !strcmp(ctx.env.request_method, "HEAD")) 722 if (ctx.env.request_method && !strcmp(ctx.env.request_method, "HEAD"))
719 ctx.cfg.nocache = 1; 723 ctx.cfg.nocache = 1;
720 if (ctx.cfg.nocache) 724 if (ctx.cfg.nocache)
721 ctx.cfg.cache_size = 0; 725 ctx.cfg.cache_size = 0;
722 err = cache_process(ctx.cfg.cache_size, ctx.cfg.cache_root, 726 err = cache_process(ctx.cfg.cache_size, ctx.cfg.cache_root,
723 ctx.qry.raw, ttl, process_request, &ctx); 727 ctx.qry.raw, ttl, process_request, &ctx);
724 if (err) 728 if (err)
725 cgit_print_error(fmt("Error processing page: %s (%d)", 729 cgit_print_error(fmt("Error processing page: %s (%d)",
726 strerror(err), err)); 730 strerror(err), err));
727 return err; 731 return err;
728} 732}
diff --git a/cgit.h b/cgit.h
index 5941ec0..cd4af72 100644
--- a/cgit.h
+++ b/cgit.h
@@ -1,297 +1,299 @@
1#ifndef CGIT_H 1#ifndef CGIT_H
2#define CGIT_H 2#define CGIT_H
3 3
4 4
5#include <git-compat-util.h> 5#include <git-compat-util.h>
6#include <cache.h> 6#include <cache.h>
7#include <grep.h> 7#include <grep.h>
8#include <object.h> 8#include <object.h>
9#include <tree.h> 9#include <tree.h>
10#include <commit.h> 10#include <commit.h>
11#include <tag.h> 11#include <tag.h>
12#include <diff.h> 12#include <diff.h>
13#include <diffcore.h> 13#include <diffcore.h>
14#include <refs.h> 14#include <refs.h>
15#include <revision.h> 15#include <revision.h>
16#include <log-tree.h> 16#include <log-tree.h>
17#include <archive.h> 17#include <archive.h>
18#include <string-list.h> 18#include <string-list.h>
19#include <xdiff-interface.h> 19#include <xdiff-interface.h>
20#include <xdiff/xdiff.h> 20#include <xdiff/xdiff.h>
21#include <utf8.h> 21#include <utf8.h>
22 22
23 23
24/* 24/*
25 * Dateformats used on misc. pages 25 * Dateformats used on misc. pages
26 */ 26 */
27#define FMT_LONGDATE "%Y-%m-%d %H:%M:%S (%Z)" 27#define FMT_LONGDATE "%Y-%m-%d %H:%M:%S (%Z)"
28#define FMT_SHORTDATE "%Y-%m-%d" 28#define FMT_SHORTDATE "%Y-%m-%d"
29#define FMT_ATOMDATE "%Y-%m-%dT%H:%M:%SZ" 29#define FMT_ATOMDATE "%Y-%m-%dT%H:%M:%SZ"
30 30
31 31
32/* 32/*
33 * Limits used for relative dates 33 * Limits used for relative dates
34 */ 34 */
35#define TM_MIN 60 35#define TM_MIN 60
36#define TM_HOUR (TM_MIN * 60) 36#define TM_HOUR (TM_MIN * 60)
37#define TM_DAY (TM_HOUR * 24) 37#define TM_DAY (TM_HOUR * 24)
38#define TM_WEEK (TM_DAY * 7) 38#define TM_WEEK (TM_DAY * 7)
39#define TM_YEAR (TM_DAY * 365) 39#define TM_YEAR (TM_DAY * 365)
40#define TM_MONTH (TM_YEAR / 12.0) 40#define TM_MONTH (TM_YEAR / 12.0)
41 41
42 42
43/* 43/*
44 * Default encoding 44 * Default encoding
45 */ 45 */
46#define PAGE_ENCODING "UTF-8" 46#define PAGE_ENCODING "UTF-8"
47 47
48typedef void (*configfn)(const char *name, const char *value); 48typedef void (*configfn)(const char *name, const char *value);
49typedef void (*filepair_fn)(struct diff_filepair *pair); 49typedef void (*filepair_fn)(struct diff_filepair *pair);
50typedef void (*linediff_fn)(char *line, int len); 50typedef void (*linediff_fn)(char *line, int len);
51 51
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 max_stats; 76 int max_stats;
76 time_t mtime; 77 time_t mtime;
77 struct cgit_filter *about_filter; 78 struct cgit_filter *about_filter;
78 struct cgit_filter *commit_filter; 79 struct cgit_filter *commit_filter;
79 struct cgit_filter *source_filter; 80 struct cgit_filter *source_filter;
80}; 81};
81 82
82typedef void (*repo_config_fn)(struct cgit_repo *repo, const char *name, 83typedef void (*repo_config_fn)(struct cgit_repo *repo, const char *name,
83 const char *value); 84 const char *value);
84 85
85struct cgit_repolist { 86struct cgit_repolist {
86 int length; 87 int length;
87 int count; 88 int count;
88 struct cgit_repo *repos; 89 struct cgit_repo *repos;
89}; 90};
90 91
91struct commitinfo { 92struct commitinfo {
92 struct commit *commit; 93 struct commit *commit;
93 char *author; 94 char *author;
94 char *author_email; 95 char *author_email;
95 unsigned long author_date; 96 unsigned long author_date;
96 char *committer; 97 char *committer;
97 char *committer_email; 98 char *committer_email;
98 unsigned long committer_date; 99 unsigned long committer_date;
99 char *subject; 100 char *subject;
100 char *msg; 101 char *msg;
101 char *msg_encoding; 102 char *msg_encoding;
102}; 103};
103 104
104struct taginfo { 105struct taginfo {
105 char *tagger; 106 char *tagger;
106 char *tagger_email; 107 char *tagger_email;
107 unsigned long tagger_date; 108 unsigned long tagger_date;
108 char *msg; 109 char *msg;
109}; 110};
110 111
111struct refinfo { 112struct refinfo {
112 const char *refname; 113 const char *refname;
113 struct object *object; 114 struct object *object;
114 union { 115 union {
115 struct taginfo *tag; 116 struct taginfo *tag;
116 struct commitinfo *commit; 117 struct commitinfo *commit;
117 }; 118 };
118}; 119};
119 120
120struct reflist { 121struct reflist {
121 struct refinfo **refs; 122 struct refinfo **refs;
122 int alloc; 123 int alloc;
123 int count; 124 int count;
124}; 125};
125 126
126struct cgit_query { 127struct cgit_query {
127 int has_symref; 128 int has_symref;
128 int has_sha1; 129 int has_sha1;
129 char *raw; 130 char *raw;
130 char *repo; 131 char *repo;
131 char *page; 132 char *page;
132 char *search; 133 char *search;
133 char *grep; 134 char *grep;
134 char *head; 135 char *head;
135 char *sha1; 136 char *sha1;
136 char *sha2; 137 char *sha2;
137 char *path; 138 char *path;
138 char *name; 139 char *name;
139 char *mimetype; 140 char *mimetype;
140 char *url; 141 char *url;
141 char *period; 142 char *period;
142 int ofs; 143 int ofs;
143 int nohead; 144 int nohead;
144 char *sort; 145 char *sort;
145 int showmsg; 146 int showmsg;
146 int ssdiff; 147 int ssdiff;
147}; 148};
148 149
149struct cgit_config { 150struct cgit_config {
150 char *agefile; 151 char *agefile;
151 char *cache_root; 152 char *cache_root;
152 char *clone_prefix; 153 char *clone_prefix;
153 char *css; 154 char *css;
154 char *favicon; 155 char *favicon;
155 char *footer; 156 char *footer;
156 char *head_include; 157 char *head_include;
157 char *header; 158 char *header;
158 char *index_header; 159 char *index_header;
159 char *index_info; 160 char *index_info;
160 char *logo; 161 char *logo;
161 char *logo_link; 162 char *logo_link;
162 char *module_link; 163 char *module_link;
163 char *robots; 164 char *robots;
164 char *root_title; 165 char *root_title;
165 char *root_desc; 166 char *root_desc;
166 char *root_readme; 167 char *root_readme;
167 char *script_name; 168 char *script_name;
168 char *section; 169 char *section;
169 char *virtual_root; 170 char *virtual_root;
170 int cache_size; 171 int cache_size;
171 int cache_dynamic_ttl; 172 int cache_dynamic_ttl;
172 int cache_max_create_time; 173 int cache_max_create_time;
173 int cache_repo_ttl; 174 int cache_repo_ttl;
174 int cache_root_ttl; 175 int cache_root_ttl;
175 int cache_scanrc_ttl; 176 int cache_scanrc_ttl;
176 int cache_static_ttl; 177 int cache_static_ttl;
177 int embedded; 178 int embedded;
178 int enable_filter_overrides; 179 int enable_filter_overrides;
179 int enable_index_links; 180 int enable_index_links;
180 int enable_log_filecount; 181 int enable_log_filecount;
181 int enable_log_linecount; 182 int enable_log_linecount;
183 int enable_remote_branches;
182 int enable_tree_linenumbers; 184 int enable_tree_linenumbers;
183 int local_time; 185 int local_time;
184 int max_repo_count; 186 int max_repo_count;
185 int max_commit_count; 187 int max_commit_count;
186 int max_lock_attempts; 188 int max_lock_attempts;
187 int max_msg_len; 189 int max_msg_len;
188 int max_repodesc_len; 190 int max_repodesc_len;
189 int max_blob_size; 191 int max_blob_size;
190 int max_stats; 192 int max_stats;
191 int nocache; 193 int nocache;
192 int noplainemail; 194 int noplainemail;
193 int noheader; 195 int noheader;
194 int renamelimit; 196 int renamelimit;
195 int snapshots; 197 int snapshots;
196 int summary_branches; 198 int summary_branches;
197 int summary_log; 199 int summary_log;
198 int summary_tags; 200 int summary_tags;
199 int ssdiff; 201 int ssdiff;
200 struct string_list mimetypes; 202 struct string_list mimetypes;
201 struct cgit_filter *about_filter; 203 struct cgit_filter *about_filter;
202 struct cgit_filter *commit_filter; 204 struct cgit_filter *commit_filter;
203 struct cgit_filter *source_filter; 205 struct cgit_filter *source_filter;
204}; 206};
205 207
206struct cgit_page { 208struct cgit_page {
207 time_t modified; 209 time_t modified;
208 time_t expires; 210 time_t expires;
209 size_t size; 211 size_t size;
210 char *mimetype; 212 char *mimetype;
211 char *charset; 213 char *charset;
212 char *filename; 214 char *filename;
213 char *etag; 215 char *etag;
214 char *title; 216 char *title;
215 int status; 217 int status;
216 char *statusmsg; 218 char *statusmsg;
217}; 219};
218 220
219struct cgit_environment { 221struct cgit_environment {
220 char *cgit_config; 222 char *cgit_config;
221 char *http_host; 223 char *http_host;
222 char *https; 224 char *https;
223 char *no_http; 225 char *no_http;
224 char *path_info; 226 char *path_info;
225 char *query_string; 227 char *query_string;
226 char *request_method; 228 char *request_method;
227 char *script_name; 229 char *script_name;
228 char *server_name; 230 char *server_name;
229 char *server_port; 231 char *server_port;
230}; 232};
231 233
232struct cgit_context { 234struct cgit_context {
233 struct cgit_environment env; 235 struct cgit_environment env;
234 struct cgit_query qry; 236 struct cgit_query qry;
235 struct cgit_config cfg; 237 struct cgit_config cfg;
236 struct cgit_repo *repo; 238 struct cgit_repo *repo;
237 struct cgit_page page; 239 struct cgit_page page;
238}; 240};
239 241
240struct cgit_snapshot_format { 242struct cgit_snapshot_format {
241 const char *suffix; 243 const char *suffix;
242 const char *mimetype; 244 const char *mimetype;
243 write_archive_fn_t write_func; 245 write_archive_fn_t write_func;
244 int bit; 246 int bit;
245}; 247};
246 248
247extern const char *cgit_version; 249extern const char *cgit_version;
248 250
249extern struct cgit_repolist cgit_repolist; 251extern struct cgit_repolist cgit_repolist;
250extern struct cgit_context ctx; 252extern struct cgit_context ctx;
251extern const struct cgit_snapshot_format cgit_snapshot_formats[]; 253extern const struct cgit_snapshot_format cgit_snapshot_formats[];
252 254
253extern struct cgit_repo *cgit_add_repo(const char *url); 255extern struct cgit_repo *cgit_add_repo(const char *url);
254extern struct cgit_repo *cgit_get_repoinfo(const char *url); 256extern struct cgit_repo *cgit_get_repoinfo(const char *url);
255extern void cgit_repo_config_cb(const char *name, const char *value); 257extern void cgit_repo_config_cb(const char *name, const char *value);
256 258
257extern int chk_zero(int result, char *msg); 259extern int chk_zero(int result, char *msg);
258extern int chk_positive(int result, char *msg); 260extern int chk_positive(int result, char *msg);
259extern int chk_non_negative(int result, char *msg); 261extern int chk_non_negative(int result, char *msg);
260 262
261extern char *trim_end(const char *str, char c); 263extern char *trim_end(const char *str, char c);
262extern char *strlpart(char *txt, int maxlen); 264extern char *strlpart(char *txt, int maxlen);
263extern char *strrpart(char *txt, int maxlen); 265extern char *strrpart(char *txt, int maxlen);
264 266
265extern void cgit_add_ref(struct reflist *list, struct refinfo *ref); 267extern void cgit_add_ref(struct reflist *list, struct refinfo *ref);
266extern int cgit_refs_cb(const char *refname, const unsigned char *sha1, 268extern int cgit_refs_cb(const char *refname, const unsigned char *sha1,
267 int flags, void *cb_data); 269 int flags, void *cb_data);
268 270
269extern void *cgit_free_commitinfo(struct commitinfo *info); 271extern void *cgit_free_commitinfo(struct commitinfo *info);
270 272
271extern int cgit_diff_files(const unsigned char *old_sha1, 273extern int cgit_diff_files(const unsigned char *old_sha1,
272 const unsigned char *new_sha1, 274 const unsigned char *new_sha1,
273 unsigned long *old_size, unsigned long *new_size, 275 unsigned long *old_size, unsigned long *new_size,
274 int *binary, linediff_fn fn); 276 int *binary, linediff_fn fn);
275 277
276extern void cgit_diff_tree(const unsigned char *old_sha1, 278extern void cgit_diff_tree(const unsigned char *old_sha1,
277 const unsigned char *new_sha1, 279 const unsigned char *new_sha1,
278 filepair_fn fn, const char *prefix); 280 filepair_fn fn, const char *prefix);
279 281
280extern void cgit_diff_commit(struct commit *commit, filepair_fn fn); 282extern void cgit_diff_commit(struct commit *commit, filepair_fn fn);
281 283
282extern char *fmt(const char *format,...); 284extern char *fmt(const char *format,...);
283 285
284extern struct commitinfo *cgit_parse_commit(struct commit *commit); 286extern struct commitinfo *cgit_parse_commit(struct commit *commit);
285extern struct taginfo *cgit_parse_tag(struct tag *tag); 287extern struct taginfo *cgit_parse_tag(struct tag *tag);
286extern void cgit_parse_url(const char *url); 288extern void cgit_parse_url(const char *url);
287 289
288extern const char *cgit_repobasename(const char *reponame); 290extern const char *cgit_repobasename(const char *reponame);
289 291
290extern int cgit_parse_snapshots_mask(const char *str); 292extern int cgit_parse_snapshots_mask(const char *str);
291 293
292extern int cgit_open_filter(struct cgit_filter *filter); 294extern int cgit_open_filter(struct cgit_filter *filter);
293extern int cgit_close_filter(struct cgit_filter *filter); 295extern int cgit_close_filter(struct cgit_filter *filter);
294 296
295extern int readfile(const char *path, char **buf, size_t *size); 297extern int readfile(const char *path, char **buf, size_t *size);
296 298
297#endif /* CGIT_H */ 299#endif /* CGIT_H */
diff --git a/cgitrc.5.txt b/cgitrc.5.txt
index 70e4c78..d74d9e7 100644
--- a/cgitrc.5.txt
+++ b/cgitrc.5.txt
@@ -1,509 +1,518 @@
1:man source: cgit 1:man source: cgit
2:man manual: cgit 2:man manual: cgit
3 3
4CGITRC(5) 4CGITRC(5)
5======== 5========
6 6
7 7
8NAME 8NAME
9---- 9----
10cgitrc - runtime configuration for cgit 10cgitrc - runtime configuration for cgit
11 11
12 12
13SYNOPSIS 13SYNOPSIS
14-------- 14--------
15Cgitrc contains all runtime settings for cgit, including the list of git 15Cgitrc contains all runtime settings for cgit, including the list of git
16repositories, formatted as a line-separated list of NAME=VALUE pairs. Blank 16repositories, formatted as a line-separated list of NAME=VALUE pairs. Blank
17lines, and lines starting with '#', are ignored. 17lines, and lines starting with '#', are ignored.
18 18
19 19
20LOCATION 20LOCATION
21-------- 21--------
22The default location of cgitrc, defined at compile time, is /etc/cgitrc. At 22The default location of cgitrc, defined at compile time, is /etc/cgitrc. At
23runtime, cgit will consult the environment variable CGIT_CONFIG and, if 23runtime, cgit will consult the environment variable CGIT_CONFIG and, if
24defined, use its value instead. 24defined, use its value instead.
25 25
26 26
27GLOBAL SETTINGS 27GLOBAL SETTINGS
28--------------- 28---------------
29about-filter:: 29about-filter::
30 Specifies a command which will be invoked to format the content of 30 Specifies a command which will be invoked to format the content of
31 about pages (both top-level and for each repository). The command will 31 about pages (both top-level and for each repository). The command will
32 get the content of the about-file on its STDIN, and the STDOUT from the 32 get the content of the about-file on its STDIN, and the STDOUT from the
33 command will be included verbatim on the about page. Default value: 33 command will be included verbatim on the about page. Default value:
34 none. 34 none.
35 35
36agefile:: 36agefile::
37 Specifies a path, relative to each repository path, which can be used 37 Specifies a path, relative to each repository path, which can be used
38 to specify the date and time of the youngest commit in the repository. 38 to specify the date and time of the youngest commit in the repository.
39 The first line in the file is used as input to the "parse_date" 39 The first line in the file is used as input to the "parse_date"
40 function in libgit. Recommended timestamp-format is "yyyy-mm-dd 40 function in libgit. Recommended timestamp-format is "yyyy-mm-dd
41 hh:mm:ss". Default value: "info/web/last-modified". 41 hh:mm:ss". Default value: "info/web/last-modified".
42 42
43cache-root:: 43cache-root::
44 Path used to store the cgit cache entries. Default value: 44 Path used to store the cgit cache entries. Default value:
45 "/var/cache/cgit". 45 "/var/cache/cgit".
46 46
47cache-dynamic-ttl:: 47cache-dynamic-ttl::
48 Number which specifies the time-to-live, in minutes, for the cached 48 Number which specifies the time-to-live, in minutes, for the cached
49 version of repository pages accessed without a fixed SHA1. Default 49 version of repository pages accessed without a fixed SHA1. Default
50 value: "5". 50 value: "5".
51 51
52cache-repo-ttl:: 52cache-repo-ttl::
53 Number which specifies the time-to-live, in minutes, for the cached 53 Number which specifies the time-to-live, in minutes, for the cached
54 version of the repository summary page. Default value: "5". 54 version of the repository summary page. Default value: "5".
55 55
56cache-root-ttl:: 56cache-root-ttl::
57 Number which specifies the time-to-live, in minutes, for the cached 57 Number which specifies the time-to-live, in minutes, for the cached
58 version of the repository index page. Default value: "5". 58 version of the repository index page. Default value: "5".
59 59
60cache-scanrc-ttl:: 60cache-scanrc-ttl::
61 Number which specifies the time-to-live, in minutes, for the result 61 Number which specifies the time-to-live, in minutes, for the result
62 of scanning a path for git repositories. Default value: "15". 62 of scanning a path for git repositories. Default value: "15".
63 63
64cache-size:: 64cache-size::
65 The maximum number of entries in the cgit cache. Default value: "0" 65 The maximum number of entries in the cgit cache. Default value: "0"
66 (i.e. caching is disabled). 66 (i.e. caching is disabled).
67 67
68cache-static-ttl:: 68cache-static-ttl::
69 Number which specifies the time-to-live, in minutes, for the cached 69 Number which specifies the time-to-live, in minutes, for the cached
70 version of repository pages accessed with a fixed SHA1. Default value: 70 version of repository pages accessed with a fixed SHA1. Default value:
71 "5". 71 "5".
72 72
73clone-prefix:: 73clone-prefix::
74 Space-separated list of common prefixes which, when combined with a 74 Space-separated list of common prefixes which, when combined with a
75 repository url, generates valid clone urls for the repository. This 75 repository url, generates valid clone urls for the repository. This
76 setting is only used if `repo.clone-url` is unspecified. Default value: 76 setting is only used if `repo.clone-url` is unspecified. Default value:
77 none. 77 none.
78 78
79commit-filter:: 79commit-filter::
80 Specifies a command which will be invoked to format commit messages. 80 Specifies a command which will be invoked to format commit messages.
81 The command will get the message on its STDIN, and the STDOUT from the 81 The command will get the message on its STDIN, and the STDOUT from the
82 command will be included verbatim as the commit message, i.e. this can 82 command will be included verbatim as the commit message, i.e. this can
83 be used to implement bugtracker integration. Default value: none. 83 be used to implement bugtracker integration. Default value: none.
84 84
85css:: 85css::
86 Url which specifies the css document to include in all cgit pages. 86 Url which specifies the css document to include in all cgit pages.
87 Default value: "/cgit.css". 87 Default value: "/cgit.css".
88 88
89embedded:: 89embedded::
90 Flag which, when set to "1", will make cgit generate a html fragment 90 Flag which, when set to "1", will make cgit generate a html fragment
91 suitable for embedding in other html pages. Default value: none. See 91 suitable for embedding in other html pages. Default value: none. See
92 also: "noheader". 92 also: "noheader".
93 93
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::
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:
116 "repo.enable-remote-branches".
117
113enable-tree-linenumbers:: 118enable-tree-linenumbers::
114 Flag which, when set to "1", will make cgit generate linenumber links 119 Flag which, when set to "1", will make cgit generate linenumber links
115 for plaintext blobs printed in the tree view. Default value: "1". 120 for plaintext blobs printed in the tree view. Default value: "1".
116 121
117favicon:: 122favicon::
118 Url used as link to a shortcut icon for cgit. If specified, it is 123 Url used as link to a shortcut icon for cgit. If specified, it is
119 suggested to use the value "/favicon.ico" since certain browsers will 124 suggested to use the value "/favicon.ico" since certain browsers will
120 ignore other values. Default value: none. 125 ignore other values. Default value: none.
121 126
122footer:: 127footer::
123 The content of the file specified with this option will be included 128 The content of the file specified with this option will be included
124 verbatim at the bottom of all pages (i.e. it replaces the standard 129 verbatim at the bottom of all pages (i.e. it replaces the standard
125 "generated by..." message. Default value: none. 130 "generated by..." message. Default value: none.
126 131
127head-include:: 132head-include::
128 The content of the file specified with this option will be included 133 The content of the file specified with this option will be included
129 verbatim in the html HEAD section on all pages. Default value: none. 134 verbatim in the html HEAD section on all pages. Default value: none.
130 135
131header:: 136header::
132 The content of the file specified with this option will be included 137 The content of the file specified with this option will be included
133 verbatim at the top of all pages. Default value: none. 138 verbatim at the top of all pages. Default value: none.
134 139
135include:: 140include::
136 Name of a configfile to include before the rest of the current config- 141 Name of a configfile to include before the rest of the current config-
137 file is parsed. Default value: none. 142 file is parsed. Default value: none.
138 143
139index-header:: 144index-header::
140 The content of the file specified with this option will be included 145 The content of the file specified with this option will be included
141 verbatim above the repository index. This setting is deprecated, and 146 verbatim above the repository index. This setting is deprecated, and
142 will not be supported by cgit-1.0 (use root-readme instead). Default 147 will not be supported by cgit-1.0 (use root-readme instead). Default
143 value: none. 148 value: none.
144 149
145index-info:: 150index-info::
146 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
147 verbatim below the heading on the repository index page. This setting 152 verbatim below the heading on the repository index page. This setting
148 is deprecated, and will not be supported by cgit-1.0 (use root-desc 153 is deprecated, and will not be supported by cgit-1.0 (use root-desc
149 instead). Default value: none. 154 instead). Default value: none.
150 155
151local-time:: 156local-time::
152 Flag which, if set to "1", makes cgit print commit and tag times in the 157 Flag which, if set to "1", makes cgit print commit and tag times in the
153 servers timezone. Default value: "0". 158 servers timezone. Default value: "0".
154 159
155logo:: 160logo::
156 Url which specifies the source of an image which will be used as a logo 161 Url which specifies the source of an image which will be used as a logo
157 on all cgit pages. Default value: "/cgit.png". 162 on all cgit pages. Default value: "/cgit.png".
158 163
159logo-link:: 164logo-link::
160 Url loaded when clicking on the cgit logo image. If unspecified the 165 Url loaded when clicking on the cgit logo image. If unspecified the
161 calculated url of the repository index page will be used. Default 166 calculated url of the repository index page will be used. Default
162 value: none. 167 value: none.
163 168
164max-commit-count:: 169max-commit-count::
165 Specifies the number of entries to list per page in "log" view. Default 170 Specifies the number of entries to list per page in "log" view. Default
166 value: "50". 171 value: "50".
167 172
168max-message-length:: 173max-message-length::
169 Specifies the maximum number of commit message characters to display in 174 Specifies the maximum number of commit message characters to display in
170 "log" view. Default value: "80". 175 "log" view. Default value: "80".
171 176
172max-repo-count:: 177max-repo-count::
173 Specifies the number of entries to list per page on therepository 178 Specifies the number of entries to list per page on therepository
174 index page. Default value: "50". 179 index page. Default value: "50".
175 180
176max-repodesc-length:: 181max-repodesc-length::
177 Specifies the maximum number of repo description characters to display 182 Specifies the maximum number of repo description characters to display
178 on the repository index page. Default value: "80". 183 on the repository index page. Default value: "80".
179 184
180max-blob-size:: 185max-blob-size::
181 Specifies the maximum size of a blob to display HTML for in KBytes. 186 Specifies the maximum size of a blob to display HTML for in KBytes.
182 Default value: "0" (limit disabled). 187 Default value: "0" (limit disabled).
183 188
184max-stats:: 189max-stats::
185 Set the default maximum statistics period. Valid values are "week", 190 Set the default maximum statistics period. Valid values are "week",
186 "month", "quarter" and "year". If unspecified, statistics are 191 "month", "quarter" and "year". If unspecified, statistics are
187 disabled. Default value: none. See also: "repo.max-stats". 192 disabled. Default value: none. See also: "repo.max-stats".
188 193
189mimetype.<ext>:: 194mimetype.<ext>::
190 Set the mimetype for the specified filename extension. This is used 195 Set the mimetype for the specified filename extension. This is used
191 by the `plain` command when returning blob content. 196 by the `plain` command when returning blob content.
192 197
193module-link:: 198module-link::
194 Text which will be used as the formatstring for a hyperlink when a 199 Text which will be used as the formatstring for a hyperlink when a
195 submodule is printed in a directory listing. The arguments for the 200 submodule is printed in a directory listing. The arguments for the
196 formatstring are the path and SHA1 of the submodule commit. Default 201 formatstring are the path and SHA1 of the submodule commit. Default
197 value: "./?repo=%s&page=commit&id=%s" 202 value: "./?repo=%s&page=commit&id=%s"
198 203
199nocache:: 204nocache::
200 If set to the value "1" caching will be disabled. This settings is 205 If set to the value "1" caching will be disabled. This settings is
201 deprecated, and will not be honored starting with cgit-1.0. Default 206 deprecated, and will not be honored starting with cgit-1.0. Default
202 value: "0". 207 value: "0".
203 208
204noplainemail:: 209noplainemail::
205 If set to "1" showing full author email adresses will be disabled. 210 If set to "1" showing full author email adresses will be disabled.
206 Default value: "0". 211 Default value: "0".
207 212
208noheader:: 213noheader::
209 Flag which, when set to "1", will make cgit omit the standard header 214 Flag which, when set to "1", will make cgit omit the standard header
210 on all pages. Default value: none. See also: "embedded". 215 on all pages. Default value: none. See also: "embedded".
211 216
212renamelimit:: 217renamelimit::
213 Maximum number of files to consider when detecting renames. The value 218 Maximum number of files to consider when detecting renames. The value
214 "-1" uses the compiletime value in git (for further info, look at 219 "-1" uses the compiletime value in git (for further info, look at
215 `man git-diff`). Default value: "-1". 220 `man git-diff`). Default value: "-1".
216 221
217repo.group:: 222repo.group::
218 Legacy alias for "section". This option is deprecated and will not be 223 Legacy alias for "section". This option is deprecated and will not be
219 supported in cgit-1.0. 224 supported in cgit-1.0.
220 225
221robots:: 226robots::
222 Text used as content for the "robots" meta-tag. Default value: 227 Text used as content for the "robots" meta-tag. Default value:
223 "index, nofollow". 228 "index, nofollow".
224 229
225root-desc:: 230root-desc::
226 Text printed below the heading on the repository index page. Default 231 Text printed below the heading on the repository index page. Default
227 value: "a fast webinterface for the git dscm". 232 value: "a fast webinterface for the git dscm".
228 233
229root-readme:: 234root-readme::
230 The content of the file specified with this option will be included 235 The content of the file specified with this option will be included
231 verbatim below the "about" link on the repository index page. Default 236 verbatim below the "about" link on the repository index page. Default
232 value: none. 237 value: none.
233 238
234root-title:: 239root-title::
235 Text printed as heading on the repository index page. Default value: 240 Text printed as heading on the repository index page. Default value:
236 "Git Repository Browser". 241 "Git Repository Browser".
237 242
238scan-path:: 243scan-path::
239 A path which will be scanned for repositories. If caching is enabled, 244 A path which will be scanned for repositories. If caching is enabled,
240 the result will be cached as a cgitrc include-file in the cache 245 the result will be cached as a cgitrc include-file in the cache
241 directory. Default value: none. See also: cache-scanrc-ttl. 246 directory. Default value: none. See also: cache-scanrc-ttl.
242 247
243section:: 248section::
244 The name of the current repository section - all repositories defined 249 The name of the current repository section - all repositories defined
245 after this option will inherit the current section name. Default value: 250 after this option will inherit the current section name. Default value:
246 none. 251 none.
247 252
248side-by-side-diffs:: 253side-by-side-diffs::
249 If set to "1" shows side-by-side diffs instead of unidiffs per 254 If set to "1" shows side-by-side diffs instead of unidiffs per
250 default. Default value: "0". 255 default. Default value: "0".
251 256
252snapshots:: 257snapshots::
253 Text which specifies the default set of snapshot formats generated by 258 Text which specifies the default set of snapshot formats generated by
254 cgit. The value is a space-separated list of zero or more of the 259 cgit. The value is a space-separated list of zero or more of the
255 values "tar", "tar.gz", "tar.bz2" and "zip". Default value: none. 260 values "tar", "tar.gz", "tar.bz2" and "zip". Default value: none.
256 261
257source-filter:: 262source-filter::
258 Specifies a command which will be invoked to format plaintext blobs 263 Specifies a command which will be invoked to format plaintext blobs
259 in the tree view. The command will get the blob content on its STDIN 264 in the tree view. The command will get the blob content on its STDIN
260 and the name of the blob as its only command line argument. The STDOUT 265 and the name of the blob as its only command line argument. The STDOUT
261 from the command will be included verbatim as the blob contents, i.e. 266 from the command will be included verbatim as the blob contents, i.e.
262 this can be used to implement e.g. syntax highlighting. Default value: 267 this can be used to implement e.g. syntax highlighting. Default value:
263 none. 268 none.
264 269
265summary-branches:: 270summary-branches::
266 Specifies the number of branches to display in the repository "summary" 271 Specifies the number of branches to display in the repository "summary"
267 view. Default value: "10". 272 view. Default value: "10".
268 273
269summary-log:: 274summary-log::
270 Specifies the number of log entries to display in the repository 275 Specifies the number of log entries to display in the repository
271 "summary" view. Default value: "10". 276 "summary" view. Default value: "10".
272 277
273summary-tags:: 278summary-tags::
274 Specifies the number of tags to display in the repository "summary" 279 Specifies the number of tags to display in the repository "summary"
275 view. Default value: "10". 280 view. Default value: "10".
276 281
277virtual-root:: 282virtual-root::
278 Url which, if specified, will be used as root for all cgit links. It 283 Url which, if specified, will be used as root for all cgit links. It
279 will also cause cgit to generate 'virtual urls', i.e. urls like 284 will also cause cgit to generate 'virtual urls', i.e. urls like
280 '/cgit/tree/README' as opposed to '?r=cgit&p=tree&path=README'. Default 285 '/cgit/tree/README' as opposed to '?r=cgit&p=tree&path=README'. Default
281 value: none. 286 value: none.
282 NOTE: cgit has recently learned how to use PATH_INFO to achieve the 287 NOTE: cgit has recently learned how to use PATH_INFO to achieve the
283 same kind of virtual urls, so this option will probably be deprecated. 288 same kind of virtual urls, so this option will probably be deprecated.
284 289
285REPOSITORY SETTINGS 290REPOSITORY SETTINGS
286------------------- 291-------------------
287repo.about-filter:: 292repo.about-filter::
288 Override the default about-filter. Default value: none. See also: 293 Override the default about-filter. Default value: none. See also:
289 "enable-filter-overrides". 294 "enable-filter-overrides".
290 295
291repo.clone-url:: 296repo.clone-url::
292 A list of space-separated urls which can be used to clone this repo. 297 A list of space-separated urls which can be used to clone this repo.
293 Default value: none. 298 Default value: none.
294 299
295repo.commit-filter:: 300repo.commit-filter::
296 Override the default commit-filter. Default value: none. See also: 301 Override the default commit-filter. Default value: none. See also:
297 "enable-filter-overrides". 302 "enable-filter-overrides".
298 303
299repo.defbranch:: 304repo.defbranch::
300 The name of the default branch for this repository. If no such branch 305 The name of the default branch for this repository. If no such branch
301 exists in the repository, the first branch name (when sorted) is used 306 exists in the repository, the first branch name (when sorted) is used
302 as default instead. Default value: "master". 307 as default instead. Default value: "master".
303 308
304repo.desc:: 309repo.desc::
305 The value to show as repository description. Default value: none. 310 The value to show as repository description. Default value: none.
306 311
307repo.enable-log-filecount:: 312repo.enable-log-filecount::
308 A flag which can be used to disable the global setting 313 A flag which can be used to disable the global setting
309 `enable-log-filecount'. Default value: none. 314 `enable-log-filecount'. Default value: none.
310 315
311repo.enable-log-linecount:: 316repo.enable-log-linecount::
312 A flag which can be used to disable the global setting 317 A flag which can be used to disable the global setting
313 `enable-log-linecount'. Default value: none. 318 `enable-log-linecount'. Default value: none.
314 319
320repo.enable-remote-branches::
321 Flag which, when set to "1", will make cgit display remote branches
322 in the summary and refs views. Default value: <enable-remote-branches>.
323
315repo.max-stats:: 324repo.max-stats::
316 Override the default maximum statistics period. Valid values are equal 325 Override the default maximum statistics period. Valid values are equal
317 to the values specified for the global "max-stats" setting. Default 326 to the values specified for the global "max-stats" setting. Default
318 value: none. 327 value: none.
319 328
320repo.name:: 329repo.name::
321 The value to show as repository name. Default value: <repo.url>. 330 The value to show as repository name. Default value: <repo.url>.
322 331
323repo.owner:: 332repo.owner::
324 A value used to identify the owner of the repository. Default value: 333 A value used to identify the owner of the repository. Default value:
325 none. 334 none.
326 335
327repo.path:: 336repo.path::
328 An absolute path to the repository directory. For non-bare repositories 337 An absolute path to the repository directory. For non-bare repositories
329 this is the .git-directory. Default value: none. 338 this is the .git-directory. Default value: none.
330 339
331repo.readme:: 340repo.readme::
332 A path (relative to <repo.path>) which specifies a file to include 341 A path (relative to <repo.path>) which specifies a file to include
333 verbatim as the "About" page for this repo. Default value: none. 342 verbatim as the "About" page for this repo. Default value: none.
334 343
335repo.snapshots:: 344repo.snapshots::
336 A mask of allowed snapshot-formats for this repo, restricted by the 345 A mask of allowed snapshot-formats for this repo, restricted by the
337 "snapshots" global setting. Default value: <snapshots>. 346 "snapshots" global setting. Default value: <snapshots>.
338 347
339repo.section:: 348repo.section::
340 Override the current section name for this repository. Default value: 349 Override the current section name for this repository. Default value:
341 none. 350 none.
342 351
343repo.source-filter:: 352repo.source-filter::
344 Override the default source-filter. Default value: none. See also: 353 Override the default source-filter. Default value: none. See also:
345 "enable-filter-overrides". 354 "enable-filter-overrides".
346 355
347repo.url:: 356repo.url::
348 The relative url used to access the repository. This must be the first 357 The relative url used to access the repository. This must be the first
349 setting specified for each repo. Default value: none. 358 setting specified for each repo. Default value: none.
350 359
351 360
352REPOSITORY-SPECIFIC CGITRC FILE 361REPOSITORY-SPECIFIC CGITRC FILE
353------------------------------- 362-------------------------------
354When the option "scan-path" is used to auto-discover git repositories, cgit 363When the option "scan-path" is used to auto-discover git repositories, cgit
355will try to parse the file "cgitrc" within any found repository. Such a 364will try to parse the file "cgitrc" within any found repository. Such a
356repo-specific config file may contain any of the repo-specific options 365repo-specific config file may contain any of the repo-specific options
357described above, except "repo.url" and "repo.path". Additionally, the "filter" 366described above, except "repo.url" and "repo.path". Additionally, the "filter"
358options are only acknowledged in repo-specific config files when 367options are only acknowledged in repo-specific config files when
359"enable-filter-overrides" is set to "1". 368"enable-filter-overrides" is set to "1".
360 369
361Note: the "repo." prefix is dropped from the option names in repo-specific 370Note: the "repo." prefix is dropped from the option names in repo-specific
362config files, e.g. "repo.desc" becomes "desc". 371config files, e.g. "repo.desc" becomes "desc".
363 372
364 373
365EXAMPLE CGITRC FILE 374EXAMPLE CGITRC FILE
366------------------- 375-------------------
367 376
368.... 377....
369# Enable caching of up to 1000 output entriess 378# Enable caching of up to 1000 output entriess
370cache-size=1000 379cache-size=1000
371 380
372 381
373# Specify some default clone prefixes 382# Specify some default clone prefixes
374clone-prefix=git://foobar.com ssh://foobar.com/pub/git http://foobar.com/git 383clone-prefix=git://foobar.com ssh://foobar.com/pub/git http://foobar.com/git
375 384
376# Specify the css url 385# Specify the css url
377css=/css/cgit.css 386css=/css/cgit.css
378 387
379 388
380# Show extra links for each repository on the index page 389# Show extra links for each repository on the index page
381enable-index-links=1 390enable-index-links=1
382 391
383 392
384# Show number of affected files per commit on the log pages 393# Show number of affected files per commit on the log pages
385enable-log-filecount=1 394enable-log-filecount=1
386 395
387 396
388# Show number of added/removed lines per commit on the log pages 397# Show number of added/removed lines per commit on the log pages
389enable-log-linecount=1 398enable-log-linecount=1
390 399
391 400
392# Add a cgit favicon 401# Add a cgit favicon
393favicon=/favicon.ico 402favicon=/favicon.ico
394 403
395 404
396# Use a custom logo 405# Use a custom logo
397logo=/img/mylogo.png 406logo=/img/mylogo.png
398 407
399 408
400# Enable statistics per week, month and quarter 409# Enable statistics per week, month and quarter
401max-stats=quarter 410max-stats=quarter
402 411
403 412
404# Set the title and heading of the repository index page 413# Set the title and heading of the repository index page
405root-title=foobar.com git repositories 414root-title=foobar.com git repositories
406 415
407 416
408# Set a subheading for the repository index page 417# Set a subheading for the repository index page
409root-desc=tracking the foobar development 418root-desc=tracking the foobar development
410 419
411 420
412# Include some more info about foobar.com on the index page 421# Include some more info about foobar.com on the index page
413root-readme=/var/www/htdocs/about.html 422root-readme=/var/www/htdocs/about.html
414 423
415 424
416# Allow download of tar.gz, tar.bz2 and zip-files 425# Allow download of tar.gz, tar.bz2 and zip-files
417snapshots=tar.gz tar.bz2 zip 426snapshots=tar.gz tar.bz2 zip
418 427
419 428
420## 429##
421## List of common mimetypes 430## List of common mimetypes
422## 431##
423 432
424mimetype.git=image/git 433mimetype.git=image/git
425mimetype.html=text/html 434mimetype.html=text/html
426mimetype.jpg=image/jpeg 435mimetype.jpg=image/jpeg
427mimetype.jpeg=image/jpeg 436mimetype.jpeg=image/jpeg
428mimetype.pdf=application/pdf 437mimetype.pdf=application/pdf
429mimetype.png=image/png 438mimetype.png=image/png
430mimetype.svg=image/svg+xml 439mimetype.svg=image/svg+xml
431 440
432 441
433## 442##
434## List of repositories. 443## List of repositories.
435## PS: Any repositories listed when section is unset will not be 444## PS: Any repositories listed when section is unset will not be
436## displayed under a section heading 445## displayed under a section heading
437## PPS: This list could be kept in a different file (e.g. '/etc/cgitrepos') 446## PPS: This list could be kept in a different file (e.g. '/etc/cgitrepos')
438## and included like this: 447## and included like this:
439## include=/etc/cgitrepos 448## include=/etc/cgitrepos
440## 449##
441 450
442 451
443repo.url=foo 452repo.url=foo
444repo.path=/pub/git/foo.git 453repo.path=/pub/git/foo.git
445repo.desc=the master foo repository 454repo.desc=the master foo repository
446repo.owner=fooman@foobar.com 455repo.owner=fooman@foobar.com
447repo.readme=info/web/about.html 456repo.readme=info/web/about.html
448 457
449 458
450repo.url=bar 459repo.url=bar
451repo.path=/pub/git/bar.git 460repo.path=/pub/git/bar.git
452repo.desc=the bars for your foo 461repo.desc=the bars for your foo
453repo.owner=barman@foobar.com 462repo.owner=barman@foobar.com
454repo.readme=info/web/about.html 463repo.readme=info/web/about.html
455 464
456 465
457# The next repositories will be displayed under the 'extras' heading 466# The next repositories will be displayed under the 'extras' heading
458section=extras 467section=extras
459 468
460 469
461repo.url=baz 470repo.url=baz
462repo.path=/pub/git/baz.git 471repo.path=/pub/git/baz.git
463repo.desc=a set of extensions for bar users 472repo.desc=a set of extensions for bar users
464 473
465repo.url=wiz 474repo.url=wiz
466repo.path=/pub/git/wiz.git 475repo.path=/pub/git/wiz.git
467repo.desc=the wizard of foo 476repo.desc=the wizard of foo
468 477
469 478
470# Add some mirrored repositories 479# Add some mirrored repositories
471section=mirrors 480section=mirrors
472 481
473 482
474repo.url=git 483repo.url=git
475repo.path=/pub/git/git.git 484repo.path=/pub/git/git.git
476repo.desc=the dscm 485repo.desc=the dscm
477 486
478 487
479repo.url=linux 488repo.url=linux
480repo.path=/pub/git/linux.git 489repo.path=/pub/git/linux.git
481repo.desc=the kernel 490repo.desc=the kernel
482 491
483# Disable adhoc downloads of this repo 492# Disable adhoc downloads of this repo
484repo.snapshots=0 493repo.snapshots=0
485 494
486# Disable line-counts for this repo 495# Disable line-counts for this repo
487repo.enable-log-linecount=0 496repo.enable-log-linecount=0
488 497
489# Restrict the max statistics period for this repo 498# Restrict the max statistics period for this repo
490repo.max-stats=month 499repo.max-stats=month
491.... 500....
492 501
493 502
494BUGS 503BUGS
495---- 504----
496Comments currently cannot appear on the same line as a setting; the comment 505Comments currently cannot appear on the same line as a setting; the comment
497will be included as part of the value. E.g. this line: 506will be included as part of the value. E.g. this line:
498 507
499 robots=index # allow indexing 508 robots=index # allow indexing
500 509
501will generate the following html element: 510will generate the following html element:
502 511
503 <meta name='robots' content='index # allow indexing'/> 512 <meta name='robots' content='index # allow indexing'/>
504 513
505 514
506 515
507AUTHOR 516AUTHOR
508------ 517------
509Lars Hjemli <hjemli@gmail.com> 518Lars Hjemli <hjemli@gmail.com>
diff --git a/shared.c b/shared.c
index 9362d21..5f46793 100644
--- a/shared.c
+++ b/shared.c
@@ -1,424 +1,425 @@
1/* shared.c: global vars + some callback functions 1/* shared.c: global vars + some callback functions
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 10
11struct cgit_repolist cgit_repolist; 11struct cgit_repolist cgit_repolist;
12struct cgit_context ctx; 12struct cgit_context ctx;
13int cgit_cmd; 13int cgit_cmd;
14 14
15int chk_zero(int result, char *msg) 15int chk_zero(int result, char *msg)
16{ 16{
17 if (result != 0) 17 if (result != 0)
18 die("%s: %s", msg, strerror(errno)); 18 die("%s: %s", msg, strerror(errno));
19 return result; 19 return result;
20} 20}
21 21
22int chk_positive(int result, char *msg) 22int chk_positive(int result, char *msg)
23{ 23{
24 if (result <= 0) 24 if (result <= 0)
25 die("%s: %s", msg, strerror(errno)); 25 die("%s: %s", msg, strerror(errno));
26 return result; 26 return result;
27} 27}
28 28
29int chk_non_negative(int result, char *msg) 29int chk_non_negative(int result, char *msg)
30{ 30{
31 if (result < 0) 31 if (result < 0)
32 die("%s: %s",msg, strerror(errno)); 32 die("%s: %s",msg, strerror(errno));
33 return result; 33 return result;
34} 34}
35 35
36struct cgit_repo *cgit_add_repo(const char *url) 36struct cgit_repo *cgit_add_repo(const char *url)
37{ 37{
38 struct cgit_repo *ret; 38 struct cgit_repo *ret;
39 39
40 if (++cgit_repolist.count > cgit_repolist.length) { 40 if (++cgit_repolist.count > cgit_repolist.length) {
41 if (cgit_repolist.length == 0) 41 if (cgit_repolist.length == 0)
42 cgit_repolist.length = 8; 42 cgit_repolist.length = 8;
43 else 43 else
44 cgit_repolist.length *= 2; 44 cgit_repolist.length *= 2;
45 cgit_repolist.repos = xrealloc(cgit_repolist.repos, 45 cgit_repolist.repos = xrealloc(cgit_repolist.repos,
46 cgit_repolist.length * 46 cgit_repolist.length *
47 sizeof(struct cgit_repo)); 47 sizeof(struct cgit_repo));
48 } 48 }
49 49
50 ret = &cgit_repolist.repos[cgit_repolist.count-1]; 50 ret = &cgit_repolist.repos[cgit_repolist.count-1];
51 memset(ret, 0, sizeof(struct cgit_repo)); 51 memset(ret, 0, sizeof(struct cgit_repo));
52 ret->url = trim_end(url, '/'); 52 ret->url = trim_end(url, '/');
53 ret->name = ret->url; 53 ret->name = ret->url;
54 ret->path = NULL; 54 ret->path = NULL;
55 ret->desc = "[no description]"; 55 ret->desc = "[no description]";
56 ret->owner = NULL; 56 ret->owner = NULL;
57 ret->section = ctx.cfg.section; 57 ret->section = ctx.cfg.section;
58 ret->defbranch = "master"; 58 ret->defbranch = "master";
59 ret->snapshots = ctx.cfg.snapshots; 59 ret->snapshots = ctx.cfg.snapshots;
60 ret->enable_log_filecount = ctx.cfg.enable_log_filecount; 60 ret->enable_log_filecount = ctx.cfg.enable_log_filecount;
61 ret->enable_log_linecount = ctx.cfg.enable_log_linecount; 61 ret->enable_log_linecount = ctx.cfg.enable_log_linecount;
62 ret->enable_remote_branches = ctx.cfg.enable_remote_branches;
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)
86{ 87{
87 free(info->author); 88 free(info->author);
88 free(info->author_email); 89 free(info->author_email);
89 free(info->committer); 90 free(info->committer);
90 free(info->committer_email); 91 free(info->committer_email);
91 free(info->subject); 92 free(info->subject);
92 free(info->msg); 93 free(info->msg);
93 free(info->msg_encoding); 94 free(info->msg_encoding);
94 free(info); 95 free(info);
95 return NULL; 96 return NULL;
96} 97}
97 98
98char *trim_end(const char *str, char c) 99char *trim_end(const char *str, char c)
99{ 100{
100 int len; 101 int len;
101 char *s, *t; 102 char *s, *t;
102 103
103 if (str == NULL) 104 if (str == NULL)
104 return NULL; 105 return NULL;
105 t = (char *)str; 106 t = (char *)str;
106 len = strlen(t); 107 len = strlen(t);
107 while(len > 0 && t[len - 1] == c) 108 while(len > 0 && t[len - 1] == c)
108 len--; 109 len--;
109 110
110 if (len == 0) 111 if (len == 0)
111 return NULL; 112 return NULL;
112 113
113 c = t[len]; 114 c = t[len];
114 t[len] = '\0'; 115 t[len] = '\0';
115 s = xstrdup(t); 116 s = xstrdup(t);
116 t[len] = c; 117 t[len] = c;
117 return s; 118 return s;
118} 119}
119 120
120char *strlpart(char *txt, int maxlen) 121char *strlpart(char *txt, int maxlen)
121{ 122{
122 char *result; 123 char *result;
123 124
124 if (!txt) 125 if (!txt)
125 return txt; 126 return txt;
126 127
127 if (strlen(txt) <= maxlen) 128 if (strlen(txt) <= maxlen)
128 return txt; 129 return txt;
129 result = xmalloc(maxlen + 1); 130 result = xmalloc(maxlen + 1);
130 memcpy(result, txt, maxlen - 3); 131 memcpy(result, txt, maxlen - 3);
131 result[maxlen-1] = result[maxlen-2] = result[maxlen-3] = '.'; 132 result[maxlen-1] = result[maxlen-2] = result[maxlen-3] = '.';
132 result[maxlen] = '\0'; 133 result[maxlen] = '\0';
133 return result; 134 return result;
134} 135}
135 136
136char *strrpart(char *txt, int maxlen) 137char *strrpart(char *txt, int maxlen)
137{ 138{
138 char *result; 139 char *result;
139 140
140 if (!txt) 141 if (!txt)
141 return txt; 142 return txt;
142 143
143 if (strlen(txt) <= maxlen) 144 if (strlen(txt) <= maxlen)
144 return txt; 145 return txt;
145 result = xmalloc(maxlen + 1); 146 result = xmalloc(maxlen + 1);
146 memcpy(result + 3, txt + strlen(txt) - maxlen + 4, maxlen - 3); 147 memcpy(result + 3, txt + strlen(txt) - maxlen + 4, maxlen - 3);
147 result[0] = result[1] = result[2] = '.'; 148 result[0] = result[1] = result[2] = '.';
148 return result; 149 return result;
149} 150}
150 151
151void cgit_add_ref(struct reflist *list, struct refinfo *ref) 152void cgit_add_ref(struct reflist *list, struct refinfo *ref)
152{ 153{
153 size_t size; 154 size_t size;
154 155
155 if (list->count >= list->alloc) { 156 if (list->count >= list->alloc) {
156 list->alloc += (list->alloc ? list->alloc : 4); 157 list->alloc += (list->alloc ? list->alloc : 4);
157 size = list->alloc * sizeof(struct refinfo *); 158 size = list->alloc * sizeof(struct refinfo *);
158 list->refs = xrealloc(list->refs, size); 159 list->refs = xrealloc(list->refs, size);
159 } 160 }
160 list->refs[list->count++] = ref; 161 list->refs[list->count++] = ref;
161} 162}
162 163
163struct refinfo *cgit_mk_refinfo(const char *refname, const unsigned char *sha1) 164struct refinfo *cgit_mk_refinfo(const char *refname, const unsigned char *sha1)
164{ 165{
165 struct refinfo *ref; 166 struct refinfo *ref;
166 167
167 ref = xmalloc(sizeof (struct refinfo)); 168 ref = xmalloc(sizeof (struct refinfo));
168 ref->refname = xstrdup(refname); 169 ref->refname = xstrdup(refname);
169 ref->object = parse_object(sha1); 170 ref->object = parse_object(sha1);
170 switch (ref->object->type) { 171 switch (ref->object->type) {
171 case OBJ_TAG: 172 case OBJ_TAG:
172 ref->tag = cgit_parse_tag((struct tag *)ref->object); 173 ref->tag = cgit_parse_tag((struct tag *)ref->object);
173 break; 174 break;
174 case OBJ_COMMIT: 175 case OBJ_COMMIT:
175 ref->commit = cgit_parse_commit((struct commit *)ref->object); 176 ref->commit = cgit_parse_commit((struct commit *)ref->object);
176 break; 177 break;
177 } 178 }
178 return ref; 179 return ref;
179} 180}
180 181
181int cgit_refs_cb(const char *refname, const unsigned char *sha1, int flags, 182int cgit_refs_cb(const char *refname, const unsigned char *sha1, int flags,
182 void *cb_data) 183 void *cb_data)
183{ 184{
184 struct reflist *list = (struct reflist *)cb_data; 185 struct reflist *list = (struct reflist *)cb_data;
185 struct refinfo *info = cgit_mk_refinfo(refname, sha1); 186 struct refinfo *info = cgit_mk_refinfo(refname, sha1);
186 187
187 if (info) 188 if (info)
188 cgit_add_ref(list, info); 189 cgit_add_ref(list, info);
189 return 0; 190 return 0;
190} 191}
191 192
192void cgit_diff_tree_cb(struct diff_queue_struct *q, 193void cgit_diff_tree_cb(struct diff_queue_struct *q,
193 struct diff_options *options, void *data) 194 struct diff_options *options, void *data)
194{ 195{
195 int i; 196 int i;
196 197
197 for (i = 0; i < q->nr; i++) { 198 for (i = 0; i < q->nr; i++) {
198 if (q->queue[i]->status == 'U') 199 if (q->queue[i]->status == 'U')
199 continue; 200 continue;
200 ((filepair_fn)data)(q->queue[i]); 201 ((filepair_fn)data)(q->queue[i]);
201 } 202 }
202} 203}
203 204
204static int load_mmfile(mmfile_t *file, const unsigned char *sha1) 205static int load_mmfile(mmfile_t *file, const unsigned char *sha1)
205{ 206{
206 enum object_type type; 207 enum object_type type;
207 208
208 if (is_null_sha1(sha1)) { 209 if (is_null_sha1(sha1)) {
209 file->ptr = (char *)""; 210 file->ptr = (char *)"";
210 file->size = 0; 211 file->size = 0;
211 } else { 212 } else {
212 file->ptr = read_sha1_file(sha1, &type, 213 file->ptr = read_sha1_file(sha1, &type,
213 (unsigned long *)&file->size); 214 (unsigned long *)&file->size);
214 } 215 }
215 return 1; 216 return 1;
216} 217}
217 218
218/* 219/*
219 * Receive diff-buffers from xdiff and concatenate them as 220 * Receive diff-buffers from xdiff and concatenate them as
220 * needed across multiple callbacks. 221 * needed across multiple callbacks.
221 * 222 *
222 * This is basically a copy of xdiff-interface.c/xdiff_outf(), 223 * This is basically a copy of xdiff-interface.c/xdiff_outf(),
223 * ripped from git and modified to use globals instead of 224 * ripped from git and modified to use globals instead of
224 * a special callback-struct. 225 * a special callback-struct.
225 */ 226 */
226char *diffbuf = NULL; 227char *diffbuf = NULL;
227int buflen = 0; 228int buflen = 0;
228 229
229int filediff_cb(void *priv, mmbuffer_t *mb, int nbuf) 230int filediff_cb(void *priv, mmbuffer_t *mb, int nbuf)
230{ 231{
231 int i; 232 int i;
232 233
233 for (i = 0; i < nbuf; i++) { 234 for (i = 0; i < nbuf; i++) {
234 if (mb[i].ptr[mb[i].size-1] != '\n') { 235 if (mb[i].ptr[mb[i].size-1] != '\n') {
235 /* Incomplete line */ 236 /* Incomplete line */
236 diffbuf = xrealloc(diffbuf, buflen + mb[i].size); 237 diffbuf = xrealloc(diffbuf, buflen + mb[i].size);
237 memcpy(diffbuf + buflen, mb[i].ptr, mb[i].size); 238 memcpy(diffbuf + buflen, mb[i].ptr, mb[i].size);
238 buflen += mb[i].size; 239 buflen += mb[i].size;
239 continue; 240 continue;
240 } 241 }
241 242
242 /* we have a complete line */ 243 /* we have a complete line */
243 if (!diffbuf) { 244 if (!diffbuf) {
244 ((linediff_fn)priv)(mb[i].ptr, mb[i].size); 245 ((linediff_fn)priv)(mb[i].ptr, mb[i].size);
245 continue; 246 continue;
246 } 247 }
247 diffbuf = xrealloc(diffbuf, buflen + mb[i].size); 248 diffbuf = xrealloc(diffbuf, buflen + mb[i].size);
248 memcpy(diffbuf + buflen, mb[i].ptr, mb[i].size); 249 memcpy(diffbuf + buflen, mb[i].ptr, mb[i].size);
249 ((linediff_fn)priv)(diffbuf, buflen + mb[i].size); 250 ((linediff_fn)priv)(diffbuf, buflen + mb[i].size);
250 free(diffbuf); 251 free(diffbuf);
251 diffbuf = NULL; 252 diffbuf = NULL;
252 buflen = 0; 253 buflen = 0;
253 } 254 }
254 if (diffbuf) { 255 if (diffbuf) {
255 ((linediff_fn)priv)(diffbuf, buflen); 256 ((linediff_fn)priv)(diffbuf, buflen);
256 free(diffbuf); 257 free(diffbuf);
257 diffbuf = NULL; 258 diffbuf = NULL;
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, linediff_fn fn) 266 unsigned long *new_size, int *binary, linediff_fn fn)
266{ 267{
267 mmfile_t file1, file2; 268 mmfile_t file1, file2;
268 xpparam_t diff_params; 269 xpparam_t diff_params;
269 xdemitconf_t emit_params; 270 xdemitconf_t emit_params;
270 xdemitcb_t emit_cb; 271 xdemitcb_t emit_cb;
271 272
272 if (!load_mmfile(&file1, old_sha1) || !load_mmfile(&file2, new_sha1)) 273 if (!load_mmfile(&file1, old_sha1) || !load_mmfile(&file2, new_sha1))
273 return 1; 274 return 1;
274 275
275 *old_size = file1.size; 276 *old_size = file1.size;
276 *new_size = file2.size; 277 *new_size = file2.size;
277 278
278 if ((file1.ptr && buffer_is_binary(file1.ptr, file1.size)) || 279 if ((file1.ptr && buffer_is_binary(file1.ptr, file1.size)) ||
279 (file2.ptr && buffer_is_binary(file2.ptr, file2.size))) { 280 (file2.ptr && buffer_is_binary(file2.ptr, file2.size))) {
280 *binary = 1; 281 *binary = 1;
281 return 0; 282 return 0;
282 } 283 }
283 284
284 memset(&diff_params, 0, sizeof(diff_params)); 285 memset(&diff_params, 0, sizeof(diff_params));
285 memset(&emit_params, 0, sizeof(emit_params)); 286 memset(&emit_params, 0, sizeof(emit_params));
286 memset(&emit_cb, 0, sizeof(emit_cb)); 287 memset(&emit_cb, 0, sizeof(emit_cb));
287 diff_params.flags = XDF_NEED_MINIMAL; 288 diff_params.flags = XDF_NEED_MINIMAL;
288 emit_params.ctxlen = 3; 289 emit_params.ctxlen = 3;
289 emit_params.flags = XDL_EMIT_FUNCNAMES; 290 emit_params.flags = XDL_EMIT_FUNCNAMES;
290 emit_cb.outf = filediff_cb; 291 emit_cb.outf = filediff_cb;
291 emit_cb.priv = fn; 292 emit_cb.priv = fn;
292 xdl_diff(&file1, &file2, &diff_params, &emit_params, &emit_cb); 293 xdl_diff(&file1, &file2, &diff_params, &emit_params, &emit_cb);
293 return 0; 294 return 0;
294} 295}
295 296
296void cgit_diff_tree(const unsigned char *old_sha1, 297void cgit_diff_tree(const unsigned char *old_sha1,
297 const unsigned char *new_sha1, 298 const unsigned char *new_sha1,
298 filepair_fn fn, const char *prefix) 299 filepair_fn fn, const char *prefix)
299{ 300{
300 struct diff_options opt; 301 struct diff_options opt;
301 int ret; 302 int ret;
302 int prefixlen; 303 int prefixlen;
303 304
304 diff_setup(&opt); 305 diff_setup(&opt);
305 opt.output_format = DIFF_FORMAT_CALLBACK; 306 opt.output_format = DIFF_FORMAT_CALLBACK;
306 opt.detect_rename = 1; 307 opt.detect_rename = 1;
307 opt.rename_limit = ctx.cfg.renamelimit; 308 opt.rename_limit = ctx.cfg.renamelimit;
308 DIFF_OPT_SET(&opt, RECURSIVE); 309 DIFF_OPT_SET(&opt, RECURSIVE);
309 opt.format_callback = cgit_diff_tree_cb; 310 opt.format_callback = cgit_diff_tree_cb;
310 opt.format_callback_data = fn; 311 opt.format_callback_data = fn;
311 if (prefix) { 312 if (prefix) {
312 opt.nr_paths = 1; 313 opt.nr_paths = 1;
313 opt.paths = &prefix; 314 opt.paths = &prefix;
314 prefixlen = strlen(prefix); 315 prefixlen = strlen(prefix);
315 opt.pathlens = &prefixlen; 316 opt.pathlens = &prefixlen;
316 } 317 }
317 diff_setup_done(&opt); 318 diff_setup_done(&opt);
318 319
319 if (old_sha1 && !is_null_sha1(old_sha1)) 320 if (old_sha1 && !is_null_sha1(old_sha1))
320 ret = diff_tree_sha1(old_sha1, new_sha1, "", &opt); 321 ret = diff_tree_sha1(old_sha1, new_sha1, "", &opt);
321 else 322 else
322 ret = diff_root_tree_sha1(new_sha1, "", &opt); 323 ret = diff_root_tree_sha1(new_sha1, "", &opt);
323 diffcore_std(&opt); 324 diffcore_std(&opt);
324 diff_flush(&opt); 325 diff_flush(&opt);
325} 326}
326 327
327void cgit_diff_commit(struct commit *commit, filepair_fn fn) 328void cgit_diff_commit(struct commit *commit, filepair_fn fn)
328{ 329{
329 unsigned char *old_sha1 = NULL; 330 unsigned char *old_sha1 = NULL;
330 331
331 if (commit->parents) 332 if (commit->parents)
332 old_sha1 = commit->parents->item->object.sha1; 333 old_sha1 = commit->parents->item->object.sha1;
333 cgit_diff_tree(old_sha1, commit->object.sha1, fn, NULL); 334 cgit_diff_tree(old_sha1, commit->object.sha1, fn, NULL);
334} 335}
335 336
336int cgit_parse_snapshots_mask(const char *str) 337int cgit_parse_snapshots_mask(const char *str)
337{ 338{
338 const struct cgit_snapshot_format *f; 339 const struct cgit_snapshot_format *f;
339 static const char *delim = " \t,:/|;"; 340 static const char *delim = " \t,:/|;";
340 int tl, sl, rv = 0; 341 int tl, sl, rv = 0;
341 342
342 /* favor legacy setting */ 343 /* favor legacy setting */
343 if(atoi(str)) 344 if(atoi(str))
344 return 1; 345 return 1;
345 for(;;) { 346 for(;;) {
346 str += strspn(str,delim); 347 str += strspn(str,delim);
347 tl = strcspn(str,delim); 348 tl = strcspn(str,delim);
348 if (!tl) 349 if (!tl)
349 break; 350 break;
350 for (f = cgit_snapshot_formats; f->suffix; f++) { 351 for (f = cgit_snapshot_formats; f->suffix; f++) {
351 sl = strlen(f->suffix); 352 sl = strlen(f->suffix);
352 if((tl == sl && !strncmp(f->suffix, str, tl)) || 353 if((tl == sl && !strncmp(f->suffix, str, tl)) ||
353 (tl == sl-1 && !strncmp(f->suffix+1, str, tl-1))) { 354 (tl == sl-1 && !strncmp(f->suffix+1, str, tl-1))) {
354 rv |= f->bit; 355 rv |= f->bit;
355 break; 356 break;
356 } 357 }
357 } 358 }
358 str += tl; 359 str += tl;
359 } 360 }
360 return rv; 361 return rv;
361} 362}
362 363
363int cgit_open_filter(struct cgit_filter *filter) 364int cgit_open_filter(struct cgit_filter *filter)
364{ 365{
365 366
366 filter->old_stdout = chk_positive(dup(STDOUT_FILENO), 367 filter->old_stdout = chk_positive(dup(STDOUT_FILENO),
367 "Unable to duplicate STDOUT"); 368 "Unable to duplicate STDOUT");
368 chk_zero(pipe(filter->pipe_fh), "Unable to create pipe to subprocess"); 369 chk_zero(pipe(filter->pipe_fh), "Unable to create pipe to subprocess");
369 filter->pid = chk_non_negative(fork(), "Unable to create subprocess"); 370 filter->pid = chk_non_negative(fork(), "Unable to create subprocess");
370 if (filter->pid == 0) { 371 if (filter->pid == 0) {
371 close(filter->pipe_fh[1]); 372 close(filter->pipe_fh[1]);
372 chk_non_negative(dup2(filter->pipe_fh[0], STDIN_FILENO), 373 chk_non_negative(dup2(filter->pipe_fh[0], STDIN_FILENO),
373 "Unable to use pipe as STDIN"); 374 "Unable to use pipe as STDIN");
374 execvp(filter->cmd, filter->argv); 375 execvp(filter->cmd, filter->argv);
375 die("Unable to exec subprocess %s: %s (%d)", filter->cmd, 376 die("Unable to exec subprocess %s: %s (%d)", filter->cmd,
376 strerror(errno), errno); 377 strerror(errno), errno);
377 } 378 }
378 close(filter->pipe_fh[0]); 379 close(filter->pipe_fh[0]);
379 chk_non_negative(dup2(filter->pipe_fh[1], STDOUT_FILENO), 380 chk_non_negative(dup2(filter->pipe_fh[1], STDOUT_FILENO),
380 "Unable to use pipe as STDOUT"); 381 "Unable to use pipe as STDOUT");
381 close(filter->pipe_fh[1]); 382 close(filter->pipe_fh[1]);
382 return 0; 383 return 0;
383} 384}
384 385
385int cgit_close_filter(struct cgit_filter *filter) 386int cgit_close_filter(struct cgit_filter *filter)
386{ 387{
387 chk_non_negative(dup2(filter->old_stdout, STDOUT_FILENO), 388 chk_non_negative(dup2(filter->old_stdout, STDOUT_FILENO),
388 "Unable to restore STDOUT"); 389 "Unable to restore STDOUT");
389 close(filter->old_stdout); 390 close(filter->old_stdout);
390 if (filter->pid < 0) 391 if (filter->pid < 0)
391 return 0; 392 return 0;
392 waitpid(filter->pid, &filter->exitstatus, 0); 393 waitpid(filter->pid, &filter->exitstatus, 0);
393 if (WIFEXITED(filter->exitstatus) && !WEXITSTATUS(filter->exitstatus)) 394 if (WIFEXITED(filter->exitstatus) && !WEXITSTATUS(filter->exitstatus))
394 return 0; 395 return 0;
395 die("Subprocess %s exited abnormally", filter->cmd); 396 die("Subprocess %s exited abnormally", filter->cmd);
396} 397}
397 398
398/* Read the content of the specified file into a newly allocated buffer, 399/* Read the content of the specified file into a newly allocated buffer,
399 * zeroterminate the buffer and return 0 on success, errno otherwise. 400 * zeroterminate the buffer and return 0 on success, errno otherwise.
400 */ 401 */
401int readfile(const char *path, char **buf, size_t *size) 402int readfile(const char *path, char **buf, size_t *size)
402{ 403{
403 int fd, e; 404 int fd, e;
404 struct stat st; 405 struct stat st;
405 406
406 fd = open(path, O_RDONLY); 407 fd = open(path, O_RDONLY);
407 if (fd == -1) 408 if (fd == -1)
408 return errno; 409 return errno;
409 if (fstat(fd, &st)) { 410 if (fstat(fd, &st)) {
410 e = errno; 411 e = errno;
411 close(fd); 412 close(fd);
412 return e; 413 return e;
413 } 414 }
414 if (!S_ISREG(st.st_mode)) { 415 if (!S_ISREG(st.st_mode)) {
415 close(fd); 416 close(fd);
416 return EISDIR; 417 return EISDIR;
417 } 418 }
418 *buf = xmalloc(st.st_size + 1); 419 *buf = xmalloc(st.st_size + 1);
419 *size = read_in_full(fd, *buf, st.st_size); 420 *size = read_in_full(fd, *buf, st.st_size);
420 e = errno; 421 e = errno;
421 (*buf)[*size] = '\0'; 422 (*buf)[*size] = '\0';
422 close(fd); 423 close(fd);
423 return (*size == st.st_size ? 0 : e); 424 return (*size == st.st_size ? 0 : e);
424} 425}
diff --git a/ui-refs.c b/ui-refs.c
index 33d9bec..98738db 100644
--- a/ui-refs.c
+++ b/ui-refs.c
@@ -1,245 +1,247 @@
1/* ui-refs.c: browse symbolic refs 1/* ui-refs.c: browse symbolic refs
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 12
13static int header; 13static int header;
14 14
15static int cmp_age(int age1, int age2) 15static int cmp_age(int age1, int age2)
16{ 16{
17 if (age1 != 0 && age2 != 0) 17 if (age1 != 0 && age2 != 0)
18 return age2 - age1; 18 return age2 - age1;
19 19
20 if (age1 == 0 && age2 == 0) 20 if (age1 == 0 && age2 == 0)
21 return 0; 21 return 0;
22 22
23 if (age1 == 0) 23 if (age1 == 0)
24 return +1; 24 return +1;
25 25
26 return -1; 26 return -1;
27} 27}
28 28
29static int cmp_ref_name(const void *a, const void *b) 29static int cmp_ref_name(const void *a, const void *b)
30{ 30{
31 struct refinfo *r1 = *(struct refinfo **)a; 31 struct refinfo *r1 = *(struct refinfo **)a;
32 struct refinfo *r2 = *(struct refinfo **)b; 32 struct refinfo *r2 = *(struct refinfo **)b;
33 33
34 return strcmp(r1->refname, r2->refname); 34 return strcmp(r1->refname, r2->refname);
35} 35}
36 36
37static int cmp_branch_age(const void *a, const void *b) 37static int cmp_branch_age(const void *a, const void *b)
38{ 38{
39 struct refinfo *r1 = *(struct refinfo **)a; 39 struct refinfo *r1 = *(struct refinfo **)a;
40 struct refinfo *r2 = *(struct refinfo **)b; 40 struct refinfo *r2 = *(struct refinfo **)b;
41 41
42 return cmp_age(r1->commit->committer_date, r2->commit->committer_date); 42 return cmp_age(r1->commit->committer_date, r2->commit->committer_date);
43} 43}
44 44
45static int cmp_tag_age(const void *a, const void *b) 45static int cmp_tag_age(const void *a, const void *b)
46{ 46{
47 struct refinfo *r1 = *(struct refinfo **)a; 47 struct refinfo *r1 = *(struct refinfo **)a;
48 struct refinfo *r2 = *(struct refinfo **)b; 48 struct refinfo *r2 = *(struct refinfo **)b;
49 int r1date, r2date; 49 int r1date, r2date;
50 50
51 if (r1->object->type != OBJ_COMMIT) 51 if (r1->object->type != OBJ_COMMIT)
52 r1date = r1->tag->tagger_date; 52 r1date = r1->tag->tagger_date;
53 else 53 else
54 r1date = r1->commit->committer_date; 54 r1date = r1->commit->committer_date;
55 55
56 if (r2->object->type != OBJ_COMMIT) 56 if (r2->object->type != OBJ_COMMIT)
57 r2date = r2->tag->tagger_date; 57 r2date = r2->tag->tagger_date;
58 else 58 else
59 r2date = r2->commit->committer_date; 59 r2date = r2->commit->committer_date;
60 60
61 return cmp_age(r1date, r2date); 61 return cmp_age(r1date, r2date);
62} 62}
63 63
64static int print_branch(struct refinfo *ref) 64static int print_branch(struct refinfo *ref)
65{ 65{
66 struct commitinfo *info = ref->commit; 66 struct commitinfo *info = ref->commit;
67 char *name = (char *)ref->refname; 67 char *name = (char *)ref->refname;
68 68
69 if (!info) 69 if (!info)
70 return 1; 70 return 1;
71 html("<tr><td>"); 71 html("<tr><td>");
72 cgit_log_link(name, NULL, NULL, name, NULL, NULL, 0, NULL, NULL, 72 cgit_log_link(name, NULL, NULL, name, NULL, NULL, 0, NULL, NULL,
73 ctx.qry.showmsg); 73 ctx.qry.showmsg);
74 html("</td><td>"); 74 html("</td><td>");
75 75
76 if (ref->object->type == OBJ_COMMIT) { 76 if (ref->object->type == OBJ_COMMIT) {
77 cgit_commit_link(info->subject, NULL, NULL, name, NULL, 0); 77 cgit_commit_link(info->subject, NULL, NULL, name, NULL, 0);
78 html("</td><td>"); 78 html("</td><td>");
79 html_txt(info->author); 79 html_txt(info->author);
80 html("</td><td colspan='2'>"); 80 html("</td><td colspan='2'>");
81 cgit_print_age(info->commit->date, -1, NULL); 81 cgit_print_age(info->commit->date, -1, NULL);
82 } else { 82 } else {
83 html("</td><td></td><td>"); 83 html("</td><td></td><td>");
84 cgit_object_link(ref->object); 84 cgit_object_link(ref->object);
85 } 85 }
86 html("</td></tr>\n"); 86 html("</td></tr>\n");
87 return 0; 87 return 0;
88} 88}
89 89
90static void print_tag_header() 90static void print_tag_header()
91{ 91{
92 html("<tr class='nohover'><th class='left'>Tag</th>" 92 html("<tr class='nohover'><th class='left'>Tag</th>"
93 "<th class='left'>Download</th>" 93 "<th class='left'>Download</th>"
94 "<th class='left'>Author</th>" 94 "<th class='left'>Author</th>"
95 "<th class='left' colspan='2'>Age</th></tr>\n"); 95 "<th class='left' colspan='2'>Age</th></tr>\n");
96 header = 1; 96 header = 1;
97} 97}
98 98
99static void print_tag_downloads(const struct cgit_repo *repo, const char *ref) 99static void print_tag_downloads(const struct cgit_repo *repo, const char *ref)
100{ 100{
101 const struct cgit_snapshot_format* f; 101 const struct cgit_snapshot_format* f;
102 char *filename; 102 char *filename;
103 const char *basename; 103 const char *basename;
104 104
105 if (!ref || strlen(ref) < 2) 105 if (!ref || strlen(ref) < 2)
106 return; 106 return;
107 107
108 basename = cgit_repobasename(repo->url); 108 basename = cgit_repobasename(repo->url);
109 if (prefixcmp(ref, basename) != 0) { 109 if (prefixcmp(ref, basename) != 0) {
110 if ((ref[0] == 'v' || ref[0] == 'V') && isdigit(ref[1])) 110 if ((ref[0] == 'v' || ref[0] == 'V') && isdigit(ref[1]))
111 ref++; 111 ref++;
112 if (isdigit(ref[0])) 112 if (isdigit(ref[0]))
113 ref = xstrdup(fmt("%s-%s", basename, ref)); 113 ref = xstrdup(fmt("%s-%s", basename, ref));
114 } 114 }
115 115
116 for (f = cgit_snapshot_formats; f->suffix; f++) { 116 for (f = cgit_snapshot_formats; f->suffix; f++) {
117 if (!(repo->snapshots & f->bit)) 117 if (!(repo->snapshots & f->bit))
118 continue; 118 continue;
119 filename = fmt("%s%s", ref, f->suffix); 119 filename = fmt("%s%s", ref, f->suffix);
120 cgit_snapshot_link(filename, NULL, NULL, NULL, NULL, filename); 120 cgit_snapshot_link(filename, NULL, NULL, NULL, NULL, filename);
121 html("&nbsp;&nbsp;"); 121 html("&nbsp;&nbsp;");
122 } 122 }
123} 123}
124static int print_tag(struct refinfo *ref) 124static int print_tag(struct refinfo *ref)
125{ 125{
126 struct tag *tag; 126 struct tag *tag;
127 struct taginfo *info; 127 struct taginfo *info;
128 char *name = (char *)ref->refname; 128 char *name = (char *)ref->refname;
129 129
130 if (ref->object->type == OBJ_TAG) { 130 if (ref->object->type == OBJ_TAG) {
131 tag = (struct tag *)ref->object; 131 tag = (struct tag *)ref->object;
132 info = ref->tag; 132 info = ref->tag;
133 if (!tag || !info) 133 if (!tag || !info)
134 return 1; 134 return 1;
135 html("<tr><td>"); 135 html("<tr><td>");
136 cgit_tag_link(name, NULL, NULL, ctx.qry.head, name); 136 cgit_tag_link(name, NULL, NULL, ctx.qry.head, name);
137 html("</td><td>"); 137 html("</td><td>");
138 if (ctx.repo->snapshots && (tag->tagged->type == OBJ_COMMIT)) 138 if (ctx.repo->snapshots && (tag->tagged->type == OBJ_COMMIT))
139 print_tag_downloads(ctx.repo, name); 139 print_tag_downloads(ctx.repo, name);
140 else 140 else
141 cgit_object_link(tag->tagged); 141 cgit_object_link(tag->tagged);
142 html("</td><td>"); 142 html("</td><td>");
143 if (info->tagger) 143 if (info->tagger)
144 html(info->tagger); 144 html(info->tagger);
145 html("</td><td colspan='2'>"); 145 html("</td><td colspan='2'>");
146 if (info->tagger_date > 0) 146 if (info->tagger_date > 0)
147 cgit_print_age(info->tagger_date, -1, NULL); 147 cgit_print_age(info->tagger_date, -1, NULL);
148 html("</td></tr>\n"); 148 html("</td></tr>\n");
149 } else { 149 } else {
150 if (!header) 150 if (!header)
151 print_tag_header(); 151 print_tag_header();
152 html("<tr><td>"); 152 html("<tr><td>");
153 cgit_tag_link(name, NULL, NULL, ctx.qry.head, name); 153 cgit_tag_link(name, NULL, NULL, ctx.qry.head, name);
154 html("</td><td>"); 154 html("</td><td>");
155 if (ctx.repo->snapshots && (ref->object->type == OBJ_COMMIT)) 155 if (ctx.repo->snapshots && (ref->object->type == OBJ_COMMIT))
156 print_tag_downloads(ctx.repo, name); 156 print_tag_downloads(ctx.repo, name);
157 else 157 else
158 cgit_object_link(ref->object); 158 cgit_object_link(ref->object);
159 html("</td><td>"); 159 html("</td><td>");
160 if (ref->object->type == OBJ_COMMIT) 160 if (ref->object->type == OBJ_COMMIT)
161 html(ref->commit->author); 161 html(ref->commit->author);
162 html("</td><td colspan='2'>"); 162 html("</td><td colspan='2'>");
163 if (ref->object->type == OBJ_COMMIT) 163 if (ref->object->type == OBJ_COMMIT)
164 cgit_print_age(ref->commit->commit->date, -1, NULL); 164 cgit_print_age(ref->commit->commit->date, -1, NULL);
165 html("</td></tr>\n"); 165 html("</td></tr>\n");
166 } 166 }
167 return 0; 167 return 0;
168} 168}
169 169
170static void print_refs_link(char *path) 170static void print_refs_link(char *path)
171{ 171{
172 html("<tr class='nohover'><td colspan='4'>"); 172 html("<tr class='nohover'><td colspan='4'>");
173 cgit_refs_link("[...]", NULL, NULL, ctx.qry.head, NULL, path); 173 cgit_refs_link("[...]", NULL, NULL, ctx.qry.head, NULL, path);
174 html("</td></tr>"); 174 html("</td></tr>");
175} 175}
176 176
177void cgit_print_branches(int maxcount) 177void cgit_print_branches(int maxcount)
178{ 178{
179 struct reflist list; 179 struct reflist list;
180 int i; 180 int i;
181 181
182 html("<tr class='nohover'><th class='left'>Branch</th>" 182 html("<tr class='nohover'><th class='left'>Branch</th>"
183 "<th class='left'>Commit message</th>" 183 "<th class='left'>Commit message</th>"
184 "<th class='left'>Author</th>" 184 "<th class='left'>Author</th>"
185 "<th class='left' colspan='2'>Age</th></tr>\n"); 185 "<th class='left' colspan='2'>Age</th></tr>\n");
186 186
187 list.refs = NULL; 187 list.refs = NULL;
188 list.alloc = list.count = 0; 188 list.alloc = list.count = 0;
189 for_each_branch_ref(cgit_refs_cb, &list); 189 for_each_branch_ref(cgit_refs_cb, &list);
190 if (ctx.repo->enable_remote_branches)
191 for_each_remote_ref(cgit_refs_cb, &list);
190 192
191 if (maxcount == 0 || maxcount > list.count) 193 if (maxcount == 0 || maxcount > list.count)
192 maxcount = list.count; 194 maxcount = list.count;
193 195
194 if (maxcount < list.count) { 196 if (maxcount < list.count) {
195 qsort(list.refs, list.count, sizeof(*list.refs), cmp_branch_age); 197 qsort(list.refs, list.count, sizeof(*list.refs), cmp_branch_age);
196 qsort(list.refs, maxcount, sizeof(*list.refs), cmp_ref_name); 198 qsort(list.refs, maxcount, sizeof(*list.refs), cmp_ref_name);
197 } 199 }
198 200
199 for(i=0; i<maxcount; i++) 201 for(i=0; i<maxcount; i++)
200 print_branch(list.refs[i]); 202 print_branch(list.refs[i]);
201 203
202 if (maxcount < list.count) 204 if (maxcount < list.count)
203 print_refs_link("heads"); 205 print_refs_link("heads");
204} 206}
205 207
206void cgit_print_tags(int maxcount) 208void cgit_print_tags(int maxcount)
207{ 209{
208 struct reflist list; 210 struct reflist list;
209 int i; 211 int i;
210 212
211 header = 0; 213 header = 0;
212 list.refs = NULL; 214 list.refs = NULL;
213 list.alloc = list.count = 0; 215 list.alloc = list.count = 0;
214 for_each_tag_ref(cgit_refs_cb, &list); 216 for_each_tag_ref(cgit_refs_cb, &list);
215 if (list.count == 0) 217 if (list.count == 0)
216 return; 218 return;
217 qsort(list.refs, list.count, sizeof(*list.refs), cmp_tag_age); 219 qsort(list.refs, list.count, sizeof(*list.refs), cmp_tag_age);
218 if (!maxcount) 220 if (!maxcount)
219 maxcount = list.count; 221 maxcount = list.count;
220 else if (maxcount > list.count) 222 else if (maxcount > list.count)
221 maxcount = list.count; 223 maxcount = list.count;
222 print_tag_header(); 224 print_tag_header();
223 for(i=0; i<maxcount; i++) 225 for(i=0; i<maxcount; i++)
224 print_tag(list.refs[i]); 226 print_tag(list.refs[i]);
225 227
226 if (maxcount < list.count) 228 if (maxcount < list.count)
227 print_refs_link("tags"); 229 print_refs_link("tags");
228} 230}
229 231
230void cgit_print_refs() 232void cgit_print_refs()
231{ 233{
232 234
233 html("<table class='list nowrap'>"); 235 html("<table class='list nowrap'>");
234 236
235 if (ctx.qry.path && !strncmp(ctx.qry.path, "heads", 5)) 237 if (ctx.qry.path && !strncmp(ctx.qry.path, "heads", 5))
236 cgit_print_branches(0); 238 cgit_print_branches(0);
237 else if (ctx.qry.path && !strncmp(ctx.qry.path, "tags", 4)) 239 else if (ctx.qry.path && !strncmp(ctx.qry.path, "tags", 4))
238 cgit_print_tags(0); 240 cgit_print_tags(0);
239 else { 241 else {
240 cgit_print_branches(0); 242 cgit_print_branches(0);
241 html("<tr class='nohover'><td colspan='4'>&nbsp;</td></tr>"); 243 html("<tr class='nohover'><td colspan='4'>&nbsp;</td></tr>");
242 cgit_print_tags(0); 244 cgit_print_tags(0);
243 } 245 }
244 html("</table>"); 246 html("</table>");
245} 247}