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