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