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