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