summaryrefslogtreecommitdiffabout
Unidiff
Diffstat (more/less context) (ignore whitespace changes)
-rw-r--r--cgit.c3
-rw-r--r--cgit.css5
-rw-r--r--cgit.h1
-rw-r--r--cgitrc.5.txt4
-rwxr-xr-xfilters/syntax-highlighting.sh29
-rw-r--r--ui-tree.c6
6 files changed, 31 insertions, 17 deletions
diff --git a/cgit.c b/cgit.c
index bfde9f9..08cb5d2 100644
--- a/cgit.c
+++ b/cgit.c
@@ -1,464 +1,467 @@
1/* cgit.c: cgi for the git scm 1/* cgit.c: cgi for the git scm
2 * 2 *
3 * Copyright (C) 2006 Lars Hjemli 3 * Copyright (C) 2006 Lars Hjemli
4 * 4 *
5 * Licensed under GNU General Public License v2 5 * Licensed under GNU General Public License v2
6 * (see COPYING for full license text) 6 * (see COPYING for full license text)
7 */ 7 */
8 8
9#include "cgit.h" 9#include "cgit.h"
10#include "cache.h" 10#include "cache.h"
11#include "cmd.h" 11#include "cmd.h"
12#include "configfile.h" 12#include "configfile.h"
13#include "html.h" 13#include "html.h"
14#include "ui-shared.h" 14#include "ui-shared.h"
15#include "ui-stats.h" 15#include "ui-stats.h"
16#include "scan-tree.h" 16#include "scan-tree.h"
17 17
18const char *cgit_version = CGIT_VERSION; 18const char *cgit_version = CGIT_VERSION;
19 19
20void add_mimetype(const char *name, const char *value) 20void add_mimetype(const char *name, const char *value)
21{ 21{
22 struct string_list_item *item; 22 struct string_list_item *item;
23 23
24 item = string_list_insert(xstrdup(name), &ctx.cfg.mimetypes); 24 item = string_list_insert(xstrdup(name), &ctx.cfg.mimetypes);
25 item->util = xstrdup(value); 25 item->util = xstrdup(value);
26} 26}
27 27
28struct cgit_filter *new_filter(const char *cmd, int extra_args) 28struct cgit_filter *new_filter(const char *cmd, int extra_args)
29{ 29{
30 struct cgit_filter *f; 30 struct cgit_filter *f;
31 31
32 if (!cmd || !cmd[0]) 32 if (!cmd || !cmd[0])
33 return NULL; 33 return NULL;
34 34
35 f = xmalloc(sizeof(struct cgit_filter)); 35 f = xmalloc(sizeof(struct cgit_filter));
36 f->cmd = xstrdup(cmd); 36 f->cmd = xstrdup(cmd);
37 f->argv = xmalloc((2 + extra_args) * sizeof(char *)); 37 f->argv = xmalloc((2 + extra_args) * sizeof(char *));
38 f->argv[0] = f->cmd; 38 f->argv[0] = f->cmd;
39 f->argv[1] = NULL; 39 f->argv[1] = NULL;
40 return f; 40 return f;
41} 41}
42 42
43static void process_cached_repolist(const char *path); 43static void process_cached_repolist(const char *path);
44 44
45void repo_config(struct cgit_repo *repo, const char *name, const char *value) 45void repo_config(struct cgit_repo *repo, const char *name, const char *value)
46{ 46{
47 if (!strcmp(name, "name")) 47 if (!strcmp(name, "name"))
48 repo->name = xstrdup(value); 48 repo->name = xstrdup(value);
49 else if (!strcmp(name, "clone-url")) 49 else if (!strcmp(name, "clone-url"))
50 repo->clone_url = xstrdup(value); 50 repo->clone_url = xstrdup(value);
51 else if (!strcmp(name, "desc")) 51 else if (!strcmp(name, "desc"))
52 repo->desc = xstrdup(value); 52 repo->desc = xstrdup(value);
53 else if (!strcmp(name, "owner")) 53 else if (!strcmp(name, "owner"))
54 repo->owner = xstrdup(value); 54 repo->owner = xstrdup(value);
55 else if (!strcmp(name, "defbranch")) 55 else if (!strcmp(name, "defbranch"))
56 repo->defbranch = xstrdup(value); 56 repo->defbranch = xstrdup(value);
57 else if (!strcmp(name, "snapshots")) 57 else if (!strcmp(name, "snapshots"))
58 repo->snapshots = ctx.cfg.snapshots & cgit_parse_snapshots_mask(value); 58 repo->snapshots = ctx.cfg.snapshots & cgit_parse_snapshots_mask(value);
59 else if (!strcmp(name, "enable-log-filecount")) 59 else if (!strcmp(name, "enable-log-filecount"))
60 repo->enable_log_filecount = ctx.cfg.enable_log_filecount * atoi(value); 60 repo->enable_log_filecount = ctx.cfg.enable_log_filecount * atoi(value);
61 else if (!strcmp(name, "enable-log-linecount")) 61 else if (!strcmp(name, "enable-log-linecount"))
62 repo->enable_log_linecount = ctx.cfg.enable_log_linecount * atoi(value); 62 repo->enable_log_linecount = ctx.cfg.enable_log_linecount * atoi(value);
63 else if (!strcmp(name, "max-stats")) 63 else if (!strcmp(name, "max-stats"))
64 repo->max_stats = cgit_find_stats_period(value, NULL); 64 repo->max_stats = cgit_find_stats_period(value, NULL);
65 else if (!strcmp(name, "module-link")) 65 else if (!strcmp(name, "module-link"))
66 repo->module_link= xstrdup(value); 66 repo->module_link= xstrdup(value);
67 else if (!strcmp(name, "section")) 67 else if (!strcmp(name, "section"))
68 repo->section = xstrdup(value); 68 repo->section = xstrdup(value);
69 else if (!strcmp(name, "readme") && value != NULL) { 69 else if (!strcmp(name, "readme") && value != NULL) {
70 if (*value == '/') 70 if (*value == '/')
71 repo->readme = xstrdup(value); 71 repo->readme = xstrdup(value);
72 else 72 else
73 repo->readme = xstrdup(fmt("%s/%s", repo->path, value)); 73 repo->readme = xstrdup(fmt("%s/%s", repo->path, value));
74 } else if (ctx.cfg.enable_filter_overrides) { 74 } else if (ctx.cfg.enable_filter_overrides) {
75 if (!strcmp(name, "about-filter")) 75 if (!strcmp(name, "about-filter"))
76 repo->about_filter = new_filter(value, 0); 76 repo->about_filter = new_filter(value, 0);
77 else if (!strcmp(name, "commit-filter")) 77 else if (!strcmp(name, "commit-filter"))
78 repo->commit_filter = new_filter(value, 0); 78 repo->commit_filter = new_filter(value, 0);
79 else if (!strcmp(name, "source-filter")) 79 else if (!strcmp(name, "source-filter"))
80 repo->source_filter = new_filter(value, 1); 80 repo->source_filter = new_filter(value, 1);
81 } 81 }
82} 82}
83 83
84void config_cb(const char *name, const char *value) 84void config_cb(const char *name, const char *value)
85{ 85{
86 if (!strcmp(name, "section") || !strcmp(name, "repo.group")) 86 if (!strcmp(name, "section") || !strcmp(name, "repo.group"))
87 ctx.cfg.section = xstrdup(value); 87 ctx.cfg.section = xstrdup(value);
88 else if (!strcmp(name, "repo.url")) 88 else if (!strcmp(name, "repo.url"))
89 ctx.repo = cgit_add_repo(value); 89 ctx.repo = cgit_add_repo(value);
90 else if (ctx.repo && !strcmp(name, "repo.path")) 90 else if (ctx.repo && !strcmp(name, "repo.path"))
91 ctx.repo->path = trim_end(value, '/'); 91 ctx.repo->path = trim_end(value, '/');
92 else if (ctx.repo && !prefixcmp(name, "repo.")) 92 else if (ctx.repo && !prefixcmp(name, "repo."))
93 repo_config(ctx.repo, name + 5, value); 93 repo_config(ctx.repo, name + 5, value);
94 else if (!strcmp(name, "root-title")) 94 else if (!strcmp(name, "root-title"))
95 ctx.cfg.root_title = xstrdup(value); 95 ctx.cfg.root_title = xstrdup(value);
96 else if (!strcmp(name, "root-desc")) 96 else if (!strcmp(name, "root-desc"))
97 ctx.cfg.root_desc = xstrdup(value); 97 ctx.cfg.root_desc = xstrdup(value);
98 else if (!strcmp(name, "root-readme")) 98 else if (!strcmp(name, "root-readme"))
99 ctx.cfg.root_readme = xstrdup(value); 99 ctx.cfg.root_readme = xstrdup(value);
100 else if (!strcmp(name, "css")) 100 else if (!strcmp(name, "css"))
101 ctx.cfg.css = xstrdup(value); 101 ctx.cfg.css = xstrdup(value);
102 else if (!strcmp(name, "favicon")) 102 else if (!strcmp(name, "favicon"))
103 ctx.cfg.favicon = xstrdup(value); 103 ctx.cfg.favicon = xstrdup(value);
104 else if (!strcmp(name, "footer")) 104 else if (!strcmp(name, "footer"))
105 ctx.cfg.footer = xstrdup(value); 105 ctx.cfg.footer = xstrdup(value);
106 else if (!strcmp(name, "head-include")) 106 else if (!strcmp(name, "head-include"))
107 ctx.cfg.head_include = xstrdup(value); 107 ctx.cfg.head_include = xstrdup(value);
108 else if (!strcmp(name, "header")) 108 else if (!strcmp(name, "header"))
109 ctx.cfg.header = xstrdup(value); 109 ctx.cfg.header = xstrdup(value);
110 else if (!strcmp(name, "logo")) 110 else if (!strcmp(name, "logo"))
111 ctx.cfg.logo = xstrdup(value); 111 ctx.cfg.logo = xstrdup(value);
112 else if (!strcmp(name, "index-header")) 112 else if (!strcmp(name, "index-header"))
113 ctx.cfg.index_header = xstrdup(value); 113 ctx.cfg.index_header = xstrdup(value);
114 else if (!strcmp(name, "index-info")) 114 else if (!strcmp(name, "index-info"))
115 ctx.cfg.index_info = xstrdup(value); 115 ctx.cfg.index_info = xstrdup(value);
116 else if (!strcmp(name, "logo-link")) 116 else if (!strcmp(name, "logo-link"))
117 ctx.cfg.logo_link = xstrdup(value); 117 ctx.cfg.logo_link = xstrdup(value);
118 else if (!strcmp(name, "module-link")) 118 else if (!strcmp(name, "module-link"))
119 ctx.cfg.module_link = xstrdup(value); 119 ctx.cfg.module_link = xstrdup(value);
120 else if (!strcmp(name, "virtual-root")) { 120 else if (!strcmp(name, "virtual-root")) {
121 ctx.cfg.virtual_root = trim_end(value, '/'); 121 ctx.cfg.virtual_root = trim_end(value, '/');
122 if (!ctx.cfg.virtual_root && (!strcmp(value, "/"))) 122 if (!ctx.cfg.virtual_root && (!strcmp(value, "/")))
123 ctx.cfg.virtual_root = ""; 123 ctx.cfg.virtual_root = "";
124 } else if (!strcmp(name, "nocache")) 124 } else if (!strcmp(name, "nocache"))
125 ctx.cfg.nocache = atoi(value); 125 ctx.cfg.nocache = atoi(value);
126 else if (!strcmp(name, "noplainemail")) 126 else if (!strcmp(name, "noplainemail"))
127 ctx.cfg.noplainemail = atoi(value); 127 ctx.cfg.noplainemail = atoi(value);
128 else if (!strcmp(name, "noheader")) 128 else if (!strcmp(name, "noheader"))
129 ctx.cfg.noheader = atoi(value); 129 ctx.cfg.noheader = atoi(value);
130 else if (!strcmp(name, "snapshots")) 130 else if (!strcmp(name, "snapshots"))
131 ctx.cfg.snapshots = cgit_parse_snapshots_mask(value); 131 ctx.cfg.snapshots = cgit_parse_snapshots_mask(value);
132 else if (!strcmp(name, "enable-filter-overrides")) 132 else if (!strcmp(name, "enable-filter-overrides"))
133 ctx.cfg.enable_filter_overrides = atoi(value); 133 ctx.cfg.enable_filter_overrides = atoi(value);
134 else if (!strcmp(name, "enable-index-links")) 134 else if (!strcmp(name, "enable-index-links"))
135 ctx.cfg.enable_index_links = atoi(value); 135 ctx.cfg.enable_index_links = atoi(value);
136 else if (!strcmp(name, "enable-log-filecount")) 136 else if (!strcmp(name, "enable-log-filecount"))
137 ctx.cfg.enable_log_filecount = atoi(value); 137 ctx.cfg.enable_log_filecount = atoi(value);
138 else if (!strcmp(name, "enable-log-linecount")) 138 else if (!strcmp(name, "enable-log-linecount"))
139 ctx.cfg.enable_log_linecount = atoi(value); 139 ctx.cfg.enable_log_linecount = atoi(value);
140 else if (!strcmp(name, "enable-tree-linenumbers")) 140 else if (!strcmp(name, "enable-tree-linenumbers"))
141 ctx.cfg.enable_tree_linenumbers = atoi(value); 141 ctx.cfg.enable_tree_linenumbers = atoi(value);
142 else if (!strcmp(name, "max-stats")) 142 else if (!strcmp(name, "max-stats"))
143 ctx.cfg.max_stats = cgit_find_stats_period(value, NULL); 143 ctx.cfg.max_stats = cgit_find_stats_period(value, NULL);
144 else if (!strcmp(name, "cache-size")) 144 else if (!strcmp(name, "cache-size"))
145 ctx.cfg.cache_size = atoi(value); 145 ctx.cfg.cache_size = atoi(value);
146 else if (!strcmp(name, "cache-root")) 146 else if (!strcmp(name, "cache-root"))
147 ctx.cfg.cache_root = xstrdup(value); 147 ctx.cfg.cache_root = xstrdup(value);
148 else if (!strcmp(name, "cache-root-ttl")) 148 else if (!strcmp(name, "cache-root-ttl"))
149 ctx.cfg.cache_root_ttl = atoi(value); 149 ctx.cfg.cache_root_ttl = atoi(value);
150 else if (!strcmp(name, "cache-repo-ttl")) 150 else if (!strcmp(name, "cache-repo-ttl"))
151 ctx.cfg.cache_repo_ttl = atoi(value); 151 ctx.cfg.cache_repo_ttl = atoi(value);
152 else if (!strcmp(name, "cache-scanrc-ttl")) 152 else if (!strcmp(name, "cache-scanrc-ttl"))
153 ctx.cfg.cache_scanrc_ttl = atoi(value); 153 ctx.cfg.cache_scanrc_ttl = atoi(value);
154 else if (!strcmp(name, "cache-static-ttl")) 154 else if (!strcmp(name, "cache-static-ttl"))
155 ctx.cfg.cache_static_ttl = atoi(value); 155 ctx.cfg.cache_static_ttl = atoi(value);
156 else if (!strcmp(name, "cache-dynamic-ttl")) 156 else if (!strcmp(name, "cache-dynamic-ttl"))
157 ctx.cfg.cache_dynamic_ttl = atoi(value); 157 ctx.cfg.cache_dynamic_ttl = atoi(value);
158 else if (!strcmp(name, "about-filter")) 158 else if (!strcmp(name, "about-filter"))
159 ctx.cfg.about_filter = new_filter(value, 0); 159 ctx.cfg.about_filter = new_filter(value, 0);
160 else if (!strcmp(name, "commit-filter")) 160 else if (!strcmp(name, "commit-filter"))
161 ctx.cfg.commit_filter = new_filter(value, 0); 161 ctx.cfg.commit_filter = new_filter(value, 0);
162 else if (!strcmp(name, "embedded")) 162 else if (!strcmp(name, "embedded"))
163 ctx.cfg.embedded = atoi(value); 163 ctx.cfg.embedded = atoi(value);
164 else if (!strcmp(name, "max-message-length")) 164 else if (!strcmp(name, "max-message-length"))
165 ctx.cfg.max_msg_len = atoi(value); 165 ctx.cfg.max_msg_len = atoi(value);
166 else if (!strcmp(name, "max-repodesc-length")) 166 else if (!strcmp(name, "max-repodesc-length"))
167 ctx.cfg.max_repodesc_len = atoi(value); 167 ctx.cfg.max_repodesc_len = atoi(value);
168 else if (!strcmp(name, "max-blob-size"))
169 ctx.cfg.max_blob_size = atoi(value);
168 else if (!strcmp(name, "max-repo-count")) 170 else if (!strcmp(name, "max-repo-count"))
169 ctx.cfg.max_repo_count = atoi(value); 171 ctx.cfg.max_repo_count = atoi(value);
170 else if (!strcmp(name, "max-commit-count")) 172 else if (!strcmp(name, "max-commit-count"))
171 ctx.cfg.max_commit_count = atoi(value); 173 ctx.cfg.max_commit_count = atoi(value);
172 else if (!strcmp(name, "scan-path")) 174 else if (!strcmp(name, "scan-path"))
173 if (!ctx.cfg.nocache && ctx.cfg.cache_size) 175 if (!ctx.cfg.nocache && ctx.cfg.cache_size)
174 process_cached_repolist(value); 176 process_cached_repolist(value);
175 else 177 else
176 scan_tree(value, repo_config); 178 scan_tree(value, repo_config);
177 else if (!strcmp(name, "source-filter")) 179 else if (!strcmp(name, "source-filter"))
178 ctx.cfg.source_filter = new_filter(value, 1); 180 ctx.cfg.source_filter = new_filter(value, 1);
179 else if (!strcmp(name, "summary-log")) 181 else if (!strcmp(name, "summary-log"))
180 ctx.cfg.summary_log = atoi(value); 182 ctx.cfg.summary_log = atoi(value);
181 else if (!strcmp(name, "summary-branches")) 183 else if (!strcmp(name, "summary-branches"))
182 ctx.cfg.summary_branches = atoi(value); 184 ctx.cfg.summary_branches = atoi(value);
183 else if (!strcmp(name, "summary-tags")) 185 else if (!strcmp(name, "summary-tags"))
184 ctx.cfg.summary_tags = atoi(value); 186 ctx.cfg.summary_tags = atoi(value);
185 else if (!strcmp(name, "agefile")) 187 else if (!strcmp(name, "agefile"))
186 ctx.cfg.agefile = xstrdup(value); 188 ctx.cfg.agefile = xstrdup(value);
187 else if (!strcmp(name, "renamelimit")) 189 else if (!strcmp(name, "renamelimit"))
188 ctx.cfg.renamelimit = atoi(value); 190 ctx.cfg.renamelimit = atoi(value);
189 else if (!strcmp(name, "robots")) 191 else if (!strcmp(name, "robots"))
190 ctx.cfg.robots = xstrdup(value); 192 ctx.cfg.robots = xstrdup(value);
191 else if (!strcmp(name, "clone-prefix")) 193 else if (!strcmp(name, "clone-prefix"))
192 ctx.cfg.clone_prefix = xstrdup(value); 194 ctx.cfg.clone_prefix = xstrdup(value);
193 else if (!strcmp(name, "local-time")) 195 else if (!strcmp(name, "local-time"))
194 ctx.cfg.local_time = atoi(value); 196 ctx.cfg.local_time = atoi(value);
195 else if (!prefixcmp(name, "mimetype.")) 197 else if (!prefixcmp(name, "mimetype."))
196 add_mimetype(name + 9, value); 198 add_mimetype(name + 9, value);
197 else if (!strcmp(name, "include")) 199 else if (!strcmp(name, "include"))
198 parse_configfile(value, config_cb); 200 parse_configfile(value, config_cb);
199} 201}
200 202
201static void querystring_cb(const char *name, const char *value) 203static void querystring_cb(const char *name, const char *value)
202{ 204{
203 if (!value) 205 if (!value)
204 value = ""; 206 value = "";
205 207
206 if (!strcmp(name,"r")) { 208 if (!strcmp(name,"r")) {
207 ctx.qry.repo = xstrdup(value); 209 ctx.qry.repo = xstrdup(value);
208 ctx.repo = cgit_get_repoinfo(value); 210 ctx.repo = cgit_get_repoinfo(value);
209 } else if (!strcmp(name, "p")) { 211 } else if (!strcmp(name, "p")) {
210 ctx.qry.page = xstrdup(value); 212 ctx.qry.page = xstrdup(value);
211 } else if (!strcmp(name, "url")) { 213 } else if (!strcmp(name, "url")) {
212 if (*value == '/') 214 if (*value == '/')
213 value++; 215 value++;
214 ctx.qry.url = xstrdup(value); 216 ctx.qry.url = xstrdup(value);
215 cgit_parse_url(value); 217 cgit_parse_url(value);
216 } else if (!strcmp(name, "qt")) { 218 } else if (!strcmp(name, "qt")) {
217 ctx.qry.grep = xstrdup(value); 219 ctx.qry.grep = xstrdup(value);
218 } else if (!strcmp(name, "q")) { 220 } else if (!strcmp(name, "q")) {
219 ctx.qry.search = xstrdup(value); 221 ctx.qry.search = xstrdup(value);
220 } else if (!strcmp(name, "h")) { 222 } else if (!strcmp(name, "h")) {
221 ctx.qry.head = xstrdup(value); 223 ctx.qry.head = xstrdup(value);
222 ctx.qry.has_symref = 1; 224 ctx.qry.has_symref = 1;
223 } else if (!strcmp(name, "id")) { 225 } else if (!strcmp(name, "id")) {
224 ctx.qry.sha1 = xstrdup(value); 226 ctx.qry.sha1 = xstrdup(value);
225 ctx.qry.has_sha1 = 1; 227 ctx.qry.has_sha1 = 1;
226 } else if (!strcmp(name, "id2")) { 228 } else if (!strcmp(name, "id2")) {
227 ctx.qry.sha2 = xstrdup(value); 229 ctx.qry.sha2 = xstrdup(value);
228 ctx.qry.has_sha1 = 1; 230 ctx.qry.has_sha1 = 1;
229 } else if (!strcmp(name, "ofs")) { 231 } else if (!strcmp(name, "ofs")) {
230 ctx.qry.ofs = atoi(value); 232 ctx.qry.ofs = atoi(value);
231 } else if (!strcmp(name, "path")) { 233 } else if (!strcmp(name, "path")) {
232 ctx.qry.path = trim_end(value, '/'); 234 ctx.qry.path = trim_end(value, '/');
233 } else if (!strcmp(name, "name")) { 235 } else if (!strcmp(name, "name")) {
234 ctx.qry.name = xstrdup(value); 236 ctx.qry.name = xstrdup(value);
235 } else if (!strcmp(name, "mimetype")) { 237 } else if (!strcmp(name, "mimetype")) {
236 ctx.qry.mimetype = xstrdup(value); 238 ctx.qry.mimetype = xstrdup(value);
237 } else if (!strcmp(name, "s")){ 239 } else if (!strcmp(name, "s")){
238 ctx.qry.sort = xstrdup(value); 240 ctx.qry.sort = xstrdup(value);
239 } else if (!strcmp(name, "showmsg")) { 241 } else if (!strcmp(name, "showmsg")) {
240 ctx.qry.showmsg = atoi(value); 242 ctx.qry.showmsg = atoi(value);
241 } else if (!strcmp(name, "period")) { 243 } else if (!strcmp(name, "period")) {
242 ctx.qry.period = xstrdup(value); 244 ctx.qry.period = xstrdup(value);
243 } 245 }
244} 246}
245 247
246char *xstrdupn(const char *str) 248char *xstrdupn(const char *str)
247{ 249{
248 return (str ? xstrdup(str) : NULL); 250 return (str ? xstrdup(str) : NULL);
249} 251}
250 252
251static void prepare_context(struct cgit_context *ctx) 253static void prepare_context(struct cgit_context *ctx)
252{ 254{
253 memset(ctx, 0, sizeof(ctx)); 255 memset(ctx, 0, sizeof(ctx));
254 ctx->cfg.agefile = "info/web/last-modified"; 256 ctx->cfg.agefile = "info/web/last-modified";
255 ctx->cfg.nocache = 0; 257 ctx->cfg.nocache = 0;
256 ctx->cfg.cache_size = 0; 258 ctx->cfg.cache_size = 0;
257 ctx->cfg.cache_dynamic_ttl = 5; 259 ctx->cfg.cache_dynamic_ttl = 5;
258 ctx->cfg.cache_max_create_time = 5; 260 ctx->cfg.cache_max_create_time = 5;
259 ctx->cfg.cache_repo_ttl = 5; 261 ctx->cfg.cache_repo_ttl = 5;
260 ctx->cfg.cache_root = CGIT_CACHE_ROOT; 262 ctx->cfg.cache_root = CGIT_CACHE_ROOT;
261 ctx->cfg.cache_root_ttl = 5; 263 ctx->cfg.cache_root_ttl = 5;
262 ctx->cfg.cache_scanrc_ttl = 15; 264 ctx->cfg.cache_scanrc_ttl = 15;
263 ctx->cfg.cache_static_ttl = -1; 265 ctx->cfg.cache_static_ttl = -1;
264 ctx->cfg.css = "/cgit.css"; 266 ctx->cfg.css = "/cgit.css";
265 ctx->cfg.logo = "/cgit.png"; 267 ctx->cfg.logo = "/cgit.png";
266 ctx->cfg.local_time = 0; 268 ctx->cfg.local_time = 0;
267 ctx->cfg.enable_tree_linenumbers = 1; 269 ctx->cfg.enable_tree_linenumbers = 1;
268 ctx->cfg.max_repo_count = 50; 270 ctx->cfg.max_repo_count = 50;
269 ctx->cfg.max_commit_count = 50; 271 ctx->cfg.max_commit_count = 50;
270 ctx->cfg.max_lock_attempts = 5; 272 ctx->cfg.max_lock_attempts = 5;
271 ctx->cfg.max_msg_len = 80; 273 ctx->cfg.max_msg_len = 80;
272 ctx->cfg.max_repodesc_len = 80; 274 ctx->cfg.max_repodesc_len = 80;
275 ctx->cfg.max_blob_size = 0;
273 ctx->cfg.max_stats = 0; 276 ctx->cfg.max_stats = 0;
274 ctx->cfg.module_link = "./?repo=%s&page=commit&id=%s"; 277 ctx->cfg.module_link = "./?repo=%s&page=commit&id=%s";
275 ctx->cfg.renamelimit = -1; 278 ctx->cfg.renamelimit = -1;
276 ctx->cfg.robots = "index, nofollow"; 279 ctx->cfg.robots = "index, nofollow";
277 ctx->cfg.root_title = "Git repository browser"; 280 ctx->cfg.root_title = "Git repository browser";
278 ctx->cfg.root_desc = "a fast webinterface for the git dscm"; 281 ctx->cfg.root_desc = "a fast webinterface for the git dscm";
279 ctx->cfg.script_name = CGIT_SCRIPT_NAME; 282 ctx->cfg.script_name = CGIT_SCRIPT_NAME;
280 ctx->cfg.section = ""; 283 ctx->cfg.section = "";
281 ctx->cfg.summary_branches = 10; 284 ctx->cfg.summary_branches = 10;
282 ctx->cfg.summary_log = 10; 285 ctx->cfg.summary_log = 10;
283 ctx->cfg.summary_tags = 10; 286 ctx->cfg.summary_tags = 10;
284 ctx->env.cgit_config = xstrdupn(getenv("CGIT_CONFIG")); 287 ctx->env.cgit_config = xstrdupn(getenv("CGIT_CONFIG"));
285 ctx->env.http_host = xstrdupn(getenv("HTTP_HOST")); 288 ctx->env.http_host = xstrdupn(getenv("HTTP_HOST"));
286 ctx->env.https = xstrdupn(getenv("HTTPS")); 289 ctx->env.https = xstrdupn(getenv("HTTPS"));
287 ctx->env.no_http = xstrdupn(getenv("NO_HTTP")); 290 ctx->env.no_http = xstrdupn(getenv("NO_HTTP"));
288 ctx->env.path_info = xstrdupn(getenv("PATH_INFO")); 291 ctx->env.path_info = xstrdupn(getenv("PATH_INFO"));
289 ctx->env.query_string = xstrdupn(getenv("QUERY_STRING")); 292 ctx->env.query_string = xstrdupn(getenv("QUERY_STRING"));
290 ctx->env.request_method = xstrdupn(getenv("REQUEST_METHOD")); 293 ctx->env.request_method = xstrdupn(getenv("REQUEST_METHOD"));
291 ctx->env.script_name = xstrdupn(getenv("SCRIPT_NAME")); 294 ctx->env.script_name = xstrdupn(getenv("SCRIPT_NAME"));
292 ctx->env.server_name = xstrdupn(getenv("SERVER_NAME")); 295 ctx->env.server_name = xstrdupn(getenv("SERVER_NAME"));
293 ctx->env.server_port = xstrdupn(getenv("SERVER_PORT")); 296 ctx->env.server_port = xstrdupn(getenv("SERVER_PORT"));
294 ctx->page.mimetype = "text/html"; 297 ctx->page.mimetype = "text/html";
295 ctx->page.charset = PAGE_ENCODING; 298 ctx->page.charset = PAGE_ENCODING;
296 ctx->page.filename = NULL; 299 ctx->page.filename = NULL;
297 ctx->page.size = 0; 300 ctx->page.size = 0;
298 ctx->page.modified = time(NULL); 301 ctx->page.modified = time(NULL);
299 ctx->page.expires = ctx->page.modified; 302 ctx->page.expires = ctx->page.modified;
300 ctx->page.etag = NULL; 303 ctx->page.etag = NULL;
301 memset(&ctx->cfg.mimetypes, 0, sizeof(struct string_list)); 304 memset(&ctx->cfg.mimetypes, 0, sizeof(struct string_list));
302 if (ctx->env.script_name) 305 if (ctx->env.script_name)
303 ctx->cfg.script_name = ctx->env.script_name; 306 ctx->cfg.script_name = ctx->env.script_name;
304 if (ctx->env.query_string) 307 if (ctx->env.query_string)
305 ctx->qry.raw = ctx->env.query_string; 308 ctx->qry.raw = ctx->env.query_string;
306 if (!ctx->env.cgit_config) 309 if (!ctx->env.cgit_config)
307 ctx->env.cgit_config = CGIT_CONFIG; 310 ctx->env.cgit_config = CGIT_CONFIG;
308} 311}
309 312
310struct refmatch { 313struct refmatch {
311 char *req_ref; 314 char *req_ref;
312 char *first_ref; 315 char *first_ref;
313 int match; 316 int match;
314}; 317};
315 318
316int find_current_ref(const char *refname, const unsigned char *sha1, 319int find_current_ref(const char *refname, const unsigned char *sha1,
317 int flags, void *cb_data) 320 int flags, void *cb_data)
318{ 321{
319 struct refmatch *info; 322 struct refmatch *info;
320 323
321 info = (struct refmatch *)cb_data; 324 info = (struct refmatch *)cb_data;
322 if (!strcmp(refname, info->req_ref)) 325 if (!strcmp(refname, info->req_ref))
323 info->match = 1; 326 info->match = 1;
324 if (!info->first_ref) 327 if (!info->first_ref)
325 info->first_ref = xstrdup(refname); 328 info->first_ref = xstrdup(refname);
326 return info->match; 329 return info->match;
327} 330}
328 331
329char *find_default_branch(struct cgit_repo *repo) 332char *find_default_branch(struct cgit_repo *repo)
330{ 333{
331 struct refmatch info; 334 struct refmatch info;
332 char *ref; 335 char *ref;
333 336
334 info.req_ref = repo->defbranch; 337 info.req_ref = repo->defbranch;
335 info.first_ref = NULL; 338 info.first_ref = NULL;
336 info.match = 0; 339 info.match = 0;
337 for_each_branch_ref(find_current_ref, &info); 340 for_each_branch_ref(find_current_ref, &info);
338 if (info.match) 341 if (info.match)
339 ref = info.req_ref; 342 ref = info.req_ref;
340 else 343 else
341 ref = info.first_ref; 344 ref = info.first_ref;
342 if (ref) 345 if (ref)
343 ref = xstrdup(ref); 346 ref = xstrdup(ref);
344 return ref; 347 return ref;
345} 348}
346 349
347static int prepare_repo_cmd(struct cgit_context *ctx) 350static int prepare_repo_cmd(struct cgit_context *ctx)
348{ 351{
349 char *tmp; 352 char *tmp;
350 unsigned char sha1[20]; 353 unsigned char sha1[20];
351 int nongit = 0; 354 int nongit = 0;
352 355
353 setenv("GIT_DIR", ctx->repo->path, 1); 356 setenv("GIT_DIR", ctx->repo->path, 1);
354 setup_git_directory_gently(&nongit); 357 setup_git_directory_gently(&nongit);
355 if (nongit) { 358 if (nongit) {
356 ctx->page.title = fmt("%s - %s", ctx->cfg.root_title, 359 ctx->page.title = fmt("%s - %s", ctx->cfg.root_title,
357 "config error"); 360 "config error");
358 tmp = fmt("Not a git repository: '%s'", ctx->repo->path); 361 tmp = fmt("Not a git repository: '%s'", ctx->repo->path);
359 ctx->repo = NULL; 362 ctx->repo = NULL;
360 cgit_print_http_headers(ctx); 363 cgit_print_http_headers(ctx);
361 cgit_print_docstart(ctx); 364 cgit_print_docstart(ctx);
362 cgit_print_pageheader(ctx); 365 cgit_print_pageheader(ctx);
363 cgit_print_error(tmp); 366 cgit_print_error(tmp);
364 cgit_print_docend(); 367 cgit_print_docend();
365 return 1; 368 return 1;
366 } 369 }
367 ctx->page.title = fmt("%s - %s", ctx->repo->name, ctx->repo->desc); 370 ctx->page.title = fmt("%s - %s", ctx->repo->name, ctx->repo->desc);
368 371
369 if (!ctx->qry.head) { 372 if (!ctx->qry.head) {
370 ctx->qry.nohead = 1; 373 ctx->qry.nohead = 1;
371 ctx->qry.head = find_default_branch(ctx->repo); 374 ctx->qry.head = find_default_branch(ctx->repo);
372 ctx->repo->defbranch = ctx->qry.head; 375 ctx->repo->defbranch = ctx->qry.head;
373 } 376 }
374 377
375 if (!ctx->qry.head) { 378 if (!ctx->qry.head) {
376 cgit_print_http_headers(ctx); 379 cgit_print_http_headers(ctx);
377 cgit_print_docstart(ctx); 380 cgit_print_docstart(ctx);
378 cgit_print_pageheader(ctx); 381 cgit_print_pageheader(ctx);
379 cgit_print_error("Repository seems to be empty"); 382 cgit_print_error("Repository seems to be empty");
380 cgit_print_docend(); 383 cgit_print_docend();
381 return 1; 384 return 1;
382 } 385 }
383 386
384 if (get_sha1(ctx->qry.head, sha1)) { 387 if (get_sha1(ctx->qry.head, sha1)) {
385 tmp = xstrdup(ctx->qry.head); 388 tmp = xstrdup(ctx->qry.head);
386 ctx->qry.head = ctx->repo->defbranch; 389 ctx->qry.head = ctx->repo->defbranch;
387 ctx->page.status = 404; 390 ctx->page.status = 404;
388 ctx->page.statusmsg = "not found"; 391 ctx->page.statusmsg = "not found";
389 cgit_print_http_headers(ctx); 392 cgit_print_http_headers(ctx);
390 cgit_print_docstart(ctx); 393 cgit_print_docstart(ctx);
391 cgit_print_pageheader(ctx); 394 cgit_print_pageheader(ctx);
392 cgit_print_error(fmt("Invalid branch: %s", tmp)); 395 cgit_print_error(fmt("Invalid branch: %s", tmp));
393 cgit_print_docend(); 396 cgit_print_docend();
394 return 1; 397 return 1;
395 } 398 }
396 return 0; 399 return 0;
397} 400}
398 401
399static void process_request(void *cbdata) 402static void process_request(void *cbdata)
400{ 403{
401 struct cgit_context *ctx = cbdata; 404 struct cgit_context *ctx = cbdata;
402 struct cgit_cmd *cmd; 405 struct cgit_cmd *cmd;
403 406
404 cmd = cgit_get_cmd(ctx); 407 cmd = cgit_get_cmd(ctx);
405 if (!cmd) { 408 if (!cmd) {
406 ctx->page.title = "cgit error"; 409 ctx->page.title = "cgit error";
407 cgit_print_http_headers(ctx); 410 cgit_print_http_headers(ctx);
408 cgit_print_docstart(ctx); 411 cgit_print_docstart(ctx);
409 cgit_print_pageheader(ctx); 412 cgit_print_pageheader(ctx);
410 cgit_print_error("Invalid request"); 413 cgit_print_error("Invalid request");
411 cgit_print_docend(); 414 cgit_print_docend();
412 return; 415 return;
413 } 416 }
414 417
415 if (cmd->want_repo && !ctx->repo) { 418 if (cmd->want_repo && !ctx->repo) {
416 cgit_print_http_headers(ctx); 419 cgit_print_http_headers(ctx);
417 cgit_print_docstart(ctx); 420 cgit_print_docstart(ctx);
418 cgit_print_pageheader(ctx); 421 cgit_print_pageheader(ctx);
419 cgit_print_error(fmt("No repository selected")); 422 cgit_print_error(fmt("No repository selected"));
420 cgit_print_docend(); 423 cgit_print_docend();
421 return; 424 return;
422 } 425 }
423 426
424 if (ctx->repo && prepare_repo_cmd(ctx)) 427 if (ctx->repo && prepare_repo_cmd(ctx))
425 return; 428 return;
426 429
427 if (cmd->want_layout) { 430 if (cmd->want_layout) {
428 cgit_print_http_headers(ctx); 431 cgit_print_http_headers(ctx);
429 cgit_print_docstart(ctx); 432 cgit_print_docstart(ctx);
430 cgit_print_pageheader(ctx); 433 cgit_print_pageheader(ctx);
431 } 434 }
432 435
433 cmd->fn(ctx); 436 cmd->fn(ctx);
434 437
435 if (cmd->want_layout) 438 if (cmd->want_layout)
436 cgit_print_docend(); 439 cgit_print_docend();
437} 440}
438 441
439int cmp_repos(const void *a, const void *b) 442int cmp_repos(const void *a, const void *b)
440{ 443{
441 const struct cgit_repo *ra = a, *rb = b; 444 const struct cgit_repo *ra = a, *rb = b;
442 return strcmp(ra->url, rb->url); 445 return strcmp(ra->url, rb->url);
443} 446}
444 447
445char *build_snapshot_setting(int bitmap) 448char *build_snapshot_setting(int bitmap)
446{ 449{
447 const struct cgit_snapshot_format *f; 450 const struct cgit_snapshot_format *f;
448 char *result = xstrdup(""); 451 char *result = xstrdup("");
449 char *tmp; 452 char *tmp;
450 int len; 453 int len;
451 454
452 for (f = cgit_snapshot_formats; f->suffix; f++) { 455 for (f = cgit_snapshot_formats; f->suffix; f++) {
453 if (f->bit & bitmap) { 456 if (f->bit & bitmap) {
454 tmp = result; 457 tmp = result;
455 result = xstrdup(fmt("%s%s ", tmp, f->suffix)); 458 result = xstrdup(fmt("%s%s ", tmp, f->suffix));
456 free(tmp); 459 free(tmp);
457 } 460 }
458 } 461 }
459 len = strlen(result); 462 len = strlen(result);
460 if (len) 463 if (len)
461 result[len - 1] = '\0'; 464 result[len - 1] = '\0';
462 return result; 465 return result;
463} 466}
464 467
diff --git a/cgit.css b/cgit.css
index c47ebc9..ef7d3c1 100644
--- a/cgit.css
+++ b/cgit.css
@@ -1,356 +1,361 @@
1body, table, form { 1body, table, form {
2 padding: 0em; 2 padding: 0em;
3 margin: 0em; 3 margin: 0em;
4} 4}
5 5
6body { 6body {
7 font-family: sans-serif; 7 font-family: sans-serif;
8 font-size: 10pt; 8 font-size: 10pt;
9 color: #333; 9 color: #333;
10 background: white; 10 background: white;
11 padding: 4px; 11 padding: 4px;
12} 12}
13 13
14a { 14a {
15 color: blue; 15 color: blue;
16 text-decoration: none; 16 text-decoration: none;
17} 17}
18 18
19a:hover { 19a:hover {
20 text-decoration: underline; 20 text-decoration: underline;
21} 21}
22 22
23table { 23table {
24 border-collapse: collapse; 24 border-collapse: collapse;
25} 25}
26 26
27table#header { 27table#header {
28 width: 100%; 28 width: 100%;
29 margin-bottom: 1em; 29 margin-bottom: 1em;
30} 30}
31 31
32table#header td.logo { 32table#header td.logo {
33 width: 96px; 33 width: 96px;
34} 34}
35 35
36table#header td.main { 36table#header td.main {
37 font-size: 250%; 37 font-size: 250%;
38 padding-left: 10px; 38 padding-left: 10px;
39 white-space: nowrap; 39 white-space: nowrap;
40} 40}
41 41
42table#header td.main a { 42table#header td.main a {
43 color: #000; 43 color: #000;
44} 44}
45 45
46table#header td.form { 46table#header td.form {
47 text-align: right; 47 text-align: right;
48 vertical-align: bottom; 48 vertical-align: bottom;
49 padding-right: 1em; 49 padding-right: 1em;
50 padding-bottom: 2px; 50 padding-bottom: 2px;
51 white-space: nowrap; 51 white-space: nowrap;
52} 52}
53 53
54table#header td.form form, 54table#header td.form form,
55table#header td.form input, 55table#header td.form input,
56table#header td.form select { 56table#header td.form select {
57 font-size: 90%; 57 font-size: 90%;
58} 58}
59 59
60table#header td.sub { 60table#header td.sub {
61 color: #777; 61 color: #777;
62 border-top: solid 1px #ccc; 62 border-top: solid 1px #ccc;
63 padding-left: 10px; 63 padding-left: 10px;
64} 64}
65 65
66table.tabs { 66table.tabs {
67 /* border-bottom: solid 2px #ccc; */ 67 /* border-bottom: solid 2px #ccc; */
68 border-collapse: collapse; 68 border-collapse: collapse;
69 margin-top: 2em; 69 margin-top: 2em;
70 margin-bottom: 0px; 70 margin-bottom: 0px;
71 width: 100%; 71 width: 100%;
72} 72}
73 73
74table.tabs td { 74table.tabs td {
75 padding: 0px 1em; 75 padding: 0px 1em;
76 vertical-align: bottom; 76 vertical-align: bottom;
77} 77}
78 78
79table.tabs td a { 79table.tabs td a {
80 padding: 2px 0.75em; 80 padding: 2px 0.75em;
81 color: #777; 81 color: #777;
82 font-size: 110%; 82 font-size: 110%;
83} 83}
84 84
85table.tabs td a.active { 85table.tabs td a.active {
86 color: #000; 86 color: #000;
87 background-color: #ccc; 87 background-color: #ccc;
88} 88}
89 89
90table.tabs td.form { 90table.tabs td.form {
91 text-align: right; 91 text-align: right;
92} 92}
93 93
94table.tabs td.form form { 94table.tabs td.form form {
95 padding-bottom: 2px; 95 padding-bottom: 2px;
96 font-size: 90%; 96 font-size: 90%;
97 white-space: nowrap; 97 white-space: nowrap;
98} 98}
99 99
100table.tabs td.form input, 100table.tabs td.form input,
101table.tabs td.form select { 101table.tabs td.form select {
102 font-size: 90%; 102 font-size: 90%;
103} 103}
104 104
105div.content { 105div.content {
106 margin: 0px; 106 margin: 0px;
107 padding: 2em; 107 padding: 2em;
108 border-top: solid 3px #ccc; 108 border-top: solid 3px #ccc;
109 border-bottom: solid 3px #ccc; 109 border-bottom: solid 3px #ccc;
110} 110}
111 111
112 112
113table.list { 113table.list {
114 width: 100%; 114 width: 100%;
115 border: none; 115 border: none;
116 border-collapse: collapse; 116 border-collapse: collapse;
117} 117}
118 118
119table.list tr { 119table.list tr {
120 background: white; 120 background: white;
121} 121}
122 122
123table.list tr.logheader { 123table.list tr.logheader {
124 background: #eee; 124 background: #eee;
125} 125}
126 126
127table.list tr:hover { 127table.list tr:hover {
128 background: #eee; 128 background: #eee;
129} 129}
130 130
131table.list tr.nohover:hover { 131table.list tr.nohover:hover {
132 background: white; 132 background: white;
133} 133}
134 134
135table.list th { 135table.list th {
136 font-weight: bold; 136 font-weight: bold;
137 /* color: #888; 137 /* color: #888;
138 border-top: dashed 1px #888; 138 border-top: dashed 1px #888;
139 border-bottom: dashed 1px #888; 139 border-bottom: dashed 1px #888;
140 */ 140 */
141 padding: 0.1em 0.5em 0.05em 0.5em; 141 padding: 0.1em 0.5em 0.05em 0.5em;
142 vertical-align: baseline; 142 vertical-align: baseline;
143} 143}
144 144
145table.list td { 145table.list td {
146 border: none; 146 border: none;
147 padding: 0.1em 0.5em 0.1em 0.5em; 147 padding: 0.1em 0.5em 0.1em 0.5em;
148} 148}
149 149
150table.list td.logsubject { 150table.list td.logsubject {
151 font-family: monospace; 151 font-family: monospace;
152 font-weight: bold; 152 font-weight: bold;
153} 153}
154 154
155table.list td.logmsg { 155table.list td.logmsg {
156 font-family: monospace; 156 font-family: monospace;
157 white-space: pre; 157 white-space: pre;
158 padding: 1em 0.5em 2em 0.5em; 158 padding: 1em 0.5em 2em 0.5em;
159} 159}
160 160
161table.list td a { 161table.list td a {
162 color: black; 162 color: black;
163} 163}
164 164
165table.list td a.ls-dir {
166 font-weight: bold;
167 color: #00f;
168}
169
165table.list td a:hover { 170table.list td a:hover {
166 color: #00f; 171 color: #00f;
167} 172}
168 173
169img { 174img {
170 border: none; 175 border: none;
171} 176}
172 177
173input#switch-btn { 178input#switch-btn {
174 margin: 2px 0px 0px 0px; 179 margin: 2px 0px 0px 0px;
175} 180}
176 181
177td#sidebar input.txt { 182td#sidebar input.txt {
178 width: 100%; 183 width: 100%;
179 margin: 2px 0px 0px 0px; 184 margin: 2px 0px 0px 0px;
180} 185}
181 186
182table#grid { 187table#grid {
183 margin: 0px; 188 margin: 0px;
184} 189}
185 190
186td#content { 191td#content {
187 vertical-align: top; 192 vertical-align: top;
188 padding: 1em 2em 1em 1em; 193 padding: 1em 2em 1em 1em;
189 border: none; 194 border: none;
190} 195}
191 196
192div#summary { 197div#summary {
193 vertical-align: top; 198 vertical-align: top;
194 margin-bottom: 1em; 199 margin-bottom: 1em;
195} 200}
196 201
197table#downloads { 202table#downloads {
198 float: right; 203 float: right;
199 border-collapse: collapse; 204 border-collapse: collapse;
200 border: solid 1px #777; 205 border: solid 1px #777;
201 margin-left: 0.5em; 206 margin-left: 0.5em;
202 margin-bottom: 0.5em; 207 margin-bottom: 0.5em;
203} 208}
204 209
205table#downloads th { 210table#downloads th {
206 background-color: #ccc; 211 background-color: #ccc;
207} 212}
208 213
209div#blob { 214div#blob {
210 border: solid 1px black; 215 border: solid 1px black;
211} 216}
212 217
213div.error { 218div.error {
214 color: red; 219 color: red;
215 font-weight: bold; 220 font-weight: bold;
216 margin: 1em 2em; 221 margin: 1em 2em;
217} 222}
218 223
219a.ls-blob, a.ls-dir, a.ls-mod { 224a.ls-blob, a.ls-dir, a.ls-mod {
220 font-family: monospace; 225 font-family: monospace;
221} 226}
222 227
223td.ls-size { 228td.ls-size {
224 text-align: right; 229 text-align: right;
225 font-family: monospace; 230 font-family: monospace;
226 width: 10em; 231 width: 10em;
227} 232}
228 233
229td.ls-mode { 234td.ls-mode {
230 font-family: monospace; 235 font-family: monospace;
231 width: 10em; 236 width: 10em;
232} 237}
233 238
234table.blob { 239table.blob {
235 margin-top: 0.5em; 240 margin-top: 0.5em;
236 border-top: solid 1px black; 241 border-top: solid 1px black;
237} 242}
238 243
239table.blob td.lines { 244table.blob td.lines {
240 margin: 0; padding: 0 0 0 0.5em; 245 margin: 0; padding: 0 0 0 0.5em;
241 vertical-align: top; 246 vertical-align: top;
242 color: black; 247 color: black;
243} 248}
244 249
245table.blob td.linenumbers { 250table.blob td.linenumbers {
246 margin: 0; padding: 0 0.5em 0 0.5em; 251 margin: 0; padding: 0 0.5em 0 0.5em;
247 vertical-align: top; 252 vertical-align: top;
248 text-align: right; 253 text-align: right;
249 border-right: 1px solid gray; 254 border-right: 1px solid gray;
250} 255}
251 256
252table.blob pre { 257table.blob pre {
253 padding: 0; margin: 0; 258 padding: 0; margin: 0;
254} 259}
255 260
256table.blob a.no { 261table.blob a.no {
257 color: gray; 262 color: gray;
258 text-align: right; 263 text-align: right;
259 text-decoration: none; 264 text-decoration: none;
260} 265}
261 266
262table.blob a.no a:hover { 267table.blob a.no a:hover {
263 color: black; 268 color: black;
264} 269}
265 270
266table.bin-blob { 271table.bin-blob {
267 margin-top: 0.5em; 272 margin-top: 0.5em;
268 border: solid 1px black; 273 border: solid 1px black;
269} 274}
270 275
271table.bin-blob th { 276table.bin-blob th {
272 font-family: monospace; 277 font-family: monospace;
273 white-space: pre; 278 white-space: pre;
274 border: solid 1px #777; 279 border: solid 1px #777;
275 padding: 0.5em 1em; 280 padding: 0.5em 1em;
276} 281}
277 282
278table.bin-blob td { 283table.bin-blob td {
279 font-family: monospace; 284 font-family: monospace;
280 white-space: pre; 285 white-space: pre;
281 border-left: solid 1px #777; 286 border-left: solid 1px #777;
282 padding: 0em 1em; 287 padding: 0em 1em;
283} 288}
284 289
285table.nowrap td { 290table.nowrap td {
286 white-space: nowrap; 291 white-space: nowrap;
287} 292}
288 293
289table.commit-info { 294table.commit-info {
290 border-collapse: collapse; 295 border-collapse: collapse;
291 margin-top: 1.5em; 296 margin-top: 1.5em;
292} 297}
293 298
294table.commit-info th { 299table.commit-info th {
295 text-align: left; 300 text-align: left;
296 font-weight: normal; 301 font-weight: normal;
297 padding: 0.1em 1em 0.1em 0.1em; 302 padding: 0.1em 1em 0.1em 0.1em;
298 vertical-align: top; 303 vertical-align: top;
299} 304}
300 305
301table.commit-info td { 306table.commit-info td {
302 font-weight: normal; 307 font-weight: normal;
303 padding: 0.1em 1em 0.1em 0.1em; 308 padding: 0.1em 1em 0.1em 0.1em;
304} 309}
305 310
306div.commit-subject { 311div.commit-subject {
307 font-weight: bold; 312 font-weight: bold;
308 font-size: 125%; 313 font-size: 125%;
309 margin: 1.5em 0em 0.5em 0em; 314 margin: 1.5em 0em 0.5em 0em;
310 padding: 0em; 315 padding: 0em;
311} 316}
312 317
313div.commit-msg { 318div.commit-msg {
314 white-space: pre; 319 white-space: pre;
315 font-family: monospace; 320 font-family: monospace;
316} 321}
317 322
318div.diffstat-header { 323div.diffstat-header {
319 font-weight: bold; 324 font-weight: bold;
320 padding-top: 1.5em; 325 padding-top: 1.5em;
321} 326}
322 327
323table.diffstat { 328table.diffstat {
324 border-collapse: collapse; 329 border-collapse: collapse;
325 border: solid 1px #aaa; 330 border: solid 1px #aaa;
326 background-color: #eee; 331 background-color: #eee;
327} 332}
328 333
329table.diffstat th { 334table.diffstat th {
330 font-weight: normal; 335 font-weight: normal;
331 text-align: left; 336 text-align: left;
332 text-decoration: underline; 337 text-decoration: underline;
333 padding: 0.1em 1em 0.1em 0.1em; 338 padding: 0.1em 1em 0.1em 0.1em;
334 font-size: 100%; 339 font-size: 100%;
335} 340}
336 341
337table.diffstat td { 342table.diffstat td {
338 padding: 0.2em 0.2em 0.1em 0.1em; 343 padding: 0.2em 0.2em 0.1em 0.1em;
339 font-size: 100%; 344 font-size: 100%;
340 border: none; 345 border: none;
341} 346}
342 347
343table.diffstat td.mode { 348table.diffstat td.mode {
344 white-space: nowrap; 349 white-space: nowrap;
345} 350}
346 351
347table.diffstat td span.modechange { 352table.diffstat td span.modechange {
348 padding-left: 1em; 353 padding-left: 1em;
349 color: red; 354 color: red;
350} 355}
351 356
352table.diffstat td.add a { 357table.diffstat td.add a {
353 color: green; 358 color: green;
354} 359}
355 360
356table.diffstat td.del a { 361table.diffstat td.del a {
diff --git a/cgit.h b/cgit.h
index 6c6c460..39853df 100644
--- a/cgit.h
+++ b/cgit.h
@@ -1,294 +1,295 @@
1#ifndef CGIT_H 1#ifndef CGIT_H
2#define CGIT_H 2#define CGIT_H
3 3
4 4
5#include <git-compat-util.h> 5#include <git-compat-util.h>
6#include <cache.h> 6#include <cache.h>
7#include <grep.h> 7#include <grep.h>
8#include <object.h> 8#include <object.h>
9#include <tree.h> 9#include <tree.h>
10#include <commit.h> 10#include <commit.h>
11#include <tag.h> 11#include <tag.h>
12#include <diff.h> 12#include <diff.h>
13#include <diffcore.h> 13#include <diffcore.h>
14#include <refs.h> 14#include <refs.h>
15#include <revision.h> 15#include <revision.h>
16#include <log-tree.h> 16#include <log-tree.h>
17#include <archive.h> 17#include <archive.h>
18#include <string-list.h> 18#include <string-list.h>
19#include <xdiff-interface.h> 19#include <xdiff-interface.h>
20#include <xdiff/xdiff.h> 20#include <xdiff/xdiff.h>
21#include <utf8.h> 21#include <utf8.h>
22 22
23 23
24/* 24/*
25 * Dateformats used on misc. pages 25 * Dateformats used on misc. pages
26 */ 26 */
27#define FMT_LONGDATE "%Y-%m-%d %H:%M:%S (%Z)" 27#define FMT_LONGDATE "%Y-%m-%d %H:%M:%S (%Z)"
28#define FMT_SHORTDATE "%Y-%m-%d" 28#define FMT_SHORTDATE "%Y-%m-%d"
29#define FMT_ATOMDATE "%Y-%m-%dT%H:%M:%SZ" 29#define FMT_ATOMDATE "%Y-%m-%dT%H:%M:%SZ"
30 30
31 31
32/* 32/*
33 * Limits used for relative dates 33 * Limits used for relative dates
34 */ 34 */
35#define TM_MIN 60 35#define TM_MIN 60
36#define TM_HOUR (TM_MIN * 60) 36#define TM_HOUR (TM_MIN * 60)
37#define TM_DAY (TM_HOUR * 24) 37#define TM_DAY (TM_HOUR * 24)
38#define TM_WEEK (TM_DAY * 7) 38#define TM_WEEK (TM_DAY * 7)
39#define TM_YEAR (TM_DAY * 365) 39#define TM_YEAR (TM_DAY * 365)
40#define TM_MONTH (TM_YEAR / 12.0) 40#define TM_MONTH (TM_YEAR / 12.0)
41 41
42 42
43/* 43/*
44 * Default encoding 44 * Default encoding
45 */ 45 */
46#define PAGE_ENCODING "UTF-8" 46#define PAGE_ENCODING "UTF-8"
47 47
48typedef void (*configfn)(const char *name, const char *value); 48typedef void (*configfn)(const char *name, const char *value);
49typedef void (*filepair_fn)(struct diff_filepair *pair); 49typedef void (*filepair_fn)(struct diff_filepair *pair);
50typedef void (*linediff_fn)(char *line, int len); 50typedef void (*linediff_fn)(char *line, int len);
51 51
52struct cgit_filter { 52struct cgit_filter {
53 char *cmd; 53 char *cmd;
54 char **argv; 54 char **argv;
55 int old_stdout; 55 int old_stdout;
56 int pipe_fh[2]; 56 int pipe_fh[2];
57 int pid; 57 int pid;
58 int exitstatus; 58 int exitstatus;
59}; 59};
60 60
61struct cgit_repo { 61struct cgit_repo {
62 char *url; 62 char *url;
63 char *name; 63 char *name;
64 char *path; 64 char *path;
65 char *desc; 65 char *desc;
66 char *owner; 66 char *owner;
67 char *defbranch; 67 char *defbranch;
68 char *module_link; 68 char *module_link;
69 char *readme; 69 char *readme;
70 char *section; 70 char *section;
71 char *clone_url; 71 char *clone_url;
72 int snapshots; 72 int snapshots;
73 int enable_log_filecount; 73 int enable_log_filecount;
74 int enable_log_linecount; 74 int enable_log_linecount;
75 int max_stats; 75 int max_stats;
76 time_t mtime; 76 time_t mtime;
77 struct cgit_filter *about_filter; 77 struct cgit_filter *about_filter;
78 struct cgit_filter *commit_filter; 78 struct cgit_filter *commit_filter;
79 struct cgit_filter *source_filter; 79 struct cgit_filter *source_filter;
80}; 80};
81 81
82typedef void (*repo_config_fn)(struct cgit_repo *repo, const char *name, 82typedef void (*repo_config_fn)(struct cgit_repo *repo, const char *name,
83 const char *value); 83 const char *value);
84 84
85struct cgit_repolist { 85struct cgit_repolist {
86 int length; 86 int length;
87 int count; 87 int count;
88 struct cgit_repo *repos; 88 struct cgit_repo *repos;
89}; 89};
90 90
91struct commitinfo { 91struct commitinfo {
92 struct commit *commit; 92 struct commit *commit;
93 char *author; 93 char *author;
94 char *author_email; 94 char *author_email;
95 unsigned long author_date; 95 unsigned long author_date;
96 char *committer; 96 char *committer;
97 char *committer_email; 97 char *committer_email;
98 unsigned long committer_date; 98 unsigned long committer_date;
99 char *subject; 99 char *subject;
100 char *msg; 100 char *msg;
101 char *msg_encoding; 101 char *msg_encoding;
102}; 102};
103 103
104struct taginfo { 104struct taginfo {
105 char *tagger; 105 char *tagger;
106 char *tagger_email; 106 char *tagger_email;
107 unsigned long tagger_date; 107 unsigned long tagger_date;
108 char *msg; 108 char *msg;
109}; 109};
110 110
111struct refinfo { 111struct refinfo {
112 const char *refname; 112 const char *refname;
113 struct object *object; 113 struct object *object;
114 union { 114 union {
115 struct taginfo *tag; 115 struct taginfo *tag;
116 struct commitinfo *commit; 116 struct commitinfo *commit;
117 }; 117 };
118}; 118};
119 119
120struct reflist { 120struct reflist {
121 struct refinfo **refs; 121 struct refinfo **refs;
122 int alloc; 122 int alloc;
123 int count; 123 int count;
124}; 124};
125 125
126struct cgit_query { 126struct cgit_query {
127 int has_symref; 127 int has_symref;
128 int has_sha1; 128 int has_sha1;
129 char *raw; 129 char *raw;
130 char *repo; 130 char *repo;
131 char *page; 131 char *page;
132 char *search; 132 char *search;
133 char *grep; 133 char *grep;
134 char *head; 134 char *head;
135 char *sha1; 135 char *sha1;
136 char *sha2; 136 char *sha2;
137 char *path; 137 char *path;
138 char *name; 138 char *name;
139 char *mimetype; 139 char *mimetype;
140 char *url; 140 char *url;
141 char *period; 141 char *period;
142 int ofs; 142 int ofs;
143 int nohead; 143 int nohead;
144 char *sort; 144 char *sort;
145 int showmsg; 145 int showmsg;
146}; 146};
147 147
148struct cgit_config { 148struct cgit_config {
149 char *agefile; 149 char *agefile;
150 char *cache_root; 150 char *cache_root;
151 char *clone_prefix; 151 char *clone_prefix;
152 char *css; 152 char *css;
153 char *favicon; 153 char *favicon;
154 char *footer; 154 char *footer;
155 char *head_include; 155 char *head_include;
156 char *header; 156 char *header;
157 char *index_header; 157 char *index_header;
158 char *index_info; 158 char *index_info;
159 char *logo; 159 char *logo;
160 char *logo_link; 160 char *logo_link;
161 char *module_link; 161 char *module_link;
162 char *robots; 162 char *robots;
163 char *root_title; 163 char *root_title;
164 char *root_desc; 164 char *root_desc;
165 char *root_readme; 165 char *root_readme;
166 char *script_name; 166 char *script_name;
167 char *section; 167 char *section;
168 char *virtual_root; 168 char *virtual_root;
169 int cache_size; 169 int cache_size;
170 int cache_dynamic_ttl; 170 int cache_dynamic_ttl;
171 int cache_max_create_time; 171 int cache_max_create_time;
172 int cache_repo_ttl; 172 int cache_repo_ttl;
173 int cache_root_ttl; 173 int cache_root_ttl;
174 int cache_scanrc_ttl; 174 int cache_scanrc_ttl;
175 int cache_static_ttl; 175 int cache_static_ttl;
176 int embedded; 176 int embedded;
177 int enable_filter_overrides; 177 int enable_filter_overrides;
178 int enable_index_links; 178 int enable_index_links;
179 int enable_log_filecount; 179 int enable_log_filecount;
180 int enable_log_linecount; 180 int enable_log_linecount;
181 int enable_tree_linenumbers; 181 int enable_tree_linenumbers;
182 int local_time; 182 int local_time;
183 int max_repo_count; 183 int max_repo_count;
184 int max_commit_count; 184 int max_commit_count;
185 int max_lock_attempts; 185 int max_lock_attempts;
186 int max_msg_len; 186 int max_msg_len;
187 int max_repodesc_len; 187 int max_repodesc_len;
188 int max_blob_size;
188 int max_stats; 189 int max_stats;
189 int nocache; 190 int nocache;
190 int noplainemail; 191 int noplainemail;
191 int noheader; 192 int noheader;
192 int renamelimit; 193 int renamelimit;
193 int snapshots; 194 int snapshots;
194 int summary_branches; 195 int summary_branches;
195 int summary_log; 196 int summary_log;
196 int summary_tags; 197 int summary_tags;
197 struct string_list mimetypes; 198 struct string_list mimetypes;
198 struct cgit_filter *about_filter; 199 struct cgit_filter *about_filter;
199 struct cgit_filter *commit_filter; 200 struct cgit_filter *commit_filter;
200 struct cgit_filter *source_filter; 201 struct cgit_filter *source_filter;
201}; 202};
202 203
203struct cgit_page { 204struct cgit_page {
204 time_t modified; 205 time_t modified;
205 time_t expires; 206 time_t expires;
206 size_t size; 207 size_t size;
207 char *mimetype; 208 char *mimetype;
208 char *charset; 209 char *charset;
209 char *filename; 210 char *filename;
210 char *etag; 211 char *etag;
211 char *title; 212 char *title;
212 int status; 213 int status;
213 char *statusmsg; 214 char *statusmsg;
214}; 215};
215 216
216struct cgit_environment { 217struct cgit_environment {
217 char *cgit_config; 218 char *cgit_config;
218 char *http_host; 219 char *http_host;
219 char *https; 220 char *https;
220 char *no_http; 221 char *no_http;
221 char *path_info; 222 char *path_info;
222 char *query_string; 223 char *query_string;
223 char *request_method; 224 char *request_method;
224 char *script_name; 225 char *script_name;
225 char *server_name; 226 char *server_name;
226 char *server_port; 227 char *server_port;
227}; 228};
228 229
229struct cgit_context { 230struct cgit_context {
230 struct cgit_environment env; 231 struct cgit_environment env;
231 struct cgit_query qry; 232 struct cgit_query qry;
232 struct cgit_config cfg; 233 struct cgit_config cfg;
233 struct cgit_repo *repo; 234 struct cgit_repo *repo;
234 struct cgit_page page; 235 struct cgit_page page;
235}; 236};
236 237
237struct cgit_snapshot_format { 238struct cgit_snapshot_format {
238 const char *suffix; 239 const char *suffix;
239 const char *mimetype; 240 const char *mimetype;
240 write_archive_fn_t write_func; 241 write_archive_fn_t write_func;
241 int bit; 242 int bit;
242}; 243};
243 244
244extern const char *cgit_version; 245extern const char *cgit_version;
245 246
246extern struct cgit_repolist cgit_repolist; 247extern struct cgit_repolist cgit_repolist;
247extern struct cgit_context ctx; 248extern struct cgit_context ctx;
248extern const struct cgit_snapshot_format cgit_snapshot_formats[]; 249extern const struct cgit_snapshot_format cgit_snapshot_formats[];
249 250
250extern struct cgit_repo *cgit_add_repo(const char *url); 251extern struct cgit_repo *cgit_add_repo(const char *url);
251extern struct cgit_repo *cgit_get_repoinfo(const char *url); 252extern struct cgit_repo *cgit_get_repoinfo(const char *url);
252extern void cgit_repo_config_cb(const char *name, const char *value); 253extern void cgit_repo_config_cb(const char *name, const char *value);
253 254
254extern int chk_zero(int result, char *msg); 255extern int chk_zero(int result, char *msg);
255extern int chk_positive(int result, char *msg); 256extern int chk_positive(int result, char *msg);
256extern int chk_non_negative(int result, char *msg); 257extern int chk_non_negative(int result, char *msg);
257 258
258extern char *trim_end(const char *str, char c); 259extern char *trim_end(const char *str, char c);
259extern char *strlpart(char *txt, int maxlen); 260extern char *strlpart(char *txt, int maxlen);
260extern char *strrpart(char *txt, int maxlen); 261extern char *strrpart(char *txt, int maxlen);
261 262
262extern void cgit_add_ref(struct reflist *list, struct refinfo *ref); 263extern void cgit_add_ref(struct reflist *list, struct refinfo *ref);
263extern int cgit_refs_cb(const char *refname, const unsigned char *sha1, 264extern int cgit_refs_cb(const char *refname, const unsigned char *sha1,
264 int flags, void *cb_data); 265 int flags, void *cb_data);
265 266
266extern void *cgit_free_commitinfo(struct commitinfo *info); 267extern void *cgit_free_commitinfo(struct commitinfo *info);
267 268
268extern int cgit_diff_files(const unsigned char *old_sha1, 269extern int cgit_diff_files(const unsigned char *old_sha1,
269 const unsigned char *new_sha1, 270 const unsigned char *new_sha1,
270 unsigned long *old_size, unsigned long *new_size, 271 unsigned long *old_size, unsigned long *new_size,
271 int *binary, linediff_fn fn); 272 int *binary, linediff_fn fn);
272 273
273extern void cgit_diff_tree(const unsigned char *old_sha1, 274extern void cgit_diff_tree(const unsigned char *old_sha1,
274 const unsigned char *new_sha1, 275 const unsigned char *new_sha1,
275 filepair_fn fn, const char *prefix); 276 filepair_fn fn, const char *prefix);
276 277
277extern void cgit_diff_commit(struct commit *commit, filepair_fn fn); 278extern void cgit_diff_commit(struct commit *commit, filepair_fn fn);
278 279
279extern char *fmt(const char *format,...); 280extern char *fmt(const char *format,...);
280 281
281extern struct commitinfo *cgit_parse_commit(struct commit *commit); 282extern struct commitinfo *cgit_parse_commit(struct commit *commit);
282extern struct taginfo *cgit_parse_tag(struct tag *tag); 283extern struct taginfo *cgit_parse_tag(struct tag *tag);
283extern void cgit_parse_url(const char *url); 284extern void cgit_parse_url(const char *url);
284 285
285extern const char *cgit_repobasename(const char *reponame); 286extern const char *cgit_repobasename(const char *reponame);
286 287
287extern int cgit_parse_snapshots_mask(const char *str); 288extern int cgit_parse_snapshots_mask(const char *str);
288 289
289extern int cgit_open_filter(struct cgit_filter *filter); 290extern int cgit_open_filter(struct cgit_filter *filter);
290extern int cgit_close_filter(struct cgit_filter *filter); 291extern int cgit_close_filter(struct cgit_filter *filter);
291 292
292extern int readfile(const char *path, char **buf, size_t *size); 293extern int readfile(const char *path, char **buf, size_t *size);
293 294
294#endif /* CGIT_H */ 295#endif /* CGIT_H */
diff --git a/cgitrc.5.txt b/cgitrc.5.txt
index 0c13485..e69140b 100644
--- a/cgitrc.5.txt
+++ b/cgitrc.5.txt
@@ -1,371 +1,375 @@
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-index-links:: 98enable-index-links::
99 Flag which, when set to "1", will make cgit generate extra links for 99 Flag which, when set to "1", will make cgit generate extra links for
100 each repo in the repository index (specifically, to the "summary", 100 each repo in the repository index (specifically, to the "summary",
101 "commit" and "tree" pages). Default value: "0". 101 "commit" and "tree" pages). Default value: "0".
102 102
103enable-log-filecount:: 103enable-log-filecount::
104 Flag which, when set to "1", will make cgit print the number of 104 Flag which, when set to "1", will make cgit print the number of
105 modified files for each commit on the repository log page. Default 105 modified files for each commit on the repository log page. Default
106 value: "0". 106 value: "0".
107 107
108enable-log-linecount:: 108enable-log-linecount::
109 Flag which, when set to "1", will make cgit print the number of added 109 Flag which, when set to "1", will make cgit print the number of added
110 and removed lines for each commit on the repository log page. Default 110 and removed lines for each commit on the repository log page. Default
111 value: "0". 111 value: "0".
112 112
113enable-tree-linenumbers:: 113enable-tree-linenumbers::
114 Flag which, when set to "1", will make cgit generate linenumber links 114 Flag which, when set to "1", will make cgit generate linenumber links
115 for plaintext blobs printed in the tree view. Default value: "1". 115 for plaintext blobs printed in the tree view. Default value: "1".
116 116
117favicon:: 117favicon::
118 Url used as link to a shortcut icon for cgit. If specified, it is 118 Url used as link to a shortcut icon for cgit. If specified, it is
119 suggested to use the value "/favicon.ico" since certain browsers will 119 suggested to use the value "/favicon.ico" since certain browsers will
120 ignore other values. Default value: none. 120 ignore other values. Default value: none.
121 121
122footer:: 122footer::
123 The content of the file specified with this option will be included 123 The content of the file specified with this option will be included
124 verbatim at the bottom of all pages (i.e. it replaces the standard 124 verbatim at the bottom of all pages (i.e. it replaces the standard
125 "generated by..." message. Default value: none. 125 "generated by..." message. Default value: none.
126 126
127head-include:: 127head-include::
128 The content of the file specified with this option will be included 128 The content of the file specified with this option will be included
129 verbatim in the html HEAD section on all pages. Default value: none. 129 verbatim in the html HEAD section on all pages. Default value: none.
130 130
131header:: 131header::
132 The content of the file specified with this option will be included 132 The content of the file specified with this option will be included
133 verbatim at the top of all pages. Default value: none. 133 verbatim at the top of all pages. Default value: none.
134 134
135include:: 135include::
136 Name of a configfile to include before the rest of the current config- 136 Name of a configfile to include before the rest of the current config-
137 file is parsed. Default value: none. 137 file is parsed. Default value: none.
138 138
139index-header:: 139index-header::
140 The content of the file specified with this option will be included 140 The content of the file specified with this option will be included
141 verbatim above the repository index. This setting is deprecated, and 141 verbatim above the repository index. This setting is deprecated, and
142 will not be supported by cgit-1.0 (use root-readme instead). Default 142 will not be supported by cgit-1.0 (use root-readme instead). Default
143 value: none. 143 value: none.
144 144
145index-info:: 145index-info::
146 The content of the file specified with this option will be included 146 The content of the file specified with this option will be included
147 verbatim below the heading on the repository index page. This setting 147 verbatim below the heading on the repository index page. This setting
148 is deprecated, and will not be supported by cgit-1.0 (use root-desc 148 is deprecated, and will not be supported by cgit-1.0 (use root-desc
149 instead). Default value: none. 149 instead). Default value: none.
150 150
151local-time:: 151local-time::
152 Flag which, if set to "1", makes cgit print commit and tag times in the 152 Flag which, if set to "1", makes cgit print commit and tag times in the
153 servers timezone. Default value: "0". 153 servers timezone. Default value: "0".
154 154
155logo:: 155logo::
156 Url which specifies the source of an image which will be used as a logo 156 Url which specifies the source of an image which will be used as a logo
157 on all cgit pages. Default value: "/cgit.png". 157 on all cgit pages. Default value: "/cgit.png".
158 158
159logo-link:: 159logo-link::
160 Url loaded when clicking on the cgit logo image. If unspecified the 160 Url loaded when clicking on the cgit logo image. If unspecified the
161 calculated url of the repository index page will be used. Default 161 calculated url of the repository index page will be used. Default
162 value: none. 162 value: none.
163 163
164max-commit-count:: 164max-commit-count::
165 Specifies the number of entries to list per page in "log" view. Default 165 Specifies the number of entries to list per page in "log" view. Default
166 value: "50". 166 value: "50".
167 167
168max-message-length:: 168max-message-length::
169 Specifies the maximum number of commit message characters to display in 169 Specifies the maximum number of commit message characters to display in
170 "log" view. Default value: "80". 170 "log" view. Default value: "80".
171 171
172max-repo-count:: 172max-repo-count::
173 Specifies the number of entries to list per page on therepository 173 Specifies the number of entries to list per page on therepository
174 index page. Default value: "50". 174 index page. Default value: "50".
175 175
176max-repodesc-length:: 176max-repodesc-length::
177 Specifies the maximum number of repo description characters to display 177 Specifies the maximum number of repo description characters to display
178 on the repository index page. Default value: "80". 178 on the repository index page. Default value: "80".
179 179
180max-blob-size::
181 Specifies the maximum size of a blob to display HTML for in KBytes.
182 Default value: "0" (limit disabled).
183
180max-stats:: 184max-stats::
181 Set the default maximum statistics period. Valid values are "week", 185 Set the default maximum statistics period. Valid values are "week",
182 "month", "quarter" and "year". If unspecified, statistics are 186 "month", "quarter" and "year". If unspecified, statistics are
183 disabled. Default value: none. See also: "repo.max-stats". 187 disabled. Default value: none. See also: "repo.max-stats".
184 188
185mimetype.<ext>:: 189mimetype.<ext>::
186 Set the mimetype for the specified filename extension. This is used 190 Set the mimetype for the specified filename extension. This is used
187 by the `plain` command when returning blob content. 191 by the `plain` command when returning blob content.
188 192
189module-link:: 193module-link::
190 Text which will be used as the formatstring for a hyperlink when a 194 Text which will be used as the formatstring for a hyperlink when a
191 submodule is printed in a directory listing. The arguments for the 195 submodule is printed in a directory listing. The arguments for the
192 formatstring are the path and SHA1 of the submodule commit. Default 196 formatstring are the path and SHA1 of the submodule commit. Default
193 value: "./?repo=%s&page=commit&id=%s" 197 value: "./?repo=%s&page=commit&id=%s"
194 198
195nocache:: 199nocache::
196 If set to the value "1" caching will be disabled. This settings is 200 If set to the value "1" caching will be disabled. This settings is
197 deprecated, and will not be honored starting with cgit-1.0. Default 201 deprecated, and will not be honored starting with cgit-1.0. Default
198 value: "0". 202 value: "0".
199 203
200noplainemail:: 204noplainemail::
201 If set to "1" showing full author email adresses will be disabled. 205 If set to "1" showing full author email adresses will be disabled.
202 Default value: "0". 206 Default value: "0".
203 207
204noheader:: 208noheader::
205 Flag which, when set to "1", will make cgit omit the standard header 209 Flag which, when set to "1", will make cgit omit the standard header
206 on all pages. Default value: none. See also: "embedded". 210 on all pages. Default value: none. See also: "embedded".
207 211
208renamelimit:: 212renamelimit::
209 Maximum number of files to consider when detecting renames. The value 213 Maximum number of files to consider when detecting renames. The value
210 "-1" uses the compiletime value in git (for further info, look at 214 "-1" uses the compiletime value in git (for further info, look at
211 `man git-diff`). Default value: "-1". 215 `man git-diff`). Default value: "-1".
212 216
213repo.group:: 217repo.group::
214 Legacy alias for "section". This option is deprecated and will not be 218 Legacy alias for "section". This option is deprecated and will not be
215 supported in cgit-1.0. 219 supported in cgit-1.0.
216 220
217robots:: 221robots::
218 Text used as content for the "robots" meta-tag. Default value: 222 Text used as content for the "robots" meta-tag. Default value:
219 "index, nofollow". 223 "index, nofollow".
220 224
221root-desc:: 225root-desc::
222 Text printed below the heading on the repository index page. Default 226 Text printed below the heading on the repository index page. Default
223 value: "a fast webinterface for the git dscm". 227 value: "a fast webinterface for the git dscm".
224 228
225root-readme:: 229root-readme::
226 The content of the file specified with this option will be included 230 The content of the file specified with this option will be included
227 verbatim below the "about" link on the repository index page. Default 231 verbatim below the "about" link on the repository index page. Default
228 value: none. 232 value: none.
229 233
230root-title:: 234root-title::
231 Text printed as heading on the repository index page. Default value: 235 Text printed as heading on the repository index page. Default value:
232 "Git Repository Browser". 236 "Git Repository Browser".
233 237
234scan-path:: 238scan-path::
235 A path which will be scanned for repositories. If caching is enabled, 239 A path which will be scanned for repositories. If caching is enabled,
236 the result will be cached as a cgitrc include-file in the cache 240 the result will be cached as a cgitrc include-file in the cache
237 directory. Default value: none. See also: cache-scanrc-ttl. 241 directory. Default value: none. See also: cache-scanrc-ttl.
238 242
239section:: 243section::
240 The name of the current repository section - all repositories defined 244 The name of the current repository section - all repositories defined
241 after this option will inherit the current section name. Default value: 245 after this option will inherit the current section name. Default value:
242 none. 246 none.
243 247
244snapshots:: 248snapshots::
245 Text which specifies the default set of snapshot formats generated by 249 Text which specifies the default set of snapshot formats generated by
246 cgit. The value is a space-separated list of zero or more of the 250 cgit. The value is a space-separated list of zero or more of the
247 values "tar", "tar.gz", "tar.bz2" and "zip". Default value: none. 251 values "tar", "tar.gz", "tar.bz2" and "zip". Default value: none.
248 252
249source-filter:: 253source-filter::
250 Specifies a command which will be invoked to format plaintext blobs 254 Specifies a command which will be invoked to format plaintext blobs
251 in the tree view. The command will get the blob content on its STDIN 255 in the tree view. The command will get the blob content on its STDIN
252 and the name of the blob as its only command line argument. The STDOUT 256 and the name of the blob as its only command line argument. The STDOUT
253 from the command will be included verbatim as the blob contents, i.e. 257 from the command will be included verbatim as the blob contents, i.e.
254 this can be used to implement e.g. syntax highlighting. Default value: 258 this can be used to implement e.g. syntax highlighting. Default value:
255 none. 259 none.
256 260
257summary-branches:: 261summary-branches::
258 Specifies the number of branches to display in the repository "summary" 262 Specifies the number of branches to display in the repository "summary"
259 view. Default value: "10". 263 view. Default value: "10".
260 264
261summary-log:: 265summary-log::
262 Specifies the number of log entries to display in the repository 266 Specifies the number of log entries to display in the repository
263 "summary" view. Default value: "10". 267 "summary" view. Default value: "10".
264 268
265summary-tags:: 269summary-tags::
266 Specifies the number of tags to display in the repository "summary" 270 Specifies the number of tags to display in the repository "summary"
267 view. Default value: "10". 271 view. Default value: "10".
268 272
269virtual-root:: 273virtual-root::
270 Url which, if specified, will be used as root for all cgit links. It 274 Url which, if specified, will be used as root for all cgit links. It
271 will also cause cgit to generate 'virtual urls', i.e. urls like 275 will also cause cgit to generate 'virtual urls', i.e. urls like
272 '/cgit/tree/README' as opposed to '?r=cgit&p=tree&path=README'. Default 276 '/cgit/tree/README' as opposed to '?r=cgit&p=tree&path=README'. Default
273 value: none. 277 value: none.
274 NOTE: cgit has recently learned how to use PATH_INFO to achieve the 278 NOTE: cgit has recently learned how to use PATH_INFO to achieve the
275 same kind of virtual urls, so this option will probably be deprecated. 279 same kind of virtual urls, so this option will probably be deprecated.
276 280
277REPOSITORY SETTINGS 281REPOSITORY SETTINGS
278------------------- 282-------------------
279repo.about-filter:: 283repo.about-filter::
280 Override the default about-filter. Default value: none. See also: 284 Override the default about-filter. Default value: none. See also:
281 "enable-filter-overrides". 285 "enable-filter-overrides".
282 286
283repo.clone-url:: 287repo.clone-url::
284 A list of space-separated urls which can be used to clone this repo. 288 A list of space-separated urls which can be used to clone this repo.
285 Default value: none. 289 Default value: none.
286 290
287repo.commit-filter:: 291repo.commit-filter::
288 Override the default commit-filter. Default value: none. See also: 292 Override the default commit-filter. Default value: none. See also:
289 "enable-filter-overrides". 293 "enable-filter-overrides".
290 294
291repo.defbranch:: 295repo.defbranch::
292 The name of the default branch for this repository. If no such branch 296 The name of the default branch for this repository. If no such branch
293 exists in the repository, the first branch name (when sorted) is used 297 exists in the repository, the first branch name (when sorted) is used
294 as default instead. Default value: "master". 298 as default instead. Default value: "master".
295 299
296repo.desc:: 300repo.desc::
297 The value to show as repository description. Default value: none. 301 The value to show as repository description. Default value: none.
298 302
299repo.enable-log-filecount:: 303repo.enable-log-filecount::
300 A flag which can be used to disable the global setting 304 A flag which can be used to disable the global setting
301 `enable-log-filecount'. Default value: none. 305 `enable-log-filecount'. Default value: none.
302 306
303repo.enable-log-linecount:: 307repo.enable-log-linecount::
304 A flag which can be used to disable the global setting 308 A flag which can be used to disable the global setting
305 `enable-log-linecount'. Default value: none. 309 `enable-log-linecount'. Default value: none.
306 310
307repo.max-stats:: 311repo.max-stats::
308 Override the default maximum statistics period. Valid values are equal 312 Override the default maximum statistics period. Valid values are equal
309 to the values specified for the global "max-stats" setting. Default 313 to the values specified for the global "max-stats" setting. Default
310 value: none. 314 value: none.
311 315
312repo.name:: 316repo.name::
313 The value to show as repository name. Default value: <repo.url>. 317 The value to show as repository name. Default value: <repo.url>.
314 318
315repo.owner:: 319repo.owner::
316 A value used to identify the owner of the repository. Default value: 320 A value used to identify the owner of the repository. Default value:
317 none. 321 none.
318 322
319repo.path:: 323repo.path::
320 An absolute path to the repository directory. For non-bare repositories 324 An absolute path to the repository directory. For non-bare repositories
321 this is the .git-directory. Default value: none. 325 this is the .git-directory. Default value: none.
322 326
323repo.readme:: 327repo.readme::
324 A path (relative to <repo.path>) which specifies a file to include 328 A path (relative to <repo.path>) which specifies a file to include
325 verbatim as the "About" page for this repo. Default value: none. 329 verbatim as the "About" page for this repo. Default value: none.
326 330
327repo.snapshots:: 331repo.snapshots::
328 A mask of allowed snapshot-formats for this repo, restricted by the 332 A mask of allowed snapshot-formats for this repo, restricted by the
329 "snapshots" global setting. Default value: <snapshots>. 333 "snapshots" global setting. Default value: <snapshots>.
330 334
331repo.section:: 335repo.section::
332 Override the current section name for this repository. Default value: 336 Override the current section name for this repository. Default value:
333 none. 337 none.
334 338
335repo.source-filter:: 339repo.source-filter::
336 Override the default source-filter. Default value: none. See also: 340 Override the default source-filter. Default value: none. See also:
337 "enable-filter-overrides". 341 "enable-filter-overrides".
338 342
339repo.url:: 343repo.url::
340 The relative url used to access the repository. This must be the first 344 The relative url used to access the repository. This must be the first
341 setting specified for each repo. Default value: none. 345 setting specified for each repo. Default value: none.
342 346
343 347
344REPOSITORY-SPECIFIC CGITRC FILE 348REPOSITORY-SPECIFIC CGITRC FILE
345------------------------------- 349-------------------------------
346When the option "scan-path" is used to auto-discover git repositories, cgit 350When the option "scan-path" is used to auto-discover git repositories, cgit
347will try to parse the file "cgitrc" within any found repository. Such a 351will try to parse the file "cgitrc" within any found repository. Such a
348repo-specific config file may contain any of the repo-specific options 352repo-specific config file may contain any of the repo-specific options
349described above, except "repo.url" and "repo.path". Additionally, the "filter" 353described above, except "repo.url" and "repo.path". Additionally, the "filter"
350options are only acknowledged in repo-specific config files when 354options are only acknowledged in repo-specific config files when
351"enable-filter-overrides" is set to "1". 355"enable-filter-overrides" is set to "1".
352 356
353Note: the "repo." prefix is dropped from the option names in repo-specific 357Note: the "repo." prefix is dropped from the option names in repo-specific
354config files, e.g. "repo.desc" becomes "desc". 358config files, e.g. "repo.desc" becomes "desc".
355 359
356 360
357EXAMPLE CGITRC FILE 361EXAMPLE CGITRC FILE
358------------------- 362-------------------
359 363
360.... 364....
361# Enable caching of up to 1000 output entriess 365# Enable caching of up to 1000 output entriess
362cache-size=1000 366cache-size=1000
363 367
364 368
365# Specify some default clone prefixes 369# Specify some default clone prefixes
366clone-prefix=git://foobar.com ssh://foobar.com/pub/git http://foobar.com/git 370clone-prefix=git://foobar.com ssh://foobar.com/pub/git http://foobar.com/git
367 371
368# Specify the css url 372# Specify the css url
369css=/css/cgit.css 373css=/css/cgit.css
370 374
371 375
diff --git a/filters/syntax-highlighting.sh b/filters/syntax-highlighting.sh
index 999ad0c..6b1c576 100755
--- a/filters/syntax-highlighting.sh
+++ b/filters/syntax-highlighting.sh
@@ -1,39 +1,34 @@
1#!/bin/sh 1#!/bin/sh
2# This script can be used to implement syntax highlighting in the cgit 2# This script can be used to implement syntax highlighting in the cgit
3# tree-view by refering to this file with the source-filter or repo.source- 3# tree-view by refering to this file with the source-filter or repo.source-
4# filter options in cgitrc. 4# filter options in cgitrc.
5# 5#
6# This script requires a shell supporting the ${var##pattern} syntax.
7# It is supported by at least dash and bash, however busybox environments
8# might have to use an external call to sed instead.
9#
6# Note: the highlight command (http://www.andre-simon.de/) uses css for syntax 10# Note: the highlight command (http://www.andre-simon.de/) uses css for syntax
7# highlighting, so you'll probably want something like the following included 11# highlighting, so you'll probably want something like the following included
8# in your css file (generated by highlight 2.4.8 and adapted for cgit): 12# in your css file (generated by highlight 2.4.8 and adapted for cgit):
9# 13#
10# table.blob .num { color:#2928ff; } 14# table.blob .num { color:#2928ff; }
11# table.blob .esc { color:#ff00ff; } 15# table.blob .esc { color:#ff00ff; }
12# table.blob .str { color:#ff0000; } 16# table.blob .str { color:#ff0000; }
13# table.blob .dstr { color:#818100; } 17# table.blob .dstr { color:#818100; }
14# table.blob .slc { color:#838183; font-style:italic; } 18# table.blob .slc { color:#838183; font-style:italic; }
15# table.blob .com { color:#838183; font-style:italic; } 19# table.blob .com { color:#838183; font-style:italic; }
16# table.blob .dir { color:#008200; } 20# table.blob .dir { color:#008200; }
17# table.blob .sym { color:#000000; } 21# table.blob .sym { color:#000000; }
18# table.blob .kwa { color:#000000; font-weight:bold; } 22# table.blob .kwa { color:#000000; font-weight:bold; }
19# table.blob .kwb { color:#830000; } 23# table.blob .kwb { color:#830000; }
20# table.blob .kwc { color:#000000; font-weight:bold; } 24# table.blob .kwc { color:#000000; font-weight:bold; }
21# table.blob .kwd { color:#010181; } 25# table.blob .kwd { color:#010181; }
22 26
23case "$1" in 27# store filename and extension in local vars
24 *.c) 28BASENAME="$1"
25 highlight -f -I -X -S c 29EXTENSION="${BASENAME##*.}"
26 ;; 30
27 *.h) 31# map Makefile and Makefile.* to .mk
28 highlight -f -I -X -S c 32[ "${BASENAME%%.*}" == "Makefile" ] && EXTENSION=mk
29 ;; 33
30 *.sh) 34exec highlight --force -f -I -X -S $EXTENSION 2>/dev/null
31 highlight -f -I -X -S sh
32 ;;
33 *.css)
34 highlight -f -I -X -S css
35 ;;
36 *)
37 highlight -f -I -X -S txt
38 ;;
39esac
diff --git a/ui-tree.c b/ui-tree.c
index f53ab64..f281937 100644
--- a/ui-tree.c
+++ b/ui-tree.c
@@ -1,285 +1,291 @@
1/* ui-tree.c: functions for tree output 1/* ui-tree.c: functions for tree output
2 * 2 *
3 * Copyright (C) 2006 Lars Hjemli 3 * Copyright (C) 2006 Lars Hjemli
4 * 4 *
5 * Licensed under GNU General Public License v2 5 * Licensed under GNU General Public License v2
6 * (see COPYING for full license text) 6 * (see COPYING for full license text)
7 */ 7 */
8 8
9#include <ctype.h> 9#include <ctype.h>
10#include "cgit.h" 10#include "cgit.h"
11#include "html.h" 11#include "html.h"
12#include "ui-shared.h" 12#include "ui-shared.h"
13 13
14char *curr_rev; 14char *curr_rev;
15char *match_path; 15char *match_path;
16int header = 0; 16int header = 0;
17 17
18static void print_text_buffer(const char *name, char *buf, unsigned long size) 18static void print_text_buffer(const char *name, char *buf, unsigned long size)
19{ 19{
20 unsigned long lineno, idx; 20 unsigned long lineno, idx;
21 const char *numberfmt = 21 const char *numberfmt =
22 "<a class='no' id='n%1$d' name='n%1$d' href='#n%1$d'>%1$d</a>\n"; 22 "<a class='no' id='n%1$d' name='n%1$d' href='#n%1$d'>%1$d</a>\n";
23 23
24 html("<table summary='blob content' class='blob'>\n"); 24 html("<table summary='blob content' class='blob'>\n");
25 25
26 if (ctx.cfg.enable_tree_linenumbers) { 26 if (ctx.cfg.enable_tree_linenumbers) {
27 html("<tr><td class='linenumbers'><pre>"); 27 html("<tr><td class='linenumbers'><pre>");
28 idx = 0; 28 idx = 0;
29 lineno = 0; 29 lineno = 0;
30 30
31 if (size) { 31 if (size) {
32 htmlf(numberfmt, ++lineno); 32 htmlf(numberfmt, ++lineno);
33 while(idx < size - 1) { // skip absolute last newline 33 while(idx < size - 1) { // skip absolute last newline
34 if (buf[idx] == '\n') 34 if (buf[idx] == '\n')
35 htmlf(numberfmt, ++lineno); 35 htmlf(numberfmt, ++lineno);
36 idx++; 36 idx++;
37 } 37 }
38 } 38 }
39 html("</pre></td>\n"); 39 html("</pre></td>\n");
40 } 40 }
41 else { 41 else {
42 html("<tr>\n"); 42 html("<tr>\n");
43 } 43 }
44 44
45 if (ctx.repo->source_filter) { 45 if (ctx.repo->source_filter) {
46 html("<td class='lines'><pre><code>"); 46 html("<td class='lines'><pre><code>");
47 ctx.repo->source_filter->argv[1] = xstrdup(name); 47 ctx.repo->source_filter->argv[1] = xstrdup(name);
48 cgit_open_filter(ctx.repo->source_filter); 48 cgit_open_filter(ctx.repo->source_filter);
49 write(STDOUT_FILENO, buf, size); 49 write(STDOUT_FILENO, buf, size);
50 cgit_close_filter(ctx.repo->source_filter); 50 cgit_close_filter(ctx.repo->source_filter);
51 html("</code></pre></td></tr></table>\n"); 51 html("</code></pre></td></tr></table>\n");
52 return; 52 return;
53 } 53 }
54 54
55 html("<td class='lines'><pre><code>"); 55 html("<td class='lines'><pre><code>");
56 html_txt(buf); 56 html_txt(buf);
57 html("</code></pre></td></tr></table>\n"); 57 html("</code></pre></td></tr></table>\n");
58} 58}
59 59
60#define ROWLEN 32 60#define ROWLEN 32
61 61
62static void print_binary_buffer(char *buf, unsigned long size) 62static void print_binary_buffer(char *buf, unsigned long size)
63{ 63{
64 unsigned long ofs, idx; 64 unsigned long ofs, idx;
65 static char ascii[ROWLEN + 1]; 65 static char ascii[ROWLEN + 1];
66 66
67 html("<table summary='blob content' class='bin-blob'>\n"); 67 html("<table summary='blob content' class='bin-blob'>\n");
68 html("<tr><th>ofs</th><th>hex dump</th><th>ascii</th></tr>"); 68 html("<tr><th>ofs</th><th>hex dump</th><th>ascii</th></tr>");
69 for (ofs = 0; ofs < size; ofs += ROWLEN, buf += ROWLEN) { 69 for (ofs = 0; ofs < size; ofs += ROWLEN, buf += ROWLEN) {
70 htmlf("<tr><td class='right'>%04x</td><td class='hex'>", ofs); 70 htmlf("<tr><td class='right'>%04x</td><td class='hex'>", ofs);
71 for (idx = 0; idx < ROWLEN && ofs + idx < size; idx++) 71 for (idx = 0; idx < ROWLEN && ofs + idx < size; idx++)
72 htmlf("%*s%02x", 72 htmlf("%*s%02x",
73 idx == 16 ? 4 : 1, "", 73 idx == 16 ? 4 : 1, "",
74 buf[idx] & 0xff); 74 buf[idx] & 0xff);
75 html(" </td><td class='hex'>"); 75 html(" </td><td class='hex'>");
76 for (idx = 0; idx < ROWLEN && ofs + idx < size; idx++) 76 for (idx = 0; idx < ROWLEN && ofs + idx < size; idx++)
77 ascii[idx] = isgraph(buf[idx]) ? buf[idx] : '.'; 77 ascii[idx] = isgraph(buf[idx]) ? buf[idx] : '.';
78 ascii[idx] = '\0'; 78 ascii[idx] = '\0';
79 html_txt(ascii); 79 html_txt(ascii);
80 html("</td></tr>\n"); 80 html("</td></tr>\n");
81 } 81 }
82 html("</table>\n"); 82 html("</table>\n");
83} 83}
84 84
85static void print_object(const unsigned char *sha1, char *path, const char *basename) 85static void print_object(const unsigned char *sha1, char *path, const char *basename)
86{ 86{
87 enum object_type type; 87 enum object_type type;
88 char *buf; 88 char *buf;
89 unsigned long size; 89 unsigned long size;
90 90
91 type = sha1_object_info(sha1, &size); 91 type = sha1_object_info(sha1, &size);
92 if (type == OBJ_BAD) { 92 if (type == OBJ_BAD) {
93 cgit_print_error(fmt("Bad object name: %s", 93 cgit_print_error(fmt("Bad object name: %s",
94 sha1_to_hex(sha1))); 94 sha1_to_hex(sha1)));
95 return; 95 return;
96 } 96 }
97 97
98 buf = read_sha1_file(sha1, &type, &size); 98 buf = read_sha1_file(sha1, &type, &size);
99 if (!buf) { 99 if (!buf) {
100 cgit_print_error(fmt("Error reading object %s", 100 cgit_print_error(fmt("Error reading object %s",
101 sha1_to_hex(sha1))); 101 sha1_to_hex(sha1)));
102 return; 102 return;
103 } 103 }
104 104
105 html(" ("); 105 html(" (");
106 cgit_plain_link("plain", NULL, NULL, ctx.qry.head, 106 cgit_plain_link("plain", NULL, NULL, ctx.qry.head,
107 curr_rev, path); 107 curr_rev, path);
108 htmlf(")<br/>blob: %s\n", sha1_to_hex(sha1)); 108 htmlf(")<br/>blob: %s\n", sha1_to_hex(sha1));
109 109
110 if (ctx.cfg.max_blob_size && size / 1024 > ctx.cfg.max_blob_size) {
111 htmlf("<div class='error'>blob size (%dKB) exceeds display size limit (%dKB).</div>",
112 size / 1024, ctx.cfg.max_blob_size);
113 return;
114 }
115
110 if (buffer_is_binary(buf, size)) 116 if (buffer_is_binary(buf, size))
111 print_binary_buffer(buf, size); 117 print_binary_buffer(buf, size);
112 else 118 else
113 print_text_buffer(basename, buf, size); 119 print_text_buffer(basename, buf, size);
114} 120}
115 121
116 122
117static int ls_item(const unsigned char *sha1, const char *base, int baselen, 123static int ls_item(const unsigned char *sha1, const char *base, int baselen,
118 const char *pathname, unsigned int mode, int stage, 124 const char *pathname, unsigned int mode, int stage,
119 void *cbdata) 125 void *cbdata)
120{ 126{
121 char *name; 127 char *name;
122 char *fullpath; 128 char *fullpath;
123 char *class; 129 char *class;
124 enum object_type type; 130 enum object_type type;
125 unsigned long size = 0; 131 unsigned long size = 0;
126 132
127 name = xstrdup(pathname); 133 name = xstrdup(pathname);
128 fullpath = fmt("%s%s%s", ctx.qry.path ? ctx.qry.path : "", 134 fullpath = fmt("%s%s%s", ctx.qry.path ? ctx.qry.path : "",
129 ctx.qry.path ? "/" : "", name); 135 ctx.qry.path ? "/" : "", name);
130 136
131 if (!S_ISGITLINK(mode)) { 137 if (!S_ISGITLINK(mode)) {
132 type = sha1_object_info(sha1, &size); 138 type = sha1_object_info(sha1, &size);
133 if (type == OBJ_BAD) { 139 if (type == OBJ_BAD) {
134 htmlf("<tr><td colspan='3'>Bad object: %s %s</td></tr>", 140 htmlf("<tr><td colspan='3'>Bad object: %s %s</td></tr>",
135 name, 141 name,
136 sha1_to_hex(sha1)); 142 sha1_to_hex(sha1));
137 return 0; 143 return 0;
138 } 144 }
139 } 145 }
140 146
141 html("<tr><td class='ls-mode'>"); 147 html("<tr><td class='ls-mode'>");
142 cgit_print_filemode(mode); 148 cgit_print_filemode(mode);
143 html("</td><td>"); 149 html("</td><td>");
144 if (S_ISGITLINK(mode)) { 150 if (S_ISGITLINK(mode)) {
145 htmlf("<a class='ls-mod' href='"); 151 htmlf("<a class='ls-mod' href='");
146 html_attr(fmt(ctx.repo->module_link, 152 html_attr(fmt(ctx.repo->module_link,
147 name, 153 name,
148 sha1_to_hex(sha1))); 154 sha1_to_hex(sha1)));
149 html("'>"); 155 html("'>");
150 html_txt(name); 156 html_txt(name);
151 html("</a>"); 157 html("</a>");
152 } else if (S_ISDIR(mode)) { 158 } else if (S_ISDIR(mode)) {
153 cgit_tree_link(name, NULL, "ls-dir", ctx.qry.head, 159 cgit_tree_link(name, NULL, "ls-dir", ctx.qry.head,
154 curr_rev, fullpath); 160 curr_rev, fullpath);
155 } else { 161 } else {
156 class = strrchr(name, '.'); 162 class = strrchr(name, '.');
157 if (class != NULL) { 163 if (class != NULL) {
158 class = fmt("ls-blob %s", class + 1); 164 class = fmt("ls-blob %s", class + 1);
159 } else 165 } else
160 class = "ls-blob"; 166 class = "ls-blob";
161 cgit_tree_link(name, NULL, class, ctx.qry.head, 167 cgit_tree_link(name, NULL, class, ctx.qry.head,
162 curr_rev, fullpath); 168 curr_rev, fullpath);
163 } 169 }
164 htmlf("</td><td class='ls-size'>%li</td>", size); 170 htmlf("</td><td class='ls-size'>%li</td>", size);
165 171
166 html("<td>"); 172 html("<td>");
167 cgit_log_link("log", NULL, "button", ctx.qry.head, curr_rev, 173 cgit_log_link("log", NULL, "button", ctx.qry.head, curr_rev,
168 fullpath, 0, NULL, NULL, ctx.qry.showmsg); 174 fullpath, 0, NULL, NULL, ctx.qry.showmsg);
169 if (ctx.repo->max_stats) 175 if (ctx.repo->max_stats)
170 cgit_stats_link("stats", NULL, "button", ctx.qry.head, 176 cgit_stats_link("stats", NULL, "button", ctx.qry.head,
171 fullpath); 177 fullpath);
172 html("</td></tr>\n"); 178 html("</td></tr>\n");
173 free(name); 179 free(name);
174 return 0; 180 return 0;
175} 181}
176 182
177static void ls_head() 183static void ls_head()
178{ 184{
179 html("<table summary='tree listing' class='list'>\n"); 185 html("<table summary='tree listing' class='list'>\n");
180 html("<tr class='nohover'>"); 186 html("<tr class='nohover'>");
181 html("<th class='left'>Mode</th>"); 187 html("<th class='left'>Mode</th>");
182 html("<th class='left'>Name</th>"); 188 html("<th class='left'>Name</th>");
183 html("<th class='right'>Size</th>"); 189 html("<th class='right'>Size</th>");
184 html("<th/>"); 190 html("<th/>");
185 html("</tr>\n"); 191 html("</tr>\n");
186 header = 1; 192 header = 1;
187} 193}
188 194
189static void ls_tail() 195static void ls_tail()
190{ 196{
191 if (!header) 197 if (!header)
192 return; 198 return;
193 html("</table>\n"); 199 html("</table>\n");
194 header = 0; 200 header = 0;
195} 201}
196 202
197static void ls_tree(const unsigned char *sha1, char *path) 203static void ls_tree(const unsigned char *sha1, char *path)
198{ 204{
199 struct tree *tree; 205 struct tree *tree;
200 206
201 tree = parse_tree_indirect(sha1); 207 tree = parse_tree_indirect(sha1);
202 if (!tree) { 208 if (!tree) {
203 cgit_print_error(fmt("Not a tree object: %s", 209 cgit_print_error(fmt("Not a tree object: %s",
204 sha1_to_hex(sha1))); 210 sha1_to_hex(sha1)));
205 return; 211 return;
206 } 212 }
207 213
208 ls_head(); 214 ls_head();
209 read_tree_recursive(tree, "", 0, 1, NULL, ls_item, NULL); 215 read_tree_recursive(tree, "", 0, 1, NULL, ls_item, NULL);
210 ls_tail(); 216 ls_tail();
211} 217}
212 218
213 219
214static int walk_tree(const unsigned char *sha1, const char *base, int baselen, 220static int walk_tree(const unsigned char *sha1, const char *base, int baselen,
215 const char *pathname, unsigned mode, int stage, 221 const char *pathname, unsigned mode, int stage,
216 void *cbdata) 222 void *cbdata)
217{ 223{
218 static int state; 224 static int state;
219 static char buffer[PATH_MAX]; 225 static char buffer[PATH_MAX];
220 char *url; 226 char *url;
221 227
222 if (state == 0) { 228 if (state == 0) {
223 memcpy(buffer, base, baselen); 229 memcpy(buffer, base, baselen);
224 strcpy(buffer+baselen, pathname); 230 strcpy(buffer+baselen, pathname);
225 url = cgit_pageurl(ctx.qry.repo, "tree", 231 url = cgit_pageurl(ctx.qry.repo, "tree",
226 fmt("h=%s&amp;path=%s", curr_rev, buffer)); 232 fmt("h=%s&amp;path=%s", curr_rev, buffer));
227 html("/"); 233 html("/");
228 cgit_tree_link(xstrdup(pathname), NULL, NULL, ctx.qry.head, 234 cgit_tree_link(xstrdup(pathname), NULL, NULL, ctx.qry.head,
229 curr_rev, buffer); 235 curr_rev, buffer);
230 236
231 if (strcmp(match_path, buffer)) 237 if (strcmp(match_path, buffer))
232 return READ_TREE_RECURSIVE; 238 return READ_TREE_RECURSIVE;
233 239
234 if (S_ISDIR(mode)) { 240 if (S_ISDIR(mode)) {
235 state = 1; 241 state = 1;
236 ls_head(); 242 ls_head();
237 return READ_TREE_RECURSIVE; 243 return READ_TREE_RECURSIVE;
238 } else { 244 } else {
239 print_object(sha1, buffer, pathname); 245 print_object(sha1, buffer, pathname);
240 return 0; 246 return 0;
241 } 247 }
242 } 248 }
243 ls_item(sha1, base, baselen, pathname, mode, stage, NULL); 249 ls_item(sha1, base, baselen, pathname, mode, stage, NULL);
244 return 0; 250 return 0;
245} 251}
246 252
247 253
248/* 254/*
249 * Show a tree or a blob 255 * Show a tree or a blob
250 * rev: the commit pointing at the root tree object 256 * rev: the commit pointing at the root tree object
251 * path: path to tree or blob 257 * path: path to tree or blob
252 */ 258 */
253void cgit_print_tree(const char *rev, char *path) 259void cgit_print_tree(const char *rev, char *path)
254{ 260{
255 unsigned char sha1[20]; 261 unsigned char sha1[20];
256 struct commit *commit; 262 struct commit *commit;
257 const char *paths[] = {path, NULL}; 263 const char *paths[] = {path, NULL};
258 264
259 if (!rev) 265 if (!rev)
260 rev = ctx.qry.head; 266 rev = ctx.qry.head;
261 267
262 curr_rev = xstrdup(rev); 268 curr_rev = xstrdup(rev);
263 if (get_sha1(rev, sha1)) { 269 if (get_sha1(rev, sha1)) {
264 cgit_print_error(fmt("Invalid revision name: %s", rev)); 270 cgit_print_error(fmt("Invalid revision name: %s", rev));
265 return; 271 return;
266 } 272 }
267 commit = lookup_commit_reference(sha1); 273 commit = lookup_commit_reference(sha1);
268 if (!commit || parse_commit(commit)) { 274 if (!commit || parse_commit(commit)) {
269 cgit_print_error(fmt("Invalid commit reference: %s", rev)); 275 cgit_print_error(fmt("Invalid commit reference: %s", rev));
270 return; 276 return;
271 } 277 }
272 278
273 html("path: <a href='"); 279 html("path: <a href='");
274 html_attr(cgit_pageurl(ctx.qry.repo, "tree", fmt("h=%s", rev))); 280 html_attr(cgit_pageurl(ctx.qry.repo, "tree", fmt("h=%s", rev)));
275 html("'>root</a>"); 281 html("'>root</a>");
276 282
277 if (path == NULL) { 283 if (path == NULL) {
278 ls_tree(commit->tree->object.sha1, NULL); 284 ls_tree(commit->tree->object.sha1, NULL);
279 return; 285 return;
280 } 286 }
281 287
282 match_path = path; 288 match_path = path;
283 read_tree_recursive(commit->tree, NULL, 0, 0, paths, walk_tree, NULL); 289 read_tree_recursive(commit->tree, NULL, 0, 0, paths, walk_tree, NULL);
284 ls_tail(); 290 ls_tail();
285} 291}