summaryrefslogtreecommitdiffabout
authorLars Hjemli <hjemli@gmail.com>2009-09-13 20:01:52 (UTC)
committer Lars Hjemli <hjemli@gmail.com>2009-09-13 20:01:52 (UTC)
commit5f12e45fe3338095916a444ff106dd9fc9991d84 (patch) (unidiff)
tree26b04e10b2ea277c09620bcf119eaa8f79e2fb88
parent45c49d6cf4b819385dfee620a81e3cb095ed5cd1 (diff)
parent6638f40868ccf7806a9668560d3608324704c2b7 (diff)
downloadcgit-5f12e45fe3338095916a444ff106dd9fc9991d84.zip
cgit-5f12e45fe3338095916a444ff106dd9fc9991d84.tar.gz
cgit-5f12e45fe3338095916a444ff106dd9fc9991d84.tar.bz2
Merge branch 'fp/linenumbers'
Diffstat (more/less context) (ignore whitespace changes)
-rw-r--r--cgit.c3
-rw-r--r--cgit.css6
-rw-r--r--cgit.h1
-rw-r--r--cgitrc.5.txt4
-rw-r--r--ui-tree.c35
5 files changed, 32 insertions, 17 deletions
diff --git a/cgit.c b/cgit.c
index b0e1c44..ec40e1f 100644
--- a/cgit.c
+++ b/cgit.c
@@ -1,558 +1,561 @@
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
43void config_cb(const char *name, const char *value) 43void config_cb(const char *name, const char *value)
44{ 44{
45 if (!strcmp(name, "root-title")) 45 if (!strcmp(name, "root-title"))
46 ctx.cfg.root_title = xstrdup(value); 46 ctx.cfg.root_title = xstrdup(value);
47 else if (!strcmp(name, "root-desc")) 47 else if (!strcmp(name, "root-desc"))
48 ctx.cfg.root_desc = xstrdup(value); 48 ctx.cfg.root_desc = xstrdup(value);
49 else if (!strcmp(name, "root-readme")) 49 else if (!strcmp(name, "root-readme"))
50 ctx.cfg.root_readme = xstrdup(value); 50 ctx.cfg.root_readme = xstrdup(value);
51 else if (!strcmp(name, "css")) 51 else if (!strcmp(name, "css"))
52 ctx.cfg.css = xstrdup(value); 52 ctx.cfg.css = xstrdup(value);
53 else if (!strcmp(name, "favicon")) 53 else if (!strcmp(name, "favicon"))
54 ctx.cfg.favicon = xstrdup(value); 54 ctx.cfg.favicon = xstrdup(value);
55 else if (!strcmp(name, "footer")) 55 else if (!strcmp(name, "footer"))
56 ctx.cfg.footer = xstrdup(value); 56 ctx.cfg.footer = xstrdup(value);
57 else if (!strcmp(name, "head-include")) 57 else if (!strcmp(name, "head-include"))
58 ctx.cfg.head_include = xstrdup(value); 58 ctx.cfg.head_include = xstrdup(value);
59 else if (!strcmp(name, "header")) 59 else if (!strcmp(name, "header"))
60 ctx.cfg.header = xstrdup(value); 60 ctx.cfg.header = xstrdup(value);
61 else if (!strcmp(name, "logo")) 61 else if (!strcmp(name, "logo"))
62 ctx.cfg.logo = xstrdup(value); 62 ctx.cfg.logo = xstrdup(value);
63 else if (!strcmp(name, "index-header")) 63 else if (!strcmp(name, "index-header"))
64 ctx.cfg.index_header = xstrdup(value); 64 ctx.cfg.index_header = xstrdup(value);
65 else if (!strcmp(name, "index-info")) 65 else if (!strcmp(name, "index-info"))
66 ctx.cfg.index_info = xstrdup(value); 66 ctx.cfg.index_info = xstrdup(value);
67 else if (!strcmp(name, "logo-link")) 67 else if (!strcmp(name, "logo-link"))
68 ctx.cfg.logo_link = xstrdup(value); 68 ctx.cfg.logo_link = xstrdup(value);
69 else if (!strcmp(name, "module-link")) 69 else if (!strcmp(name, "module-link"))
70 ctx.cfg.module_link = xstrdup(value); 70 ctx.cfg.module_link = xstrdup(value);
71 else if (!strcmp(name, "virtual-root")) { 71 else if (!strcmp(name, "virtual-root")) {
72 ctx.cfg.virtual_root = trim_end(value, '/'); 72 ctx.cfg.virtual_root = trim_end(value, '/');
73 if (!ctx.cfg.virtual_root && (!strcmp(value, "/"))) 73 if (!ctx.cfg.virtual_root && (!strcmp(value, "/")))
74 ctx.cfg.virtual_root = ""; 74 ctx.cfg.virtual_root = "";
75 } else if (!strcmp(name, "nocache")) 75 } else if (!strcmp(name, "nocache"))
76 ctx.cfg.nocache = atoi(value); 76 ctx.cfg.nocache = atoi(value);
77 else if (!strcmp(name, "noplainemail")) 77 else if (!strcmp(name, "noplainemail"))
78 ctx.cfg.noplainemail = atoi(value); 78 ctx.cfg.noplainemail = atoi(value);
79 else if (!strcmp(name, "noheader")) 79 else if (!strcmp(name, "noheader"))
80 ctx.cfg.noheader = atoi(value); 80 ctx.cfg.noheader = atoi(value);
81 else if (!strcmp(name, "snapshots")) 81 else if (!strcmp(name, "snapshots"))
82 ctx.cfg.snapshots = cgit_parse_snapshots_mask(value); 82 ctx.cfg.snapshots = cgit_parse_snapshots_mask(value);
83 else if (!strcmp(name, "enable-index-links")) 83 else if (!strcmp(name, "enable-index-links"))
84 ctx.cfg.enable_index_links = atoi(value); 84 ctx.cfg.enable_index_links = atoi(value);
85 else if (!strcmp(name, "enable-log-filecount")) 85 else if (!strcmp(name, "enable-log-filecount"))
86 ctx.cfg.enable_log_filecount = atoi(value); 86 ctx.cfg.enable_log_filecount = atoi(value);
87 else if (!strcmp(name, "enable-log-linecount")) 87 else if (!strcmp(name, "enable-log-linecount"))
88 ctx.cfg.enable_log_linecount = atoi(value); 88 ctx.cfg.enable_log_linecount = atoi(value);
89 else if (!strcmp(name, "enable-tree-linenumbers"))
90 ctx.cfg.enable_tree_linenumbers = atoi(value);
89 else if (!strcmp(name, "max-stats")) 91 else if (!strcmp(name, "max-stats"))
90 ctx.cfg.max_stats = cgit_find_stats_period(value, NULL); 92 ctx.cfg.max_stats = cgit_find_stats_period(value, NULL);
91 else if (!strcmp(name, "cache-size")) 93 else if (!strcmp(name, "cache-size"))
92 ctx.cfg.cache_size = atoi(value); 94 ctx.cfg.cache_size = atoi(value);
93 else if (!strcmp(name, "cache-root")) 95 else if (!strcmp(name, "cache-root"))
94 ctx.cfg.cache_root = xstrdup(value); 96 ctx.cfg.cache_root = xstrdup(value);
95 else if (!strcmp(name, "cache-root-ttl")) 97 else if (!strcmp(name, "cache-root-ttl"))
96 ctx.cfg.cache_root_ttl = atoi(value); 98 ctx.cfg.cache_root_ttl = atoi(value);
97 else if (!strcmp(name, "cache-repo-ttl")) 99 else if (!strcmp(name, "cache-repo-ttl"))
98 ctx.cfg.cache_repo_ttl = atoi(value); 100 ctx.cfg.cache_repo_ttl = atoi(value);
99 else if (!strcmp(name, "cache-static-ttl")) 101 else if (!strcmp(name, "cache-static-ttl"))
100 ctx.cfg.cache_static_ttl = atoi(value); 102 ctx.cfg.cache_static_ttl = atoi(value);
101 else if (!strcmp(name, "cache-dynamic-ttl")) 103 else if (!strcmp(name, "cache-dynamic-ttl"))
102 ctx.cfg.cache_dynamic_ttl = atoi(value); 104 ctx.cfg.cache_dynamic_ttl = atoi(value);
103 else if (!strcmp(name, "about-filter")) 105 else if (!strcmp(name, "about-filter"))
104 ctx.cfg.about_filter = new_filter(value, 0); 106 ctx.cfg.about_filter = new_filter(value, 0);
105 else if (!strcmp(name, "commit-filter")) 107 else if (!strcmp(name, "commit-filter"))
106 ctx.cfg.commit_filter = new_filter(value, 0); 108 ctx.cfg.commit_filter = new_filter(value, 0);
107 else if (!strcmp(name, "embedded")) 109 else if (!strcmp(name, "embedded"))
108 ctx.cfg.embedded = atoi(value); 110 ctx.cfg.embedded = atoi(value);
109 else if (!strcmp(name, "max-message-length")) 111 else if (!strcmp(name, "max-message-length"))
110 ctx.cfg.max_msg_len = atoi(value); 112 ctx.cfg.max_msg_len = atoi(value);
111 else if (!strcmp(name, "max-repodesc-length")) 113 else if (!strcmp(name, "max-repodesc-length"))
112 ctx.cfg.max_repodesc_len = atoi(value); 114 ctx.cfg.max_repodesc_len = atoi(value);
113 else if (!strcmp(name, "max-repo-count")) 115 else if (!strcmp(name, "max-repo-count"))
114 ctx.cfg.max_repo_count = atoi(value); 116 ctx.cfg.max_repo_count = atoi(value);
115 else if (!strcmp(name, "max-commit-count")) 117 else if (!strcmp(name, "max-commit-count"))
116 ctx.cfg.max_commit_count = atoi(value); 118 ctx.cfg.max_commit_count = atoi(value);
117 else if (!strcmp(name, "source-filter")) 119 else if (!strcmp(name, "source-filter"))
118 ctx.cfg.source_filter = new_filter(value, 1); 120 ctx.cfg.source_filter = new_filter(value, 1);
119 else if (!strcmp(name, "summary-log")) 121 else if (!strcmp(name, "summary-log"))
120 ctx.cfg.summary_log = atoi(value); 122 ctx.cfg.summary_log = atoi(value);
121 else if (!strcmp(name, "summary-branches")) 123 else if (!strcmp(name, "summary-branches"))
122 ctx.cfg.summary_branches = atoi(value); 124 ctx.cfg.summary_branches = atoi(value);
123 else if (!strcmp(name, "summary-tags")) 125 else if (!strcmp(name, "summary-tags"))
124 ctx.cfg.summary_tags = atoi(value); 126 ctx.cfg.summary_tags = atoi(value);
125 else if (!strcmp(name, "agefile")) 127 else if (!strcmp(name, "agefile"))
126 ctx.cfg.agefile = xstrdup(value); 128 ctx.cfg.agefile = xstrdup(value);
127 else if (!strcmp(name, "renamelimit")) 129 else if (!strcmp(name, "renamelimit"))
128 ctx.cfg.renamelimit = atoi(value); 130 ctx.cfg.renamelimit = atoi(value);
129 else if (!strcmp(name, "robots")) 131 else if (!strcmp(name, "robots"))
130 ctx.cfg.robots = xstrdup(value); 132 ctx.cfg.robots = xstrdup(value);
131 else if (!strcmp(name, "clone-prefix")) 133 else if (!strcmp(name, "clone-prefix"))
132 ctx.cfg.clone_prefix = xstrdup(value); 134 ctx.cfg.clone_prefix = xstrdup(value);
133 else if (!strcmp(name, "local-time")) 135 else if (!strcmp(name, "local-time"))
134 ctx.cfg.local_time = atoi(value); 136 ctx.cfg.local_time = atoi(value);
135 else if (!prefixcmp(name, "mimetype.")) 137 else if (!prefixcmp(name, "mimetype."))
136 add_mimetype(name + 9, value); 138 add_mimetype(name + 9, value);
137 else if (!strcmp(name, "repo.group")) 139 else if (!strcmp(name, "repo.group"))
138 ctx.cfg.repo_group = xstrdup(value); 140 ctx.cfg.repo_group = xstrdup(value);
139 else if (!strcmp(name, "repo.url")) 141 else if (!strcmp(name, "repo.url"))
140 ctx.repo = cgit_add_repo(value); 142 ctx.repo = cgit_add_repo(value);
141 else if (!strcmp(name, "repo.name")) 143 else if (!strcmp(name, "repo.name"))
142 ctx.repo->name = xstrdup(value); 144 ctx.repo->name = xstrdup(value);
143 else if (ctx.repo && !strcmp(name, "repo.path")) 145 else if (ctx.repo && !strcmp(name, "repo.path"))
144 ctx.repo->path = trim_end(value, '/'); 146 ctx.repo->path = trim_end(value, '/');
145 else if (ctx.repo && !strcmp(name, "repo.clone-url")) 147 else if (ctx.repo && !strcmp(name, "repo.clone-url"))
146 ctx.repo->clone_url = xstrdup(value); 148 ctx.repo->clone_url = xstrdup(value);
147 else if (ctx.repo && !strcmp(name, "repo.desc")) 149 else if (ctx.repo && !strcmp(name, "repo.desc"))
148 ctx.repo->desc = xstrdup(value); 150 ctx.repo->desc = xstrdup(value);
149 else if (ctx.repo && !strcmp(name, "repo.owner")) 151 else if (ctx.repo && !strcmp(name, "repo.owner"))
150 ctx.repo->owner = xstrdup(value); 152 ctx.repo->owner = xstrdup(value);
151 else if (ctx.repo && !strcmp(name, "repo.defbranch")) 153 else if (ctx.repo && !strcmp(name, "repo.defbranch"))
152 ctx.repo->defbranch = xstrdup(value); 154 ctx.repo->defbranch = xstrdup(value);
153 else if (ctx.repo && !strcmp(name, "repo.snapshots")) 155 else if (ctx.repo && !strcmp(name, "repo.snapshots"))
154 ctx.repo->snapshots = ctx.cfg.snapshots & cgit_parse_snapshots_mask(value); /* XXX: &? */ 156 ctx.repo->snapshots = ctx.cfg.snapshots & cgit_parse_snapshots_mask(value); /* XXX: &? */
155 else if (ctx.repo && !strcmp(name, "repo.enable-log-filecount")) 157 else if (ctx.repo && !strcmp(name, "repo.enable-log-filecount"))
156 ctx.repo->enable_log_filecount = ctx.cfg.enable_log_filecount * atoi(value); 158 ctx.repo->enable_log_filecount = ctx.cfg.enable_log_filecount * atoi(value);
157 else if (ctx.repo && !strcmp(name, "repo.enable-log-linecount")) 159 else if (ctx.repo && !strcmp(name, "repo.enable-log-linecount"))
158 ctx.repo->enable_log_linecount = ctx.cfg.enable_log_linecount * atoi(value); 160 ctx.repo->enable_log_linecount = ctx.cfg.enable_log_linecount * atoi(value);
159 else if (ctx.repo && !strcmp(name, "repo.max-stats")) 161 else if (ctx.repo && !strcmp(name, "repo.max-stats"))
160 ctx.repo->max_stats = cgit_find_stats_period(value, NULL); 162 ctx.repo->max_stats = cgit_find_stats_period(value, NULL);
161 else if (ctx.repo && !strcmp(name, "repo.module-link")) 163 else if (ctx.repo && !strcmp(name, "repo.module-link"))
162 ctx.repo->module_link= xstrdup(value); 164 ctx.repo->module_link= xstrdup(value);
163 else if (ctx.repo && !strcmp(name, "repo.about-filter")) 165 else if (ctx.repo && !strcmp(name, "repo.about-filter"))
164 ctx.repo->about_filter = new_filter(value, 0); 166 ctx.repo->about_filter = new_filter(value, 0);
165 else if (ctx.repo && !strcmp(name, "repo.commit-filter")) 167 else if (ctx.repo && !strcmp(name, "repo.commit-filter"))
166 ctx.repo->commit_filter = new_filter(value, 0); 168 ctx.repo->commit_filter = new_filter(value, 0);
167 else if (ctx.repo && !strcmp(name, "repo.source-filter")) 169 else if (ctx.repo && !strcmp(name, "repo.source-filter"))
168 ctx.repo->source_filter = new_filter(value, 1); 170 ctx.repo->source_filter = new_filter(value, 1);
169 else if (ctx.repo && !strcmp(name, "repo.readme") && value != NULL) { 171 else if (ctx.repo && !strcmp(name, "repo.readme") && value != NULL) {
170 if (*value == '/') 172 if (*value == '/')
171 ctx.repo->readme = xstrdup(value); 173 ctx.repo->readme = xstrdup(value);
172 else 174 else
173 ctx.repo->readme = xstrdup(fmt("%s/%s", ctx.repo->path, value)); 175 ctx.repo->readme = xstrdup(fmt("%s/%s", ctx.repo->path, value));
174 } else if (!strcmp(name, "include")) 176 } else if (!strcmp(name, "include"))
175 parse_configfile(value, config_cb); 177 parse_configfile(value, config_cb);
176} 178}
177 179
178static void querystring_cb(const char *name, const char *value) 180static void querystring_cb(const char *name, const char *value)
179{ 181{
180 if (!value) 182 if (!value)
181 value = ""; 183 value = "";
182 184
183 if (!strcmp(name,"r")) { 185 if (!strcmp(name,"r")) {
184 ctx.qry.repo = xstrdup(value); 186 ctx.qry.repo = xstrdup(value);
185 ctx.repo = cgit_get_repoinfo(value); 187 ctx.repo = cgit_get_repoinfo(value);
186 } else if (!strcmp(name, "p")) { 188 } else if (!strcmp(name, "p")) {
187 ctx.qry.page = xstrdup(value); 189 ctx.qry.page = xstrdup(value);
188 } else if (!strcmp(name, "url")) { 190 } else if (!strcmp(name, "url")) {
189 ctx.qry.url = xstrdup(value); 191 ctx.qry.url = xstrdup(value);
190 cgit_parse_url(value); 192 cgit_parse_url(value);
191 } else if (!strcmp(name, "qt")) { 193 } else if (!strcmp(name, "qt")) {
192 ctx.qry.grep = xstrdup(value); 194 ctx.qry.grep = xstrdup(value);
193 } else if (!strcmp(name, "q")) { 195 } else if (!strcmp(name, "q")) {
194 ctx.qry.search = xstrdup(value); 196 ctx.qry.search = xstrdup(value);
195 } else if (!strcmp(name, "h")) { 197 } else if (!strcmp(name, "h")) {
196 ctx.qry.head = xstrdup(value); 198 ctx.qry.head = xstrdup(value);
197 ctx.qry.has_symref = 1; 199 ctx.qry.has_symref = 1;
198 } else if (!strcmp(name, "id")) { 200 } else if (!strcmp(name, "id")) {
199 ctx.qry.sha1 = xstrdup(value); 201 ctx.qry.sha1 = xstrdup(value);
200 ctx.qry.has_sha1 = 1; 202 ctx.qry.has_sha1 = 1;
201 } else if (!strcmp(name, "id2")) { 203 } else if (!strcmp(name, "id2")) {
202 ctx.qry.sha2 = xstrdup(value); 204 ctx.qry.sha2 = xstrdup(value);
203 ctx.qry.has_sha1 = 1; 205 ctx.qry.has_sha1 = 1;
204 } else if (!strcmp(name, "ofs")) { 206 } else if (!strcmp(name, "ofs")) {
205 ctx.qry.ofs = atoi(value); 207 ctx.qry.ofs = atoi(value);
206 } else if (!strcmp(name, "path")) { 208 } else if (!strcmp(name, "path")) {
207 ctx.qry.path = trim_end(value, '/'); 209 ctx.qry.path = trim_end(value, '/');
208 } else if (!strcmp(name, "name")) { 210 } else if (!strcmp(name, "name")) {
209 ctx.qry.name = xstrdup(value); 211 ctx.qry.name = xstrdup(value);
210 } else if (!strcmp(name, "mimetype")) { 212 } else if (!strcmp(name, "mimetype")) {
211 ctx.qry.mimetype = xstrdup(value); 213 ctx.qry.mimetype = xstrdup(value);
212 } else if (!strcmp(name, "s")){ 214 } else if (!strcmp(name, "s")){
213 ctx.qry.sort = xstrdup(value); 215 ctx.qry.sort = xstrdup(value);
214 } else if (!strcmp(name, "showmsg")) { 216 } else if (!strcmp(name, "showmsg")) {
215 ctx.qry.showmsg = atoi(value); 217 ctx.qry.showmsg = atoi(value);
216 } else if (!strcmp(name, "period")) { 218 } else if (!strcmp(name, "period")) {
217 ctx.qry.period = xstrdup(value); 219 ctx.qry.period = xstrdup(value);
218 } 220 }
219} 221}
220 222
221char *xstrdupn(const char *str) 223char *xstrdupn(const char *str)
222{ 224{
223 return (str ? xstrdup(str) : NULL); 225 return (str ? xstrdup(str) : NULL);
224} 226}
225 227
226static void prepare_context(struct cgit_context *ctx) 228static void prepare_context(struct cgit_context *ctx)
227{ 229{
228 memset(ctx, 0, sizeof(ctx)); 230 memset(ctx, 0, sizeof(ctx));
229 ctx->cfg.agefile = "info/web/last-modified"; 231 ctx->cfg.agefile = "info/web/last-modified";
230 ctx->cfg.nocache = 0; 232 ctx->cfg.nocache = 0;
231 ctx->cfg.cache_size = 0; 233 ctx->cfg.cache_size = 0;
232 ctx->cfg.cache_dynamic_ttl = 5; 234 ctx->cfg.cache_dynamic_ttl = 5;
233 ctx->cfg.cache_max_create_time = 5; 235 ctx->cfg.cache_max_create_time = 5;
234 ctx->cfg.cache_repo_ttl = 5; 236 ctx->cfg.cache_repo_ttl = 5;
235 ctx->cfg.cache_root = CGIT_CACHE_ROOT; 237 ctx->cfg.cache_root = CGIT_CACHE_ROOT;
236 ctx->cfg.cache_root_ttl = 5; 238 ctx->cfg.cache_root_ttl = 5;
237 ctx->cfg.cache_static_ttl = -1; 239 ctx->cfg.cache_static_ttl = -1;
238 ctx->cfg.css = "/cgit.css"; 240 ctx->cfg.css = "/cgit.css";
239 ctx->cfg.logo = "/cgit.png"; 241 ctx->cfg.logo = "/cgit.png";
240 ctx->cfg.local_time = 0; 242 ctx->cfg.local_time = 0;
243 ctx->cfg.enable_tree_linenumbers = 1;
241 ctx->cfg.max_repo_count = 50; 244 ctx->cfg.max_repo_count = 50;
242 ctx->cfg.max_commit_count = 50; 245 ctx->cfg.max_commit_count = 50;
243 ctx->cfg.max_lock_attempts = 5; 246 ctx->cfg.max_lock_attempts = 5;
244 ctx->cfg.max_msg_len = 80; 247 ctx->cfg.max_msg_len = 80;
245 ctx->cfg.max_repodesc_len = 80; 248 ctx->cfg.max_repodesc_len = 80;
246 ctx->cfg.max_stats = 0; 249 ctx->cfg.max_stats = 0;
247 ctx->cfg.module_link = "./?repo=%s&page=commit&id=%s"; 250 ctx->cfg.module_link = "./?repo=%s&page=commit&id=%s";
248 ctx->cfg.renamelimit = -1; 251 ctx->cfg.renamelimit = -1;
249 ctx->cfg.robots = "index, nofollow"; 252 ctx->cfg.robots = "index, nofollow";
250 ctx->cfg.root_title = "Git repository browser"; 253 ctx->cfg.root_title = "Git repository browser";
251 ctx->cfg.root_desc = "a fast webinterface for the git dscm"; 254 ctx->cfg.root_desc = "a fast webinterface for the git dscm";
252 ctx->cfg.script_name = CGIT_SCRIPT_NAME; 255 ctx->cfg.script_name = CGIT_SCRIPT_NAME;
253 ctx->cfg.summary_branches = 10; 256 ctx->cfg.summary_branches = 10;
254 ctx->cfg.summary_log = 10; 257 ctx->cfg.summary_log = 10;
255 ctx->cfg.summary_tags = 10; 258 ctx->cfg.summary_tags = 10;
256 ctx->env.cgit_config = xstrdupn(getenv("CGIT_CONFIG")); 259 ctx->env.cgit_config = xstrdupn(getenv("CGIT_CONFIG"));
257 ctx->env.http_host = xstrdupn(getenv("HTTP_HOST")); 260 ctx->env.http_host = xstrdupn(getenv("HTTP_HOST"));
258 ctx->env.https = xstrdupn(getenv("HTTPS")); 261 ctx->env.https = xstrdupn(getenv("HTTPS"));
259 ctx->env.no_http = xstrdupn(getenv("NO_HTTP")); 262 ctx->env.no_http = xstrdupn(getenv("NO_HTTP"));
260 ctx->env.path_info = xstrdupn(getenv("PATH_INFO")); 263 ctx->env.path_info = xstrdupn(getenv("PATH_INFO"));
261 ctx->env.query_string = xstrdupn(getenv("QUERY_STRING")); 264 ctx->env.query_string = xstrdupn(getenv("QUERY_STRING"));
262 ctx->env.request_method = xstrdupn(getenv("REQUEST_METHOD")); 265 ctx->env.request_method = xstrdupn(getenv("REQUEST_METHOD"));
263 ctx->env.script_name = xstrdupn(getenv("SCRIPT_NAME")); 266 ctx->env.script_name = xstrdupn(getenv("SCRIPT_NAME"));
264 ctx->env.server_name = xstrdupn(getenv("SERVER_NAME")); 267 ctx->env.server_name = xstrdupn(getenv("SERVER_NAME"));
265 ctx->env.server_port = xstrdupn(getenv("SERVER_PORT")); 268 ctx->env.server_port = xstrdupn(getenv("SERVER_PORT"));
266 ctx->page.mimetype = "text/html"; 269 ctx->page.mimetype = "text/html";
267 ctx->page.charset = PAGE_ENCODING; 270 ctx->page.charset = PAGE_ENCODING;
268 ctx->page.filename = NULL; 271 ctx->page.filename = NULL;
269 ctx->page.size = 0; 272 ctx->page.size = 0;
270 ctx->page.modified = time(NULL); 273 ctx->page.modified = time(NULL);
271 ctx->page.expires = ctx->page.modified; 274 ctx->page.expires = ctx->page.modified;
272 ctx->page.etag = NULL; 275 ctx->page.etag = NULL;
273 memset(&ctx->cfg.mimetypes, 0, sizeof(struct string_list)); 276 memset(&ctx->cfg.mimetypes, 0, sizeof(struct string_list));
274 if (ctx->env.script_name) 277 if (ctx->env.script_name)
275 ctx->cfg.script_name = ctx->env.script_name; 278 ctx->cfg.script_name = ctx->env.script_name;
276 if (ctx->env.query_string) 279 if (ctx->env.query_string)
277 ctx->qry.raw = ctx->env.query_string; 280 ctx->qry.raw = ctx->env.query_string;
278 if (!ctx->env.cgit_config) 281 if (!ctx->env.cgit_config)
279 ctx->env.cgit_config = CGIT_CONFIG; 282 ctx->env.cgit_config = CGIT_CONFIG;
280} 283}
281 284
282struct refmatch { 285struct refmatch {
283 char *req_ref; 286 char *req_ref;
284 char *first_ref; 287 char *first_ref;
285 int match; 288 int match;
286}; 289};
287 290
288int find_current_ref(const char *refname, const unsigned char *sha1, 291int find_current_ref(const char *refname, const unsigned char *sha1,
289 int flags, void *cb_data) 292 int flags, void *cb_data)
290{ 293{
291 struct refmatch *info; 294 struct refmatch *info;
292 295
293 info = (struct refmatch *)cb_data; 296 info = (struct refmatch *)cb_data;
294 if (!strcmp(refname, info->req_ref)) 297 if (!strcmp(refname, info->req_ref))
295 info->match = 1; 298 info->match = 1;
296 if (!info->first_ref) 299 if (!info->first_ref)
297 info->first_ref = xstrdup(refname); 300 info->first_ref = xstrdup(refname);
298 return info->match; 301 return info->match;
299} 302}
300 303
301char *find_default_branch(struct cgit_repo *repo) 304char *find_default_branch(struct cgit_repo *repo)
302{ 305{
303 struct refmatch info; 306 struct refmatch info;
304 char *ref; 307 char *ref;
305 308
306 info.req_ref = repo->defbranch; 309 info.req_ref = repo->defbranch;
307 info.first_ref = NULL; 310 info.first_ref = NULL;
308 info.match = 0; 311 info.match = 0;
309 for_each_branch_ref(find_current_ref, &info); 312 for_each_branch_ref(find_current_ref, &info);
310 if (info.match) 313 if (info.match)
311 ref = info.req_ref; 314 ref = info.req_ref;
312 else 315 else
313 ref = info.first_ref; 316 ref = info.first_ref;
314 if (ref) 317 if (ref)
315 ref = xstrdup(ref); 318 ref = xstrdup(ref);
316 return ref; 319 return ref;
317} 320}
318 321
319static int prepare_repo_cmd(struct cgit_context *ctx) 322static int prepare_repo_cmd(struct cgit_context *ctx)
320{ 323{
321 char *tmp; 324 char *tmp;
322 unsigned char sha1[20]; 325 unsigned char sha1[20];
323 int nongit = 0; 326 int nongit = 0;
324 327
325 setenv("GIT_DIR", ctx->repo->path, 1); 328 setenv("GIT_DIR", ctx->repo->path, 1);
326 setup_git_directory_gently(&nongit); 329 setup_git_directory_gently(&nongit);
327 if (nongit) { 330 if (nongit) {
328 ctx->page.title = fmt("%s - %s", ctx->cfg.root_title, 331 ctx->page.title = fmt("%s - %s", ctx->cfg.root_title,
329 "config error"); 332 "config error");
330 tmp = fmt("Not a git repository: '%s'", ctx->repo->path); 333 tmp = fmt("Not a git repository: '%s'", ctx->repo->path);
331 ctx->repo = NULL; 334 ctx->repo = NULL;
332 cgit_print_http_headers(ctx); 335 cgit_print_http_headers(ctx);
333 cgit_print_docstart(ctx); 336 cgit_print_docstart(ctx);
334 cgit_print_pageheader(ctx); 337 cgit_print_pageheader(ctx);
335 cgit_print_error(tmp); 338 cgit_print_error(tmp);
336 cgit_print_docend(); 339 cgit_print_docend();
337 return 1; 340 return 1;
338 } 341 }
339 ctx->page.title = fmt("%s - %s", ctx->repo->name, ctx->repo->desc); 342 ctx->page.title = fmt("%s - %s", ctx->repo->name, ctx->repo->desc);
340 343
341 if (!ctx->qry.head) { 344 if (!ctx->qry.head) {
342 ctx->qry.nohead = 1; 345 ctx->qry.nohead = 1;
343 ctx->qry.head = find_default_branch(ctx->repo); 346 ctx->qry.head = find_default_branch(ctx->repo);
344 ctx->repo->defbranch = ctx->qry.head; 347 ctx->repo->defbranch = ctx->qry.head;
345 } 348 }
346 349
347 if (!ctx->qry.head) { 350 if (!ctx->qry.head) {
348 cgit_print_http_headers(ctx); 351 cgit_print_http_headers(ctx);
349 cgit_print_docstart(ctx); 352 cgit_print_docstart(ctx);
350 cgit_print_pageheader(ctx); 353 cgit_print_pageheader(ctx);
351 cgit_print_error("Repository seems to be empty"); 354 cgit_print_error("Repository seems to be empty");
352 cgit_print_docend(); 355 cgit_print_docend();
353 return 1; 356 return 1;
354 } 357 }
355 358
356 if (get_sha1(ctx->qry.head, sha1)) { 359 if (get_sha1(ctx->qry.head, sha1)) {
357 tmp = xstrdup(ctx->qry.head); 360 tmp = xstrdup(ctx->qry.head);
358 ctx->qry.head = ctx->repo->defbranch; 361 ctx->qry.head = ctx->repo->defbranch;
359 ctx->page.status = 404; 362 ctx->page.status = 404;
360 ctx->page.statusmsg = "not found"; 363 ctx->page.statusmsg = "not found";
361 cgit_print_http_headers(ctx); 364 cgit_print_http_headers(ctx);
362 cgit_print_docstart(ctx); 365 cgit_print_docstart(ctx);
363 cgit_print_pageheader(ctx); 366 cgit_print_pageheader(ctx);
364 cgit_print_error(fmt("Invalid branch: %s", tmp)); 367 cgit_print_error(fmt("Invalid branch: %s", tmp));
365 cgit_print_docend(); 368 cgit_print_docend();
366 return 1; 369 return 1;
367 } 370 }
368 return 0; 371 return 0;
369} 372}
370 373
371static void process_request(void *cbdata) 374static void process_request(void *cbdata)
372{ 375{
373 struct cgit_context *ctx = cbdata; 376 struct cgit_context *ctx = cbdata;
374 struct cgit_cmd *cmd; 377 struct cgit_cmd *cmd;
375 378
376 cmd = cgit_get_cmd(ctx); 379 cmd = cgit_get_cmd(ctx);
377 if (!cmd) { 380 if (!cmd) {
378 ctx->page.title = "cgit error"; 381 ctx->page.title = "cgit error";
379 cgit_print_http_headers(ctx); 382 cgit_print_http_headers(ctx);
380 cgit_print_docstart(ctx); 383 cgit_print_docstart(ctx);
381 cgit_print_pageheader(ctx); 384 cgit_print_pageheader(ctx);
382 cgit_print_error("Invalid request"); 385 cgit_print_error("Invalid request");
383 cgit_print_docend(); 386 cgit_print_docend();
384 return; 387 return;
385 } 388 }
386 389
387 if (cmd->want_repo && !ctx->repo) { 390 if (cmd->want_repo && !ctx->repo) {
388 cgit_print_http_headers(ctx); 391 cgit_print_http_headers(ctx);
389 cgit_print_docstart(ctx); 392 cgit_print_docstart(ctx);
390 cgit_print_pageheader(ctx); 393 cgit_print_pageheader(ctx);
391 cgit_print_error(fmt("No repository selected")); 394 cgit_print_error(fmt("No repository selected"));
392 cgit_print_docend(); 395 cgit_print_docend();
393 return; 396 return;
394 } 397 }
395 398
396 if (ctx->repo && prepare_repo_cmd(ctx)) 399 if (ctx->repo && prepare_repo_cmd(ctx))
397 return; 400 return;
398 401
399 if (cmd->want_layout) { 402 if (cmd->want_layout) {
400 cgit_print_http_headers(ctx); 403 cgit_print_http_headers(ctx);
401 cgit_print_docstart(ctx); 404 cgit_print_docstart(ctx);
402 cgit_print_pageheader(ctx); 405 cgit_print_pageheader(ctx);
403 } 406 }
404 407
405 cmd->fn(ctx); 408 cmd->fn(ctx);
406 409
407 if (cmd->want_layout) 410 if (cmd->want_layout)
408 cgit_print_docend(); 411 cgit_print_docend();
409} 412}
410 413
411int cmp_repos(const void *a, const void *b) 414int cmp_repos(const void *a, const void *b)
412{ 415{
413 const struct cgit_repo *ra = a, *rb = b; 416 const struct cgit_repo *ra = a, *rb = b;
414 return strcmp(ra->url, rb->url); 417 return strcmp(ra->url, rb->url);
415} 418}
416 419
417void print_repo(struct cgit_repo *repo) 420void print_repo(struct cgit_repo *repo)
418{ 421{
419 printf("repo.url=%s\n", repo->url); 422 printf("repo.url=%s\n", repo->url);
420 printf("repo.name=%s\n", repo->name); 423 printf("repo.name=%s\n", repo->name);
421 printf("repo.path=%s\n", repo->path); 424 printf("repo.path=%s\n", repo->path);
422 if (repo->owner) 425 if (repo->owner)
423 printf("repo.owner=%s\n", repo->owner); 426 printf("repo.owner=%s\n", repo->owner);
424 if (repo->desc) 427 if (repo->desc)
425 printf("repo.desc=%s\n", repo->desc); 428 printf("repo.desc=%s\n", repo->desc);
426 if (repo->readme) 429 if (repo->readme)
427 printf("repo.readme=%s\n", repo->readme); 430 printf("repo.readme=%s\n", repo->readme);
428 printf("\n"); 431 printf("\n");
429} 432}
430 433
431void print_repolist(struct cgit_repolist *list) 434void print_repolist(struct cgit_repolist *list)
432{ 435{
433 int i; 436 int i;
434 437
435 for(i = 0; i < list->count; i++) 438 for(i = 0; i < list->count; i++)
436 print_repo(&list->repos[i]); 439 print_repo(&list->repos[i]);
437} 440}
438 441
439 442
440static void cgit_parse_args(int argc, const char **argv) 443static void cgit_parse_args(int argc, const char **argv)
441{ 444{
442 int i; 445 int i;
443 int scan = 0; 446 int scan = 0;
444 447
445 for (i = 1; i < argc; i++) { 448 for (i = 1; i < argc; i++) {
446 if (!strncmp(argv[i], "--cache=", 8)) { 449 if (!strncmp(argv[i], "--cache=", 8)) {
447 ctx.cfg.cache_root = xstrdup(argv[i]+8); 450 ctx.cfg.cache_root = xstrdup(argv[i]+8);
448 } 451 }
449 if (!strcmp(argv[i], "--nocache")) { 452 if (!strcmp(argv[i], "--nocache")) {
450 ctx.cfg.nocache = 1; 453 ctx.cfg.nocache = 1;
451 } 454 }
452 if (!strcmp(argv[i], "--nohttp")) { 455 if (!strcmp(argv[i], "--nohttp")) {
453 ctx.env.no_http = "1"; 456 ctx.env.no_http = "1";
454 } 457 }
455 if (!strncmp(argv[i], "--query=", 8)) { 458 if (!strncmp(argv[i], "--query=", 8)) {
456 ctx.qry.raw = xstrdup(argv[i]+8); 459 ctx.qry.raw = xstrdup(argv[i]+8);
457 } 460 }
458 if (!strncmp(argv[i], "--repo=", 7)) { 461 if (!strncmp(argv[i], "--repo=", 7)) {
459 ctx.qry.repo = xstrdup(argv[i]+7); 462 ctx.qry.repo = xstrdup(argv[i]+7);
460 } 463 }
461 if (!strncmp(argv[i], "--page=", 7)) { 464 if (!strncmp(argv[i], "--page=", 7)) {
462 ctx.qry.page = xstrdup(argv[i]+7); 465 ctx.qry.page = xstrdup(argv[i]+7);
463 } 466 }
464 if (!strncmp(argv[i], "--head=", 7)) { 467 if (!strncmp(argv[i], "--head=", 7)) {
465 ctx.qry.head = xstrdup(argv[i]+7); 468 ctx.qry.head = xstrdup(argv[i]+7);
466 ctx.qry.has_symref = 1; 469 ctx.qry.has_symref = 1;
467 } 470 }
468 if (!strncmp(argv[i], "--sha1=", 7)) { 471 if (!strncmp(argv[i], "--sha1=", 7)) {
469 ctx.qry.sha1 = xstrdup(argv[i]+7); 472 ctx.qry.sha1 = xstrdup(argv[i]+7);
470 ctx.qry.has_sha1 = 1; 473 ctx.qry.has_sha1 = 1;
471 } 474 }
472 if (!strncmp(argv[i], "--ofs=", 6)) { 475 if (!strncmp(argv[i], "--ofs=", 6)) {
473 ctx.qry.ofs = atoi(argv[i]+6); 476 ctx.qry.ofs = atoi(argv[i]+6);
474 } 477 }
475 if (!strncmp(argv[i], "--scan-tree=", 12)) { 478 if (!strncmp(argv[i], "--scan-tree=", 12)) {
476 scan++; 479 scan++;
477 scan_tree(argv[i] + 12); 480 scan_tree(argv[i] + 12);
478 } 481 }
479 } 482 }
480 if (scan) { 483 if (scan) {
481 qsort(cgit_repolist.repos, cgit_repolist.count, 484 qsort(cgit_repolist.repos, cgit_repolist.count,
482 sizeof(struct cgit_repo), cmp_repos); 485 sizeof(struct cgit_repo), cmp_repos);
483 print_repolist(&cgit_repolist); 486 print_repolist(&cgit_repolist);
484 exit(0); 487 exit(0);
485 } 488 }
486} 489}
487 490
488static int calc_ttl() 491static int calc_ttl()
489{ 492{
490 if (!ctx.repo) 493 if (!ctx.repo)
491 return ctx.cfg.cache_root_ttl; 494 return ctx.cfg.cache_root_ttl;
492 495
493 if (!ctx.qry.page) 496 if (!ctx.qry.page)
494 return ctx.cfg.cache_repo_ttl; 497 return ctx.cfg.cache_repo_ttl;
495 498
496 if (ctx.qry.has_symref) 499 if (ctx.qry.has_symref)
497 return ctx.cfg.cache_dynamic_ttl; 500 return ctx.cfg.cache_dynamic_ttl;
498 501
499 if (ctx.qry.has_sha1) 502 if (ctx.qry.has_sha1)
500 return ctx.cfg.cache_static_ttl; 503 return ctx.cfg.cache_static_ttl;
501 504
502 return ctx.cfg.cache_repo_ttl; 505 return ctx.cfg.cache_repo_ttl;
503} 506}
504 507
505int main(int argc, const char **argv) 508int main(int argc, const char **argv)
506{ 509{
507 const char *path; 510 const char *path;
508 char *qry; 511 char *qry;
509 int err, ttl; 512 int err, ttl;
510 513
511 prepare_context(&ctx); 514 prepare_context(&ctx);
512 cgit_repolist.length = 0; 515 cgit_repolist.length = 0;
513 cgit_repolist.count = 0; 516 cgit_repolist.count = 0;
514 cgit_repolist.repos = NULL; 517 cgit_repolist.repos = NULL;
515 518
516 cgit_parse_args(argc, argv); 519 cgit_parse_args(argc, argv);
517 parse_configfile(ctx.env.cgit_config, config_cb); 520 parse_configfile(ctx.env.cgit_config, config_cb);
518 ctx.repo = NULL; 521 ctx.repo = NULL;
519 http_parse_querystring(ctx.qry.raw, querystring_cb); 522 http_parse_querystring(ctx.qry.raw, querystring_cb);
520 523
521 /* If virtual-root isn't specified in cgitrc, lets pretend 524 /* If virtual-root isn't specified in cgitrc, lets pretend
522 * that virtual-root equals SCRIPT_NAME. 525 * that virtual-root equals SCRIPT_NAME.
523 */ 526 */
524 if (!ctx.cfg.virtual_root) 527 if (!ctx.cfg.virtual_root)
525 ctx.cfg.virtual_root = ctx.cfg.script_name; 528 ctx.cfg.virtual_root = ctx.cfg.script_name;
526 529
527 /* If no url parameter is specified on the querystring, lets 530 /* If no url parameter is specified on the querystring, lets
528 * use PATH_INFO as url. This allows cgit to work with virtual 531 * use PATH_INFO as url. This allows cgit to work with virtual
529 * urls without the need for rewriterules in the webserver (as 532 * urls without the need for rewriterules in the webserver (as
530 * long as PATH_INFO is included in the cache lookup key). 533 * long as PATH_INFO is included in the cache lookup key).
531 */ 534 */
532 path = ctx.env.path_info; 535 path = ctx.env.path_info;
533 if (!ctx.qry.url && path) { 536 if (!ctx.qry.url && path) {
534 if (path[0] == '/') 537 if (path[0] == '/')
535 path++; 538 path++;
536 ctx.qry.url = xstrdup(path); 539 ctx.qry.url = xstrdup(path);
537 if (ctx.qry.raw) { 540 if (ctx.qry.raw) {
538 qry = ctx.qry.raw; 541 qry = ctx.qry.raw;
539 ctx.qry.raw = xstrdup(fmt("%s?%s", path, qry)); 542 ctx.qry.raw = xstrdup(fmt("%s?%s", path, qry));
540 free(qry); 543 free(qry);
541 } else 544 } else
542 ctx.qry.raw = xstrdup(ctx.qry.url); 545 ctx.qry.raw = xstrdup(ctx.qry.url);
543 cgit_parse_url(ctx.qry.url); 546 cgit_parse_url(ctx.qry.url);
544 } 547 }
545 548
546 ttl = calc_ttl(); 549 ttl = calc_ttl();
547 ctx.page.expires += ttl*60; 550 ctx.page.expires += ttl*60;
548 if (ctx.env.request_method && !strcmp(ctx.env.request_method, "HEAD")) 551 if (ctx.env.request_method && !strcmp(ctx.env.request_method, "HEAD"))
549 ctx.cfg.nocache = 1; 552 ctx.cfg.nocache = 1;
550 if (ctx.cfg.nocache) 553 if (ctx.cfg.nocache)
551 ctx.cfg.cache_size = 0; 554 ctx.cfg.cache_size = 0;
552 err = cache_process(ctx.cfg.cache_size, ctx.cfg.cache_root, 555 err = cache_process(ctx.cfg.cache_size, ctx.cfg.cache_root,
553 ctx.qry.raw, ttl, process_request, &ctx); 556 ctx.qry.raw, ttl, process_request, &ctx);
554 if (err) 557 if (err)
555 cgit_print_error(fmt("Error processing page: %s (%d)", 558 cgit_print_error(fmt("Error processing page: %s (%d)",
556 strerror(err), err)); 559 strerror(err), err));
557 return err; 560 return err;
558} 561}
diff --git a/cgit.css b/cgit.css
index e3b32e7..ebf3322 100644
--- a/cgit.css
+++ b/cgit.css
@@ -1,603 +1,603 @@
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:hover { 165table.list td a:hover {
166 color: #00f; 166 color: #00f;
167} 167}
168 168
169img { 169img {
170 border: none; 170 border: none;
171} 171}
172 172
173input#switch-btn { 173input#switch-btn {
174 margin: 2px 0px 0px 0px; 174 margin: 2px 0px 0px 0px;
175} 175}
176 176
177td#sidebar input.txt { 177td#sidebar input.txt {
178 width: 100%; 178 width: 100%;
179 margin: 2px 0px 0px 0px; 179 margin: 2px 0px 0px 0px;
180} 180}
181 181
182table#grid { 182table#grid {
183 margin: 0px; 183 margin: 0px;
184} 184}
185 185
186td#content { 186td#content {
187 vertical-align: top; 187 vertical-align: top;
188 padding: 1em 2em 1em 1em; 188 padding: 1em 2em 1em 1em;
189 border: none; 189 border: none;
190} 190}
191 191
192div#summary { 192div#summary {
193 vertical-align: top; 193 vertical-align: top;
194 margin-bottom: 1em; 194 margin-bottom: 1em;
195} 195}
196 196
197table#downloads { 197table#downloads {
198 float: right; 198 float: right;
199 border-collapse: collapse; 199 border-collapse: collapse;
200 border: solid 1px #777; 200 border: solid 1px #777;
201 margin-left: 0.5em; 201 margin-left: 0.5em;
202 margin-bottom: 0.5em; 202 margin-bottom: 0.5em;
203} 203}
204 204
205table#downloads th { 205table#downloads th {
206 background-color: #ccc; 206 background-color: #ccc;
207} 207}
208 208
209div#blob { 209div#blob {
210 border: solid 1px black; 210 border: solid 1px black;
211} 211}
212 212
213div.error { 213div.error {
214 color: red; 214 color: red;
215 font-weight: bold; 215 font-weight: bold;
216 margin: 1em 2em; 216 margin: 1em 2em;
217} 217}
218 218
219a.ls-blob, a.ls-dir, a.ls-mod { 219a.ls-blob, a.ls-dir, a.ls-mod {
220 font-family: monospace; 220 font-family: monospace;
221} 221}
222 222
223td.ls-size { 223td.ls-size {
224 text-align: right; 224 text-align: right;
225 font-family: monospace; 225 font-family: monospace;
226 width: 10em; 226 width: 10em;
227} 227}
228 228
229td.ls-mode { 229td.ls-mode {
230 font-family: monospace; 230 font-family: monospace;
231 width: 10em; 231 width: 10em;
232} 232}
233 233
234table.blob { 234table.blob {
235 margin-top: 0.5em; 235 margin-top: 0.5em;
236 border-top: solid 1px black; 236 border-top: solid 1px black;
237} 237}
238 238
239table.blob td.lines { 239table.blob td.lines {
240 margin: 0; padding: 0; 240 margin: 0; padding: 0 0 0 0.5em;
241 vertical-align: top; 241 vertical-align: top;
242 color: black; 242 color: black;
243} 243}
244 244
245table.blob td.linenumbers { 245table.blob td.linenumbers {
246 margin: 0; padding: 0; 246 margin: 0; padding: 0 0.5em 0 0.5em;
247 vertical-align: top; 247 vertical-align: top;
248 text-align: right;
248 border-right: 1px solid gray; 249 border-right: 1px solid gray;
249 background-color: #eee;
250} 250}
251 251
252table.blob pre { 252table.blob pre {
253 padding: 0; margin: 0; 253 padding: 0; margin: 0;
254} 254}
255 255
256table.blob a.no { 256table.blob a.no {
257 color: gray; 257 color: gray;
258 text-align: right; 258 text-align: right;
259 text-decoration: none; 259 text-decoration: none;
260} 260}
261 261
262table.blob a.no a:hover { 262table.blob a.no a:hover {
263 color: black; 263 color: black;
264} 264}
265 265
266table.bin-blob { 266table.bin-blob {
267 margin-top: 0.5em; 267 margin-top: 0.5em;
268 border: solid 1px black; 268 border: solid 1px black;
269} 269}
270 270
271table.bin-blob th { 271table.bin-blob th {
272 font-family: monospace; 272 font-family: monospace;
273 white-space: pre; 273 white-space: pre;
274 border: solid 1px #777; 274 border: solid 1px #777;
275 padding: 0.5em 1em; 275 padding: 0.5em 1em;
276} 276}
277 277
278table.bin-blob td { 278table.bin-blob td {
279 font-family: monospace; 279 font-family: monospace;
280 white-space: pre; 280 white-space: pre;
281 border-left: solid 1px #777; 281 border-left: solid 1px #777;
282 padding: 0em 1em; 282 padding: 0em 1em;
283} 283}
284 284
285table.nowrap td { 285table.nowrap td {
286 white-space: nowrap; 286 white-space: nowrap;
287} 287}
288 288
289table.commit-info { 289table.commit-info {
290 border-collapse: collapse; 290 border-collapse: collapse;
291 margin-top: 1.5em; 291 margin-top: 1.5em;
292} 292}
293 293
294table.commit-info th { 294table.commit-info th {
295 text-align: left; 295 text-align: left;
296 font-weight: normal; 296 font-weight: normal;
297 padding: 0.1em 1em 0.1em 0.1em; 297 padding: 0.1em 1em 0.1em 0.1em;
298 vertical-align: top; 298 vertical-align: top;
299} 299}
300 300
301table.commit-info td { 301table.commit-info td {
302 font-weight: normal; 302 font-weight: normal;
303 padding: 0.1em 1em 0.1em 0.1em; 303 padding: 0.1em 1em 0.1em 0.1em;
304} 304}
305 305
306div.commit-subject { 306div.commit-subject {
307 font-weight: bold; 307 font-weight: bold;
308 font-size: 125%; 308 font-size: 125%;
309 margin: 1.5em 0em 0.5em 0em; 309 margin: 1.5em 0em 0.5em 0em;
310 padding: 0em; 310 padding: 0em;
311} 311}
312 312
313div.commit-msg { 313div.commit-msg {
314 white-space: pre; 314 white-space: pre;
315 font-family: monospace; 315 font-family: monospace;
316} 316}
317 317
318div.diffstat-header { 318div.diffstat-header {
319 font-weight: bold; 319 font-weight: bold;
320 padding-top: 1.5em; 320 padding-top: 1.5em;
321} 321}
322 322
323table.diffstat { 323table.diffstat {
324 border-collapse: collapse; 324 border-collapse: collapse;
325 border: solid 1px #aaa; 325 border: solid 1px #aaa;
326 background-color: #eee; 326 background-color: #eee;
327} 327}
328 328
329table.diffstat th { 329table.diffstat th {
330 font-weight: normal; 330 font-weight: normal;
331 text-align: left; 331 text-align: left;
332 text-decoration: underline; 332 text-decoration: underline;
333 padding: 0.1em 1em 0.1em 0.1em; 333 padding: 0.1em 1em 0.1em 0.1em;
334 font-size: 100%; 334 font-size: 100%;
335} 335}
336 336
337table.diffstat td { 337table.diffstat td {
338 padding: 0.2em 0.2em 0.1em 0.1em; 338 padding: 0.2em 0.2em 0.1em 0.1em;
339 font-size: 100%; 339 font-size: 100%;
340 border: none; 340 border: none;
341} 341}
342 342
343table.diffstat td.mode { 343table.diffstat td.mode {
344 white-space: nowrap; 344 white-space: nowrap;
345} 345}
346 346
347table.diffstat td span.modechange { 347table.diffstat td span.modechange {
348 padding-left: 1em; 348 padding-left: 1em;
349 color: red; 349 color: red;
350} 350}
351 351
352table.diffstat td.add a { 352table.diffstat td.add a {
353 color: green; 353 color: green;
354} 354}
355 355
356table.diffstat td.del a { 356table.diffstat td.del a {
357 color: red; 357 color: red;
358} 358}
359 359
360table.diffstat td.upd a { 360table.diffstat td.upd a {
361 color: blue; 361 color: blue;
362} 362}
363 363
364table.diffstat td.graph { 364table.diffstat td.graph {
365 width: 500px; 365 width: 500px;
366 vertical-align: middle; 366 vertical-align: middle;
367} 367}
368 368
369table.diffstat td.graph table { 369table.diffstat td.graph table {
370 border: none; 370 border: none;
371} 371}
372 372
373table.diffstat td.graph td { 373table.diffstat td.graph td {
374 padding: 0px; 374 padding: 0px;
375 border: 0px; 375 border: 0px;
376 height: 7pt; 376 height: 7pt;
377} 377}
378 378
379table.diffstat td.graph td.add { 379table.diffstat td.graph td.add {
380 background-color: #5c5; 380 background-color: #5c5;
381} 381}
382 382
383table.diffstat td.graph td.rem { 383table.diffstat td.graph td.rem {
384 background-color: #c55; 384 background-color: #c55;
385} 385}
386 386
387div.diffstat-summary { 387div.diffstat-summary {
388 color: #888; 388 color: #888;
389 padding-top: 0.5em; 389 padding-top: 0.5em;
390} 390}
391 391
392table.diff { 392table.diff {
393 width: 100%; 393 width: 100%;
394} 394}
395 395
396table.diff td { 396table.diff td {
397 font-family: monospace; 397 font-family: monospace;
398 white-space: pre; 398 white-space: pre;
399} 399}
400 400
401table.diff td div.head { 401table.diff td div.head {
402 font-weight: bold; 402 font-weight: bold;
403 margin-top: 1em; 403 margin-top: 1em;
404 color: black; 404 color: black;
405} 405}
406 406
407table.diff td div.hunk { 407table.diff td div.hunk {
408 color: #009; 408 color: #009;
409} 409}
410 410
411table.diff td div.add { 411table.diff td div.add {
412 color: green; 412 color: green;
413} 413}
414 414
415table.diff td div.del { 415table.diff td div.del {
416 color: red; 416 color: red;
417} 417}
418 418
419.sha1 { 419.sha1 {
420 font-family: monospace; 420 font-family: monospace;
421 font-size: 90%; 421 font-size: 90%;
422} 422}
423 423
424.left { 424.left {
425 text-align: left; 425 text-align: left;
426} 426}
427 427
428.right { 428.right {
429 text-align: right; 429 text-align: right;
430} 430}
431 431
432table.list td.repogroup { 432table.list td.repogroup {
433 font-style: italic; 433 font-style: italic;
434 color: #888; 434 color: #888;
435} 435}
436 436
437a.button { 437a.button {
438 font-size: 80%; 438 font-size: 80%;
439 padding: 0em 0.5em; 439 padding: 0em 0.5em;
440} 440}
441 441
442a.primary { 442a.primary {
443 font-size: 100%; 443 font-size: 100%;
444} 444}
445 445
446a.secondary { 446a.secondary {
447 font-size: 90%; 447 font-size: 90%;
448} 448}
449 449
450td.toplevel-repo { 450td.toplevel-repo {
451 451
452} 452}
453 453
454table.list td.sublevel-repo { 454table.list td.sublevel-repo {
455 padding-left: 1.5em; 455 padding-left: 1.5em;
456} 456}
457 457
458div.pager { 458div.pager {
459 text-align: center; 459 text-align: center;
460 margin: 1em 0em 0em 0em; 460 margin: 1em 0em 0em 0em;
461} 461}
462 462
463div.pager a { 463div.pager a {
464 color: #777; 464 color: #777;
465 margin: 0em 0.5em; 465 margin: 0em 0.5em;
466} 466}
467 467
468span.age-mins { 468span.age-mins {
469 font-weight: bold; 469 font-weight: bold;
470 color: #080; 470 color: #080;
471} 471}
472 472
473span.age-hours { 473span.age-hours {
474 color: #080; 474 color: #080;
475} 475}
476 476
477span.age-days { 477span.age-days {
478 color: #040; 478 color: #040;
479} 479}
480 480
481span.age-weeks { 481span.age-weeks {
482 color: #444; 482 color: #444;
483} 483}
484 484
485span.age-months { 485span.age-months {
486 color: #888; 486 color: #888;
487} 487}
488 488
489span.age-years { 489span.age-years {
490 color: #bbb; 490 color: #bbb;
491} 491}
492div.footer { 492div.footer {
493 margin-top: 0.5em; 493 margin-top: 0.5em;
494 text-align: center; 494 text-align: center;
495 font-size: 80%; 495 font-size: 80%;
496 color: #ccc; 496 color: #ccc;
497} 497}
498a.branch-deco { 498a.branch-deco {
499 margin: 0px 0.5em; 499 margin: 0px 0.5em;
500 padding: 0px 0.25em; 500 padding: 0px 0.25em;
501 background-color: #88ff88; 501 background-color: #88ff88;
502 border: solid 1px #007700; 502 border: solid 1px #007700;
503} 503}
504a.tag-deco { 504a.tag-deco {
505 margin: 0px 0.5em; 505 margin: 0px 0.5em;
506 padding: 0px 0.25em; 506 padding: 0px 0.25em;
507 background-color: #ffff88; 507 background-color: #ffff88;
508 border: solid 1px #777700; 508 border: solid 1px #777700;
509} 509}
510a.remote-deco { 510a.remote-deco {
511 margin: 0px 0.5em; 511 margin: 0px 0.5em;
512 padding: 0px 0.25em; 512 padding: 0px 0.25em;
513 background-color: #ccccff; 513 background-color: #ccccff;
514 border: solid 1px #000077; 514 border: solid 1px #000077;
515} 515}
516a.deco { 516a.deco {
517 margin: 0px 0.5em; 517 margin: 0px 0.5em;
518 padding: 0px 0.25em; 518 padding: 0px 0.25em;
519 background-color: #ff8888; 519 background-color: #ff8888;
520 border: solid 1px #770000; 520 border: solid 1px #770000;
521} 521}
522 522
523div.commit-subject a { 523div.commit-subject a {
524 margin-left: 1em; 524 margin-left: 1em;
525 font-size: 75%; 525 font-size: 75%;
526} 526}
527 527
528table.stats { 528table.stats {
529 border: solid 1px black; 529 border: solid 1px black;
530 border-collapse: collapse; 530 border-collapse: collapse;
531} 531}
532 532
533table.stats th { 533table.stats th {
534 text-align: left; 534 text-align: left;
535 padding: 1px 0.5em; 535 padding: 1px 0.5em;
536 background-color: #eee; 536 background-color: #eee;
537 border: solid 1px black; 537 border: solid 1px black;
538} 538}
539 539
540table.stats td { 540table.stats td {
541 text-align: right; 541 text-align: right;
542 padding: 1px 0.5em; 542 padding: 1px 0.5em;
543 border: solid 1px black; 543 border: solid 1px black;
544} 544}
545 545
546table.stats td.total { 546table.stats td.total {
547 font-weight: bold; 547 font-weight: bold;
548 text-align: left; 548 text-align: left;
549} 549}
550 550
551table.stats td.sum { 551table.stats td.sum {
552 color: #c00; 552 color: #c00;
553 font-weight: bold; 553 font-weight: bold;
554 /*background-color: #eee; */ 554 /*background-color: #eee; */
555} 555}
556 556
557table.stats td.left { 557table.stats td.left {
558 text-align: left; 558 text-align: left;
559} 559}
560 560
561table.vgraph { 561table.vgraph {
562 border-collapse: separate; 562 border-collapse: separate;
563 border: solid 1px black; 563 border: solid 1px black;
564 height: 200px; 564 height: 200px;
565} 565}
566 566
567table.vgraph th { 567table.vgraph th {
568 background-color: #eee; 568 background-color: #eee;
569 font-weight: bold; 569 font-weight: bold;
570 border: solid 1px white; 570 border: solid 1px white;
571 padding: 1px 0.5em; 571 padding: 1px 0.5em;
572} 572}
573 573
574table.vgraph td { 574table.vgraph td {
575 vertical-align: bottom; 575 vertical-align: bottom;
576 padding: 0px 10px; 576 padding: 0px 10px;
577} 577}
578 578
579table.vgraph div.bar { 579table.vgraph div.bar {
580 background-color: #eee; 580 background-color: #eee;
581} 581}
582 582
583table.hgraph { 583table.hgraph {
584 border: solid 1px black; 584 border: solid 1px black;
585 width: 800px; 585 width: 800px;
586} 586}
587 587
588table.hgraph th { 588table.hgraph th {
589 background-color: #eee; 589 background-color: #eee;
590 font-weight: bold; 590 font-weight: bold;
591 border: solid 1px black; 591 border: solid 1px black;
592 padding: 1px 0.5em; 592 padding: 1px 0.5em;
593} 593}
594 594
595table.hgraph td { 595table.hgraph td {
596 vertical-align: center; 596 vertical-align: center;
597 padding: 2px 2px; 597 padding: 2px 2px;
598} 598}
599 599
600table.hgraph div.bar { 600table.hgraph div.bar {
601 background-color: #eee; 601 background-color: #eee;
602 height: 1em; 602 height: 1em;
603} 603}
diff --git a/cgit.h b/cgit.h
index adb8da4..a20679a 100644
--- a/cgit.h
+++ b/cgit.h
@@ -1,288 +1,289 @@
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 *group; 68 char *group;
69 char *module_link; 69 char *module_link;
70 char *readme; 70 char *readme;
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
82struct cgit_repolist { 82struct cgit_repolist {
83 int length; 83 int length;
84 int count; 84 int count;
85 struct cgit_repo *repos; 85 struct cgit_repo *repos;
86}; 86};
87 87
88struct commitinfo { 88struct commitinfo {
89 struct commit *commit; 89 struct commit *commit;
90 char *author; 90 char *author;
91 char *author_email; 91 char *author_email;
92 unsigned long author_date; 92 unsigned long author_date;
93 char *committer; 93 char *committer;
94 char *committer_email; 94 char *committer_email;
95 unsigned long committer_date; 95 unsigned long committer_date;
96 char *subject; 96 char *subject;
97 char *msg; 97 char *msg;
98 char *msg_encoding; 98 char *msg_encoding;
99}; 99};
100 100
101struct taginfo { 101struct taginfo {
102 char *tagger; 102 char *tagger;
103 char *tagger_email; 103 char *tagger_email;
104 unsigned long tagger_date; 104 unsigned long tagger_date;
105 char *msg; 105 char *msg;
106}; 106};
107 107
108struct refinfo { 108struct refinfo {
109 const char *refname; 109 const char *refname;
110 struct object *object; 110 struct object *object;
111 union { 111 union {
112 struct taginfo *tag; 112 struct taginfo *tag;
113 struct commitinfo *commit; 113 struct commitinfo *commit;
114 }; 114 };
115}; 115};
116 116
117struct reflist { 117struct reflist {
118 struct refinfo **refs; 118 struct refinfo **refs;
119 int alloc; 119 int alloc;
120 int count; 120 int count;
121}; 121};
122 122
123struct cgit_query { 123struct cgit_query {
124 int has_symref; 124 int has_symref;
125 int has_sha1; 125 int has_sha1;
126 char *raw; 126 char *raw;
127 char *repo; 127 char *repo;
128 char *page; 128 char *page;
129 char *search; 129 char *search;
130 char *grep; 130 char *grep;
131 char *head; 131 char *head;
132 char *sha1; 132 char *sha1;
133 char *sha2; 133 char *sha2;
134 char *path; 134 char *path;
135 char *name; 135 char *name;
136 char *mimetype; 136 char *mimetype;
137 char *url; 137 char *url;
138 char *period; 138 char *period;
139 int ofs; 139 int ofs;
140 int nohead; 140 int nohead;
141 char *sort; 141 char *sort;
142 int showmsg; 142 int showmsg;
143}; 143};
144 144
145struct cgit_config { 145struct cgit_config {
146 char *agefile; 146 char *agefile;
147 char *cache_root; 147 char *cache_root;
148 char *clone_prefix; 148 char *clone_prefix;
149 char *css; 149 char *css;
150 char *favicon; 150 char *favicon;
151 char *footer; 151 char *footer;
152 char *head_include; 152 char *head_include;
153 char *header; 153 char *header;
154 char *index_header; 154 char *index_header;
155 char *index_info; 155 char *index_info;
156 char *logo; 156 char *logo;
157 char *logo_link; 157 char *logo_link;
158 char *module_link; 158 char *module_link;
159 char *repo_group; 159 char *repo_group;
160 char *robots; 160 char *robots;
161 char *root_title; 161 char *root_title;
162 char *root_desc; 162 char *root_desc;
163 char *root_readme; 163 char *root_readme;
164 char *script_name; 164 char *script_name;
165 char *virtual_root; 165 char *virtual_root;
166 int cache_size; 166 int cache_size;
167 int cache_dynamic_ttl; 167 int cache_dynamic_ttl;
168 int cache_max_create_time; 168 int cache_max_create_time;
169 int cache_repo_ttl; 169 int cache_repo_ttl;
170 int cache_root_ttl; 170 int cache_root_ttl;
171 int cache_static_ttl; 171 int cache_static_ttl;
172 int embedded; 172 int embedded;
173 int enable_index_links; 173 int enable_index_links;
174 int enable_log_filecount; 174 int enable_log_filecount;
175 int enable_log_linecount; 175 int enable_log_linecount;
176 int enable_tree_linenumbers;
176 int local_time; 177 int local_time;
177 int max_repo_count; 178 int max_repo_count;
178 int max_commit_count; 179 int max_commit_count;
179 int max_lock_attempts; 180 int max_lock_attempts;
180 int max_msg_len; 181 int max_msg_len;
181 int max_repodesc_len; 182 int max_repodesc_len;
182 int max_stats; 183 int max_stats;
183 int nocache; 184 int nocache;
184 int noplainemail; 185 int noplainemail;
185 int noheader; 186 int noheader;
186 int renamelimit; 187 int renamelimit;
187 int snapshots; 188 int snapshots;
188 int summary_branches; 189 int summary_branches;
189 int summary_log; 190 int summary_log;
190 int summary_tags; 191 int summary_tags;
191 struct string_list mimetypes; 192 struct string_list mimetypes;
192 struct cgit_filter *about_filter; 193 struct cgit_filter *about_filter;
193 struct cgit_filter *commit_filter; 194 struct cgit_filter *commit_filter;
194 struct cgit_filter *source_filter; 195 struct cgit_filter *source_filter;
195}; 196};
196 197
197struct cgit_page { 198struct cgit_page {
198 time_t modified; 199 time_t modified;
199 time_t expires; 200 time_t expires;
200 size_t size; 201 size_t size;
201 char *mimetype; 202 char *mimetype;
202 char *charset; 203 char *charset;
203 char *filename; 204 char *filename;
204 char *etag; 205 char *etag;
205 char *title; 206 char *title;
206 int status; 207 int status;
207 char *statusmsg; 208 char *statusmsg;
208}; 209};
209 210
210struct cgit_environment { 211struct cgit_environment {
211 char *cgit_config; 212 char *cgit_config;
212 char *http_host; 213 char *http_host;
213 char *https; 214 char *https;
214 char *no_http; 215 char *no_http;
215 char *path_info; 216 char *path_info;
216 char *query_string; 217 char *query_string;
217 char *request_method; 218 char *request_method;
218 char *script_name; 219 char *script_name;
219 char *server_name; 220 char *server_name;
220 char *server_port; 221 char *server_port;
221}; 222};
222 223
223struct cgit_context { 224struct cgit_context {
224 struct cgit_environment env; 225 struct cgit_environment env;
225 struct cgit_query qry; 226 struct cgit_query qry;
226 struct cgit_config cfg; 227 struct cgit_config cfg;
227 struct cgit_repo *repo; 228 struct cgit_repo *repo;
228 struct cgit_page page; 229 struct cgit_page page;
229}; 230};
230 231
231struct cgit_snapshot_format { 232struct cgit_snapshot_format {
232 const char *suffix; 233 const char *suffix;
233 const char *mimetype; 234 const char *mimetype;
234 write_archive_fn_t write_func; 235 write_archive_fn_t write_func;
235 int bit; 236 int bit;
236}; 237};
237 238
238extern const char *cgit_version; 239extern const char *cgit_version;
239 240
240extern struct cgit_repolist cgit_repolist; 241extern struct cgit_repolist cgit_repolist;
241extern struct cgit_context ctx; 242extern struct cgit_context ctx;
242extern const struct cgit_snapshot_format cgit_snapshot_formats[]; 243extern const struct cgit_snapshot_format cgit_snapshot_formats[];
243 244
244extern struct cgit_repo *cgit_add_repo(const char *url); 245extern struct cgit_repo *cgit_add_repo(const char *url);
245extern struct cgit_repo *cgit_get_repoinfo(const char *url); 246extern struct cgit_repo *cgit_get_repoinfo(const char *url);
246extern void cgit_repo_config_cb(const char *name, const char *value); 247extern void cgit_repo_config_cb(const char *name, const char *value);
247 248
248extern int chk_zero(int result, char *msg); 249extern int chk_zero(int result, char *msg);
249extern int chk_positive(int result, char *msg); 250extern int chk_positive(int result, char *msg);
250extern int chk_non_negative(int result, char *msg); 251extern int chk_non_negative(int result, char *msg);
251 252
252extern char *trim_end(const char *str, char c); 253extern char *trim_end(const char *str, char c);
253extern char *strlpart(char *txt, int maxlen); 254extern char *strlpart(char *txt, int maxlen);
254extern char *strrpart(char *txt, int maxlen); 255extern char *strrpart(char *txt, int maxlen);
255 256
256extern void cgit_add_ref(struct reflist *list, struct refinfo *ref); 257extern void cgit_add_ref(struct reflist *list, struct refinfo *ref);
257extern int cgit_refs_cb(const char *refname, const unsigned char *sha1, 258extern int cgit_refs_cb(const char *refname, const unsigned char *sha1,
258 int flags, void *cb_data); 259 int flags, void *cb_data);
259 260
260extern void *cgit_free_commitinfo(struct commitinfo *info); 261extern void *cgit_free_commitinfo(struct commitinfo *info);
261 262
262extern int cgit_diff_files(const unsigned char *old_sha1, 263extern int cgit_diff_files(const unsigned char *old_sha1,
263 const unsigned char *new_sha1, 264 const unsigned char *new_sha1,
264 unsigned long *old_size, unsigned long *new_size, 265 unsigned long *old_size, unsigned long *new_size,
265 int *binary, linediff_fn fn); 266 int *binary, linediff_fn fn);
266 267
267extern void cgit_diff_tree(const unsigned char *old_sha1, 268extern void cgit_diff_tree(const unsigned char *old_sha1,
268 const unsigned char *new_sha1, 269 const unsigned char *new_sha1,
269 filepair_fn fn, const char *prefix); 270 filepair_fn fn, const char *prefix);
270 271
271extern void cgit_diff_commit(struct commit *commit, filepair_fn fn); 272extern void cgit_diff_commit(struct commit *commit, filepair_fn fn);
272 273
273extern char *fmt(const char *format,...); 274extern char *fmt(const char *format,...);
274 275
275extern struct commitinfo *cgit_parse_commit(struct commit *commit); 276extern struct commitinfo *cgit_parse_commit(struct commit *commit);
276extern struct taginfo *cgit_parse_tag(struct tag *tag); 277extern struct taginfo *cgit_parse_tag(struct tag *tag);
277extern void cgit_parse_url(const char *url); 278extern void cgit_parse_url(const char *url);
278 279
279extern const char *cgit_repobasename(const char *reponame); 280extern const char *cgit_repobasename(const char *reponame);
280 281
281extern int cgit_parse_snapshots_mask(const char *str); 282extern int cgit_parse_snapshots_mask(const char *str);
282 283
283extern int cgit_open_filter(struct cgit_filter *filter); 284extern int cgit_open_filter(struct cgit_filter *filter);
284extern int cgit_close_filter(struct cgit_filter *filter); 285extern int cgit_close_filter(struct cgit_filter *filter);
285 286
286extern int readfile(const char *path, char **buf, size_t *size); 287extern int readfile(const char *path, char **buf, size_t *size);
287 288
288#endif /* CGIT_H */ 289#endif /* CGIT_H */
diff --git a/cgitrc.5.txt b/cgitrc.5.txt
index 0d18290..3b16db9 100644
--- a/cgitrc.5.txt
+++ b/cgitrc.5.txt
@@ -1,456 +1,460 @@
1CGITRC(5) 1CGITRC(5)
2======== 2========
3 3
4 4
5NAME 5NAME
6---- 6----
7cgitrc - runtime configuration for cgit 7cgitrc - runtime configuration for cgit
8 8
9 9
10SYNOPSIS 10SYNOPSIS
11-------- 11--------
12Cgitrc contains all runtime settings for cgit, including the list of git 12Cgitrc contains all runtime settings for cgit, including the list of git
13repositories, formatted as a line-separated list of NAME=VALUE pairs. Blank 13repositories, formatted as a line-separated list of NAME=VALUE pairs. Blank
14lines, and lines starting with '#', are ignored. 14lines, and lines starting with '#', are ignored.
15 15
16 16
17LOCATION 17LOCATION
18-------- 18--------
19The default location of cgitrc, defined at compile time, is /etc/cgitrc. At 19The default location of cgitrc, defined at compile time, is /etc/cgitrc. At
20runtime, cgit will consult the environment variable CGIT_CONFIG and, if 20runtime, cgit will consult the environment variable CGIT_CONFIG and, if
21defined, use its value instead. 21defined, use its value instead.
22 22
23 23
24GLOBAL SETTINGS 24GLOBAL SETTINGS
25--------------- 25---------------
26about-filter:: 26about-filter::
27 Specifies a command which will be invoked to format the content of 27 Specifies a command which will be invoked to format the content of
28 about pages (both top-level and for each repository). The command will 28 about pages (both top-level and for each repository). The command will
29 get the content of the about-file on its STDIN, and the STDOUT from the 29 get the content of the about-file on its STDIN, and the STDOUT from the
30 command will be included verbatim on the about page. Default value: 30 command will be included verbatim on the about page. Default value:
31 none. 31 none.
32 32
33agefile:: 33agefile::
34 Specifies a path, relative to each repository path, which can be used 34 Specifies a path, relative to each repository path, which can be used
35 to specify the date and time of the youngest commit in the repository. 35 to specify the date and time of the youngest commit in the repository.
36 The first line in the file is used as input to the "parse_date" 36 The first line in the file is used as input to the "parse_date"
37 function in libgit. Recommended timestamp-format is "yyyy-mm-dd 37 function in libgit. Recommended timestamp-format is "yyyy-mm-dd
38 hh:mm:ss". Default value: "info/web/last-modified". 38 hh:mm:ss". Default value: "info/web/last-modified".
39 39
40cache-root:: 40cache-root::
41 Path used to store the cgit cache entries. Default value: 41 Path used to store the cgit cache entries. Default value:
42 "/var/cache/cgit". 42 "/var/cache/cgit".
43 43
44cache-dynamic-ttl:: 44cache-dynamic-ttl::
45 Number which specifies the time-to-live, in minutes, for the cached 45 Number which specifies the time-to-live, in minutes, for the cached
46 version of repository pages accessed without a fixed SHA1. Default 46 version of repository pages accessed without a fixed SHA1. Default
47 value: "5". 47 value: "5".
48 48
49cache-repo-ttl:: 49cache-repo-ttl::
50 Number which specifies the time-to-live, in minutes, for the cached 50 Number which specifies the time-to-live, in minutes, for the cached
51 version of the repository summary page. Default value: "5". 51 version of the repository summary page. Default value: "5".
52 52
53cache-root-ttl:: 53cache-root-ttl::
54 Number which specifies the time-to-live, in minutes, for the cached 54 Number which specifies the time-to-live, in minutes, for the cached
55 version of the repository index page. Default value: "5". 55 version of the repository index page. Default value: "5".
56 56
57cache-size:: 57cache-size::
58 The maximum number of entries in the cgit cache. Default value: "0" 58 The maximum number of entries in the cgit cache. Default value: "0"
59 (i.e. caching is disabled). 59 (i.e. caching is disabled).
60 60
61cache-static-ttl:: 61cache-static-ttl::
62 Number which specifies the time-to-live, in minutes, for the cached 62 Number which specifies the time-to-live, in minutes, for the cached
63 version of repository pages accessed with a fixed SHA1. Default value: 63 version of repository pages accessed with a fixed SHA1. Default value:
64 "5". 64 "5".
65 65
66clone-prefix:: 66clone-prefix::
67 Space-separated list of common prefixes which, when combined with a 67 Space-separated list of common prefixes which, when combined with a
68 repository url, generates valid clone urls for the repository. This 68 repository url, generates valid clone urls for the repository. This
69 setting is only used if `repo.clone-url` is unspecified. Default value: 69 setting is only used if `repo.clone-url` is unspecified. Default value:
70 none. 70 none.
71 71
72commit-filter:: 72commit-filter::
73 Specifies a command which will be invoked to format commit messages. 73 Specifies a command which will be invoked to format commit messages.
74 The command will get the message on its STDIN, and the STDOUT from the 74 The command will get the message on its STDIN, and the STDOUT from the
75 command will be included verbatim as the commit message, i.e. this can 75 command will be included verbatim as the commit message, i.e. this can
76 be used to implement bugtracker integration. Default value: none. 76 be used to implement bugtracker integration. Default value: none.
77 77
78css:: 78css::
79 Url which specifies the css document to include in all cgit pages. 79 Url which specifies the css document to include in all cgit pages.
80 Default value: "/cgit.css". 80 Default value: "/cgit.css".
81 81
82embedded:: 82embedded::
83 Flag which, when set to "1", will make cgit generate a html fragment 83 Flag which, when set to "1", will make cgit generate a html fragment
84 suitable for embedding in other html pages. Default value: none. See 84 suitable for embedding in other html pages. Default value: none. See
85 also: "noheader". 85 also: "noheader".
86 86
87enable-index-links:: 87enable-index-links::
88 Flag which, when set to "1", will make cgit generate extra links for 88 Flag which, when set to "1", will make cgit generate extra links for
89 each repo in the repository index (specifically, to the "summary", 89 each repo in the repository index (specifically, to the "summary",
90 "commit" and "tree" pages). Default value: "0". 90 "commit" and "tree" pages). Default value: "0".
91 91
92enable-log-filecount:: 92enable-log-filecount::
93 Flag which, when set to "1", will make cgit print the number of 93 Flag which, when set to "1", will make cgit print the number of
94 modified files for each commit on the repository log page. Default 94 modified files for each commit on the repository log page. Default
95 value: "0". 95 value: "0".
96 96
97enable-log-linecount:: 97enable-log-linecount::
98 Flag which, when set to "1", will make cgit print the number of added 98 Flag which, when set to "1", will make cgit print the number of added
99 and removed lines for each commit on the repository log page. Default 99 and removed lines for each commit on the repository log page. Default
100 value: "0". 100 value: "0".
101 101
102enable-tree-linenumbers::
103 Flag which, when set to "1", will make cgit generate linenumber links
104 for plaintext blobs printed in the tree view. Default value: "1".
105
102favicon:: 106favicon::
103 Url used as link to a shortcut icon for cgit. If specified, it is 107 Url used as link to a shortcut icon for cgit. If specified, it is
104 suggested to use the value "/favicon.ico" since certain browsers will 108 suggested to use the value "/favicon.ico" since certain browsers will
105 ignore other values. Default value: none. 109 ignore other values. Default value: none.
106 110
107footer:: 111footer::
108 The content of the file specified with this option will be included 112 The content of the file specified with this option will be included
109 verbatim at the bottom of all pages (i.e. it replaces the standard 113 verbatim at the bottom of all pages (i.e. it replaces the standard
110 "generated by..." message. Default value: none. 114 "generated by..." message. Default value: none.
111 115
112head-include:: 116head-include::
113 The content of the file specified with this option will be included 117 The content of the file specified with this option will be included
114 verbatim in the html HEAD section on all pages. Default value: none. 118 verbatim in the html HEAD section on all pages. Default value: none.
115 119
116header:: 120header::
117 The content of the file specified with this option will be included 121 The content of the file specified with this option will be included
118 verbatim at the top of all pages. Default value: none. 122 verbatim at the top of all pages. Default value: none.
119 123
120include:: 124include::
121 Name of a configfile to include before the rest of the current config- 125 Name of a configfile to include before the rest of the current config-
122 file is parsed. Default value: none. 126 file is parsed. Default value: none.
123 127
124index-header:: 128index-header::
125 The content of the file specified with this option will be included 129 The content of the file specified with this option will be included
126 verbatim above the repository index. This setting is deprecated, and 130 verbatim above the repository index. This setting is deprecated, and
127 will not be supported by cgit-1.0 (use root-readme instead). Default 131 will not be supported by cgit-1.0 (use root-readme instead). Default
128 value: none. 132 value: none.
129 133
130index-info:: 134index-info::
131 The content of the file specified with this option will be included 135 The content of the file specified with this option will be included
132 verbatim below the heading on the repository index page. This setting 136 verbatim below the heading on the repository index page. This setting
133 is deprecated, and will not be supported by cgit-1.0 (use root-desc 137 is deprecated, and will not be supported by cgit-1.0 (use root-desc
134 instead). Default value: none. 138 instead). Default value: none.
135 139
136local-time:: 140local-time::
137 Flag which, if set to "1", makes cgit print commit and tag times in the 141 Flag which, if set to "1", makes cgit print commit and tag times in the
138 servers timezone. Default value: "0". 142 servers timezone. Default value: "0".
139 143
140logo:: 144logo::
141 Url which specifies the source of an image which will be used as a logo 145 Url which specifies the source of an image which will be used as a logo
142 on all cgit pages. Default value: "/cgit.png". 146 on all cgit pages. Default value: "/cgit.png".
143 147
144logo-link:: 148logo-link::
145 Url loaded when clicking on the cgit logo image. If unspecified the 149 Url loaded when clicking on the cgit logo image. If unspecified the
146 calculated url of the repository index page will be used. Default 150 calculated url of the repository index page will be used. Default
147 value: none. 151 value: none.
148 152
149max-commit-count:: 153max-commit-count::
150 Specifies the number of entries to list per page in "log" view. Default 154 Specifies the number of entries to list per page in "log" view. Default
151 value: "50". 155 value: "50".
152 156
153max-message-length:: 157max-message-length::
154 Specifies the maximum number of commit message characters to display in 158 Specifies the maximum number of commit message characters to display in
155 "log" view. Default value: "80". 159 "log" view. Default value: "80".
156 160
157max-repo-count:: 161max-repo-count::
158 Specifies the number of entries to list per page on therepository 162 Specifies the number of entries to list per page on therepository
159 index page. Default value: "50". 163 index page. Default value: "50".
160 164
161max-repodesc-length:: 165max-repodesc-length::
162 Specifies the maximum number of repo description characters to display 166 Specifies the maximum number of repo description characters to display
163 on the repository index page. Default value: "80". 167 on the repository index page. Default value: "80".
164 168
165max-stats:: 169max-stats::
166 Set the default maximum statistics period. Valid values are "week", 170 Set the default maximum statistics period. Valid values are "week",
167 "month", "quarter" and "year". If unspecified, statistics are 171 "month", "quarter" and "year". If unspecified, statistics are
168 disabled. Default value: none. See also: "repo.max-stats". 172 disabled. Default value: none. See also: "repo.max-stats".
169 173
170mimetype.<ext>:: 174mimetype.<ext>::
171 Set the mimetype for the specified filename extension. This is used 175 Set the mimetype for the specified filename extension. This is used
172 by the `plain` command when returning blob content. 176 by the `plain` command when returning blob content.
173 177
174module-link:: 178module-link::
175 Text which will be used as the formatstring for a hyperlink when a 179 Text which will be used as the formatstring for a hyperlink when a
176 submodule is printed in a directory listing. The arguments for the 180 submodule is printed in a directory listing. The arguments for the
177 formatstring are the path and SHA1 of the submodule commit. Default 181 formatstring are the path and SHA1 of the submodule commit. Default
178 value: "./?repo=%s&page=commit&id=%s" 182 value: "./?repo=%s&page=commit&id=%s"
179 183
180nocache:: 184nocache::
181 If set to the value "1" caching will be disabled. This settings is 185 If set to the value "1" caching will be disabled. This settings is
182 deprecated, and will not be honored starting with cgit-1.0. Default 186 deprecated, and will not be honored starting with cgit-1.0. Default
183 value: "0". 187 value: "0".
184 188
185noplainemail:: 189noplainemail::
186 If set to "1" showing full author email adresses will be disabled. 190 If set to "1" showing full author email adresses will be disabled.
187 Default value: "0". 191 Default value: "0".
188 192
189noheader:: 193noheader::
190 Flag which, when set to "1", will make cgit omit the standard header 194 Flag which, when set to "1", will make cgit omit the standard header
191 on all pages. Default value: none. See also: "embedded". 195 on all pages. Default value: none. See also: "embedded".
192 196
193renamelimit:: 197renamelimit::
194 Maximum number of files to consider when detecting renames. The value 198 Maximum number of files to consider when detecting renames. The value
195 "-1" uses the compiletime value in git (for further info, look at 199 "-1" uses the compiletime value in git (for further info, look at
196 `man git-diff`). Default value: "-1". 200 `man git-diff`). Default value: "-1".
197 201
198repo.group:: 202repo.group::
199 A value for the current repository group, which all repositories 203 A value for the current repository group, which all repositories
200 specified after this setting will inherit. Default value: none. 204 specified after this setting will inherit. Default value: none.
201 205
202robots:: 206robots::
203 Text used as content for the "robots" meta-tag. Default value: 207 Text used as content for the "robots" meta-tag. Default value:
204 "index, nofollow". 208 "index, nofollow".
205 209
206root-desc:: 210root-desc::
207 Text printed below the heading on the repository index page. Default 211 Text printed below the heading on the repository index page. Default
208 value: "a fast webinterface for the git dscm". 212 value: "a fast webinterface for the git dscm".
209 213
210root-readme:: 214root-readme::
211 The content of the file specified with this option will be included 215 The content of the file specified with this option will be included
212 verbatim below the "about" link on the repository index page. Default 216 verbatim below the "about" link on the repository index page. Default
213 value: none. 217 value: none.
214 218
215root-title:: 219root-title::
216 Text printed as heading on the repository index page. Default value: 220 Text printed as heading on the repository index page. Default value:
217 "Git Repository Browser". 221 "Git Repository Browser".
218 222
219snapshots:: 223snapshots::
220 Text which specifies the default set of snapshot formats generated by 224 Text which specifies the default set of snapshot formats generated by
221 cgit. The value is a space-separated list of zero or more of the 225 cgit. The value is a space-separated list of zero or more of the
222 values "tar", "tar.gz", "tar.bz2" and "zip". Default value: none. 226 values "tar", "tar.gz", "tar.bz2" and "zip". Default value: none.
223 227
224source-filter:: 228source-filter::
225 Specifies a command which will be invoked to format plaintext blobs 229 Specifies a command which will be invoked to format plaintext blobs
226 in the tree view. The command will get the blob content on its STDIN 230 in the tree view. The command will get the blob content on its STDIN
227 and the name of the blob as its only command line argument. The STDOUT 231 and the name of the blob as its only command line argument. The STDOUT
228 from the command will be included verbatim as the blob contents, i.e. 232 from the command will be included verbatim as the blob contents, i.e.
229 this can be used to implement e.g. syntax highlighting. Default value: 233 this can be used to implement e.g. syntax highlighting. Default value:
230 none. 234 none.
231 235
232summary-branches:: 236summary-branches::
233 Specifies the number of branches to display in the repository "summary" 237 Specifies the number of branches to display in the repository "summary"
234 view. Default value: "10". 238 view. Default value: "10".
235 239
236summary-log:: 240summary-log::
237 Specifies the number of log entries to display in the repository 241 Specifies the number of log entries to display in the repository
238 "summary" view. Default value: "10". 242 "summary" view. Default value: "10".
239 243
240summary-tags:: 244summary-tags::
241 Specifies the number of tags to display in the repository "summary" 245 Specifies the number of tags to display in the repository "summary"
242 view. Default value: "10". 246 view. Default value: "10".
243 247
244virtual-root:: 248virtual-root::
245 Url which, if specified, will be used as root for all cgit links. It 249 Url which, if specified, will be used as root for all cgit links. It
246 will also cause cgit to generate 'virtual urls', i.e. urls like 250 will also cause cgit to generate 'virtual urls', i.e. urls like
247 '/cgit/tree/README' as opposed to '?r=cgit&p=tree&path=README'. Default 251 '/cgit/tree/README' as opposed to '?r=cgit&p=tree&path=README'. Default
248 value: none. 252 value: none.
249 NOTE: cgit has recently learned how to use PATH_INFO to achieve the 253 NOTE: cgit has recently learned how to use PATH_INFO to achieve the
250 same kind of virtual urls, so this option will probably be deprecated. 254 same kind of virtual urls, so this option will probably be deprecated.
251 255
252REPOSITORY SETTINGS 256REPOSITORY SETTINGS
253------------------- 257-------------------
254repo.about-filter:: 258repo.about-filter::
255 Override the default about-filter. Default value: <about-filter>. 259 Override the default about-filter. Default value: <about-filter>.
256 260
257repo.clone-url:: 261repo.clone-url::
258 A list of space-separated urls which can be used to clone this repo. 262 A list of space-separated urls which can be used to clone this repo.
259 Default value: none. 263 Default value: none.
260 264
261repo.commit-filter:: 265repo.commit-filter::
262 Override the default commit-filter. Default value: <commit-filter>. 266 Override the default commit-filter. Default value: <commit-filter>.
263 267
264repo.defbranch:: 268repo.defbranch::
265 The name of the default branch for this repository. If no such branch 269 The name of the default branch for this repository. If no such branch
266 exists in the repository, the first branch name (when sorted) is used 270 exists in the repository, the first branch name (when sorted) is used
267 as default instead. Default value: "master". 271 as default instead. Default value: "master".
268 272
269repo.desc:: 273repo.desc::
270 The value to show as repository description. Default value: none. 274 The value to show as repository description. Default value: none.
271 275
272repo.enable-log-filecount:: 276repo.enable-log-filecount::
273 A flag which can be used to disable the global setting 277 A flag which can be used to disable the global setting
274 `enable-log-filecount'. Default value: none. 278 `enable-log-filecount'. Default value: none.
275 279
276repo.enable-log-linecount:: 280repo.enable-log-linecount::
277 A flag which can be used to disable the global setting 281 A flag which can be used to disable the global setting
278 `enable-log-linecount'. Default value: none. 282 `enable-log-linecount'. Default value: none.
279 283
280repo.max-stats:: 284repo.max-stats::
281 Override the default maximum statistics period. Valid values are equal 285 Override the default maximum statistics period. Valid values are equal
282 to the values specified for the global "max-stats" setting. Default 286 to the values specified for the global "max-stats" setting. Default
283 value: none. 287 value: none.
284 288
285repo.name:: 289repo.name::
286 The value to show as repository name. Default value: <repo.url>. 290 The value to show as repository name. Default value: <repo.url>.
287 291
288repo.owner:: 292repo.owner::
289 A value used to identify the owner of the repository. Default value: 293 A value used to identify the owner of the repository. Default value:
290 none. 294 none.
291 295
292repo.path:: 296repo.path::
293 An absolute path to the repository directory. For non-bare repositories 297 An absolute path to the repository directory. For non-bare repositories
294 this is the .git-directory. Default value: none. 298 this is the .git-directory. Default value: none.
295 299
296repo.readme:: 300repo.readme::
297 A path (relative to <repo.path>) which specifies a file to include 301 A path (relative to <repo.path>) which specifies a file to include
298 verbatim as the "About" page for this repo. Default value: none. 302 verbatim as the "About" page for this repo. Default value: none.
299 303
300repo.snapshots:: 304repo.snapshots::
301 A mask of allowed snapshot-formats for this repo, restricted by the 305 A mask of allowed snapshot-formats for this repo, restricted by the
302 "snapshots" global setting. Default value: <snapshots>. 306 "snapshots" global setting. Default value: <snapshots>.
303 307
304repo.source-filter:: 308repo.source-filter::
305 Override the default source-filter. Default value: <source-filter>. 309 Override the default source-filter. Default value: <source-filter>.
306 310
307repo.url:: 311repo.url::
308 The relative url used to access the repository. This must be the first 312 The relative url used to access the repository. This must be the first
309 setting specified for each repo. Default value: none. 313 setting specified for each repo. Default value: none.
310 314
311 315
312EXAMPLE CGITRC FILE 316EXAMPLE CGITRC FILE
313------------------- 317-------------------
314 318
315.... 319....
316# Enable caching of up to 1000 output entriess 320# Enable caching of up to 1000 output entriess
317cache-size=1000 321cache-size=1000
318 322
319 323
320# Specify some default clone prefixes 324# Specify some default clone prefixes
321clone-prefix=git://foobar.com ssh://foobar.com/pub/git http://foobar.com/git 325clone-prefix=git://foobar.com ssh://foobar.com/pub/git http://foobar.com/git
322 326
323# Specify the css url 327# Specify the css url
324css=/css/cgit.css 328css=/css/cgit.css
325 329
326 330
327# Show extra links for each repository on the index page 331# Show extra links for each repository on the index page
328enable-index-links=1 332enable-index-links=1
329 333
330 334
331# Show number of affected files per commit on the log pages 335# Show number of affected files per commit on the log pages
332enable-log-filecount=1 336enable-log-filecount=1
333 337
334 338
335# Show number of added/removed lines per commit on the log pages 339# Show number of added/removed lines per commit on the log pages
336enable-log-linecount=1 340enable-log-linecount=1
337 341
338 342
339# Add a cgit favicon 343# Add a cgit favicon
340favicon=/favicon.ico 344favicon=/favicon.ico
341 345
342 346
343# Use a custom logo 347# Use a custom logo
344logo=/img/mylogo.png 348logo=/img/mylogo.png
345 349
346 350
347# Enable statistics per week, month and quarter 351# Enable statistics per week, month and quarter
348max-stats=quarter 352max-stats=quarter
349 353
350 354
351# Set the title and heading of the repository index page 355# Set the title and heading of the repository index page
352root-title=foobar.com git repositories 356root-title=foobar.com git repositories
353 357
354 358
355# Set a subheading for the repository index page 359# Set a subheading for the repository index page
356root-desc=tracking the foobar development 360root-desc=tracking the foobar development
357 361
358 362
359# Include some more info about foobar.com on the index page 363# Include some more info about foobar.com on the index page
360root-readme=/var/www/htdocs/about.html 364root-readme=/var/www/htdocs/about.html
361 365
362 366
363# Allow download of tar.gz, tar.bz2 and zip-files 367# Allow download of tar.gz, tar.bz2 and zip-files
364snapshots=tar.gz tar.bz2 zip 368snapshots=tar.gz tar.bz2 zip
365 369
366 370
367## 371##
368## List of common mimetypes 372## List of common mimetypes
369## 373##
370 374
371mimetype.git=image/git 375mimetype.git=image/git
372mimetype.html=text/html 376mimetype.html=text/html
373mimetype.jpg=image/jpeg 377mimetype.jpg=image/jpeg
374mimetype.jpeg=image/jpeg 378mimetype.jpeg=image/jpeg
375mimetype.pdf=application/pdf 379mimetype.pdf=application/pdf
376mimetype.png=image/png 380mimetype.png=image/png
377mimetype.svg=image/svg+xml 381mimetype.svg=image/svg+xml
378 382
379 383
380## 384##
381## List of repositories. 385## List of repositories.
382## PS: Any repositories listed when repo.group is unset will not be 386## PS: Any repositories listed when repo.group is unset will not be
383## displayed under a group heading 387## displayed under a group heading
384## PPS: This list could be kept in a different file (e.g. '/etc/cgitrepos') 388## PPS: This list could be kept in a different file (e.g. '/etc/cgitrepos')
385## and included like this: 389## and included like this:
386## include=/etc/cgitrepos 390## include=/etc/cgitrepos
387## 391##
388 392
389 393
390repo.url=foo 394repo.url=foo
391repo.path=/pub/git/foo.git 395repo.path=/pub/git/foo.git
392repo.desc=the master foo repository 396repo.desc=the master foo repository
393repo.owner=fooman@foobar.com 397repo.owner=fooman@foobar.com
394repo.readme=info/web/about.html 398repo.readme=info/web/about.html
395 399
396 400
397repo.url=bar 401repo.url=bar
398repo.path=/pub/git/bar.git 402repo.path=/pub/git/bar.git
399repo.desc=the bars for your foo 403repo.desc=the bars for your foo
400repo.owner=barman@foobar.com 404repo.owner=barman@foobar.com
401repo.readme=info/web/about.html 405repo.readme=info/web/about.html
402 406
403 407
404# The next repositories will be displayed under the 'extras' heading 408# The next repositories will be displayed under the 'extras' heading
405repo.group=extras 409repo.group=extras
406 410
407 411
408repo.url=baz 412repo.url=baz
409repo.path=/pub/git/baz.git 413repo.path=/pub/git/baz.git
410repo.desc=a set of extensions for bar users 414repo.desc=a set of extensions for bar users
411 415
412repo.url=wiz 416repo.url=wiz
413repo.path=/pub/git/wiz.git 417repo.path=/pub/git/wiz.git
414repo.desc=the wizard of foo 418repo.desc=the wizard of foo
415 419
416 420
417# Add some mirrored repositories 421# Add some mirrored repositories
418repo.group=mirrors 422repo.group=mirrors
419 423
420 424
421repo.url=git 425repo.url=git
422repo.path=/pub/git/git.git 426repo.path=/pub/git/git.git
423repo.desc=the dscm 427repo.desc=the dscm
424 428
425 429
426repo.url=linux 430repo.url=linux
427repo.path=/pub/git/linux.git 431repo.path=/pub/git/linux.git
428repo.desc=the kernel 432repo.desc=the kernel
429 433
430# Disable adhoc downloads of this repo 434# Disable adhoc downloads of this repo
431repo.snapshots=0 435repo.snapshots=0
432 436
433# Disable line-counts for this repo 437# Disable line-counts for this repo
434repo.enable-log-linecount=0 438repo.enable-log-linecount=0
435 439
436# Restrict the max statistics period for this repo 440# Restrict the max statistics period for this repo
437repo.max-stats=month 441repo.max-stats=month
438.... 442....
439 443
440 444
441BUGS 445BUGS
442---- 446----
443Comments currently cannot appear on the same line as a setting; the comment 447Comments currently cannot appear on the same line as a setting; the comment
444will be included as part of the value. E.g. this line: 448will be included as part of the value. E.g. this line:
445 449
446 robots=index # allow indexing 450 robots=index # allow indexing
447 451
448will generate the following html element: 452will generate the following html element:
449 453
450 <meta name='robots' content='index # allow indexing'/> 454 <meta name='robots' content='index # allow indexing'/>
451 455
452 456
453 457
454AUTHOR 458AUTHOR
455------ 459------
456Lars Hjemli <hjemli@gmail.com> 460Lars Hjemli <hjemli@gmail.com>
diff --git a/ui-tree.c b/ui-tree.c
index c608754..f53ab64 100644
--- a/ui-tree.c
+++ b/ui-tree.c
@@ -1,278 +1,285 @@
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
26 if (ctx.cfg.enable_tree_linenumbers) {
27 html("<tr><td class='linenumbers'><pre>");
28 idx = 0;
29 lineno = 0;
30
31 if (size) {
32 htmlf(numberfmt, ++lineno);
33 while(idx < size - 1) { // skip absolute last newline
34 if (buf[idx] == '\n')
35 htmlf(numberfmt, ++lineno);
36 idx++;
37 }
38 }
39 html("</pre></td>\n");
40 }
41 else {
42 html("<tr>\n");
43 }
44
25 if (ctx.repo->source_filter) { 45 if (ctx.repo->source_filter) {
26 html("<tr><td class='lines'><pre><code>"); 46 html("<td class='lines'><pre><code>");
27 ctx.repo->source_filter->argv[1] = xstrdup(name); 47 ctx.repo->source_filter->argv[1] = xstrdup(name);
28 cgit_open_filter(ctx.repo->source_filter); 48 cgit_open_filter(ctx.repo->source_filter);
29 write(STDOUT_FILENO, buf, size); 49 write(STDOUT_FILENO, buf, size);
30 cgit_close_filter(ctx.repo->source_filter); 50 cgit_close_filter(ctx.repo->source_filter);
31 html("</code></pre></td></tr></table>\n"); 51 html("</code></pre></td></tr></table>\n");
32 return; 52 return;
33 } 53 }
34 54
35 html("<tr><td class='linenumbers'><pre>");
36 idx = 0;
37 lineno = 0;
38
39 if (size) {
40 htmlf(numberfmt, ++lineno);
41 while(idx < size - 1) { // skip absolute last newline
42 if (buf[idx] == '\n')
43 htmlf(numberfmt, ++lineno);
44 idx++;
45 }
46 }
47 html("</pre></td>\n");
48 html("<td class='lines'><pre><code>"); 55 html("<td class='lines'><pre><code>");
49 html_txt(buf); 56 html_txt(buf);
50 html("</code></pre></td></tr></table>\n"); 57 html("</code></pre></td></tr></table>\n");
51} 58}
52 59
53#define ROWLEN 32 60#define ROWLEN 32
54 61
55static void print_binary_buffer(char *buf, unsigned long size) 62static void print_binary_buffer(char *buf, unsigned long size)
56{ 63{
57 unsigned long ofs, idx; 64 unsigned long ofs, idx;
58 static char ascii[ROWLEN + 1]; 65 static char ascii[ROWLEN + 1];
59 66
60 html("<table summary='blob content' class='bin-blob'>\n"); 67 html("<table summary='blob content' class='bin-blob'>\n");
61 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>");
62 for (ofs = 0; ofs < size; ofs += ROWLEN, buf += ROWLEN) { 69 for (ofs = 0; ofs < size; ofs += ROWLEN, buf += ROWLEN) {
63 htmlf("<tr><td class='right'>%04x</td><td class='hex'>", ofs); 70 htmlf("<tr><td class='right'>%04x</td><td class='hex'>", ofs);
64 for (idx = 0; idx < ROWLEN && ofs + idx < size; idx++) 71 for (idx = 0; idx < ROWLEN && ofs + idx < size; idx++)
65 htmlf("%*s%02x", 72 htmlf("%*s%02x",
66 idx == 16 ? 4 : 1, "", 73 idx == 16 ? 4 : 1, "",
67 buf[idx] & 0xff); 74 buf[idx] & 0xff);
68 html(" </td><td class='hex'>"); 75 html(" </td><td class='hex'>");
69 for (idx = 0; idx < ROWLEN && ofs + idx < size; idx++) 76 for (idx = 0; idx < ROWLEN && ofs + idx < size; idx++)
70 ascii[idx] = isgraph(buf[idx]) ? buf[idx] : '.'; 77 ascii[idx] = isgraph(buf[idx]) ? buf[idx] : '.';
71 ascii[idx] = '\0'; 78 ascii[idx] = '\0';
72 html_txt(ascii); 79 html_txt(ascii);
73 html("</td></tr>\n"); 80 html("</td></tr>\n");
74 } 81 }
75 html("</table>\n"); 82 html("</table>\n");
76} 83}
77 84
78static 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)
79{ 86{
80 enum object_type type; 87 enum object_type type;
81 char *buf; 88 char *buf;
82 unsigned long size; 89 unsigned long size;
83 90
84 type = sha1_object_info(sha1, &size); 91 type = sha1_object_info(sha1, &size);
85 if (type == OBJ_BAD) { 92 if (type == OBJ_BAD) {
86 cgit_print_error(fmt("Bad object name: %s", 93 cgit_print_error(fmt("Bad object name: %s",
87 sha1_to_hex(sha1))); 94 sha1_to_hex(sha1)));
88 return; 95 return;
89 } 96 }
90 97
91 buf = read_sha1_file(sha1, &type, &size); 98 buf = read_sha1_file(sha1, &type, &size);
92 if (!buf) { 99 if (!buf) {
93 cgit_print_error(fmt("Error reading object %s", 100 cgit_print_error(fmt("Error reading object %s",
94 sha1_to_hex(sha1))); 101 sha1_to_hex(sha1)));
95 return; 102 return;
96 } 103 }
97 104
98 html(" ("); 105 html(" (");
99 cgit_plain_link("plain", NULL, NULL, ctx.qry.head, 106 cgit_plain_link("plain", NULL, NULL, ctx.qry.head,
100 curr_rev, path); 107 curr_rev, path);
101 htmlf(")<br/>blob: %s\n", sha1_to_hex(sha1)); 108 htmlf(")<br/>blob: %s\n", sha1_to_hex(sha1));
102 109
103 if (buffer_is_binary(buf, size)) 110 if (buffer_is_binary(buf, size))
104 print_binary_buffer(buf, size); 111 print_binary_buffer(buf, size);
105 else 112 else
106 print_text_buffer(basename, buf, size); 113 print_text_buffer(basename, buf, size);
107} 114}
108 115
109 116
110static int ls_item(const unsigned char *sha1, const char *base, int baselen, 117static int ls_item(const unsigned char *sha1, const char *base, int baselen,
111 const char *pathname, unsigned int mode, int stage, 118 const char *pathname, unsigned int mode, int stage,
112 void *cbdata) 119 void *cbdata)
113{ 120{
114 char *name; 121 char *name;
115 char *fullpath; 122 char *fullpath;
116 char *class; 123 char *class;
117 enum object_type type; 124 enum object_type type;
118 unsigned long size = 0; 125 unsigned long size = 0;
119 126
120 name = xstrdup(pathname); 127 name = xstrdup(pathname);
121 fullpath = fmt("%s%s%s", ctx.qry.path ? ctx.qry.path : "", 128 fullpath = fmt("%s%s%s", ctx.qry.path ? ctx.qry.path : "",
122 ctx.qry.path ? "/" : "", name); 129 ctx.qry.path ? "/" : "", name);
123 130
124 if (!S_ISGITLINK(mode)) { 131 if (!S_ISGITLINK(mode)) {
125 type = sha1_object_info(sha1, &size); 132 type = sha1_object_info(sha1, &size);
126 if (type == OBJ_BAD) { 133 if (type == OBJ_BAD) {
127 htmlf("<tr><td colspan='3'>Bad object: %s %s</td></tr>", 134 htmlf("<tr><td colspan='3'>Bad object: %s %s</td></tr>",
128 name, 135 name,
129 sha1_to_hex(sha1)); 136 sha1_to_hex(sha1));
130 return 0; 137 return 0;
131 } 138 }
132 } 139 }
133 140
134 html("<tr><td class='ls-mode'>"); 141 html("<tr><td class='ls-mode'>");
135 cgit_print_filemode(mode); 142 cgit_print_filemode(mode);
136 html("</td><td>"); 143 html("</td><td>");
137 if (S_ISGITLINK(mode)) { 144 if (S_ISGITLINK(mode)) {
138 htmlf("<a class='ls-mod' href='"); 145 htmlf("<a class='ls-mod' href='");
139 html_attr(fmt(ctx.repo->module_link, 146 html_attr(fmt(ctx.repo->module_link,
140 name, 147 name,
141 sha1_to_hex(sha1))); 148 sha1_to_hex(sha1)));
142 html("'>"); 149 html("'>");
143 html_txt(name); 150 html_txt(name);
144 html("</a>"); 151 html("</a>");
145 } else if (S_ISDIR(mode)) { 152 } else if (S_ISDIR(mode)) {
146 cgit_tree_link(name, NULL, "ls-dir", ctx.qry.head, 153 cgit_tree_link(name, NULL, "ls-dir", ctx.qry.head,
147 curr_rev, fullpath); 154 curr_rev, fullpath);
148 } else { 155 } else {
149 class = strrchr(name, '.'); 156 class = strrchr(name, '.');
150 if (class != NULL) { 157 if (class != NULL) {
151 class = fmt("ls-blob %s", class + 1); 158 class = fmt("ls-blob %s", class + 1);
152 } else 159 } else
153 class = "ls-blob"; 160 class = "ls-blob";
154 cgit_tree_link(name, NULL, class, ctx.qry.head, 161 cgit_tree_link(name, NULL, class, ctx.qry.head,
155 curr_rev, fullpath); 162 curr_rev, fullpath);
156 } 163 }
157 htmlf("</td><td class='ls-size'>%li</td>", size); 164 htmlf("</td><td class='ls-size'>%li</td>", size);
158 165
159 html("<td>"); 166 html("<td>");
160 cgit_log_link("log", NULL, "button", ctx.qry.head, curr_rev, 167 cgit_log_link("log", NULL, "button", ctx.qry.head, curr_rev,
161 fullpath, 0, NULL, NULL, ctx.qry.showmsg); 168 fullpath, 0, NULL, NULL, ctx.qry.showmsg);
162 if (ctx.repo->max_stats) 169 if (ctx.repo->max_stats)
163 cgit_stats_link("stats", NULL, "button", ctx.qry.head, 170 cgit_stats_link("stats", NULL, "button", ctx.qry.head,
164 fullpath); 171 fullpath);
165 html("</td></tr>\n"); 172 html("</td></tr>\n");
166 free(name); 173 free(name);
167 return 0; 174 return 0;
168} 175}
169 176
170static void ls_head() 177static void ls_head()
171{ 178{
172 html("<table summary='tree listing' class='list'>\n"); 179 html("<table summary='tree listing' class='list'>\n");
173 html("<tr class='nohover'>"); 180 html("<tr class='nohover'>");
174 html("<th class='left'>Mode</th>"); 181 html("<th class='left'>Mode</th>");
175 html("<th class='left'>Name</th>"); 182 html("<th class='left'>Name</th>");
176 html("<th class='right'>Size</th>"); 183 html("<th class='right'>Size</th>");
177 html("<th/>"); 184 html("<th/>");
178 html("</tr>\n"); 185 html("</tr>\n");
179 header = 1; 186 header = 1;
180} 187}
181 188
182static void ls_tail() 189static void ls_tail()
183{ 190{
184 if (!header) 191 if (!header)
185 return; 192 return;
186 html("</table>\n"); 193 html("</table>\n");
187 header = 0; 194 header = 0;
188} 195}
189 196
190static void ls_tree(const unsigned char *sha1, char *path) 197static void ls_tree(const unsigned char *sha1, char *path)
191{ 198{
192 struct tree *tree; 199 struct tree *tree;
193 200
194 tree = parse_tree_indirect(sha1); 201 tree = parse_tree_indirect(sha1);
195 if (!tree) { 202 if (!tree) {
196 cgit_print_error(fmt("Not a tree object: %s", 203 cgit_print_error(fmt("Not a tree object: %s",
197 sha1_to_hex(sha1))); 204 sha1_to_hex(sha1)));
198 return; 205 return;
199 } 206 }
200 207
201 ls_head(); 208 ls_head();
202 read_tree_recursive(tree, "", 0, 1, NULL, ls_item, NULL); 209 read_tree_recursive(tree, "", 0, 1, NULL, ls_item, NULL);
203 ls_tail(); 210 ls_tail();
204} 211}
205 212
206 213
207static int walk_tree(const unsigned char *sha1, const char *base, int baselen, 214static int walk_tree(const unsigned char *sha1, const char *base, int baselen,
208 const char *pathname, unsigned mode, int stage, 215 const char *pathname, unsigned mode, int stage,
209 void *cbdata) 216 void *cbdata)
210{ 217{
211 static int state; 218 static int state;
212 static char buffer[PATH_MAX]; 219 static char buffer[PATH_MAX];
213 char *url; 220 char *url;
214 221
215 if (state == 0) { 222 if (state == 0) {
216 memcpy(buffer, base, baselen); 223 memcpy(buffer, base, baselen);
217 strcpy(buffer+baselen, pathname); 224 strcpy(buffer+baselen, pathname);
218 url = cgit_pageurl(ctx.qry.repo, "tree", 225 url = cgit_pageurl(ctx.qry.repo, "tree",
219 fmt("h=%s&amp;path=%s", curr_rev, buffer)); 226 fmt("h=%s&amp;path=%s", curr_rev, buffer));
220 html("/"); 227 html("/");
221 cgit_tree_link(xstrdup(pathname), NULL, NULL, ctx.qry.head, 228 cgit_tree_link(xstrdup(pathname), NULL, NULL, ctx.qry.head,
222 curr_rev, buffer); 229 curr_rev, buffer);
223 230
224 if (strcmp(match_path, buffer)) 231 if (strcmp(match_path, buffer))
225 return READ_TREE_RECURSIVE; 232 return READ_TREE_RECURSIVE;
226 233
227 if (S_ISDIR(mode)) { 234 if (S_ISDIR(mode)) {
228 state = 1; 235 state = 1;
229 ls_head(); 236 ls_head();
230 return READ_TREE_RECURSIVE; 237 return READ_TREE_RECURSIVE;
231 } else { 238 } else {
232 print_object(sha1, buffer, pathname); 239 print_object(sha1, buffer, pathname);
233 return 0; 240 return 0;
234 } 241 }
235 } 242 }
236 ls_item(sha1, base, baselen, pathname, mode, stage, NULL); 243 ls_item(sha1, base, baselen, pathname, mode, stage, NULL);
237 return 0; 244 return 0;
238} 245}
239 246
240 247
241/* 248/*
242 * Show a tree or a blob 249 * Show a tree or a blob
243 * rev: the commit pointing at the root tree object 250 * rev: the commit pointing at the root tree object
244 * path: path to tree or blob 251 * path: path to tree or blob
245 */ 252 */
246void cgit_print_tree(const char *rev, char *path) 253void cgit_print_tree(const char *rev, char *path)
247{ 254{
248 unsigned char sha1[20]; 255 unsigned char sha1[20];
249 struct commit *commit; 256 struct commit *commit;
250 const char *paths[] = {path, NULL}; 257 const char *paths[] = {path, NULL};
251 258
252 if (!rev) 259 if (!rev)
253 rev = ctx.qry.head; 260 rev = ctx.qry.head;
254 261
255 curr_rev = xstrdup(rev); 262 curr_rev = xstrdup(rev);
256 if (get_sha1(rev, sha1)) { 263 if (get_sha1(rev, sha1)) {
257 cgit_print_error(fmt("Invalid revision name: %s", rev)); 264 cgit_print_error(fmt("Invalid revision name: %s", rev));
258 return; 265 return;
259 } 266 }
260 commit = lookup_commit_reference(sha1); 267 commit = lookup_commit_reference(sha1);
261 if (!commit || parse_commit(commit)) { 268 if (!commit || parse_commit(commit)) {
262 cgit_print_error(fmt("Invalid commit reference: %s", rev)); 269 cgit_print_error(fmt("Invalid commit reference: %s", rev));
263 return; 270 return;
264 } 271 }
265 272
266 html("path: <a href='"); 273 html("path: <a href='");
267 html_attr(cgit_pageurl(ctx.qry.repo, "tree", fmt("h=%s", rev))); 274 html_attr(cgit_pageurl(ctx.qry.repo, "tree", fmt("h=%s", rev)));
268 html("'>root</a>"); 275 html("'>root</a>");
269 276
270 if (path == NULL) { 277 if (path == NULL) {
271 ls_tree(commit->tree->object.sha1, NULL); 278 ls_tree(commit->tree->object.sha1, NULL);
272 return; 279 return;
273 } 280 }
274 281
275 match_path = path; 282 match_path = path;
276 read_tree_recursive(commit->tree, NULL, 0, 0, paths, walk_tree, NULL); 283 read_tree_recursive(commit->tree, NULL, 0, 0, paths, walk_tree, NULL);
277 ls_tail(); 284 ls_tail();
278} 285}