summaryrefslogtreecommitdiffabout
authorLars Hjemli <hjemli@gmail.com>2008-11-29 17:39:41 (UTC)
committer Lars Hjemli <hjemli@gmail.com>2008-11-29 17:39:41 (UTC)
commit0274b57d55a12ed38259757dbfae96b79cfa2e0b (patch) (unidiff)
tree34c7204f88168f791ef48f603bb8ab66e9df523c
parent7b5cee65fd9cf31e4f19ce4ff613778cb95512a9 (diff)
downloadcgit-0274b57d55a12ed38259757dbfae96b79cfa2e0b.zip
cgit-0274b57d55a12ed38259757dbfae96b79cfa2e0b.tar.gz
cgit-0274b57d55a12ed38259757dbfae96b79cfa2e0b.tar.bz2
ui-log: add support for showing the full commit message
Some users prefer to see the full message, so to make these users happy the new querystring parameter "showmsg" can be used to print the full commit message per log entry. A link is provided in the log heading to make this function accessible, and all links and forms tries to preserve the users preference. Note: the new link is not displayed on the summary page since the point of the summary page is to be a summary, but it is still obeyed if specified manually. Signed-off-by: Lars Hjemli <hjemli@gmail.com>
Diffstat (more/less context) (ignore whitespace changes)
-rw-r--r--cgit.c2
-rw-r--r--cgit.h1
-rw-r--r--ui-log.c32
-rw-r--r--ui-refs.c3
-rw-r--r--ui-repolist.c2
-rw-r--r--ui-shared.c12
-rw-r--r--ui-shared.h2
-rw-r--r--ui-tree.c2
8 files changed, 45 insertions, 11 deletions
diff --git a/cgit.c b/cgit.c
index c82587b..db5d342 100644
--- a/cgit.c
+++ b/cgit.c
@@ -1,472 +1,474 @@
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 "scan-tree.h" 15#include "scan-tree.h"
16 16
17const char *cgit_version = CGIT_VERSION; 17const char *cgit_version = CGIT_VERSION;
18 18
19void config_cb(const char *name, const char *value) 19void config_cb(const char *name, const char *value)
20{ 20{
21 if (!strcmp(name, "root-title")) 21 if (!strcmp(name, "root-title"))
22 ctx.cfg.root_title = xstrdup(value); 22 ctx.cfg.root_title = xstrdup(value);
23 else if (!strcmp(name, "root-desc")) 23 else if (!strcmp(name, "root-desc"))
24 ctx.cfg.root_desc = xstrdup(value); 24 ctx.cfg.root_desc = xstrdup(value);
25 else if (!strcmp(name, "root-readme")) 25 else if (!strcmp(name, "root-readme"))
26 ctx.cfg.root_readme = xstrdup(value); 26 ctx.cfg.root_readme = xstrdup(value);
27 else if (!strcmp(name, "css")) 27 else if (!strcmp(name, "css"))
28 ctx.cfg.css = xstrdup(value); 28 ctx.cfg.css = xstrdup(value);
29 else if (!strcmp(name, "favicon")) 29 else if (!strcmp(name, "favicon"))
30 ctx.cfg.favicon = xstrdup(value); 30 ctx.cfg.favicon = xstrdup(value);
31 else if (!strcmp(name, "footer")) 31 else if (!strcmp(name, "footer"))
32 ctx.cfg.footer = xstrdup(value); 32 ctx.cfg.footer = xstrdup(value);
33 else if (!strcmp(name, "logo")) 33 else if (!strcmp(name, "logo"))
34 ctx.cfg.logo = xstrdup(value); 34 ctx.cfg.logo = xstrdup(value);
35 else if (!strcmp(name, "index-header")) 35 else if (!strcmp(name, "index-header"))
36 ctx.cfg.index_header = xstrdup(value); 36 ctx.cfg.index_header = xstrdup(value);
37 else if (!strcmp(name, "index-info")) 37 else if (!strcmp(name, "index-info"))
38 ctx.cfg.index_info = xstrdup(value); 38 ctx.cfg.index_info = xstrdup(value);
39 else if (!strcmp(name, "logo-link")) 39 else if (!strcmp(name, "logo-link"))
40 ctx.cfg.logo_link = xstrdup(value); 40 ctx.cfg.logo_link = xstrdup(value);
41 else if (!strcmp(name, "module-link")) 41 else if (!strcmp(name, "module-link"))
42 ctx.cfg.module_link = xstrdup(value); 42 ctx.cfg.module_link = xstrdup(value);
43 else if (!strcmp(name, "virtual-root")) { 43 else if (!strcmp(name, "virtual-root")) {
44 ctx.cfg.virtual_root = trim_end(value, '/'); 44 ctx.cfg.virtual_root = trim_end(value, '/');
45 if (!ctx.cfg.virtual_root && (!strcmp(value, "/"))) 45 if (!ctx.cfg.virtual_root && (!strcmp(value, "/")))
46 ctx.cfg.virtual_root = ""; 46 ctx.cfg.virtual_root = "";
47 } else if (!strcmp(name, "nocache")) 47 } else if (!strcmp(name, "nocache"))
48 ctx.cfg.nocache = atoi(value); 48 ctx.cfg.nocache = atoi(value);
49 else if (!strcmp(name, "snapshots")) 49 else if (!strcmp(name, "snapshots"))
50 ctx.cfg.snapshots = cgit_parse_snapshots_mask(value); 50 ctx.cfg.snapshots = cgit_parse_snapshots_mask(value);
51 else if (!strcmp(name, "enable-index-links")) 51 else if (!strcmp(name, "enable-index-links"))
52 ctx.cfg.enable_index_links = atoi(value); 52 ctx.cfg.enable_index_links = atoi(value);
53 else if (!strcmp(name, "enable-log-filecount")) 53 else if (!strcmp(name, "enable-log-filecount"))
54 ctx.cfg.enable_log_filecount = atoi(value); 54 ctx.cfg.enable_log_filecount = atoi(value);
55 else if (!strcmp(name, "enable-log-linecount")) 55 else if (!strcmp(name, "enable-log-linecount"))
56 ctx.cfg.enable_log_linecount = atoi(value); 56 ctx.cfg.enable_log_linecount = atoi(value);
57 else if (!strcmp(name, "cache-size")) 57 else if (!strcmp(name, "cache-size"))
58 ctx.cfg.cache_size = atoi(value); 58 ctx.cfg.cache_size = atoi(value);
59 else if (!strcmp(name, "cache-root")) 59 else if (!strcmp(name, "cache-root"))
60 ctx.cfg.cache_root = xstrdup(value); 60 ctx.cfg.cache_root = xstrdup(value);
61 else if (!strcmp(name, "cache-root-ttl")) 61 else if (!strcmp(name, "cache-root-ttl"))
62 ctx.cfg.cache_root_ttl = atoi(value); 62 ctx.cfg.cache_root_ttl = atoi(value);
63 else if (!strcmp(name, "cache-repo-ttl")) 63 else if (!strcmp(name, "cache-repo-ttl"))
64 ctx.cfg.cache_repo_ttl = atoi(value); 64 ctx.cfg.cache_repo_ttl = atoi(value);
65 else if (!strcmp(name, "cache-static-ttl")) 65 else if (!strcmp(name, "cache-static-ttl"))
66 ctx.cfg.cache_static_ttl = atoi(value); 66 ctx.cfg.cache_static_ttl = atoi(value);
67 else if (!strcmp(name, "cache-dynamic-ttl")) 67 else if (!strcmp(name, "cache-dynamic-ttl"))
68 ctx.cfg.cache_dynamic_ttl = atoi(value); 68 ctx.cfg.cache_dynamic_ttl = atoi(value);
69 else if (!strcmp(name, "max-message-length")) 69 else if (!strcmp(name, "max-message-length"))
70 ctx.cfg.max_msg_len = atoi(value); 70 ctx.cfg.max_msg_len = atoi(value);
71 else if (!strcmp(name, "max-repodesc-length")) 71 else if (!strcmp(name, "max-repodesc-length"))
72 ctx.cfg.max_repodesc_len = atoi(value); 72 ctx.cfg.max_repodesc_len = atoi(value);
73 else if (!strcmp(name, "max-repo-count")) 73 else if (!strcmp(name, "max-repo-count"))
74 ctx.cfg.max_repo_count = atoi(value); 74 ctx.cfg.max_repo_count = atoi(value);
75 else if (!strcmp(name, "max-commit-count")) 75 else if (!strcmp(name, "max-commit-count"))
76 ctx.cfg.max_commit_count = atoi(value); 76 ctx.cfg.max_commit_count = atoi(value);
77 else if (!strcmp(name, "summary-log")) 77 else if (!strcmp(name, "summary-log"))
78 ctx.cfg.summary_log = atoi(value); 78 ctx.cfg.summary_log = atoi(value);
79 else if (!strcmp(name, "summary-branches")) 79 else if (!strcmp(name, "summary-branches"))
80 ctx.cfg.summary_branches = atoi(value); 80 ctx.cfg.summary_branches = atoi(value);
81 else if (!strcmp(name, "summary-tags")) 81 else if (!strcmp(name, "summary-tags"))
82 ctx.cfg.summary_tags = atoi(value); 82 ctx.cfg.summary_tags = atoi(value);
83 else if (!strcmp(name, "agefile")) 83 else if (!strcmp(name, "agefile"))
84 ctx.cfg.agefile = xstrdup(value); 84 ctx.cfg.agefile = xstrdup(value);
85 else if (!strcmp(name, "renamelimit")) 85 else if (!strcmp(name, "renamelimit"))
86 ctx.cfg.renamelimit = atoi(value); 86 ctx.cfg.renamelimit = atoi(value);
87 else if (!strcmp(name, "robots")) 87 else if (!strcmp(name, "robots"))
88 ctx.cfg.robots = xstrdup(value); 88 ctx.cfg.robots = xstrdup(value);
89 else if (!strcmp(name, "clone-prefix")) 89 else if (!strcmp(name, "clone-prefix"))
90 ctx.cfg.clone_prefix = xstrdup(value); 90 ctx.cfg.clone_prefix = xstrdup(value);
91 else if (!strcmp(name, "local-time")) 91 else if (!strcmp(name, "local-time"))
92 ctx.cfg.local_time = atoi(value); 92 ctx.cfg.local_time = atoi(value);
93 else if (!strcmp(name, "repo.group")) 93 else if (!strcmp(name, "repo.group"))
94 ctx.cfg.repo_group = xstrdup(value); 94 ctx.cfg.repo_group = xstrdup(value);
95 else if (!strcmp(name, "repo.url")) 95 else if (!strcmp(name, "repo.url"))
96 ctx.repo = cgit_add_repo(value); 96 ctx.repo = cgit_add_repo(value);
97 else if (!strcmp(name, "repo.name")) 97 else if (!strcmp(name, "repo.name"))
98 ctx.repo->name = xstrdup(value); 98 ctx.repo->name = xstrdup(value);
99 else if (ctx.repo && !strcmp(name, "repo.path")) 99 else if (ctx.repo && !strcmp(name, "repo.path"))
100 ctx.repo->path = trim_end(value, '/'); 100 ctx.repo->path = trim_end(value, '/');
101 else if (ctx.repo && !strcmp(name, "repo.clone-url")) 101 else if (ctx.repo && !strcmp(name, "repo.clone-url"))
102 ctx.repo->clone_url = xstrdup(value); 102 ctx.repo->clone_url = xstrdup(value);
103 else if (ctx.repo && !strcmp(name, "repo.desc")) 103 else if (ctx.repo && !strcmp(name, "repo.desc"))
104 ctx.repo->desc = xstrdup(value); 104 ctx.repo->desc = xstrdup(value);
105 else if (ctx.repo && !strcmp(name, "repo.owner")) 105 else if (ctx.repo && !strcmp(name, "repo.owner"))
106 ctx.repo->owner = xstrdup(value); 106 ctx.repo->owner = xstrdup(value);
107 else if (ctx.repo && !strcmp(name, "repo.defbranch")) 107 else if (ctx.repo && !strcmp(name, "repo.defbranch"))
108 ctx.repo->defbranch = xstrdup(value); 108 ctx.repo->defbranch = xstrdup(value);
109 else if (ctx.repo && !strcmp(name, "repo.snapshots")) 109 else if (ctx.repo && !strcmp(name, "repo.snapshots"))
110 ctx.repo->snapshots = ctx.cfg.snapshots & cgit_parse_snapshots_mask(value); /* XXX: &? */ 110 ctx.repo->snapshots = ctx.cfg.snapshots & cgit_parse_snapshots_mask(value); /* XXX: &? */
111 else if (ctx.repo && !strcmp(name, "repo.enable-log-filecount")) 111 else if (ctx.repo && !strcmp(name, "repo.enable-log-filecount"))
112 ctx.repo->enable_log_filecount = ctx.cfg.enable_log_filecount * atoi(value); 112 ctx.repo->enable_log_filecount = ctx.cfg.enable_log_filecount * atoi(value);
113 else if (ctx.repo && !strcmp(name, "repo.enable-log-linecount")) 113 else if (ctx.repo && !strcmp(name, "repo.enable-log-linecount"))
114 ctx.repo->enable_log_linecount = ctx.cfg.enable_log_linecount * atoi(value); 114 ctx.repo->enable_log_linecount = ctx.cfg.enable_log_linecount * atoi(value);
115 else if (ctx.repo && !strcmp(name, "repo.module-link")) 115 else if (ctx.repo && !strcmp(name, "repo.module-link"))
116 ctx.repo->module_link= xstrdup(value); 116 ctx.repo->module_link= xstrdup(value);
117 else if (ctx.repo && !strcmp(name, "repo.readme") && value != NULL) { 117 else if (ctx.repo && !strcmp(name, "repo.readme") && value != NULL) {
118 if (*value == '/') 118 if (*value == '/')
119 ctx.repo->readme = xstrdup(value); 119 ctx.repo->readme = xstrdup(value);
120 else 120 else
121 ctx.repo->readme = xstrdup(fmt("%s/%s", ctx.repo->path, value)); 121 ctx.repo->readme = xstrdup(fmt("%s/%s", ctx.repo->path, value));
122 } else if (!strcmp(name, "include")) 122 } else if (!strcmp(name, "include"))
123 parse_configfile(value, config_cb); 123 parse_configfile(value, config_cb);
124} 124}
125 125
126static void querystring_cb(const char *name, const char *value) 126static void querystring_cb(const char *name, const char *value)
127{ 127{
128 if (!strcmp(name,"r")) { 128 if (!strcmp(name,"r")) {
129 ctx.qry.repo = xstrdup(value); 129 ctx.qry.repo = xstrdup(value);
130 ctx.repo = cgit_get_repoinfo(value); 130 ctx.repo = cgit_get_repoinfo(value);
131 } else if (!strcmp(name, "p")) { 131 } else if (!strcmp(name, "p")) {
132 ctx.qry.page = xstrdup(value); 132 ctx.qry.page = xstrdup(value);
133 } else if (!strcmp(name, "url")) { 133 } else if (!strcmp(name, "url")) {
134 ctx.qry.url = xstrdup(value); 134 ctx.qry.url = xstrdup(value);
135 cgit_parse_url(value); 135 cgit_parse_url(value);
136 } else if (!strcmp(name, "qt")) { 136 } else if (!strcmp(name, "qt")) {
137 ctx.qry.grep = xstrdup(value); 137 ctx.qry.grep = xstrdup(value);
138 } else if (!strcmp(name, "q")) { 138 } else if (!strcmp(name, "q")) {
139 ctx.qry.search = xstrdup(value); 139 ctx.qry.search = xstrdup(value);
140 } else if (!strcmp(name, "h")) { 140 } else if (!strcmp(name, "h")) {
141 ctx.qry.head = xstrdup(value); 141 ctx.qry.head = xstrdup(value);
142 ctx.qry.has_symref = 1; 142 ctx.qry.has_symref = 1;
143 } else if (!strcmp(name, "id")) { 143 } else if (!strcmp(name, "id")) {
144 ctx.qry.sha1 = xstrdup(value); 144 ctx.qry.sha1 = xstrdup(value);
145 ctx.qry.has_sha1 = 1; 145 ctx.qry.has_sha1 = 1;
146 } else if (!strcmp(name, "id2")) { 146 } else if (!strcmp(name, "id2")) {
147 ctx.qry.sha2 = xstrdup(value); 147 ctx.qry.sha2 = xstrdup(value);
148 ctx.qry.has_sha1 = 1; 148 ctx.qry.has_sha1 = 1;
149 } else if (!strcmp(name, "ofs")) { 149 } else if (!strcmp(name, "ofs")) {
150 ctx.qry.ofs = atoi(value); 150 ctx.qry.ofs = atoi(value);
151 } else if (!strcmp(name, "path")) { 151 } else if (!strcmp(name, "path")) {
152 ctx.qry.path = trim_end(value, '/'); 152 ctx.qry.path = trim_end(value, '/');
153 } else if (!strcmp(name, "name")) { 153 } else if (!strcmp(name, "name")) {
154 ctx.qry.name = xstrdup(value); 154 ctx.qry.name = xstrdup(value);
155 } else if (!strcmp(name, "mimetype")) { 155 } else if (!strcmp(name, "mimetype")) {
156 ctx.qry.mimetype = xstrdup(value); 156 ctx.qry.mimetype = xstrdup(value);
157 } else if (!strcmp(name, "showmsg")) {
158 ctx.qry.showmsg = atoi(value);
157 } 159 }
158} 160}
159 161
160static void prepare_context(struct cgit_context *ctx) 162static void prepare_context(struct cgit_context *ctx)
161{ 163{
162 memset(ctx, 0, sizeof(ctx)); 164 memset(ctx, 0, sizeof(ctx));
163 ctx->cfg.agefile = "info/web/last-modified"; 165 ctx->cfg.agefile = "info/web/last-modified";
164 ctx->cfg.nocache = 0; 166 ctx->cfg.nocache = 0;
165 ctx->cfg.cache_size = 0; 167 ctx->cfg.cache_size = 0;
166 ctx->cfg.cache_dynamic_ttl = 5; 168 ctx->cfg.cache_dynamic_ttl = 5;
167 ctx->cfg.cache_max_create_time = 5; 169 ctx->cfg.cache_max_create_time = 5;
168 ctx->cfg.cache_repo_ttl = 5; 170 ctx->cfg.cache_repo_ttl = 5;
169 ctx->cfg.cache_root = CGIT_CACHE_ROOT; 171 ctx->cfg.cache_root = CGIT_CACHE_ROOT;
170 ctx->cfg.cache_root_ttl = 5; 172 ctx->cfg.cache_root_ttl = 5;
171 ctx->cfg.cache_static_ttl = -1; 173 ctx->cfg.cache_static_ttl = -1;
172 ctx->cfg.css = "/cgit.css"; 174 ctx->cfg.css = "/cgit.css";
173 ctx->cfg.logo = "/git-logo.png"; 175 ctx->cfg.logo = "/git-logo.png";
174 ctx->cfg.local_time = 0; 176 ctx->cfg.local_time = 0;
175 ctx->cfg.max_repo_count = 50; 177 ctx->cfg.max_repo_count = 50;
176 ctx->cfg.max_commit_count = 50; 178 ctx->cfg.max_commit_count = 50;
177 ctx->cfg.max_lock_attempts = 5; 179 ctx->cfg.max_lock_attempts = 5;
178 ctx->cfg.max_msg_len = 80; 180 ctx->cfg.max_msg_len = 80;
179 ctx->cfg.max_repodesc_len = 80; 181 ctx->cfg.max_repodesc_len = 80;
180 ctx->cfg.module_link = "./?repo=%s&page=commit&id=%s"; 182 ctx->cfg.module_link = "./?repo=%s&page=commit&id=%s";
181 ctx->cfg.renamelimit = -1; 183 ctx->cfg.renamelimit = -1;
182 ctx->cfg.robots = "index, nofollow"; 184 ctx->cfg.robots = "index, nofollow";
183 ctx->cfg.root_title = "Git repository browser"; 185 ctx->cfg.root_title = "Git repository browser";
184 ctx->cfg.root_desc = "a fast webinterface for the git dscm"; 186 ctx->cfg.root_desc = "a fast webinterface for the git dscm";
185 ctx->cfg.script_name = CGIT_SCRIPT_NAME; 187 ctx->cfg.script_name = CGIT_SCRIPT_NAME;
186 ctx->cfg.summary_branches = 10; 188 ctx->cfg.summary_branches = 10;
187 ctx->cfg.summary_log = 10; 189 ctx->cfg.summary_log = 10;
188 ctx->cfg.summary_tags = 10; 190 ctx->cfg.summary_tags = 10;
189 ctx->page.mimetype = "text/html"; 191 ctx->page.mimetype = "text/html";
190 ctx->page.charset = PAGE_ENCODING; 192 ctx->page.charset = PAGE_ENCODING;
191 ctx->page.filename = NULL; 193 ctx->page.filename = NULL;
192 ctx->page.size = 0; 194 ctx->page.size = 0;
193 ctx->page.modified = time(NULL); 195 ctx->page.modified = time(NULL);
194 ctx->page.expires = ctx->page.modified; 196 ctx->page.expires = ctx->page.modified;
195} 197}
196 198
197struct refmatch { 199struct refmatch {
198 char *req_ref; 200 char *req_ref;
199 char *first_ref; 201 char *first_ref;
200 int match; 202 int match;
201}; 203};
202 204
203int find_current_ref(const char *refname, const unsigned char *sha1, 205int find_current_ref(const char *refname, const unsigned char *sha1,
204 int flags, void *cb_data) 206 int flags, void *cb_data)
205{ 207{
206 struct refmatch *info; 208 struct refmatch *info;
207 209
208 info = (struct refmatch *)cb_data; 210 info = (struct refmatch *)cb_data;
209 if (!strcmp(refname, info->req_ref)) 211 if (!strcmp(refname, info->req_ref))
210 info->match = 1; 212 info->match = 1;
211 if (!info->first_ref) 213 if (!info->first_ref)
212 info->first_ref = xstrdup(refname); 214 info->first_ref = xstrdup(refname);
213 return info->match; 215 return info->match;
214} 216}
215 217
216char *find_default_branch(struct cgit_repo *repo) 218char *find_default_branch(struct cgit_repo *repo)
217{ 219{
218 struct refmatch info; 220 struct refmatch info;
219 char *ref; 221 char *ref;
220 222
221 info.req_ref = repo->defbranch; 223 info.req_ref = repo->defbranch;
222 info.first_ref = NULL; 224 info.first_ref = NULL;
223 info.match = 0; 225 info.match = 0;
224 for_each_branch_ref(find_current_ref, &info); 226 for_each_branch_ref(find_current_ref, &info);
225 if (info.match) 227 if (info.match)
226 ref = info.req_ref; 228 ref = info.req_ref;
227 else 229 else
228 ref = info.first_ref; 230 ref = info.first_ref;
229 if (ref) 231 if (ref)
230 ref = xstrdup(ref); 232 ref = xstrdup(ref);
231 return ref; 233 return ref;
232} 234}
233 235
234static int prepare_repo_cmd(struct cgit_context *ctx) 236static int prepare_repo_cmd(struct cgit_context *ctx)
235{ 237{
236 char *tmp; 238 char *tmp;
237 unsigned char sha1[20]; 239 unsigned char sha1[20];
238 int nongit = 0; 240 int nongit = 0;
239 241
240 setenv("GIT_DIR", ctx->repo->path, 1); 242 setenv("GIT_DIR", ctx->repo->path, 1);
241 setup_git_directory_gently(&nongit); 243 setup_git_directory_gently(&nongit);
242 if (nongit) { 244 if (nongit) {
243 ctx->page.title = fmt("%s - %s", ctx->cfg.root_title, 245 ctx->page.title = fmt("%s - %s", ctx->cfg.root_title,
244 "config error"); 246 "config error");
245 tmp = fmt("Not a git repository: '%s'", ctx->repo->path); 247 tmp = fmt("Not a git repository: '%s'", ctx->repo->path);
246 ctx->repo = NULL; 248 ctx->repo = NULL;
247 cgit_print_http_headers(ctx); 249 cgit_print_http_headers(ctx);
248 cgit_print_docstart(ctx); 250 cgit_print_docstart(ctx);
249 cgit_print_pageheader(ctx); 251 cgit_print_pageheader(ctx);
250 cgit_print_error(tmp); 252 cgit_print_error(tmp);
251 cgit_print_docend(); 253 cgit_print_docend();
252 return 1; 254 return 1;
253 } 255 }
254 ctx->page.title = fmt("%s - %s", ctx->repo->name, ctx->repo->desc); 256 ctx->page.title = fmt("%s - %s", ctx->repo->name, ctx->repo->desc);
255 257
256 if (!ctx->qry.head) { 258 if (!ctx->qry.head) {
257 ctx->qry.nohead = 1; 259 ctx->qry.nohead = 1;
258 ctx->qry.head = find_default_branch(ctx->repo); 260 ctx->qry.head = find_default_branch(ctx->repo);
259 ctx->repo->defbranch = ctx->qry.head; 261 ctx->repo->defbranch = ctx->qry.head;
260 } 262 }
261 263
262 if (!ctx->qry.head) { 264 if (!ctx->qry.head) {
263 cgit_print_http_headers(ctx); 265 cgit_print_http_headers(ctx);
264 cgit_print_docstart(ctx); 266 cgit_print_docstart(ctx);
265 cgit_print_pageheader(ctx); 267 cgit_print_pageheader(ctx);
266 cgit_print_error("Repository seems to be empty"); 268 cgit_print_error("Repository seems to be empty");
267 cgit_print_docend(); 269 cgit_print_docend();
268 return 1; 270 return 1;
269 } 271 }
270 272
271 if (get_sha1(ctx->qry.head, sha1)) { 273 if (get_sha1(ctx->qry.head, sha1)) {
272 tmp = xstrdup(ctx->qry.head); 274 tmp = xstrdup(ctx->qry.head);
273 ctx->qry.head = ctx->repo->defbranch; 275 ctx->qry.head = ctx->repo->defbranch;
274 cgit_print_http_headers(ctx); 276 cgit_print_http_headers(ctx);
275 cgit_print_docstart(ctx); 277 cgit_print_docstart(ctx);
276 cgit_print_pageheader(ctx); 278 cgit_print_pageheader(ctx);
277 cgit_print_error(fmt("Invalid branch: %s", tmp)); 279 cgit_print_error(fmt("Invalid branch: %s", tmp));
278 cgit_print_docend(); 280 cgit_print_docend();
279 return 1; 281 return 1;
280 } 282 }
281 return 0; 283 return 0;
282} 284}
283 285
284static void process_request(void *cbdata) 286static void process_request(void *cbdata)
285{ 287{
286 struct cgit_context *ctx = cbdata; 288 struct cgit_context *ctx = cbdata;
287 struct cgit_cmd *cmd; 289 struct cgit_cmd *cmd;
288 290
289 cmd = cgit_get_cmd(ctx); 291 cmd = cgit_get_cmd(ctx);
290 if (!cmd) { 292 if (!cmd) {
291 ctx->page.title = "cgit error"; 293 ctx->page.title = "cgit error";
292 ctx->repo = NULL; 294 ctx->repo = NULL;
293 cgit_print_http_headers(ctx); 295 cgit_print_http_headers(ctx);
294 cgit_print_docstart(ctx); 296 cgit_print_docstart(ctx);
295 cgit_print_pageheader(ctx); 297 cgit_print_pageheader(ctx);
296 cgit_print_error("Invalid request"); 298 cgit_print_error("Invalid request");
297 cgit_print_docend(); 299 cgit_print_docend();
298 return; 300 return;
299 } 301 }
300 302
301 if (cmd->want_repo && !ctx->repo) { 303 if (cmd->want_repo && !ctx->repo) {
302 cgit_print_http_headers(ctx); 304 cgit_print_http_headers(ctx);
303 cgit_print_docstart(ctx); 305 cgit_print_docstart(ctx);
304 cgit_print_pageheader(ctx); 306 cgit_print_pageheader(ctx);
305 cgit_print_error(fmt("No repository selected")); 307 cgit_print_error(fmt("No repository selected"));
306 cgit_print_docend(); 308 cgit_print_docend();
307 return; 309 return;
308 } 310 }
309 311
310 if (ctx->repo && prepare_repo_cmd(ctx)) 312 if (ctx->repo && prepare_repo_cmd(ctx))
311 return; 313 return;
312 314
313 if (cmd->want_layout) { 315 if (cmd->want_layout) {
314 cgit_print_http_headers(ctx); 316 cgit_print_http_headers(ctx);
315 cgit_print_docstart(ctx); 317 cgit_print_docstart(ctx);
316 cgit_print_pageheader(ctx); 318 cgit_print_pageheader(ctx);
317 } 319 }
318 320
319 cmd->fn(ctx); 321 cmd->fn(ctx);
320 322
321 if (cmd->want_layout) 323 if (cmd->want_layout)
322 cgit_print_docend(); 324 cgit_print_docend();
323} 325}
324 326
325int cmp_repos(const void *a, const void *b) 327int cmp_repos(const void *a, const void *b)
326{ 328{
327 const struct cgit_repo *ra = a, *rb = b; 329 const struct cgit_repo *ra = a, *rb = b;
328 return strcmp(ra->url, rb->url); 330 return strcmp(ra->url, rb->url);
329} 331}
330 332
331void print_repo(struct cgit_repo *repo) 333void print_repo(struct cgit_repo *repo)
332{ 334{
333 printf("repo.url=%s\n", repo->url); 335 printf("repo.url=%s\n", repo->url);
334 printf("repo.name=%s\n", repo->name); 336 printf("repo.name=%s\n", repo->name);
335 printf("repo.path=%s\n", repo->path); 337 printf("repo.path=%s\n", repo->path);
336 if (repo->owner) 338 if (repo->owner)
337 printf("repo.owner=%s\n", repo->owner); 339 printf("repo.owner=%s\n", repo->owner);
338 if (repo->desc) 340 if (repo->desc)
339 printf("repo.desc=%s\n", repo->desc); 341 printf("repo.desc=%s\n", repo->desc);
340 if (repo->readme) 342 if (repo->readme)
341 printf("repo.readme=%s\n", repo->readme); 343 printf("repo.readme=%s\n", repo->readme);
342 printf("\n"); 344 printf("\n");
343} 345}
344 346
345void print_repolist(struct cgit_repolist *list) 347void print_repolist(struct cgit_repolist *list)
346{ 348{
347 int i; 349 int i;
348 350
349 for(i = 0; i < list->count; i++) 351 for(i = 0; i < list->count; i++)
350 print_repo(&list->repos[i]); 352 print_repo(&list->repos[i]);
351} 353}
352 354
353 355
354static void cgit_parse_args(int argc, const char **argv) 356static void cgit_parse_args(int argc, const char **argv)
355{ 357{
356 int i; 358 int i;
357 int scan = 0; 359 int scan = 0;
358 360
359 for (i = 1; i < argc; i++) { 361 for (i = 1; i < argc; i++) {
360 if (!strncmp(argv[i], "--cache=", 8)) { 362 if (!strncmp(argv[i], "--cache=", 8)) {
361 ctx.cfg.cache_root = xstrdup(argv[i]+8); 363 ctx.cfg.cache_root = xstrdup(argv[i]+8);
362 } 364 }
363 if (!strcmp(argv[i], "--nocache")) { 365 if (!strcmp(argv[i], "--nocache")) {
364 ctx.cfg.nocache = 1; 366 ctx.cfg.nocache = 1;
365 } 367 }
366 if (!strncmp(argv[i], "--query=", 8)) { 368 if (!strncmp(argv[i], "--query=", 8)) {
367 ctx.qry.raw = xstrdup(argv[i]+8); 369 ctx.qry.raw = xstrdup(argv[i]+8);
368 } 370 }
369 if (!strncmp(argv[i], "--repo=", 7)) { 371 if (!strncmp(argv[i], "--repo=", 7)) {
370 ctx.qry.repo = xstrdup(argv[i]+7); 372 ctx.qry.repo = xstrdup(argv[i]+7);
371 } 373 }
372 if (!strncmp(argv[i], "--page=", 7)) { 374 if (!strncmp(argv[i], "--page=", 7)) {
373 ctx.qry.page = xstrdup(argv[i]+7); 375 ctx.qry.page = xstrdup(argv[i]+7);
374 } 376 }
375 if (!strncmp(argv[i], "--head=", 7)) { 377 if (!strncmp(argv[i], "--head=", 7)) {
376 ctx.qry.head = xstrdup(argv[i]+7); 378 ctx.qry.head = xstrdup(argv[i]+7);
377 ctx.qry.has_symref = 1; 379 ctx.qry.has_symref = 1;
378 } 380 }
379 if (!strncmp(argv[i], "--sha1=", 7)) { 381 if (!strncmp(argv[i], "--sha1=", 7)) {
380 ctx.qry.sha1 = xstrdup(argv[i]+7); 382 ctx.qry.sha1 = xstrdup(argv[i]+7);
381 ctx.qry.has_sha1 = 1; 383 ctx.qry.has_sha1 = 1;
382 } 384 }
383 if (!strncmp(argv[i], "--ofs=", 6)) { 385 if (!strncmp(argv[i], "--ofs=", 6)) {
384 ctx.qry.ofs = atoi(argv[i]+6); 386 ctx.qry.ofs = atoi(argv[i]+6);
385 } 387 }
386 if (!strncmp(argv[i], "--scan-tree=", 12)) { 388 if (!strncmp(argv[i], "--scan-tree=", 12)) {
387 scan++; 389 scan++;
388 scan_tree(argv[i] + 12); 390 scan_tree(argv[i] + 12);
389 } 391 }
390 } 392 }
391 if (scan) { 393 if (scan) {
392 qsort(cgit_repolist.repos, cgit_repolist.count, 394 qsort(cgit_repolist.repos, cgit_repolist.count,
393 sizeof(struct cgit_repo), cmp_repos); 395 sizeof(struct cgit_repo), cmp_repos);
394 print_repolist(&cgit_repolist); 396 print_repolist(&cgit_repolist);
395 exit(0); 397 exit(0);
396 } 398 }
397} 399}
398 400
399static int calc_ttl() 401static int calc_ttl()
400{ 402{
401 if (!ctx.repo) 403 if (!ctx.repo)
402 return ctx.cfg.cache_root_ttl; 404 return ctx.cfg.cache_root_ttl;
403 405
404 if (!ctx.qry.page) 406 if (!ctx.qry.page)
405 return ctx.cfg.cache_repo_ttl; 407 return ctx.cfg.cache_repo_ttl;
406 408
407 if (ctx.qry.has_symref) 409 if (ctx.qry.has_symref)
408 return ctx.cfg.cache_dynamic_ttl; 410 return ctx.cfg.cache_dynamic_ttl;
409 411
410 if (ctx.qry.has_sha1) 412 if (ctx.qry.has_sha1)
411 return ctx.cfg.cache_static_ttl; 413 return ctx.cfg.cache_static_ttl;
412 414
413 return ctx.cfg.cache_repo_ttl; 415 return ctx.cfg.cache_repo_ttl;
414} 416}
415 417
416int main(int argc, const char **argv) 418int main(int argc, const char **argv)
417{ 419{
418 const char *cgit_config_env = getenv("CGIT_CONFIG"); 420 const char *cgit_config_env = getenv("CGIT_CONFIG");
419 const char *path; 421 const char *path;
420 char *qry; 422 char *qry;
421 int err, ttl; 423 int err, ttl;
422 424
423 prepare_context(&ctx); 425 prepare_context(&ctx);
424 cgit_repolist.length = 0; 426 cgit_repolist.length = 0;
425 cgit_repolist.count = 0; 427 cgit_repolist.count = 0;
426 cgit_repolist.repos = NULL; 428 cgit_repolist.repos = NULL;
427 429
428 if (getenv("SCRIPT_NAME")) 430 if (getenv("SCRIPT_NAME"))
429 ctx.cfg.script_name = xstrdup(getenv("SCRIPT_NAME")); 431 ctx.cfg.script_name = xstrdup(getenv("SCRIPT_NAME"));
430 if (getenv("QUERY_STRING")) 432 if (getenv("QUERY_STRING"))
431 ctx.qry.raw = xstrdup(getenv("QUERY_STRING")); 433 ctx.qry.raw = xstrdup(getenv("QUERY_STRING"));
432 cgit_parse_args(argc, argv); 434 cgit_parse_args(argc, argv);
433 parse_configfile(cgit_config_env ? cgit_config_env : CGIT_CONFIG, 435 parse_configfile(cgit_config_env ? cgit_config_env : CGIT_CONFIG,
434 config_cb); 436 config_cb);
435 ctx.repo = NULL; 437 ctx.repo = NULL;
436 http_parse_querystring(ctx.qry.raw, querystring_cb); 438 http_parse_querystring(ctx.qry.raw, querystring_cb);
437 439
438 /* If virtual-root isn't specified in cgitrc and no url 440 /* If virtual-root isn't specified in cgitrc and no url
439 * parameter is specified on the querystring, lets pretend 441 * parameter is specified on the querystring, lets pretend
440 * that virtualroot equals SCRIPT_NAME and use PATH_INFO as 442 * that virtualroot equals SCRIPT_NAME and use PATH_INFO as
441 * url. This allows cgit to work with virtual urls without 443 * url. This allows cgit to work with virtual urls without
442 * the need for rewriterules in the webserver (as long as 444 * the need for rewriterules in the webserver (as long as
443 * PATH_INFO is included in the cache lookup key). 445 * PATH_INFO is included in the cache lookup key).
444 */ 446 */
445 if (!ctx.cfg.virtual_root && !ctx.qry.url) { 447 if (!ctx.cfg.virtual_root && !ctx.qry.url) {
446 ctx.cfg.virtual_root = ctx.cfg.script_name; 448 ctx.cfg.virtual_root = ctx.cfg.script_name;
447 path = getenv("PATH_INFO"); 449 path = getenv("PATH_INFO");
448 if (path) { 450 if (path) {
449 if (path[0] == '/') 451 if (path[0] == '/')
450 path++; 452 path++;
451 ctx.qry.url = xstrdup(path); 453 ctx.qry.url = xstrdup(path);
452 if (ctx.qry.raw) { 454 if (ctx.qry.raw) {
453 qry = ctx.qry.raw; 455 qry = ctx.qry.raw;
454 ctx.qry.raw = xstrdup(fmt("%s?%s", path, qry)); 456 ctx.qry.raw = xstrdup(fmt("%s?%s", path, qry));
455 free(qry); 457 free(qry);
456 } else 458 } else
457 ctx.qry.raw = ctx.qry.url; 459 ctx.qry.raw = ctx.qry.url;
458 cgit_parse_url(ctx.qry.url); 460 cgit_parse_url(ctx.qry.url);
459 } 461 }
460 } 462 }
461 463
462 ttl = calc_ttl(); 464 ttl = calc_ttl();
463 ctx.page.expires += ttl*60; 465 ctx.page.expires += ttl*60;
464 if (ctx.cfg.nocache) 466 if (ctx.cfg.nocache)
465 ctx.cfg.cache_size = 0; 467 ctx.cfg.cache_size = 0;
466 err = cache_process(ctx.cfg.cache_size, ctx.cfg.cache_root, 468 err = cache_process(ctx.cfg.cache_size, ctx.cfg.cache_root,
467 ctx.qry.raw, ttl, process_request, &ctx); 469 ctx.qry.raw, ttl, process_request, &ctx);
468 if (err) 470 if (err)
469 cgit_print_error(fmt("Error processing page: %s (%d)", 471 cgit_print_error(fmt("Error processing page: %s (%d)",
470 strerror(err), err)); 472 strerror(err), err));
471 return err; 473 return err;
472} 474}
diff --git a/cgit.h b/cgit.h
index 91db98a..aab898b 100644
--- a/cgit.h
+++ b/cgit.h
@@ -1,243 +1,244 @@
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 <xdiff/xdiff.h> 18#include <xdiff/xdiff.h>
19#include <utf8.h> 19#include <utf8.h>
20 20
21 21
22/* 22/*
23 * Dateformats used on misc. pages 23 * Dateformats used on misc. pages
24 */ 24 */
25#define FMT_LONGDATE "%Y-%m-%d %H:%M:%S (%Z)" 25#define FMT_LONGDATE "%Y-%m-%d %H:%M:%S (%Z)"
26#define FMT_SHORTDATE "%Y-%m-%d" 26#define FMT_SHORTDATE "%Y-%m-%d"
27#define FMT_ATOMDATE "%Y-%m-%dT%H:%M:%SZ" 27#define FMT_ATOMDATE "%Y-%m-%dT%H:%M:%SZ"
28 28
29 29
30/* 30/*
31 * Limits used for relative dates 31 * Limits used for relative dates
32 */ 32 */
33#define TM_MIN 60 33#define TM_MIN 60
34#define TM_HOUR (TM_MIN * 60) 34#define TM_HOUR (TM_MIN * 60)
35#define TM_DAY (TM_HOUR * 24) 35#define TM_DAY (TM_HOUR * 24)
36#define TM_WEEK (TM_DAY * 7) 36#define TM_WEEK (TM_DAY * 7)
37#define TM_YEAR (TM_DAY * 365) 37#define TM_YEAR (TM_DAY * 365)
38#define TM_MONTH (TM_YEAR / 12.0) 38#define TM_MONTH (TM_YEAR / 12.0)
39 39
40 40
41/* 41/*
42 * Default encoding 42 * Default encoding
43 */ 43 */
44#define PAGE_ENCODING "UTF-8" 44#define PAGE_ENCODING "UTF-8"
45 45
46typedef void (*configfn)(const char *name, const char *value); 46typedef void (*configfn)(const char *name, const char *value);
47typedef void (*filepair_fn)(struct diff_filepair *pair); 47typedef void (*filepair_fn)(struct diff_filepair *pair);
48typedef void (*linediff_fn)(char *line, int len); 48typedef void (*linediff_fn)(char *line, int len);
49 49
50struct cgit_repo { 50struct cgit_repo {
51 char *url; 51 char *url;
52 char *name; 52 char *name;
53 char *path; 53 char *path;
54 char *desc; 54 char *desc;
55 char *owner; 55 char *owner;
56 char *defbranch; 56 char *defbranch;
57 char *group; 57 char *group;
58 char *module_link; 58 char *module_link;
59 char *readme; 59 char *readme;
60 char *clone_url; 60 char *clone_url;
61 int snapshots; 61 int snapshots;
62 int enable_log_filecount; 62 int enable_log_filecount;
63 int enable_log_linecount; 63 int enable_log_linecount;
64}; 64};
65 65
66struct cgit_repolist { 66struct cgit_repolist {
67 int length; 67 int length;
68 int count; 68 int count;
69 struct cgit_repo *repos; 69 struct cgit_repo *repos;
70}; 70};
71 71
72struct commitinfo { 72struct commitinfo {
73 struct commit *commit; 73 struct commit *commit;
74 char *author; 74 char *author;
75 char *author_email; 75 char *author_email;
76 unsigned long author_date; 76 unsigned long author_date;
77 char *committer; 77 char *committer;
78 char *committer_email; 78 char *committer_email;
79 unsigned long committer_date; 79 unsigned long committer_date;
80 char *subject; 80 char *subject;
81 char *msg; 81 char *msg;
82 char *msg_encoding; 82 char *msg_encoding;
83}; 83};
84 84
85struct taginfo { 85struct taginfo {
86 char *tagger; 86 char *tagger;
87 char *tagger_email; 87 char *tagger_email;
88 unsigned long tagger_date; 88 unsigned long tagger_date;
89 char *msg; 89 char *msg;
90}; 90};
91 91
92struct refinfo { 92struct refinfo {
93 const char *refname; 93 const char *refname;
94 struct object *object; 94 struct object *object;
95 union { 95 union {
96 struct taginfo *tag; 96 struct taginfo *tag;
97 struct commitinfo *commit; 97 struct commitinfo *commit;
98 }; 98 };
99}; 99};
100 100
101struct reflist { 101struct reflist {
102 struct refinfo **refs; 102 struct refinfo **refs;
103 int alloc; 103 int alloc;
104 int count; 104 int count;
105}; 105};
106 106
107struct cgit_query { 107struct cgit_query {
108 int has_symref; 108 int has_symref;
109 int has_sha1; 109 int has_sha1;
110 char *raw; 110 char *raw;
111 char *repo; 111 char *repo;
112 char *page; 112 char *page;
113 char *search; 113 char *search;
114 char *grep; 114 char *grep;
115 char *head; 115 char *head;
116 char *sha1; 116 char *sha1;
117 char *sha2; 117 char *sha2;
118 char *path; 118 char *path;
119 char *name; 119 char *name;
120 char *mimetype; 120 char *mimetype;
121 char *url; 121 char *url;
122 int ofs; 122 int ofs;
123 int nohead; 123 int nohead;
124 int showmsg;
124}; 125};
125 126
126struct cgit_config { 127struct cgit_config {
127 char *agefile; 128 char *agefile;
128 char *cache_root; 129 char *cache_root;
129 char *clone_prefix; 130 char *clone_prefix;
130 char *css; 131 char *css;
131 char *favicon; 132 char *favicon;
132 char *footer; 133 char *footer;
133 char *index_header; 134 char *index_header;
134 char *index_info; 135 char *index_info;
135 char *logo; 136 char *logo;
136 char *logo_link; 137 char *logo_link;
137 char *module_link; 138 char *module_link;
138 char *repo_group; 139 char *repo_group;
139 char *robots; 140 char *robots;
140 char *root_title; 141 char *root_title;
141 char *root_desc; 142 char *root_desc;
142 char *root_readme; 143 char *root_readme;
143 char *script_name; 144 char *script_name;
144 char *virtual_root; 145 char *virtual_root;
145 int cache_size; 146 int cache_size;
146 int cache_dynamic_ttl; 147 int cache_dynamic_ttl;
147 int cache_max_create_time; 148 int cache_max_create_time;
148 int cache_repo_ttl; 149 int cache_repo_ttl;
149 int cache_root_ttl; 150 int cache_root_ttl;
150 int cache_static_ttl; 151 int cache_static_ttl;
151 int enable_index_links; 152 int enable_index_links;
152 int enable_log_filecount; 153 int enable_log_filecount;
153 int enable_log_linecount; 154 int enable_log_linecount;
154 int local_time; 155 int local_time;
155 int max_repo_count; 156 int max_repo_count;
156 int max_commit_count; 157 int max_commit_count;
157 int max_lock_attempts; 158 int max_lock_attempts;
158 int max_msg_len; 159 int max_msg_len;
159 int max_repodesc_len; 160 int max_repodesc_len;
160 int nocache; 161 int nocache;
161 int renamelimit; 162 int renamelimit;
162 int snapshots; 163 int snapshots;
163 int summary_branches; 164 int summary_branches;
164 int summary_log; 165 int summary_log;
165 int summary_tags; 166 int summary_tags;
166}; 167};
167 168
168struct cgit_page { 169struct cgit_page {
169 time_t modified; 170 time_t modified;
170 time_t expires; 171 time_t expires;
171 size_t size; 172 size_t size;
172 char *mimetype; 173 char *mimetype;
173 char *charset; 174 char *charset;
174 char *filename; 175 char *filename;
175 char *title; 176 char *title;
176}; 177};
177 178
178struct cgit_context { 179struct cgit_context {
179 struct cgit_query qry; 180 struct cgit_query qry;
180 struct cgit_config cfg; 181 struct cgit_config cfg;
181 struct cgit_repo *repo; 182 struct cgit_repo *repo;
182 struct cgit_page page; 183 struct cgit_page page;
183}; 184};
184 185
185struct cgit_snapshot_format { 186struct cgit_snapshot_format {
186 const char *suffix; 187 const char *suffix;
187 const char *mimetype; 188 const char *mimetype;
188 write_archive_fn_t write_func; 189 write_archive_fn_t write_func;
189 int bit; 190 int bit;
190}; 191};
191 192
192extern const char *cgit_version; 193extern const char *cgit_version;
193 194
194extern struct cgit_repolist cgit_repolist; 195extern struct cgit_repolist cgit_repolist;
195extern struct cgit_context ctx; 196extern struct cgit_context ctx;
196extern const struct cgit_snapshot_format cgit_snapshot_formats[]; 197extern const struct cgit_snapshot_format cgit_snapshot_formats[];
197 198
198extern struct cgit_repo *cgit_add_repo(const char *url); 199extern struct cgit_repo *cgit_add_repo(const char *url);
199extern struct cgit_repo *cgit_get_repoinfo(const char *url); 200extern struct cgit_repo *cgit_get_repoinfo(const char *url);
200extern void cgit_repo_config_cb(const char *name, const char *value); 201extern void cgit_repo_config_cb(const char *name, const char *value);
201 202
202extern int chk_zero(int result, char *msg); 203extern int chk_zero(int result, char *msg);
203extern int chk_positive(int result, char *msg); 204extern int chk_positive(int result, char *msg);
204extern int chk_non_negative(int result, char *msg); 205extern int chk_non_negative(int result, char *msg);
205 206
206extern char *trim_end(const char *str, char c); 207extern char *trim_end(const char *str, char c);
207extern char *strlpart(char *txt, int maxlen); 208extern char *strlpart(char *txt, int maxlen);
208extern char *strrpart(char *txt, int maxlen); 209extern char *strrpart(char *txt, int maxlen);
209 210
210extern void cgit_add_ref(struct reflist *list, struct refinfo *ref); 211extern void cgit_add_ref(struct reflist *list, struct refinfo *ref);
211extern int cgit_refs_cb(const char *refname, const unsigned char *sha1, 212extern int cgit_refs_cb(const char *refname, const unsigned char *sha1,
212 int flags, void *cb_data); 213 int flags, void *cb_data);
213 214
214extern void *cgit_free_commitinfo(struct commitinfo *info); 215extern void *cgit_free_commitinfo(struct commitinfo *info);
215 216
216extern int cgit_diff_files(const unsigned char *old_sha1, 217extern int cgit_diff_files(const unsigned char *old_sha1,
217 const unsigned char *new_sha1, 218 const unsigned char *new_sha1,
218 linediff_fn fn); 219 linediff_fn fn);
219 220
220extern void cgit_diff_tree(const unsigned char *old_sha1, 221extern void cgit_diff_tree(const unsigned char *old_sha1,
221 const unsigned char *new_sha1, 222 const unsigned char *new_sha1,
222 filepair_fn fn, const char *prefix); 223 filepair_fn fn, const char *prefix);
223 224
224extern void cgit_diff_commit(struct commit *commit, filepair_fn fn); 225extern void cgit_diff_commit(struct commit *commit, filepair_fn fn);
225 226
226extern char *fmt(const char *format,...); 227extern char *fmt(const char *format,...);
227 228
228extern struct commitinfo *cgit_parse_commit(struct commit *commit); 229extern struct commitinfo *cgit_parse_commit(struct commit *commit);
229extern struct taginfo *cgit_parse_tag(struct tag *tag); 230extern struct taginfo *cgit_parse_tag(struct tag *tag);
230extern void cgit_parse_url(const char *url); 231extern void cgit_parse_url(const char *url);
231 232
232extern const char *cgit_repobasename(const char *reponame); 233extern const char *cgit_repobasename(const char *reponame);
233 234
234extern int cgit_parse_snapshots_mask(const char *str); 235extern int cgit_parse_snapshots_mask(const char *str);
235 236
236/* libgit.a either links against or compiles its own implementation of 237/* libgit.a either links against or compiles its own implementation of
237 * strcasestr(), and we'd like to reuse it. Simply re-declaring it 238 * strcasestr(), and we'd like to reuse it. Simply re-declaring it
238 * seems to do the trick. 239 * seems to do the trick.
239 */ 240 */
240extern char *strcasestr(const char *haystack, const char *needle); 241extern char *strcasestr(const char *haystack, const char *needle);
241 242
242 243
243#endif /* CGIT_H */ 244#endif /* CGIT_H */
diff --git a/ui-log.c b/ui-log.c
index 8dd8b89..631e46d 100644
--- a/ui-log.c
+++ b/ui-log.c
@@ -1,155 +1,177 @@
1/* ui-log.c: functions for log output 1/* ui-log.c: functions for log 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 "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
13int files, add_lines, rem_lines; 13int files, add_lines, rem_lines;
14 14
15void count_lines(char *line, int size) 15void count_lines(char *line, int size)
16{ 16{
17 if (size <= 0) 17 if (size <= 0)
18 return; 18 return;
19 19
20 if (line[0] == '+') 20 if (line[0] == '+')
21 add_lines++; 21 add_lines++;
22 22
23 else if (line[0] == '-') 23 else if (line[0] == '-')
24 rem_lines++; 24 rem_lines++;
25} 25}
26 26
27void inspect_files(struct diff_filepair *pair) 27void inspect_files(struct diff_filepair *pair)
28{ 28{
29 files++; 29 files++;
30 if (ctx.repo->enable_log_linecount) 30 if (ctx.repo->enable_log_linecount)
31 cgit_diff_files(pair->one->sha1, pair->two->sha1, count_lines); 31 cgit_diff_files(pair->one->sha1, pair->two->sha1, count_lines);
32} 32}
33 33
34void print_commit(struct commit *commit) 34void print_commit(struct commit *commit)
35{ 35{
36 struct commitinfo *info; 36 struct commitinfo *info;
37 char *tmp; 37 char *tmp;
38 38
39 info = cgit_parse_commit(commit); 39 info = cgit_parse_commit(commit);
40 html("<tr><td>"); 40 html("<tr><td>");
41 tmp = fmt("id=%s", sha1_to_hex(commit->object.sha1)); 41 tmp = fmt("id=%s", sha1_to_hex(commit->object.sha1));
42 tmp = cgit_pageurl(ctx.repo->url, "commit", tmp); 42 tmp = cgit_pageurl(ctx.repo->url, "commit", tmp);
43 html_link_open(tmp, NULL, NULL); 43 html_link_open(tmp, NULL, NULL);
44 cgit_print_age(commit->date, TM_WEEK * 2, FMT_SHORTDATE); 44 cgit_print_age(commit->date, TM_WEEK * 2, FMT_SHORTDATE);
45 html_link_close(); 45 html_link_close();
46 html("</td><td>"); 46 html("</td><td>");
47 if (ctx.qry.showmsg)
48 html("<u>");
47 cgit_commit_link(info->subject, NULL, NULL, ctx.qry.head, 49 cgit_commit_link(info->subject, NULL, NULL, ctx.qry.head,
48 sha1_to_hex(commit->object.sha1)); 50 sha1_to_hex(commit->object.sha1));
51 if (ctx.qry.showmsg)
52 html("</u>");
49 html("</td><td>"); 53 html("</td><td>");
50 html_txt(info->author); 54 html_txt(info->author);
51 if (ctx.repo->enable_log_filecount) { 55 if (ctx.repo->enable_log_filecount) {
52 files = 0; 56 files = 0;
53 add_lines = 0; 57 add_lines = 0;
54 rem_lines = 0; 58 rem_lines = 0;
55 cgit_diff_commit(commit, inspect_files); 59 cgit_diff_commit(commit, inspect_files);
56 html("</td><td>"); 60 html("</td><td>");
57 htmlf("%d", files); 61 htmlf("%d", files);
58 if (ctx.repo->enable_log_linecount) { 62 if (ctx.repo->enable_log_linecount) {
59 html("</td><td>"); 63 html("</td><td>");
60 htmlf("-%d/+%d", rem_lines, add_lines); 64 htmlf("-%d/+%d", rem_lines, add_lines);
61 } 65 }
62 } 66 }
63 html("</td></tr>\n"); 67 html("</td></tr>\n");
68 if (ctx.qry.showmsg) {
69 html("<tr class='nohover'><td></td><td><div class='commit-msg'>");
70 html_txt(info->msg);
71 html("</div><br/></td><td></td>");
72 if (ctx.repo->enable_log_filecount) {
73 html("<td></td>");
74 if (ctx.repo->enable_log_linecount)
75 html("<td></td>");
76 }
77 html("</tr>\n");
78 }
64 cgit_free_commitinfo(info); 79 cgit_free_commitinfo(info);
65} 80}
66 81
67 82
68void cgit_print_log(const char *tip, int ofs, int cnt, char *grep, char *pattern, 83void cgit_print_log(const char *tip, int ofs, int cnt, char *grep, char *pattern,
69 char *path, int pager) 84 char *path, int pager)
70{ 85{
71 struct rev_info rev; 86 struct rev_info rev;
72 struct commit *commit; 87 struct commit *commit;
73 const char *argv[] = {NULL, tip, NULL, NULL, NULL}; 88 const char *argv[] = {NULL, tip, NULL, NULL, NULL};
74 int argc = 2; 89 int argc = 2;
75 int i, columns = 3; 90 int i, columns = 3;
76 91
77 if (!tip) 92 if (!tip)
78 argv[1] = ctx.qry.head; 93 argv[1] = ctx.qry.head;
79 94
80 if (grep && pattern && (!strcmp(grep, "grep") || 95 if (grep && pattern && (!strcmp(grep, "grep") ||
81 !strcmp(grep, "author") || 96 !strcmp(grep, "author") ||
82 !strcmp(grep, "committer"))) 97 !strcmp(grep, "committer")))
83 argv[argc++] = fmt("--%s=%s", grep, pattern); 98 argv[argc++] = fmt("--%s=%s", grep, pattern);
84 99
85 if (path) { 100 if (path) {
86 argv[argc++] = "--"; 101 argv[argc++] = "--";
87 argv[argc++] = path; 102 argv[argc++] = path;
88 } 103 }
89 init_revisions(&rev, NULL); 104 init_revisions(&rev, NULL);
90 rev.abbrev = DEFAULT_ABBREV; 105 rev.abbrev = DEFAULT_ABBREV;
91 rev.commit_format = CMIT_FMT_DEFAULT; 106 rev.commit_format = CMIT_FMT_DEFAULT;
92 rev.verbose_header = 1; 107 rev.verbose_header = 1;
93 rev.show_root_diff = 0; 108 rev.show_root_diff = 0;
94 setup_revisions(argc, argv, &rev, NULL); 109 setup_revisions(argc, argv, &rev, NULL);
95 rev.grep_filter.regflags |= REG_ICASE; 110 rev.grep_filter.regflags |= REG_ICASE;
96 compile_grep_patterns(&rev.grep_filter); 111 compile_grep_patterns(&rev.grep_filter);
97 prepare_revision_walk(&rev); 112 prepare_revision_walk(&rev);
98 113
99 if (pager) 114 if (pager)
100 html("<table class='list nowrap'>"); 115 html("<table class='list nowrap'>");
101 116
102 html("<tr class='nohover'><th class='left'>Age</th>" 117 html("<tr class='nohover'><th class='left'>Age</th>"
103 "<th class='left'>Commit message</th>" 118 "<th class='left'>Commit message");
104 "<th class='left'>Author</th>"); 119 if (pager) {
120 html(" (");
121 cgit_log_link("toggle", NULL, NULL, ctx.qry.head, ctx.qry.sha1,
122 ctx.qry.path, ctx.qry.ofs, ctx.qry.grep,
123 ctx.qry.search, ctx.qry.showmsg ? 0 : 1);
124 html(")");
125 }
126 html("</th><th class='left'>Author</th>");
105 if (ctx.repo->enable_log_filecount) { 127 if (ctx.repo->enable_log_filecount) {
106 html("<th class='left'>Files</th>"); 128 html("<th class='left'>Files</th>");
107 columns++; 129 columns++;
108 if (ctx.repo->enable_log_linecount) { 130 if (ctx.repo->enable_log_linecount) {
109 html("<th class='left'>Lines</th>"); 131 html("<th class='left'>Lines</th>");
110 columns++; 132 columns++;
111 } 133 }
112 } 134 }
113 html("</tr>\n"); 135 html("</tr>\n");
114 136
115 if (ofs<0) 137 if (ofs<0)
116 ofs = 0; 138 ofs = 0;
117 139
118 for (i = 0; i < ofs && (commit = get_revision(&rev)) != NULL; i++) { 140 for (i = 0; i < ofs && (commit = get_revision(&rev)) != NULL; i++) {
119 free(commit->buffer); 141 free(commit->buffer);
120 commit->buffer = NULL; 142 commit->buffer = NULL;
121 free_commit_list(commit->parents); 143 free_commit_list(commit->parents);
122 commit->parents = NULL; 144 commit->parents = NULL;
123 } 145 }
124 146
125 for (i = 0; i < cnt && (commit = get_revision(&rev)) != NULL; i++) { 147 for (i = 0; i < cnt && (commit = get_revision(&rev)) != NULL; i++) {
126 print_commit(commit); 148 print_commit(commit);
127 free(commit->buffer); 149 free(commit->buffer);
128 commit->buffer = NULL; 150 commit->buffer = NULL;
129 free_commit_list(commit->parents); 151 free_commit_list(commit->parents);
130 commit->parents = NULL; 152 commit->parents = NULL;
131 } 153 }
132 if (pager) { 154 if (pager) {
133 htmlf("</table><div class='pager'>", 155 htmlf("</table><div class='pager'>",
134 columns); 156 columns);
135 if (ofs > 0) { 157 if (ofs > 0) {
136 cgit_log_link("[prev]", NULL, NULL, ctx.qry.head, 158 cgit_log_link("[prev]", NULL, NULL, ctx.qry.head,
137 ctx.qry.sha1, ctx.qry.path, 159 ctx.qry.sha1, ctx.qry.path,
138 ofs - cnt, ctx.qry.grep, 160 ofs - cnt, ctx.qry.grep,
139 ctx.qry.search); 161 ctx.qry.search, ctx.qry.showmsg);
140 html("&nbsp;"); 162 html("&nbsp;");
141 } 163 }
142 if ((commit = get_revision(&rev)) != NULL) { 164 if ((commit = get_revision(&rev)) != NULL) {
143 cgit_log_link("[next]", NULL, NULL, ctx.qry.head, 165 cgit_log_link("[next]", NULL, NULL, ctx.qry.head,
144 ctx.qry.sha1, ctx.qry.path, 166 ctx.qry.sha1, ctx.qry.path,
145 ofs + cnt, ctx.qry.grep, 167 ofs + cnt, ctx.qry.grep,
146 ctx.qry.search); 168 ctx.qry.search, ctx.qry.showmsg);
147 } 169 }
148 html("</div>"); 170 html("</div>");
149 } else if ((commit = get_revision(&rev)) != NULL) { 171 } else if ((commit = get_revision(&rev)) != NULL) {
150 html("<tr class='nohover'><td colspan='3'>"); 172 html("<tr class='nohover'><td colspan='3'>");
151 cgit_log_link("[...]", NULL, NULL, ctx.qry.head, NULL, NULL, 0, 173 cgit_log_link("[...]", NULL, NULL, ctx.qry.head, NULL, NULL, 0,
152 NULL, NULL); 174 NULL, NULL, ctx.qry.showmsg);
153 html("</td></tr>\n"); 175 html("</td></tr>\n");
154 } 176 }
155} 177}
diff --git a/ui-refs.c b/ui-refs.c
index 32e0429..7eb16d5 100644
--- a/ui-refs.c
+++ b/ui-refs.c
@@ -1,196 +1,197 @@
1/* ui-refs.c: browse symbolic refs 1/* ui-refs.c: browse symbolic refs
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 12
13static int header; 13static int header;
14 14
15static int cmp_age(int age1, int age2) 15static int cmp_age(int age1, int age2)
16{ 16{
17 if (age1 != 0 && age2 != 0) 17 if (age1 != 0 && age2 != 0)
18 return age2 - age1; 18 return age2 - age1;
19 19
20 if (age1 == 0 && age2 == 0) 20 if (age1 == 0 && age2 == 0)
21 return 0; 21 return 0;
22 22
23 if (age1 == 0) 23 if (age1 == 0)
24 return +1; 24 return +1;
25 25
26 return -1; 26 return -1;
27} 27}
28 28
29static int cmp_ref_name(const void *a, const void *b) 29static int cmp_ref_name(const void *a, const void *b)
30{ 30{
31 struct refinfo *r1 = *(struct refinfo **)a; 31 struct refinfo *r1 = *(struct refinfo **)a;
32 struct refinfo *r2 = *(struct refinfo **)b; 32 struct refinfo *r2 = *(struct refinfo **)b;
33 33
34 return strcmp(r1->refname, r2->refname); 34 return strcmp(r1->refname, r2->refname);
35} 35}
36 36
37static int cmp_branch_age(const void *a, const void *b) 37static int cmp_branch_age(const void *a, const void *b)
38{ 38{
39 struct refinfo *r1 = *(struct refinfo **)a; 39 struct refinfo *r1 = *(struct refinfo **)a;
40 struct refinfo *r2 = *(struct refinfo **)b; 40 struct refinfo *r2 = *(struct refinfo **)b;
41 41
42 return cmp_age(r1->commit->committer_date, r2->commit->committer_date); 42 return cmp_age(r1->commit->committer_date, r2->commit->committer_date);
43} 43}
44 44
45static int cmp_tag_age(const void *a, const void *b) 45static int cmp_tag_age(const void *a, const void *b)
46{ 46{
47 struct refinfo *r1 = *(struct refinfo **)a; 47 struct refinfo *r1 = *(struct refinfo **)a;
48 struct refinfo *r2 = *(struct refinfo **)b; 48 struct refinfo *r2 = *(struct refinfo **)b;
49 49
50 return cmp_age(r1->tag->tagger_date, r2->tag->tagger_date); 50 return cmp_age(r1->tag->tagger_date, r2->tag->tagger_date);
51} 51}
52 52
53static int print_branch(struct refinfo *ref) 53static int print_branch(struct refinfo *ref)
54{ 54{
55 struct commitinfo *info = ref->commit; 55 struct commitinfo *info = ref->commit;
56 char *name = (char *)ref->refname; 56 char *name = (char *)ref->refname;
57 57
58 if (!info) 58 if (!info)
59 return 1; 59 return 1;
60 html("<tr><td>"); 60 html("<tr><td>");
61 cgit_log_link(name, NULL, NULL, name, NULL, NULL, 0, NULL, NULL); 61 cgit_log_link(name, NULL, NULL, name, NULL, NULL, 0, NULL, NULL,
62 ctx.qry.showmsg);
62 html("</td><td>"); 63 html("</td><td>");
63 64
64 if (ref->object->type == OBJ_COMMIT) { 65 if (ref->object->type == OBJ_COMMIT) {
65 cgit_commit_link(info->subject, NULL, NULL, name, NULL); 66 cgit_commit_link(info->subject, NULL, NULL, name, NULL);
66 html("</td><td>"); 67 html("</td><td>");
67 html_txt(info->author); 68 html_txt(info->author);
68 html("</td><td colspan='2'>"); 69 html("</td><td colspan='2'>");
69 cgit_print_age(info->commit->date, -1, NULL); 70 cgit_print_age(info->commit->date, -1, NULL);
70 } else { 71 } else {
71 html("</td><td></td><td>"); 72 html("</td><td></td><td>");
72 cgit_object_link(ref->object); 73 cgit_object_link(ref->object);
73 } 74 }
74 html("</td></tr>\n"); 75 html("</td></tr>\n");
75 return 0; 76 return 0;
76} 77}
77 78
78static void print_tag_header() 79static void print_tag_header()
79{ 80{
80 html("<tr class='nohover'><th class='left'>Tag</th>" 81 html("<tr class='nohover'><th class='left'>Tag</th>"
81 "<th class='left'>Reference</th>" 82 "<th class='left'>Reference</th>"
82 "<th class='left'>Author</th>" 83 "<th class='left'>Author</th>"
83 "<th class='left' colspan='2'>Age</th></tr>\n"); 84 "<th class='left' colspan='2'>Age</th></tr>\n");
84 header = 1; 85 header = 1;
85} 86}
86 87
87static int print_tag(struct refinfo *ref) 88static int print_tag(struct refinfo *ref)
88{ 89{
89 struct tag *tag; 90 struct tag *tag;
90 struct taginfo *info; 91 struct taginfo *info;
91 char *name = (char *)ref->refname; 92 char *name = (char *)ref->refname;
92 93
93 if (ref->object->type == OBJ_TAG) { 94 if (ref->object->type == OBJ_TAG) {
94 tag = (struct tag *)ref->object; 95 tag = (struct tag *)ref->object;
95 info = ref->tag; 96 info = ref->tag;
96 if (!tag || !info) 97 if (!tag || !info)
97 return 1; 98 return 1;
98 html("<tr><td>"); 99 html("<tr><td>");
99 cgit_tag_link(name, NULL, NULL, ctx.qry.head, name); 100 cgit_tag_link(name, NULL, NULL, ctx.qry.head, name);
100 html("</td><td>"); 101 html("</td><td>");
101 cgit_object_link(tag->tagged); 102 cgit_object_link(tag->tagged);
102 html("</td><td>"); 103 html("</td><td>");
103 if (info->tagger) 104 if (info->tagger)
104 html(info->tagger); 105 html(info->tagger);
105 html("</td><td colspan='2'>"); 106 html("</td><td colspan='2'>");
106 if (info->tagger_date > 0) 107 if (info->tagger_date > 0)
107 cgit_print_age(info->tagger_date, -1, NULL); 108 cgit_print_age(info->tagger_date, -1, NULL);
108 html("</td></tr>\n"); 109 html("</td></tr>\n");
109 } else { 110 } else {
110 if (!header) 111 if (!header)
111 print_tag_header(); 112 print_tag_header();
112 html("<tr><td>"); 113 html("<tr><td>");
113 html_txt(name); 114 html_txt(name);
114 html("</td><td>"); 115 html("</td><td>");
115 cgit_object_link(ref->object); 116 cgit_object_link(ref->object);
116 html("</td></tr>\n"); 117 html("</td></tr>\n");
117 } 118 }
118 return 0; 119 return 0;
119} 120}
120 121
121static void print_refs_link(char *path) 122static void print_refs_link(char *path)
122{ 123{
123 html("<tr class='nohover'><td colspan='4'>"); 124 html("<tr class='nohover'><td colspan='4'>");
124 cgit_refs_link("[...]", NULL, NULL, ctx.qry.head, NULL, path); 125 cgit_refs_link("[...]", NULL, NULL, ctx.qry.head, NULL, path);
125 html("</td></tr>"); 126 html("</td></tr>");
126} 127}
127 128
128void cgit_print_branches(int maxcount) 129void cgit_print_branches(int maxcount)
129{ 130{
130 struct reflist list; 131 struct reflist list;
131 int i; 132 int i;
132 133
133 html("<tr class='nohover'><th class='left'>Branch</th>" 134 html("<tr class='nohover'><th class='left'>Branch</th>"
134 "<th class='left'>Commit message</th>" 135 "<th class='left'>Commit message</th>"
135 "<th class='left'>Author</th>" 136 "<th class='left'>Author</th>"
136 "<th class='left' colspan='2'>Age</th></tr>\n"); 137 "<th class='left' colspan='2'>Age</th></tr>\n");
137 138
138 list.refs = NULL; 139 list.refs = NULL;
139 list.alloc = list.count = 0; 140 list.alloc = list.count = 0;
140 for_each_branch_ref(cgit_refs_cb, &list); 141 for_each_branch_ref(cgit_refs_cb, &list);
141 142
142 if (maxcount == 0 || maxcount > list.count) 143 if (maxcount == 0 || maxcount > list.count)
143 maxcount = list.count; 144 maxcount = list.count;
144 145
145 if (maxcount < list.count) { 146 if (maxcount < list.count) {
146 qsort(list.refs, list.count, sizeof(*list.refs), cmp_branch_age); 147 qsort(list.refs, list.count, sizeof(*list.refs), cmp_branch_age);
147 qsort(list.refs, maxcount, sizeof(*list.refs), cmp_ref_name); 148 qsort(list.refs, maxcount, sizeof(*list.refs), cmp_ref_name);
148 } 149 }
149 150
150 for(i=0; i<maxcount; i++) 151 for(i=0; i<maxcount; i++)
151 print_branch(list.refs[i]); 152 print_branch(list.refs[i]);
152 153
153 if (maxcount < list.count) 154 if (maxcount < list.count)
154 print_refs_link("heads"); 155 print_refs_link("heads");
155} 156}
156 157
157void cgit_print_tags(int maxcount) 158void cgit_print_tags(int maxcount)
158{ 159{
159 struct reflist list; 160 struct reflist list;
160 int i; 161 int i;
161 162
162 header = 0; 163 header = 0;
163 list.refs = NULL; 164 list.refs = NULL;
164 list.alloc = list.count = 0; 165 list.alloc = list.count = 0;
165 for_each_tag_ref(cgit_refs_cb, &list); 166 for_each_tag_ref(cgit_refs_cb, &list);
166 if (list.count == 0) 167 if (list.count == 0)
167 return; 168 return;
168 qsort(list.refs, list.count, sizeof(*list.refs), cmp_tag_age); 169 qsort(list.refs, list.count, sizeof(*list.refs), cmp_tag_age);
169 if (!maxcount) 170 if (!maxcount)
170 maxcount = list.count; 171 maxcount = list.count;
171 else if (maxcount > list.count) 172 else if (maxcount > list.count)
172 maxcount = list.count; 173 maxcount = list.count;
173 print_tag_header(); 174 print_tag_header();
174 for(i=0; i<maxcount; i++) 175 for(i=0; i<maxcount; i++)
175 print_tag(list.refs[i]); 176 print_tag(list.refs[i]);
176 177
177 if (maxcount < list.count) 178 if (maxcount < list.count)
178 print_refs_link("tags"); 179 print_refs_link("tags");
179} 180}
180 181
181void cgit_print_refs() 182void cgit_print_refs()
182{ 183{
183 184
184 html("<table class='list nowrap'>"); 185 html("<table class='list nowrap'>");
185 186
186 if (ctx.qry.path && !strncmp(ctx.qry.path, "heads", 5)) 187 if (ctx.qry.path && !strncmp(ctx.qry.path, "heads", 5))
187 cgit_print_branches(0); 188 cgit_print_branches(0);
188 else if (ctx.qry.path && !strncmp(ctx.qry.path, "tags", 4)) 189 else if (ctx.qry.path && !strncmp(ctx.qry.path, "tags", 4))
189 cgit_print_tags(0); 190 cgit_print_tags(0);
190 else { 191 else {
191 cgit_print_branches(0); 192 cgit_print_branches(0);
192 html("<tr class='nohover'><td colspan='4'>&nbsp;</td></tr>"); 193 html("<tr class='nohover'><td colspan='4'>&nbsp;</td></tr>");
193 cgit_print_tags(0); 194 cgit_print_tags(0);
194 } 195 }
195 html("</table>"); 196 html("</table>");
196} 197}
diff --git a/ui-repolist.c b/ui-repolist.c
index c23232c..5833140 100644
--- a/ui-repolist.c
+++ b/ui-repolist.c
@@ -1,167 +1,167 @@
1/* ui-repolist.c: functions for generating the repolist page 1/* ui-repolist.c: functions for generating the repolist page
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 <time.h> 9#include <time.h>
10 10
11#include "cgit.h" 11#include "cgit.h"
12#include "html.h" 12#include "html.h"
13#include "ui-shared.h" 13#include "ui-shared.h"
14 14
15time_t read_agefile(char *path) 15time_t read_agefile(char *path)
16{ 16{
17 FILE *f; 17 FILE *f;
18 static char buf[64], buf2[64]; 18 static char buf[64], buf2[64];
19 19
20 if (!(f = fopen(path, "r"))) 20 if (!(f = fopen(path, "r")))
21 return -1; 21 return -1;
22 if (fgets(buf, sizeof(buf), f) == NULL) 22 if (fgets(buf, sizeof(buf), f) == NULL)
23 return -1; 23 return -1;
24 fclose(f); 24 fclose(f);
25 if (parse_date(buf, buf2, sizeof(buf2))) 25 if (parse_date(buf, buf2, sizeof(buf2)))
26 return strtoul(buf2, NULL, 10); 26 return strtoul(buf2, NULL, 10);
27 else 27 else
28 return 0; 28 return 0;
29} 29}
30 30
31static void print_modtime(struct cgit_repo *repo) 31static void print_modtime(struct cgit_repo *repo)
32{ 32{
33 char *path; 33 char *path;
34 struct stat s; 34 struct stat s;
35 35
36 path = fmt("%s/%s", repo->path, ctx.cfg.agefile); 36 path = fmt("%s/%s", repo->path, ctx.cfg.agefile);
37 if (stat(path, &s) == 0) { 37 if (stat(path, &s) == 0) {
38 cgit_print_age(read_agefile(path), -1, NULL); 38 cgit_print_age(read_agefile(path), -1, NULL);
39 return; 39 return;
40 } 40 }
41 41
42 path = fmt("%s/refs/heads/%s", repo->path, repo->defbranch); 42 path = fmt("%s/refs/heads/%s", repo->path, repo->defbranch);
43 if (stat(path, &s) != 0) 43 if (stat(path, &s) != 0)
44 return; 44 return;
45 cgit_print_age(s.st_mtime, -1, NULL); 45 cgit_print_age(s.st_mtime, -1, NULL);
46} 46}
47 47
48int is_match(struct cgit_repo *repo) 48int is_match(struct cgit_repo *repo)
49{ 49{
50 if (!ctx.qry.search) 50 if (!ctx.qry.search)
51 return 1; 51 return 1;
52 if (repo->url && strcasestr(repo->url, ctx.qry.search)) 52 if (repo->url && strcasestr(repo->url, ctx.qry.search))
53 return 1; 53 return 1;
54 if (repo->name && strcasestr(repo->name, ctx.qry.search)) 54 if (repo->name && strcasestr(repo->name, ctx.qry.search))
55 return 1; 55 return 1;
56 if (repo->desc && strcasestr(repo->desc, ctx.qry.search)) 56 if (repo->desc && strcasestr(repo->desc, ctx.qry.search))
57 return 1; 57 return 1;
58 if (repo->owner && strcasestr(repo->owner, ctx.qry.search)) 58 if (repo->owner && strcasestr(repo->owner, ctx.qry.search))
59 return 1; 59 return 1;
60 return 0; 60 return 0;
61} 61}
62 62
63int is_in_url(struct cgit_repo *repo) 63int is_in_url(struct cgit_repo *repo)
64{ 64{
65 if (!ctx.qry.url) 65 if (!ctx.qry.url)
66 return 1; 66 return 1;
67 if (repo->url && !prefixcmp(repo->url, ctx.qry.url)) 67 if (repo->url && !prefixcmp(repo->url, ctx.qry.url))
68 return 1; 68 return 1;
69 return 0; 69 return 0;
70} 70}
71 71
72void print_header(int columns) 72void print_header(int columns)
73{ 73{
74 html("<tr class='nohover'>" 74 html("<tr class='nohover'>"
75 "<th class='left'>Name</th>" 75 "<th class='left'>Name</th>"
76 "<th class='left'>Description</th>" 76 "<th class='left'>Description</th>"
77 "<th class='left'>Owner</th>" 77 "<th class='left'>Owner</th>"
78 "<th class='left'>Idle</th>"); 78 "<th class='left'>Idle</th>");
79 if (ctx.cfg.enable_index_links) 79 if (ctx.cfg.enable_index_links)
80 html("<th class='left'>Links</th>"); 80 html("<th class='left'>Links</th>");
81 html("</tr>\n"); 81 html("</tr>\n");
82} 82}
83 83
84 84
85void print_pager(int items, int pagelen, char *search) 85void print_pager(int items, int pagelen, char *search)
86{ 86{
87 int i; 87 int i;
88 html("<div class='pager'>"); 88 html("<div class='pager'>");
89 for(i = 0; i * pagelen < items; i++) 89 for(i = 0; i * pagelen < items; i++)
90 cgit_index_link(fmt("[%d]", i+1), fmt("Page %d", i+1), NULL, 90 cgit_index_link(fmt("[%d]", i+1), fmt("Page %d", i+1), NULL,
91 search, i * pagelen); 91 search, i * pagelen);
92 html("</div>"); 92 html("</div>");
93} 93}
94 94
95void cgit_print_repolist() 95void cgit_print_repolist()
96{ 96{
97 int i, columns = 4, hits = 0, header = 0; 97 int i, columns = 4, hits = 0, header = 0;
98 char *last_group = NULL; 98 char *last_group = NULL;
99 99
100 if (ctx.cfg.enable_index_links) 100 if (ctx.cfg.enable_index_links)
101 columns++; 101 columns++;
102 102
103 ctx.page.title = ctx.cfg.root_title; 103 ctx.page.title = ctx.cfg.root_title;
104 cgit_print_http_headers(&ctx); 104 cgit_print_http_headers(&ctx);
105 cgit_print_docstart(&ctx); 105 cgit_print_docstart(&ctx);
106 cgit_print_pageheader(&ctx); 106 cgit_print_pageheader(&ctx);
107 107
108 if (ctx.cfg.index_header) 108 if (ctx.cfg.index_header)
109 html_include(ctx.cfg.index_header); 109 html_include(ctx.cfg.index_header);
110 110
111 html("<table summary='repository list' class='list nowrap'>"); 111 html("<table summary='repository list' class='list nowrap'>");
112 for (i=0; i<cgit_repolist.count; i++) { 112 for (i=0; i<cgit_repolist.count; i++) {
113 ctx.repo = &cgit_repolist.repos[i]; 113 ctx.repo = &cgit_repolist.repos[i];
114 if (!(is_match(ctx.repo) && is_in_url(ctx.repo))) 114 if (!(is_match(ctx.repo) && is_in_url(ctx.repo)))
115 continue; 115 continue;
116 hits++; 116 hits++;
117 if (hits <= ctx.qry.ofs) 117 if (hits <= ctx.qry.ofs)
118 continue; 118 continue;
119 if (hits > ctx.qry.ofs + ctx.cfg.max_repo_count) 119 if (hits > ctx.qry.ofs + ctx.cfg.max_repo_count)
120 continue; 120 continue;
121 if (!header++) 121 if (!header++)
122 print_header(columns); 122 print_header(columns);
123 if ((last_group == NULL && ctx.repo->group != NULL) || 123 if ((last_group == NULL && ctx.repo->group != NULL) ||
124 (last_group != NULL && ctx.repo->group == NULL) || 124 (last_group != NULL && ctx.repo->group == NULL) ||
125 (last_group != NULL && ctx.repo->group != NULL && 125 (last_group != NULL && ctx.repo->group != NULL &&
126 strcmp(ctx.repo->group, last_group))) { 126 strcmp(ctx.repo->group, last_group))) {
127 htmlf("<tr class='nohover'><td colspan='%d' class='repogroup'>", 127 htmlf("<tr class='nohover'><td colspan='%d' class='repogroup'>",
128 columns); 128 columns);
129 html_txt(ctx.repo->group); 129 html_txt(ctx.repo->group);
130 html("</td></tr>"); 130 html("</td></tr>");
131 last_group = ctx.repo->group; 131 last_group = ctx.repo->group;
132 } 132 }
133 htmlf("<tr><td class='%s'>", 133 htmlf("<tr><td class='%s'>",
134 ctx.repo->group ? "sublevel-repo" : "toplevel-repo"); 134 ctx.repo->group ? "sublevel-repo" : "toplevel-repo");
135 cgit_summary_link(ctx.repo->name, ctx.repo->name, NULL, NULL); 135 cgit_summary_link(ctx.repo->name, ctx.repo->name, NULL, NULL);
136 html("</td><td>"); 136 html("</td><td>");
137 html_link_open(cgit_repourl(ctx.repo->url), NULL, NULL); 137 html_link_open(cgit_repourl(ctx.repo->url), NULL, NULL);
138 html_ntxt(ctx.cfg.max_repodesc_len, ctx.repo->desc); 138 html_ntxt(ctx.cfg.max_repodesc_len, ctx.repo->desc);
139 html_link_close(); 139 html_link_close();
140 html("</td><td>"); 140 html("</td><td>");
141 html_txt(ctx.repo->owner); 141 html_txt(ctx.repo->owner);
142 html("</td><td>"); 142 html("</td><td>");
143 print_modtime(ctx.repo); 143 print_modtime(ctx.repo);
144 html("</td>"); 144 html("</td>");
145 if (ctx.cfg.enable_index_links) { 145 if (ctx.cfg.enable_index_links) {
146 html("<td>"); 146 html("<td>");
147 cgit_summary_link("summary", NULL, "button", NULL); 147 cgit_summary_link("summary", NULL, "button", NULL);
148 cgit_log_link("log", NULL, "button", NULL, NULL, NULL, 148 cgit_log_link("log", NULL, "button", NULL, NULL, NULL,
149 0, NULL, NULL); 149 0, NULL, NULL, ctx.qry.showmsg);
150 cgit_tree_link("tree", NULL, "button", NULL, NULL, NULL); 150 cgit_tree_link("tree", NULL, "button", NULL, NULL, NULL);
151 html("</td>"); 151 html("</td>");
152 } 152 }
153 html("</tr>\n"); 153 html("</tr>\n");
154 } 154 }
155 html("</table>"); 155 html("</table>");
156 if (!hits) 156 if (!hits)
157 cgit_print_error("No repositories found"); 157 cgit_print_error("No repositories found");
158 else if (hits > ctx.cfg.max_repo_count) 158 else if (hits > ctx.cfg.max_repo_count)
159 print_pager(hits, ctx.cfg.max_repo_count, ctx.qry.search); 159 print_pager(hits, ctx.cfg.max_repo_count, ctx.qry.search);
160 cgit_print_docend(); 160 cgit_print_docend();
161} 161}
162 162
163void cgit_print_site_readme() 163void cgit_print_site_readme()
164{ 164{
165 if (ctx.cfg.root_readme) 165 if (ctx.cfg.root_readme)
166 html_include(ctx.cfg.root_readme); 166 html_include(ctx.cfg.root_readme);
167} 167}
diff --git a/ui-shared.c b/ui-shared.c
index 224e5f3..dc39e64 100644
--- a/ui-shared.c
+++ b/ui-shared.c
@@ -1,714 +1,722 @@
1/* ui-shared.c: common web output functions 1/* ui-shared.c: common web output functions
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 "cmd.h" 10#include "cmd.h"
11#include "html.h" 11#include "html.h"
12 12
13const char cgit_doctype[] = 13const char cgit_doctype[] =
14"<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\"\n" 14"<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\"\n"
15" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n"; 15" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n";
16 16
17static char *http_date(time_t t) 17static char *http_date(time_t t)
18{ 18{
19 static char day[][4] = 19 static char day[][4] =
20 {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"}; 20 {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"};
21 static char month[][4] = 21 static char month[][4] =
22 {"Jan", "Feb", "Mar", "Apr", "May", "Jun", 22 {"Jan", "Feb", "Mar", "Apr", "May", "Jun",
23 "Jul", "Aug", "Sep", "Oct", "Now", "Dec"}; 23 "Jul", "Aug", "Sep", "Oct", "Now", "Dec"};
24 struct tm *tm = gmtime(&t); 24 struct tm *tm = gmtime(&t);
25 return fmt("%s, %02d %s %04d %02d:%02d:%02d GMT", day[tm->tm_wday], 25 return fmt("%s, %02d %s %04d %02d:%02d:%02d GMT", day[tm->tm_wday],
26 tm->tm_mday, month[tm->tm_mon], 1900+tm->tm_year, 26 tm->tm_mday, month[tm->tm_mon], 1900+tm->tm_year,
27 tm->tm_hour, tm->tm_min, tm->tm_sec); 27 tm->tm_hour, tm->tm_min, tm->tm_sec);
28} 28}
29 29
30void cgit_print_error(char *msg) 30void cgit_print_error(char *msg)
31{ 31{
32 html("<div class='error'>"); 32 html("<div class='error'>");
33 html_txt(msg); 33 html_txt(msg);
34 html("</div>\n"); 34 html("</div>\n");
35} 35}
36 36
37char *cgit_hosturl() 37char *cgit_hosturl()
38{ 38{
39 char *host, *port; 39 char *host, *port;
40 40
41 host = getenv("HTTP_HOST"); 41 host = getenv("HTTP_HOST");
42 if (host) { 42 if (host) {
43 host = xstrdup(host); 43 host = xstrdup(host);
44 } else { 44 } else {
45 host = getenv("SERVER_NAME"); 45 host = getenv("SERVER_NAME");
46 if (!host) 46 if (!host)
47 return NULL; 47 return NULL;
48 port = getenv("SERVER_PORT"); 48 port = getenv("SERVER_PORT");
49 if (port && atoi(port) != 80) 49 if (port && atoi(port) != 80)
50 host = xstrdup(fmt("%s:%d", host, atoi(port))); 50 host = xstrdup(fmt("%s:%d", host, atoi(port)));
51 else 51 else
52 host = xstrdup(host); 52 host = xstrdup(host);
53 } 53 }
54 return host; 54 return host;
55} 55}
56 56
57char *cgit_rooturl() 57char *cgit_rooturl()
58{ 58{
59 if (ctx.cfg.virtual_root) 59 if (ctx.cfg.virtual_root)
60 return fmt("%s/", ctx.cfg.virtual_root); 60 return fmt("%s/", ctx.cfg.virtual_root);
61 else 61 else
62 return ctx.cfg.script_name; 62 return ctx.cfg.script_name;
63} 63}
64 64
65char *cgit_repourl(const char *reponame) 65char *cgit_repourl(const char *reponame)
66{ 66{
67 if (ctx.cfg.virtual_root) { 67 if (ctx.cfg.virtual_root) {
68 return fmt("%s/%s/", ctx.cfg.virtual_root, reponame); 68 return fmt("%s/%s/", ctx.cfg.virtual_root, reponame);
69 } else { 69 } else {
70 return fmt("?r=%s", reponame); 70 return fmt("?r=%s", reponame);
71 } 71 }
72} 72}
73 73
74char *cgit_fileurl(const char *reponame, const char *pagename, 74char *cgit_fileurl(const char *reponame, const char *pagename,
75 const char *filename, const char *query) 75 const char *filename, const char *query)
76{ 76{
77 char *tmp; 77 char *tmp;
78 char *delim; 78 char *delim;
79 79
80 if (ctx.cfg.virtual_root) { 80 if (ctx.cfg.virtual_root) {
81 tmp = fmt("%s/%s/%s/%s", ctx.cfg.virtual_root, reponame, 81 tmp = fmt("%s/%s/%s/%s", ctx.cfg.virtual_root, reponame,
82 pagename, (filename ? filename:"")); 82 pagename, (filename ? filename:""));
83 delim = "?"; 83 delim = "?";
84 } else { 84 } else {
85 tmp = fmt("?url=%s/%s/%s", reponame, pagename, 85 tmp = fmt("?url=%s/%s/%s", reponame, pagename,
86 (filename ? filename : "")); 86 (filename ? filename : ""));
87 delim = "&"; 87 delim = "&";
88 } 88 }
89 if (query) 89 if (query)
90 tmp = fmt("%s%s%s", tmp, delim, query); 90 tmp = fmt("%s%s%s", tmp, delim, query);
91 return tmp; 91 return tmp;
92} 92}
93 93
94char *cgit_pageurl(const char *reponame, const char *pagename, 94char *cgit_pageurl(const char *reponame, const char *pagename,
95 const char *query) 95 const char *query)
96{ 96{
97 return cgit_fileurl(reponame,pagename,0,query); 97 return cgit_fileurl(reponame,pagename,0,query);
98} 98}
99 99
100const char *cgit_repobasename(const char *reponame) 100const char *cgit_repobasename(const char *reponame)
101{ 101{
102 /* I assume we don't need to store more than one repo basename */ 102 /* I assume we don't need to store more than one repo basename */
103 static char rvbuf[1024]; 103 static char rvbuf[1024];
104 int p; 104 int p;
105 const char *rv; 105 const char *rv;
106 strncpy(rvbuf,reponame,sizeof(rvbuf)); 106 strncpy(rvbuf,reponame,sizeof(rvbuf));
107 if(rvbuf[sizeof(rvbuf)-1]) 107 if(rvbuf[sizeof(rvbuf)-1])
108 die("cgit_repobasename: truncated repository name '%s'", reponame); 108 die("cgit_repobasename: truncated repository name '%s'", reponame);
109 p = strlen(rvbuf)-1; 109 p = strlen(rvbuf)-1;
110 /* strip trailing slashes */ 110 /* strip trailing slashes */
111 while(p && rvbuf[p]=='/') rvbuf[p--]=0; 111 while(p && rvbuf[p]=='/') rvbuf[p--]=0;
112 /* strip trailing .git */ 112 /* strip trailing .git */
113 if(p>=3 && !strncmp(&rvbuf[p-3],".git",4)) { 113 if(p>=3 && !strncmp(&rvbuf[p-3],".git",4)) {
114 p -= 3; rvbuf[p--] = 0; 114 p -= 3; rvbuf[p--] = 0;
115 } 115 }
116 /* strip more trailing slashes if any */ 116 /* strip more trailing slashes if any */
117 while( p && rvbuf[p]=='/') rvbuf[p--]=0; 117 while( p && rvbuf[p]=='/') rvbuf[p--]=0;
118 /* find last slash in the remaining string */ 118 /* find last slash in the remaining string */
119 rv = strrchr(rvbuf,'/'); 119 rv = strrchr(rvbuf,'/');
120 if(rv) 120 if(rv)
121 return ++rv; 121 return ++rv;
122 return rvbuf; 122 return rvbuf;
123} 123}
124 124
125char *cgit_currurl() 125char *cgit_currurl()
126{ 126{
127 if (!ctx.cfg.virtual_root) 127 if (!ctx.cfg.virtual_root)
128 return ctx.cfg.script_name; 128 return ctx.cfg.script_name;
129 else if (ctx.qry.page) 129 else if (ctx.qry.page)
130 return fmt("%s/%s/%s/", ctx.cfg.virtual_root, ctx.qry.repo, ctx.qry.page); 130 return fmt("%s/%s/%s/", ctx.cfg.virtual_root, ctx.qry.repo, ctx.qry.page);
131 else if (ctx.qry.repo) 131 else if (ctx.qry.repo)
132 return fmt("%s/%s/", ctx.cfg.virtual_root, ctx.qry.repo); 132 return fmt("%s/%s/", ctx.cfg.virtual_root, ctx.qry.repo);
133 else 133 else
134 return fmt("%s/", ctx.cfg.virtual_root); 134 return fmt("%s/", ctx.cfg.virtual_root);
135} 135}
136 136
137static void site_url(char *page, char *search, int ofs) 137static void site_url(char *page, char *search, int ofs)
138{ 138{
139 char *delim = "?"; 139 char *delim = "?";
140 140
141 if (ctx.cfg.virtual_root) { 141 if (ctx.cfg.virtual_root) {
142 html_attr(ctx.cfg.virtual_root); 142 html_attr(ctx.cfg.virtual_root);
143 if (ctx.cfg.virtual_root[strlen(ctx.cfg.virtual_root) - 1] != '/') 143 if (ctx.cfg.virtual_root[strlen(ctx.cfg.virtual_root) - 1] != '/')
144 html("/"); 144 html("/");
145 } else 145 } else
146 html(ctx.cfg.script_name); 146 html(ctx.cfg.script_name);
147 147
148 if (page) { 148 if (page) {
149 htmlf("?p=%s", page); 149 htmlf("?p=%s", page);
150 delim = "&"; 150 delim = "&";
151 } 151 }
152 if (search) { 152 if (search) {
153 html(delim); 153 html(delim);
154 html("q="); 154 html("q=");
155 html_attr(search); 155 html_attr(search);
156 delim = "&"; 156 delim = "&";
157 } 157 }
158 if (ofs) { 158 if (ofs) {
159 html(delim); 159 html(delim);
160 htmlf("ofs=%d", ofs); 160 htmlf("ofs=%d", ofs);
161 } 161 }
162} 162}
163 163
164static void site_link(char *page, char *name, char *title, char *class, 164static void site_link(char *page, char *name, char *title, char *class,
165 char *search, int ofs) 165 char *search, int ofs)
166{ 166{
167 html("<a"); 167 html("<a");
168 if (title) { 168 if (title) {
169 html(" title='"); 169 html(" title='");
170 html_attr(title); 170 html_attr(title);
171 html("'"); 171 html("'");
172 } 172 }
173 if (class) { 173 if (class) {
174 html(" class='"); 174 html(" class='");
175 html_attr(class); 175 html_attr(class);
176 html("'"); 176 html("'");
177 } 177 }
178 html(" href='"); 178 html(" href='");
179 site_url(page, search, ofs); 179 site_url(page, search, ofs);
180 html("'>"); 180 html("'>");
181 html_txt(name); 181 html_txt(name);
182 html("</a>"); 182 html("</a>");
183} 183}
184 184
185void cgit_index_link(char *name, char *title, char *class, char *pattern, 185void cgit_index_link(char *name, char *title, char *class, char *pattern,
186 int ofs) 186 int ofs)
187{ 187{
188 site_link(NULL, name, title, class, pattern, ofs); 188 site_link(NULL, name, title, class, pattern, ofs);
189} 189}
190 190
191static char *repolink(char *title, char *class, char *page, char *head, 191static char *repolink(char *title, char *class, char *page, char *head,
192 char *path) 192 char *path)
193{ 193{
194 char *delim = "?"; 194 char *delim = "?";
195 195
196 html("<a"); 196 html("<a");
197 if (title) { 197 if (title) {
198 html(" title='"); 198 html(" title='");
199 html_attr(title); 199 html_attr(title);
200 html("'"); 200 html("'");
201 } 201 }
202 if (class) { 202 if (class) {
203 html(" class='"); 203 html(" class='");
204 html_attr(class); 204 html_attr(class);
205 html("'"); 205 html("'");
206 } 206 }
207 html(" href='"); 207 html(" href='");
208 if (ctx.cfg.virtual_root) { 208 if (ctx.cfg.virtual_root) {
209 html_url_path(ctx.cfg.virtual_root); 209 html_url_path(ctx.cfg.virtual_root);
210 if (ctx.cfg.virtual_root[strlen(ctx.cfg.virtual_root) - 1] != '/') 210 if (ctx.cfg.virtual_root[strlen(ctx.cfg.virtual_root) - 1] != '/')
211 html("/"); 211 html("/");
212 html_url_path(ctx.repo->url); 212 html_url_path(ctx.repo->url);
213 if (ctx.repo->url[strlen(ctx.repo->url) - 1] != '/') 213 if (ctx.repo->url[strlen(ctx.repo->url) - 1] != '/')
214 html("/"); 214 html("/");
215 if (page) { 215 if (page) {
216 html_url_path(page); 216 html_url_path(page);
217 html("/"); 217 html("/");
218 if (path) 218 if (path)
219 html_url_path(path); 219 html_url_path(path);
220 } 220 }
221 } else { 221 } else {
222 html(ctx.cfg.script_name); 222 html(ctx.cfg.script_name);
223 html("?url="); 223 html("?url=");
224 html_url_arg(ctx.repo->url); 224 html_url_arg(ctx.repo->url);
225 if (ctx.repo->url[strlen(ctx.repo->url) - 1] != '/') 225 if (ctx.repo->url[strlen(ctx.repo->url) - 1] != '/')
226 html("/"); 226 html("/");
227 if (page) { 227 if (page) {
228 html_url_arg(page); 228 html_url_arg(page);
229 html("/"); 229 html("/");
230 if (path) 230 if (path)
231 html_url_arg(path); 231 html_url_arg(path);
232 } 232 }
233 delim = "&amp;"; 233 delim = "&amp;";
234 } 234 }
235 if (head && strcmp(head, ctx.repo->defbranch)) { 235 if (head && strcmp(head, ctx.repo->defbranch)) {
236 html(delim); 236 html(delim);
237 html("h="); 237 html("h=");
238 html_url_arg(head); 238 html_url_arg(head);
239 delim = "&amp;"; 239 delim = "&amp;";
240 } 240 }
241 return fmt("%s", delim); 241 return fmt("%s", delim);
242} 242}
243 243
244static void reporevlink(char *page, char *name, char *title, char *class, 244static void reporevlink(char *page, char *name, char *title, char *class,
245 char *head, char *rev, char *path) 245 char *head, char *rev, char *path)
246{ 246{
247 char *delim; 247 char *delim;
248 248
249 delim = repolink(title, class, page, head, path); 249 delim = repolink(title, class, page, head, path);
250 if (rev && strcmp(rev, ctx.qry.head)) { 250 if (rev && strcmp(rev, ctx.qry.head)) {
251 html(delim); 251 html(delim);
252 html("id="); 252 html("id=");
253 html_url_arg(rev); 253 html_url_arg(rev);
254 } 254 }
255 html("'>"); 255 html("'>");
256 html_txt(name); 256 html_txt(name);
257 html("</a>"); 257 html("</a>");
258} 258}
259 259
260void cgit_summary_link(char *name, char *title, char *class, char *head) 260void cgit_summary_link(char *name, char *title, char *class, char *head)
261{ 261{
262 reporevlink(NULL, name, title, class, head, NULL, NULL); 262 reporevlink(NULL, name, title, class, head, NULL, NULL);
263} 263}
264 264
265void cgit_tag_link(char *name, char *title, char *class, char *head, 265void cgit_tag_link(char *name, char *title, char *class, char *head,
266 char *rev) 266 char *rev)
267{ 267{
268 reporevlink("tag", name, title, class, head, rev, NULL); 268 reporevlink("tag", name, title, class, head, rev, NULL);
269} 269}
270 270
271void cgit_tree_link(char *name, char *title, char *class, char *head, 271void cgit_tree_link(char *name, char *title, char *class, char *head,
272 char *rev, char *path) 272 char *rev, char *path)
273{ 273{
274 reporevlink("tree", name, title, class, head, rev, path); 274 reporevlink("tree", name, title, class, head, rev, path);
275} 275}
276 276
277void cgit_plain_link(char *name, char *title, char *class, char *head, 277void cgit_plain_link(char *name, char *title, char *class, char *head,
278 char *rev, char *path) 278 char *rev, char *path)
279{ 279{
280 reporevlink("plain", name, title, class, head, rev, path); 280 reporevlink("plain", name, title, class, head, rev, path);
281} 281}
282 282
283void cgit_log_link(char *name, char *title, char *class, char *head, 283void cgit_log_link(char *name, char *title, char *class, char *head,
284 char *rev, char *path, int ofs, char *grep, char *pattern) 284 char *rev, char *path, int ofs, char *grep, char *pattern,
285 int showmsg)
285{ 286{
286 char *delim; 287 char *delim;
287 288
288 delim = repolink(title, class, "log", head, path); 289 delim = repolink(title, class, "log", head, path);
289 if (rev && strcmp(rev, ctx.qry.head)) { 290 if (rev && strcmp(rev, ctx.qry.head)) {
290 html(delim); 291 html(delim);
291 html("id="); 292 html("id=");
292 html_url_arg(rev); 293 html_url_arg(rev);
293 delim = "&"; 294 delim = "&";
294 } 295 }
295 if (grep && pattern) { 296 if (grep && pattern) {
296 html(delim); 297 html(delim);
297 html("qt="); 298 html("qt=");
298 html_url_arg(grep); 299 html_url_arg(grep);
299 delim = "&"; 300 delim = "&";
300 html(delim); 301 html(delim);
301 html("q="); 302 html("q=");
302 html_url_arg(pattern); 303 html_url_arg(pattern);
303 } 304 }
304 if (ofs > 0) { 305 if (ofs > 0) {
305 html(delim); 306 html(delim);
306 html("ofs="); 307 html("ofs=");
307 htmlf("%d", ofs); 308 htmlf("%d", ofs);
309 delim = "&";
310 }
311 if (showmsg) {
312 html(delim);
313 html("showmsg=1");
308 } 314 }
309 html("'>"); 315 html("'>");
310 html_txt(name); 316 html_txt(name);
311 html("</a>"); 317 html("</a>");
312} 318}
313 319
314void cgit_commit_link(char *name, char *title, char *class, char *head, 320void cgit_commit_link(char *name, char *title, char *class, char *head,
315 char *rev) 321 char *rev)
316{ 322{
317 if (strlen(name) > ctx.cfg.max_msg_len && ctx.cfg.max_msg_len >= 15) { 323 if (strlen(name) > ctx.cfg.max_msg_len && ctx.cfg.max_msg_len >= 15) {
318 name[ctx.cfg.max_msg_len] = '\0'; 324 name[ctx.cfg.max_msg_len] = '\0';
319 name[ctx.cfg.max_msg_len - 1] = '.'; 325 name[ctx.cfg.max_msg_len - 1] = '.';
320 name[ctx.cfg.max_msg_len - 2] = '.'; 326 name[ctx.cfg.max_msg_len - 2] = '.';
321 name[ctx.cfg.max_msg_len - 3] = '.'; 327 name[ctx.cfg.max_msg_len - 3] = '.';
322 } 328 }
323 reporevlink("commit", name, title, class, head, rev, NULL); 329 reporevlink("commit", name, title, class, head, rev, NULL);
324} 330}
325 331
326void cgit_refs_link(char *name, char *title, char *class, char *head, 332void cgit_refs_link(char *name, char *title, char *class, char *head,
327 char *rev, char *path) 333 char *rev, char *path)
328{ 334{
329 reporevlink("refs", name, title, class, head, rev, path); 335 reporevlink("refs", name, title, class, head, rev, path);
330} 336}
331 337
332void cgit_snapshot_link(char *name, char *title, char *class, char *head, 338void cgit_snapshot_link(char *name, char *title, char *class, char *head,
333 char *rev, char *archivename) 339 char *rev, char *archivename)
334{ 340{
335 reporevlink("snapshot", name, title, class, head, rev, archivename); 341 reporevlink("snapshot", name, title, class, head, rev, archivename);
336} 342}
337 343
338void cgit_diff_link(char *name, char *title, char *class, char *head, 344void cgit_diff_link(char *name, char *title, char *class, char *head,
339 char *new_rev, char *old_rev, char *path) 345 char *new_rev, char *old_rev, char *path)
340{ 346{
341 char *delim; 347 char *delim;
342 348
343 delim = repolink(title, class, "diff", head, path); 349 delim = repolink(title, class, "diff", head, path);
344 if (new_rev && strcmp(new_rev, ctx.qry.head)) { 350 if (new_rev && strcmp(new_rev, ctx.qry.head)) {
345 html(delim); 351 html(delim);
346 html("id="); 352 html("id=");
347 html_url_arg(new_rev); 353 html_url_arg(new_rev);
348 delim = "&amp;"; 354 delim = "&amp;";
349 } 355 }
350 if (old_rev) { 356 if (old_rev) {
351 html(delim); 357 html(delim);
352 html("id2="); 358 html("id2=");
353 html_url_arg(old_rev); 359 html_url_arg(old_rev);
354 } 360 }
355 html("'>"); 361 html("'>");
356 html_txt(name); 362 html_txt(name);
357 html("</a>"); 363 html("</a>");
358} 364}
359 365
360void cgit_patch_link(char *name, char *title, char *class, char *head, 366void cgit_patch_link(char *name, char *title, char *class, char *head,
361 char *rev) 367 char *rev)
362{ 368{
363 reporevlink("patch", name, title, class, head, rev, NULL); 369 reporevlink("patch", name, title, class, head, rev, NULL);
364} 370}
365 371
366void cgit_object_link(struct object *obj) 372void cgit_object_link(struct object *obj)
367{ 373{
368 char *page, *rev, *name; 374 char *page, *rev, *name;
369 375
370 if (obj->type == OBJ_COMMIT) { 376 if (obj->type == OBJ_COMMIT) {
371 cgit_commit_link(fmt("commit %s", sha1_to_hex(obj->sha1)), NULL, NULL, 377 cgit_commit_link(fmt("commit %s", sha1_to_hex(obj->sha1)), NULL, NULL,
372 ctx.qry.head, sha1_to_hex(obj->sha1)); 378 ctx.qry.head, sha1_to_hex(obj->sha1));
373 return; 379 return;
374 } else if (obj->type == OBJ_TREE) 380 } else if (obj->type == OBJ_TREE)
375 page = "tree"; 381 page = "tree";
376 else if (obj->type == OBJ_TAG) 382 else if (obj->type == OBJ_TAG)
377 page = "tag"; 383 page = "tag";
378 else 384 else
379 page = "blob"; 385 page = "blob";
380 rev = sha1_to_hex(obj->sha1); 386 rev = sha1_to_hex(obj->sha1);
381 name = fmt("%s %s", typename(obj->type), rev); 387 name = fmt("%s %s", typename(obj->type), rev);
382 reporevlink(page, name, NULL, NULL, ctx.qry.head, rev, NULL); 388 reporevlink(page, name, NULL, NULL, ctx.qry.head, rev, NULL);
383} 389}
384 390
385void cgit_print_date(time_t secs, char *format, int local_time) 391void cgit_print_date(time_t secs, char *format, int local_time)
386{ 392{
387 char buf[64]; 393 char buf[64];
388 struct tm *time; 394 struct tm *time;
389 395
390 if (!secs) 396 if (!secs)
391 return; 397 return;
392 if(local_time) 398 if(local_time)
393 time = localtime(&secs); 399 time = localtime(&secs);
394 else 400 else
395 time = gmtime(&secs); 401 time = gmtime(&secs);
396 strftime(buf, sizeof(buf)-1, format, time); 402 strftime(buf, sizeof(buf)-1, format, time);
397 html_txt(buf); 403 html_txt(buf);
398} 404}
399 405
400void cgit_print_age(time_t t, time_t max_relative, char *format) 406void cgit_print_age(time_t t, time_t max_relative, char *format)
401{ 407{
402 time_t now, secs; 408 time_t now, secs;
403 409
404 if (!t) 410 if (!t)
405 return; 411 return;
406 time(&now); 412 time(&now);
407 secs = now - t; 413 secs = now - t;
408 414
409 if (secs > max_relative && max_relative >= 0) { 415 if (secs > max_relative && max_relative >= 0) {
410 cgit_print_date(t, format, ctx.cfg.local_time); 416 cgit_print_date(t, format, ctx.cfg.local_time);
411 return; 417 return;
412 } 418 }
413 419
414 if (secs < TM_HOUR * 2) { 420 if (secs < TM_HOUR * 2) {
415 htmlf("<span class='age-mins'>%.0f min.</span>", 421 htmlf("<span class='age-mins'>%.0f min.</span>",
416 secs * 1.0 / TM_MIN); 422 secs * 1.0 / TM_MIN);
417 return; 423 return;
418 } 424 }
419 if (secs < TM_DAY * 2) { 425 if (secs < TM_DAY * 2) {
420 htmlf("<span class='age-hours'>%.0f hours</span>", 426 htmlf("<span class='age-hours'>%.0f hours</span>",
421 secs * 1.0 / TM_HOUR); 427 secs * 1.0 / TM_HOUR);
422 return; 428 return;
423 } 429 }
424 if (secs < TM_WEEK * 2) { 430 if (secs < TM_WEEK * 2) {
425 htmlf("<span class='age-days'>%.0f days</span>", 431 htmlf("<span class='age-days'>%.0f days</span>",
426 secs * 1.0 / TM_DAY); 432 secs * 1.0 / TM_DAY);
427 return; 433 return;
428 } 434 }
429 if (secs < TM_MONTH * 2) { 435 if (secs < TM_MONTH * 2) {
430 htmlf("<span class='age-weeks'>%.0f weeks</span>", 436 htmlf("<span class='age-weeks'>%.0f weeks</span>",
431 secs * 1.0 / TM_WEEK); 437 secs * 1.0 / TM_WEEK);
432 return; 438 return;
433 } 439 }
434 if (secs < TM_YEAR * 2) { 440 if (secs < TM_YEAR * 2) {
435 htmlf("<span class='age-months'>%.0f months</span>", 441 htmlf("<span class='age-months'>%.0f months</span>",
436 secs * 1.0 / TM_MONTH); 442 secs * 1.0 / TM_MONTH);
437 return; 443 return;
438 } 444 }
439 htmlf("<span class='age-years'>%.0f years</span>", 445 htmlf("<span class='age-years'>%.0f years</span>",
440 secs * 1.0 / TM_YEAR); 446 secs * 1.0 / TM_YEAR);
441} 447}
442 448
443void cgit_print_http_headers(struct cgit_context *ctx) 449void cgit_print_http_headers(struct cgit_context *ctx)
444{ 450{
445 if (ctx->page.mimetype && ctx->page.charset) 451 if (ctx->page.mimetype && ctx->page.charset)
446 htmlf("Content-Type: %s; charset=%s\n", ctx->page.mimetype, 452 htmlf("Content-Type: %s; charset=%s\n", ctx->page.mimetype,
447 ctx->page.charset); 453 ctx->page.charset);
448 else if (ctx->page.mimetype) 454 else if (ctx->page.mimetype)
449 htmlf("Content-Type: %s\n", ctx->page.mimetype); 455 htmlf("Content-Type: %s\n", ctx->page.mimetype);
450 if (ctx->page.size) 456 if (ctx->page.size)
451 htmlf("Content-Length: %ld\n", ctx->page.size); 457 htmlf("Content-Length: %ld\n", ctx->page.size);
452 if (ctx->page.filename) 458 if (ctx->page.filename)
453 htmlf("Content-Disposition: inline; filename=\"%s\"\n", 459 htmlf("Content-Disposition: inline; filename=\"%s\"\n",
454 ctx->page.filename); 460 ctx->page.filename);
455 htmlf("Last-Modified: %s\n", http_date(ctx->page.modified)); 461 htmlf("Last-Modified: %s\n", http_date(ctx->page.modified));
456 htmlf("Expires: %s\n", http_date(ctx->page.expires)); 462 htmlf("Expires: %s\n", http_date(ctx->page.expires));
457 html("\n"); 463 html("\n");
458} 464}
459 465
460void cgit_print_docstart(struct cgit_context *ctx) 466void cgit_print_docstart(struct cgit_context *ctx)
461{ 467{
462 char *host = cgit_hosturl(); 468 char *host = cgit_hosturl();
463 html(cgit_doctype); 469 html(cgit_doctype);
464 html("<html xmlns='http://www.w3.org/1999/xhtml' xml:lang='en' lang='en'>\n"); 470 html("<html xmlns='http://www.w3.org/1999/xhtml' xml:lang='en' lang='en'>\n");
465 html("<head>\n"); 471 html("<head>\n");
466 html("<title>"); 472 html("<title>");
467 html_txt(ctx->page.title); 473 html_txt(ctx->page.title);
468 html("</title>\n"); 474 html("</title>\n");
469 htmlf("<meta name='generator' content='cgit %s'/>\n", cgit_version); 475 htmlf("<meta name='generator' content='cgit %s'/>\n", cgit_version);
470 if (ctx->cfg.robots && *ctx->cfg.robots) 476 if (ctx->cfg.robots && *ctx->cfg.robots)
471 htmlf("<meta name='robots' content='%s'/>\n", ctx->cfg.robots); 477 htmlf("<meta name='robots' content='%s'/>\n", ctx->cfg.robots);
472 html("<link rel='stylesheet' type='text/css' href='"); 478 html("<link rel='stylesheet' type='text/css' href='");
473 html_attr(ctx->cfg.css); 479 html_attr(ctx->cfg.css);
474 html("'/>\n"); 480 html("'/>\n");
475 if (ctx->cfg.favicon) { 481 if (ctx->cfg.favicon) {
476 html("<link rel='shortcut icon' href='"); 482 html("<link rel='shortcut icon' href='");
477 html_attr(ctx->cfg.favicon); 483 html_attr(ctx->cfg.favicon);
478 html("'/>\n"); 484 html("'/>\n");
479 } 485 }
480 if (host && ctx->repo) { 486 if (host && ctx->repo) {
481 html("<link rel='alternate' title='Atom feed' href='http://"); 487 html("<link rel='alternate' title='Atom feed' href='http://");
482 html_attr(cgit_hosturl()); 488 html_attr(cgit_hosturl());
483 html_attr(cgit_fileurl(ctx->repo->url, "atom", ctx->qry.path, 489 html_attr(cgit_fileurl(ctx->repo->url, "atom", ctx->qry.path,
484 fmt("h=%s", ctx->qry.head))); 490 fmt("h=%s", ctx->qry.head)));
485 html("' type='application/atom+xml'/>"); 491 html("' type='application/atom+xml'/>");
486 } 492 }
487 html("</head>\n"); 493 html("</head>\n");
488 html("<body>\n"); 494 html("<body>\n");
489} 495}
490 496
491void cgit_print_docend() 497void cgit_print_docend()
492{ 498{
493 html("</div>"); 499 html("</div>");
494 if (ctx.cfg.footer) 500 if (ctx.cfg.footer)
495 html_include(ctx.cfg.footer); 501 html_include(ctx.cfg.footer);
496 else { 502 else {
497 htmlf("<div class='footer'>generated by cgit %s at ", 503 htmlf("<div class='footer'>generated by cgit %s at ",
498 cgit_version); 504 cgit_version);
499 cgit_print_date(time(NULL), FMT_LONGDATE, ctx.cfg.local_time); 505 cgit_print_date(time(NULL), FMT_LONGDATE, ctx.cfg.local_time);
500 html("</div>\n"); 506 html("</div>\n");
501 } 507 }
502 html("</body>\n</html>\n"); 508 html("</body>\n</html>\n");
503} 509}
504 510
505int print_branch_option(const char *refname, const unsigned char *sha1, 511int print_branch_option(const char *refname, const unsigned char *sha1,
506 int flags, void *cb_data) 512 int flags, void *cb_data)
507{ 513{
508 char *name = (char *)refname; 514 char *name = (char *)refname;
509 html_option(name, name, ctx.qry.head); 515 html_option(name, name, ctx.qry.head);
510 return 0; 516 return 0;
511} 517}
512 518
513int print_archive_ref(const char *refname, const unsigned char *sha1, 519int print_archive_ref(const char *refname, const unsigned char *sha1,
514 int flags, void *cb_data) 520 int flags, void *cb_data)
515{ 521{
516 struct tag *tag; 522 struct tag *tag;
517 struct taginfo *info; 523 struct taginfo *info;
518 struct object *obj; 524 struct object *obj;
519 char buf[256], *url; 525 char buf[256], *url;
520 unsigned char fileid[20]; 526 unsigned char fileid[20];
521 int *header = (int *)cb_data; 527 int *header = (int *)cb_data;
522 528
523 if (prefixcmp(refname, "refs/archives")) 529 if (prefixcmp(refname, "refs/archives"))
524 return 0; 530 return 0;
525 strncpy(buf, refname+14, sizeof(buf)); 531 strncpy(buf, refname+14, sizeof(buf));
526 obj = parse_object(sha1); 532 obj = parse_object(sha1);
527 if (!obj) 533 if (!obj)
528 return 1; 534 return 1;
529 if (obj->type == OBJ_TAG) { 535 if (obj->type == OBJ_TAG) {
530 tag = lookup_tag(sha1); 536 tag = lookup_tag(sha1);
531 if (!tag || parse_tag(tag) || !(info = cgit_parse_tag(tag))) 537 if (!tag || parse_tag(tag) || !(info = cgit_parse_tag(tag)))
532 return 0; 538 return 0;
533 hashcpy(fileid, tag->tagged->sha1); 539 hashcpy(fileid, tag->tagged->sha1);
534 } else if (obj->type != OBJ_BLOB) { 540 } else if (obj->type != OBJ_BLOB) {
535 return 0; 541 return 0;
536 } else { 542 } else {
537 hashcpy(fileid, sha1); 543 hashcpy(fileid, sha1);
538 } 544 }
539 if (!*header) { 545 if (!*header) {
540 html("<h1>download</h1>\n"); 546 html("<h1>download</h1>\n");
541 *header = 1; 547 *header = 1;
542 } 548 }
543 url = cgit_pageurl(ctx.qry.repo, "blob", 549 url = cgit_pageurl(ctx.qry.repo, "blob",
544 fmt("id=%s&amp;path=%s", sha1_to_hex(fileid), 550 fmt("id=%s&amp;path=%s", sha1_to_hex(fileid),
545 buf)); 551 buf));
546 html_link_open(url, NULL, "menu"); 552 html_link_open(url, NULL, "menu");
547 html_txt(strlpart(buf, 20)); 553 html_txt(strlpart(buf, 20));
548 html_link_close(); 554 html_link_close();
549 return 0; 555 return 0;
550} 556}
551 557
552void add_hidden_formfields(int incl_head, int incl_search, char *page) 558void add_hidden_formfields(int incl_head, int incl_search, char *page)
553{ 559{
554 char *url; 560 char *url;
555 561
556 if (!ctx.cfg.virtual_root) { 562 if (!ctx.cfg.virtual_root) {
557 url = fmt("%s/%s", ctx.qry.repo, page); 563 url = fmt("%s/%s", ctx.qry.repo, page);
558 if (ctx.qry.path) 564 if (ctx.qry.path)
559 url = fmt("%s/%s", url, ctx.qry.path); 565 url = fmt("%s/%s", url, ctx.qry.path);
560 html_hidden("url", url); 566 html_hidden("url", url);
561 } 567 }
562 568
563 if (incl_head && ctx.qry.head && ctx.repo->defbranch && 569 if (incl_head && ctx.qry.head && ctx.repo->defbranch &&
564 strcmp(ctx.qry.head, ctx.repo->defbranch)) 570 strcmp(ctx.qry.head, ctx.repo->defbranch))
565 html_hidden("h", ctx.qry.head); 571 html_hidden("h", ctx.qry.head);
566 572
567 if (ctx.qry.sha1) 573 if (ctx.qry.sha1)
568 html_hidden("id", ctx.qry.sha1); 574 html_hidden("id", ctx.qry.sha1);
569 if (ctx.qry.sha2) 575 if (ctx.qry.sha2)
570 html_hidden("id2", ctx.qry.sha2); 576 html_hidden("id2", ctx.qry.sha2);
577 if (ctx.qry.showmsg)
578 html_hidden("showmsg", "1");
571 579
572 if (incl_search) { 580 if (incl_search) {
573 if (ctx.qry.grep) 581 if (ctx.qry.grep)
574 html_hidden("qt", ctx.qry.grep); 582 html_hidden("qt", ctx.qry.grep);
575 if (ctx.qry.search) 583 if (ctx.qry.search)
576 html_hidden("q", ctx.qry.search); 584 html_hidden("q", ctx.qry.search);
577 } 585 }
578} 586}
579 587
580char *hc(struct cgit_cmd *cmd, const char *page) 588char *hc(struct cgit_cmd *cmd, const char *page)
581{ 589{
582 return (strcmp(cmd->name, page) ? NULL : "active"); 590 return (strcmp(cmd->name, page) ? NULL : "active");
583} 591}
584 592
585void cgit_print_pageheader(struct cgit_context *ctx) 593void cgit_print_pageheader(struct cgit_context *ctx)
586{ 594{
587 struct cgit_cmd *cmd = cgit_get_cmd(ctx); 595 struct cgit_cmd *cmd = cgit_get_cmd(ctx);
588 596
589 html("<table id='header'>\n"); 597 html("<table id='header'>\n");
590 html("<tr>\n"); 598 html("<tr>\n");
591 html("<td class='logo' rowspan='2'><a href='"); 599 html("<td class='logo' rowspan='2'><a href='");
592 if (ctx->cfg.logo_link) 600 if (ctx->cfg.logo_link)
593 html_attr(ctx->cfg.logo_link); 601 html_attr(ctx->cfg.logo_link);
594 else 602 else
595 html_attr(cgit_rooturl()); 603 html_attr(cgit_rooturl());
596 html("'><img src='"); 604 html("'><img src='");
597 html_attr(ctx->cfg.logo); 605 html_attr(ctx->cfg.logo);
598 html("' alt='cgit logo'/></a></td>\n"); 606 html("' alt='cgit logo'/></a></td>\n");
599 607
600 html("<td class='main'>"); 608 html("<td class='main'>");
601 if (ctx->repo) { 609 if (ctx->repo) {
602 cgit_index_link("index", NULL, NULL, NULL, 0); 610 cgit_index_link("index", NULL, NULL, NULL, 0);
603 html(" : "); 611 html(" : ");
604 cgit_summary_link(ctx->repo->name, ctx->repo->name, NULL, NULL); 612 cgit_summary_link(ctx->repo->name, ctx->repo->name, NULL, NULL);
605 html("</td><td class='form'>"); 613 html("</td><td class='form'>");
606 html("<form method='get' action=''>\n"); 614 html("<form method='get' action=''>\n");
607 add_hidden_formfields(0, 1, ctx->qry.page); 615 add_hidden_formfields(0, 1, ctx->qry.page);
608 html("<select name='h' onchange='this.form.submit();'>\n"); 616 html("<select name='h' onchange='this.form.submit();'>\n");
609 for_each_branch_ref(print_branch_option, ctx->qry.head); 617 for_each_branch_ref(print_branch_option, ctx->qry.head);
610 html("</select> "); 618 html("</select> ");
611 html("<input type='submit' name='' value='switch'/>"); 619 html("<input type='submit' name='' value='switch'/>");
612 html("</form>"); 620 html("</form>");
613 } else 621 } else
614 html_txt(ctx->cfg.root_title); 622 html_txt(ctx->cfg.root_title);
615 html("</td></tr>\n"); 623 html("</td></tr>\n");
616 624
617 html("<tr><td class='sub'>"); 625 html("<tr><td class='sub'>");
618 if (ctx->repo) { 626 if (ctx->repo) {
619 html_txt(ctx->repo->desc); 627 html_txt(ctx->repo->desc);
620 html("</td><td class='sub right'>"); 628 html("</td><td class='sub right'>");
621 html_txt(ctx->repo->owner); 629 html_txt(ctx->repo->owner);
622 } else { 630 } else {
623 if (ctx->cfg.root_desc) 631 if (ctx->cfg.root_desc)
624 html_txt(ctx->cfg.root_desc); 632 html_txt(ctx->cfg.root_desc);
625 else if (ctx->cfg.index_info) 633 else if (ctx->cfg.index_info)
626 html_include(ctx->cfg.index_info); 634 html_include(ctx->cfg.index_info);
627 } 635 }
628 html("</td></tr></table>\n"); 636 html("</td></tr></table>\n");
629 637
630 html("<table class='tabs'><tr><td>\n"); 638 html("<table class='tabs'><tr><td>\n");
631 if (ctx->repo) { 639 if (ctx->repo) {
632 cgit_summary_link("summary", NULL, hc(cmd, "summary"), 640 cgit_summary_link("summary", NULL, hc(cmd, "summary"),
633 ctx->qry.head); 641 ctx->qry.head);
634 cgit_refs_link("refs", NULL, hc(cmd, "refs"), ctx->qry.head, 642 cgit_refs_link("refs", NULL, hc(cmd, "refs"), ctx->qry.head,
635 ctx->qry.sha1, NULL); 643 ctx->qry.sha1, NULL);
636 cgit_log_link("log", NULL, hc(cmd, "log"), ctx->qry.head, 644 cgit_log_link("log", NULL, hc(cmd, "log"), ctx->qry.head,
637 NULL, NULL, 0, NULL, NULL); 645 NULL, NULL, 0, NULL, NULL, ctx->qry.showmsg);
638 cgit_tree_link("tree", NULL, hc(cmd, "tree"), ctx->qry.head, 646 cgit_tree_link("tree", NULL, hc(cmd, "tree"), ctx->qry.head,
639 ctx->qry.sha1, NULL); 647 ctx->qry.sha1, NULL);
640 cgit_commit_link("commit", NULL, hc(cmd, "commit"), 648 cgit_commit_link("commit", NULL, hc(cmd, "commit"),
641 ctx->qry.head, ctx->qry.sha1); 649 ctx->qry.head, ctx->qry.sha1);
642 cgit_diff_link("diff", NULL, hc(cmd, "diff"), ctx->qry.head, 650 cgit_diff_link("diff", NULL, hc(cmd, "diff"), ctx->qry.head,
643 ctx->qry.sha1, ctx->qry.sha2, NULL); 651 ctx->qry.sha1, ctx->qry.sha2, NULL);
644 if (ctx->repo->readme) 652 if (ctx->repo->readme)
645 reporevlink("about", "about", NULL, 653 reporevlink("about", "about", NULL,
646 hc(cmd, "about"), ctx->qry.head, NULL, 654 hc(cmd, "about"), ctx->qry.head, NULL,
647 NULL); 655 NULL);
648 html("</td><td class='form'>"); 656 html("</td><td class='form'>");
649 html("<form class='right' method='get' action='"); 657 html("<form class='right' method='get' action='");
650 if (ctx->cfg.virtual_root) 658 if (ctx->cfg.virtual_root)
651 html_url_path(cgit_fileurl(ctx->qry.repo, "log", 659 html_url_path(cgit_fileurl(ctx->qry.repo, "log",
652 ctx->qry.path, NULL)); 660 ctx->qry.path, NULL));
653 html("'>\n"); 661 html("'>\n");
654 add_hidden_formfields(1, 0, "log"); 662 add_hidden_formfields(1, 0, "log");
655 html("<select name='qt'>\n"); 663 html("<select name='qt'>\n");
656 html_option("grep", "log msg", ctx->qry.grep); 664 html_option("grep", "log msg", ctx->qry.grep);
657 html_option("author", "author", ctx->qry.grep); 665 html_option("author", "author", ctx->qry.grep);
658 html_option("committer", "committer", ctx->qry.grep); 666 html_option("committer", "committer", ctx->qry.grep);
659 html("</select>\n"); 667 html("</select>\n");
660 html("<input class='txt' type='text' size='10' name='q' value='"); 668 html("<input class='txt' type='text' size='10' name='q' value='");
661 html_attr(ctx->qry.search); 669 html_attr(ctx->qry.search);
662 html("'/>\n"); 670 html("'/>\n");
663 html("<input type='submit' value='search'/>\n"); 671 html("<input type='submit' value='search'/>\n");
664 html("</form>\n"); 672 html("</form>\n");
665 } else { 673 } else {
666 site_link(NULL, "index", NULL, hc(cmd, "repolist"), NULL, 0); 674 site_link(NULL, "index", NULL, hc(cmd, "repolist"), NULL, 0);
667 if (ctx->cfg.root_readme) 675 if (ctx->cfg.root_readme)
668 site_link("about", "about", NULL, hc(cmd, "about"), 676 site_link("about", "about", NULL, hc(cmd, "about"),
669 NULL, 0); 677 NULL, 0);
670 html("</td><td class='form'>"); 678 html("</td><td class='form'>");
671 html("<form method='get' action='"); 679 html("<form method='get' action='");
672 html_attr(cgit_rooturl()); 680 html_attr(cgit_rooturl());
673 html("'>\n"); 681 html("'>\n");
674 html("<input type='text' name='q' size='10' value='"); 682 html("<input type='text' name='q' size='10' value='");
675 html_attr(ctx->qry.search); 683 html_attr(ctx->qry.search);
676 html("'/>\n"); 684 html("'/>\n");
677 html("<input type='submit' value='search'/>\n"); 685 html("<input type='submit' value='search'/>\n");
678 html("</form>"); 686 html("</form>");
679 } 687 }
680 html("</td></tr></table>\n"); 688 html("</td></tr></table>\n");
681 html("<div class='content'>"); 689 html("<div class='content'>");
682} 690}
683 691
684void cgit_print_filemode(unsigned short mode) 692void cgit_print_filemode(unsigned short mode)
685{ 693{
686 if (S_ISDIR(mode)) 694 if (S_ISDIR(mode))
687 html("d"); 695 html("d");
688 else if (S_ISLNK(mode)) 696 else if (S_ISLNK(mode))
689 html("l"); 697 html("l");
690 else if (S_ISGITLINK(mode)) 698 else if (S_ISGITLINK(mode))
691 html("m"); 699 html("m");
692 else 700 else
693 html("-"); 701 html("-");
694 html_fileperm(mode >> 6); 702 html_fileperm(mode >> 6);
695 html_fileperm(mode >> 3); 703 html_fileperm(mode >> 3);
696 html_fileperm(mode); 704 html_fileperm(mode);
697} 705}
698 706
699void cgit_print_snapshot_links(const char *repo, const char *head, 707void cgit_print_snapshot_links(const char *repo, const char *head,
700 const char *hex, int snapshots) 708 const char *hex, int snapshots)
701{ 709{
702 const struct cgit_snapshot_format* f; 710 const struct cgit_snapshot_format* f;
703 char *filename; 711 char *filename;
704 712
705 for (f = cgit_snapshot_formats; f->suffix; f++) { 713 for (f = cgit_snapshot_formats; f->suffix; f++) {
706 if (!(snapshots & f->bit)) 714 if (!(snapshots & f->bit))
707 continue; 715 continue;
708 filename = fmt("%s-%s%s", cgit_repobasename(repo), hex, 716 filename = fmt("%s-%s%s", cgit_repobasename(repo), hex,
709 f->suffix); 717 f->suffix);
710 cgit_snapshot_link(filename, NULL, NULL, (char *)head, 718 cgit_snapshot_link(filename, NULL, NULL, (char *)head,
711 (char *)hex, filename); 719 (char *)hex, filename);
712 html("<br/>"); 720 html("<br/>");
713 } 721 }
714} 722}
diff --git a/ui-shared.h b/ui-shared.h
index 3c8a6d0..2ab53ae 100644
--- a/ui-shared.h
+++ b/ui-shared.h
@@ -1,46 +1,46 @@
1#ifndef UI_SHARED_H 1#ifndef UI_SHARED_H
2#define UI_SHARED_H 2#define UI_SHARED_H
3 3
4extern char *cgit_hosturl(); 4extern char *cgit_hosturl();
5extern char *cgit_repourl(const char *reponame); 5extern char *cgit_repourl(const char *reponame);
6extern char *cgit_fileurl(const char *reponame, const char *pagename, 6extern char *cgit_fileurl(const char *reponame, const char *pagename,
7 const char *filename, const char *query); 7 const char *filename, const char *query);
8extern char *cgit_pageurl(const char *reponame, const char *pagename, 8extern char *cgit_pageurl(const char *reponame, const char *pagename,
9 const char *query); 9 const char *query);
10 10
11extern void cgit_index_link(char *name, char *title, char *class, 11extern void cgit_index_link(char *name, char *title, char *class,
12 char *pattern, int ofs); 12 char *pattern, int ofs);
13extern void cgit_summary_link(char *name, char *title, char *class, char *head); 13extern void cgit_summary_link(char *name, char *title, char *class, char *head);
14extern void cgit_tag_link(char *name, char *title, char *class, char *head, 14extern void cgit_tag_link(char *name, char *title, char *class, char *head,
15 char *rev); 15 char *rev);
16extern void cgit_tree_link(char *name, char *title, char *class, char *head, 16extern void cgit_tree_link(char *name, char *title, char *class, char *head,
17 char *rev, char *path); 17 char *rev, char *path);
18extern void cgit_plain_link(char *name, char *title, char *class, char *head, 18extern void cgit_plain_link(char *name, char *title, char *class, char *head,
19 char *rev, char *path); 19 char *rev, char *path);
20extern void cgit_log_link(char *name, char *title, char *class, char *head, 20extern void cgit_log_link(char *name, char *title, char *class, char *head,
21 char *rev, char *path, int ofs, char *grep, 21 char *rev, char *path, int ofs, char *grep,
22 char *pattern); 22 char *pattern, int showmsg);
23extern void cgit_commit_link(char *name, char *title, char *class, char *head, 23extern void cgit_commit_link(char *name, char *title, char *class, char *head,
24 char *rev); 24 char *rev);
25extern void cgit_patch_link(char *name, char *title, char *class, char *head, 25extern void cgit_patch_link(char *name, char *title, char *class, char *head,
26 char *rev); 26 char *rev);
27extern void cgit_refs_link(char *name, char *title, char *class, char *head, 27extern void cgit_refs_link(char *name, char *title, char *class, char *head,
28 char *rev, char *path); 28 char *rev, char *path);
29extern void cgit_snapshot_link(char *name, char *title, char *class, 29extern void cgit_snapshot_link(char *name, char *title, char *class,
30 char *head, char *rev, char *archivename); 30 char *head, char *rev, char *archivename);
31extern void cgit_diff_link(char *name, char *title, char *class, char *head, 31extern void cgit_diff_link(char *name, char *title, char *class, char *head,
32 char *new_rev, char *old_rev, char *path); 32 char *new_rev, char *old_rev, char *path);
33extern void cgit_object_link(struct object *obj); 33extern void cgit_object_link(struct object *obj);
34 34
35extern void cgit_print_error(char *msg); 35extern void cgit_print_error(char *msg);
36extern void cgit_print_date(time_t secs, char *format, int local_time); 36extern void cgit_print_date(time_t secs, char *format, int local_time);
37extern void cgit_print_age(time_t t, time_t max_relative, char *format); 37extern void cgit_print_age(time_t t, time_t max_relative, char *format);
38extern void cgit_print_http_headers(struct cgit_context *ctx); 38extern void cgit_print_http_headers(struct cgit_context *ctx);
39extern void cgit_print_docstart(struct cgit_context *ctx); 39extern void cgit_print_docstart(struct cgit_context *ctx);
40extern void cgit_print_docend(); 40extern void cgit_print_docend();
41extern void cgit_print_pageheader(struct cgit_context *ctx); 41extern void cgit_print_pageheader(struct cgit_context *ctx);
42extern void cgit_print_filemode(unsigned short mode); 42extern void cgit_print_filemode(unsigned short mode);
43extern void cgit_print_snapshot_links(const char *repo, const char *head, 43extern void cgit_print_snapshot_links(const char *repo, const char *head,
44 const char *hex, int snapshots); 44 const char *hex, int snapshots);
45 45
46#endif /* UI_SHARED_H */ 46#endif /* UI_SHARED_H */
diff --git a/ui-tree.c b/ui-tree.c
index 79332fc..051db7c 100644
--- a/ui-tree.c
+++ b/ui-tree.c
@@ -1,223 +1,223 @@
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 "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 header = 0; 15int header = 0;
16 16
17static void print_object(const unsigned char *sha1, char *path) 17static void print_object(const unsigned char *sha1, char *path)
18{ 18{
19 enum object_type type; 19 enum object_type type;
20 char *buf; 20 char *buf;
21 unsigned long size, lineno, start, idx; 21 unsigned long size, lineno, start, idx;
22 const char *linefmt = "<tr><td class='no'><a id='n%1$d' name='n%1$d' href='#n%1$d'>%1$d</a></td><td class='txt'>"; 22 const char *linefmt = "<tr><td class='no'><a id='n%1$d' name='n%1$d' href='#n%1$d'>%1$d</a></td><td class='txt'>";
23 23
24 type = sha1_object_info(sha1, &size); 24 type = sha1_object_info(sha1, &size);
25 if (type == OBJ_BAD) { 25 if (type == OBJ_BAD) {
26 cgit_print_error(fmt("Bad object name: %s", 26 cgit_print_error(fmt("Bad object name: %s",
27 sha1_to_hex(sha1))); 27 sha1_to_hex(sha1)));
28 return; 28 return;
29 } 29 }
30 30
31 buf = read_sha1_file(sha1, &type, &size); 31 buf = read_sha1_file(sha1, &type, &size);
32 if (!buf) { 32 if (!buf) {
33 cgit_print_error(fmt("Error reading object %s", 33 cgit_print_error(fmt("Error reading object %s",
34 sha1_to_hex(sha1))); 34 sha1_to_hex(sha1)));
35 return; 35 return;
36 } 36 }
37 37
38 html(" ("); 38 html(" (");
39 cgit_plain_link("plain", NULL, NULL, ctx.qry.head, 39 cgit_plain_link("plain", NULL, NULL, ctx.qry.head,
40 curr_rev, path); 40 curr_rev, path);
41 htmlf(")<br/>blob: %s", sha1_to_hex(sha1)); 41 htmlf(")<br/>blob: %s", sha1_to_hex(sha1));
42 42
43 html("<table summary='blob content' class='blob'>\n"); 43 html("<table summary='blob content' class='blob'>\n");
44 idx = 0; 44 idx = 0;
45 start = 0; 45 start = 0;
46 lineno = 0; 46 lineno = 0;
47 while(idx < size) { 47 while(idx < size) {
48 if (buf[idx] == '\n') { 48 if (buf[idx] == '\n') {
49 buf[idx] = '\0'; 49 buf[idx] = '\0';
50 htmlf(linefmt, ++lineno); 50 htmlf(linefmt, ++lineno);
51 html_txt(buf + start); 51 html_txt(buf + start);
52 html("</td></tr>\n"); 52 html("</td></tr>\n");
53 start = idx + 1; 53 start = idx + 1;
54 } 54 }
55 idx++; 55 idx++;
56 } 56 }
57 htmlf(linefmt, ++lineno); 57 htmlf(linefmt, ++lineno);
58 html_txt(buf + start); 58 html_txt(buf + start);
59 html("</td></tr>\n"); 59 html("</td></tr>\n");
60 html("</table>\n"); 60 html("</table>\n");
61} 61}
62 62
63 63
64static int ls_item(const unsigned char *sha1, const char *base, int baselen, 64static int ls_item(const unsigned char *sha1, const char *base, int baselen,
65 const char *pathname, unsigned int mode, int stage, 65 const char *pathname, unsigned int mode, int stage,
66 void *cbdata) 66 void *cbdata)
67{ 67{
68 char *name; 68 char *name;
69 char *fullpath; 69 char *fullpath;
70 enum object_type type; 70 enum object_type type;
71 unsigned long size = 0; 71 unsigned long size = 0;
72 72
73 name = xstrdup(pathname); 73 name = xstrdup(pathname);
74 fullpath = fmt("%s%s%s", ctx.qry.path ? ctx.qry.path : "", 74 fullpath = fmt("%s%s%s", ctx.qry.path ? ctx.qry.path : "",
75 ctx.qry.path ? "/" : "", name); 75 ctx.qry.path ? "/" : "", name);
76 76
77 if (!S_ISGITLINK(mode)) { 77 if (!S_ISGITLINK(mode)) {
78 type = sha1_object_info(sha1, &size); 78 type = sha1_object_info(sha1, &size);
79 if (type == OBJ_BAD) { 79 if (type == OBJ_BAD) {
80 htmlf("<tr><td colspan='3'>Bad object: %s %s</td></tr>", 80 htmlf("<tr><td colspan='3'>Bad object: %s %s</td></tr>",
81 name, 81 name,
82 sha1_to_hex(sha1)); 82 sha1_to_hex(sha1));
83 return 0; 83 return 0;
84 } 84 }
85 } 85 }
86 86
87 html("<tr><td class='ls-mode'>"); 87 html("<tr><td class='ls-mode'>");
88 cgit_print_filemode(mode); 88 cgit_print_filemode(mode);
89 html("</td><td>"); 89 html("</td><td>");
90 if (S_ISGITLINK(mode)) { 90 if (S_ISGITLINK(mode)) {
91 htmlf("<a class='ls-mod' href='"); 91 htmlf("<a class='ls-mod' href='");
92 html_attr(fmt(ctx.repo->module_link, 92 html_attr(fmt(ctx.repo->module_link,
93 name, 93 name,
94 sha1_to_hex(sha1))); 94 sha1_to_hex(sha1)));
95 html("'>"); 95 html("'>");
96 html_txt(name); 96 html_txt(name);
97 html("</a>"); 97 html("</a>");
98 } else if (S_ISDIR(mode)) { 98 } else if (S_ISDIR(mode)) {
99 cgit_tree_link(name, NULL, "ls-dir", ctx.qry.head, 99 cgit_tree_link(name, NULL, "ls-dir", ctx.qry.head,
100 curr_rev, fullpath); 100 curr_rev, fullpath);
101 } else { 101 } else {
102 cgit_tree_link(name, NULL, "ls-blob", ctx.qry.head, 102 cgit_tree_link(name, NULL, "ls-blob", ctx.qry.head,
103 curr_rev, fullpath); 103 curr_rev, fullpath);
104 } 104 }
105 htmlf("</td><td class='ls-size'>%li</td>", size); 105 htmlf("</td><td class='ls-size'>%li</td>", size);
106 106
107 html("<td>"); 107 html("<td>");
108 cgit_log_link("log", NULL, "button", ctx.qry.head, curr_rev, 108 cgit_log_link("log", NULL, "button", ctx.qry.head, curr_rev,
109 fullpath, 0, NULL, NULL); 109 fullpath, 0, NULL, NULL, ctx.qry.showmsg);
110 html("</td></tr>\n"); 110 html("</td></tr>\n");
111 free(name); 111 free(name);
112 return 0; 112 return 0;
113} 113}
114 114
115static void ls_head() 115static void ls_head()
116{ 116{
117 html("<table summary='tree listing' class='list'>\n"); 117 html("<table summary='tree listing' class='list'>\n");
118 html("<tr class='nohover'>"); 118 html("<tr class='nohover'>");
119 html("<th class='left'>Mode</th>"); 119 html("<th class='left'>Mode</th>");
120 html("<th class='left'>Name</th>"); 120 html("<th class='left'>Name</th>");
121 html("<th class='right'>Size</th>"); 121 html("<th class='right'>Size</th>");
122 html("<th/>"); 122 html("<th/>");
123 html("</tr>\n"); 123 html("</tr>\n");
124 header = 1; 124 header = 1;
125} 125}
126 126
127static void ls_tail() 127static void ls_tail()
128{ 128{
129 if (!header) 129 if (!header)
130 return; 130 return;
131 html("</table>\n"); 131 html("</table>\n");
132 header = 0; 132 header = 0;
133} 133}
134 134
135static void ls_tree(const unsigned char *sha1, char *path) 135static void ls_tree(const unsigned char *sha1, char *path)
136{ 136{
137 struct tree *tree; 137 struct tree *tree;
138 138
139 tree = parse_tree_indirect(sha1); 139 tree = parse_tree_indirect(sha1);
140 if (!tree) { 140 if (!tree) {
141 cgit_print_error(fmt("Not a tree object: %s", 141 cgit_print_error(fmt("Not a tree object: %s",
142 sha1_to_hex(sha1))); 142 sha1_to_hex(sha1)));
143 return; 143 return;
144 } 144 }
145 145
146 ls_head(); 146 ls_head();
147 read_tree_recursive(tree, "", 0, 1, NULL, ls_item, NULL); 147 read_tree_recursive(tree, "", 0, 1, NULL, ls_item, NULL);
148 ls_tail(); 148 ls_tail();
149} 149}
150 150
151 151
152static int walk_tree(const unsigned char *sha1, const char *base, int baselen, 152static int walk_tree(const unsigned char *sha1, const char *base, int baselen,
153 const char *pathname, unsigned mode, int stage, 153 const char *pathname, unsigned mode, int stage,
154 void *cbdata) 154 void *cbdata)
155{ 155{
156 static int state; 156 static int state;
157 static char buffer[PATH_MAX]; 157 static char buffer[PATH_MAX];
158 char *url; 158 char *url;
159 159
160 if (state == 0) { 160 if (state == 0) {
161 memcpy(buffer, base, baselen); 161 memcpy(buffer, base, baselen);
162 strcpy(buffer+baselen, pathname); 162 strcpy(buffer+baselen, pathname);
163 url = cgit_pageurl(ctx.qry.repo, "tree", 163 url = cgit_pageurl(ctx.qry.repo, "tree",
164 fmt("h=%s&amp;path=%s", curr_rev, buffer)); 164 fmt("h=%s&amp;path=%s", curr_rev, buffer));
165 html("/"); 165 html("/");
166 cgit_tree_link(xstrdup(pathname), NULL, NULL, ctx.qry.head, 166 cgit_tree_link(xstrdup(pathname), NULL, NULL, ctx.qry.head,
167 curr_rev, buffer); 167 curr_rev, buffer);
168 168
169 if (strcmp(match_path, buffer)) 169 if (strcmp(match_path, buffer))
170 return READ_TREE_RECURSIVE; 170 return READ_TREE_RECURSIVE;
171 171
172 if (S_ISDIR(mode)) { 172 if (S_ISDIR(mode)) {
173 state = 1; 173 state = 1;
174 ls_head(); 174 ls_head();
175 return READ_TREE_RECURSIVE; 175 return READ_TREE_RECURSIVE;
176 } else { 176 } else {
177 print_object(sha1, buffer); 177 print_object(sha1, buffer);
178 return 0; 178 return 0;
179 } 179 }
180 } 180 }
181 ls_item(sha1, base, baselen, pathname, mode, stage, NULL); 181 ls_item(sha1, base, baselen, pathname, mode, stage, NULL);
182 return 0; 182 return 0;
183} 183}
184 184
185 185
186/* 186/*
187 * Show a tree or a blob 187 * Show a tree or a blob
188 * rev: the commit pointing at the root tree object 188 * rev: the commit pointing at the root tree object
189 * path: path to tree or blob 189 * path: path to tree or blob
190 */ 190 */
191void cgit_print_tree(const char *rev, char *path) 191void cgit_print_tree(const char *rev, char *path)
192{ 192{
193 unsigned char sha1[20]; 193 unsigned char sha1[20];
194 struct commit *commit; 194 struct commit *commit;
195 const char *paths[] = {path, NULL}; 195 const char *paths[] = {path, NULL};
196 196
197 if (!rev) 197 if (!rev)
198 rev = ctx.qry.head; 198 rev = ctx.qry.head;
199 199
200 curr_rev = xstrdup(rev); 200 curr_rev = xstrdup(rev);
201 if (get_sha1(rev, sha1)) { 201 if (get_sha1(rev, sha1)) {
202 cgit_print_error(fmt("Invalid revision name: %s", rev)); 202 cgit_print_error(fmt("Invalid revision name: %s", rev));
203 return; 203 return;
204 } 204 }
205 commit = lookup_commit_reference(sha1); 205 commit = lookup_commit_reference(sha1);
206 if (!commit || parse_commit(commit)) { 206 if (!commit || parse_commit(commit)) {
207 cgit_print_error(fmt("Invalid commit reference: %s", rev)); 207 cgit_print_error(fmt("Invalid commit reference: %s", rev));
208 return; 208 return;
209 } 209 }
210 210
211 html("path: <a href='"); 211 html("path: <a href='");
212 html_attr(cgit_pageurl(ctx.qry.repo, "tree", fmt("h=%s", rev))); 212 html_attr(cgit_pageurl(ctx.qry.repo, "tree", fmt("h=%s", rev)));
213 html("'>root</a>"); 213 html("'>root</a>");
214 214
215 if (path == NULL) { 215 if (path == NULL) {
216 ls_tree(commit->tree->object.sha1, NULL); 216 ls_tree(commit->tree->object.sha1, NULL);
217 return; 217 return;
218 } 218 }
219 219
220 match_path = path; 220 match_path = path;
221 read_tree_recursive(commit->tree, NULL, 0, 0, paths, walk_tree, NULL); 221 read_tree_recursive(commit->tree, NULL, 0, 0, paths, walk_tree, NULL);
222 ls_tail(); 222 ls_tail();
223} 223}