summaryrefslogtreecommitdiffabout
Unidiff
Diffstat (more/less context) (ignore whitespace changes)
-rw-r--r--cgit.c8
-rw-r--r--cgit.h2
-rw-r--r--cgitrc.5.txt9
-rw-r--r--ui-shared.c18
4 files changed, 31 insertions, 6 deletions
diff --git a/cgit.c b/cgit.c
index 412fbf0..e8c1f94 100644
--- a/cgit.c
+++ b/cgit.c
@@ -1,778 +1,782 @@
1/* cgit.c: cgi for the git scm 1/* cgit.c: cgi for the git scm
2 * 2 *
3 * Copyright (C) 2006 Lars Hjemli 3 * Copyright (C) 2006 Lars Hjemli
4 * Copyright (C) 2010 Jason A. Donenfeld <Jason@zx2c4.com> 4 * Copyright (C) 2010 Jason A. Donenfeld <Jason@zx2c4.com>
5 * 5 *
6 * Licensed under GNU General Public License v2 6 * Licensed under GNU General Public License v2
7 * (see COPYING for full license text) 7 * (see COPYING for full license text)
8 */ 8 */
9 9
10#include "cgit.h" 10#include "cgit.h"
11#include "cache.h" 11#include "cache.h"
12#include "cmd.h" 12#include "cmd.h"
13#include "configfile.h" 13#include "configfile.h"
14#include "html.h" 14#include "html.h"
15#include "ui-shared.h" 15#include "ui-shared.h"
16#include "ui-stats.h" 16#include "ui-stats.h"
17#include "scan-tree.h" 17#include "scan-tree.h"
18 18
19const char *cgit_version = CGIT_VERSION; 19const char *cgit_version = CGIT_VERSION;
20 20
21void add_mimetype(const char *name, const char *value) 21void add_mimetype(const char *name, const char *value)
22{ 22{
23 struct string_list_item *item; 23 struct string_list_item *item;
24 24
25 item = string_list_insert(&ctx.cfg.mimetypes, xstrdup(name)); 25 item = string_list_insert(&ctx.cfg.mimetypes, xstrdup(name));
26 item->util = xstrdup(value); 26 item->util = xstrdup(value);
27} 27}
28 28
29struct cgit_filter *new_filter(const char *cmd, int extra_args) 29struct cgit_filter *new_filter(const char *cmd, int extra_args)
30{ 30{
31 struct cgit_filter *f; 31 struct cgit_filter *f;
32 32
33 if (!cmd || !cmd[0]) 33 if (!cmd || !cmd[0])
34 return NULL; 34 return NULL;
35 35
36 f = xmalloc(sizeof(struct cgit_filter)); 36 f = xmalloc(sizeof(struct cgit_filter));
37 f->cmd = xstrdup(cmd); 37 f->cmd = xstrdup(cmd);
38 f->argv = xmalloc((2 + extra_args) * sizeof(char *)); 38 f->argv = xmalloc((2 + extra_args) * sizeof(char *));
39 f->argv[0] = f->cmd; 39 f->argv[0] = f->cmd;
40 f->argv[1] = NULL; 40 f->argv[1] = NULL;
41 return f; 41 return f;
42} 42}
43 43
44static void process_cached_repolist(const char *path); 44static void process_cached_repolist(const char *path);
45 45
46void repo_config(struct cgit_repo *repo, const char *name, const char *value) 46void repo_config(struct cgit_repo *repo, const char *name, const char *value)
47{ 47{
48 if (!strcmp(name, "name")) 48 if (!strcmp(name, "name"))
49 repo->name = xstrdup(value); 49 repo->name = xstrdup(value);
50 else if (!strcmp(name, "clone-url")) 50 else if (!strcmp(name, "clone-url"))
51 repo->clone_url = xstrdup(value); 51 repo->clone_url = xstrdup(value);
52 else if (!strcmp(name, "desc")) 52 else if (!strcmp(name, "desc"))
53 repo->desc = xstrdup(value); 53 repo->desc = xstrdup(value);
54 else if (!strcmp(name, "owner")) 54 else if (!strcmp(name, "owner"))
55 repo->owner = xstrdup(value); 55 repo->owner = xstrdup(value);
56 else if (!strcmp(name, "defbranch")) 56 else if (!strcmp(name, "defbranch"))
57 repo->defbranch = xstrdup(value); 57 repo->defbranch = xstrdup(value);
58 else if (!strcmp(name, "snapshots")) 58 else if (!strcmp(name, "snapshots"))
59 repo->snapshots = ctx.cfg.snapshots & cgit_parse_snapshots_mask(value); 59 repo->snapshots = ctx.cfg.snapshots & cgit_parse_snapshots_mask(value);
60 else if (!strcmp(name, "enable-log-filecount")) 60 else if (!strcmp(name, "enable-log-filecount"))
61 repo->enable_log_filecount = ctx.cfg.enable_log_filecount * atoi(value); 61 repo->enable_log_filecount = ctx.cfg.enable_log_filecount * atoi(value);
62 else if (!strcmp(name, "enable-log-linecount")) 62 else if (!strcmp(name, "enable-log-linecount"))
63 repo->enable_log_linecount = ctx.cfg.enable_log_linecount * atoi(value); 63 repo->enable_log_linecount = ctx.cfg.enable_log_linecount * atoi(value);
64 else if (!strcmp(name, "enable-remote-branches")) 64 else if (!strcmp(name, "enable-remote-branches"))
65 repo->enable_remote_branches = atoi(value); 65 repo->enable_remote_branches = atoi(value);
66 else if (!strcmp(name, "enable-subject-links")) 66 else if (!strcmp(name, "enable-subject-links"))
67 repo->enable_subject_links = atoi(value); 67 repo->enable_subject_links = atoi(value);
68 else if (!strcmp(name, "max-stats")) 68 else if (!strcmp(name, "max-stats"))
69 repo->max_stats = cgit_find_stats_period(value, NULL); 69 repo->max_stats = cgit_find_stats_period(value, NULL);
70 else if (!strcmp(name, "module-link")) 70 else if (!strcmp(name, "module-link"))
71 repo->module_link= xstrdup(value); 71 repo->module_link= xstrdup(value);
72 else if (!strcmp(name, "section")) 72 else if (!strcmp(name, "section"))
73 repo->section = xstrdup(value); 73 repo->section = xstrdup(value);
74 else if (!strcmp(name, "readme") && value != NULL) { 74 else if (!strcmp(name, "readme") && value != NULL)
75 repo->readme = xstrdup(value); 75 repo->readme = xstrdup(value);
76 } else if (ctx.cfg.enable_filter_overrides) { 76 else if (!strcmp(name, "logo") && value != NULL)
77 repo->logo = xstrdup(value);
78 else if (!strcmp(name, "logo-link") && value != NULL)
79 repo->logo_link = xstrdup(value);
80 else if (ctx.cfg.enable_filter_overrides) {
77 if (!strcmp(name, "about-filter")) 81 if (!strcmp(name, "about-filter"))
78 repo->about_filter = new_filter(value, 0); 82 repo->about_filter = new_filter(value, 0);
79 else if (!strcmp(name, "commit-filter")) 83 else if (!strcmp(name, "commit-filter"))
80 repo->commit_filter = new_filter(value, 0); 84 repo->commit_filter = new_filter(value, 0);
81 else if (!strcmp(name, "source-filter")) 85 else if (!strcmp(name, "source-filter"))
82 repo->source_filter = new_filter(value, 1); 86 repo->source_filter = new_filter(value, 1);
83 } 87 }
84} 88}
85 89
86void config_cb(const char *name, const char *value) 90void config_cb(const char *name, const char *value)
87{ 91{
88 if (!strcmp(name, "section") || !strcmp(name, "repo.group")) 92 if (!strcmp(name, "section") || !strcmp(name, "repo.group"))
89 ctx.cfg.section = xstrdup(value); 93 ctx.cfg.section = xstrdup(value);
90 else if (!strcmp(name, "repo.url")) 94 else if (!strcmp(name, "repo.url"))
91 ctx.repo = cgit_add_repo(value); 95 ctx.repo = cgit_add_repo(value);
92 else if (ctx.repo && !strcmp(name, "repo.path")) 96 else if (ctx.repo && !strcmp(name, "repo.path"))
93 ctx.repo->path = trim_end(value, '/'); 97 ctx.repo->path = trim_end(value, '/');
94 else if (ctx.repo && !prefixcmp(name, "repo.")) 98 else if (ctx.repo && !prefixcmp(name, "repo."))
95 repo_config(ctx.repo, name + 5, value); 99 repo_config(ctx.repo, name + 5, value);
96 else if (!strcmp(name, "readme")) 100 else if (!strcmp(name, "readme"))
97 ctx.cfg.readme = xstrdup(value); 101 ctx.cfg.readme = xstrdup(value);
98 else if (!strcmp(name, "root-title")) 102 else if (!strcmp(name, "root-title"))
99 ctx.cfg.root_title = xstrdup(value); 103 ctx.cfg.root_title = xstrdup(value);
100 else if (!strcmp(name, "root-desc")) 104 else if (!strcmp(name, "root-desc"))
101 ctx.cfg.root_desc = xstrdup(value); 105 ctx.cfg.root_desc = xstrdup(value);
102 else if (!strcmp(name, "root-readme")) 106 else if (!strcmp(name, "root-readme"))
103 ctx.cfg.root_readme = xstrdup(value); 107 ctx.cfg.root_readme = xstrdup(value);
104 else if (!strcmp(name, "css")) 108 else if (!strcmp(name, "css"))
105 ctx.cfg.css = xstrdup(value); 109 ctx.cfg.css = xstrdup(value);
106 else if (!strcmp(name, "favicon")) 110 else if (!strcmp(name, "favicon"))
107 ctx.cfg.favicon = xstrdup(value); 111 ctx.cfg.favicon = xstrdup(value);
108 else if (!strcmp(name, "footer")) 112 else if (!strcmp(name, "footer"))
109 ctx.cfg.footer = xstrdup(value); 113 ctx.cfg.footer = xstrdup(value);
110 else if (!strcmp(name, "head-include")) 114 else if (!strcmp(name, "head-include"))
111 ctx.cfg.head_include = xstrdup(value); 115 ctx.cfg.head_include = xstrdup(value);
112 else if (!strcmp(name, "header")) 116 else if (!strcmp(name, "header"))
113 ctx.cfg.header = xstrdup(value); 117 ctx.cfg.header = xstrdup(value);
114 else if (!strcmp(name, "logo")) 118 else if (!strcmp(name, "logo"))
115 ctx.cfg.logo = xstrdup(value); 119 ctx.cfg.logo = xstrdup(value);
116 else if (!strcmp(name, "index-header")) 120 else if (!strcmp(name, "index-header"))
117 ctx.cfg.index_header = xstrdup(value); 121 ctx.cfg.index_header = xstrdup(value);
118 else if (!strcmp(name, "index-info")) 122 else if (!strcmp(name, "index-info"))
119 ctx.cfg.index_info = xstrdup(value); 123 ctx.cfg.index_info = xstrdup(value);
120 else if (!strcmp(name, "logo-link")) 124 else if (!strcmp(name, "logo-link"))
121 ctx.cfg.logo_link = xstrdup(value); 125 ctx.cfg.logo_link = xstrdup(value);
122 else if (!strcmp(name, "module-link")) 126 else if (!strcmp(name, "module-link"))
123 ctx.cfg.module_link = xstrdup(value); 127 ctx.cfg.module_link = xstrdup(value);
124 else if (!strcmp(name, "strict-export")) 128 else if (!strcmp(name, "strict-export"))
125 ctx.cfg.strict_export = xstrdup(value); 129 ctx.cfg.strict_export = xstrdup(value);
126 else if (!strcmp(name, "virtual-root")) { 130 else if (!strcmp(name, "virtual-root")) {
127 ctx.cfg.virtual_root = trim_end(value, '/'); 131 ctx.cfg.virtual_root = trim_end(value, '/');
128 if (!ctx.cfg.virtual_root && (!strcmp(value, "/"))) 132 if (!ctx.cfg.virtual_root && (!strcmp(value, "/")))
129 ctx.cfg.virtual_root = ""; 133 ctx.cfg.virtual_root = "";
130 } else if (!strcmp(name, "nocache")) 134 } else if (!strcmp(name, "nocache"))
131 ctx.cfg.nocache = atoi(value); 135 ctx.cfg.nocache = atoi(value);
132 else if (!strcmp(name, "noplainemail")) 136 else if (!strcmp(name, "noplainemail"))
133 ctx.cfg.noplainemail = atoi(value); 137 ctx.cfg.noplainemail = atoi(value);
134 else if (!strcmp(name, "noheader")) 138 else if (!strcmp(name, "noheader"))
135 ctx.cfg.noheader = atoi(value); 139 ctx.cfg.noheader = atoi(value);
136 else if (!strcmp(name, "snapshots")) 140 else if (!strcmp(name, "snapshots"))
137 ctx.cfg.snapshots = cgit_parse_snapshots_mask(value); 141 ctx.cfg.snapshots = cgit_parse_snapshots_mask(value);
138 else if (!strcmp(name, "enable-filter-overrides")) 142 else if (!strcmp(name, "enable-filter-overrides"))
139 ctx.cfg.enable_filter_overrides = atoi(value); 143 ctx.cfg.enable_filter_overrides = atoi(value);
140 else if (!strcmp(name, "enable-gitweb-owner")) 144 else if (!strcmp(name, "enable-gitweb-owner"))
141 ctx.cfg.enable_gitweb_owner = atoi(value); 145 ctx.cfg.enable_gitweb_owner = atoi(value);
142 else if (!strcmp(name, "enable-index-links")) 146 else if (!strcmp(name, "enable-index-links"))
143 ctx.cfg.enable_index_links = atoi(value); 147 ctx.cfg.enable_index_links = atoi(value);
144 else if (!strcmp(name, "enable-log-filecount")) 148 else if (!strcmp(name, "enable-log-filecount"))
145 ctx.cfg.enable_log_filecount = atoi(value); 149 ctx.cfg.enable_log_filecount = atoi(value);
146 else if (!strcmp(name, "enable-log-linecount")) 150 else if (!strcmp(name, "enable-log-linecount"))
147 ctx.cfg.enable_log_linecount = atoi(value); 151 ctx.cfg.enable_log_linecount = atoi(value);
148 else if (!strcmp(name, "enable-remote-branches")) 152 else if (!strcmp(name, "enable-remote-branches"))
149 ctx.cfg.enable_remote_branches = atoi(value); 153 ctx.cfg.enable_remote_branches = atoi(value);
150 else if (!strcmp(name, "enable-subject-links")) 154 else if (!strcmp(name, "enable-subject-links"))
151 ctx.cfg.enable_subject_links = atoi(value); 155 ctx.cfg.enable_subject_links = atoi(value);
152 else if (!strcmp(name, "enable-tree-linenumbers")) 156 else if (!strcmp(name, "enable-tree-linenumbers"))
153 ctx.cfg.enable_tree_linenumbers = atoi(value); 157 ctx.cfg.enable_tree_linenumbers = atoi(value);
154 else if (!strcmp(name, "max-stats")) 158 else if (!strcmp(name, "max-stats"))
155 ctx.cfg.max_stats = cgit_find_stats_period(value, NULL); 159 ctx.cfg.max_stats = cgit_find_stats_period(value, NULL);
156 else if (!strcmp(name, "cache-size")) 160 else if (!strcmp(name, "cache-size"))
157 ctx.cfg.cache_size = atoi(value); 161 ctx.cfg.cache_size = atoi(value);
158 else if (!strcmp(name, "cache-root")) 162 else if (!strcmp(name, "cache-root"))
159 ctx.cfg.cache_root = xstrdup(expand_macros(value)); 163 ctx.cfg.cache_root = xstrdup(expand_macros(value));
160 else if (!strcmp(name, "cache-root-ttl")) 164 else if (!strcmp(name, "cache-root-ttl"))
161 ctx.cfg.cache_root_ttl = atoi(value); 165 ctx.cfg.cache_root_ttl = atoi(value);
162 else if (!strcmp(name, "cache-repo-ttl")) 166 else if (!strcmp(name, "cache-repo-ttl"))
163 ctx.cfg.cache_repo_ttl = atoi(value); 167 ctx.cfg.cache_repo_ttl = atoi(value);
164 else if (!strcmp(name, "cache-scanrc-ttl")) 168 else if (!strcmp(name, "cache-scanrc-ttl"))
165 ctx.cfg.cache_scanrc_ttl = atoi(value); 169 ctx.cfg.cache_scanrc_ttl = atoi(value);
166 else if (!strcmp(name, "cache-static-ttl")) 170 else if (!strcmp(name, "cache-static-ttl"))
167 ctx.cfg.cache_static_ttl = atoi(value); 171 ctx.cfg.cache_static_ttl = atoi(value);
168 else if (!strcmp(name, "cache-dynamic-ttl")) 172 else if (!strcmp(name, "cache-dynamic-ttl"))
169 ctx.cfg.cache_dynamic_ttl = atoi(value); 173 ctx.cfg.cache_dynamic_ttl = atoi(value);
170 else if (!strcmp(name, "about-filter")) 174 else if (!strcmp(name, "about-filter"))
171 ctx.cfg.about_filter = new_filter(value, 0); 175 ctx.cfg.about_filter = new_filter(value, 0);
172 else if (!strcmp(name, "commit-filter")) 176 else if (!strcmp(name, "commit-filter"))
173 ctx.cfg.commit_filter = new_filter(value, 0); 177 ctx.cfg.commit_filter = new_filter(value, 0);
174 else if (!strcmp(name, "embedded")) 178 else if (!strcmp(name, "embedded"))
175 ctx.cfg.embedded = atoi(value); 179 ctx.cfg.embedded = atoi(value);
176 else if (!strcmp(name, "max-atom-items")) 180 else if (!strcmp(name, "max-atom-items"))
177 ctx.cfg.max_atom_items = atoi(value); 181 ctx.cfg.max_atom_items = atoi(value);
178 else if (!strcmp(name, "max-message-length")) 182 else if (!strcmp(name, "max-message-length"))
179 ctx.cfg.max_msg_len = atoi(value); 183 ctx.cfg.max_msg_len = atoi(value);
180 else if (!strcmp(name, "max-repodesc-length")) 184 else if (!strcmp(name, "max-repodesc-length"))
181 ctx.cfg.max_repodesc_len = atoi(value); 185 ctx.cfg.max_repodesc_len = atoi(value);
182 else if (!strcmp(name, "max-blob-size")) 186 else if (!strcmp(name, "max-blob-size"))
183 ctx.cfg.max_blob_size = atoi(value); 187 ctx.cfg.max_blob_size = atoi(value);
184 else if (!strcmp(name, "max-repo-count")) 188 else if (!strcmp(name, "max-repo-count"))
185 ctx.cfg.max_repo_count = atoi(value); 189 ctx.cfg.max_repo_count = atoi(value);
186 else if (!strcmp(name, "max-commit-count")) 190 else if (!strcmp(name, "max-commit-count"))
187 ctx.cfg.max_commit_count = atoi(value); 191 ctx.cfg.max_commit_count = atoi(value);
188 else if (!strcmp(name, "project-list")) 192 else if (!strcmp(name, "project-list"))
189 ctx.cfg.project_list = xstrdup(expand_macros(value)); 193 ctx.cfg.project_list = xstrdup(expand_macros(value));
190 else if (!strcmp(name, "scan-path")) 194 else if (!strcmp(name, "scan-path"))
191 if (!ctx.cfg.nocache && ctx.cfg.cache_size) 195 if (!ctx.cfg.nocache && ctx.cfg.cache_size)
192 process_cached_repolist(expand_macros(value)); 196 process_cached_repolist(expand_macros(value));
193 else if (ctx.cfg.project_list) 197 else if (ctx.cfg.project_list)
194 scan_projects(expand_macros(value), 198 scan_projects(expand_macros(value),
195 ctx.cfg.project_list, repo_config); 199 ctx.cfg.project_list, repo_config);
196 else 200 else
197 scan_tree(expand_macros(value), repo_config); 201 scan_tree(expand_macros(value), repo_config);
198 else if (!strcmp(name, "section-from-path")) 202 else if (!strcmp(name, "section-from-path"))
199 ctx.cfg.section_from_path = atoi(value); 203 ctx.cfg.section_from_path = atoi(value);
200 else if (!strcmp(name, "source-filter")) 204 else if (!strcmp(name, "source-filter"))
201 ctx.cfg.source_filter = new_filter(value, 1); 205 ctx.cfg.source_filter = new_filter(value, 1);
202 else if (!strcmp(name, "summary-log")) 206 else if (!strcmp(name, "summary-log"))
203 ctx.cfg.summary_log = atoi(value); 207 ctx.cfg.summary_log = atoi(value);
204 else if (!strcmp(name, "summary-branches")) 208 else if (!strcmp(name, "summary-branches"))
205 ctx.cfg.summary_branches = atoi(value); 209 ctx.cfg.summary_branches = atoi(value);
206 else if (!strcmp(name, "summary-tags")) 210 else if (!strcmp(name, "summary-tags"))
207 ctx.cfg.summary_tags = atoi(value); 211 ctx.cfg.summary_tags = atoi(value);
208 else if (!strcmp(name, "side-by-side-diffs")) 212 else if (!strcmp(name, "side-by-side-diffs"))
209 ctx.cfg.ssdiff = atoi(value); 213 ctx.cfg.ssdiff = atoi(value);
210 else if (!strcmp(name, "agefile")) 214 else if (!strcmp(name, "agefile"))
211 ctx.cfg.agefile = xstrdup(value); 215 ctx.cfg.agefile = xstrdup(value);
212 else if (!strcmp(name, "renamelimit")) 216 else if (!strcmp(name, "renamelimit"))
213 ctx.cfg.renamelimit = atoi(value); 217 ctx.cfg.renamelimit = atoi(value);
214 else if (!strcmp(name, "remove-suffix")) 218 else if (!strcmp(name, "remove-suffix"))
215 ctx.cfg.remove_suffix = atoi(value); 219 ctx.cfg.remove_suffix = atoi(value);
216 else if (!strcmp(name, "robots")) 220 else if (!strcmp(name, "robots"))
217 ctx.cfg.robots = xstrdup(value); 221 ctx.cfg.robots = xstrdup(value);
218 else if (!strcmp(name, "clone-prefix")) 222 else if (!strcmp(name, "clone-prefix"))
219 ctx.cfg.clone_prefix = xstrdup(value); 223 ctx.cfg.clone_prefix = xstrdup(value);
220 else if (!strcmp(name, "local-time")) 224 else if (!strcmp(name, "local-time"))
221 ctx.cfg.local_time = atoi(value); 225 ctx.cfg.local_time = atoi(value);
222 else if (!prefixcmp(name, "mimetype.")) 226 else if (!prefixcmp(name, "mimetype."))
223 add_mimetype(name + 9, value); 227 add_mimetype(name + 9, value);
224 else if (!strcmp(name, "include")) 228 else if (!strcmp(name, "include"))
225 parse_configfile(expand_macros(value), config_cb); 229 parse_configfile(expand_macros(value), config_cb);
226} 230}
227 231
228static void querystring_cb(const char *name, const char *value) 232static void querystring_cb(const char *name, const char *value)
229{ 233{
230 if (!value) 234 if (!value)
231 value = ""; 235 value = "";
232 236
233 if (!strcmp(name,"r")) { 237 if (!strcmp(name,"r")) {
234 ctx.qry.repo = xstrdup(value); 238 ctx.qry.repo = xstrdup(value);
235 ctx.repo = cgit_get_repoinfo(value); 239 ctx.repo = cgit_get_repoinfo(value);
236 } else if (!strcmp(name, "p")) { 240 } else if (!strcmp(name, "p")) {
237 ctx.qry.page = xstrdup(value); 241 ctx.qry.page = xstrdup(value);
238 } else if (!strcmp(name, "url")) { 242 } else if (!strcmp(name, "url")) {
239 if (*value == '/') 243 if (*value == '/')
240 value++; 244 value++;
241 ctx.qry.url = xstrdup(value); 245 ctx.qry.url = xstrdup(value);
242 cgit_parse_url(value); 246 cgit_parse_url(value);
243 } else if (!strcmp(name, "qt")) { 247 } else if (!strcmp(name, "qt")) {
244 ctx.qry.grep = xstrdup(value); 248 ctx.qry.grep = xstrdup(value);
245 } else if (!strcmp(name, "q")) { 249 } else if (!strcmp(name, "q")) {
246 ctx.qry.search = xstrdup(value); 250 ctx.qry.search = xstrdup(value);
247 } else if (!strcmp(name, "h")) { 251 } else if (!strcmp(name, "h")) {
248 ctx.qry.head = xstrdup(value); 252 ctx.qry.head = xstrdup(value);
249 ctx.qry.has_symref = 1; 253 ctx.qry.has_symref = 1;
250 } else if (!strcmp(name, "id")) { 254 } else if (!strcmp(name, "id")) {
251 ctx.qry.sha1 = xstrdup(value); 255 ctx.qry.sha1 = xstrdup(value);
252 ctx.qry.has_sha1 = 1; 256 ctx.qry.has_sha1 = 1;
253 } else if (!strcmp(name, "id2")) { 257 } else if (!strcmp(name, "id2")) {
254 ctx.qry.sha2 = xstrdup(value); 258 ctx.qry.sha2 = xstrdup(value);
255 ctx.qry.has_sha1 = 1; 259 ctx.qry.has_sha1 = 1;
256 } else if (!strcmp(name, "ofs")) { 260 } else if (!strcmp(name, "ofs")) {
257 ctx.qry.ofs = atoi(value); 261 ctx.qry.ofs = atoi(value);
258 } else if (!strcmp(name, "path")) { 262 } else if (!strcmp(name, "path")) {
259 ctx.qry.path = trim_end(value, '/'); 263 ctx.qry.path = trim_end(value, '/');
260 } else if (!strcmp(name, "name")) { 264 } else if (!strcmp(name, "name")) {
261 ctx.qry.name = xstrdup(value); 265 ctx.qry.name = xstrdup(value);
262 } else if (!strcmp(name, "mimetype")) { 266 } else if (!strcmp(name, "mimetype")) {
263 ctx.qry.mimetype = xstrdup(value); 267 ctx.qry.mimetype = xstrdup(value);
264 } else if (!strcmp(name, "s")){ 268 } else if (!strcmp(name, "s")){
265 ctx.qry.sort = xstrdup(value); 269 ctx.qry.sort = xstrdup(value);
266 } else if (!strcmp(name, "showmsg")) { 270 } else if (!strcmp(name, "showmsg")) {
267 ctx.qry.showmsg = atoi(value); 271 ctx.qry.showmsg = atoi(value);
268 } else if (!strcmp(name, "period")) { 272 } else if (!strcmp(name, "period")) {
269 ctx.qry.period = xstrdup(value); 273 ctx.qry.period = xstrdup(value);
270 } else if (!strcmp(name, "ss")) { 274 } else if (!strcmp(name, "ss")) {
271 ctx.qry.ssdiff = atoi(value); 275 ctx.qry.ssdiff = atoi(value);
272 } else if (!strcmp(name, "all")) { 276 } else if (!strcmp(name, "all")) {
273 ctx.qry.show_all = atoi(value); 277 ctx.qry.show_all = atoi(value);
274 } else if (!strcmp(name, "context")) { 278 } else if (!strcmp(name, "context")) {
275 ctx.qry.context = atoi(value); 279 ctx.qry.context = atoi(value);
276 } else if (!strcmp(name, "ignorews")) { 280 } else if (!strcmp(name, "ignorews")) {
277 ctx.qry.ignorews = atoi(value); 281 ctx.qry.ignorews = atoi(value);
278 } 282 }
279} 283}
280 284
281char *xstrdupn(const char *str) 285char *xstrdupn(const char *str)
282{ 286{
283 return (str ? xstrdup(str) : NULL); 287 return (str ? xstrdup(str) : NULL);
284} 288}
285 289
286static void prepare_context(struct cgit_context *ctx) 290static void prepare_context(struct cgit_context *ctx)
287{ 291{
288 memset(ctx, 0, sizeof(*ctx)); 292 memset(ctx, 0, sizeof(*ctx));
289 ctx->cfg.agefile = "info/web/last-modified"; 293 ctx->cfg.agefile = "info/web/last-modified";
290 ctx->cfg.nocache = 0; 294 ctx->cfg.nocache = 0;
291 ctx->cfg.cache_size = 0; 295 ctx->cfg.cache_size = 0;
292 ctx->cfg.cache_dynamic_ttl = 5; 296 ctx->cfg.cache_dynamic_ttl = 5;
293 ctx->cfg.cache_max_create_time = 5; 297 ctx->cfg.cache_max_create_time = 5;
294 ctx->cfg.cache_repo_ttl = 5; 298 ctx->cfg.cache_repo_ttl = 5;
295 ctx->cfg.cache_root = CGIT_CACHE_ROOT; 299 ctx->cfg.cache_root = CGIT_CACHE_ROOT;
296 ctx->cfg.cache_root_ttl = 5; 300 ctx->cfg.cache_root_ttl = 5;
297 ctx->cfg.cache_scanrc_ttl = 15; 301 ctx->cfg.cache_scanrc_ttl = 15;
298 ctx->cfg.cache_static_ttl = -1; 302 ctx->cfg.cache_static_ttl = -1;
299 ctx->cfg.css = "/cgit.css"; 303 ctx->cfg.css = "/cgit.css";
300 ctx->cfg.logo = "/cgit.png"; 304 ctx->cfg.logo = "/cgit.png";
301 ctx->cfg.local_time = 0; 305 ctx->cfg.local_time = 0;
302 ctx->cfg.enable_gitweb_owner = 1; 306 ctx->cfg.enable_gitweb_owner = 1;
303 ctx->cfg.enable_tree_linenumbers = 1; 307 ctx->cfg.enable_tree_linenumbers = 1;
304 ctx->cfg.max_repo_count = 50; 308 ctx->cfg.max_repo_count = 50;
305 ctx->cfg.max_commit_count = 50; 309 ctx->cfg.max_commit_count = 50;
306 ctx->cfg.max_lock_attempts = 5; 310 ctx->cfg.max_lock_attempts = 5;
307 ctx->cfg.max_msg_len = 80; 311 ctx->cfg.max_msg_len = 80;
308 ctx->cfg.max_repodesc_len = 80; 312 ctx->cfg.max_repodesc_len = 80;
309 ctx->cfg.max_blob_size = 0; 313 ctx->cfg.max_blob_size = 0;
310 ctx->cfg.max_stats = 0; 314 ctx->cfg.max_stats = 0;
311 ctx->cfg.module_link = "./?repo=%s&page=commit&id=%s"; 315 ctx->cfg.module_link = "./?repo=%s&page=commit&id=%s";
312 ctx->cfg.project_list = NULL; 316 ctx->cfg.project_list = NULL;
313 ctx->cfg.renamelimit = -1; 317 ctx->cfg.renamelimit = -1;
314 ctx->cfg.remove_suffix = 0; 318 ctx->cfg.remove_suffix = 0;
315 ctx->cfg.robots = "index, nofollow"; 319 ctx->cfg.robots = "index, nofollow";
316 ctx->cfg.root_title = "Git repository browser"; 320 ctx->cfg.root_title = "Git repository browser";
317 ctx->cfg.root_desc = "a fast webinterface for the git dscm"; 321 ctx->cfg.root_desc = "a fast webinterface for the git dscm";
318 ctx->cfg.script_name = CGIT_SCRIPT_NAME; 322 ctx->cfg.script_name = CGIT_SCRIPT_NAME;
319 ctx->cfg.section = ""; 323 ctx->cfg.section = "";
320 ctx->cfg.summary_branches = 10; 324 ctx->cfg.summary_branches = 10;
321 ctx->cfg.summary_log = 10; 325 ctx->cfg.summary_log = 10;
322 ctx->cfg.summary_tags = 10; 326 ctx->cfg.summary_tags = 10;
323 ctx->cfg.max_atom_items = 10; 327 ctx->cfg.max_atom_items = 10;
324 ctx->cfg.ssdiff = 0; 328 ctx->cfg.ssdiff = 0;
325 ctx->env.cgit_config = xstrdupn(getenv("CGIT_CONFIG")); 329 ctx->env.cgit_config = xstrdupn(getenv("CGIT_CONFIG"));
326 ctx->env.http_host = xstrdupn(getenv("HTTP_HOST")); 330 ctx->env.http_host = xstrdupn(getenv("HTTP_HOST"));
327 ctx->env.https = xstrdupn(getenv("HTTPS")); 331 ctx->env.https = xstrdupn(getenv("HTTPS"));
328 ctx->env.no_http = xstrdupn(getenv("NO_HTTP")); 332 ctx->env.no_http = xstrdupn(getenv("NO_HTTP"));
329 ctx->env.path_info = xstrdupn(getenv("PATH_INFO")); 333 ctx->env.path_info = xstrdupn(getenv("PATH_INFO"));
330 ctx->env.query_string = xstrdupn(getenv("QUERY_STRING")); 334 ctx->env.query_string = xstrdupn(getenv("QUERY_STRING"));
331 ctx->env.request_method = xstrdupn(getenv("REQUEST_METHOD")); 335 ctx->env.request_method = xstrdupn(getenv("REQUEST_METHOD"));
332 ctx->env.script_name = xstrdupn(getenv("SCRIPT_NAME")); 336 ctx->env.script_name = xstrdupn(getenv("SCRIPT_NAME"));
333 ctx->env.server_name = xstrdupn(getenv("SERVER_NAME")); 337 ctx->env.server_name = xstrdupn(getenv("SERVER_NAME"));
334 ctx->env.server_port = xstrdupn(getenv("SERVER_PORT")); 338 ctx->env.server_port = xstrdupn(getenv("SERVER_PORT"));
335 ctx->page.mimetype = "text/html"; 339 ctx->page.mimetype = "text/html";
336 ctx->page.charset = PAGE_ENCODING; 340 ctx->page.charset = PAGE_ENCODING;
337 ctx->page.filename = NULL; 341 ctx->page.filename = NULL;
338 ctx->page.size = 0; 342 ctx->page.size = 0;
339 ctx->page.modified = time(NULL); 343 ctx->page.modified = time(NULL);
340 ctx->page.expires = ctx->page.modified; 344 ctx->page.expires = ctx->page.modified;
341 ctx->page.etag = NULL; 345 ctx->page.etag = NULL;
342 memset(&ctx->cfg.mimetypes, 0, sizeof(struct string_list)); 346 memset(&ctx->cfg.mimetypes, 0, sizeof(struct string_list));
343 if (ctx->env.script_name) 347 if (ctx->env.script_name)
344 ctx->cfg.script_name = ctx->env.script_name; 348 ctx->cfg.script_name = ctx->env.script_name;
345 if (ctx->env.query_string) 349 if (ctx->env.query_string)
346 ctx->qry.raw = ctx->env.query_string; 350 ctx->qry.raw = ctx->env.query_string;
347 if (!ctx->env.cgit_config) 351 if (!ctx->env.cgit_config)
348 ctx->env.cgit_config = CGIT_CONFIG; 352 ctx->env.cgit_config = CGIT_CONFIG;
349} 353}
350 354
351struct refmatch { 355struct refmatch {
352 char *req_ref; 356 char *req_ref;
353 char *first_ref; 357 char *first_ref;
354 int match; 358 int match;
355}; 359};
356 360
357int find_current_ref(const char *refname, const unsigned char *sha1, 361int find_current_ref(const char *refname, const unsigned char *sha1,
358 int flags, void *cb_data) 362 int flags, void *cb_data)
359{ 363{
360 struct refmatch *info; 364 struct refmatch *info;
361 365
362 info = (struct refmatch *)cb_data; 366 info = (struct refmatch *)cb_data;
363 if (!strcmp(refname, info->req_ref)) 367 if (!strcmp(refname, info->req_ref))
364 info->match = 1; 368 info->match = 1;
365 if (!info->first_ref) 369 if (!info->first_ref)
366 info->first_ref = xstrdup(refname); 370 info->first_ref = xstrdup(refname);
367 return info->match; 371 return info->match;
368} 372}
369 373
370char *find_default_branch(struct cgit_repo *repo) 374char *find_default_branch(struct cgit_repo *repo)
371{ 375{
372 struct refmatch info; 376 struct refmatch info;
373 char *ref; 377 char *ref;
374 378
375 info.req_ref = repo->defbranch; 379 info.req_ref = repo->defbranch;
376 info.first_ref = NULL; 380 info.first_ref = NULL;
377 info.match = 0; 381 info.match = 0;
378 for_each_branch_ref(find_current_ref, &info); 382 for_each_branch_ref(find_current_ref, &info);
379 if (info.match) 383 if (info.match)
380 ref = info.req_ref; 384 ref = info.req_ref;
381 else 385 else
382 ref = info.first_ref; 386 ref = info.first_ref;
383 if (ref) 387 if (ref)
384 ref = xstrdup(ref); 388 ref = xstrdup(ref);
385 return ref; 389 return ref;
386} 390}
387 391
388static int prepare_repo_cmd(struct cgit_context *ctx) 392static int prepare_repo_cmd(struct cgit_context *ctx)
389{ 393{
390 char *tmp; 394 char *tmp;
391 unsigned char sha1[20]; 395 unsigned char sha1[20];
392 int nongit = 0; 396 int nongit = 0;
393 397
394 setenv("GIT_DIR", ctx->repo->path, 1); 398 setenv("GIT_DIR", ctx->repo->path, 1);
395 setup_git_directory_gently(&nongit); 399 setup_git_directory_gently(&nongit);
396 if (nongit) { 400 if (nongit) {
397 ctx->page.title = fmt("%s - %s", ctx->cfg.root_title, 401 ctx->page.title = fmt("%s - %s", ctx->cfg.root_title,
398 "config error"); 402 "config error");
399 tmp = fmt("Not a git repository: '%s'", ctx->repo->path); 403 tmp = fmt("Not a git repository: '%s'", ctx->repo->path);
400 ctx->repo = NULL; 404 ctx->repo = NULL;
401 cgit_print_http_headers(ctx); 405 cgit_print_http_headers(ctx);
402 cgit_print_docstart(ctx); 406 cgit_print_docstart(ctx);
403 cgit_print_pageheader(ctx); 407 cgit_print_pageheader(ctx);
404 cgit_print_error(tmp); 408 cgit_print_error(tmp);
405 cgit_print_docend(); 409 cgit_print_docend();
406 return 1; 410 return 1;
407 } 411 }
408 ctx->page.title = fmt("%s - %s", ctx->repo->name, ctx->repo->desc); 412 ctx->page.title = fmt("%s - %s", ctx->repo->name, ctx->repo->desc);
409 413
410 if (!ctx->qry.head) { 414 if (!ctx->qry.head) {
411 ctx->qry.nohead = 1; 415 ctx->qry.nohead = 1;
412 ctx->qry.head = find_default_branch(ctx->repo); 416 ctx->qry.head = find_default_branch(ctx->repo);
413 ctx->repo->defbranch = ctx->qry.head; 417 ctx->repo->defbranch = ctx->qry.head;
414 } 418 }
415 419
416 if (!ctx->qry.head) { 420 if (!ctx->qry.head) {
417 cgit_print_http_headers(ctx); 421 cgit_print_http_headers(ctx);
418 cgit_print_docstart(ctx); 422 cgit_print_docstart(ctx);
419 cgit_print_pageheader(ctx); 423 cgit_print_pageheader(ctx);
420 cgit_print_error("Repository seems to be empty"); 424 cgit_print_error("Repository seems to be empty");
421 cgit_print_docend(); 425 cgit_print_docend();
422 return 1; 426 return 1;
423 } 427 }
424 428
425 if (get_sha1(ctx->qry.head, sha1)) { 429 if (get_sha1(ctx->qry.head, sha1)) {
426 tmp = xstrdup(ctx->qry.head); 430 tmp = xstrdup(ctx->qry.head);
427 ctx->qry.head = ctx->repo->defbranch; 431 ctx->qry.head = ctx->repo->defbranch;
428 ctx->page.status = 404; 432 ctx->page.status = 404;
429 ctx->page.statusmsg = "not found"; 433 ctx->page.statusmsg = "not found";
430 cgit_print_http_headers(ctx); 434 cgit_print_http_headers(ctx);
431 cgit_print_docstart(ctx); 435 cgit_print_docstart(ctx);
432 cgit_print_pageheader(ctx); 436 cgit_print_pageheader(ctx);
433 cgit_print_error(fmt("Invalid branch: %s", tmp)); 437 cgit_print_error(fmt("Invalid branch: %s", tmp));
434 cgit_print_docend(); 438 cgit_print_docend();
435 return 1; 439 return 1;
436 } 440 }
437 return 0; 441 return 0;
438} 442}
439 443
440static void process_request(void *cbdata) 444static void process_request(void *cbdata)
441{ 445{
442 struct cgit_context *ctx = cbdata; 446 struct cgit_context *ctx = cbdata;
443 struct cgit_cmd *cmd; 447 struct cgit_cmd *cmd;
444 448
445 cmd = cgit_get_cmd(ctx); 449 cmd = cgit_get_cmd(ctx);
446 if (!cmd) { 450 if (!cmd) {
447 ctx->page.title = "cgit error"; 451 ctx->page.title = "cgit error";
448 cgit_print_http_headers(ctx); 452 cgit_print_http_headers(ctx);
449 cgit_print_docstart(ctx); 453 cgit_print_docstart(ctx);
450 cgit_print_pageheader(ctx); 454 cgit_print_pageheader(ctx);
451 cgit_print_error("Invalid request"); 455 cgit_print_error("Invalid request");
452 cgit_print_docend(); 456 cgit_print_docend();
453 return; 457 return;
454 } 458 }
455 459
456 /* If cmd->want_vpath is set, assume ctx->qry.path contains a "virtual" 460 /* If cmd->want_vpath is set, assume ctx->qry.path contains a "virtual"
457 * in-project path limit to be made available at ctx->qry.vpath. 461 * in-project path limit to be made available at ctx->qry.vpath.
458 * Otherwise, no path limit is in effect (ctx->qry.vpath = NULL). 462 * Otherwise, no path limit is in effect (ctx->qry.vpath = NULL).
459 */ 463 */
460 ctx->qry.vpath = cmd->want_vpath ? ctx->qry.path : NULL; 464 ctx->qry.vpath = cmd->want_vpath ? ctx->qry.path : NULL;
461 465
462 if (cmd->want_repo && !ctx->repo) { 466 if (cmd->want_repo && !ctx->repo) {
463 cgit_print_http_headers(ctx); 467 cgit_print_http_headers(ctx);
464 cgit_print_docstart(ctx); 468 cgit_print_docstart(ctx);
465 cgit_print_pageheader(ctx); 469 cgit_print_pageheader(ctx);
466 cgit_print_error(fmt("No repository selected")); 470 cgit_print_error(fmt("No repository selected"));
467 cgit_print_docend(); 471 cgit_print_docend();
468 return; 472 return;
469 } 473 }
470 474
471 if (ctx->repo && prepare_repo_cmd(ctx)) 475 if (ctx->repo && prepare_repo_cmd(ctx))
472 return; 476 return;
473 477
474 if (cmd->want_layout) { 478 if (cmd->want_layout) {
475 cgit_print_http_headers(ctx); 479 cgit_print_http_headers(ctx);
476 cgit_print_docstart(ctx); 480 cgit_print_docstart(ctx);
477 cgit_print_pageheader(ctx); 481 cgit_print_pageheader(ctx);
478 } 482 }
479 483
480 cmd->fn(ctx); 484 cmd->fn(ctx);
481 485
482 if (cmd->want_layout) 486 if (cmd->want_layout)
483 cgit_print_docend(); 487 cgit_print_docend();
484} 488}
485 489
486int cmp_repos(const void *a, const void *b) 490int cmp_repos(const void *a, const void *b)
487{ 491{
488 const struct cgit_repo *ra = a, *rb = b; 492 const struct cgit_repo *ra = a, *rb = b;
489 return strcmp(ra->url, rb->url); 493 return strcmp(ra->url, rb->url);
490} 494}
491 495
492char *build_snapshot_setting(int bitmap) 496char *build_snapshot_setting(int bitmap)
493{ 497{
494 const struct cgit_snapshot_format *f; 498 const struct cgit_snapshot_format *f;
495 char *result = xstrdup(""); 499 char *result = xstrdup("");
496 char *tmp; 500 char *tmp;
497 int len; 501 int len;
498 502
499 for (f = cgit_snapshot_formats; f->suffix; f++) { 503 for (f = cgit_snapshot_formats; f->suffix; f++) {
500 if (f->bit & bitmap) { 504 if (f->bit & bitmap) {
501 tmp = result; 505 tmp = result;
502 result = xstrdup(fmt("%s%s ", tmp, f->suffix)); 506 result = xstrdup(fmt("%s%s ", tmp, f->suffix));
503 free(tmp); 507 free(tmp);
504 } 508 }
505 } 509 }
506 len = strlen(result); 510 len = strlen(result);
507 if (len) 511 if (len)
508 result[len - 1] = '\0'; 512 result[len - 1] = '\0';
509 return result; 513 return result;
510} 514}
511 515
512char *get_first_line(char *txt) 516char *get_first_line(char *txt)
513{ 517{
514 char *t = xstrdup(txt); 518 char *t = xstrdup(txt);
515 char *p = strchr(t, '\n'); 519 char *p = strchr(t, '\n');
516 if (p) 520 if (p)
517 *p = '\0'; 521 *p = '\0';
518 return t; 522 return t;
519} 523}
520 524
521void print_repo(FILE *f, struct cgit_repo *repo) 525void print_repo(FILE *f, struct cgit_repo *repo)
522{ 526{
523 fprintf(f, "repo.url=%s\n", repo->url); 527 fprintf(f, "repo.url=%s\n", repo->url);
524 fprintf(f, "repo.name=%s\n", repo->name); 528 fprintf(f, "repo.name=%s\n", repo->name);
525 fprintf(f, "repo.path=%s\n", repo->path); 529 fprintf(f, "repo.path=%s\n", repo->path);
526 if (repo->owner) 530 if (repo->owner)
527 fprintf(f, "repo.owner=%s\n", repo->owner); 531 fprintf(f, "repo.owner=%s\n", repo->owner);
528 if (repo->desc) { 532 if (repo->desc) {
529 char *tmp = get_first_line(repo->desc); 533 char *tmp = get_first_line(repo->desc);
530 fprintf(f, "repo.desc=%s\n", tmp); 534 fprintf(f, "repo.desc=%s\n", tmp);
531 free(tmp); 535 free(tmp);
532 } 536 }
533 if (repo->readme) 537 if (repo->readme)
534 fprintf(f, "repo.readme=%s\n", repo->readme); 538 fprintf(f, "repo.readme=%s\n", repo->readme);
535 if (repo->defbranch) 539 if (repo->defbranch)
536 fprintf(f, "repo.defbranch=%s\n", repo->defbranch); 540 fprintf(f, "repo.defbranch=%s\n", repo->defbranch);
537 if (repo->module_link) 541 if (repo->module_link)
538 fprintf(f, "repo.module-link=%s\n", repo->module_link); 542 fprintf(f, "repo.module-link=%s\n", repo->module_link);
539 if (repo->section) 543 if (repo->section)
540 fprintf(f, "repo.section=%s\n", repo->section); 544 fprintf(f, "repo.section=%s\n", repo->section);
541 if (repo->clone_url) 545 if (repo->clone_url)
542 fprintf(f, "repo.clone-url=%s\n", repo->clone_url); 546 fprintf(f, "repo.clone-url=%s\n", repo->clone_url);
543 fprintf(f, "repo.enable-log-filecount=%d\n", 547 fprintf(f, "repo.enable-log-filecount=%d\n",
544 repo->enable_log_filecount); 548 repo->enable_log_filecount);
545 fprintf(f, "repo.enable-log-linecount=%d\n", 549 fprintf(f, "repo.enable-log-linecount=%d\n",
546 repo->enable_log_linecount); 550 repo->enable_log_linecount);
547 if (repo->about_filter && repo->about_filter != ctx.cfg.about_filter) 551 if (repo->about_filter && repo->about_filter != ctx.cfg.about_filter)
548 fprintf(f, "repo.about-filter=%s\n", repo->about_filter->cmd); 552 fprintf(f, "repo.about-filter=%s\n", repo->about_filter->cmd);
549 if (repo->commit_filter && repo->commit_filter != ctx.cfg.commit_filter) 553 if (repo->commit_filter && repo->commit_filter != ctx.cfg.commit_filter)
550 fprintf(f, "repo.commit-filter=%s\n", repo->commit_filter->cmd); 554 fprintf(f, "repo.commit-filter=%s\n", repo->commit_filter->cmd);
551 if (repo->source_filter && repo->source_filter != ctx.cfg.source_filter) 555 if (repo->source_filter && repo->source_filter != ctx.cfg.source_filter)
552 fprintf(f, "repo.source-filter=%s\n", repo->source_filter->cmd); 556 fprintf(f, "repo.source-filter=%s\n", repo->source_filter->cmd);
553 if (repo->snapshots != ctx.cfg.snapshots) { 557 if (repo->snapshots != ctx.cfg.snapshots) {
554 char *tmp = build_snapshot_setting(repo->snapshots); 558 char *tmp = build_snapshot_setting(repo->snapshots);
555 fprintf(f, "repo.snapshots=%s\n", tmp); 559 fprintf(f, "repo.snapshots=%s\n", tmp);
556 free(tmp); 560 free(tmp);
557 } 561 }
558 if (repo->max_stats != ctx.cfg.max_stats) 562 if (repo->max_stats != ctx.cfg.max_stats)
559 fprintf(f, "repo.max-stats=%s\n", 563 fprintf(f, "repo.max-stats=%s\n",
560 cgit_find_stats_periodname(repo->max_stats)); 564 cgit_find_stats_periodname(repo->max_stats));
561 fprintf(f, "\n"); 565 fprintf(f, "\n");
562} 566}
563 567
564void print_repolist(FILE *f, struct cgit_repolist *list, int start) 568void print_repolist(FILE *f, struct cgit_repolist *list, int start)
565{ 569{
566 int i; 570 int i;
567 571
568 for(i = start; i < list->count; i++) 572 for(i = start; i < list->count; i++)
569 print_repo(f, &list->repos[i]); 573 print_repo(f, &list->repos[i]);
570} 574}
571 575
572/* Scan 'path' for git repositories, save the resulting repolist in 'cached_rc' 576/* Scan 'path' for git repositories, save the resulting repolist in 'cached_rc'
573 * and return 0 on success. 577 * and return 0 on success.
574 */ 578 */
575static int generate_cached_repolist(const char *path, const char *cached_rc) 579static int generate_cached_repolist(const char *path, const char *cached_rc)
576{ 580{
577 char *locked_rc; 581 char *locked_rc;
578 int idx; 582 int idx;
579 FILE *f; 583 FILE *f;
580 584
581 locked_rc = xstrdup(fmt("%s.lock", cached_rc)); 585 locked_rc = xstrdup(fmt("%s.lock", cached_rc));
582 f = fopen(locked_rc, "wx"); 586 f = fopen(locked_rc, "wx");
583 if (!f) { 587 if (!f) {
584 /* Inform about the error unless the lockfile already existed, 588 /* Inform about the error unless the lockfile already existed,
585 * since that only means we've got concurrent requests. 589 * since that only means we've got concurrent requests.
586 */ 590 */
587 if (errno != EEXIST) 591 if (errno != EEXIST)
588 fprintf(stderr, "[cgit] Error opening %s: %s (%d)\n", 592 fprintf(stderr, "[cgit] Error opening %s: %s (%d)\n",
589 locked_rc, strerror(errno), errno); 593 locked_rc, strerror(errno), errno);
590 return errno; 594 return errno;
591 } 595 }
592 idx = cgit_repolist.count; 596 idx = cgit_repolist.count;
593 if (ctx.cfg.project_list) 597 if (ctx.cfg.project_list)
594 scan_projects(path, ctx.cfg.project_list, repo_config); 598 scan_projects(path, ctx.cfg.project_list, repo_config);
595 else 599 else
596 scan_tree(path, repo_config); 600 scan_tree(path, repo_config);
597 print_repolist(f, &cgit_repolist, idx); 601 print_repolist(f, &cgit_repolist, idx);
598 if (rename(locked_rc, cached_rc)) 602 if (rename(locked_rc, cached_rc))
599 fprintf(stderr, "[cgit] Error renaming %s to %s: %s (%d)\n", 603 fprintf(stderr, "[cgit] Error renaming %s to %s: %s (%d)\n",
600 locked_rc, cached_rc, strerror(errno), errno); 604 locked_rc, cached_rc, strerror(errno), errno);
601 fclose(f); 605 fclose(f);
602 return 0; 606 return 0;
603} 607}
604 608
605static void process_cached_repolist(const char *path) 609static void process_cached_repolist(const char *path)
606{ 610{
607 struct stat st; 611 struct stat st;
608 char *cached_rc; 612 char *cached_rc;
609 time_t age; 613 time_t age;
610 unsigned long hash; 614 unsigned long hash;
611 615
612 hash = hash_str(path); 616 hash = hash_str(path);
613 if (ctx.cfg.project_list) 617 if (ctx.cfg.project_list)
614 hash += hash_str(ctx.cfg.project_list); 618 hash += hash_str(ctx.cfg.project_list);
615 cached_rc = xstrdup(fmt("%s/rc-%8lx", ctx.cfg.cache_root, hash)); 619 cached_rc = xstrdup(fmt("%s/rc-%8lx", ctx.cfg.cache_root, hash));
616 620
617 if (stat(cached_rc, &st)) { 621 if (stat(cached_rc, &st)) {
618 /* Nothing is cached, we need to scan without forking. And 622 /* Nothing is cached, we need to scan without forking. And
619 * if we fail to generate a cached repolist, we need to 623 * if we fail to generate a cached repolist, we need to
620 * invoke scan_tree manually. 624 * invoke scan_tree manually.
621 */ 625 */
622 if (generate_cached_repolist(path, cached_rc)) { 626 if (generate_cached_repolist(path, cached_rc)) {
623 if (ctx.cfg.project_list) 627 if (ctx.cfg.project_list)
624 scan_projects(path, ctx.cfg.project_list, 628 scan_projects(path, ctx.cfg.project_list,
625 repo_config); 629 repo_config);
626 else 630 else
627 scan_tree(path, repo_config); 631 scan_tree(path, repo_config);
628 } 632 }
629 return; 633 return;
630 } 634 }
631 635
632 parse_configfile(cached_rc, config_cb); 636 parse_configfile(cached_rc, config_cb);
633 637
634 /* If the cached configfile hasn't expired, lets exit now */ 638 /* If the cached configfile hasn't expired, lets exit now */
635 age = time(NULL) - st.st_mtime; 639 age = time(NULL) - st.st_mtime;
636 if (age <= (ctx.cfg.cache_scanrc_ttl * 60)) 640 if (age <= (ctx.cfg.cache_scanrc_ttl * 60))
637 return; 641 return;
638 642
639 /* The cached repolist has been parsed, but it was old. So lets 643 /* The cached repolist has been parsed, but it was old. So lets
640 * rescan the specified path and generate a new cached repolist 644 * rescan the specified path and generate a new cached repolist
641 * in a child-process to avoid latency for the current request. 645 * in a child-process to avoid latency for the current request.
642 */ 646 */
643 if (fork()) 647 if (fork())
644 return; 648 return;
645 649
646 exit(generate_cached_repolist(path, cached_rc)); 650 exit(generate_cached_repolist(path, cached_rc));
647} 651}
648 652
649static void cgit_parse_args(int argc, const char **argv) 653static void cgit_parse_args(int argc, const char **argv)
650{ 654{
651 int i; 655 int i;
652 int scan = 0; 656 int scan = 0;
653 657
654 for (i = 1; i < argc; i++) { 658 for (i = 1; i < argc; i++) {
655 if (!strncmp(argv[i], "--cache=", 8)) { 659 if (!strncmp(argv[i], "--cache=", 8)) {
656 ctx.cfg.cache_root = xstrdup(argv[i]+8); 660 ctx.cfg.cache_root = xstrdup(argv[i]+8);
657 } 661 }
658 if (!strcmp(argv[i], "--nocache")) { 662 if (!strcmp(argv[i], "--nocache")) {
659 ctx.cfg.nocache = 1; 663 ctx.cfg.nocache = 1;
660 } 664 }
661 if (!strcmp(argv[i], "--nohttp")) { 665 if (!strcmp(argv[i], "--nohttp")) {
662 ctx.env.no_http = "1"; 666 ctx.env.no_http = "1";
663 } 667 }
664 if (!strncmp(argv[i], "--query=", 8)) { 668 if (!strncmp(argv[i], "--query=", 8)) {
665 ctx.qry.raw = xstrdup(argv[i]+8); 669 ctx.qry.raw = xstrdup(argv[i]+8);
666 } 670 }
667 if (!strncmp(argv[i], "--repo=", 7)) { 671 if (!strncmp(argv[i], "--repo=", 7)) {
668 ctx.qry.repo = xstrdup(argv[i]+7); 672 ctx.qry.repo = xstrdup(argv[i]+7);
669 } 673 }
670 if (!strncmp(argv[i], "--page=", 7)) { 674 if (!strncmp(argv[i], "--page=", 7)) {
671 ctx.qry.page = xstrdup(argv[i]+7); 675 ctx.qry.page = xstrdup(argv[i]+7);
672 } 676 }
673 if (!strncmp(argv[i], "--head=", 7)) { 677 if (!strncmp(argv[i], "--head=", 7)) {
674 ctx.qry.head = xstrdup(argv[i]+7); 678 ctx.qry.head = xstrdup(argv[i]+7);
675 ctx.qry.has_symref = 1; 679 ctx.qry.has_symref = 1;
676 } 680 }
677 if (!strncmp(argv[i], "--sha1=", 7)) { 681 if (!strncmp(argv[i], "--sha1=", 7)) {
678 ctx.qry.sha1 = xstrdup(argv[i]+7); 682 ctx.qry.sha1 = xstrdup(argv[i]+7);
679 ctx.qry.has_sha1 = 1; 683 ctx.qry.has_sha1 = 1;
680 } 684 }
681 if (!strncmp(argv[i], "--ofs=", 6)) { 685 if (!strncmp(argv[i], "--ofs=", 6)) {
682 ctx.qry.ofs = atoi(argv[i]+6); 686 ctx.qry.ofs = atoi(argv[i]+6);
683 } 687 }
684 if (!strncmp(argv[i], "--scan-tree=", 12) || 688 if (!strncmp(argv[i], "--scan-tree=", 12) ||
685 !strncmp(argv[i], "--scan-path=", 12)) { 689 !strncmp(argv[i], "--scan-path=", 12)) {
686 /* HACK: the global snapshot bitmask defines the 690 /* HACK: the global snapshot bitmask defines the
687 * set of allowed snapshot formats, but the config 691 * set of allowed snapshot formats, but the config
688 * file hasn't been parsed yet so the mask is 692 * file hasn't been parsed yet so the mask is
689 * currently 0. By setting all bits high before 693 * currently 0. By setting all bits high before
690 * scanning we make sure that any in-repo cgitrc 694 * scanning we make sure that any in-repo cgitrc
691 * snapshot setting is respected by scan_tree(). 695 * snapshot setting is respected by scan_tree().
692 * BTW: we assume that there'll never be more than 696 * BTW: we assume that there'll never be more than
693 * 255 different snapshot formats supported by cgit... 697 * 255 different snapshot formats supported by cgit...
694 */ 698 */
695 ctx.cfg.snapshots = 0xFF; 699 ctx.cfg.snapshots = 0xFF;
696 scan++; 700 scan++;
697 scan_tree(argv[i] + 12, repo_config); 701 scan_tree(argv[i] + 12, repo_config);
698 } 702 }
699 } 703 }
700 if (scan) { 704 if (scan) {
701 qsort(cgit_repolist.repos, cgit_repolist.count, 705 qsort(cgit_repolist.repos, cgit_repolist.count,
702 sizeof(struct cgit_repo), cmp_repos); 706 sizeof(struct cgit_repo), cmp_repos);
703 print_repolist(stdout, &cgit_repolist, 0); 707 print_repolist(stdout, &cgit_repolist, 0);
704 exit(0); 708 exit(0);
705 } 709 }
706} 710}
707 711
708static int calc_ttl() 712static int calc_ttl()
709{ 713{
710 if (!ctx.repo) 714 if (!ctx.repo)
711 return ctx.cfg.cache_root_ttl; 715 return ctx.cfg.cache_root_ttl;
712 716
713 if (!ctx.qry.page) 717 if (!ctx.qry.page)
714 return ctx.cfg.cache_repo_ttl; 718 return ctx.cfg.cache_repo_ttl;
715 719
716 if (ctx.qry.has_symref) 720 if (ctx.qry.has_symref)
717 return ctx.cfg.cache_dynamic_ttl; 721 return ctx.cfg.cache_dynamic_ttl;
718 722
719 if (ctx.qry.has_sha1) 723 if (ctx.qry.has_sha1)
720 return ctx.cfg.cache_static_ttl; 724 return ctx.cfg.cache_static_ttl;
721 725
722 return ctx.cfg.cache_repo_ttl; 726 return ctx.cfg.cache_repo_ttl;
723} 727}
724 728
725int main(int argc, const char **argv) 729int main(int argc, const char **argv)
726{ 730{
727 const char *path; 731 const char *path;
728 char *qry; 732 char *qry;
729 int err, ttl; 733 int err, ttl;
730 734
731 prepare_context(&ctx); 735 prepare_context(&ctx);
732 cgit_repolist.length = 0; 736 cgit_repolist.length = 0;
733 cgit_repolist.count = 0; 737 cgit_repolist.count = 0;
734 cgit_repolist.repos = NULL; 738 cgit_repolist.repos = NULL;
735 739
736 cgit_parse_args(argc, argv); 740 cgit_parse_args(argc, argv);
737 parse_configfile(expand_macros(ctx.env.cgit_config), config_cb); 741 parse_configfile(expand_macros(ctx.env.cgit_config), config_cb);
738 ctx.repo = NULL; 742 ctx.repo = NULL;
739 http_parse_querystring(ctx.qry.raw, querystring_cb); 743 http_parse_querystring(ctx.qry.raw, querystring_cb);
740 744
741 /* If virtual-root isn't specified in cgitrc, lets pretend 745 /* If virtual-root isn't specified in cgitrc, lets pretend
742 * that virtual-root equals SCRIPT_NAME. 746 * that virtual-root equals SCRIPT_NAME.
743 */ 747 */
744 if (!ctx.cfg.virtual_root) 748 if (!ctx.cfg.virtual_root)
745 ctx.cfg.virtual_root = ctx.cfg.script_name; 749 ctx.cfg.virtual_root = ctx.cfg.script_name;
746 750
747 /* If no url parameter is specified on the querystring, lets 751 /* If no url parameter is specified on the querystring, lets
748 * use PATH_INFO as url. This allows cgit to work with virtual 752 * use PATH_INFO as url. This allows cgit to work with virtual
749 * urls without the need for rewriterules in the webserver (as 753 * urls without the need for rewriterules in the webserver (as
750 * long as PATH_INFO is included in the cache lookup key). 754 * long as PATH_INFO is included in the cache lookup key).
751 */ 755 */
752 path = ctx.env.path_info; 756 path = ctx.env.path_info;
753 if (!ctx.qry.url && path) { 757 if (!ctx.qry.url && path) {
754 if (path[0] == '/') 758 if (path[0] == '/')
755 path++; 759 path++;
756 ctx.qry.url = xstrdup(path); 760 ctx.qry.url = xstrdup(path);
757 if (ctx.qry.raw) { 761 if (ctx.qry.raw) {
758 qry = ctx.qry.raw; 762 qry = ctx.qry.raw;
759 ctx.qry.raw = xstrdup(fmt("%s?%s", path, qry)); 763 ctx.qry.raw = xstrdup(fmt("%s?%s", path, qry));
760 free(qry); 764 free(qry);
761 } else 765 } else
762 ctx.qry.raw = xstrdup(ctx.qry.url); 766 ctx.qry.raw = xstrdup(ctx.qry.url);
763 cgit_parse_url(ctx.qry.url); 767 cgit_parse_url(ctx.qry.url);
764 } 768 }
765 769
766 ttl = calc_ttl(); 770 ttl = calc_ttl();
767 ctx.page.expires += ttl*60; 771 ctx.page.expires += ttl*60;
768 if (ctx.env.request_method && !strcmp(ctx.env.request_method, "HEAD")) 772 if (ctx.env.request_method && !strcmp(ctx.env.request_method, "HEAD"))
769 ctx.cfg.nocache = 1; 773 ctx.cfg.nocache = 1;
770 if (ctx.cfg.nocache) 774 if (ctx.cfg.nocache)
771 ctx.cfg.cache_size = 0; 775 ctx.cfg.cache_size = 0;
772 err = cache_process(ctx.cfg.cache_size, ctx.cfg.cache_root, 776 err = cache_process(ctx.cfg.cache_size, ctx.cfg.cache_root,
773 ctx.qry.raw, ttl, process_request, &ctx); 777 ctx.qry.raw, ttl, process_request, &ctx);
774 if (err) 778 if (err)
775 cgit_print_error(fmt("Error processing page: %s (%d)", 779 cgit_print_error(fmt("Error processing page: %s (%d)",
776 strerror(err), err)); 780 strerror(err), err));
777 return err; 781 return err;
778} 782}
diff --git a/cgit.h b/cgit.h
index f5f68ac..8a9d5fa 100644
--- a/cgit.h
+++ b/cgit.h
@@ -1,318 +1,320 @@
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 char *logo;
74 char *logo_link;
73 int snapshots; 75 int snapshots;
74 int enable_log_filecount; 76 int enable_log_filecount;
75 int enable_log_linecount; 77 int enable_log_linecount;
76 int enable_remote_branches; 78 int enable_remote_branches;
77 int enable_subject_links; 79 int enable_subject_links;
78 int max_stats; 80 int max_stats;
79 time_t mtime; 81 time_t mtime;
80 struct cgit_filter *about_filter; 82 struct cgit_filter *about_filter;
81 struct cgit_filter *commit_filter; 83 struct cgit_filter *commit_filter;
82 struct cgit_filter *source_filter; 84 struct cgit_filter *source_filter;
83}; 85};
84 86
85typedef void (*repo_config_fn)(struct cgit_repo *repo, const char *name, 87typedef void (*repo_config_fn)(struct cgit_repo *repo, const char *name,
86 const char *value); 88 const char *value);
87 89
88struct cgit_repolist { 90struct cgit_repolist {
89 int length; 91 int length;
90 int count; 92 int count;
91 struct cgit_repo *repos; 93 struct cgit_repo *repos;
92}; 94};
93 95
94struct commitinfo { 96struct commitinfo {
95 struct commit *commit; 97 struct commit *commit;
96 char *author; 98 char *author;
97 char *author_email; 99 char *author_email;
98 unsigned long author_date; 100 unsigned long author_date;
99 char *committer; 101 char *committer;
100 char *committer_email; 102 char *committer_email;
101 unsigned long committer_date; 103 unsigned long committer_date;
102 char *subject; 104 char *subject;
103 char *msg; 105 char *msg;
104 char *msg_encoding; 106 char *msg_encoding;
105}; 107};
106 108
107struct taginfo { 109struct taginfo {
108 char *tagger; 110 char *tagger;
109 char *tagger_email; 111 char *tagger_email;
110 unsigned long tagger_date; 112 unsigned long tagger_date;
111 char *msg; 113 char *msg;
112}; 114};
113 115
114struct refinfo { 116struct refinfo {
115 const char *refname; 117 const char *refname;
116 struct object *object; 118 struct object *object;
117 union { 119 union {
118 struct taginfo *tag; 120 struct taginfo *tag;
119 struct commitinfo *commit; 121 struct commitinfo *commit;
120 }; 122 };
121}; 123};
122 124
123struct reflist { 125struct reflist {
124 struct refinfo **refs; 126 struct refinfo **refs;
125 int alloc; 127 int alloc;
126 int count; 128 int count;
127}; 129};
128 130
129struct cgit_query { 131struct cgit_query {
130 int has_symref; 132 int has_symref;
131 int has_sha1; 133 int has_sha1;
132 char *raw; 134 char *raw;
133 char *repo; 135 char *repo;
134 char *page; 136 char *page;
135 char *search; 137 char *search;
136 char *grep; 138 char *grep;
137 char *head; 139 char *head;
138 char *sha1; 140 char *sha1;
139 char *sha2; 141 char *sha2;
140 char *path; 142 char *path;
141 char *name; 143 char *name;
142 char *mimetype; 144 char *mimetype;
143 char *url; 145 char *url;
144 char *period; 146 char *period;
145 int ofs; 147 int ofs;
146 int nohead; 148 int nohead;
147 char *sort; 149 char *sort;
148 int showmsg; 150 int showmsg;
149 int ssdiff; 151 int ssdiff;
150 int show_all; 152 int show_all;
151 int context; 153 int context;
152 int ignorews; 154 int ignorews;
153 char *vpath; 155 char *vpath;
154}; 156};
155 157
156struct cgit_config { 158struct cgit_config {
157 char *agefile; 159 char *agefile;
158 char *cache_root; 160 char *cache_root;
159 char *clone_prefix; 161 char *clone_prefix;
160 char *css; 162 char *css;
161 char *favicon; 163 char *favicon;
162 char *footer; 164 char *footer;
163 char *head_include; 165 char *head_include;
164 char *header; 166 char *header;
165 char *index_header; 167 char *index_header;
166 char *index_info; 168 char *index_info;
167 char *logo; 169 char *logo;
168 char *logo_link; 170 char *logo_link;
169 char *module_link; 171 char *module_link;
170 char *project_list; 172 char *project_list;
171 char *readme; 173 char *readme;
172 char *robots; 174 char *robots;
173 char *root_title; 175 char *root_title;
174 char *root_desc; 176 char *root_desc;
175 char *root_readme; 177 char *root_readme;
176 char *script_name; 178 char *script_name;
177 char *section; 179 char *section;
178 char *virtual_root; 180 char *virtual_root;
179 char *strict_export; 181 char *strict_export;
180 int cache_size; 182 int cache_size;
181 int cache_dynamic_ttl; 183 int cache_dynamic_ttl;
182 int cache_max_create_time; 184 int cache_max_create_time;
183 int cache_repo_ttl; 185 int cache_repo_ttl;
184 int cache_root_ttl; 186 int cache_root_ttl;
185 int cache_scanrc_ttl; 187 int cache_scanrc_ttl;
186 int cache_static_ttl; 188 int cache_static_ttl;
187 int embedded; 189 int embedded;
188 int enable_filter_overrides; 190 int enable_filter_overrides;
189 int enable_gitweb_owner; 191 int enable_gitweb_owner;
190 int enable_index_links; 192 int enable_index_links;
191 int enable_log_filecount; 193 int enable_log_filecount;
192 int enable_log_linecount; 194 int enable_log_linecount;
193 int enable_remote_branches; 195 int enable_remote_branches;
194 int enable_subject_links; 196 int enable_subject_links;
195 int enable_tree_linenumbers; 197 int enable_tree_linenumbers;
196 int local_time; 198 int local_time;
197 int max_atom_items; 199 int max_atom_items;
198 int max_repo_count; 200 int max_repo_count;
199 int max_commit_count; 201 int max_commit_count;
200 int max_lock_attempts; 202 int max_lock_attempts;
201 int max_msg_len; 203 int max_msg_len;
202 int max_repodesc_len; 204 int max_repodesc_len;
203 int max_blob_size; 205 int max_blob_size;
204 int max_stats; 206 int max_stats;
205 int nocache; 207 int nocache;
206 int noplainemail; 208 int noplainemail;
207 int noheader; 209 int noheader;
208 int renamelimit; 210 int renamelimit;
209 int remove_suffix; 211 int remove_suffix;
210 int section_from_path; 212 int section_from_path;
211 int snapshots; 213 int snapshots;
212 int summary_branches; 214 int summary_branches;
213 int summary_log; 215 int summary_log;
214 int summary_tags; 216 int summary_tags;
215 int ssdiff; 217 int ssdiff;
216 struct string_list mimetypes; 218 struct string_list mimetypes;
217 struct cgit_filter *about_filter; 219 struct cgit_filter *about_filter;
218 struct cgit_filter *commit_filter; 220 struct cgit_filter *commit_filter;
219 struct cgit_filter *source_filter; 221 struct cgit_filter *source_filter;
220}; 222};
221 223
222struct cgit_page { 224struct cgit_page {
223 time_t modified; 225 time_t modified;
224 time_t expires; 226 time_t expires;
225 size_t size; 227 size_t size;
226 char *mimetype; 228 char *mimetype;
227 char *charset; 229 char *charset;
228 char *filename; 230 char *filename;
229 char *etag; 231 char *etag;
230 char *title; 232 char *title;
231 int status; 233 int status;
232 char *statusmsg; 234 char *statusmsg;
233}; 235};
234 236
235struct cgit_environment { 237struct cgit_environment {
236 char *cgit_config; 238 char *cgit_config;
237 char *http_host; 239 char *http_host;
238 char *https; 240 char *https;
239 char *no_http; 241 char *no_http;
240 char *path_info; 242 char *path_info;
241 char *query_string; 243 char *query_string;
242 char *request_method; 244 char *request_method;
243 char *script_name; 245 char *script_name;
244 char *server_name; 246 char *server_name;
245 char *server_port; 247 char *server_port;
246}; 248};
247 249
248struct cgit_context { 250struct cgit_context {
249 struct cgit_environment env; 251 struct cgit_environment env;
250 struct cgit_query qry; 252 struct cgit_query qry;
251 struct cgit_config cfg; 253 struct cgit_config cfg;
252 struct cgit_repo *repo; 254 struct cgit_repo *repo;
253 struct cgit_page page; 255 struct cgit_page page;
254}; 256};
255 257
256struct cgit_snapshot_format { 258struct cgit_snapshot_format {
257 const char *suffix; 259 const char *suffix;
258 const char *mimetype; 260 const char *mimetype;
259 write_archive_fn_t write_func; 261 write_archive_fn_t write_func;
260 int bit; 262 int bit;
261}; 263};
262 264
263extern const char *cgit_version; 265extern const char *cgit_version;
264 266
265extern struct cgit_repolist cgit_repolist; 267extern struct cgit_repolist cgit_repolist;
266extern struct cgit_context ctx; 268extern struct cgit_context ctx;
267extern const struct cgit_snapshot_format cgit_snapshot_formats[]; 269extern const struct cgit_snapshot_format cgit_snapshot_formats[];
268 270
269extern struct cgit_repo *cgit_add_repo(const char *url); 271extern struct cgit_repo *cgit_add_repo(const char *url);
270extern struct cgit_repo *cgit_get_repoinfo(const char *url); 272extern struct cgit_repo *cgit_get_repoinfo(const char *url);
271extern void cgit_repo_config_cb(const char *name, const char *value); 273extern void cgit_repo_config_cb(const char *name, const char *value);
272 274
273extern int chk_zero(int result, char *msg); 275extern int chk_zero(int result, char *msg);
274extern int chk_positive(int result, char *msg); 276extern int chk_positive(int result, char *msg);
275extern int chk_non_negative(int result, char *msg); 277extern int chk_non_negative(int result, char *msg);
276 278
277extern char *trim_end(const char *str, char c); 279extern char *trim_end(const char *str, char c);
278extern char *strlpart(char *txt, int maxlen); 280extern char *strlpart(char *txt, int maxlen);
279extern char *strrpart(char *txt, int maxlen); 281extern char *strrpart(char *txt, int maxlen);
280 282
281extern void cgit_add_ref(struct reflist *list, struct refinfo *ref); 283extern void cgit_add_ref(struct reflist *list, struct refinfo *ref);
282extern int cgit_refs_cb(const char *refname, const unsigned char *sha1, 284extern int cgit_refs_cb(const char *refname, const unsigned char *sha1,
283 int flags, void *cb_data); 285 int flags, void *cb_data);
284 286
285extern void *cgit_free_commitinfo(struct commitinfo *info); 287extern void *cgit_free_commitinfo(struct commitinfo *info);
286 288
287extern int cgit_diff_files(const unsigned char *old_sha1, 289extern int cgit_diff_files(const unsigned char *old_sha1,
288 const unsigned char *new_sha1, 290 const unsigned char *new_sha1,
289 unsigned long *old_size, unsigned long *new_size, 291 unsigned long *old_size, unsigned long *new_size,
290 int *binary, int context, int ignorews, 292 int *binary, int context, int ignorews,
291 linediff_fn fn); 293 linediff_fn fn);
292 294
293extern void cgit_diff_tree(const unsigned char *old_sha1, 295extern void cgit_diff_tree(const unsigned char *old_sha1,
294 const unsigned char *new_sha1, 296 const unsigned char *new_sha1,
295 filepair_fn fn, const char *prefix, int ignorews); 297 filepair_fn fn, const char *prefix, int ignorews);
296 298
297extern void cgit_diff_commit(struct commit *commit, filepair_fn fn, 299extern void cgit_diff_commit(struct commit *commit, filepair_fn fn,
298 const char *prefix); 300 const char *prefix);
299 301
300__attribute__((format (printf,1,2))) 302__attribute__((format (printf,1,2)))
301extern char *fmt(const char *format,...); 303extern char *fmt(const char *format,...);
302 304
303extern struct commitinfo *cgit_parse_commit(struct commit *commit); 305extern struct commitinfo *cgit_parse_commit(struct commit *commit);
304extern struct taginfo *cgit_parse_tag(struct tag *tag); 306extern struct taginfo *cgit_parse_tag(struct tag *tag);
305extern void cgit_parse_url(const char *url); 307extern void cgit_parse_url(const char *url);
306 308
307extern const char *cgit_repobasename(const char *reponame); 309extern const char *cgit_repobasename(const char *reponame);
308 310
309extern int cgit_parse_snapshots_mask(const char *str); 311extern int cgit_parse_snapshots_mask(const char *str);
310 312
311extern int cgit_open_filter(struct cgit_filter *filter); 313extern int cgit_open_filter(struct cgit_filter *filter);
312extern int cgit_close_filter(struct cgit_filter *filter); 314extern int cgit_close_filter(struct cgit_filter *filter);
313 315
314extern int readfile(const char *path, char **buf, size_t *size); 316extern int readfile(const char *path, char **buf, size_t *size);
315 317
316extern char *expand_macros(const char *txt); 318extern char *expand_macros(const char *txt);
317 319
318#endif /* CGIT_H */ 320#endif /* CGIT_H */
diff --git a/cgitrc.5.txt b/cgitrc.5.txt
index 8e51ca5..01157a9 100644
--- a/cgitrc.5.txt
+++ b/cgitrc.5.txt
@@ -1,570 +1,579 @@
1:man source: cgit 1:man source: cgit
2:man manual: cgit 2:man manual: cgit
3 3
4CGITRC(5) 4CGITRC(5)
5======== 5========
6 6
7 7
8NAME 8NAME
9---- 9----
10cgitrc - runtime configuration for cgit 10cgitrc - runtime configuration for cgit
11 11
12 12
13SYNOPSIS 13SYNOPSIS
14-------- 14--------
15Cgitrc contains all runtime settings for cgit, including the list of git 15Cgitrc contains all runtime settings for cgit, including the list of git
16repositories, formatted as a line-separated list of NAME=VALUE pairs. Blank 16repositories, formatted as a line-separated list of NAME=VALUE pairs. Blank
17lines, and lines starting with '#', are ignored. 17lines, and lines starting with '#', are ignored.
18 18
19 19
20LOCATION 20LOCATION
21-------- 21--------
22The default location of cgitrc, defined at compile time, is /etc/cgitrc. At 22The default location of cgitrc, defined at compile time, is /etc/cgitrc. At
23runtime, cgit will consult the environment variable CGIT_CONFIG and, if 23runtime, cgit will consult the environment variable CGIT_CONFIG and, if
24defined, use its value instead. 24defined, use its value instead.
25 25
26 26
27GLOBAL SETTINGS 27GLOBAL SETTINGS
28--------------- 28---------------
29about-filter:: 29about-filter::
30 Specifies a command which will be invoked to format the content of 30 Specifies a command which will be invoked to format the content of
31 about pages (both top-level and for each repository). The command will 31 about pages (both top-level and for each repository). The command will
32 get the content of the about-file on its STDIN, and the STDOUT from the 32 get the content of the about-file on its STDIN, and the STDOUT from the
33 command will be included verbatim on the about page. Default value: 33 command will be included verbatim on the about page. Default value:
34 none. 34 none.
35 35
36agefile:: 36agefile::
37 Specifies a path, relative to each repository path, which can be used 37 Specifies a path, relative to each repository path, which can be used
38 to specify the date and time of the youngest commit in the repository. 38 to specify the date and time of the youngest commit in the repository.
39 The first line in the file is used as input to the "parse_date" 39 The first line in the file is used as input to the "parse_date"
40 function in libgit. Recommended timestamp-format is "yyyy-mm-dd 40 function in libgit. Recommended timestamp-format is "yyyy-mm-dd
41 hh:mm:ss". Default value: "info/web/last-modified". 41 hh:mm:ss". Default value: "info/web/last-modified".
42 42
43cache-root:: 43cache-root::
44 Path used to store the cgit cache entries. Default value: 44 Path used to store the cgit cache entries. Default value:
45 "/var/cache/cgit". 45 "/var/cache/cgit".
46 46
47cache-dynamic-ttl:: 47cache-dynamic-ttl::
48 Number which specifies the time-to-live, in minutes, for the cached 48 Number which specifies the time-to-live, in minutes, for the cached
49 version of repository pages accessed without a fixed SHA1. Default 49 version of repository pages accessed without a fixed SHA1. Default
50 value: "5". 50 value: "5".
51 51
52cache-repo-ttl:: 52cache-repo-ttl::
53 Number which specifies the time-to-live, in minutes, for the cached 53 Number which specifies the time-to-live, in minutes, for the cached
54 version of the repository summary page. Default value: "5". 54 version of the repository summary page. Default value: "5".
55 55
56cache-root-ttl:: 56cache-root-ttl::
57 Number which specifies the time-to-live, in minutes, for the cached 57 Number which specifies the time-to-live, in minutes, for the cached
58 version of the repository index page. Default value: "5". 58 version of the repository index page. Default value: "5".
59 59
60cache-scanrc-ttl:: 60cache-scanrc-ttl::
61 Number which specifies the time-to-live, in minutes, for the result 61 Number which specifies the time-to-live, in minutes, for the result
62 of scanning a path for git repositories. Default value: "15". 62 of scanning a path for git repositories. Default value: "15".
63 63
64cache-size:: 64cache-size::
65 The maximum number of entries in the cgit cache. Default value: "0" 65 The maximum number of entries in the cgit cache. Default value: "0"
66 (i.e. caching is disabled). 66 (i.e. caching is disabled).
67 67
68cache-static-ttl:: 68cache-static-ttl::
69 Number which specifies the time-to-live, in minutes, for the cached 69 Number which specifies the time-to-live, in minutes, for the cached
70 version of repository pages accessed with a fixed SHA1. Default value: 70 version of repository pages accessed with a fixed SHA1. Default value:
71 "5". 71 "5".
72 72
73clone-prefix:: 73clone-prefix::
74 Space-separated list of common prefixes which, when combined with a 74 Space-separated list of common prefixes which, when combined with a
75 repository url, generates valid clone urls for the repository. This 75 repository url, generates valid clone urls for the repository. This
76 setting is only used if `repo.clone-url` is unspecified. Default value: 76 setting is only used if `repo.clone-url` is unspecified. Default value:
77 none. 77 none.
78 78
79commit-filter:: 79commit-filter::
80 Specifies a command which will be invoked to format commit messages. 80 Specifies a command which will be invoked to format commit messages.
81 The command will get the message on its STDIN, and the STDOUT from the 81 The command will get the message on its STDIN, and the STDOUT from the
82 command will be included verbatim as the commit message, i.e. this can 82 command will be included verbatim as the commit message, i.e. this can
83 be used to implement bugtracker integration. Default value: none. 83 be used to implement bugtracker integration. Default value: none.
84 84
85css:: 85css::
86 Url which specifies the css document to include in all cgit pages. 86 Url which specifies the css document to include in all cgit pages.
87 Default value: "/cgit.css". 87 Default value: "/cgit.css".
88 88
89embedded:: 89embedded::
90 Flag which, when set to "1", will make cgit generate a html fragment 90 Flag which, when set to "1", will make cgit generate a html fragment
91 suitable for embedding in other html pages. Default value: none. See 91 suitable for embedding in other html pages. Default value: none. See
92 also: "noheader". 92 also: "noheader".
93 93
94enable-filter-overrides:: 94enable-filter-overrides::
95 Flag which, when set to "1", allows all filter settings to be 95 Flag which, when set to "1", allows all filter settings to be
96 overridden in repository-specific cgitrc files. Default value: none. 96 overridden in repository-specific cgitrc files. Default value: none.
97 97
98enable-gitweb-owner:: 98enable-gitweb-owner::
99 If set to "1" and scan-path is enabled, we first check each repository 99 If set to "1" and scan-path is enabled, we first check each repository
100 for the git config value "gitweb.owner" to determine the owner. 100 for the git config value "gitweb.owner" to determine the owner.
101 Default value: "1". See also: scan-path. 101 Default value: "1". See also: scan-path.
102 102
103enable-index-links:: 103enable-index-links::
104 Flag which, when set to "1", will make cgit generate extra links for 104 Flag which, when set to "1", will make cgit generate extra links for
105 each repo in the repository index (specifically, to the "summary", 105 each repo in the repository index (specifically, to the "summary",
106 "commit" and "tree" pages). Default value: "0". 106 "commit" and "tree" pages). Default value: "0".
107 107
108enable-log-filecount:: 108enable-log-filecount::
109 Flag which, when set to "1", will make cgit print the number of 109 Flag which, when set to "1", will make cgit print the number of
110 modified files for each commit on the repository log page. Default 110 modified files for each commit on the repository log page. Default
111 value: "0". 111 value: "0".
112 112
113enable-log-linecount:: 113enable-log-linecount::
114 Flag which, when set to "1", will make cgit print the number of added 114 Flag which, when set to "1", will make cgit print the number of added
115 and removed lines for each commit on the repository log page. Default 115 and removed lines for each commit on the repository log page. Default
116 value: "0". 116 value: "0".
117 117
118enable-remote-branches:: 118enable-remote-branches::
119 Flag which, when set to "1", will make cgit display remote branches 119 Flag which, when set to "1", will make cgit display remote branches
120 in the summary and refs views. Default value: "0". See also: 120 in the summary and refs views. Default value: "0". See also:
121 "repo.enable-remote-branches". 121 "repo.enable-remote-branches".
122 122
123enable-subject-links:: 123enable-subject-links::
124 Flag which, when set to "1", will make cgit use the subject of the 124 Flag which, when set to "1", will make cgit use the subject of the
125 parent commit as link text when generating links to parent commits 125 parent commit as link text when generating links to parent commits
126 in commit view. Default value: "0". See also: 126 in commit view. Default value: "0". See also:
127 "repo.enable-subject-links". 127 "repo.enable-subject-links".
128 128
129enable-tree-linenumbers:: 129enable-tree-linenumbers::
130 Flag which, when set to "1", will make cgit generate linenumber links 130 Flag which, when set to "1", will make cgit generate linenumber links
131 for plaintext blobs printed in the tree view. Default value: "1". 131 for plaintext blobs printed in the tree view. Default value: "1".
132 132
133favicon:: 133favicon::
134 Url used as link to a shortcut icon for cgit. If specified, it is 134 Url used as link to a shortcut icon for cgit. If specified, it is
135 suggested to use the value "/favicon.ico" since certain browsers will 135 suggested to use the value "/favicon.ico" since certain browsers will
136 ignore other values. Default value: none. 136 ignore other values. Default value: none.
137 137
138footer:: 138footer::
139 The content of the file specified with this option will be included 139 The content of the file specified with this option will be included
140 verbatim at the bottom of all pages (i.e. it replaces the standard 140 verbatim at the bottom of all pages (i.e. it replaces the standard
141 "generated by..." message. Default value: none. 141 "generated by..." message. Default value: none.
142 142
143head-include:: 143head-include::
144 The content of the file specified with this option will be included 144 The content of the file specified with this option will be included
145 verbatim in the html HEAD section on all pages. Default value: none. 145 verbatim in the html HEAD section on all pages. Default value: none.
146 146
147header:: 147header::
148 The content of the file specified with this option will be included 148 The content of the file specified with this option will be included
149 verbatim at the top of all pages. Default value: none. 149 verbatim at the top of all pages. Default value: none.
150 150
151include:: 151include::
152 Name of a configfile to include before the rest of the current config- 152 Name of a configfile to include before the rest of the current config-
153 file is parsed. Default value: none. 153 file is parsed. Default value: none.
154 154
155index-header:: 155index-header::
156 The content of the file specified with this option will be included 156 The content of the file specified with this option will be included
157 verbatim above the repository index. This setting is deprecated, and 157 verbatim above the repository index. This setting is deprecated, and
158 will not be supported by cgit-1.0 (use root-readme instead). Default 158 will not be supported by cgit-1.0 (use root-readme instead). Default
159 value: none. 159 value: none.
160 160
161index-info:: 161index-info::
162 The content of the file specified with this option will be included 162 The content of the file specified with this option will be included
163 verbatim below the heading on the repository index page. This setting 163 verbatim below the heading on the repository index page. This setting
164 is deprecated, and will not be supported by cgit-1.0 (use root-desc 164 is deprecated, and will not be supported by cgit-1.0 (use root-desc
165 instead). Default value: none. 165 instead). Default value: none.
166 166
167local-time:: 167local-time::
168 Flag which, if set to "1", makes cgit print commit and tag times in the 168 Flag which, if set to "1", makes cgit print commit and tag times in the
169 servers timezone. Default value: "0". 169 servers timezone. Default value: "0".
170 170
171logo:: 171logo::
172 Url which specifies the source of an image which will be used as a logo 172 Url which specifies the source of an image which will be used as a logo
173 on all cgit pages. Default value: "/cgit.png". 173 on all cgit pages. Default value: "/cgit.png".
174 174
175logo-link:: 175logo-link::
176 Url loaded when clicking on the cgit logo image. If unspecified the 176 Url loaded when clicking on the cgit logo image. If unspecified the
177 calculated url of the repository index page will be used. Default 177 calculated url of the repository index page will be used. Default
178 value: none. 178 value: none.
179 179
180max-atom-items:: 180max-atom-items::
181 Specifies the number of items to display in atom feeds view. Default 181 Specifies the number of items to display in atom feeds view. Default
182 value: "10". 182 value: "10".
183 183
184max-commit-count:: 184max-commit-count::
185 Specifies the number of entries to list per page in "log" view. Default 185 Specifies the number of entries to list per page in "log" view. Default
186 value: "50". 186 value: "50".
187 187
188max-message-length:: 188max-message-length::
189 Specifies the maximum number of commit message characters to display in 189 Specifies the maximum number of commit message characters to display in
190 "log" view. Default value: "80". 190 "log" view. Default value: "80".
191 191
192max-repo-count:: 192max-repo-count::
193 Specifies the number of entries to list per page on therepository 193 Specifies the number of entries to list per page on therepository
194 index page. Default value: "50". 194 index page. Default value: "50".
195 195
196max-repodesc-length:: 196max-repodesc-length::
197 Specifies the maximum number of repo description characters to display 197 Specifies the maximum number of repo description characters to display
198 on the repository index page. Default value: "80". 198 on the repository index page. Default value: "80".
199 199
200max-blob-size:: 200max-blob-size::
201 Specifies the maximum size of a blob to display HTML for in KBytes. 201 Specifies the maximum size of a blob to display HTML for in KBytes.
202 Default value: "0" (limit disabled). 202 Default value: "0" (limit disabled).
203 203
204max-stats:: 204max-stats::
205 Set the default maximum statistics period. Valid values are "week", 205 Set the default maximum statistics period. Valid values are "week",
206 "month", "quarter" and "year". If unspecified, statistics are 206 "month", "quarter" and "year". If unspecified, statistics are
207 disabled. Default value: none. See also: "repo.max-stats". 207 disabled. Default value: none. See also: "repo.max-stats".
208 208
209mimetype.<ext>:: 209mimetype.<ext>::
210 Set the mimetype for the specified filename extension. This is used 210 Set the mimetype for the specified filename extension. This is used
211 by the `plain` command when returning blob content. 211 by the `plain` command when returning blob content.
212 212
213module-link:: 213module-link::
214 Text which will be used as the formatstring for a hyperlink when a 214 Text which will be used as the formatstring for a hyperlink when a
215 submodule is printed in a directory listing. The arguments for the 215 submodule is printed in a directory listing. The arguments for the
216 formatstring are the path and SHA1 of the submodule commit. Default 216 formatstring are the path and SHA1 of the submodule commit. Default
217 value: "./?repo=%s&page=commit&id=%s" 217 value: "./?repo=%s&page=commit&id=%s"
218 218
219nocache:: 219nocache::
220 If set to the value "1" caching will be disabled. This settings is 220 If set to the value "1" caching will be disabled. This settings is
221 deprecated, and will not be honored starting with cgit-1.0. Default 221 deprecated, and will not be honored starting with cgit-1.0. Default
222 value: "0". 222 value: "0".
223 223
224noplainemail:: 224noplainemail::
225 If set to "1" showing full author email adresses will be disabled. 225 If set to "1" showing full author email adresses will be disabled.
226 Default value: "0". 226 Default value: "0".
227 227
228noheader:: 228noheader::
229 Flag which, when set to "1", will make cgit omit the standard header 229 Flag which, when set to "1", will make cgit omit the standard header
230 on all pages. Default value: none. See also: "embedded". 230 on all pages. Default value: none. See also: "embedded".
231 231
232project-list:: 232project-list::
233 A list of subdirectories inside of scan-path, relative to it, that 233 A list of subdirectories inside of scan-path, relative to it, that
234 should loaded as git repositories. This must be defined prior to 234 should loaded as git repositories. This must be defined prior to
235 scan-path. Default value: none. See also: scan-path. 235 scan-path. Default value: none. See also: scan-path.
236 236
237readme:: 237readme::
238 Text which will be used as default value for "repo.readme". Default 238 Text which will be used as default value for "repo.readme". Default
239 value: none. 239 value: none.
240 240
241remove-suffix:: 241remove-suffix::
242 If set to "1" and scan-path is enabled, if any repositories are found 242 If set to "1" and scan-path is enabled, if any repositories are found
243 with a suffix of ".git", this suffix will be removed for the url and 243 with a suffix of ".git", this suffix will be removed for the url and
244 name. Default value: "0". See also: scan-path. 244 name. Default value: "0". See also: scan-path.
245 245
246renamelimit:: 246renamelimit::
247 Maximum number of files to consider when detecting renames. The value 247 Maximum number of files to consider when detecting renames. The value
248 "-1" uses the compiletime value in git (for further info, look at 248 "-1" uses the compiletime value in git (for further info, look at
249 `man git-diff`). Default value: "-1". 249 `man git-diff`). Default value: "-1".
250 250
251repo.group:: 251repo.group::
252 Legacy alias for "section". This option is deprecated and will not be 252 Legacy alias for "section". This option is deprecated and will not be
253 supported in cgit-1.0. 253 supported in cgit-1.0.
254 254
255robots:: 255robots::
256 Text used as content for the "robots" meta-tag. Default value: 256 Text used as content for the "robots" meta-tag. Default value:
257 "index, nofollow". 257 "index, nofollow".
258 258
259root-desc:: 259root-desc::
260 Text printed below the heading on the repository index page. Default 260 Text printed below the heading on the repository index page. Default
261 value: "a fast webinterface for the git dscm". 261 value: "a fast webinterface for the git dscm".
262 262
263root-readme:: 263root-readme::
264 The content of the file specified with this option will be included 264 The content of the file specified with this option will be included
265 verbatim below the "about" link on the repository index page. Default 265 verbatim below the "about" link on the repository index page. Default
266 value: none. 266 value: none.
267 267
268root-title:: 268root-title::
269 Text printed as heading on the repository index page. Default value: 269 Text printed as heading on the repository index page. Default value:
270 "Git Repository Browser". 270 "Git Repository Browser".
271 271
272scan-path:: 272scan-path::
273 A path which will be scanned for repositories. If caching is enabled, 273 A path which will be scanned for repositories. If caching is enabled,
274 the result will be cached as a cgitrc include-file in the cache 274 the result will be cached as a cgitrc include-file in the cache
275 directory. If project-list has been defined prior to scan-path, 275 directory. If project-list has been defined prior to scan-path,
276 scan-path loads only the directories listed in the file pointed to by 276 scan-path loads only the directories listed in the file pointed to by
277 project-list. Default value: none. See also: cache-scanrc-ttl, 277 project-list. Default value: none. See also: cache-scanrc-ttl,
278 project-list. 278 project-list.
279 279
280section:: 280section::
281 The name of the current repository section - all repositories defined 281 The name of the current repository section - all repositories defined
282 after this option will inherit the current section name. Default value: 282 after this option will inherit the current section name. Default value:
283 none. 283 none.
284 284
285section-from-path:: 285section-from-path::
286 A number which, if specified before scan-path, specifies how many 286 A number which, if specified before scan-path, specifies how many
287 path elements from each repo path to use as a default section name. 287 path elements from each repo path to use as a default section name.
288 If negative, cgit will discard the specified number of path elements 288 If negative, cgit will discard the specified number of path elements
289 above the repo directory. Default value: 0. 289 above the repo directory. Default value: 0.
290 290
291side-by-side-diffs:: 291side-by-side-diffs::
292 If set to "1" shows side-by-side diffs instead of unidiffs per 292 If set to "1" shows side-by-side diffs instead of unidiffs per
293 default. Default value: "0". 293 default. Default value: "0".
294 294
295snapshots:: 295snapshots::
296 Text which specifies the default set of snapshot formats generated by 296 Text which specifies the default set of snapshot formats generated by
297 cgit. The value is a space-separated list of zero or more of the 297 cgit. The value is a space-separated list of zero or more of the
298 values "tar", "tar.gz", "tar.bz2" and "zip". Default value: none. 298 values "tar", "tar.gz", "tar.bz2" and "zip". Default value: none.
299 299
300source-filter:: 300source-filter::
301 Specifies a command which will be invoked to format plaintext blobs 301 Specifies a command which will be invoked to format plaintext blobs
302 in the tree view. The command will get the blob content on its STDIN 302 in the tree view. The command will get the blob content on its STDIN
303 and the name of the blob as its only command line argument. The STDOUT 303 and the name of the blob as its only command line argument. The STDOUT
304 from the command will be included verbatim as the blob contents, i.e. 304 from the command will be included verbatim as the blob contents, i.e.
305 this can be used to implement e.g. syntax highlighting. Default value: 305 this can be used to implement e.g. syntax highlighting. Default value:
306 none. 306 none.
307 307
308summary-branches:: 308summary-branches::
309 Specifies the number of branches to display in the repository "summary" 309 Specifies the number of branches to display in the repository "summary"
310 view. Default value: "10". 310 view. Default value: "10".
311 311
312summary-log:: 312summary-log::
313 Specifies the number of log entries to display in the repository 313 Specifies the number of log entries to display in the repository
314 "summary" view. Default value: "10". 314 "summary" view. Default value: "10".
315 315
316summary-tags:: 316summary-tags::
317 Specifies the number of tags to display in the repository "summary" 317 Specifies the number of tags to display in the repository "summary"
318 view. Default value: "10". 318 view. Default value: "10".
319 319
320strict-export:: 320strict-export::
321 Filename which, if specified, needs to be present within the repository 321 Filename which, if specified, needs to be present within the repository
322 for cgit to allow access to that repository. This can be used to emulate 322 for cgit to allow access to that repository. This can be used to emulate
323 gitweb's EXPORT_OK and STRICT_EXPORT functionality and limit cgit's 323 gitweb's EXPORT_OK and STRICT_EXPORT functionality and limit cgit's
324 repositories to match those exported by git-daemon. This option MUST come 324 repositories to match those exported by git-daemon. This option MUST come
325 before 'scan-path'. 325 before 'scan-path'.
326 326
327virtual-root:: 327virtual-root::
328 Url which, if specified, will be used as root for all cgit links. It 328 Url which, if specified, will be used as root for all cgit links. It
329 will also cause cgit to generate 'virtual urls', i.e. urls like 329 will also cause cgit to generate 'virtual urls', i.e. urls like
330 '/cgit/tree/README' as opposed to '?r=cgit&p=tree&path=README'. Default 330 '/cgit/tree/README' as opposed to '?r=cgit&p=tree&path=README'. Default
331 value: none. 331 value: none.
332 NOTE: cgit has recently learned how to use PATH_INFO to achieve the 332 NOTE: cgit has recently learned how to use PATH_INFO to achieve the
333 same kind of virtual urls, so this option will probably be deprecated. 333 same kind of virtual urls, so this option will probably be deprecated.
334 334
335REPOSITORY SETTINGS 335REPOSITORY SETTINGS
336------------------- 336-------------------
337repo.about-filter:: 337repo.about-filter::
338 Override the default about-filter. Default value: none. See also: 338 Override the default about-filter. Default value: none. See also:
339 "enable-filter-overrides". 339 "enable-filter-overrides".
340 340
341repo.clone-url:: 341repo.clone-url::
342 A list of space-separated urls which can be used to clone this repo. 342 A list of space-separated urls which can be used to clone this repo.
343 Default value: none. 343 Default value: none.
344 344
345repo.commit-filter:: 345repo.commit-filter::
346 Override the default commit-filter. Default value: none. See also: 346 Override the default commit-filter. Default value: none. See also:
347 "enable-filter-overrides". 347 "enable-filter-overrides".
348 348
349repo.defbranch:: 349repo.defbranch::
350 The name of the default branch for this repository. If no such branch 350 The name of the default branch for this repository. If no such branch
351 exists in the repository, the first branch name (when sorted) is used 351 exists in the repository, the first branch name (when sorted) is used
352 as default instead. Default value: "master". 352 as default instead. Default value: "master".
353 353
354repo.desc:: 354repo.desc::
355 The value to show as repository description. Default value: none. 355 The value to show as repository description. Default value: none.
356 356
357repo.enable-log-filecount:: 357repo.enable-log-filecount::
358 A flag which can be used to disable the global setting 358 A flag which can be used to disable the global setting
359 `enable-log-filecount'. Default value: none. 359 `enable-log-filecount'. Default value: none.
360 360
361repo.enable-log-linecount:: 361repo.enable-log-linecount::
362 A flag which can be used to disable the global setting 362 A flag which can be used to disable the global setting
363 `enable-log-linecount'. Default value: none. 363 `enable-log-linecount'. Default value: none.
364 364
365repo.enable-remote-branches:: 365repo.enable-remote-branches::
366 Flag which, when set to "1", will make cgit display remote branches 366 Flag which, when set to "1", will make cgit display remote branches
367 in the summary and refs views. Default value: <enable-remote-branches>. 367 in the summary and refs views. Default value: <enable-remote-branches>.
368 368
369repo.enable-subject-links:: 369repo.enable-subject-links::
370 A flag which can be used to override the global setting 370 A flag which can be used to override the global setting
371 `enable-subject-links'. Default value: none. 371 `enable-subject-links'. Default value: none.
372 372
373repo.logo::
374 Url which specifies the source of an image which will be used as a logo
375 on this repo's pages. Default value: global logo.
376
377repo.logo-link::
378 Url loaded when clicking on the cgit logo image. If unspecified the
379 calculated url of the repository index page will be used. Default
380 value: global logo-link.
381
373repo.max-stats:: 382repo.max-stats::
374 Override the default maximum statistics period. Valid values are equal 383 Override the default maximum statistics period. Valid values are equal
375 to the values specified for the global "max-stats" setting. Default 384 to the values specified for the global "max-stats" setting. Default
376 value: none. 385 value: none.
377 386
378repo.name:: 387repo.name::
379 The value to show as repository name. Default value: <repo.url>. 388 The value to show as repository name. Default value: <repo.url>.
380 389
381repo.owner:: 390repo.owner::
382 A value used to identify the owner of the repository. Default value: 391 A value used to identify the owner of the repository. Default value:
383 none. 392 none.
384 393
385repo.path:: 394repo.path::
386 An absolute path to the repository directory. For non-bare repositories 395 An absolute path to the repository directory. For non-bare repositories
387 this is the .git-directory. Default value: none. 396 this is the .git-directory. Default value: none.
388 397
389repo.readme:: 398repo.readme::
390 A path (relative to <repo.path>) which specifies a file to include 399 A path (relative to <repo.path>) which specifies a file to include
391 verbatim as the "About" page for this repo. You may also specify a 400 verbatim as the "About" page for this repo. You may also specify a
392 git refspec by head or by hash by prepending the refspec followed by 401 git refspec by head or by hash by prepending the refspec followed by
393 a colon. For example, "master:docs/readme.mkd" Default value: <readme>. 402 a colon. For example, "master:docs/readme.mkd" Default value: <readme>.
394 403
395repo.snapshots:: 404repo.snapshots::
396 A mask of allowed snapshot-formats for this repo, restricted by the 405 A mask of allowed snapshot-formats for this repo, restricted by the
397 "snapshots" global setting. Default value: <snapshots>. 406 "snapshots" global setting. Default value: <snapshots>.
398 407
399repo.section:: 408repo.section::
400 Override the current section name for this repository. Default value: 409 Override the current section name for this repository. Default value:
401 none. 410 none.
402 411
403repo.source-filter:: 412repo.source-filter::
404 Override the default source-filter. Default value: none. See also: 413 Override the default source-filter. Default value: none. See also:
405 "enable-filter-overrides". 414 "enable-filter-overrides".
406 415
407repo.url:: 416repo.url::
408 The relative url used to access the repository. This must be the first 417 The relative url used to access the repository. This must be the first
409 setting specified for each repo. Default value: none. 418 setting specified for each repo. Default value: none.
410 419
411 420
412REPOSITORY-SPECIFIC CGITRC FILE 421REPOSITORY-SPECIFIC CGITRC FILE
413------------------------------- 422-------------------------------
414When the option "scan-path" is used to auto-discover git repositories, cgit 423When the option "scan-path" is used to auto-discover git repositories, cgit
415will try to parse the file "cgitrc" within any found repository. Such a 424will try to parse the file "cgitrc" within any found repository. Such a
416repo-specific config file may contain any of the repo-specific options 425repo-specific config file may contain any of the repo-specific options
417described above, except "repo.url" and "repo.path". Additionally, the "filter" 426described above, except "repo.url" and "repo.path". Additionally, the "filter"
418options are only acknowledged in repo-specific config files when 427options are only acknowledged in repo-specific config files when
419"enable-filter-overrides" is set to "1". 428"enable-filter-overrides" is set to "1".
420 429
421Note: the "repo." prefix is dropped from the option names in repo-specific 430Note: the "repo." prefix is dropped from the option names in repo-specific
422config files, e.g. "repo.desc" becomes "desc". 431config files, e.g. "repo.desc" becomes "desc".
423 432
424 433
425EXAMPLE CGITRC FILE 434EXAMPLE CGITRC FILE
426------------------- 435-------------------
427 436
428.... 437....
429# Enable caching of up to 1000 output entriess 438# Enable caching of up to 1000 output entriess
430cache-size=1000 439cache-size=1000
431 440
432 441
433# Specify some default clone prefixes 442# Specify some default clone prefixes
434clone-prefix=git://example.com ssh://example.com/pub/git http://example.com/git 443clone-prefix=git://example.com ssh://example.com/pub/git http://example.com/git
435 444
436# Specify the css url 445# Specify the css url
437css=/css/cgit.css 446css=/css/cgit.css
438 447
439 448
440# Show extra links for each repository on the index page 449# Show extra links for each repository on the index page
441enable-index-links=1 450enable-index-links=1
442 451
443 452
444# Show number of affected files per commit on the log pages 453# Show number of affected files per commit on the log pages
445enable-log-filecount=1 454enable-log-filecount=1
446 455
447 456
448# Show number of added/removed lines per commit on the log pages 457# Show number of added/removed lines per commit on the log pages
449enable-log-linecount=1 458enable-log-linecount=1
450 459
451 460
452# Add a cgit favicon 461# Add a cgit favicon
453favicon=/favicon.ico 462favicon=/favicon.ico
454 463
455 464
456# Use a custom logo 465# Use a custom logo
457logo=/img/mylogo.png 466logo=/img/mylogo.png
458 467
459 468
460# Enable statistics per week, month and quarter 469# Enable statistics per week, month and quarter
461max-stats=quarter 470max-stats=quarter
462 471
463 472
464# Set the title and heading of the repository index page 473# Set the title and heading of the repository index page
465root-title=example.com git repositories 474root-title=example.com git repositories
466 475
467 476
468# Set a subheading for the repository index page 477# Set a subheading for the repository index page
469root-desc=tracking the foobar development 478root-desc=tracking the foobar development
470 479
471 480
472# Include some more info about example.com on the index page 481# Include some more info about example.com on the index page
473root-readme=/var/www/htdocs/about.html 482root-readme=/var/www/htdocs/about.html
474 483
475 484
476# Allow download of tar.gz, tar.bz2 and zip-files 485# Allow download of tar.gz, tar.bz2 and zip-files
477snapshots=tar.gz tar.bz2 zip 486snapshots=tar.gz tar.bz2 zip
478 487
479 488
480## 489##
481## List of common mimetypes 490## List of common mimetypes
482## 491##
483 492
484mimetype.gif=image/gif 493mimetype.gif=image/gif
485mimetype.html=text/html 494mimetype.html=text/html
486mimetype.jpg=image/jpeg 495mimetype.jpg=image/jpeg
487mimetype.jpeg=image/jpeg 496mimetype.jpeg=image/jpeg
488mimetype.pdf=application/pdf 497mimetype.pdf=application/pdf
489mimetype.png=image/png 498mimetype.png=image/png
490mimetype.svg=image/svg+xml 499mimetype.svg=image/svg+xml
491 500
492 501
493## 502##
494## List of repositories. 503## List of repositories.
495## PS: Any repositories listed when section is unset will not be 504## PS: Any repositories listed when section is unset will not be
496## displayed under a section heading 505## displayed under a section heading
497## PPS: This list could be kept in a different file (e.g. '/etc/cgitrepos') 506## PPS: This list could be kept in a different file (e.g. '/etc/cgitrepos')
498## and included like this: 507## and included like this:
499## include=/etc/cgitrepos 508## include=/etc/cgitrepos
500## 509##
501 510
502 511
503repo.url=foo 512repo.url=foo
504repo.path=/pub/git/foo.git 513repo.path=/pub/git/foo.git
505repo.desc=the master foo repository 514repo.desc=the master foo repository
506repo.owner=fooman@example.com 515repo.owner=fooman@example.com
507repo.readme=info/web/about.html 516repo.readme=info/web/about.html
508 517
509 518
510repo.url=bar 519repo.url=bar
511repo.path=/pub/git/bar.git 520repo.path=/pub/git/bar.git
512repo.desc=the bars for your foo 521repo.desc=the bars for your foo
513repo.owner=barman@example.com 522repo.owner=barman@example.com
514repo.readme=info/web/about.html 523repo.readme=info/web/about.html
515 524
516 525
517# The next repositories will be displayed under the 'extras' heading 526# The next repositories will be displayed under the 'extras' heading
518section=extras 527section=extras
519 528
520 529
521repo.url=baz 530repo.url=baz
522repo.path=/pub/git/baz.git 531repo.path=/pub/git/baz.git
523repo.desc=a set of extensions for bar users 532repo.desc=a set of extensions for bar users
524 533
525repo.url=wiz 534repo.url=wiz
526repo.path=/pub/git/wiz.git 535repo.path=/pub/git/wiz.git
527repo.desc=the wizard of foo 536repo.desc=the wizard of foo
528 537
529 538
530# Add some mirrored repositories 539# Add some mirrored repositories
531section=mirrors 540section=mirrors
532 541
533 542
534repo.url=git 543repo.url=git
535repo.path=/pub/git/git.git 544repo.path=/pub/git/git.git
536repo.desc=the dscm 545repo.desc=the dscm
537 546
538 547
539repo.url=linux 548repo.url=linux
540repo.path=/pub/git/linux.git 549repo.path=/pub/git/linux.git
541repo.desc=the kernel 550repo.desc=the kernel
542 551
543# Disable adhoc downloads of this repo 552# Disable adhoc downloads of this repo
544repo.snapshots=0 553repo.snapshots=0
545 554
546# Disable line-counts for this repo 555# Disable line-counts for this repo
547repo.enable-log-linecount=0 556repo.enable-log-linecount=0
548 557
549# Restrict the max statistics period for this repo 558# Restrict the max statistics period for this repo
550repo.max-stats=month 559repo.max-stats=month
551.... 560....
552 561
553 562
554BUGS 563BUGS
555---- 564----
556Comments currently cannot appear on the same line as a setting; the comment 565Comments currently cannot appear on the same line as a setting; the comment
557will be included as part of the value. E.g. this line: 566will be included as part of the value. E.g. this line:
558 567
559 robots=index # allow indexing 568 robots=index # allow indexing
560 569
561will generate the following html element: 570will generate the following html element:
562 571
563 <meta name='robots' content='index # allow indexing'/> 572 <meta name='robots' content='index # allow indexing'/>
564 573
565 574
566 575
567AUTHOR 576AUTHOR
568------ 577------
569Lars Hjemli <hjemli@gmail.com> 578Lars Hjemli <hjemli@gmail.com>
570Jason A. Donenfeld <Jason@zx2c4.com> 579Jason A. Donenfeld <Jason@zx2c4.com>
diff --git a/ui-shared.c b/ui-shared.c
index ae29615..7efae7a 100644
--- a/ui-shared.c
+++ b/ui-shared.c
@@ -1,909 +1,919 @@
1/* ui-shared.c: common web output functions 1/* ui-shared.c: common web output 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#include "cmd.h" 10#include "cmd.h"
11#include "html.h" 11#include "html.h"
12 12
13const char cgit_doctype[] = 13const char cgit_doctype[] =
14"<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\"\n" 14"<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\"\n"
15" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n"; 15" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n";
16 16
17static char *http_date(time_t t) 17static char *http_date(time_t t)
18{ 18{
19 static char day[][4] = 19 static char day[][4] =
20 {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"}; 20 {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"};
21 static char month[][4] = 21 static char month[][4] =
22 {"Jan", "Feb", "Mar", "Apr", "May", "Jun", 22 {"Jan", "Feb", "Mar", "Apr", "May", "Jun",
23 "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"}; 23 "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"};
24 struct tm *tm = gmtime(&t); 24 struct tm *tm = gmtime(&t);
25 return fmt("%s, %02d %s %04d %02d:%02d:%02d GMT", day[tm->tm_wday], 25 return fmt("%s, %02d %s %04d %02d:%02d:%02d GMT", day[tm->tm_wday],
26 tm->tm_mday, month[tm->tm_mon], 1900+tm->tm_year, 26 tm->tm_mday, month[tm->tm_mon], 1900+tm->tm_year,
27 tm->tm_hour, tm->tm_min, tm->tm_sec); 27 tm->tm_hour, tm->tm_min, tm->tm_sec);
28} 28}
29 29
30void cgit_print_error(const char *msg) 30void cgit_print_error(const char *msg)
31{ 31{
32 html("<div class='error'>"); 32 html("<div class='error'>");
33 html_txt(msg); 33 html_txt(msg);
34 html("</div>\n"); 34 html("</div>\n");
35} 35}
36 36
37char *cgit_httpscheme() 37char *cgit_httpscheme()
38{ 38{
39 if (ctx.env.https && !strcmp(ctx.env.https, "on")) 39 if (ctx.env.https && !strcmp(ctx.env.https, "on"))
40 return "https://"; 40 return "https://";
41 else 41 else
42 return "http://"; 42 return "http://";
43} 43}
44 44
45char *cgit_hosturl() 45char *cgit_hosturl()
46{ 46{
47 if (ctx.env.http_host) 47 if (ctx.env.http_host)
48 return ctx.env.http_host; 48 return ctx.env.http_host;
49 if (!ctx.env.server_name) 49 if (!ctx.env.server_name)
50 return NULL; 50 return NULL;
51 if (!ctx.env.server_port || atoi(ctx.env.server_port) == 80) 51 if (!ctx.env.server_port || atoi(ctx.env.server_port) == 80)
52 return ctx.env.server_name; 52 return ctx.env.server_name;
53 return xstrdup(fmt("%s:%s", ctx.env.server_name, ctx.env.server_port)); 53 return xstrdup(fmt("%s:%s", ctx.env.server_name, ctx.env.server_port));
54} 54}
55 55
56char *cgit_rooturl() 56char *cgit_rooturl()
57{ 57{
58 if (ctx.cfg.virtual_root) 58 if (ctx.cfg.virtual_root)
59 return fmt("%s/", ctx.cfg.virtual_root); 59 return fmt("%s/", ctx.cfg.virtual_root);
60 else 60 else
61 return ctx.cfg.script_name; 61 return ctx.cfg.script_name;
62} 62}
63 63
64char *cgit_repourl(const char *reponame) 64char *cgit_repourl(const char *reponame)
65{ 65{
66 if (ctx.cfg.virtual_root) { 66 if (ctx.cfg.virtual_root) {
67 return fmt("%s/%s/", ctx.cfg.virtual_root, reponame); 67 return fmt("%s/%s/", ctx.cfg.virtual_root, reponame);
68 } else { 68 } else {
69 return fmt("?r=%s", reponame); 69 return fmt("?r=%s", reponame);
70 } 70 }
71} 71}
72 72
73char *cgit_fileurl(const char *reponame, const char *pagename, 73char *cgit_fileurl(const char *reponame, const char *pagename,
74 const char *filename, const char *query) 74 const char *filename, const char *query)
75{ 75{
76 char *tmp; 76 char *tmp;
77 char *delim; 77 char *delim;
78 78
79 if (ctx.cfg.virtual_root) { 79 if (ctx.cfg.virtual_root) {
80 tmp = fmt("%s/%s/%s/%s", ctx.cfg.virtual_root, reponame, 80 tmp = fmt("%s/%s/%s/%s", ctx.cfg.virtual_root, reponame,
81 pagename, (filename ? filename:"")); 81 pagename, (filename ? filename:""));
82 delim = "?"; 82 delim = "?";
83 } else { 83 } else {
84 tmp = fmt("?url=%s/%s/%s", reponame, pagename, 84 tmp = fmt("?url=%s/%s/%s", reponame, pagename,
85 (filename ? filename : "")); 85 (filename ? filename : ""));
86 delim = "&"; 86 delim = "&";
87 } 87 }
88 if (query) 88 if (query)
89 tmp = fmt("%s%s%s", tmp, delim, query); 89 tmp = fmt("%s%s%s", tmp, delim, query);
90 return tmp; 90 return tmp;
91} 91}
92 92
93char *cgit_pageurl(const char *reponame, const char *pagename, 93char *cgit_pageurl(const char *reponame, const char *pagename,
94 const char *query) 94 const char *query)
95{ 95{
96 return cgit_fileurl(reponame,pagename,0,query); 96 return cgit_fileurl(reponame,pagename,0,query);
97} 97}
98 98
99const char *cgit_repobasename(const char *reponame) 99const char *cgit_repobasename(const char *reponame)
100{ 100{
101 /* I assume we don't need to store more than one repo basename */ 101 /* I assume we don't need to store more than one repo basename */
102 static char rvbuf[1024]; 102 static char rvbuf[1024];
103 int p; 103 int p;
104 const char *rv; 104 const char *rv;
105 strncpy(rvbuf,reponame,sizeof(rvbuf)); 105 strncpy(rvbuf,reponame,sizeof(rvbuf));
106 if(rvbuf[sizeof(rvbuf)-1]) 106 if(rvbuf[sizeof(rvbuf)-1])
107 die("cgit_repobasename: truncated repository name '%s'", reponame); 107 die("cgit_repobasename: truncated repository name '%s'", reponame);
108 p = strlen(rvbuf)-1; 108 p = strlen(rvbuf)-1;
109 /* strip trailing slashes */ 109 /* strip trailing slashes */
110 while(p && rvbuf[p]=='/') rvbuf[p--]=0; 110 while(p && rvbuf[p]=='/') rvbuf[p--]=0;
111 /* strip trailing .git */ 111 /* strip trailing .git */
112 if(p>=3 && !strncmp(&rvbuf[p-3],".git",4)) { 112 if(p>=3 && !strncmp(&rvbuf[p-3],".git",4)) {
113 p -= 3; rvbuf[p--] = 0; 113 p -= 3; rvbuf[p--] = 0;
114 } 114 }
115 /* strip more trailing slashes if any */ 115 /* strip more trailing slashes if any */
116 while( p && rvbuf[p]=='/') rvbuf[p--]=0; 116 while( p && rvbuf[p]=='/') rvbuf[p--]=0;
117 /* find last slash in the remaining string */ 117 /* find last slash in the remaining string */
118 rv = strrchr(rvbuf,'/'); 118 rv = strrchr(rvbuf,'/');
119 if(rv) 119 if(rv)
120 return ++rv; 120 return ++rv;
121 return rvbuf; 121 return rvbuf;
122} 122}
123 123
124char *cgit_currurl() 124char *cgit_currurl()
125{ 125{
126 if (!ctx.cfg.virtual_root) 126 if (!ctx.cfg.virtual_root)
127 return ctx.cfg.script_name; 127 return ctx.cfg.script_name;
128 else if (ctx.qry.page) 128 else if (ctx.qry.page)
129 return fmt("%s/%s/%s/", ctx.cfg.virtual_root, ctx.qry.repo, ctx.qry.page); 129 return fmt("%s/%s/%s/", ctx.cfg.virtual_root, ctx.qry.repo, ctx.qry.page);
130 else if (ctx.qry.repo) 130 else if (ctx.qry.repo)
131 return fmt("%s/%s/", ctx.cfg.virtual_root, ctx.qry.repo); 131 return fmt("%s/%s/", ctx.cfg.virtual_root, ctx.qry.repo);
132 else 132 else
133 return fmt("%s/", ctx.cfg.virtual_root); 133 return fmt("%s/", ctx.cfg.virtual_root);
134} 134}
135 135
136static void site_url(const char *page, const char *search, int ofs) 136static void site_url(const char *page, const char *search, int ofs)
137{ 137{
138 char *delim = "?"; 138 char *delim = "?";
139 139
140 if (ctx.cfg.virtual_root) { 140 if (ctx.cfg.virtual_root) {
141 html_attr(ctx.cfg.virtual_root); 141 html_attr(ctx.cfg.virtual_root);
142 if (ctx.cfg.virtual_root[strlen(ctx.cfg.virtual_root) - 1] != '/') 142 if (ctx.cfg.virtual_root[strlen(ctx.cfg.virtual_root) - 1] != '/')
143 html("/"); 143 html("/");
144 } else 144 } else
145 html(ctx.cfg.script_name); 145 html(ctx.cfg.script_name);
146 146
147 if (page) { 147 if (page) {
148 htmlf("?p=%s", page); 148 htmlf("?p=%s", page);
149 delim = "&"; 149 delim = "&";
150 } 150 }
151 if (search) { 151 if (search) {
152 html(delim); 152 html(delim);
153 html("q="); 153 html("q=");
154 html_attr(search); 154 html_attr(search);
155 delim = "&"; 155 delim = "&";
156 } 156 }
157 if (ofs) { 157 if (ofs) {
158 html(delim); 158 html(delim);
159 htmlf("ofs=%d", ofs); 159 htmlf("ofs=%d", ofs);
160 } 160 }
161} 161}
162 162
163static void site_link(const char *page, const char *name, const char *title, 163static void site_link(const char *page, const char *name, const char *title,
164 const char *class, const char *search, int ofs) 164 const char *class, const char *search, int ofs)
165{ 165{
166 html("<a"); 166 html("<a");
167 if (title) { 167 if (title) {
168 html(" title='"); 168 html(" title='");
169 html_attr(title); 169 html_attr(title);
170 html("'"); 170 html("'");
171 } 171 }
172 if (class) { 172 if (class) {
173 html(" class='"); 173 html(" class='");
174 html_attr(class); 174 html_attr(class);
175 html("'"); 175 html("'");
176 } 176 }
177 html(" href='"); 177 html(" href='");
178 site_url(page, search, ofs); 178 site_url(page, search, ofs);
179 html("'>"); 179 html("'>");
180 html_txt(name); 180 html_txt(name);
181 html("</a>"); 181 html("</a>");
182} 182}
183 183
184void cgit_index_link(const char *name, const char *title, const char *class, 184void cgit_index_link(const char *name, const char *title, const char *class,
185 const char *pattern, int ofs) 185 const char *pattern, int ofs)
186{ 186{
187 site_link(NULL, name, title, class, pattern, ofs); 187 site_link(NULL, name, title, class, pattern, ofs);
188} 188}
189 189
190static char *repolink(const char *title, const char *class, const char *page, 190static char *repolink(const char *title, const char *class, const char *page,
191 const char *head, const char *path) 191 const char *head, const char *path)
192{ 192{
193 char *delim = "?"; 193 char *delim = "?";
194 194
195 html("<a"); 195 html("<a");
196 if (title) { 196 if (title) {
197 html(" title='"); 197 html(" title='");
198 html_attr(title); 198 html_attr(title);
199 html("'"); 199 html("'");
200 } 200 }
201 if (class) { 201 if (class) {
202 html(" class='"); 202 html(" class='");
203 html_attr(class); 203 html_attr(class);
204 html("'"); 204 html("'");
205 } 205 }
206 html(" href='"); 206 html(" href='");
207 if (ctx.cfg.virtual_root) { 207 if (ctx.cfg.virtual_root) {
208 html_url_path(ctx.cfg.virtual_root); 208 html_url_path(ctx.cfg.virtual_root);
209 if (ctx.cfg.virtual_root[strlen(ctx.cfg.virtual_root) - 1] != '/') 209 if (ctx.cfg.virtual_root[strlen(ctx.cfg.virtual_root) - 1] != '/')
210 html("/"); 210 html("/");
211 html_url_path(ctx.repo->url); 211 html_url_path(ctx.repo->url);
212 if (ctx.repo->url[strlen(ctx.repo->url) - 1] != '/') 212 if (ctx.repo->url[strlen(ctx.repo->url) - 1] != '/')
213 html("/"); 213 html("/");
214 if (page) { 214 if (page) {
215 html_url_path(page); 215 html_url_path(page);
216 html("/"); 216 html("/");
217 if (path) 217 if (path)
218 html_url_path(path); 218 html_url_path(path);
219 } 219 }
220 } else { 220 } else {
221 html(ctx.cfg.script_name); 221 html(ctx.cfg.script_name);
222 html("?url="); 222 html("?url=");
223 html_url_arg(ctx.repo->url); 223 html_url_arg(ctx.repo->url);
224 if (ctx.repo->url[strlen(ctx.repo->url) - 1] != '/') 224 if (ctx.repo->url[strlen(ctx.repo->url) - 1] != '/')
225 html("/"); 225 html("/");
226 if (page) { 226 if (page) {
227 html_url_arg(page); 227 html_url_arg(page);
228 html("/"); 228 html("/");
229 if (path) 229 if (path)
230 html_url_arg(path); 230 html_url_arg(path);
231 } 231 }
232 delim = "&amp;"; 232 delim = "&amp;";
233 } 233 }
234 if (head && strcmp(head, ctx.repo->defbranch)) { 234 if (head && strcmp(head, ctx.repo->defbranch)) {
235 html(delim); 235 html(delim);
236 html("h="); 236 html("h=");
237 html_url_arg(head); 237 html_url_arg(head);
238 delim = "&amp;"; 238 delim = "&amp;";
239 } 239 }
240 return fmt("%s", delim); 240 return fmt("%s", delim);
241} 241}
242 242
243static void reporevlink(const char *page, const char *name, const char *title, 243static void reporevlink(const char *page, const char *name, const char *title,
244 const char *class, const char *head, const char *rev, 244 const char *class, const char *head, const char *rev,
245 const char *path) 245 const char *path)
246{ 246{
247 char *delim; 247 char *delim;
248 248
249 delim = repolink(title, class, page, head, path); 249 delim = repolink(title, class, page, head, path);
250 if (rev && ctx.qry.head != NULL && strcmp(rev, ctx.qry.head)) { 250 if (rev && ctx.qry.head != NULL && strcmp(rev, ctx.qry.head)) {
251 html(delim); 251 html(delim);
252 html("id="); 252 html("id=");
253 html_url_arg(rev); 253 html_url_arg(rev);
254 } 254 }
255 html("'>"); 255 html("'>");
256 html_txt(name); 256 html_txt(name);
257 html("</a>"); 257 html("</a>");
258} 258}
259 259
260void cgit_summary_link(const char *name, const char *title, const char *class, 260void cgit_summary_link(const char *name, const char *title, const char *class,
261 const char *head) 261 const char *head)
262{ 262{
263 reporevlink(NULL, name, title, class, head, NULL, NULL); 263 reporevlink(NULL, name, title, class, head, NULL, NULL);
264} 264}
265 265
266void cgit_tag_link(const char *name, const char *title, const char *class, 266void cgit_tag_link(const char *name, const char *title, const char *class,
267 const char *head, const char *rev) 267 const char *head, const char *rev)
268{ 268{
269 reporevlink("tag", name, title, class, head, rev, NULL); 269 reporevlink("tag", name, title, class, head, rev, NULL);
270} 270}
271 271
272void cgit_tree_link(const char *name, const char *title, const char *class, 272void cgit_tree_link(const char *name, const char *title, const char *class,
273 const char *head, const char *rev, const char *path) 273 const char *head, const char *rev, const char *path)
274{ 274{
275 reporevlink("tree", name, title, class, head, rev, path); 275 reporevlink("tree", name, title, class, head, rev, path);
276} 276}
277 277
278void cgit_plain_link(const char *name, const char *title, const char *class, 278void cgit_plain_link(const char *name, const char *title, const char *class,
279 const char *head, const char *rev, const char *path) 279 const char *head, const char *rev, const char *path)
280{ 280{
281 reporevlink("plain", name, title, class, head, rev, path); 281 reporevlink("plain", name, title, class, head, rev, path);
282} 282}
283 283
284void cgit_log_link(const char *name, const char *title, const char *class, 284void cgit_log_link(const char *name, const char *title, const char *class,
285 const char *head, const char *rev, const char *path, 285 const char *head, const char *rev, const char *path,
286 int ofs, const char *grep, const char *pattern, int showmsg) 286 int ofs, const char *grep, const char *pattern, int showmsg)
287{ 287{
288 char *delim; 288 char *delim;
289 289
290 delim = repolink(title, class, "log", head, path); 290 delim = repolink(title, class, "log", head, path);
291 if (rev && strcmp(rev, ctx.qry.head)) { 291 if (rev && strcmp(rev, ctx.qry.head)) {
292 html(delim); 292 html(delim);
293 html("id="); 293 html("id=");
294 html_url_arg(rev); 294 html_url_arg(rev);
295 delim = "&"; 295 delim = "&";
296 } 296 }
297 if (grep && pattern) { 297 if (grep && pattern) {
298 html(delim); 298 html(delim);
299 html("qt="); 299 html("qt=");
300 html_url_arg(grep); 300 html_url_arg(grep);
301 delim = "&"; 301 delim = "&";
302 html(delim); 302 html(delim);
303 html("q="); 303 html("q=");
304 html_url_arg(pattern); 304 html_url_arg(pattern);
305 } 305 }
306 if (ofs > 0) { 306 if (ofs > 0) {
307 html(delim); 307 html(delim);
308 html("ofs="); 308 html("ofs=");
309 htmlf("%d", ofs); 309 htmlf("%d", ofs);
310 delim = "&"; 310 delim = "&";
311 } 311 }
312 if (showmsg) { 312 if (showmsg) {
313 html(delim); 313 html(delim);
314 html("showmsg=1"); 314 html("showmsg=1");
315 } 315 }
316 html("'>"); 316 html("'>");
317 html_txt(name); 317 html_txt(name);
318 html("</a>"); 318 html("</a>");
319} 319}
320 320
321void cgit_commit_link(char *name, const char *title, const char *class, 321void cgit_commit_link(char *name, const char *title, const char *class,
322 const char *head, const char *rev, const char *path, 322 const char *head, const char *rev, const char *path,
323 int toggle_ssdiff) 323 int toggle_ssdiff)
324{ 324{
325 if (strlen(name) > ctx.cfg.max_msg_len && ctx.cfg.max_msg_len >= 15) { 325 if (strlen(name) > ctx.cfg.max_msg_len && ctx.cfg.max_msg_len >= 15) {
326 name[ctx.cfg.max_msg_len] = '\0'; 326 name[ctx.cfg.max_msg_len] = '\0';
327 name[ctx.cfg.max_msg_len - 1] = '.'; 327 name[ctx.cfg.max_msg_len - 1] = '.';
328 name[ctx.cfg.max_msg_len - 2] = '.'; 328 name[ctx.cfg.max_msg_len - 2] = '.';
329 name[ctx.cfg.max_msg_len - 3] = '.'; 329 name[ctx.cfg.max_msg_len - 3] = '.';
330 } 330 }
331 331
332 char *delim; 332 char *delim;
333 333
334 delim = repolink(title, class, "commit", head, path); 334 delim = repolink(title, class, "commit", head, path);
335 if (rev && strcmp(rev, ctx.qry.head)) { 335 if (rev && strcmp(rev, ctx.qry.head)) {
336 html(delim); 336 html(delim);
337 html("id="); 337 html("id=");
338 html_url_arg(rev); 338 html_url_arg(rev);
339 delim = "&amp;"; 339 delim = "&amp;";
340 } 340 }
341 if ((ctx.qry.ssdiff && !toggle_ssdiff) || (!ctx.qry.ssdiff && toggle_ssdiff)) { 341 if ((ctx.qry.ssdiff && !toggle_ssdiff) || (!ctx.qry.ssdiff && toggle_ssdiff)) {
342 html(delim); 342 html(delim);
343 html("ss=1"); 343 html("ss=1");
344 delim = "&amp;"; 344 delim = "&amp;";
345 } 345 }
346 if (ctx.qry.context > 0 && ctx.qry.context != 3) { 346 if (ctx.qry.context > 0 && ctx.qry.context != 3) {
347 html(delim); 347 html(delim);
348 html("context="); 348 html("context=");
349 htmlf("%d", ctx.qry.context); 349 htmlf("%d", ctx.qry.context);
350 delim = "&amp;"; 350 delim = "&amp;";
351 } 351 }
352 if (ctx.qry.ignorews) { 352 if (ctx.qry.ignorews) {
353 html(delim); 353 html(delim);
354 html("ignorews=1"); 354 html("ignorews=1");
355 delim = "&amp;"; 355 delim = "&amp;";
356 } 356 }
357 html("'>"); 357 html("'>");
358 html_txt(name); 358 html_txt(name);
359 html("</a>"); 359 html("</a>");
360} 360}
361 361
362void cgit_refs_link(const char *name, const char *title, const char *class, 362void cgit_refs_link(const char *name, const char *title, const char *class,
363 const char *head, const char *rev, const char *path) 363 const char *head, const char *rev, const char *path)
364{ 364{
365 reporevlink("refs", name, title, class, head, rev, path); 365 reporevlink("refs", name, title, class, head, rev, path);
366} 366}
367 367
368void cgit_snapshot_link(const char *name, const char *title, const char *class, 368void cgit_snapshot_link(const char *name, const char *title, const char *class,
369 const char *head, const char *rev, 369 const char *head, const char *rev,
370 const char *archivename) 370 const char *archivename)
371{ 371{
372 reporevlink("snapshot", name, title, class, head, rev, archivename); 372 reporevlink("snapshot", name, title, class, head, rev, archivename);
373} 373}
374 374
375void cgit_diff_link(const char *name, const char *title, const char *class, 375void cgit_diff_link(const char *name, const char *title, const char *class,
376 const char *head, const char *new_rev, const char *old_rev, 376 const char *head, const char *new_rev, const char *old_rev,
377 const char *path, int toggle_ssdiff) 377 const char *path, int toggle_ssdiff)
378{ 378{
379 char *delim; 379 char *delim;
380 380
381 delim = repolink(title, class, "diff", head, path); 381 delim = repolink(title, class, "diff", head, path);
382 if (new_rev && ctx.qry.head != NULL && strcmp(new_rev, ctx.qry.head)) { 382 if (new_rev && ctx.qry.head != NULL && strcmp(new_rev, ctx.qry.head)) {
383 html(delim); 383 html(delim);
384 html("id="); 384 html("id=");
385 html_url_arg(new_rev); 385 html_url_arg(new_rev);
386 delim = "&amp;"; 386 delim = "&amp;";
387 } 387 }
388 if (old_rev) { 388 if (old_rev) {
389 html(delim); 389 html(delim);
390 html("id2="); 390 html("id2=");
391 html_url_arg(old_rev); 391 html_url_arg(old_rev);
392 delim = "&amp;"; 392 delim = "&amp;";
393 } 393 }
394 if ((ctx.qry.ssdiff && !toggle_ssdiff) || (!ctx.qry.ssdiff && toggle_ssdiff)) { 394 if ((ctx.qry.ssdiff && !toggle_ssdiff) || (!ctx.qry.ssdiff && toggle_ssdiff)) {
395 html(delim); 395 html(delim);
396 html("ss=1"); 396 html("ss=1");
397 delim = "&amp;"; 397 delim = "&amp;";
398 } 398 }
399 if (ctx.qry.context > 0 && ctx.qry.context != 3) { 399 if (ctx.qry.context > 0 && ctx.qry.context != 3) {
400 html(delim); 400 html(delim);
401 html("context="); 401 html("context=");
402 htmlf("%d", ctx.qry.context); 402 htmlf("%d", ctx.qry.context);
403 delim = "&amp;"; 403 delim = "&amp;";
404 } 404 }
405 if (ctx.qry.ignorews) { 405 if (ctx.qry.ignorews) {
406 html(delim); 406 html(delim);
407 html("ignorews=1"); 407 html("ignorews=1");
408 delim = "&amp;"; 408 delim = "&amp;";
409 } 409 }
410 html("'>"); 410 html("'>");
411 html_txt(name); 411 html_txt(name);
412 html("</a>"); 412 html("</a>");
413} 413}
414 414
415void cgit_patch_link(const char *name, const char *title, const char *class, 415void cgit_patch_link(const char *name, const char *title, const char *class,
416 const char *head, const char *rev, const char *path) 416 const char *head, const char *rev, const char *path)
417{ 417{
418 reporevlink("patch", name, title, class, head, rev, path); 418 reporevlink("patch", name, title, class, head, rev, path);
419} 419}
420 420
421void cgit_stats_link(const char *name, const char *title, const char *class, 421void cgit_stats_link(const char *name, const char *title, const char *class,
422 const char *head, const char *path) 422 const char *head, const char *path)
423{ 423{
424 reporevlink("stats", name, title, class, head, NULL, path); 424 reporevlink("stats", name, title, class, head, NULL, path);
425} 425}
426 426
427void cgit_self_link(char *name, const char *title, const char *class, 427void cgit_self_link(char *name, const char *title, const char *class,
428 struct cgit_context *ctx) 428 struct cgit_context *ctx)
429{ 429{
430 if (!strcmp(ctx->qry.page, "repolist")) 430 if (!strcmp(ctx->qry.page, "repolist"))
431 return cgit_index_link(name, title, class, ctx->qry.search, 431 return cgit_index_link(name, title, class, ctx->qry.search,
432 ctx->qry.ofs); 432 ctx->qry.ofs);
433 else if (!strcmp(ctx->qry.page, "summary")) 433 else if (!strcmp(ctx->qry.page, "summary"))
434 return cgit_summary_link(name, title, class, ctx->qry.head); 434 return cgit_summary_link(name, title, class, ctx->qry.head);
435 else if (!strcmp(ctx->qry.page, "tag")) 435 else if (!strcmp(ctx->qry.page, "tag"))
436 return cgit_tag_link(name, title, class, ctx->qry.head, 436 return cgit_tag_link(name, title, class, ctx->qry.head,
437 ctx->qry.has_sha1 ? ctx->qry.sha1 : NULL); 437 ctx->qry.has_sha1 ? ctx->qry.sha1 : NULL);
438 else if (!strcmp(ctx->qry.page, "tree")) 438 else if (!strcmp(ctx->qry.page, "tree"))
439 return cgit_tree_link(name, title, class, ctx->qry.head, 439 return cgit_tree_link(name, title, class, ctx->qry.head,
440 ctx->qry.has_sha1 ? ctx->qry.sha1 : NULL, 440 ctx->qry.has_sha1 ? ctx->qry.sha1 : NULL,
441 ctx->qry.path); 441 ctx->qry.path);
442 else if (!strcmp(ctx->qry.page, "plain")) 442 else if (!strcmp(ctx->qry.page, "plain"))
443 return cgit_plain_link(name, title, class, ctx->qry.head, 443 return cgit_plain_link(name, title, class, ctx->qry.head,
444 ctx->qry.has_sha1 ? ctx->qry.sha1 : NULL, 444 ctx->qry.has_sha1 ? ctx->qry.sha1 : NULL,
445 ctx->qry.path); 445 ctx->qry.path);
446 else if (!strcmp(ctx->qry.page, "log")) 446 else if (!strcmp(ctx->qry.page, "log"))
447 return cgit_log_link(name, title, class, ctx->qry.head, 447 return cgit_log_link(name, title, class, ctx->qry.head,
448 ctx->qry.has_sha1 ? ctx->qry.sha1 : NULL, 448 ctx->qry.has_sha1 ? ctx->qry.sha1 : NULL,
449 ctx->qry.path, ctx->qry.ofs, 449 ctx->qry.path, ctx->qry.ofs,
450 ctx->qry.grep, ctx->qry.search, 450 ctx->qry.grep, ctx->qry.search,
451 ctx->qry.showmsg); 451 ctx->qry.showmsg);
452 else if (!strcmp(ctx->qry.page, "commit")) 452 else if (!strcmp(ctx->qry.page, "commit"))
453 return cgit_commit_link(name, title, class, ctx->qry.head, 453 return cgit_commit_link(name, title, class, ctx->qry.head,
454 ctx->qry.has_sha1 ? ctx->qry.sha1 : NULL, 454 ctx->qry.has_sha1 ? ctx->qry.sha1 : NULL,
455 ctx->qry.path, 0); 455 ctx->qry.path, 0);
456 else if (!strcmp(ctx->qry.page, "patch")) 456 else if (!strcmp(ctx->qry.page, "patch"))
457 return cgit_patch_link(name, title, class, ctx->qry.head, 457 return cgit_patch_link(name, title, class, ctx->qry.head,
458 ctx->qry.has_sha1 ? ctx->qry.sha1 : NULL, 458 ctx->qry.has_sha1 ? ctx->qry.sha1 : NULL,
459 ctx->qry.path); 459 ctx->qry.path);
460 else if (!strcmp(ctx->qry.page, "refs")) 460 else if (!strcmp(ctx->qry.page, "refs"))
461 return cgit_refs_link(name, title, class, ctx->qry.head, 461 return cgit_refs_link(name, title, class, ctx->qry.head,
462 ctx->qry.has_sha1 ? ctx->qry.sha1 : NULL, 462 ctx->qry.has_sha1 ? ctx->qry.sha1 : NULL,
463 ctx->qry.path); 463 ctx->qry.path);
464 else if (!strcmp(ctx->qry.page, "snapshot")) 464 else if (!strcmp(ctx->qry.page, "snapshot"))
465 return cgit_snapshot_link(name, title, class, ctx->qry.head, 465 return cgit_snapshot_link(name, title, class, ctx->qry.head,
466 ctx->qry.has_sha1 ? ctx->qry.sha1 : NULL, 466 ctx->qry.has_sha1 ? ctx->qry.sha1 : NULL,
467 ctx->qry.path); 467 ctx->qry.path);
468 else if (!strcmp(ctx->qry.page, "diff")) 468 else if (!strcmp(ctx->qry.page, "diff"))
469 return cgit_diff_link(name, title, class, ctx->qry.head, 469 return cgit_diff_link(name, title, class, ctx->qry.head,
470 ctx->qry.sha1, ctx->qry.sha2, 470 ctx->qry.sha1, ctx->qry.sha2,
471 ctx->qry.path, 0); 471 ctx->qry.path, 0);
472 else if (!strcmp(ctx->qry.page, "stats")) 472 else if (!strcmp(ctx->qry.page, "stats"))
473 return cgit_stats_link(name, title, class, ctx->qry.head, 473 return cgit_stats_link(name, title, class, ctx->qry.head,
474 ctx->qry.path); 474 ctx->qry.path);
475 475
476 /* Don't known how to make link for this page */ 476 /* Don't known how to make link for this page */
477 repolink(title, class, ctx->qry.page, ctx->qry.head, ctx->qry.path); 477 repolink(title, class, ctx->qry.page, ctx->qry.head, ctx->qry.path);
478 html("><!-- cgit_self_link() doesn't know how to make link for page '"); 478 html("><!-- cgit_self_link() doesn't know how to make link for page '");
479 html_txt(ctx->qry.page); 479 html_txt(ctx->qry.page);
480 html("' -->"); 480 html("' -->");
481 html_txt(name); 481 html_txt(name);
482 html("</a>"); 482 html("</a>");
483} 483}
484 484
485void cgit_object_link(struct object *obj) 485void cgit_object_link(struct object *obj)
486{ 486{
487 char *page, *shortrev, *fullrev, *name; 487 char *page, *shortrev, *fullrev, *name;
488 488
489 fullrev = sha1_to_hex(obj->sha1); 489 fullrev = sha1_to_hex(obj->sha1);
490 shortrev = xstrdup(fullrev); 490 shortrev = xstrdup(fullrev);
491 shortrev[10] = '\0'; 491 shortrev[10] = '\0';
492 if (obj->type == OBJ_COMMIT) { 492 if (obj->type == OBJ_COMMIT) {
493 cgit_commit_link(fmt("commit %s...", shortrev), NULL, NULL, 493 cgit_commit_link(fmt("commit %s...", shortrev), NULL, NULL,
494 ctx.qry.head, fullrev, NULL, 0); 494 ctx.qry.head, fullrev, NULL, 0);
495 return; 495 return;
496 } else if (obj->type == OBJ_TREE) 496 } else if (obj->type == OBJ_TREE)
497 page = "tree"; 497 page = "tree";
498 else if (obj->type == OBJ_TAG) 498 else if (obj->type == OBJ_TAG)
499 page = "tag"; 499 page = "tag";
500 else 500 else
501 page = "blob"; 501 page = "blob";
502 name = fmt("%s %s...", typename(obj->type), shortrev); 502 name = fmt("%s %s...", typename(obj->type), shortrev);
503 reporevlink(page, name, NULL, NULL, ctx.qry.head, fullrev, NULL); 503 reporevlink(page, name, NULL, NULL, ctx.qry.head, fullrev, NULL);
504} 504}
505 505
506void cgit_print_date(time_t secs, const char *format, int local_time) 506void cgit_print_date(time_t secs, const char *format, int local_time)
507{ 507{
508 char buf[64]; 508 char buf[64];
509 struct tm *time; 509 struct tm *time;
510 510
511 if (!secs) 511 if (!secs)
512 return; 512 return;
513 if(local_time) 513 if(local_time)
514 time = localtime(&secs); 514 time = localtime(&secs);
515 else 515 else
516 time = gmtime(&secs); 516 time = gmtime(&secs);
517 strftime(buf, sizeof(buf)-1, format, time); 517 strftime(buf, sizeof(buf)-1, format, time);
518 html_txt(buf); 518 html_txt(buf);
519} 519}
520 520
521void cgit_print_age(time_t t, time_t max_relative, const char *format) 521void cgit_print_age(time_t t, time_t max_relative, const char *format)
522{ 522{
523 time_t now, secs; 523 time_t now, secs;
524 524
525 if (!t) 525 if (!t)
526 return; 526 return;
527 time(&now); 527 time(&now);
528 secs = now - t; 528 secs = now - t;
529 529
530 if (secs > max_relative && max_relative >= 0) { 530 if (secs > max_relative && max_relative >= 0) {
531 cgit_print_date(t, format, ctx.cfg.local_time); 531 cgit_print_date(t, format, ctx.cfg.local_time);
532 return; 532 return;
533 } 533 }
534 534
535 if (secs < TM_HOUR * 2) { 535 if (secs < TM_HOUR * 2) {
536 htmlf("<span class='age-mins'>%.0f min.</span>", 536 htmlf("<span class='age-mins'>%.0f min.</span>",
537 secs * 1.0 / TM_MIN); 537 secs * 1.0 / TM_MIN);
538 return; 538 return;
539 } 539 }
540 if (secs < TM_DAY * 2) { 540 if (secs < TM_DAY * 2) {
541 htmlf("<span class='age-hours'>%.0f hours</span>", 541 htmlf("<span class='age-hours'>%.0f hours</span>",
542 secs * 1.0 / TM_HOUR); 542 secs * 1.0 / TM_HOUR);
543 return; 543 return;
544 } 544 }
545 if (secs < TM_WEEK * 2) { 545 if (secs < TM_WEEK * 2) {
546 htmlf("<span class='age-days'>%.0f days</span>", 546 htmlf("<span class='age-days'>%.0f days</span>",
547 secs * 1.0 / TM_DAY); 547 secs * 1.0 / TM_DAY);
548 return; 548 return;
549 } 549 }
550 if (secs < TM_MONTH * 2) { 550 if (secs < TM_MONTH * 2) {
551 htmlf("<span class='age-weeks'>%.0f weeks</span>", 551 htmlf("<span class='age-weeks'>%.0f weeks</span>",
552 secs * 1.0 / TM_WEEK); 552 secs * 1.0 / TM_WEEK);
553 return; 553 return;
554 } 554 }
555 if (secs < TM_YEAR * 2) { 555 if (secs < TM_YEAR * 2) {
556 htmlf("<span class='age-months'>%.0f months</span>", 556 htmlf("<span class='age-months'>%.0f months</span>",
557 secs * 1.0 / TM_MONTH); 557 secs * 1.0 / TM_MONTH);
558 return; 558 return;
559 } 559 }
560 htmlf("<span class='age-years'>%.0f years</span>", 560 htmlf("<span class='age-years'>%.0f years</span>",
561 secs * 1.0 / TM_YEAR); 561 secs * 1.0 / TM_YEAR);
562} 562}
563 563
564void cgit_print_http_headers(struct cgit_context *ctx) 564void cgit_print_http_headers(struct cgit_context *ctx)
565{ 565{
566 if (ctx->env.no_http && !strcmp(ctx->env.no_http, "1")) 566 if (ctx->env.no_http && !strcmp(ctx->env.no_http, "1"))
567 return; 567 return;
568 568
569 if (ctx->page.status) 569 if (ctx->page.status)
570 htmlf("Status: %d %s\n", ctx->page.status, ctx->page.statusmsg); 570 htmlf("Status: %d %s\n", ctx->page.status, ctx->page.statusmsg);
571 if (ctx->page.mimetype && ctx->page.charset) 571 if (ctx->page.mimetype && ctx->page.charset)
572 htmlf("Content-Type: %s; charset=%s\n", ctx->page.mimetype, 572 htmlf("Content-Type: %s; charset=%s\n", ctx->page.mimetype,
573 ctx->page.charset); 573 ctx->page.charset);
574 else if (ctx->page.mimetype) 574 else if (ctx->page.mimetype)
575 htmlf("Content-Type: %s\n", ctx->page.mimetype); 575 htmlf("Content-Type: %s\n", ctx->page.mimetype);
576 if (ctx->page.size) 576 if (ctx->page.size)
577 htmlf("Content-Length: %ld\n", ctx->page.size); 577 htmlf("Content-Length: %ld\n", ctx->page.size);
578 if (ctx->page.filename) 578 if (ctx->page.filename)
579 htmlf("Content-Disposition: inline; filename=\"%s\"\n", 579 htmlf("Content-Disposition: inline; filename=\"%s\"\n",
580 ctx->page.filename); 580 ctx->page.filename);
581 htmlf("Last-Modified: %s\n", http_date(ctx->page.modified)); 581 htmlf("Last-Modified: %s\n", http_date(ctx->page.modified));
582 htmlf("Expires: %s\n", http_date(ctx->page.expires)); 582 htmlf("Expires: %s\n", http_date(ctx->page.expires));
583 if (ctx->page.etag) 583 if (ctx->page.etag)
584 htmlf("ETag: \"%s\"\n", ctx->page.etag); 584 htmlf("ETag: \"%s\"\n", ctx->page.etag);
585 html("\n"); 585 html("\n");
586 if (ctx->env.request_method && !strcmp(ctx->env.request_method, "HEAD")) 586 if (ctx->env.request_method && !strcmp(ctx->env.request_method, "HEAD"))
587 exit(0); 587 exit(0);
588} 588}
589 589
590void cgit_print_docstart(struct cgit_context *ctx) 590void cgit_print_docstart(struct cgit_context *ctx)
591{ 591{
592 if (ctx->cfg.embedded) { 592 if (ctx->cfg.embedded) {
593 if (ctx->cfg.header) 593 if (ctx->cfg.header)
594 html_include(ctx->cfg.header); 594 html_include(ctx->cfg.header);
595 return; 595 return;
596 } 596 }
597 597
598 char *host = cgit_hosturl(); 598 char *host = cgit_hosturl();
599 html(cgit_doctype); 599 html(cgit_doctype);
600 html("<html xmlns='http://www.w3.org/1999/xhtml' xml:lang='en' lang='en'>\n"); 600 html("<html xmlns='http://www.w3.org/1999/xhtml' xml:lang='en' lang='en'>\n");
601 html("<head>\n"); 601 html("<head>\n");
602 html("<title>"); 602 html("<title>");
603 html_txt(ctx->page.title); 603 html_txt(ctx->page.title);
604 html("</title>\n"); 604 html("</title>\n");
605 htmlf("<meta name='generator' content='cgit %s'/>\n", cgit_version); 605 htmlf("<meta name='generator' content='cgit %s'/>\n", cgit_version);
606 if (ctx->cfg.robots && *ctx->cfg.robots) 606 if (ctx->cfg.robots && *ctx->cfg.robots)
607 htmlf("<meta name='robots' content='%s'/>\n", ctx->cfg.robots); 607 htmlf("<meta name='robots' content='%s'/>\n", ctx->cfg.robots);
608 html("<link rel='stylesheet' type='text/css' href='"); 608 html("<link rel='stylesheet' type='text/css' href='");
609 html_attr(ctx->cfg.css); 609 html_attr(ctx->cfg.css);
610 html("'/>\n"); 610 html("'/>\n");
611 if (ctx->cfg.favicon) { 611 if (ctx->cfg.favicon) {
612 html("<link rel='shortcut icon' href='"); 612 html("<link rel='shortcut icon' href='");
613 html_attr(ctx->cfg.favicon); 613 html_attr(ctx->cfg.favicon);
614 html("'/>\n"); 614 html("'/>\n");
615 } 615 }
616 if (host && ctx->repo) { 616 if (host && ctx->repo) {
617 html("<link rel='alternate' title='Atom feed' href='"); 617 html("<link rel='alternate' title='Atom feed' href='");
618 html(cgit_httpscheme()); 618 html(cgit_httpscheme());
619 html_attr(cgit_hosturl()); 619 html_attr(cgit_hosturl());
620 html_attr(cgit_fileurl(ctx->repo->url, "atom", ctx->qry.vpath, 620 html_attr(cgit_fileurl(ctx->repo->url, "atom", ctx->qry.vpath,
621 fmt("h=%s", ctx->qry.head))); 621 fmt("h=%s", ctx->qry.head)));
622 html("' type='application/atom+xml'/>\n"); 622 html("' type='application/atom+xml'/>\n");
623 } 623 }
624 if (ctx->cfg.head_include) 624 if (ctx->cfg.head_include)
625 html_include(ctx->cfg.head_include); 625 html_include(ctx->cfg.head_include);
626 html("</head>\n"); 626 html("</head>\n");
627 html("<body>\n"); 627 html("<body>\n");
628 if (ctx->cfg.header) 628 if (ctx->cfg.header)
629 html_include(ctx->cfg.header); 629 html_include(ctx->cfg.header);
630} 630}
631 631
632void cgit_print_docend() 632void cgit_print_docend()
633{ 633{
634 html("</div> <!-- class=content -->\n"); 634 html("</div> <!-- class=content -->\n");
635 if (ctx.cfg.embedded) { 635 if (ctx.cfg.embedded) {
636 html("</div> <!-- id=cgit -->\n"); 636 html("</div> <!-- id=cgit -->\n");
637 if (ctx.cfg.footer) 637 if (ctx.cfg.footer)
638 html_include(ctx.cfg.footer); 638 html_include(ctx.cfg.footer);
639 return; 639 return;
640 } 640 }
641 if (ctx.cfg.footer) 641 if (ctx.cfg.footer)
642 html_include(ctx.cfg.footer); 642 html_include(ctx.cfg.footer);
643 else { 643 else {
644 htmlf("<div class='footer'>generated by cgit %s at ", 644 htmlf("<div class='footer'>generated by cgit %s at ",
645 cgit_version); 645 cgit_version);
646 cgit_print_date(time(NULL), FMT_LONGDATE, ctx.cfg.local_time); 646 cgit_print_date(time(NULL), FMT_LONGDATE, ctx.cfg.local_time);
647 html("</div>\n"); 647 html("</div>\n");
648 } 648 }
649 html("</div> <!-- id=cgit -->\n"); 649 html("</div> <!-- id=cgit -->\n");
650 html("</body>\n</html>\n"); 650 html("</body>\n</html>\n");
651} 651}
652 652
653int print_branch_option(const char *refname, const unsigned char *sha1, 653int print_branch_option(const char *refname, const unsigned char *sha1,
654 int flags, void *cb_data) 654 int flags, void *cb_data)
655{ 655{
656 char *name = (char *)refname; 656 char *name = (char *)refname;
657 html_option(name, name, ctx.qry.head); 657 html_option(name, name, ctx.qry.head);
658 return 0; 658 return 0;
659} 659}
660 660
661int print_archive_ref(const char *refname, const unsigned char *sha1, 661int print_archive_ref(const char *refname, const unsigned char *sha1,
662 int flags, void *cb_data) 662 int flags, void *cb_data)
663{ 663{
664 struct tag *tag; 664 struct tag *tag;
665 struct taginfo *info; 665 struct taginfo *info;
666 struct object *obj; 666 struct object *obj;
667 char buf[256], *url; 667 char buf[256], *url;
668 unsigned char fileid[20]; 668 unsigned char fileid[20];
669 int *header = (int *)cb_data; 669 int *header = (int *)cb_data;
670 670
671 if (prefixcmp(refname, "refs/archives")) 671 if (prefixcmp(refname, "refs/archives"))
672 return 0; 672 return 0;
673 strncpy(buf, refname+14, sizeof(buf)); 673 strncpy(buf, refname+14, sizeof(buf));
674 obj = parse_object(sha1); 674 obj = parse_object(sha1);
675 if (!obj) 675 if (!obj)
676 return 1; 676 return 1;
677 if (obj->type == OBJ_TAG) { 677 if (obj->type == OBJ_TAG) {
678 tag = lookup_tag(sha1); 678 tag = lookup_tag(sha1);
679 if (!tag || parse_tag(tag) || !(info = cgit_parse_tag(tag))) 679 if (!tag || parse_tag(tag) || !(info = cgit_parse_tag(tag)))
680 return 0; 680 return 0;
681 hashcpy(fileid, tag->tagged->sha1); 681 hashcpy(fileid, tag->tagged->sha1);
682 } else if (obj->type != OBJ_BLOB) { 682 } else if (obj->type != OBJ_BLOB) {
683 return 0; 683 return 0;
684 } else { 684 } else {
685 hashcpy(fileid, sha1); 685 hashcpy(fileid, sha1);
686 } 686 }
687 if (!*header) { 687 if (!*header) {
688 html("<h1>download</h1>\n"); 688 html("<h1>download</h1>\n");
689 *header = 1; 689 *header = 1;
690 } 690 }
691 url = cgit_pageurl(ctx.qry.repo, "blob", 691 url = cgit_pageurl(ctx.qry.repo, "blob",
692 fmt("id=%s&amp;path=%s", sha1_to_hex(fileid), 692 fmt("id=%s&amp;path=%s", sha1_to_hex(fileid),
693 buf)); 693 buf));
694 html_link_open(url, NULL, "menu"); 694 html_link_open(url, NULL, "menu");
695 html_txt(strlpart(buf, 20)); 695 html_txt(strlpart(buf, 20));
696 html_link_close(); 696 html_link_close();
697 return 0; 697 return 0;
698} 698}
699 699
700void cgit_add_hidden_formfields(int incl_head, int incl_search, 700void cgit_add_hidden_formfields(int incl_head, int incl_search,
701 const char *page) 701 const char *page)
702{ 702{
703 char *url; 703 char *url;
704 704
705 if (!ctx.cfg.virtual_root) { 705 if (!ctx.cfg.virtual_root) {
706 url = fmt("%s/%s", ctx.qry.repo, page); 706 url = fmt("%s/%s", ctx.qry.repo, page);
707 if (ctx.qry.vpath) 707 if (ctx.qry.vpath)
708 url = fmt("%s/%s", url, ctx.qry.vpath); 708 url = fmt("%s/%s", url, ctx.qry.vpath);
709 html_hidden("url", url); 709 html_hidden("url", url);
710 } 710 }
711 711
712 if (incl_head && ctx.qry.head && ctx.repo->defbranch && 712 if (incl_head && ctx.qry.head && ctx.repo->defbranch &&
713 strcmp(ctx.qry.head, ctx.repo->defbranch)) 713 strcmp(ctx.qry.head, ctx.repo->defbranch))
714 html_hidden("h", ctx.qry.head); 714 html_hidden("h", ctx.qry.head);
715 715
716 if (ctx.qry.sha1) 716 if (ctx.qry.sha1)
717 html_hidden("id", ctx.qry.sha1); 717 html_hidden("id", ctx.qry.sha1);
718 if (ctx.qry.sha2) 718 if (ctx.qry.sha2)
719 html_hidden("id2", ctx.qry.sha2); 719 html_hidden("id2", ctx.qry.sha2);
720 if (ctx.qry.showmsg) 720 if (ctx.qry.showmsg)
721 html_hidden("showmsg", "1"); 721 html_hidden("showmsg", "1");
722 722
723 if (incl_search) { 723 if (incl_search) {
724 if (ctx.qry.grep) 724 if (ctx.qry.grep)
725 html_hidden("qt", ctx.qry.grep); 725 html_hidden("qt", ctx.qry.grep);
726 if (ctx.qry.search) 726 if (ctx.qry.search)
727 html_hidden("q", ctx.qry.search); 727 html_hidden("q", ctx.qry.search);
728 } 728 }
729} 729}
730 730
731static const char *hc(struct cgit_context *ctx, const char *page) 731static const char *hc(struct cgit_context *ctx, const char *page)
732{ 732{
733 return strcmp(ctx->qry.page, page) ? NULL : "active"; 733 return strcmp(ctx->qry.page, page) ? NULL : "active";
734} 734}
735 735
736static void cgit_print_path_crumbs(struct cgit_context *ctx, char *path) 736static void cgit_print_path_crumbs(struct cgit_context *ctx, char *path)
737{ 737{
738 char *old_path = ctx->qry.path; 738 char *old_path = ctx->qry.path;
739 char *p = path, *q, *end = path + strlen(path); 739 char *p = path, *q, *end = path + strlen(path);
740 740
741 ctx->qry.path = NULL; 741 ctx->qry.path = NULL;
742 cgit_self_link("root", NULL, NULL, ctx); 742 cgit_self_link("root", NULL, NULL, ctx);
743 ctx->qry.path = p = path; 743 ctx->qry.path = p = path;
744 while (p < end) { 744 while (p < end) {
745 if (!(q = strchr(p, '/'))) 745 if (!(q = strchr(p, '/')))
746 q = end; 746 q = end;
747 *q = '\0'; 747 *q = '\0';
748 html_txt("/"); 748 html_txt("/");
749 cgit_self_link(p, NULL, NULL, ctx); 749 cgit_self_link(p, NULL, NULL, ctx);
750 if (q < end) 750 if (q < end)
751 *q = '/'; 751 *q = '/';
752 p = q + 1; 752 p = q + 1;
753 } 753 }
754 ctx->qry.path = old_path; 754 ctx->qry.path = old_path;
755} 755}
756 756
757static void print_header(struct cgit_context *ctx) 757static void print_header(struct cgit_context *ctx)
758{ 758{
759 char *logo = NULL, *logo_link = NULL;
760
759 html("<table id='header'>\n"); 761 html("<table id='header'>\n");
760 html("<tr>\n"); 762 html("<tr>\n");
761 763
762 if (ctx->cfg.logo && ctx->cfg.logo[0] != 0) { 764 if (ctx->repo && ctx->repo->logo && *ctx->repo->logo)
765 logo = ctx->repo->logo;
766 else
767 logo = ctx->cfg.logo;
768 if (ctx->repo && ctx->repo->logo_link && *ctx->repo->logo_link)
769 logo_link = ctx->repo->logo_link;
770 else
771 logo_link = ctx->cfg.logo_link;
772 if (logo && *logo) {
763 html("<td class='logo' rowspan='2'><a href='"); 773 html("<td class='logo' rowspan='2'><a href='");
764 if (ctx->cfg.logo_link) 774 if (logo_link && *logo_link)
765 html_attr(ctx->cfg.logo_link); 775 html_attr(logo_link);
766 else 776 else
767 html_attr(cgit_rooturl()); 777 html_attr(cgit_rooturl());
768 html("'><img src='"); 778 html("'><img src='");
769 html_attr(ctx->cfg.logo); 779 html_attr(logo);
770 html("' alt='cgit logo'/></a></td>\n"); 780 html("' alt='cgit logo'/></a></td>\n");
771 } 781 }
772 782
773 html("<td class='main'>"); 783 html("<td class='main'>");
774 if (ctx->repo) { 784 if (ctx->repo) {
775 cgit_index_link("index", NULL, NULL, NULL, 0); 785 cgit_index_link("index", NULL, NULL, NULL, 0);
776 html(" : "); 786 html(" : ");
777 cgit_summary_link(ctx->repo->name, ctx->repo->name, NULL, NULL); 787 cgit_summary_link(ctx->repo->name, ctx->repo->name, NULL, NULL);
778 html("</td><td class='form'>"); 788 html("</td><td class='form'>");
779 html("<form method='get' action=''>\n"); 789 html("<form method='get' action=''>\n");
780 cgit_add_hidden_formfields(0, 1, ctx->qry.page); 790 cgit_add_hidden_formfields(0, 1, ctx->qry.page);
781 html("<select name='h' onchange='this.form.submit();'>\n"); 791 html("<select name='h' onchange='this.form.submit();'>\n");
782 for_each_branch_ref(print_branch_option, ctx->qry.head); 792 for_each_branch_ref(print_branch_option, ctx->qry.head);
783 html("</select> "); 793 html("</select> ");
784 html("<input type='submit' name='' value='switch'/>"); 794 html("<input type='submit' name='' value='switch'/>");
785 html("</form>"); 795 html("</form>");
786 } else 796 } else
787 html_txt(ctx->cfg.root_title); 797 html_txt(ctx->cfg.root_title);
788 html("</td></tr>\n"); 798 html("</td></tr>\n");
789 799
790 html("<tr><td class='sub'>"); 800 html("<tr><td class='sub'>");
791 if (ctx->repo) { 801 if (ctx->repo) {
792 html_txt(ctx->repo->desc); 802 html_txt(ctx->repo->desc);
793 html("</td><td class='sub right'>"); 803 html("</td><td class='sub right'>");
794 html_txt(ctx->repo->owner); 804 html_txt(ctx->repo->owner);
795 } else { 805 } else {
796 if (ctx->cfg.root_desc) 806 if (ctx->cfg.root_desc)
797 html_txt(ctx->cfg.root_desc); 807 html_txt(ctx->cfg.root_desc);
798 else if (ctx->cfg.index_info) 808 else if (ctx->cfg.index_info)
799 html_include(ctx->cfg.index_info); 809 html_include(ctx->cfg.index_info);
800 } 810 }
801 html("</td></tr></table>\n"); 811 html("</td></tr></table>\n");
802} 812}
803 813
804void cgit_print_pageheader(struct cgit_context *ctx) 814void cgit_print_pageheader(struct cgit_context *ctx)
805{ 815{
806 html("<div id='cgit'>"); 816 html("<div id='cgit'>");
807 if (!ctx->cfg.noheader) 817 if (!ctx->cfg.noheader)
808 print_header(ctx); 818 print_header(ctx);
809 819
810 html("<table class='tabs'><tr><td>\n"); 820 html("<table class='tabs'><tr><td>\n");
811 if (ctx->repo) { 821 if (ctx->repo) {
812 cgit_summary_link("summary", NULL, hc(ctx, "summary"), 822 cgit_summary_link("summary", NULL, hc(ctx, "summary"),
813 ctx->qry.head); 823 ctx->qry.head);
814 cgit_refs_link("refs", NULL, hc(ctx, "refs"), ctx->qry.head, 824 cgit_refs_link("refs", NULL, hc(ctx, "refs"), ctx->qry.head,
815 ctx->qry.sha1, NULL); 825 ctx->qry.sha1, NULL);
816 cgit_log_link("log", NULL, hc(ctx, "log"), ctx->qry.head, 826 cgit_log_link("log", NULL, hc(ctx, "log"), ctx->qry.head,
817 NULL, ctx->qry.vpath, 0, NULL, NULL, 827 NULL, ctx->qry.vpath, 0, NULL, NULL,
818 ctx->qry.showmsg); 828 ctx->qry.showmsg);
819 cgit_tree_link("tree", NULL, hc(ctx, "tree"), ctx->qry.head, 829 cgit_tree_link("tree", NULL, hc(ctx, "tree"), ctx->qry.head,
820 ctx->qry.sha1, ctx->qry.vpath); 830 ctx->qry.sha1, ctx->qry.vpath);
821 cgit_commit_link("commit", NULL, hc(ctx, "commit"), 831 cgit_commit_link("commit", NULL, hc(ctx, "commit"),
822 ctx->qry.head, ctx->qry.sha1, ctx->qry.vpath, 0); 832 ctx->qry.head, ctx->qry.sha1, ctx->qry.vpath, 0);
823 cgit_diff_link("diff", NULL, hc(ctx, "diff"), ctx->qry.head, 833 cgit_diff_link("diff", NULL, hc(ctx, "diff"), ctx->qry.head,
824 ctx->qry.sha1, ctx->qry.sha2, ctx->qry.vpath, 0); 834 ctx->qry.sha1, ctx->qry.sha2, ctx->qry.vpath, 0);
825 if (ctx->repo->max_stats) 835 if (ctx->repo->max_stats)
826 cgit_stats_link("stats", NULL, hc(ctx, "stats"), 836 cgit_stats_link("stats", NULL, hc(ctx, "stats"),
827 ctx->qry.head, ctx->qry.vpath); 837 ctx->qry.head, ctx->qry.vpath);
828 if (ctx->repo->readme) 838 if (ctx->repo->readme)
829 reporevlink("about", "about", NULL, 839 reporevlink("about", "about", NULL,
830 hc(ctx, "about"), ctx->qry.head, NULL, 840 hc(ctx, "about"), ctx->qry.head, NULL,
831 NULL); 841 NULL);
832 html("</td><td class='form'>"); 842 html("</td><td class='form'>");
833 html("<form class='right' method='get' action='"); 843 html("<form class='right' method='get' action='");
834 if (ctx->cfg.virtual_root) 844 if (ctx->cfg.virtual_root)
835 html_url_path(cgit_fileurl(ctx->qry.repo, "log", 845 html_url_path(cgit_fileurl(ctx->qry.repo, "log",
836 ctx->qry.vpath, NULL)); 846 ctx->qry.vpath, NULL));
837 html("'>\n"); 847 html("'>\n");
838 cgit_add_hidden_formfields(1, 0, "log"); 848 cgit_add_hidden_formfields(1, 0, "log");
839 html("<select name='qt'>\n"); 849 html("<select name='qt'>\n");
840 html_option("grep", "log msg", ctx->qry.grep); 850 html_option("grep", "log msg", ctx->qry.grep);
841 html_option("author", "author", ctx->qry.grep); 851 html_option("author", "author", ctx->qry.grep);
842 html_option("committer", "committer", ctx->qry.grep); 852 html_option("committer", "committer", ctx->qry.grep);
843 html_option("range", "range", ctx->qry.grep); 853 html_option("range", "range", ctx->qry.grep);
844 html("</select>\n"); 854 html("</select>\n");
845 html("<input class='txt' type='text' size='10' name='q' value='"); 855 html("<input class='txt' type='text' size='10' name='q' value='");
846 html_attr(ctx->qry.search); 856 html_attr(ctx->qry.search);
847 html("'/>\n"); 857 html("'/>\n");
848 html("<input type='submit' value='search'/>\n"); 858 html("<input type='submit' value='search'/>\n");
849 html("</form>\n"); 859 html("</form>\n");
850 } else { 860 } else {
851 site_link(NULL, "index", NULL, hc(ctx, "repolist"), NULL, 0); 861 site_link(NULL, "index", NULL, hc(ctx, "repolist"), NULL, 0);
852 if (ctx->cfg.root_readme) 862 if (ctx->cfg.root_readme)
853 site_link("about", "about", NULL, hc(ctx, "about"), 863 site_link("about", "about", NULL, hc(ctx, "about"),
854 NULL, 0); 864 NULL, 0);
855 html("</td><td class='form'>"); 865 html("</td><td class='form'>");
856 html("<form method='get' action='"); 866 html("<form method='get' action='");
857 html_attr(cgit_rooturl()); 867 html_attr(cgit_rooturl());
858 html("'>\n"); 868 html("'>\n");
859 html("<input type='text' name='q' size='10' value='"); 869 html("<input type='text' name='q' size='10' value='");
860 html_attr(ctx->qry.search); 870 html_attr(ctx->qry.search);
861 html("'/>\n"); 871 html("'/>\n");
862 html("<input type='submit' value='search'/>\n"); 872 html("<input type='submit' value='search'/>\n");
863 html("</form>"); 873 html("</form>");
864 } 874 }
865 html("</td></tr></table>\n"); 875 html("</td></tr></table>\n");
866 if (ctx->qry.vpath) { 876 if (ctx->qry.vpath) {
867 html("<div class='path'>"); 877 html("<div class='path'>");
868 html("path: "); 878 html("path: ");
869 cgit_print_path_crumbs(ctx, ctx->qry.vpath); 879 cgit_print_path_crumbs(ctx, ctx->qry.vpath);
870 html("</div>"); 880 html("</div>");
871 } 881 }
872 html("<div class='content'>"); 882 html("<div class='content'>");
873} 883}
874 884
875void cgit_print_filemode(unsigned short mode) 885void cgit_print_filemode(unsigned short mode)
876{ 886{
877 if (S_ISDIR(mode)) 887 if (S_ISDIR(mode))
878 html("d"); 888 html("d");
879 else if (S_ISLNK(mode)) 889 else if (S_ISLNK(mode))
880 html("l"); 890 html("l");
881 else if (S_ISGITLINK(mode)) 891 else if (S_ISGITLINK(mode))
882 html("m"); 892 html("m");
883 else 893 else
884 html("-"); 894 html("-");
885 html_fileperm(mode >> 6); 895 html_fileperm(mode >> 6);
886 html_fileperm(mode >> 3); 896 html_fileperm(mode >> 3);
887 html_fileperm(mode); 897 html_fileperm(mode);
888} 898}
889 899
890void cgit_print_snapshot_links(const char *repo, const char *head, 900void cgit_print_snapshot_links(const char *repo, const char *head,
891 const char *hex, int snapshots) 901 const char *hex, int snapshots)
892{ 902{
893 const struct cgit_snapshot_format* f; 903 const struct cgit_snapshot_format* f;
894 char *prefix; 904 char *prefix;
895 char *filename; 905 char *filename;
896 unsigned char sha1[20]; 906 unsigned char sha1[20];
897 907
898 if (get_sha1(fmt("refs/tags/%s", hex), sha1) == 0 && 908 if (get_sha1(fmt("refs/tags/%s", hex), sha1) == 0 &&
899 (hex[0] == 'v' || hex[0] == 'V') && isdigit(hex[1])) 909 (hex[0] == 'v' || hex[0] == 'V') && isdigit(hex[1]))
900 hex++; 910 hex++;
901 prefix = xstrdup(fmt("%s-%s", cgit_repobasename(repo), hex)); 911 prefix = xstrdup(fmt("%s-%s", cgit_repobasename(repo), hex));
902 for (f = cgit_snapshot_formats; f->suffix; f++) { 912 for (f = cgit_snapshot_formats; f->suffix; f++) {
903 if (!(snapshots & f->bit)) 913 if (!(snapshots & f->bit))
904 continue; 914 continue;
905 filename = fmt("%s%s", prefix, f->suffix); 915 filename = fmt("%s%s", prefix, f->suffix);
906 cgit_snapshot_link(filename, NULL, NULL, NULL, NULL, filename); 916 cgit_snapshot_link(filename, NULL, NULL, NULL, NULL, filename);
907 html("<br/>"); 917 html("<br/>");
908 } 918 }
909} 919}