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