summaryrefslogtreecommitdiffabout
Unidiff
Diffstat (more/less context) (ignore whitespace changes)
-rw-r--r--cgit.c6
-rw-r--r--cgit.css10
-rw-r--r--cgit.h1
-rw-r--r--cgitrc.5.txt2
-rw-r--r--cmd.c46
-rw-r--r--cmd.h3
-rw-r--r--shared.c1
-rw-r--r--ui-commit.c20
-rw-r--r--ui-commit.h2
-rw-r--r--ui-diff.c8
-rw-r--r--ui-log.c27
-rw-r--r--ui-patch.c6
-rw-r--r--ui-patch.h2
-rw-r--r--ui-refs.c2
-rw-r--r--ui-shared.c216
-rw-r--r--ui-shared.h72
-rw-r--r--ui-tree.c15
17 files changed, 275 insertions, 164 deletions
diff --git a/cgit.c b/cgit.c
index 38bc136..d4fcfa7 100644
--- a/cgit.c
+++ b/cgit.c
@@ -1,741 +1,747 @@
1/* cgit.c: cgi for the git scm 1/* cgit.c: cgi for the git scm
2 * 2 *
3 * Copyright (C) 2006 Lars Hjemli 3 * Copyright (C) 2006 Lars Hjemli
4 * 4 *
5 * Licensed under GNU General Public License v2 5 * Licensed under GNU General Public License v2
6 * (see COPYING for full license text) 6 * (see COPYING for full license text)
7 */ 7 */
8 8
9#include "cgit.h" 9#include "cgit.h"
10#include "cache.h" 10#include "cache.h"
11#include "cmd.h" 11#include "cmd.h"
12#include "configfile.h" 12#include "configfile.h"
13#include "html.h" 13#include "html.h"
14#include "ui-shared.h" 14#include "ui-shared.h"
15#include "ui-stats.h" 15#include "ui-stats.h"
16#include "scan-tree.h" 16#include "scan-tree.h"
17 17
18const char *cgit_version = CGIT_VERSION; 18const char *cgit_version = CGIT_VERSION;
19 19
20void add_mimetype(const char *name, const char *value) 20void add_mimetype(const char *name, const char *value)
21{ 21{
22 struct string_list_item *item; 22 struct string_list_item *item;
23 23
24 item = string_list_insert(xstrdup(name), &ctx.cfg.mimetypes); 24 item = string_list_insert(xstrdup(name), &ctx.cfg.mimetypes);
25 item->util = xstrdup(value); 25 item->util = xstrdup(value);
26} 26}
27 27
28struct cgit_filter *new_filter(const char *cmd, int extra_args) 28struct cgit_filter *new_filter(const char *cmd, int extra_args)
29{ 29{
30 struct cgit_filter *f; 30 struct cgit_filter *f;
31 31
32 if (!cmd || !cmd[0]) 32 if (!cmd || !cmd[0])
33 return NULL; 33 return NULL;
34 34
35 f = xmalloc(sizeof(struct cgit_filter)); 35 f = xmalloc(sizeof(struct cgit_filter));
36 f->cmd = xstrdup(cmd); 36 f->cmd = xstrdup(cmd);
37 f->argv = xmalloc((2 + extra_args) * sizeof(char *)); 37 f->argv = xmalloc((2 + extra_args) * sizeof(char *));
38 f->argv[0] = f->cmd; 38 f->argv[0] = f->cmd;
39 f->argv[1] = NULL; 39 f->argv[1] = NULL;
40 return f; 40 return f;
41} 41}
42 42
43static void process_cached_repolist(const char *path); 43static void process_cached_repolist(const char *path);
44 44
45void repo_config(struct cgit_repo *repo, const char *name, const char *value) 45void repo_config(struct cgit_repo *repo, const char *name, const char *value)
46{ 46{
47 if (!strcmp(name, "name")) 47 if (!strcmp(name, "name"))
48 repo->name = xstrdup(value); 48 repo->name = xstrdup(value);
49 else if (!strcmp(name, "clone-url")) 49 else if (!strcmp(name, "clone-url"))
50 repo->clone_url = xstrdup(value); 50 repo->clone_url = xstrdup(value);
51 else if (!strcmp(name, "desc")) 51 else if (!strcmp(name, "desc"))
52 repo->desc = xstrdup(value); 52 repo->desc = xstrdup(value);
53 else if (!strcmp(name, "owner")) 53 else if (!strcmp(name, "owner"))
54 repo->owner = xstrdup(value); 54 repo->owner = xstrdup(value);
55 else if (!strcmp(name, "defbranch")) 55 else if (!strcmp(name, "defbranch"))
56 repo->defbranch = xstrdup(value); 56 repo->defbranch = xstrdup(value);
57 else if (!strcmp(name, "snapshots")) 57 else if (!strcmp(name, "snapshots"))
58 repo->snapshots = ctx.cfg.snapshots & cgit_parse_snapshots_mask(value); 58 repo->snapshots = ctx.cfg.snapshots & cgit_parse_snapshots_mask(value);
59 else if (!strcmp(name, "enable-log-filecount")) 59 else if (!strcmp(name, "enable-log-filecount"))
60 repo->enable_log_filecount = ctx.cfg.enable_log_filecount * atoi(value); 60 repo->enable_log_filecount = ctx.cfg.enable_log_filecount * atoi(value);
61 else if (!strcmp(name, "enable-log-linecount")) 61 else if (!strcmp(name, "enable-log-linecount"))
62 repo->enable_log_linecount = ctx.cfg.enable_log_linecount * atoi(value); 62 repo->enable_log_linecount = ctx.cfg.enable_log_linecount * atoi(value);
63 else if (!strcmp(name, "enable-remote-branches")) 63 else if (!strcmp(name, "enable-remote-branches"))
64 repo->enable_remote_branches = atoi(value); 64 repo->enable_remote_branches = atoi(value);
65 else if (!strcmp(name, "enable-subject-links")) 65 else if (!strcmp(name, "enable-subject-links"))
66 repo->enable_subject_links = atoi(value); 66 repo->enable_subject_links = atoi(value);
67 else if (!strcmp(name, "max-stats")) 67 else if (!strcmp(name, "max-stats"))
68 repo->max_stats = cgit_find_stats_period(value, NULL); 68 repo->max_stats = cgit_find_stats_period(value, NULL);
69 else if (!strcmp(name, "module-link")) 69 else if (!strcmp(name, "module-link"))
70 repo->module_link= xstrdup(value); 70 repo->module_link= xstrdup(value);
71 else if (!strcmp(name, "section")) 71 else if (!strcmp(name, "section"))
72 repo->section = xstrdup(value); 72 repo->section = xstrdup(value);
73 else if (!strcmp(name, "readme") && value != NULL) { 73 else if (!strcmp(name, "readme") && value != NULL) {
74 if (*value == '/') 74 if (*value == '/')
75 repo->readme = xstrdup(value); 75 repo->readme = xstrdup(value);
76 else 76 else
77 repo->readme = xstrdup(fmt("%s/%s", repo->path, value)); 77 repo->readme = xstrdup(fmt("%s/%s", repo->path, value));
78 } else if (ctx.cfg.enable_filter_overrides) { 78 } else if (ctx.cfg.enable_filter_overrides) {
79 if (!strcmp(name, "about-filter")) 79 if (!strcmp(name, "about-filter"))
80 repo->about_filter = new_filter(value, 0); 80 repo->about_filter = new_filter(value, 0);
81 else if (!strcmp(name, "commit-filter")) 81 else if (!strcmp(name, "commit-filter"))
82 repo->commit_filter = new_filter(value, 0); 82 repo->commit_filter = new_filter(value, 0);
83 else if (!strcmp(name, "source-filter")) 83 else if (!strcmp(name, "source-filter"))
84 repo->source_filter = new_filter(value, 1); 84 repo->source_filter = new_filter(value, 1);
85 } 85 }
86} 86}
87 87
88void config_cb(const char *name, const char *value) 88void config_cb(const char *name, const char *value)
89{ 89{
90 if (!strcmp(name, "section") || !strcmp(name, "repo.group")) 90 if (!strcmp(name, "section") || !strcmp(name, "repo.group"))
91 ctx.cfg.section = xstrdup(value); 91 ctx.cfg.section = xstrdup(value);
92 else if (!strcmp(name, "repo.url")) 92 else if (!strcmp(name, "repo.url"))
93 ctx.repo = cgit_add_repo(value); 93 ctx.repo = cgit_add_repo(value);
94 else if (ctx.repo && !strcmp(name, "repo.path")) 94 else if (ctx.repo && !strcmp(name, "repo.path"))
95 ctx.repo->path = trim_end(value, '/'); 95 ctx.repo->path = trim_end(value, '/');
96 else if (ctx.repo && !prefixcmp(name, "repo.")) 96 else if (ctx.repo && !prefixcmp(name, "repo."))
97 repo_config(ctx.repo, name + 5, value); 97 repo_config(ctx.repo, name + 5, value);
98 else if (!strcmp(name, "root-title")) 98 else if (!strcmp(name, "root-title"))
99 ctx.cfg.root_title = xstrdup(value); 99 ctx.cfg.root_title = xstrdup(value);
100 else if (!strcmp(name, "root-desc")) 100 else if (!strcmp(name, "root-desc"))
101 ctx.cfg.root_desc = xstrdup(value); 101 ctx.cfg.root_desc = xstrdup(value);
102 else if (!strcmp(name, "root-readme")) 102 else if (!strcmp(name, "root-readme"))
103 ctx.cfg.root_readme = xstrdup(value); 103 ctx.cfg.root_readme = xstrdup(value);
104 else if (!strcmp(name, "css")) 104 else if (!strcmp(name, "css"))
105 ctx.cfg.css = xstrdup(value); 105 ctx.cfg.css = xstrdup(value);
106 else if (!strcmp(name, "favicon")) 106 else if (!strcmp(name, "favicon"))
107 ctx.cfg.favicon = xstrdup(value); 107 ctx.cfg.favicon = xstrdup(value);
108 else if (!strcmp(name, "footer")) 108 else if (!strcmp(name, "footer"))
109 ctx.cfg.footer = xstrdup(value); 109 ctx.cfg.footer = xstrdup(value);
110 else if (!strcmp(name, "head-include")) 110 else if (!strcmp(name, "head-include"))
111 ctx.cfg.head_include = xstrdup(value); 111 ctx.cfg.head_include = xstrdup(value);
112 else if (!strcmp(name, "header")) 112 else if (!strcmp(name, "header"))
113 ctx.cfg.header = xstrdup(value); 113 ctx.cfg.header = xstrdup(value);
114 else if (!strcmp(name, "logo")) 114 else if (!strcmp(name, "logo"))
115 ctx.cfg.logo = xstrdup(value); 115 ctx.cfg.logo = xstrdup(value);
116 else if (!strcmp(name, "index-header")) 116 else if (!strcmp(name, "index-header"))
117 ctx.cfg.index_header = xstrdup(value); 117 ctx.cfg.index_header = xstrdup(value);
118 else if (!strcmp(name, "index-info")) 118 else if (!strcmp(name, "index-info"))
119 ctx.cfg.index_info = xstrdup(value); 119 ctx.cfg.index_info = xstrdup(value);
120 else if (!strcmp(name, "logo-link")) 120 else if (!strcmp(name, "logo-link"))
121 ctx.cfg.logo_link = xstrdup(value); 121 ctx.cfg.logo_link = xstrdup(value);
122 else if (!strcmp(name, "module-link")) 122 else if (!strcmp(name, "module-link"))
123 ctx.cfg.module_link = xstrdup(value); 123 ctx.cfg.module_link = xstrdup(value);
124 else if (!strcmp(name, "virtual-root")) { 124 else if (!strcmp(name, "virtual-root")) {
125 ctx.cfg.virtual_root = trim_end(value, '/'); 125 ctx.cfg.virtual_root = trim_end(value, '/');
126 if (!ctx.cfg.virtual_root && (!strcmp(value, "/"))) 126 if (!ctx.cfg.virtual_root && (!strcmp(value, "/")))
127 ctx.cfg.virtual_root = ""; 127 ctx.cfg.virtual_root = "";
128 } else if (!strcmp(name, "nocache")) 128 } else if (!strcmp(name, "nocache"))
129 ctx.cfg.nocache = atoi(value); 129 ctx.cfg.nocache = atoi(value);
130 else if (!strcmp(name, "noplainemail")) 130 else if (!strcmp(name, "noplainemail"))
131 ctx.cfg.noplainemail = atoi(value); 131 ctx.cfg.noplainemail = atoi(value);
132 else if (!strcmp(name, "noheader")) 132 else if (!strcmp(name, "noheader"))
133 ctx.cfg.noheader = atoi(value); 133 ctx.cfg.noheader = atoi(value);
134 else if (!strcmp(name, "snapshots")) 134 else if (!strcmp(name, "snapshots"))
135 ctx.cfg.snapshots = cgit_parse_snapshots_mask(value); 135 ctx.cfg.snapshots = cgit_parse_snapshots_mask(value);
136 else if (!strcmp(name, "enable-filter-overrides")) 136 else if (!strcmp(name, "enable-filter-overrides"))
137 ctx.cfg.enable_filter_overrides = atoi(value); 137 ctx.cfg.enable_filter_overrides = atoi(value);
138 else if (!strcmp(name, "enable-index-links")) 138 else if (!strcmp(name, "enable-index-links"))
139 ctx.cfg.enable_index_links = atoi(value); 139 ctx.cfg.enable_index_links = atoi(value);
140 else if (!strcmp(name, "enable-log-filecount")) 140 else if (!strcmp(name, "enable-log-filecount"))
141 ctx.cfg.enable_log_filecount = atoi(value); 141 ctx.cfg.enable_log_filecount = atoi(value);
142 else if (!strcmp(name, "enable-log-linecount")) 142 else if (!strcmp(name, "enable-log-linecount"))
143 ctx.cfg.enable_log_linecount = atoi(value); 143 ctx.cfg.enable_log_linecount = atoi(value);
144 else if (!strcmp(name, "enable-remote-branches")) 144 else if (!strcmp(name, "enable-remote-branches"))
145 ctx.cfg.enable_remote_branches = atoi(value); 145 ctx.cfg.enable_remote_branches = atoi(value);
146 else if (!strcmp(name, "enable-subject-links")) 146 else if (!strcmp(name, "enable-subject-links"))
147 ctx.cfg.enable_subject_links = atoi(value); 147 ctx.cfg.enable_subject_links = atoi(value);
148 else if (!strcmp(name, "enable-tree-linenumbers")) 148 else if (!strcmp(name, "enable-tree-linenumbers"))
149 ctx.cfg.enable_tree_linenumbers = atoi(value); 149 ctx.cfg.enable_tree_linenumbers = atoi(value);
150 else if (!strcmp(name, "max-stats")) 150 else if (!strcmp(name, "max-stats"))
151 ctx.cfg.max_stats = cgit_find_stats_period(value, NULL); 151 ctx.cfg.max_stats = cgit_find_stats_period(value, NULL);
152 else if (!strcmp(name, "cache-size")) 152 else if (!strcmp(name, "cache-size"))
153 ctx.cfg.cache_size = atoi(value); 153 ctx.cfg.cache_size = atoi(value);
154 else if (!strcmp(name, "cache-root")) 154 else if (!strcmp(name, "cache-root"))
155 ctx.cfg.cache_root = xstrdup(value); 155 ctx.cfg.cache_root = xstrdup(value);
156 else if (!strcmp(name, "cache-root-ttl")) 156 else if (!strcmp(name, "cache-root-ttl"))
157 ctx.cfg.cache_root_ttl = atoi(value); 157 ctx.cfg.cache_root_ttl = atoi(value);
158 else if (!strcmp(name, "cache-repo-ttl")) 158 else if (!strcmp(name, "cache-repo-ttl"))
159 ctx.cfg.cache_repo_ttl = atoi(value); 159 ctx.cfg.cache_repo_ttl = atoi(value);
160 else if (!strcmp(name, "cache-scanrc-ttl")) 160 else if (!strcmp(name, "cache-scanrc-ttl"))
161 ctx.cfg.cache_scanrc_ttl = atoi(value); 161 ctx.cfg.cache_scanrc_ttl = atoi(value);
162 else if (!strcmp(name, "cache-static-ttl")) 162 else if (!strcmp(name, "cache-static-ttl"))
163 ctx.cfg.cache_static_ttl = atoi(value); 163 ctx.cfg.cache_static_ttl = atoi(value);
164 else if (!strcmp(name, "cache-dynamic-ttl")) 164 else if (!strcmp(name, "cache-dynamic-ttl"))
165 ctx.cfg.cache_dynamic_ttl = atoi(value); 165 ctx.cfg.cache_dynamic_ttl = atoi(value);
166 else if (!strcmp(name, "about-filter")) 166 else if (!strcmp(name, "about-filter"))
167 ctx.cfg.about_filter = new_filter(value, 0); 167 ctx.cfg.about_filter = new_filter(value, 0);
168 else if (!strcmp(name, "commit-filter")) 168 else if (!strcmp(name, "commit-filter"))
169 ctx.cfg.commit_filter = new_filter(value, 0); 169 ctx.cfg.commit_filter = new_filter(value, 0);
170 else if (!strcmp(name, "embedded")) 170 else if (!strcmp(name, "embedded"))
171 ctx.cfg.embedded = atoi(value); 171 ctx.cfg.embedded = atoi(value);
172 else if (!strcmp(name, "max-atom-items")) 172 else if (!strcmp(name, "max-atom-items"))
173 ctx.cfg.max_atom_items = atoi(value); 173 ctx.cfg.max_atom_items = atoi(value);
174 else if (!strcmp(name, "max-message-length")) 174 else if (!strcmp(name, "max-message-length"))
175 ctx.cfg.max_msg_len = atoi(value); 175 ctx.cfg.max_msg_len = atoi(value);
176 else if (!strcmp(name, "max-repodesc-length")) 176 else if (!strcmp(name, "max-repodesc-length"))
177 ctx.cfg.max_repodesc_len = atoi(value); 177 ctx.cfg.max_repodesc_len = atoi(value);
178 else if (!strcmp(name, "max-blob-size")) 178 else if (!strcmp(name, "max-blob-size"))
179 ctx.cfg.max_blob_size = atoi(value); 179 ctx.cfg.max_blob_size = atoi(value);
180 else if (!strcmp(name, "max-repo-count")) 180 else if (!strcmp(name, "max-repo-count"))
181 ctx.cfg.max_repo_count = atoi(value); 181 ctx.cfg.max_repo_count = atoi(value);
182 else if (!strcmp(name, "max-commit-count")) 182 else if (!strcmp(name, "max-commit-count"))
183 ctx.cfg.max_commit_count = atoi(value); 183 ctx.cfg.max_commit_count = atoi(value);
184 else if (!strcmp(name, "scan-path")) 184 else if (!strcmp(name, "scan-path"))
185 if (!ctx.cfg.nocache && ctx.cfg.cache_size) 185 if (!ctx.cfg.nocache && ctx.cfg.cache_size)
186 process_cached_repolist(value); 186 process_cached_repolist(value);
187 else 187 else
188 scan_tree(value, repo_config); 188 scan_tree(value, repo_config);
189 else if (!strcmp(name, "source-filter")) 189 else if (!strcmp(name, "source-filter"))
190 ctx.cfg.source_filter = new_filter(value, 1); 190 ctx.cfg.source_filter = new_filter(value, 1);
191 else if (!strcmp(name, "summary-log")) 191 else if (!strcmp(name, "summary-log"))
192 ctx.cfg.summary_log = atoi(value); 192 ctx.cfg.summary_log = atoi(value);
193 else if (!strcmp(name, "summary-branches")) 193 else if (!strcmp(name, "summary-branches"))
194 ctx.cfg.summary_branches = atoi(value); 194 ctx.cfg.summary_branches = atoi(value);
195 else if (!strcmp(name, "summary-tags")) 195 else if (!strcmp(name, "summary-tags"))
196 ctx.cfg.summary_tags = atoi(value); 196 ctx.cfg.summary_tags = atoi(value);
197 else if (!strcmp(name, "side-by-side-diffs")) 197 else if (!strcmp(name, "side-by-side-diffs"))
198 ctx.cfg.ssdiff = atoi(value); 198 ctx.cfg.ssdiff = atoi(value);
199 else if (!strcmp(name, "agefile")) 199 else if (!strcmp(name, "agefile"))
200 ctx.cfg.agefile = xstrdup(value); 200 ctx.cfg.agefile = xstrdup(value);
201 else if (!strcmp(name, "renamelimit")) 201 else if (!strcmp(name, "renamelimit"))
202 ctx.cfg.renamelimit = atoi(value); 202 ctx.cfg.renamelimit = atoi(value);
203 else if (!strcmp(name, "robots")) 203 else if (!strcmp(name, "robots"))
204 ctx.cfg.robots = xstrdup(value); 204 ctx.cfg.robots = xstrdup(value);
205 else if (!strcmp(name, "clone-prefix")) 205 else if (!strcmp(name, "clone-prefix"))
206 ctx.cfg.clone_prefix = xstrdup(value); 206 ctx.cfg.clone_prefix = xstrdup(value);
207 else if (!strcmp(name, "local-time")) 207 else if (!strcmp(name, "local-time"))
208 ctx.cfg.local_time = atoi(value); 208 ctx.cfg.local_time = atoi(value);
209 else if (!prefixcmp(name, "mimetype.")) 209 else if (!prefixcmp(name, "mimetype."))
210 add_mimetype(name + 9, value); 210 add_mimetype(name + 9, value);
211 else if (!strcmp(name, "include")) 211 else if (!strcmp(name, "include"))
212 parse_configfile(value, config_cb); 212 parse_configfile(value, config_cb);
213} 213}
214 214
215static void querystring_cb(const char *name, const char *value) 215static void querystring_cb(const char *name, const char *value)
216{ 216{
217 if (!value) 217 if (!value)
218 value = ""; 218 value = "";
219 219
220 if (!strcmp(name,"r")) { 220 if (!strcmp(name,"r")) {
221 ctx.qry.repo = xstrdup(value); 221 ctx.qry.repo = xstrdup(value);
222 ctx.repo = cgit_get_repoinfo(value); 222 ctx.repo = cgit_get_repoinfo(value);
223 } else if (!strcmp(name, "p")) { 223 } else if (!strcmp(name, "p")) {
224 ctx.qry.page = xstrdup(value); 224 ctx.qry.page = xstrdup(value);
225 } else if (!strcmp(name, "url")) { 225 } else if (!strcmp(name, "url")) {
226 if (*value == '/') 226 if (*value == '/')
227 value++; 227 value++;
228 ctx.qry.url = xstrdup(value); 228 ctx.qry.url = xstrdup(value);
229 cgit_parse_url(value); 229 cgit_parse_url(value);
230 } else if (!strcmp(name, "qt")) { 230 } else if (!strcmp(name, "qt")) {
231 ctx.qry.grep = xstrdup(value); 231 ctx.qry.grep = xstrdup(value);
232 } else if (!strcmp(name, "q")) { 232 } else if (!strcmp(name, "q")) {
233 ctx.qry.search = xstrdup(value); 233 ctx.qry.search = xstrdup(value);
234 } else if (!strcmp(name, "h")) { 234 } else if (!strcmp(name, "h")) {
235 ctx.qry.head = xstrdup(value); 235 ctx.qry.head = xstrdup(value);
236 ctx.qry.has_symref = 1; 236 ctx.qry.has_symref = 1;
237 } else if (!strcmp(name, "id")) { 237 } else if (!strcmp(name, "id")) {
238 ctx.qry.sha1 = xstrdup(value); 238 ctx.qry.sha1 = xstrdup(value);
239 ctx.qry.has_sha1 = 1; 239 ctx.qry.has_sha1 = 1;
240 } else if (!strcmp(name, "id2")) { 240 } else if (!strcmp(name, "id2")) {
241 ctx.qry.sha2 = xstrdup(value); 241 ctx.qry.sha2 = xstrdup(value);
242 ctx.qry.has_sha1 = 1; 242 ctx.qry.has_sha1 = 1;
243 } else if (!strcmp(name, "ofs")) { 243 } else if (!strcmp(name, "ofs")) {
244 ctx.qry.ofs = atoi(value); 244 ctx.qry.ofs = atoi(value);
245 } else if (!strcmp(name, "path")) { 245 } else if (!strcmp(name, "path")) {
246 ctx.qry.path = trim_end(value, '/'); 246 ctx.qry.path = trim_end(value, '/');
247 } else if (!strcmp(name, "name")) { 247 } else if (!strcmp(name, "name")) {
248 ctx.qry.name = xstrdup(value); 248 ctx.qry.name = xstrdup(value);
249 } else if (!strcmp(name, "mimetype")) { 249 } else if (!strcmp(name, "mimetype")) {
250 ctx.qry.mimetype = xstrdup(value); 250 ctx.qry.mimetype = xstrdup(value);
251 } else if (!strcmp(name, "s")){ 251 } else if (!strcmp(name, "s")){
252 ctx.qry.sort = xstrdup(value); 252 ctx.qry.sort = xstrdup(value);
253 } else if (!strcmp(name, "showmsg")) { 253 } else if (!strcmp(name, "showmsg")) {
254 ctx.qry.showmsg = atoi(value); 254 ctx.qry.showmsg = atoi(value);
255 } else if (!strcmp(name, "period")) { 255 } else if (!strcmp(name, "period")) {
256 ctx.qry.period = xstrdup(value); 256 ctx.qry.period = xstrdup(value);
257 } else if (!strcmp(name, "ss")) { 257 } else if (!strcmp(name, "ss")) {
258 ctx.qry.ssdiff = atoi(value); 258 ctx.qry.ssdiff = atoi(value);
259 } else if (!strcmp(name, "all")) { 259 } else if (!strcmp(name, "all")) {
260 ctx.qry.show_all = atoi(value); 260 ctx.qry.show_all = atoi(value);
261 } 261 }
262} 262}
263 263
264char *xstrdupn(const char *str) 264char *xstrdupn(const char *str)
265{ 265{
266 return (str ? xstrdup(str) : NULL); 266 return (str ? xstrdup(str) : NULL);
267} 267}
268 268
269static void prepare_context(struct cgit_context *ctx) 269static void prepare_context(struct cgit_context *ctx)
270{ 270{
271 memset(ctx, 0, sizeof(*ctx)); 271 memset(ctx, 0, sizeof(*ctx));
272 ctx->cfg.agefile = "info/web/last-modified"; 272 ctx->cfg.agefile = "info/web/last-modified";
273 ctx->cfg.nocache = 0; 273 ctx->cfg.nocache = 0;
274 ctx->cfg.cache_size = 0; 274 ctx->cfg.cache_size = 0;
275 ctx->cfg.cache_dynamic_ttl = 5; 275 ctx->cfg.cache_dynamic_ttl = 5;
276 ctx->cfg.cache_max_create_time = 5; 276 ctx->cfg.cache_max_create_time = 5;
277 ctx->cfg.cache_repo_ttl = 5; 277 ctx->cfg.cache_repo_ttl = 5;
278 ctx->cfg.cache_root = CGIT_CACHE_ROOT; 278 ctx->cfg.cache_root = CGIT_CACHE_ROOT;
279 ctx->cfg.cache_root_ttl = 5; 279 ctx->cfg.cache_root_ttl = 5;
280 ctx->cfg.cache_scanrc_ttl = 15; 280 ctx->cfg.cache_scanrc_ttl = 15;
281 ctx->cfg.cache_static_ttl = -1; 281 ctx->cfg.cache_static_ttl = -1;
282 ctx->cfg.css = "/cgit.css"; 282 ctx->cfg.css = "/cgit.css";
283 ctx->cfg.logo = "/cgit.png"; 283 ctx->cfg.logo = "/cgit.png";
284 ctx->cfg.local_time = 0; 284 ctx->cfg.local_time = 0;
285 ctx->cfg.enable_tree_linenumbers = 1; 285 ctx->cfg.enable_tree_linenumbers = 1;
286 ctx->cfg.max_repo_count = 50; 286 ctx->cfg.max_repo_count = 50;
287 ctx->cfg.max_commit_count = 50; 287 ctx->cfg.max_commit_count = 50;
288 ctx->cfg.max_lock_attempts = 5; 288 ctx->cfg.max_lock_attempts = 5;
289 ctx->cfg.max_msg_len = 80; 289 ctx->cfg.max_msg_len = 80;
290 ctx->cfg.max_repodesc_len = 80; 290 ctx->cfg.max_repodesc_len = 80;
291 ctx->cfg.max_blob_size = 0; 291 ctx->cfg.max_blob_size = 0;
292 ctx->cfg.max_stats = 0; 292 ctx->cfg.max_stats = 0;
293 ctx->cfg.module_link = "./?repo=%s&page=commit&id=%s"; 293 ctx->cfg.module_link = "./?repo=%s&page=commit&id=%s";
294 ctx->cfg.renamelimit = -1; 294 ctx->cfg.renamelimit = -1;
295 ctx->cfg.robots = "index, nofollow"; 295 ctx->cfg.robots = "index, nofollow";
296 ctx->cfg.root_title = "Git repository browser"; 296 ctx->cfg.root_title = "Git repository browser";
297 ctx->cfg.root_desc = "a fast webinterface for the git dscm"; 297 ctx->cfg.root_desc = "a fast webinterface for the git dscm";
298 ctx->cfg.script_name = CGIT_SCRIPT_NAME; 298 ctx->cfg.script_name = CGIT_SCRIPT_NAME;
299 ctx->cfg.section = ""; 299 ctx->cfg.section = "";
300 ctx->cfg.summary_branches = 10; 300 ctx->cfg.summary_branches = 10;
301 ctx->cfg.summary_log = 10; 301 ctx->cfg.summary_log = 10;
302 ctx->cfg.summary_tags = 10; 302 ctx->cfg.summary_tags = 10;
303 ctx->cfg.max_atom_items = 10; 303 ctx->cfg.max_atom_items = 10;
304 ctx->cfg.ssdiff = 0; 304 ctx->cfg.ssdiff = 0;
305 ctx->env.cgit_config = xstrdupn(getenv("CGIT_CONFIG")); 305 ctx->env.cgit_config = xstrdupn(getenv("CGIT_CONFIG"));
306 ctx->env.http_host = xstrdupn(getenv("HTTP_HOST")); 306 ctx->env.http_host = xstrdupn(getenv("HTTP_HOST"));
307 ctx->env.https = xstrdupn(getenv("HTTPS")); 307 ctx->env.https = xstrdupn(getenv("HTTPS"));
308 ctx->env.no_http = xstrdupn(getenv("NO_HTTP")); 308 ctx->env.no_http = xstrdupn(getenv("NO_HTTP"));
309 ctx->env.path_info = xstrdupn(getenv("PATH_INFO")); 309 ctx->env.path_info = xstrdupn(getenv("PATH_INFO"));
310 ctx->env.query_string = xstrdupn(getenv("QUERY_STRING")); 310 ctx->env.query_string = xstrdupn(getenv("QUERY_STRING"));
311 ctx->env.request_method = xstrdupn(getenv("REQUEST_METHOD")); 311 ctx->env.request_method = xstrdupn(getenv("REQUEST_METHOD"));
312 ctx->env.script_name = xstrdupn(getenv("SCRIPT_NAME")); 312 ctx->env.script_name = xstrdupn(getenv("SCRIPT_NAME"));
313 ctx->env.server_name = xstrdupn(getenv("SERVER_NAME")); 313 ctx->env.server_name = xstrdupn(getenv("SERVER_NAME"));
314 ctx->env.server_port = xstrdupn(getenv("SERVER_PORT")); 314 ctx->env.server_port = xstrdupn(getenv("SERVER_PORT"));
315 ctx->page.mimetype = "text/html"; 315 ctx->page.mimetype = "text/html";
316 ctx->page.charset = PAGE_ENCODING; 316 ctx->page.charset = PAGE_ENCODING;
317 ctx->page.filename = NULL; 317 ctx->page.filename = NULL;
318 ctx->page.size = 0; 318 ctx->page.size = 0;
319 ctx->page.modified = time(NULL); 319 ctx->page.modified = time(NULL);
320 ctx->page.expires = ctx->page.modified; 320 ctx->page.expires = ctx->page.modified;
321 ctx->page.etag = NULL; 321 ctx->page.etag = NULL;
322 memset(&ctx->cfg.mimetypes, 0, sizeof(struct string_list)); 322 memset(&ctx->cfg.mimetypes, 0, sizeof(struct string_list));
323 if (ctx->env.script_name) 323 if (ctx->env.script_name)
324 ctx->cfg.script_name = ctx->env.script_name; 324 ctx->cfg.script_name = ctx->env.script_name;
325 if (ctx->env.query_string) 325 if (ctx->env.query_string)
326 ctx->qry.raw = ctx->env.query_string; 326 ctx->qry.raw = ctx->env.query_string;
327 if (!ctx->env.cgit_config) 327 if (!ctx->env.cgit_config)
328 ctx->env.cgit_config = CGIT_CONFIG; 328 ctx->env.cgit_config = CGIT_CONFIG;
329} 329}
330 330
331struct refmatch { 331struct refmatch {
332 char *req_ref; 332 char *req_ref;
333 char *first_ref; 333 char *first_ref;
334 int match; 334 int match;
335}; 335};
336 336
337int find_current_ref(const char *refname, const unsigned char *sha1, 337int find_current_ref(const char *refname, const unsigned char *sha1,
338 int flags, void *cb_data) 338 int flags, void *cb_data)
339{ 339{
340 struct refmatch *info; 340 struct refmatch *info;
341 341
342 info = (struct refmatch *)cb_data; 342 info = (struct refmatch *)cb_data;
343 if (!strcmp(refname, info->req_ref)) 343 if (!strcmp(refname, info->req_ref))
344 info->match = 1; 344 info->match = 1;
345 if (!info->first_ref) 345 if (!info->first_ref)
346 info->first_ref = xstrdup(refname); 346 info->first_ref = xstrdup(refname);
347 return info->match; 347 return info->match;
348} 348}
349 349
350char *find_default_branch(struct cgit_repo *repo) 350char *find_default_branch(struct cgit_repo *repo)
351{ 351{
352 struct refmatch info; 352 struct refmatch info;
353 char *ref; 353 char *ref;
354 354
355 info.req_ref = repo->defbranch; 355 info.req_ref = repo->defbranch;
356 info.first_ref = NULL; 356 info.first_ref = NULL;
357 info.match = 0; 357 info.match = 0;
358 for_each_branch_ref(find_current_ref, &info); 358 for_each_branch_ref(find_current_ref, &info);
359 if (info.match) 359 if (info.match)
360 ref = info.req_ref; 360 ref = info.req_ref;
361 else 361 else
362 ref = info.first_ref; 362 ref = info.first_ref;
363 if (ref) 363 if (ref)
364 ref = xstrdup(ref); 364 ref = xstrdup(ref);
365 return ref; 365 return ref;
366} 366}
367 367
368static int prepare_repo_cmd(struct cgit_context *ctx) 368static int prepare_repo_cmd(struct cgit_context *ctx)
369{ 369{
370 char *tmp; 370 char *tmp;
371 unsigned char sha1[20]; 371 unsigned char sha1[20];
372 int nongit = 0; 372 int nongit = 0;
373 373
374 setenv("GIT_DIR", ctx->repo->path, 1); 374 setenv("GIT_DIR", ctx->repo->path, 1);
375 setup_git_directory_gently(&nongit); 375 setup_git_directory_gently(&nongit);
376 if (nongit) { 376 if (nongit) {
377 ctx->page.title = fmt("%s - %s", ctx->cfg.root_title, 377 ctx->page.title = fmt("%s - %s", ctx->cfg.root_title,
378 "config error"); 378 "config error");
379 tmp = fmt("Not a git repository: '%s'", ctx->repo->path); 379 tmp = fmt("Not a git repository: '%s'", ctx->repo->path);
380 ctx->repo = NULL; 380 ctx->repo = NULL;
381 cgit_print_http_headers(ctx); 381 cgit_print_http_headers(ctx);
382 cgit_print_docstart(ctx); 382 cgit_print_docstart(ctx);
383 cgit_print_pageheader(ctx); 383 cgit_print_pageheader(ctx);
384 cgit_print_error(tmp); 384 cgit_print_error(tmp);
385 cgit_print_docend(); 385 cgit_print_docend();
386 return 1; 386 return 1;
387 } 387 }
388 ctx->page.title = fmt("%s - %s", ctx->repo->name, ctx->repo->desc); 388 ctx->page.title = fmt("%s - %s", ctx->repo->name, ctx->repo->desc);
389 389
390 if (!ctx->qry.head) { 390 if (!ctx->qry.head) {
391 ctx->qry.nohead = 1; 391 ctx->qry.nohead = 1;
392 ctx->qry.head = find_default_branch(ctx->repo); 392 ctx->qry.head = find_default_branch(ctx->repo);
393 ctx->repo->defbranch = ctx->qry.head; 393 ctx->repo->defbranch = ctx->qry.head;
394 } 394 }
395 395
396 if (!ctx->qry.head) { 396 if (!ctx->qry.head) {
397 cgit_print_http_headers(ctx); 397 cgit_print_http_headers(ctx);
398 cgit_print_docstart(ctx); 398 cgit_print_docstart(ctx);
399 cgit_print_pageheader(ctx); 399 cgit_print_pageheader(ctx);
400 cgit_print_error("Repository seems to be empty"); 400 cgit_print_error("Repository seems to be empty");
401 cgit_print_docend(); 401 cgit_print_docend();
402 return 1; 402 return 1;
403 } 403 }
404 404
405 if (get_sha1(ctx->qry.head, sha1)) { 405 if (get_sha1(ctx->qry.head, sha1)) {
406 tmp = xstrdup(ctx->qry.head); 406 tmp = xstrdup(ctx->qry.head);
407 ctx->qry.head = ctx->repo->defbranch; 407 ctx->qry.head = ctx->repo->defbranch;
408 ctx->page.status = 404; 408 ctx->page.status = 404;
409 ctx->page.statusmsg = "not found"; 409 ctx->page.statusmsg = "not found";
410 cgit_print_http_headers(ctx); 410 cgit_print_http_headers(ctx);
411 cgit_print_docstart(ctx); 411 cgit_print_docstart(ctx);
412 cgit_print_pageheader(ctx); 412 cgit_print_pageheader(ctx);
413 cgit_print_error(fmt("Invalid branch: %s", tmp)); 413 cgit_print_error(fmt("Invalid branch: %s", tmp));
414 cgit_print_docend(); 414 cgit_print_docend();
415 return 1; 415 return 1;
416 } 416 }
417 return 0; 417 return 0;
418} 418}
419 419
420static void process_request(void *cbdata) 420static void process_request(void *cbdata)
421{ 421{
422 struct cgit_context *ctx = cbdata; 422 struct cgit_context *ctx = cbdata;
423 struct cgit_cmd *cmd; 423 struct cgit_cmd *cmd;
424 424
425 cmd = cgit_get_cmd(ctx); 425 cmd = cgit_get_cmd(ctx);
426 if (!cmd) { 426 if (!cmd) {
427 ctx->page.title = "cgit error"; 427 ctx->page.title = "cgit error";
428 cgit_print_http_headers(ctx); 428 cgit_print_http_headers(ctx);
429 cgit_print_docstart(ctx); 429 cgit_print_docstart(ctx);
430 cgit_print_pageheader(ctx); 430 cgit_print_pageheader(ctx);
431 cgit_print_error("Invalid request"); 431 cgit_print_error("Invalid request");
432 cgit_print_docend(); 432 cgit_print_docend();
433 return; 433 return;
434 } 434 }
435 435
436 /* If cmd->want_vpath is set, assume ctx->qry.path contains a "virtual"
437 * in-project path limit to be made available at ctx->qry.vpath.
438 * Otherwise, no path limit is in effect (ctx->qry.vpath = NULL).
439 */
440 ctx->qry.vpath = cmd->want_vpath ? ctx->qry.path : NULL;
441
436 if (cmd->want_repo && !ctx->repo) { 442 if (cmd->want_repo && !ctx->repo) {
437 cgit_print_http_headers(ctx); 443 cgit_print_http_headers(ctx);
438 cgit_print_docstart(ctx); 444 cgit_print_docstart(ctx);
439 cgit_print_pageheader(ctx); 445 cgit_print_pageheader(ctx);
440 cgit_print_error(fmt("No repository selected")); 446 cgit_print_error(fmt("No repository selected"));
441 cgit_print_docend(); 447 cgit_print_docend();
442 return; 448 return;
443 } 449 }
444 450
445 if (ctx->repo && prepare_repo_cmd(ctx)) 451 if (ctx->repo && prepare_repo_cmd(ctx))
446 return; 452 return;
447 453
448 if (cmd->want_layout) { 454 if (cmd->want_layout) {
449 cgit_print_http_headers(ctx); 455 cgit_print_http_headers(ctx);
450 cgit_print_docstart(ctx); 456 cgit_print_docstart(ctx);
451 cgit_print_pageheader(ctx); 457 cgit_print_pageheader(ctx);
452 } 458 }
453 459
454 cmd->fn(ctx); 460 cmd->fn(ctx);
455 461
456 if (cmd->want_layout) 462 if (cmd->want_layout)
457 cgit_print_docend(); 463 cgit_print_docend();
458} 464}
459 465
460int cmp_repos(const void *a, const void *b) 466int cmp_repos(const void *a, const void *b)
461{ 467{
462 const struct cgit_repo *ra = a, *rb = b; 468 const struct cgit_repo *ra = a, *rb = b;
463 return strcmp(ra->url, rb->url); 469 return strcmp(ra->url, rb->url);
464} 470}
465 471
466char *build_snapshot_setting(int bitmap) 472char *build_snapshot_setting(int bitmap)
467{ 473{
468 const struct cgit_snapshot_format *f; 474 const struct cgit_snapshot_format *f;
469 char *result = xstrdup(""); 475 char *result = xstrdup("");
470 char *tmp; 476 char *tmp;
471 int len; 477 int len;
472 478
473 for (f = cgit_snapshot_formats; f->suffix; f++) { 479 for (f = cgit_snapshot_formats; f->suffix; f++) {
474 if (f->bit & bitmap) { 480 if (f->bit & bitmap) {
475 tmp = result; 481 tmp = result;
476 result = xstrdup(fmt("%s%s ", tmp, f->suffix)); 482 result = xstrdup(fmt("%s%s ", tmp, f->suffix));
477 free(tmp); 483 free(tmp);
478 } 484 }
479 } 485 }
480 len = strlen(result); 486 len = strlen(result);
481 if (len) 487 if (len)
482 result[len - 1] = '\0'; 488 result[len - 1] = '\0';
483 return result; 489 return result;
484} 490}
485 491
486char *get_first_line(char *txt) 492char *get_first_line(char *txt)
487{ 493{
488 char *t = xstrdup(txt); 494 char *t = xstrdup(txt);
489 char *p = strchr(t, '\n'); 495 char *p = strchr(t, '\n');
490 if (p) 496 if (p)
491 *p = '\0'; 497 *p = '\0';
492 return t; 498 return t;
493} 499}
494 500
495void print_repo(FILE *f, struct cgit_repo *repo) 501void print_repo(FILE *f, struct cgit_repo *repo)
496{ 502{
497 fprintf(f, "repo.url=%s\n", repo->url); 503 fprintf(f, "repo.url=%s\n", repo->url);
498 fprintf(f, "repo.name=%s\n", repo->name); 504 fprintf(f, "repo.name=%s\n", repo->name);
499 fprintf(f, "repo.path=%s\n", repo->path); 505 fprintf(f, "repo.path=%s\n", repo->path);
500 if (repo->owner) 506 if (repo->owner)
501 fprintf(f, "repo.owner=%s\n", repo->owner); 507 fprintf(f, "repo.owner=%s\n", repo->owner);
502 if (repo->desc) { 508 if (repo->desc) {
503 char *tmp = get_first_line(repo->desc); 509 char *tmp = get_first_line(repo->desc);
504 fprintf(f, "repo.desc=%s\n", tmp); 510 fprintf(f, "repo.desc=%s\n", tmp);
505 free(tmp); 511 free(tmp);
506 } 512 }
507 if (repo->readme) 513 if (repo->readme)
508 fprintf(f, "repo.readme=%s\n", repo->readme); 514 fprintf(f, "repo.readme=%s\n", repo->readme);
509 if (repo->defbranch) 515 if (repo->defbranch)
510 fprintf(f, "repo.defbranch=%s\n", repo->defbranch); 516 fprintf(f, "repo.defbranch=%s\n", repo->defbranch);
511 if (repo->module_link) 517 if (repo->module_link)
512 fprintf(f, "repo.module-link=%s\n", repo->module_link); 518 fprintf(f, "repo.module-link=%s\n", repo->module_link);
513 if (repo->section) 519 if (repo->section)
514 fprintf(f, "repo.section=%s\n", repo->section); 520 fprintf(f, "repo.section=%s\n", repo->section);
515 if (repo->clone_url) 521 if (repo->clone_url)
516 fprintf(f, "repo.clone-url=%s\n", repo->clone_url); 522 fprintf(f, "repo.clone-url=%s\n", repo->clone_url);
517 fprintf(f, "repo.enable-log-filecount=%d\n", 523 fprintf(f, "repo.enable-log-filecount=%d\n",
518 repo->enable_log_filecount); 524 repo->enable_log_filecount);
519 fprintf(f, "repo.enable-log-linecount=%d\n", 525 fprintf(f, "repo.enable-log-linecount=%d\n",
520 repo->enable_log_linecount); 526 repo->enable_log_linecount);
521 if (repo->about_filter && repo->about_filter != ctx.cfg.about_filter) 527 if (repo->about_filter && repo->about_filter != ctx.cfg.about_filter)
522 fprintf(f, "repo.about-filter=%s\n", repo->about_filter->cmd); 528 fprintf(f, "repo.about-filter=%s\n", repo->about_filter->cmd);
523 if (repo->commit_filter && repo->commit_filter != ctx.cfg.commit_filter) 529 if (repo->commit_filter && repo->commit_filter != ctx.cfg.commit_filter)
524 fprintf(f, "repo.commit-filter=%s\n", repo->commit_filter->cmd); 530 fprintf(f, "repo.commit-filter=%s\n", repo->commit_filter->cmd);
525 if (repo->source_filter && repo->source_filter != ctx.cfg.source_filter) 531 if (repo->source_filter && repo->source_filter != ctx.cfg.source_filter)
526 fprintf(f, "repo.source-filter=%s\n", repo->source_filter->cmd); 532 fprintf(f, "repo.source-filter=%s\n", repo->source_filter->cmd);
527 if (repo->snapshots != ctx.cfg.snapshots) { 533 if (repo->snapshots != ctx.cfg.snapshots) {
528 char *tmp = build_snapshot_setting(repo->snapshots); 534 char *tmp = build_snapshot_setting(repo->snapshots);
529 fprintf(f, "repo.snapshots=%s\n", tmp); 535 fprintf(f, "repo.snapshots=%s\n", tmp);
530 free(tmp); 536 free(tmp);
531 } 537 }
532 if (repo->max_stats != ctx.cfg.max_stats) 538 if (repo->max_stats != ctx.cfg.max_stats)
533 fprintf(f, "repo.max-stats=%s\n", 539 fprintf(f, "repo.max-stats=%s\n",
534 cgit_find_stats_periodname(repo->max_stats)); 540 cgit_find_stats_periodname(repo->max_stats));
535 fprintf(f, "\n"); 541 fprintf(f, "\n");
536} 542}
537 543
538void print_repolist(FILE *f, struct cgit_repolist *list, int start) 544void print_repolist(FILE *f, struct cgit_repolist *list, int start)
539{ 545{
540 int i; 546 int i;
541 547
542 for(i = start; i < list->count; i++) 548 for(i = start; i < list->count; i++)
543 print_repo(f, &list->repos[i]); 549 print_repo(f, &list->repos[i]);
544} 550}
545 551
546/* Scan 'path' for git repositories, save the resulting repolist in 'cached_rc' 552/* Scan 'path' for git repositories, save the resulting repolist in 'cached_rc'
547 * and return 0 on success. 553 * and return 0 on success.
548 */ 554 */
549static int generate_cached_repolist(const char *path, const char *cached_rc) 555static int generate_cached_repolist(const char *path, const char *cached_rc)
550{ 556{
551 char *locked_rc; 557 char *locked_rc;
552 int idx; 558 int idx;
553 FILE *f; 559 FILE *f;
554 560
555 locked_rc = xstrdup(fmt("%s.lock", cached_rc)); 561 locked_rc = xstrdup(fmt("%s.lock", cached_rc));
556 f = fopen(locked_rc, "wx"); 562 f = fopen(locked_rc, "wx");
557 if (!f) { 563 if (!f) {
558 /* Inform about the error unless the lockfile already existed, 564 /* Inform about the error unless the lockfile already existed,
559 * since that only means we've got concurrent requests. 565 * since that only means we've got concurrent requests.
560 */ 566 */
561 if (errno != EEXIST) 567 if (errno != EEXIST)
562 fprintf(stderr, "[cgit] Error opening %s: %s (%d)\n", 568 fprintf(stderr, "[cgit] Error opening %s: %s (%d)\n",
563 locked_rc, strerror(errno), errno); 569 locked_rc, strerror(errno), errno);
564 return errno; 570 return errno;
565 } 571 }
566 idx = cgit_repolist.count; 572 idx = cgit_repolist.count;
567 scan_tree(path, repo_config); 573 scan_tree(path, repo_config);
568 print_repolist(f, &cgit_repolist, idx); 574 print_repolist(f, &cgit_repolist, idx);
569 if (rename(locked_rc, cached_rc)) 575 if (rename(locked_rc, cached_rc))
570 fprintf(stderr, "[cgit] Error renaming %s to %s: %s (%d)\n", 576 fprintf(stderr, "[cgit] Error renaming %s to %s: %s (%d)\n",
571 locked_rc, cached_rc, strerror(errno), errno); 577 locked_rc, cached_rc, strerror(errno), errno);
572 fclose(f); 578 fclose(f);
573 return 0; 579 return 0;
574} 580}
575 581
576static void process_cached_repolist(const char *path) 582static void process_cached_repolist(const char *path)
577{ 583{
578 struct stat st; 584 struct stat st;
579 char *cached_rc; 585 char *cached_rc;
580 time_t age; 586 time_t age;
581 587
582 cached_rc = xstrdup(fmt("%s/rc-%8x", ctx.cfg.cache_root, 588 cached_rc = xstrdup(fmt("%s/rc-%8x", ctx.cfg.cache_root,
583 hash_str(path))); 589 hash_str(path)));
584 590
585 if (stat(cached_rc, &st)) { 591 if (stat(cached_rc, &st)) {
586 /* Nothing is cached, we need to scan without forking. And 592 /* Nothing is cached, we need to scan without forking. And
587 * if we fail to generate a cached repolist, we need to 593 * if we fail to generate a cached repolist, we need to
588 * invoke scan_tree manually. 594 * invoke scan_tree manually.
589 */ 595 */
590 if (generate_cached_repolist(path, cached_rc)) 596 if (generate_cached_repolist(path, cached_rc))
591 scan_tree(path, repo_config); 597 scan_tree(path, repo_config);
592 return; 598 return;
593 } 599 }
594 600
595 parse_configfile(cached_rc, config_cb); 601 parse_configfile(cached_rc, config_cb);
596 602
597 /* If the cached configfile hasn't expired, lets exit now */ 603 /* If the cached configfile hasn't expired, lets exit now */
598 age = time(NULL) - st.st_mtime; 604 age = time(NULL) - st.st_mtime;
599 if (age <= (ctx.cfg.cache_scanrc_ttl * 60)) 605 if (age <= (ctx.cfg.cache_scanrc_ttl * 60))
600 return; 606 return;
601 607
602 /* The cached repolist has been parsed, but it was old. So lets 608 /* The cached repolist has been parsed, but it was old. So lets
603 * rescan the specified path and generate a new cached repolist 609 * rescan the specified path and generate a new cached repolist
604 * in a child-process to avoid latency for the current request. 610 * in a child-process to avoid latency for the current request.
605 */ 611 */
606 if (fork()) 612 if (fork())
607 return; 613 return;
608 614
609 exit(generate_cached_repolist(path, cached_rc)); 615 exit(generate_cached_repolist(path, cached_rc));
610} 616}
611 617
612static void cgit_parse_args(int argc, const char **argv) 618static void cgit_parse_args(int argc, const char **argv)
613{ 619{
614 int i; 620 int i;
615 int scan = 0; 621 int scan = 0;
616 622
617 for (i = 1; i < argc; i++) { 623 for (i = 1; i < argc; i++) {
618 if (!strncmp(argv[i], "--cache=", 8)) { 624 if (!strncmp(argv[i], "--cache=", 8)) {
619 ctx.cfg.cache_root = xstrdup(argv[i]+8); 625 ctx.cfg.cache_root = xstrdup(argv[i]+8);
620 } 626 }
621 if (!strcmp(argv[i], "--nocache")) { 627 if (!strcmp(argv[i], "--nocache")) {
622 ctx.cfg.nocache = 1; 628 ctx.cfg.nocache = 1;
623 } 629 }
624 if (!strcmp(argv[i], "--nohttp")) { 630 if (!strcmp(argv[i], "--nohttp")) {
625 ctx.env.no_http = "1"; 631 ctx.env.no_http = "1";
626 } 632 }
627 if (!strncmp(argv[i], "--query=", 8)) { 633 if (!strncmp(argv[i], "--query=", 8)) {
628 ctx.qry.raw = xstrdup(argv[i]+8); 634 ctx.qry.raw = xstrdup(argv[i]+8);
629 } 635 }
630 if (!strncmp(argv[i], "--repo=", 7)) { 636 if (!strncmp(argv[i], "--repo=", 7)) {
631 ctx.qry.repo = xstrdup(argv[i]+7); 637 ctx.qry.repo = xstrdup(argv[i]+7);
632 } 638 }
633 if (!strncmp(argv[i], "--page=", 7)) { 639 if (!strncmp(argv[i], "--page=", 7)) {
634 ctx.qry.page = xstrdup(argv[i]+7); 640 ctx.qry.page = xstrdup(argv[i]+7);
635 } 641 }
636 if (!strncmp(argv[i], "--head=", 7)) { 642 if (!strncmp(argv[i], "--head=", 7)) {
637 ctx.qry.head = xstrdup(argv[i]+7); 643 ctx.qry.head = xstrdup(argv[i]+7);
638 ctx.qry.has_symref = 1; 644 ctx.qry.has_symref = 1;
639 } 645 }
640 if (!strncmp(argv[i], "--sha1=", 7)) { 646 if (!strncmp(argv[i], "--sha1=", 7)) {
641 ctx.qry.sha1 = xstrdup(argv[i]+7); 647 ctx.qry.sha1 = xstrdup(argv[i]+7);
642 ctx.qry.has_sha1 = 1; 648 ctx.qry.has_sha1 = 1;
643 } 649 }
644 if (!strncmp(argv[i], "--ofs=", 6)) { 650 if (!strncmp(argv[i], "--ofs=", 6)) {
645 ctx.qry.ofs = atoi(argv[i]+6); 651 ctx.qry.ofs = atoi(argv[i]+6);
646 } 652 }
647 if (!strncmp(argv[i], "--scan-tree=", 12) || 653 if (!strncmp(argv[i], "--scan-tree=", 12) ||
648 !strncmp(argv[i], "--scan-path=", 12)) { 654 !strncmp(argv[i], "--scan-path=", 12)) {
649 /* HACK: the global snapshot bitmask defines the 655 /* HACK: the global snapshot bitmask defines the
650 * set of allowed snapshot formats, but the config 656 * set of allowed snapshot formats, but the config
651 * file hasn't been parsed yet so the mask is 657 * file hasn't been parsed yet so the mask is
652 * currently 0. By setting all bits high before 658 * currently 0. By setting all bits high before
653 * scanning we make sure that any in-repo cgitrc 659 * scanning we make sure that any in-repo cgitrc
654 * snapshot setting is respected by scan_tree(). 660 * snapshot setting is respected by scan_tree().
655 * BTW: we assume that there'll never be more than 661 * BTW: we assume that there'll never be more than
656 * 255 different snapshot formats supported by cgit... 662 * 255 different snapshot formats supported by cgit...
657 */ 663 */
658 ctx.cfg.snapshots = 0xFF; 664 ctx.cfg.snapshots = 0xFF;
659 scan++; 665 scan++;
660 scan_tree(argv[i] + 12, repo_config); 666 scan_tree(argv[i] + 12, repo_config);
661 } 667 }
662 } 668 }
663 if (scan) { 669 if (scan) {
664 qsort(cgit_repolist.repos, cgit_repolist.count, 670 qsort(cgit_repolist.repos, cgit_repolist.count,
665 sizeof(struct cgit_repo), cmp_repos); 671 sizeof(struct cgit_repo), cmp_repos);
666 print_repolist(stdout, &cgit_repolist, 0); 672 print_repolist(stdout, &cgit_repolist, 0);
667 exit(0); 673 exit(0);
668 } 674 }
669} 675}
670 676
671static int calc_ttl() 677static int calc_ttl()
672{ 678{
673 if (!ctx.repo) 679 if (!ctx.repo)
674 return ctx.cfg.cache_root_ttl; 680 return ctx.cfg.cache_root_ttl;
675 681
676 if (!ctx.qry.page) 682 if (!ctx.qry.page)
677 return ctx.cfg.cache_repo_ttl; 683 return ctx.cfg.cache_repo_ttl;
678 684
679 if (ctx.qry.has_symref) 685 if (ctx.qry.has_symref)
680 return ctx.cfg.cache_dynamic_ttl; 686 return ctx.cfg.cache_dynamic_ttl;
681 687
682 if (ctx.qry.has_sha1) 688 if (ctx.qry.has_sha1)
683 return ctx.cfg.cache_static_ttl; 689 return ctx.cfg.cache_static_ttl;
684 690
685 return ctx.cfg.cache_repo_ttl; 691 return ctx.cfg.cache_repo_ttl;
686} 692}
687 693
688int main(int argc, const char **argv) 694int main(int argc, const char **argv)
689{ 695{
690 const char *path; 696 const char *path;
691 char *qry; 697 char *qry;
692 int err, ttl; 698 int err, ttl;
693 699
694 prepare_context(&ctx); 700 prepare_context(&ctx);
695 cgit_repolist.length = 0; 701 cgit_repolist.length = 0;
696 cgit_repolist.count = 0; 702 cgit_repolist.count = 0;
697 cgit_repolist.repos = NULL; 703 cgit_repolist.repos = NULL;
698 704
699 cgit_parse_args(argc, argv); 705 cgit_parse_args(argc, argv);
700 parse_configfile(ctx.env.cgit_config, config_cb); 706 parse_configfile(ctx.env.cgit_config, config_cb);
701 ctx.repo = NULL; 707 ctx.repo = NULL;
702 http_parse_querystring(ctx.qry.raw, querystring_cb); 708 http_parse_querystring(ctx.qry.raw, querystring_cb);
703 709
704 /* If virtual-root isn't specified in cgitrc, lets pretend 710 /* If virtual-root isn't specified in cgitrc, lets pretend
705 * that virtual-root equals SCRIPT_NAME. 711 * that virtual-root equals SCRIPT_NAME.
706 */ 712 */
707 if (!ctx.cfg.virtual_root) 713 if (!ctx.cfg.virtual_root)
708 ctx.cfg.virtual_root = ctx.cfg.script_name; 714 ctx.cfg.virtual_root = ctx.cfg.script_name;
709 715
710 /* If no url parameter is specified on the querystring, lets 716 /* If no url parameter is specified on the querystring, lets
711 * use PATH_INFO as url. This allows cgit to work with virtual 717 * use PATH_INFO as url. This allows cgit to work with virtual
712 * urls without the need for rewriterules in the webserver (as 718 * urls without the need for rewriterules in the webserver (as
713 * long as PATH_INFO is included in the cache lookup key). 719 * long as PATH_INFO is included in the cache lookup key).
714 */ 720 */
715 path = ctx.env.path_info; 721 path = ctx.env.path_info;
716 if (!ctx.qry.url && path) { 722 if (!ctx.qry.url && path) {
717 if (path[0] == '/') 723 if (path[0] == '/')
718 path++; 724 path++;
719 ctx.qry.url = xstrdup(path); 725 ctx.qry.url = xstrdup(path);
720 if (ctx.qry.raw) { 726 if (ctx.qry.raw) {
721 qry = ctx.qry.raw; 727 qry = ctx.qry.raw;
722 ctx.qry.raw = xstrdup(fmt("%s?%s", path, qry)); 728 ctx.qry.raw = xstrdup(fmt("%s?%s", path, qry));
723 free(qry); 729 free(qry);
724 } else 730 } else
725 ctx.qry.raw = xstrdup(ctx.qry.url); 731 ctx.qry.raw = xstrdup(ctx.qry.url);
726 cgit_parse_url(ctx.qry.url); 732 cgit_parse_url(ctx.qry.url);
727 } 733 }
728 734
729 ttl = calc_ttl(); 735 ttl = calc_ttl();
730 ctx.page.expires += ttl*60; 736 ctx.page.expires += ttl*60;
731 if (ctx.env.request_method && !strcmp(ctx.env.request_method, "HEAD")) 737 if (ctx.env.request_method && !strcmp(ctx.env.request_method, "HEAD"))
732 ctx.cfg.nocache = 1; 738 ctx.cfg.nocache = 1;
733 if (ctx.cfg.nocache) 739 if (ctx.cfg.nocache)
734 ctx.cfg.cache_size = 0; 740 ctx.cfg.cache_size = 0;
735 err = cache_process(ctx.cfg.cache_size, ctx.cfg.cache_root, 741 err = cache_process(ctx.cfg.cache_size, ctx.cfg.cache_root,
736 ctx.qry.raw, ttl, process_request, &ctx); 742 ctx.qry.raw, ttl, process_request, &ctx);
737 if (err) 743 if (err)
738 cgit_print_error(fmt("Error processing page: %s (%d)", 744 cgit_print_error(fmt("Error processing page: %s (%d)",
739 strerror(err), err)); 745 strerror(err), err));
740 return err; 746 return err;
741} 747}
diff --git a/cgit.css b/cgit.css
index 6198403..6e47eb3 100644
--- a/cgit.css
+++ b/cgit.css
@@ -1,620 +1,626 @@
1body, table, form { 1body, table, form {
2 padding: 0em; 2 padding: 0em;
3 margin: 0em; 3 margin: 0em;
4} 4}
5 5
6body { 6body {
7 font-family: sans-serif; 7 font-family: sans-serif;
8 font-size: 10pt; 8 font-size: 10pt;
9 color: #333; 9 color: #333;
10 background: white; 10 background: white;
11 padding: 4px; 11 padding: 4px;
12} 12}
13 13
14a { 14a {
15 color: blue; 15 color: blue;
16 text-decoration: none; 16 text-decoration: none;
17} 17}
18 18
19a:hover { 19a:hover {
20 text-decoration: underline; 20 text-decoration: underline;
21} 21}
22 22
23table { 23table {
24 border-collapse: collapse; 24 border-collapse: collapse;
25} 25}
26 26
27table#header { 27table#header {
28 width: 100%; 28 width: 100%;
29 margin-bottom: 1em; 29 margin-bottom: 1em;
30} 30}
31 31
32table#header td.logo { 32table#header td.logo {
33 width: 96px; 33 width: 96px;
34} 34}
35 35
36table#header td.main { 36table#header td.main {
37 font-size: 250%; 37 font-size: 250%;
38 padding-left: 10px; 38 padding-left: 10px;
39 white-space: nowrap; 39 white-space: nowrap;
40} 40}
41 41
42table#header td.main a { 42table#header td.main a {
43 color: #000; 43 color: #000;
44} 44}
45 45
46table#header td.form { 46table#header td.form {
47 text-align: right; 47 text-align: right;
48 vertical-align: bottom; 48 vertical-align: bottom;
49 padding-right: 1em; 49 padding-right: 1em;
50 padding-bottom: 2px; 50 padding-bottom: 2px;
51 white-space: nowrap; 51 white-space: nowrap;
52} 52}
53 53
54table#header td.form form, 54table#header td.form form,
55table#header td.form input, 55table#header td.form input,
56table#header td.form select { 56table#header td.form select {
57 font-size: 90%; 57 font-size: 90%;
58} 58}
59 59
60table#header td.sub { 60table#header td.sub {
61 color: #777; 61 color: #777;
62 border-top: solid 1px #ccc; 62 border-top: solid 1px #ccc;
63 padding-left: 10px; 63 padding-left: 10px;
64} 64}
65 65
66table.tabs { 66table.tabs {
67 /* border-bottom: solid 2px #ccc; */ 67 border-bottom: solid 3px #ccc;
68 border-collapse: collapse; 68 border-collapse: collapse;
69 margin-top: 2em; 69 margin-top: 2em;
70 margin-bottom: 0px; 70 margin-bottom: 0px;
71 width: 100%; 71 width: 100%;
72} 72}
73 73
74table.tabs td { 74table.tabs td {
75 padding: 0px 1em; 75 padding: 0px 1em;
76 vertical-align: bottom; 76 vertical-align: bottom;
77} 77}
78 78
79table.tabs td a { 79table.tabs td a {
80 padding: 2px 0.75em; 80 padding: 2px 0.75em;
81 color: #777; 81 color: #777;
82 font-size: 110%; 82 font-size: 110%;
83} 83}
84 84
85table.tabs td a.active { 85table.tabs td a.active {
86 color: #000; 86 color: #000;
87 background-color: #ccc; 87 background-color: #ccc;
88} 88}
89 89
90table.tabs td.form { 90table.tabs td.form {
91 text-align: right; 91 text-align: right;
92} 92}
93 93
94table.tabs td.form form { 94table.tabs td.form form {
95 padding-bottom: 2px; 95 padding-bottom: 2px;
96 font-size: 90%; 96 font-size: 90%;
97 white-space: nowrap; 97 white-space: nowrap;
98} 98}
99 99
100table.tabs td.form input, 100table.tabs td.form input,
101table.tabs td.form select { 101table.tabs td.form select {
102 font-size: 90%; 102 font-size: 90%;
103} 103}
104 104
105div.path {
106 margin: 0px;
107 padding: 5px 2em 2px 2em;
108 color: #000;
109 background-color: #eee;
110}
111
105div.content { 112div.content {
106 margin: 0px; 113 margin: 0px;
107 padding: 2em; 114 padding: 2em;
108 border-top: solid 3px #ccc;
109 border-bottom: solid 3px #ccc; 115 border-bottom: solid 3px #ccc;
110} 116}
111 117
112 118
113table.list { 119table.list {
114 width: 100%; 120 width: 100%;
115 border: none; 121 border: none;
116 border-collapse: collapse; 122 border-collapse: collapse;
117} 123}
118 124
119table.list tr { 125table.list tr {
120 background: white; 126 background: white;
121} 127}
122 128
123table.list tr.logheader { 129table.list tr.logheader {
124 background: #eee; 130 background: #eee;
125} 131}
126 132
127table.list tr:hover { 133table.list tr:hover {
128 background: #eee; 134 background: #eee;
129} 135}
130 136
131table.list tr.nohover:hover { 137table.list tr.nohover:hover {
132 background: white; 138 background: white;
133} 139}
134 140
135table.list th { 141table.list th {
136 font-weight: bold; 142 font-weight: bold;
137 /* color: #888; 143 /* color: #888;
138 border-top: dashed 1px #888; 144 border-top: dashed 1px #888;
139 border-bottom: dashed 1px #888; 145 border-bottom: dashed 1px #888;
140 */ 146 */
141 padding: 0.1em 0.5em 0.05em 0.5em; 147 padding: 0.1em 0.5em 0.05em 0.5em;
142 vertical-align: baseline; 148 vertical-align: baseline;
143} 149}
144 150
145table.list td { 151table.list td {
146 border: none; 152 border: none;
147 padding: 0.1em 0.5em 0.1em 0.5em; 153 padding: 0.1em 0.5em 0.1em 0.5em;
148} 154}
149 155
150table.list td.logsubject { 156table.list td.logsubject {
151 font-family: monospace; 157 font-family: monospace;
152 font-weight: bold; 158 font-weight: bold;
153} 159}
154 160
155table.list td.logmsg { 161table.list td.logmsg {
156 font-family: monospace; 162 font-family: monospace;
157 white-space: pre; 163 white-space: pre;
158 padding: 1em 0.5em 2em 0.5em; 164 padding: 1em 0.5em 2em 0.5em;
159} 165}
160 166
161table.list td a { 167table.list td a {
162 color: black; 168 color: black;
163} 169}
164 170
165table.list td a.ls-dir { 171table.list td a.ls-dir {
166 font-weight: bold; 172 font-weight: bold;
167 color: #00f; 173 color: #00f;
168} 174}
169 175
170table.list td a:hover { 176table.list td a:hover {
171 color: #00f; 177 color: #00f;
172} 178}
173 179
174img { 180img {
175 border: none; 181 border: none;
176} 182}
177 183
178input#switch-btn { 184input#switch-btn {
179 margin: 2px 0px 0px 0px; 185 margin: 2px 0px 0px 0px;
180} 186}
181 187
182td#sidebar input.txt { 188td#sidebar input.txt {
183 width: 100%; 189 width: 100%;
184 margin: 2px 0px 0px 0px; 190 margin: 2px 0px 0px 0px;
185} 191}
186 192
187table#grid { 193table#grid {
188 margin: 0px; 194 margin: 0px;
189} 195}
190 196
191td#content { 197td#content {
192 vertical-align: top; 198 vertical-align: top;
193 padding: 1em 2em 1em 1em; 199 padding: 1em 2em 1em 1em;
194 border: none; 200 border: none;
195} 201}
196 202
197div#summary { 203div#summary {
198 vertical-align: top; 204 vertical-align: top;
199 margin-bottom: 1em; 205 margin-bottom: 1em;
200} 206}
201 207
202table#downloads { 208table#downloads {
203 float: right; 209 float: right;
204 border-collapse: collapse; 210 border-collapse: collapse;
205 border: solid 1px #777; 211 border: solid 1px #777;
206 margin-left: 0.5em; 212 margin-left: 0.5em;
207 margin-bottom: 0.5em; 213 margin-bottom: 0.5em;
208} 214}
209 215
210table#downloads th { 216table#downloads th {
211 background-color: #ccc; 217 background-color: #ccc;
212} 218}
213 219
214div#blob { 220div#blob {
215 border: solid 1px black; 221 border: solid 1px black;
216} 222}
217 223
218div.error { 224div.error {
219 color: red; 225 color: red;
220 font-weight: bold; 226 font-weight: bold;
221 margin: 1em 2em; 227 margin: 1em 2em;
222} 228}
223 229
224a.ls-blob, a.ls-dir, a.ls-mod { 230a.ls-blob, a.ls-dir, a.ls-mod {
225 font-family: monospace; 231 font-family: monospace;
226} 232}
227 233
228td.ls-size { 234td.ls-size {
229 text-align: right; 235 text-align: right;
230 font-family: monospace; 236 font-family: monospace;
231 width: 10em; 237 width: 10em;
232} 238}
233 239
234td.ls-mode { 240td.ls-mode {
235 font-family: monospace; 241 font-family: monospace;
236 width: 10em; 242 width: 10em;
237} 243}
238 244
239table.blob { 245table.blob {
240 margin-top: 0.5em; 246 margin-top: 0.5em;
241 border-top: solid 1px black; 247 border-top: solid 1px black;
242} 248}
243 249
244table.blob td.lines { 250table.blob td.lines {
245 margin: 0; padding: 0 0 0 0.5em; 251 margin: 0; padding: 0 0 0 0.5em;
246 vertical-align: top; 252 vertical-align: top;
247 color: black; 253 color: black;
248} 254}
249 255
250table.blob td.linenumbers { 256table.blob td.linenumbers {
251 margin: 0; padding: 0 0.5em 0 0.5em; 257 margin: 0; padding: 0 0.5em 0 0.5em;
252 vertical-align: top; 258 vertical-align: top;
253 text-align: right; 259 text-align: right;
254 border-right: 1px solid gray; 260 border-right: 1px solid gray;
255} 261}
256 262
257table.blob pre { 263table.blob pre {
258 padding: 0; margin: 0; 264 padding: 0; margin: 0;
259} 265}
260 266
261table.blob a.no { 267table.blob a.no {
262 color: gray; 268 color: gray;
263 text-align: right; 269 text-align: right;
264 text-decoration: none; 270 text-decoration: none;
265} 271}
266 272
267table.blob a.no a:hover { 273table.blob a.no a:hover {
268 color: black; 274 color: black;
269} 275}
270 276
271table.bin-blob { 277table.bin-blob {
272 margin-top: 0.5em; 278 margin-top: 0.5em;
273 border: solid 1px black; 279 border: solid 1px black;
274} 280}
275 281
276table.bin-blob th { 282table.bin-blob th {
277 font-family: monospace; 283 font-family: monospace;
278 white-space: pre; 284 white-space: pre;
279 border: solid 1px #777; 285 border: solid 1px #777;
280 padding: 0.5em 1em; 286 padding: 0.5em 1em;
281} 287}
282 288
283table.bin-blob td { 289table.bin-blob td {
284 font-family: monospace; 290 font-family: monospace;
285 white-space: pre; 291 white-space: pre;
286 border-left: solid 1px #777; 292 border-left: solid 1px #777;
287 padding: 0em 1em; 293 padding: 0em 1em;
288} 294}
289 295
290table.nowrap td { 296table.nowrap td {
291 white-space: nowrap; 297 white-space: nowrap;
292} 298}
293 299
294table.commit-info { 300table.commit-info {
295 border-collapse: collapse; 301 border-collapse: collapse;
296 margin-top: 1.5em; 302 margin-top: 1.5em;
297} 303}
298 304
299table.commit-info th { 305table.commit-info th {
300 text-align: left; 306 text-align: left;
301 font-weight: normal; 307 font-weight: normal;
302 padding: 0.1em 1em 0.1em 0.1em; 308 padding: 0.1em 1em 0.1em 0.1em;
303 vertical-align: top; 309 vertical-align: top;
304} 310}
305 311
306table.commit-info td { 312table.commit-info td {
307 font-weight: normal; 313 font-weight: normal;
308 padding: 0.1em 1em 0.1em 0.1em; 314 padding: 0.1em 1em 0.1em 0.1em;
309} 315}
310 316
311div.commit-subject { 317div.commit-subject {
312 font-weight: bold; 318 font-weight: bold;
313 font-size: 125%; 319 font-size: 125%;
314 margin: 1.5em 0em 0.5em 0em; 320 margin: 1.5em 0em 0.5em 0em;
315 padding: 0em; 321 padding: 0em;
316} 322}
317 323
318div.commit-msg { 324div.commit-msg {
319 white-space: pre; 325 white-space: pre;
320 font-family: monospace; 326 font-family: monospace;
321} 327}
322 328
323div.diffstat-header { 329div.diffstat-header {
324 font-weight: bold; 330 font-weight: bold;
325 padding-top: 1.5em; 331 padding-top: 1.5em;
326} 332}
327 333
328table.diffstat { 334table.diffstat {
329 border-collapse: collapse; 335 border-collapse: collapse;
330 border: solid 1px #aaa; 336 border: solid 1px #aaa;
331 background-color: #eee; 337 background-color: #eee;
332} 338}
333 339
334table.diffstat th { 340table.diffstat th {
335 font-weight: normal; 341 font-weight: normal;
336 text-align: left; 342 text-align: left;
337 text-decoration: underline; 343 text-decoration: underline;
338 padding: 0.1em 1em 0.1em 0.1em; 344 padding: 0.1em 1em 0.1em 0.1em;
339 font-size: 100%; 345 font-size: 100%;
340} 346}
341 347
342table.diffstat td { 348table.diffstat td {
343 padding: 0.2em 0.2em 0.1em 0.1em; 349 padding: 0.2em 0.2em 0.1em 0.1em;
344 font-size: 100%; 350 font-size: 100%;
345 border: none; 351 border: none;
346} 352}
347 353
348table.diffstat td.mode { 354table.diffstat td.mode {
349 white-space: nowrap; 355 white-space: nowrap;
350} 356}
351 357
352table.diffstat td span.modechange { 358table.diffstat td span.modechange {
353 padding-left: 1em; 359 padding-left: 1em;
354 color: red; 360 color: red;
355} 361}
356 362
357table.diffstat td.add a { 363table.diffstat td.add a {
358 color: green; 364 color: green;
359} 365}
360 366
361table.diffstat td.del a { 367table.diffstat td.del a {
362 color: red; 368 color: red;
363} 369}
364 370
365table.diffstat td.upd a { 371table.diffstat td.upd a {
366 color: blue; 372 color: blue;
367} 373}
368 374
369table.diffstat td.graph { 375table.diffstat td.graph {
370 width: 500px; 376 width: 500px;
371 vertical-align: middle; 377 vertical-align: middle;
372} 378}
373 379
374table.diffstat td.graph table { 380table.diffstat td.graph table {
375 border: none; 381 border: none;
376} 382}
377 383
378table.diffstat td.graph td { 384table.diffstat td.graph td {
379 padding: 0px; 385 padding: 0px;
380 border: 0px; 386 border: 0px;
381 height: 7pt; 387 height: 7pt;
382} 388}
383 389
384table.diffstat td.graph td.add { 390table.diffstat td.graph td.add {
385 background-color: #5c5; 391 background-color: #5c5;
386} 392}
387 393
388table.diffstat td.graph td.rem { 394table.diffstat td.graph td.rem {
389 background-color: #c55; 395 background-color: #c55;
390} 396}
391 397
392div.diffstat-summary { 398div.diffstat-summary {
393 color: #888; 399 color: #888;
394 padding-top: 0.5em; 400 padding-top: 0.5em;
395} 401}
396 402
397table.diff { 403table.diff {
398 width: 100%; 404 width: 100%;
399} 405}
400 406
401table.diff td { 407table.diff td {
402 font-family: monospace; 408 font-family: monospace;
403 white-space: pre; 409 white-space: pre;
404} 410}
405 411
406table.diff td div.head { 412table.diff td div.head {
407 font-weight: bold; 413 font-weight: bold;
408 margin-top: 1em; 414 margin-top: 1em;
409 color: black; 415 color: black;
410} 416}
411 417
412table.diff td div.hunk { 418table.diff td div.hunk {
413 color: #009; 419 color: #009;
414} 420}
415 421
416table.diff td div.add { 422table.diff td div.add {
417 color: green; 423 color: green;
418} 424}
419 425
420table.diff td div.del { 426table.diff td div.del {
421 color: red; 427 color: red;
422} 428}
423 429
424.sha1 { 430.sha1 {
425 font-family: monospace; 431 font-family: monospace;
426 font-size: 90%; 432 font-size: 90%;
427} 433}
428 434
429.left { 435.left {
430 text-align: left; 436 text-align: left;
431} 437}
432 438
433.right { 439.right {
434 text-align: right; 440 text-align: right;
435} 441}
436 442
437table.list td.reposection { 443table.list td.reposection {
438 font-style: italic; 444 font-style: italic;
439 color: #888; 445 color: #888;
440} 446}
441 447
442a.button { 448a.button {
443 font-size: 80%; 449 font-size: 80%;
444 padding: 0em 0.5em; 450 padding: 0em 0.5em;
445} 451}
446 452
447a.primary { 453a.primary {
448 font-size: 100%; 454 font-size: 100%;
449} 455}
450 456
451a.secondary { 457a.secondary {
452 font-size: 90%; 458 font-size: 90%;
453} 459}
454 460
455td.toplevel-repo { 461td.toplevel-repo {
456 462
457} 463}
458 464
459table.list td.sublevel-repo { 465table.list td.sublevel-repo {
460 padding-left: 1.5em; 466 padding-left: 1.5em;
461} 467}
462 468
463div.pager { 469div.pager {
464 text-align: center; 470 text-align: center;
465 margin: 1em 0em 0em 0em; 471 margin: 1em 0em 0em 0em;
466} 472}
467 473
468div.pager a { 474div.pager a {
469 color: #777; 475 color: #777;
470 margin: 0em 0.5em; 476 margin: 0em 0.5em;
471} 477}
472 478
473span.age-mins { 479span.age-mins {
474 font-weight: bold; 480 font-weight: bold;
475 color: #080; 481 color: #080;
476} 482}
477 483
478span.age-hours { 484span.age-hours {
479 color: #080; 485 color: #080;
480} 486}
481 487
482span.age-days { 488span.age-days {
483 color: #040; 489 color: #040;
484} 490}
485 491
486span.age-weeks { 492span.age-weeks {
487 color: #444; 493 color: #444;
488} 494}
489 495
490span.age-months { 496span.age-months {
491 color: #888; 497 color: #888;
492} 498}
493 499
494span.age-years { 500span.age-years {
495 color: #bbb; 501 color: #bbb;
496} 502}
497div.footer { 503div.footer {
498 margin-top: 0.5em; 504 margin-top: 0.5em;
499 text-align: center; 505 text-align: center;
500 font-size: 80%; 506 font-size: 80%;
501 color: #ccc; 507 color: #ccc;
502} 508}
503a.branch-deco { 509a.branch-deco {
504 margin: 0px 0.5em; 510 margin: 0px 0.5em;
505 padding: 0px 0.25em; 511 padding: 0px 0.25em;
506 background-color: #88ff88; 512 background-color: #88ff88;
507 border: solid 1px #007700; 513 border: solid 1px #007700;
508} 514}
509a.tag-deco { 515a.tag-deco {
510 margin: 0px 0.5em; 516 margin: 0px 0.5em;
511 padding: 0px 0.25em; 517 padding: 0px 0.25em;
512 background-color: #ffff88; 518 background-color: #ffff88;
513 border: solid 1px #777700; 519 border: solid 1px #777700;
514} 520}
515a.remote-deco { 521a.remote-deco {
516 margin: 0px 0.5em; 522 margin: 0px 0.5em;
517 padding: 0px 0.25em; 523 padding: 0px 0.25em;
518 background-color: #ccccff; 524 background-color: #ccccff;
519 border: solid 1px #000077; 525 border: solid 1px #000077;
520} 526}
521a.deco { 527a.deco {
522 margin: 0px 0.5em; 528 margin: 0px 0.5em;
523 padding: 0px 0.25em; 529 padding: 0px 0.25em;
524 background-color: #ff8888; 530 background-color: #ff8888;
525 border: solid 1px #770000; 531 border: solid 1px #770000;
526} 532}
527 533
528div.commit-subject a.branch-deco, 534div.commit-subject a.branch-deco,
529div.commit-subject a.tag-deco, 535div.commit-subject a.tag-deco,
530div.commit-subject a.remote-deco, 536div.commit-subject a.remote-deco,
531div.commit-subject a.deco { 537div.commit-subject a.deco {
532 margin-left: 1em; 538 margin-left: 1em;
533 font-size: 75%; 539 font-size: 75%;
534} 540}
535 541
536table.stats { 542table.stats {
537 border: solid 1px black; 543 border: solid 1px black;
538 border-collapse: collapse; 544 border-collapse: collapse;
539} 545}
540 546
541table.stats th { 547table.stats th {
542 text-align: left; 548 text-align: left;
543 padding: 1px 0.5em; 549 padding: 1px 0.5em;
544 background-color: #eee; 550 background-color: #eee;
545 border: solid 1px black; 551 border: solid 1px black;
546} 552}
547 553
548table.stats td { 554table.stats td {
549 text-align: right; 555 text-align: right;
550 padding: 1px 0.5em; 556 padding: 1px 0.5em;
551 border: solid 1px black; 557 border: solid 1px black;
552} 558}
553 559
554table.stats td.total { 560table.stats td.total {
555 font-weight: bold; 561 font-weight: bold;
556 text-align: left; 562 text-align: left;
557} 563}
558 564
559table.stats td.sum { 565table.stats td.sum {
560 color: #c00; 566 color: #c00;
561 font-weight: bold; 567 font-weight: bold;
562 /*background-color: #eee; */ 568 /*background-color: #eee; */
563} 569}
564 570
565table.stats td.left { 571table.stats td.left {
566 text-align: left; 572 text-align: left;
567} 573}
568 574
569table.vgraph { 575table.vgraph {
570 border-collapse: separate; 576 border-collapse: separate;
571 border: solid 1px black; 577 border: solid 1px black;
572 height: 200px; 578 height: 200px;
573} 579}
574 580
575table.vgraph th { 581table.vgraph th {
576 background-color: #eee; 582 background-color: #eee;
577 font-weight: bold; 583 font-weight: bold;
578 border: solid 1px white; 584 border: solid 1px white;
579 padding: 1px 0.5em; 585 padding: 1px 0.5em;
580} 586}
581 587
582table.vgraph td { 588table.vgraph td {
583 vertical-align: bottom; 589 vertical-align: bottom;
584 padding: 0px 10px; 590 padding: 0px 10px;
585} 591}
586 592
587table.vgraph div.bar { 593table.vgraph div.bar {
588 background-color: #eee; 594 background-color: #eee;
589} 595}
590 596
591table.hgraph { 597table.hgraph {
592 border: solid 1px black; 598 border: solid 1px black;
593 width: 800px; 599 width: 800px;
594} 600}
595 601
596table.hgraph th { 602table.hgraph th {
597 background-color: #eee; 603 background-color: #eee;
598 font-weight: bold; 604 font-weight: bold;
599 border: solid 1px black; 605 border: solid 1px black;
600 padding: 1px 0.5em; 606 padding: 1px 0.5em;
601} 607}
602 608
603table.hgraph td { 609table.hgraph td {
604 vertical-align: center; 610 vertical-align: center;
605 padding: 2px 2px; 611 padding: 2px 2px;
606} 612}
607 613
608table.hgraph div.bar { 614table.hgraph div.bar {
609 background-color: #eee; 615 background-color: #eee;
610 height: 1em; 616 height: 1em;
611} 617}
612 618
613table.ssdiff { 619table.ssdiff {
614 width: 100%; 620 width: 100%;
615} 621}
616 622
617table.ssdiff td { 623table.ssdiff td {
618 font-size: 75%; 624 font-size: 75%;
619 font-family: monospace; 625 font-family: monospace;
620 white-space: pre; 626 white-space: pre;
diff --git a/cgit.h b/cgit.h
index 8884f9e..80c3902 100644
--- a/cgit.h
+++ b/cgit.h
@@ -1,303 +1,304 @@
1#ifndef CGIT_H 1#ifndef CGIT_H
2#define CGIT_H 2#define CGIT_H
3 3
4 4
5#include <git-compat-util.h> 5#include <git-compat-util.h>
6#include <cache.h> 6#include <cache.h>
7#include <grep.h> 7#include <grep.h>
8#include <object.h> 8#include <object.h>
9#include <tree.h> 9#include <tree.h>
10#include <commit.h> 10#include <commit.h>
11#include <tag.h> 11#include <tag.h>
12#include <diff.h> 12#include <diff.h>
13#include <diffcore.h> 13#include <diffcore.h>
14#include <refs.h> 14#include <refs.h>
15#include <revision.h> 15#include <revision.h>
16#include <log-tree.h> 16#include <log-tree.h>
17#include <archive.h> 17#include <archive.h>
18#include <string-list.h> 18#include <string-list.h>
19#include <xdiff-interface.h> 19#include <xdiff-interface.h>
20#include <xdiff/xdiff.h> 20#include <xdiff/xdiff.h>
21#include <utf8.h> 21#include <utf8.h>
22 22
23 23
24/* 24/*
25 * Dateformats used on misc. pages 25 * Dateformats used on misc. pages
26 */ 26 */
27#define FMT_LONGDATE "%Y-%m-%d %H:%M:%S (%Z)" 27#define FMT_LONGDATE "%Y-%m-%d %H:%M:%S (%Z)"
28#define FMT_SHORTDATE "%Y-%m-%d" 28#define FMT_SHORTDATE "%Y-%m-%d"
29#define FMT_ATOMDATE "%Y-%m-%dT%H:%M:%SZ" 29#define FMT_ATOMDATE "%Y-%m-%dT%H:%M:%SZ"
30 30
31 31
32/* 32/*
33 * Limits used for relative dates 33 * Limits used for relative dates
34 */ 34 */
35#define TM_MIN 60 35#define TM_MIN 60
36#define TM_HOUR (TM_MIN * 60) 36#define TM_HOUR (TM_MIN * 60)
37#define TM_DAY (TM_HOUR * 24) 37#define TM_DAY (TM_HOUR * 24)
38#define TM_WEEK (TM_DAY * 7) 38#define TM_WEEK (TM_DAY * 7)
39#define TM_YEAR (TM_DAY * 365) 39#define TM_YEAR (TM_DAY * 365)
40#define TM_MONTH (TM_YEAR / 12.0) 40#define TM_MONTH (TM_YEAR / 12.0)
41 41
42 42
43/* 43/*
44 * Default encoding 44 * Default encoding
45 */ 45 */
46#define PAGE_ENCODING "UTF-8" 46#define PAGE_ENCODING "UTF-8"
47 47
48typedef void (*configfn)(const char *name, const char *value); 48typedef void (*configfn)(const char *name, const char *value);
49typedef void (*filepair_fn)(struct diff_filepair *pair); 49typedef void (*filepair_fn)(struct diff_filepair *pair);
50typedef void (*linediff_fn)(char *line, int len); 50typedef void (*linediff_fn)(char *line, int len);
51 51
52struct cgit_filter { 52struct cgit_filter {
53 char *cmd; 53 char *cmd;
54 char **argv; 54 char **argv;
55 int old_stdout; 55 int old_stdout;
56 int pipe_fh[2]; 56 int pipe_fh[2];
57 int pid; 57 int pid;
58 int exitstatus; 58 int exitstatus;
59}; 59};
60 60
61struct cgit_repo { 61struct cgit_repo {
62 char *url; 62 char *url;
63 char *name; 63 char *name;
64 char *path; 64 char *path;
65 char *desc; 65 char *desc;
66 char *owner; 66 char *owner;
67 char *defbranch; 67 char *defbranch;
68 char *module_link; 68 char *module_link;
69 char *readme; 69 char *readme;
70 char *section; 70 char *section;
71 char *clone_url; 71 char *clone_url;
72 int snapshots; 72 int snapshots;
73 int enable_log_filecount; 73 int enable_log_filecount;
74 int enable_log_linecount; 74 int enable_log_linecount;
75 int enable_remote_branches; 75 int enable_remote_branches;
76 int enable_subject_links; 76 int enable_subject_links;
77 int max_stats; 77 int max_stats;
78 time_t mtime; 78 time_t mtime;
79 struct cgit_filter *about_filter; 79 struct cgit_filter *about_filter;
80 struct cgit_filter *commit_filter; 80 struct cgit_filter *commit_filter;
81 struct cgit_filter *source_filter; 81 struct cgit_filter *source_filter;
82}; 82};
83 83
84typedef void (*repo_config_fn)(struct cgit_repo *repo, const char *name, 84typedef void (*repo_config_fn)(struct cgit_repo *repo, const char *name,
85 const char *value); 85 const char *value);
86 86
87struct cgit_repolist { 87struct cgit_repolist {
88 int length; 88 int length;
89 int count; 89 int count;
90 struct cgit_repo *repos; 90 struct cgit_repo *repos;
91}; 91};
92 92
93struct commitinfo { 93struct commitinfo {
94 struct commit *commit; 94 struct commit *commit;
95 char *author; 95 char *author;
96 char *author_email; 96 char *author_email;
97 unsigned long author_date; 97 unsigned long author_date;
98 char *committer; 98 char *committer;
99 char *committer_email; 99 char *committer_email;
100 unsigned long committer_date; 100 unsigned long committer_date;
101 char *subject; 101 char *subject;
102 char *msg; 102 char *msg;
103 char *msg_encoding; 103 char *msg_encoding;
104}; 104};
105 105
106struct taginfo { 106struct taginfo {
107 char *tagger; 107 char *tagger;
108 char *tagger_email; 108 char *tagger_email;
109 unsigned long tagger_date; 109 unsigned long tagger_date;
110 char *msg; 110 char *msg;
111}; 111};
112 112
113struct refinfo { 113struct refinfo {
114 const char *refname; 114 const char *refname;
115 struct object *object; 115 struct object *object;
116 union { 116 union {
117 struct taginfo *tag; 117 struct taginfo *tag;
118 struct commitinfo *commit; 118 struct commitinfo *commit;
119 }; 119 };
120}; 120};
121 121
122struct reflist { 122struct reflist {
123 struct refinfo **refs; 123 struct refinfo **refs;
124 int alloc; 124 int alloc;
125 int count; 125 int count;
126}; 126};
127 127
128struct cgit_query { 128struct cgit_query {
129 int has_symref; 129 int has_symref;
130 int has_sha1; 130 int has_sha1;
131 char *raw; 131 char *raw;
132 char *repo; 132 char *repo;
133 char *page; 133 char *page;
134 char *search; 134 char *search;
135 char *grep; 135 char *grep;
136 char *head; 136 char *head;
137 char *sha1; 137 char *sha1;
138 char *sha2; 138 char *sha2;
139 char *path; 139 char *path;
140 char *name; 140 char *name;
141 char *mimetype; 141 char *mimetype;
142 char *url; 142 char *url;
143 char *period; 143 char *period;
144 int ofs; 144 int ofs;
145 int nohead; 145 int nohead;
146 char *sort; 146 char *sort;
147 int showmsg; 147 int showmsg;
148 int ssdiff; 148 int ssdiff;
149 int show_all; 149 int show_all;
150 char *vpath;
150}; 151};
151 152
152struct cgit_config { 153struct cgit_config {
153 char *agefile; 154 char *agefile;
154 char *cache_root; 155 char *cache_root;
155 char *clone_prefix; 156 char *clone_prefix;
156 char *css; 157 char *css;
157 char *favicon; 158 char *favicon;
158 char *footer; 159 char *footer;
159 char *head_include; 160 char *head_include;
160 char *header; 161 char *header;
161 char *index_header; 162 char *index_header;
162 char *index_info; 163 char *index_info;
163 char *logo; 164 char *logo;
164 char *logo_link; 165 char *logo_link;
165 char *module_link; 166 char *module_link;
166 char *robots; 167 char *robots;
167 char *root_title; 168 char *root_title;
168 char *root_desc; 169 char *root_desc;
169 char *root_readme; 170 char *root_readme;
170 char *script_name; 171 char *script_name;
171 char *section; 172 char *section;
172 char *virtual_root; 173 char *virtual_root;
173 int cache_size; 174 int cache_size;
174 int cache_dynamic_ttl; 175 int cache_dynamic_ttl;
175 int cache_max_create_time; 176 int cache_max_create_time;
176 int cache_repo_ttl; 177 int cache_repo_ttl;
177 int cache_root_ttl; 178 int cache_root_ttl;
178 int cache_scanrc_ttl; 179 int cache_scanrc_ttl;
179 int cache_static_ttl; 180 int cache_static_ttl;
180 int embedded; 181 int embedded;
181 int enable_filter_overrides; 182 int enable_filter_overrides;
182 int enable_index_links; 183 int enable_index_links;
183 int enable_log_filecount; 184 int enable_log_filecount;
184 int enable_log_linecount; 185 int enable_log_linecount;
185 int enable_remote_branches; 186 int enable_remote_branches;
186 int enable_subject_links; 187 int enable_subject_links;
187 int enable_tree_linenumbers; 188 int enable_tree_linenumbers;
188 int local_time; 189 int local_time;
189 int max_atom_items; 190 int max_atom_items;
190 int max_repo_count; 191 int max_repo_count;
191 int max_commit_count; 192 int max_commit_count;
192 int max_lock_attempts; 193 int max_lock_attempts;
193 int max_msg_len; 194 int max_msg_len;
194 int max_repodesc_len; 195 int max_repodesc_len;
195 int max_blob_size; 196 int max_blob_size;
196 int max_stats; 197 int max_stats;
197 int nocache; 198 int nocache;
198 int noplainemail; 199 int noplainemail;
199 int noheader; 200 int noheader;
200 int renamelimit; 201 int renamelimit;
201 int snapshots; 202 int snapshots;
202 int summary_branches; 203 int summary_branches;
203 int summary_log; 204 int summary_log;
204 int summary_tags; 205 int summary_tags;
205 int ssdiff; 206 int ssdiff;
206 struct string_list mimetypes; 207 struct string_list mimetypes;
207 struct cgit_filter *about_filter; 208 struct cgit_filter *about_filter;
208 struct cgit_filter *commit_filter; 209 struct cgit_filter *commit_filter;
209 struct cgit_filter *source_filter; 210 struct cgit_filter *source_filter;
210}; 211};
211 212
212struct cgit_page { 213struct cgit_page {
213 time_t modified; 214 time_t modified;
214 time_t expires; 215 time_t expires;
215 size_t size; 216 size_t size;
216 char *mimetype; 217 char *mimetype;
217 char *charset; 218 char *charset;
218 char *filename; 219 char *filename;
219 char *etag; 220 char *etag;
220 char *title; 221 char *title;
221 int status; 222 int status;
222 char *statusmsg; 223 char *statusmsg;
223}; 224};
224 225
225struct cgit_environment { 226struct cgit_environment {
226 char *cgit_config; 227 char *cgit_config;
227 char *http_host; 228 char *http_host;
228 char *https; 229 char *https;
229 char *no_http; 230 char *no_http;
230 char *path_info; 231 char *path_info;
231 char *query_string; 232 char *query_string;
232 char *request_method; 233 char *request_method;
233 char *script_name; 234 char *script_name;
234 char *server_name; 235 char *server_name;
235 char *server_port; 236 char *server_port;
236}; 237};
237 238
238struct cgit_context { 239struct cgit_context {
239 struct cgit_environment env; 240 struct cgit_environment env;
240 struct cgit_query qry; 241 struct cgit_query qry;
241 struct cgit_config cfg; 242 struct cgit_config cfg;
242 struct cgit_repo *repo; 243 struct cgit_repo *repo;
243 struct cgit_page page; 244 struct cgit_page page;
244}; 245};
245 246
246struct cgit_snapshot_format { 247struct cgit_snapshot_format {
247 const char *suffix; 248 const char *suffix;
248 const char *mimetype; 249 const char *mimetype;
249 write_archive_fn_t write_func; 250 write_archive_fn_t write_func;
250 int bit; 251 int bit;
251}; 252};
252 253
253extern const char *cgit_version; 254extern const char *cgit_version;
254 255
255extern struct cgit_repolist cgit_repolist; 256extern struct cgit_repolist cgit_repolist;
256extern struct cgit_context ctx; 257extern struct cgit_context ctx;
257extern const struct cgit_snapshot_format cgit_snapshot_formats[]; 258extern const struct cgit_snapshot_format cgit_snapshot_formats[];
258 259
259extern struct cgit_repo *cgit_add_repo(const char *url); 260extern struct cgit_repo *cgit_add_repo(const char *url);
260extern struct cgit_repo *cgit_get_repoinfo(const char *url); 261extern struct cgit_repo *cgit_get_repoinfo(const char *url);
261extern void cgit_repo_config_cb(const char *name, const char *value); 262extern void cgit_repo_config_cb(const char *name, const char *value);
262 263
263extern int chk_zero(int result, char *msg); 264extern int chk_zero(int result, char *msg);
264extern int chk_positive(int result, char *msg); 265extern int chk_positive(int result, char *msg);
265extern int chk_non_negative(int result, char *msg); 266extern int chk_non_negative(int result, char *msg);
266 267
267extern char *trim_end(const char *str, char c); 268extern char *trim_end(const char *str, char c);
268extern char *strlpart(char *txt, int maxlen); 269extern char *strlpart(char *txt, int maxlen);
269extern char *strrpart(char *txt, int maxlen); 270extern char *strrpart(char *txt, int maxlen);
270 271
271extern void cgit_add_ref(struct reflist *list, struct refinfo *ref); 272extern void cgit_add_ref(struct reflist *list, struct refinfo *ref);
272extern int cgit_refs_cb(const char *refname, const unsigned char *sha1, 273extern int cgit_refs_cb(const char *refname, const unsigned char *sha1,
273 int flags, void *cb_data); 274 int flags, void *cb_data);
274 275
275extern void *cgit_free_commitinfo(struct commitinfo *info); 276extern void *cgit_free_commitinfo(struct commitinfo *info);
276 277
277extern int cgit_diff_files(const unsigned char *old_sha1, 278extern int cgit_diff_files(const unsigned char *old_sha1,
278 const unsigned char *new_sha1, 279 const unsigned char *new_sha1,
279 unsigned long *old_size, unsigned long *new_size, 280 unsigned long *old_size, unsigned long *new_size,
280 int *binary, linediff_fn fn); 281 int *binary, linediff_fn fn);
281 282
282extern void cgit_diff_tree(const unsigned char *old_sha1, 283extern void cgit_diff_tree(const unsigned char *old_sha1,
283 const unsigned char *new_sha1, 284 const unsigned char *new_sha1,
284 filepair_fn fn, const char *prefix); 285 filepair_fn fn, const char *prefix);
285 286
286extern void cgit_diff_commit(struct commit *commit, filepair_fn fn); 287extern void cgit_diff_commit(struct commit *commit, filepair_fn fn);
287 288
288extern char *fmt(const char *format,...); 289extern char *fmt(const char *format,...);
289 290
290extern struct commitinfo *cgit_parse_commit(struct commit *commit); 291extern struct commitinfo *cgit_parse_commit(struct commit *commit);
291extern struct taginfo *cgit_parse_tag(struct tag *tag); 292extern struct taginfo *cgit_parse_tag(struct tag *tag);
292extern void cgit_parse_url(const char *url); 293extern void cgit_parse_url(const char *url);
293 294
294extern const char *cgit_repobasename(const char *reponame); 295extern const char *cgit_repobasename(const char *reponame);
295 296
296extern int cgit_parse_snapshots_mask(const char *str); 297extern int cgit_parse_snapshots_mask(const char *str);
297 298
298extern int cgit_open_filter(struct cgit_filter *filter); 299extern int cgit_open_filter(struct cgit_filter *filter);
299extern int cgit_close_filter(struct cgit_filter *filter); 300extern int cgit_close_filter(struct cgit_filter *filter);
300 301
301extern int readfile(const char *path, char **buf, size_t *size); 302extern int readfile(const char *path, char **buf, size_t *size);
302 303
303#endif /* CGIT_H */ 304#endif /* CGIT_H */
diff --git a/cgitrc.5.txt b/cgitrc.5.txt
index 5c24381..a853522 100644
--- a/cgitrc.5.txt
+++ b/cgitrc.5.txt
@@ -1,532 +1,532 @@
1:man source: cgit 1:man source: cgit
2:man manual: cgit 2:man manual: cgit
3 3
4CGITRC(5) 4CGITRC(5)
5======== 5========
6 6
7 7
8NAME 8NAME
9---- 9----
10cgitrc - runtime configuration for cgit 10cgitrc - runtime configuration for cgit
11 11
12 12
13SYNOPSIS 13SYNOPSIS
14-------- 14--------
15Cgitrc contains all runtime settings for cgit, including the list of git 15Cgitrc contains all runtime settings for cgit, including the list of git
16repositories, formatted as a line-separated list of NAME=VALUE pairs. Blank 16repositories, formatted as a line-separated list of NAME=VALUE pairs. Blank
17lines, and lines starting with '#', are ignored. 17lines, and lines starting with '#', are ignored.
18 18
19 19
20LOCATION 20LOCATION
21-------- 21--------
22The default location of cgitrc, defined at compile time, is /etc/cgitrc. At 22The default location of cgitrc, defined at compile time, is /etc/cgitrc. At
23runtime, cgit will consult the environment variable CGIT_CONFIG and, if 23runtime, cgit will consult the environment variable CGIT_CONFIG and, if
24defined, use its value instead. 24defined, use its value instead.
25 25
26 26
27GLOBAL SETTINGS 27GLOBAL SETTINGS
28--------------- 28---------------
29about-filter:: 29about-filter::
30 Specifies a command which will be invoked to format the content of 30 Specifies a command which will be invoked to format the content of
31 about pages (both top-level and for each repository). The command will 31 about pages (both top-level and for each repository). The command will
32 get the content of the about-file on its STDIN, and the STDOUT from the 32 get the content of the about-file on its STDIN, and the STDOUT from the
33 command will be included verbatim on the about page. Default value: 33 command will be included verbatim on the about page. Default value:
34 none. 34 none.
35 35
36agefile:: 36agefile::
37 Specifies a path, relative to each repository path, which can be used 37 Specifies a path, relative to each repository path, which can be used
38 to specify the date and time of the youngest commit in the repository. 38 to specify the date and time of the youngest commit in the repository.
39 The first line in the file is used as input to the "parse_date" 39 The first line in the file is used as input to the "parse_date"
40 function in libgit. Recommended timestamp-format is "yyyy-mm-dd 40 function in libgit. Recommended timestamp-format is "yyyy-mm-dd
41 hh:mm:ss". Default value: "info/web/last-modified". 41 hh:mm:ss". Default value: "info/web/last-modified".
42 42
43cache-root:: 43cache-root::
44 Path used to store the cgit cache entries. Default value: 44 Path used to store the cgit cache entries. Default value:
45 "/var/cache/cgit". 45 "/var/cache/cgit".
46 46
47cache-dynamic-ttl:: 47cache-dynamic-ttl::
48 Number which specifies the time-to-live, in minutes, for the cached 48 Number which specifies the time-to-live, in minutes, for the cached
49 version of repository pages accessed without a fixed SHA1. Default 49 version of repository pages accessed without a fixed SHA1. Default
50 value: "5". 50 value: "5".
51 51
52cache-repo-ttl:: 52cache-repo-ttl::
53 Number which specifies the time-to-live, in minutes, for the cached 53 Number which specifies the time-to-live, in minutes, for the cached
54 version of the repository summary page. Default value: "5". 54 version of the repository summary page. Default value: "5".
55 55
56cache-root-ttl:: 56cache-root-ttl::
57 Number which specifies the time-to-live, in minutes, for the cached 57 Number which specifies the time-to-live, in minutes, for the cached
58 version of the repository index page. Default value: "5". 58 version of the repository index page. Default value: "5".
59 59
60cache-scanrc-ttl:: 60cache-scanrc-ttl::
61 Number which specifies the time-to-live, in minutes, for the result 61 Number which specifies the time-to-live, in minutes, for the result
62 of scanning a path for git repositories. Default value: "15". 62 of scanning a path for git repositories. Default value: "15".
63 63
64cache-size:: 64cache-size::
65 The maximum number of entries in the cgit cache. Default value: "0" 65 The maximum number of entries in the cgit cache. Default value: "0"
66 (i.e. caching is disabled). 66 (i.e. caching is disabled).
67 67
68cache-static-ttl:: 68cache-static-ttl::
69 Number which specifies the time-to-live, in minutes, for the cached 69 Number which specifies the time-to-live, in minutes, for the cached
70 version of repository pages accessed with a fixed SHA1. Default value: 70 version of repository pages accessed with a fixed SHA1. Default value:
71 "5". 71 "5".
72 72
73clone-prefix:: 73clone-prefix::
74 Space-separated list of common prefixes which, when combined with a 74 Space-separated list of common prefixes which, when combined with a
75 repository url, generates valid clone urls for the repository. This 75 repository url, generates valid clone urls for the repository. This
76 setting is only used if `repo.clone-url` is unspecified. Default value: 76 setting is only used if `repo.clone-url` is unspecified. Default value:
77 none. 77 none.
78 78
79commit-filter:: 79commit-filter::
80 Specifies a command which will be invoked to format commit messages. 80 Specifies a command which will be invoked to format commit messages.
81 The command will get the message on its STDIN, and the STDOUT from the 81 The command will get the message on its STDIN, and the STDOUT from the
82 command will be included verbatim as the commit message, i.e. this can 82 command will be included verbatim as the commit message, i.e. this can
83 be used to implement bugtracker integration. Default value: none. 83 be used to implement bugtracker integration. Default value: none.
84 84
85css:: 85css::
86 Url which specifies the css document to include in all cgit pages. 86 Url which specifies the css document to include in all cgit pages.
87 Default value: "/cgit.css". 87 Default value: "/cgit.css".
88 88
89embedded:: 89embedded::
90 Flag which, when set to "1", will make cgit generate a html fragment 90 Flag which, when set to "1", will make cgit generate a html fragment
91 suitable for embedding in other html pages. Default value: none. See 91 suitable for embedding in other html pages. Default value: none. See
92 also: "noheader". 92 also: "noheader".
93 93
94enable-filter-overrides:: 94enable-filter-overrides::
95 Flag which, when set to "1", allows all filter settings to be 95 Flag which, when set to "1", allows all filter settings to be
96 overridden in repository-specific cgitrc files. Default value: none. 96 overridden in repository-specific cgitrc files. Default value: none.
97 97
98enable-index-links:: 98enable-index-links::
99 Flag which, when set to "1", will make cgit generate extra links for 99 Flag which, when set to "1", will make cgit generate extra links for
100 each repo in the repository index (specifically, to the "summary", 100 each repo in the repository index (specifically, to the "summary",
101 "commit" and "tree" pages). Default value: "0". 101 "commit" and "tree" pages). Default value: "0".
102 102
103enable-log-filecount:: 103enable-log-filecount::
104 Flag which, when set to "1", will make cgit print the number of 104 Flag which, when set to "1", will make cgit print the number of
105 modified files for each commit on the repository log page. Default 105 modified files for each commit on the repository log page. Default
106 value: "0". 106 value: "0".
107 107
108enable-log-linecount:: 108enable-log-linecount::
109 Flag which, when set to "1", will make cgit print the number of added 109 Flag which, when set to "1", will make cgit print the number of added
110 and removed lines for each commit on the repository log page. Default 110 and removed lines for each commit on the repository log page. Default
111 value: "0". 111 value: "0".
112 112
113enable-remote-branches:: 113enable-remote-branches::
114 Flag which, when set to "1", will make cgit display remote branches 114 Flag which, when set to "1", will make cgit display remote branches
115 in the summary and refs views. Default value: "0". See also: 115 in the summary and refs views. Default value: "0". See also:
116 "repo.enable-remote-branches". 116 "repo.enable-remote-branches".
117 117
118enable-subject-links:: 118enable-subject-links::
119 Flag which, when set to "1", will make cgit use the subject of the 119 Flag which, when set to "1", will make cgit use the subject of the
120 parent commit as link text when generating links to parent commits 120 parent commit as link text when generating links to parent commits
121 in commit view. Default value: "0". See also: 121 in commit view. Default value: "0". See also:
122 "repo.enable-subject-links". 122 "repo.enable-subject-links".
123 123
124enable-tree-linenumbers:: 124enable-tree-linenumbers::
125 Flag which, when set to "1", will make cgit generate linenumber links 125 Flag which, when set to "1", will make cgit generate linenumber links
126 for plaintext blobs printed in the tree view. Default value: "1". 126 for plaintext blobs printed in the tree view. Default value: "1".
127 127
128favicon:: 128favicon::
129 Url used as link to a shortcut icon for cgit. If specified, it is 129 Url used as link to a shortcut icon for cgit. If specified, it is
130 suggested to use the value "/favicon.ico" since certain browsers will 130 suggested to use the value "/favicon.ico" since certain browsers will
131 ignore other values. Default value: none. 131 ignore other values. Default value: none.
132 132
133footer:: 133footer::
134 The content of the file specified with this option will be included 134 The content of the file specified with this option will be included
135 verbatim at the bottom of all pages (i.e. it replaces the standard 135 verbatim at the bottom of all pages (i.e. it replaces the standard
136 "generated by..." message. Default value: none. 136 "generated by..." message. Default value: none.
137 137
138head-include:: 138head-include::
139 The content of the file specified with this option will be included 139 The content of the file specified with this option will be included
140 verbatim in the html HEAD section on all pages. Default value: none. 140 verbatim in the html HEAD section on all pages. Default value: none.
141 141
142header:: 142header::
143 The content of the file specified with this option will be included 143 The content of the file specified with this option will be included
144 verbatim at the top of all pages. Default value: none. 144 verbatim at the top of all pages. Default value: none.
145 145
146include:: 146include::
147 Name of a configfile to include before the rest of the current config- 147 Name of a configfile to include before the rest of the current config-
148 file is parsed. Default value: none. 148 file is parsed. Default value: none.
149 149
150index-header:: 150index-header::
151 The content of the file specified with this option will be included 151 The content of the file specified with this option will be included
152 verbatim above the repository index. This setting is deprecated, and 152 verbatim above the repository index. This setting is deprecated, and
153 will not be supported by cgit-1.0 (use root-readme instead). Default 153 will not be supported by cgit-1.0 (use root-readme instead). Default
154 value: none. 154 value: none.
155 155
156index-info:: 156index-info::
157 The content of the file specified with this option will be included 157 The content of the file specified with this option will be included
158 verbatim below the heading on the repository index page. This setting 158 verbatim below the heading on the repository index page. This setting
159 is deprecated, and will not be supported by cgit-1.0 (use root-desc 159 is deprecated, and will not be supported by cgit-1.0 (use root-desc
160 instead). Default value: none. 160 instead). Default value: none.
161 161
162local-time:: 162local-time::
163 Flag which, if set to "1", makes cgit print commit and tag times in the 163 Flag which, if set to "1", makes cgit print commit and tag times in the
164 servers timezone. Default value: "0". 164 servers timezone. Default value: "0".
165 165
166logo:: 166logo::
167 Url which specifies the source of an image which will be used as a logo 167 Url which specifies the source of an image which will be used as a logo
168 on all cgit pages. Default value: "/cgit.png". 168 on all cgit pages. Default value: "/cgit.png".
169 169
170logo-link:: 170logo-link::
171 Url loaded when clicking on the cgit logo image. If unspecified the 171 Url loaded when clicking on the cgit logo image. If unspecified the
172 calculated url of the repository index page will be used. Default 172 calculated url of the repository index page will be used. Default
173 value: none. 173 value: none.
174 174
175max-atom-items:: 175max-atom-items::
176 Specifies the number of items to display in atom feeds view. Default 176 Specifies the number of items to display in atom feeds view. Default
177 value: "10". 177 value: "10".
178 178
179max-commit-count:: 179max-commit-count::
180 Specifies the number of entries to list per page in "log" view. Default 180 Specifies the number of entries to list per page in "log" view. Default
181 value: "50". 181 value: "50".
182 182
183max-message-length:: 183max-message-length::
184 Specifies the maximum number of commit message characters to display in 184 Specifies the maximum number of commit message characters to display in
185 "log" view. Default value: "80". 185 "log" view. Default value: "80".
186 186
187max-repo-count:: 187max-repo-count::
188 Specifies the number of entries to list per page on therepository 188 Specifies the number of entries to list per page on therepository
189 index page. Default value: "50". 189 index page. Default value: "50".
190 190
191max-repodesc-length:: 191max-repodesc-length::
192 Specifies the maximum number of repo description characters to display 192 Specifies the maximum number of repo description characters to display
193 on the repository index page. Default value: "80". 193 on the repository index page. Default value: "80".
194 194
195max-blob-size:: 195max-blob-size::
196 Specifies the maximum size of a blob to display HTML for in KBytes. 196 Specifies the maximum size of a blob to display HTML for in KBytes.
197 Default value: "0" (limit disabled). 197 Default value: "0" (limit disabled).
198 198
199max-stats:: 199max-stats::
200 Set the default maximum statistics period. Valid values are "week", 200 Set the default maximum statistics period. Valid values are "week",
201 "month", "quarter" and "year". If unspecified, statistics are 201 "month", "quarter" and "year". If unspecified, statistics are
202 disabled. Default value: none. See also: "repo.max-stats". 202 disabled. Default value: none. See also: "repo.max-stats".
203 203
204mimetype.<ext>:: 204mimetype.<ext>::
205 Set the mimetype for the specified filename extension. This is used 205 Set the mimetype for the specified filename extension. This is used
206 by the `plain` command when returning blob content. 206 by the `plain` command when returning blob content.
207 207
208module-link:: 208module-link::
209 Text which will be used as the formatstring for a hyperlink when a 209 Text which will be used as the formatstring for a hyperlink when a
210 submodule is printed in a directory listing. The arguments for the 210 submodule is printed in a directory listing. The arguments for the
211 formatstring are the path and SHA1 of the submodule commit. Default 211 formatstring are the path and SHA1 of the submodule commit. Default
212 value: "./?repo=%s&page=commit&id=%s" 212 value: "./?repo=%s&page=commit&id=%s"
213 213
214nocache:: 214nocache::
215 If set to the value "1" caching will be disabled. This settings is 215 If set to the value "1" caching will be disabled. This settings is
216 deprecated, and will not be honored starting with cgit-1.0. Default 216 deprecated, and will not be honored starting with cgit-1.0. Default
217 value: "0". 217 value: "0".
218 218
219noplainemail:: 219noplainemail::
220 If set to "1" showing full author email adresses will be disabled. 220 If set to "1" showing full author email adresses will be disabled.
221 Default value: "0". 221 Default value: "0".
222 222
223noheader:: 223noheader::
224 Flag which, when set to "1", will make cgit omit the standard header 224 Flag which, when set to "1", will make cgit omit the standard header
225 on all pages. Default value: none. See also: "embedded". 225 on all pages. Default value: none. See also: "embedded".
226 226
227renamelimit:: 227renamelimit::
228 Maximum number of files to consider when detecting renames. The value 228 Maximum number of files to consider when detecting renames. The value
229 "-1" uses the compiletime value in git (for further info, look at 229 "-1" uses the compiletime value in git (for further info, look at
230 `man git-diff`). Default value: "-1". 230 `man git-diff`). Default value: "-1".
231 231
232repo.group:: 232repo.group::
233 Legacy alias for "section". This option is deprecated and will not be 233 Legacy alias for "section". This option is deprecated and will not be
234 supported in cgit-1.0. 234 supported in cgit-1.0.
235 235
236robots:: 236robots::
237 Text used as content for the "robots" meta-tag. Default value: 237 Text used as content for the "robots" meta-tag. Default value:
238 "index, nofollow". 238 "index, nofollow".
239 239
240root-desc:: 240root-desc::
241 Text printed below the heading on the repository index page. Default 241 Text printed below the heading on the repository index page. Default
242 value: "a fast webinterface for the git dscm". 242 value: "a fast webinterface for the git dscm".
243 243
244root-readme:: 244root-readme::
245 The content of the file specified with this option will be included 245 The content of the file specified with this option will be included
246 verbatim below the "about" link on the repository index page. Default 246 verbatim below the "about" link on the repository index page. Default
247 value: none. 247 value: none.
248 248
249root-title:: 249root-title::
250 Text printed as heading on the repository index page. Default value: 250 Text printed as heading on the repository index page. Default value:
251 "Git Repository Browser". 251 "Git Repository Browser".
252 252
253scan-path:: 253scan-path::
254 A path which will be scanned for repositories. If caching is enabled, 254 A path which will be scanned for repositories. If caching is enabled,
255 the result will be cached as a cgitrc include-file in the cache 255 the result will be cached as a cgitrc include-file in the cache
256 directory. Default value: none. See also: cache-scanrc-ttl. 256 directory. Default value: none. See also: cache-scanrc-ttl.
257 257
258section:: 258section::
259 The name of the current repository section - all repositories defined 259 The name of the current repository section - all repositories defined
260 after this option will inherit the current section name. Default value: 260 after this option will inherit the current section name. Default value:
261 none. 261 none.
262 262
263side-by-side-diffs:: 263side-by-side-diffs::
264 If set to "1" shows side-by-side diffs instead of unidiffs per 264 If set to "1" shows side-by-side diffs instead of unidiffs per
265 default. Default value: "0". 265 default. Default value: "0".
266 266
267snapshots:: 267snapshots::
268 Text which specifies the default set of snapshot formats generated by 268 Text which specifies the default set of snapshot formats generated by
269 cgit. The value is a space-separated list of zero or more of the 269 cgit. The value is a space-separated list of zero or more of the
270 values "tar", "tar.gz", "tar.bz2" and "zip". Default value: none. 270 values "tar", "tar.gz", "tar.bz2" and "zip". Default value: none.
271 271
272source-filter:: 272source-filter::
273 Specifies a command which will be invoked to format plaintext blobs 273 Specifies a command which will be invoked to format plaintext blobs
274 in the tree view. The command will get the blob content on its STDIN 274 in the tree view. The command will get the blob content on its STDIN
275 and the name of the blob as its only command line argument. The STDOUT 275 and the name of the blob as its only command line argument. The STDOUT
276 from the command will be included verbatim as the blob contents, i.e. 276 from the command will be included verbatim as the blob contents, i.e.
277 this can be used to implement e.g. syntax highlighting. Default value: 277 this can be used to implement e.g. syntax highlighting. Default value:
278 none. 278 none.
279 279
280summary-branches:: 280summary-branches::
281 Specifies the number of branches to display in the repository "summary" 281 Specifies the number of branches to display in the repository "summary"
282 view. Default value: "10". 282 view. Default value: "10".
283 283
284summary-log:: 284summary-log::
285 Specifies the number of log entries to display in the repository 285 Specifies the number of log entries to display in the repository
286 "summary" view. Default value: "10". 286 "summary" view. Default value: "10".
287 287
288summary-tags:: 288summary-tags::
289 Specifies the number of tags to display in the repository "summary" 289 Specifies the number of tags to display in the repository "summary"
290 view. Default value: "10". 290 view. Default value: "10".
291 291
292virtual-root:: 292virtual-root::
293 Url which, if specified, will be used as root for all cgit links. It 293 Url which, if specified, will be used as root for all cgit links. It
294 will also cause cgit to generate 'virtual urls', i.e. urls like 294 will also cause cgit to generate 'virtual urls', i.e. urls like
295 '/cgit/tree/README' as opposed to '?r=cgit&p=tree&path=README'. Default 295 '/cgit/tree/README' as opposed to '?r=cgit&p=tree&path=README'. Default
296 value: none. 296 value: none.
297 NOTE: cgit has recently learned how to use PATH_INFO to achieve the 297 NOTE: cgit has recently learned how to use PATH_INFO to achieve the
298 same kind of virtual urls, so this option will probably be deprecated. 298 same kind of virtual urls, so this option will probably be deprecated.
299 299
300REPOSITORY SETTINGS 300REPOSITORY SETTINGS
301------------------- 301-------------------
302repo.about-filter:: 302repo.about-filter::
303 Override the default about-filter. Default value: none. See also: 303 Override the default about-filter. Default value: none. See also:
304 "enable-filter-overrides". 304 "enable-filter-overrides".
305 305
306repo.clone-url:: 306repo.clone-url::
307 A list of space-separated urls which can be used to clone this repo. 307 A list of space-separated urls which can be used to clone this repo.
308 Default value: none. 308 Default value: none.
309 309
310repo.commit-filter:: 310repo.commit-filter::
311 Override the default commit-filter. Default value: none. See also: 311 Override the default commit-filter. Default value: none. See also:
312 "enable-filter-overrides". 312 "enable-filter-overrides".
313 313
314repo.defbranch:: 314repo.defbranch::
315 The name of the default branch for this repository. If no such branch 315 The name of the default branch for this repository. If no such branch
316 exists in the repository, the first branch name (when sorted) is used 316 exists in the repository, the first branch name (when sorted) is used
317 as default instead. Default value: "master". 317 as default instead. Default value: "master".
318 318
319repo.desc:: 319repo.desc::
320 The value to show as repository description. Default value: none. 320 The value to show as repository description. Default value: none.
321 321
322repo.enable-log-filecount:: 322repo.enable-log-filecount::
323 A flag which can be used to disable the global setting 323 A flag which can be used to disable the global setting
324 `enable-log-filecount'. Default value: none. 324 `enable-log-filecount'. Default value: none.
325 325
326repo.enable-log-linecount:: 326repo.enable-log-linecount::
327 A flag which can be used to disable the global setting 327 A flag which can be used to disable the global setting
328 `enable-log-linecount'. Default value: none. 328 `enable-log-linecount'. Default value: none.
329 329
330repo.enable-remote-branches:: 330repo.enable-remote-branches::
331 Flag which, when set to "1", will make cgit display remote branches 331 Flag which, when set to "1", will make cgit display remote branches
332 in the summary and refs views. Default value: <enable-remote-branches>. 332 in the summary and refs views. Default value: <enable-remote-branches>.
333 333
334repo.enable-subject-links:: 334repo.enable-subject-links::
335 A flag which can be used to override the global setting 335 A flag which can be used to override the global setting
336 `enable-subject-links'. Default value: none. 336 `enable-subject-links'. Default value: none.
337 337
338repo.max-stats:: 338repo.max-stats::
339 Override the default maximum statistics period. Valid values are equal 339 Override the default maximum statistics period. Valid values are equal
340 to the values specified for the global "max-stats" setting. Default 340 to the values specified for the global "max-stats" setting. Default
341 value: none. 341 value: none.
342 342
343repo.name:: 343repo.name::
344 The value to show as repository name. Default value: <repo.url>. 344 The value to show as repository name. Default value: <repo.url>.
345 345
346repo.owner:: 346repo.owner::
347 A value used to identify the owner of the repository. Default value: 347 A value used to identify the owner of the repository. Default value:
348 none. 348 none.
349 349
350repo.path:: 350repo.path::
351 An absolute path to the repository directory. For non-bare repositories 351 An absolute path to the repository directory. For non-bare repositories
352 this is the .git-directory. Default value: none. 352 this is the .git-directory. Default value: none.
353 353
354repo.readme:: 354repo.readme::
355 A path (relative to <repo.path>) which specifies a file to include 355 A path (relative to <repo.path>) which specifies a file to include
356 verbatim as the "About" page for this repo. Default value: none. 356 verbatim as the "About" page for this repo. Default value: none.
357 357
358repo.snapshots:: 358repo.snapshots::
359 A mask of allowed snapshot-formats for this repo, restricted by the 359 A mask of allowed snapshot-formats for this repo, restricted by the
360 "snapshots" global setting. Default value: <snapshots>. 360 "snapshots" global setting. Default value: <snapshots>.
361 361
362repo.section:: 362repo.section::
363 Override the current section name for this repository. Default value: 363 Override the current section name for this repository. Default value:
364 none. 364 none.
365 365
366repo.source-filter:: 366repo.source-filter::
367 Override the default source-filter. Default value: none. See also: 367 Override the default source-filter. Default value: none. See also:
368 "enable-filter-overrides". 368 "enable-filter-overrides".
369 369
370repo.url:: 370repo.url::
371 The relative url used to access the repository. This must be the first 371 The relative url used to access the repository. This must be the first
372 setting specified for each repo. Default value: none. 372 setting specified for each repo. Default value: none.
373 373
374 374
375REPOSITORY-SPECIFIC CGITRC FILE 375REPOSITORY-SPECIFIC CGITRC FILE
376------------------------------- 376-------------------------------
377When the option "scan-path" is used to auto-discover git repositories, cgit 377When the option "scan-path" is used to auto-discover git repositories, cgit
378will try to parse the file "cgitrc" within any found repository. Such a 378will try to parse the file "cgitrc" within any found repository. Such a
379repo-specific config file may contain any of the repo-specific options 379repo-specific config file may contain any of the repo-specific options
380described above, except "repo.url" and "repo.path". Additionally, the "filter" 380described above, except "repo.url" and "repo.path". Additionally, the "filter"
381options are only acknowledged in repo-specific config files when 381options are only acknowledged in repo-specific config files when
382"enable-filter-overrides" is set to "1". 382"enable-filter-overrides" is set to "1".
383 383
384Note: the "repo." prefix is dropped from the option names in repo-specific 384Note: the "repo." prefix is dropped from the option names in repo-specific
385config files, e.g. "repo.desc" becomes "desc". 385config files, e.g. "repo.desc" becomes "desc".
386 386
387 387
388EXAMPLE CGITRC FILE 388EXAMPLE CGITRC FILE
389------------------- 389-------------------
390 390
391.... 391....
392# Enable caching of up to 1000 output entriess 392# Enable caching of up to 1000 output entriess
393cache-size=1000 393cache-size=1000
394 394
395 395
396# Specify some default clone prefixes 396# Specify some default clone prefixes
397clone-prefix=git://foobar.com ssh://foobar.com/pub/git http://foobar.com/git 397clone-prefix=git://foobar.com ssh://foobar.com/pub/git http://foobar.com/git
398 398
399# Specify the css url 399# Specify the css url
400css=/css/cgit.css 400css=/css/cgit.css
401 401
402 402
403# Show extra links for each repository on the index page 403# Show extra links for each repository on the index page
404enable-index-links=1 404enable-index-links=1
405 405
406 406
407# Show number of affected files per commit on the log pages 407# Show number of affected files per commit on the log pages
408enable-log-filecount=1 408enable-log-filecount=1
409 409
410 410
411# Show number of added/removed lines per commit on the log pages 411# Show number of added/removed lines per commit on the log pages
412enable-log-linecount=1 412enable-log-linecount=1
413 413
414 414
415# Add a cgit favicon 415# Add a cgit favicon
416favicon=/favicon.ico 416favicon=/favicon.ico
417 417
418 418
419# Use a custom logo 419# Use a custom logo
420logo=/img/mylogo.png 420logo=/img/mylogo.png
421 421
422 422
423# Enable statistics per week, month and quarter 423# Enable statistics per week, month and quarter
424max-stats=quarter 424max-stats=quarter
425 425
426 426
427# Set the title and heading of the repository index page 427# Set the title and heading of the repository index page
428root-title=foobar.com git repositories 428root-title=foobar.com git repositories
429 429
430 430
431# Set a subheading for the repository index page 431# Set a subheading for the repository index page
432root-desc=tracking the foobar development 432root-desc=tracking the foobar development
433 433
434 434
435# Include some more info about foobar.com on the index page 435# Include some more info about foobar.com on the index page
436root-readme=/var/www/htdocs/about.html 436root-readme=/var/www/htdocs/about.html
437 437
438 438
439# Allow download of tar.gz, tar.bz2 and zip-files 439# Allow download of tar.gz, tar.bz2 and zip-files
440snapshots=tar.gz tar.bz2 zip 440snapshots=tar.gz tar.bz2 zip
441 441
442 442
443## 443##
444## List of common mimetypes 444## List of common mimetypes
445## 445##
446 446
447mimetype.git=image/git 447mimetype.gif=image/gif
448mimetype.html=text/html 448mimetype.html=text/html
449mimetype.jpg=image/jpeg 449mimetype.jpg=image/jpeg
450mimetype.jpeg=image/jpeg 450mimetype.jpeg=image/jpeg
451mimetype.pdf=application/pdf 451mimetype.pdf=application/pdf
452mimetype.png=image/png 452mimetype.png=image/png
453mimetype.svg=image/svg+xml 453mimetype.svg=image/svg+xml
454 454
455 455
456## 456##
457## List of repositories. 457## List of repositories.
458## PS: Any repositories listed when section is unset will not be 458## PS: Any repositories listed when section is unset will not be
459## displayed under a section heading 459## displayed under a section heading
460## PPS: This list could be kept in a different file (e.g. '/etc/cgitrepos') 460## PPS: This list could be kept in a different file (e.g. '/etc/cgitrepos')
461## and included like this: 461## and included like this:
462## include=/etc/cgitrepos 462## include=/etc/cgitrepos
463## 463##
464 464
465 465
466repo.url=foo 466repo.url=foo
467repo.path=/pub/git/foo.git 467repo.path=/pub/git/foo.git
468repo.desc=the master foo repository 468repo.desc=the master foo repository
469repo.owner=fooman@foobar.com 469repo.owner=fooman@foobar.com
470repo.readme=info/web/about.html 470repo.readme=info/web/about.html
471 471
472 472
473repo.url=bar 473repo.url=bar
474repo.path=/pub/git/bar.git 474repo.path=/pub/git/bar.git
475repo.desc=the bars for your foo 475repo.desc=the bars for your foo
476repo.owner=barman@foobar.com 476repo.owner=barman@foobar.com
477repo.readme=info/web/about.html 477repo.readme=info/web/about.html
478 478
479 479
480# The next repositories will be displayed under the 'extras' heading 480# The next repositories will be displayed under the 'extras' heading
481section=extras 481section=extras
482 482
483 483
484repo.url=baz 484repo.url=baz
485repo.path=/pub/git/baz.git 485repo.path=/pub/git/baz.git
486repo.desc=a set of extensions for bar users 486repo.desc=a set of extensions for bar users
487 487
488repo.url=wiz 488repo.url=wiz
489repo.path=/pub/git/wiz.git 489repo.path=/pub/git/wiz.git
490repo.desc=the wizard of foo 490repo.desc=the wizard of foo
491 491
492 492
493# Add some mirrored repositories 493# Add some mirrored repositories
494section=mirrors 494section=mirrors
495 495
496 496
497repo.url=git 497repo.url=git
498repo.path=/pub/git/git.git 498repo.path=/pub/git/git.git
499repo.desc=the dscm 499repo.desc=the dscm
500 500
501 501
502repo.url=linux 502repo.url=linux
503repo.path=/pub/git/linux.git 503repo.path=/pub/git/linux.git
504repo.desc=the kernel 504repo.desc=the kernel
505 505
506# Disable adhoc downloads of this repo 506# Disable adhoc downloads of this repo
507repo.snapshots=0 507repo.snapshots=0
508 508
509# Disable line-counts for this repo 509# Disable line-counts for this repo
510repo.enable-log-linecount=0 510repo.enable-log-linecount=0
511 511
512# Restrict the max statistics period for this repo 512# Restrict the max statistics period for this repo
513repo.max-stats=month 513repo.max-stats=month
514.... 514....
515 515
516 516
517BUGS 517BUGS
518---- 518----
519Comments currently cannot appear on the same line as a setting; the comment 519Comments currently cannot appear on the same line as a setting; the comment
520will be included as part of the value. E.g. this line: 520will be included as part of the value. E.g. this line:
521 521
522 robots=index # allow indexing 522 robots=index # allow indexing
523 523
524will generate the following html element: 524will generate the following html element:
525 525
526 <meta name='robots' content='index # allow indexing'/> 526 <meta name='robots' content='index # allow indexing'/>
527 527
528 528
529 529
530AUTHOR 530AUTHOR
531------ 531------
532Lars Hjemli <hjemli@gmail.com> 532Lars Hjemli <hjemli@gmail.com>
diff --git a/cmd.c b/cmd.c
index ad784fc..6dc9f5e 100644
--- a/cmd.c
+++ b/cmd.c
@@ -1,171 +1,171 @@
1/* cmd.c: the cgit command dispatcher 1/* cmd.c: the cgit command dispatcher
2 * 2 *
3 * Copyright (C) 2008 Lars Hjemli 3 * Copyright (C) 2008 Lars Hjemli
4 * 4 *
5 * Licensed under GNU General Public License v2 5 * Licensed under GNU General Public License v2
6 * (see COPYING for full license text) 6 * (see COPYING for full license text)
7 */ 7 */
8 8
9#include "cgit.h" 9#include "cgit.h"
10#include "cmd.h" 10#include "cmd.h"
11#include "cache.h" 11#include "cache.h"
12#include "ui-shared.h" 12#include "ui-shared.h"
13#include "ui-atom.h" 13#include "ui-atom.h"
14#include "ui-blob.h" 14#include "ui-blob.h"
15#include "ui-clone.h" 15#include "ui-clone.h"
16#include "ui-commit.h" 16#include "ui-commit.h"
17#include "ui-diff.h" 17#include "ui-diff.h"
18#include "ui-log.h" 18#include "ui-log.h"
19#include "ui-patch.h" 19#include "ui-patch.h"
20#include "ui-plain.h" 20#include "ui-plain.h"
21#include "ui-refs.h" 21#include "ui-refs.h"
22#include "ui-repolist.h" 22#include "ui-repolist.h"
23#include "ui-snapshot.h" 23#include "ui-snapshot.h"
24#include "ui-stats.h" 24#include "ui-stats.h"
25#include "ui-summary.h" 25#include "ui-summary.h"
26#include "ui-tag.h" 26#include "ui-tag.h"
27#include "ui-tree.h" 27#include "ui-tree.h"
28 28
29static void HEAD_fn(struct cgit_context *ctx) 29static void HEAD_fn(struct cgit_context *ctx)
30{ 30{
31 cgit_clone_head(ctx); 31 cgit_clone_head(ctx);
32} 32}
33 33
34static void atom_fn(struct cgit_context *ctx) 34static void atom_fn(struct cgit_context *ctx)
35{ 35{
36 cgit_print_atom(ctx->qry.head, ctx->qry.path, ctx->cfg.max_atom_items); 36 cgit_print_atom(ctx->qry.head, ctx->qry.path, ctx->cfg.max_atom_items);
37} 37}
38 38
39static void about_fn(struct cgit_context *ctx) 39static void about_fn(struct cgit_context *ctx)
40{ 40{
41 if (ctx->repo) 41 if (ctx->repo)
42 cgit_print_repo_readme(ctx->qry.path); 42 cgit_print_repo_readme(ctx->qry.path);
43 else 43 else
44 cgit_print_site_readme(); 44 cgit_print_site_readme();
45} 45}
46 46
47static void blob_fn(struct cgit_context *ctx) 47static void blob_fn(struct cgit_context *ctx)
48{ 48{
49 cgit_print_blob(ctx->qry.sha1, ctx->qry.path, ctx->qry.head); 49 cgit_print_blob(ctx->qry.sha1, ctx->qry.path, ctx->qry.head);
50} 50}
51 51
52static void commit_fn(struct cgit_context *ctx) 52static void commit_fn(struct cgit_context *ctx)
53{ 53{
54 cgit_print_commit(ctx->qry.sha1); 54 cgit_print_commit(ctx->qry.sha1, ctx->qry.path);
55} 55}
56 56
57static void diff_fn(struct cgit_context *ctx) 57static void diff_fn(struct cgit_context *ctx)
58{ 58{
59 cgit_print_diff(ctx->qry.sha1, ctx->qry.sha2, ctx->qry.path); 59 cgit_print_diff(ctx->qry.sha1, ctx->qry.sha2, ctx->qry.path);
60} 60}
61 61
62static void info_fn(struct cgit_context *ctx) 62static void info_fn(struct cgit_context *ctx)
63{ 63{
64 cgit_clone_info(ctx); 64 cgit_clone_info(ctx);
65} 65}
66 66
67static void log_fn(struct cgit_context *ctx) 67static void log_fn(struct cgit_context *ctx)
68{ 68{
69 cgit_print_log(ctx->qry.sha1, ctx->qry.ofs, ctx->cfg.max_commit_count, 69 cgit_print_log(ctx->qry.sha1, ctx->qry.ofs, ctx->cfg.max_commit_count,
70 ctx->qry.grep, ctx->qry.search, ctx->qry.path, 1); 70 ctx->qry.grep, ctx->qry.search, ctx->qry.path, 1);
71} 71}
72 72
73static void ls_cache_fn(struct cgit_context *ctx) 73static void ls_cache_fn(struct cgit_context *ctx)
74{ 74{
75 ctx->page.mimetype = "text/plain"; 75 ctx->page.mimetype = "text/plain";
76 ctx->page.filename = "ls-cache.txt"; 76 ctx->page.filename = "ls-cache.txt";
77 cgit_print_http_headers(ctx); 77 cgit_print_http_headers(ctx);
78 cache_ls(ctx->cfg.cache_root); 78 cache_ls(ctx->cfg.cache_root);
79} 79}
80 80
81static void objects_fn(struct cgit_context *ctx) 81static void objects_fn(struct cgit_context *ctx)
82{ 82{
83 cgit_clone_objects(ctx); 83 cgit_clone_objects(ctx);
84} 84}
85 85
86static void repolist_fn(struct cgit_context *ctx) 86static void repolist_fn(struct cgit_context *ctx)
87{ 87{
88 cgit_print_repolist(); 88 cgit_print_repolist();
89} 89}
90 90
91static void patch_fn(struct cgit_context *ctx) 91static void patch_fn(struct cgit_context *ctx)
92{ 92{
93 cgit_print_patch(ctx->qry.sha1); 93 cgit_print_patch(ctx->qry.sha1, ctx->qry.path);
94} 94}
95 95
96static void plain_fn(struct cgit_context *ctx) 96static void plain_fn(struct cgit_context *ctx)
97{ 97{
98 cgit_print_plain(ctx); 98 cgit_print_plain(ctx);
99} 99}
100 100
101static void refs_fn(struct cgit_context *ctx) 101static void refs_fn(struct cgit_context *ctx)
102{ 102{
103 cgit_print_refs(); 103 cgit_print_refs();
104} 104}
105 105
106static void snapshot_fn(struct cgit_context *ctx) 106static void snapshot_fn(struct cgit_context *ctx)
107{ 107{
108 cgit_print_snapshot(ctx->qry.head, ctx->qry.sha1, ctx->qry.path, 108 cgit_print_snapshot(ctx->qry.head, ctx->qry.sha1, ctx->qry.path,
109 ctx->repo->snapshots, ctx->qry.nohead); 109 ctx->repo->snapshots, ctx->qry.nohead);
110} 110}
111 111
112static void stats_fn(struct cgit_context *ctx) 112static void stats_fn(struct cgit_context *ctx)
113{ 113{
114 cgit_show_stats(ctx); 114 cgit_show_stats(ctx);
115} 115}
116 116
117static void summary_fn(struct cgit_context *ctx) 117static void summary_fn(struct cgit_context *ctx)
118{ 118{
119 cgit_print_summary(); 119 cgit_print_summary();
120} 120}
121 121
122static void tag_fn(struct cgit_context *ctx) 122static void tag_fn(struct cgit_context *ctx)
123{ 123{
124 cgit_print_tag(ctx->qry.sha1); 124 cgit_print_tag(ctx->qry.sha1);
125} 125}
126 126
127static void tree_fn(struct cgit_context *ctx) 127static void tree_fn(struct cgit_context *ctx)
128{ 128{
129 cgit_print_tree(ctx->qry.sha1, ctx->qry.path); 129 cgit_print_tree(ctx->qry.sha1, ctx->qry.path);
130} 130}
131 131
132#define def_cmd(name, want_repo, want_layout) \ 132#define def_cmd(name, want_repo, want_layout, want_vpath) \
133 {#name, name##_fn, want_repo, want_layout} 133 {#name, name##_fn, want_repo, want_layout, want_vpath}
134 134
135struct cgit_cmd *cgit_get_cmd(struct cgit_context *ctx) 135struct cgit_cmd *cgit_get_cmd(struct cgit_context *ctx)
136{ 136{
137 static struct cgit_cmd cmds[] = { 137 static struct cgit_cmd cmds[] = {
138 def_cmd(HEAD, 1, 0), 138 def_cmd(HEAD, 1, 0, 0),
139 def_cmd(atom, 1, 0), 139 def_cmd(atom, 1, 0, 0),
140 def_cmd(about, 0, 1), 140 def_cmd(about, 0, 1, 0),
141 def_cmd(blob, 1, 0), 141 def_cmd(blob, 1, 0, 0),
142 def_cmd(commit, 1, 1), 142 def_cmd(commit, 1, 1, 1),
143 def_cmd(diff, 1, 1), 143 def_cmd(diff, 1, 1, 1),
144 def_cmd(info, 1, 0), 144 def_cmd(info, 1, 0, 0),
145 def_cmd(log, 1, 1), 145 def_cmd(log, 1, 1, 1),
146 def_cmd(ls_cache, 0, 0), 146 def_cmd(ls_cache, 0, 0, 0),
147 def_cmd(objects, 1, 0), 147 def_cmd(objects, 1, 0, 0),
148 def_cmd(patch, 1, 0), 148 def_cmd(patch, 1, 0, 1),
149 def_cmd(plain, 1, 0), 149 def_cmd(plain, 1, 0, 0),
150 def_cmd(refs, 1, 1), 150 def_cmd(refs, 1, 1, 0),
151 def_cmd(repolist, 0, 0), 151 def_cmd(repolist, 0, 0, 0),
152 def_cmd(snapshot, 1, 0), 152 def_cmd(snapshot, 1, 0, 0),
153 def_cmd(stats, 1, 1), 153 def_cmd(stats, 1, 1, 1),
154 def_cmd(summary, 1, 1), 154 def_cmd(summary, 1, 1, 0),
155 def_cmd(tag, 1, 1), 155 def_cmd(tag, 1, 1, 0),
156 def_cmd(tree, 1, 1), 156 def_cmd(tree, 1, 1, 1),
157 }; 157 };
158 int i; 158 int i;
159 159
160 if (ctx->qry.page == NULL) { 160 if (ctx->qry.page == NULL) {
161 if (ctx->repo) 161 if (ctx->repo)
162 ctx->qry.page = "summary"; 162 ctx->qry.page = "summary";
163 else 163 else
164 ctx->qry.page = "repolist"; 164 ctx->qry.page = "repolist";
165 } 165 }
166 166
167 for(i = 0; i < sizeof(cmds)/sizeof(*cmds); i++) 167 for(i = 0; i < sizeof(cmds)/sizeof(*cmds); i++)
168 if (!strcmp(ctx->qry.page, cmds[i].name)) 168 if (!strcmp(ctx->qry.page, cmds[i].name))
169 return &cmds[i]; 169 return &cmds[i];
170 return NULL; 170 return NULL;
171} 171}
diff --git a/cmd.h b/cmd.h
index ec9e691..8dc01bd 100644
--- a/cmd.h
+++ b/cmd.h
@@ -1,15 +1,16 @@
1#ifndef CMD_H 1#ifndef CMD_H
2#define CMD_H 2#define CMD_H
3 3
4typedef void (*cgit_cmd_fn)(struct cgit_context *ctx); 4typedef void (*cgit_cmd_fn)(struct cgit_context *ctx);
5 5
6struct cgit_cmd { 6struct cgit_cmd {
7 const char *name; 7 const char *name;
8 cgit_cmd_fn fn; 8 cgit_cmd_fn fn;
9 unsigned int want_repo:1, 9 unsigned int want_repo:1,
10 want_layout:1; 10 want_layout:1,
11 want_vpath:1;
11}; 12};
12 13
13extern struct cgit_cmd *cgit_get_cmd(struct cgit_context *ctx); 14extern struct cgit_cmd *cgit_get_cmd(struct cgit_context *ctx);
14 15
15#endif /* CMD_H */ 16#endif /* CMD_H */
diff --git a/shared.c b/shared.c
index 8b3a045..58837dc 100644
--- a/shared.c
+++ b/shared.c
@@ -1,434 +1,433 @@
1/* shared.c: global vars + some callback functions 1/* shared.c: global vars + some callback 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 10
11struct cgit_repolist cgit_repolist; 11struct cgit_repolist cgit_repolist;
12struct cgit_context ctx; 12struct cgit_context ctx;
13int cgit_cmd;
14 13
15int chk_zero(int result, char *msg) 14int chk_zero(int result, char *msg)
16{ 15{
17 if (result != 0) 16 if (result != 0)
18 die("%s: %s", msg, strerror(errno)); 17 die("%s: %s", msg, strerror(errno));
19 return result; 18 return result;
20} 19}
21 20
22int chk_positive(int result, char *msg) 21int chk_positive(int result, char *msg)
23{ 22{
24 if (result <= 0) 23 if (result <= 0)
25 die("%s: %s", msg, strerror(errno)); 24 die("%s: %s", msg, strerror(errno));
26 return result; 25 return result;
27} 26}
28 27
29int chk_non_negative(int result, char *msg) 28int chk_non_negative(int result, char *msg)
30{ 29{
31 if (result < 0) 30 if (result < 0)
32 die("%s: %s",msg, strerror(errno)); 31 die("%s: %s",msg, strerror(errno));
33 return result; 32 return result;
34} 33}
35 34
36struct cgit_repo *cgit_add_repo(const char *url) 35struct cgit_repo *cgit_add_repo(const char *url)
37{ 36{
38 struct cgit_repo *ret; 37 struct cgit_repo *ret;
39 38
40 if (++cgit_repolist.count > cgit_repolist.length) { 39 if (++cgit_repolist.count > cgit_repolist.length) {
41 if (cgit_repolist.length == 0) 40 if (cgit_repolist.length == 0)
42 cgit_repolist.length = 8; 41 cgit_repolist.length = 8;
43 else 42 else
44 cgit_repolist.length *= 2; 43 cgit_repolist.length *= 2;
45 cgit_repolist.repos = xrealloc(cgit_repolist.repos, 44 cgit_repolist.repos = xrealloc(cgit_repolist.repos,
46 cgit_repolist.length * 45 cgit_repolist.length *
47 sizeof(struct cgit_repo)); 46 sizeof(struct cgit_repo));
48 } 47 }
49 48
50 ret = &cgit_repolist.repos[cgit_repolist.count-1]; 49 ret = &cgit_repolist.repos[cgit_repolist.count-1];
51 memset(ret, 0, sizeof(struct cgit_repo)); 50 memset(ret, 0, sizeof(struct cgit_repo));
52 ret->url = trim_end(url, '/'); 51 ret->url = trim_end(url, '/');
53 ret->name = ret->url; 52 ret->name = ret->url;
54 ret->path = NULL; 53 ret->path = NULL;
55 ret->desc = "[no description]"; 54 ret->desc = "[no description]";
56 ret->owner = NULL; 55 ret->owner = NULL;
57 ret->section = ctx.cfg.section; 56 ret->section = ctx.cfg.section;
58 ret->defbranch = "master"; 57 ret->defbranch = "master";
59 ret->snapshots = ctx.cfg.snapshots; 58 ret->snapshots = ctx.cfg.snapshots;
60 ret->enable_log_filecount = ctx.cfg.enable_log_filecount; 59 ret->enable_log_filecount = ctx.cfg.enable_log_filecount;
61 ret->enable_log_linecount = ctx.cfg.enable_log_linecount; 60 ret->enable_log_linecount = ctx.cfg.enable_log_linecount;
62 ret->enable_remote_branches = ctx.cfg.enable_remote_branches; 61 ret->enable_remote_branches = ctx.cfg.enable_remote_branches;
63 ret->enable_subject_links = ctx.cfg.enable_subject_links; 62 ret->enable_subject_links = ctx.cfg.enable_subject_links;
64 ret->max_stats = ctx.cfg.max_stats; 63 ret->max_stats = ctx.cfg.max_stats;
65 ret->module_link = ctx.cfg.module_link; 64 ret->module_link = ctx.cfg.module_link;
66 ret->readme = NULL; 65 ret->readme = NULL;
67 ret->mtime = -1; 66 ret->mtime = -1;
68 ret->about_filter = ctx.cfg.about_filter; 67 ret->about_filter = ctx.cfg.about_filter;
69 ret->commit_filter = ctx.cfg.commit_filter; 68 ret->commit_filter = ctx.cfg.commit_filter;
70 ret->source_filter = ctx.cfg.source_filter; 69 ret->source_filter = ctx.cfg.source_filter;
71 return ret; 70 return ret;
72} 71}
73 72
74struct cgit_repo *cgit_get_repoinfo(const char *url) 73struct cgit_repo *cgit_get_repoinfo(const char *url)
75{ 74{
76 int i; 75 int i;
77 struct cgit_repo *repo; 76 struct cgit_repo *repo;
78 77
79 for (i=0; i<cgit_repolist.count; i++) { 78 for (i=0; i<cgit_repolist.count; i++) {
80 repo = &cgit_repolist.repos[i]; 79 repo = &cgit_repolist.repos[i];
81 if (!strcmp(repo->url, url)) 80 if (!strcmp(repo->url, url))
82 return repo; 81 return repo;
83 } 82 }
84 return NULL; 83 return NULL;
85} 84}
86 85
87void *cgit_free_commitinfo(struct commitinfo *info) 86void *cgit_free_commitinfo(struct commitinfo *info)
88{ 87{
89 free(info->author); 88 free(info->author);
90 free(info->author_email); 89 free(info->author_email);
91 free(info->committer); 90 free(info->committer);
92 free(info->committer_email); 91 free(info->committer_email);
93 free(info->subject); 92 free(info->subject);
94 free(info->msg); 93 free(info->msg);
95 free(info->msg_encoding); 94 free(info->msg_encoding);
96 free(info); 95 free(info);
97 return NULL; 96 return NULL;
98} 97}
99 98
100char *trim_end(const char *str, char c) 99char *trim_end(const char *str, char c)
101{ 100{
102 int len; 101 int len;
103 char *s, *t; 102 char *s, *t;
104 103
105 if (str == NULL) 104 if (str == NULL)
106 return NULL; 105 return NULL;
107 t = (char *)str; 106 t = (char *)str;
108 len = strlen(t); 107 len = strlen(t);
109 while(len > 0 && t[len - 1] == c) 108 while(len > 0 && t[len - 1] == c)
110 len--; 109 len--;
111 110
112 if (len == 0) 111 if (len == 0)
113 return NULL; 112 return NULL;
114 113
115 c = t[len]; 114 c = t[len];
116 t[len] = '\0'; 115 t[len] = '\0';
117 s = xstrdup(t); 116 s = xstrdup(t);
118 t[len] = c; 117 t[len] = c;
119 return s; 118 return s;
120} 119}
121 120
122char *strlpart(char *txt, int maxlen) 121char *strlpart(char *txt, int maxlen)
123{ 122{
124 char *result; 123 char *result;
125 124
126 if (!txt) 125 if (!txt)
127 return txt; 126 return txt;
128 127
129 if (strlen(txt) <= maxlen) 128 if (strlen(txt) <= maxlen)
130 return txt; 129 return txt;
131 result = xmalloc(maxlen + 1); 130 result = xmalloc(maxlen + 1);
132 memcpy(result, txt, maxlen - 3); 131 memcpy(result, txt, maxlen - 3);
133 result[maxlen-1] = result[maxlen-2] = result[maxlen-3] = '.'; 132 result[maxlen-1] = result[maxlen-2] = result[maxlen-3] = '.';
134 result[maxlen] = '\0'; 133 result[maxlen] = '\0';
135 return result; 134 return result;
136} 135}
137 136
138char *strrpart(char *txt, int maxlen) 137char *strrpart(char *txt, int maxlen)
139{ 138{
140 char *result; 139 char *result;
141 140
142 if (!txt) 141 if (!txt)
143 return txt; 142 return txt;
144 143
145 if (strlen(txt) <= maxlen) 144 if (strlen(txt) <= maxlen)
146 return txt; 145 return txt;
147 result = xmalloc(maxlen + 1); 146 result = xmalloc(maxlen + 1);
148 memcpy(result + 3, txt + strlen(txt) - maxlen + 4, maxlen - 3); 147 memcpy(result + 3, txt + strlen(txt) - maxlen + 4, maxlen - 3);
149 result[0] = result[1] = result[2] = '.'; 148 result[0] = result[1] = result[2] = '.';
150 return result; 149 return result;
151} 150}
152 151
153void cgit_add_ref(struct reflist *list, struct refinfo *ref) 152void cgit_add_ref(struct reflist *list, struct refinfo *ref)
154{ 153{
155 size_t size; 154 size_t size;
156 155
157 if (list->count >= list->alloc) { 156 if (list->count >= list->alloc) {
158 list->alloc += (list->alloc ? list->alloc : 4); 157 list->alloc += (list->alloc ? list->alloc : 4);
159 size = list->alloc * sizeof(struct refinfo *); 158 size = list->alloc * sizeof(struct refinfo *);
160 list->refs = xrealloc(list->refs, size); 159 list->refs = xrealloc(list->refs, size);
161 } 160 }
162 list->refs[list->count++] = ref; 161 list->refs[list->count++] = ref;
163} 162}
164 163
165struct refinfo *cgit_mk_refinfo(const char *refname, const unsigned char *sha1) 164struct refinfo *cgit_mk_refinfo(const char *refname, const unsigned char *sha1)
166{ 165{
167 struct refinfo *ref; 166 struct refinfo *ref;
168 167
169 ref = xmalloc(sizeof (struct refinfo)); 168 ref = xmalloc(sizeof (struct refinfo));
170 ref->refname = xstrdup(refname); 169 ref->refname = xstrdup(refname);
171 ref->object = parse_object(sha1); 170 ref->object = parse_object(sha1);
172 switch (ref->object->type) { 171 switch (ref->object->type) {
173 case OBJ_TAG: 172 case OBJ_TAG:
174 ref->tag = cgit_parse_tag((struct tag *)ref->object); 173 ref->tag = cgit_parse_tag((struct tag *)ref->object);
175 break; 174 break;
176 case OBJ_COMMIT: 175 case OBJ_COMMIT:
177 ref->commit = cgit_parse_commit((struct commit *)ref->object); 176 ref->commit = cgit_parse_commit((struct commit *)ref->object);
178 break; 177 break;
179 } 178 }
180 return ref; 179 return ref;
181} 180}
182 181
183int cgit_refs_cb(const char *refname, const unsigned char *sha1, int flags, 182int cgit_refs_cb(const char *refname, const unsigned char *sha1, int flags,
184 void *cb_data) 183 void *cb_data)
185{ 184{
186 struct reflist *list = (struct reflist *)cb_data; 185 struct reflist *list = (struct reflist *)cb_data;
187 struct refinfo *info = cgit_mk_refinfo(refname, sha1); 186 struct refinfo *info = cgit_mk_refinfo(refname, sha1);
188 187
189 if (info) 188 if (info)
190 cgit_add_ref(list, info); 189 cgit_add_ref(list, info);
191 return 0; 190 return 0;
192} 191}
193 192
194void cgit_diff_tree_cb(struct diff_queue_struct *q, 193void cgit_diff_tree_cb(struct diff_queue_struct *q,
195 struct diff_options *options, void *data) 194 struct diff_options *options, void *data)
196{ 195{
197 int i; 196 int i;
198 197
199 for (i = 0; i < q->nr; i++) { 198 for (i = 0; i < q->nr; i++) {
200 if (q->queue[i]->status == 'U') 199 if (q->queue[i]->status == 'U')
201 continue; 200 continue;
202 ((filepair_fn)data)(q->queue[i]); 201 ((filepair_fn)data)(q->queue[i]);
203 } 202 }
204} 203}
205 204
206static int load_mmfile(mmfile_t *file, const unsigned char *sha1) 205static int load_mmfile(mmfile_t *file, const unsigned char *sha1)
207{ 206{
208 enum object_type type; 207 enum object_type type;
209 208
210 if (is_null_sha1(sha1)) { 209 if (is_null_sha1(sha1)) {
211 file->ptr = (char *)""; 210 file->ptr = (char *)"";
212 file->size = 0; 211 file->size = 0;
213 } else { 212 } else {
214 file->ptr = read_sha1_file(sha1, &type, 213 file->ptr = read_sha1_file(sha1, &type,
215 (unsigned long *)&file->size); 214 (unsigned long *)&file->size);
216 } 215 }
217 return 1; 216 return 1;
218} 217}
219 218
220/* 219/*
221 * Receive diff-buffers from xdiff and concatenate them as 220 * Receive diff-buffers from xdiff and concatenate them as
222 * needed across multiple callbacks. 221 * needed across multiple callbacks.
223 * 222 *
224 * This is basically a copy of xdiff-interface.c/xdiff_outf(), 223 * This is basically a copy of xdiff-interface.c/xdiff_outf(),
225 * ripped from git and modified to use globals instead of 224 * ripped from git and modified to use globals instead of
226 * a special callback-struct. 225 * a special callback-struct.
227 */ 226 */
228char *diffbuf = NULL; 227char *diffbuf = NULL;
229int buflen = 0; 228int buflen = 0;
230 229
231int filediff_cb(void *priv, mmbuffer_t *mb, int nbuf) 230int filediff_cb(void *priv, mmbuffer_t *mb, int nbuf)
232{ 231{
233 int i; 232 int i;
234 233
235 for (i = 0; i < nbuf; i++) { 234 for (i = 0; i < nbuf; i++) {
236 if (mb[i].ptr[mb[i].size-1] != '\n') { 235 if (mb[i].ptr[mb[i].size-1] != '\n') {
237 /* Incomplete line */ 236 /* Incomplete line */
238 diffbuf = xrealloc(diffbuf, buflen + mb[i].size); 237 diffbuf = xrealloc(diffbuf, buflen + mb[i].size);
239 memcpy(diffbuf + buflen, mb[i].ptr, mb[i].size); 238 memcpy(diffbuf + buflen, mb[i].ptr, mb[i].size);
240 buflen += mb[i].size; 239 buflen += mb[i].size;
241 continue; 240 continue;
242 } 241 }
243 242
244 /* we have a complete line */ 243 /* we have a complete line */
245 if (!diffbuf) { 244 if (!diffbuf) {
246 ((linediff_fn)priv)(mb[i].ptr, mb[i].size); 245 ((linediff_fn)priv)(mb[i].ptr, mb[i].size);
247 continue; 246 continue;
248 } 247 }
249 diffbuf = xrealloc(diffbuf, buflen + mb[i].size); 248 diffbuf = xrealloc(diffbuf, buflen + mb[i].size);
250 memcpy(diffbuf + buflen, mb[i].ptr, mb[i].size); 249 memcpy(diffbuf + buflen, mb[i].ptr, mb[i].size);
251 ((linediff_fn)priv)(diffbuf, buflen + mb[i].size); 250 ((linediff_fn)priv)(diffbuf, buflen + mb[i].size);
252 free(diffbuf); 251 free(diffbuf);
253 diffbuf = NULL; 252 diffbuf = NULL;
254 buflen = 0; 253 buflen = 0;
255 } 254 }
256 if (diffbuf) { 255 if (diffbuf) {
257 ((linediff_fn)priv)(diffbuf, buflen); 256 ((linediff_fn)priv)(diffbuf, buflen);
258 free(diffbuf); 257 free(diffbuf);
259 diffbuf = NULL; 258 diffbuf = NULL;
260 buflen = 0; 259 buflen = 0;
261 } 260 }
262 return 0; 261 return 0;
263} 262}
264 263
265int cgit_diff_files(const unsigned char *old_sha1, 264int cgit_diff_files(const unsigned char *old_sha1,
266 const unsigned char *new_sha1, unsigned long *old_size, 265 const unsigned char *new_sha1, unsigned long *old_size,
267 unsigned long *new_size, int *binary, linediff_fn fn) 266 unsigned long *new_size, int *binary, linediff_fn fn)
268{ 267{
269 mmfile_t file1, file2; 268 mmfile_t file1, file2;
270 xpparam_t diff_params; 269 xpparam_t diff_params;
271 xdemitconf_t emit_params; 270 xdemitconf_t emit_params;
272 xdemitcb_t emit_cb; 271 xdemitcb_t emit_cb;
273 272
274 if (!load_mmfile(&file1, old_sha1) || !load_mmfile(&file2, new_sha1)) 273 if (!load_mmfile(&file1, old_sha1) || !load_mmfile(&file2, new_sha1))
275 return 1; 274 return 1;
276 275
277 *old_size = file1.size; 276 *old_size = file1.size;
278 *new_size = file2.size; 277 *new_size = file2.size;
279 278
280 if ((file1.ptr && buffer_is_binary(file1.ptr, file1.size)) || 279 if ((file1.ptr && buffer_is_binary(file1.ptr, file1.size)) ||
281 (file2.ptr && buffer_is_binary(file2.ptr, file2.size))) { 280 (file2.ptr && buffer_is_binary(file2.ptr, file2.size))) {
282 *binary = 1; 281 *binary = 1;
283 if (file1.size) 282 if (file1.size)
284 free(file1.ptr); 283 free(file1.ptr);
285 if (file2.size) 284 if (file2.size)
286 free(file2.ptr); 285 free(file2.ptr);
287 return 0; 286 return 0;
288 } 287 }
289 288
290 memset(&diff_params, 0, sizeof(diff_params)); 289 memset(&diff_params, 0, sizeof(diff_params));
291 memset(&emit_params, 0, sizeof(emit_params)); 290 memset(&emit_params, 0, sizeof(emit_params));
292 memset(&emit_cb, 0, sizeof(emit_cb)); 291 memset(&emit_cb, 0, sizeof(emit_cb));
293 diff_params.flags = XDF_NEED_MINIMAL; 292 diff_params.flags = XDF_NEED_MINIMAL;
294 emit_params.ctxlen = 3; 293 emit_params.ctxlen = 3;
295 emit_params.flags = XDL_EMIT_FUNCNAMES; 294 emit_params.flags = XDL_EMIT_FUNCNAMES;
296 emit_cb.outf = filediff_cb; 295 emit_cb.outf = filediff_cb;
297 emit_cb.priv = fn; 296 emit_cb.priv = fn;
298 xdl_diff(&file1, &file2, &diff_params, &emit_params, &emit_cb); 297 xdl_diff(&file1, &file2, &diff_params, &emit_params, &emit_cb);
299 if (file1.size) 298 if (file1.size)
300 free(file1.ptr); 299 free(file1.ptr);
301 if (file2.size) 300 if (file2.size)
302 free(file2.ptr); 301 free(file2.ptr);
303 return 0; 302 return 0;
304} 303}
305 304
306void cgit_diff_tree(const unsigned char *old_sha1, 305void cgit_diff_tree(const unsigned char *old_sha1,
307 const unsigned char *new_sha1, 306 const unsigned char *new_sha1,
308 filepair_fn fn, const char *prefix) 307 filepair_fn fn, const char *prefix)
309{ 308{
310 struct diff_options opt; 309 struct diff_options opt;
311 int ret; 310 int ret;
312 int prefixlen; 311 int prefixlen;
313 312
314 diff_setup(&opt); 313 diff_setup(&opt);
315 opt.output_format = DIFF_FORMAT_CALLBACK; 314 opt.output_format = DIFF_FORMAT_CALLBACK;
316 opt.detect_rename = 1; 315 opt.detect_rename = 1;
317 opt.rename_limit = ctx.cfg.renamelimit; 316 opt.rename_limit = ctx.cfg.renamelimit;
318 DIFF_OPT_SET(&opt, RECURSIVE); 317 DIFF_OPT_SET(&opt, RECURSIVE);
319 opt.format_callback = cgit_diff_tree_cb; 318 opt.format_callback = cgit_diff_tree_cb;
320 opt.format_callback_data = fn; 319 opt.format_callback_data = fn;
321 if (prefix) { 320 if (prefix) {
322 opt.nr_paths = 1; 321 opt.nr_paths = 1;
323 opt.paths = &prefix; 322 opt.paths = &prefix;
324 prefixlen = strlen(prefix); 323 prefixlen = strlen(prefix);
325 opt.pathlens = &prefixlen; 324 opt.pathlens = &prefixlen;
326 } 325 }
327 diff_setup_done(&opt); 326 diff_setup_done(&opt);
328 327
329 if (old_sha1 && !is_null_sha1(old_sha1)) 328 if (old_sha1 && !is_null_sha1(old_sha1))
330 ret = diff_tree_sha1(old_sha1, new_sha1, "", &opt); 329 ret = diff_tree_sha1(old_sha1, new_sha1, "", &opt);
331 else 330 else
332 ret = diff_root_tree_sha1(new_sha1, "", &opt); 331 ret = diff_root_tree_sha1(new_sha1, "", &opt);
333 diffcore_std(&opt); 332 diffcore_std(&opt);
334 diff_flush(&opt); 333 diff_flush(&opt);
335} 334}
336 335
337void cgit_diff_commit(struct commit *commit, filepair_fn fn) 336void cgit_diff_commit(struct commit *commit, filepair_fn fn)
338{ 337{
339 unsigned char *old_sha1 = NULL; 338 unsigned char *old_sha1 = NULL;
340 339
341 if (commit->parents) 340 if (commit->parents)
342 old_sha1 = commit->parents->item->object.sha1; 341 old_sha1 = commit->parents->item->object.sha1;
343 cgit_diff_tree(old_sha1, commit->object.sha1, fn, NULL); 342 cgit_diff_tree(old_sha1, commit->object.sha1, fn, NULL);
344} 343}
345 344
346int cgit_parse_snapshots_mask(const char *str) 345int cgit_parse_snapshots_mask(const char *str)
347{ 346{
348 const struct cgit_snapshot_format *f; 347 const struct cgit_snapshot_format *f;
349 static const char *delim = " \t,:/|;"; 348 static const char *delim = " \t,:/|;";
350 int tl, sl, rv = 0; 349 int tl, sl, rv = 0;
351 350
352 /* favor legacy setting */ 351 /* favor legacy setting */
353 if(atoi(str)) 352 if(atoi(str))
354 return 1; 353 return 1;
355 for(;;) { 354 for(;;) {
356 str += strspn(str,delim); 355 str += strspn(str,delim);
357 tl = strcspn(str,delim); 356 tl = strcspn(str,delim);
358 if (!tl) 357 if (!tl)
359 break; 358 break;
360 for (f = cgit_snapshot_formats; f->suffix; f++) { 359 for (f = cgit_snapshot_formats; f->suffix; f++) {
361 sl = strlen(f->suffix); 360 sl = strlen(f->suffix);
362 if((tl == sl && !strncmp(f->suffix, str, tl)) || 361 if((tl == sl && !strncmp(f->suffix, str, tl)) ||
363 (tl == sl-1 && !strncmp(f->suffix+1, str, tl-1))) { 362 (tl == sl-1 && !strncmp(f->suffix+1, str, tl-1))) {
364 rv |= f->bit; 363 rv |= f->bit;
365 break; 364 break;
366 } 365 }
367 } 366 }
368 str += tl; 367 str += tl;
369 } 368 }
370 return rv; 369 return rv;
371} 370}
372 371
373int cgit_open_filter(struct cgit_filter *filter) 372int cgit_open_filter(struct cgit_filter *filter)
374{ 373{
375 374
376 filter->old_stdout = chk_positive(dup(STDOUT_FILENO), 375 filter->old_stdout = chk_positive(dup(STDOUT_FILENO),
377 "Unable to duplicate STDOUT"); 376 "Unable to duplicate STDOUT");
378 chk_zero(pipe(filter->pipe_fh), "Unable to create pipe to subprocess"); 377 chk_zero(pipe(filter->pipe_fh), "Unable to create pipe to subprocess");
379 filter->pid = chk_non_negative(fork(), "Unable to create subprocess"); 378 filter->pid = chk_non_negative(fork(), "Unable to create subprocess");
380 if (filter->pid == 0) { 379 if (filter->pid == 0) {
381 close(filter->pipe_fh[1]); 380 close(filter->pipe_fh[1]);
382 chk_non_negative(dup2(filter->pipe_fh[0], STDIN_FILENO), 381 chk_non_negative(dup2(filter->pipe_fh[0], STDIN_FILENO),
383 "Unable to use pipe as STDIN"); 382 "Unable to use pipe as STDIN");
384 execvp(filter->cmd, filter->argv); 383 execvp(filter->cmd, filter->argv);
385 die("Unable to exec subprocess %s: %s (%d)", filter->cmd, 384 die("Unable to exec subprocess %s: %s (%d)", filter->cmd,
386 strerror(errno), errno); 385 strerror(errno), errno);
387 } 386 }
388 close(filter->pipe_fh[0]); 387 close(filter->pipe_fh[0]);
389 chk_non_negative(dup2(filter->pipe_fh[1], STDOUT_FILENO), 388 chk_non_negative(dup2(filter->pipe_fh[1], STDOUT_FILENO),
390 "Unable to use pipe as STDOUT"); 389 "Unable to use pipe as STDOUT");
391 close(filter->pipe_fh[1]); 390 close(filter->pipe_fh[1]);
392 return 0; 391 return 0;
393} 392}
394 393
395int cgit_close_filter(struct cgit_filter *filter) 394int cgit_close_filter(struct cgit_filter *filter)
396{ 395{
397 chk_non_negative(dup2(filter->old_stdout, STDOUT_FILENO), 396 chk_non_negative(dup2(filter->old_stdout, STDOUT_FILENO),
398 "Unable to restore STDOUT"); 397 "Unable to restore STDOUT");
399 close(filter->old_stdout); 398 close(filter->old_stdout);
400 if (filter->pid < 0) 399 if (filter->pid < 0)
401 return 0; 400 return 0;
402 waitpid(filter->pid, &filter->exitstatus, 0); 401 waitpid(filter->pid, &filter->exitstatus, 0);
403 if (WIFEXITED(filter->exitstatus) && !WEXITSTATUS(filter->exitstatus)) 402 if (WIFEXITED(filter->exitstatus) && !WEXITSTATUS(filter->exitstatus))
404 return 0; 403 return 0;
405 die("Subprocess %s exited abnormally", filter->cmd); 404 die("Subprocess %s exited abnormally", filter->cmd);
406} 405}
407 406
408/* Read the content of the specified file into a newly allocated buffer, 407/* Read the content of the specified file into a newly allocated buffer,
409 * zeroterminate the buffer and return 0 on success, errno otherwise. 408 * zeroterminate the buffer and return 0 on success, errno otherwise.
410 */ 409 */
411int readfile(const char *path, char **buf, size_t *size) 410int readfile(const char *path, char **buf, size_t *size)
412{ 411{
413 int fd, e; 412 int fd, e;
414 struct stat st; 413 struct stat st;
415 414
416 fd = open(path, O_RDONLY); 415 fd = open(path, O_RDONLY);
417 if (fd == -1) 416 if (fd == -1)
418 return errno; 417 return errno;
419 if (fstat(fd, &st)) { 418 if (fstat(fd, &st)) {
420 e = errno; 419 e = errno;
421 close(fd); 420 close(fd);
422 return e; 421 return e;
423 } 422 }
424 if (!S_ISREG(st.st_mode)) { 423 if (!S_ISREG(st.st_mode)) {
425 close(fd); 424 close(fd);
426 return EISDIR; 425 return EISDIR;
427 } 426 }
428 *buf = xmalloc(st.st_size + 1); 427 *buf = xmalloc(st.st_size + 1);
429 *size = read_in_full(fd, *buf, st.st_size); 428 *size = read_in_full(fd, *buf, st.st_size);
430 e = errno; 429 e = errno;
431 (*buf)[*size] = '\0'; 430 (*buf)[*size] = '\0';
432 close(fd); 431 close(fd);
433 return (*size == st.st_size ? 0 : e); 432 return (*size == st.st_size ? 0 : e);
434} 433}
diff --git a/ui-commit.c b/ui-commit.c
index 41313b9..a11bc5f 100644
--- a/ui-commit.c
+++ b/ui-commit.c
@@ -1,127 +1,131 @@
1/* ui-commit.c: generate commit view 1/* ui-commit.c: generate commit view
2 * 2 *
3 * Copyright (C) 2006 Lars Hjemli 3 * Copyright (C) 2006 Lars Hjemli
4 * 4 *
5 * Licensed under GNU General Public License v2 5 * Licensed under GNU General Public License v2
6 * (see COPYING for full license text) 6 * (see COPYING for full license text)
7 */ 7 */
8 8
9#include "cgit.h" 9#include "cgit.h"
10#include "html.h" 10#include "html.h"
11#include "ui-shared.h" 11#include "ui-shared.h"
12#include "ui-diff.h" 12#include "ui-diff.h"
13#include "ui-log.h" 13#include "ui-log.h"
14 14
15void cgit_print_commit(char *hex) 15void cgit_print_commit(char *hex, const char *prefix)
16{ 16{
17 struct commit *commit, *parent; 17 struct commit *commit, *parent;
18 struct commitinfo *info, *parent_info; 18 struct commitinfo *info, *parent_info;
19 struct commit_list *p; 19 struct commit_list *p;
20 unsigned char sha1[20]; 20 unsigned char sha1[20];
21 char *tmp, *tmp2; 21 char *tmp, *tmp2;
22 int parents = 0; 22 int parents = 0;
23 23
24 if (!hex) 24 if (!hex)
25 hex = ctx.qry.head; 25 hex = ctx.qry.head;
26 26
27 if (get_sha1(hex, sha1)) { 27 if (get_sha1(hex, sha1)) {
28 cgit_print_error(fmt("Bad object id: %s", hex)); 28 cgit_print_error(fmt("Bad object id: %s", hex));
29 return; 29 return;
30 } 30 }
31 commit = lookup_commit_reference(sha1); 31 commit = lookup_commit_reference(sha1);
32 if (!commit) { 32 if (!commit) {
33 cgit_print_error(fmt("Bad commit reference: %s", hex)); 33 cgit_print_error(fmt("Bad commit reference: %s", hex));
34 return; 34 return;
35 } 35 }
36 info = cgit_parse_commit(commit); 36 info = cgit_parse_commit(commit);
37 37
38 load_ref_decorations(DECORATE_FULL_REFS); 38 load_ref_decorations(DECORATE_FULL_REFS);
39 39
40 html("<table summary='commit info' class='commit-info'>\n"); 40 html("<table summary='commit info' class='commit-info'>\n");
41 html("<tr><th>author</th><td>"); 41 html("<tr><th>author</th><td>");
42 html_txt(info->author); 42 html_txt(info->author);
43 if (!ctx.cfg.noplainemail) { 43 if (!ctx.cfg.noplainemail) {
44 html(" "); 44 html(" ");
45 html_txt(info->author_email); 45 html_txt(info->author_email);
46 } 46 }
47 html("</td><td class='right'>"); 47 html("</td><td class='right'>");
48 cgit_print_date(info->author_date, FMT_LONGDATE, ctx.cfg.local_time); 48 cgit_print_date(info->author_date, FMT_LONGDATE, ctx.cfg.local_time);
49 html("</td></tr>\n"); 49 html("</td></tr>\n");
50 html("<tr><th>committer</th><td>"); 50 html("<tr><th>committer</th><td>");
51 html_txt(info->committer); 51 html_txt(info->committer);
52 if (!ctx.cfg.noplainemail) { 52 if (!ctx.cfg.noplainemail) {
53 html(" "); 53 html(" ");
54 html_txt(info->committer_email); 54 html_txt(info->committer_email);
55 } 55 }
56 html("</td><td class='right'>"); 56 html("</td><td class='right'>");
57 cgit_print_date(info->committer_date, FMT_LONGDATE, ctx.cfg.local_time); 57 cgit_print_date(info->committer_date, FMT_LONGDATE, ctx.cfg.local_time);
58 html("</td></tr>\n"); 58 html("</td></tr>\n");
59 html("<tr><th>commit</th><td colspan='2' class='sha1'>"); 59 html("<tr><th>commit</th><td colspan='2' class='sha1'>");
60 tmp = sha1_to_hex(commit->object.sha1); 60 tmp = sha1_to_hex(commit->object.sha1);
61 cgit_commit_link(tmp, NULL, NULL, ctx.qry.head, tmp, 0); 61 cgit_commit_link(tmp, NULL, NULL, ctx.qry.head, tmp, prefix, 0);
62 html(" ("); 62 html(" (");
63 cgit_patch_link("patch", NULL, NULL, NULL, tmp); 63 cgit_patch_link("patch", NULL, NULL, NULL, tmp, prefix);
64 html(") ("); 64 html(") (");
65 if ((ctx.qry.ssdiff && !ctx.cfg.ssdiff) || (!ctx.qry.ssdiff && ctx.cfg.ssdiff)) 65 if ((ctx.qry.ssdiff && !ctx.cfg.ssdiff) || (!ctx.qry.ssdiff && ctx.cfg.ssdiff))
66 cgit_commit_link("unidiff", NULL, NULL, ctx.qry.head, tmp, 1); 66 cgit_commit_link("unidiff", NULL, NULL, ctx.qry.head, tmp, prefix, 1);
67 else 67 else
68 cgit_commit_link("side-by-side diff", NULL, NULL, ctx.qry.head, tmp, 1); 68 cgit_commit_link("side-by-side diff", NULL, NULL, ctx.qry.head, tmp, prefix, 1);
69 html(")</td></tr>\n"); 69 html(")</td></tr>\n");
70 html("<tr><th>tree</th><td colspan='2' class='sha1'>"); 70 html("<tr><th>tree</th><td colspan='2' class='sha1'>");
71 tmp = xstrdup(hex); 71 tmp = xstrdup(hex);
72 cgit_tree_link(sha1_to_hex(commit->tree->object.sha1), NULL, NULL, 72 cgit_tree_link(sha1_to_hex(commit->tree->object.sha1), NULL, NULL,
73 ctx.qry.head, tmp, NULL); 73 ctx.qry.head, tmp, NULL);
74 if (prefix) {
75 html(" /");
76 cgit_tree_link(prefix, NULL, NULL, ctx.qry.head, tmp, prefix);
77 }
74 html("</td></tr>\n"); 78 html("</td></tr>\n");
75 for (p = commit->parents; p ; p = p->next) { 79 for (p = commit->parents; p ; p = p->next) {
76 parent = lookup_commit_reference(p->item->object.sha1); 80 parent = lookup_commit_reference(p->item->object.sha1);
77 if (!parent) { 81 if (!parent) {
78 html("<tr><td colspan='3'>"); 82 html("<tr><td colspan='3'>");
79 cgit_print_error("Error reading parent commit"); 83 cgit_print_error("Error reading parent commit");
80 html("</td></tr>"); 84 html("</td></tr>");
81 continue; 85 continue;
82 } 86 }
83 html("<tr><th>parent</th>" 87 html("<tr><th>parent</th>"
84 "<td colspan='2' class='sha1'>"); 88 "<td colspan='2' class='sha1'>");
85 tmp = tmp2 = sha1_to_hex(p->item->object.sha1); 89 tmp = tmp2 = sha1_to_hex(p->item->object.sha1);
86 if (ctx.repo->enable_subject_links) { 90 if (ctx.repo->enable_subject_links) {
87 parent_info = cgit_parse_commit(parent); 91 parent_info = cgit_parse_commit(parent);
88 tmp2 = parent_info->subject; 92 tmp2 = parent_info->subject;
89 } 93 }
90 cgit_commit_link(tmp2, NULL, NULL, ctx.qry.head, tmp, 0); 94 cgit_commit_link(tmp2, NULL, NULL, ctx.qry.head, tmp, prefix, 0);
91 html(" ("); 95 html(" (");
92 cgit_diff_link("diff", NULL, NULL, ctx.qry.head, hex, 96 cgit_diff_link("diff", NULL, NULL, ctx.qry.head, hex,
93 sha1_to_hex(p->item->object.sha1), NULL, 0); 97 sha1_to_hex(p->item->object.sha1), prefix, 0);
94 html(")</td></tr>"); 98 html(")</td></tr>");
95 parents++; 99 parents++;
96 } 100 }
97 if (ctx.repo->snapshots) { 101 if (ctx.repo->snapshots) {
98 html("<tr><th>download</th><td colspan='2' class='sha1'>"); 102 html("<tr><th>download</th><td colspan='2' class='sha1'>");
99 cgit_print_snapshot_links(ctx.qry.repo, ctx.qry.head, 103 cgit_print_snapshot_links(ctx.qry.repo, ctx.qry.head,
100 hex, ctx.repo->snapshots); 104 hex, ctx.repo->snapshots);
101 html("</td></tr>"); 105 html("</td></tr>");
102 } 106 }
103 html("</table>\n"); 107 html("</table>\n");
104 html("<div class='commit-subject'>"); 108 html("<div class='commit-subject'>");
105 if (ctx.repo->commit_filter) 109 if (ctx.repo->commit_filter)
106 cgit_open_filter(ctx.repo->commit_filter); 110 cgit_open_filter(ctx.repo->commit_filter);
107 html_txt(info->subject); 111 html_txt(info->subject);
108 if (ctx.repo->commit_filter) 112 if (ctx.repo->commit_filter)
109 cgit_close_filter(ctx.repo->commit_filter); 113 cgit_close_filter(ctx.repo->commit_filter);
110 show_commit_decorations(commit); 114 show_commit_decorations(commit);
111 html("</div>"); 115 html("</div>");
112 html("<div class='commit-msg'>"); 116 html("<div class='commit-msg'>");
113 if (ctx.repo->commit_filter) 117 if (ctx.repo->commit_filter)
114 cgit_open_filter(ctx.repo->commit_filter); 118 cgit_open_filter(ctx.repo->commit_filter);
115 html_txt(info->msg); 119 html_txt(info->msg);
116 if (ctx.repo->commit_filter) 120 if (ctx.repo->commit_filter)
117 cgit_close_filter(ctx.repo->commit_filter); 121 cgit_close_filter(ctx.repo->commit_filter);
118 html("</div>"); 122 html("</div>");
119 if (parents < 3) { 123 if (parents < 3) {
120 if (parents) 124 if (parents)
121 tmp = sha1_to_hex(commit->parents->item->object.sha1); 125 tmp = sha1_to_hex(commit->parents->item->object.sha1);
122 else 126 else
123 tmp = NULL; 127 tmp = NULL;
124 cgit_print_diff(ctx.qry.sha1, tmp, NULL); 128 cgit_print_diff(ctx.qry.sha1, tmp, prefix);
125 } 129 }
126 cgit_free_commitinfo(info); 130 cgit_free_commitinfo(info);
127} 131}
diff --git a/ui-commit.h b/ui-commit.h
index 40bcb31..8198b4b 100644
--- a/ui-commit.h
+++ b/ui-commit.h
@@ -1,6 +1,6 @@
1#ifndef UI_COMMIT_H 1#ifndef UI_COMMIT_H
2#define UI_COMMIT_H 2#define UI_COMMIT_H
3 3
4extern void cgit_print_commit(char *hex); 4extern void cgit_print_commit(char *hex, const char *prefix);
5 5
6#endif /* UI_COMMIT_H */ 6#endif /* UI_COMMIT_H */
diff --git a/ui-diff.c b/ui-diff.c
index a92a768..fb836df 100644
--- a/ui-diff.c
+++ b/ui-diff.c
@@ -1,353 +1,355 @@
1/* ui-diff.c: show diff between two blobs 1/* ui-diff.c: show diff between two blobs
2 * 2 *
3 * Copyright (C) 2006 Lars Hjemli 3 * Copyright (C) 2006 Lars Hjemli
4 * 4 *
5 * Licensed under GNU General Public License v2 5 * Licensed under GNU General Public License v2
6 * (see COPYING for full license text) 6 * (see COPYING for full license text)
7 */ 7 */
8 8
9#include "cgit.h" 9#include "cgit.h"
10#include "html.h" 10#include "html.h"
11#include "ui-shared.h" 11#include "ui-shared.h"
12#include "ui-ssdiff.h" 12#include "ui-ssdiff.h"
13 13
14unsigned char old_rev_sha1[20]; 14unsigned char old_rev_sha1[20];
15unsigned char new_rev_sha1[20]; 15unsigned char new_rev_sha1[20];
16 16
17static int files, slots; 17static int files, slots;
18static int total_adds, total_rems, max_changes; 18static int total_adds, total_rems, max_changes;
19static int lines_added, lines_removed; 19static int lines_added, lines_removed;
20 20
21static struct fileinfo { 21static struct fileinfo {
22 char status; 22 char status;
23 unsigned char old_sha1[20]; 23 unsigned char old_sha1[20];
24 unsigned char new_sha1[20]; 24 unsigned char new_sha1[20];
25 unsigned short old_mode; 25 unsigned short old_mode;
26 unsigned short new_mode; 26 unsigned short new_mode;
27 char *old_path; 27 char *old_path;
28 char *new_path; 28 char *new_path;
29 unsigned int added; 29 unsigned int added;
30 unsigned int removed; 30 unsigned int removed;
31 unsigned long old_size; 31 unsigned long old_size;
32 unsigned long new_size; 32 unsigned long new_size;
33 int binary:1; 33 int binary:1;
34} *items; 34} *items;
35 35
36static int use_ssdiff = 0; 36static int use_ssdiff = 0;
37 37
38static void print_fileinfo(struct fileinfo *info) 38static void print_fileinfo(struct fileinfo *info)
39{ 39{
40 char *class; 40 char *class;
41 41
42 switch (info->status) { 42 switch (info->status) {
43 case DIFF_STATUS_ADDED: 43 case DIFF_STATUS_ADDED:
44 class = "add"; 44 class = "add";
45 break; 45 break;
46 case DIFF_STATUS_COPIED: 46 case DIFF_STATUS_COPIED:
47 class = "cpy"; 47 class = "cpy";
48 break; 48 break;
49 case DIFF_STATUS_DELETED: 49 case DIFF_STATUS_DELETED:
50 class = "del"; 50 class = "del";
51 break; 51 break;
52 case DIFF_STATUS_MODIFIED: 52 case DIFF_STATUS_MODIFIED:
53 class = "upd"; 53 class = "upd";
54 break; 54 break;
55 case DIFF_STATUS_RENAMED: 55 case DIFF_STATUS_RENAMED:
56 class = "mov"; 56 class = "mov";
57 break; 57 break;
58 case DIFF_STATUS_TYPE_CHANGED: 58 case DIFF_STATUS_TYPE_CHANGED:
59 class = "typ"; 59 class = "typ";
60 break; 60 break;
61 case DIFF_STATUS_UNKNOWN: 61 case DIFF_STATUS_UNKNOWN:
62 class = "unk"; 62 class = "unk";
63 break; 63 break;
64 case DIFF_STATUS_UNMERGED: 64 case DIFF_STATUS_UNMERGED:
65 class = "stg"; 65 class = "stg";
66 break; 66 break;
67 default: 67 default:
68 die("bug: unhandled diff status %c", info->status); 68 die("bug: unhandled diff status %c", info->status);
69 } 69 }
70 70
71 html("<tr>"); 71 html("<tr>");
72 htmlf("<td class='mode'>"); 72 htmlf("<td class='mode'>");
73 if (is_null_sha1(info->new_sha1)) { 73 if (is_null_sha1(info->new_sha1)) {
74 cgit_print_filemode(info->old_mode); 74 cgit_print_filemode(info->old_mode);
75 } else { 75 } else {
76 cgit_print_filemode(info->new_mode); 76 cgit_print_filemode(info->new_mode);
77 } 77 }
78 78
79 if (info->old_mode != info->new_mode && 79 if (info->old_mode != info->new_mode &&
80 !is_null_sha1(info->old_sha1) && 80 !is_null_sha1(info->old_sha1) &&
81 !is_null_sha1(info->new_sha1)) { 81 !is_null_sha1(info->new_sha1)) {
82 html("<span class='modechange'>["); 82 html("<span class='modechange'>[");
83 cgit_print_filemode(info->old_mode); 83 cgit_print_filemode(info->old_mode);
84 html("]</span>"); 84 html("]</span>");
85 } 85 }
86 htmlf("</td><td class='%s'>", class); 86 htmlf("</td><td class='%s'>", class);
87 cgit_diff_link(info->new_path, NULL, NULL, ctx.qry.head, ctx.qry.sha1, 87 cgit_diff_link(info->new_path, NULL, NULL, ctx.qry.head, ctx.qry.sha1,
88 ctx.qry.sha2, info->new_path, 0); 88 ctx.qry.sha2, info->new_path, 0);
89 if (info->status == DIFF_STATUS_COPIED || info->status == DIFF_STATUS_RENAMED) 89 if (info->status == DIFF_STATUS_COPIED || info->status == DIFF_STATUS_RENAMED)
90 htmlf(" (%s from %s)", 90 htmlf(" (%s from %s)",
91 info->status == DIFF_STATUS_COPIED ? "copied" : "renamed", 91 info->status == DIFF_STATUS_COPIED ? "copied" : "renamed",
92 info->old_path); 92 info->old_path);
93 html("</td><td class='right'>"); 93 html("</td><td class='right'>");
94 if (info->binary) { 94 if (info->binary) {
95 htmlf("bin</td><td class='graph'>%d -> %d bytes", 95 htmlf("bin</td><td class='graph'>%d -> %d bytes",
96 info->old_size, info->new_size); 96 info->old_size, info->new_size);
97 return; 97 return;
98 } 98 }
99 htmlf("%d", info->added + info->removed); 99 htmlf("%d", info->added + info->removed);
100 html("</td><td class='graph'>"); 100 html("</td><td class='graph'>");
101 htmlf("<table summary='file diffstat' width='%d%%'><tr>", (max_changes > 100 ? 100 : max_changes)); 101 htmlf("<table summary='file diffstat' width='%d%%'><tr>", (max_changes > 100 ? 100 : max_changes));
102 htmlf("<td class='add' style='width: %.1f%%;'/>", 102 htmlf("<td class='add' style='width: %.1f%%;'/>",
103 info->added * 100.0 / max_changes); 103 info->added * 100.0 / max_changes);
104 htmlf("<td class='rem' style='width: %.1f%%;'/>", 104 htmlf("<td class='rem' style='width: %.1f%%;'/>",
105 info->removed * 100.0 / max_changes); 105 info->removed * 100.0 / max_changes);
106 htmlf("<td class='none' style='width: %.1f%%;'/>", 106 htmlf("<td class='none' style='width: %.1f%%;'/>",
107 (max_changes - info->removed - info->added) * 100.0 / max_changes); 107 (max_changes - info->removed - info->added) * 100.0 / max_changes);
108 html("</tr></table></td></tr>\n"); 108 html("</tr></table></td></tr>\n");
109} 109}
110 110
111static void count_diff_lines(char *line, int len) 111static void count_diff_lines(char *line, int len)
112{ 112{
113 if (line && (len > 0)) { 113 if (line && (len > 0)) {
114 if (line[0] == '+') 114 if (line[0] == '+')
115 lines_added++; 115 lines_added++;
116 else if (line[0] == '-') 116 else if (line[0] == '-')
117 lines_removed++; 117 lines_removed++;
118 } 118 }
119} 119}
120 120
121static void inspect_filepair(struct diff_filepair *pair) 121static void inspect_filepair(struct diff_filepair *pair)
122{ 122{
123 int binary = 0; 123 int binary = 0;
124 unsigned long old_size = 0; 124 unsigned long old_size = 0;
125 unsigned long new_size = 0; 125 unsigned long new_size = 0;
126 files++; 126 files++;
127 lines_added = 0; 127 lines_added = 0;
128 lines_removed = 0; 128 lines_removed = 0;
129 cgit_diff_files(pair->one->sha1, pair->two->sha1, &old_size, &new_size, 129 cgit_diff_files(pair->one->sha1, pair->two->sha1, &old_size, &new_size,
130 &binary, count_diff_lines); 130 &binary, count_diff_lines);
131 if (files >= slots) { 131 if (files >= slots) {
132 if (slots == 0) 132 if (slots == 0)
133 slots = 4; 133 slots = 4;
134 else 134 else
135 slots = slots * 2; 135 slots = slots * 2;
136 items = xrealloc(items, slots * sizeof(struct fileinfo)); 136 items = xrealloc(items, slots * sizeof(struct fileinfo));
137 } 137 }
138 items[files-1].status = pair->status; 138 items[files-1].status = pair->status;
139 hashcpy(items[files-1].old_sha1, pair->one->sha1); 139 hashcpy(items[files-1].old_sha1, pair->one->sha1);
140 hashcpy(items[files-1].new_sha1, pair->two->sha1); 140 hashcpy(items[files-1].new_sha1, pair->two->sha1);
141 items[files-1].old_mode = pair->one->mode; 141 items[files-1].old_mode = pair->one->mode;
142 items[files-1].new_mode = pair->two->mode; 142 items[files-1].new_mode = pair->two->mode;
143 items[files-1].old_path = xstrdup(pair->one->path); 143 items[files-1].old_path = xstrdup(pair->one->path);
144 items[files-1].new_path = xstrdup(pair->two->path); 144 items[files-1].new_path = xstrdup(pair->two->path);
145 items[files-1].added = lines_added; 145 items[files-1].added = lines_added;
146 items[files-1].removed = lines_removed; 146 items[files-1].removed = lines_removed;
147 items[files-1].old_size = old_size; 147 items[files-1].old_size = old_size;
148 items[files-1].new_size = new_size; 148 items[files-1].new_size = new_size;
149 items[files-1].binary = binary; 149 items[files-1].binary = binary;
150 if (lines_added + lines_removed > max_changes) 150 if (lines_added + lines_removed > max_changes)
151 max_changes = lines_added + lines_removed; 151 max_changes = lines_added + lines_removed;
152 total_adds += lines_added; 152 total_adds += lines_added;
153 total_rems += lines_removed; 153 total_rems += lines_removed;
154} 154}
155 155
156void cgit_print_diffstat(const unsigned char *old_sha1, 156void cgit_print_diffstat(const unsigned char *old_sha1,
157 const unsigned char *new_sha1) 157 const unsigned char *new_sha1, const char *prefix)
158{ 158{
159 int i; 159 int i;
160 160
161 html("<div class='diffstat-header'>"); 161 html("<div class='diffstat-header'>");
162 cgit_diff_link("Diffstat", NULL, NULL, ctx.qry.head, ctx.qry.sha1, 162 cgit_diff_link("Diffstat", NULL, NULL, ctx.qry.head, ctx.qry.sha1,
163 ctx.qry.sha2, NULL, 0); 163 ctx.qry.sha2, NULL, 0);
164 if (prefix)
165 htmlf(" (limited to '%s')", prefix);
164 html("</div>"); 166 html("</div>");
165 html("<table summary='diffstat' class='diffstat'>"); 167 html("<table summary='diffstat' class='diffstat'>");
166 max_changes = 0; 168 max_changes = 0;
167 cgit_diff_tree(old_sha1, new_sha1, inspect_filepair, NULL); 169 cgit_diff_tree(old_sha1, new_sha1, inspect_filepair, prefix);
168 for(i = 0; i<files; i++) 170 for(i = 0; i<files; i++)
169 print_fileinfo(&items[i]); 171 print_fileinfo(&items[i]);
170 html("</table>"); 172 html("</table>");
171 html("<div class='diffstat-summary'>"); 173 html("<div class='diffstat-summary'>");
172 htmlf("%d files changed, %d insertions, %d deletions", 174 htmlf("%d files changed, %d insertions, %d deletions",
173 files, total_adds, total_rems); 175 files, total_adds, total_rems);
174 html("</div>"); 176 html("</div>");
175} 177}
176 178
177 179
178/* 180/*
179 * print a single line returned from xdiff 181 * print a single line returned from xdiff
180 */ 182 */
181static void print_line(char *line, int len) 183static void print_line(char *line, int len)
182{ 184{
183 char *class = "ctx"; 185 char *class = "ctx";
184 char c = line[len-1]; 186 char c = line[len-1];
185 187
186 if (line[0] == '+') 188 if (line[0] == '+')
187 class = "add"; 189 class = "add";
188 else if (line[0] == '-') 190 else if (line[0] == '-')
189 class = "del"; 191 class = "del";
190 else if (line[0] == '@') 192 else if (line[0] == '@')
191 class = "hunk"; 193 class = "hunk";
192 194
193 htmlf("<div class='%s'>", class); 195 htmlf("<div class='%s'>", class);
194 line[len-1] = '\0'; 196 line[len-1] = '\0';
195 html_txt(line); 197 html_txt(line);
196 html("</div>"); 198 html("</div>");
197 line[len-1] = c; 199 line[len-1] = c;
198} 200}
199 201
200static void header(unsigned char *sha1, char *path1, int mode1, 202static void header(unsigned char *sha1, char *path1, int mode1,
201 unsigned char *sha2, char *path2, int mode2) 203 unsigned char *sha2, char *path2, int mode2)
202{ 204{
203 char *abbrev1, *abbrev2; 205 char *abbrev1, *abbrev2;
204 int subproject; 206 int subproject;
205 207
206 subproject = (S_ISGITLINK(mode1) || S_ISGITLINK(mode2)); 208 subproject = (S_ISGITLINK(mode1) || S_ISGITLINK(mode2));
207 html("<div class='head'>"); 209 html("<div class='head'>");
208 html("diff --git a/"); 210 html("diff --git a/");
209 html_txt(path1); 211 html_txt(path1);
210 html(" b/"); 212 html(" b/");
211 html_txt(path2); 213 html_txt(path2);
212 214
213 if (is_null_sha1(sha1)) 215 if (is_null_sha1(sha1))
214 path1 = "dev/null"; 216 path1 = "dev/null";
215 if (is_null_sha1(sha2)) 217 if (is_null_sha1(sha2))
216 path2 = "dev/null"; 218 path2 = "dev/null";
217 219
218 if (mode1 == 0) 220 if (mode1 == 0)
219 htmlf("<br/>new file mode %.6o", mode2); 221 htmlf("<br/>new file mode %.6o", mode2);
220 222
221 if (mode2 == 0) 223 if (mode2 == 0)
222 htmlf("<br/>deleted file mode %.6o", mode1); 224 htmlf("<br/>deleted file mode %.6o", mode1);
223 225
224 if (!subproject) { 226 if (!subproject) {
225 abbrev1 = xstrdup(find_unique_abbrev(sha1, DEFAULT_ABBREV)); 227 abbrev1 = xstrdup(find_unique_abbrev(sha1, DEFAULT_ABBREV));
226 abbrev2 = xstrdup(find_unique_abbrev(sha2, DEFAULT_ABBREV)); 228 abbrev2 = xstrdup(find_unique_abbrev(sha2, DEFAULT_ABBREV));
227 htmlf("<br/>index %s..%s", abbrev1, abbrev2); 229 htmlf("<br/>index %s..%s", abbrev1, abbrev2);
228 free(abbrev1); 230 free(abbrev1);
229 free(abbrev2); 231 free(abbrev2);
230 if (mode1 != 0 && mode2 != 0) { 232 if (mode1 != 0 && mode2 != 0) {
231 htmlf(" %.6o", mode1); 233 htmlf(" %.6o", mode1);
232 if (mode2 != mode1) 234 if (mode2 != mode1)
233 htmlf("..%.6o", mode2); 235 htmlf("..%.6o", mode2);
234 } 236 }
235 html("<br/>--- a/"); 237 html("<br/>--- a/");
236 if (mode1 != 0) 238 if (mode1 != 0)
237 cgit_tree_link(path1, NULL, NULL, ctx.qry.head, 239 cgit_tree_link(path1, NULL, NULL, ctx.qry.head,
238 sha1_to_hex(old_rev_sha1), path1); 240 sha1_to_hex(old_rev_sha1), path1);
239 else 241 else
240 html_txt(path1); 242 html_txt(path1);
241 html("<br/>+++ b/"); 243 html("<br/>+++ b/");
242 if (mode2 != 0) 244 if (mode2 != 0)
243 cgit_tree_link(path2, NULL, NULL, ctx.qry.head, 245 cgit_tree_link(path2, NULL, NULL, ctx.qry.head,
244 sha1_to_hex(new_rev_sha1), path2); 246 sha1_to_hex(new_rev_sha1), path2);
245 else 247 else
246 html_txt(path2); 248 html_txt(path2);
247 } 249 }
248 html("</div>"); 250 html("</div>");
249} 251}
250 252
251static void print_ssdiff_link() 253static void print_ssdiff_link()
252{ 254{
253 if (!strcmp(ctx.qry.page, "diff")) { 255 if (!strcmp(ctx.qry.page, "diff")) {
254 if (use_ssdiff) 256 if (use_ssdiff)
255 cgit_diff_link("Unidiff", NULL, NULL, ctx.qry.head, 257 cgit_diff_link("Unidiff", NULL, NULL, ctx.qry.head,
256 ctx.qry.sha1, ctx.qry.sha2, ctx.qry.path, 1); 258 ctx.qry.sha1, ctx.qry.sha2, ctx.qry.path, 1);
257 else 259 else
258 cgit_diff_link("Side-by-side diff", NULL, NULL, 260 cgit_diff_link("Side-by-side diff", NULL, NULL,
259 ctx.qry.head, ctx.qry.sha1, 261 ctx.qry.head, ctx.qry.sha1,
260 ctx.qry.sha2, ctx.qry.path, 1); 262 ctx.qry.sha2, ctx.qry.path, 1);
261 } 263 }
262} 264}
263 265
264static void filepair_cb(struct diff_filepair *pair) 266static void filepair_cb(struct diff_filepair *pair)
265{ 267{
266 unsigned long old_size = 0; 268 unsigned long old_size = 0;
267 unsigned long new_size = 0; 269 unsigned long new_size = 0;
268 int binary = 0; 270 int binary = 0;
269 linediff_fn print_line_fn = print_line; 271 linediff_fn print_line_fn = print_line;
270 272
271 if (use_ssdiff) { 273 if (use_ssdiff) {
272 cgit_ssdiff_header_begin(); 274 cgit_ssdiff_header_begin();
273 print_line_fn = cgit_ssdiff_line_cb; 275 print_line_fn = cgit_ssdiff_line_cb;
274 } 276 }
275 header(pair->one->sha1, pair->one->path, pair->one->mode, 277 header(pair->one->sha1, pair->one->path, pair->one->mode,
276 pair->two->sha1, pair->two->path, pair->two->mode); 278 pair->two->sha1, pair->two->path, pair->two->mode);
277 if (use_ssdiff) 279 if (use_ssdiff)
278 cgit_ssdiff_header_end(); 280 cgit_ssdiff_header_end();
279 if (S_ISGITLINK(pair->one->mode) || S_ISGITLINK(pair->two->mode)) { 281 if (S_ISGITLINK(pair->one->mode) || S_ISGITLINK(pair->two->mode)) {
280 if (S_ISGITLINK(pair->one->mode)) 282 if (S_ISGITLINK(pair->one->mode))
281 print_line_fn(fmt("-Subproject %s", sha1_to_hex(pair->one->sha1)), 52); 283 print_line_fn(fmt("-Subproject %s", sha1_to_hex(pair->one->sha1)), 52);
282 if (S_ISGITLINK(pair->two->mode)) 284 if (S_ISGITLINK(pair->two->mode))
283 print_line_fn(fmt("+Subproject %s", sha1_to_hex(pair->two->sha1)), 52); 285 print_line_fn(fmt("+Subproject %s", sha1_to_hex(pair->two->sha1)), 52);
284 if (use_ssdiff) 286 if (use_ssdiff)
285 cgit_ssdiff_footer(); 287 cgit_ssdiff_footer();
286 return; 288 return;
287 } 289 }
288 if (cgit_diff_files(pair->one->sha1, pair->two->sha1, &old_size, 290 if (cgit_diff_files(pair->one->sha1, pair->two->sha1, &old_size,
289 &new_size, &binary, print_line_fn)) 291 &new_size, &binary, print_line_fn))
290 cgit_print_error("Error running diff"); 292 cgit_print_error("Error running diff");
291 if (binary) { 293 if (binary) {
292 if (use_ssdiff) 294 if (use_ssdiff)
293 html("<tr><td colspan='4'>Binary files differ</td></tr>"); 295 html("<tr><td colspan='4'>Binary files differ</td></tr>");
294 else 296 else
295 html("Binary files differ"); 297 html("Binary files differ");
296 } 298 }
297 if (use_ssdiff) 299 if (use_ssdiff)
298 cgit_ssdiff_footer(); 300 cgit_ssdiff_footer();
299} 301}
300 302
301void cgit_print_diff(const char *new_rev, const char *old_rev, const char *prefix) 303void cgit_print_diff(const char *new_rev, const char *old_rev, const char *prefix)
302{ 304{
303 enum object_type type; 305 enum object_type type;
304 unsigned long size; 306 unsigned long size;
305 struct commit *commit, *commit2; 307 struct commit *commit, *commit2;
306 308
307 if (!new_rev) 309 if (!new_rev)
308 new_rev = ctx.qry.head; 310 new_rev = ctx.qry.head;
309 get_sha1(new_rev, new_rev_sha1); 311 get_sha1(new_rev, new_rev_sha1);
310 type = sha1_object_info(new_rev_sha1, &size); 312 type = sha1_object_info(new_rev_sha1, &size);
311 if (type == OBJ_BAD) { 313 if (type == OBJ_BAD) {
312 cgit_print_error(fmt("Bad object name: %s", new_rev)); 314 cgit_print_error(fmt("Bad object name: %s", new_rev));
313 return; 315 return;
314 } 316 }
315 commit = lookup_commit_reference(new_rev_sha1); 317 commit = lookup_commit_reference(new_rev_sha1);
316 if (!commit || parse_commit(commit)) 318 if (!commit || parse_commit(commit))
317 cgit_print_error(fmt("Bad commit: %s", sha1_to_hex(new_rev_sha1))); 319 cgit_print_error(fmt("Bad commit: %s", sha1_to_hex(new_rev_sha1)));
318 320
319 if (old_rev) 321 if (old_rev)
320 get_sha1(old_rev, old_rev_sha1); 322 get_sha1(old_rev, old_rev_sha1);
321 else if (commit->parents && commit->parents->item) 323 else if (commit->parents && commit->parents->item)
322 hashcpy(old_rev_sha1, commit->parents->item->object.sha1); 324 hashcpy(old_rev_sha1, commit->parents->item->object.sha1);
323 else 325 else
324 hashclr(old_rev_sha1); 326 hashclr(old_rev_sha1);
325 327
326 if (!is_null_sha1(old_rev_sha1)) { 328 if (!is_null_sha1(old_rev_sha1)) {
327 type = sha1_object_info(old_rev_sha1, &size); 329 type = sha1_object_info(old_rev_sha1, &size);
328 if (type == OBJ_BAD) { 330 if (type == OBJ_BAD) {
329 cgit_print_error(fmt("Bad object name: %s", sha1_to_hex(old_rev_sha1))); 331 cgit_print_error(fmt("Bad object name: %s", sha1_to_hex(old_rev_sha1)));
330 return; 332 return;
331 } 333 }
332 commit2 = lookup_commit_reference(old_rev_sha1); 334 commit2 = lookup_commit_reference(old_rev_sha1);
333 if (!commit2 || parse_commit(commit2)) 335 if (!commit2 || parse_commit(commit2))
334 cgit_print_error(fmt("Bad commit: %s", sha1_to_hex(old_rev_sha1))); 336 cgit_print_error(fmt("Bad commit: %s", sha1_to_hex(old_rev_sha1)));
335 } 337 }
336 338
337 if ((ctx.qry.ssdiff && !ctx.cfg.ssdiff) || (!ctx.qry.ssdiff && ctx.cfg.ssdiff)) 339 if ((ctx.qry.ssdiff && !ctx.cfg.ssdiff) || (!ctx.qry.ssdiff && ctx.cfg.ssdiff))
338 use_ssdiff = 1; 340 use_ssdiff = 1;
339 341
340 print_ssdiff_link(); 342 print_ssdiff_link();
341 cgit_print_diffstat(old_rev_sha1, new_rev_sha1); 343 cgit_print_diffstat(old_rev_sha1, new_rev_sha1, prefix);
342 344
343 if (use_ssdiff) { 345 if (use_ssdiff) {
344 html("<table summary='ssdiff' class='ssdiff'>"); 346 html("<table summary='ssdiff' class='ssdiff'>");
345 } else { 347 } else {
346 html("<table summary='diff' class='diff'>"); 348 html("<table summary='diff' class='diff'>");
347 html("<tr><td>"); 349 html("<tr><td>");
348 } 350 }
349 cgit_diff_tree(old_rev_sha1, new_rev_sha1, filepair_cb, prefix); 351 cgit_diff_tree(old_rev_sha1, new_rev_sha1, filepair_cb, prefix);
350 if (!use_ssdiff) 352 if (!use_ssdiff)
351 html("</td></tr>"); 353 html("</td></tr>");
352 html("</table>"); 354 html("</table>");
353} 355}
diff --git a/ui-log.c b/ui-log.c
index 0947604..bfbe436 100644
--- a/ui-log.c
+++ b/ui-log.c
@@ -1,234 +1,237 @@
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 unsigned long old_size = 0; 29 unsigned long old_size = 0;
30 unsigned long new_size = 0; 30 unsigned long new_size = 0;
31 int binary = 0; 31 int binary = 0;
32 32
33 files++; 33 files++;
34 if (ctx.repo->enable_log_linecount) 34 if (ctx.repo->enable_log_linecount)
35 cgit_diff_files(pair->one->sha1, pair->two->sha1, &old_size, 35 cgit_diff_files(pair->one->sha1, pair->two->sha1, &old_size,
36 &new_size, &binary, count_lines); 36 &new_size, &binary, count_lines);
37} 37}
38 38
39void show_commit_decorations(struct commit *commit) 39void show_commit_decorations(struct commit *commit)
40{ 40{
41 struct name_decoration *deco; 41 struct name_decoration *deco;
42 static char buf[1024]; 42 static char buf[1024];
43 43
44 buf[sizeof(buf) - 1] = 0; 44 buf[sizeof(buf) - 1] = 0;
45 deco = lookup_decoration(&name_decoration, &commit->object); 45 deco = lookup_decoration(&name_decoration, &commit->object);
46 while (deco) { 46 while (deco) {
47 if (!prefixcmp(deco->name, "refs/heads/")) { 47 if (!prefixcmp(deco->name, "refs/heads/")) {
48 strncpy(buf, deco->name + 11, sizeof(buf) - 1); 48 strncpy(buf, deco->name + 11, sizeof(buf) - 1);
49 cgit_log_link(buf, NULL, "branch-deco", buf, NULL, NULL, 49 cgit_log_link(buf, NULL, "branch-deco", buf, NULL,
50 0, NULL, NULL, ctx.qry.showmsg); 50 ctx.qry.vpath, 0, NULL, NULL,
51 ctx.qry.showmsg);
51 } 52 }
52 else if (!prefixcmp(deco->name, "tag: refs/tags/")) { 53 else if (!prefixcmp(deco->name, "tag: refs/tags/")) {
53 strncpy(buf, deco->name + 15, sizeof(buf) - 1); 54 strncpy(buf, deco->name + 15, sizeof(buf) - 1);
54 cgit_tag_link(buf, NULL, "tag-deco", ctx.qry.head, buf); 55 cgit_tag_link(buf, NULL, "tag-deco", ctx.qry.head, buf);
55 } 56 }
56 else if (!prefixcmp(deco->name, "refs/tags/")) { 57 else if (!prefixcmp(deco->name, "refs/tags/")) {
57 strncpy(buf, deco->name + 10, sizeof(buf) - 1); 58 strncpy(buf, deco->name + 10, sizeof(buf) - 1);
58 cgit_tag_link(buf, NULL, "tag-deco", ctx.qry.head, buf); 59 cgit_tag_link(buf, NULL, "tag-deco", ctx.qry.head, buf);
59 } 60 }
60 else if (!prefixcmp(deco->name, "refs/remotes/")) { 61 else if (!prefixcmp(deco->name, "refs/remotes/")) {
61 strncpy(buf, deco->name + 13, sizeof(buf) - 1); 62 strncpy(buf, deco->name + 13, sizeof(buf) - 1);
62 cgit_log_link(buf, NULL, "remote-deco", NULL, 63 cgit_log_link(buf, NULL, "remote-deco", NULL,
63 sha1_to_hex(commit->object.sha1), NULL, 64 sha1_to_hex(commit->object.sha1),
64 0, NULL, NULL, ctx.qry.showmsg); 65 ctx.qry.vpath, 0, NULL, NULL,
66 ctx.qry.showmsg);
65 } 67 }
66 else { 68 else {
67 strncpy(buf, deco->name, sizeof(buf) - 1); 69 strncpy(buf, deco->name, sizeof(buf) - 1);
68 cgit_commit_link(buf, NULL, "deco", ctx.qry.head, 70 cgit_commit_link(buf, NULL, "deco", ctx.qry.head,
69 sha1_to_hex(commit->object.sha1), 0); 71 sha1_to_hex(commit->object.sha1),
72 ctx.qry.vpath, 0);
70 } 73 }
71 deco = deco->next; 74 deco = deco->next;
72 } 75 }
73} 76}
74 77
75void print_commit(struct commit *commit) 78void print_commit(struct commit *commit)
76{ 79{
77 struct commitinfo *info; 80 struct commitinfo *info;
78 char *tmp; 81 char *tmp;
79 int cols = 2; 82 int cols = 2;
80 83
81 info = cgit_parse_commit(commit); 84 info = cgit_parse_commit(commit);
82 htmlf("<tr%s><td>", 85 htmlf("<tr%s><td>",
83 ctx.qry.showmsg ? " class='logheader'" : ""); 86 ctx.qry.showmsg ? " class='logheader'" : "");
84 tmp = fmt("id=%s", sha1_to_hex(commit->object.sha1)); 87 tmp = fmt("id=%s", sha1_to_hex(commit->object.sha1));
85 tmp = cgit_pageurl(ctx.repo->url, "commit", tmp); 88 tmp = cgit_fileurl(ctx.repo->url, "commit", ctx.qry.vpath, tmp);
86 html_link_open(tmp, NULL, NULL); 89 html_link_open(tmp, NULL, NULL);
87 cgit_print_age(commit->date, TM_WEEK * 2, FMT_SHORTDATE); 90 cgit_print_age(commit->date, TM_WEEK * 2, FMT_SHORTDATE);
88 html_link_close(); 91 html_link_close();
89 htmlf("</td><td%s>", 92 htmlf("</td><td%s>",
90 ctx.qry.showmsg ? " class='logsubject'" : ""); 93 ctx.qry.showmsg ? " class='logsubject'" : "");
91 cgit_commit_link(info->subject, NULL, NULL, ctx.qry.head, 94 cgit_commit_link(info->subject, NULL, NULL, ctx.qry.head,
92 sha1_to_hex(commit->object.sha1), 0); 95 sha1_to_hex(commit->object.sha1), ctx.qry.vpath, 0);
93 show_commit_decorations(commit); 96 show_commit_decorations(commit);
94 html("</td><td>"); 97 html("</td><td>");
95 html_txt(info->author); 98 html_txt(info->author);
96 if (ctx.repo->enable_log_filecount) { 99 if (ctx.repo->enable_log_filecount) {
97 files = 0; 100 files = 0;
98 add_lines = 0; 101 add_lines = 0;
99 rem_lines = 0; 102 rem_lines = 0;
100 cgit_diff_commit(commit, inspect_files); 103 cgit_diff_commit(commit, inspect_files);
101 html("</td><td>"); 104 html("</td><td>");
102 htmlf("%d", files); 105 htmlf("%d", files);
103 if (ctx.repo->enable_log_linecount) { 106 if (ctx.repo->enable_log_linecount) {
104 html("</td><td>"); 107 html("</td><td>");
105 htmlf("-%d/+%d", rem_lines, add_lines); 108 htmlf("-%d/+%d", rem_lines, add_lines);
106 } 109 }
107 } 110 }
108 html("</td></tr>\n"); 111 html("</td></tr>\n");
109 if (ctx.qry.showmsg) { 112 if (ctx.qry.showmsg) {
110 if (ctx.repo->enable_log_filecount) { 113 if (ctx.repo->enable_log_filecount) {
111 cols++; 114 cols++;
112 if (ctx.repo->enable_log_linecount) 115 if (ctx.repo->enable_log_linecount)
113 cols++; 116 cols++;
114 } 117 }
115 htmlf("<tr class='nohover'><td/><td colspan='%d' class='logmsg'>", 118 htmlf("<tr class='nohover'><td/><td colspan='%d' class='logmsg'>",
116 cols); 119 cols);
117 html_txt(info->msg); 120 html_txt(info->msg);
118 html("</td></tr>\n"); 121 html("</td></tr>\n");
119 } 122 }
120 cgit_free_commitinfo(info); 123 cgit_free_commitinfo(info);
121} 124}
122 125
123static const char *disambiguate_ref(const char *ref) 126static const char *disambiguate_ref(const char *ref)
124{ 127{
125 unsigned char sha1[20]; 128 unsigned char sha1[20];
126 const char *longref; 129 const char *longref;
127 130
128 longref = fmt("refs/heads/%s", ref); 131 longref = fmt("refs/heads/%s", ref);
129 if (get_sha1(longref, sha1) == 0) 132 if (get_sha1(longref, sha1) == 0)
130 return longref; 133 return longref;
131 134
132 return ref; 135 return ref;
133} 136}
134 137
135void cgit_print_log(const char *tip, int ofs, int cnt, char *grep, char *pattern, 138void cgit_print_log(const char *tip, int ofs, int cnt, char *grep, char *pattern,
136 char *path, int pager) 139 char *path, int pager)
137{ 140{
138 struct rev_info rev; 141 struct rev_info rev;
139 struct commit *commit; 142 struct commit *commit;
140 const char *argv[] = {NULL, NULL, NULL, NULL, NULL}; 143 const char *argv[] = {NULL, NULL, NULL, NULL, NULL};
141 int argc = 2; 144 int argc = 2;
142 int i, columns = 3; 145 int i, columns = 3;
143 146
144 if (!tip) 147 if (!tip)
145 tip = ctx.qry.head; 148 tip = ctx.qry.head;
146 149
147 argv[1] = disambiguate_ref(tip); 150 argv[1] = disambiguate_ref(tip);
148 151
149 if (grep && pattern && (!strcmp(grep, "grep") || 152 if (grep && pattern && (!strcmp(grep, "grep") ||
150 !strcmp(grep, "author") || 153 !strcmp(grep, "author") ||
151 !strcmp(grep, "committer"))) 154 !strcmp(grep, "committer")))
152 argv[argc++] = fmt("--%s=%s", grep, pattern); 155 argv[argc++] = fmt("--%s=%s", grep, pattern);
153 156
154 if (path) { 157 if (path) {
155 argv[argc++] = "--"; 158 argv[argc++] = "--";
156 argv[argc++] = path; 159 argv[argc++] = path;
157 } 160 }
158 init_revisions(&rev, NULL); 161 init_revisions(&rev, NULL);
159 rev.abbrev = DEFAULT_ABBREV; 162 rev.abbrev = DEFAULT_ABBREV;
160 rev.commit_format = CMIT_FMT_DEFAULT; 163 rev.commit_format = CMIT_FMT_DEFAULT;
161 rev.verbose_header = 1; 164 rev.verbose_header = 1;
162 rev.show_root_diff = 0; 165 rev.show_root_diff = 0;
163 setup_revisions(argc, argv, &rev, NULL); 166 setup_revisions(argc, argv, &rev, NULL);
164 load_ref_decorations(DECORATE_FULL_REFS); 167 load_ref_decorations(DECORATE_FULL_REFS);
165 rev.show_decorations = 1; 168 rev.show_decorations = 1;
166 rev.grep_filter.regflags |= REG_ICASE; 169 rev.grep_filter.regflags |= REG_ICASE;
167 compile_grep_patterns(&rev.grep_filter); 170 compile_grep_patterns(&rev.grep_filter);
168 prepare_revision_walk(&rev); 171 prepare_revision_walk(&rev);
169 172
170 if (pager) 173 if (pager)
171 html("<table class='list nowrap'>"); 174 html("<table class='list nowrap'>");
172 175
173 html("<tr class='nohover'><th class='left'>Age</th>" 176 html("<tr class='nohover'><th class='left'>Age</th>"
174 "<th class='left'>Commit message"); 177 "<th class='left'>Commit message");
175 if (pager) { 178 if (pager) {
176 html(" ("); 179 html(" (");
177 cgit_log_link(ctx.qry.showmsg ? "Collapse" : "Expand", NULL, 180 cgit_log_link(ctx.qry.showmsg ? "Collapse" : "Expand", NULL,
178 NULL, ctx.qry.head, ctx.qry.sha1, 181 NULL, ctx.qry.head, ctx.qry.sha1,
179 ctx.qry.path, ctx.qry.ofs, ctx.qry.grep, 182 ctx.qry.vpath, ctx.qry.ofs, ctx.qry.grep,
180 ctx.qry.search, ctx.qry.showmsg ? 0 : 1); 183 ctx.qry.search, ctx.qry.showmsg ? 0 : 1);
181 html(")"); 184 html(")");
182 } 185 }
183 html("</th><th class='left'>Author</th>"); 186 html("</th><th class='left'>Author</th>");
184 if (ctx.repo->enable_log_filecount) { 187 if (ctx.repo->enable_log_filecount) {
185 html("<th class='left'>Files</th>"); 188 html("<th class='left'>Files</th>");
186 columns++; 189 columns++;
187 if (ctx.repo->enable_log_linecount) { 190 if (ctx.repo->enable_log_linecount) {
188 html("<th class='left'>Lines</th>"); 191 html("<th class='left'>Lines</th>");
189 columns++; 192 columns++;
190 } 193 }
191 } 194 }
192 html("</tr>\n"); 195 html("</tr>\n");
193 196
194 if (ofs<0) 197 if (ofs<0)
195 ofs = 0; 198 ofs = 0;
196 199
197 for (i = 0; i < ofs && (commit = get_revision(&rev)) != NULL; i++) { 200 for (i = 0; i < ofs && (commit = get_revision(&rev)) != NULL; i++) {
198 free(commit->buffer); 201 free(commit->buffer);
199 commit->buffer = NULL; 202 commit->buffer = NULL;
200 free_commit_list(commit->parents); 203 free_commit_list(commit->parents);
201 commit->parents = NULL; 204 commit->parents = NULL;
202 } 205 }
203 206
204 for (i = 0; i < cnt && (commit = get_revision(&rev)) != NULL; i++) { 207 for (i = 0; i < cnt && (commit = get_revision(&rev)) != NULL; i++) {
205 print_commit(commit); 208 print_commit(commit);
206 free(commit->buffer); 209 free(commit->buffer);
207 commit->buffer = NULL; 210 commit->buffer = NULL;
208 free_commit_list(commit->parents); 211 free_commit_list(commit->parents);
209 commit->parents = NULL; 212 commit->parents = NULL;
210 } 213 }
211 if (pager) { 214 if (pager) {
212 htmlf("</table><div class='pager'>", 215 htmlf("</table><div class='pager'>",
213 columns); 216 columns);
214 if (ofs > 0) { 217 if (ofs > 0) {
215 cgit_log_link("[prev]", NULL, NULL, ctx.qry.head, 218 cgit_log_link("[prev]", NULL, NULL, ctx.qry.head,
216 ctx.qry.sha1, ctx.qry.path, 219 ctx.qry.sha1, ctx.qry.vpath,
217 ofs - cnt, ctx.qry.grep, 220 ofs - cnt, ctx.qry.grep,
218 ctx.qry.search, ctx.qry.showmsg); 221 ctx.qry.search, ctx.qry.showmsg);
219 html("&nbsp;"); 222 html("&nbsp;");
220 } 223 }
221 if ((commit = get_revision(&rev)) != NULL) { 224 if ((commit = get_revision(&rev)) != NULL) {
222 cgit_log_link("[next]", NULL, NULL, ctx.qry.head, 225 cgit_log_link("[next]", NULL, NULL, ctx.qry.head,
223 ctx.qry.sha1, ctx.qry.path, 226 ctx.qry.sha1, ctx.qry.vpath,
224 ofs + cnt, ctx.qry.grep, 227 ofs + cnt, ctx.qry.grep,
225 ctx.qry.search, ctx.qry.showmsg); 228 ctx.qry.search, ctx.qry.showmsg);
226 } 229 }
227 html("</div>"); 230 html("</div>");
228 } else if ((commit = get_revision(&rev)) != NULL) { 231 } else if ((commit = get_revision(&rev)) != NULL) {
229 html("<tr class='nohover'><td colspan='3'>"); 232 html("<tr class='nohover'><td colspan='3'>");
230 cgit_log_link("[...]", NULL, NULL, ctx.qry.head, NULL, NULL, 0, 233 cgit_log_link("[...]", NULL, NULL, ctx.qry.head, NULL,
231 NULL, NULL, ctx.qry.showmsg); 234 ctx.qry.vpath, 0, NULL, NULL, ctx.qry.showmsg);
232 html("</td></tr>\n"); 235 html("</td></tr>\n");
233 } 236 }
234} 237}
diff --git a/ui-patch.c b/ui-patch.c
index 2a8f7a5..25dc9fe 100644
--- a/ui-patch.c
+++ b/ui-patch.c
@@ -1,129 +1,131 @@
1/* ui-patch.c: generate patch view 1/* ui-patch.c: generate patch view
2 * 2 *
3 * Copyright (C) 2007 Lars Hjemli 3 * Copyright (C) 2007 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 void print_line(char *line, int len) 13static void print_line(char *line, int len)
14{ 14{
15 char c = line[len-1]; 15 char c = line[len-1];
16 16
17 line[len-1] = '\0'; 17 line[len-1] = '\0';
18 htmlf("%s\n", line); 18 htmlf("%s\n", line);
19 line[len-1] = c; 19 line[len-1] = c;
20} 20}
21 21
22static void header(unsigned char *sha1, char *path1, int mode1, 22static void header(unsigned char *sha1, char *path1, int mode1,
23 unsigned char *sha2, char *path2, int mode2) 23 unsigned char *sha2, char *path2, int mode2)
24{ 24{
25 char *abbrev1, *abbrev2; 25 char *abbrev1, *abbrev2;
26 int subproject; 26 int subproject;
27 27
28 subproject = (S_ISGITLINK(mode1) || S_ISGITLINK(mode2)); 28 subproject = (S_ISGITLINK(mode1) || S_ISGITLINK(mode2));
29 htmlf("diff --git a/%s b/%s\n", path1, path2); 29 htmlf("diff --git a/%s b/%s\n", path1, path2);
30 30
31 if (is_null_sha1(sha1)) 31 if (is_null_sha1(sha1))
32 path1 = "dev/null"; 32 path1 = "dev/null";
33 if (is_null_sha1(sha2)) 33 if (is_null_sha1(sha2))
34 path2 = "dev/null"; 34 path2 = "dev/null";
35 35
36 if (mode1 == 0) 36 if (mode1 == 0)
37 htmlf("new file mode %.6o\n", mode2); 37 htmlf("new file mode %.6o\n", mode2);
38 38
39 if (mode2 == 0) 39 if (mode2 == 0)
40 htmlf("deleted file mode %.6o\n", mode1); 40 htmlf("deleted file mode %.6o\n", mode1);
41 41
42 if (!subproject) { 42 if (!subproject) {
43 abbrev1 = xstrdup(find_unique_abbrev(sha1, DEFAULT_ABBREV)); 43 abbrev1 = xstrdup(find_unique_abbrev(sha1, DEFAULT_ABBREV));
44 abbrev2 = xstrdup(find_unique_abbrev(sha2, DEFAULT_ABBREV)); 44 abbrev2 = xstrdup(find_unique_abbrev(sha2, DEFAULT_ABBREV));
45 htmlf("index %s..%s", abbrev1, abbrev2); 45 htmlf("index %s..%s", abbrev1, abbrev2);
46 free(abbrev1); 46 free(abbrev1);
47 free(abbrev2); 47 free(abbrev2);
48 if (mode1 != 0 && mode2 != 0) { 48 if (mode1 != 0 && mode2 != 0) {
49 htmlf(" %.6o", mode1); 49 htmlf(" %.6o", mode1);
50 if (mode2 != mode1) 50 if (mode2 != mode1)
51 htmlf("..%.6o", mode2); 51 htmlf("..%.6o", mode2);
52 } 52 }
53 htmlf("\n--- a/%s\n", path1); 53 htmlf("\n--- a/%s\n", path1);
54 htmlf("+++ b/%s\n", path2); 54 htmlf("+++ b/%s\n", path2);
55 } 55 }
56} 56}
57 57
58static void filepair_cb(struct diff_filepair *pair) 58static void filepair_cb(struct diff_filepair *pair)
59{ 59{
60 unsigned long old_size = 0; 60 unsigned long old_size = 0;
61 unsigned long new_size = 0; 61 unsigned long new_size = 0;
62 int binary = 0; 62 int binary = 0;
63 63
64 header(pair->one->sha1, pair->one->path, pair->one->mode, 64 header(pair->one->sha1, pair->one->path, pair->one->mode,
65 pair->two->sha1, pair->two->path, pair->two->mode); 65 pair->two->sha1, pair->two->path, pair->two->mode);
66 if (S_ISGITLINK(pair->one->mode) || S_ISGITLINK(pair->two->mode)) { 66 if (S_ISGITLINK(pair->one->mode) || S_ISGITLINK(pair->two->mode)) {
67 if (S_ISGITLINK(pair->one->mode)) 67 if (S_ISGITLINK(pair->one->mode))
68 print_line(fmt("-Subproject %s", sha1_to_hex(pair->one->sha1)), 52); 68 print_line(fmt("-Subproject %s", sha1_to_hex(pair->one->sha1)), 52);
69 if (S_ISGITLINK(pair->two->mode)) 69 if (S_ISGITLINK(pair->two->mode))
70 print_line(fmt("+Subproject %s", sha1_to_hex(pair->two->sha1)), 52); 70 print_line(fmt("+Subproject %s", sha1_to_hex(pair->two->sha1)), 52);
71 return; 71 return;
72 } 72 }
73 if (cgit_diff_files(pair->one->sha1, pair->two->sha1, &old_size, 73 if (cgit_diff_files(pair->one->sha1, pair->two->sha1, &old_size,
74 &new_size, &binary, print_line)) 74 &new_size, &binary, print_line))
75 html("Error running diff"); 75 html("Error running diff");
76 if (binary) 76 if (binary)
77 html("Binary files differ\n"); 77 html("Binary files differ\n");
78} 78}
79 79
80void cgit_print_patch(char *hex) 80void cgit_print_patch(char *hex, const char *prefix)
81{ 81{
82 struct commit *commit; 82 struct commit *commit;
83 struct commitinfo *info; 83 struct commitinfo *info;
84 unsigned char sha1[20], old_sha1[20]; 84 unsigned char sha1[20], old_sha1[20];
85 char *patchname; 85 char *patchname;
86 86
87 if (!hex) 87 if (!hex)
88 hex = ctx.qry.head; 88 hex = ctx.qry.head;
89 89
90 if (get_sha1(hex, sha1)) { 90 if (get_sha1(hex, sha1)) {
91 cgit_print_error(fmt("Bad object id: %s", hex)); 91 cgit_print_error(fmt("Bad object id: %s", hex));
92 return; 92 return;
93 } 93 }
94 commit = lookup_commit_reference(sha1); 94 commit = lookup_commit_reference(sha1);
95 if (!commit) { 95 if (!commit) {
96 cgit_print_error(fmt("Bad commit reference: %s", hex)); 96 cgit_print_error(fmt("Bad commit reference: %s", hex));
97 return; 97 return;
98 } 98 }
99 info = cgit_parse_commit(commit); 99 info = cgit_parse_commit(commit);
100 100
101 if (commit->parents && commit->parents->item) 101 if (commit->parents && commit->parents->item)
102 hashcpy(old_sha1, commit->parents->item->object.sha1); 102 hashcpy(old_sha1, commit->parents->item->object.sha1);
103 else 103 else
104 hashclr(old_sha1); 104 hashclr(old_sha1);
105 105
106 patchname = fmt("%s.patch", sha1_to_hex(sha1)); 106 patchname = fmt("%s.patch", sha1_to_hex(sha1));
107 ctx.page.mimetype = "text/plain"; 107 ctx.page.mimetype = "text/plain";
108 ctx.page.filename = patchname; 108 ctx.page.filename = patchname;
109 cgit_print_http_headers(&ctx); 109 cgit_print_http_headers(&ctx);
110 htmlf("From %s Mon Sep 17 00:00:00 2001\n", sha1_to_hex(sha1)); 110 htmlf("From %s Mon Sep 17 00:00:00 2001\n", sha1_to_hex(sha1));
111 htmlf("From: %s", info->author); 111 htmlf("From: %s", info->author);
112 if (!ctx.cfg.noplainemail) { 112 if (!ctx.cfg.noplainemail) {
113 htmlf(" %s", info->author_email); 113 htmlf(" %s", info->author_email);
114 } 114 }
115 html("\n"); 115 html("\n");
116 html("Date: "); 116 html("Date: ");
117 cgit_print_date(info->author_date, "%a, %d %b %Y %H:%M:%S %z%n", ctx.cfg.local_time); 117 cgit_print_date(info->author_date, "%a, %d %b %Y %H:%M:%S %z%n", ctx.cfg.local_time);
118 htmlf("Subject: %s\n\n", info->subject); 118 htmlf("Subject: %s\n\n", info->subject);
119 if (info->msg && *info->msg) { 119 if (info->msg && *info->msg) {
120 htmlf("%s", info->msg); 120 htmlf("%s", info->msg);
121 if (info->msg[strlen(info->msg) - 1] != '\n') 121 if (info->msg[strlen(info->msg) - 1] != '\n')
122 html("\n"); 122 html("\n");
123 } 123 }
124 html("---\n"); 124 html("---\n");
125 cgit_diff_tree(old_sha1, sha1, filepair_cb, NULL); 125 if (prefix)
126 htmlf("(limited to '%s')\n\n", prefix);
127 cgit_diff_tree(old_sha1, sha1, filepair_cb, prefix);
126 html("--\n"); 128 html("--\n");
127 htmlf("cgit %s\n", CGIT_VERSION); 129 htmlf("cgit %s\n", CGIT_VERSION);
128 cgit_free_commitinfo(info); 130 cgit_free_commitinfo(info);
129} 131}
diff --git a/ui-patch.h b/ui-patch.h
index 9f68212..1641cea 100644
--- a/ui-patch.h
+++ b/ui-patch.h
@@ -1,6 +1,6 @@
1#ifndef UI_PATCH_H 1#ifndef UI_PATCH_H
2#define UI_PATCH_H 2#define UI_PATCH_H
3 3
4extern void cgit_print_patch(char *hex); 4extern void cgit_print_patch(char *hex, const char *prefix);
5 5
6#endif /* UI_PATCH_H */ 6#endif /* UI_PATCH_H */
diff --git a/ui-refs.c b/ui-refs.c
index 98738db..94ff6be 100644
--- a/ui-refs.c
+++ b/ui-refs.c
@@ -1,247 +1,247 @@
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 int r1date, r2date; 49 int r1date, r2date;
50 50
51 if (r1->object->type != OBJ_COMMIT) 51 if (r1->object->type != OBJ_COMMIT)
52 r1date = r1->tag->tagger_date; 52 r1date = r1->tag->tagger_date;
53 else 53 else
54 r1date = r1->commit->committer_date; 54 r1date = r1->commit->committer_date;
55 55
56 if (r2->object->type != OBJ_COMMIT) 56 if (r2->object->type != OBJ_COMMIT)
57 r2date = r2->tag->tagger_date; 57 r2date = r2->tag->tagger_date;
58 else 58 else
59 r2date = r2->commit->committer_date; 59 r2date = r2->commit->committer_date;
60 60
61 return cmp_age(r1date, r2date); 61 return cmp_age(r1date, r2date);
62} 62}
63 63
64static int print_branch(struct refinfo *ref) 64static int print_branch(struct refinfo *ref)
65{ 65{
66 struct commitinfo *info = ref->commit; 66 struct commitinfo *info = ref->commit;
67 char *name = (char *)ref->refname; 67 char *name = (char *)ref->refname;
68 68
69 if (!info) 69 if (!info)
70 return 1; 70 return 1;
71 html("<tr><td>"); 71 html("<tr><td>");
72 cgit_log_link(name, NULL, NULL, name, NULL, NULL, 0, NULL, NULL, 72 cgit_log_link(name, NULL, NULL, name, NULL, NULL, 0, NULL, NULL,
73 ctx.qry.showmsg); 73 ctx.qry.showmsg);
74 html("</td><td>"); 74 html("</td><td>");
75 75
76 if (ref->object->type == OBJ_COMMIT) { 76 if (ref->object->type == OBJ_COMMIT) {
77 cgit_commit_link(info->subject, NULL, NULL, name, NULL, 0); 77 cgit_commit_link(info->subject, NULL, NULL, name, NULL, NULL, 0);
78 html("</td><td>"); 78 html("</td><td>");
79 html_txt(info->author); 79 html_txt(info->author);
80 html("</td><td colspan='2'>"); 80 html("</td><td colspan='2'>");
81 cgit_print_age(info->commit->date, -1, NULL); 81 cgit_print_age(info->commit->date, -1, NULL);
82 } else { 82 } else {
83 html("</td><td></td><td>"); 83 html("</td><td></td><td>");
84 cgit_object_link(ref->object); 84 cgit_object_link(ref->object);
85 } 85 }
86 html("</td></tr>\n"); 86 html("</td></tr>\n");
87 return 0; 87 return 0;
88} 88}
89 89
90static void print_tag_header() 90static void print_tag_header()
91{ 91{
92 html("<tr class='nohover'><th class='left'>Tag</th>" 92 html("<tr class='nohover'><th class='left'>Tag</th>"
93 "<th class='left'>Download</th>" 93 "<th class='left'>Download</th>"
94 "<th class='left'>Author</th>" 94 "<th class='left'>Author</th>"
95 "<th class='left' colspan='2'>Age</th></tr>\n"); 95 "<th class='left' colspan='2'>Age</th></tr>\n");
96 header = 1; 96 header = 1;
97} 97}
98 98
99static void print_tag_downloads(const struct cgit_repo *repo, const char *ref) 99static void print_tag_downloads(const struct cgit_repo *repo, const char *ref)
100{ 100{
101 const struct cgit_snapshot_format* f; 101 const struct cgit_snapshot_format* f;
102 char *filename; 102 char *filename;
103 const char *basename; 103 const char *basename;
104 104
105 if (!ref || strlen(ref) < 2) 105 if (!ref || strlen(ref) < 2)
106 return; 106 return;
107 107
108 basename = cgit_repobasename(repo->url); 108 basename = cgit_repobasename(repo->url);
109 if (prefixcmp(ref, basename) != 0) { 109 if (prefixcmp(ref, basename) != 0) {
110 if ((ref[0] == 'v' || ref[0] == 'V') && isdigit(ref[1])) 110 if ((ref[0] == 'v' || ref[0] == 'V') && isdigit(ref[1]))
111 ref++; 111 ref++;
112 if (isdigit(ref[0])) 112 if (isdigit(ref[0]))
113 ref = xstrdup(fmt("%s-%s", basename, ref)); 113 ref = xstrdup(fmt("%s-%s", basename, ref));
114 } 114 }
115 115
116 for (f = cgit_snapshot_formats; f->suffix; f++) { 116 for (f = cgit_snapshot_formats; f->suffix; f++) {
117 if (!(repo->snapshots & f->bit)) 117 if (!(repo->snapshots & f->bit))
118 continue; 118 continue;
119 filename = fmt("%s%s", ref, f->suffix); 119 filename = fmt("%s%s", ref, f->suffix);
120 cgit_snapshot_link(filename, NULL, NULL, NULL, NULL, filename); 120 cgit_snapshot_link(filename, NULL, NULL, NULL, NULL, filename);
121 html("&nbsp;&nbsp;"); 121 html("&nbsp;&nbsp;");
122 } 122 }
123} 123}
124static int print_tag(struct refinfo *ref) 124static int print_tag(struct refinfo *ref)
125{ 125{
126 struct tag *tag; 126 struct tag *tag;
127 struct taginfo *info; 127 struct taginfo *info;
128 char *name = (char *)ref->refname; 128 char *name = (char *)ref->refname;
129 129
130 if (ref->object->type == OBJ_TAG) { 130 if (ref->object->type == OBJ_TAG) {
131 tag = (struct tag *)ref->object; 131 tag = (struct tag *)ref->object;
132 info = ref->tag; 132 info = ref->tag;
133 if (!tag || !info) 133 if (!tag || !info)
134 return 1; 134 return 1;
135 html("<tr><td>"); 135 html("<tr><td>");
136 cgit_tag_link(name, NULL, NULL, ctx.qry.head, name); 136 cgit_tag_link(name, NULL, NULL, ctx.qry.head, name);
137 html("</td><td>"); 137 html("</td><td>");
138 if (ctx.repo->snapshots && (tag->tagged->type == OBJ_COMMIT)) 138 if (ctx.repo->snapshots && (tag->tagged->type == OBJ_COMMIT))
139 print_tag_downloads(ctx.repo, name); 139 print_tag_downloads(ctx.repo, name);
140 else 140 else
141 cgit_object_link(tag->tagged); 141 cgit_object_link(tag->tagged);
142 html("</td><td>"); 142 html("</td><td>");
143 if (info->tagger) 143 if (info->tagger)
144 html(info->tagger); 144 html(info->tagger);
145 html("</td><td colspan='2'>"); 145 html("</td><td colspan='2'>");
146 if (info->tagger_date > 0) 146 if (info->tagger_date > 0)
147 cgit_print_age(info->tagger_date, -1, NULL); 147 cgit_print_age(info->tagger_date, -1, NULL);
148 html("</td></tr>\n"); 148 html("</td></tr>\n");
149 } else { 149 } else {
150 if (!header) 150 if (!header)
151 print_tag_header(); 151 print_tag_header();
152 html("<tr><td>"); 152 html("<tr><td>");
153 cgit_tag_link(name, NULL, NULL, ctx.qry.head, name); 153 cgit_tag_link(name, NULL, NULL, ctx.qry.head, name);
154 html("</td><td>"); 154 html("</td><td>");
155 if (ctx.repo->snapshots && (ref->object->type == OBJ_COMMIT)) 155 if (ctx.repo->snapshots && (ref->object->type == OBJ_COMMIT))
156 print_tag_downloads(ctx.repo, name); 156 print_tag_downloads(ctx.repo, name);
157 else 157 else
158 cgit_object_link(ref->object); 158 cgit_object_link(ref->object);
159 html("</td><td>"); 159 html("</td><td>");
160 if (ref->object->type == OBJ_COMMIT) 160 if (ref->object->type == OBJ_COMMIT)
161 html(ref->commit->author); 161 html(ref->commit->author);
162 html("</td><td colspan='2'>"); 162 html("</td><td colspan='2'>");
163 if (ref->object->type == OBJ_COMMIT) 163 if (ref->object->type == OBJ_COMMIT)
164 cgit_print_age(ref->commit->commit->date, -1, NULL); 164 cgit_print_age(ref->commit->commit->date, -1, NULL);
165 html("</td></tr>\n"); 165 html("</td></tr>\n");
166 } 166 }
167 return 0; 167 return 0;
168} 168}
169 169
170static void print_refs_link(char *path) 170static void print_refs_link(char *path)
171{ 171{
172 html("<tr class='nohover'><td colspan='4'>"); 172 html("<tr class='nohover'><td colspan='4'>");
173 cgit_refs_link("[...]", NULL, NULL, ctx.qry.head, NULL, path); 173 cgit_refs_link("[...]", NULL, NULL, ctx.qry.head, NULL, path);
174 html("</td></tr>"); 174 html("</td></tr>");
175} 175}
176 176
177void cgit_print_branches(int maxcount) 177void cgit_print_branches(int maxcount)
178{ 178{
179 struct reflist list; 179 struct reflist list;
180 int i; 180 int i;
181 181
182 html("<tr class='nohover'><th class='left'>Branch</th>" 182 html("<tr class='nohover'><th class='left'>Branch</th>"
183 "<th class='left'>Commit message</th>" 183 "<th class='left'>Commit message</th>"
184 "<th class='left'>Author</th>" 184 "<th class='left'>Author</th>"
185 "<th class='left' colspan='2'>Age</th></tr>\n"); 185 "<th class='left' colspan='2'>Age</th></tr>\n");
186 186
187 list.refs = NULL; 187 list.refs = NULL;
188 list.alloc = list.count = 0; 188 list.alloc = list.count = 0;
189 for_each_branch_ref(cgit_refs_cb, &list); 189 for_each_branch_ref(cgit_refs_cb, &list);
190 if (ctx.repo->enable_remote_branches) 190 if (ctx.repo->enable_remote_branches)
191 for_each_remote_ref(cgit_refs_cb, &list); 191 for_each_remote_ref(cgit_refs_cb, &list);
192 192
193 if (maxcount == 0 || maxcount > list.count) 193 if (maxcount == 0 || maxcount > list.count)
194 maxcount = list.count; 194 maxcount = list.count;
195 195
196 if (maxcount < list.count) { 196 if (maxcount < list.count) {
197 qsort(list.refs, list.count, sizeof(*list.refs), cmp_branch_age); 197 qsort(list.refs, list.count, sizeof(*list.refs), cmp_branch_age);
198 qsort(list.refs, maxcount, sizeof(*list.refs), cmp_ref_name); 198 qsort(list.refs, maxcount, sizeof(*list.refs), cmp_ref_name);
199 } 199 }
200 200
201 for(i=0; i<maxcount; i++) 201 for(i=0; i<maxcount; i++)
202 print_branch(list.refs[i]); 202 print_branch(list.refs[i]);
203 203
204 if (maxcount < list.count) 204 if (maxcount < list.count)
205 print_refs_link("heads"); 205 print_refs_link("heads");
206} 206}
207 207
208void cgit_print_tags(int maxcount) 208void cgit_print_tags(int maxcount)
209{ 209{
210 struct reflist list; 210 struct reflist list;
211 int i; 211 int i;
212 212
213 header = 0; 213 header = 0;
214 list.refs = NULL; 214 list.refs = NULL;
215 list.alloc = list.count = 0; 215 list.alloc = list.count = 0;
216 for_each_tag_ref(cgit_refs_cb, &list); 216 for_each_tag_ref(cgit_refs_cb, &list);
217 if (list.count == 0) 217 if (list.count == 0)
218 return; 218 return;
219 qsort(list.refs, list.count, sizeof(*list.refs), cmp_tag_age); 219 qsort(list.refs, list.count, sizeof(*list.refs), cmp_tag_age);
220 if (!maxcount) 220 if (!maxcount)
221 maxcount = list.count; 221 maxcount = list.count;
222 else if (maxcount > list.count) 222 else if (maxcount > list.count)
223 maxcount = list.count; 223 maxcount = list.count;
224 print_tag_header(); 224 print_tag_header();
225 for(i=0; i<maxcount; i++) 225 for(i=0; i<maxcount; i++)
226 print_tag(list.refs[i]); 226 print_tag(list.refs[i]);
227 227
228 if (maxcount < list.count) 228 if (maxcount < list.count)
229 print_refs_link("tags"); 229 print_refs_link("tags");
230} 230}
231 231
232void cgit_print_refs() 232void cgit_print_refs()
233{ 233{
234 234
235 html("<table class='list nowrap'>"); 235 html("<table class='list nowrap'>");
236 236
237 if (ctx.qry.path && !strncmp(ctx.qry.path, "heads", 5)) 237 if (ctx.qry.path && !strncmp(ctx.qry.path, "heads", 5))
238 cgit_print_branches(0); 238 cgit_print_branches(0);
239 else if (ctx.qry.path && !strncmp(ctx.qry.path, "tags", 4)) 239 else if (ctx.qry.path && !strncmp(ctx.qry.path, "tags", 4))
240 cgit_print_tags(0); 240 cgit_print_tags(0);
241 else { 241 else {
242 cgit_print_branches(0); 242 cgit_print_branches(0);
243 html("<tr class='nohover'><td colspan='4'>&nbsp;</td></tr>"); 243 html("<tr class='nohover'><td colspan='4'>&nbsp;</td></tr>");
244 cgit_print_tags(0); 244 cgit_print_tags(0);
245 } 245 }
246 html("</table>"); 246 html("</table>");
247} 247}
diff --git a/ui-shared.c b/ui-shared.c
index 8827fff..e991799 100644
--- a/ui-shared.c
+++ b/ui-shared.c
@@ -1,800 +1,884 @@
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", "Nov", "Dec"}; 23 "Jul", "Aug", "Sep", "Oct", "Nov", "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(const 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_httpscheme() 37char *cgit_httpscheme()
38{ 38{
39 if (ctx.env.https && !strcmp(ctx.env.https, "on")) 39 if (ctx.env.https && !strcmp(ctx.env.https, "on"))
40 return "https://"; 40 return "https://";
41 else 41 else
42 return "http://"; 42 return "http://";
43} 43}
44 44
45char *cgit_hosturl() 45char *cgit_hosturl()
46{ 46{
47 if (ctx.env.http_host) 47 if (ctx.env.http_host)
48 return ctx.env.http_host; 48 return ctx.env.http_host;
49 if (!ctx.env.server_name) 49 if (!ctx.env.server_name)
50 return NULL; 50 return NULL;
51 if (!ctx.env.server_port || atoi(ctx.env.server_port) == 80) 51 if (!ctx.env.server_port || atoi(ctx.env.server_port) == 80)
52 return ctx.env.server_name; 52 return ctx.env.server_name;
53 return xstrdup(fmt("%s:%s", ctx.env.server_name, ctx.env.server_port)); 53 return xstrdup(fmt("%s:%s", ctx.env.server_name, ctx.env.server_port));
54} 54}
55 55
56char *cgit_rooturl() 56char *cgit_rooturl()
57{ 57{
58 if (ctx.cfg.virtual_root) 58 if (ctx.cfg.virtual_root)
59 return fmt("%s/", ctx.cfg.virtual_root); 59 return fmt("%s/", ctx.cfg.virtual_root);
60 else 60 else
61 return ctx.cfg.script_name; 61 return ctx.cfg.script_name;
62} 62}
63 63
64char *cgit_repourl(const char *reponame) 64char *cgit_repourl(const char *reponame)
65{ 65{
66 if (ctx.cfg.virtual_root) { 66 if (ctx.cfg.virtual_root) {
67 return fmt("%s/%s/", ctx.cfg.virtual_root, reponame); 67 return fmt("%s/%s/", ctx.cfg.virtual_root, reponame);
68 } else { 68 } else {
69 return fmt("?r=%s", reponame); 69 return fmt("?r=%s", reponame);
70 } 70 }
71} 71}
72 72
73char *cgit_fileurl(const char *reponame, const char *pagename, 73char *cgit_fileurl(const char *reponame, const char *pagename,
74 const char *filename, const char *query) 74 const char *filename, const char *query)
75{ 75{
76 char *tmp; 76 char *tmp;
77 char *delim; 77 char *delim;
78 78
79 if (ctx.cfg.virtual_root) { 79 if (ctx.cfg.virtual_root) {
80 tmp = fmt("%s/%s/%s/%s", ctx.cfg.virtual_root, reponame, 80 tmp = fmt("%s/%s/%s/%s", ctx.cfg.virtual_root, reponame,
81 pagename, (filename ? filename:"")); 81 pagename, (filename ? filename:""));
82 delim = "?"; 82 delim = "?";
83 } else { 83 } else {
84 tmp = fmt("?url=%s/%s/%s", reponame, pagename, 84 tmp = fmt("?url=%s/%s/%s", reponame, pagename,
85 (filename ? filename : "")); 85 (filename ? filename : ""));
86 delim = "&"; 86 delim = "&";
87 } 87 }
88 if (query) 88 if (query)
89 tmp = fmt("%s%s%s", tmp, delim, query); 89 tmp = fmt("%s%s%s", tmp, delim, query);
90 return tmp; 90 return tmp;
91} 91}
92 92
93char *cgit_pageurl(const char *reponame, const char *pagename, 93char *cgit_pageurl(const char *reponame, const char *pagename,
94 const char *query) 94 const char *query)
95{ 95{
96 return cgit_fileurl(reponame,pagename,0,query); 96 return cgit_fileurl(reponame,pagename,0,query);
97} 97}
98 98
99const char *cgit_repobasename(const char *reponame) 99const char *cgit_repobasename(const char *reponame)
100{ 100{
101 /* I assume we don't need to store more than one repo basename */ 101 /* I assume we don't need to store more than one repo basename */
102 static char rvbuf[1024]; 102 static char rvbuf[1024];
103 int p; 103 int p;
104 const char *rv; 104 const char *rv;
105 strncpy(rvbuf,reponame,sizeof(rvbuf)); 105 strncpy(rvbuf,reponame,sizeof(rvbuf));
106 if(rvbuf[sizeof(rvbuf)-1]) 106 if(rvbuf[sizeof(rvbuf)-1])
107 die("cgit_repobasename: truncated repository name '%s'", reponame); 107 die("cgit_repobasename: truncated repository name '%s'", reponame);
108 p = strlen(rvbuf)-1; 108 p = strlen(rvbuf)-1;
109 /* strip trailing slashes */ 109 /* strip trailing slashes */
110 while(p && rvbuf[p]=='/') rvbuf[p--]=0; 110 while(p && rvbuf[p]=='/') rvbuf[p--]=0;
111 /* strip trailing .git */ 111 /* strip trailing .git */
112 if(p>=3 && !strncmp(&rvbuf[p-3],".git",4)) { 112 if(p>=3 && !strncmp(&rvbuf[p-3],".git",4)) {
113 p -= 3; rvbuf[p--] = 0; 113 p -= 3; rvbuf[p--] = 0;
114 } 114 }
115 /* strip more trailing slashes if any */ 115 /* strip more trailing slashes if any */
116 while( p && rvbuf[p]=='/') rvbuf[p--]=0; 116 while( p && rvbuf[p]=='/') rvbuf[p--]=0;
117 /* find last slash in the remaining string */ 117 /* find last slash in the remaining string */
118 rv = strrchr(rvbuf,'/'); 118 rv = strrchr(rvbuf,'/');
119 if(rv) 119 if(rv)
120 return ++rv; 120 return ++rv;
121 return rvbuf; 121 return rvbuf;
122} 122}
123 123
124char *cgit_currurl() 124char *cgit_currurl()
125{ 125{
126 if (!ctx.cfg.virtual_root) 126 if (!ctx.cfg.virtual_root)
127 return ctx.cfg.script_name; 127 return ctx.cfg.script_name;
128 else if (ctx.qry.page) 128 else if (ctx.qry.page)
129 return fmt("%s/%s/%s/", ctx.cfg.virtual_root, ctx.qry.repo, ctx.qry.page); 129 return fmt("%s/%s/%s/", ctx.cfg.virtual_root, ctx.qry.repo, ctx.qry.page);
130 else if (ctx.qry.repo) 130 else if (ctx.qry.repo)
131 return fmt("%s/%s/", ctx.cfg.virtual_root, ctx.qry.repo); 131 return fmt("%s/%s/", ctx.cfg.virtual_root, ctx.qry.repo);
132 else 132 else
133 return fmt("%s/", ctx.cfg.virtual_root); 133 return fmt("%s/", ctx.cfg.virtual_root);
134} 134}
135 135
136static void site_url(char *page, char *search, int ofs) 136static void site_url(const char *page, const char *search, int ofs)
137{ 137{
138 char *delim = "?"; 138 char *delim = "?";
139 139
140 if (ctx.cfg.virtual_root) { 140 if (ctx.cfg.virtual_root) {
141 html_attr(ctx.cfg.virtual_root); 141 html_attr(ctx.cfg.virtual_root);
142 if (ctx.cfg.virtual_root[strlen(ctx.cfg.virtual_root) - 1] != '/') 142 if (ctx.cfg.virtual_root[strlen(ctx.cfg.virtual_root) - 1] != '/')
143 html("/"); 143 html("/");
144 } else 144 } else
145 html(ctx.cfg.script_name); 145 html(ctx.cfg.script_name);
146 146
147 if (page) { 147 if (page) {
148 htmlf("?p=%s", page); 148 htmlf("?p=%s", page);
149 delim = "&"; 149 delim = "&";
150 } 150 }
151 if (search) { 151 if (search) {
152 html(delim); 152 html(delim);
153 html("q="); 153 html("q=");
154 html_attr(search); 154 html_attr(search);
155 delim = "&"; 155 delim = "&";
156 } 156 }
157 if (ofs) { 157 if (ofs) {
158 html(delim); 158 html(delim);
159 htmlf("ofs=%d", ofs); 159 htmlf("ofs=%d", ofs);
160 } 160 }
161} 161}
162 162
163static void site_link(char *page, char *name, char *title, char *class, 163static void site_link(const char *page, const char *name, const char *title,
164 char *search, int ofs) 164 const char *class, const char *search, int ofs)
165{ 165{
166 html("<a"); 166 html("<a");
167 if (title) { 167 if (title) {
168 html(" title='"); 168 html(" title='");
169 html_attr(title); 169 html_attr(title);
170 html("'"); 170 html("'");
171 } 171 }
172 if (class) { 172 if (class) {
173 html(" class='"); 173 html(" class='");
174 html_attr(class); 174 html_attr(class);
175 html("'"); 175 html("'");
176 } 176 }
177 html(" href='"); 177 html(" href='");
178 site_url(page, search, ofs); 178 site_url(page, search, ofs);
179 html("'>"); 179 html("'>");
180 html_txt(name); 180 html_txt(name);
181 html("</a>"); 181 html("</a>");
182} 182}
183 183
184void cgit_index_link(char *name, char *title, char *class, char *pattern, 184void cgit_index_link(const char *name, const char *title, const char *class,
185 int ofs) 185 const char *pattern, int ofs)
186{ 186{
187 site_link(NULL, name, title, class, pattern, ofs); 187 site_link(NULL, name, title, class, pattern, ofs);
188} 188}
189 189
190static char *repolink(char *title, char *class, char *page, char *head, 190static char *repolink(const char *title, const char *class, const char *page,
191 char *path) 191 const char *head, const char *path)
192{ 192{
193 char *delim = "?"; 193 char *delim = "?";
194 194
195 html("<a"); 195 html("<a");
196 if (title) { 196 if (title) {
197 html(" title='"); 197 html(" title='");
198 html_attr(title); 198 html_attr(title);
199 html("'"); 199 html("'");
200 } 200 }
201 if (class) { 201 if (class) {
202 html(" class='"); 202 html(" class='");
203 html_attr(class); 203 html_attr(class);
204 html("'"); 204 html("'");
205 } 205 }
206 html(" href='"); 206 html(" href='");
207 if (ctx.cfg.virtual_root) { 207 if (ctx.cfg.virtual_root) {
208 html_url_path(ctx.cfg.virtual_root); 208 html_url_path(ctx.cfg.virtual_root);
209 if (ctx.cfg.virtual_root[strlen(ctx.cfg.virtual_root) - 1] != '/') 209 if (ctx.cfg.virtual_root[strlen(ctx.cfg.virtual_root) - 1] != '/')
210 html("/"); 210 html("/");
211 html_url_path(ctx.repo->url); 211 html_url_path(ctx.repo->url);
212 if (ctx.repo->url[strlen(ctx.repo->url) - 1] != '/') 212 if (ctx.repo->url[strlen(ctx.repo->url) - 1] != '/')
213 html("/"); 213 html("/");
214 if (page) { 214 if (page) {
215 html_url_path(page); 215 html_url_path(page);
216 html("/"); 216 html("/");
217 if (path) 217 if (path)
218 html_url_path(path); 218 html_url_path(path);
219 } 219 }
220 } else { 220 } else {
221 html(ctx.cfg.script_name); 221 html(ctx.cfg.script_name);
222 html("?url="); 222 html("?url=");
223 html_url_arg(ctx.repo->url); 223 html_url_arg(ctx.repo->url);
224 if (ctx.repo->url[strlen(ctx.repo->url) - 1] != '/') 224 if (ctx.repo->url[strlen(ctx.repo->url) - 1] != '/')
225 html("/"); 225 html("/");
226 if (page) { 226 if (page) {
227 html_url_arg(page); 227 html_url_arg(page);
228 html("/"); 228 html("/");
229 if (path) 229 if (path)
230 html_url_arg(path); 230 html_url_arg(path);
231 } 231 }
232 delim = "&amp;"; 232 delim = "&amp;";
233 } 233 }
234 if (head && strcmp(head, ctx.repo->defbranch)) { 234 if (head && strcmp(head, ctx.repo->defbranch)) {
235 html(delim); 235 html(delim);
236 html("h="); 236 html("h=");
237 html_url_arg(head); 237 html_url_arg(head);
238 delim = "&amp;"; 238 delim = "&amp;";
239 } 239 }
240 return fmt("%s", delim); 240 return fmt("%s", delim);
241} 241}
242 242
243static void reporevlink(char *page, char *name, char *title, char *class, 243static void reporevlink(const char *page, const char *name, const char *title,
244 char *head, char *rev, char *path) 244 const char *class, const char *head, const char *rev,
245 const char *path)
245{ 246{
246 char *delim; 247 char *delim;
247 248
248 delim = repolink(title, class, page, head, path); 249 delim = repolink(title, class, page, head, path);
249 if (rev && ctx.qry.head != NULL && strcmp(rev, ctx.qry.head)) { 250 if (rev && ctx.qry.head != NULL && strcmp(rev, ctx.qry.head)) {
250 html(delim); 251 html(delim);
251 html("id="); 252 html("id=");
252 html_url_arg(rev); 253 html_url_arg(rev);
253 } 254 }
254 html("'>"); 255 html("'>");
255 html_txt(name); 256 html_txt(name);
256 html("</a>"); 257 html("</a>");
257} 258}
258 259
259void cgit_summary_link(char *name, char *title, char *class, char *head) 260void cgit_summary_link(const char *name, const char *title, const char *class,
261 const char *head)
260{ 262{
261 reporevlink(NULL, name, title, class, head, NULL, NULL); 263 reporevlink(NULL, name, title, class, head, NULL, NULL);
262} 264}
263 265
264void cgit_tag_link(char *name, char *title, char *class, char *head, 266void cgit_tag_link(const char *name, const char *title, const char *class,
265 char *rev) 267 const char *head, const char *rev)
266{ 268{
267 reporevlink("tag", name, title, class, head, rev, NULL); 269 reporevlink("tag", name, title, class, head, rev, NULL);
268} 270}
269 271
270void cgit_tree_link(char *name, char *title, char *class, char *head, 272void cgit_tree_link(const char *name, const char *title, const char *class,
271 char *rev, char *path) 273 const char *head, const char *rev, const char *path)
272{ 274{
273 reporevlink("tree", name, title, class, head, rev, path); 275 reporevlink("tree", name, title, class, head, rev, path);
274} 276}
275 277
276void cgit_plain_link(char *name, char *title, char *class, char *head, 278void cgit_plain_link(const char *name, const char *title, const char *class,
277 char *rev, char *path) 279 const char *head, const char *rev, const char *path)
278{ 280{
279 reporevlink("plain", name, title, class, head, rev, path); 281 reporevlink("plain", name, title, class, head, rev, path);
280} 282}
281 283
282void cgit_log_link(char *name, char *title, char *class, char *head, 284void cgit_log_link(const char *name, const char *title, const char *class,
283 char *rev, char *path, int ofs, char *grep, char *pattern, 285 const char *head, const char *rev, const char *path,
284 int showmsg) 286 int ofs, const char *grep, const char *pattern, int showmsg)
285{ 287{
286 char *delim; 288 char *delim;
287 289
288 delim = repolink(title, class, "log", head, path); 290 delim = repolink(title, class, "log", head, path);
289 if (rev && strcmp(rev, ctx.qry.head)) { 291 if (rev && strcmp(rev, ctx.qry.head)) {
290 html(delim); 292 html(delim);
291 html("id="); 293 html("id=");
292 html_url_arg(rev); 294 html_url_arg(rev);
293 delim = "&"; 295 delim = "&";
294 } 296 }
295 if (grep && pattern) { 297 if (grep && pattern) {
296 html(delim); 298 html(delim);
297 html("qt="); 299 html("qt=");
298 html_url_arg(grep); 300 html_url_arg(grep);
299 delim = "&"; 301 delim = "&";
300 html(delim); 302 html(delim);
301 html("q="); 303 html("q=");
302 html_url_arg(pattern); 304 html_url_arg(pattern);
303 } 305 }
304 if (ofs > 0) { 306 if (ofs > 0) {
305 html(delim); 307 html(delim);
306 html("ofs="); 308 html("ofs=");
307 htmlf("%d", ofs); 309 htmlf("%d", ofs);
308 delim = "&"; 310 delim = "&";
309 } 311 }
310 if (showmsg) { 312 if (showmsg) {
311 html(delim); 313 html(delim);
312 html("showmsg=1"); 314 html("showmsg=1");
313 } 315 }
314 html("'>"); 316 html("'>");
315 html_txt(name); 317 html_txt(name);
316 html("</a>"); 318 html("</a>");
317} 319}
318 320
319void cgit_commit_link(char *name, char *title, char *class, char *head, 321void cgit_commit_link(char *name, const char *title, const char *class,
320 char *rev, int toggle_ssdiff) 322 const char *head, const char *rev, const char *path,
323 int toggle_ssdiff)
321{ 324{
322 if (strlen(name) > ctx.cfg.max_msg_len && ctx.cfg.max_msg_len >= 15) { 325 if (strlen(name) > ctx.cfg.max_msg_len && ctx.cfg.max_msg_len >= 15) {
323 name[ctx.cfg.max_msg_len] = '\0'; 326 name[ctx.cfg.max_msg_len] = '\0';
324 name[ctx.cfg.max_msg_len - 1] = '.'; 327 name[ctx.cfg.max_msg_len - 1] = '.';
325 name[ctx.cfg.max_msg_len - 2] = '.'; 328 name[ctx.cfg.max_msg_len - 2] = '.';
326 name[ctx.cfg.max_msg_len - 3] = '.'; 329 name[ctx.cfg.max_msg_len - 3] = '.';
327 } 330 }
328 331
329 char *delim; 332 char *delim;
330 333
331 delim = repolink(title, class, "commit", head, NULL); 334 delim = repolink(title, class, "commit", head, path);
332 if (rev && strcmp(rev, ctx.qry.head)) { 335 if (rev && strcmp(rev, ctx.qry.head)) {
333 html(delim); 336 html(delim);
334 html("id="); 337 html("id=");
335 html_url_arg(rev); 338 html_url_arg(rev);
336 delim = "&amp;"; 339 delim = "&amp;";
337 } 340 }
338 if ((ctx.qry.ssdiff && !toggle_ssdiff) || (!ctx.qry.ssdiff && toggle_ssdiff)) { 341 if ((ctx.qry.ssdiff && !toggle_ssdiff) || (!ctx.qry.ssdiff && toggle_ssdiff)) {
339 html(delim); 342 html(delim);
340 html("ss=1"); 343 html("ss=1");
341 } 344 }
342 html("'>"); 345 html("'>");
343 html_txt(name); 346 html_txt(name);
344 html("</a>"); 347 html("</a>");
345} 348}
346 349
347void cgit_refs_link(char *name, char *title, char *class, char *head, 350void cgit_refs_link(const char *name, const char *title, const char *class,
348 char *rev, char *path) 351 const char *head, const char *rev, const char *path)
349{ 352{
350 reporevlink("refs", name, title, class, head, rev, path); 353 reporevlink("refs", name, title, class, head, rev, path);
351} 354}
352 355
353void cgit_snapshot_link(char *name, char *title, char *class, char *head, 356void cgit_snapshot_link(const char *name, const char *title, const char *class,
354 char *rev, char *archivename) 357 const char *head, const char *rev,
358 const char *archivename)
355{ 359{
356 reporevlink("snapshot", name, title, class, head, rev, archivename); 360 reporevlink("snapshot", name, title, class, head, rev, archivename);
357} 361}
358 362
359void cgit_diff_link(char *name, char *title, char *class, char *head, 363void cgit_diff_link(const char *name, const char *title, const char *class,
360 char *new_rev, char *old_rev, char *path, 364 const char *head, const char *new_rev, const char *old_rev,
361 int toggle_ssdiff) 365 const char *path, int toggle_ssdiff)
362{ 366{
363 char *delim; 367 char *delim;
364 368
365 delim = repolink(title, class, "diff", head, path); 369 delim = repolink(title, class, "diff", head, path);
366 if (new_rev && ctx.qry.head != NULL && strcmp(new_rev, ctx.qry.head)) { 370 if (new_rev && ctx.qry.head != NULL && strcmp(new_rev, ctx.qry.head)) {
367 html(delim); 371 html(delim);
368 html("id="); 372 html("id=");
369 html_url_arg(new_rev); 373 html_url_arg(new_rev);
370 delim = "&amp;"; 374 delim = "&amp;";
371 } 375 }
372 if (old_rev) { 376 if (old_rev) {
373 html(delim); 377 html(delim);
374 html("id2="); 378 html("id2=");
375 html_url_arg(old_rev); 379 html_url_arg(old_rev);
376 delim = "&amp;"; 380 delim = "&amp;";
377 } 381 }
378 if ((ctx.qry.ssdiff && !toggle_ssdiff) || (!ctx.qry.ssdiff && toggle_ssdiff)) { 382 if ((ctx.qry.ssdiff && !toggle_ssdiff) || (!ctx.qry.ssdiff && toggle_ssdiff)) {
379 html(delim); 383 html(delim);
380 html("ss=1"); 384 html("ss=1");
381 } 385 }
382 html("'>"); 386 html("'>");
383 html_txt(name); 387 html_txt(name);
384 html("</a>"); 388 html("</a>");
385} 389}
386 390
387void cgit_patch_link(char *name, char *title, char *class, char *head, 391void cgit_patch_link(const char *name, const char *title, const char *class,
388 char *rev) 392 const char *head, const char *rev, const char *path)
389{ 393{
390 reporevlink("patch", name, title, class, head, rev, NULL); 394 reporevlink("patch", name, title, class, head, rev, path);
391} 395}
392 396
393void cgit_stats_link(char *name, char *title, char *class, char *head, 397void cgit_stats_link(const char *name, const char *title, const char *class,
394 char *path) 398 const char *head, const char *path)
395{ 399{
396 reporevlink("stats", name, title, class, head, NULL, path); 400 reporevlink("stats", name, title, class, head, NULL, path);
397} 401}
398 402
403void cgit_self_link(char *name, const char *title, const char *class,
404 struct cgit_context *ctx)
405{
406 if (!strcmp(ctx->qry.page, "repolist"))
407 return cgit_index_link(name, title, class, ctx->qry.search,
408 ctx->qry.ofs);
409 else if (!strcmp(ctx->qry.page, "summary"))
410 return cgit_summary_link(name, title, class, ctx->qry.head);
411 else if (!strcmp(ctx->qry.page, "tag"))
412 return cgit_tag_link(name, title, class, ctx->qry.head,
413 ctx->qry.has_sha1 ? ctx->qry.sha1 : NULL);
414 else if (!strcmp(ctx->qry.page, "tree"))
415 return cgit_tree_link(name, title, class, ctx->qry.head,
416 ctx->qry.has_sha1 ? ctx->qry.sha1 : NULL,
417 ctx->qry.path);
418 else if (!strcmp(ctx->qry.page, "plain"))
419 return cgit_plain_link(name, title, class, ctx->qry.head,
420 ctx->qry.has_sha1 ? ctx->qry.sha1 : NULL,
421 ctx->qry.path);
422 else if (!strcmp(ctx->qry.page, "log"))
423 return cgit_log_link(name, title, class, ctx->qry.head,
424 ctx->qry.has_sha1 ? ctx->qry.sha1 : NULL,
425 ctx->qry.path, ctx->qry.ofs,
426 ctx->qry.grep, ctx->qry.search,
427 ctx->qry.showmsg);
428 else if (!strcmp(ctx->qry.page, "commit"))
429 return cgit_commit_link(name, title, class, ctx->qry.head,
430 ctx->qry.has_sha1 ? ctx->qry.sha1 : NULL,
431 ctx->qry.path, 0);
432 else if (!strcmp(ctx->qry.page, "patch"))
433 return cgit_patch_link(name, title, class, ctx->qry.head,
434 ctx->qry.has_sha1 ? ctx->qry.sha1 : NULL,
435 ctx->qry.path);
436 else if (!strcmp(ctx->qry.page, "refs"))
437 return cgit_refs_link(name, title, class, ctx->qry.head,
438 ctx->qry.has_sha1 ? ctx->qry.sha1 : NULL,
439 ctx->qry.path);
440 else if (!strcmp(ctx->qry.page, "snapshot"))
441 return cgit_snapshot_link(name, title, class, ctx->qry.head,
442 ctx->qry.has_sha1 ? ctx->qry.sha1 : NULL,
443 ctx->qry.path);
444 else if (!strcmp(ctx->qry.page, "diff"))
445 return cgit_diff_link(name, title, class, ctx->qry.head,
446 ctx->qry.sha1, ctx->qry.sha2,
447 ctx->qry.path, 0);
448 else if (!strcmp(ctx->qry.page, "stats"))
449 return cgit_stats_link(name, title, class, ctx->qry.head,
450 ctx->qry.path);
451
452 /* Don't known how to make link for this page */
453 repolink(title, class, ctx->qry.page, ctx->qry.head, ctx->qry.path);
454 html("><!-- cgit_self_link() doesn't know how to make link for page '");
455 html_txt(ctx->qry.page);
456 html("' -->");
457 html_txt(name);
458 html("</a>");
459}
460
399void cgit_object_link(struct object *obj) 461void cgit_object_link(struct object *obj)
400{ 462{
401 char *page, *shortrev, *fullrev, *name; 463 char *page, *shortrev, *fullrev, *name;
402 464
403 fullrev = sha1_to_hex(obj->sha1); 465 fullrev = sha1_to_hex(obj->sha1);
404 shortrev = xstrdup(fullrev); 466 shortrev = xstrdup(fullrev);
405 shortrev[10] = '\0'; 467 shortrev[10] = '\0';
406 if (obj->type == OBJ_COMMIT) { 468 if (obj->type == OBJ_COMMIT) {
407 cgit_commit_link(fmt("commit %s...", shortrev), NULL, NULL, 469 cgit_commit_link(fmt("commit %s...", shortrev), NULL, NULL,
408 ctx.qry.head, fullrev, 0); 470 ctx.qry.head, fullrev, NULL, 0);
409 return; 471 return;
410 } else if (obj->type == OBJ_TREE) 472 } else if (obj->type == OBJ_TREE)
411 page = "tree"; 473 page = "tree";
412 else if (obj->type == OBJ_TAG) 474 else if (obj->type == OBJ_TAG)
413 page = "tag"; 475 page = "tag";
414 else 476 else
415 page = "blob"; 477 page = "blob";
416 name = fmt("%s %s...", typename(obj->type), shortrev); 478 name = fmt("%s %s...", typename(obj->type), shortrev);
417 reporevlink(page, name, NULL, NULL, ctx.qry.head, fullrev, NULL); 479 reporevlink(page, name, NULL, NULL, ctx.qry.head, fullrev, NULL);
418} 480}
419 481
420void cgit_print_date(time_t secs, char *format, int local_time) 482void cgit_print_date(time_t secs, const char *format, int local_time)
421{ 483{
422 char buf[64]; 484 char buf[64];
423 struct tm *time; 485 struct tm *time;
424 486
425 if (!secs) 487 if (!secs)
426 return; 488 return;
427 if(local_time) 489 if(local_time)
428 time = localtime(&secs); 490 time = localtime(&secs);
429 else 491 else
430 time = gmtime(&secs); 492 time = gmtime(&secs);
431 strftime(buf, sizeof(buf)-1, format, time); 493 strftime(buf, sizeof(buf)-1, format, time);
432 html_txt(buf); 494 html_txt(buf);
433} 495}
434 496
435void cgit_print_age(time_t t, time_t max_relative, char *format) 497void cgit_print_age(time_t t, time_t max_relative, const char *format)
436{ 498{
437 time_t now, secs; 499 time_t now, secs;
438 500
439 if (!t) 501 if (!t)
440 return; 502 return;
441 time(&now); 503 time(&now);
442 secs = now - t; 504 secs = now - t;
443 505
444 if (secs > max_relative && max_relative >= 0) { 506 if (secs > max_relative && max_relative >= 0) {
445 cgit_print_date(t, format, ctx.cfg.local_time); 507 cgit_print_date(t, format, ctx.cfg.local_time);
446 return; 508 return;
447 } 509 }
448 510
449 if (secs < TM_HOUR * 2) { 511 if (secs < TM_HOUR * 2) {
450 htmlf("<span class='age-mins'>%.0f min.</span>", 512 htmlf("<span class='age-mins'>%.0f min.</span>",
451 secs * 1.0 / TM_MIN); 513 secs * 1.0 / TM_MIN);
452 return; 514 return;
453 } 515 }
454 if (secs < TM_DAY * 2) { 516 if (secs < TM_DAY * 2) {
455 htmlf("<span class='age-hours'>%.0f hours</span>", 517 htmlf("<span class='age-hours'>%.0f hours</span>",
456 secs * 1.0 / TM_HOUR); 518 secs * 1.0 / TM_HOUR);
457 return; 519 return;
458 } 520 }
459 if (secs < TM_WEEK * 2) { 521 if (secs < TM_WEEK * 2) {
460 htmlf("<span class='age-days'>%.0f days</span>", 522 htmlf("<span class='age-days'>%.0f days</span>",
461 secs * 1.0 / TM_DAY); 523 secs * 1.0 / TM_DAY);
462 return; 524 return;
463 } 525 }
464 if (secs < TM_MONTH * 2) { 526 if (secs < TM_MONTH * 2) {
465 htmlf("<span class='age-weeks'>%.0f weeks</span>", 527 htmlf("<span class='age-weeks'>%.0f weeks</span>",
466 secs * 1.0 / TM_WEEK); 528 secs * 1.0 / TM_WEEK);
467 return; 529 return;
468 } 530 }
469 if (secs < TM_YEAR * 2) { 531 if (secs < TM_YEAR * 2) {
470 htmlf("<span class='age-months'>%.0f months</span>", 532 htmlf("<span class='age-months'>%.0f months</span>",
471 secs * 1.0 / TM_MONTH); 533 secs * 1.0 / TM_MONTH);
472 return; 534 return;
473 } 535 }
474 htmlf("<span class='age-years'>%.0f years</span>", 536 htmlf("<span class='age-years'>%.0f years</span>",
475 secs * 1.0 / TM_YEAR); 537 secs * 1.0 / TM_YEAR);
476} 538}
477 539
478void cgit_print_http_headers(struct cgit_context *ctx) 540void cgit_print_http_headers(struct cgit_context *ctx)
479{ 541{
480 if (ctx->env.no_http && !strcmp(ctx->env.no_http, "1")) 542 if (ctx->env.no_http && !strcmp(ctx->env.no_http, "1"))
481 return; 543 return;
482 544
483 if (ctx->page.status) 545 if (ctx->page.status)
484 htmlf("Status: %d %s\n", ctx->page.status, ctx->page.statusmsg); 546 htmlf("Status: %d %s\n", ctx->page.status, ctx->page.statusmsg);
485 if (ctx->page.mimetype && ctx->page.charset) 547 if (ctx->page.mimetype && ctx->page.charset)
486 htmlf("Content-Type: %s; charset=%s\n", ctx->page.mimetype, 548 htmlf("Content-Type: %s; charset=%s\n", ctx->page.mimetype,
487 ctx->page.charset); 549 ctx->page.charset);
488 else if (ctx->page.mimetype) 550 else if (ctx->page.mimetype)
489 htmlf("Content-Type: %s\n", ctx->page.mimetype); 551 htmlf("Content-Type: %s\n", ctx->page.mimetype);
490 if (ctx->page.size) 552 if (ctx->page.size)
491 htmlf("Content-Length: %ld\n", ctx->page.size); 553 htmlf("Content-Length: %ld\n", ctx->page.size);
492 if (ctx->page.filename) 554 if (ctx->page.filename)
493 htmlf("Content-Disposition: inline; filename=\"%s\"\n", 555 htmlf("Content-Disposition: inline; filename=\"%s\"\n",
494 ctx->page.filename); 556 ctx->page.filename);
495 htmlf("Last-Modified: %s\n", http_date(ctx->page.modified)); 557 htmlf("Last-Modified: %s\n", http_date(ctx->page.modified));
496 htmlf("Expires: %s\n", http_date(ctx->page.expires)); 558 htmlf("Expires: %s\n", http_date(ctx->page.expires));
497 if (ctx->page.etag) 559 if (ctx->page.etag)
498 htmlf("ETag: \"%s\"\n", ctx->page.etag); 560 htmlf("ETag: \"%s\"\n", ctx->page.etag);
499 html("\n"); 561 html("\n");
500 if (ctx->env.request_method && !strcmp(ctx->env.request_method, "HEAD")) 562 if (ctx->env.request_method && !strcmp(ctx->env.request_method, "HEAD"))
501 exit(0); 563 exit(0);
502} 564}
503 565
504void cgit_print_docstart(struct cgit_context *ctx) 566void cgit_print_docstart(struct cgit_context *ctx)
505{ 567{
506 if (ctx->cfg.embedded) { 568 if (ctx->cfg.embedded) {
507 if (ctx->cfg.header) 569 if (ctx->cfg.header)
508 html_include(ctx->cfg.header); 570 html_include(ctx->cfg.header);
509 return; 571 return;
510 } 572 }
511 573
512 char *host = cgit_hosturl(); 574 char *host = cgit_hosturl();
513 html(cgit_doctype); 575 html(cgit_doctype);
514 html("<html xmlns='http://www.w3.org/1999/xhtml' xml:lang='en' lang='en'>\n"); 576 html("<html xmlns='http://www.w3.org/1999/xhtml' xml:lang='en' lang='en'>\n");
515 html("<head>\n"); 577 html("<head>\n");
516 html("<title>"); 578 html("<title>");
517 html_txt(ctx->page.title); 579 html_txt(ctx->page.title);
518 html("</title>\n"); 580 html("</title>\n");
519 htmlf("<meta name='generator' content='cgit %s'/>\n", cgit_version); 581 htmlf("<meta name='generator' content='cgit %s'/>\n", cgit_version);
520 if (ctx->cfg.robots && *ctx->cfg.robots) 582 if (ctx->cfg.robots && *ctx->cfg.robots)
521 htmlf("<meta name='robots' content='%s'/>\n", ctx->cfg.robots); 583 htmlf("<meta name='robots' content='%s'/>\n", ctx->cfg.robots);
522 html("<link rel='stylesheet' type='text/css' href='"); 584 html("<link rel='stylesheet' type='text/css' href='");
523 html_attr(ctx->cfg.css); 585 html_attr(ctx->cfg.css);
524 html("'/>\n"); 586 html("'/>\n");
525 if (ctx->cfg.favicon) { 587 if (ctx->cfg.favicon) {
526 html("<link rel='shortcut icon' href='"); 588 html("<link rel='shortcut icon' href='");
527 html_attr(ctx->cfg.favicon); 589 html_attr(ctx->cfg.favicon);
528 html("'/>\n"); 590 html("'/>\n");
529 } 591 }
530 if (host && ctx->repo) { 592 if (host && ctx->repo) {
531 html("<link rel='alternate' title='Atom feed' href='"); 593 html("<link rel='alternate' title='Atom feed' href='");
532 html(cgit_httpscheme()); 594 html(cgit_httpscheme());
533 html_attr(cgit_hosturl()); 595 html_attr(cgit_hosturl());
534 html_attr(cgit_fileurl(ctx->repo->url, "atom", ctx->qry.path, 596 html_attr(cgit_fileurl(ctx->repo->url, "atom", ctx->qry.vpath,
535 fmt("h=%s", ctx->qry.head))); 597 fmt("h=%s", ctx->qry.head)));
536 html("' type='application/atom+xml'/>\n"); 598 html("' type='application/atom+xml'/>\n");
537 } 599 }
538 if (ctx->cfg.head_include) 600 if (ctx->cfg.head_include)
539 html_include(ctx->cfg.head_include); 601 html_include(ctx->cfg.head_include);
540 html("</head>\n"); 602 html("</head>\n");
541 html("<body>\n"); 603 html("<body>\n");
542 if (ctx->cfg.header) 604 if (ctx->cfg.header)
543 html_include(ctx->cfg.header); 605 html_include(ctx->cfg.header);
544} 606}
545 607
546void cgit_print_docend() 608void cgit_print_docend()
547{ 609{
548 html("</div> <!-- class=content -->\n"); 610 html("</div> <!-- class=content -->\n");
549 if (ctx.cfg.embedded) { 611 if (ctx.cfg.embedded) {
550 html("</div> <!-- id=cgit -->\n"); 612 html("</div> <!-- id=cgit -->\n");
551 if (ctx.cfg.footer) 613 if (ctx.cfg.footer)
552 html_include(ctx.cfg.footer); 614 html_include(ctx.cfg.footer);
553 return; 615 return;
554 } 616 }
555 if (ctx.cfg.footer) 617 if (ctx.cfg.footer)
556 html_include(ctx.cfg.footer); 618 html_include(ctx.cfg.footer);
557 else { 619 else {
558 htmlf("<div class='footer'>generated by cgit %s at ", 620 htmlf("<div class='footer'>generated by cgit %s at ",
559 cgit_version); 621 cgit_version);
560 cgit_print_date(time(NULL), FMT_LONGDATE, ctx.cfg.local_time); 622 cgit_print_date(time(NULL), FMT_LONGDATE, ctx.cfg.local_time);
561 html("</div>\n"); 623 html("</div>\n");
562 } 624 }
563 html("</div> <!-- id=cgit -->\n"); 625 html("</div> <!-- id=cgit -->\n");
564 html("</body>\n</html>\n"); 626 html("</body>\n</html>\n");
565} 627}
566 628
567int print_branch_option(const char *refname, const unsigned char *sha1, 629int print_branch_option(const char *refname, const unsigned char *sha1,
568 int flags, void *cb_data) 630 int flags, void *cb_data)
569{ 631{
570 char *name = (char *)refname; 632 char *name = (char *)refname;
571 html_option(name, name, ctx.qry.head); 633 html_option(name, name, ctx.qry.head);
572 return 0; 634 return 0;
573} 635}
574 636
575int print_archive_ref(const char *refname, const unsigned char *sha1, 637int print_archive_ref(const char *refname, const unsigned char *sha1,
576 int flags, void *cb_data) 638 int flags, void *cb_data)
577{ 639{
578 struct tag *tag; 640 struct tag *tag;
579 struct taginfo *info; 641 struct taginfo *info;
580 struct object *obj; 642 struct object *obj;
581 char buf[256], *url; 643 char buf[256], *url;
582 unsigned char fileid[20]; 644 unsigned char fileid[20];
583 int *header = (int *)cb_data; 645 int *header = (int *)cb_data;
584 646
585 if (prefixcmp(refname, "refs/archives")) 647 if (prefixcmp(refname, "refs/archives"))
586 return 0; 648 return 0;
587 strncpy(buf, refname+14, sizeof(buf)); 649 strncpy(buf, refname+14, sizeof(buf));
588 obj = parse_object(sha1); 650 obj = parse_object(sha1);
589 if (!obj) 651 if (!obj)
590 return 1; 652 return 1;
591 if (obj->type == OBJ_TAG) { 653 if (obj->type == OBJ_TAG) {
592 tag = lookup_tag(sha1); 654 tag = lookup_tag(sha1);
593 if (!tag || parse_tag(tag) || !(info = cgit_parse_tag(tag))) 655 if (!tag || parse_tag(tag) || !(info = cgit_parse_tag(tag)))
594 return 0; 656 return 0;
595 hashcpy(fileid, tag->tagged->sha1); 657 hashcpy(fileid, tag->tagged->sha1);
596 } else if (obj->type != OBJ_BLOB) { 658 } else if (obj->type != OBJ_BLOB) {
597 return 0; 659 return 0;
598 } else { 660 } else {
599 hashcpy(fileid, sha1); 661 hashcpy(fileid, sha1);
600 } 662 }
601 if (!*header) { 663 if (!*header) {
602 html("<h1>download</h1>\n"); 664 html("<h1>download</h1>\n");
603 *header = 1; 665 *header = 1;
604 } 666 }
605 url = cgit_pageurl(ctx.qry.repo, "blob", 667 url = cgit_pageurl(ctx.qry.repo, "blob",
606 fmt("id=%s&amp;path=%s", sha1_to_hex(fileid), 668 fmt("id=%s&amp;path=%s", sha1_to_hex(fileid),
607 buf)); 669 buf));
608 html_link_open(url, NULL, "menu"); 670 html_link_open(url, NULL, "menu");
609 html_txt(strlpart(buf, 20)); 671 html_txt(strlpart(buf, 20));
610 html_link_close(); 672 html_link_close();
611 return 0; 673 return 0;
612} 674}
613 675
614void cgit_add_hidden_formfields(int incl_head, int incl_search, char *page) 676void cgit_add_hidden_formfields(int incl_head, int incl_search,
677 const char *page)
615{ 678{
616 char *url; 679 char *url;
617 680
618 if (!ctx.cfg.virtual_root) { 681 if (!ctx.cfg.virtual_root) {
619 url = fmt("%s/%s", ctx.qry.repo, page); 682 url = fmt("%s/%s", ctx.qry.repo, page);
620 if (ctx.qry.path) 683 if (ctx.qry.vpath)
621 url = fmt("%s/%s", url, ctx.qry.path); 684 url = fmt("%s/%s", url, ctx.qry.vpath);
622 html_hidden("url", url); 685 html_hidden("url", url);
623 } 686 }
624 687
625 if (incl_head && ctx.qry.head && ctx.repo->defbranch && 688 if (incl_head && ctx.qry.head && ctx.repo->defbranch &&
626 strcmp(ctx.qry.head, ctx.repo->defbranch)) 689 strcmp(ctx.qry.head, ctx.repo->defbranch))
627 html_hidden("h", ctx.qry.head); 690 html_hidden("h", ctx.qry.head);
628 691
629 if (ctx.qry.sha1) 692 if (ctx.qry.sha1)
630 html_hidden("id", ctx.qry.sha1); 693 html_hidden("id", ctx.qry.sha1);
631 if (ctx.qry.sha2) 694 if (ctx.qry.sha2)
632 html_hidden("id2", ctx.qry.sha2); 695 html_hidden("id2", ctx.qry.sha2);
633 if (ctx.qry.showmsg) 696 if (ctx.qry.showmsg)
634 html_hidden("showmsg", "1"); 697 html_hidden("showmsg", "1");
635 698
636 if (incl_search) { 699 if (incl_search) {
637 if (ctx.qry.grep) 700 if (ctx.qry.grep)
638 html_hidden("qt", ctx.qry.grep); 701 html_hidden("qt", ctx.qry.grep);
639 if (ctx.qry.search) 702 if (ctx.qry.search)
640 html_hidden("q", ctx.qry.search); 703 html_hidden("q", ctx.qry.search);
641 } 704 }
642} 705}
643 706
644const char *fallback_cmd = "repolist"; 707static const char *hc(struct cgit_context *ctx, const char *page)
708{
709 return strcmp(ctx->qry.page, page) ? NULL : "active";
710}
645 711
646char *hc(struct cgit_cmd *cmd, const char *page) 712static void cgit_print_path_crumbs(struct cgit_context *ctx, char *path)
647{ 713{
648 return (strcmp(cmd ? cmd->name : fallback_cmd, page) ? NULL : "active"); 714 char *old_path = ctx->qry.path;
715 char *p = path, *q, *end = path + strlen(path);
716
717 ctx->qry.path = NULL;
718 cgit_self_link("root", NULL, NULL, ctx);
719 ctx->qry.path = p = path;
720 while (p < end) {
721 if (!(q = strchr(p, '/')))
722 q = end;
723 *q = '\0';
724 html_txt("/");
725 cgit_self_link(p, NULL, NULL, ctx);
726 if (q < end)
727 *q = '/';
728 p = q + 1;
729 }
730 ctx->qry.path = old_path;
649} 731}
650 732
651static void print_header(struct cgit_context *ctx) 733static void print_header(struct cgit_context *ctx)
652{ 734{
653 html("<table id='header'>\n"); 735 html("<table id='header'>\n");
654 html("<tr>\n"); 736 html("<tr>\n");
655 737
656 if (ctx->cfg.logo && ctx->cfg.logo[0] != 0) { 738 if (ctx->cfg.logo && ctx->cfg.logo[0] != 0) {
657 html("<td class='logo' rowspan='2'><a href='"); 739 html("<td class='logo' rowspan='2'><a href='");
658 if (ctx->cfg.logo_link) 740 if (ctx->cfg.logo_link)
659 html_attr(ctx->cfg.logo_link); 741 html_attr(ctx->cfg.logo_link);
660 else 742 else
661 html_attr(cgit_rooturl()); 743 html_attr(cgit_rooturl());
662 html("'><img src='"); 744 html("'><img src='");
663 html_attr(ctx->cfg.logo); 745 html_attr(ctx->cfg.logo);
664 html("' alt='cgit logo'/></a></td>\n"); 746 html("' alt='cgit logo'/></a></td>\n");
665 } 747 }
666 748
667 html("<td class='main'>"); 749 html("<td class='main'>");
668 if (ctx->repo) { 750 if (ctx->repo) {
669 cgit_index_link("index", NULL, NULL, NULL, 0); 751 cgit_index_link("index", NULL, NULL, NULL, 0);
670 html(" : "); 752 html(" : ");
671 cgit_summary_link(ctx->repo->name, ctx->repo->name, NULL, NULL); 753 cgit_summary_link(ctx->repo->name, ctx->repo->name, NULL, NULL);
672 html("</td><td class='form'>"); 754 html("</td><td class='form'>");
673 html("<form method='get' action=''>\n"); 755 html("<form method='get' action=''>\n");
674 cgit_add_hidden_formfields(0, 1, ctx->qry.page); 756 cgit_add_hidden_formfields(0, 1, ctx->qry.page);
675 html("<select name='h' onchange='this.form.submit();'>\n"); 757 html("<select name='h' onchange='this.form.submit();'>\n");
676 for_each_branch_ref(print_branch_option, ctx->qry.head); 758 for_each_branch_ref(print_branch_option, ctx->qry.head);
677 html("</select> "); 759 html("</select> ");
678 html("<input type='submit' name='' value='switch'/>"); 760 html("<input type='submit' name='' value='switch'/>");
679 html("</form>"); 761 html("</form>");
680 } else 762 } else
681 html_txt(ctx->cfg.root_title); 763 html_txt(ctx->cfg.root_title);
682 html("</td></tr>\n"); 764 html("</td></tr>\n");
683 765
684 html("<tr><td class='sub'>"); 766 html("<tr><td class='sub'>");
685 if (ctx->repo) { 767 if (ctx->repo) {
686 html_txt(ctx->repo->desc); 768 html_txt(ctx->repo->desc);
687 html("</td><td class='sub right'>"); 769 html("</td><td class='sub right'>");
688 html_txt(ctx->repo->owner); 770 html_txt(ctx->repo->owner);
689 } else { 771 } else {
690 if (ctx->cfg.root_desc) 772 if (ctx->cfg.root_desc)
691 html_txt(ctx->cfg.root_desc); 773 html_txt(ctx->cfg.root_desc);
692 else if (ctx->cfg.index_info) 774 else if (ctx->cfg.index_info)
693 html_include(ctx->cfg.index_info); 775 html_include(ctx->cfg.index_info);
694 } 776 }
695 html("</td></tr></table>\n"); 777 html("</td></tr></table>\n");
696} 778}
697 779
698void cgit_print_pageheader(struct cgit_context *ctx) 780void cgit_print_pageheader(struct cgit_context *ctx)
699{ 781{
700 struct cgit_cmd *cmd = cgit_get_cmd(ctx);
701
702 if (!cmd && ctx->repo)
703 fallback_cmd = "summary";
704
705 html("<div id='cgit'>"); 782 html("<div id='cgit'>");
706 if (!ctx->cfg.noheader) 783 if (!ctx->cfg.noheader)
707 print_header(ctx); 784 print_header(ctx);
708 785
709 html("<table class='tabs'><tr><td>\n"); 786 html("<table class='tabs'><tr><td>\n");
710 if (ctx->repo) { 787 if (ctx->repo) {
711 cgit_summary_link("summary", NULL, hc(cmd, "summary"), 788 cgit_summary_link("summary", NULL, hc(ctx, "summary"),
712 ctx->qry.head); 789 ctx->qry.head);
713 cgit_refs_link("refs", NULL, hc(cmd, "refs"), ctx->qry.head, 790 cgit_refs_link("refs", NULL, hc(ctx, "refs"), ctx->qry.head,
714 ctx->qry.sha1, NULL);
715 cgit_log_link("log", NULL, hc(cmd, "log"), ctx->qry.head,
716 NULL, NULL, 0, NULL, NULL, ctx->qry.showmsg);
717 cgit_tree_link("tree", NULL, hc(cmd, "tree"), ctx->qry.head,
718 ctx->qry.sha1, NULL); 791 ctx->qry.sha1, NULL);
719 cgit_commit_link("commit", NULL, hc(cmd, "commit"), 792 cgit_log_link("log", NULL, hc(ctx, "log"), ctx->qry.head,
720 ctx->qry.head, ctx->qry.sha1, 0); 793 NULL, ctx->qry.vpath, 0, NULL, NULL,
721 cgit_diff_link("diff", NULL, hc(cmd, "diff"), ctx->qry.head, 794 ctx->qry.showmsg);
722 ctx->qry.sha1, ctx->qry.sha2, NULL, 0); 795 cgit_tree_link("tree", NULL, hc(ctx, "tree"), ctx->qry.head,
796 ctx->qry.sha1, ctx->qry.vpath);
797 cgit_commit_link("commit", NULL, hc(ctx, "commit"),
798 ctx->qry.head, ctx->qry.sha1, ctx->qry.vpath, 0);
799 cgit_diff_link("diff", NULL, hc(ctx, "diff"), ctx->qry.head,
800 ctx->qry.sha1, ctx->qry.sha2, ctx->qry.vpath, 0);
723 if (ctx->repo->max_stats) 801 if (ctx->repo->max_stats)
724 cgit_stats_link("stats", NULL, hc(cmd, "stats"), 802 cgit_stats_link("stats", NULL, hc(ctx, "stats"),
725 ctx->qry.head, NULL); 803 ctx->qry.head, ctx->qry.vpath);
726 if (ctx->repo->readme) 804 if (ctx->repo->readme)
727 reporevlink("about", "about", NULL, 805 reporevlink("about", "about", NULL,
728 hc(cmd, "about"), ctx->qry.head, NULL, 806 hc(ctx, "about"), ctx->qry.head, NULL,
729 NULL); 807 NULL);
730 html("</td><td class='form'>"); 808 html("</td><td class='form'>");
731 html("<form class='right' method='get' action='"); 809 html("<form class='right' method='get' action='");
732 if (ctx->cfg.virtual_root) 810 if (ctx->cfg.virtual_root)
733 html_url_path(cgit_fileurl(ctx->qry.repo, "log", 811 html_url_path(cgit_fileurl(ctx->qry.repo, "log",
734 ctx->qry.path, NULL)); 812 ctx->qry.vpath, NULL));
735 html("'>\n"); 813 html("'>\n");
736 cgit_add_hidden_formfields(1, 0, "log"); 814 cgit_add_hidden_formfields(1, 0, "log");
737 html("<select name='qt'>\n"); 815 html("<select name='qt'>\n");
738 html_option("grep", "log msg", ctx->qry.grep); 816 html_option("grep", "log msg", ctx->qry.grep);
739 html_option("author", "author", ctx->qry.grep); 817 html_option("author", "author", ctx->qry.grep);
740 html_option("committer", "committer", ctx->qry.grep); 818 html_option("committer", "committer", ctx->qry.grep);
741 html("</select>\n"); 819 html("</select>\n");
742 html("<input class='txt' type='text' size='10' name='q' value='"); 820 html("<input class='txt' type='text' size='10' name='q' value='");
743 html_attr(ctx->qry.search); 821 html_attr(ctx->qry.search);
744 html("'/>\n"); 822 html("'/>\n");
745 html("<input type='submit' value='search'/>\n"); 823 html("<input type='submit' value='search'/>\n");
746 html("</form>\n"); 824 html("</form>\n");
747 } else { 825 } else {
748 site_link(NULL, "index", NULL, hc(cmd, "repolist"), NULL, 0); 826 site_link(NULL, "index", NULL, hc(ctx, "repolist"), NULL, 0);
749 if (ctx->cfg.root_readme) 827 if (ctx->cfg.root_readme)
750 site_link("about", "about", NULL, hc(cmd, "about"), 828 site_link("about", "about", NULL, hc(ctx, "about"),
751 NULL, 0); 829 NULL, 0);
752 html("</td><td class='form'>"); 830 html("</td><td class='form'>");
753 html("<form method='get' action='"); 831 html("<form method='get' action='");
754 html_attr(cgit_rooturl()); 832 html_attr(cgit_rooturl());
755 html("'>\n"); 833 html("'>\n");
756 html("<input type='text' name='q' size='10' value='"); 834 html("<input type='text' name='q' size='10' value='");
757 html_attr(ctx->qry.search); 835 html_attr(ctx->qry.search);
758 html("'/>\n"); 836 html("'/>\n");
759 html("<input type='submit' value='search'/>\n"); 837 html("<input type='submit' value='search'/>\n");
760 html("</form>"); 838 html("</form>");
761 } 839 }
762 html("</td></tr></table>\n"); 840 html("</td></tr></table>\n");
841 if (ctx->qry.vpath) {
842 html("<div class='path'>");
843 html("path: ");
844 cgit_print_path_crumbs(ctx, ctx->qry.vpath);
845 html("</div>");
846 }
763 html("<div class='content'>"); 847 html("<div class='content'>");
764} 848}
765 849
766void cgit_print_filemode(unsigned short mode) 850void cgit_print_filemode(unsigned short mode)
767{ 851{
768 if (S_ISDIR(mode)) 852 if (S_ISDIR(mode))
769 html("d"); 853 html("d");
770 else if (S_ISLNK(mode)) 854 else if (S_ISLNK(mode))
771 html("l"); 855 html("l");
772 else if (S_ISGITLINK(mode)) 856 else if (S_ISGITLINK(mode))
773 html("m"); 857 html("m");
774 else 858 else
775 html("-"); 859 html("-");
776 html_fileperm(mode >> 6); 860 html_fileperm(mode >> 6);
777 html_fileperm(mode >> 3); 861 html_fileperm(mode >> 3);
778 html_fileperm(mode); 862 html_fileperm(mode);
779} 863}
780 864
781void cgit_print_snapshot_links(const char *repo, const char *head, 865void cgit_print_snapshot_links(const char *repo, const char *head,
782 const char *hex, int snapshots) 866 const char *hex, int snapshots)
783{ 867{
784 const struct cgit_snapshot_format* f; 868 const struct cgit_snapshot_format* f;
785 char *prefix; 869 char *prefix;
786 char *filename; 870 char *filename;
787 unsigned char sha1[20]; 871 unsigned char sha1[20];
788 872
789 if (get_sha1(fmt("refs/tags/%s", hex), sha1) == 0 && 873 if (get_sha1(fmt("refs/tags/%s", hex), sha1) == 0 &&
790 (hex[0] == 'v' || hex[0] == 'V') && isdigit(hex[1])) 874 (hex[0] == 'v' || hex[0] == 'V') && isdigit(hex[1]))
791 hex++; 875 hex++;
792 prefix = xstrdup(fmt("%s-%s", cgit_repobasename(repo), hex)); 876 prefix = xstrdup(fmt("%s-%s", cgit_repobasename(repo), hex));
793 for (f = cgit_snapshot_formats; f->suffix; f++) { 877 for (f = cgit_snapshot_formats; f->suffix; f++) {
794 if (!(snapshots & f->bit)) 878 if (!(snapshots & f->bit))
795 continue; 879 continue;
796 filename = fmt("%s%s", prefix, f->suffix); 880 filename = fmt("%s%s", prefix, f->suffix);
797 cgit_snapshot_link(filename, NULL, NULL, NULL, NULL, filename); 881 cgit_snapshot_link(filename, NULL, NULL, NULL, NULL, filename);
798 html("<br/>"); 882 html("<br/>");
799 } 883 }
800} 884}
diff --git a/ui-shared.h b/ui-shared.h
index 9ebc1f9..3cc1258 100644
--- a/ui-shared.h
+++ b/ui-shared.h
@@ -1,52 +1,66 @@
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_httpscheme(); 4extern char *cgit_httpscheme();
5extern char *cgit_hosturl(); 5extern char *cgit_hosturl();
6extern char *cgit_rooturl(); 6extern char *cgit_rooturl();
7extern char *cgit_repourl(const char *reponame); 7extern char *cgit_repourl(const char *reponame);
8extern char *cgit_fileurl(const char *reponame, const char *pagename, 8extern char *cgit_fileurl(const char *reponame, const char *pagename,
9 const char *filename, const char *query); 9 const char *filename, const char *query);
10extern char *cgit_pageurl(const char *reponame, const char *pagename, 10extern char *cgit_pageurl(const char *reponame, const char *pagename,
11 const char *query); 11 const char *query);
12 12
13extern void cgit_index_link(char *name, char *title, char *class, 13extern void cgit_index_link(const char *name, const char *title,
14 char *pattern, int ofs); 14 const char *class, const char *pattern, int ofs);
15extern void cgit_summary_link(char *name, char *title, char *class, char *head); 15extern void cgit_summary_link(const char *name, const char *title,
16extern void cgit_tag_link(char *name, char *title, char *class, char *head, 16 const char *class, const char *head);
17 char *rev); 17extern void cgit_tag_link(const char *name, const char *title,
18extern void cgit_tree_link(char *name, char *title, char *class, char *head, 18 const char *class, const char *head,
19 char *rev, char *path); 19 const char *rev);
20extern void cgit_plain_link(char *name, char *title, char *class, char *head, 20extern void cgit_tree_link(const char *name, const char *title,
21 char *rev, char *path); 21 const char *class, const char *head,
22extern void cgit_log_link(char *name, char *title, char *class, char *head, 22 const char *rev, const char *path);
23 char *rev, char *path, int ofs, char *grep, 23extern void cgit_plain_link(const char *name, const char *title,
24 char *pattern, int showmsg); 24 const char *class, const char *head,
25extern void cgit_commit_link(char *name, char *title, char *class, char *head, 25 const char *rev, const char *path);
26 char *rev, int toggle_ssdiff); 26extern void cgit_log_link(const char *name, const char *title,
27extern void cgit_patch_link(char *name, char *title, char *class, char *head, 27 const char *class, const char *head, const char *rev,
28 char *rev); 28 const char *path, int ofs, const char *grep,
29extern void cgit_refs_link(char *name, char *title, char *class, char *head, 29 const char *pattern, int showmsg);
30 char *rev, char *path); 30extern void cgit_commit_link(char *name, const char *title,
31extern void cgit_snapshot_link(char *name, char *title, char *class, 31 const char *class, const char *head,
32 char *head, char *rev, char *archivename); 32 const char *rev, const char *path,
33extern void cgit_diff_link(char *name, char *title, char *class, char *head, 33 int toggle_ssdiff);
34 char *new_rev, char *old_rev, char *path, 34extern void cgit_patch_link(const char *name, const char *title,
35 int toggle_ssdiff); 35 const char *class, const char *head,
36extern void cgit_stats_link(char *name, char *title, char *class, char *head, 36 const char *rev, const char *path);
37 char *path); 37extern void cgit_refs_link(const char *name, const char *title,
38 const char *class, const char *head,
39 const char *rev, const char *path);
40extern void cgit_snapshot_link(const char *name, const char *title,
41 const char *class, const char *head,
42 const char *rev, const char *archivename);
43extern void cgit_diff_link(const char *name, const char *title,
44 const char *class, const char *head,
45 const char *new_rev, const char *old_rev,
46 const char *path, int toggle_ssdiff);
47extern void cgit_stats_link(const char *name, const char *title,
48 const char *class, const char *head,
49 const char *path);
50extern void cgit_self_link(char *name, const char *title,
51 const char *class, struct cgit_context *ctx);
38extern void cgit_object_link(struct object *obj); 52extern void cgit_object_link(struct object *obj);
39 53
40extern void cgit_print_error(char *msg); 54extern void cgit_print_error(const char *msg);
41extern void cgit_print_date(time_t secs, char *format, int local_time); 55extern void cgit_print_date(time_t secs, const char *format, int local_time);
42extern void cgit_print_age(time_t t, time_t max_relative, char *format); 56extern void cgit_print_age(time_t t, time_t max_relative, const char *format);
43extern void cgit_print_http_headers(struct cgit_context *ctx); 57extern void cgit_print_http_headers(struct cgit_context *ctx);
44extern void cgit_print_docstart(struct cgit_context *ctx); 58extern void cgit_print_docstart(struct cgit_context *ctx);
45extern void cgit_print_docend(); 59extern void cgit_print_docend();
46extern void cgit_print_pageheader(struct cgit_context *ctx); 60extern void cgit_print_pageheader(struct cgit_context *ctx);
47extern void cgit_print_filemode(unsigned short mode); 61extern void cgit_print_filemode(unsigned short mode);
48extern void cgit_print_snapshot_links(const char *repo, const char *head, 62extern void cgit_print_snapshot_links(const char *repo, const char *head,
49 const char *hex, int snapshots); 63 const char *hex, int snapshots);
50extern void cgit_add_hidden_formfields(int incl_head, int incl_search, 64extern void cgit_add_hidden_formfields(int incl_head, int incl_search,
51 char *page); 65 const char *page);
52#endif /* UI_SHARED_H */ 66#endif /* UI_SHARED_H */
diff --git a/ui-tree.c b/ui-tree.c
index 0ee38f2..75ec9cb 100644
--- a/ui-tree.c
+++ b/ui-tree.c
@@ -1,293 +1,282 @@
1/* ui-tree.c: functions for tree output 1/* ui-tree.c: functions for tree output
2 * 2 *
3 * Copyright (C) 2006 Lars Hjemli 3 * Copyright (C) 2006 Lars Hjemli
4 * 4 *
5 * Licensed under GNU General Public License v2 5 * Licensed under GNU General Public License v2
6 * (see COPYING for full license text) 6 * (see COPYING for full license text)
7 */ 7 */
8 8
9#include <ctype.h> 9#include <ctype.h>
10#include "cgit.h" 10#include "cgit.h"
11#include "html.h" 11#include "html.h"
12#include "ui-shared.h" 12#include "ui-shared.h"
13 13
14char *curr_rev; 14char *curr_rev;
15char *match_path; 15char *match_path;
16int header = 0; 16int header = 0;
17 17
18static void print_text_buffer(const char *name, char *buf, unsigned long size) 18static void print_text_buffer(const char *name, char *buf, unsigned long size)
19{ 19{
20 unsigned long lineno, idx; 20 unsigned long lineno, idx;
21 const char *numberfmt = 21 const char *numberfmt =
22 "<a class='no' id='n%1$d' name='n%1$d' href='#n%1$d'>%1$d</a>\n"; 22 "<a class='no' id='n%1$d' name='n%1$d' href='#n%1$d'>%1$d</a>\n";
23 23
24 html("<table summary='blob content' class='blob'>\n"); 24 html("<table summary='blob content' class='blob'>\n");
25 25
26 if (ctx.cfg.enable_tree_linenumbers) { 26 if (ctx.cfg.enable_tree_linenumbers) {
27 html("<tr><td class='linenumbers'><pre>"); 27 html("<tr><td class='linenumbers'><pre>");
28 idx = 0; 28 idx = 0;
29 lineno = 0; 29 lineno = 0;
30 30
31 if (size) { 31 if (size) {
32 htmlf(numberfmt, ++lineno); 32 htmlf(numberfmt, ++lineno);
33 while(idx < size - 1) { // skip absolute last newline 33 while(idx < size - 1) { // skip absolute last newline
34 if (buf[idx] == '\n') 34 if (buf[idx] == '\n')
35 htmlf(numberfmt, ++lineno); 35 htmlf(numberfmt, ++lineno);
36 idx++; 36 idx++;
37 } 37 }
38 } 38 }
39 html("</pre></td>\n"); 39 html("</pre></td>\n");
40 } 40 }
41 else { 41 else {
42 html("<tr>\n"); 42 html("<tr>\n");
43 } 43 }
44 44
45 if (ctx.repo->source_filter) { 45 if (ctx.repo->source_filter) {
46 html("<td class='lines'><pre><code>"); 46 html("<td class='lines'><pre><code>");
47 ctx.repo->source_filter->argv[1] = xstrdup(name); 47 ctx.repo->source_filter->argv[1] = xstrdup(name);
48 cgit_open_filter(ctx.repo->source_filter); 48 cgit_open_filter(ctx.repo->source_filter);
49 write(STDOUT_FILENO, buf, size); 49 write(STDOUT_FILENO, buf, size);
50 cgit_close_filter(ctx.repo->source_filter); 50 cgit_close_filter(ctx.repo->source_filter);
51 html("</code></pre></td></tr></table>\n"); 51 html("</code></pre></td></tr></table>\n");
52 return; 52 return;
53 } 53 }
54 54
55 html("<td class='lines'><pre><code>"); 55 html("<td class='lines'><pre><code>");
56 html_txt(buf); 56 html_txt(buf);
57 html("</code></pre></td></tr></table>\n"); 57 html("</code></pre></td></tr></table>\n");
58} 58}
59 59
60#define ROWLEN 32 60#define ROWLEN 32
61 61
62static void print_binary_buffer(char *buf, unsigned long size) 62static void print_binary_buffer(char *buf, unsigned long size)
63{ 63{
64 unsigned long ofs, idx; 64 unsigned long ofs, idx;
65 static char ascii[ROWLEN + 1]; 65 static char ascii[ROWLEN + 1];
66 66
67 html("<table summary='blob content' class='bin-blob'>\n"); 67 html("<table summary='blob content' class='bin-blob'>\n");
68 html("<tr><th>ofs</th><th>hex dump</th><th>ascii</th></tr>"); 68 html("<tr><th>ofs</th><th>hex dump</th><th>ascii</th></tr>");
69 for (ofs = 0; ofs < size; ofs += ROWLEN, buf += ROWLEN) { 69 for (ofs = 0; ofs < size; ofs += ROWLEN, buf += ROWLEN) {
70 htmlf("<tr><td class='right'>%04x</td><td class='hex'>", ofs); 70 htmlf("<tr><td class='right'>%04x</td><td class='hex'>", ofs);
71 for (idx = 0; idx < ROWLEN && ofs + idx < size; idx++) 71 for (idx = 0; idx < ROWLEN && ofs + idx < size; idx++)
72 htmlf("%*s%02x", 72 htmlf("%*s%02x",
73 idx == 16 ? 4 : 1, "", 73 idx == 16 ? 4 : 1, "",
74 buf[idx] & 0xff); 74 buf[idx] & 0xff);
75 html(" </td><td class='hex'>"); 75 html(" </td><td class='hex'>");
76 for (idx = 0; idx < ROWLEN && ofs + idx < size; idx++) 76 for (idx = 0; idx < ROWLEN && ofs + idx < size; idx++)
77 ascii[idx] = isgraph(buf[idx]) ? buf[idx] : '.'; 77 ascii[idx] = isgraph(buf[idx]) ? buf[idx] : '.';
78 ascii[idx] = '\0'; 78 ascii[idx] = '\0';
79 html_txt(ascii); 79 html_txt(ascii);
80 html("</td></tr>\n"); 80 html("</td></tr>\n");
81 } 81 }
82 html("</table>\n"); 82 html("</table>\n");
83} 83}
84 84
85static void print_object(const unsigned char *sha1, char *path, const char *basename) 85static void print_object(const unsigned char *sha1, char *path, const char *basename)
86{ 86{
87 enum object_type type; 87 enum object_type type;
88 char *buf; 88 char *buf;
89 unsigned long size; 89 unsigned long size;
90 90
91 type = sha1_object_info(sha1, &size); 91 type = sha1_object_info(sha1, &size);
92 if (type == OBJ_BAD) { 92 if (type == OBJ_BAD) {
93 cgit_print_error(fmt("Bad object name: %s", 93 cgit_print_error(fmt("Bad object name: %s",
94 sha1_to_hex(sha1))); 94 sha1_to_hex(sha1)));
95 return; 95 return;
96 } 96 }
97 97
98 buf = read_sha1_file(sha1, &type, &size); 98 buf = read_sha1_file(sha1, &type, &size);
99 if (!buf) { 99 if (!buf) {
100 cgit_print_error(fmt("Error reading object %s", 100 cgit_print_error(fmt("Error reading object %s",
101 sha1_to_hex(sha1))); 101 sha1_to_hex(sha1)));
102 return; 102 return;
103 } 103 }
104 104
105 html(" ("); 105 htmlf("blob: %s (", sha1_to_hex(sha1));
106 cgit_plain_link("plain", NULL, NULL, ctx.qry.head, 106 cgit_plain_link("plain", NULL, NULL, ctx.qry.head,
107 curr_rev, path); 107 curr_rev, path);
108 htmlf(")<br/>blob: %s\n", sha1_to_hex(sha1)); 108 html(")\n");
109 109
110 if (ctx.cfg.max_blob_size && size / 1024 > ctx.cfg.max_blob_size) { 110 if (ctx.cfg.max_blob_size && size / 1024 > ctx.cfg.max_blob_size) {
111 htmlf("<div class='error'>blob size (%dKB) exceeds display size limit (%dKB).</div>", 111 htmlf("<div class='error'>blob size (%dKB) exceeds display size limit (%dKB).</div>",
112 size / 1024, ctx.cfg.max_blob_size); 112 size / 1024, ctx.cfg.max_blob_size);
113 return; 113 return;
114 } 114 }
115 115
116 if (buffer_is_binary(buf, size)) 116 if (buffer_is_binary(buf, size))
117 print_binary_buffer(buf, size); 117 print_binary_buffer(buf, size);
118 else 118 else
119 print_text_buffer(basename, buf, size); 119 print_text_buffer(basename, buf, size);
120} 120}
121 121
122 122
123static int ls_item(const unsigned char *sha1, const char *base, int baselen, 123static int ls_item(const unsigned char *sha1, const char *base, int baselen,
124 const char *pathname, unsigned int mode, int stage, 124 const char *pathname, unsigned int mode, int stage,
125 void *cbdata) 125 void *cbdata)
126{ 126{
127 char *name; 127 char *name;
128 char *fullpath; 128 char *fullpath;
129 char *class; 129 char *class;
130 enum object_type type; 130 enum object_type type;
131 unsigned long size = 0; 131 unsigned long size = 0;
132 132
133 name = xstrdup(pathname); 133 name = xstrdup(pathname);
134 fullpath = fmt("%s%s%s", ctx.qry.path ? ctx.qry.path : "", 134 fullpath = fmt("%s%s%s", ctx.qry.path ? ctx.qry.path : "",
135 ctx.qry.path ? "/" : "", name); 135 ctx.qry.path ? "/" : "", name);
136 136
137 if (!S_ISGITLINK(mode)) { 137 if (!S_ISGITLINK(mode)) {
138 type = sha1_object_info(sha1, &size); 138 type = sha1_object_info(sha1, &size);
139 if (type == OBJ_BAD) { 139 if (type == OBJ_BAD) {
140 htmlf("<tr><td colspan='3'>Bad object: %s %s</td></tr>", 140 htmlf("<tr><td colspan='3'>Bad object: %s %s</td></tr>",
141 name, 141 name,
142 sha1_to_hex(sha1)); 142 sha1_to_hex(sha1));
143 return 0; 143 return 0;
144 } 144 }
145 } 145 }
146 146
147 html("<tr><td class='ls-mode'>"); 147 html("<tr><td class='ls-mode'>");
148 cgit_print_filemode(mode); 148 cgit_print_filemode(mode);
149 html("</td><td>"); 149 html("</td><td>");
150 if (S_ISGITLINK(mode)) { 150 if (S_ISGITLINK(mode)) {
151 htmlf("<a class='ls-mod' href='"); 151 htmlf("<a class='ls-mod' href='");
152 html_attr(fmt(ctx.repo->module_link, 152 html_attr(fmt(ctx.repo->module_link,
153 name, 153 name,
154 sha1_to_hex(sha1))); 154 sha1_to_hex(sha1)));
155 html("'>"); 155 html("'>");
156 html_txt(name); 156 html_txt(name);
157 html("</a>"); 157 html("</a>");
158 } else if (S_ISDIR(mode)) { 158 } else if (S_ISDIR(mode)) {
159 cgit_tree_link(name, NULL, "ls-dir", ctx.qry.head, 159 cgit_tree_link(name, NULL, "ls-dir", ctx.qry.head,
160 curr_rev, fullpath); 160 curr_rev, fullpath);
161 } else { 161 } else {
162 class = strrchr(name, '.'); 162 class = strrchr(name, '.');
163 if (class != NULL) { 163 if (class != NULL) {
164 class = fmt("ls-blob %s", class + 1); 164 class = fmt("ls-blob %s", class + 1);
165 } else 165 } else
166 class = "ls-blob"; 166 class = "ls-blob";
167 cgit_tree_link(name, NULL, class, ctx.qry.head, 167 cgit_tree_link(name, NULL, class, ctx.qry.head,
168 curr_rev, fullpath); 168 curr_rev, fullpath);
169 } 169 }
170 htmlf("</td><td class='ls-size'>%li</td>", size); 170 htmlf("</td><td class='ls-size'>%li</td>", size);
171 171
172 html("<td>"); 172 html("<td>");
173 cgit_log_link("log", NULL, "button", ctx.qry.head, curr_rev, 173 cgit_log_link("log", NULL, "button", ctx.qry.head, curr_rev,
174 fullpath, 0, NULL, NULL, ctx.qry.showmsg); 174 fullpath, 0, NULL, NULL, ctx.qry.showmsg);
175 if (ctx.repo->max_stats) 175 if (ctx.repo->max_stats)
176 cgit_stats_link("stats", NULL, "button", ctx.qry.head, 176 cgit_stats_link("stats", NULL, "button", ctx.qry.head,
177 fullpath); 177 fullpath);
178 cgit_plain_link("plain", NULL, "button", ctx.qry.head, curr_rev, 178 cgit_plain_link("plain", NULL, "button", ctx.qry.head, curr_rev,
179 fullpath); 179 fullpath);
180 html("</td></tr>\n"); 180 html("</td></tr>\n");
181 free(name); 181 free(name);
182 return 0; 182 return 0;
183} 183}
184 184
185static void ls_head() 185static void ls_head()
186{ 186{
187 html("<table summary='tree listing' class='list'>\n"); 187 html("<table summary='tree listing' class='list'>\n");
188 html("<tr class='nohover'>"); 188 html("<tr class='nohover'>");
189 html("<th class='left'>Mode</th>"); 189 html("<th class='left'>Mode</th>");
190 html("<th class='left'>Name</th>"); 190 html("<th class='left'>Name</th>");
191 html("<th class='right'>Size</th>"); 191 html("<th class='right'>Size</th>");
192 html("<th/>"); 192 html("<th/>");
193 html("</tr>\n"); 193 html("</tr>\n");
194 header = 1; 194 header = 1;
195} 195}
196 196
197static void ls_tail() 197static void ls_tail()
198{ 198{
199 if (!header) 199 if (!header)
200 return; 200 return;
201 html("</table>\n"); 201 html("</table>\n");
202 header = 0; 202 header = 0;
203} 203}
204 204
205static void ls_tree(const unsigned char *sha1, char *path) 205static void ls_tree(const unsigned char *sha1, char *path)
206{ 206{
207 struct tree *tree; 207 struct tree *tree;
208 208
209 tree = parse_tree_indirect(sha1); 209 tree = parse_tree_indirect(sha1);
210 if (!tree) { 210 if (!tree) {
211 cgit_print_error(fmt("Not a tree object: %s", 211 cgit_print_error(fmt("Not a tree object: %s",
212 sha1_to_hex(sha1))); 212 sha1_to_hex(sha1)));
213 return; 213 return;
214 } 214 }
215 215
216 ls_head(); 216 ls_head();
217 read_tree_recursive(tree, "", 0, 1, NULL, ls_item, NULL); 217 read_tree_recursive(tree, "", 0, 1, NULL, ls_item, NULL);
218 ls_tail(); 218 ls_tail();
219} 219}
220 220
221 221
222static int walk_tree(const unsigned char *sha1, const char *base, int baselen, 222static int walk_tree(const unsigned char *sha1, const char *base, int baselen,
223 const char *pathname, unsigned mode, int stage, 223 const char *pathname, unsigned mode, int stage,
224 void *cbdata) 224 void *cbdata)
225{ 225{
226 static int state; 226 static int state;
227 static char buffer[PATH_MAX]; 227 static char buffer[PATH_MAX];
228 char *url;
229 228
230 if (state == 0) { 229 if (state == 0) {
231 memcpy(buffer, base, baselen); 230 memcpy(buffer, base, baselen);
232 strcpy(buffer+baselen, pathname); 231 strcpy(buffer+baselen, pathname);
233 url = cgit_pageurl(ctx.qry.repo, "tree",
234 fmt("h=%s&amp;path=%s", curr_rev, buffer));
235 html("/");
236 cgit_tree_link(xstrdup(pathname), NULL, NULL, ctx.qry.head,
237 curr_rev, buffer);
238
239 if (strcmp(match_path, buffer)) 232 if (strcmp(match_path, buffer))
240 return READ_TREE_RECURSIVE; 233 return READ_TREE_RECURSIVE;
241 234
242 if (S_ISDIR(mode)) { 235 if (S_ISDIR(mode)) {
243 state = 1; 236 state = 1;
244 ls_head(); 237 ls_head();
245 return READ_TREE_RECURSIVE; 238 return READ_TREE_RECURSIVE;
246 } else { 239 } else {
247 print_object(sha1, buffer, pathname); 240 print_object(sha1, buffer, pathname);
248 return 0; 241 return 0;
249 } 242 }
250 } 243 }
251 ls_item(sha1, base, baselen, pathname, mode, stage, NULL); 244 ls_item(sha1, base, baselen, pathname, mode, stage, NULL);
252 return 0; 245 return 0;
253} 246}
254 247
255 248
256/* 249/*
257 * Show a tree or a blob 250 * Show a tree or a blob
258 * rev: the commit pointing at the root tree object 251 * rev: the commit pointing at the root tree object
259 * path: path to tree or blob 252 * path: path to tree or blob
260 */ 253 */
261void cgit_print_tree(const char *rev, char *path) 254void cgit_print_tree(const char *rev, char *path)
262{ 255{
263 unsigned char sha1[20]; 256 unsigned char sha1[20];
264 struct commit *commit; 257 struct commit *commit;
265 const char *paths[] = {path, NULL}; 258 const char *paths[] = {path, NULL};
266 259
267 if (!rev) 260 if (!rev)
268 rev = ctx.qry.head; 261 rev = ctx.qry.head;
269 262
270 curr_rev = xstrdup(rev); 263 curr_rev = xstrdup(rev);
271 if (get_sha1(rev, sha1)) { 264 if (get_sha1(rev, sha1)) {
272 cgit_print_error(fmt("Invalid revision name: %s", rev)); 265 cgit_print_error(fmt("Invalid revision name: %s", rev));
273 return; 266 return;
274 } 267 }
275 commit = lookup_commit_reference(sha1); 268 commit = lookup_commit_reference(sha1);
276 if (!commit || parse_commit(commit)) { 269 if (!commit || parse_commit(commit)) {
277 cgit_print_error(fmt("Invalid commit reference: %s", rev)); 270 cgit_print_error(fmt("Invalid commit reference: %s", rev));
278 return; 271 return;
279 } 272 }
280 273
281 html("path: <a href='");
282 html_attr(cgit_pageurl(ctx.qry.repo, "tree", fmt("h=%s", rev)));
283 html("'>root</a>");
284
285 if (path == NULL) { 274 if (path == NULL) {
286 ls_tree(commit->tree->object.sha1, NULL); 275 ls_tree(commit->tree->object.sha1, NULL);
287 return; 276 return;
288 } 277 }
289 278
290 match_path = path; 279 match_path = path;
291 read_tree_recursive(commit->tree, "", 0, 0, paths, walk_tree, NULL); 280 read_tree_recursive(commit->tree, "", 0, 0, paths, walk_tree, NULL);
292 ls_tail(); 281 ls_tail();
293} 282}