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