summaryrefslogtreecommitdiffabout
Unidiff
Diffstat (more/less context) (show 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.c25
5 files changed, 27 insertions, 12 deletions
diff --git a/cgit.c b/cgit.c
index b0e1c44..ec40e1f 100644
--- a/cgit.c
+++ b/cgit.c
@@ -1,432 +1,435 @@
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{
diff --git a/cgit.css b/cgit.css
index e3b32e7..ebf3322 100644
--- a/cgit.css
+++ b/cgit.css
@@ -48,394 +48,394 @@ table#header td.form {
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
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,293 +1,297 @@
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
diff --git a/ui-tree.c b/ui-tree.c
index c608754..f53ab64 100644
--- a/ui-tree.c
+++ b/ui-tree.c
@@ -1,239 +1,246 @@
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 if (ctx.repo->source_filter) {
26 html("<tr><td class='lines'><pre><code>");
27 ctx.repo->source_filter->argv[1] = xstrdup(name);
28 cgit_open_filter(ctx.repo->source_filter);
29 write(STDOUT_FILENO, buf, size);
30 cgit_close_filter(ctx.repo->source_filter);
31 html("</code></pre></td></tr></table>\n");
32 return;
33 }
34 25
26 if (ctx.cfg.enable_tree_linenumbers) {
35 html("<tr><td class='linenumbers'><pre>"); 27 html("<tr><td class='linenumbers'><pre>");
36 idx = 0; 28 idx = 0;
37 lineno = 0; 29 lineno = 0;
38 30
39 if (size) { 31 if (size) {
40 htmlf(numberfmt, ++lineno); 32 htmlf(numberfmt, ++lineno);
41 while(idx < size - 1) { // skip absolute last newline 33 while(idx < size - 1) { // skip absolute last newline
42 if (buf[idx] == '\n') 34 if (buf[idx] == '\n')
43 htmlf(numberfmt, ++lineno); 35 htmlf(numberfmt, ++lineno);
44 idx++; 36 idx++;
45 } 37 }
46 } 38 }
47 html("</pre></td>\n"); 39 html("</pre></td>\n");
40 }
41 else {
42 html("<tr>\n");
43 }
44
45 if (ctx.repo->source_filter) {
46 html("<td class='lines'><pre><code>");
47 ctx.repo->source_filter->argv[1] = xstrdup(name);
48 cgit_open_filter(ctx.repo->source_filter);
49 write(STDOUT_FILENO, buf, size);
50 cgit_close_filter(ctx.repo->source_filter);
51 html("</code></pre></td></tr></table>\n");
52 return;
53 }
54
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