summaryrefslogtreecommitdiffabout
authorJason A. Donenfeld <Jason@zx2c4.com>2010-07-29 17:47:50 (UTC)
committer Lars Hjemli <hjemli@gmail.com>2010-08-04 01:09:32 (UTC)
commit2e4a941626c240bc7858aa7564882c01f657f4e8 (patch) (unidiff)
tree505c17fab5afcb99cfddf2c4aad0b95c8670f001
parent3516502aa0df95ecc241caa30161741f59e4e600 (diff)
downloadcgit-2e4a941626c240bc7858aa7564882c01f657f4e8.zip
cgit-2e4a941626c240bc7858aa7564882c01f657f4e8.tar.gz
cgit-2e4a941626c240bc7858aa7564882c01f657f4e8.tar.bz2
Add support for 'remove-suffix' option
When this option is enabled, the '.git' suffix of repository directories found while processing the 'scan-path' option will be removed. Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com> Signed-off-by: Lars Hjemli <hjemli@gmail.com>
Diffstat (more/less context) (ignore whitespace changes)
-rw-r--r--cgit.c3
-rw-r--r--cgit.h1
-rw-r--r--cgitrc.5.txt6
-rw-r--r--scan-tree.c3
4 files changed, 13 insertions, 0 deletions
diff --git a/cgit.c b/cgit.c
index 2364d1c..f9a42bb 100644
--- a/cgit.c
+++ b/cgit.c
@@ -1,768 +1,771 @@
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")) 63 else if (!strcmp(name, "enable-remote-branches"))
64 repo->enable_remote_branches = atoi(value); 64 repo->enable_remote_branches = atoi(value);
65 else if (!strcmp(name, "enable-subject-links")) 65 else if (!strcmp(name, "enable-subject-links"))
66 repo->enable_subject_links = atoi(value); 66 repo->enable_subject_links = atoi(value);
67 else if (!strcmp(name, "max-stats")) 67 else if (!strcmp(name, "max-stats"))
68 repo->max_stats = cgit_find_stats_period(value, NULL); 68 repo->max_stats = cgit_find_stats_period(value, NULL);
69 else if (!strcmp(name, "module-link")) 69 else if (!strcmp(name, "module-link"))
70 repo->module_link= xstrdup(value); 70 repo->module_link= xstrdup(value);
71 else if (!strcmp(name, "section")) 71 else if (!strcmp(name, "section"))
72 repo->section = xstrdup(value); 72 repo->section = xstrdup(value);
73 else if (!strcmp(name, "readme") && value != NULL) { 73 else if (!strcmp(name, "readme") && value != NULL) {
74 if (*value == '/') 74 if (*value == '/')
75 repo->readme = xstrdup(value); 75 repo->readme = xstrdup(value);
76 else 76 else
77 repo->readme = xstrdup(fmt("%s/%s", repo->path, value)); 77 repo->readme = xstrdup(fmt("%s/%s", repo->path, value));
78 } else if (ctx.cfg.enable_filter_overrides) { 78 } else if (ctx.cfg.enable_filter_overrides) {
79 if (!strcmp(name, "about-filter")) 79 if (!strcmp(name, "about-filter"))
80 repo->about_filter = new_filter(value, 0); 80 repo->about_filter = new_filter(value, 0);
81 else if (!strcmp(name, "commit-filter")) 81 else if (!strcmp(name, "commit-filter"))
82 repo->commit_filter = new_filter(value, 0); 82 repo->commit_filter = new_filter(value, 0);
83 else if (!strcmp(name, "source-filter")) 83 else if (!strcmp(name, "source-filter"))
84 repo->source_filter = new_filter(value, 1); 84 repo->source_filter = new_filter(value, 1);
85 } 85 }
86} 86}
87 87
88void config_cb(const char *name, const char *value) 88void config_cb(const char *name, const char *value)
89{ 89{
90 if (!strcmp(name, "section") || !strcmp(name, "repo.group")) 90 if (!strcmp(name, "section") || !strcmp(name, "repo.group"))
91 ctx.cfg.section = xstrdup(value); 91 ctx.cfg.section = xstrdup(value);
92 else if (!strcmp(name, "repo.url")) 92 else if (!strcmp(name, "repo.url"))
93 ctx.repo = cgit_add_repo(value); 93 ctx.repo = cgit_add_repo(value);
94 else if (ctx.repo && !strcmp(name, "repo.path")) 94 else if (ctx.repo && !strcmp(name, "repo.path"))
95 ctx.repo->path = trim_end(value, '/'); 95 ctx.repo->path = trim_end(value, '/');
96 else if (ctx.repo && !prefixcmp(name, "repo.")) 96 else if (ctx.repo && !prefixcmp(name, "repo."))
97 repo_config(ctx.repo, name + 5, value); 97 repo_config(ctx.repo, name + 5, value);
98 else if (!strcmp(name, "root-title")) 98 else if (!strcmp(name, "root-title"))
99 ctx.cfg.root_title = xstrdup(value); 99 ctx.cfg.root_title = xstrdup(value);
100 else if (!strcmp(name, "root-desc")) 100 else if (!strcmp(name, "root-desc"))
101 ctx.cfg.root_desc = xstrdup(value); 101 ctx.cfg.root_desc = xstrdup(value);
102 else if (!strcmp(name, "root-readme")) 102 else if (!strcmp(name, "root-readme"))
103 ctx.cfg.root_readme = xstrdup(value); 103 ctx.cfg.root_readme = xstrdup(value);
104 else if (!strcmp(name, "css")) 104 else if (!strcmp(name, "css"))
105 ctx.cfg.css = xstrdup(value); 105 ctx.cfg.css = xstrdup(value);
106 else if (!strcmp(name, "favicon")) 106 else if (!strcmp(name, "favicon"))
107 ctx.cfg.favicon = xstrdup(value); 107 ctx.cfg.favicon = xstrdup(value);
108 else if (!strcmp(name, "footer")) 108 else if (!strcmp(name, "footer"))
109 ctx.cfg.footer = xstrdup(value); 109 ctx.cfg.footer = xstrdup(value);
110 else if (!strcmp(name, "head-include")) 110 else if (!strcmp(name, "head-include"))
111 ctx.cfg.head_include = xstrdup(value); 111 ctx.cfg.head_include = xstrdup(value);
112 else if (!strcmp(name, "header")) 112 else if (!strcmp(name, "header"))
113 ctx.cfg.header = xstrdup(value); 113 ctx.cfg.header = xstrdup(value);
114 else if (!strcmp(name, "logo")) 114 else if (!strcmp(name, "logo"))
115 ctx.cfg.logo = xstrdup(value); 115 ctx.cfg.logo = xstrdup(value);
116 else if (!strcmp(name, "index-header")) 116 else if (!strcmp(name, "index-header"))
117 ctx.cfg.index_header = xstrdup(value); 117 ctx.cfg.index_header = xstrdup(value);
118 else if (!strcmp(name, "index-info")) 118 else if (!strcmp(name, "index-info"))
119 ctx.cfg.index_info = xstrdup(value); 119 ctx.cfg.index_info = xstrdup(value);
120 else if (!strcmp(name, "logo-link")) 120 else if (!strcmp(name, "logo-link"))
121 ctx.cfg.logo_link = xstrdup(value); 121 ctx.cfg.logo_link = xstrdup(value);
122 else if (!strcmp(name, "module-link")) 122 else if (!strcmp(name, "module-link"))
123 ctx.cfg.module_link = xstrdup(value); 123 ctx.cfg.module_link = xstrdup(value);
124 else if (!strcmp(name, "virtual-root")) { 124 else if (!strcmp(name, "virtual-root")) {
125 ctx.cfg.virtual_root = trim_end(value, '/'); 125 ctx.cfg.virtual_root = trim_end(value, '/');
126 if (!ctx.cfg.virtual_root && (!strcmp(value, "/"))) 126 if (!ctx.cfg.virtual_root && (!strcmp(value, "/")))
127 ctx.cfg.virtual_root = ""; 127 ctx.cfg.virtual_root = "";
128 } else if (!strcmp(name, "nocache")) 128 } else if (!strcmp(name, "nocache"))
129 ctx.cfg.nocache = atoi(value); 129 ctx.cfg.nocache = atoi(value);
130 else if (!strcmp(name, "noplainemail")) 130 else if (!strcmp(name, "noplainemail"))
131 ctx.cfg.noplainemail = atoi(value); 131 ctx.cfg.noplainemail = atoi(value);
132 else if (!strcmp(name, "noheader")) 132 else if (!strcmp(name, "noheader"))
133 ctx.cfg.noheader = atoi(value); 133 ctx.cfg.noheader = atoi(value);
134 else if (!strcmp(name, "snapshots")) 134 else if (!strcmp(name, "snapshots"))
135 ctx.cfg.snapshots = cgit_parse_snapshots_mask(value); 135 ctx.cfg.snapshots = cgit_parse_snapshots_mask(value);
136 else if (!strcmp(name, "enable-filter-overrides")) 136 else if (!strcmp(name, "enable-filter-overrides"))
137 ctx.cfg.enable_filter_overrides = atoi(value); 137 ctx.cfg.enable_filter_overrides = atoi(value);
138 else if (!strcmp(name, "enable-index-links")) 138 else if (!strcmp(name, "enable-index-links"))
139 ctx.cfg.enable_index_links = atoi(value); 139 ctx.cfg.enable_index_links = atoi(value);
140 else if (!strcmp(name, "enable-log-filecount")) 140 else if (!strcmp(name, "enable-log-filecount"))
141 ctx.cfg.enable_log_filecount = atoi(value); 141 ctx.cfg.enable_log_filecount = atoi(value);
142 else if (!strcmp(name, "enable-log-linecount")) 142 else if (!strcmp(name, "enable-log-linecount"))
143 ctx.cfg.enable_log_linecount = atoi(value); 143 ctx.cfg.enable_log_linecount = atoi(value);
144 else if (!strcmp(name, "enable-remote-branches")) 144 else if (!strcmp(name, "enable-remote-branches"))
145 ctx.cfg.enable_remote_branches = atoi(value); 145 ctx.cfg.enable_remote_branches = atoi(value);
146 else if (!strcmp(name, "enable-subject-links")) 146 else if (!strcmp(name, "enable-subject-links"))
147 ctx.cfg.enable_subject_links = atoi(value); 147 ctx.cfg.enable_subject_links = atoi(value);
148 else if (!strcmp(name, "enable-tree-linenumbers")) 148 else if (!strcmp(name, "enable-tree-linenumbers"))
149 ctx.cfg.enable_tree_linenumbers = atoi(value); 149 ctx.cfg.enable_tree_linenumbers = atoi(value);
150 else if (!strcmp(name, "max-stats")) 150 else if (!strcmp(name, "max-stats"))
151 ctx.cfg.max_stats = cgit_find_stats_period(value, NULL); 151 ctx.cfg.max_stats = cgit_find_stats_period(value, NULL);
152 else if (!strcmp(name, "cache-size")) 152 else if (!strcmp(name, "cache-size"))
153 ctx.cfg.cache_size = atoi(value); 153 ctx.cfg.cache_size = atoi(value);
154 else if (!strcmp(name, "cache-root")) 154 else if (!strcmp(name, "cache-root"))
155 ctx.cfg.cache_root = xstrdup(expand_macros(value)); 155 ctx.cfg.cache_root = xstrdup(expand_macros(value));
156 else if (!strcmp(name, "cache-root-ttl")) 156 else if (!strcmp(name, "cache-root-ttl"))
157 ctx.cfg.cache_root_ttl = atoi(value); 157 ctx.cfg.cache_root_ttl = atoi(value);
158 else if (!strcmp(name, "cache-repo-ttl")) 158 else if (!strcmp(name, "cache-repo-ttl"))
159 ctx.cfg.cache_repo_ttl = atoi(value); 159 ctx.cfg.cache_repo_ttl = atoi(value);
160 else if (!strcmp(name, "cache-scanrc-ttl")) 160 else if (!strcmp(name, "cache-scanrc-ttl"))
161 ctx.cfg.cache_scanrc_ttl = atoi(value); 161 ctx.cfg.cache_scanrc_ttl = atoi(value);
162 else if (!strcmp(name, "cache-static-ttl")) 162 else if (!strcmp(name, "cache-static-ttl"))
163 ctx.cfg.cache_static_ttl = atoi(value); 163 ctx.cfg.cache_static_ttl = atoi(value);
164 else if (!strcmp(name, "cache-dynamic-ttl")) 164 else if (!strcmp(name, "cache-dynamic-ttl"))
165 ctx.cfg.cache_dynamic_ttl = atoi(value); 165 ctx.cfg.cache_dynamic_ttl = atoi(value);
166 else if (!strcmp(name, "about-filter")) 166 else if (!strcmp(name, "about-filter"))
167 ctx.cfg.about_filter = new_filter(value, 0); 167 ctx.cfg.about_filter = new_filter(value, 0);
168 else if (!strcmp(name, "commit-filter")) 168 else if (!strcmp(name, "commit-filter"))
169 ctx.cfg.commit_filter = new_filter(value, 0); 169 ctx.cfg.commit_filter = new_filter(value, 0);
170 else if (!strcmp(name, "embedded")) 170 else if (!strcmp(name, "embedded"))
171 ctx.cfg.embedded = atoi(value); 171 ctx.cfg.embedded = atoi(value);
172 else if (!strcmp(name, "max-atom-items")) 172 else if (!strcmp(name, "max-atom-items"))
173 ctx.cfg.max_atom_items = atoi(value); 173 ctx.cfg.max_atom_items = atoi(value);
174 else if (!strcmp(name, "max-message-length")) 174 else if (!strcmp(name, "max-message-length"))
175 ctx.cfg.max_msg_len = atoi(value); 175 ctx.cfg.max_msg_len = atoi(value);
176 else if (!strcmp(name, "max-repodesc-length")) 176 else if (!strcmp(name, "max-repodesc-length"))
177 ctx.cfg.max_repodesc_len = atoi(value); 177 ctx.cfg.max_repodesc_len = atoi(value);
178 else if (!strcmp(name, "max-blob-size")) 178 else if (!strcmp(name, "max-blob-size"))
179 ctx.cfg.max_blob_size = atoi(value); 179 ctx.cfg.max_blob_size = atoi(value);
180 else if (!strcmp(name, "max-repo-count")) 180 else if (!strcmp(name, "max-repo-count"))
181 ctx.cfg.max_repo_count = atoi(value); 181 ctx.cfg.max_repo_count = atoi(value);
182 else if (!strcmp(name, "max-commit-count")) 182 else if (!strcmp(name, "max-commit-count"))
183 ctx.cfg.max_commit_count = atoi(value); 183 ctx.cfg.max_commit_count = atoi(value);
184 else if (!strcmp(name, "project-list")) 184 else if (!strcmp(name, "project-list"))
185 ctx.cfg.project_list = xstrdup(expand_macros(value)); 185 ctx.cfg.project_list = xstrdup(expand_macros(value));
186 else if (!strcmp(name, "scan-path")) 186 else if (!strcmp(name, "scan-path"))
187 if (!ctx.cfg.nocache && ctx.cfg.cache_size) 187 if (!ctx.cfg.nocache && ctx.cfg.cache_size)
188 process_cached_repolist(expand_macros(value)); 188 process_cached_repolist(expand_macros(value));
189 else if (ctx.cfg.project_list) 189 else if (ctx.cfg.project_list)
190 scan_projects(expand_macros(value), 190 scan_projects(expand_macros(value),
191 ctx.cfg.project_list, repo_config); 191 ctx.cfg.project_list, repo_config);
192 else 192 else
193 scan_tree(expand_macros(value), repo_config); 193 scan_tree(expand_macros(value), repo_config);
194 else if (!strcmp(name, "source-filter")) 194 else if (!strcmp(name, "source-filter"))
195 ctx.cfg.source_filter = new_filter(value, 1); 195 ctx.cfg.source_filter = new_filter(value, 1);
196 else if (!strcmp(name, "summary-log")) 196 else if (!strcmp(name, "summary-log"))
197 ctx.cfg.summary_log = atoi(value); 197 ctx.cfg.summary_log = atoi(value);
198 else if (!strcmp(name, "summary-branches")) 198 else if (!strcmp(name, "summary-branches"))
199 ctx.cfg.summary_branches = atoi(value); 199 ctx.cfg.summary_branches = atoi(value);
200 else if (!strcmp(name, "summary-tags")) 200 else if (!strcmp(name, "summary-tags"))
201 ctx.cfg.summary_tags = atoi(value); 201 ctx.cfg.summary_tags = atoi(value);
202 else if (!strcmp(name, "side-by-side-diffs")) 202 else if (!strcmp(name, "side-by-side-diffs"))
203 ctx.cfg.ssdiff = atoi(value); 203 ctx.cfg.ssdiff = atoi(value);
204 else if (!strcmp(name, "agefile")) 204 else if (!strcmp(name, "agefile"))
205 ctx.cfg.agefile = xstrdup(value); 205 ctx.cfg.agefile = xstrdup(value);
206 else if (!strcmp(name, "renamelimit")) 206 else if (!strcmp(name, "renamelimit"))
207 ctx.cfg.renamelimit = atoi(value); 207 ctx.cfg.renamelimit = atoi(value);
208 else if (!strcmp(name, "remove-suffix"))
209 ctx.cfg.remove_suffix = atoi(value);
208 else if (!strcmp(name, "robots")) 210 else if (!strcmp(name, "robots"))
209 ctx.cfg.robots = xstrdup(value); 211 ctx.cfg.robots = xstrdup(value);
210 else if (!strcmp(name, "clone-prefix")) 212 else if (!strcmp(name, "clone-prefix"))
211 ctx.cfg.clone_prefix = xstrdup(value); 213 ctx.cfg.clone_prefix = xstrdup(value);
212 else if (!strcmp(name, "local-time")) 214 else if (!strcmp(name, "local-time"))
213 ctx.cfg.local_time = atoi(value); 215 ctx.cfg.local_time = atoi(value);
214 else if (!prefixcmp(name, "mimetype.")) 216 else if (!prefixcmp(name, "mimetype."))
215 add_mimetype(name + 9, value); 217 add_mimetype(name + 9, value);
216 else if (!strcmp(name, "include")) 218 else if (!strcmp(name, "include"))
217 parse_configfile(expand_macros(value), config_cb); 219 parse_configfile(expand_macros(value), config_cb);
218} 220}
219 221
220static void querystring_cb(const char *name, const char *value) 222static void querystring_cb(const char *name, const char *value)
221{ 223{
222 if (!value) 224 if (!value)
223 value = ""; 225 value = "";
224 226
225 if (!strcmp(name,"r")) { 227 if (!strcmp(name,"r")) {
226 ctx.qry.repo = xstrdup(value); 228 ctx.qry.repo = xstrdup(value);
227 ctx.repo = cgit_get_repoinfo(value); 229 ctx.repo = cgit_get_repoinfo(value);
228 } else if (!strcmp(name, "p")) { 230 } else if (!strcmp(name, "p")) {
229 ctx.qry.page = xstrdup(value); 231 ctx.qry.page = xstrdup(value);
230 } else if (!strcmp(name, "url")) { 232 } else if (!strcmp(name, "url")) {
231 if (*value == '/') 233 if (*value == '/')
232 value++; 234 value++;
233 ctx.qry.url = xstrdup(value); 235 ctx.qry.url = xstrdup(value);
234 cgit_parse_url(value); 236 cgit_parse_url(value);
235 } else if (!strcmp(name, "qt")) { 237 } else if (!strcmp(name, "qt")) {
236 ctx.qry.grep = xstrdup(value); 238 ctx.qry.grep = xstrdup(value);
237 } else if (!strcmp(name, "q")) { 239 } else if (!strcmp(name, "q")) {
238 ctx.qry.search = xstrdup(value); 240 ctx.qry.search = xstrdup(value);
239 } else if (!strcmp(name, "h")) { 241 } else if (!strcmp(name, "h")) {
240 ctx.qry.head = xstrdup(value); 242 ctx.qry.head = xstrdup(value);
241 ctx.qry.has_symref = 1; 243 ctx.qry.has_symref = 1;
242 } else if (!strcmp(name, "id")) { 244 } else if (!strcmp(name, "id")) {
243 ctx.qry.sha1 = xstrdup(value); 245 ctx.qry.sha1 = xstrdup(value);
244 ctx.qry.has_sha1 = 1; 246 ctx.qry.has_sha1 = 1;
245 } else if (!strcmp(name, "id2")) { 247 } else if (!strcmp(name, "id2")) {
246 ctx.qry.sha2 = xstrdup(value); 248 ctx.qry.sha2 = xstrdup(value);
247 ctx.qry.has_sha1 = 1; 249 ctx.qry.has_sha1 = 1;
248 } else if (!strcmp(name, "ofs")) { 250 } else if (!strcmp(name, "ofs")) {
249 ctx.qry.ofs = atoi(value); 251 ctx.qry.ofs = atoi(value);
250 } else if (!strcmp(name, "path")) { 252 } else if (!strcmp(name, "path")) {
251 ctx.qry.path = trim_end(value, '/'); 253 ctx.qry.path = trim_end(value, '/');
252 } else if (!strcmp(name, "name")) { 254 } else if (!strcmp(name, "name")) {
253 ctx.qry.name = xstrdup(value); 255 ctx.qry.name = xstrdup(value);
254 } else if (!strcmp(name, "mimetype")) { 256 } else if (!strcmp(name, "mimetype")) {
255 ctx.qry.mimetype = xstrdup(value); 257 ctx.qry.mimetype = xstrdup(value);
256 } else if (!strcmp(name, "s")){ 258 } else if (!strcmp(name, "s")){
257 ctx.qry.sort = xstrdup(value); 259 ctx.qry.sort = xstrdup(value);
258 } else if (!strcmp(name, "showmsg")) { 260 } else if (!strcmp(name, "showmsg")) {
259 ctx.qry.showmsg = atoi(value); 261 ctx.qry.showmsg = atoi(value);
260 } else if (!strcmp(name, "period")) { 262 } else if (!strcmp(name, "period")) {
261 ctx.qry.period = xstrdup(value); 263 ctx.qry.period = xstrdup(value);
262 } else if (!strcmp(name, "ss")) { 264 } else if (!strcmp(name, "ss")) {
263 ctx.qry.ssdiff = atoi(value); 265 ctx.qry.ssdiff = atoi(value);
264 } else if (!strcmp(name, "all")) { 266 } else if (!strcmp(name, "all")) {
265 ctx.qry.show_all = atoi(value); 267 ctx.qry.show_all = atoi(value);
266 } else if (!strcmp(name, "context")) { 268 } else if (!strcmp(name, "context")) {
267 ctx.qry.context = atoi(value); 269 ctx.qry.context = atoi(value);
268 } else if (!strcmp(name, "ignorews")) { 270 } else if (!strcmp(name, "ignorews")) {
269 ctx.qry.ignorews = atoi(value); 271 ctx.qry.ignorews = atoi(value);
270 } 272 }
271} 273}
272 274
273char *xstrdupn(const char *str) 275char *xstrdupn(const char *str)
274{ 276{
275 return (str ? xstrdup(str) : NULL); 277 return (str ? xstrdup(str) : NULL);
276} 278}
277 279
278static void prepare_context(struct cgit_context *ctx) 280static void prepare_context(struct cgit_context *ctx)
279{ 281{
280 memset(ctx, 0, sizeof(*ctx)); 282 memset(ctx, 0, sizeof(*ctx));
281 ctx->cfg.agefile = "info/web/last-modified"; 283 ctx->cfg.agefile = "info/web/last-modified";
282 ctx->cfg.nocache = 0; 284 ctx->cfg.nocache = 0;
283 ctx->cfg.cache_size = 0; 285 ctx->cfg.cache_size = 0;
284 ctx->cfg.cache_dynamic_ttl = 5; 286 ctx->cfg.cache_dynamic_ttl = 5;
285 ctx->cfg.cache_max_create_time = 5; 287 ctx->cfg.cache_max_create_time = 5;
286 ctx->cfg.cache_repo_ttl = 5; 288 ctx->cfg.cache_repo_ttl = 5;
287 ctx->cfg.cache_root = CGIT_CACHE_ROOT; 289 ctx->cfg.cache_root = CGIT_CACHE_ROOT;
288 ctx->cfg.cache_root_ttl = 5; 290 ctx->cfg.cache_root_ttl = 5;
289 ctx->cfg.cache_scanrc_ttl = 15; 291 ctx->cfg.cache_scanrc_ttl = 15;
290 ctx->cfg.cache_static_ttl = -1; 292 ctx->cfg.cache_static_ttl = -1;
291 ctx->cfg.css = "/cgit.css"; 293 ctx->cfg.css = "/cgit.css";
292 ctx->cfg.logo = "/cgit.png"; 294 ctx->cfg.logo = "/cgit.png";
293 ctx->cfg.local_time = 0; 295 ctx->cfg.local_time = 0;
294 ctx->cfg.enable_tree_linenumbers = 1; 296 ctx->cfg.enable_tree_linenumbers = 1;
295 ctx->cfg.max_repo_count = 50; 297 ctx->cfg.max_repo_count = 50;
296 ctx->cfg.max_commit_count = 50; 298 ctx->cfg.max_commit_count = 50;
297 ctx->cfg.max_lock_attempts = 5; 299 ctx->cfg.max_lock_attempts = 5;
298 ctx->cfg.max_msg_len = 80; 300 ctx->cfg.max_msg_len = 80;
299 ctx->cfg.max_repodesc_len = 80; 301 ctx->cfg.max_repodesc_len = 80;
300 ctx->cfg.max_blob_size = 0; 302 ctx->cfg.max_blob_size = 0;
301 ctx->cfg.max_stats = 0; 303 ctx->cfg.max_stats = 0;
302 ctx->cfg.module_link = "./?repo=%s&page=commit&id=%s"; 304 ctx->cfg.module_link = "./?repo=%s&page=commit&id=%s";
303 ctx->cfg.project_list = NULL; 305 ctx->cfg.project_list = NULL;
304 ctx->cfg.renamelimit = -1; 306 ctx->cfg.renamelimit = -1;
307 ctx->cfg.remove_suffix = 0;
305 ctx->cfg.robots = "index, nofollow"; 308 ctx->cfg.robots = "index, nofollow";
306 ctx->cfg.root_title = "Git repository browser"; 309 ctx->cfg.root_title = "Git repository browser";
307 ctx->cfg.root_desc = "a fast webinterface for the git dscm"; 310 ctx->cfg.root_desc = "a fast webinterface for the git dscm";
308 ctx->cfg.script_name = CGIT_SCRIPT_NAME; 311 ctx->cfg.script_name = CGIT_SCRIPT_NAME;
309 ctx->cfg.section = ""; 312 ctx->cfg.section = "";
310 ctx->cfg.summary_branches = 10; 313 ctx->cfg.summary_branches = 10;
311 ctx->cfg.summary_log = 10; 314 ctx->cfg.summary_log = 10;
312 ctx->cfg.summary_tags = 10; 315 ctx->cfg.summary_tags = 10;
313 ctx->cfg.max_atom_items = 10; 316 ctx->cfg.max_atom_items = 10;
314 ctx->cfg.ssdiff = 0; 317 ctx->cfg.ssdiff = 0;
315 ctx->env.cgit_config = xstrdupn(getenv("CGIT_CONFIG")); 318 ctx->env.cgit_config = xstrdupn(getenv("CGIT_CONFIG"));
316 ctx->env.http_host = xstrdupn(getenv("HTTP_HOST")); 319 ctx->env.http_host = xstrdupn(getenv("HTTP_HOST"));
317 ctx->env.https = xstrdupn(getenv("HTTPS")); 320 ctx->env.https = xstrdupn(getenv("HTTPS"));
318 ctx->env.no_http = xstrdupn(getenv("NO_HTTP")); 321 ctx->env.no_http = xstrdupn(getenv("NO_HTTP"));
319 ctx->env.path_info = xstrdupn(getenv("PATH_INFO")); 322 ctx->env.path_info = xstrdupn(getenv("PATH_INFO"));
320 ctx->env.query_string = xstrdupn(getenv("QUERY_STRING")); 323 ctx->env.query_string = xstrdupn(getenv("QUERY_STRING"));
321 ctx->env.request_method = xstrdupn(getenv("REQUEST_METHOD")); 324 ctx->env.request_method = xstrdupn(getenv("REQUEST_METHOD"));
322 ctx->env.script_name = xstrdupn(getenv("SCRIPT_NAME")); 325 ctx->env.script_name = xstrdupn(getenv("SCRIPT_NAME"));
323 ctx->env.server_name = xstrdupn(getenv("SERVER_NAME")); 326 ctx->env.server_name = xstrdupn(getenv("SERVER_NAME"));
324 ctx->env.server_port = xstrdupn(getenv("SERVER_PORT")); 327 ctx->env.server_port = xstrdupn(getenv("SERVER_PORT"));
325 ctx->page.mimetype = "text/html"; 328 ctx->page.mimetype = "text/html";
326 ctx->page.charset = PAGE_ENCODING; 329 ctx->page.charset = PAGE_ENCODING;
327 ctx->page.filename = NULL; 330 ctx->page.filename = NULL;
328 ctx->page.size = 0; 331 ctx->page.size = 0;
329 ctx->page.modified = time(NULL); 332 ctx->page.modified = time(NULL);
330 ctx->page.expires = ctx->page.modified; 333 ctx->page.expires = ctx->page.modified;
331 ctx->page.etag = NULL; 334 ctx->page.etag = NULL;
332 memset(&ctx->cfg.mimetypes, 0, sizeof(struct string_list)); 335 memset(&ctx->cfg.mimetypes, 0, sizeof(struct string_list));
333 if (ctx->env.script_name) 336 if (ctx->env.script_name)
334 ctx->cfg.script_name = ctx->env.script_name; 337 ctx->cfg.script_name = ctx->env.script_name;
335 if (ctx->env.query_string) 338 if (ctx->env.query_string)
336 ctx->qry.raw = ctx->env.query_string; 339 ctx->qry.raw = ctx->env.query_string;
337 if (!ctx->env.cgit_config) 340 if (!ctx->env.cgit_config)
338 ctx->env.cgit_config = CGIT_CONFIG; 341 ctx->env.cgit_config = CGIT_CONFIG;
339} 342}
340 343
341struct refmatch { 344struct refmatch {
342 char *req_ref; 345 char *req_ref;
343 char *first_ref; 346 char *first_ref;
344 int match; 347 int match;
345}; 348};
346 349
347int find_current_ref(const char *refname, const unsigned char *sha1, 350int find_current_ref(const char *refname, const unsigned char *sha1,
348 int flags, void *cb_data) 351 int flags, void *cb_data)
349{ 352{
350 struct refmatch *info; 353 struct refmatch *info;
351 354
352 info = (struct refmatch *)cb_data; 355 info = (struct refmatch *)cb_data;
353 if (!strcmp(refname, info->req_ref)) 356 if (!strcmp(refname, info->req_ref))
354 info->match = 1; 357 info->match = 1;
355 if (!info->first_ref) 358 if (!info->first_ref)
356 info->first_ref = xstrdup(refname); 359 info->first_ref = xstrdup(refname);
357 return info->match; 360 return info->match;
358} 361}
359 362
360char *find_default_branch(struct cgit_repo *repo) 363char *find_default_branch(struct cgit_repo *repo)
361{ 364{
362 struct refmatch info; 365 struct refmatch info;
363 char *ref; 366 char *ref;
364 367
365 info.req_ref = repo->defbranch; 368 info.req_ref = repo->defbranch;
366 info.first_ref = NULL; 369 info.first_ref = NULL;
367 info.match = 0; 370 info.match = 0;
368 for_each_branch_ref(find_current_ref, &info); 371 for_each_branch_ref(find_current_ref, &info);
369 if (info.match) 372 if (info.match)
370 ref = info.req_ref; 373 ref = info.req_ref;
371 else 374 else
372 ref = info.first_ref; 375 ref = info.first_ref;
373 if (ref) 376 if (ref)
374 ref = xstrdup(ref); 377 ref = xstrdup(ref);
375 return ref; 378 return ref;
376} 379}
377 380
378static int prepare_repo_cmd(struct cgit_context *ctx) 381static int prepare_repo_cmd(struct cgit_context *ctx)
379{ 382{
380 char *tmp; 383 char *tmp;
381 unsigned char sha1[20]; 384 unsigned char sha1[20];
382 int nongit = 0; 385 int nongit = 0;
383 386
384 setenv("GIT_DIR", ctx->repo->path, 1); 387 setenv("GIT_DIR", ctx->repo->path, 1);
385 setup_git_directory_gently(&nongit); 388 setup_git_directory_gently(&nongit);
386 if (nongit) { 389 if (nongit) {
387 ctx->page.title = fmt("%s - %s", ctx->cfg.root_title, 390 ctx->page.title = fmt("%s - %s", ctx->cfg.root_title,
388 "config error"); 391 "config error");
389 tmp = fmt("Not a git repository: '%s'", ctx->repo->path); 392 tmp = fmt("Not a git repository: '%s'", ctx->repo->path);
390 ctx->repo = NULL; 393 ctx->repo = NULL;
391 cgit_print_http_headers(ctx); 394 cgit_print_http_headers(ctx);
392 cgit_print_docstart(ctx); 395 cgit_print_docstart(ctx);
393 cgit_print_pageheader(ctx); 396 cgit_print_pageheader(ctx);
394 cgit_print_error(tmp); 397 cgit_print_error(tmp);
395 cgit_print_docend(); 398 cgit_print_docend();
396 return 1; 399 return 1;
397 } 400 }
398 ctx->page.title = fmt("%s - %s", ctx->repo->name, ctx->repo->desc); 401 ctx->page.title = fmt("%s - %s", ctx->repo->name, ctx->repo->desc);
399 402
400 if (!ctx->qry.head) { 403 if (!ctx->qry.head) {
401 ctx->qry.nohead = 1; 404 ctx->qry.nohead = 1;
402 ctx->qry.head = find_default_branch(ctx->repo); 405 ctx->qry.head = find_default_branch(ctx->repo);
403 ctx->repo->defbranch = ctx->qry.head; 406 ctx->repo->defbranch = ctx->qry.head;
404 } 407 }
405 408
406 if (!ctx->qry.head) { 409 if (!ctx->qry.head) {
407 cgit_print_http_headers(ctx); 410 cgit_print_http_headers(ctx);
408 cgit_print_docstart(ctx); 411 cgit_print_docstart(ctx);
409 cgit_print_pageheader(ctx); 412 cgit_print_pageheader(ctx);
410 cgit_print_error("Repository seems to be empty"); 413 cgit_print_error("Repository seems to be empty");
411 cgit_print_docend(); 414 cgit_print_docend();
412 return 1; 415 return 1;
413 } 416 }
414 417
415 if (get_sha1(ctx->qry.head, sha1)) { 418 if (get_sha1(ctx->qry.head, sha1)) {
416 tmp = xstrdup(ctx->qry.head); 419 tmp = xstrdup(ctx->qry.head);
417 ctx->qry.head = ctx->repo->defbranch; 420 ctx->qry.head = ctx->repo->defbranch;
418 ctx->page.status = 404; 421 ctx->page.status = 404;
419 ctx->page.statusmsg = "not found"; 422 ctx->page.statusmsg = "not found";
420 cgit_print_http_headers(ctx); 423 cgit_print_http_headers(ctx);
421 cgit_print_docstart(ctx); 424 cgit_print_docstart(ctx);
422 cgit_print_pageheader(ctx); 425 cgit_print_pageheader(ctx);
423 cgit_print_error(fmt("Invalid branch: %s", tmp)); 426 cgit_print_error(fmt("Invalid branch: %s", tmp));
424 cgit_print_docend(); 427 cgit_print_docend();
425 return 1; 428 return 1;
426 } 429 }
427 return 0; 430 return 0;
428} 431}
429 432
430static void process_request(void *cbdata) 433static void process_request(void *cbdata)
431{ 434{
432 struct cgit_context *ctx = cbdata; 435 struct cgit_context *ctx = cbdata;
433 struct cgit_cmd *cmd; 436 struct cgit_cmd *cmd;
434 437
435 cmd = cgit_get_cmd(ctx); 438 cmd = cgit_get_cmd(ctx);
436 if (!cmd) { 439 if (!cmd) {
437 ctx->page.title = "cgit error"; 440 ctx->page.title = "cgit error";
438 cgit_print_http_headers(ctx); 441 cgit_print_http_headers(ctx);
439 cgit_print_docstart(ctx); 442 cgit_print_docstart(ctx);
440 cgit_print_pageheader(ctx); 443 cgit_print_pageheader(ctx);
441 cgit_print_error("Invalid request"); 444 cgit_print_error("Invalid request");
442 cgit_print_docend(); 445 cgit_print_docend();
443 return; 446 return;
444 } 447 }
445 448
446 /* If cmd->want_vpath is set, assume ctx->qry.path contains a "virtual" 449 /* If cmd->want_vpath is set, assume ctx->qry.path contains a "virtual"
447 * in-project path limit to be made available at ctx->qry.vpath. 450 * in-project path limit to be made available at ctx->qry.vpath.
448 * Otherwise, no path limit is in effect (ctx->qry.vpath = NULL). 451 * Otherwise, no path limit is in effect (ctx->qry.vpath = NULL).
449 */ 452 */
450 ctx->qry.vpath = cmd->want_vpath ? ctx->qry.path : NULL; 453 ctx->qry.vpath = cmd->want_vpath ? ctx->qry.path : NULL;
451 454
452 if (cmd->want_repo && !ctx->repo) { 455 if (cmd->want_repo && !ctx->repo) {
453 cgit_print_http_headers(ctx); 456 cgit_print_http_headers(ctx);
454 cgit_print_docstart(ctx); 457 cgit_print_docstart(ctx);
455 cgit_print_pageheader(ctx); 458 cgit_print_pageheader(ctx);
456 cgit_print_error(fmt("No repository selected")); 459 cgit_print_error(fmt("No repository selected"));
457 cgit_print_docend(); 460 cgit_print_docend();
458 return; 461 return;
459 } 462 }
460 463
461 if (ctx->repo && prepare_repo_cmd(ctx)) 464 if (ctx->repo && prepare_repo_cmd(ctx))
462 return; 465 return;
463 466
464 if (cmd->want_layout) { 467 if (cmd->want_layout) {
465 cgit_print_http_headers(ctx); 468 cgit_print_http_headers(ctx);
466 cgit_print_docstart(ctx); 469 cgit_print_docstart(ctx);
467 cgit_print_pageheader(ctx); 470 cgit_print_pageheader(ctx);
468 } 471 }
469 472
470 cmd->fn(ctx); 473 cmd->fn(ctx);
471 474
472 if (cmd->want_layout) 475 if (cmd->want_layout)
473 cgit_print_docend(); 476 cgit_print_docend();
474} 477}
475 478
476int cmp_repos(const void *a, const void *b) 479int cmp_repos(const void *a, const void *b)
477{ 480{
478 const struct cgit_repo *ra = a, *rb = b; 481 const struct cgit_repo *ra = a, *rb = b;
479 return strcmp(ra->url, rb->url); 482 return strcmp(ra->url, rb->url);
480} 483}
481 484
482char *build_snapshot_setting(int bitmap) 485char *build_snapshot_setting(int bitmap)
483{ 486{
484 const struct cgit_snapshot_format *f; 487 const struct cgit_snapshot_format *f;
485 char *result = xstrdup(""); 488 char *result = xstrdup("");
486 char *tmp; 489 char *tmp;
487 int len; 490 int len;
488 491
489 for (f = cgit_snapshot_formats; f->suffix; f++) { 492 for (f = cgit_snapshot_formats; f->suffix; f++) {
490 if (f->bit & bitmap) { 493 if (f->bit & bitmap) {
491 tmp = result; 494 tmp = result;
492 result = xstrdup(fmt("%s%s ", tmp, f->suffix)); 495 result = xstrdup(fmt("%s%s ", tmp, f->suffix));
493 free(tmp); 496 free(tmp);
494 } 497 }
495 } 498 }
496 len = strlen(result); 499 len = strlen(result);
497 if (len) 500 if (len)
498 result[len - 1] = '\0'; 501 result[len - 1] = '\0';
499 return result; 502 return result;
500} 503}
501 504
502char *get_first_line(char *txt) 505char *get_first_line(char *txt)
503{ 506{
504 char *t = xstrdup(txt); 507 char *t = xstrdup(txt);
505 char *p = strchr(t, '\n'); 508 char *p = strchr(t, '\n');
506 if (p) 509 if (p)
507 *p = '\0'; 510 *p = '\0';
508 return t; 511 return t;
509} 512}
510 513
511void print_repo(FILE *f, struct cgit_repo *repo) 514void print_repo(FILE *f, struct cgit_repo *repo)
512{ 515{
513 fprintf(f, "repo.url=%s\n", repo->url); 516 fprintf(f, "repo.url=%s\n", repo->url);
514 fprintf(f, "repo.name=%s\n", repo->name); 517 fprintf(f, "repo.name=%s\n", repo->name);
515 fprintf(f, "repo.path=%s\n", repo->path); 518 fprintf(f, "repo.path=%s\n", repo->path);
516 if (repo->owner) 519 if (repo->owner)
517 fprintf(f, "repo.owner=%s\n", repo->owner); 520 fprintf(f, "repo.owner=%s\n", repo->owner);
518 if (repo->desc) { 521 if (repo->desc) {
519 char *tmp = get_first_line(repo->desc); 522 char *tmp = get_first_line(repo->desc);
520 fprintf(f, "repo.desc=%s\n", tmp); 523 fprintf(f, "repo.desc=%s\n", tmp);
521 free(tmp); 524 free(tmp);
522 } 525 }
523 if (repo->readme) 526 if (repo->readme)
524 fprintf(f, "repo.readme=%s\n", repo->readme); 527 fprintf(f, "repo.readme=%s\n", repo->readme);
525 if (repo->defbranch) 528 if (repo->defbranch)
526 fprintf(f, "repo.defbranch=%s\n", repo->defbranch); 529 fprintf(f, "repo.defbranch=%s\n", repo->defbranch);
527 if (repo->module_link) 530 if (repo->module_link)
528 fprintf(f, "repo.module-link=%s\n", repo->module_link); 531 fprintf(f, "repo.module-link=%s\n", repo->module_link);
529 if (repo->section) 532 if (repo->section)
530 fprintf(f, "repo.section=%s\n", repo->section); 533 fprintf(f, "repo.section=%s\n", repo->section);
531 if (repo->clone_url) 534 if (repo->clone_url)
532 fprintf(f, "repo.clone-url=%s\n", repo->clone_url); 535 fprintf(f, "repo.clone-url=%s\n", repo->clone_url);
533 fprintf(f, "repo.enable-log-filecount=%d\n", 536 fprintf(f, "repo.enable-log-filecount=%d\n",
534 repo->enable_log_filecount); 537 repo->enable_log_filecount);
535 fprintf(f, "repo.enable-log-linecount=%d\n", 538 fprintf(f, "repo.enable-log-linecount=%d\n",
536 repo->enable_log_linecount); 539 repo->enable_log_linecount);
537 if (repo->about_filter && repo->about_filter != ctx.cfg.about_filter) 540 if (repo->about_filter && repo->about_filter != ctx.cfg.about_filter)
538 fprintf(f, "repo.about-filter=%s\n", repo->about_filter->cmd); 541 fprintf(f, "repo.about-filter=%s\n", repo->about_filter->cmd);
539 if (repo->commit_filter && repo->commit_filter != ctx.cfg.commit_filter) 542 if (repo->commit_filter && repo->commit_filter != ctx.cfg.commit_filter)
540 fprintf(f, "repo.commit-filter=%s\n", repo->commit_filter->cmd); 543 fprintf(f, "repo.commit-filter=%s\n", repo->commit_filter->cmd);
541 if (repo->source_filter && repo->source_filter != ctx.cfg.source_filter) 544 if (repo->source_filter && repo->source_filter != ctx.cfg.source_filter)
542 fprintf(f, "repo.source-filter=%s\n", repo->source_filter->cmd); 545 fprintf(f, "repo.source-filter=%s\n", repo->source_filter->cmd);
543 if (repo->snapshots != ctx.cfg.snapshots) { 546 if (repo->snapshots != ctx.cfg.snapshots) {
544 char *tmp = build_snapshot_setting(repo->snapshots); 547 char *tmp = build_snapshot_setting(repo->snapshots);
545 fprintf(f, "repo.snapshots=%s\n", tmp); 548 fprintf(f, "repo.snapshots=%s\n", tmp);
546 free(tmp); 549 free(tmp);
547 } 550 }
548 if (repo->max_stats != ctx.cfg.max_stats) 551 if (repo->max_stats != ctx.cfg.max_stats)
549 fprintf(f, "repo.max-stats=%s\n", 552 fprintf(f, "repo.max-stats=%s\n",
550 cgit_find_stats_periodname(repo->max_stats)); 553 cgit_find_stats_periodname(repo->max_stats));
551 fprintf(f, "\n"); 554 fprintf(f, "\n");
552} 555}
553 556
554void print_repolist(FILE *f, struct cgit_repolist *list, int start) 557void print_repolist(FILE *f, struct cgit_repolist *list, int start)
555{ 558{
556 int i; 559 int i;
557 560
558 for(i = start; i < list->count; i++) 561 for(i = start; i < list->count; i++)
559 print_repo(f, &list->repos[i]); 562 print_repo(f, &list->repos[i]);
560} 563}
561 564
562/* Scan 'path' for git repositories, save the resulting repolist in 'cached_rc' 565/* Scan 'path' for git repositories, save the resulting repolist in 'cached_rc'
563 * and return 0 on success. 566 * and return 0 on success.
564 */ 567 */
565static int generate_cached_repolist(const char *path, const char *cached_rc) 568static int generate_cached_repolist(const char *path, const char *cached_rc)
566{ 569{
567 char *locked_rc; 570 char *locked_rc;
568 int idx; 571 int idx;
569 FILE *f; 572 FILE *f;
570 573
571 locked_rc = xstrdup(fmt("%s.lock", cached_rc)); 574 locked_rc = xstrdup(fmt("%s.lock", cached_rc));
572 f = fopen(locked_rc, "wx"); 575 f = fopen(locked_rc, "wx");
573 if (!f) { 576 if (!f) {
574 /* Inform about the error unless the lockfile already existed, 577 /* Inform about the error unless the lockfile already existed,
575 * since that only means we've got concurrent requests. 578 * since that only means we've got concurrent requests.
576 */ 579 */
577 if (errno != EEXIST) 580 if (errno != EEXIST)
578 fprintf(stderr, "[cgit] Error opening %s: %s (%d)\n", 581 fprintf(stderr, "[cgit] Error opening %s: %s (%d)\n",
579 locked_rc, strerror(errno), errno); 582 locked_rc, strerror(errno), errno);
580 return errno; 583 return errno;
581 } 584 }
582 idx = cgit_repolist.count; 585 idx = cgit_repolist.count;
583 if (ctx.cfg.project_list) 586 if (ctx.cfg.project_list)
584 scan_projects(path, ctx.cfg.project_list, repo_config); 587 scan_projects(path, ctx.cfg.project_list, repo_config);
585 else 588 else
586 scan_tree(path, repo_config); 589 scan_tree(path, repo_config);
587 print_repolist(f, &cgit_repolist, idx); 590 print_repolist(f, &cgit_repolist, idx);
588 if (rename(locked_rc, cached_rc)) 591 if (rename(locked_rc, cached_rc))
589 fprintf(stderr, "[cgit] Error renaming %s to %s: %s (%d)\n", 592 fprintf(stderr, "[cgit] Error renaming %s to %s: %s (%d)\n",
590 locked_rc, cached_rc, strerror(errno), errno); 593 locked_rc, cached_rc, strerror(errno), errno);
591 fclose(f); 594 fclose(f);
592 return 0; 595 return 0;
593} 596}
594 597
595static void process_cached_repolist(const char *path) 598static void process_cached_repolist(const char *path)
596{ 599{
597 struct stat st; 600 struct stat st;
598 char *cached_rc; 601 char *cached_rc;
599 time_t age; 602 time_t age;
600 unsigned long hash; 603 unsigned long hash;
601 604
602 hash = hash_str(path); 605 hash = hash_str(path);
603 if (ctx.cfg.project_list) 606 if (ctx.cfg.project_list)
604 hash += hash_str(ctx.cfg.project_list); 607 hash += hash_str(ctx.cfg.project_list);
605 cached_rc = xstrdup(fmt("%s/rc-%8x", ctx.cfg.cache_root, hash)); 608 cached_rc = xstrdup(fmt("%s/rc-%8x", ctx.cfg.cache_root, hash));
606 609
607 if (stat(cached_rc, &st)) { 610 if (stat(cached_rc, &st)) {
608 /* Nothing is cached, we need to scan without forking. And 611 /* Nothing is cached, we need to scan without forking. And
609 * if we fail to generate a cached repolist, we need to 612 * if we fail to generate a cached repolist, we need to
610 * invoke scan_tree manually. 613 * invoke scan_tree manually.
611 */ 614 */
612 if (generate_cached_repolist(path, cached_rc)) { 615 if (generate_cached_repolist(path, cached_rc)) {
613 if (ctx.cfg.project_list) 616 if (ctx.cfg.project_list)
614 scan_projects(path, ctx.cfg.project_list, 617 scan_projects(path, ctx.cfg.project_list,
615 repo_config); 618 repo_config);
616 else 619 else
617 scan_tree(path, repo_config); 620 scan_tree(path, repo_config);
618 } 621 }
619 return; 622 return;
620 } 623 }
621 624
622 parse_configfile(cached_rc, config_cb); 625 parse_configfile(cached_rc, config_cb);
623 626
624 /* If the cached configfile hasn't expired, lets exit now */ 627 /* If the cached configfile hasn't expired, lets exit now */
625 age = time(NULL) - st.st_mtime; 628 age = time(NULL) - st.st_mtime;
626 if (age <= (ctx.cfg.cache_scanrc_ttl * 60)) 629 if (age <= (ctx.cfg.cache_scanrc_ttl * 60))
627 return; 630 return;
628 631
629 /* The cached repolist has been parsed, but it was old. So lets 632 /* The cached repolist has been parsed, but it was old. So lets
630 * rescan the specified path and generate a new cached repolist 633 * rescan the specified path and generate a new cached repolist
631 * in a child-process to avoid latency for the current request. 634 * in a child-process to avoid latency for the current request.
632 */ 635 */
633 if (fork()) 636 if (fork())
634 return; 637 return;
635 638
636 exit(generate_cached_repolist(path, cached_rc)); 639 exit(generate_cached_repolist(path, cached_rc));
637} 640}
638 641
639static void cgit_parse_args(int argc, const char **argv) 642static void cgit_parse_args(int argc, const char **argv)
640{ 643{
641 int i; 644 int i;
642 int scan = 0; 645 int scan = 0;
643 646
644 for (i = 1; i < argc; i++) { 647 for (i = 1; i < argc; i++) {
645 if (!strncmp(argv[i], "--cache=", 8)) { 648 if (!strncmp(argv[i], "--cache=", 8)) {
646 ctx.cfg.cache_root = xstrdup(argv[i]+8); 649 ctx.cfg.cache_root = xstrdup(argv[i]+8);
647 } 650 }
648 if (!strcmp(argv[i], "--nocache")) { 651 if (!strcmp(argv[i], "--nocache")) {
649 ctx.cfg.nocache = 1; 652 ctx.cfg.nocache = 1;
650 } 653 }
651 if (!strcmp(argv[i], "--nohttp")) { 654 if (!strcmp(argv[i], "--nohttp")) {
652 ctx.env.no_http = "1"; 655 ctx.env.no_http = "1";
653 } 656 }
654 if (!strncmp(argv[i], "--query=", 8)) { 657 if (!strncmp(argv[i], "--query=", 8)) {
655 ctx.qry.raw = xstrdup(argv[i]+8); 658 ctx.qry.raw = xstrdup(argv[i]+8);
656 } 659 }
657 if (!strncmp(argv[i], "--repo=", 7)) { 660 if (!strncmp(argv[i], "--repo=", 7)) {
658 ctx.qry.repo = xstrdup(argv[i]+7); 661 ctx.qry.repo = xstrdup(argv[i]+7);
659 } 662 }
660 if (!strncmp(argv[i], "--page=", 7)) { 663 if (!strncmp(argv[i], "--page=", 7)) {
661 ctx.qry.page = xstrdup(argv[i]+7); 664 ctx.qry.page = xstrdup(argv[i]+7);
662 } 665 }
663 if (!strncmp(argv[i], "--head=", 7)) { 666 if (!strncmp(argv[i], "--head=", 7)) {
664 ctx.qry.head = xstrdup(argv[i]+7); 667 ctx.qry.head = xstrdup(argv[i]+7);
665 ctx.qry.has_symref = 1; 668 ctx.qry.has_symref = 1;
666 } 669 }
667 if (!strncmp(argv[i], "--sha1=", 7)) { 670 if (!strncmp(argv[i], "--sha1=", 7)) {
668 ctx.qry.sha1 = xstrdup(argv[i]+7); 671 ctx.qry.sha1 = xstrdup(argv[i]+7);
669 ctx.qry.has_sha1 = 1; 672 ctx.qry.has_sha1 = 1;
670 } 673 }
671 if (!strncmp(argv[i], "--ofs=", 6)) { 674 if (!strncmp(argv[i], "--ofs=", 6)) {
672 ctx.qry.ofs = atoi(argv[i]+6); 675 ctx.qry.ofs = atoi(argv[i]+6);
673 } 676 }
674 if (!strncmp(argv[i], "--scan-tree=", 12) || 677 if (!strncmp(argv[i], "--scan-tree=", 12) ||
675 !strncmp(argv[i], "--scan-path=", 12)) { 678 !strncmp(argv[i], "--scan-path=", 12)) {
676 /* HACK: the global snapshot bitmask defines the 679 /* HACK: the global snapshot bitmask defines the
677 * set of allowed snapshot formats, but the config 680 * set of allowed snapshot formats, but the config
678 * file hasn't been parsed yet so the mask is 681 * file hasn't been parsed yet so the mask is
679 * currently 0. By setting all bits high before 682 * currently 0. By setting all bits high before
680 * scanning we make sure that any in-repo cgitrc 683 * scanning we make sure that any in-repo cgitrc
681 * snapshot setting is respected by scan_tree(). 684 * snapshot setting is respected by scan_tree().
682 * BTW: we assume that there'll never be more than 685 * BTW: we assume that there'll never be more than
683 * 255 different snapshot formats supported by cgit... 686 * 255 different snapshot formats supported by cgit...
684 */ 687 */
685 ctx.cfg.snapshots = 0xFF; 688 ctx.cfg.snapshots = 0xFF;
686 scan++; 689 scan++;
687 scan_tree(argv[i] + 12, repo_config); 690 scan_tree(argv[i] + 12, repo_config);
688 } 691 }
689 } 692 }
690 if (scan) { 693 if (scan) {
691 qsort(cgit_repolist.repos, cgit_repolist.count, 694 qsort(cgit_repolist.repos, cgit_repolist.count,
692 sizeof(struct cgit_repo), cmp_repos); 695 sizeof(struct cgit_repo), cmp_repos);
693 print_repolist(stdout, &cgit_repolist, 0); 696 print_repolist(stdout, &cgit_repolist, 0);
694 exit(0); 697 exit(0);
695 } 698 }
696} 699}
697 700
698static int calc_ttl() 701static int calc_ttl()
699{ 702{
700 if (!ctx.repo) 703 if (!ctx.repo)
701 return ctx.cfg.cache_root_ttl; 704 return ctx.cfg.cache_root_ttl;
702 705
703 if (!ctx.qry.page) 706 if (!ctx.qry.page)
704 return ctx.cfg.cache_repo_ttl; 707 return ctx.cfg.cache_repo_ttl;
705 708
706 if (ctx.qry.has_symref) 709 if (ctx.qry.has_symref)
707 return ctx.cfg.cache_dynamic_ttl; 710 return ctx.cfg.cache_dynamic_ttl;
708 711
709 if (ctx.qry.has_sha1) 712 if (ctx.qry.has_sha1)
710 return ctx.cfg.cache_static_ttl; 713 return ctx.cfg.cache_static_ttl;
711 714
712 return ctx.cfg.cache_repo_ttl; 715 return ctx.cfg.cache_repo_ttl;
713} 716}
714 717
715int main(int argc, const char **argv) 718int main(int argc, const char **argv)
716{ 719{
717 const char *path; 720 const char *path;
718 char *qry; 721 char *qry;
719 int err, ttl; 722 int err, ttl;
720 723
721 prepare_context(&ctx); 724 prepare_context(&ctx);
722 cgit_repolist.length = 0; 725 cgit_repolist.length = 0;
723 cgit_repolist.count = 0; 726 cgit_repolist.count = 0;
724 cgit_repolist.repos = NULL; 727 cgit_repolist.repos = NULL;
725 728
726 cgit_parse_args(argc, argv); 729 cgit_parse_args(argc, argv);
727 parse_configfile(expand_macros(ctx.env.cgit_config), config_cb); 730 parse_configfile(expand_macros(ctx.env.cgit_config), config_cb);
728 ctx.repo = NULL; 731 ctx.repo = NULL;
729 http_parse_querystring(ctx.qry.raw, querystring_cb); 732 http_parse_querystring(ctx.qry.raw, querystring_cb);
730 733
731 /* If virtual-root isn't specified in cgitrc, lets pretend 734 /* If virtual-root isn't specified in cgitrc, lets pretend
732 * that virtual-root equals SCRIPT_NAME. 735 * that virtual-root equals SCRIPT_NAME.
733 */ 736 */
734 if (!ctx.cfg.virtual_root) 737 if (!ctx.cfg.virtual_root)
735 ctx.cfg.virtual_root = ctx.cfg.script_name; 738 ctx.cfg.virtual_root = ctx.cfg.script_name;
736 739
737 /* If no url parameter is specified on the querystring, lets 740 /* If no url parameter is specified on the querystring, lets
738 * use PATH_INFO as url. This allows cgit to work with virtual 741 * use PATH_INFO as url. This allows cgit to work with virtual
739 * urls without the need for rewriterules in the webserver (as 742 * urls without the need for rewriterules in the webserver (as
740 * long as PATH_INFO is included in the cache lookup key). 743 * long as PATH_INFO is included in the cache lookup key).
741 */ 744 */
742 path = ctx.env.path_info; 745 path = ctx.env.path_info;
743 if (!ctx.qry.url && path) { 746 if (!ctx.qry.url && path) {
744 if (path[0] == '/') 747 if (path[0] == '/')
745 path++; 748 path++;
746 ctx.qry.url = xstrdup(path); 749 ctx.qry.url = xstrdup(path);
747 if (ctx.qry.raw) { 750 if (ctx.qry.raw) {
748 qry = ctx.qry.raw; 751 qry = ctx.qry.raw;
749 ctx.qry.raw = xstrdup(fmt("%s?%s", path, qry)); 752 ctx.qry.raw = xstrdup(fmt("%s?%s", path, qry));
750 free(qry); 753 free(qry);
751 } else 754 } else
752 ctx.qry.raw = xstrdup(ctx.qry.url); 755 ctx.qry.raw = xstrdup(ctx.qry.url);
753 cgit_parse_url(ctx.qry.url); 756 cgit_parse_url(ctx.qry.url);
754 } 757 }
755 758
756 ttl = calc_ttl(); 759 ttl = calc_ttl();
757 ctx.page.expires += ttl*60; 760 ctx.page.expires += ttl*60;
758 if (ctx.env.request_method && !strcmp(ctx.env.request_method, "HEAD")) 761 if (ctx.env.request_method && !strcmp(ctx.env.request_method, "HEAD"))
759 ctx.cfg.nocache = 1; 762 ctx.cfg.nocache = 1;
760 if (ctx.cfg.nocache) 763 if (ctx.cfg.nocache)
761 ctx.cfg.cache_size = 0; 764 ctx.cfg.cache_size = 0;
762 err = cache_process(ctx.cfg.cache_size, ctx.cfg.cache_root, 765 err = cache_process(ctx.cfg.cache_size, ctx.cfg.cache_root,
763 ctx.qry.raw, ttl, process_request, &ctx); 766 ctx.qry.raw, ttl, process_request, &ctx);
764 if (err) 767 if (err)
765 cgit_print_error(fmt("Error processing page: %s (%d)", 768 cgit_print_error(fmt("Error processing page: %s (%d)",
766 strerror(err), err)); 769 strerror(err), err));
767 return err; 770 return err;
768} 771}
diff --git a/cgit.h b/cgit.h
index 4591f8c..ada8535 100644
--- a/cgit.h
+++ b/cgit.h
@@ -1,310 +1,311 @@
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 enable_remote_branches;
76 int enable_subject_links; 76 int enable_subject_links;
77 int max_stats; 77 int max_stats;
78 time_t mtime; 78 time_t mtime;
79 struct cgit_filter *about_filter; 79 struct cgit_filter *about_filter;
80 struct cgit_filter *commit_filter; 80 struct cgit_filter *commit_filter;
81 struct cgit_filter *source_filter; 81 struct cgit_filter *source_filter;
82}; 82};
83 83
84typedef void (*repo_config_fn)(struct cgit_repo *repo, const char *name, 84typedef void (*repo_config_fn)(struct cgit_repo *repo, const char *name,
85 const char *value); 85 const char *value);
86 86
87struct cgit_repolist { 87struct cgit_repolist {
88 int length; 88 int length;
89 int count; 89 int count;
90 struct cgit_repo *repos; 90 struct cgit_repo *repos;
91}; 91};
92 92
93struct commitinfo { 93struct commitinfo {
94 struct commit *commit; 94 struct commit *commit;
95 char *author; 95 char *author;
96 char *author_email; 96 char *author_email;
97 unsigned long author_date; 97 unsigned long author_date;
98 char *committer; 98 char *committer;
99 char *committer_email; 99 char *committer_email;
100 unsigned long committer_date; 100 unsigned long committer_date;
101 char *subject; 101 char *subject;
102 char *msg; 102 char *msg;
103 char *msg_encoding; 103 char *msg_encoding;
104}; 104};
105 105
106struct taginfo { 106struct taginfo {
107 char *tagger; 107 char *tagger;
108 char *tagger_email; 108 char *tagger_email;
109 unsigned long tagger_date; 109 unsigned long tagger_date;
110 char *msg; 110 char *msg;
111}; 111};
112 112
113struct refinfo { 113struct refinfo {
114 const char *refname; 114 const char *refname;
115 struct object *object; 115 struct object *object;
116 union { 116 union {
117 struct taginfo *tag; 117 struct taginfo *tag;
118 struct commitinfo *commit; 118 struct commitinfo *commit;
119 }; 119 };
120}; 120};
121 121
122struct reflist { 122struct reflist {
123 struct refinfo **refs; 123 struct refinfo **refs;
124 int alloc; 124 int alloc;
125 int count; 125 int count;
126}; 126};
127 127
128struct cgit_query { 128struct cgit_query {
129 int has_symref; 129 int has_symref;
130 int has_sha1; 130 int has_sha1;
131 char *raw; 131 char *raw;
132 char *repo; 132 char *repo;
133 char *page; 133 char *page;
134 char *search; 134 char *search;
135 char *grep; 135 char *grep;
136 char *head; 136 char *head;
137 char *sha1; 137 char *sha1;
138 char *sha2; 138 char *sha2;
139 char *path; 139 char *path;
140 char *name; 140 char *name;
141 char *mimetype; 141 char *mimetype;
142 char *url; 142 char *url;
143 char *period; 143 char *period;
144 int ofs; 144 int ofs;
145 int nohead; 145 int nohead;
146 char *sort; 146 char *sort;
147 int showmsg; 147 int showmsg;
148 int ssdiff; 148 int ssdiff;
149 int show_all; 149 int show_all;
150 int context; 150 int context;
151 int ignorews; 151 int ignorews;
152 char *vpath; 152 char *vpath;
153}; 153};
154 154
155struct cgit_config { 155struct cgit_config {
156 char *agefile; 156 char *agefile;
157 char *cache_root; 157 char *cache_root;
158 char *clone_prefix; 158 char *clone_prefix;
159 char *css; 159 char *css;
160 char *favicon; 160 char *favicon;
161 char *footer; 161 char *footer;
162 char *head_include; 162 char *head_include;
163 char *header; 163 char *header;
164 char *index_header; 164 char *index_header;
165 char *index_info; 165 char *index_info;
166 char *logo; 166 char *logo;
167 char *logo_link; 167 char *logo_link;
168 char *module_link; 168 char *module_link;
169 char *project_list; 169 char *project_list;
170 char *robots; 170 char *robots;
171 char *root_title; 171 char *root_title;
172 char *root_desc; 172 char *root_desc;
173 char *root_readme; 173 char *root_readme;
174 char *script_name; 174 char *script_name;
175 char *section; 175 char *section;
176 char *virtual_root; 176 char *virtual_root;
177 int cache_size; 177 int cache_size;
178 int cache_dynamic_ttl; 178 int cache_dynamic_ttl;
179 int cache_max_create_time; 179 int cache_max_create_time;
180 int cache_repo_ttl; 180 int cache_repo_ttl;
181 int cache_root_ttl; 181 int cache_root_ttl;
182 int cache_scanrc_ttl; 182 int cache_scanrc_ttl;
183 int cache_static_ttl; 183 int cache_static_ttl;
184 int embedded; 184 int embedded;
185 int enable_filter_overrides; 185 int enable_filter_overrides;
186 int enable_index_links; 186 int enable_index_links;
187 int enable_log_filecount; 187 int enable_log_filecount;
188 int enable_log_linecount; 188 int enable_log_linecount;
189 int enable_remote_branches; 189 int enable_remote_branches;
190 int enable_subject_links; 190 int enable_subject_links;
191 int enable_tree_linenumbers; 191 int enable_tree_linenumbers;
192 int local_time; 192 int local_time;
193 int max_atom_items; 193 int max_atom_items;
194 int max_repo_count; 194 int max_repo_count;
195 int max_commit_count; 195 int max_commit_count;
196 int max_lock_attempts; 196 int max_lock_attempts;
197 int max_msg_len; 197 int max_msg_len;
198 int max_repodesc_len; 198 int max_repodesc_len;
199 int max_blob_size; 199 int max_blob_size;
200 int max_stats; 200 int max_stats;
201 int nocache; 201 int nocache;
202 int noplainemail; 202 int noplainemail;
203 int noheader; 203 int noheader;
204 int renamelimit; 204 int renamelimit;
205 int remove_suffix;
205 int snapshots; 206 int snapshots;
206 int summary_branches; 207 int summary_branches;
207 int summary_log; 208 int summary_log;
208 int summary_tags; 209 int summary_tags;
209 int ssdiff; 210 int ssdiff;
210 struct string_list mimetypes; 211 struct string_list mimetypes;
211 struct cgit_filter *about_filter; 212 struct cgit_filter *about_filter;
212 struct cgit_filter *commit_filter; 213 struct cgit_filter *commit_filter;
213 struct cgit_filter *source_filter; 214 struct cgit_filter *source_filter;
214}; 215};
215 216
216struct cgit_page { 217struct cgit_page {
217 time_t modified; 218 time_t modified;
218 time_t expires; 219 time_t expires;
219 size_t size; 220 size_t size;
220 char *mimetype; 221 char *mimetype;
221 char *charset; 222 char *charset;
222 char *filename; 223 char *filename;
223 char *etag; 224 char *etag;
224 char *title; 225 char *title;
225 int status; 226 int status;
226 char *statusmsg; 227 char *statusmsg;
227}; 228};
228 229
229struct cgit_environment { 230struct cgit_environment {
230 char *cgit_config; 231 char *cgit_config;
231 char *http_host; 232 char *http_host;
232 char *https; 233 char *https;
233 char *no_http; 234 char *no_http;
234 char *path_info; 235 char *path_info;
235 char *query_string; 236 char *query_string;
236 char *request_method; 237 char *request_method;
237 char *script_name; 238 char *script_name;
238 char *server_name; 239 char *server_name;
239 char *server_port; 240 char *server_port;
240}; 241};
241 242
242struct cgit_context { 243struct cgit_context {
243 struct cgit_environment env; 244 struct cgit_environment env;
244 struct cgit_query qry; 245 struct cgit_query qry;
245 struct cgit_config cfg; 246 struct cgit_config cfg;
246 struct cgit_repo *repo; 247 struct cgit_repo *repo;
247 struct cgit_page page; 248 struct cgit_page page;
248}; 249};
249 250
250struct cgit_snapshot_format { 251struct cgit_snapshot_format {
251 const char *suffix; 252 const char *suffix;
252 const char *mimetype; 253 const char *mimetype;
253 write_archive_fn_t write_func; 254 write_archive_fn_t write_func;
254 int bit; 255 int bit;
255}; 256};
256 257
257extern const char *cgit_version; 258extern const char *cgit_version;
258 259
259extern struct cgit_repolist cgit_repolist; 260extern struct cgit_repolist cgit_repolist;
260extern struct cgit_context ctx; 261extern struct cgit_context ctx;
261extern const struct cgit_snapshot_format cgit_snapshot_formats[]; 262extern const struct cgit_snapshot_format cgit_snapshot_formats[];
262 263
263extern struct cgit_repo *cgit_add_repo(const char *url); 264extern struct cgit_repo *cgit_add_repo(const char *url);
264extern struct cgit_repo *cgit_get_repoinfo(const char *url); 265extern struct cgit_repo *cgit_get_repoinfo(const char *url);
265extern void cgit_repo_config_cb(const char *name, const char *value); 266extern void cgit_repo_config_cb(const char *name, const char *value);
266 267
267extern int chk_zero(int result, char *msg); 268extern int chk_zero(int result, char *msg);
268extern int chk_positive(int result, char *msg); 269extern int chk_positive(int result, char *msg);
269extern int chk_non_negative(int result, char *msg); 270extern int chk_non_negative(int result, char *msg);
270 271
271extern char *trim_end(const char *str, char c); 272extern char *trim_end(const char *str, char c);
272extern char *strlpart(char *txt, int maxlen); 273extern char *strlpart(char *txt, int maxlen);
273extern char *strrpart(char *txt, int maxlen); 274extern char *strrpart(char *txt, int maxlen);
274 275
275extern void cgit_add_ref(struct reflist *list, struct refinfo *ref); 276extern void cgit_add_ref(struct reflist *list, struct refinfo *ref);
276extern int cgit_refs_cb(const char *refname, const unsigned char *sha1, 277extern int cgit_refs_cb(const char *refname, const unsigned char *sha1,
277 int flags, void *cb_data); 278 int flags, void *cb_data);
278 279
279extern void *cgit_free_commitinfo(struct commitinfo *info); 280extern void *cgit_free_commitinfo(struct commitinfo *info);
280 281
281extern int cgit_diff_files(const unsigned char *old_sha1, 282extern int cgit_diff_files(const unsigned char *old_sha1,
282 const unsigned char *new_sha1, 283 const unsigned char *new_sha1,
283 unsigned long *old_size, unsigned long *new_size, 284 unsigned long *old_size, unsigned long *new_size,
284 int *binary, int context, int ignorews, 285 int *binary, int context, int ignorews,
285 linediff_fn fn); 286 linediff_fn fn);
286 287
287extern void cgit_diff_tree(const unsigned char *old_sha1, 288extern void cgit_diff_tree(const unsigned char *old_sha1,
288 const unsigned char *new_sha1, 289 const unsigned char *new_sha1,
289 filepair_fn fn, const char *prefix, int ignorews); 290 filepair_fn fn, const char *prefix, int ignorews);
290 291
291extern void cgit_diff_commit(struct commit *commit, filepair_fn fn); 292extern void cgit_diff_commit(struct commit *commit, filepair_fn fn);
292 293
293extern char *fmt(const char *format,...); 294extern char *fmt(const char *format,...);
294 295
295extern struct commitinfo *cgit_parse_commit(struct commit *commit); 296extern struct commitinfo *cgit_parse_commit(struct commit *commit);
296extern struct taginfo *cgit_parse_tag(struct tag *tag); 297extern struct taginfo *cgit_parse_tag(struct tag *tag);
297extern void cgit_parse_url(const char *url); 298extern void cgit_parse_url(const char *url);
298 299
299extern const char *cgit_repobasename(const char *reponame); 300extern const char *cgit_repobasename(const char *reponame);
300 301
301extern int cgit_parse_snapshots_mask(const char *str); 302extern int cgit_parse_snapshots_mask(const char *str);
302 303
303extern int cgit_open_filter(struct cgit_filter *filter); 304extern int cgit_open_filter(struct cgit_filter *filter);
304extern int cgit_close_filter(struct cgit_filter *filter); 305extern int cgit_close_filter(struct cgit_filter *filter);
305 306
306extern int readfile(const char *path, char **buf, size_t *size); 307extern int readfile(const char *path, char **buf, size_t *size);
307 308
308extern char *expand_macros(const char *txt); 309extern char *expand_macros(const char *txt);
309 310
310#endif /* CGIT_H */ 311#endif /* CGIT_H */
diff --git a/cgitrc.5.txt b/cgitrc.5.txt
index ec004d4..6fb1083 100644
--- a/cgitrc.5.txt
+++ b/cgitrc.5.txt
@@ -1,540 +1,546 @@
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:: 113enable-remote-branches::
114 Flag which, when set to "1", will make cgit display remote branches 114 Flag which, when set to "1", will make cgit display remote branches
115 in the summary and refs views. Default value: "0". See also: 115 in the summary and refs views. Default value: "0". See also:
116 "repo.enable-remote-branches". 116 "repo.enable-remote-branches".
117 117
118enable-subject-links:: 118enable-subject-links::
119 Flag which, when set to "1", will make cgit use the subject of the 119 Flag which, when set to "1", will make cgit use the subject of the
120 parent commit as link text when generating links to parent commits 120 parent commit as link text when generating links to parent commits
121 in commit view. Default value: "0". See also: 121 in commit view. Default value: "0". See also:
122 "repo.enable-subject-links". 122 "repo.enable-subject-links".
123 123
124enable-tree-linenumbers:: 124enable-tree-linenumbers::
125 Flag which, when set to "1", will make cgit generate linenumber links 125 Flag which, when set to "1", will make cgit generate linenumber links
126 for plaintext blobs printed in the tree view. Default value: "1". 126 for plaintext blobs printed in the tree view. Default value: "1".
127 127
128favicon:: 128favicon::
129 Url used as link to a shortcut icon for cgit. If specified, it is 129 Url used as link to a shortcut icon for cgit. If specified, it is
130 suggested to use the value "/favicon.ico" since certain browsers will 130 suggested to use the value "/favicon.ico" since certain browsers will
131 ignore other values. Default value: none. 131 ignore other values. Default value: none.
132 132
133footer:: 133footer::
134 The content of the file specified with this option will be included 134 The content of the file specified with this option will be included
135 verbatim at the bottom of all pages (i.e. it replaces the standard 135 verbatim at the bottom of all pages (i.e. it replaces the standard
136 "generated by..." message. Default value: none. 136 "generated by..." message. Default value: none.
137 137
138head-include:: 138head-include::
139 The content of the file specified with this option will be included 139 The content of the file specified with this option will be included
140 verbatim in the html HEAD section on all pages. Default value: none. 140 verbatim in the html HEAD section on all pages. Default value: none.
141 141
142header:: 142header::
143 The content of the file specified with this option will be included 143 The content of the file specified with this option will be included
144 verbatim at the top of all pages. Default value: none. 144 verbatim at the top of all pages. Default value: none.
145 145
146include:: 146include::
147 Name of a configfile to include before the rest of the current config- 147 Name of a configfile to include before the rest of the current config-
148 file is parsed. Default value: none. 148 file is parsed. Default value: none.
149 149
150index-header:: 150index-header::
151 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
152 verbatim above the repository index. This setting is deprecated, and 152 verbatim above the repository index. This setting is deprecated, and
153 will not be supported by cgit-1.0 (use root-readme instead). Default 153 will not be supported by cgit-1.0 (use root-readme instead). Default
154 value: none. 154 value: none.
155 155
156index-info:: 156index-info::
157 The content of the file specified with this option will be included 157 The content of the file specified with this option will be included
158 verbatim below the heading on the repository index page. This setting 158 verbatim below the heading on the repository index page. This setting
159 is deprecated, and will not be supported by cgit-1.0 (use root-desc 159 is deprecated, and will not be supported by cgit-1.0 (use root-desc
160 instead). Default value: none. 160 instead). Default value: none.
161 161
162local-time:: 162local-time::
163 Flag which, if set to "1", makes cgit print commit and tag times in the 163 Flag which, if set to "1", makes cgit print commit and tag times in the
164 servers timezone. Default value: "0". 164 servers timezone. Default value: "0".
165 165
166logo:: 166logo::
167 Url which specifies the source of an image which will be used as a logo 167 Url which specifies the source of an image which will be used as a logo
168 on all cgit pages. Default value: "/cgit.png". 168 on all cgit pages. Default value: "/cgit.png".
169 169
170logo-link:: 170logo-link::
171 Url loaded when clicking on the cgit logo image. If unspecified the 171 Url loaded when clicking on the cgit logo image. If unspecified the
172 calculated url of the repository index page will be used. Default 172 calculated url of the repository index page will be used. Default
173 value: none. 173 value: none.
174 174
175max-atom-items:: 175max-atom-items::
176 Specifies the number of items to display in atom feeds view. Default 176 Specifies the number of items to display in atom feeds view. Default
177 value: "10". 177 value: "10".
178 178
179max-commit-count:: 179max-commit-count::
180 Specifies the number of entries to list per page in "log" view. Default 180 Specifies the number of entries to list per page in "log" view. Default
181 value: "50". 181 value: "50".
182 182
183max-message-length:: 183max-message-length::
184 Specifies the maximum number of commit message characters to display in 184 Specifies the maximum number of commit message characters to display in
185 "log" view. Default value: "80". 185 "log" view. Default value: "80".
186 186
187max-repo-count:: 187max-repo-count::
188 Specifies the number of entries to list per page on therepository 188 Specifies the number of entries to list per page on therepository
189 index page. Default value: "50". 189 index page. Default value: "50".
190 190
191max-repodesc-length:: 191max-repodesc-length::
192 Specifies the maximum number of repo description characters to display 192 Specifies the maximum number of repo description characters to display
193 on the repository index page. Default value: "80". 193 on the repository index page. Default value: "80".
194 194
195max-blob-size:: 195max-blob-size::
196 Specifies the maximum size of a blob to display HTML for in KBytes. 196 Specifies the maximum size of a blob to display HTML for in KBytes.
197 Default value: "0" (limit disabled). 197 Default value: "0" (limit disabled).
198 198
199max-stats:: 199max-stats::
200 Set the default maximum statistics period. Valid values are "week", 200 Set the default maximum statistics period. Valid values are "week",
201 "month", "quarter" and "year". If unspecified, statistics are 201 "month", "quarter" and "year". If unspecified, statistics are
202 disabled. Default value: none. See also: "repo.max-stats". 202 disabled. Default value: none. See also: "repo.max-stats".
203 203
204mimetype.<ext>:: 204mimetype.<ext>::
205 Set the mimetype for the specified filename extension. This is used 205 Set the mimetype for the specified filename extension. This is used
206 by the `plain` command when returning blob content. 206 by the `plain` command when returning blob content.
207 207
208module-link:: 208module-link::
209 Text which will be used as the formatstring for a hyperlink when a 209 Text which will be used as the formatstring for a hyperlink when a
210 submodule is printed in a directory listing. The arguments for the 210 submodule is printed in a directory listing. The arguments for the
211 formatstring are the path and SHA1 of the submodule commit. Default 211 formatstring are the path and SHA1 of the submodule commit. Default
212 value: "./?repo=%s&page=commit&id=%s" 212 value: "./?repo=%s&page=commit&id=%s"
213 213
214nocache:: 214nocache::
215 If set to the value "1" caching will be disabled. This settings is 215 If set to the value "1" caching will be disabled. This settings is
216 deprecated, and will not be honored starting with cgit-1.0. Default 216 deprecated, and will not be honored starting with cgit-1.0. Default
217 value: "0". 217 value: "0".
218 218
219noplainemail:: 219noplainemail::
220 If set to "1" showing full author email adresses will be disabled. 220 If set to "1" showing full author email adresses will be disabled.
221 Default value: "0". 221 Default value: "0".
222 222
223noheader:: 223noheader::
224 Flag which, when set to "1", will make cgit omit the standard header 224 Flag which, when set to "1", will make cgit omit the standard header
225 on all pages. Default value: none. See also: "embedded". 225 on all pages. Default value: none. See also: "embedded".
226 226
227project-list:: 227project-list::
228 A list of subdirectories inside of scan-path, relative to it, that 228 A list of subdirectories inside of scan-path, relative to it, that
229 should loaded as git repositories. This must be defined prior to 229 should loaded as git repositories. This must be defined prior to
230 scan-path. Default value: none. See also: scan-path. 230 scan-path. Default value: none. See also: scan-path.
231 231
232remove-suffix::
233 If set to "1" and scan-path is enabled, if any repositories are found
234 with a suffix of ".git", this suffix will be removed for the url and
235 name. Default value: "0". See also: scan-path.
236
232renamelimit:: 237renamelimit::
233 Maximum number of files to consider when detecting renames. The value 238 Maximum number of files to consider when detecting renames. The value
234 "-1" uses the compiletime value in git (for further info, look at 239 "-1" uses the compiletime value in git (for further info, look at
235 `man git-diff`). Default value: "-1". 240 `man git-diff`). Default value: "-1".
236 241
237repo.group:: 242repo.group::
238 Legacy alias for "section". This option is deprecated and will not be 243 Legacy alias for "section". This option is deprecated and will not be
239 supported in cgit-1.0. 244 supported in cgit-1.0.
240 245
241robots:: 246robots::
242 Text used as content for the "robots" meta-tag. Default value: 247 Text used as content for the "robots" meta-tag. Default value:
243 "index, nofollow". 248 "index, nofollow".
244 249
245root-desc:: 250root-desc::
246 Text printed below the heading on the repository index page. Default 251 Text printed below the heading on the repository index page. Default
247 value: "a fast webinterface for the git dscm". 252 value: "a fast webinterface for the git dscm".
248 253
249root-readme:: 254root-readme::
250 The content of the file specified with this option will be included 255 The content of the file specified with this option will be included
251 verbatim below the "about" link on the repository index page. Default 256 verbatim below the "about" link on the repository index page. Default
252 value: none. 257 value: none.
253 258
254root-title:: 259root-title::
255 Text printed as heading on the repository index page. Default value: 260 Text printed as heading on the repository index page. Default value:
256 "Git Repository Browser". 261 "Git Repository Browser".
257 262
258scan-path:: 263scan-path::
259 A path which will be scanned for repositories. If caching is enabled, 264 A path which will be scanned for repositories. If caching is enabled,
260 the result will be cached as a cgitrc include-file in the cache 265 the result will be cached as a cgitrc include-file in the cache
261 directory. If project-list has been defined prior to scan-path, 266 directory. If project-list has been defined prior to scan-path,
262 scan-path loads only the directories listed in the file pointed to by 267 scan-path loads only the directories listed in the file pointed to by
263 project-list. Default value: none. See also: cache-scanrc-ttl, 268 project-list. Default value: none. See also: cache-scanrc-ttl,
264 project-list. 269 project-list.
265 270
266section:: 271section::
267 The name of the current repository section - all repositories defined 272 The name of the current repository section - all repositories defined
268 after this option will inherit the current section name. Default value: 273 after this option will inherit the current section name. Default value:
269 none. 274 none.
270 275
271side-by-side-diffs:: 276side-by-side-diffs::
272 If set to "1" shows side-by-side diffs instead of unidiffs per 277 If set to "1" shows side-by-side diffs instead of unidiffs per
273 default. Default value: "0". 278 default. Default value: "0".
274 279
275snapshots:: 280snapshots::
276 Text which specifies the default set of snapshot formats generated by 281 Text which specifies the default set of snapshot formats generated by
277 cgit. The value is a space-separated list of zero or more of the 282 cgit. The value is a space-separated list of zero or more of the
278 values "tar", "tar.gz", "tar.bz2" and "zip". Default value: none. 283 values "tar", "tar.gz", "tar.bz2" and "zip". Default value: none.
279 284
280source-filter:: 285source-filter::
281 Specifies a command which will be invoked to format plaintext blobs 286 Specifies a command which will be invoked to format plaintext blobs
282 in the tree view. The command will get the blob content on its STDIN 287 in the tree view. The command will get the blob content on its STDIN
283 and the name of the blob as its only command line argument. The STDOUT 288 and the name of the blob as its only command line argument. The STDOUT
284 from the command will be included verbatim as the blob contents, i.e. 289 from the command will be included verbatim as the blob contents, i.e.
285 this can be used to implement e.g. syntax highlighting. Default value: 290 this can be used to implement e.g. syntax highlighting. Default value:
286 none. 291 none.
287 292
288summary-branches:: 293summary-branches::
289 Specifies the number of branches to display in the repository "summary" 294 Specifies the number of branches to display in the repository "summary"
290 view. Default value: "10". 295 view. Default value: "10".
291 296
292summary-log:: 297summary-log::
293 Specifies the number of log entries to display in the repository 298 Specifies the number of log entries to display in the repository
294 "summary" view. Default value: "10". 299 "summary" view. Default value: "10".
295 300
296summary-tags:: 301summary-tags::
297 Specifies the number of tags to display in the repository "summary" 302 Specifies the number of tags to display in the repository "summary"
298 view. Default value: "10". 303 view. Default value: "10".
299 304
300virtual-root:: 305virtual-root::
301 Url which, if specified, will be used as root for all cgit links. It 306 Url which, if specified, will be used as root for all cgit links. It
302 will also cause cgit to generate 'virtual urls', i.e. urls like 307 will also cause cgit to generate 'virtual urls', i.e. urls like
303 '/cgit/tree/README' as opposed to '?r=cgit&p=tree&path=README'. Default 308 '/cgit/tree/README' as opposed to '?r=cgit&p=tree&path=README'. Default
304 value: none. 309 value: none.
305 NOTE: cgit has recently learned how to use PATH_INFO to achieve the 310 NOTE: cgit has recently learned how to use PATH_INFO to achieve the
306 same kind of virtual urls, so this option will probably be deprecated. 311 same kind of virtual urls, so this option will probably be deprecated.
307 312
308REPOSITORY SETTINGS 313REPOSITORY SETTINGS
309------------------- 314-------------------
310repo.about-filter:: 315repo.about-filter::
311 Override the default about-filter. Default value: none. See also: 316 Override the default about-filter. Default value: none. See also:
312 "enable-filter-overrides". 317 "enable-filter-overrides".
313 318
314repo.clone-url:: 319repo.clone-url::
315 A list of space-separated urls which can be used to clone this repo. 320 A list of space-separated urls which can be used to clone this repo.
316 Default value: none. 321 Default value: none.
317 322
318repo.commit-filter:: 323repo.commit-filter::
319 Override the default commit-filter. Default value: none. See also: 324 Override the default commit-filter. Default value: none. See also:
320 "enable-filter-overrides". 325 "enable-filter-overrides".
321 326
322repo.defbranch:: 327repo.defbranch::
323 The name of the default branch for this repository. If no such branch 328 The name of the default branch for this repository. If no such branch
324 exists in the repository, the first branch name (when sorted) is used 329 exists in the repository, the first branch name (when sorted) is used
325 as default instead. Default value: "master". 330 as default instead. Default value: "master".
326 331
327repo.desc:: 332repo.desc::
328 The value to show as repository description. Default value: none. 333 The value to show as repository description. Default value: none.
329 334
330repo.enable-log-filecount:: 335repo.enable-log-filecount::
331 A flag which can be used to disable the global setting 336 A flag which can be used to disable the global setting
332 `enable-log-filecount'. Default value: none. 337 `enable-log-filecount'. Default value: none.
333 338
334repo.enable-log-linecount:: 339repo.enable-log-linecount::
335 A flag which can be used to disable the global setting 340 A flag which can be used to disable the global setting
336 `enable-log-linecount'. Default value: none. 341 `enable-log-linecount'. Default value: none.
337 342
338repo.enable-remote-branches:: 343repo.enable-remote-branches::
339 Flag which, when set to "1", will make cgit display remote branches 344 Flag which, when set to "1", will make cgit display remote branches
340 in the summary and refs views. Default value: <enable-remote-branches>. 345 in the summary and refs views. Default value: <enable-remote-branches>.
341 346
342repo.enable-subject-links:: 347repo.enable-subject-links::
343 A flag which can be used to override the global setting 348 A flag which can be used to override the global setting
344 `enable-subject-links'. Default value: none. 349 `enable-subject-links'. Default value: none.
345 350
346repo.max-stats:: 351repo.max-stats::
347 Override the default maximum statistics period. Valid values are equal 352 Override the default maximum statistics period. Valid values are equal
348 to the values specified for the global "max-stats" setting. Default 353 to the values specified for the global "max-stats" setting. Default
349 value: none. 354 value: none.
350 355
351repo.name:: 356repo.name::
352 The value to show as repository name. Default value: <repo.url>. 357 The value to show as repository name. Default value: <repo.url>.
353 358
354repo.owner:: 359repo.owner::
355 A value used to identify the owner of the repository. Default value: 360 A value used to identify the owner of the repository. Default value:
356 none. 361 none.
357 362
358repo.path:: 363repo.path::
359 An absolute path to the repository directory. For non-bare repositories 364 An absolute path to the repository directory. For non-bare repositories
360 this is the .git-directory. Default value: none. 365 this is the .git-directory. Default value: none.
361 366
362repo.readme:: 367repo.readme::
363 A path (relative to <repo.path>) which specifies a file to include 368 A path (relative to <repo.path>) which specifies a file to include
364 verbatim as the "About" page for this repo. Default value: none. 369 verbatim as the "About" page for this repo. Default value: none.
365 370
366repo.snapshots:: 371repo.snapshots::
367 A mask of allowed snapshot-formats for this repo, restricted by the 372 A mask of allowed snapshot-formats for this repo, restricted by the
368 "snapshots" global setting. Default value: <snapshots>. 373 "snapshots" global setting. Default value: <snapshots>.
369 374
370repo.section:: 375repo.section::
371 Override the current section name for this repository. Default value: 376 Override the current section name for this repository. Default value:
372 none. 377 none.
373 378
374repo.source-filter:: 379repo.source-filter::
375 Override the default source-filter. Default value: none. See also: 380 Override the default source-filter. Default value: none. See also:
376 "enable-filter-overrides". 381 "enable-filter-overrides".
377 382
378repo.url:: 383repo.url::
379 The relative url used to access the repository. This must be the first 384 The relative url used to access the repository. This must be the first
380 setting specified for each repo. Default value: none. 385 setting specified for each repo. Default value: none.
381 386
382 387
383REPOSITORY-SPECIFIC CGITRC FILE 388REPOSITORY-SPECIFIC CGITRC FILE
384------------------------------- 389-------------------------------
385When the option "scan-path" is used to auto-discover git repositories, cgit 390When the option "scan-path" is used to auto-discover git repositories, cgit
386will try to parse the file "cgitrc" within any found repository. Such a 391will try to parse the file "cgitrc" within any found repository. Such a
387repo-specific config file may contain any of the repo-specific options 392repo-specific config file may contain any of the repo-specific options
388described above, except "repo.url" and "repo.path". Additionally, the "filter" 393described above, except "repo.url" and "repo.path". Additionally, the "filter"
389options are only acknowledged in repo-specific config files when 394options are only acknowledged in repo-specific config files when
390"enable-filter-overrides" is set to "1". 395"enable-filter-overrides" is set to "1".
391 396
392Note: the "repo." prefix is dropped from the option names in repo-specific 397Note: the "repo." prefix is dropped from the option names in repo-specific
393config files, e.g. "repo.desc" becomes "desc". 398config files, e.g. "repo.desc" becomes "desc".
394 399
395 400
396EXAMPLE CGITRC FILE 401EXAMPLE CGITRC FILE
397------------------- 402-------------------
398 403
399.... 404....
400# Enable caching of up to 1000 output entriess 405# Enable caching of up to 1000 output entriess
401cache-size=1000 406cache-size=1000
402 407
403 408
404# Specify some default clone prefixes 409# Specify some default clone prefixes
405clone-prefix=git://foobar.com ssh://foobar.com/pub/git http://foobar.com/git 410clone-prefix=git://foobar.com ssh://foobar.com/pub/git http://foobar.com/git
406 411
407# Specify the css url 412# Specify the css url
408css=/css/cgit.css 413css=/css/cgit.css
409 414
410 415
411# Show extra links for each repository on the index page 416# Show extra links for each repository on the index page
412enable-index-links=1 417enable-index-links=1
413 418
414 419
415# Show number of affected files per commit on the log pages 420# Show number of affected files per commit on the log pages
416enable-log-filecount=1 421enable-log-filecount=1
417 422
418 423
419# Show number of added/removed lines per commit on the log pages 424# Show number of added/removed lines per commit on the log pages
420enable-log-linecount=1 425enable-log-linecount=1
421 426
422 427
423# Add a cgit favicon 428# Add a cgit favicon
424favicon=/favicon.ico 429favicon=/favicon.ico
425 430
426 431
427# Use a custom logo 432# Use a custom logo
428logo=/img/mylogo.png 433logo=/img/mylogo.png
429 434
430 435
431# Enable statistics per week, month and quarter 436# Enable statistics per week, month and quarter
432max-stats=quarter 437max-stats=quarter
433 438
434 439
435# Set the title and heading of the repository index page 440# Set the title and heading of the repository index page
436root-title=foobar.com git repositories 441root-title=foobar.com git repositories
437 442
438 443
439# Set a subheading for the repository index page 444# Set a subheading for the repository index page
440root-desc=tracking the foobar development 445root-desc=tracking the foobar development
441 446
442 447
443# Include some more info about foobar.com on the index page 448# Include some more info about foobar.com on the index page
444root-readme=/var/www/htdocs/about.html 449root-readme=/var/www/htdocs/about.html
445 450
446 451
447# Allow download of tar.gz, tar.bz2 and zip-files 452# Allow download of tar.gz, tar.bz2 and zip-files
448snapshots=tar.gz tar.bz2 zip 453snapshots=tar.gz tar.bz2 zip
449 454
450 455
451## 456##
452## List of common mimetypes 457## List of common mimetypes
453## 458##
454 459
455mimetype.gif=image/gif 460mimetype.gif=image/gif
456mimetype.html=text/html 461mimetype.html=text/html
457mimetype.jpg=image/jpeg 462mimetype.jpg=image/jpeg
458mimetype.jpeg=image/jpeg 463mimetype.jpeg=image/jpeg
459mimetype.pdf=application/pdf 464mimetype.pdf=application/pdf
460mimetype.png=image/png 465mimetype.png=image/png
461mimetype.svg=image/svg+xml 466mimetype.svg=image/svg+xml
462 467
463 468
464## 469##
465## List of repositories. 470## List of repositories.
466## PS: Any repositories listed when section is unset will not be 471## PS: Any repositories listed when section is unset will not be
467## displayed under a section heading 472## displayed under a section heading
468## PPS: This list could be kept in a different file (e.g. '/etc/cgitrepos') 473## PPS: This list could be kept in a different file (e.g. '/etc/cgitrepos')
469## and included like this: 474## and included like this:
470## include=/etc/cgitrepos 475## include=/etc/cgitrepos
471## 476##
472 477
473 478
474repo.url=foo 479repo.url=foo
475repo.path=/pub/git/foo.git 480repo.path=/pub/git/foo.git
476repo.desc=the master foo repository 481repo.desc=the master foo repository
477repo.owner=fooman@foobar.com 482repo.owner=fooman@foobar.com
478repo.readme=info/web/about.html 483repo.readme=info/web/about.html
479 484
480 485
481repo.url=bar 486repo.url=bar
482repo.path=/pub/git/bar.git 487repo.path=/pub/git/bar.git
483repo.desc=the bars for your foo 488repo.desc=the bars for your foo
484repo.owner=barman@foobar.com 489repo.owner=barman@foobar.com
485repo.readme=info/web/about.html 490repo.readme=info/web/about.html
486 491
487 492
488# The next repositories will be displayed under the 'extras' heading 493# The next repositories will be displayed under the 'extras' heading
489section=extras 494section=extras
490 495
491 496
492repo.url=baz 497repo.url=baz
493repo.path=/pub/git/baz.git 498repo.path=/pub/git/baz.git
494repo.desc=a set of extensions for bar users 499repo.desc=a set of extensions for bar users
495 500
496repo.url=wiz 501repo.url=wiz
497repo.path=/pub/git/wiz.git 502repo.path=/pub/git/wiz.git
498repo.desc=the wizard of foo 503repo.desc=the wizard of foo
499 504
500 505
501# Add some mirrored repositories 506# Add some mirrored repositories
502section=mirrors 507section=mirrors
503 508
504 509
505repo.url=git 510repo.url=git
506repo.path=/pub/git/git.git 511repo.path=/pub/git/git.git
507repo.desc=the dscm 512repo.desc=the dscm
508 513
509 514
510repo.url=linux 515repo.url=linux
511repo.path=/pub/git/linux.git 516repo.path=/pub/git/linux.git
512repo.desc=the kernel 517repo.desc=the kernel
513 518
514# Disable adhoc downloads of this repo 519# Disable adhoc downloads of this repo
515repo.snapshots=0 520repo.snapshots=0
516 521
517# Disable line-counts for this repo 522# Disable line-counts for this repo
518repo.enable-log-linecount=0 523repo.enable-log-linecount=0
519 524
520# Restrict the max statistics period for this repo 525# Restrict the max statistics period for this repo
521repo.max-stats=month 526repo.max-stats=month
522.... 527....
523 528
524 529
525BUGS 530BUGS
526---- 531----
527Comments currently cannot appear on the same line as a setting; the comment 532Comments currently cannot appear on the same line as a setting; the comment
528will be included as part of the value. E.g. this line: 533will be included as part of the value. E.g. this line:
529 534
530 robots=index # allow indexing 535 robots=index # allow indexing
531 536
532will generate the following html element: 537will generate the following html element:
533 538
534 <meta name='robots' content='index # allow indexing'/> 539 <meta name='robots' content='index # allow indexing'/>
535 540
536 541
537 542
538AUTHOR 543AUTHOR
539------ 544------
540Lars Hjemli <hjemli@gmail.com> 545Lars Hjemli <hjemli@gmail.com>
546Jason A. Donenfeld <Jason@zx2c4.com>
diff --git a/scan-tree.c b/scan-tree.c
index 9bf9b38..a83a78c 100644
--- a/scan-tree.c
+++ b/scan-tree.c
@@ -1,185 +1,188 @@
1/* scan-tree.c 1/* scan-tree.c
2 * 2 *
3 * Copyright (C) 2008-2009 Lars Hjemli 3 * Copyright (C) 2008-2009 Lars Hjemli
4 * Copyright (C) 2010 Jason A. Donenfeld <Jason@zx2c4.com> 4 * Copyright (C) 2010 Jason A. Donenfeld <Jason@zx2c4.com>
5 * 5 *
6 * Licensed under GNU General Public License v2 6 * Licensed under GNU General Public License v2
7 * (see COPYING for full license text) 7 * (see COPYING for full license text)
8 */ 8 */
9 9
10#include "cgit.h" 10#include "cgit.h"
11#include "configfile.h" 11#include "configfile.h"
12#include "html.h" 12#include "html.h"
13 13
14#define MAX_PATH 4096 14#define MAX_PATH 4096
15 15
16/* return 1 if path contains a objects/ directory and a HEAD file */ 16/* return 1 if path contains a objects/ directory and a HEAD file */
17static int is_git_dir(const char *path) 17static int is_git_dir(const char *path)
18{ 18{
19 struct stat st; 19 struct stat st;
20 static char buf[MAX_PATH]; 20 static char buf[MAX_PATH];
21 21
22 if (snprintf(buf, MAX_PATH, "%s/objects", path) >= MAX_PATH) { 22 if (snprintf(buf, MAX_PATH, "%s/objects", path) >= MAX_PATH) {
23 fprintf(stderr, "Insanely long path: %s\n", path); 23 fprintf(stderr, "Insanely long path: %s\n", path);
24 return 0; 24 return 0;
25 } 25 }
26 if (stat(buf, &st)) { 26 if (stat(buf, &st)) {
27 if (errno != ENOENT) 27 if (errno != ENOENT)
28 fprintf(stderr, "Error checking path %s: %s (%d)\n", 28 fprintf(stderr, "Error checking path %s: %s (%d)\n",
29 path, strerror(errno), errno); 29 path, strerror(errno), errno);
30 return 0; 30 return 0;
31 } 31 }
32 if (!S_ISDIR(st.st_mode)) 32 if (!S_ISDIR(st.st_mode))
33 return 0; 33 return 0;
34 34
35 sprintf(buf, "%s/HEAD", path); 35 sprintf(buf, "%s/HEAD", path);
36 if (stat(buf, &st)) { 36 if (stat(buf, &st)) {
37 if (errno != ENOENT) 37 if (errno != ENOENT)
38 fprintf(stderr, "Error checking path %s: %s (%d)\n", 38 fprintf(stderr, "Error checking path %s: %s (%d)\n",
39 path, strerror(errno), errno); 39 path, strerror(errno), errno);
40 return 0; 40 return 0;
41 } 41 }
42 if (!S_ISREG(st.st_mode)) 42 if (!S_ISREG(st.st_mode))
43 return 0; 43 return 0;
44 44
45 return 1; 45 return 1;
46} 46}
47 47
48struct cgit_repo *repo; 48struct cgit_repo *repo;
49repo_config_fn config_fn; 49repo_config_fn config_fn;
50 50
51static void repo_config(const char *name, const char *value) 51static void repo_config(const char *name, const char *value)
52{ 52{
53 config_fn(repo, name, value); 53 config_fn(repo, name, value);
54} 54}
55 55
56static void add_repo(const char *base, const char *path, repo_config_fn fn) 56static void add_repo(const char *base, const char *path, repo_config_fn fn)
57{ 57{
58 struct stat st; 58 struct stat st;
59 struct passwd *pwd; 59 struct passwd *pwd;
60 char *p; 60 char *p;
61 size_t size; 61 size_t size;
62 62
63 if (stat(path, &st)) { 63 if (stat(path, &st)) {
64 fprintf(stderr, "Error accessing %s: %s (%d)\n", 64 fprintf(stderr, "Error accessing %s: %s (%d)\n",
65 path, strerror(errno), errno); 65 path, strerror(errno), errno);
66 return; 66 return;
67 } 67 }
68 if (!stat(fmt("%s/noweb", path), &st)) 68 if (!stat(fmt("%s/noweb", path), &st))
69 return; 69 return;
70 if ((pwd = getpwuid(st.st_uid)) == NULL) { 70 if ((pwd = getpwuid(st.st_uid)) == NULL) {
71 fprintf(stderr, "Error reading owner-info for %s: %s (%d)\n", 71 fprintf(stderr, "Error reading owner-info for %s: %s (%d)\n",
72 path, strerror(errno), errno); 72 path, strerror(errno), errno);
73 return; 73 return;
74 } 74 }
75 if (base == path) 75 if (base == path)
76 p = fmt("%s", path); 76 p = fmt("%s", path);
77 else 77 else
78 p = fmt("%s", path + strlen(base) + 1); 78 p = fmt("%s", path + strlen(base) + 1);
79 79
80 if (!strcmp(p + strlen(p) - 5, "/.git")) 80 if (!strcmp(p + strlen(p) - 5, "/.git"))
81 p[strlen(p) - 5] = '\0'; 81 p[strlen(p) - 5] = '\0';
82 82
83 repo = cgit_add_repo(xstrdup(p)); 83 repo = cgit_add_repo(xstrdup(p));
84 if (ctx.cfg.remove_suffix)
85 if ((p = strrchr(repo->url, '.')) && !strcmp(p, ".git"))
86 *p = '\0';
84 repo->name = repo->url; 87 repo->name = repo->url;
85 repo->path = xstrdup(path); 88 repo->path = xstrdup(path);
86 p = (pwd && pwd->pw_gecos) ? strchr(pwd->pw_gecos, ',') : NULL; 89 p = (pwd && pwd->pw_gecos) ? strchr(pwd->pw_gecos, ',') : NULL;
87 if (p) 90 if (p)
88 *p = '\0'; 91 *p = '\0';
89 repo->owner = (pwd ? xstrdup(pwd->pw_gecos ? pwd->pw_gecos : pwd->pw_name) : ""); 92 repo->owner = (pwd ? xstrdup(pwd->pw_gecos ? pwd->pw_gecos : pwd->pw_name) : "");
90 93
91 p = fmt("%s/description", path); 94 p = fmt("%s/description", path);
92 if (!stat(p, &st)) 95 if (!stat(p, &st))
93 readfile(p, &repo->desc, &size); 96 readfile(p, &repo->desc, &size);
94 97
95 p = fmt("%s/README.html", path); 98 p = fmt("%s/README.html", path);
96 if (!stat(p, &st)) 99 if (!stat(p, &st))
97 repo->readme = "README.html"; 100 repo->readme = "README.html";
98 101
99 p = fmt("%s/cgitrc", path); 102 p = fmt("%s/cgitrc", path);
100 if (!stat(p, &st)) { 103 if (!stat(p, &st)) {
101 config_fn = fn; 104 config_fn = fn;
102 parse_configfile(xstrdup(p), &repo_config); 105 parse_configfile(xstrdup(p), &repo_config);
103 } 106 }
104} 107}
105 108
106static void scan_path(const char *base, const char *path, repo_config_fn fn) 109static void scan_path(const char *base, const char *path, repo_config_fn fn)
107{ 110{
108 DIR *dir; 111 DIR *dir;
109 struct dirent *ent; 112 struct dirent *ent;
110 char *buf; 113 char *buf;
111 struct stat st; 114 struct stat st;
112 115
113 if (is_git_dir(path)) { 116 if (is_git_dir(path)) {
114 add_repo(base, path, fn); 117 add_repo(base, path, fn);
115 return; 118 return;
116 } 119 }
117 if (is_git_dir(fmt("%s/.git", path))) { 120 if (is_git_dir(fmt("%s/.git", path))) {
118 add_repo(base, fmt("%s/.git", path), fn); 121 add_repo(base, fmt("%s/.git", path), fn);
119 return; 122 return;
120 } 123 }
121 dir = opendir(path); 124 dir = opendir(path);
122 if (!dir) { 125 if (!dir) {
123 fprintf(stderr, "Error opening directory %s: %s (%d)\n", 126 fprintf(stderr, "Error opening directory %s: %s (%d)\n",
124 path, strerror(errno), errno); 127 path, strerror(errno), errno);
125 return; 128 return;
126 } 129 }
127 while((ent = readdir(dir)) != NULL) { 130 while((ent = readdir(dir)) != NULL) {
128 if (ent->d_name[0] == '.') { 131 if (ent->d_name[0] == '.') {
129 if (ent->d_name[1] == '\0') 132 if (ent->d_name[1] == '\0')
130 continue; 133 continue;
131 if (ent->d_name[1] == '.' && ent->d_name[2] == '\0') 134 if (ent->d_name[1] == '.' && ent->d_name[2] == '\0')
132 continue; 135 continue;
133 } 136 }
134 buf = malloc(strlen(path) + strlen(ent->d_name) + 2); 137 buf = malloc(strlen(path) + strlen(ent->d_name) + 2);
135 if (!buf) { 138 if (!buf) {
136 fprintf(stderr, "Alloc error on %s: %s (%d)\n", 139 fprintf(stderr, "Alloc error on %s: %s (%d)\n",
137 path, strerror(errno), errno); 140 path, strerror(errno), errno);
138 exit(1); 141 exit(1);
139 } 142 }
140 sprintf(buf, "%s/%s", path, ent->d_name); 143 sprintf(buf, "%s/%s", path, ent->d_name);
141 if (stat(buf, &st)) { 144 if (stat(buf, &st)) {
142 fprintf(stderr, "Error checking path %s: %s (%d)\n", 145 fprintf(stderr, "Error checking path %s: %s (%d)\n",
143 buf, strerror(errno), errno); 146 buf, strerror(errno), errno);
144 free(buf); 147 free(buf);
145 continue; 148 continue;
146 } 149 }
147 if (S_ISDIR(st.st_mode)) 150 if (S_ISDIR(st.st_mode))
148 scan_path(base, buf, fn); 151 scan_path(base, buf, fn);
149 free(buf); 152 free(buf);
150 } 153 }
151 closedir(dir); 154 closedir(dir);
152} 155}
153 156
154#define lastc(s) s[strlen(s) - 1] 157#define lastc(s) s[strlen(s) - 1]
155 158
156void scan_projects(const char *path, const char *projectsfile, repo_config_fn fn) 159void scan_projects(const char *path, const char *projectsfile, repo_config_fn fn)
157{ 160{
158 char line[MAX_PATH * 2], *z; 161 char line[MAX_PATH * 2], *z;
159 FILE *projects; 162 FILE *projects;
160 int err; 163 int err;
161 164
162 projects = fopen(projectsfile, "r"); 165 projects = fopen(projectsfile, "r");
163 if (!projects) { 166 if (!projects) {
164 fprintf(stderr, "Error opening projectsfile %s: %s (%d)\n", 167 fprintf(stderr, "Error opening projectsfile %s: %s (%d)\n",
165 projectsfile, strerror(errno), errno); 168 projectsfile, strerror(errno), errno);
166 } 169 }
167 while (fgets(line, sizeof(line), projects) != NULL) { 170 while (fgets(line, sizeof(line), projects) != NULL) {
168 for (z = &lastc(line); 171 for (z = &lastc(line);
169 strlen(line) && strchr("\n\r", *z); 172 strlen(line) && strchr("\n\r", *z);
170 z = &lastc(line)) 173 z = &lastc(line))
171 *z = '\0'; 174 *z = '\0';
172 if (strlen(line)) 175 if (strlen(line))
173 scan_path(path, fmt("%s/%s", path, line), fn); 176 scan_path(path, fmt("%s/%s", path, line), fn);
174 } 177 }
175 if ((err = ferror(projects))) { 178 if ((err = ferror(projects))) {
176 fprintf(stderr, "Error reading from projectsfile %s: %s (%d)\n", 179 fprintf(stderr, "Error reading from projectsfile %s: %s (%d)\n",
177 projectsfile, strerror(err), err); 180 projectsfile, strerror(err), err);
178 } 181 }
179 fclose(projects); 182 fclose(projects);
180} 183}
181 184
182void scan_tree(const char *path, repo_config_fn fn) 185void scan_tree(const char *path, repo_config_fn fn)
183{ 186{
184 scan_path(path, path, fn); 187 scan_path(path, path, fn);
185} 188}