summaryrefslogtreecommitdiffabout
authorJohan Herland <johan@herland.net>2010-06-10 18:15:27 (UTC)
committer Lars Hjemli <hjemli@gmail.com>2010-06-19 11:00:07 (UTC)
commit6180e6169d6e87a3bea7e4da835dca17f93e5cfd (patch) (unidiff)
tree7caa053b419c75081179cc4a6056ce6533d01d99
parenta2cbd3c30b64a26b52b2003ba6569f3c083f4092 (diff)
downloadcgit-6180e6169d6e87a3bea7e4da835dca17f93e5cfd.zip
cgit-6180e6169d6e87a3bea7e4da835dca17f93e5cfd.tar.gz
cgit-6180e6169d6e87a3bea7e4da835dca17f93e5cfd.tar.bz2
Add URL parameter 'context' for changing the number of context lines in diffs
The new ctx.qry.context variable is picked up by cgit_print_diff(), and passed via cgit_diff_files() to Git's diff machinery. Signed-off-by: Johan Herland <johan@herland.net> Signed-off-by: Lars Hjemli <hjemli@gmail.com>
Diffstat (more/less context) (ignore whitespace changes)
-rw-r--r--cgit.c2
-rw-r--r--cgit.h3
-rw-r--r--shared.c5
-rw-r--r--ui-diff.c4
-rw-r--r--ui-log.c2
-rw-r--r--ui-patch.c2
6 files changed, 11 insertions, 7 deletions
diff --git a/cgit.c b/cgit.c
index 2c3ad73..e9bafb5 100644
--- a/cgit.c
+++ b/cgit.c
@@ -1,738 +1,740 @@
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, "max-stats")) 65 else if (!strcmp(name, "max-stats"))
66 repo->max_stats = cgit_find_stats_period(value, NULL); 66 repo->max_stats = cgit_find_stats_period(value, NULL);
67 else if (!strcmp(name, "module-link")) 67 else if (!strcmp(name, "module-link"))
68 repo->module_link= xstrdup(value); 68 repo->module_link= xstrdup(value);
69 else if (!strcmp(name, "section")) 69 else if (!strcmp(name, "section"))
70 repo->section = xstrdup(value); 70 repo->section = xstrdup(value);
71 else if (!strcmp(name, "readme") && value != NULL) { 71 else if (!strcmp(name, "readme") && value != NULL) {
72 if (*value == '/') 72 if (*value == '/')
73 repo->readme = xstrdup(value); 73 repo->readme = xstrdup(value);
74 else 74 else
75 repo->readme = xstrdup(fmt("%s/%s", repo->path, value)); 75 repo->readme = xstrdup(fmt("%s/%s", repo->path, value));
76 } else if (ctx.cfg.enable_filter_overrides) { 76 } else if (ctx.cfg.enable_filter_overrides) {
77 if (!strcmp(name, "about-filter")) 77 if (!strcmp(name, "about-filter"))
78 repo->about_filter = new_filter(value, 0); 78 repo->about_filter = new_filter(value, 0);
79 else if (!strcmp(name, "commit-filter")) 79 else if (!strcmp(name, "commit-filter"))
80 repo->commit_filter = new_filter(value, 0); 80 repo->commit_filter = new_filter(value, 0);
81 else if (!strcmp(name, "source-filter")) 81 else if (!strcmp(name, "source-filter"))
82 repo->source_filter = new_filter(value, 1); 82 repo->source_filter = new_filter(value, 1);
83 } 83 }
84} 84}
85 85
86void config_cb(const char *name, const char *value) 86void config_cb(const char *name, const char *value)
87{ 87{
88 if (!strcmp(name, "section") || !strcmp(name, "repo.group")) 88 if (!strcmp(name, "section") || !strcmp(name, "repo.group"))
89 ctx.cfg.section = xstrdup(value); 89 ctx.cfg.section = xstrdup(value);
90 else if (!strcmp(name, "repo.url")) 90 else if (!strcmp(name, "repo.url"))
91 ctx.repo = cgit_add_repo(value); 91 ctx.repo = cgit_add_repo(value);
92 else if (ctx.repo && !strcmp(name, "repo.path")) 92 else if (ctx.repo && !strcmp(name, "repo.path"))
93 ctx.repo->path = trim_end(value, '/'); 93 ctx.repo->path = trim_end(value, '/');
94 else if (ctx.repo && !prefixcmp(name, "repo.")) 94 else if (ctx.repo && !prefixcmp(name, "repo."))
95 repo_config(ctx.repo, name + 5, value); 95 repo_config(ctx.repo, name + 5, value);
96 else if (!strcmp(name, "root-title")) 96 else if (!strcmp(name, "root-title"))
97 ctx.cfg.root_title = xstrdup(value); 97 ctx.cfg.root_title = xstrdup(value);
98 else if (!strcmp(name, "root-desc")) 98 else if (!strcmp(name, "root-desc"))
99 ctx.cfg.root_desc = xstrdup(value); 99 ctx.cfg.root_desc = xstrdup(value);
100 else if (!strcmp(name, "root-readme")) 100 else if (!strcmp(name, "root-readme"))
101 ctx.cfg.root_readme = xstrdup(value); 101 ctx.cfg.root_readme = xstrdup(value);
102 else if (!strcmp(name, "css")) 102 else if (!strcmp(name, "css"))
103 ctx.cfg.css = xstrdup(value); 103 ctx.cfg.css = xstrdup(value);
104 else if (!strcmp(name, "favicon")) 104 else if (!strcmp(name, "favicon"))
105 ctx.cfg.favicon = xstrdup(value); 105 ctx.cfg.favicon = xstrdup(value);
106 else if (!strcmp(name, "footer")) 106 else if (!strcmp(name, "footer"))
107 ctx.cfg.footer = xstrdup(value); 107 ctx.cfg.footer = xstrdup(value);
108 else if (!strcmp(name, "head-include")) 108 else if (!strcmp(name, "head-include"))
109 ctx.cfg.head_include = xstrdup(value); 109 ctx.cfg.head_include = xstrdup(value);
110 else if (!strcmp(name, "header")) 110 else if (!strcmp(name, "header"))
111 ctx.cfg.header = xstrdup(value); 111 ctx.cfg.header = xstrdup(value);
112 else if (!strcmp(name, "logo")) 112 else if (!strcmp(name, "logo"))
113 ctx.cfg.logo = xstrdup(value); 113 ctx.cfg.logo = xstrdup(value);
114 else if (!strcmp(name, "index-header")) 114 else if (!strcmp(name, "index-header"))
115 ctx.cfg.index_header = xstrdup(value); 115 ctx.cfg.index_header = xstrdup(value);
116 else if (!strcmp(name, "index-info")) 116 else if (!strcmp(name, "index-info"))
117 ctx.cfg.index_info = xstrdup(value); 117 ctx.cfg.index_info = xstrdup(value);
118 else if (!strcmp(name, "logo-link")) 118 else if (!strcmp(name, "logo-link"))
119 ctx.cfg.logo_link = xstrdup(value); 119 ctx.cfg.logo_link = xstrdup(value);
120 else if (!strcmp(name, "module-link")) 120 else if (!strcmp(name, "module-link"))
121 ctx.cfg.module_link = xstrdup(value); 121 ctx.cfg.module_link = xstrdup(value);
122 else if (!strcmp(name, "virtual-root")) { 122 else if (!strcmp(name, "virtual-root")) {
123 ctx.cfg.virtual_root = trim_end(value, '/'); 123 ctx.cfg.virtual_root = trim_end(value, '/');
124 if (!ctx.cfg.virtual_root && (!strcmp(value, "/"))) 124 if (!ctx.cfg.virtual_root && (!strcmp(value, "/")))
125 ctx.cfg.virtual_root = ""; 125 ctx.cfg.virtual_root = "";
126 } else if (!strcmp(name, "nocache")) 126 } else if (!strcmp(name, "nocache"))
127 ctx.cfg.nocache = atoi(value); 127 ctx.cfg.nocache = atoi(value);
128 else if (!strcmp(name, "noplainemail")) 128 else if (!strcmp(name, "noplainemail"))
129 ctx.cfg.noplainemail = atoi(value); 129 ctx.cfg.noplainemail = atoi(value);
130 else if (!strcmp(name, "noheader")) 130 else if (!strcmp(name, "noheader"))
131 ctx.cfg.noheader = atoi(value); 131 ctx.cfg.noheader = atoi(value);
132 else if (!strcmp(name, "snapshots")) 132 else if (!strcmp(name, "snapshots"))
133 ctx.cfg.snapshots = cgit_parse_snapshots_mask(value); 133 ctx.cfg.snapshots = cgit_parse_snapshots_mask(value);
134 else if (!strcmp(name, "enable-filter-overrides")) 134 else if (!strcmp(name, "enable-filter-overrides"))
135 ctx.cfg.enable_filter_overrides = atoi(value); 135 ctx.cfg.enable_filter_overrides = atoi(value);
136 else if (!strcmp(name, "enable-index-links")) 136 else if (!strcmp(name, "enable-index-links"))
137 ctx.cfg.enable_index_links = atoi(value); 137 ctx.cfg.enable_index_links = atoi(value);
138 else if (!strcmp(name, "enable-log-filecount")) 138 else if (!strcmp(name, "enable-log-filecount"))
139 ctx.cfg.enable_log_filecount = atoi(value); 139 ctx.cfg.enable_log_filecount = atoi(value);
140 else if (!strcmp(name, "enable-log-linecount")) 140 else if (!strcmp(name, "enable-log-linecount"))
141 ctx.cfg.enable_log_linecount = atoi(value); 141 ctx.cfg.enable_log_linecount = atoi(value);
142 else if (!strcmp(name, "enable-remote-branches")) 142 else if (!strcmp(name, "enable-remote-branches"))
143 ctx.cfg.enable_remote_branches = atoi(value); 143 ctx.cfg.enable_remote_branches = atoi(value);
144 else if (!strcmp(name, "enable-tree-linenumbers")) 144 else if (!strcmp(name, "enable-tree-linenumbers"))
145 ctx.cfg.enable_tree_linenumbers = atoi(value); 145 ctx.cfg.enable_tree_linenumbers = atoi(value);
146 else if (!strcmp(name, "max-stats")) 146 else if (!strcmp(name, "max-stats"))
147 ctx.cfg.max_stats = cgit_find_stats_period(value, NULL); 147 ctx.cfg.max_stats = cgit_find_stats_period(value, NULL);
148 else if (!strcmp(name, "cache-size")) 148 else if (!strcmp(name, "cache-size"))
149 ctx.cfg.cache_size = atoi(value); 149 ctx.cfg.cache_size = atoi(value);
150 else if (!strcmp(name, "cache-root")) 150 else if (!strcmp(name, "cache-root"))
151 ctx.cfg.cache_root = xstrdup(value); 151 ctx.cfg.cache_root = xstrdup(value);
152 else if (!strcmp(name, "cache-root-ttl")) 152 else if (!strcmp(name, "cache-root-ttl"))
153 ctx.cfg.cache_root_ttl = atoi(value); 153 ctx.cfg.cache_root_ttl = atoi(value);
154 else if (!strcmp(name, "cache-repo-ttl")) 154 else if (!strcmp(name, "cache-repo-ttl"))
155 ctx.cfg.cache_repo_ttl = atoi(value); 155 ctx.cfg.cache_repo_ttl = atoi(value);
156 else if (!strcmp(name, "cache-scanrc-ttl")) 156 else if (!strcmp(name, "cache-scanrc-ttl"))
157 ctx.cfg.cache_scanrc_ttl = atoi(value); 157 ctx.cfg.cache_scanrc_ttl = atoi(value);
158 else if (!strcmp(name, "cache-static-ttl")) 158 else if (!strcmp(name, "cache-static-ttl"))
159 ctx.cfg.cache_static_ttl = atoi(value); 159 ctx.cfg.cache_static_ttl = atoi(value);
160 else if (!strcmp(name, "cache-dynamic-ttl")) 160 else if (!strcmp(name, "cache-dynamic-ttl"))
161 ctx.cfg.cache_dynamic_ttl = atoi(value); 161 ctx.cfg.cache_dynamic_ttl = atoi(value);
162 else if (!strcmp(name, "about-filter")) 162 else if (!strcmp(name, "about-filter"))
163 ctx.cfg.about_filter = new_filter(value, 0); 163 ctx.cfg.about_filter = new_filter(value, 0);
164 else if (!strcmp(name, "commit-filter")) 164 else if (!strcmp(name, "commit-filter"))
165 ctx.cfg.commit_filter = new_filter(value, 0); 165 ctx.cfg.commit_filter = new_filter(value, 0);
166 else if (!strcmp(name, "embedded")) 166 else if (!strcmp(name, "embedded"))
167 ctx.cfg.embedded = atoi(value); 167 ctx.cfg.embedded = atoi(value);
168 else if (!strcmp(name, "max-message-length")) 168 else if (!strcmp(name, "max-message-length"))
169 ctx.cfg.max_msg_len = atoi(value); 169 ctx.cfg.max_msg_len = atoi(value);
170 else if (!strcmp(name, "max-repodesc-length")) 170 else if (!strcmp(name, "max-repodesc-length"))
171 ctx.cfg.max_repodesc_len = atoi(value); 171 ctx.cfg.max_repodesc_len = atoi(value);
172 else if (!strcmp(name, "max-blob-size")) 172 else if (!strcmp(name, "max-blob-size"))
173 ctx.cfg.max_blob_size = atoi(value); 173 ctx.cfg.max_blob_size = atoi(value);
174 else if (!strcmp(name, "max-repo-count")) 174 else if (!strcmp(name, "max-repo-count"))
175 ctx.cfg.max_repo_count = atoi(value); 175 ctx.cfg.max_repo_count = atoi(value);
176 else if (!strcmp(name, "max-commit-count")) 176 else if (!strcmp(name, "max-commit-count"))
177 ctx.cfg.max_commit_count = atoi(value); 177 ctx.cfg.max_commit_count = atoi(value);
178 else if (!strcmp(name, "scan-path")) 178 else if (!strcmp(name, "scan-path"))
179 if (!ctx.cfg.nocache && ctx.cfg.cache_size) 179 if (!ctx.cfg.nocache && ctx.cfg.cache_size)
180 process_cached_repolist(value); 180 process_cached_repolist(value);
181 else 181 else
182 scan_tree(value, repo_config); 182 scan_tree(value, repo_config);
183 else if (!strcmp(name, "source-filter")) 183 else if (!strcmp(name, "source-filter"))
184 ctx.cfg.source_filter = new_filter(value, 1); 184 ctx.cfg.source_filter = new_filter(value, 1);
185 else if (!strcmp(name, "summary-log")) 185 else if (!strcmp(name, "summary-log"))
186 ctx.cfg.summary_log = atoi(value); 186 ctx.cfg.summary_log = atoi(value);
187 else if (!strcmp(name, "summary-branches")) 187 else if (!strcmp(name, "summary-branches"))
188 ctx.cfg.summary_branches = atoi(value); 188 ctx.cfg.summary_branches = atoi(value);
189 else if (!strcmp(name, "summary-tags")) 189 else if (!strcmp(name, "summary-tags"))
190 ctx.cfg.summary_tags = atoi(value); 190 ctx.cfg.summary_tags = atoi(value);
191 else if (!strcmp(name, "side-by-side-diffs")) 191 else if (!strcmp(name, "side-by-side-diffs"))
192 ctx.cfg.ssdiff = atoi(value); 192 ctx.cfg.ssdiff = atoi(value);
193 else if (!strcmp(name, "agefile")) 193 else if (!strcmp(name, "agefile"))
194 ctx.cfg.agefile = xstrdup(value); 194 ctx.cfg.agefile = xstrdup(value);
195 else if (!strcmp(name, "renamelimit")) 195 else if (!strcmp(name, "renamelimit"))
196 ctx.cfg.renamelimit = atoi(value); 196 ctx.cfg.renamelimit = atoi(value);
197 else if (!strcmp(name, "robots")) 197 else if (!strcmp(name, "robots"))
198 ctx.cfg.robots = xstrdup(value); 198 ctx.cfg.robots = xstrdup(value);
199 else if (!strcmp(name, "clone-prefix")) 199 else if (!strcmp(name, "clone-prefix"))
200 ctx.cfg.clone_prefix = xstrdup(value); 200 ctx.cfg.clone_prefix = xstrdup(value);
201 else if (!strcmp(name, "local-time")) 201 else if (!strcmp(name, "local-time"))
202 ctx.cfg.local_time = atoi(value); 202 ctx.cfg.local_time = atoi(value);
203 else if (!prefixcmp(name, "mimetype.")) 203 else if (!prefixcmp(name, "mimetype."))
204 add_mimetype(name + 9, value); 204 add_mimetype(name + 9, value);
205 else if (!strcmp(name, "include")) 205 else if (!strcmp(name, "include"))
206 parse_configfile(value, config_cb); 206 parse_configfile(value, config_cb);
207} 207}
208 208
209static void querystring_cb(const char *name, const char *value) 209static void querystring_cb(const char *name, const char *value)
210{ 210{
211 if (!value) 211 if (!value)
212 value = ""; 212 value = "";
213 213
214 if (!strcmp(name,"r")) { 214 if (!strcmp(name,"r")) {
215 ctx.qry.repo = xstrdup(value); 215 ctx.qry.repo = xstrdup(value);
216 ctx.repo = cgit_get_repoinfo(value); 216 ctx.repo = cgit_get_repoinfo(value);
217 } else if (!strcmp(name, "p")) { 217 } else if (!strcmp(name, "p")) {
218 ctx.qry.page = xstrdup(value); 218 ctx.qry.page = xstrdup(value);
219 } else if (!strcmp(name, "url")) { 219 } else if (!strcmp(name, "url")) {
220 if (*value == '/') 220 if (*value == '/')
221 value++; 221 value++;
222 ctx.qry.url = xstrdup(value); 222 ctx.qry.url = xstrdup(value);
223 cgit_parse_url(value); 223 cgit_parse_url(value);
224 } else if (!strcmp(name, "qt")) { 224 } else if (!strcmp(name, "qt")) {
225 ctx.qry.grep = xstrdup(value); 225 ctx.qry.grep = xstrdup(value);
226 } else if (!strcmp(name, "q")) { 226 } else if (!strcmp(name, "q")) {
227 ctx.qry.search = xstrdup(value); 227 ctx.qry.search = xstrdup(value);
228 } else if (!strcmp(name, "h")) { 228 } else if (!strcmp(name, "h")) {
229 ctx.qry.head = xstrdup(value); 229 ctx.qry.head = xstrdup(value);
230 ctx.qry.has_symref = 1; 230 ctx.qry.has_symref = 1;
231 } else if (!strcmp(name, "id")) { 231 } else if (!strcmp(name, "id")) {
232 ctx.qry.sha1 = xstrdup(value); 232 ctx.qry.sha1 = xstrdup(value);
233 ctx.qry.has_sha1 = 1; 233 ctx.qry.has_sha1 = 1;
234 } else if (!strcmp(name, "id2")) { 234 } else if (!strcmp(name, "id2")) {
235 ctx.qry.sha2 = xstrdup(value); 235 ctx.qry.sha2 = xstrdup(value);
236 ctx.qry.has_sha1 = 1; 236 ctx.qry.has_sha1 = 1;
237 } else if (!strcmp(name, "ofs")) { 237 } else if (!strcmp(name, "ofs")) {
238 ctx.qry.ofs = atoi(value); 238 ctx.qry.ofs = atoi(value);
239 } else if (!strcmp(name, "path")) { 239 } else if (!strcmp(name, "path")) {
240 ctx.qry.path = trim_end(value, '/'); 240 ctx.qry.path = trim_end(value, '/');
241 } else if (!strcmp(name, "name")) { 241 } else if (!strcmp(name, "name")) {
242 ctx.qry.name = xstrdup(value); 242 ctx.qry.name = xstrdup(value);
243 } else if (!strcmp(name, "mimetype")) { 243 } else if (!strcmp(name, "mimetype")) {
244 ctx.qry.mimetype = xstrdup(value); 244 ctx.qry.mimetype = xstrdup(value);
245 } else if (!strcmp(name, "s")){ 245 } else if (!strcmp(name, "s")){
246 ctx.qry.sort = xstrdup(value); 246 ctx.qry.sort = xstrdup(value);
247 } else if (!strcmp(name, "showmsg")) { 247 } else if (!strcmp(name, "showmsg")) {
248 ctx.qry.showmsg = atoi(value); 248 ctx.qry.showmsg = atoi(value);
249 } else if (!strcmp(name, "period")) { 249 } else if (!strcmp(name, "period")) {
250 ctx.qry.period = xstrdup(value); 250 ctx.qry.period = xstrdup(value);
251 } else if (!strcmp(name, "ss")) { 251 } else if (!strcmp(name, "ss")) {
252 ctx.qry.ssdiff = atoi(value); 252 ctx.qry.ssdiff = atoi(value);
253 } else if (!strcmp(name, "context")) {
254 ctx.qry.context = atoi(value);
253 } 255 }
254} 256}
255 257
256char *xstrdupn(const char *str) 258char *xstrdupn(const char *str)
257{ 259{
258 return (str ? xstrdup(str) : NULL); 260 return (str ? xstrdup(str) : NULL);
259} 261}
260 262
261static void prepare_context(struct cgit_context *ctx) 263static void prepare_context(struct cgit_context *ctx)
262{ 264{
263 memset(ctx, 0, sizeof(*ctx)); 265 memset(ctx, 0, sizeof(*ctx));
264 ctx->cfg.agefile = "info/web/last-modified"; 266 ctx->cfg.agefile = "info/web/last-modified";
265 ctx->cfg.nocache = 0; 267 ctx->cfg.nocache = 0;
266 ctx->cfg.cache_size = 0; 268 ctx->cfg.cache_size = 0;
267 ctx->cfg.cache_dynamic_ttl = 5; 269 ctx->cfg.cache_dynamic_ttl = 5;
268 ctx->cfg.cache_max_create_time = 5; 270 ctx->cfg.cache_max_create_time = 5;
269 ctx->cfg.cache_repo_ttl = 5; 271 ctx->cfg.cache_repo_ttl = 5;
270 ctx->cfg.cache_root = CGIT_CACHE_ROOT; 272 ctx->cfg.cache_root = CGIT_CACHE_ROOT;
271 ctx->cfg.cache_root_ttl = 5; 273 ctx->cfg.cache_root_ttl = 5;
272 ctx->cfg.cache_scanrc_ttl = 15; 274 ctx->cfg.cache_scanrc_ttl = 15;
273 ctx->cfg.cache_static_ttl = -1; 275 ctx->cfg.cache_static_ttl = -1;
274 ctx->cfg.css = "/cgit.css"; 276 ctx->cfg.css = "/cgit.css";
275 ctx->cfg.logo = "/cgit.png"; 277 ctx->cfg.logo = "/cgit.png";
276 ctx->cfg.local_time = 0; 278 ctx->cfg.local_time = 0;
277 ctx->cfg.enable_tree_linenumbers = 1; 279 ctx->cfg.enable_tree_linenumbers = 1;
278 ctx->cfg.max_repo_count = 50; 280 ctx->cfg.max_repo_count = 50;
279 ctx->cfg.max_commit_count = 50; 281 ctx->cfg.max_commit_count = 50;
280 ctx->cfg.max_lock_attempts = 5; 282 ctx->cfg.max_lock_attempts = 5;
281 ctx->cfg.max_msg_len = 80; 283 ctx->cfg.max_msg_len = 80;
282 ctx->cfg.max_repodesc_len = 80; 284 ctx->cfg.max_repodesc_len = 80;
283 ctx->cfg.max_blob_size = 0; 285 ctx->cfg.max_blob_size = 0;
284 ctx->cfg.max_stats = 0; 286 ctx->cfg.max_stats = 0;
285 ctx->cfg.module_link = "./?repo=%s&page=commit&id=%s"; 287 ctx->cfg.module_link = "./?repo=%s&page=commit&id=%s";
286 ctx->cfg.renamelimit = -1; 288 ctx->cfg.renamelimit = -1;
287 ctx->cfg.robots = "index, nofollow"; 289 ctx->cfg.robots = "index, nofollow";
288 ctx->cfg.root_title = "Git repository browser"; 290 ctx->cfg.root_title = "Git repository browser";
289 ctx->cfg.root_desc = "a fast webinterface for the git dscm"; 291 ctx->cfg.root_desc = "a fast webinterface for the git dscm";
290 ctx->cfg.script_name = CGIT_SCRIPT_NAME; 292 ctx->cfg.script_name = CGIT_SCRIPT_NAME;
291 ctx->cfg.section = ""; 293 ctx->cfg.section = "";
292 ctx->cfg.summary_branches = 10; 294 ctx->cfg.summary_branches = 10;
293 ctx->cfg.summary_log = 10; 295 ctx->cfg.summary_log = 10;
294 ctx->cfg.summary_tags = 10; 296 ctx->cfg.summary_tags = 10;
295 ctx->cfg.ssdiff = 0; 297 ctx->cfg.ssdiff = 0;
296 ctx->env.cgit_config = xstrdupn(getenv("CGIT_CONFIG")); 298 ctx->env.cgit_config = xstrdupn(getenv("CGIT_CONFIG"));
297 ctx->env.http_host = xstrdupn(getenv("HTTP_HOST")); 299 ctx->env.http_host = xstrdupn(getenv("HTTP_HOST"));
298 ctx->env.https = xstrdupn(getenv("HTTPS")); 300 ctx->env.https = xstrdupn(getenv("HTTPS"));
299 ctx->env.no_http = xstrdupn(getenv("NO_HTTP")); 301 ctx->env.no_http = xstrdupn(getenv("NO_HTTP"));
300 ctx->env.path_info = xstrdupn(getenv("PATH_INFO")); 302 ctx->env.path_info = xstrdupn(getenv("PATH_INFO"));
301 ctx->env.query_string = xstrdupn(getenv("QUERY_STRING")); 303 ctx->env.query_string = xstrdupn(getenv("QUERY_STRING"));
302 ctx->env.request_method = xstrdupn(getenv("REQUEST_METHOD")); 304 ctx->env.request_method = xstrdupn(getenv("REQUEST_METHOD"));
303 ctx->env.script_name = xstrdupn(getenv("SCRIPT_NAME")); 305 ctx->env.script_name = xstrdupn(getenv("SCRIPT_NAME"));
304 ctx->env.server_name = xstrdupn(getenv("SERVER_NAME")); 306 ctx->env.server_name = xstrdupn(getenv("SERVER_NAME"));
305 ctx->env.server_port = xstrdupn(getenv("SERVER_PORT")); 307 ctx->env.server_port = xstrdupn(getenv("SERVER_PORT"));
306 ctx->page.mimetype = "text/html"; 308 ctx->page.mimetype = "text/html";
307 ctx->page.charset = PAGE_ENCODING; 309 ctx->page.charset = PAGE_ENCODING;
308 ctx->page.filename = NULL; 310 ctx->page.filename = NULL;
309 ctx->page.size = 0; 311 ctx->page.size = 0;
310 ctx->page.modified = time(NULL); 312 ctx->page.modified = time(NULL);
311 ctx->page.expires = ctx->page.modified; 313 ctx->page.expires = ctx->page.modified;
312 ctx->page.etag = NULL; 314 ctx->page.etag = NULL;
313 memset(&ctx->cfg.mimetypes, 0, sizeof(struct string_list)); 315 memset(&ctx->cfg.mimetypes, 0, sizeof(struct string_list));
314 if (ctx->env.script_name) 316 if (ctx->env.script_name)
315 ctx->cfg.script_name = ctx->env.script_name; 317 ctx->cfg.script_name = ctx->env.script_name;
316 if (ctx->env.query_string) 318 if (ctx->env.query_string)
317 ctx->qry.raw = ctx->env.query_string; 319 ctx->qry.raw = ctx->env.query_string;
318 if (!ctx->env.cgit_config) 320 if (!ctx->env.cgit_config)
319 ctx->env.cgit_config = CGIT_CONFIG; 321 ctx->env.cgit_config = CGIT_CONFIG;
320} 322}
321 323
322struct refmatch { 324struct refmatch {
323 char *req_ref; 325 char *req_ref;
324 char *first_ref; 326 char *first_ref;
325 int match; 327 int match;
326}; 328};
327 329
328int find_current_ref(const char *refname, const unsigned char *sha1, 330int find_current_ref(const char *refname, const unsigned char *sha1,
329 int flags, void *cb_data) 331 int flags, void *cb_data)
330{ 332{
331 struct refmatch *info; 333 struct refmatch *info;
332 334
333 info = (struct refmatch *)cb_data; 335 info = (struct refmatch *)cb_data;
334 if (!strcmp(refname, info->req_ref)) 336 if (!strcmp(refname, info->req_ref))
335 info->match = 1; 337 info->match = 1;
336 if (!info->first_ref) 338 if (!info->first_ref)
337 info->first_ref = xstrdup(refname); 339 info->first_ref = xstrdup(refname);
338 return info->match; 340 return info->match;
339} 341}
340 342
341char *find_default_branch(struct cgit_repo *repo) 343char *find_default_branch(struct cgit_repo *repo)
342{ 344{
343 struct refmatch info; 345 struct refmatch info;
344 char *ref; 346 char *ref;
345 347
346 info.req_ref = repo->defbranch; 348 info.req_ref = repo->defbranch;
347 info.first_ref = NULL; 349 info.first_ref = NULL;
348 info.match = 0; 350 info.match = 0;
349 for_each_branch_ref(find_current_ref, &info); 351 for_each_branch_ref(find_current_ref, &info);
350 if (info.match) 352 if (info.match)
351 ref = info.req_ref; 353 ref = info.req_ref;
352 else 354 else
353 ref = info.first_ref; 355 ref = info.first_ref;
354 if (ref) 356 if (ref)
355 ref = xstrdup(ref); 357 ref = xstrdup(ref);
356 return ref; 358 return ref;
357} 359}
358 360
359static int prepare_repo_cmd(struct cgit_context *ctx) 361static int prepare_repo_cmd(struct cgit_context *ctx)
360{ 362{
361 char *tmp; 363 char *tmp;
362 unsigned char sha1[20]; 364 unsigned char sha1[20];
363 int nongit = 0; 365 int nongit = 0;
364 366
365 setenv("GIT_DIR", ctx->repo->path, 1); 367 setenv("GIT_DIR", ctx->repo->path, 1);
366 setup_git_directory_gently(&nongit); 368 setup_git_directory_gently(&nongit);
367 if (nongit) { 369 if (nongit) {
368 ctx->page.title = fmt("%s - %s", ctx->cfg.root_title, 370 ctx->page.title = fmt("%s - %s", ctx->cfg.root_title,
369 "config error"); 371 "config error");
370 tmp = fmt("Not a git repository: '%s'", ctx->repo->path); 372 tmp = fmt("Not a git repository: '%s'", ctx->repo->path);
371 ctx->repo = NULL; 373 ctx->repo = NULL;
372 cgit_print_http_headers(ctx); 374 cgit_print_http_headers(ctx);
373 cgit_print_docstart(ctx); 375 cgit_print_docstart(ctx);
374 cgit_print_pageheader(ctx); 376 cgit_print_pageheader(ctx);
375 cgit_print_error(tmp); 377 cgit_print_error(tmp);
376 cgit_print_docend(); 378 cgit_print_docend();
377 return 1; 379 return 1;
378 } 380 }
379 ctx->page.title = fmt("%s - %s", ctx->repo->name, ctx->repo->desc); 381 ctx->page.title = fmt("%s - %s", ctx->repo->name, ctx->repo->desc);
380 382
381 if (!ctx->qry.head) { 383 if (!ctx->qry.head) {
382 ctx->qry.nohead = 1; 384 ctx->qry.nohead = 1;
383 ctx->qry.head = find_default_branch(ctx->repo); 385 ctx->qry.head = find_default_branch(ctx->repo);
384 ctx->repo->defbranch = ctx->qry.head; 386 ctx->repo->defbranch = ctx->qry.head;
385 } 387 }
386 388
387 if (!ctx->qry.head) { 389 if (!ctx->qry.head) {
388 cgit_print_http_headers(ctx); 390 cgit_print_http_headers(ctx);
389 cgit_print_docstart(ctx); 391 cgit_print_docstart(ctx);
390 cgit_print_pageheader(ctx); 392 cgit_print_pageheader(ctx);
391 cgit_print_error("Repository seems to be empty"); 393 cgit_print_error("Repository seems to be empty");
392 cgit_print_docend(); 394 cgit_print_docend();
393 return 1; 395 return 1;
394 } 396 }
395 397
396 if (get_sha1(ctx->qry.head, sha1)) { 398 if (get_sha1(ctx->qry.head, sha1)) {
397 tmp = xstrdup(ctx->qry.head); 399 tmp = xstrdup(ctx->qry.head);
398 ctx->qry.head = ctx->repo->defbranch; 400 ctx->qry.head = ctx->repo->defbranch;
399 ctx->page.status = 404; 401 ctx->page.status = 404;
400 ctx->page.statusmsg = "not found"; 402 ctx->page.statusmsg = "not found";
401 cgit_print_http_headers(ctx); 403 cgit_print_http_headers(ctx);
402 cgit_print_docstart(ctx); 404 cgit_print_docstart(ctx);
403 cgit_print_pageheader(ctx); 405 cgit_print_pageheader(ctx);
404 cgit_print_error(fmt("Invalid branch: %s", tmp)); 406 cgit_print_error(fmt("Invalid branch: %s", tmp));
405 cgit_print_docend(); 407 cgit_print_docend();
406 return 1; 408 return 1;
407 } 409 }
408 return 0; 410 return 0;
409} 411}
410 412
411static void process_request(void *cbdata) 413static void process_request(void *cbdata)
412{ 414{
413 struct cgit_context *ctx = cbdata; 415 struct cgit_context *ctx = cbdata;
414 struct cgit_cmd *cmd; 416 struct cgit_cmd *cmd;
415 417
416 cmd = cgit_get_cmd(ctx); 418 cmd = cgit_get_cmd(ctx);
417 if (!cmd) { 419 if (!cmd) {
418 ctx->page.title = "cgit error"; 420 ctx->page.title = "cgit error";
419 cgit_print_http_headers(ctx); 421 cgit_print_http_headers(ctx);
420 cgit_print_docstart(ctx); 422 cgit_print_docstart(ctx);
421 cgit_print_pageheader(ctx); 423 cgit_print_pageheader(ctx);
422 cgit_print_error("Invalid request"); 424 cgit_print_error("Invalid request");
423 cgit_print_docend(); 425 cgit_print_docend();
424 return; 426 return;
425 } 427 }
426 428
427 /* If cmd->want_vpath is set, assume ctx->qry.path contains a "virtual" 429 /* If cmd->want_vpath is set, assume ctx->qry.path contains a "virtual"
428 * in-project path limit to be made available at ctx->qry.vpath. 430 * in-project path limit to be made available at ctx->qry.vpath.
429 * Otherwise, no path limit is in effect (ctx->qry.vpath = NULL). 431 * Otherwise, no path limit is in effect (ctx->qry.vpath = NULL).
430 */ 432 */
431 ctx->qry.vpath = cmd->want_vpath ? ctx->qry.path : NULL; 433 ctx->qry.vpath = cmd->want_vpath ? ctx->qry.path : NULL;
432 434
433 if (cmd->want_repo && !ctx->repo) { 435 if (cmd->want_repo && !ctx->repo) {
434 cgit_print_http_headers(ctx); 436 cgit_print_http_headers(ctx);
435 cgit_print_docstart(ctx); 437 cgit_print_docstart(ctx);
436 cgit_print_pageheader(ctx); 438 cgit_print_pageheader(ctx);
437 cgit_print_error(fmt("No repository selected")); 439 cgit_print_error(fmt("No repository selected"));
438 cgit_print_docend(); 440 cgit_print_docend();
439 return; 441 return;
440 } 442 }
441 443
442 if (ctx->repo && prepare_repo_cmd(ctx)) 444 if (ctx->repo && prepare_repo_cmd(ctx))
443 return; 445 return;
444 446
445 if (cmd->want_layout) { 447 if (cmd->want_layout) {
446 cgit_print_http_headers(ctx); 448 cgit_print_http_headers(ctx);
447 cgit_print_docstart(ctx); 449 cgit_print_docstart(ctx);
448 cgit_print_pageheader(ctx); 450 cgit_print_pageheader(ctx);
449 } 451 }
450 452
451 cmd->fn(ctx); 453 cmd->fn(ctx);
452 454
453 if (cmd->want_layout) 455 if (cmd->want_layout)
454 cgit_print_docend(); 456 cgit_print_docend();
455} 457}
456 458
457int cmp_repos(const void *a, const void *b) 459int cmp_repos(const void *a, const void *b)
458{ 460{
459 const struct cgit_repo *ra = a, *rb = b; 461 const struct cgit_repo *ra = a, *rb = b;
460 return strcmp(ra->url, rb->url); 462 return strcmp(ra->url, rb->url);
461} 463}
462 464
463char *build_snapshot_setting(int bitmap) 465char *build_snapshot_setting(int bitmap)
464{ 466{
465 const struct cgit_snapshot_format *f; 467 const struct cgit_snapshot_format *f;
466 char *result = xstrdup(""); 468 char *result = xstrdup("");
467 char *tmp; 469 char *tmp;
468 int len; 470 int len;
469 471
470 for (f = cgit_snapshot_formats; f->suffix; f++) { 472 for (f = cgit_snapshot_formats; f->suffix; f++) {
471 if (f->bit & bitmap) { 473 if (f->bit & bitmap) {
472 tmp = result; 474 tmp = result;
473 result = xstrdup(fmt("%s%s ", tmp, f->suffix)); 475 result = xstrdup(fmt("%s%s ", tmp, f->suffix));
474 free(tmp); 476 free(tmp);
475 } 477 }
476 } 478 }
477 len = strlen(result); 479 len = strlen(result);
478 if (len) 480 if (len)
479 result[len - 1] = '\0'; 481 result[len - 1] = '\0';
480 return result; 482 return result;
481} 483}
482 484
483char *get_first_line(char *txt) 485char *get_first_line(char *txt)
484{ 486{
485 char *t = xstrdup(txt); 487 char *t = xstrdup(txt);
486 char *p = strchr(t, '\n'); 488 char *p = strchr(t, '\n');
487 if (p) 489 if (p)
488 *p = '\0'; 490 *p = '\0';
489 return t; 491 return t;
490} 492}
491 493
492void print_repo(FILE *f, struct cgit_repo *repo) 494void print_repo(FILE *f, struct cgit_repo *repo)
493{ 495{
494 fprintf(f, "repo.url=%s\n", repo->url); 496 fprintf(f, "repo.url=%s\n", repo->url);
495 fprintf(f, "repo.name=%s\n", repo->name); 497 fprintf(f, "repo.name=%s\n", repo->name);
496 fprintf(f, "repo.path=%s\n", repo->path); 498 fprintf(f, "repo.path=%s\n", repo->path);
497 if (repo->owner) 499 if (repo->owner)
498 fprintf(f, "repo.owner=%s\n", repo->owner); 500 fprintf(f, "repo.owner=%s\n", repo->owner);
499 if (repo->desc) { 501 if (repo->desc) {
500 char *tmp = get_first_line(repo->desc); 502 char *tmp = get_first_line(repo->desc);
501 fprintf(f, "repo.desc=%s\n", tmp); 503 fprintf(f, "repo.desc=%s\n", tmp);
502 free(tmp); 504 free(tmp);
503 } 505 }
504 if (repo->readme) 506 if (repo->readme)
505 fprintf(f, "repo.readme=%s\n", repo->readme); 507 fprintf(f, "repo.readme=%s\n", repo->readme);
506 if (repo->defbranch) 508 if (repo->defbranch)
507 fprintf(f, "repo.defbranch=%s\n", repo->defbranch); 509 fprintf(f, "repo.defbranch=%s\n", repo->defbranch);
508 if (repo->module_link) 510 if (repo->module_link)
509 fprintf(f, "repo.module-link=%s\n", repo->module_link); 511 fprintf(f, "repo.module-link=%s\n", repo->module_link);
510 if (repo->section) 512 if (repo->section)
511 fprintf(f, "repo.section=%s\n", repo->section); 513 fprintf(f, "repo.section=%s\n", repo->section);
512 if (repo->clone_url) 514 if (repo->clone_url)
513 fprintf(f, "repo.clone-url=%s\n", repo->clone_url); 515 fprintf(f, "repo.clone-url=%s\n", repo->clone_url);
514 fprintf(f, "repo.enable-log-filecount=%d\n", 516 fprintf(f, "repo.enable-log-filecount=%d\n",
515 repo->enable_log_filecount); 517 repo->enable_log_filecount);
516 fprintf(f, "repo.enable-log-linecount=%d\n", 518 fprintf(f, "repo.enable-log-linecount=%d\n",
517 repo->enable_log_linecount); 519 repo->enable_log_linecount);
518 if (repo->about_filter && repo->about_filter != ctx.cfg.about_filter) 520 if (repo->about_filter && repo->about_filter != ctx.cfg.about_filter)
519 fprintf(f, "repo.about-filter=%s\n", repo->about_filter->cmd); 521 fprintf(f, "repo.about-filter=%s\n", repo->about_filter->cmd);
520 if (repo->commit_filter && repo->commit_filter != ctx.cfg.commit_filter) 522 if (repo->commit_filter && repo->commit_filter != ctx.cfg.commit_filter)
521 fprintf(f, "repo.commit-filter=%s\n", repo->commit_filter->cmd); 523 fprintf(f, "repo.commit-filter=%s\n", repo->commit_filter->cmd);
522 if (repo->source_filter && repo->source_filter != ctx.cfg.source_filter) 524 if (repo->source_filter && repo->source_filter != ctx.cfg.source_filter)
523 fprintf(f, "repo.source-filter=%s\n", repo->source_filter->cmd); 525 fprintf(f, "repo.source-filter=%s\n", repo->source_filter->cmd);
524 if (repo->snapshots != ctx.cfg.snapshots) { 526 if (repo->snapshots != ctx.cfg.snapshots) {
525 char *tmp = build_snapshot_setting(repo->snapshots); 527 char *tmp = build_snapshot_setting(repo->snapshots);
526 fprintf(f, "repo.snapshots=%s\n", tmp); 528 fprintf(f, "repo.snapshots=%s\n", tmp);
527 free(tmp); 529 free(tmp);
528 } 530 }
529 if (repo->max_stats != ctx.cfg.max_stats) 531 if (repo->max_stats != ctx.cfg.max_stats)
530 fprintf(f, "repo.max-stats=%s\n", 532 fprintf(f, "repo.max-stats=%s\n",
531 cgit_find_stats_periodname(repo->max_stats)); 533 cgit_find_stats_periodname(repo->max_stats));
532 fprintf(f, "\n"); 534 fprintf(f, "\n");
533} 535}
534 536
535void print_repolist(FILE *f, struct cgit_repolist *list, int start) 537void print_repolist(FILE *f, struct cgit_repolist *list, int start)
536{ 538{
537 int i; 539 int i;
538 540
539 for(i = start; i < list->count; i++) 541 for(i = start; i < list->count; i++)
540 print_repo(f, &list->repos[i]); 542 print_repo(f, &list->repos[i]);
541} 543}
542 544
543/* Scan 'path' for git repositories, save the resulting repolist in 'cached_rc' 545/* Scan 'path' for git repositories, save the resulting repolist in 'cached_rc'
544 * and return 0 on success. 546 * and return 0 on success.
545 */ 547 */
546static int generate_cached_repolist(const char *path, const char *cached_rc) 548static int generate_cached_repolist(const char *path, const char *cached_rc)
547{ 549{
548 char *locked_rc; 550 char *locked_rc;
549 int idx; 551 int idx;
550 FILE *f; 552 FILE *f;
551 553
552 locked_rc = xstrdup(fmt("%s.lock", cached_rc)); 554 locked_rc = xstrdup(fmt("%s.lock", cached_rc));
553 f = fopen(locked_rc, "wx"); 555 f = fopen(locked_rc, "wx");
554 if (!f) { 556 if (!f) {
555 /* Inform about the error unless the lockfile already existed, 557 /* Inform about the error unless the lockfile already existed,
556 * since that only means we've got concurrent requests. 558 * since that only means we've got concurrent requests.
557 */ 559 */
558 if (errno != EEXIST) 560 if (errno != EEXIST)
559 fprintf(stderr, "[cgit] Error opening %s: %s (%d)\n", 561 fprintf(stderr, "[cgit] Error opening %s: %s (%d)\n",
560 locked_rc, strerror(errno), errno); 562 locked_rc, strerror(errno), errno);
561 return errno; 563 return errno;
562 } 564 }
563 idx = cgit_repolist.count; 565 idx = cgit_repolist.count;
564 scan_tree(path, repo_config); 566 scan_tree(path, repo_config);
565 print_repolist(f, &cgit_repolist, idx); 567 print_repolist(f, &cgit_repolist, idx);
566 if (rename(locked_rc, cached_rc)) 568 if (rename(locked_rc, cached_rc))
567 fprintf(stderr, "[cgit] Error renaming %s to %s: %s (%d)\n", 569 fprintf(stderr, "[cgit] Error renaming %s to %s: %s (%d)\n",
568 locked_rc, cached_rc, strerror(errno), errno); 570 locked_rc, cached_rc, strerror(errno), errno);
569 fclose(f); 571 fclose(f);
570 return 0; 572 return 0;
571} 573}
572 574
573static void process_cached_repolist(const char *path) 575static void process_cached_repolist(const char *path)
574{ 576{
575 struct stat st; 577 struct stat st;
576 char *cached_rc; 578 char *cached_rc;
577 time_t age; 579 time_t age;
578 580
579 cached_rc = xstrdup(fmt("%s/rc-%8x", ctx.cfg.cache_root, 581 cached_rc = xstrdup(fmt("%s/rc-%8x", ctx.cfg.cache_root,
580 hash_str(path))); 582 hash_str(path)));
581 583
582 if (stat(cached_rc, &st)) { 584 if (stat(cached_rc, &st)) {
583 /* Nothing is cached, we need to scan without forking. And 585 /* Nothing is cached, we need to scan without forking. And
584 * if we fail to generate a cached repolist, we need to 586 * if we fail to generate a cached repolist, we need to
585 * invoke scan_tree manually. 587 * invoke scan_tree manually.
586 */ 588 */
587 if (generate_cached_repolist(path, cached_rc)) 589 if (generate_cached_repolist(path, cached_rc))
588 scan_tree(path, repo_config); 590 scan_tree(path, repo_config);
589 return; 591 return;
590 } 592 }
591 593
592 parse_configfile(cached_rc, config_cb); 594 parse_configfile(cached_rc, config_cb);
593 595
594 /* If the cached configfile hasn't expired, lets exit now */ 596 /* If the cached configfile hasn't expired, lets exit now */
595 age = time(NULL) - st.st_mtime; 597 age = time(NULL) - st.st_mtime;
596 if (age <= (ctx.cfg.cache_scanrc_ttl * 60)) 598 if (age <= (ctx.cfg.cache_scanrc_ttl * 60))
597 return; 599 return;
598 600
599 /* The cached repolist has been parsed, but it was old. So lets 601 /* The cached repolist has been parsed, but it was old. So lets
600 * rescan the specified path and generate a new cached repolist 602 * rescan the specified path and generate a new cached repolist
601 * in a child-process to avoid latency for the current request. 603 * in a child-process to avoid latency for the current request.
602 */ 604 */
603 if (fork()) 605 if (fork())
604 return; 606 return;
605 607
606 exit(generate_cached_repolist(path, cached_rc)); 608 exit(generate_cached_repolist(path, cached_rc));
607} 609}
608 610
609static void cgit_parse_args(int argc, const char **argv) 611static void cgit_parse_args(int argc, const char **argv)
610{ 612{
611 int i; 613 int i;
612 int scan = 0; 614 int scan = 0;
613 615
614 for (i = 1; i < argc; i++) { 616 for (i = 1; i < argc; i++) {
615 if (!strncmp(argv[i], "--cache=", 8)) { 617 if (!strncmp(argv[i], "--cache=", 8)) {
616 ctx.cfg.cache_root = xstrdup(argv[i]+8); 618 ctx.cfg.cache_root = xstrdup(argv[i]+8);
617 } 619 }
618 if (!strcmp(argv[i], "--nocache")) { 620 if (!strcmp(argv[i], "--nocache")) {
619 ctx.cfg.nocache = 1; 621 ctx.cfg.nocache = 1;
620 } 622 }
621 if (!strcmp(argv[i], "--nohttp")) { 623 if (!strcmp(argv[i], "--nohttp")) {
622 ctx.env.no_http = "1"; 624 ctx.env.no_http = "1";
623 } 625 }
624 if (!strncmp(argv[i], "--query=", 8)) { 626 if (!strncmp(argv[i], "--query=", 8)) {
625 ctx.qry.raw = xstrdup(argv[i]+8); 627 ctx.qry.raw = xstrdup(argv[i]+8);
626 } 628 }
627 if (!strncmp(argv[i], "--repo=", 7)) { 629 if (!strncmp(argv[i], "--repo=", 7)) {
628 ctx.qry.repo = xstrdup(argv[i]+7); 630 ctx.qry.repo = xstrdup(argv[i]+7);
629 } 631 }
630 if (!strncmp(argv[i], "--page=", 7)) { 632 if (!strncmp(argv[i], "--page=", 7)) {
631 ctx.qry.page = xstrdup(argv[i]+7); 633 ctx.qry.page = xstrdup(argv[i]+7);
632 } 634 }
633 if (!strncmp(argv[i], "--head=", 7)) { 635 if (!strncmp(argv[i], "--head=", 7)) {
634 ctx.qry.head = xstrdup(argv[i]+7); 636 ctx.qry.head = xstrdup(argv[i]+7);
635 ctx.qry.has_symref = 1; 637 ctx.qry.has_symref = 1;
636 } 638 }
637 if (!strncmp(argv[i], "--sha1=", 7)) { 639 if (!strncmp(argv[i], "--sha1=", 7)) {
638 ctx.qry.sha1 = xstrdup(argv[i]+7); 640 ctx.qry.sha1 = xstrdup(argv[i]+7);
639 ctx.qry.has_sha1 = 1; 641 ctx.qry.has_sha1 = 1;
640 } 642 }
641 if (!strncmp(argv[i], "--ofs=", 6)) { 643 if (!strncmp(argv[i], "--ofs=", 6)) {
642 ctx.qry.ofs = atoi(argv[i]+6); 644 ctx.qry.ofs = atoi(argv[i]+6);
643 } 645 }
644 if (!strncmp(argv[i], "--scan-tree=", 12) || 646 if (!strncmp(argv[i], "--scan-tree=", 12) ||
645 !strncmp(argv[i], "--scan-path=", 12)) { 647 !strncmp(argv[i], "--scan-path=", 12)) {
646 /* HACK: the global snapshot bitmask defines the 648 /* HACK: the global snapshot bitmask defines the
647 * set of allowed snapshot formats, but the config 649 * set of allowed snapshot formats, but the config
648 * file hasn't been parsed yet so the mask is 650 * file hasn't been parsed yet so the mask is
649 * currently 0. By setting all bits high before 651 * currently 0. By setting all bits high before
650 * scanning we make sure that any in-repo cgitrc 652 * scanning we make sure that any in-repo cgitrc
651 * snapshot setting is respected by scan_tree(). 653 * snapshot setting is respected by scan_tree().
652 * BTW: we assume that there'll never be more than 654 * BTW: we assume that there'll never be more than
653 * 255 different snapshot formats supported by cgit... 655 * 255 different snapshot formats supported by cgit...
654 */ 656 */
655 ctx.cfg.snapshots = 0xFF; 657 ctx.cfg.snapshots = 0xFF;
656 scan++; 658 scan++;
657 scan_tree(argv[i] + 12, repo_config); 659 scan_tree(argv[i] + 12, repo_config);
658 } 660 }
659 } 661 }
660 if (scan) { 662 if (scan) {
661 qsort(cgit_repolist.repos, cgit_repolist.count, 663 qsort(cgit_repolist.repos, cgit_repolist.count,
662 sizeof(struct cgit_repo), cmp_repos); 664 sizeof(struct cgit_repo), cmp_repos);
663 print_repolist(stdout, &cgit_repolist, 0); 665 print_repolist(stdout, &cgit_repolist, 0);
664 exit(0); 666 exit(0);
665 } 667 }
666} 668}
667 669
668static int calc_ttl() 670static int calc_ttl()
669{ 671{
670 if (!ctx.repo) 672 if (!ctx.repo)
671 return ctx.cfg.cache_root_ttl; 673 return ctx.cfg.cache_root_ttl;
672 674
673 if (!ctx.qry.page) 675 if (!ctx.qry.page)
674 return ctx.cfg.cache_repo_ttl; 676 return ctx.cfg.cache_repo_ttl;
675 677
676 if (ctx.qry.has_symref) 678 if (ctx.qry.has_symref)
677 return ctx.cfg.cache_dynamic_ttl; 679 return ctx.cfg.cache_dynamic_ttl;
678 680
679 if (ctx.qry.has_sha1) 681 if (ctx.qry.has_sha1)
680 return ctx.cfg.cache_static_ttl; 682 return ctx.cfg.cache_static_ttl;
681 683
682 return ctx.cfg.cache_repo_ttl; 684 return ctx.cfg.cache_repo_ttl;
683} 685}
684 686
685int main(int argc, const char **argv) 687int main(int argc, const char **argv)
686{ 688{
687 const char *path; 689 const char *path;
688 char *qry; 690 char *qry;
689 int err, ttl; 691 int err, ttl;
690 692
691 prepare_context(&ctx); 693 prepare_context(&ctx);
692 cgit_repolist.length = 0; 694 cgit_repolist.length = 0;
693 cgit_repolist.count = 0; 695 cgit_repolist.count = 0;
694 cgit_repolist.repos = NULL; 696 cgit_repolist.repos = NULL;
695 697
696 cgit_parse_args(argc, argv); 698 cgit_parse_args(argc, argv);
697 parse_configfile(ctx.env.cgit_config, config_cb); 699 parse_configfile(ctx.env.cgit_config, config_cb);
698 ctx.repo = NULL; 700 ctx.repo = NULL;
699 http_parse_querystring(ctx.qry.raw, querystring_cb); 701 http_parse_querystring(ctx.qry.raw, querystring_cb);
700 702
701 /* If virtual-root isn't specified in cgitrc, lets pretend 703 /* If virtual-root isn't specified in cgitrc, lets pretend
702 * that virtual-root equals SCRIPT_NAME. 704 * that virtual-root equals SCRIPT_NAME.
703 */ 705 */
704 if (!ctx.cfg.virtual_root) 706 if (!ctx.cfg.virtual_root)
705 ctx.cfg.virtual_root = ctx.cfg.script_name; 707 ctx.cfg.virtual_root = ctx.cfg.script_name;
706 708
707 /* If no url parameter is specified on the querystring, lets 709 /* If no url parameter is specified on the querystring, lets
708 * use PATH_INFO as url. This allows cgit to work with virtual 710 * use PATH_INFO as url. This allows cgit to work with virtual
709 * urls without the need for rewriterules in the webserver (as 711 * urls without the need for rewriterules in the webserver (as
710 * long as PATH_INFO is included in the cache lookup key). 712 * long as PATH_INFO is included in the cache lookup key).
711 */ 713 */
712 path = ctx.env.path_info; 714 path = ctx.env.path_info;
713 if (!ctx.qry.url && path) { 715 if (!ctx.qry.url && path) {
714 if (path[0] == '/') 716 if (path[0] == '/')
715 path++; 717 path++;
716 ctx.qry.url = xstrdup(path); 718 ctx.qry.url = xstrdup(path);
717 if (ctx.qry.raw) { 719 if (ctx.qry.raw) {
718 qry = ctx.qry.raw; 720 qry = ctx.qry.raw;
719 ctx.qry.raw = xstrdup(fmt("%s?%s", path, qry)); 721 ctx.qry.raw = xstrdup(fmt("%s?%s", path, qry));
720 free(qry); 722 free(qry);
721 } else 723 } else
722 ctx.qry.raw = xstrdup(ctx.qry.url); 724 ctx.qry.raw = xstrdup(ctx.qry.url);
723 cgit_parse_url(ctx.qry.url); 725 cgit_parse_url(ctx.qry.url);
724 } 726 }
725 727
726 ttl = calc_ttl(); 728 ttl = calc_ttl();
727 ctx.page.expires += ttl*60; 729 ctx.page.expires += ttl*60;
728 if (ctx.env.request_method && !strcmp(ctx.env.request_method, "HEAD")) 730 if (ctx.env.request_method && !strcmp(ctx.env.request_method, "HEAD"))
729 ctx.cfg.nocache = 1; 731 ctx.cfg.nocache = 1;
730 if (ctx.cfg.nocache) 732 if (ctx.cfg.nocache)
731 ctx.cfg.cache_size = 0; 733 ctx.cfg.cache_size = 0;
732 err = cache_process(ctx.cfg.cache_size, ctx.cfg.cache_root, 734 err = cache_process(ctx.cfg.cache_size, ctx.cfg.cache_root,
733 ctx.qry.raw, ttl, process_request, &ctx); 735 ctx.qry.raw, ttl, process_request, &ctx);
734 if (err) 736 if (err)
735 cgit_print_error(fmt("Error processing page: %s (%d)", 737 cgit_print_error(fmt("Error processing page: %s (%d)",
736 strerror(err), err)); 738 strerror(err), err));
737 return err; 739 return err;
738} 740}
diff --git a/cgit.h b/cgit.h
index f990b15..bb8f598 100644
--- a/cgit.h
+++ b/cgit.h
@@ -1,300 +1,301 @@
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 max_stats; 76 int max_stats;
77 time_t mtime; 77 time_t mtime;
78 struct cgit_filter *about_filter; 78 struct cgit_filter *about_filter;
79 struct cgit_filter *commit_filter; 79 struct cgit_filter *commit_filter;
80 struct cgit_filter *source_filter; 80 struct cgit_filter *source_filter;
81}; 81};
82 82
83typedef void (*repo_config_fn)(struct cgit_repo *repo, const char *name, 83typedef void (*repo_config_fn)(struct cgit_repo *repo, const char *name,
84 const char *value); 84 const char *value);
85 85
86struct cgit_repolist { 86struct cgit_repolist {
87 int length; 87 int length;
88 int count; 88 int count;
89 struct cgit_repo *repos; 89 struct cgit_repo *repos;
90}; 90};
91 91
92struct commitinfo { 92struct commitinfo {
93 struct commit *commit; 93 struct commit *commit;
94 char *author; 94 char *author;
95 char *author_email; 95 char *author_email;
96 unsigned long author_date; 96 unsigned long author_date;
97 char *committer; 97 char *committer;
98 char *committer_email; 98 char *committer_email;
99 unsigned long committer_date; 99 unsigned long committer_date;
100 char *subject; 100 char *subject;
101 char *msg; 101 char *msg;
102 char *msg_encoding; 102 char *msg_encoding;
103}; 103};
104 104
105struct taginfo { 105struct taginfo {
106 char *tagger; 106 char *tagger;
107 char *tagger_email; 107 char *tagger_email;
108 unsigned long tagger_date; 108 unsigned long tagger_date;
109 char *msg; 109 char *msg;
110}; 110};
111 111
112struct refinfo { 112struct refinfo {
113 const char *refname; 113 const char *refname;
114 struct object *object; 114 struct object *object;
115 union { 115 union {
116 struct taginfo *tag; 116 struct taginfo *tag;
117 struct commitinfo *commit; 117 struct commitinfo *commit;
118 }; 118 };
119}; 119};
120 120
121struct reflist { 121struct reflist {
122 struct refinfo **refs; 122 struct refinfo **refs;
123 int alloc; 123 int alloc;
124 int count; 124 int count;
125}; 125};
126 126
127struct cgit_query { 127struct cgit_query {
128 int has_symref; 128 int has_symref;
129 int has_sha1; 129 int has_sha1;
130 char *raw; 130 char *raw;
131 char *repo; 131 char *repo;
132 char *page; 132 char *page;
133 char *search; 133 char *search;
134 char *grep; 134 char *grep;
135 char *head; 135 char *head;
136 char *sha1; 136 char *sha1;
137 char *sha2; 137 char *sha2;
138 char *path; 138 char *path;
139 char *name; 139 char *name;
140 char *mimetype; 140 char *mimetype;
141 char *url; 141 char *url;
142 char *period; 142 char *period;
143 int ofs; 143 int ofs;
144 int nohead; 144 int nohead;
145 char *sort; 145 char *sort;
146 int showmsg; 146 int showmsg;
147 int ssdiff; 147 int ssdiff;
148 int context;
148 char *vpath; 149 char *vpath;
149}; 150};
150 151
151struct cgit_config { 152struct cgit_config {
152 char *agefile; 153 char *agefile;
153 char *cache_root; 154 char *cache_root;
154 char *clone_prefix; 155 char *clone_prefix;
155 char *css; 156 char *css;
156 char *favicon; 157 char *favicon;
157 char *footer; 158 char *footer;
158 char *head_include; 159 char *head_include;
159 char *header; 160 char *header;
160 char *index_header; 161 char *index_header;
161 char *index_info; 162 char *index_info;
162 char *logo; 163 char *logo;
163 char *logo_link; 164 char *logo_link;
164 char *module_link; 165 char *module_link;
165 char *robots; 166 char *robots;
166 char *root_title; 167 char *root_title;
167 char *root_desc; 168 char *root_desc;
168 char *root_readme; 169 char *root_readme;
169 char *script_name; 170 char *script_name;
170 char *section; 171 char *section;
171 char *virtual_root; 172 char *virtual_root;
172 int cache_size; 173 int cache_size;
173 int cache_dynamic_ttl; 174 int cache_dynamic_ttl;
174 int cache_max_create_time; 175 int cache_max_create_time;
175 int cache_repo_ttl; 176 int cache_repo_ttl;
176 int cache_root_ttl; 177 int cache_root_ttl;
177 int cache_scanrc_ttl; 178 int cache_scanrc_ttl;
178 int cache_static_ttl; 179 int cache_static_ttl;
179 int embedded; 180 int embedded;
180 int enable_filter_overrides; 181 int enable_filter_overrides;
181 int enable_index_links; 182 int enable_index_links;
182 int enable_log_filecount; 183 int enable_log_filecount;
183 int enable_log_linecount; 184 int enable_log_linecount;
184 int enable_remote_branches; 185 int enable_remote_branches;
185 int enable_tree_linenumbers; 186 int enable_tree_linenumbers;
186 int local_time; 187 int local_time;
187 int max_repo_count; 188 int max_repo_count;
188 int max_commit_count; 189 int max_commit_count;
189 int max_lock_attempts; 190 int max_lock_attempts;
190 int max_msg_len; 191 int max_msg_len;
191 int max_repodesc_len; 192 int max_repodesc_len;
192 int max_blob_size; 193 int max_blob_size;
193 int max_stats; 194 int max_stats;
194 int nocache; 195 int nocache;
195 int noplainemail; 196 int noplainemail;
196 int noheader; 197 int noheader;
197 int renamelimit; 198 int renamelimit;
198 int snapshots; 199 int snapshots;
199 int summary_branches; 200 int summary_branches;
200 int summary_log; 201 int summary_log;
201 int summary_tags; 202 int summary_tags;
202 int ssdiff; 203 int ssdiff;
203 struct string_list mimetypes; 204 struct string_list mimetypes;
204 struct cgit_filter *about_filter; 205 struct cgit_filter *about_filter;
205 struct cgit_filter *commit_filter; 206 struct cgit_filter *commit_filter;
206 struct cgit_filter *source_filter; 207 struct cgit_filter *source_filter;
207}; 208};
208 209
209struct cgit_page { 210struct cgit_page {
210 time_t modified; 211 time_t modified;
211 time_t expires; 212 time_t expires;
212 size_t size; 213 size_t size;
213 char *mimetype; 214 char *mimetype;
214 char *charset; 215 char *charset;
215 char *filename; 216 char *filename;
216 char *etag; 217 char *etag;
217 char *title; 218 char *title;
218 int status; 219 int status;
219 char *statusmsg; 220 char *statusmsg;
220}; 221};
221 222
222struct cgit_environment { 223struct cgit_environment {
223 char *cgit_config; 224 char *cgit_config;
224 char *http_host; 225 char *http_host;
225 char *https; 226 char *https;
226 char *no_http; 227 char *no_http;
227 char *path_info; 228 char *path_info;
228 char *query_string; 229 char *query_string;
229 char *request_method; 230 char *request_method;
230 char *script_name; 231 char *script_name;
231 char *server_name; 232 char *server_name;
232 char *server_port; 233 char *server_port;
233}; 234};
234 235
235struct cgit_context { 236struct cgit_context {
236 struct cgit_environment env; 237 struct cgit_environment env;
237 struct cgit_query qry; 238 struct cgit_query qry;
238 struct cgit_config cfg; 239 struct cgit_config cfg;
239 struct cgit_repo *repo; 240 struct cgit_repo *repo;
240 struct cgit_page page; 241 struct cgit_page page;
241}; 242};
242 243
243struct cgit_snapshot_format { 244struct cgit_snapshot_format {
244 const char *suffix; 245 const char *suffix;
245 const char *mimetype; 246 const char *mimetype;
246 write_archive_fn_t write_func; 247 write_archive_fn_t write_func;
247 int bit; 248 int bit;
248}; 249};
249 250
250extern const char *cgit_version; 251extern const char *cgit_version;
251 252
252extern struct cgit_repolist cgit_repolist; 253extern struct cgit_repolist cgit_repolist;
253extern struct cgit_context ctx; 254extern struct cgit_context ctx;
254extern const struct cgit_snapshot_format cgit_snapshot_formats[]; 255extern const struct cgit_snapshot_format cgit_snapshot_formats[];
255 256
256extern struct cgit_repo *cgit_add_repo(const char *url); 257extern struct cgit_repo *cgit_add_repo(const char *url);
257extern struct cgit_repo *cgit_get_repoinfo(const char *url); 258extern struct cgit_repo *cgit_get_repoinfo(const char *url);
258extern void cgit_repo_config_cb(const char *name, const char *value); 259extern void cgit_repo_config_cb(const char *name, const char *value);
259 260
260extern int chk_zero(int result, char *msg); 261extern int chk_zero(int result, char *msg);
261extern int chk_positive(int result, char *msg); 262extern int chk_positive(int result, char *msg);
262extern int chk_non_negative(int result, char *msg); 263extern int chk_non_negative(int result, char *msg);
263 264
264extern char *trim_end(const char *str, char c); 265extern char *trim_end(const char *str, char c);
265extern char *strlpart(char *txt, int maxlen); 266extern char *strlpart(char *txt, int maxlen);
266extern char *strrpart(char *txt, int maxlen); 267extern char *strrpart(char *txt, int maxlen);
267 268
268extern void cgit_add_ref(struct reflist *list, struct refinfo *ref); 269extern void cgit_add_ref(struct reflist *list, struct refinfo *ref);
269extern int cgit_refs_cb(const char *refname, const unsigned char *sha1, 270extern int cgit_refs_cb(const char *refname, const unsigned char *sha1,
270 int flags, void *cb_data); 271 int flags, void *cb_data);
271 272
272extern void *cgit_free_commitinfo(struct commitinfo *info); 273extern void *cgit_free_commitinfo(struct commitinfo *info);
273 274
274extern int cgit_diff_files(const unsigned char *old_sha1, 275extern int cgit_diff_files(const unsigned char *old_sha1,
275 const unsigned char *new_sha1, 276 const unsigned char *new_sha1,
276 unsigned long *old_size, unsigned long *new_size, 277 unsigned long *old_size, unsigned long *new_size,
277 int *binary, linediff_fn fn); 278 int *binary, int context, linediff_fn fn);
278 279
279extern void cgit_diff_tree(const unsigned char *old_sha1, 280extern void cgit_diff_tree(const unsigned char *old_sha1,
280 const unsigned char *new_sha1, 281 const unsigned char *new_sha1,
281 filepair_fn fn, const char *prefix); 282 filepair_fn fn, const char *prefix);
282 283
283extern void cgit_diff_commit(struct commit *commit, filepair_fn fn); 284extern void cgit_diff_commit(struct commit *commit, filepair_fn fn);
284 285
285extern char *fmt(const char *format,...); 286extern char *fmt(const char *format,...);
286 287
287extern struct commitinfo *cgit_parse_commit(struct commit *commit); 288extern struct commitinfo *cgit_parse_commit(struct commit *commit);
288extern struct taginfo *cgit_parse_tag(struct tag *tag); 289extern struct taginfo *cgit_parse_tag(struct tag *tag);
289extern void cgit_parse_url(const char *url); 290extern void cgit_parse_url(const char *url);
290 291
291extern const char *cgit_repobasename(const char *reponame); 292extern const char *cgit_repobasename(const char *reponame);
292 293
293extern int cgit_parse_snapshots_mask(const char *str); 294extern int cgit_parse_snapshots_mask(const char *str);
294 295
295extern int cgit_open_filter(struct cgit_filter *filter); 296extern int cgit_open_filter(struct cgit_filter *filter);
296extern int cgit_close_filter(struct cgit_filter *filter); 297extern int cgit_close_filter(struct cgit_filter *filter);
297 298
298extern int readfile(const char *path, char **buf, size_t *size); 299extern int readfile(const char *path, char **buf, size_t *size);
299 300
300#endif /* CGIT_H */ 301#endif /* CGIT_H */
diff --git a/shared.c b/shared.c
index 6e8f0ce..7cf1e59 100644
--- a/shared.c
+++ b/shared.c
@@ -1,424 +1,425 @@
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;
13 13
14int chk_zero(int result, char *msg) 14int chk_zero(int result, char *msg)
15{ 15{
16 if (result != 0) 16 if (result != 0)
17 die("%s: %s", msg, strerror(errno)); 17 die("%s: %s", msg, strerror(errno));
18 return result; 18 return result;
19} 19}
20 20
21int chk_positive(int result, char *msg) 21int chk_positive(int result, char *msg)
22{ 22{
23 if (result <= 0) 23 if (result <= 0)
24 die("%s: %s", msg, strerror(errno)); 24 die("%s: %s", msg, strerror(errno));
25 return result; 25 return result;
26} 26}
27 27
28int chk_non_negative(int result, char *msg) 28int chk_non_negative(int result, char *msg)
29{ 29{
30 if (result < 0) 30 if (result < 0)
31 die("%s: %s",msg, strerror(errno)); 31 die("%s: %s",msg, strerror(errno));
32 return result; 32 return result;
33} 33}
34 34
35struct cgit_repo *cgit_add_repo(const char *url) 35struct cgit_repo *cgit_add_repo(const char *url)
36{ 36{
37 struct cgit_repo *ret; 37 struct cgit_repo *ret;
38 38
39 if (++cgit_repolist.count > cgit_repolist.length) { 39 if (++cgit_repolist.count > cgit_repolist.length) {
40 if (cgit_repolist.length == 0) 40 if (cgit_repolist.length == 0)
41 cgit_repolist.length = 8; 41 cgit_repolist.length = 8;
42 else 42 else
43 cgit_repolist.length *= 2; 43 cgit_repolist.length *= 2;
44 cgit_repolist.repos = xrealloc(cgit_repolist.repos, 44 cgit_repolist.repos = xrealloc(cgit_repolist.repos,
45 cgit_repolist.length * 45 cgit_repolist.length *
46 sizeof(struct cgit_repo)); 46 sizeof(struct cgit_repo));
47 } 47 }
48 48
49 ret = &cgit_repolist.repos[cgit_repolist.count-1]; 49 ret = &cgit_repolist.repos[cgit_repolist.count-1];
50 memset(ret, 0, sizeof(struct cgit_repo)); 50 memset(ret, 0, sizeof(struct cgit_repo));
51 ret->url = trim_end(url, '/'); 51 ret->url = trim_end(url, '/');
52 ret->name = ret->url; 52 ret->name = ret->url;
53 ret->path = NULL; 53 ret->path = NULL;
54 ret->desc = "[no description]"; 54 ret->desc = "[no description]";
55 ret->owner = NULL; 55 ret->owner = NULL;
56 ret->section = ctx.cfg.section; 56 ret->section = ctx.cfg.section;
57 ret->defbranch = "master"; 57 ret->defbranch = "master";
58 ret->snapshots = ctx.cfg.snapshots; 58 ret->snapshots = ctx.cfg.snapshots;
59 ret->enable_log_filecount = ctx.cfg.enable_log_filecount; 59 ret->enable_log_filecount = ctx.cfg.enable_log_filecount;
60 ret->enable_log_linecount = ctx.cfg.enable_log_linecount; 60 ret->enable_log_linecount = ctx.cfg.enable_log_linecount;
61 ret->enable_remote_branches = ctx.cfg.enable_remote_branches; 61 ret->enable_remote_branches = ctx.cfg.enable_remote_branches;
62 ret->max_stats = ctx.cfg.max_stats; 62 ret->max_stats = ctx.cfg.max_stats;
63 ret->module_link = ctx.cfg.module_link; 63 ret->module_link = ctx.cfg.module_link;
64 ret->readme = NULL; 64 ret->readme = NULL;
65 ret->mtime = -1; 65 ret->mtime = -1;
66 ret->about_filter = ctx.cfg.about_filter; 66 ret->about_filter = ctx.cfg.about_filter;
67 ret->commit_filter = ctx.cfg.commit_filter; 67 ret->commit_filter = ctx.cfg.commit_filter;
68 ret->source_filter = ctx.cfg.source_filter; 68 ret->source_filter = ctx.cfg.source_filter;
69 return ret; 69 return ret;
70} 70}
71 71
72struct cgit_repo *cgit_get_repoinfo(const char *url) 72struct cgit_repo *cgit_get_repoinfo(const char *url)
73{ 73{
74 int i; 74 int i;
75 struct cgit_repo *repo; 75 struct cgit_repo *repo;
76 76
77 for (i=0; i<cgit_repolist.count; i++) { 77 for (i=0; i<cgit_repolist.count; i++) {
78 repo = &cgit_repolist.repos[i]; 78 repo = &cgit_repolist.repos[i];
79 if (!strcmp(repo->url, url)) 79 if (!strcmp(repo->url, url))
80 return repo; 80 return repo;
81 } 81 }
82 return NULL; 82 return NULL;
83} 83}
84 84
85void *cgit_free_commitinfo(struct commitinfo *info) 85void *cgit_free_commitinfo(struct commitinfo *info)
86{ 86{
87 free(info->author); 87 free(info->author);
88 free(info->author_email); 88 free(info->author_email);
89 free(info->committer); 89 free(info->committer);
90 free(info->committer_email); 90 free(info->committer_email);
91 free(info->subject); 91 free(info->subject);
92 free(info->msg); 92 free(info->msg);
93 free(info->msg_encoding); 93 free(info->msg_encoding);
94 free(info); 94 free(info);
95 return NULL; 95 return NULL;
96} 96}
97 97
98char *trim_end(const char *str, char c) 98char *trim_end(const char *str, char c)
99{ 99{
100 int len; 100 int len;
101 char *s, *t; 101 char *s, *t;
102 102
103 if (str == NULL) 103 if (str == NULL)
104 return NULL; 104 return NULL;
105 t = (char *)str; 105 t = (char *)str;
106 len = strlen(t); 106 len = strlen(t);
107 while(len > 0 && t[len - 1] == c) 107 while(len > 0 && t[len - 1] == c)
108 len--; 108 len--;
109 109
110 if (len == 0) 110 if (len == 0)
111 return NULL; 111 return NULL;
112 112
113 c = t[len]; 113 c = t[len];
114 t[len] = '\0'; 114 t[len] = '\0';
115 s = xstrdup(t); 115 s = xstrdup(t);
116 t[len] = c; 116 t[len] = c;
117 return s; 117 return s;
118} 118}
119 119
120char *strlpart(char *txt, int maxlen) 120char *strlpart(char *txt, int maxlen)
121{ 121{
122 char *result; 122 char *result;
123 123
124 if (!txt) 124 if (!txt)
125 return txt; 125 return txt;
126 126
127 if (strlen(txt) <= maxlen) 127 if (strlen(txt) <= maxlen)
128 return txt; 128 return txt;
129 result = xmalloc(maxlen + 1); 129 result = xmalloc(maxlen + 1);
130 memcpy(result, txt, maxlen - 3); 130 memcpy(result, txt, maxlen - 3);
131 result[maxlen-1] = result[maxlen-2] = result[maxlen-3] = '.'; 131 result[maxlen-1] = result[maxlen-2] = result[maxlen-3] = '.';
132 result[maxlen] = '\0'; 132 result[maxlen] = '\0';
133 return result; 133 return result;
134} 134}
135 135
136char *strrpart(char *txt, int maxlen) 136char *strrpart(char *txt, int maxlen)
137{ 137{
138 char *result; 138 char *result;
139 139
140 if (!txt) 140 if (!txt)
141 return txt; 141 return txt;
142 142
143 if (strlen(txt) <= maxlen) 143 if (strlen(txt) <= maxlen)
144 return txt; 144 return txt;
145 result = xmalloc(maxlen + 1); 145 result = xmalloc(maxlen + 1);
146 memcpy(result + 3, txt + strlen(txt) - maxlen + 4, maxlen - 3); 146 memcpy(result + 3, txt + strlen(txt) - maxlen + 4, maxlen - 3);
147 result[0] = result[1] = result[2] = '.'; 147 result[0] = result[1] = result[2] = '.';
148 return result; 148 return result;
149} 149}
150 150
151void cgit_add_ref(struct reflist *list, struct refinfo *ref) 151void cgit_add_ref(struct reflist *list, struct refinfo *ref)
152{ 152{
153 size_t size; 153 size_t size;
154 154
155 if (list->count >= list->alloc) { 155 if (list->count >= list->alloc) {
156 list->alloc += (list->alloc ? list->alloc : 4); 156 list->alloc += (list->alloc ? list->alloc : 4);
157 size = list->alloc * sizeof(struct refinfo *); 157 size = list->alloc * sizeof(struct refinfo *);
158 list->refs = xrealloc(list->refs, size); 158 list->refs = xrealloc(list->refs, size);
159 } 159 }
160 list->refs[list->count++] = ref; 160 list->refs[list->count++] = ref;
161} 161}
162 162
163struct refinfo *cgit_mk_refinfo(const char *refname, const unsigned char *sha1) 163struct refinfo *cgit_mk_refinfo(const char *refname, const unsigned char *sha1)
164{ 164{
165 struct refinfo *ref; 165 struct refinfo *ref;
166 166
167 ref = xmalloc(sizeof (struct refinfo)); 167 ref = xmalloc(sizeof (struct refinfo));
168 ref->refname = xstrdup(refname); 168 ref->refname = xstrdup(refname);
169 ref->object = parse_object(sha1); 169 ref->object = parse_object(sha1);
170 switch (ref->object->type) { 170 switch (ref->object->type) {
171 case OBJ_TAG: 171 case OBJ_TAG:
172 ref->tag = cgit_parse_tag((struct tag *)ref->object); 172 ref->tag = cgit_parse_tag((struct tag *)ref->object);
173 break; 173 break;
174 case OBJ_COMMIT: 174 case OBJ_COMMIT:
175 ref->commit = cgit_parse_commit((struct commit *)ref->object); 175 ref->commit = cgit_parse_commit((struct commit *)ref->object);
176 break; 176 break;
177 } 177 }
178 return ref; 178 return ref;
179} 179}
180 180
181int cgit_refs_cb(const char *refname, const unsigned char *sha1, int flags, 181int cgit_refs_cb(const char *refname, const unsigned char *sha1, int flags,
182 void *cb_data) 182 void *cb_data)
183{ 183{
184 struct reflist *list = (struct reflist *)cb_data; 184 struct reflist *list = (struct reflist *)cb_data;
185 struct refinfo *info = cgit_mk_refinfo(refname, sha1); 185 struct refinfo *info = cgit_mk_refinfo(refname, sha1);
186 186
187 if (info) 187 if (info)
188 cgit_add_ref(list, info); 188 cgit_add_ref(list, info);
189 return 0; 189 return 0;
190} 190}
191 191
192void cgit_diff_tree_cb(struct diff_queue_struct *q, 192void cgit_diff_tree_cb(struct diff_queue_struct *q,
193 struct diff_options *options, void *data) 193 struct diff_options *options, void *data)
194{ 194{
195 int i; 195 int i;
196 196
197 for (i = 0; i < q->nr; i++) { 197 for (i = 0; i < q->nr; i++) {
198 if (q->queue[i]->status == 'U') 198 if (q->queue[i]->status == 'U')
199 continue; 199 continue;
200 ((filepair_fn)data)(q->queue[i]); 200 ((filepair_fn)data)(q->queue[i]);
201 } 201 }
202} 202}
203 203
204static int load_mmfile(mmfile_t *file, const unsigned char *sha1) 204static int load_mmfile(mmfile_t *file, const unsigned char *sha1)
205{ 205{
206 enum object_type type; 206 enum object_type type;
207 207
208 if (is_null_sha1(sha1)) { 208 if (is_null_sha1(sha1)) {
209 file->ptr = (char *)""; 209 file->ptr = (char *)"";
210 file->size = 0; 210 file->size = 0;
211 } else { 211 } else {
212 file->ptr = read_sha1_file(sha1, &type, 212 file->ptr = read_sha1_file(sha1, &type,
213 (unsigned long *)&file->size); 213 (unsigned long *)&file->size);
214 } 214 }
215 return 1; 215 return 1;
216} 216}
217 217
218/* 218/*
219 * Receive diff-buffers from xdiff and concatenate them as 219 * Receive diff-buffers from xdiff and concatenate them as
220 * needed across multiple callbacks. 220 * needed across multiple callbacks.
221 * 221 *
222 * This is basically a copy of xdiff-interface.c/xdiff_outf(), 222 * This is basically a copy of xdiff-interface.c/xdiff_outf(),
223 * ripped from git and modified to use globals instead of 223 * ripped from git and modified to use globals instead of
224 * a special callback-struct. 224 * a special callback-struct.
225 */ 225 */
226char *diffbuf = NULL; 226char *diffbuf = NULL;
227int buflen = 0; 227int buflen = 0;
228 228
229int filediff_cb(void *priv, mmbuffer_t *mb, int nbuf) 229int filediff_cb(void *priv, mmbuffer_t *mb, int nbuf)
230{ 230{
231 int i; 231 int i;
232 232
233 for (i = 0; i < nbuf; i++) { 233 for (i = 0; i < nbuf; i++) {
234 if (mb[i].ptr[mb[i].size-1] != '\n') { 234 if (mb[i].ptr[mb[i].size-1] != '\n') {
235 /* Incomplete line */ 235 /* Incomplete line */
236 diffbuf = xrealloc(diffbuf, buflen + mb[i].size); 236 diffbuf = xrealloc(diffbuf, buflen + mb[i].size);
237 memcpy(diffbuf + buflen, mb[i].ptr, mb[i].size); 237 memcpy(diffbuf + buflen, mb[i].ptr, mb[i].size);
238 buflen += mb[i].size; 238 buflen += mb[i].size;
239 continue; 239 continue;
240 } 240 }
241 241
242 /* we have a complete line */ 242 /* we have a complete line */
243 if (!diffbuf) { 243 if (!diffbuf) {
244 ((linediff_fn)priv)(mb[i].ptr, mb[i].size); 244 ((linediff_fn)priv)(mb[i].ptr, mb[i].size);
245 continue; 245 continue;
246 } 246 }
247 diffbuf = xrealloc(diffbuf, buflen + mb[i].size); 247 diffbuf = xrealloc(diffbuf, buflen + mb[i].size);
248 memcpy(diffbuf + buflen, mb[i].ptr, mb[i].size); 248 memcpy(diffbuf + buflen, mb[i].ptr, mb[i].size);
249 ((linediff_fn)priv)(diffbuf, buflen + mb[i].size); 249 ((linediff_fn)priv)(diffbuf, buflen + mb[i].size);
250 free(diffbuf); 250 free(diffbuf);
251 diffbuf = NULL; 251 diffbuf = NULL;
252 buflen = 0; 252 buflen = 0;
253 } 253 }
254 if (diffbuf) { 254 if (diffbuf) {
255 ((linediff_fn)priv)(diffbuf, buflen); 255 ((linediff_fn)priv)(diffbuf, buflen);
256 free(diffbuf); 256 free(diffbuf);
257 diffbuf = NULL; 257 diffbuf = NULL;
258 buflen = 0; 258 buflen = 0;
259 } 259 }
260 return 0; 260 return 0;
261} 261}
262 262
263int cgit_diff_files(const unsigned char *old_sha1, 263int cgit_diff_files(const unsigned char *old_sha1,
264 const unsigned char *new_sha1, unsigned long *old_size, 264 const unsigned char *new_sha1, unsigned long *old_size,
265 unsigned long *new_size, int *binary, linediff_fn fn) 265 unsigned long *new_size, int *binary, int context,
266 linediff_fn fn)
266{ 267{
267 mmfile_t file1, file2; 268 mmfile_t file1, file2;
268 xpparam_t diff_params; 269 xpparam_t diff_params;
269 xdemitconf_t emit_params; 270 xdemitconf_t emit_params;
270 xdemitcb_t emit_cb; 271 xdemitcb_t emit_cb;
271 272
272 if (!load_mmfile(&file1, old_sha1) || !load_mmfile(&file2, new_sha1)) 273 if (!load_mmfile(&file1, old_sha1) || !load_mmfile(&file2, new_sha1))
273 return 1; 274 return 1;
274 275
275 *old_size = file1.size; 276 *old_size = file1.size;
276 *new_size = file2.size; 277 *new_size = file2.size;
277 278
278 if ((file1.ptr && buffer_is_binary(file1.ptr, file1.size)) || 279 if ((file1.ptr && buffer_is_binary(file1.ptr, file1.size)) ||
279 (file2.ptr && buffer_is_binary(file2.ptr, file2.size))) { 280 (file2.ptr && buffer_is_binary(file2.ptr, file2.size))) {
280 *binary = 1; 281 *binary = 1;
281 return 0; 282 return 0;
282 } 283 }
283 284
284 memset(&diff_params, 0, sizeof(diff_params)); 285 memset(&diff_params, 0, sizeof(diff_params));
285 memset(&emit_params, 0, sizeof(emit_params)); 286 memset(&emit_params, 0, sizeof(emit_params));
286 memset(&emit_cb, 0, sizeof(emit_cb)); 287 memset(&emit_cb, 0, sizeof(emit_cb));
287 diff_params.flags = XDF_NEED_MINIMAL; 288 diff_params.flags = XDF_NEED_MINIMAL;
288 emit_params.ctxlen = 3; 289 emit_params.ctxlen = context > 0 ? context : 3;
289 emit_params.flags = XDL_EMIT_FUNCNAMES; 290 emit_params.flags = XDL_EMIT_FUNCNAMES;
290 emit_cb.outf = filediff_cb; 291 emit_cb.outf = filediff_cb;
291 emit_cb.priv = fn; 292 emit_cb.priv = fn;
292 xdl_diff(&file1, &file2, &diff_params, &emit_params, &emit_cb); 293 xdl_diff(&file1, &file2, &diff_params, &emit_params, &emit_cb);
293 return 0; 294 return 0;
294} 295}
295 296
296void cgit_diff_tree(const unsigned char *old_sha1, 297void cgit_diff_tree(const unsigned char *old_sha1,
297 const unsigned char *new_sha1, 298 const unsigned char *new_sha1,
298 filepair_fn fn, const char *prefix) 299 filepair_fn fn, const char *prefix)
299{ 300{
300 struct diff_options opt; 301 struct diff_options opt;
301 int ret; 302 int ret;
302 int prefixlen; 303 int prefixlen;
303 304
304 diff_setup(&opt); 305 diff_setup(&opt);
305 opt.output_format = DIFF_FORMAT_CALLBACK; 306 opt.output_format = DIFF_FORMAT_CALLBACK;
306 opt.detect_rename = 1; 307 opt.detect_rename = 1;
307 opt.rename_limit = ctx.cfg.renamelimit; 308 opt.rename_limit = ctx.cfg.renamelimit;
308 DIFF_OPT_SET(&opt, RECURSIVE); 309 DIFF_OPT_SET(&opt, RECURSIVE);
309 opt.format_callback = cgit_diff_tree_cb; 310 opt.format_callback = cgit_diff_tree_cb;
310 opt.format_callback_data = fn; 311 opt.format_callback_data = fn;
311 if (prefix) { 312 if (prefix) {
312 opt.nr_paths = 1; 313 opt.nr_paths = 1;
313 opt.paths = &prefix; 314 opt.paths = &prefix;
314 prefixlen = strlen(prefix); 315 prefixlen = strlen(prefix);
315 opt.pathlens = &prefixlen; 316 opt.pathlens = &prefixlen;
316 } 317 }
317 diff_setup_done(&opt); 318 diff_setup_done(&opt);
318 319
319 if (old_sha1 && !is_null_sha1(old_sha1)) 320 if (old_sha1 && !is_null_sha1(old_sha1))
320 ret = diff_tree_sha1(old_sha1, new_sha1, "", &opt); 321 ret = diff_tree_sha1(old_sha1, new_sha1, "", &opt);
321 else 322 else
322 ret = diff_root_tree_sha1(new_sha1, "", &opt); 323 ret = diff_root_tree_sha1(new_sha1, "", &opt);
323 diffcore_std(&opt); 324 diffcore_std(&opt);
324 diff_flush(&opt); 325 diff_flush(&opt);
325} 326}
326 327
327void cgit_diff_commit(struct commit *commit, filepair_fn fn) 328void cgit_diff_commit(struct commit *commit, filepair_fn fn)
328{ 329{
329 unsigned char *old_sha1 = NULL; 330 unsigned char *old_sha1 = NULL;
330 331
331 if (commit->parents) 332 if (commit->parents)
332 old_sha1 = commit->parents->item->object.sha1; 333 old_sha1 = commit->parents->item->object.sha1;
333 cgit_diff_tree(old_sha1, commit->object.sha1, fn, NULL); 334 cgit_diff_tree(old_sha1, commit->object.sha1, fn, NULL);
334} 335}
335 336
336int cgit_parse_snapshots_mask(const char *str) 337int cgit_parse_snapshots_mask(const char *str)
337{ 338{
338 const struct cgit_snapshot_format *f; 339 const struct cgit_snapshot_format *f;
339 static const char *delim = " \t,:/|;"; 340 static const char *delim = " \t,:/|;";
340 int tl, sl, rv = 0; 341 int tl, sl, rv = 0;
341 342
342 /* favor legacy setting */ 343 /* favor legacy setting */
343 if(atoi(str)) 344 if(atoi(str))
344 return 1; 345 return 1;
345 for(;;) { 346 for(;;) {
346 str += strspn(str,delim); 347 str += strspn(str,delim);
347 tl = strcspn(str,delim); 348 tl = strcspn(str,delim);
348 if (!tl) 349 if (!tl)
349 break; 350 break;
350 for (f = cgit_snapshot_formats; f->suffix; f++) { 351 for (f = cgit_snapshot_formats; f->suffix; f++) {
351 sl = strlen(f->suffix); 352 sl = strlen(f->suffix);
352 if((tl == sl && !strncmp(f->suffix, str, tl)) || 353 if((tl == sl && !strncmp(f->suffix, str, tl)) ||
353 (tl == sl-1 && !strncmp(f->suffix+1, str, tl-1))) { 354 (tl == sl-1 && !strncmp(f->suffix+1, str, tl-1))) {
354 rv |= f->bit; 355 rv |= f->bit;
355 break; 356 break;
356 } 357 }
357 } 358 }
358 str += tl; 359 str += tl;
359 } 360 }
360 return rv; 361 return rv;
361} 362}
362 363
363int cgit_open_filter(struct cgit_filter *filter) 364int cgit_open_filter(struct cgit_filter *filter)
364{ 365{
365 366
366 filter->old_stdout = chk_positive(dup(STDOUT_FILENO), 367 filter->old_stdout = chk_positive(dup(STDOUT_FILENO),
367 "Unable to duplicate STDOUT"); 368 "Unable to duplicate STDOUT");
368 chk_zero(pipe(filter->pipe_fh), "Unable to create pipe to subprocess"); 369 chk_zero(pipe(filter->pipe_fh), "Unable to create pipe to subprocess");
369 filter->pid = chk_non_negative(fork(), "Unable to create subprocess"); 370 filter->pid = chk_non_negative(fork(), "Unable to create subprocess");
370 if (filter->pid == 0) { 371 if (filter->pid == 0) {
371 close(filter->pipe_fh[1]); 372 close(filter->pipe_fh[1]);
372 chk_non_negative(dup2(filter->pipe_fh[0], STDIN_FILENO), 373 chk_non_negative(dup2(filter->pipe_fh[0], STDIN_FILENO),
373 "Unable to use pipe as STDIN"); 374 "Unable to use pipe as STDIN");
374 execvp(filter->cmd, filter->argv); 375 execvp(filter->cmd, filter->argv);
375 die("Unable to exec subprocess %s: %s (%d)", filter->cmd, 376 die("Unable to exec subprocess %s: %s (%d)", filter->cmd,
376 strerror(errno), errno); 377 strerror(errno), errno);
377 } 378 }
378 close(filter->pipe_fh[0]); 379 close(filter->pipe_fh[0]);
379 chk_non_negative(dup2(filter->pipe_fh[1], STDOUT_FILENO), 380 chk_non_negative(dup2(filter->pipe_fh[1], STDOUT_FILENO),
380 "Unable to use pipe as STDOUT"); 381 "Unable to use pipe as STDOUT");
381 close(filter->pipe_fh[1]); 382 close(filter->pipe_fh[1]);
382 return 0; 383 return 0;
383} 384}
384 385
385int cgit_close_filter(struct cgit_filter *filter) 386int cgit_close_filter(struct cgit_filter *filter)
386{ 387{
387 chk_non_negative(dup2(filter->old_stdout, STDOUT_FILENO), 388 chk_non_negative(dup2(filter->old_stdout, STDOUT_FILENO),
388 "Unable to restore STDOUT"); 389 "Unable to restore STDOUT");
389 close(filter->old_stdout); 390 close(filter->old_stdout);
390 if (filter->pid < 0) 391 if (filter->pid < 0)
391 return 0; 392 return 0;
392 waitpid(filter->pid, &filter->exitstatus, 0); 393 waitpid(filter->pid, &filter->exitstatus, 0);
393 if (WIFEXITED(filter->exitstatus) && !WEXITSTATUS(filter->exitstatus)) 394 if (WIFEXITED(filter->exitstatus) && !WEXITSTATUS(filter->exitstatus))
394 return 0; 395 return 0;
395 die("Subprocess %s exited abnormally", filter->cmd); 396 die("Subprocess %s exited abnormally", filter->cmd);
396} 397}
397 398
398/* Read the content of the specified file into a newly allocated buffer, 399/* Read the content of the specified file into a newly allocated buffer,
399 * zeroterminate the buffer and return 0 on success, errno otherwise. 400 * zeroterminate the buffer and return 0 on success, errno otherwise.
400 */ 401 */
401int readfile(const char *path, char **buf, size_t *size) 402int readfile(const char *path, char **buf, size_t *size)
402{ 403{
403 int fd, e; 404 int fd, e;
404 struct stat st; 405 struct stat st;
405 406
406 fd = open(path, O_RDONLY); 407 fd = open(path, O_RDONLY);
407 if (fd == -1) 408 if (fd == -1)
408 return errno; 409 return errno;
409 if (fstat(fd, &st)) { 410 if (fstat(fd, &st)) {
410 e = errno; 411 e = errno;
411 close(fd); 412 close(fd);
412 return e; 413 return e;
413 } 414 }
414 if (!S_ISREG(st.st_mode)) { 415 if (!S_ISREG(st.st_mode)) {
415 close(fd); 416 close(fd);
416 return EISDIR; 417 return EISDIR;
417 } 418 }
418 *buf = xmalloc(st.st_size + 1); 419 *buf = xmalloc(st.st_size + 1);
419 *size = read_in_full(fd, *buf, st.st_size); 420 *size = read_in_full(fd, *buf, st.st_size);
420 e = errno; 421 e = errno;
421 (*buf)[*size] = '\0'; 422 (*buf)[*size] = '\0';
422 close(fd); 423 close(fd);
423 return (*size == st.st_size ? 0 : e); 424 return (*size == st.st_size ? 0 : e);
424} 425}
diff --git a/ui-diff.c b/ui-diff.c
index fb836df..d3a3b2c 100644
--- a/ui-diff.c
+++ b/ui-diff.c
@@ -1,355 +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, 0, 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, const char *prefix) 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) 164 if (prefix)
165 htmlf(" (limited to '%s')", prefix); 165 htmlf(" (limited to '%s')", prefix);
166 html("</div>"); 166 html("</div>");
167 html("<table summary='diffstat' class='diffstat'>"); 167 html("<table summary='diffstat' class='diffstat'>");
168 max_changes = 0; 168 max_changes = 0;
169 cgit_diff_tree(old_sha1, new_sha1, inspect_filepair, prefix); 169 cgit_diff_tree(old_sha1, new_sha1, inspect_filepair, prefix);
170 for(i = 0; i<files; i++) 170 for(i = 0; i<files; i++)
171 print_fileinfo(&items[i]); 171 print_fileinfo(&items[i]);
172 html("</table>"); 172 html("</table>");
173 html("<div class='diffstat-summary'>"); 173 html("<div class='diffstat-summary'>");
174 htmlf("%d files changed, %d insertions, %d deletions", 174 htmlf("%d files changed, %d insertions, %d deletions",
175 files, total_adds, total_rems); 175 files, total_adds, total_rems);
176 html("</div>"); 176 html("</div>");
177} 177}
178 178
179 179
180/* 180/*
181 * print a single line returned from xdiff 181 * print a single line returned from xdiff
182 */ 182 */
183static void print_line(char *line, int len) 183static void print_line(char *line, int len)
184{ 184{
185 char *class = "ctx"; 185 char *class = "ctx";
186 char c = line[len-1]; 186 char c = line[len-1];
187 187
188 if (line[0] == '+') 188 if (line[0] == '+')
189 class = "add"; 189 class = "add";
190 else if (line[0] == '-') 190 else if (line[0] == '-')
191 class = "del"; 191 class = "del";
192 else if (line[0] == '@') 192 else if (line[0] == '@')
193 class = "hunk"; 193 class = "hunk";
194 194
195 htmlf("<div class='%s'>", class); 195 htmlf("<div class='%s'>", class);
196 line[len-1] = '\0'; 196 line[len-1] = '\0';
197 html_txt(line); 197 html_txt(line);
198 html("</div>"); 198 html("</div>");
199 line[len-1] = c; 199 line[len-1] = c;
200} 200}
201 201
202static void header(unsigned char *sha1, char *path1, int mode1, 202static void header(unsigned char *sha1, char *path1, int mode1,
203 unsigned char *sha2, char *path2, int mode2) 203 unsigned char *sha2, char *path2, int mode2)
204{ 204{
205 char *abbrev1, *abbrev2; 205 char *abbrev1, *abbrev2;
206 int subproject; 206 int subproject;
207 207
208 subproject = (S_ISGITLINK(mode1) || S_ISGITLINK(mode2)); 208 subproject = (S_ISGITLINK(mode1) || S_ISGITLINK(mode2));
209 html("<div class='head'>"); 209 html("<div class='head'>");
210 html("diff --git a/"); 210 html("diff --git a/");
211 html_txt(path1); 211 html_txt(path1);
212 html(" b/"); 212 html(" b/");
213 html_txt(path2); 213 html_txt(path2);
214 214
215 if (is_null_sha1(sha1)) 215 if (is_null_sha1(sha1))
216 path1 = "dev/null"; 216 path1 = "dev/null";
217 if (is_null_sha1(sha2)) 217 if (is_null_sha1(sha2))
218 path2 = "dev/null"; 218 path2 = "dev/null";
219 219
220 if (mode1 == 0) 220 if (mode1 == 0)
221 htmlf("<br/>new file mode %.6o", mode2); 221 htmlf("<br/>new file mode %.6o", mode2);
222 222
223 if (mode2 == 0) 223 if (mode2 == 0)
224 htmlf("<br/>deleted file mode %.6o", mode1); 224 htmlf("<br/>deleted file mode %.6o", mode1);
225 225
226 if (!subproject) { 226 if (!subproject) {
227 abbrev1 = xstrdup(find_unique_abbrev(sha1, DEFAULT_ABBREV)); 227 abbrev1 = xstrdup(find_unique_abbrev(sha1, DEFAULT_ABBREV));
228 abbrev2 = xstrdup(find_unique_abbrev(sha2, DEFAULT_ABBREV)); 228 abbrev2 = xstrdup(find_unique_abbrev(sha2, DEFAULT_ABBREV));
229 htmlf("<br/>index %s..%s", abbrev1, abbrev2); 229 htmlf("<br/>index %s..%s", abbrev1, abbrev2);
230 free(abbrev1); 230 free(abbrev1);
231 free(abbrev2); 231 free(abbrev2);
232 if (mode1 != 0 && mode2 != 0) { 232 if (mode1 != 0 && mode2 != 0) {
233 htmlf(" %.6o", mode1); 233 htmlf(" %.6o", mode1);
234 if (mode2 != mode1) 234 if (mode2 != mode1)
235 htmlf("..%.6o", mode2); 235 htmlf("..%.6o", mode2);
236 } 236 }
237 html("<br/>--- a/"); 237 html("<br/>--- a/");
238 if (mode1 != 0) 238 if (mode1 != 0)
239 cgit_tree_link(path1, NULL, NULL, ctx.qry.head, 239 cgit_tree_link(path1, NULL, NULL, ctx.qry.head,
240 sha1_to_hex(old_rev_sha1), path1); 240 sha1_to_hex(old_rev_sha1), path1);
241 else 241 else
242 html_txt(path1); 242 html_txt(path1);
243 html("<br/>+++ b/"); 243 html("<br/>+++ b/");
244 if (mode2 != 0) 244 if (mode2 != 0)
245 cgit_tree_link(path2, NULL, NULL, ctx.qry.head, 245 cgit_tree_link(path2, NULL, NULL, ctx.qry.head,
246 sha1_to_hex(new_rev_sha1), path2); 246 sha1_to_hex(new_rev_sha1), path2);
247 else 247 else
248 html_txt(path2); 248 html_txt(path2);
249 } 249 }
250 html("</div>"); 250 html("</div>");
251} 251}
252 252
253static void print_ssdiff_link() 253static void print_ssdiff_link()
254{ 254{
255 if (!strcmp(ctx.qry.page, "diff")) { 255 if (!strcmp(ctx.qry.page, "diff")) {
256 if (use_ssdiff) 256 if (use_ssdiff)
257 cgit_diff_link("Unidiff", NULL, NULL, ctx.qry.head, 257 cgit_diff_link("Unidiff", NULL, NULL, ctx.qry.head,
258 ctx.qry.sha1, ctx.qry.sha2, ctx.qry.path, 1); 258 ctx.qry.sha1, ctx.qry.sha2, ctx.qry.path, 1);
259 else 259 else
260 cgit_diff_link("Side-by-side diff", NULL, NULL, 260 cgit_diff_link("Side-by-side diff", NULL, NULL,
261 ctx.qry.head, ctx.qry.sha1, 261 ctx.qry.head, ctx.qry.sha1,
262 ctx.qry.sha2, ctx.qry.path, 1); 262 ctx.qry.sha2, ctx.qry.path, 1);
263 } 263 }
264} 264}
265 265
266static void filepair_cb(struct diff_filepair *pair) 266static void filepair_cb(struct diff_filepair *pair)
267{ 267{
268 unsigned long old_size = 0; 268 unsigned long old_size = 0;
269 unsigned long new_size = 0; 269 unsigned long new_size = 0;
270 int binary = 0; 270 int binary = 0;
271 linediff_fn print_line_fn = print_line; 271 linediff_fn print_line_fn = print_line;
272 272
273 if (use_ssdiff) { 273 if (use_ssdiff) {
274 cgit_ssdiff_header_begin(); 274 cgit_ssdiff_header_begin();
275 print_line_fn = cgit_ssdiff_line_cb; 275 print_line_fn = cgit_ssdiff_line_cb;
276 } 276 }
277 header(pair->one->sha1, pair->one->path, pair->one->mode, 277 header(pair->one->sha1, pair->one->path, pair->one->mode,
278 pair->two->sha1, pair->two->path, pair->two->mode); 278 pair->two->sha1, pair->two->path, pair->two->mode);
279 if (use_ssdiff) 279 if (use_ssdiff)
280 cgit_ssdiff_header_end(); 280 cgit_ssdiff_header_end();
281 if (S_ISGITLINK(pair->one->mode) || S_ISGITLINK(pair->two->mode)) { 281 if (S_ISGITLINK(pair->one->mode) || S_ISGITLINK(pair->two->mode)) {
282 if (S_ISGITLINK(pair->one->mode)) 282 if (S_ISGITLINK(pair->one->mode))
283 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);
284 if (S_ISGITLINK(pair->two->mode)) 284 if (S_ISGITLINK(pair->two->mode))
285 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);
286 if (use_ssdiff) 286 if (use_ssdiff)
287 cgit_ssdiff_footer(); 287 cgit_ssdiff_footer();
288 return; 288 return;
289 } 289 }
290 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,
291 &new_size, &binary, print_line_fn)) 291 &new_size, &binary, ctx.qry.context, print_line_fn))
292 cgit_print_error("Error running diff"); 292 cgit_print_error("Error running diff");
293 if (binary) { 293 if (binary) {
294 if (use_ssdiff) 294 if (use_ssdiff)
295 html("<tr><td colspan='4'>Binary files differ</td></tr>"); 295 html("<tr><td colspan='4'>Binary files differ</td></tr>");
296 else 296 else
297 html("Binary files differ"); 297 html("Binary files differ");
298 } 298 }
299 if (use_ssdiff) 299 if (use_ssdiff)
300 cgit_ssdiff_footer(); 300 cgit_ssdiff_footer();
301} 301}
302 302
303void 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)
304{ 304{
305 enum object_type type; 305 enum object_type type;
306 unsigned long size; 306 unsigned long size;
307 struct commit *commit, *commit2; 307 struct commit *commit, *commit2;
308 308
309 if (!new_rev) 309 if (!new_rev)
310 new_rev = ctx.qry.head; 310 new_rev = ctx.qry.head;
311 get_sha1(new_rev, new_rev_sha1); 311 get_sha1(new_rev, new_rev_sha1);
312 type = sha1_object_info(new_rev_sha1, &size); 312 type = sha1_object_info(new_rev_sha1, &size);
313 if (type == OBJ_BAD) { 313 if (type == OBJ_BAD) {
314 cgit_print_error(fmt("Bad object name: %s", new_rev)); 314 cgit_print_error(fmt("Bad object name: %s", new_rev));
315 return; 315 return;
316 } 316 }
317 commit = lookup_commit_reference(new_rev_sha1); 317 commit = lookup_commit_reference(new_rev_sha1);
318 if (!commit || parse_commit(commit)) 318 if (!commit || parse_commit(commit))
319 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)));
320 320
321 if (old_rev) 321 if (old_rev)
322 get_sha1(old_rev, old_rev_sha1); 322 get_sha1(old_rev, old_rev_sha1);
323 else if (commit->parents && commit->parents->item) 323 else if (commit->parents && commit->parents->item)
324 hashcpy(old_rev_sha1, commit->parents->item->object.sha1); 324 hashcpy(old_rev_sha1, commit->parents->item->object.sha1);
325 else 325 else
326 hashclr(old_rev_sha1); 326 hashclr(old_rev_sha1);
327 327
328 if (!is_null_sha1(old_rev_sha1)) { 328 if (!is_null_sha1(old_rev_sha1)) {
329 type = sha1_object_info(old_rev_sha1, &size); 329 type = sha1_object_info(old_rev_sha1, &size);
330 if (type == OBJ_BAD) { 330 if (type == OBJ_BAD) {
331 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)));
332 return; 332 return;
333 } 333 }
334 commit2 = lookup_commit_reference(old_rev_sha1); 334 commit2 = lookup_commit_reference(old_rev_sha1);
335 if (!commit2 || parse_commit(commit2)) 335 if (!commit2 || parse_commit(commit2))
336 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)));
337 } 337 }
338 338
339 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))
340 use_ssdiff = 1; 340 use_ssdiff = 1;
341 341
342 print_ssdiff_link(); 342 print_ssdiff_link();
343 cgit_print_diffstat(old_rev_sha1, new_rev_sha1, prefix); 343 cgit_print_diffstat(old_rev_sha1, new_rev_sha1, prefix);
344 344
345 if (use_ssdiff) { 345 if (use_ssdiff) {
346 html("<table summary='ssdiff' class='ssdiff'>"); 346 html("<table summary='ssdiff' class='ssdiff'>");
347 } else { 347 } else {
348 html("<table summary='diff' class='diff'>"); 348 html("<table summary='diff' class='diff'>");
349 html("<tr><td>"); 349 html("<tr><td>");
350 } 350 }
351 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);
352 if (!use_ssdiff) 352 if (!use_ssdiff)
353 html("</td></tr>"); 353 html("</td></tr>");
354 html("</table>"); 354 html("</table>");
355} 355}
diff --git a/ui-log.c b/ui-log.c
index bfbe436..5eb5c81 100644
--- a/ui-log.c
+++ b/ui-log.c
@@ -1,237 +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, 0, 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, 49 cgit_log_link(buf, NULL, "branch-deco", buf, NULL,
50 ctx.qry.vpath, 0, NULL, NULL, 50 ctx.qry.vpath, 0, NULL, NULL,
51 ctx.qry.showmsg); 51 ctx.qry.showmsg);
52 } 52 }
53 else if (!prefixcmp(deco->name, "tag: refs/tags/")) { 53 else if (!prefixcmp(deco->name, "tag: refs/tags/")) {
54 strncpy(buf, deco->name + 15, sizeof(buf) - 1); 54 strncpy(buf, deco->name + 15, sizeof(buf) - 1);
55 cgit_tag_link(buf, NULL, "tag-deco", ctx.qry.head, buf); 55 cgit_tag_link(buf, NULL, "tag-deco", ctx.qry.head, buf);
56 } 56 }
57 else if (!prefixcmp(deco->name, "refs/tags/")) { 57 else if (!prefixcmp(deco->name, "refs/tags/")) {
58 strncpy(buf, deco->name + 10, sizeof(buf) - 1); 58 strncpy(buf, deco->name + 10, sizeof(buf) - 1);
59 cgit_tag_link(buf, NULL, "tag-deco", ctx.qry.head, buf); 59 cgit_tag_link(buf, NULL, "tag-deco", ctx.qry.head, buf);
60 } 60 }
61 else if (!prefixcmp(deco->name, "refs/remotes/")) { 61 else if (!prefixcmp(deco->name, "refs/remotes/")) {
62 strncpy(buf, deco->name + 13, sizeof(buf) - 1); 62 strncpy(buf, deco->name + 13, sizeof(buf) - 1);
63 cgit_log_link(buf, NULL, "remote-deco", NULL, 63 cgit_log_link(buf, NULL, "remote-deco", NULL,
64 sha1_to_hex(commit->object.sha1), 64 sha1_to_hex(commit->object.sha1),
65 ctx.qry.vpath, 0, NULL, NULL, 65 ctx.qry.vpath, 0, NULL, NULL,
66 ctx.qry.showmsg); 66 ctx.qry.showmsg);
67 } 67 }
68 else { 68 else {
69 strncpy(buf, deco->name, sizeof(buf) - 1); 69 strncpy(buf, deco->name, sizeof(buf) - 1);
70 cgit_commit_link(buf, NULL, "deco", ctx.qry.head, 70 cgit_commit_link(buf, NULL, "deco", ctx.qry.head,
71 sha1_to_hex(commit->object.sha1), 71 sha1_to_hex(commit->object.sha1),
72 ctx.qry.vpath, 0); 72 ctx.qry.vpath, 0);
73 } 73 }
74 deco = deco->next; 74 deco = deco->next;
75 } 75 }
76} 76}
77 77
78void print_commit(struct commit *commit) 78void print_commit(struct commit *commit)
79{ 79{
80 struct commitinfo *info; 80 struct commitinfo *info;
81 char *tmp; 81 char *tmp;
82 int cols = 2; 82 int cols = 2;
83 83
84 info = cgit_parse_commit(commit); 84 info = cgit_parse_commit(commit);
85 htmlf("<tr%s><td>", 85 htmlf("<tr%s><td>",
86 ctx.qry.showmsg ? " class='logheader'" : ""); 86 ctx.qry.showmsg ? " class='logheader'" : "");
87 tmp = fmt("id=%s", sha1_to_hex(commit->object.sha1)); 87 tmp = fmt("id=%s", sha1_to_hex(commit->object.sha1));
88 tmp = cgit_fileurl(ctx.repo->url, "commit", ctx.qry.vpath, tmp); 88 tmp = cgit_fileurl(ctx.repo->url, "commit", ctx.qry.vpath, tmp);
89 html_link_open(tmp, NULL, NULL); 89 html_link_open(tmp, NULL, NULL);
90 cgit_print_age(commit->date, TM_WEEK * 2, FMT_SHORTDATE); 90 cgit_print_age(commit->date, TM_WEEK * 2, FMT_SHORTDATE);
91 html_link_close(); 91 html_link_close();
92 htmlf("</td><td%s>", 92 htmlf("</td><td%s>",
93 ctx.qry.showmsg ? " class='logsubject'" : ""); 93 ctx.qry.showmsg ? " class='logsubject'" : "");
94 cgit_commit_link(info->subject, NULL, NULL, ctx.qry.head, 94 cgit_commit_link(info->subject, NULL, NULL, ctx.qry.head,
95 sha1_to_hex(commit->object.sha1), ctx.qry.vpath, 0); 95 sha1_to_hex(commit->object.sha1), ctx.qry.vpath, 0);
96 show_commit_decorations(commit); 96 show_commit_decorations(commit);
97 html("</td><td>"); 97 html("</td><td>");
98 html_txt(info->author); 98 html_txt(info->author);
99 if (ctx.repo->enable_log_filecount) { 99 if (ctx.repo->enable_log_filecount) {
100 files = 0; 100 files = 0;
101 add_lines = 0; 101 add_lines = 0;
102 rem_lines = 0; 102 rem_lines = 0;
103 cgit_diff_commit(commit, inspect_files); 103 cgit_diff_commit(commit, inspect_files);
104 html("</td><td>"); 104 html("</td><td>");
105 htmlf("%d", files); 105 htmlf("%d", files);
106 if (ctx.repo->enable_log_linecount) { 106 if (ctx.repo->enable_log_linecount) {
107 html("</td><td>"); 107 html("</td><td>");
108 htmlf("-%d/+%d", rem_lines, add_lines); 108 htmlf("-%d/+%d", rem_lines, add_lines);
109 } 109 }
110 } 110 }
111 html("</td></tr>\n"); 111 html("</td></tr>\n");
112 if (ctx.qry.showmsg) { 112 if (ctx.qry.showmsg) {
113 if (ctx.repo->enable_log_filecount) { 113 if (ctx.repo->enable_log_filecount) {
114 cols++; 114 cols++;
115 if (ctx.repo->enable_log_linecount) 115 if (ctx.repo->enable_log_linecount)
116 cols++; 116 cols++;
117 } 117 }
118 htmlf("<tr class='nohover'><td/><td colspan='%d' class='logmsg'>", 118 htmlf("<tr class='nohover'><td/><td colspan='%d' class='logmsg'>",
119 cols); 119 cols);
120 html_txt(info->msg); 120 html_txt(info->msg);
121 html("</td></tr>\n"); 121 html("</td></tr>\n");
122 } 122 }
123 cgit_free_commitinfo(info); 123 cgit_free_commitinfo(info);
124} 124}
125 125
126static const char *disambiguate_ref(const char *ref) 126static const char *disambiguate_ref(const char *ref)
127{ 127{
128 unsigned char sha1[20]; 128 unsigned char sha1[20];
129 const char *longref; 129 const char *longref;
130 130
131 longref = fmt("refs/heads/%s", ref); 131 longref = fmt("refs/heads/%s", ref);
132 if (get_sha1(longref, sha1) == 0) 132 if (get_sha1(longref, sha1) == 0)
133 return longref; 133 return longref;
134 134
135 return ref; 135 return ref;
136} 136}
137 137
138void 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,
139 char *path, int pager) 139 char *path, int pager)
140{ 140{
141 struct rev_info rev; 141 struct rev_info rev;
142 struct commit *commit; 142 struct commit *commit;
143 const char *argv[] = {NULL, NULL, NULL, NULL, NULL}; 143 const char *argv[] = {NULL, NULL, NULL, NULL, NULL};
144 int argc = 2; 144 int argc = 2;
145 int i, columns = 3; 145 int i, columns = 3;
146 146
147 if (!tip) 147 if (!tip)
148 tip = ctx.qry.head; 148 tip = ctx.qry.head;
149 149
150 argv[1] = disambiguate_ref(tip); 150 argv[1] = disambiguate_ref(tip);
151 151
152 if (grep && pattern && (!strcmp(grep, "grep") || 152 if (grep && pattern && (!strcmp(grep, "grep") ||
153 !strcmp(grep, "author") || 153 !strcmp(grep, "author") ||
154 !strcmp(grep, "committer"))) 154 !strcmp(grep, "committer")))
155 argv[argc++] = fmt("--%s=%s", grep, pattern); 155 argv[argc++] = fmt("--%s=%s", grep, pattern);
156 156
157 if (path) { 157 if (path) {
158 argv[argc++] = "--"; 158 argv[argc++] = "--";
159 argv[argc++] = path; 159 argv[argc++] = path;
160 } 160 }
161 init_revisions(&rev, NULL); 161 init_revisions(&rev, NULL);
162 rev.abbrev = DEFAULT_ABBREV; 162 rev.abbrev = DEFAULT_ABBREV;
163 rev.commit_format = CMIT_FMT_DEFAULT; 163 rev.commit_format = CMIT_FMT_DEFAULT;
164 rev.verbose_header = 1; 164 rev.verbose_header = 1;
165 rev.show_root_diff = 0; 165 rev.show_root_diff = 0;
166 setup_revisions(argc, argv, &rev, NULL); 166 setup_revisions(argc, argv, &rev, NULL);
167 load_ref_decorations(DECORATE_FULL_REFS); 167 load_ref_decorations(DECORATE_FULL_REFS);
168 rev.show_decorations = 1; 168 rev.show_decorations = 1;
169 rev.grep_filter.regflags |= REG_ICASE; 169 rev.grep_filter.regflags |= REG_ICASE;
170 compile_grep_patterns(&rev.grep_filter); 170 compile_grep_patterns(&rev.grep_filter);
171 prepare_revision_walk(&rev); 171 prepare_revision_walk(&rev);
172 172
173 if (pager) 173 if (pager)
174 html("<table class='list nowrap'>"); 174 html("<table class='list nowrap'>");
175 175
176 html("<tr class='nohover'><th class='left'>Age</th>" 176 html("<tr class='nohover'><th class='left'>Age</th>"
177 "<th class='left'>Commit message"); 177 "<th class='left'>Commit message");
178 if (pager) { 178 if (pager) {
179 html(" ("); 179 html(" (");
180 cgit_log_link(ctx.qry.showmsg ? "Collapse" : "Expand", NULL, 180 cgit_log_link(ctx.qry.showmsg ? "Collapse" : "Expand", NULL,
181 NULL, ctx.qry.head, ctx.qry.sha1, 181 NULL, ctx.qry.head, ctx.qry.sha1,
182 ctx.qry.vpath, ctx.qry.ofs, ctx.qry.grep, 182 ctx.qry.vpath, ctx.qry.ofs, ctx.qry.grep,
183 ctx.qry.search, ctx.qry.showmsg ? 0 : 1); 183 ctx.qry.search, ctx.qry.showmsg ? 0 : 1);
184 html(")"); 184 html(")");
185 } 185 }
186 html("</th><th class='left'>Author</th>"); 186 html("</th><th class='left'>Author</th>");
187 if (ctx.repo->enable_log_filecount) { 187 if (ctx.repo->enable_log_filecount) {
188 html("<th class='left'>Files</th>"); 188 html("<th class='left'>Files</th>");
189 columns++; 189 columns++;
190 if (ctx.repo->enable_log_linecount) { 190 if (ctx.repo->enable_log_linecount) {
191 html("<th class='left'>Lines</th>"); 191 html("<th class='left'>Lines</th>");
192 columns++; 192 columns++;
193 } 193 }
194 } 194 }
195 html("</tr>\n"); 195 html("</tr>\n");
196 196
197 if (ofs<0) 197 if (ofs<0)
198 ofs = 0; 198 ofs = 0;
199 199
200 for (i = 0; i < ofs && (commit = get_revision(&rev)) != NULL; i++) { 200 for (i = 0; i < ofs && (commit = get_revision(&rev)) != NULL; i++) {
201 free(commit->buffer); 201 free(commit->buffer);
202 commit->buffer = NULL; 202 commit->buffer = NULL;
203 free_commit_list(commit->parents); 203 free_commit_list(commit->parents);
204 commit->parents = NULL; 204 commit->parents = NULL;
205 } 205 }
206 206
207 for (i = 0; i < cnt && (commit = get_revision(&rev)) != NULL; i++) { 207 for (i = 0; i < cnt && (commit = get_revision(&rev)) != NULL; i++) {
208 print_commit(commit); 208 print_commit(commit);
209 free(commit->buffer); 209 free(commit->buffer);
210 commit->buffer = NULL; 210 commit->buffer = NULL;
211 free_commit_list(commit->parents); 211 free_commit_list(commit->parents);
212 commit->parents = NULL; 212 commit->parents = NULL;
213 } 213 }
214 if (pager) { 214 if (pager) {
215 htmlf("</table><div class='pager'>", 215 htmlf("</table><div class='pager'>",
216 columns); 216 columns);
217 if (ofs > 0) { 217 if (ofs > 0) {
218 cgit_log_link("[prev]", NULL, NULL, ctx.qry.head, 218 cgit_log_link("[prev]", NULL, NULL, ctx.qry.head,
219 ctx.qry.sha1, ctx.qry.vpath, 219 ctx.qry.sha1, ctx.qry.vpath,
220 ofs - cnt, ctx.qry.grep, 220 ofs - cnt, ctx.qry.grep,
221 ctx.qry.search, ctx.qry.showmsg); 221 ctx.qry.search, ctx.qry.showmsg);
222 html("&nbsp;"); 222 html("&nbsp;");
223 } 223 }
224 if ((commit = get_revision(&rev)) != NULL) { 224 if ((commit = get_revision(&rev)) != NULL) {
225 cgit_log_link("[next]", NULL, NULL, ctx.qry.head, 225 cgit_log_link("[next]", NULL, NULL, ctx.qry.head,
226 ctx.qry.sha1, ctx.qry.vpath, 226 ctx.qry.sha1, ctx.qry.vpath,
227 ofs + cnt, ctx.qry.grep, 227 ofs + cnt, ctx.qry.grep,
228 ctx.qry.search, ctx.qry.showmsg); 228 ctx.qry.search, ctx.qry.showmsg);
229 } 229 }
230 html("</div>"); 230 html("</div>");
231 } else if ((commit = get_revision(&rev)) != NULL) { 231 } else if ((commit = get_revision(&rev)) != NULL) {
232 html("<tr class='nohover'><td colspan='3'>"); 232 html("<tr class='nohover'><td colspan='3'>");
233 cgit_log_link("[...]", NULL, NULL, ctx.qry.head, NULL, 233 cgit_log_link("[...]", NULL, NULL, ctx.qry.head, NULL,
234 ctx.qry.vpath, 0, NULL, NULL, ctx.qry.showmsg); 234 ctx.qry.vpath, 0, NULL, NULL, ctx.qry.showmsg);
235 html("</td></tr>\n"); 235 html("</td></tr>\n");
236 } 236 }
237} 237}
diff --git a/ui-patch.c b/ui-patch.c
index 25dc9fe..d13104c 100644
--- a/ui-patch.c
+++ b/ui-patch.c
@@ -1,131 +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, 0, 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, const char *prefix) 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 if (prefix) 125 if (prefix)
126 htmlf("(limited to '%s')\n\n", prefix); 126 htmlf("(limited to '%s')\n\n", prefix);
127 cgit_diff_tree(old_sha1, sha1, filepair_cb, prefix); 127 cgit_diff_tree(old_sha1, sha1, filepair_cb, prefix);
128 html("--\n"); 128 html("--\n");
129 htmlf("cgit %s\n", CGIT_VERSION); 129 htmlf("cgit %s\n", CGIT_VERSION);
130 cgit_free_commitinfo(info); 130 cgit_free_commitinfo(info);
131} 131}