summaryrefslogtreecommitdiffabout
Unidiff
Diffstat (more/less context) (ignore whitespace changes)
-rw-r--r--cgit.c13
-rw-r--r--cgit.h3
-rw-r--r--cgitrc.5.txt21
-rw-r--r--ui-atom.c2
-rw-r--r--ui-commit.c12
-rw-r--r--ui-patch.c6
-rw-r--r--ui-plain.c20
-rw-r--r--ui-tag.c2
-rw-r--r--ui-tree.c8
9 files changed, 74 insertions, 13 deletions
diff --git a/cgit.c b/cgit.c
index b3a98c1..dbec196 100644
--- a/cgit.c
+++ b/cgit.c
@@ -1,170 +1,182 @@
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)
21{
22 struct string_list_item *item;
23
24 item = string_list_insert(xstrdup(name), &ctx.cfg.mimetypes);
25 item->util = xstrdup(value);
26}
27
20struct cgit_filter *new_filter(const char *cmd, int extra_args) 28struct cgit_filter *new_filter(const char *cmd, int extra_args)
21{ 29{
22 struct cgit_filter *f; 30 struct cgit_filter *f;
23 31
24 if (!cmd || !cmd[0]) 32 if (!cmd || !cmd[0])
25 return NULL; 33 return NULL;
26 34
27 f = xmalloc(sizeof(struct cgit_filter)); 35 f = xmalloc(sizeof(struct cgit_filter));
28 f->cmd = xstrdup(cmd); 36 f->cmd = xstrdup(cmd);
29 f->argv = xmalloc((2 + extra_args) * sizeof(char *)); 37 f->argv = xmalloc((2 + extra_args) * sizeof(char *));
30 f->argv[0] = f->cmd; 38 f->argv[0] = f->cmd;
31 f->argv[1] = NULL; 39 f->argv[1] = NULL;
32 return f; 40 return f;
33} 41}
34 42
35void config_cb(const char *name, const char *value) 43void config_cb(const char *name, const char *value)
36{ 44{
37 if (!strcmp(name, "root-title")) 45 if (!strcmp(name, "root-title"))
38 ctx.cfg.root_title = xstrdup(value); 46 ctx.cfg.root_title = xstrdup(value);
39 else if (!strcmp(name, "root-desc")) 47 else if (!strcmp(name, "root-desc"))
40 ctx.cfg.root_desc = xstrdup(value); 48 ctx.cfg.root_desc = xstrdup(value);
41 else if (!strcmp(name, "root-readme")) 49 else if (!strcmp(name, "root-readme"))
42 ctx.cfg.root_readme = xstrdup(value); 50 ctx.cfg.root_readme = xstrdup(value);
43 else if (!strcmp(name, "css")) 51 else if (!strcmp(name, "css"))
44 ctx.cfg.css = xstrdup(value); 52 ctx.cfg.css = xstrdup(value);
45 else if (!strcmp(name, "favicon")) 53 else if (!strcmp(name, "favicon"))
46 ctx.cfg.favicon = xstrdup(value); 54 ctx.cfg.favicon = xstrdup(value);
47 else if (!strcmp(name, "footer")) 55 else if (!strcmp(name, "footer"))
48 ctx.cfg.footer = xstrdup(value); 56 ctx.cfg.footer = xstrdup(value);
49 else if (!strcmp(name, "head-include")) 57 else if (!strcmp(name, "head-include"))
50 ctx.cfg.head_include = xstrdup(value); 58 ctx.cfg.head_include = xstrdup(value);
51 else if (!strcmp(name, "header")) 59 else if (!strcmp(name, "header"))
52 ctx.cfg.header = xstrdup(value); 60 ctx.cfg.header = xstrdup(value);
53 else if (!strcmp(name, "logo")) 61 else if (!strcmp(name, "logo"))
54 ctx.cfg.logo = xstrdup(value); 62 ctx.cfg.logo = xstrdup(value);
55 else if (!strcmp(name, "index-header")) 63 else if (!strcmp(name, "index-header"))
56 ctx.cfg.index_header = xstrdup(value); 64 ctx.cfg.index_header = xstrdup(value);
57 else if (!strcmp(name, "index-info")) 65 else if (!strcmp(name, "index-info"))
58 ctx.cfg.index_info = xstrdup(value); 66 ctx.cfg.index_info = xstrdup(value);
59 else if (!strcmp(name, "logo-link")) 67 else if (!strcmp(name, "logo-link"))
60 ctx.cfg.logo_link = xstrdup(value); 68 ctx.cfg.logo_link = xstrdup(value);
61 else if (!strcmp(name, "module-link")) 69 else if (!strcmp(name, "module-link"))
62 ctx.cfg.module_link = xstrdup(value); 70 ctx.cfg.module_link = xstrdup(value);
63 else if (!strcmp(name, "virtual-root")) { 71 else if (!strcmp(name, "virtual-root")) {
64 ctx.cfg.virtual_root = trim_end(value, '/'); 72 ctx.cfg.virtual_root = trim_end(value, '/');
65 if (!ctx.cfg.virtual_root && (!strcmp(value, "/"))) 73 if (!ctx.cfg.virtual_root && (!strcmp(value, "/")))
66 ctx.cfg.virtual_root = ""; 74 ctx.cfg.virtual_root = "";
67 } else if (!strcmp(name, "nocache")) 75 } else if (!strcmp(name, "nocache"))
68 ctx.cfg.nocache = atoi(value); 76 ctx.cfg.nocache = atoi(value);
77 else if (!strcmp(name, "noplainemail"))
78 ctx.cfg.noplainemail = atoi(value);
69 else if (!strcmp(name, "noheader")) 79 else if (!strcmp(name, "noheader"))
70 ctx.cfg.noheader = atoi(value); 80 ctx.cfg.noheader = atoi(value);
71 else if (!strcmp(name, "snapshots")) 81 else if (!strcmp(name, "snapshots"))
72 ctx.cfg.snapshots = cgit_parse_snapshots_mask(value); 82 ctx.cfg.snapshots = cgit_parse_snapshots_mask(value);
73 else if (!strcmp(name, "enable-index-links")) 83 else if (!strcmp(name, "enable-index-links"))
74 ctx.cfg.enable_index_links = atoi(value); 84 ctx.cfg.enable_index_links = atoi(value);
75 else if (!strcmp(name, "enable-log-filecount")) 85 else if (!strcmp(name, "enable-log-filecount"))
76 ctx.cfg.enable_log_filecount = atoi(value); 86 ctx.cfg.enable_log_filecount = atoi(value);
77 else if (!strcmp(name, "enable-log-linecount")) 87 else if (!strcmp(name, "enable-log-linecount"))
78 ctx.cfg.enable_log_linecount = atoi(value); 88 ctx.cfg.enable_log_linecount = atoi(value);
79 else if (!strcmp(name, "max-stats")) 89 else if (!strcmp(name, "max-stats"))
80 ctx.cfg.max_stats = cgit_find_stats_period(value, NULL); 90 ctx.cfg.max_stats = cgit_find_stats_period(value, NULL);
81 else if (!strcmp(name, "cache-size")) 91 else if (!strcmp(name, "cache-size"))
82 ctx.cfg.cache_size = atoi(value); 92 ctx.cfg.cache_size = atoi(value);
83 else if (!strcmp(name, "cache-root")) 93 else if (!strcmp(name, "cache-root"))
84 ctx.cfg.cache_root = xstrdup(value); 94 ctx.cfg.cache_root = xstrdup(value);
85 else if (!strcmp(name, "cache-root-ttl")) 95 else if (!strcmp(name, "cache-root-ttl"))
86 ctx.cfg.cache_root_ttl = atoi(value); 96 ctx.cfg.cache_root_ttl = atoi(value);
87 else if (!strcmp(name, "cache-repo-ttl")) 97 else if (!strcmp(name, "cache-repo-ttl"))
88 ctx.cfg.cache_repo_ttl = atoi(value); 98 ctx.cfg.cache_repo_ttl = atoi(value);
89 else if (!strcmp(name, "cache-static-ttl")) 99 else if (!strcmp(name, "cache-static-ttl"))
90 ctx.cfg.cache_static_ttl = atoi(value); 100 ctx.cfg.cache_static_ttl = atoi(value);
91 else if (!strcmp(name, "cache-dynamic-ttl")) 101 else if (!strcmp(name, "cache-dynamic-ttl"))
92 ctx.cfg.cache_dynamic_ttl = atoi(value); 102 ctx.cfg.cache_dynamic_ttl = atoi(value);
93 else if (!strcmp(name, "commit-filter")) 103 else if (!strcmp(name, "commit-filter"))
94 ctx.cfg.commit_filter = new_filter(value, 0); 104 ctx.cfg.commit_filter = new_filter(value, 0);
95 else if (!strcmp(name, "embedded")) 105 else if (!strcmp(name, "embedded"))
96 ctx.cfg.embedded = atoi(value); 106 ctx.cfg.embedded = atoi(value);
97 else if (!strcmp(name, "max-message-length")) 107 else if (!strcmp(name, "max-message-length"))
98 ctx.cfg.max_msg_len = atoi(value); 108 ctx.cfg.max_msg_len = atoi(value);
99 else if (!strcmp(name, "max-repodesc-length")) 109 else if (!strcmp(name, "max-repodesc-length"))
100 ctx.cfg.max_repodesc_len = atoi(value); 110 ctx.cfg.max_repodesc_len = atoi(value);
101 else if (!strcmp(name, "max-repo-count")) 111 else if (!strcmp(name, "max-repo-count"))
102 ctx.cfg.max_repo_count = atoi(value); 112 ctx.cfg.max_repo_count = atoi(value);
103 else if (!strcmp(name, "max-commit-count")) 113 else if (!strcmp(name, "max-commit-count"))
104 ctx.cfg.max_commit_count = atoi(value); 114 ctx.cfg.max_commit_count = atoi(value);
105 else if (!strcmp(name, "source-filter")) 115 else if (!strcmp(name, "source-filter"))
106 ctx.cfg.source_filter = new_filter(value, 1); 116 ctx.cfg.source_filter = new_filter(value, 1);
107 else if (!strcmp(name, "summary-log")) 117 else if (!strcmp(name, "summary-log"))
108 ctx.cfg.summary_log = atoi(value); 118 ctx.cfg.summary_log = atoi(value);
109 else if (!strcmp(name, "summary-branches")) 119 else if (!strcmp(name, "summary-branches"))
110 ctx.cfg.summary_branches = atoi(value); 120 ctx.cfg.summary_branches = atoi(value);
111 else if (!strcmp(name, "summary-tags")) 121 else if (!strcmp(name, "summary-tags"))
112 ctx.cfg.summary_tags = atoi(value); 122 ctx.cfg.summary_tags = atoi(value);
113 else if (!strcmp(name, "agefile")) 123 else if (!strcmp(name, "agefile"))
114 ctx.cfg.agefile = xstrdup(value); 124 ctx.cfg.agefile = xstrdup(value);
115 else if (!strcmp(name, "renamelimit")) 125 else if (!strcmp(name, "renamelimit"))
116 ctx.cfg.renamelimit = atoi(value); 126 ctx.cfg.renamelimit = atoi(value);
117 else if (!strcmp(name, "robots")) 127 else if (!strcmp(name, "robots"))
118 ctx.cfg.robots = xstrdup(value); 128 ctx.cfg.robots = xstrdup(value);
119 else if (!strcmp(name, "clone-prefix")) 129 else if (!strcmp(name, "clone-prefix"))
120 ctx.cfg.clone_prefix = xstrdup(value); 130 ctx.cfg.clone_prefix = xstrdup(value);
121 else if (!strcmp(name, "local-time")) 131 else if (!strcmp(name, "local-time"))
122 ctx.cfg.local_time = atoi(value); 132 ctx.cfg.local_time = atoi(value);
133 else if (!prefixcmp(name, "mimetype."))
134 add_mimetype(name + 9, value);
123 else if (!strcmp(name, "repo.group")) 135 else if (!strcmp(name, "repo.group"))
124 ctx.cfg.repo_group = xstrdup(value); 136 ctx.cfg.repo_group = xstrdup(value);
125 else if (!strcmp(name, "repo.url")) 137 else if (!strcmp(name, "repo.url"))
126 ctx.repo = cgit_add_repo(value); 138 ctx.repo = cgit_add_repo(value);
127 else if (!strcmp(name, "repo.name")) 139 else if (!strcmp(name, "repo.name"))
128 ctx.repo->name = xstrdup(value); 140 ctx.repo->name = xstrdup(value);
129 else if (ctx.repo && !strcmp(name, "repo.path")) 141 else if (ctx.repo && !strcmp(name, "repo.path"))
130 ctx.repo->path = trim_end(value, '/'); 142 ctx.repo->path = trim_end(value, '/');
131 else if (ctx.repo && !strcmp(name, "repo.clone-url")) 143 else if (ctx.repo && !strcmp(name, "repo.clone-url"))
132 ctx.repo->clone_url = xstrdup(value); 144 ctx.repo->clone_url = xstrdup(value);
133 else if (ctx.repo && !strcmp(name, "repo.desc")) 145 else if (ctx.repo && !strcmp(name, "repo.desc"))
134 ctx.repo->desc = xstrdup(value); 146 ctx.repo->desc = xstrdup(value);
135 else if (ctx.repo && !strcmp(name, "repo.owner")) 147 else if (ctx.repo && !strcmp(name, "repo.owner"))
136 ctx.repo->owner = xstrdup(value); 148 ctx.repo->owner = xstrdup(value);
137 else if (ctx.repo && !strcmp(name, "repo.defbranch")) 149 else if (ctx.repo && !strcmp(name, "repo.defbranch"))
138 ctx.repo->defbranch = xstrdup(value); 150 ctx.repo->defbranch = xstrdup(value);
139 else if (ctx.repo && !strcmp(name, "repo.snapshots")) 151 else if (ctx.repo && !strcmp(name, "repo.snapshots"))
140 ctx.repo->snapshots = ctx.cfg.snapshots & cgit_parse_snapshots_mask(value); /* XXX: &? */ 152 ctx.repo->snapshots = ctx.cfg.snapshots & cgit_parse_snapshots_mask(value); /* XXX: &? */
141 else if (ctx.repo && !strcmp(name, "repo.enable-log-filecount")) 153 else if (ctx.repo && !strcmp(name, "repo.enable-log-filecount"))
142 ctx.repo->enable_log_filecount = ctx.cfg.enable_log_filecount * atoi(value); 154 ctx.repo->enable_log_filecount = ctx.cfg.enable_log_filecount * atoi(value);
143 else if (ctx.repo && !strcmp(name, "repo.enable-log-linecount")) 155 else if (ctx.repo && !strcmp(name, "repo.enable-log-linecount"))
144 ctx.repo->enable_log_linecount = ctx.cfg.enable_log_linecount * atoi(value); 156 ctx.repo->enable_log_linecount = ctx.cfg.enable_log_linecount * atoi(value);
145 else if (ctx.repo && !strcmp(name, "repo.max-stats")) 157 else if (ctx.repo && !strcmp(name, "repo.max-stats"))
146 ctx.repo->max_stats = cgit_find_stats_period(value, NULL); 158 ctx.repo->max_stats = cgit_find_stats_period(value, NULL);
147 else if (ctx.repo && !strcmp(name, "repo.module-link")) 159 else if (ctx.repo && !strcmp(name, "repo.module-link"))
148 ctx.repo->module_link= xstrdup(value); 160 ctx.repo->module_link= xstrdup(value);
149 else if (ctx.repo && !strcmp(name, "repo.commit-filter")) 161 else if (ctx.repo && !strcmp(name, "repo.commit-filter"))
150 ctx.repo->commit_filter = new_filter(value, 0); 162 ctx.repo->commit_filter = new_filter(value, 0);
151 else if (ctx.repo && !strcmp(name, "repo.source-filter")) 163 else if (ctx.repo && !strcmp(name, "repo.source-filter"))
152 ctx.repo->source_filter = new_filter(value, 1); 164 ctx.repo->source_filter = new_filter(value, 1);
153 else if (ctx.repo && !strcmp(name, "repo.readme") && value != NULL) { 165 else if (ctx.repo && !strcmp(name, "repo.readme") && value != NULL) {
154 if (*value == '/') 166 if (*value == '/')
155 ctx.repo->readme = xstrdup(value); 167 ctx.repo->readme = xstrdup(value);
156 else 168 else
157 ctx.repo->readme = xstrdup(fmt("%s/%s", ctx.repo->path, value)); 169 ctx.repo->readme = xstrdup(fmt("%s/%s", ctx.repo->path, value));
158 } else if (!strcmp(name, "include")) 170 } else if (!strcmp(name, "include"))
159 parse_configfile(value, config_cb); 171 parse_configfile(value, config_cb);
160} 172}
161 173
162static void querystring_cb(const char *name, const char *value) 174static void querystring_cb(const char *name, const char *value)
163{ 175{
164 if (!strcmp(name,"r")) { 176 if (!strcmp(name,"r")) {
165 ctx.qry.repo = xstrdup(value); 177 ctx.qry.repo = xstrdup(value);
166 ctx.repo = cgit_get_repoinfo(value); 178 ctx.repo = cgit_get_repoinfo(value);
167 } else if (!strcmp(name, "p")) { 179 } else if (!strcmp(name, "p")) {
168 ctx.qry.page = xstrdup(value); 180 ctx.qry.page = xstrdup(value);
169 } else if (!strcmp(name, "url")) { 181 } else if (!strcmp(name, "url")) {
170 ctx.qry.url = xstrdup(value); 182 ctx.qry.url = xstrdup(value);
@@ -191,96 +203,97 @@ static void querystring_cb(const char *name, const char *value)
191 } else if (!strcmp(name, "mimetype")) { 203 } else if (!strcmp(name, "mimetype")) {
192 ctx.qry.mimetype = xstrdup(value); 204 ctx.qry.mimetype = xstrdup(value);
193 } else if (!strcmp(name, "s")){ 205 } else if (!strcmp(name, "s")){
194 ctx.qry.sort = xstrdup(value); 206 ctx.qry.sort = xstrdup(value);
195 } else if (!strcmp(name, "showmsg")) { 207 } else if (!strcmp(name, "showmsg")) {
196 ctx.qry.showmsg = atoi(value); 208 ctx.qry.showmsg = atoi(value);
197 } else if (!strcmp(name, "period")) { 209 } else if (!strcmp(name, "period")) {
198 ctx.qry.period = xstrdup(value); 210 ctx.qry.period = xstrdup(value);
199 } 211 }
200} 212}
201 213
202static void prepare_context(struct cgit_context *ctx) 214static void prepare_context(struct cgit_context *ctx)
203{ 215{
204 memset(ctx, 0, sizeof(ctx)); 216 memset(ctx, 0, sizeof(ctx));
205 ctx->cfg.agefile = "info/web/last-modified"; 217 ctx->cfg.agefile = "info/web/last-modified";
206 ctx->cfg.nocache = 0; 218 ctx->cfg.nocache = 0;
207 ctx->cfg.cache_size = 0; 219 ctx->cfg.cache_size = 0;
208 ctx->cfg.cache_dynamic_ttl = 5; 220 ctx->cfg.cache_dynamic_ttl = 5;
209 ctx->cfg.cache_max_create_time = 5; 221 ctx->cfg.cache_max_create_time = 5;
210 ctx->cfg.cache_repo_ttl = 5; 222 ctx->cfg.cache_repo_ttl = 5;
211 ctx->cfg.cache_root = CGIT_CACHE_ROOT; 223 ctx->cfg.cache_root = CGIT_CACHE_ROOT;
212 ctx->cfg.cache_root_ttl = 5; 224 ctx->cfg.cache_root_ttl = 5;
213 ctx->cfg.cache_static_ttl = -1; 225 ctx->cfg.cache_static_ttl = -1;
214 ctx->cfg.css = "/cgit.css"; 226 ctx->cfg.css = "/cgit.css";
215 ctx->cfg.logo = "/git-logo.png"; 227 ctx->cfg.logo = "/git-logo.png";
216 ctx->cfg.local_time = 0; 228 ctx->cfg.local_time = 0;
217 ctx->cfg.max_repo_count = 50; 229 ctx->cfg.max_repo_count = 50;
218 ctx->cfg.max_commit_count = 50; 230 ctx->cfg.max_commit_count = 50;
219 ctx->cfg.max_lock_attempts = 5; 231 ctx->cfg.max_lock_attempts = 5;
220 ctx->cfg.max_msg_len = 80; 232 ctx->cfg.max_msg_len = 80;
221 ctx->cfg.max_repodesc_len = 80; 233 ctx->cfg.max_repodesc_len = 80;
222 ctx->cfg.max_stats = 0; 234 ctx->cfg.max_stats = 0;
223 ctx->cfg.module_link = "./?repo=%s&page=commit&id=%s"; 235 ctx->cfg.module_link = "./?repo=%s&page=commit&id=%s";
224 ctx->cfg.renamelimit = -1; 236 ctx->cfg.renamelimit = -1;
225 ctx->cfg.robots = "index, nofollow"; 237 ctx->cfg.robots = "index, nofollow";
226 ctx->cfg.root_title = "Git repository browser"; 238 ctx->cfg.root_title = "Git repository browser";
227 ctx->cfg.root_desc = "a fast webinterface for the git dscm"; 239 ctx->cfg.root_desc = "a fast webinterface for the git dscm";
228 ctx->cfg.script_name = CGIT_SCRIPT_NAME; 240 ctx->cfg.script_name = CGIT_SCRIPT_NAME;
229 ctx->cfg.summary_branches = 10; 241 ctx->cfg.summary_branches = 10;
230 ctx->cfg.summary_log = 10; 242 ctx->cfg.summary_log = 10;
231 ctx->cfg.summary_tags = 10; 243 ctx->cfg.summary_tags = 10;
232 ctx->page.mimetype = "text/html"; 244 ctx->page.mimetype = "text/html";
233 ctx->page.charset = PAGE_ENCODING; 245 ctx->page.charset = PAGE_ENCODING;
234 ctx->page.filename = NULL; 246 ctx->page.filename = NULL;
235 ctx->page.size = 0; 247 ctx->page.size = 0;
236 ctx->page.modified = time(NULL); 248 ctx->page.modified = time(NULL);
237 ctx->page.expires = ctx->page.modified; 249 ctx->page.expires = ctx->page.modified;
238 ctx->page.etag = NULL; 250 ctx->page.etag = NULL;
251 memset(&ctx->cfg.mimetypes, 0, sizeof(struct string_list));
239} 252}
240 253
241struct refmatch { 254struct refmatch {
242 char *req_ref; 255 char *req_ref;
243 char *first_ref; 256 char *first_ref;
244 int match; 257 int match;
245}; 258};
246 259
247int find_current_ref(const char *refname, const unsigned char *sha1, 260int find_current_ref(const char *refname, const unsigned char *sha1,
248 int flags, void *cb_data) 261 int flags, void *cb_data)
249{ 262{
250 struct refmatch *info; 263 struct refmatch *info;
251 264
252 info = (struct refmatch *)cb_data; 265 info = (struct refmatch *)cb_data;
253 if (!strcmp(refname, info->req_ref)) 266 if (!strcmp(refname, info->req_ref))
254 info->match = 1; 267 info->match = 1;
255 if (!info->first_ref) 268 if (!info->first_ref)
256 info->first_ref = xstrdup(refname); 269 info->first_ref = xstrdup(refname);
257 return info->match; 270 return info->match;
258} 271}
259 272
260char *find_default_branch(struct cgit_repo *repo) 273char *find_default_branch(struct cgit_repo *repo)
261{ 274{
262 struct refmatch info; 275 struct refmatch info;
263 char *ref; 276 char *ref;
264 277
265 info.req_ref = repo->defbranch; 278 info.req_ref = repo->defbranch;
266 info.first_ref = NULL; 279 info.first_ref = NULL;
267 info.match = 0; 280 info.match = 0;
268 for_each_branch_ref(find_current_ref, &info); 281 for_each_branch_ref(find_current_ref, &info);
269 if (info.match) 282 if (info.match)
270 ref = info.req_ref; 283 ref = info.req_ref;
271 else 284 else
272 ref = info.first_ref; 285 ref = info.first_ref;
273 if (ref) 286 if (ref)
274 ref = xstrdup(ref); 287 ref = xstrdup(ref);
275 return ref; 288 return ref;
276} 289}
277 290
278static int prepare_repo_cmd(struct cgit_context *ctx) 291static int prepare_repo_cmd(struct cgit_context *ctx)
279{ 292{
280 char *tmp; 293 char *tmp;
281 unsigned char sha1[20]; 294 unsigned char sha1[20];
282 int nongit = 0; 295 int nongit = 0;
283 296
284 setenv("GIT_DIR", ctx->repo->path, 1); 297 setenv("GIT_DIR", ctx->repo->path, 1);
285 setup_git_directory_gently(&nongit); 298 setup_git_directory_gently(&nongit);
286 if (nongit) { 299 if (nongit) {
diff --git a/cgit.h b/cgit.h
index f10ba05..b8557ac 100644
--- a/cgit.h
+++ b/cgit.h
@@ -1,65 +1,66 @@
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 <xdiff-interface.h> 19#include <xdiff-interface.h>
19#include <xdiff/xdiff.h> 20#include <xdiff/xdiff.h>
20#include <utf8.h> 21#include <utf8.h>
21 22
22 23
23/* 24/*
24 * Dateformats used on misc. pages 25 * Dateformats used on misc. pages
25 */ 26 */
26#define FMT_LONGDATE "%Y-%m-%d %H:%M:%S (%Z)" 27#define FMT_LONGDATE "%Y-%m-%d %H:%M:%S (%Z)"
27#define FMT_SHORTDATE "%Y-%m-%d" 28#define FMT_SHORTDATE "%Y-%m-%d"
28#define FMT_ATOMDATE "%Y-%m-%dT%H:%M:%SZ" 29#define FMT_ATOMDATE "%Y-%m-%dT%H:%M:%SZ"
29 30
30 31
31/* 32/*
32 * Limits used for relative dates 33 * Limits used for relative dates
33 */ 34 */
34#define TM_MIN 60 35#define TM_MIN 60
35#define TM_HOUR (TM_MIN * 60) 36#define TM_HOUR (TM_MIN * 60)
36#define TM_DAY (TM_HOUR * 24) 37#define TM_DAY (TM_HOUR * 24)
37#define TM_WEEK (TM_DAY * 7) 38#define TM_WEEK (TM_DAY * 7)
38#define TM_YEAR (TM_DAY * 365) 39#define TM_YEAR (TM_DAY * 365)
39#define TM_MONTH (TM_YEAR / 12.0) 40#define TM_MONTH (TM_YEAR / 12.0)
40 41
41 42
42/* 43/*
43 * Default encoding 44 * Default encoding
44 */ 45 */
45#define PAGE_ENCODING "UTF-8" 46#define PAGE_ENCODING "UTF-8"
46 47
47typedef void (*configfn)(const char *name, const char *value); 48typedef void (*configfn)(const char *name, const char *value);
48typedef void (*filepair_fn)(struct diff_filepair *pair); 49typedef void (*filepair_fn)(struct diff_filepair *pair);
49typedef void (*linediff_fn)(char *line, int len); 50typedef void (*linediff_fn)(char *line, int len);
50 51
51struct cgit_filter { 52struct cgit_filter {
52 char *cmd; 53 char *cmd;
53 char **argv; 54 char **argv;
54 int old_stdout; 55 int old_stdout;
55 int pipe_fh[2]; 56 int pipe_fh[2];
56 int pid; 57 int pid;
57 int exitstatus; 58 int exitstatus;
58}; 59};
59 60
60struct cgit_repo { 61struct cgit_repo {
61 char *url; 62 char *url;
62 char *name; 63 char *name;
63 char *path; 64 char *path;
64 char *desc; 65 char *desc;
65 char *owner; 66 char *owner;
@@ -134,102 +135,104 @@ struct cgit_query {
134 char *mimetype; 135 char *mimetype;
135 char *url; 136 char *url;
136 char *period; 137 char *period;
137 int ofs; 138 int ofs;
138 int nohead; 139 int nohead;
139 char *sort; 140 char *sort;
140 int showmsg; 141 int showmsg;
141}; 142};
142 143
143struct cgit_config { 144struct cgit_config {
144 char *agefile; 145 char *agefile;
145 char *cache_root; 146 char *cache_root;
146 char *clone_prefix; 147 char *clone_prefix;
147 char *css; 148 char *css;
148 char *favicon; 149 char *favicon;
149 char *footer; 150 char *footer;
150 char *head_include; 151 char *head_include;
151 char *header; 152 char *header;
152 char *index_header; 153 char *index_header;
153 char *index_info; 154 char *index_info;
154 char *logo; 155 char *logo;
155 char *logo_link; 156 char *logo_link;
156 char *module_link; 157 char *module_link;
157 char *repo_group; 158 char *repo_group;
158 char *robots; 159 char *robots;
159 char *root_title; 160 char *root_title;
160 char *root_desc; 161 char *root_desc;
161 char *root_readme; 162 char *root_readme;
162 char *script_name; 163 char *script_name;
163 char *virtual_root; 164 char *virtual_root;
164 int cache_size; 165 int cache_size;
165 int cache_dynamic_ttl; 166 int cache_dynamic_ttl;
166 int cache_max_create_time; 167 int cache_max_create_time;
167 int cache_repo_ttl; 168 int cache_repo_ttl;
168 int cache_root_ttl; 169 int cache_root_ttl;
169 int cache_static_ttl; 170 int cache_static_ttl;
170 int embedded; 171 int embedded;
171 int enable_index_links; 172 int enable_index_links;
172 int enable_log_filecount; 173 int enable_log_filecount;
173 int enable_log_linecount; 174 int enable_log_linecount;
174 int local_time; 175 int local_time;
175 int max_repo_count; 176 int max_repo_count;
176 int max_commit_count; 177 int max_commit_count;
177 int max_lock_attempts; 178 int max_lock_attempts;
178 int max_msg_len; 179 int max_msg_len;
179 int max_repodesc_len; 180 int max_repodesc_len;
180 int max_stats; 181 int max_stats;
181 int nocache; 182 int nocache;
183 int noplainemail;
182 int noheader; 184 int noheader;
183 int renamelimit; 185 int renamelimit;
184 int snapshots; 186 int snapshots;
185 int summary_branches; 187 int summary_branches;
186 int summary_log; 188 int summary_log;
187 int summary_tags; 189 int summary_tags;
190 struct string_list mimetypes;
188 struct cgit_filter *commit_filter; 191 struct cgit_filter *commit_filter;
189 struct cgit_filter *source_filter; 192 struct cgit_filter *source_filter;
190}; 193};
191 194
192struct cgit_page { 195struct cgit_page {
193 time_t modified; 196 time_t modified;
194 time_t expires; 197 time_t expires;
195 size_t size; 198 size_t size;
196 char *mimetype; 199 char *mimetype;
197 char *charset; 200 char *charset;
198 char *filename; 201 char *filename;
199 char *etag; 202 char *etag;
200 char *title; 203 char *title;
201 int status; 204 int status;
202 char *statusmsg; 205 char *statusmsg;
203}; 206};
204 207
205struct cgit_context { 208struct cgit_context {
206 struct cgit_query qry; 209 struct cgit_query qry;
207 struct cgit_config cfg; 210 struct cgit_config cfg;
208 struct cgit_repo *repo; 211 struct cgit_repo *repo;
209 struct cgit_page page; 212 struct cgit_page page;
210}; 213};
211 214
212struct cgit_snapshot_format { 215struct cgit_snapshot_format {
213 const char *suffix; 216 const char *suffix;
214 const char *mimetype; 217 const char *mimetype;
215 write_archive_fn_t write_func; 218 write_archive_fn_t write_func;
216 int bit; 219 int bit;
217}; 220};
218 221
219extern const char *cgit_version; 222extern const char *cgit_version;
220 223
221extern struct cgit_repolist cgit_repolist; 224extern struct cgit_repolist cgit_repolist;
222extern struct cgit_context ctx; 225extern struct cgit_context ctx;
223extern const struct cgit_snapshot_format cgit_snapshot_formats[]; 226extern const struct cgit_snapshot_format cgit_snapshot_formats[];
224 227
225extern struct cgit_repo *cgit_add_repo(const char *url); 228extern struct cgit_repo *cgit_add_repo(const char *url);
226extern struct cgit_repo *cgit_get_repoinfo(const char *url); 229extern struct cgit_repo *cgit_get_repoinfo(const char *url);
227extern void cgit_repo_config_cb(const char *name, const char *value); 230extern void cgit_repo_config_cb(const char *name, const char *value);
228 231
229extern int chk_zero(int result, char *msg); 232extern int chk_zero(int result, char *msg);
230extern int chk_positive(int result, char *msg); 233extern int chk_positive(int result, char *msg);
231extern int chk_non_negative(int result, char *msg); 234extern int chk_non_negative(int result, char *msg);
232 235
233extern char *trim_end(const char *str, char c); 236extern char *trim_end(const char *str, char c);
234extern char *strlpart(char *txt, int maxlen); 237extern char *strlpart(char *txt, int maxlen);
235extern char *strrpart(char *txt, int maxlen); 238extern char *strrpart(char *txt, int maxlen);
diff --git a/cgitrc.5.txt b/cgitrc.5.txt
index ffb3e0f..dc63637 100644
--- a/cgitrc.5.txt
+++ b/cgitrc.5.txt
@@ -108,107 +108,115 @@ include::
108 file is parsed. Default value: none. 108 file is parsed. Default value: none.
109 109
110index-header:: 110index-header::
111 The content of the file specified with this option will be included 111 The content of the file specified with this option will be included
112 verbatim above the repository index. This setting is deprecated, and 112 verbatim above the repository index. This setting is deprecated, and
113 will not be supported by cgit-1.0 (use root-readme instead). Default 113 will not be supported by cgit-1.0 (use root-readme instead). Default
114 value: none. 114 value: none.
115 115
116index-info:: 116index-info::
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 below the heading on the repository index page. This setting 118 verbatim below the heading on the repository index page. This setting
119 is deprecated, and will not be supported by cgit-1.0 (use root-desc 119 is deprecated, and will not be supported by cgit-1.0 (use root-desc
120 instead). Default value: none. 120 instead). Default value: none.
121 121
122local-time:: 122local-time::
123 Flag which, if set to "1", makes cgit print commit and tag times in the 123 Flag which, if set to "1", makes cgit print commit and tag times in the
124 servers timezone. Default value: "0". 124 servers timezone. Default value: "0".
125 125
126logo:: 126logo::
127 Url which specifies the source of an image which will be used as a logo 127 Url which specifies the source of an image which will be used as a logo
128 on all cgit pages. 128 on all cgit pages.
129 129
130logo-link:: 130logo-link::
131 Url loaded when clicking on the cgit logo image. If unspecified the 131 Url loaded when clicking on the cgit logo image. If unspecified the
132 calculated url of the repository index page will be used. Default 132 calculated url of the repository index page will be used. Default
133 value: none. 133 value: none.
134 134
135max-commit-count:: 135max-commit-count::
136 Specifies the number of entries to list per page in "log" view. Default 136 Specifies the number of entries to list per page in "log" view. Default
137 value: "50". 137 value: "50".
138 138
139max-message-length:: 139max-message-length::
140 Specifies the maximum number of commit message characters to display in 140 Specifies the maximum number of commit message characters to display in
141 "log" view. Default value: "80". 141 "log" view. Default value: "80".
142 142
143max-repo-count:: 143max-repo-count::
144 Specifies the number of entries to list per page on therepository 144 Specifies the number of entries to list per page on therepository
145 index page. Default value: "50". 145 index page. Default value: "50".
146 146
147max-repodesc-length:: 147max-repodesc-length::
148 Specifies the maximum number of repo description characters to display 148 Specifies the maximum number of repo description characters to display
149 on the repository index page. Default value: "80". 149 on the repository index page. Default value: "80".
150 150
151max-stats:: 151max-stats::
152 Set the default maximum statistics period. Valid values are "week", 152 Set the default maximum statistics period. Valid values are "week",
153 "month", "quarter" and "year". If unspecified, statistics are 153 "month", "quarter" and "year". If unspecified, statistics are
154 disabled. Default value: none. See also: "repo.max-stats". 154 disabled. Default value: none. See also: "repo.max-stats".
155 155
156mimetype.<ext>::
157 Set the mimetype for the specified filename extension. This is used
158 by the `plain` command when returning blob content.
159
156module-link:: 160module-link::
157 Text which will be used as the formatstring for a hyperlink when a 161 Text which will be used as the formatstring for a hyperlink when a
158 submodule is printed in a directory listing. The arguments for the 162 submodule is printed in a directory listing. The arguments for the
159 formatstring are the path and SHA1 of the submodule commit. Default 163 formatstring are the path and SHA1 of the submodule commit. Default
160 value: "./?repo=%s&page=commit&id=%s" 164 value: "./?repo=%s&page=commit&id=%s"
161 165
162nocache:: 166nocache::
163 If set to the value "1" caching will be disabled. This settings is 167 If set to the value "1" caching will be disabled. This settings is
164 deprecated, and will not be honored starting with cgit-1.0. Default 168 deprecated, and will not be honored starting with cgit-1.0. Default
165 value: "0". 169 value: "0".
166 170
171noplainemail::
172 If set to "1" showing full author email adresses will be disabled.
173 Default value: "0".
174
167noheader:: 175noheader::
168 Flag which, when set to "1", will make cgit omit the standard header 176 Flag which, when set to "1", will make cgit omit the standard header
169 on all pages. Default value: none. See also: "embedded". 177 on all pages. Default value: none. See also: "embedded".
170 178
171renamelimit:: 179renamelimit::
172 Maximum number of files to consider when detecting renames. The value 180 Maximum number of files to consider when detecting renames. The value
173 "-1" uses the compiletime value in git (for further info, look at 181 "-1" uses the compiletime value in git (for further info, look at
174 `man git-diff`). Default value: "-1". 182 `man git-diff`). Default value: "-1".
175 183
176repo.group:: 184repo.group::
177 A value for the current repository group, which all repositories 185 A value for the current repository group, which all repositories
178 specified after this setting will inherit. Default value: none. 186 specified after this setting will inherit. Default value: none.
179 187
180robots:: 188robots::
181 Text used as content for the "robots" meta-tag. Default value: 189 Text used as content for the "robots" meta-tag. Default value:
182 "index, nofollow". 190 "index, nofollow".
183 191
184root-desc:: 192root-desc::
185 Text printed below the heading on the repository index page. Default 193 Text printed below the heading on the repository index page. Default
186 value: "a fast webinterface for the git dscm". 194 value: "a fast webinterface for the git dscm".
187 195
188root-readme:: 196root-readme::
189 The content of the file specified with this option will be included 197 The content of the file specified with this option will be included
190 verbatim below the "about" link on the repository index page. Default 198 verbatim below the "about" link on the repository index page. Default
191 value: none. 199 value: none.
192 200
193root-title:: 201root-title::
194 Text printed as heading on the repository index page. Default value: 202 Text printed as heading on the repository index page. Default value:
195 "Git Repository Browser". 203 "Git Repository Browser".
196 204
197snapshots:: 205snapshots::
198 Text which specifies the default (and allowed) set of snapshot formats 206 Text which specifies the default (and allowed) set of snapshot formats
199 supported by cgit. The value is a space-separated list of zero or more 207 supported by cgit. The value is a space-separated list of zero or more
200 of the following values: 208 of the following values:
201 "tar" uncompressed tar-file 209 "tar" uncompressed tar-file
202 "tar.gz"gzip-compressed tar-file 210 "tar.gz"gzip-compressed tar-file
203 "tar.bz2"bzip-compressed tar-file 211 "tar.bz2"bzip-compressed tar-file
204 "zip" zip-file 212 "zip" zip-file
205 Default value: none. 213 Default value: none.
206 214
207source-filter:: 215source-filter::
208 Specifies a command which will be invoked to format plaintext blobs 216 Specifies a command which will be invoked to format plaintext blobs
209 in the tree view. The command will get the blob content on its STDIN 217 in the tree view. The command will get the blob content on its STDIN
210 and the name of the blob as its only command line argument. The STDOUT 218 and the name of the blob as its only command line argument. The STDOUT
211 from the command will be included verbatim as the blob contents, i.e. 219 from the command will be included verbatim as the blob contents, i.e.
212 this can be used to implement e.g. syntax highlighting. Default value: 220 this can be used to implement e.g. syntax highlighting. Default value:
213 none. 221 none.
214 222
@@ -300,96 +308,109 @@ cache-size=1000
300# Specify some default clone prefixes 308# Specify some default clone prefixes
301clone-prefix=git://foobar.com ssh://foobar.com/pub/git http://foobar.com/git 309clone-prefix=git://foobar.com ssh://foobar.com/pub/git http://foobar.com/git
302 310
303# Specify the css url 311# Specify the css url
304css=/css/cgit.css 312css=/css/cgit.css
305 313
306 314
307# Show extra links for each repository on the index page 315# Show extra links for each repository on the index page
308enable-index-links=1 316enable-index-links=1
309 317
310 318
311# Show number of affected files per commit on the log pages 319# Show number of affected files per commit on the log pages
312enable-log-filecount=1 320enable-log-filecount=1
313 321
314 322
315# Show number of added/removed lines per commit on the log pages 323# Show number of added/removed lines per commit on the log pages
316enable-log-linecount=1 324enable-log-linecount=1
317 325
318 326
319# Add a cgit favicon 327# Add a cgit favicon
320favicon=/favicon.ico 328favicon=/favicon.ico
321 329
322 330
323# Use a custom logo 331# Use a custom logo
324logo=/img/mylogo.png 332logo=/img/mylogo.png
325 333
326 334
327# Enable statistics per week, month and quarter 335# Enable statistics per week, month and quarter
328max-stats=quarter 336max-stats=quarter
329 337
330 338
331# Set the title and heading of the repository index page 339# Set the title and heading of the repository index page
332root-title=foobar.com git repositories 340root-title=foobar.com git repositories
333 341
334 342
335# Set a subheading for the repository index page 343# Set a subheading for the repository index page
336root-desc=tracking the foobar development 344root-desc=tracking the foobar development
337 345
338 346
339# Include some more info about foobar.com on the index page 347# Include some more info about foobar.com on the index page
340root-readme=/var/www/htdocs/about.html 348root-readme=/var/www/htdocs/about.html
341 349
342 350
343# Allow download of tar.gz, tar.bz2 and zip-files 351# Allow download of tar.gz, tar.bz2 and zip-files
344snapshots=tar.gz tar.bz2 zip 352snapshots=tar.gz tar.bz2 zip
345 353
346 354
347## 355##
356## List of common mimetypes
357##
358
359mimetype.git=image/git
360mimetype.html=text/html
361mimetype.jpg=image/jpeg
362mimetype.jpeg=image/jpeg
363mimetype.pdf=application/pdf
364mimetype.png=image/png
365mimetype.svg=image/svg+xml
366
367
368##
348## List of repositories. 369## List of repositories.
349## PS: Any repositories listed when repo.group is unset will not be 370## PS: Any repositories listed when repo.group is unset will not be
350## displayed under a group heading 371## displayed under a group heading
351## PPS: This list could be kept in a different file (e.g. '/etc/cgitrepos') 372## PPS: This list could be kept in a different file (e.g. '/etc/cgitrepos')
352## and included like this: 373## and included like this:
353## include=/etc/cgitrepos 374## include=/etc/cgitrepos
354## 375##
355 376
356 377
357repo.url=foo 378repo.url=foo
358repo.path=/pub/git/foo.git 379repo.path=/pub/git/foo.git
359repo.desc=the master foo repository 380repo.desc=the master foo repository
360repo.owner=fooman@foobar.com 381repo.owner=fooman@foobar.com
361repo.readme=info/web/about.html 382repo.readme=info/web/about.html
362 383
363 384
364repo.url=bar 385repo.url=bar
365repo.path=/pub/git/bar.git 386repo.path=/pub/git/bar.git
366repo.desc=the bars for your foo 387repo.desc=the bars for your foo
367repo.owner=barman@foobar.com 388repo.owner=barman@foobar.com
368repo.readme=info/web/about.html 389repo.readme=info/web/about.html
369 390
370 391
371# The next repositories will be displayed under the 'extras' heading 392# The next repositories will be displayed under the 'extras' heading
372repo.group=extras 393repo.group=extras
373 394
374 395
375repo.url=baz 396repo.url=baz
376repo.path=/pub/git/baz.git 397repo.path=/pub/git/baz.git
377repo.desc=a set of extensions for bar users 398repo.desc=a set of extensions for bar users
378 399
379repo.url=wiz 400repo.url=wiz
380repo.path=/pub/git/wiz.git 401repo.path=/pub/git/wiz.git
381repo.desc=the wizard of foo 402repo.desc=the wizard of foo
382 403
383 404
384# Add some mirrored repositories 405# Add some mirrored repositories
385repo.group=mirrors 406repo.group=mirrors
386 407
387 408
388repo.url=git 409repo.url=git
389repo.path=/pub/git/git.git 410repo.path=/pub/git/git.git
390repo.desc=the dscm 411repo.desc=the dscm
391 412
392 413
393repo.url=linux 414repo.url=linux
394repo.path=/pub/git/linux.git 415repo.path=/pub/git/linux.git
395repo.desc=the kernel 416repo.desc=the kernel
diff --git a/ui-atom.c b/ui-atom.c
index e5c31d9..808b2d0 100644
--- a/ui-atom.c
+++ b/ui-atom.c
@@ -1,83 +1,83 @@
1/* ui-atom.c: functions for atom feeds 1/* ui-atom.c: functions for atom feeds
2 * 2 *
3 * Copyright (C) 2008 Lars Hjemli 3 * Copyright (C) 2008 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 "html.h" 10#include "html.h"
11#include "ui-shared.h" 11#include "ui-shared.h"
12 12
13void add_entry(struct commit *commit, char *host) 13void add_entry(struct commit *commit, char *host)
14{ 14{
15 char delim = '&'; 15 char delim = '&';
16 char *hex; 16 char *hex;
17 char *mail, *t, *t2; 17 char *mail, *t, *t2;
18 struct commitinfo *info; 18 struct commitinfo *info;
19 19
20 info = cgit_parse_commit(commit); 20 info = cgit_parse_commit(commit);
21 hex = sha1_to_hex(commit->object.sha1); 21 hex = sha1_to_hex(commit->object.sha1);
22 html("<entry>\n"); 22 html("<entry>\n");
23 html("<title>"); 23 html("<title>");
24 html_txt(info->subject); 24 html_txt(info->subject);
25 html("</title>\n"); 25 html("</title>\n");
26 html("<updated>"); 26 html("<updated>");
27 cgit_print_date(info->author_date, FMT_ATOMDATE, ctx.cfg.local_time); 27 cgit_print_date(info->author_date, FMT_ATOMDATE, ctx.cfg.local_time);
28 html("</updated>\n"); 28 html("</updated>\n");
29 html("<author>\n"); 29 html("<author>\n");
30 if (info->author) { 30 if (info->author) {
31 html("<name>"); 31 html("<name>");
32 html_txt(info->author); 32 html_txt(info->author);
33 html("</name>\n"); 33 html("</name>\n");
34 } 34 }
35 if (info->author_email) { 35 if (info->author_email && !ctx.cfg.noplainemail) {
36 mail = xstrdup(info->author_email); 36 mail = xstrdup(info->author_email);
37 t = strchr(mail, '<'); 37 t = strchr(mail, '<');
38 if (t) 38 if (t)
39 t++; 39 t++;
40 else 40 else
41 t = mail; 41 t = mail;
42 t2 = strchr(t, '>'); 42 t2 = strchr(t, '>');
43 if (t2) 43 if (t2)
44 *t2 = '\0'; 44 *t2 = '\0';
45 html("<email>"); 45 html("<email>");
46 html_txt(t); 46 html_txt(t);
47 html("</email>\n"); 47 html("</email>\n");
48 free(mail); 48 free(mail);
49 } 49 }
50 html("</author>\n"); 50 html("</author>\n");
51 html("<published>"); 51 html("<published>");
52 cgit_print_date(info->author_date, FMT_ATOMDATE, ctx.cfg.local_time); 52 cgit_print_date(info->author_date, FMT_ATOMDATE, ctx.cfg.local_time);
53 html("</published>\n"); 53 html("</published>\n");
54 if (host) { 54 if (host) {
55 html("<link rel='alternate' type='text/html' href='"); 55 html("<link rel='alternate' type='text/html' href='");
56 html(cgit_httpscheme()); 56 html(cgit_httpscheme());
57 html_attr(host); 57 html_attr(host);
58 html_attr(cgit_pageurl(ctx.repo->url, "commit", NULL)); 58 html_attr(cgit_pageurl(ctx.repo->url, "commit", NULL));
59 if (ctx.cfg.virtual_root) 59 if (ctx.cfg.virtual_root)
60 delim = '?'; 60 delim = '?';
61 htmlf("%cid=%s", delim, hex); 61 htmlf("%cid=%s", delim, hex);
62 html("'/>\n"); 62 html("'/>\n");
63 } 63 }
64 htmlf("<id>%s</id>\n", hex); 64 htmlf("<id>%s</id>\n", hex);
65 html("<content type='text'>\n"); 65 html("<content type='text'>\n");
66 html_txt(info->msg); 66 html_txt(info->msg);
67 html("</content>\n"); 67 html("</content>\n");
68 html("<content type='xhtml'>\n"); 68 html("<content type='xhtml'>\n");
69 html("<div xmlns='http://www.w3.org/1999/xhtml'>\n"); 69 html("<div xmlns='http://www.w3.org/1999/xhtml'>\n");
70 html("<pre>\n"); 70 html("<pre>\n");
71 html_txt(info->msg); 71 html_txt(info->msg);
72 html("</pre>\n"); 72 html("</pre>\n");
73 html("</div>\n"); 73 html("</div>\n");
74 html("</content>\n"); 74 html("</content>\n");
75 html("</entry>\n"); 75 html("</entry>\n");
76 cgit_free_commitinfo(info); 76 cgit_free_commitinfo(info);
77} 77}
78 78
79 79
80void cgit_print_atom(char *tip, char *path, int max_count) 80void cgit_print_atom(char *tip, char *path, int max_count)
81{ 81{
82 char *host; 82 char *host;
83 const char *argv[] = {NULL, tip, NULL, NULL, NULL}; 83 const char *argv[] = {NULL, tip, NULL, NULL, NULL};
diff --git a/ui-commit.c b/ui-commit.c
index 5815b58..d6b73ee 100644
--- a/ui-commit.c
+++ b/ui-commit.c
@@ -1,99 +1,103 @@
1/* ui-commit.c: generate commit view 1/* ui-commit.c: generate commit view
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 "html.h" 10#include "html.h"
11#include "ui-shared.h" 11#include "ui-shared.h"
12#include "ui-diff.h" 12#include "ui-diff.h"
13#include "ui-log.h" 13#include "ui-log.h"
14 14
15void cgit_print_commit(char *hex) 15void cgit_print_commit(char *hex)
16{ 16{
17 struct commit *commit, *parent; 17 struct commit *commit, *parent;
18 struct commitinfo *info; 18 struct commitinfo *info;
19 struct commit_list *p; 19 struct commit_list *p;
20 unsigned char sha1[20]; 20 unsigned char sha1[20];
21 char *tmp; 21 char *tmp;
22 int parents = 0; 22 int parents = 0;
23 23
24 if (!hex) 24 if (!hex)
25 hex = ctx.qry.head; 25 hex = ctx.qry.head;
26 26
27 if (get_sha1(hex, sha1)) { 27 if (get_sha1(hex, sha1)) {
28 cgit_print_error(fmt("Bad object id: %s", hex)); 28 cgit_print_error(fmt("Bad object id: %s", hex));
29 return; 29 return;
30 } 30 }
31 commit = lookup_commit_reference(sha1); 31 commit = lookup_commit_reference(sha1);
32 if (!commit) { 32 if (!commit) {
33 cgit_print_error(fmt("Bad commit reference: %s", hex)); 33 cgit_print_error(fmt("Bad commit reference: %s", hex));
34 return; 34 return;
35 } 35 }
36 info = cgit_parse_commit(commit); 36 info = cgit_parse_commit(commit);
37 37
38 load_ref_decorations(); 38 load_ref_decorations();
39 39
40 html("<table summary='commit info' class='commit-info'>\n"); 40 html("<table summary='commit info' class='commit-info'>\n");
41 html("<tr><th>author</th><td>"); 41 html("<tr><th>author</th><td>");
42 html_txt(info->author); 42 html_txt(info->author);
43 html(" "); 43 if (!ctx.cfg.noplainemail) {
44 html_txt(info->author_email); 44 html(" ");
45 html_txt(info->author_email);
46 }
45 html("</td><td class='right'>"); 47 html("</td><td class='right'>");
46 cgit_print_date(info->author_date, FMT_LONGDATE, ctx.cfg.local_time); 48 cgit_print_date(info->author_date, FMT_LONGDATE, ctx.cfg.local_time);
47 html("</td></tr>\n"); 49 html("</td></tr>\n");
48 html("<tr><th>committer</th><td>"); 50 html("<tr><th>committer</th><td>");
49 html_txt(info->committer); 51 html_txt(info->committer);
50 html(" "); 52 if (!ctx.cfg.noplainemail) {
51 html_txt(info->committer_email); 53 html(" ");
54 html_txt(info->committer_email);
55 }
52 html("</td><td class='right'>"); 56 html("</td><td class='right'>");
53 cgit_print_date(info->committer_date, FMT_LONGDATE, ctx.cfg.local_time); 57 cgit_print_date(info->committer_date, FMT_LONGDATE, ctx.cfg.local_time);
54 html("</td></tr>\n"); 58 html("</td></tr>\n");
55 html("<tr><th>commit</th><td colspan='2' class='sha1'>"); 59 html("<tr><th>commit</th><td colspan='2' class='sha1'>");
56 tmp = sha1_to_hex(commit->object.sha1); 60 tmp = sha1_to_hex(commit->object.sha1);
57 cgit_commit_link(tmp, NULL, NULL, ctx.qry.head, tmp); 61 cgit_commit_link(tmp, NULL, NULL, ctx.qry.head, tmp);
58 html(" ("); 62 html(" (");
59 cgit_patch_link("patch", NULL, NULL, NULL, tmp); 63 cgit_patch_link("patch", NULL, NULL, NULL, tmp);
60 html(")</td></tr>\n"); 64 html(")</td></tr>\n");
61 html("<tr><th>tree</th><td colspan='2' class='sha1'>"); 65 html("<tr><th>tree</th><td colspan='2' class='sha1'>");
62 tmp = xstrdup(hex); 66 tmp = xstrdup(hex);
63 cgit_tree_link(sha1_to_hex(commit->tree->object.sha1), NULL, NULL, 67 cgit_tree_link(sha1_to_hex(commit->tree->object.sha1), NULL, NULL,
64 ctx.qry.head, tmp, NULL); 68 ctx.qry.head, tmp, NULL);
65 html("</td></tr>\n"); 69 html("</td></tr>\n");
66 for (p = commit->parents; p ; p = p->next) { 70 for (p = commit->parents; p ; p = p->next) {
67 parent = lookup_commit_reference(p->item->object.sha1); 71 parent = lookup_commit_reference(p->item->object.sha1);
68 if (!parent) { 72 if (!parent) {
69 html("<tr><td colspan='3'>"); 73 html("<tr><td colspan='3'>");
70 cgit_print_error("Error reading parent commit"); 74 cgit_print_error("Error reading parent commit");
71 html("</td></tr>"); 75 html("</td></tr>");
72 continue; 76 continue;
73 } 77 }
74 html("<tr><th>parent</th>" 78 html("<tr><th>parent</th>"
75 "<td colspan='2' class='sha1'>"); 79 "<td colspan='2' class='sha1'>");
76 cgit_commit_link(sha1_to_hex(p->item->object.sha1), NULL, NULL, 80 cgit_commit_link(sha1_to_hex(p->item->object.sha1), NULL, NULL,
77 ctx.qry.head, sha1_to_hex(p->item->object.sha1)); 81 ctx.qry.head, sha1_to_hex(p->item->object.sha1));
78 html(" ("); 82 html(" (");
79 cgit_diff_link("diff", NULL, NULL, ctx.qry.head, hex, 83 cgit_diff_link("diff", NULL, NULL, ctx.qry.head, hex,
80 sha1_to_hex(p->item->object.sha1), NULL); 84 sha1_to_hex(p->item->object.sha1), NULL);
81 html(")</td></tr>"); 85 html(")</td></tr>");
82 parents++; 86 parents++;
83 } 87 }
84 if (ctx.repo->snapshots) { 88 if (ctx.repo->snapshots) {
85 html("<tr><th>download</th><td colspan='2' class='sha1'>"); 89 html("<tr><th>download</th><td colspan='2' class='sha1'>");
86 cgit_print_snapshot_links(ctx.qry.repo, ctx.qry.head, 90 cgit_print_snapshot_links(ctx.qry.repo, ctx.qry.head,
87 hex, ctx.repo->snapshots); 91 hex, ctx.repo->snapshots);
88 html("</td></tr>"); 92 html("</td></tr>");
89 } 93 }
90 html("</table>\n"); 94 html("</table>\n");
91 html("<div class='commit-subject'>"); 95 html("<div class='commit-subject'>");
92 if (ctx.repo->commit_filter) 96 if (ctx.repo->commit_filter)
93 cgit_open_filter(ctx.repo->commit_filter); 97 cgit_open_filter(ctx.repo->commit_filter);
94 html_txt(info->subject); 98 html_txt(info->subject);
95 if (ctx.repo->commit_filter) 99 if (ctx.repo->commit_filter)
96 cgit_close_filter(ctx.repo->commit_filter); 100 cgit_close_filter(ctx.repo->commit_filter);
97 show_commit_decorations(commit); 101 show_commit_decorations(commit);
98 html("</div>"); 102 html("</div>");
99 html("<div class='commit-msg'>"); 103 html("<div class='commit-msg'>");
diff --git a/ui-patch.c b/ui-patch.c
index 5d665d3..2a8f7a5 100644
--- a/ui-patch.c
+++ b/ui-patch.c
@@ -63,63 +63,67 @@ static void filepair_cb(struct diff_filepair *pair)
63 63
64 header(pair->one->sha1, pair->one->path, pair->one->mode, 64 header(pair->one->sha1, pair->one->path, pair->one->mode,
65 pair->two->sha1, pair->two->path, pair->two->mode); 65 pair->two->sha1, pair->two->path, pair->two->mode);
66 if (S_ISGITLINK(pair->one->mode) || S_ISGITLINK(pair->two->mode)) { 66 if (S_ISGITLINK(pair->one->mode) || S_ISGITLINK(pair->two->mode)) {
67 if (S_ISGITLINK(pair->one->mode)) 67 if (S_ISGITLINK(pair->one->mode))
68 print_line(fmt("-Subproject %s", sha1_to_hex(pair->one->sha1)), 52); 68 print_line(fmt("-Subproject %s", sha1_to_hex(pair->one->sha1)), 52);
69 if (S_ISGITLINK(pair->two->mode)) 69 if (S_ISGITLINK(pair->two->mode))
70 print_line(fmt("+Subproject %s", sha1_to_hex(pair->two->sha1)), 52); 70 print_line(fmt("+Subproject %s", sha1_to_hex(pair->two->sha1)), 52);
71 return; 71 return;
72 } 72 }
73 if (cgit_diff_files(pair->one->sha1, pair->two->sha1, &old_size, 73 if (cgit_diff_files(pair->one->sha1, pair->two->sha1, &old_size,
74 &new_size, &binary, print_line)) 74 &new_size, &binary, print_line))
75 html("Error running diff"); 75 html("Error running diff");
76 if (binary) 76 if (binary)
77 html("Binary files differ\n"); 77 html("Binary files differ\n");
78} 78}
79 79
80void cgit_print_patch(char *hex) 80void cgit_print_patch(char *hex)
81{ 81{
82 struct commit *commit; 82 struct commit *commit;
83 struct commitinfo *info; 83 struct commitinfo *info;
84 unsigned char sha1[20], old_sha1[20]; 84 unsigned char sha1[20], old_sha1[20];
85 char *patchname; 85 char *patchname;
86 86
87 if (!hex) 87 if (!hex)
88 hex = ctx.qry.head; 88 hex = ctx.qry.head;
89 89
90 if (get_sha1(hex, sha1)) { 90 if (get_sha1(hex, sha1)) {
91 cgit_print_error(fmt("Bad object id: %s", hex)); 91 cgit_print_error(fmt("Bad object id: %s", hex));
92 return; 92 return;
93 } 93 }
94 commit = lookup_commit_reference(sha1); 94 commit = lookup_commit_reference(sha1);
95 if (!commit) { 95 if (!commit) {
96 cgit_print_error(fmt("Bad commit reference: %s", hex)); 96 cgit_print_error(fmt("Bad commit reference: %s", hex));
97 return; 97 return;
98 } 98 }
99 info = cgit_parse_commit(commit); 99 info = cgit_parse_commit(commit);
100 100
101 if (commit->parents && commit->parents->item) 101 if (commit->parents && commit->parents->item)
102 hashcpy(old_sha1, commit->parents->item->object.sha1); 102 hashcpy(old_sha1, commit->parents->item->object.sha1);
103 else 103 else
104 hashclr(old_sha1); 104 hashclr(old_sha1);
105 105
106 patchname = fmt("%s.patch", sha1_to_hex(sha1)); 106 patchname = fmt("%s.patch", sha1_to_hex(sha1));
107 ctx.page.mimetype = "text/plain"; 107 ctx.page.mimetype = "text/plain";
108 ctx.page.filename = patchname; 108 ctx.page.filename = patchname;
109 cgit_print_http_headers(&ctx); 109 cgit_print_http_headers(&ctx);
110 htmlf("From %s Mon Sep 17 00:00:00 2001\n", sha1_to_hex(sha1)); 110 htmlf("From %s Mon Sep 17 00:00:00 2001\n", sha1_to_hex(sha1));
111 htmlf("From: %s %s\n", info->author, info->author_email); 111 htmlf("From: %s", info->author);
112 if (!ctx.cfg.noplainemail) {
113 htmlf(" %s", info->author_email);
114 }
115 html("\n");
112 html("Date: "); 116 html("Date: ");
113 cgit_print_date(info->author_date, "%a, %d %b %Y %H:%M:%S %z%n", ctx.cfg.local_time); 117 cgit_print_date(info->author_date, "%a, %d %b %Y %H:%M:%S %z%n", ctx.cfg.local_time);
114 htmlf("Subject: %s\n\n", info->subject); 118 htmlf("Subject: %s\n\n", info->subject);
115 if (info->msg && *info->msg) { 119 if (info->msg && *info->msg) {
116 htmlf("%s", info->msg); 120 htmlf("%s", info->msg);
117 if (info->msg[strlen(info->msg) - 1] != '\n') 121 if (info->msg[strlen(info->msg) - 1] != '\n')
118 html("\n"); 122 html("\n");
119 } 123 }
120 html("---\n"); 124 html("---\n");
121 cgit_diff_tree(old_sha1, sha1, filepair_cb, NULL); 125 cgit_diff_tree(old_sha1, sha1, filepair_cb, NULL);
122 html("--\n"); 126 html("--\n");
123 htmlf("cgit %s\n", CGIT_VERSION); 127 htmlf("cgit %s\n", CGIT_VERSION);
124 cgit_free_commitinfo(info); 128 cgit_free_commitinfo(info);
125} 129}
diff --git a/ui-plain.c b/ui-plain.c
index 93a3a05..27c6dae 100644
--- a/ui-plain.c
+++ b/ui-plain.c
@@ -1,83 +1,93 @@
1/* ui-plain.c: functions for output of plain blobs by path 1/* ui-plain.c: functions for output of plain blobs by path
2 * 2 *
3 * Copyright (C) 2008 Lars Hjemli 3 * Copyright (C) 2008 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 "html.h" 10#include "html.h"
11#include "ui-shared.h" 11#include "ui-shared.h"
12 12
13char *curr_rev; 13char *curr_rev;
14char *match_path; 14char *match_path;
15int match; 15int match;
16 16
17static void print_object(const unsigned char *sha1, const char *path) 17static void print_object(const unsigned char *sha1, const char *path)
18{ 18{
19 enum object_type type; 19 enum object_type type;
20 char *buf; 20 char *buf, *ext;
21 unsigned long size; 21 unsigned long size;
22 struct string_list_item *mime;
22 23
23 type = sha1_object_info(sha1, &size); 24 type = sha1_object_info(sha1, &size);
24 if (type == OBJ_BAD) { 25 if (type == OBJ_BAD) {
25 html_status(404, "Not found", 0); 26 html_status(404, "Not found", 0);
26 return; 27 return;
27 } 28 }
28 29
29 buf = read_sha1_file(sha1, &type, &size); 30 buf = read_sha1_file(sha1, &type, &size);
30 if (!buf) { 31 if (!buf) {
31 html_status(404, "Not found", 0); 32 html_status(404, "Not found", 0);
32 return; 33 return;
33 } 34 }
34 if (buffer_is_binary(buf, size)) 35 ctx.page.mimetype = NULL;
35 ctx.page.mimetype = "application/octet-stream"; 36 ext = strrchr(path, '.');
36 else 37 if (ext && *(++ext)) {
37 ctx.page.mimetype = "text/plain"; 38 mime = string_list_lookup(ext, &ctx.cfg.mimetypes);
39 if (mime)
40 ctx.page.mimetype = (char *)mime->util;
41 }
42 if (!ctx.page.mimetype) {
43 if (buffer_is_binary(buf, size))
44 ctx.page.mimetype = "application/octet-stream";
45 else
46 ctx.page.mimetype = "text/plain";
47 }
38 ctx.page.filename = fmt("%s", path); 48 ctx.page.filename = fmt("%s", path);
39 ctx.page.size = size; 49 ctx.page.size = size;
40 ctx.page.etag = sha1_to_hex(sha1); 50 ctx.page.etag = sha1_to_hex(sha1);
41 cgit_print_http_headers(&ctx); 51 cgit_print_http_headers(&ctx);
42 html_raw(buf, size); 52 html_raw(buf, size);
43 match = 1; 53 match = 1;
44} 54}
45 55
46static int walk_tree(const unsigned char *sha1, const char *base, int baselen, 56static int walk_tree(const unsigned char *sha1, const char *base, int baselen,
47 const char *pathname, unsigned mode, int stage, 57 const char *pathname, unsigned mode, int stage,
48 void *cbdata) 58 void *cbdata)
49{ 59{
50 if (S_ISDIR(mode)) 60 if (S_ISDIR(mode))
51 return READ_TREE_RECURSIVE; 61 return READ_TREE_RECURSIVE;
52 62
53 if (S_ISREG(mode)) 63 if (S_ISREG(mode))
54 print_object(sha1, pathname); 64 print_object(sha1, pathname);
55 65
56 return 0; 66 return 0;
57} 67}
58 68
59void cgit_print_plain(struct cgit_context *ctx) 69void cgit_print_plain(struct cgit_context *ctx)
60{ 70{
61 const char *rev = ctx->qry.sha1; 71 const char *rev = ctx->qry.sha1;
62 unsigned char sha1[20]; 72 unsigned char sha1[20];
63 struct commit *commit; 73 struct commit *commit;
64 const char *paths[] = {ctx->qry.path, NULL}; 74 const char *paths[] = {ctx->qry.path, NULL};
65 75
66 if (!rev) 76 if (!rev)
67 rev = ctx->qry.head; 77 rev = ctx->qry.head;
68 78
69 curr_rev = xstrdup(rev); 79 curr_rev = xstrdup(rev);
70 if (get_sha1(rev, sha1)) { 80 if (get_sha1(rev, sha1)) {
71 html_status(404, "Not found", 0); 81 html_status(404, "Not found", 0);
72 return; 82 return;
73 } 83 }
74 commit = lookup_commit_reference(sha1); 84 commit = lookup_commit_reference(sha1);
75 if (!commit || parse_commit(commit)) { 85 if (!commit || parse_commit(commit)) {
76 html_status(404, "Not found", 0); 86 html_status(404, "Not found", 0);
77 return; 87 return;
78 } 88 }
79 match_path = ctx->qry.path; 89 match_path = ctx->qry.path;
80 read_tree_recursive(commit->tree, NULL, 0, 0, paths, walk_tree, NULL); 90 read_tree_recursive(commit->tree, NULL, 0, 0, paths, walk_tree, NULL);
81 if (!match) 91 if (!match)
82 html_status(404, "Not found", 0); 92 html_status(404, "Not found", 0);
83} 93}
diff --git a/ui-tag.c b/ui-tag.c
index 0e056e0..a9c8670 100644
--- a/ui-tag.c
+++ b/ui-tag.c
@@ -19,71 +19,71 @@ static void print_tag_content(char *buf)
19 19
20 html("<div class='commit-subject'>"); 20 html("<div class='commit-subject'>");
21 p = strchr(buf, '\n'); 21 p = strchr(buf, '\n');
22 if (p) 22 if (p)
23 *p = '\0'; 23 *p = '\0';
24 html_txt(buf); 24 html_txt(buf);
25 html("</div>"); 25 html("</div>");
26 if (p) { 26 if (p) {
27 html("<div class='commit-msg'>"); 27 html("<div class='commit-msg'>");
28 html_txt(++p); 28 html_txt(++p);
29 html("</div>"); 29 html("</div>");
30 } 30 }
31} 31}
32 32
33void cgit_print_tag(char *revname) 33void cgit_print_tag(char *revname)
34{ 34{
35 unsigned char sha1[20]; 35 unsigned char sha1[20];
36 struct object *obj; 36 struct object *obj;
37 struct tag *tag; 37 struct tag *tag;
38 struct taginfo *info; 38 struct taginfo *info;
39 39
40 if (get_sha1(revname, sha1)) { 40 if (get_sha1(revname, sha1)) {
41 cgit_print_error(fmt("Bad tag reference: %s", revname)); 41 cgit_print_error(fmt("Bad tag reference: %s", revname));
42 return; 42 return;
43 } 43 }
44 obj = parse_object(sha1); 44 obj = parse_object(sha1);
45 if (!obj) { 45 if (!obj) {
46 cgit_print_error(fmt("Bad object id: %s", sha1_to_hex(sha1))); 46 cgit_print_error(fmt("Bad object id: %s", sha1_to_hex(sha1)));
47 return; 47 return;
48 } 48 }
49 if (obj->type == OBJ_TAG) { 49 if (obj->type == OBJ_TAG) {
50 tag = lookup_tag(sha1); 50 tag = lookup_tag(sha1);
51 if (!tag || parse_tag(tag) || !(info = cgit_parse_tag(tag))) { 51 if (!tag || parse_tag(tag) || !(info = cgit_parse_tag(tag))) {
52 cgit_print_error(fmt("Bad tag object: %s", revname)); 52 cgit_print_error(fmt("Bad tag object: %s", revname));
53 return; 53 return;
54 } 54 }
55 html("<table class='commit-info'>\n"); 55 html("<table class='commit-info'>\n");
56 htmlf("<tr><td>Tag name</td><td>"); 56 htmlf("<tr><td>Tag name</td><td>");
57 html_txt(revname); 57 html_txt(revname);
58 htmlf(" (%s)</td></tr>\n", sha1_to_hex(sha1)); 58 htmlf(" (%s)</td></tr>\n", sha1_to_hex(sha1));
59 if (info->tagger_date > 0) { 59 if (info->tagger_date > 0) {
60 html("<tr><td>Tag date</td><td>"); 60 html("<tr><td>Tag date</td><td>");
61 cgit_print_date(info->tagger_date, FMT_LONGDATE, ctx.cfg.local_time); 61 cgit_print_date(info->tagger_date, FMT_LONGDATE, ctx.cfg.local_time);
62 html("</td></tr>\n"); 62 html("</td></tr>\n");
63 } 63 }
64 if (info->tagger) { 64 if (info->tagger) {
65 html("<tr><td>Tagged by</td><td>"); 65 html("<tr><td>Tagged by</td><td>");
66 html_txt(info->tagger); 66 html_txt(info->tagger);
67 if (info->tagger_email) { 67 if (info->tagger_email && !ctx.cfg.noplainemail) {
68 html(" "); 68 html(" ");
69 html_txt(info->tagger_email); 69 html_txt(info->tagger_email);
70 } 70 }
71 html("</td></tr>\n"); 71 html("</td></tr>\n");
72 } 72 }
73 html("<tr><td>Tagged object</td><td>"); 73 html("<tr><td>Tagged object</td><td>");
74 cgit_object_link(tag->tagged); 74 cgit_object_link(tag->tagged);
75 html("</td></tr>\n"); 75 html("</td></tr>\n");
76 html("</table>\n"); 76 html("</table>\n");
77 print_tag_content(info->msg); 77 print_tag_content(info->msg);
78 } else { 78 } else {
79 html("<table class='commit-info'>\n"); 79 html("<table class='commit-info'>\n");
80 htmlf("<tr><td>Tag name</td><td>"); 80 htmlf("<tr><td>Tag name</td><td>");
81 html_txt(revname); 81 html_txt(revname);
82 html("</td></tr>\n"); 82 html("</td></tr>\n");
83 html("<tr><td>Tagged object</td><td>"); 83 html("<tr><td>Tagged object</td><td>");
84 cgit_object_link(obj); 84 cgit_object_link(obj);
85 html("</td></tr>\n"); 85 html("</td></tr>\n");
86 html("</table>\n"); 86 html("</table>\n");
87 } 87 }
88 return; 88 return;
89} 89}
diff --git a/ui-tree.c b/ui-tree.c
index caf6a9e..c608754 100644
--- a/ui-tree.c
+++ b/ui-tree.c
@@ -68,129 +68,135 @@ static void print_binary_buffer(char *buf, unsigned long size)
68 html(" </td><td class='hex'>"); 68 html(" </td><td class='hex'>");
69 for (idx = 0; idx < ROWLEN && ofs + idx < size; idx++) 69 for (idx = 0; idx < ROWLEN && ofs + idx < size; idx++)
70 ascii[idx] = isgraph(buf[idx]) ? buf[idx] : '.'; 70 ascii[idx] = isgraph(buf[idx]) ? buf[idx] : '.';
71 ascii[idx] = '\0'; 71 ascii[idx] = '\0';
72 html_txt(ascii); 72 html_txt(ascii);
73 html("</td></tr>\n"); 73 html("</td></tr>\n");
74 } 74 }
75 html("</table>\n"); 75 html("</table>\n");
76} 76}
77 77
78static void print_object(const unsigned char *sha1, char *path, const char *basename) 78static void print_object(const unsigned char *sha1, char *path, const char *basename)
79{ 79{
80 enum object_type type; 80 enum object_type type;
81 char *buf; 81 char *buf;
82 unsigned long size; 82 unsigned long size;
83 83
84 type = sha1_object_info(sha1, &size); 84 type = sha1_object_info(sha1, &size);
85 if (type == OBJ_BAD) { 85 if (type == OBJ_BAD) {
86 cgit_print_error(fmt("Bad object name: %s", 86 cgit_print_error(fmt("Bad object name: %s",
87 sha1_to_hex(sha1))); 87 sha1_to_hex(sha1)));
88 return; 88 return;
89 } 89 }
90 90
91 buf = read_sha1_file(sha1, &type, &size); 91 buf = read_sha1_file(sha1, &type, &size);
92 if (!buf) { 92 if (!buf) {
93 cgit_print_error(fmt("Error reading object %s", 93 cgit_print_error(fmt("Error reading object %s",
94 sha1_to_hex(sha1))); 94 sha1_to_hex(sha1)));
95 return; 95 return;
96 } 96 }
97 97
98 html(" ("); 98 html(" (");
99 cgit_plain_link("plain", NULL, NULL, ctx.qry.head, 99 cgit_plain_link("plain", NULL, NULL, ctx.qry.head,
100 curr_rev, path); 100 curr_rev, path);
101 htmlf(")<br/>blob: %s\n", sha1_to_hex(sha1)); 101 htmlf(")<br/>blob: %s\n", sha1_to_hex(sha1));
102 102
103 if (buffer_is_binary(buf, size)) 103 if (buffer_is_binary(buf, size))
104 print_binary_buffer(buf, size); 104 print_binary_buffer(buf, size);
105 else 105 else
106 print_text_buffer(basename, buf, size); 106 print_text_buffer(basename, buf, size);
107} 107}
108 108
109 109
110static int ls_item(const unsigned char *sha1, const char *base, int baselen, 110static int ls_item(const unsigned char *sha1, const char *base, int baselen,
111 const char *pathname, unsigned int mode, int stage, 111 const char *pathname, unsigned int mode, int stage,
112 void *cbdata) 112 void *cbdata)
113{ 113{
114 char *name; 114 char *name;
115 char *fullpath; 115 char *fullpath;
116 char *class;
116 enum object_type type; 117 enum object_type type;
117 unsigned long size = 0; 118 unsigned long size = 0;
118 119
119 name = xstrdup(pathname); 120 name = xstrdup(pathname);
120 fullpath = fmt("%s%s%s", ctx.qry.path ? ctx.qry.path : "", 121 fullpath = fmt("%s%s%s", ctx.qry.path ? ctx.qry.path : "",
121 ctx.qry.path ? "/" : "", name); 122 ctx.qry.path ? "/" : "", name);
122 123
123 if (!S_ISGITLINK(mode)) { 124 if (!S_ISGITLINK(mode)) {
124 type = sha1_object_info(sha1, &size); 125 type = sha1_object_info(sha1, &size);
125 if (type == OBJ_BAD) { 126 if (type == OBJ_BAD) {
126 htmlf("<tr><td colspan='3'>Bad object: %s %s</td></tr>", 127 htmlf("<tr><td colspan='3'>Bad object: %s %s</td></tr>",
127 name, 128 name,
128 sha1_to_hex(sha1)); 129 sha1_to_hex(sha1));
129 return 0; 130 return 0;
130 } 131 }
131 } 132 }
132 133
133 html("<tr><td class='ls-mode'>"); 134 html("<tr><td class='ls-mode'>");
134 cgit_print_filemode(mode); 135 cgit_print_filemode(mode);
135 html("</td><td>"); 136 html("</td><td>");
136 if (S_ISGITLINK(mode)) { 137 if (S_ISGITLINK(mode)) {
137 htmlf("<a class='ls-mod' href='"); 138 htmlf("<a class='ls-mod' href='");
138 html_attr(fmt(ctx.repo->module_link, 139 html_attr(fmt(ctx.repo->module_link,
139 name, 140 name,
140 sha1_to_hex(sha1))); 141 sha1_to_hex(sha1)));
141 html("'>"); 142 html("'>");
142 html_txt(name); 143 html_txt(name);
143 html("</a>"); 144 html("</a>");
144 } else if (S_ISDIR(mode)) { 145 } else if (S_ISDIR(mode)) {
145 cgit_tree_link(name, NULL, "ls-dir", ctx.qry.head, 146 cgit_tree_link(name, NULL, "ls-dir", ctx.qry.head,
146 curr_rev, fullpath); 147 curr_rev, fullpath);
147 } else { 148 } else {
148 cgit_tree_link(name, NULL, "ls-blob", ctx.qry.head, 149 class = strrchr(name, '.');
150 if (class != NULL) {
151 class = fmt("ls-blob %s", class + 1);
152 } else
153 class = "ls-blob";
154 cgit_tree_link(name, NULL, class, ctx.qry.head,
149 curr_rev, fullpath); 155 curr_rev, fullpath);
150 } 156 }
151 htmlf("</td><td class='ls-size'>%li</td>", size); 157 htmlf("</td><td class='ls-size'>%li</td>", size);
152 158
153 html("<td>"); 159 html("<td>");
154 cgit_log_link("log", NULL, "button", ctx.qry.head, curr_rev, 160 cgit_log_link("log", NULL, "button", ctx.qry.head, curr_rev,
155 fullpath, 0, NULL, NULL, ctx.qry.showmsg); 161 fullpath, 0, NULL, NULL, ctx.qry.showmsg);
156 if (ctx.repo->max_stats) 162 if (ctx.repo->max_stats)
157 cgit_stats_link("stats", NULL, "button", ctx.qry.head, 163 cgit_stats_link("stats", NULL, "button", ctx.qry.head,
158 fullpath); 164 fullpath);
159 html("</td></tr>\n"); 165 html("</td></tr>\n");
160 free(name); 166 free(name);
161 return 0; 167 return 0;
162} 168}
163 169
164static void ls_head() 170static void ls_head()
165{ 171{
166 html("<table summary='tree listing' class='list'>\n"); 172 html("<table summary='tree listing' class='list'>\n");
167 html("<tr class='nohover'>"); 173 html("<tr class='nohover'>");
168 html("<th class='left'>Mode</th>"); 174 html("<th class='left'>Mode</th>");
169 html("<th class='left'>Name</th>"); 175 html("<th class='left'>Name</th>");
170 html("<th class='right'>Size</th>"); 176 html("<th class='right'>Size</th>");
171 html("<th/>"); 177 html("<th/>");
172 html("</tr>\n"); 178 html("</tr>\n");
173 header = 1; 179 header = 1;
174} 180}
175 181
176static void ls_tail() 182static void ls_tail()
177{ 183{
178 if (!header) 184 if (!header)
179 return; 185 return;
180 html("</table>\n"); 186 html("</table>\n");
181 header = 0; 187 header = 0;
182} 188}
183 189
184static void ls_tree(const unsigned char *sha1, char *path) 190static void ls_tree(const unsigned char *sha1, char *path)
185{ 191{
186 struct tree *tree; 192 struct tree *tree;
187 193
188 tree = parse_tree_indirect(sha1); 194 tree = parse_tree_indirect(sha1);
189 if (!tree) { 195 if (!tree) {
190 cgit_print_error(fmt("Not a tree object: %s", 196 cgit_print_error(fmt("Not a tree object: %s",
191 sha1_to_hex(sha1))); 197 sha1_to_hex(sha1)));
192 return; 198 return;
193 } 199 }
194 200
195 ls_head(); 201 ls_head();
196 read_tree_recursive(tree, "", 0, 1, NULL, ls_item, NULL); 202 read_tree_recursive(tree, "", 0, 1, NULL, ls_item, NULL);