summaryrefslogtreecommitdiffabout
Unidiff
Diffstat (more/less context) (ignore whitespace changes)
-rw-r--r--cgit.c2
-rw-r--r--cgit.h1
-rw-r--r--cgitrc.5.txt4
-rw-r--r--ui-tree.c27
4 files changed, 23 insertions, 11 deletions
diff --git a/cgit.c b/cgit.c
index b0e1c44..a4788cb 100644
--- a/cgit.c
+++ b/cgit.c
@@ -1,558 +1,560 @@
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, "linenumbers"))
70 ctx.cfg.linenumbers = atoi(value);
69 else if (!strcmp(name, "module-link")) 71 else if (!strcmp(name, "module-link"))
70 ctx.cfg.module_link = xstrdup(value); 72 ctx.cfg.module_link = xstrdup(value);
71 else if (!strcmp(name, "virtual-root")) { 73 else if (!strcmp(name, "virtual-root")) {
72 ctx.cfg.virtual_root = trim_end(value, '/'); 74 ctx.cfg.virtual_root = trim_end(value, '/');
73 if (!ctx.cfg.virtual_root && (!strcmp(value, "/"))) 75 if (!ctx.cfg.virtual_root && (!strcmp(value, "/")))
74 ctx.cfg.virtual_root = ""; 76 ctx.cfg.virtual_root = "";
75 } else if (!strcmp(name, "nocache")) 77 } else if (!strcmp(name, "nocache"))
76 ctx.cfg.nocache = atoi(value); 78 ctx.cfg.nocache = atoi(value);
77 else if (!strcmp(name, "noplainemail")) 79 else if (!strcmp(name, "noplainemail"))
78 ctx.cfg.noplainemail = atoi(value); 80 ctx.cfg.noplainemail = atoi(value);
79 else if (!strcmp(name, "noheader")) 81 else if (!strcmp(name, "noheader"))
80 ctx.cfg.noheader = atoi(value); 82 ctx.cfg.noheader = atoi(value);
81 else if (!strcmp(name, "snapshots")) 83 else if (!strcmp(name, "snapshots"))
82 ctx.cfg.snapshots = cgit_parse_snapshots_mask(value); 84 ctx.cfg.snapshots = cgit_parse_snapshots_mask(value);
83 else if (!strcmp(name, "enable-index-links")) 85 else if (!strcmp(name, "enable-index-links"))
84 ctx.cfg.enable_index_links = atoi(value); 86 ctx.cfg.enable_index_links = atoi(value);
85 else if (!strcmp(name, "enable-log-filecount")) 87 else if (!strcmp(name, "enable-log-filecount"))
86 ctx.cfg.enable_log_filecount = atoi(value); 88 ctx.cfg.enable_log_filecount = atoi(value);
87 else if (!strcmp(name, "enable-log-linecount")) 89 else if (!strcmp(name, "enable-log-linecount"))
88 ctx.cfg.enable_log_linecount = atoi(value); 90 ctx.cfg.enable_log_linecount = 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;
241 ctx->cfg.max_repo_count = 50; 243 ctx->cfg.max_repo_count = 50;
242 ctx->cfg.max_commit_count = 50; 244 ctx->cfg.max_commit_count = 50;
243 ctx->cfg.max_lock_attempts = 5; 245 ctx->cfg.max_lock_attempts = 5;
244 ctx->cfg.max_msg_len = 80; 246 ctx->cfg.max_msg_len = 80;
245 ctx->cfg.max_repodesc_len = 80; 247 ctx->cfg.max_repodesc_len = 80;
246 ctx->cfg.max_stats = 0; 248 ctx->cfg.max_stats = 0;
247 ctx->cfg.module_link = "./?repo=%s&page=commit&id=%s"; 249 ctx->cfg.module_link = "./?repo=%s&page=commit&id=%s";
248 ctx->cfg.renamelimit = -1; 250 ctx->cfg.renamelimit = -1;
249 ctx->cfg.robots = "index, nofollow"; 251 ctx->cfg.robots = "index, nofollow";
250 ctx->cfg.root_title = "Git repository browser"; 252 ctx->cfg.root_title = "Git repository browser";
251 ctx->cfg.root_desc = "a fast webinterface for the git dscm"; 253 ctx->cfg.root_desc = "a fast webinterface for the git dscm";
252 ctx->cfg.script_name = CGIT_SCRIPT_NAME; 254 ctx->cfg.script_name = CGIT_SCRIPT_NAME;
253 ctx->cfg.summary_branches = 10; 255 ctx->cfg.summary_branches = 10;
254 ctx->cfg.summary_log = 10; 256 ctx->cfg.summary_log = 10;
255 ctx->cfg.summary_tags = 10; 257 ctx->cfg.summary_tags = 10;
256 ctx->env.cgit_config = xstrdupn(getenv("CGIT_CONFIG")); 258 ctx->env.cgit_config = xstrdupn(getenv("CGIT_CONFIG"));
257 ctx->env.http_host = xstrdupn(getenv("HTTP_HOST")); 259 ctx->env.http_host = xstrdupn(getenv("HTTP_HOST"));
258 ctx->env.https = xstrdupn(getenv("HTTPS")); 260 ctx->env.https = xstrdupn(getenv("HTTPS"));
259 ctx->env.no_http = xstrdupn(getenv("NO_HTTP")); 261 ctx->env.no_http = xstrdupn(getenv("NO_HTTP"));
260 ctx->env.path_info = xstrdupn(getenv("PATH_INFO")); 262 ctx->env.path_info = xstrdupn(getenv("PATH_INFO"));
261 ctx->env.query_string = xstrdupn(getenv("QUERY_STRING")); 263 ctx->env.query_string = xstrdupn(getenv("QUERY_STRING"));
262 ctx->env.request_method = xstrdupn(getenv("REQUEST_METHOD")); 264 ctx->env.request_method = xstrdupn(getenv("REQUEST_METHOD"));
263 ctx->env.script_name = xstrdupn(getenv("SCRIPT_NAME")); 265 ctx->env.script_name = xstrdupn(getenv("SCRIPT_NAME"));
264 ctx->env.server_name = xstrdupn(getenv("SERVER_NAME")); 266 ctx->env.server_name = xstrdupn(getenv("SERVER_NAME"));
265 ctx->env.server_port = xstrdupn(getenv("SERVER_PORT")); 267 ctx->env.server_port = xstrdupn(getenv("SERVER_PORT"));
266 ctx->page.mimetype = "text/html"; 268 ctx->page.mimetype = "text/html";
267 ctx->page.charset = PAGE_ENCODING; 269 ctx->page.charset = PAGE_ENCODING;
268 ctx->page.filename = NULL; 270 ctx->page.filename = NULL;
269 ctx->page.size = 0; 271 ctx->page.size = 0;
270 ctx->page.modified = time(NULL); 272 ctx->page.modified = time(NULL);
271 ctx->page.expires = ctx->page.modified; 273 ctx->page.expires = ctx->page.modified;
272 ctx->page.etag = NULL; 274 ctx->page.etag = NULL;
273 memset(&ctx->cfg.mimetypes, 0, sizeof(struct string_list)); 275 memset(&ctx->cfg.mimetypes, 0, sizeof(struct string_list));
274 if (ctx->env.script_name) 276 if (ctx->env.script_name)
275 ctx->cfg.script_name = ctx->env.script_name; 277 ctx->cfg.script_name = ctx->env.script_name;
276 if (ctx->env.query_string) 278 if (ctx->env.query_string)
277 ctx->qry.raw = ctx->env.query_string; 279 ctx->qry.raw = ctx->env.query_string;
278 if (!ctx->env.cgit_config) 280 if (!ctx->env.cgit_config)
279 ctx->env.cgit_config = CGIT_CONFIG; 281 ctx->env.cgit_config = CGIT_CONFIG;
280} 282}
281 283
282struct refmatch { 284struct refmatch {
283 char *req_ref; 285 char *req_ref;
284 char *first_ref; 286 char *first_ref;
285 int match; 287 int match;
286}; 288};
287 289
288int find_current_ref(const char *refname, const unsigned char *sha1, 290int find_current_ref(const char *refname, const unsigned char *sha1,
289 int flags, void *cb_data) 291 int flags, void *cb_data)
290{ 292{
291 struct refmatch *info; 293 struct refmatch *info;
292 294
293 info = (struct refmatch *)cb_data; 295 info = (struct refmatch *)cb_data;
294 if (!strcmp(refname, info->req_ref)) 296 if (!strcmp(refname, info->req_ref))
295 info->match = 1; 297 info->match = 1;
296 if (!info->first_ref) 298 if (!info->first_ref)
297 info->first_ref = xstrdup(refname); 299 info->first_ref = xstrdup(refname);
298 return info->match; 300 return info->match;
299} 301}
300 302
301char *find_default_branch(struct cgit_repo *repo) 303char *find_default_branch(struct cgit_repo *repo)
302{ 304{
303 struct refmatch info; 305 struct refmatch info;
304 char *ref; 306 char *ref;
305 307
306 info.req_ref = repo->defbranch; 308 info.req_ref = repo->defbranch;
307 info.first_ref = NULL; 309 info.first_ref = NULL;
308 info.match = 0; 310 info.match = 0;
309 for_each_branch_ref(find_current_ref, &info); 311 for_each_branch_ref(find_current_ref, &info);
310 if (info.match) 312 if (info.match)
311 ref = info.req_ref; 313 ref = info.req_ref;
312 else 314 else
313 ref = info.first_ref; 315 ref = info.first_ref;
314 if (ref) 316 if (ref)
315 ref = xstrdup(ref); 317 ref = xstrdup(ref);
316 return ref; 318 return ref;
317} 319}
318 320
319static int prepare_repo_cmd(struct cgit_context *ctx) 321static int prepare_repo_cmd(struct cgit_context *ctx)
320{ 322{
321 char *tmp; 323 char *tmp;
322 unsigned char sha1[20]; 324 unsigned char sha1[20];
323 int nongit = 0; 325 int nongit = 0;
324 326
325 setenv("GIT_DIR", ctx->repo->path, 1); 327 setenv("GIT_DIR", ctx->repo->path, 1);
326 setup_git_directory_gently(&nongit); 328 setup_git_directory_gently(&nongit);
327 if (nongit) { 329 if (nongit) {
328 ctx->page.title = fmt("%s - %s", ctx->cfg.root_title, 330 ctx->page.title = fmt("%s - %s", ctx->cfg.root_title,
329 "config error"); 331 "config error");
330 tmp = fmt("Not a git repository: '%s'", ctx->repo->path); 332 tmp = fmt("Not a git repository: '%s'", ctx->repo->path);
331 ctx->repo = NULL; 333 ctx->repo = NULL;
332 cgit_print_http_headers(ctx); 334 cgit_print_http_headers(ctx);
333 cgit_print_docstart(ctx); 335 cgit_print_docstart(ctx);
334 cgit_print_pageheader(ctx); 336 cgit_print_pageheader(ctx);
335 cgit_print_error(tmp); 337 cgit_print_error(tmp);
336 cgit_print_docend(); 338 cgit_print_docend();
337 return 1; 339 return 1;
338 } 340 }
339 ctx->page.title = fmt("%s - %s", ctx->repo->name, ctx->repo->desc); 341 ctx->page.title = fmt("%s - %s", ctx->repo->name, ctx->repo->desc);
340 342
341 if (!ctx->qry.head) { 343 if (!ctx->qry.head) {
342 ctx->qry.nohead = 1; 344 ctx->qry.nohead = 1;
343 ctx->qry.head = find_default_branch(ctx->repo); 345 ctx->qry.head = find_default_branch(ctx->repo);
344 ctx->repo->defbranch = ctx->qry.head; 346 ctx->repo->defbranch = ctx->qry.head;
345 } 347 }
346 348
347 if (!ctx->qry.head) { 349 if (!ctx->qry.head) {
348 cgit_print_http_headers(ctx); 350 cgit_print_http_headers(ctx);
349 cgit_print_docstart(ctx); 351 cgit_print_docstart(ctx);
350 cgit_print_pageheader(ctx); 352 cgit_print_pageheader(ctx);
351 cgit_print_error("Repository seems to be empty"); 353 cgit_print_error("Repository seems to be empty");
352 cgit_print_docend(); 354 cgit_print_docend();
353 return 1; 355 return 1;
354 } 356 }
355 357
356 if (get_sha1(ctx->qry.head, sha1)) { 358 if (get_sha1(ctx->qry.head, sha1)) {
357 tmp = xstrdup(ctx->qry.head); 359 tmp = xstrdup(ctx->qry.head);
358 ctx->qry.head = ctx->repo->defbranch; 360 ctx->qry.head = ctx->repo->defbranch;
359 ctx->page.status = 404; 361 ctx->page.status = 404;
360 ctx->page.statusmsg = "not found"; 362 ctx->page.statusmsg = "not found";
361 cgit_print_http_headers(ctx); 363 cgit_print_http_headers(ctx);
362 cgit_print_docstart(ctx); 364 cgit_print_docstart(ctx);
363 cgit_print_pageheader(ctx); 365 cgit_print_pageheader(ctx);
364 cgit_print_error(fmt("Invalid branch: %s", tmp)); 366 cgit_print_error(fmt("Invalid branch: %s", tmp));
365 cgit_print_docend(); 367 cgit_print_docend();
366 return 1; 368 return 1;
367 } 369 }
368 return 0; 370 return 0;
369} 371}
370 372
371static void process_request(void *cbdata) 373static void process_request(void *cbdata)
372{ 374{
373 struct cgit_context *ctx = cbdata; 375 struct cgit_context *ctx = cbdata;
374 struct cgit_cmd *cmd; 376 struct cgit_cmd *cmd;
375 377
376 cmd = cgit_get_cmd(ctx); 378 cmd = cgit_get_cmd(ctx);
377 if (!cmd) { 379 if (!cmd) {
378 ctx->page.title = "cgit error"; 380 ctx->page.title = "cgit error";
379 cgit_print_http_headers(ctx); 381 cgit_print_http_headers(ctx);
380 cgit_print_docstart(ctx); 382 cgit_print_docstart(ctx);
381 cgit_print_pageheader(ctx); 383 cgit_print_pageheader(ctx);
382 cgit_print_error("Invalid request"); 384 cgit_print_error("Invalid request");
383 cgit_print_docend(); 385 cgit_print_docend();
384 return; 386 return;
385 } 387 }
386 388
387 if (cmd->want_repo && !ctx->repo) { 389 if (cmd->want_repo && !ctx->repo) {
388 cgit_print_http_headers(ctx); 390 cgit_print_http_headers(ctx);
389 cgit_print_docstart(ctx); 391 cgit_print_docstart(ctx);
390 cgit_print_pageheader(ctx); 392 cgit_print_pageheader(ctx);
391 cgit_print_error(fmt("No repository selected")); 393 cgit_print_error(fmt("No repository selected"));
392 cgit_print_docend(); 394 cgit_print_docend();
393 return; 395 return;
394 } 396 }
395 397
396 if (ctx->repo && prepare_repo_cmd(ctx)) 398 if (ctx->repo && prepare_repo_cmd(ctx))
397 return; 399 return;
398 400
399 if (cmd->want_layout) { 401 if (cmd->want_layout) {
400 cgit_print_http_headers(ctx); 402 cgit_print_http_headers(ctx);
401 cgit_print_docstart(ctx); 403 cgit_print_docstart(ctx);
402 cgit_print_pageheader(ctx); 404 cgit_print_pageheader(ctx);
403 } 405 }
404 406
405 cmd->fn(ctx); 407 cmd->fn(ctx);
406 408
407 if (cmd->want_layout) 409 if (cmd->want_layout)
408 cgit_print_docend(); 410 cgit_print_docend();
409} 411}
410 412
411int cmp_repos(const void *a, const void *b) 413int cmp_repos(const void *a, const void *b)
412{ 414{
413 const struct cgit_repo *ra = a, *rb = b; 415 const struct cgit_repo *ra = a, *rb = b;
414 return strcmp(ra->url, rb->url); 416 return strcmp(ra->url, rb->url);
415} 417}
416 418
417void print_repo(struct cgit_repo *repo) 419void print_repo(struct cgit_repo *repo)
418{ 420{
419 printf("repo.url=%s\n", repo->url); 421 printf("repo.url=%s\n", repo->url);
420 printf("repo.name=%s\n", repo->name); 422 printf("repo.name=%s\n", repo->name);
421 printf("repo.path=%s\n", repo->path); 423 printf("repo.path=%s\n", repo->path);
422 if (repo->owner) 424 if (repo->owner)
423 printf("repo.owner=%s\n", repo->owner); 425 printf("repo.owner=%s\n", repo->owner);
424 if (repo->desc) 426 if (repo->desc)
425 printf("repo.desc=%s\n", repo->desc); 427 printf("repo.desc=%s\n", repo->desc);
426 if (repo->readme) 428 if (repo->readme)
427 printf("repo.readme=%s\n", repo->readme); 429 printf("repo.readme=%s\n", repo->readme);
428 printf("\n"); 430 printf("\n");
429} 431}
430 432
431void print_repolist(struct cgit_repolist *list) 433void print_repolist(struct cgit_repolist *list)
432{ 434{
433 int i; 435 int i;
434 436
435 for(i = 0; i < list->count; i++) 437 for(i = 0; i < list->count; i++)
436 print_repo(&list->repos[i]); 438 print_repo(&list->repos[i]);
437} 439}
438 440
439 441
440static void cgit_parse_args(int argc, const char **argv) 442static void cgit_parse_args(int argc, const char **argv)
441{ 443{
442 int i; 444 int i;
443 int scan = 0; 445 int scan = 0;
444 446
445 for (i = 1; i < argc; i++) { 447 for (i = 1; i < argc; i++) {
446 if (!strncmp(argv[i], "--cache=", 8)) { 448 if (!strncmp(argv[i], "--cache=", 8)) {
447 ctx.cfg.cache_root = xstrdup(argv[i]+8); 449 ctx.cfg.cache_root = xstrdup(argv[i]+8);
448 } 450 }
449 if (!strcmp(argv[i], "--nocache")) { 451 if (!strcmp(argv[i], "--nocache")) {
450 ctx.cfg.nocache = 1; 452 ctx.cfg.nocache = 1;
451 } 453 }
452 if (!strcmp(argv[i], "--nohttp")) { 454 if (!strcmp(argv[i], "--nohttp")) {
453 ctx.env.no_http = "1"; 455 ctx.env.no_http = "1";
454 } 456 }
455 if (!strncmp(argv[i], "--query=", 8)) { 457 if (!strncmp(argv[i], "--query=", 8)) {
456 ctx.qry.raw = xstrdup(argv[i]+8); 458 ctx.qry.raw = xstrdup(argv[i]+8);
457 } 459 }
458 if (!strncmp(argv[i], "--repo=", 7)) { 460 if (!strncmp(argv[i], "--repo=", 7)) {
459 ctx.qry.repo = xstrdup(argv[i]+7); 461 ctx.qry.repo = xstrdup(argv[i]+7);
460 } 462 }
461 if (!strncmp(argv[i], "--page=", 7)) { 463 if (!strncmp(argv[i], "--page=", 7)) {
462 ctx.qry.page = xstrdup(argv[i]+7); 464 ctx.qry.page = xstrdup(argv[i]+7);
463 } 465 }
464 if (!strncmp(argv[i], "--head=", 7)) { 466 if (!strncmp(argv[i], "--head=", 7)) {
465 ctx.qry.head = xstrdup(argv[i]+7); 467 ctx.qry.head = xstrdup(argv[i]+7);
466 ctx.qry.has_symref = 1; 468 ctx.qry.has_symref = 1;
467 } 469 }
468 if (!strncmp(argv[i], "--sha1=", 7)) { 470 if (!strncmp(argv[i], "--sha1=", 7)) {
469 ctx.qry.sha1 = xstrdup(argv[i]+7); 471 ctx.qry.sha1 = xstrdup(argv[i]+7);
470 ctx.qry.has_sha1 = 1; 472 ctx.qry.has_sha1 = 1;
471 } 473 }
472 if (!strncmp(argv[i], "--ofs=", 6)) { 474 if (!strncmp(argv[i], "--ofs=", 6)) {
473 ctx.qry.ofs = atoi(argv[i]+6); 475 ctx.qry.ofs = atoi(argv[i]+6);
474 } 476 }
475 if (!strncmp(argv[i], "--scan-tree=", 12)) { 477 if (!strncmp(argv[i], "--scan-tree=", 12)) {
476 scan++; 478 scan++;
477 scan_tree(argv[i] + 12); 479 scan_tree(argv[i] + 12);
478 } 480 }
479 } 481 }
480 if (scan) { 482 if (scan) {
481 qsort(cgit_repolist.repos, cgit_repolist.count, 483 qsort(cgit_repolist.repos, cgit_repolist.count,
482 sizeof(struct cgit_repo), cmp_repos); 484 sizeof(struct cgit_repo), cmp_repos);
483 print_repolist(&cgit_repolist); 485 print_repolist(&cgit_repolist);
484 exit(0); 486 exit(0);
485 } 487 }
486} 488}
487 489
488static int calc_ttl() 490static int calc_ttl()
489{ 491{
490 if (!ctx.repo) 492 if (!ctx.repo)
491 return ctx.cfg.cache_root_ttl; 493 return ctx.cfg.cache_root_ttl;
492 494
493 if (!ctx.qry.page) 495 if (!ctx.qry.page)
494 return ctx.cfg.cache_repo_ttl; 496 return ctx.cfg.cache_repo_ttl;
495 497
496 if (ctx.qry.has_symref) 498 if (ctx.qry.has_symref)
497 return ctx.cfg.cache_dynamic_ttl; 499 return ctx.cfg.cache_dynamic_ttl;
498 500
499 if (ctx.qry.has_sha1) 501 if (ctx.qry.has_sha1)
500 return ctx.cfg.cache_static_ttl; 502 return ctx.cfg.cache_static_ttl;
501 503
502 return ctx.cfg.cache_repo_ttl; 504 return ctx.cfg.cache_repo_ttl;
503} 505}
504 506
505int main(int argc, const char **argv) 507int main(int argc, const char **argv)
506{ 508{
507 const char *path; 509 const char *path;
508 char *qry; 510 char *qry;
509 int err, ttl; 511 int err, ttl;
510 512
511 prepare_context(&ctx); 513 prepare_context(&ctx);
512 cgit_repolist.length = 0; 514 cgit_repolist.length = 0;
513 cgit_repolist.count = 0; 515 cgit_repolist.count = 0;
514 cgit_repolist.repos = NULL; 516 cgit_repolist.repos = NULL;
515 517
516 cgit_parse_args(argc, argv); 518 cgit_parse_args(argc, argv);
517 parse_configfile(ctx.env.cgit_config, config_cb); 519 parse_configfile(ctx.env.cgit_config, config_cb);
518 ctx.repo = NULL; 520 ctx.repo = NULL;
519 http_parse_querystring(ctx.qry.raw, querystring_cb); 521 http_parse_querystring(ctx.qry.raw, querystring_cb);
520 522
521 /* If virtual-root isn't specified in cgitrc, lets pretend 523 /* If virtual-root isn't specified in cgitrc, lets pretend
522 * that virtual-root equals SCRIPT_NAME. 524 * that virtual-root equals SCRIPT_NAME.
523 */ 525 */
524 if (!ctx.cfg.virtual_root) 526 if (!ctx.cfg.virtual_root)
525 ctx.cfg.virtual_root = ctx.cfg.script_name; 527 ctx.cfg.virtual_root = ctx.cfg.script_name;
526 528
527 /* If no url parameter is specified on the querystring, lets 529 /* If no url parameter is specified on the querystring, lets
528 * use PATH_INFO as url. This allows cgit to work with virtual 530 * use PATH_INFO as url. This allows cgit to work with virtual
529 * urls without the need for rewriterules in the webserver (as 531 * urls without the need for rewriterules in the webserver (as
530 * long as PATH_INFO is included in the cache lookup key). 532 * long as PATH_INFO is included in the cache lookup key).
531 */ 533 */
532 path = ctx.env.path_info; 534 path = ctx.env.path_info;
533 if (!ctx.qry.url && path) { 535 if (!ctx.qry.url && path) {
534 if (path[0] == '/') 536 if (path[0] == '/')
535 path++; 537 path++;
536 ctx.qry.url = xstrdup(path); 538 ctx.qry.url = xstrdup(path);
537 if (ctx.qry.raw) { 539 if (ctx.qry.raw) {
538 qry = ctx.qry.raw; 540 qry = ctx.qry.raw;
539 ctx.qry.raw = xstrdup(fmt("%s?%s", path, qry)); 541 ctx.qry.raw = xstrdup(fmt("%s?%s", path, qry));
540 free(qry); 542 free(qry);
541 } else 543 } else
542 ctx.qry.raw = xstrdup(ctx.qry.url); 544 ctx.qry.raw = xstrdup(ctx.qry.url);
543 cgit_parse_url(ctx.qry.url); 545 cgit_parse_url(ctx.qry.url);
544 } 546 }
545 547
546 ttl = calc_ttl(); 548 ttl = calc_ttl();
547 ctx.page.expires += ttl*60; 549 ctx.page.expires += ttl*60;
548 if (ctx.env.request_method && !strcmp(ctx.env.request_method, "HEAD")) 550 if (ctx.env.request_method && !strcmp(ctx.env.request_method, "HEAD"))
549 ctx.cfg.nocache = 1; 551 ctx.cfg.nocache = 1;
550 if (ctx.cfg.nocache) 552 if (ctx.cfg.nocache)
551 ctx.cfg.cache_size = 0; 553 ctx.cfg.cache_size = 0;
552 err = cache_process(ctx.cfg.cache_size, ctx.cfg.cache_root, 554 err = cache_process(ctx.cfg.cache_size, ctx.cfg.cache_root,
553 ctx.qry.raw, ttl, process_request, &ctx); 555 ctx.qry.raw, ttl, process_request, &ctx);
554 if (err) 556 if (err)
555 cgit_print_error(fmt("Error processing page: %s (%d)", 557 cgit_print_error(fmt("Error processing page: %s (%d)",
556 strerror(err), err)); 558 strerror(err), err));
557 return err; 559 return err;
558} 560}
diff --git a/cgit.h b/cgit.h
index adb8da4..2fdc531 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 local_time; 176 int local_time;
177 int linenumbers;
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 3c35b02..a762ccc 100644
--- a/cgitrc.5.txt
+++ b/cgitrc.5.txt
@@ -1,461 +1,465 @@
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
102favicon:: 102favicon::
103 Url used as link to a shortcut icon for cgit. If specified, it is 103 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 104 suggested to use the value "/favicon.ico" since certain browsers will
105 ignore other values. Default value: none. 105 ignore other values. Default value: none.
106 106
107footer:: 107footer::
108 The content of the file specified with this option will be included 108 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 109 verbatim at the bottom of all pages (i.e. it replaces the standard
110 "generated by..." message. Default value: none. 110 "generated by..." message. Default value: none.
111 111
112head-include:: 112head-include::
113 The content of the file specified with this option will be included 113 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. 114 verbatim in the html HEAD section on all pages. Default value: none.
115 115
116header:: 116header::
117 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
118 verbatim at the top of all pages. Default value: none. 118 verbatim at the top of all pages. Default value: none.
119 119
120include:: 120include::
121 Name of a configfile to include before the rest of the current config- 121 Name of a configfile to include before the rest of the current config-
122 file is parsed. Default value: none. 122 file is parsed. Default value: none.
123 123
124index-header:: 124index-header::
125 The content of the file specified with this option will be included 125 The content of the file specified with this option will be included
126 verbatim above the repository index. This setting is deprecated, and 126 verbatim above the repository index. This setting is deprecated, and
127 will not be supported by cgit-1.0 (use root-readme instead). Default 127 will not be supported by cgit-1.0 (use root-readme instead). Default
128 value: none. 128 value: none.
129 129
130index-info:: 130index-info::
131 The content of the file specified with this option will be included 131 The content of the file specified with this option will be included
132 verbatim below the heading on the repository index page. This setting 132 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 133 is deprecated, and will not be supported by cgit-1.0 (use root-desc
134 instead). Default value: none. 134 instead). Default value: none.
135 135
136local-time:: 136local-time::
137 Flag which, if set to "1", makes cgit print commit and tag times in the 137 Flag which, if set to "1", makes cgit print commit and tag times in the
138 servers timezone. Default value: "0". 138 servers timezone. Default value: "0".
139 139
140logo:: 140logo::
141 Url which specifies the source of an image which will be used as a logo 141 Url which specifies the source of an image which will be used as a logo
142 on all cgit pages. Default value: "/cgit.png". 142 on all cgit pages. Default value: "/cgit.png".
143 143
144logo-link:: 144logo-link::
145 Url loaded when clicking on the cgit logo image. If unspecified the 145 Url loaded when clicking on the cgit logo image. If unspecified the
146 calculated url of the repository index page will be used. Default 146 calculated url of the repository index page will be used. Default
147 value: none. 147 value: none.
148 148
149linenumbers::
150 If set to "1" lines in tree view will have numbers.
151 Default value: "0".
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 (and allowed) set of snapshot formats 224 Text which specifies the default (and allowed) set of snapshot formats
221 supported by cgit. The value is a space-separated list of zero or more 225 supported by cgit. The value is a space-separated list of zero or more
222 of the following values: 226 of the following values:
223 "tar" uncompressed tar-file 227 "tar" uncompressed tar-file
224 "tar.gz"gzip-compressed tar-file 228 "tar.gz"gzip-compressed tar-file
225 "tar.bz2"bzip-compressed tar-file 229 "tar.bz2"bzip-compressed tar-file
226 "zip" zip-file 230 "zip" zip-file
227 Default value: none. 231 Default value: none.
228 232
229source-filter:: 233source-filter::
230 Specifies a command which will be invoked to format plaintext blobs 234 Specifies a command which will be invoked to format plaintext blobs
231 in the tree view. The command will get the blob content on its STDIN 235 in the tree view. The command will get the blob content on its STDIN
232 and the name of the blob as its only command line argument. The STDOUT 236 and the name of the blob as its only command line argument. The STDOUT
233 from the command will be included verbatim as the blob contents, i.e. 237 from the command will be included verbatim as the blob contents, i.e.
234 this can be used to implement e.g. syntax highlighting. Default value: 238 this can be used to implement e.g. syntax highlighting. Default value:
235 none. 239 none.
236 240
237summary-branches:: 241summary-branches::
238 Specifies the number of branches to display in the repository "summary" 242 Specifies the number of branches to display in the repository "summary"
239 view. Default value: "10". 243 view. Default value: "10".
240 244
241summary-log:: 245summary-log::
242 Specifies the number of log entries to display in the repository 246 Specifies the number of log entries to display in the repository
243 "summary" view. Default value: "10". 247 "summary" view. Default value: "10".
244 248
245summary-tags:: 249summary-tags::
246 Specifies the number of tags to display in the repository "summary" 250 Specifies the number of tags to display in the repository "summary"
247 view. Default value: "10". 251 view. Default value: "10".
248 252
249virtual-root:: 253virtual-root::
250 Url which, if specified, will be used as root for all cgit links. It 254 Url which, if specified, will be used as root for all cgit links. It
251 will also cause cgit to generate 'virtual urls', i.e. urls like 255 will also cause cgit to generate 'virtual urls', i.e. urls like
252 '/cgit/tree/README' as opposed to '?r=cgit&p=tree&path=README'. Default 256 '/cgit/tree/README' as opposed to '?r=cgit&p=tree&path=README'. Default
253 value: none. 257 value: none.
254 NOTE: cgit has recently learned how to use PATH_INFO to achieve the 258 NOTE: cgit has recently learned how to use PATH_INFO to achieve the
255 same kind of virtual urls, so this option will probably be deprecated. 259 same kind of virtual urls, so this option will probably be deprecated.
256 260
257REPOSITORY SETTINGS 261REPOSITORY SETTINGS
258------------------- 262-------------------
259repo.about-filter:: 263repo.about-filter::
260 Override the default about-filter. Default value: <about-filter>. 264 Override the default about-filter. Default value: <about-filter>.
261 265
262repo.clone-url:: 266repo.clone-url::
263 A list of space-separated urls which can be used to clone this repo. 267 A list of space-separated urls which can be used to clone this repo.
264 Default value: none. 268 Default value: none.
265 269
266repo.commit-filter:: 270repo.commit-filter::
267 Override the default commit-filter. Default value: <commit-filter>. 271 Override the default commit-filter. Default value: <commit-filter>.
268 272
269repo.defbranch:: 273repo.defbranch::
270 The name of the default branch for this repository. If no such branch 274 The name of the default branch for this repository. If no such branch
271 exists in the repository, the first branch name (when sorted) is used 275 exists in the repository, the first branch name (when sorted) is used
272 as default instead. Default value: "master". 276 as default instead. Default value: "master".
273 277
274repo.desc:: 278repo.desc::
275 The value to show as repository description. Default value: none. 279 The value to show as repository description. Default value: none.
276 280
277repo.enable-log-filecount:: 281repo.enable-log-filecount::
278 A flag which can be used to disable the global setting 282 A flag which can be used to disable the global setting
279 `enable-log-filecount'. Default value: none. 283 `enable-log-filecount'. Default value: none.
280 284
281repo.enable-log-linecount:: 285repo.enable-log-linecount::
282 A flag which can be used to disable the global setting 286 A flag which can be used to disable the global setting
283 `enable-log-linecount'. Default value: none. 287 `enable-log-linecount'. Default value: none.
284 288
285repo.max-stats:: 289repo.max-stats::
286 Override the default maximum statistics period. Valid values are equal 290 Override the default maximum statistics period. Valid values are equal
287 to the values specified for the global "max-stats" setting. Default 291 to the values specified for the global "max-stats" setting. Default
288 value: none. 292 value: none.
289 293
290repo.name:: 294repo.name::
291 The value to show as repository name. Default value: <repo.url>. 295 The value to show as repository name. Default value: <repo.url>.
292 296
293repo.owner:: 297repo.owner::
294 A value used to identify the owner of the repository. Default value: 298 A value used to identify the owner of the repository. Default value:
295 none. 299 none.
296 300
297repo.path:: 301repo.path::
298 An absolute path to the repository directory. For non-bare repositories 302 An absolute path to the repository directory. For non-bare repositories
299 this is the .git-directory. Default value: none. 303 this is the .git-directory. Default value: none.
300 304
301repo.readme:: 305repo.readme::
302 A path (relative to <repo.path>) which specifies a file to include 306 A path (relative to <repo.path>) which specifies a file to include
303 verbatim as the "About" page for this repo. Default value: none. 307 verbatim as the "About" page for this repo. Default value: none.
304 308
305repo.snapshots:: 309repo.snapshots::
306 A mask of allowed snapshot-formats for this repo, restricted by the 310 A mask of allowed snapshot-formats for this repo, restricted by the
307 "snapshots" global setting. Default value: <snapshots>. 311 "snapshots" global setting. Default value: <snapshots>.
308 312
309repo.source-filter:: 313repo.source-filter::
310 Override the default source-filter. Default value: <source-filter>. 314 Override the default source-filter. Default value: <source-filter>.
311 315
312repo.url:: 316repo.url::
313 The relative url used to access the repository. This must be the first 317 The relative url used to access the repository. This must be the first
314 setting specified for each repo. Default value: none. 318 setting specified for each repo. Default value: none.
315 319
316 320
317EXAMPLE CGITRC FILE 321EXAMPLE CGITRC FILE
318------------------- 322-------------------
319 323
320.... 324....
321# Enable caching of up to 1000 output entriess 325# Enable caching of up to 1000 output entriess
322cache-size=1000 326cache-size=1000
323 327
324 328
325# Specify some default clone prefixes 329# Specify some default clone prefixes
326clone-prefix=git://foobar.com ssh://foobar.com/pub/git http://foobar.com/git 330clone-prefix=git://foobar.com ssh://foobar.com/pub/git http://foobar.com/git
327 331
328# Specify the css url 332# Specify the css url
329css=/css/cgit.css 333css=/css/cgit.css
330 334
331 335
332# Show extra links for each repository on the index page 336# Show extra links for each repository on the index page
333enable-index-links=1 337enable-index-links=1
334 338
335 339
336# Show number of affected files per commit on the log pages 340# Show number of affected files per commit on the log pages
337enable-log-filecount=1 341enable-log-filecount=1
338 342
339 343
340# Show number of added/removed lines per commit on the log pages 344# Show number of added/removed lines per commit on the log pages
341enable-log-linecount=1 345enable-log-linecount=1
342 346
343 347
344# Add a cgit favicon 348# Add a cgit favicon
345favicon=/favicon.ico 349favicon=/favicon.ico
346 350
347 351
348# Use a custom logo 352# Use a custom logo
349logo=/img/mylogo.png 353logo=/img/mylogo.png
350 354
351 355
352# Enable statistics per week, month and quarter 356# Enable statistics per week, month and quarter
353max-stats=quarter 357max-stats=quarter
354 358
355 359
356# Set the title and heading of the repository index page 360# Set the title and heading of the repository index page
357root-title=foobar.com git repositories 361root-title=foobar.com git repositories
358 362
359 363
360# Set a subheading for the repository index page 364# Set a subheading for the repository index page
361root-desc=tracking the foobar development 365root-desc=tracking the foobar development
362 366
363 367
364# Include some more info about foobar.com on the index page 368# Include some more info about foobar.com on the index page
365root-readme=/var/www/htdocs/about.html 369root-readme=/var/www/htdocs/about.html
366 370
367 371
368# Allow download of tar.gz, tar.bz2 and zip-files 372# Allow download of tar.gz, tar.bz2 and zip-files
369snapshots=tar.gz tar.bz2 zip 373snapshots=tar.gz tar.bz2 zip
370 374
371 375
372## 376##
373## List of common mimetypes 377## List of common mimetypes
374## 378##
375 379
376mimetype.git=image/git 380mimetype.git=image/git
377mimetype.html=text/html 381mimetype.html=text/html
378mimetype.jpg=image/jpeg 382mimetype.jpg=image/jpeg
379mimetype.jpeg=image/jpeg 383mimetype.jpeg=image/jpeg
380mimetype.pdf=application/pdf 384mimetype.pdf=application/pdf
381mimetype.png=image/png 385mimetype.png=image/png
382mimetype.svg=image/svg+xml 386mimetype.svg=image/svg+xml
383 387
384 388
385## 389##
386## List of repositories. 390## List of repositories.
387## PS: Any repositories listed when repo.group is unset will not be 391## PS: Any repositories listed when repo.group is unset will not be
388## displayed under a group heading 392## displayed under a group heading
389## PPS: This list could be kept in a different file (e.g. '/etc/cgitrepos') 393## PPS: This list could be kept in a different file (e.g. '/etc/cgitrepos')
390## and included like this: 394## and included like this:
391## include=/etc/cgitrepos 395## include=/etc/cgitrepos
392## 396##
393 397
394 398
395repo.url=foo 399repo.url=foo
396repo.path=/pub/git/foo.git 400repo.path=/pub/git/foo.git
397repo.desc=the master foo repository 401repo.desc=the master foo repository
398repo.owner=fooman@foobar.com 402repo.owner=fooman@foobar.com
399repo.readme=info/web/about.html 403repo.readme=info/web/about.html
400 404
401 405
402repo.url=bar 406repo.url=bar
403repo.path=/pub/git/bar.git 407repo.path=/pub/git/bar.git
404repo.desc=the bars for your foo 408repo.desc=the bars for your foo
405repo.owner=barman@foobar.com 409repo.owner=barman@foobar.com
406repo.readme=info/web/about.html 410repo.readme=info/web/about.html
407 411
408 412
409# The next repositories will be displayed under the 'extras' heading 413# The next repositories will be displayed under the 'extras' heading
410repo.group=extras 414repo.group=extras
411 415
412 416
413repo.url=baz 417repo.url=baz
414repo.path=/pub/git/baz.git 418repo.path=/pub/git/baz.git
415repo.desc=a set of extensions for bar users 419repo.desc=a set of extensions for bar users
416 420
417repo.url=wiz 421repo.url=wiz
418repo.path=/pub/git/wiz.git 422repo.path=/pub/git/wiz.git
419repo.desc=the wizard of foo 423repo.desc=the wizard of foo
420 424
421 425
422# Add some mirrored repositories 426# Add some mirrored repositories
423repo.group=mirrors 427repo.group=mirrors
424 428
425 429
426repo.url=git 430repo.url=git
427repo.path=/pub/git/git.git 431repo.path=/pub/git/git.git
428repo.desc=the dscm 432repo.desc=the dscm
429 433
430 434
431repo.url=linux 435repo.url=linux
432repo.path=/pub/git/linux.git 436repo.path=/pub/git/linux.git
433repo.desc=the kernel 437repo.desc=the kernel
434 438
435# Disable adhoc downloads of this repo 439# Disable adhoc downloads of this repo
436repo.snapshots=0 440repo.snapshots=0
437 441
438# Disable line-counts for this repo 442# Disable line-counts for this repo
439repo.enable-log-linecount=0 443repo.enable-log-linecount=0
440 444
441# Restrict the max statistics period for this repo 445# Restrict the max statistics period for this repo
442repo.max-stats=month 446repo.max-stats=month
443.... 447....
444 448
445 449
446BUGS 450BUGS
447---- 451----
448Comments currently cannot appear on the same line as a setting; the comment 452Comments currently cannot appear on the same line as a setting; the comment
449will be included as part of the value. E.g. this line: 453will be included as part of the value. E.g. this line:
450 454
451 robots=index # allow indexing 455 robots=index # allow indexing
452 456
453will generate the following html element: 457will generate the following html element:
454 458
455 <meta name='robots' content='index # allow indexing'/> 459 <meta name='robots' content='index # allow indexing'/>
456 460
457 461
458 462
459AUTHOR 463AUTHOR
460------ 464------
461Lars Hjemli <hjemli@gmail.com> 465Lars Hjemli <hjemli@gmail.com>
diff --git a/ui-tree.c b/ui-tree.c
index 7bf2ad2..f64e6e0 100644
--- a/ui-tree.c
+++ b/ui-tree.c
@@ -1,280 +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 25
26 html("<tr><td class='linenumbers'><pre>"); 26 if (ctx.cfg.linenumbers) {
27 idx = 0; 27 html("<tr><td class='linenumbers'><pre>");
28 lineno = 0; 28 idx = 0;
29 29 lineno = 0;
30 if (size) { 30
31 htmlf(numberfmt, ++lineno); 31 if (size) {
32 while(idx < size - 1) { // skip absolute last newline 32 htmlf(numberfmt, ++lineno);
33 if (buf[idx] == '\n') 33 while(idx < size - 1) { // skip absolute last newline
34 htmlf(numberfmt, ++lineno); 34 if (buf[idx] == '\n')
35 idx++; 35 htmlf(numberfmt, ++lineno);
36 idx++;
37 }
36 } 38 }
39 html("</pre></td>\n");
40 }
41 else {
42 html("<tr>\n");
37 } 43 }
38 html("</pre></td>\n");
39 44
40 if (ctx.repo->source_filter) { 45 if (ctx.repo->source_filter) {
41 html("<td class='lines'><pre><code>"); 46 html("<td class='lines'><pre><code>");
42 ctx.repo->source_filter->argv[1] = xstrdup(name); 47 ctx.repo->source_filter->argv[1] = xstrdup(name);
43 cgit_open_filter(ctx.repo->source_filter); 48 cgit_open_filter(ctx.repo->source_filter);
44 write(STDOUT_FILENO, buf, size); 49 write(STDOUT_FILENO, buf, size);
45 cgit_close_filter(ctx.repo->source_filter); 50 cgit_close_filter(ctx.repo->source_filter);
46 html("</code></pre></td></tr></table>\n"); 51 html("</code></pre></td></tr></table>\n");
47 return; 52 return;
48 } 53 }
49 54
50 html("<td class='lines'><pre><code>"); 55 html("<td class='lines'><pre><code>");
51 html_txt(buf); 56 html_txt(buf);
52 html("</code></pre></td></tr></table>\n"); 57 html("</code></pre></td></tr></table>\n");
53} 58}
54 59
55#define ROWLEN 32 60#define ROWLEN 32
56 61
57static void print_binary_buffer(char *buf, unsigned long size) 62static void print_binary_buffer(char *buf, unsigned long size)
58{ 63{
59 unsigned long ofs, idx; 64 unsigned long ofs, idx;
60 static char ascii[ROWLEN + 1]; 65 static char ascii[ROWLEN + 1];
61 66
62 html("<table summary='blob content' class='bin-blob'>\n"); 67 html("<table summary='blob content' class='bin-blob'>\n");
63 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>");
64 for (ofs = 0; ofs < size; ofs += ROWLEN, buf += ROWLEN) { 69 for (ofs = 0; ofs < size; ofs += ROWLEN, buf += ROWLEN) {
65 htmlf("<tr><td class='right'>%04x</td><td class='hex'>", ofs); 70 htmlf("<tr><td class='right'>%04x</td><td class='hex'>", ofs);
66 for (idx = 0; idx < ROWLEN && ofs + idx < size; idx++) 71 for (idx = 0; idx < ROWLEN && ofs + idx < size; idx++)
67 htmlf("%*s%02x", 72 htmlf("%*s%02x",
68 idx == 16 ? 4 : 1, "", 73 idx == 16 ? 4 : 1, "",
69 buf[idx] & 0xff); 74 buf[idx] & 0xff);
70 html(" </td><td class='hex'>"); 75 html(" </td><td class='hex'>");
71 for (idx = 0; idx < ROWLEN && ofs + idx < size; idx++) 76 for (idx = 0; idx < ROWLEN && ofs + idx < size; idx++)
72 ascii[idx] = isgraph(buf[idx]) ? buf[idx] : '.'; 77 ascii[idx] = isgraph(buf[idx]) ? buf[idx] : '.';
73 ascii[idx] = '\0'; 78 ascii[idx] = '\0';
74 html_txt(ascii); 79 html_txt(ascii);
75 html("</td></tr>\n"); 80 html("</td></tr>\n");
76 } 81 }
77 html("</table>\n"); 82 html("</table>\n");
78} 83}
79 84
80static 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)
81{ 86{
82 enum object_type type; 87 enum object_type type;
83 char *buf; 88 char *buf;
84 unsigned long size; 89 unsigned long size;
85 90
86 type = sha1_object_info(sha1, &size); 91 type = sha1_object_info(sha1, &size);
87 if (type == OBJ_BAD) { 92 if (type == OBJ_BAD) {
88 cgit_print_error(fmt("Bad object name: %s", 93 cgit_print_error(fmt("Bad object name: %s",
89 sha1_to_hex(sha1))); 94 sha1_to_hex(sha1)));
90 return; 95 return;
91 } 96 }
92 97
93 buf = read_sha1_file(sha1, &type, &size); 98 buf = read_sha1_file(sha1, &type, &size);
94 if (!buf) { 99 if (!buf) {
95 cgit_print_error(fmt("Error reading object %s", 100 cgit_print_error(fmt("Error reading object %s",
96 sha1_to_hex(sha1))); 101 sha1_to_hex(sha1)));
97 return; 102 return;
98 } 103 }
99 104
100 html(" ("); 105 html(" (");
101 cgit_plain_link("plain", NULL, NULL, ctx.qry.head, 106 cgit_plain_link("plain", NULL, NULL, ctx.qry.head,
102 curr_rev, path); 107 curr_rev, path);
103 htmlf(")<br/>blob: %s\n", sha1_to_hex(sha1)); 108 htmlf(")<br/>blob: %s\n", sha1_to_hex(sha1));
104 109
105 if (buffer_is_binary(buf, size)) 110 if (buffer_is_binary(buf, size))
106 print_binary_buffer(buf, size); 111 print_binary_buffer(buf, size);
107 else 112 else
108 print_text_buffer(basename, buf, size); 113 print_text_buffer(basename, buf, size);
109} 114}
110 115
111 116
112static 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,
113 const char *pathname, unsigned int mode, int stage, 118 const char *pathname, unsigned int mode, int stage,
114 void *cbdata) 119 void *cbdata)
115{ 120{
116 char *name; 121 char *name;
117 char *fullpath; 122 char *fullpath;
118 char *class; 123 char *class;
119 enum object_type type; 124 enum object_type type;
120 unsigned long size = 0; 125 unsigned long size = 0;
121 126
122 name = xstrdup(pathname); 127 name = xstrdup(pathname);
123 fullpath = fmt("%s%s%s", ctx.qry.path ? ctx.qry.path : "", 128 fullpath = fmt("%s%s%s", ctx.qry.path ? ctx.qry.path : "",
124 ctx.qry.path ? "/" : "", name); 129 ctx.qry.path ? "/" : "", name);
125 130
126 if (!S_ISGITLINK(mode)) { 131 if (!S_ISGITLINK(mode)) {
127 type = sha1_object_info(sha1, &size); 132 type = sha1_object_info(sha1, &size);
128 if (type == OBJ_BAD) { 133 if (type == OBJ_BAD) {
129 htmlf("<tr><td colspan='3'>Bad object: %s %s</td></tr>", 134 htmlf("<tr><td colspan='3'>Bad object: %s %s</td></tr>",
130 name, 135 name,
131 sha1_to_hex(sha1)); 136 sha1_to_hex(sha1));
132 return 0; 137 return 0;
133 } 138 }
134 } 139 }
135 140
136 html("<tr><td class='ls-mode'>"); 141 html("<tr><td class='ls-mode'>");
137 cgit_print_filemode(mode); 142 cgit_print_filemode(mode);
138 html("</td><td>"); 143 html("</td><td>");
139 if (S_ISGITLINK(mode)) { 144 if (S_ISGITLINK(mode)) {
140 htmlf("<a class='ls-mod' href='"); 145 htmlf("<a class='ls-mod' href='");
141 html_attr(fmt(ctx.repo->module_link, 146 html_attr(fmt(ctx.repo->module_link,
142 name, 147 name,
143 sha1_to_hex(sha1))); 148 sha1_to_hex(sha1)));
144 html("'>"); 149 html("'>");
145 html_txt(name); 150 html_txt(name);
146 html("</a>"); 151 html("</a>");
147 } else if (S_ISDIR(mode)) { 152 } else if (S_ISDIR(mode)) {
148 cgit_tree_link(name, NULL, "ls-dir", ctx.qry.head, 153 cgit_tree_link(name, NULL, "ls-dir", ctx.qry.head,
149 curr_rev, fullpath); 154 curr_rev, fullpath);
150 } else { 155 } else {
151 class = strrchr(name, '.'); 156 class = strrchr(name, '.');
152 if (class != NULL) { 157 if (class != NULL) {
153 class = fmt("ls-blob %s", class + 1); 158 class = fmt("ls-blob %s", class + 1);
154 } else 159 } else
155 class = "ls-blob"; 160 class = "ls-blob";
156 cgit_tree_link(name, NULL, class, ctx.qry.head, 161 cgit_tree_link(name, NULL, class, ctx.qry.head,
157 curr_rev, fullpath); 162 curr_rev, fullpath);
158 } 163 }
159 htmlf("</td><td class='ls-size'>%li</td>", size); 164 htmlf("</td><td class='ls-size'>%li</td>", size);
160 165
161 html("<td>"); 166 html("<td>");
162 cgit_log_link("log", NULL, "button", ctx.qry.head, curr_rev, 167 cgit_log_link("log", NULL, "button", ctx.qry.head, curr_rev,
163 fullpath, 0, NULL, NULL, ctx.qry.showmsg); 168 fullpath, 0, NULL, NULL, ctx.qry.showmsg);
164 if (ctx.repo->max_stats) 169 if (ctx.repo->max_stats)
165 cgit_stats_link("stats", NULL, "button", ctx.qry.head, 170 cgit_stats_link("stats", NULL, "button", ctx.qry.head,
166 fullpath); 171 fullpath);
167 html("</td></tr>\n"); 172 html("</td></tr>\n");
168 free(name); 173 free(name);
169 return 0; 174 return 0;
170} 175}
171 176
172static void ls_head() 177static void ls_head()
173{ 178{
174 html("<table summary='tree listing' class='list'>\n"); 179 html("<table summary='tree listing' class='list'>\n");
175 html("<tr class='nohover'>"); 180 html("<tr class='nohover'>");
176 html("<th class='left'>Mode</th>"); 181 html("<th class='left'>Mode</th>");
177 html("<th class='left'>Name</th>"); 182 html("<th class='left'>Name</th>");
178 html("<th class='right'>Size</th>"); 183 html("<th class='right'>Size</th>");
179 html("<th/>"); 184 html("<th/>");
180 html("</tr>\n"); 185 html("</tr>\n");
181 header = 1; 186 header = 1;
182} 187}
183 188
184static void ls_tail() 189static void ls_tail()
185{ 190{
186 if (!header) 191 if (!header)
187 return; 192 return;
188 html("</table>\n"); 193 html("</table>\n");
189 header = 0; 194 header = 0;
190} 195}
191 196
192static void ls_tree(const unsigned char *sha1, char *path) 197static void ls_tree(const unsigned char *sha1, char *path)
193{ 198{
194 struct tree *tree; 199 struct tree *tree;
195 200
196 tree = parse_tree_indirect(sha1); 201 tree = parse_tree_indirect(sha1);
197 if (!tree) { 202 if (!tree) {
198 cgit_print_error(fmt("Not a tree object: %s", 203 cgit_print_error(fmt("Not a tree object: %s",
199 sha1_to_hex(sha1))); 204 sha1_to_hex(sha1)));
200 return; 205 return;
201 } 206 }
202 207
203 ls_head(); 208 ls_head();
204 read_tree_recursive(tree, "", 0, 1, NULL, ls_item, NULL); 209 read_tree_recursive(tree, "", 0, 1, NULL, ls_item, NULL);
205 ls_tail(); 210 ls_tail();
206} 211}
207 212
208 213
209static 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,
210 const char *pathname, unsigned mode, int stage, 215 const char *pathname, unsigned mode, int stage,
211 void *cbdata) 216 void *cbdata)
212{ 217{
213 static int state; 218 static int state;
214 static char buffer[PATH_MAX]; 219 static char buffer[PATH_MAX];
215 char *url; 220 char *url;
216 221
217 if (state == 0) { 222 if (state == 0) {
218 memcpy(buffer, base, baselen); 223 memcpy(buffer, base, baselen);
219 strcpy(buffer+baselen, pathname); 224 strcpy(buffer+baselen, pathname);
220 url = cgit_pageurl(ctx.qry.repo, "tree", 225 url = cgit_pageurl(ctx.qry.repo, "tree",
221 fmt("h=%s&amp;path=%s", curr_rev, buffer)); 226 fmt("h=%s&amp;path=%s", curr_rev, buffer));
222 html("/"); 227 html("/");
223 cgit_tree_link(xstrdup(pathname), NULL, NULL, ctx.qry.head, 228 cgit_tree_link(xstrdup(pathname), NULL, NULL, ctx.qry.head,
224 curr_rev, buffer); 229 curr_rev, buffer);
225 230
226 if (strcmp(match_path, buffer)) 231 if (strcmp(match_path, buffer))
227 return READ_TREE_RECURSIVE; 232 return READ_TREE_RECURSIVE;
228 233
229 if (S_ISDIR(mode)) { 234 if (S_ISDIR(mode)) {
230 state = 1; 235 state = 1;
231 ls_head(); 236 ls_head();
232 return READ_TREE_RECURSIVE; 237 return READ_TREE_RECURSIVE;
233 } else { 238 } else {
234 print_object(sha1, buffer, pathname); 239 print_object(sha1, buffer, pathname);
235 return 0; 240 return 0;
236 } 241 }
237 } 242 }
238 ls_item(sha1, base, baselen, pathname, mode, stage, NULL); 243 ls_item(sha1, base, baselen, pathname, mode, stage, NULL);
239 return 0; 244 return 0;
240} 245}
241 246
242 247
243/* 248/*
244 * Show a tree or a blob 249 * Show a tree or a blob
245 * rev: the commit pointing at the root tree object 250 * rev: the commit pointing at the root tree object
246 * path: path to tree or blob 251 * path: path to tree or blob
247 */ 252 */
248void cgit_print_tree(const char *rev, char *path) 253void cgit_print_tree(const char *rev, char *path)
249{ 254{
250 unsigned char sha1[20]; 255 unsigned char sha1[20];
251 struct commit *commit; 256 struct commit *commit;
252 const char *paths[] = {path, NULL}; 257 const char *paths[] = {path, NULL};
253 258
254 if (!rev) 259 if (!rev)
255 rev = ctx.qry.head; 260 rev = ctx.qry.head;
256 261
257 curr_rev = xstrdup(rev); 262 curr_rev = xstrdup(rev);
258 if (get_sha1(rev, sha1)) { 263 if (get_sha1(rev, sha1)) {
259 cgit_print_error(fmt("Invalid revision name: %s", rev)); 264 cgit_print_error(fmt("Invalid revision name: %s", rev));
260 return; 265 return;
261 } 266 }
262 commit = lookup_commit_reference(sha1); 267 commit = lookup_commit_reference(sha1);
263 if (!commit || parse_commit(commit)) { 268 if (!commit || parse_commit(commit)) {
264 cgit_print_error(fmt("Invalid commit reference: %s", rev)); 269 cgit_print_error(fmt("Invalid commit reference: %s", rev));
265 return; 270 return;
266 } 271 }
267 272
268 html("path: <a href='"); 273 html("path: <a href='");
269 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)));
270 html("'>root</a>"); 275 html("'>root</a>");
271 276
272 if (path == NULL) { 277 if (path == NULL) {
273 ls_tree(commit->tree->object.sha1, NULL); 278 ls_tree(commit->tree->object.sha1, NULL);
274 return; 279 return;
275 } 280 }
276 281
277 match_path = path; 282 match_path = path;
278 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);
279 ls_tail(); 284 ls_tail();
280} 285}