summaryrefslogtreecommitdiffabout
authorRagnar Ouchterlony <ragnar@lysator.liu.se>2009-09-14 18:19:02 (UTC)
committer Lars Hjemli <hjemli@gmail.com>2009-09-16 18:17:56 (UTC)
commitc358aa3dfebf4fc1f3005dd960aa5c1c020eed76 (patch) (unidiff)
tree475fa421d673b270c7d1bde872b96425abfafce8
parent40e174d5364910750413d94b5417e57d108190ef (diff)
downloadcgit-c358aa3dfebf4fc1f3005dd960aa5c1c020eed76.zip
cgit-c358aa3dfebf4fc1f3005dd960aa5c1c020eed76.tar.gz
cgit-c358aa3dfebf4fc1f3005dd960aa5c1c020eed76.tar.bz2
Add possibility to switch between unidiff and side-by-side-diff.
A new config option side-by-side-diffs added, defaulting to 0, meaning unidiff. Also a query option (ss) is used toggle this. In the commit page you can switch between the two diff formats by clicking on the link on the "commit"-row, to the right of (patch). In the diff page you can switch by using the link at the start of the page. All commit-links and diff-links will remember the choice. Signed-off-by: Ragnar Ouchterlony <ragnar@lysator.liu.se> Signed-off-by: Lars Hjemli <hjemli@gmail.com>
Diffstat (more/less context) (ignore whitespace changes)
-rw-r--r--cgit.c5
-rw-r--r--cgit.h2
-rw-r--r--cgitrc.5.txt4
-rw-r--r--ui-commit.c11
-rw-r--r--ui-diff.c22
-rw-r--r--ui-log.c4
-rw-r--r--ui-refs.c2
-rw-r--r--ui-shared.c34
-rw-r--r--ui-shared.h5
9 files changed, 73 insertions, 16 deletions
diff --git a/cgit.c b/cgit.c
index bd37788..ff678fb 100644
--- a/cgit.c
+++ b/cgit.c
@@ -137,193 +137,198 @@ void config_cb(const char *name, const char *value)
137 ctx.cfg.enable_log_filecount = atoi(value); 137 ctx.cfg.enable_log_filecount = atoi(value);
138 else if (!strcmp(name, "enable-log-linecount")) 138 else if (!strcmp(name, "enable-log-linecount"))
139 ctx.cfg.enable_log_linecount = atoi(value); 139 ctx.cfg.enable_log_linecount = atoi(value);
140 else if (!strcmp(name, "enable-tree-linenumbers")) 140 else if (!strcmp(name, "enable-tree-linenumbers"))
141 ctx.cfg.enable_tree_linenumbers = atoi(value); 141 ctx.cfg.enable_tree_linenumbers = atoi(value);
142 else if (!strcmp(name, "max-stats")) 142 else if (!strcmp(name, "max-stats"))
143 ctx.cfg.max_stats = cgit_find_stats_period(value, NULL); 143 ctx.cfg.max_stats = cgit_find_stats_period(value, NULL);
144 else if (!strcmp(name, "cache-size")) 144 else if (!strcmp(name, "cache-size"))
145 ctx.cfg.cache_size = atoi(value); 145 ctx.cfg.cache_size = atoi(value);
146 else if (!strcmp(name, "cache-root")) 146 else if (!strcmp(name, "cache-root"))
147 ctx.cfg.cache_root = xstrdup(value); 147 ctx.cfg.cache_root = xstrdup(value);
148 else if (!strcmp(name, "cache-root-ttl")) 148 else if (!strcmp(name, "cache-root-ttl"))
149 ctx.cfg.cache_root_ttl = atoi(value); 149 ctx.cfg.cache_root_ttl = atoi(value);
150 else if (!strcmp(name, "cache-repo-ttl")) 150 else if (!strcmp(name, "cache-repo-ttl"))
151 ctx.cfg.cache_repo_ttl = atoi(value); 151 ctx.cfg.cache_repo_ttl = atoi(value);
152 else if (!strcmp(name, "cache-scanrc-ttl")) 152 else if (!strcmp(name, "cache-scanrc-ttl"))
153 ctx.cfg.cache_scanrc_ttl = atoi(value); 153 ctx.cfg.cache_scanrc_ttl = atoi(value);
154 else if (!strcmp(name, "cache-static-ttl")) 154 else if (!strcmp(name, "cache-static-ttl"))
155 ctx.cfg.cache_static_ttl = atoi(value); 155 ctx.cfg.cache_static_ttl = atoi(value);
156 else if (!strcmp(name, "cache-dynamic-ttl")) 156 else if (!strcmp(name, "cache-dynamic-ttl"))
157 ctx.cfg.cache_dynamic_ttl = atoi(value); 157 ctx.cfg.cache_dynamic_ttl = atoi(value);
158 else if (!strcmp(name, "about-filter")) 158 else if (!strcmp(name, "about-filter"))
159 ctx.cfg.about_filter = new_filter(value, 0); 159 ctx.cfg.about_filter = new_filter(value, 0);
160 else if (!strcmp(name, "commit-filter")) 160 else if (!strcmp(name, "commit-filter"))
161 ctx.cfg.commit_filter = new_filter(value, 0); 161 ctx.cfg.commit_filter = new_filter(value, 0);
162 else if (!strcmp(name, "embedded")) 162 else if (!strcmp(name, "embedded"))
163 ctx.cfg.embedded = atoi(value); 163 ctx.cfg.embedded = atoi(value);
164 else if (!strcmp(name, "max-message-length")) 164 else if (!strcmp(name, "max-message-length"))
165 ctx.cfg.max_msg_len = atoi(value); 165 ctx.cfg.max_msg_len = atoi(value);
166 else if (!strcmp(name, "max-repodesc-length")) 166 else if (!strcmp(name, "max-repodesc-length"))
167 ctx.cfg.max_repodesc_len = atoi(value); 167 ctx.cfg.max_repodesc_len = atoi(value);
168 else if (!strcmp(name, "max-repo-count")) 168 else if (!strcmp(name, "max-repo-count"))
169 ctx.cfg.max_repo_count = atoi(value); 169 ctx.cfg.max_repo_count = atoi(value);
170 else if (!strcmp(name, "max-commit-count")) 170 else if (!strcmp(name, "max-commit-count"))
171 ctx.cfg.max_commit_count = atoi(value); 171 ctx.cfg.max_commit_count = atoi(value);
172 else if (!strcmp(name, "scan-path")) 172 else if (!strcmp(name, "scan-path"))
173 if (!ctx.cfg.nocache && ctx.cfg.cache_size) 173 if (!ctx.cfg.nocache && ctx.cfg.cache_size)
174 process_cached_repolist(value); 174 process_cached_repolist(value);
175 else 175 else
176 scan_tree(value, repo_config); 176 scan_tree(value, repo_config);
177 else if (!strcmp(name, "source-filter")) 177 else if (!strcmp(name, "source-filter"))
178 ctx.cfg.source_filter = new_filter(value, 1); 178 ctx.cfg.source_filter = new_filter(value, 1);
179 else if (!strcmp(name, "summary-log")) 179 else if (!strcmp(name, "summary-log"))
180 ctx.cfg.summary_log = atoi(value); 180 ctx.cfg.summary_log = atoi(value);
181 else if (!strcmp(name, "summary-branches")) 181 else if (!strcmp(name, "summary-branches"))
182 ctx.cfg.summary_branches = atoi(value); 182 ctx.cfg.summary_branches = atoi(value);
183 else if (!strcmp(name, "summary-tags")) 183 else if (!strcmp(name, "summary-tags"))
184 ctx.cfg.summary_tags = atoi(value); 184 ctx.cfg.summary_tags = atoi(value);
185 else if (!strcmp(name, "side-by-side-diffs"))
186 ctx.cfg.ssdiff = atoi(value);
185 else if (!strcmp(name, "agefile")) 187 else if (!strcmp(name, "agefile"))
186 ctx.cfg.agefile = xstrdup(value); 188 ctx.cfg.agefile = xstrdup(value);
187 else if (!strcmp(name, "renamelimit")) 189 else if (!strcmp(name, "renamelimit"))
188 ctx.cfg.renamelimit = atoi(value); 190 ctx.cfg.renamelimit = atoi(value);
189 else if (!strcmp(name, "robots")) 191 else if (!strcmp(name, "robots"))
190 ctx.cfg.robots = xstrdup(value); 192 ctx.cfg.robots = xstrdup(value);
191 else if (!strcmp(name, "clone-prefix")) 193 else if (!strcmp(name, "clone-prefix"))
192 ctx.cfg.clone_prefix = xstrdup(value); 194 ctx.cfg.clone_prefix = xstrdup(value);
193 else if (!strcmp(name, "local-time")) 195 else if (!strcmp(name, "local-time"))
194 ctx.cfg.local_time = atoi(value); 196 ctx.cfg.local_time = atoi(value);
195 else if (!prefixcmp(name, "mimetype.")) 197 else if (!prefixcmp(name, "mimetype."))
196 add_mimetype(name + 9, value); 198 add_mimetype(name + 9, value);
197 else if (!strcmp(name, "include")) 199 else if (!strcmp(name, "include"))
198 parse_configfile(value, config_cb); 200 parse_configfile(value, config_cb);
199} 201}
200 202
201static void querystring_cb(const char *name, const char *value) 203static void querystring_cb(const char *name, const char *value)
202{ 204{
203 if (!value) 205 if (!value)
204 value = ""; 206 value = "";
205 207
206 if (!strcmp(name,"r")) { 208 if (!strcmp(name,"r")) {
207 ctx.qry.repo = xstrdup(value); 209 ctx.qry.repo = xstrdup(value);
208 ctx.repo = cgit_get_repoinfo(value); 210 ctx.repo = cgit_get_repoinfo(value);
209 } else if (!strcmp(name, "p")) { 211 } else if (!strcmp(name, "p")) {
210 ctx.qry.page = xstrdup(value); 212 ctx.qry.page = xstrdup(value);
211 } else if (!strcmp(name, "url")) { 213 } else if (!strcmp(name, "url")) {
212 ctx.qry.url = xstrdup(value); 214 ctx.qry.url = xstrdup(value);
213 cgit_parse_url(value); 215 cgit_parse_url(value);
214 } else if (!strcmp(name, "qt")) { 216 } else if (!strcmp(name, "qt")) {
215 ctx.qry.grep = xstrdup(value); 217 ctx.qry.grep = xstrdup(value);
216 } else if (!strcmp(name, "q")) { 218 } else if (!strcmp(name, "q")) {
217 ctx.qry.search = xstrdup(value); 219 ctx.qry.search = xstrdup(value);
218 } else if (!strcmp(name, "h")) { 220 } else if (!strcmp(name, "h")) {
219 ctx.qry.head = xstrdup(value); 221 ctx.qry.head = xstrdup(value);
220 ctx.qry.has_symref = 1; 222 ctx.qry.has_symref = 1;
221 } else if (!strcmp(name, "id")) { 223 } else if (!strcmp(name, "id")) {
222 ctx.qry.sha1 = xstrdup(value); 224 ctx.qry.sha1 = xstrdup(value);
223 ctx.qry.has_sha1 = 1; 225 ctx.qry.has_sha1 = 1;
224 } else if (!strcmp(name, "id2")) { 226 } else if (!strcmp(name, "id2")) {
225 ctx.qry.sha2 = xstrdup(value); 227 ctx.qry.sha2 = xstrdup(value);
226 ctx.qry.has_sha1 = 1; 228 ctx.qry.has_sha1 = 1;
227 } else if (!strcmp(name, "ofs")) { 229 } else if (!strcmp(name, "ofs")) {
228 ctx.qry.ofs = atoi(value); 230 ctx.qry.ofs = atoi(value);
229 } else if (!strcmp(name, "path")) { 231 } else if (!strcmp(name, "path")) {
230 ctx.qry.path = trim_end(value, '/'); 232 ctx.qry.path = trim_end(value, '/');
231 } else if (!strcmp(name, "name")) { 233 } else if (!strcmp(name, "name")) {
232 ctx.qry.name = xstrdup(value); 234 ctx.qry.name = xstrdup(value);
233 } else if (!strcmp(name, "mimetype")) { 235 } else if (!strcmp(name, "mimetype")) {
234 ctx.qry.mimetype = xstrdup(value); 236 ctx.qry.mimetype = xstrdup(value);
235 } else if (!strcmp(name, "s")){ 237 } else if (!strcmp(name, "s")){
236 ctx.qry.sort = xstrdup(value); 238 ctx.qry.sort = xstrdup(value);
237 } else if (!strcmp(name, "showmsg")) { 239 } else if (!strcmp(name, "showmsg")) {
238 ctx.qry.showmsg = atoi(value); 240 ctx.qry.showmsg = atoi(value);
239 } else if (!strcmp(name, "period")) { 241 } else if (!strcmp(name, "period")) {
240 ctx.qry.period = xstrdup(value); 242 ctx.qry.period = xstrdup(value);
243 } else if (!strcmp(name, "ss")) {
244 ctx.qry.ssdiff = atoi(value);
241 } 245 }
242} 246}
243 247
244char *xstrdupn(const char *str) 248char *xstrdupn(const char *str)
245{ 249{
246 return (str ? xstrdup(str) : NULL); 250 return (str ? xstrdup(str) : NULL);
247} 251}
248 252
249static void prepare_context(struct cgit_context *ctx) 253static void prepare_context(struct cgit_context *ctx)
250{ 254{
251 memset(ctx, 0, sizeof(ctx)); 255 memset(ctx, 0, sizeof(ctx));
252 ctx->cfg.agefile = "info/web/last-modified"; 256 ctx->cfg.agefile = "info/web/last-modified";
253 ctx->cfg.nocache = 0; 257 ctx->cfg.nocache = 0;
254 ctx->cfg.cache_size = 0; 258 ctx->cfg.cache_size = 0;
255 ctx->cfg.cache_dynamic_ttl = 5; 259 ctx->cfg.cache_dynamic_ttl = 5;
256 ctx->cfg.cache_max_create_time = 5; 260 ctx->cfg.cache_max_create_time = 5;
257 ctx->cfg.cache_repo_ttl = 5; 261 ctx->cfg.cache_repo_ttl = 5;
258 ctx->cfg.cache_root = CGIT_CACHE_ROOT; 262 ctx->cfg.cache_root = CGIT_CACHE_ROOT;
259 ctx->cfg.cache_root_ttl = 5; 263 ctx->cfg.cache_root_ttl = 5;
260 ctx->cfg.cache_scanrc_ttl = 15; 264 ctx->cfg.cache_scanrc_ttl = 15;
261 ctx->cfg.cache_static_ttl = -1; 265 ctx->cfg.cache_static_ttl = -1;
262 ctx->cfg.css = "/cgit.css"; 266 ctx->cfg.css = "/cgit.css";
263 ctx->cfg.logo = "/cgit.png"; 267 ctx->cfg.logo = "/cgit.png";
264 ctx->cfg.local_time = 0; 268 ctx->cfg.local_time = 0;
265 ctx->cfg.enable_tree_linenumbers = 1; 269 ctx->cfg.enable_tree_linenumbers = 1;
266 ctx->cfg.max_repo_count = 50; 270 ctx->cfg.max_repo_count = 50;
267 ctx->cfg.max_commit_count = 50; 271 ctx->cfg.max_commit_count = 50;
268 ctx->cfg.max_lock_attempts = 5; 272 ctx->cfg.max_lock_attempts = 5;
269 ctx->cfg.max_msg_len = 80; 273 ctx->cfg.max_msg_len = 80;
270 ctx->cfg.max_repodesc_len = 80; 274 ctx->cfg.max_repodesc_len = 80;
271 ctx->cfg.max_stats = 0; 275 ctx->cfg.max_stats = 0;
272 ctx->cfg.module_link = "./?repo=%s&page=commit&id=%s"; 276 ctx->cfg.module_link = "./?repo=%s&page=commit&id=%s";
273 ctx->cfg.renamelimit = -1; 277 ctx->cfg.renamelimit = -1;
274 ctx->cfg.robots = "index, nofollow"; 278 ctx->cfg.robots = "index, nofollow";
275 ctx->cfg.root_title = "Git repository browser"; 279 ctx->cfg.root_title = "Git repository browser";
276 ctx->cfg.root_desc = "a fast webinterface for the git dscm"; 280 ctx->cfg.root_desc = "a fast webinterface for the git dscm";
277 ctx->cfg.script_name = CGIT_SCRIPT_NAME; 281 ctx->cfg.script_name = CGIT_SCRIPT_NAME;
278 ctx->cfg.section = ""; 282 ctx->cfg.section = "";
279 ctx->cfg.summary_branches = 10; 283 ctx->cfg.summary_branches = 10;
280 ctx->cfg.summary_log = 10; 284 ctx->cfg.summary_log = 10;
281 ctx->cfg.summary_tags = 10; 285 ctx->cfg.summary_tags = 10;
286 ctx->cfg.ssdiff = 0;
282 ctx->env.cgit_config = xstrdupn(getenv("CGIT_CONFIG")); 287 ctx->env.cgit_config = xstrdupn(getenv("CGIT_CONFIG"));
283 ctx->env.http_host = xstrdupn(getenv("HTTP_HOST")); 288 ctx->env.http_host = xstrdupn(getenv("HTTP_HOST"));
284 ctx->env.https = xstrdupn(getenv("HTTPS")); 289 ctx->env.https = xstrdupn(getenv("HTTPS"));
285 ctx->env.no_http = xstrdupn(getenv("NO_HTTP")); 290 ctx->env.no_http = xstrdupn(getenv("NO_HTTP"));
286 ctx->env.path_info = xstrdupn(getenv("PATH_INFO")); 291 ctx->env.path_info = xstrdupn(getenv("PATH_INFO"));
287 ctx->env.query_string = xstrdupn(getenv("QUERY_STRING")); 292 ctx->env.query_string = xstrdupn(getenv("QUERY_STRING"));
288 ctx->env.request_method = xstrdupn(getenv("REQUEST_METHOD")); 293 ctx->env.request_method = xstrdupn(getenv("REQUEST_METHOD"));
289 ctx->env.script_name = xstrdupn(getenv("SCRIPT_NAME")); 294 ctx->env.script_name = xstrdupn(getenv("SCRIPT_NAME"));
290 ctx->env.server_name = xstrdupn(getenv("SERVER_NAME")); 295 ctx->env.server_name = xstrdupn(getenv("SERVER_NAME"));
291 ctx->env.server_port = xstrdupn(getenv("SERVER_PORT")); 296 ctx->env.server_port = xstrdupn(getenv("SERVER_PORT"));
292 ctx->page.mimetype = "text/html"; 297 ctx->page.mimetype = "text/html";
293 ctx->page.charset = PAGE_ENCODING; 298 ctx->page.charset = PAGE_ENCODING;
294 ctx->page.filename = NULL; 299 ctx->page.filename = NULL;
295 ctx->page.size = 0; 300 ctx->page.size = 0;
296 ctx->page.modified = time(NULL); 301 ctx->page.modified = time(NULL);
297 ctx->page.expires = ctx->page.modified; 302 ctx->page.expires = ctx->page.modified;
298 ctx->page.etag = NULL; 303 ctx->page.etag = NULL;
299 memset(&ctx->cfg.mimetypes, 0, sizeof(struct string_list)); 304 memset(&ctx->cfg.mimetypes, 0, sizeof(struct string_list));
300 if (ctx->env.script_name) 305 if (ctx->env.script_name)
301 ctx->cfg.script_name = ctx->env.script_name; 306 ctx->cfg.script_name = ctx->env.script_name;
302 if (ctx->env.query_string) 307 if (ctx->env.query_string)
303 ctx->qry.raw = ctx->env.query_string; 308 ctx->qry.raw = ctx->env.query_string;
304 if (!ctx->env.cgit_config) 309 if (!ctx->env.cgit_config)
305 ctx->env.cgit_config = CGIT_CONFIG; 310 ctx->env.cgit_config = CGIT_CONFIG;
306} 311}
307 312
308struct refmatch { 313struct refmatch {
309 char *req_ref; 314 char *req_ref;
310 char *first_ref; 315 char *first_ref;
311 int match; 316 int match;
312}; 317};
313 318
314int find_current_ref(const char *refname, const unsigned char *sha1, 319int find_current_ref(const char *refname, const unsigned char *sha1,
315 int flags, void *cb_data) 320 int flags, void *cb_data)
316{ 321{
317 struct refmatch *info; 322 struct refmatch *info;
318 323
319 info = (struct refmatch *)cb_data; 324 info = (struct refmatch *)cb_data;
320 if (!strcmp(refname, info->req_ref)) 325 if (!strcmp(refname, info->req_ref))
321 info->match = 1; 326 info->match = 1;
322 if (!info->first_ref) 327 if (!info->first_ref)
323 info->first_ref = xstrdup(refname); 328 info->first_ref = xstrdup(refname);
324 return info->match; 329 return info->match;
325} 330}
326 331
327char *find_default_branch(struct cgit_repo *repo) 332char *find_default_branch(struct cgit_repo *repo)
328{ 333{
329 struct refmatch info; 334 struct refmatch info;
diff --git a/cgit.h b/cgit.h
index 6c6c460..b7b0adb 100644
--- a/cgit.h
+++ b/cgit.h
@@ -98,147 +98,149 @@ struct commitinfo {
98 unsigned long committer_date; 98 unsigned long committer_date;
99 char *subject; 99 char *subject;
100 char *msg; 100 char *msg;
101 char *msg_encoding; 101 char *msg_encoding;
102}; 102};
103 103
104struct taginfo { 104struct taginfo {
105 char *tagger; 105 char *tagger;
106 char *tagger_email; 106 char *tagger_email;
107 unsigned long tagger_date; 107 unsigned long tagger_date;
108 char *msg; 108 char *msg;
109}; 109};
110 110
111struct refinfo { 111struct refinfo {
112 const char *refname; 112 const char *refname;
113 struct object *object; 113 struct object *object;
114 union { 114 union {
115 struct taginfo *tag; 115 struct taginfo *tag;
116 struct commitinfo *commit; 116 struct commitinfo *commit;
117 }; 117 };
118}; 118};
119 119
120struct reflist { 120struct reflist {
121 struct refinfo **refs; 121 struct refinfo **refs;
122 int alloc; 122 int alloc;
123 int count; 123 int count;
124}; 124};
125 125
126struct cgit_query { 126struct cgit_query {
127 int has_symref; 127 int has_symref;
128 int has_sha1; 128 int has_sha1;
129 char *raw; 129 char *raw;
130 char *repo; 130 char *repo;
131 char *page; 131 char *page;
132 char *search; 132 char *search;
133 char *grep; 133 char *grep;
134 char *head; 134 char *head;
135 char *sha1; 135 char *sha1;
136 char *sha2; 136 char *sha2;
137 char *path; 137 char *path;
138 char *name; 138 char *name;
139 char *mimetype; 139 char *mimetype;
140 char *url; 140 char *url;
141 char *period; 141 char *period;
142 int ofs; 142 int ofs;
143 int nohead; 143 int nohead;
144 char *sort; 144 char *sort;
145 int showmsg; 145 int showmsg;
146 int ssdiff;
146}; 147};
147 148
148struct cgit_config { 149struct cgit_config {
149 char *agefile; 150 char *agefile;
150 char *cache_root; 151 char *cache_root;
151 char *clone_prefix; 152 char *clone_prefix;
152 char *css; 153 char *css;
153 char *favicon; 154 char *favicon;
154 char *footer; 155 char *footer;
155 char *head_include; 156 char *head_include;
156 char *header; 157 char *header;
157 char *index_header; 158 char *index_header;
158 char *index_info; 159 char *index_info;
159 char *logo; 160 char *logo;
160 char *logo_link; 161 char *logo_link;
161 char *module_link; 162 char *module_link;
162 char *robots; 163 char *robots;
163 char *root_title; 164 char *root_title;
164 char *root_desc; 165 char *root_desc;
165 char *root_readme; 166 char *root_readme;
166 char *script_name; 167 char *script_name;
167 char *section; 168 char *section;
168 char *virtual_root; 169 char *virtual_root;
169 int cache_size; 170 int cache_size;
170 int cache_dynamic_ttl; 171 int cache_dynamic_ttl;
171 int cache_max_create_time; 172 int cache_max_create_time;
172 int cache_repo_ttl; 173 int cache_repo_ttl;
173 int cache_root_ttl; 174 int cache_root_ttl;
174 int cache_scanrc_ttl; 175 int cache_scanrc_ttl;
175 int cache_static_ttl; 176 int cache_static_ttl;
176 int embedded; 177 int embedded;
177 int enable_filter_overrides; 178 int enable_filter_overrides;
178 int enable_index_links; 179 int enable_index_links;
179 int enable_log_filecount; 180 int enable_log_filecount;
180 int enable_log_linecount; 181 int enable_log_linecount;
181 int enable_tree_linenumbers; 182 int enable_tree_linenumbers;
182 int local_time; 183 int local_time;
183 int max_repo_count; 184 int max_repo_count;
184 int max_commit_count; 185 int max_commit_count;
185 int max_lock_attempts; 186 int max_lock_attempts;
186 int max_msg_len; 187 int max_msg_len;
187 int max_repodesc_len; 188 int max_repodesc_len;
188 int max_stats; 189 int max_stats;
189 int nocache; 190 int nocache;
190 int noplainemail; 191 int noplainemail;
191 int noheader; 192 int noheader;
192 int renamelimit; 193 int renamelimit;
193 int snapshots; 194 int snapshots;
194 int summary_branches; 195 int summary_branches;
195 int summary_log; 196 int summary_log;
196 int summary_tags; 197 int summary_tags;
198 int ssdiff;
197 struct string_list mimetypes; 199 struct string_list mimetypes;
198 struct cgit_filter *about_filter; 200 struct cgit_filter *about_filter;
199 struct cgit_filter *commit_filter; 201 struct cgit_filter *commit_filter;
200 struct cgit_filter *source_filter; 202 struct cgit_filter *source_filter;
201}; 203};
202 204
203struct cgit_page { 205struct cgit_page {
204 time_t modified; 206 time_t modified;
205 time_t expires; 207 time_t expires;
206 size_t size; 208 size_t size;
207 char *mimetype; 209 char *mimetype;
208 char *charset; 210 char *charset;
209 char *filename; 211 char *filename;
210 char *etag; 212 char *etag;
211 char *title; 213 char *title;
212 int status; 214 int status;
213 char *statusmsg; 215 char *statusmsg;
214}; 216};
215 217
216struct cgit_environment { 218struct cgit_environment {
217 char *cgit_config; 219 char *cgit_config;
218 char *http_host; 220 char *http_host;
219 char *https; 221 char *https;
220 char *no_http; 222 char *no_http;
221 char *path_info; 223 char *path_info;
222 char *query_string; 224 char *query_string;
223 char *request_method; 225 char *request_method;
224 char *script_name; 226 char *script_name;
225 char *server_name; 227 char *server_name;
226 char *server_port; 228 char *server_port;
227}; 229};
228 230
229struct cgit_context { 231struct cgit_context {
230 struct cgit_environment env; 232 struct cgit_environment env;
231 struct cgit_query qry; 233 struct cgit_query qry;
232 struct cgit_config cfg; 234 struct cgit_config cfg;
233 struct cgit_repo *repo; 235 struct cgit_repo *repo;
234 struct cgit_page page; 236 struct cgit_page page;
235}; 237};
236 238
237struct cgit_snapshot_format { 239struct cgit_snapshot_format {
238 const char *suffix; 240 const char *suffix;
239 const char *mimetype; 241 const char *mimetype;
240 write_archive_fn_t write_func; 242 write_archive_fn_t write_func;
241 int bit; 243 int bit;
242}; 244};
243 245
244extern const char *cgit_version; 246extern const char *cgit_version;
diff --git a/cgitrc.5.txt b/cgitrc.5.txt
index 4dc383d..252d546 100644
--- a/cgitrc.5.txt
+++ b/cgitrc.5.txt
@@ -193,96 +193,100 @@ nocache::
193 If set to the value "1" caching will be disabled. This settings is 193 If set to the value "1" caching will be disabled. This settings is
194 deprecated, and will not be honored starting with cgit-1.0. Default 194 deprecated, and will not be honored starting with cgit-1.0. Default
195 value: "0". 195 value: "0".
196 196
197noplainemail:: 197noplainemail::
198 If set to "1" showing full author email adresses will be disabled. 198 If set to "1" showing full author email adresses will be disabled.
199 Default value: "0". 199 Default value: "0".
200 200
201noheader:: 201noheader::
202 Flag which, when set to "1", will make cgit omit the standard header 202 Flag which, when set to "1", will make cgit omit the standard header
203 on all pages. Default value: none. See also: "embedded". 203 on all pages. Default value: none. See also: "embedded".
204 204
205renamelimit:: 205renamelimit::
206 Maximum number of files to consider when detecting renames. The value 206 Maximum number of files to consider when detecting renames. The value
207 "-1" uses the compiletime value in git (for further info, look at 207 "-1" uses the compiletime value in git (for further info, look at
208 `man git-diff`). Default value: "-1". 208 `man git-diff`). Default value: "-1".
209 209
210repo.group:: 210repo.group::
211 Legacy alias for "section". This option is deprecated and will not be 211 Legacy alias for "section". This option is deprecated and will not be
212 supported in cgit-1.0. 212 supported in cgit-1.0.
213 213
214robots:: 214robots::
215 Text used as content for the "robots" meta-tag. Default value: 215 Text used as content for the "robots" meta-tag. Default value:
216 "index, nofollow". 216 "index, nofollow".
217 217
218root-desc:: 218root-desc::
219 Text printed below the heading on the repository index page. Default 219 Text printed below the heading on the repository index page. Default
220 value: "a fast webinterface for the git dscm". 220 value: "a fast webinterface for the git dscm".
221 221
222root-readme:: 222root-readme::
223 The content of the file specified with this option will be included 223 The content of the file specified with this option will be included
224 verbatim below the "about" link on the repository index page. Default 224 verbatim below the "about" link on the repository index page. Default
225 value: none. 225 value: none.
226 226
227root-title:: 227root-title::
228 Text printed as heading on the repository index page. Default value: 228 Text printed as heading on the repository index page. Default value:
229 "Git Repository Browser". 229 "Git Repository Browser".
230 230
231scan-path:: 231scan-path::
232 A path which will be scanned for repositories. If caching is enabled, 232 A path which will be scanned for repositories. If caching is enabled,
233 the result will be cached as a cgitrc include-file in the cache 233 the result will be cached as a cgitrc include-file in the cache
234 directory. Default value: none. See also: cache-scanrc-ttl. 234 directory. Default value: none. See also: cache-scanrc-ttl.
235 235
236section:: 236section::
237 The name of the current repository section - all repositories defined 237 The name of the current repository section - all repositories defined
238 after this option will inherit the current section name. Default value: 238 after this option will inherit the current section name. Default value:
239 none. 239 none.
240 240
241side-by-side-diffs::
242 If set to "1" shows side-by-side diffs instead of unidiffs per
243 default. Default value: "0".
244
241snapshots:: 245snapshots::
242 Text which specifies the default set of snapshot formats generated by 246 Text which specifies the default set of snapshot formats generated by
243 cgit. The value is a space-separated list of zero or more of the 247 cgit. The value is a space-separated list of zero or more of the
244 values "tar", "tar.gz", "tar.bz2" and "zip". Default value: none. 248 values "tar", "tar.gz", "tar.bz2" and "zip". Default value: none.
245 249
246source-filter:: 250source-filter::
247 Specifies a command which will be invoked to format plaintext blobs 251 Specifies a command which will be invoked to format plaintext blobs
248 in the tree view. The command will get the blob content on its STDIN 252 in the tree view. The command will get the blob content on its STDIN
249 and the name of the blob as its only command line argument. The STDOUT 253 and the name of the blob as its only command line argument. The STDOUT
250 from the command will be included verbatim as the blob contents, i.e. 254 from the command will be included verbatim as the blob contents, i.e.
251 this can be used to implement e.g. syntax highlighting. Default value: 255 this can be used to implement e.g. syntax highlighting. Default value:
252 none. 256 none.
253 257
254summary-branches:: 258summary-branches::
255 Specifies the number of branches to display in the repository "summary" 259 Specifies the number of branches to display in the repository "summary"
256 view. Default value: "10". 260 view. Default value: "10".
257 261
258summary-log:: 262summary-log::
259 Specifies the number of log entries to display in the repository 263 Specifies the number of log entries to display in the repository
260 "summary" view. Default value: "10". 264 "summary" view. Default value: "10".
261 265
262summary-tags:: 266summary-tags::
263 Specifies the number of tags to display in the repository "summary" 267 Specifies the number of tags to display in the repository "summary"
264 view. Default value: "10". 268 view. Default value: "10".
265 269
266virtual-root:: 270virtual-root::
267 Url which, if specified, will be used as root for all cgit links. It 271 Url which, if specified, will be used as root for all cgit links. It
268 will also cause cgit to generate 'virtual urls', i.e. urls like 272 will also cause cgit to generate 'virtual urls', i.e. urls like
269 '/cgit/tree/README' as opposed to '?r=cgit&p=tree&path=README'. Default 273 '/cgit/tree/README' as opposed to '?r=cgit&p=tree&path=README'. Default
270 value: none. 274 value: none.
271 NOTE: cgit has recently learned how to use PATH_INFO to achieve the 275 NOTE: cgit has recently learned how to use PATH_INFO to achieve the
272 same kind of virtual urls, so this option will probably be deprecated. 276 same kind of virtual urls, so this option will probably be deprecated.
273 277
274REPOSITORY SETTINGS 278REPOSITORY SETTINGS
275------------------- 279-------------------
276repo.about-filter:: 280repo.about-filter::
277 Override the default about-filter. Default value: none. See also: 281 Override the default about-filter. Default value: none. See also:
278 "enable-filter-overrides". 282 "enable-filter-overrides".
279 283
280repo.clone-url:: 284repo.clone-url::
281 A list of space-separated urls which can be used to clone this repo. 285 A list of space-separated urls which can be used to clone this repo.
282 Default value: none. 286 Default value: none.
283 287
284repo.commit-filter:: 288repo.commit-filter::
285 Override the default commit-filter. Default value: none. See also: 289 Override the default commit-filter. Default value: none. See also:
286 "enable-filter-overrides". 290 "enable-filter-overrides".
287 291
288repo.defbranch:: 292repo.defbranch::
diff --git a/ui-commit.c b/ui-commit.c
index f5b0ae5..b5e3c01 100644
--- a/ui-commit.c
+++ b/ui-commit.c
@@ -13,106 +13,111 @@
13#include "ui-log.h" 13#include "ui-log.h"
14 14
15void cgit_print_commit(char *hex) 15void cgit_print_commit(char *hex)
16{ 16{
17 struct commit *commit, *parent; 17 struct commit *commit, *parent;
18 struct commitinfo *info; 18 struct commitinfo *info;
19 struct commit_list *p; 19 struct commit_list *p;
20 unsigned char sha1[20]; 20 unsigned char sha1[20];
21 char *tmp; 21 char *tmp;
22 int parents = 0; 22 int parents = 0;
23 23
24 if (!hex) 24 if (!hex)
25 hex = ctx.qry.head; 25 hex = ctx.qry.head;
26 26
27 if (get_sha1(hex, sha1)) { 27 if (get_sha1(hex, sha1)) {
28 cgit_print_error(fmt("Bad object id: %s", hex)); 28 cgit_print_error(fmt("Bad object id: %s", hex));
29 return; 29 return;
30 } 30 }
31 commit = lookup_commit_reference(sha1); 31 commit = lookup_commit_reference(sha1);
32 if (!commit) { 32 if (!commit) {
33 cgit_print_error(fmt("Bad commit reference: %s", hex)); 33 cgit_print_error(fmt("Bad commit reference: %s", hex));
34 return; 34 return;
35 } 35 }
36 info = cgit_parse_commit(commit); 36 info = cgit_parse_commit(commit);
37 37
38 load_ref_decorations(DECORATE_FULL_REFS); 38 load_ref_decorations(DECORATE_FULL_REFS);
39 39
40 html("<table summary='commit info' class='commit-info'>\n"); 40 html("<table summary='commit info' class='commit-info'>\n");
41 html("<tr><th>author</th><td>"); 41 html("<tr><th>author</th><td>");
42 html_txt(info->author); 42 html_txt(info->author);
43 if (!ctx.cfg.noplainemail) { 43 if (!ctx.cfg.noplainemail) {
44 html(" "); 44 html(" ");
45 html_txt(info->author_email); 45 html_txt(info->author_email);
46 } 46 }
47 html("</td><td class='right'>"); 47 html("</td><td class='right'>");
48 cgit_print_date(info->author_date, FMT_LONGDATE, ctx.cfg.local_time); 48 cgit_print_date(info->author_date, FMT_LONGDATE, ctx.cfg.local_time);
49 html("</td></tr>\n"); 49 html("</td></tr>\n");
50 html("<tr><th>committer</th><td>"); 50 html("<tr><th>committer</th><td>");
51 html_txt(info->committer); 51 html_txt(info->committer);
52 if (!ctx.cfg.noplainemail) { 52 if (!ctx.cfg.noplainemail) {
53 html(" "); 53 html(" ");
54 html_txt(info->committer_email); 54 html_txt(info->committer_email);
55 } 55 }
56 html("</td><td class='right'>"); 56 html("</td><td class='right'>");
57 cgit_print_date(info->committer_date, FMT_LONGDATE, ctx.cfg.local_time); 57 cgit_print_date(info->committer_date, FMT_LONGDATE, ctx.cfg.local_time);
58 html("</td></tr>\n"); 58 html("</td></tr>\n");
59 html("<tr><th>commit</th><td colspan='2' class='sha1'>"); 59 html("<tr><th>commit</th><td colspan='2' class='sha1'>");
60 tmp = sha1_to_hex(commit->object.sha1); 60 tmp = sha1_to_hex(commit->object.sha1);
61 cgit_commit_link(tmp, NULL, NULL, ctx.qry.head, tmp); 61 cgit_commit_link(tmp, NULL, NULL, ctx.qry.head, tmp, 0);
62 html(" ("); 62 html(" (");
63 cgit_patch_link("patch", NULL, NULL, NULL, tmp); 63 cgit_patch_link("patch", NULL, NULL, NULL, tmp);
64 html(") (");
65 if ((ctx.qry.ssdiff && !ctx.cfg.ssdiff) || (!ctx.qry.ssdiff && ctx.cfg.ssdiff))
66 cgit_commit_link("unidiff", NULL, NULL, ctx.qry.head, tmp, 1);
67 else
68 cgit_commit_link("side-by-side diff", NULL, NULL, ctx.qry.head, tmp, 1);
64 html(")</td></tr>\n"); 69 html(")</td></tr>\n");
65 html("<tr><th>tree</th><td colspan='2' class='sha1'>"); 70 html("<tr><th>tree</th><td colspan='2' class='sha1'>");
66 tmp = xstrdup(hex); 71 tmp = xstrdup(hex);
67 cgit_tree_link(sha1_to_hex(commit->tree->object.sha1), NULL, NULL, 72 cgit_tree_link(sha1_to_hex(commit->tree->object.sha1), NULL, NULL,
68 ctx.qry.head, tmp, NULL); 73 ctx.qry.head, tmp, NULL);
69 html("</td></tr>\n"); 74 html("</td></tr>\n");
70 for (p = commit->parents; p ; p = p->next) { 75 for (p = commit->parents; p ; p = p->next) {
71 parent = lookup_commit_reference(p->item->object.sha1); 76 parent = lookup_commit_reference(p->item->object.sha1);
72 if (!parent) { 77 if (!parent) {
73 html("<tr><td colspan='3'>"); 78 html("<tr><td colspan='3'>");
74 cgit_print_error("Error reading parent commit"); 79 cgit_print_error("Error reading parent commit");
75 html("</td></tr>"); 80 html("</td></tr>");
76 continue; 81 continue;
77 } 82 }
78 html("<tr><th>parent</th>" 83 html("<tr><th>parent</th>"
79 "<td colspan='2' class='sha1'>"); 84 "<td colspan='2' class='sha1'>");
80 cgit_commit_link(sha1_to_hex(p->item->object.sha1), NULL, NULL, 85 cgit_commit_link(sha1_to_hex(p->item->object.sha1), NULL, NULL,
81 ctx.qry.head, sha1_to_hex(p->item->object.sha1)); 86 ctx.qry.head, sha1_to_hex(p->item->object.sha1), 0);
82 html(" ("); 87 html(" (");
83 cgit_diff_link("diff", NULL, NULL, ctx.qry.head, hex, 88 cgit_diff_link("diff", NULL, NULL, ctx.qry.head, hex,
84 sha1_to_hex(p->item->object.sha1), NULL); 89 sha1_to_hex(p->item->object.sha1), NULL, 0);
85 html(")</td></tr>"); 90 html(")</td></tr>");
86 parents++; 91 parents++;
87 } 92 }
88 if (ctx.repo->snapshots) { 93 if (ctx.repo->snapshots) {
89 html("<tr><th>download</th><td colspan='2' class='sha1'>"); 94 html("<tr><th>download</th><td colspan='2' class='sha1'>");
90 cgit_print_snapshot_links(ctx.qry.repo, ctx.qry.head, 95 cgit_print_snapshot_links(ctx.qry.repo, ctx.qry.head,
91 hex, ctx.repo->snapshots); 96 hex, ctx.repo->snapshots);
92 html("</td></tr>"); 97 html("</td></tr>");
93 } 98 }
94 html("</table>\n"); 99 html("</table>\n");
95 html("<div class='commit-subject'>"); 100 html("<div class='commit-subject'>");
96 if (ctx.repo->commit_filter) 101 if (ctx.repo->commit_filter)
97 cgit_open_filter(ctx.repo->commit_filter); 102 cgit_open_filter(ctx.repo->commit_filter);
98 html_txt(info->subject); 103 html_txt(info->subject);
99 if (ctx.repo->commit_filter) 104 if (ctx.repo->commit_filter)
100 cgit_close_filter(ctx.repo->commit_filter); 105 cgit_close_filter(ctx.repo->commit_filter);
101 show_commit_decorations(commit); 106 show_commit_decorations(commit);
102 html("</div>"); 107 html("</div>");
103 html("<div class='commit-msg'>"); 108 html("<div class='commit-msg'>");
104 if (ctx.repo->commit_filter) 109 if (ctx.repo->commit_filter)
105 cgit_open_filter(ctx.repo->commit_filter); 110 cgit_open_filter(ctx.repo->commit_filter);
106 html_txt(info->msg); 111 html_txt(info->msg);
107 if (ctx.repo->commit_filter) 112 if (ctx.repo->commit_filter)
108 cgit_close_filter(ctx.repo->commit_filter); 113 cgit_close_filter(ctx.repo->commit_filter);
109 html("</div>"); 114 html("</div>");
110 if (parents < 3) { 115 if (parents < 3) {
111 if (parents) 116 if (parents)
112 tmp = sha1_to_hex(commit->parents->item->object.sha1); 117 tmp = sha1_to_hex(commit->parents->item->object.sha1);
113 else 118 else
114 tmp = NULL; 119 tmp = NULL;
115 cgit_print_diff(ctx.qry.sha1, tmp, NULL); 120 cgit_print_diff(ctx.qry.sha1, tmp, NULL);
116 } 121 }
117 cgit_free_commitinfo(info); 122 cgit_free_commitinfo(info);
118} 123}
diff --git a/ui-diff.c b/ui-diff.c
index 0c6f8d7..42e81ac 100644
--- a/ui-diff.c
+++ b/ui-diff.c
@@ -40,285 +40,303 @@ static void print_fileinfo(struct fileinfo *info)
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); 88 ctx.qry.sha2, info->new_path, 0);
89 if (info->status == DIFF_STATUS_COPIED || info->status == DIFF_STATUS_RENAMED) 89 if (info->status == DIFF_STATUS_COPIED || info->status == DIFF_STATUS_RENAMED)
90 htmlf(" (%s from %s)", 90 htmlf(" (%s from %s)",
91 info->status == DIFF_STATUS_COPIED ? "copied" : "renamed", 91 info->status == DIFF_STATUS_COPIED ? "copied" : "renamed",
92 info->old_path); 92 info->old_path);
93 html("</td><td class='right'>"); 93 html("</td><td class='right'>");
94 if (info->binary) { 94 if (info->binary) {
95 htmlf("bin</td><td class='graph'>%d -> %d bytes", 95 htmlf("bin</td><td class='graph'>%d -> %d bytes",
96 info->old_size, info->new_size); 96 info->old_size, info->new_size);
97 return; 97 return;
98 } 98 }
99 htmlf("%d", info->added + info->removed); 99 htmlf("%d", info->added + info->removed);
100 html("</td><td class='graph'>"); 100 html("</td><td class='graph'>");
101 htmlf("<table summary='file diffstat' width='%d%%'><tr>", (max_changes > 100 ? 100 : max_changes)); 101 htmlf("<table summary='file diffstat' width='%d%%'><tr>", (max_changes > 100 ? 100 : max_changes));
102 htmlf("<td class='add' style='width: %.1f%%;'/>", 102 htmlf("<td class='add' style='width: %.1f%%;'/>",
103 info->added * 100.0 / max_changes); 103 info->added * 100.0 / max_changes);
104 htmlf("<td class='rem' style='width: %.1f%%;'/>", 104 htmlf("<td class='rem' style='width: %.1f%%;'/>",
105 info->removed * 100.0 / max_changes); 105 info->removed * 100.0 / max_changes);
106 htmlf("<td class='none' style='width: %.1f%%;'/>", 106 htmlf("<td class='none' style='width: %.1f%%;'/>",
107 (max_changes - info->removed - info->added) * 100.0 / max_changes); 107 (max_changes - info->removed - info->added) * 100.0 / max_changes);
108 html("</tr></table></td></tr>\n"); 108 html("</tr></table></td></tr>\n");
109} 109}
110 110
111static void count_diff_lines(char *line, int len) 111static void count_diff_lines(char *line, int len)
112{ 112{
113 if (line && (len > 0)) { 113 if (line && (len > 0)) {
114 if (line[0] == '+') 114 if (line[0] == '+')
115 lines_added++; 115 lines_added++;
116 else if (line[0] == '-') 116 else if (line[0] == '-')
117 lines_removed++; 117 lines_removed++;
118 } 118 }
119} 119}
120 120
121static void inspect_filepair(struct diff_filepair *pair) 121static void inspect_filepair(struct diff_filepair *pair)
122{ 122{
123 int binary = 0; 123 int binary = 0;
124 unsigned long old_size = 0; 124 unsigned long old_size = 0;
125 unsigned long new_size = 0; 125 unsigned long new_size = 0;
126 files++; 126 files++;
127 lines_added = 0; 127 lines_added = 0;
128 lines_removed = 0; 128 lines_removed = 0;
129 cgit_diff_files(pair->one->sha1, pair->two->sha1, &old_size, &new_size, 129 cgit_diff_files(pair->one->sha1, pair->two->sha1, &old_size, &new_size,
130 &binary, count_diff_lines); 130 &binary, count_diff_lines);
131 if (files >= slots) { 131 if (files >= slots) {
132 if (slots == 0) 132 if (slots == 0)
133 slots = 4; 133 slots = 4;
134 else 134 else
135 slots = slots * 2; 135 slots = slots * 2;
136 items = xrealloc(items, slots * sizeof(struct fileinfo)); 136 items = xrealloc(items, slots * sizeof(struct fileinfo));
137 } 137 }
138 items[files-1].status = pair->status; 138 items[files-1].status = pair->status;
139 hashcpy(items[files-1].old_sha1, pair->one->sha1); 139 hashcpy(items[files-1].old_sha1, pair->one->sha1);
140 hashcpy(items[files-1].new_sha1, pair->two->sha1); 140 hashcpy(items[files-1].new_sha1, pair->two->sha1);
141 items[files-1].old_mode = pair->one->mode; 141 items[files-1].old_mode = pair->one->mode;
142 items[files-1].new_mode = pair->two->mode; 142 items[files-1].new_mode = pair->two->mode;
143 items[files-1].old_path = xstrdup(pair->one->path); 143 items[files-1].old_path = xstrdup(pair->one->path);
144 items[files-1].new_path = xstrdup(pair->two->path); 144 items[files-1].new_path = xstrdup(pair->two->path);
145 items[files-1].added = lines_added; 145 items[files-1].added = lines_added;
146 items[files-1].removed = lines_removed; 146 items[files-1].removed = lines_removed;
147 items[files-1].old_size = old_size; 147 items[files-1].old_size = old_size;
148 items[files-1].new_size = new_size; 148 items[files-1].new_size = new_size;
149 items[files-1].binary = binary; 149 items[files-1].binary = binary;
150 if (lines_added + lines_removed > max_changes) 150 if (lines_added + lines_removed > max_changes)
151 max_changes = lines_added + lines_removed; 151 max_changes = lines_added + lines_removed;
152 total_adds += lines_added; 152 total_adds += lines_added;
153 total_rems += lines_removed; 153 total_rems += lines_removed;
154} 154}
155 155
156void cgit_print_diffstat(const unsigned char *old_sha1, 156void cgit_print_diffstat(const unsigned char *old_sha1,
157 const unsigned char *new_sha1) 157 const unsigned char *new_sha1)
158{ 158{
159 int i; 159 int i;
160 160
161 html("<div class='diffstat-header'>"); 161 html("<div class='diffstat-header'>");
162 cgit_diff_link("Diffstat", NULL, NULL, ctx.qry.head, ctx.qry.sha1, 162 cgit_diff_link("Diffstat", NULL, NULL, ctx.qry.head, ctx.qry.sha1,
163 ctx.qry.sha2, NULL); 163 ctx.qry.sha2, NULL, 0);
164 html("</div>"); 164 html("</div>");
165 html("<table summary='diffstat' class='diffstat'>"); 165 html("<table summary='diffstat' class='diffstat'>");
166 max_changes = 0; 166 max_changes = 0;
167 cgit_diff_tree(old_sha1, new_sha1, inspect_filepair, NULL); 167 cgit_diff_tree(old_sha1, new_sha1, inspect_filepair, NULL);
168 for(i = 0; i<files; i++) 168 for(i = 0; i<files; i++)
169 print_fileinfo(&items[i]); 169 print_fileinfo(&items[i]);
170 html("</table>"); 170 html("</table>");
171 html("<div class='diffstat-summary'>"); 171 html("<div class='diffstat-summary'>");
172 htmlf("%d files changed, %d insertions, %d deletions", 172 htmlf("%d files changed, %d insertions, %d deletions",
173 files, total_adds, total_rems); 173 files, total_adds, total_rems);
174 html("</div>"); 174 html("</div>");
175} 175}
176 176
177 177
178/* 178/*
179 * print a single line returned from xdiff 179 * print a single line returned from xdiff
180 */ 180 */
181static void print_line(char *line, int len) 181static void print_line(char *line, int len)
182{ 182{
183 char *class = "ctx"; 183 char *class = "ctx";
184 char c = line[len-1]; 184 char c = line[len-1];
185 185
186 if (line[0] == '+') 186 if (line[0] == '+')
187 class = "add"; 187 class = "add";
188 else if (line[0] == '-') 188 else if (line[0] == '-')
189 class = "del"; 189 class = "del";
190 else if (line[0] == '@') 190 else if (line[0] == '@')
191 class = "hunk"; 191 class = "hunk";
192 192
193 htmlf("<div class='%s'>", class); 193 htmlf("<div class='%s'>", class);
194 line[len-1] = '\0'; 194 line[len-1] = '\0';
195 html_txt(line); 195 html_txt(line);
196 html("</div>"); 196 html("</div>");
197 line[len-1] = c; 197 line[len-1] = c;
198} 198}
199 199
200static void header(unsigned char *sha1, char *path1, int mode1, 200static void header(unsigned char *sha1, char *path1, int mode1,
201 unsigned char *sha2, char *path2, int mode2) 201 unsigned char *sha2, char *path2, int mode2)
202{ 202{
203 char *abbrev1, *abbrev2; 203 char *abbrev1, *abbrev2;
204 int subproject; 204 int subproject;
205 205
206 subproject = (S_ISGITLINK(mode1) || S_ISGITLINK(mode2)); 206 subproject = (S_ISGITLINK(mode1) || S_ISGITLINK(mode2));
207 html("<div class='head'>"); 207 html("<div class='head'>");
208 html("diff --git a/"); 208 html("diff --git a/");
209 html_txt(path1); 209 html_txt(path1);
210 html(" b/"); 210 html(" b/");
211 html_txt(path2); 211 html_txt(path2);
212 212
213 if (is_null_sha1(sha1)) 213 if (is_null_sha1(sha1))
214 path1 = "dev/null"; 214 path1 = "dev/null";
215 if (is_null_sha1(sha2)) 215 if (is_null_sha1(sha2))
216 path2 = "dev/null"; 216 path2 = "dev/null";
217 217
218 if (mode1 == 0) 218 if (mode1 == 0)
219 htmlf("<br/>new file mode %.6o", mode2); 219 htmlf("<br/>new file mode %.6o", mode2);
220 220
221 if (mode2 == 0) 221 if (mode2 == 0)
222 htmlf("<br/>deleted file mode %.6o", mode1); 222 htmlf("<br/>deleted file mode %.6o", mode1);
223 223
224 if (!subproject) { 224 if (!subproject) {
225 abbrev1 = xstrdup(find_unique_abbrev(sha1, DEFAULT_ABBREV)); 225 abbrev1 = xstrdup(find_unique_abbrev(sha1, DEFAULT_ABBREV));
226 abbrev2 = xstrdup(find_unique_abbrev(sha2, DEFAULT_ABBREV)); 226 abbrev2 = xstrdup(find_unique_abbrev(sha2, DEFAULT_ABBREV));
227 htmlf("<br/>index %s..%s", abbrev1, abbrev2); 227 htmlf("<br/>index %s..%s", abbrev1, abbrev2);
228 free(abbrev1); 228 free(abbrev1);
229 free(abbrev2); 229 free(abbrev2);
230 if (mode1 != 0 && mode2 != 0) { 230 if (mode1 != 0 && mode2 != 0) {
231 htmlf(" %.6o", mode1); 231 htmlf(" %.6o", mode1);
232 if (mode2 != mode1) 232 if (mode2 != mode1)
233 htmlf("..%.6o", mode2); 233 htmlf("..%.6o", mode2);
234 } 234 }
235 html("<br/>--- a/"); 235 html("<br/>--- a/");
236 if (mode1 != 0) 236 if (mode1 != 0)
237 cgit_tree_link(path1, NULL, NULL, ctx.qry.head, 237 cgit_tree_link(path1, NULL, NULL, ctx.qry.head,
238 sha1_to_hex(old_rev_sha1), path1); 238 sha1_to_hex(old_rev_sha1), path1);
239 else 239 else
240 html_txt(path1); 240 html_txt(path1);
241 html("<br/>+++ b/"); 241 html("<br/>+++ b/");
242 if (mode2 != 0) 242 if (mode2 != 0)
243 cgit_tree_link(path2, NULL, NULL, ctx.qry.head, 243 cgit_tree_link(path2, NULL, NULL, ctx.qry.head,
244 sha1_to_hex(new_rev_sha1), path2); 244 sha1_to_hex(new_rev_sha1), path2);
245 else 245 else
246 html_txt(path2); 246 html_txt(path2);
247 } 247 }
248 html("</div>"); 248 html("</div>");
249 if (use_ssdiff) 249 if (use_ssdiff)
250 cgit_ssdiff_header(); 250 cgit_ssdiff_header();
251} 251}
252 252
253static void print_ssdiff_link()
254{
255 if (!strcmp(ctx.qry.page, "diff")) {
256 if (use_ssdiff)
257 cgit_diff_link("Unidiff", NULL, NULL, ctx.qry.head,
258 ctx.qry.sha1, ctx.qry.sha2, NULL, 1);
259 else
260 cgit_diff_link("Side-by-side diff", NULL, NULL,
261 ctx.qry.head, ctx.qry.sha1,
262 ctx.qry.sha2, NULL, 1);
263 }
264}
265
253static void filepair_cb(struct diff_filepair *pair) 266static void filepair_cb(struct diff_filepair *pair)
254{ 267{
255 unsigned long old_size = 0; 268 unsigned long old_size = 0;
256 unsigned long new_size = 0; 269 unsigned long new_size = 0;
257 int binary = 0; 270 int binary = 0;
258 linediff_fn print_line_fn = print_line; 271 linediff_fn print_line_fn = print_line;
259 272
260 header(pair->one->sha1, pair->one->path, pair->one->mode, 273 header(pair->one->sha1, pair->one->path, pair->one->mode,
261 pair->two->sha1, pair->two->path, pair->two->mode); 274 pair->two->sha1, pair->two->path, pair->two->mode);
262 if (use_ssdiff) { 275 if (use_ssdiff) {
263 cgit_ssdiff_header(); 276 cgit_ssdiff_header();
264 print_line_fn = cgit_ssdiff_line_cb; 277 print_line_fn = cgit_ssdiff_line_cb;
265 } 278 }
266 if (S_ISGITLINK(pair->one->mode) || S_ISGITLINK(pair->two->mode)) { 279 if (S_ISGITLINK(pair->one->mode) || S_ISGITLINK(pair->two->mode)) {
267 if (S_ISGITLINK(pair->one->mode)) 280 if (S_ISGITLINK(pair->one->mode))
268 print_line(fmt("-Subproject %s", sha1_to_hex(pair->one->sha1)), 52); 281 print_line(fmt("-Subproject %s", sha1_to_hex(pair->one->sha1)), 52);
269 if (S_ISGITLINK(pair->two->mode)) 282 if (S_ISGITLINK(pair->two->mode))
270 print_line(fmt("+Subproject %s", sha1_to_hex(pair->two->sha1)), 52); 283 print_line(fmt("+Subproject %s", sha1_to_hex(pair->two->sha1)), 52);
271 return; 284 return;
272 } 285 }
273 if (cgit_diff_files(pair->one->sha1, pair->two->sha1, &old_size, 286 if (cgit_diff_files(pair->one->sha1, pair->two->sha1, &old_size,
274 &new_size, &binary, print_line_fn)) 287 &new_size, &binary, print_line_fn))
275 cgit_print_error("Error running diff"); 288 cgit_print_error("Error running diff");
276 if (binary) 289 if (binary)
277 html("Binary files differ"); 290 html("Binary files differ");
278 if (use_ssdiff) 291 if (use_ssdiff)
279 cgit_ssdiff_footer(); 292 cgit_ssdiff_footer();
280} 293}
281 294
282void cgit_print_diff(const char *new_rev, const char *old_rev, const char *prefix) 295void cgit_print_diff(const char *new_rev, const char *old_rev, const char *prefix)
283{ 296{
284 enum object_type type; 297 enum object_type type;
285 unsigned long size; 298 unsigned long size;
286 struct commit *commit, *commit2; 299 struct commit *commit, *commit2;
287 300
288 if (!new_rev) 301 if (!new_rev)
289 new_rev = ctx.qry.head; 302 new_rev = ctx.qry.head;
290 get_sha1(new_rev, new_rev_sha1); 303 get_sha1(new_rev, new_rev_sha1);
291 type = sha1_object_info(new_rev_sha1, &size); 304 type = sha1_object_info(new_rev_sha1, &size);
292 if (type == OBJ_BAD) { 305 if (type == OBJ_BAD) {
293 cgit_print_error(fmt("Bad object name: %s", new_rev)); 306 cgit_print_error(fmt("Bad object name: %s", new_rev));
294 return; 307 return;
295 } 308 }
296 commit = lookup_commit_reference(new_rev_sha1); 309 commit = lookup_commit_reference(new_rev_sha1);
297 if (!commit || parse_commit(commit)) 310 if (!commit || parse_commit(commit))
298 cgit_print_error(fmt("Bad commit: %s", sha1_to_hex(new_rev_sha1))); 311 cgit_print_error(fmt("Bad commit: %s", sha1_to_hex(new_rev_sha1)));
299 312
300 if (old_rev) 313 if (old_rev)
301 get_sha1(old_rev, old_rev_sha1); 314 get_sha1(old_rev, old_rev_sha1);
302 else if (commit->parents && commit->parents->item) 315 else if (commit->parents && commit->parents->item)
303 hashcpy(old_rev_sha1, commit->parents->item->object.sha1); 316 hashcpy(old_rev_sha1, commit->parents->item->object.sha1);
304 else 317 else
305 hashclr(old_rev_sha1); 318 hashclr(old_rev_sha1);
306 319
307 if (!is_null_sha1(old_rev_sha1)) { 320 if (!is_null_sha1(old_rev_sha1)) {
308 type = sha1_object_info(old_rev_sha1, &size); 321 type = sha1_object_info(old_rev_sha1, &size);
309 if (type == OBJ_BAD) { 322 if (type == OBJ_BAD) {
310 cgit_print_error(fmt("Bad object name: %s", sha1_to_hex(old_rev_sha1))); 323 cgit_print_error(fmt("Bad object name: %s", sha1_to_hex(old_rev_sha1)));
311 return; 324 return;
312 } 325 }
313 commit2 = lookup_commit_reference(old_rev_sha1); 326 commit2 = lookup_commit_reference(old_rev_sha1);
314 if (!commit2 || parse_commit(commit2)) 327 if (!commit2 || parse_commit(commit2))
315 cgit_print_error(fmt("Bad commit: %s", sha1_to_hex(old_rev_sha1))); 328 cgit_print_error(fmt("Bad commit: %s", sha1_to_hex(old_rev_sha1)));
316 } 329 }
330
331 if ((ctx.qry.ssdiff && !ctx.cfg.ssdiff) || (!ctx.qry.ssdiff && ctx.cfg.ssdiff))
332 use_ssdiff = 1;
333
334 print_ssdiff_link();
317 cgit_print_diffstat(old_rev_sha1, new_rev_sha1); 335 cgit_print_diffstat(old_rev_sha1, new_rev_sha1);
318 336
319 html("<table summary='diff' class='diff'>"); 337 html("<table summary='diff' class='diff'>");
320 html("<tr><td>"); 338 html("<tr><td>");
321 cgit_diff_tree(old_rev_sha1, new_rev_sha1, filepair_cb, prefix); 339 cgit_diff_tree(old_rev_sha1, new_rev_sha1, filepair_cb, prefix);
322 html("</td></tr>"); 340 html("</td></tr>");
323 html("</table>"); 341 html("</table>");
324} 342}
diff --git a/ui-log.c b/ui-log.c
index f3132c9..0947604 100644
--- a/ui-log.c
+++ b/ui-log.c
@@ -21,120 +21,120 @@ void count_lines(char *line, int size)
21 add_lines++; 21 add_lines++;
22 22
23 else if (line[0] == '-') 23 else if (line[0] == '-')
24 rem_lines++; 24 rem_lines++;
25} 25}
26 26
27void inspect_files(struct diff_filepair *pair) 27void inspect_files(struct diff_filepair *pair)
28{ 28{
29 unsigned long old_size = 0; 29 unsigned long old_size = 0;
30 unsigned long new_size = 0; 30 unsigned long new_size = 0;
31 int binary = 0; 31 int binary = 0;
32 32
33 files++; 33 files++;
34 if (ctx.repo->enable_log_linecount) 34 if (ctx.repo->enable_log_linecount)
35 cgit_diff_files(pair->one->sha1, pair->two->sha1, &old_size, 35 cgit_diff_files(pair->one->sha1, pair->two->sha1, &old_size,
36 &new_size, &binary, count_lines); 36 &new_size, &binary, count_lines);
37} 37}
38 38
39void show_commit_decorations(struct commit *commit) 39void show_commit_decorations(struct commit *commit)
40{ 40{
41 struct name_decoration *deco; 41 struct name_decoration *deco;
42 static char buf[1024]; 42 static char buf[1024];
43 43
44 buf[sizeof(buf) - 1] = 0; 44 buf[sizeof(buf) - 1] = 0;
45 deco = lookup_decoration(&name_decoration, &commit->object); 45 deco = lookup_decoration(&name_decoration, &commit->object);
46 while (deco) { 46 while (deco) {
47 if (!prefixcmp(deco->name, "refs/heads/")) { 47 if (!prefixcmp(deco->name, "refs/heads/")) {
48 strncpy(buf, deco->name + 11, sizeof(buf) - 1); 48 strncpy(buf, deco->name + 11, sizeof(buf) - 1);
49 cgit_log_link(buf, NULL, "branch-deco", buf, NULL, NULL, 49 cgit_log_link(buf, NULL, "branch-deco", buf, NULL, NULL,
50 0, NULL, NULL, ctx.qry.showmsg); 50 0, NULL, NULL, ctx.qry.showmsg);
51 } 51 }
52 else if (!prefixcmp(deco->name, "tag: refs/tags/")) { 52 else if (!prefixcmp(deco->name, "tag: refs/tags/")) {
53 strncpy(buf, deco->name + 15, sizeof(buf) - 1); 53 strncpy(buf, deco->name + 15, sizeof(buf) - 1);
54 cgit_tag_link(buf, NULL, "tag-deco", ctx.qry.head, buf); 54 cgit_tag_link(buf, NULL, "tag-deco", ctx.qry.head, buf);
55 } 55 }
56 else if (!prefixcmp(deco->name, "refs/tags/")) { 56 else if (!prefixcmp(deco->name, "refs/tags/")) {
57 strncpy(buf, deco->name + 10, sizeof(buf) - 1); 57 strncpy(buf, deco->name + 10, sizeof(buf) - 1);
58 cgit_tag_link(buf, NULL, "tag-deco", ctx.qry.head, buf); 58 cgit_tag_link(buf, NULL, "tag-deco", ctx.qry.head, buf);
59 } 59 }
60 else if (!prefixcmp(deco->name, "refs/remotes/")) { 60 else if (!prefixcmp(deco->name, "refs/remotes/")) {
61 strncpy(buf, deco->name + 13, sizeof(buf) - 1); 61 strncpy(buf, deco->name + 13, sizeof(buf) - 1);
62 cgit_log_link(buf, NULL, "remote-deco", NULL, 62 cgit_log_link(buf, NULL, "remote-deco", NULL,
63 sha1_to_hex(commit->object.sha1), NULL, 63 sha1_to_hex(commit->object.sha1), NULL,
64 0, NULL, NULL, ctx.qry.showmsg); 64 0, NULL, NULL, ctx.qry.showmsg);
65 } 65 }
66 else { 66 else {
67 strncpy(buf, deco->name, sizeof(buf) - 1); 67 strncpy(buf, deco->name, sizeof(buf) - 1);
68 cgit_commit_link(buf, NULL, "deco", ctx.qry.head, 68 cgit_commit_link(buf, NULL, "deco", ctx.qry.head,
69 sha1_to_hex(commit->object.sha1)); 69 sha1_to_hex(commit->object.sha1), 0);
70 } 70 }
71 deco = deco->next; 71 deco = deco->next;
72 } 72 }
73} 73}
74 74
75void print_commit(struct commit *commit) 75void print_commit(struct commit *commit)
76{ 76{
77 struct commitinfo *info; 77 struct commitinfo *info;
78 char *tmp; 78 char *tmp;
79 int cols = 2; 79 int cols = 2;
80 80
81 info = cgit_parse_commit(commit); 81 info = cgit_parse_commit(commit);
82 htmlf("<tr%s><td>", 82 htmlf("<tr%s><td>",
83 ctx.qry.showmsg ? " class='logheader'" : ""); 83 ctx.qry.showmsg ? " class='logheader'" : "");
84 tmp = fmt("id=%s", sha1_to_hex(commit->object.sha1)); 84 tmp = fmt("id=%s", sha1_to_hex(commit->object.sha1));
85 tmp = cgit_pageurl(ctx.repo->url, "commit", tmp); 85 tmp = cgit_pageurl(ctx.repo->url, "commit", tmp);
86 html_link_open(tmp, NULL, NULL); 86 html_link_open(tmp, NULL, NULL);
87 cgit_print_age(commit->date, TM_WEEK * 2, FMT_SHORTDATE); 87 cgit_print_age(commit->date, TM_WEEK * 2, FMT_SHORTDATE);
88 html_link_close(); 88 html_link_close();
89 htmlf("</td><td%s>", 89 htmlf("</td><td%s>",
90 ctx.qry.showmsg ? " class='logsubject'" : ""); 90 ctx.qry.showmsg ? " class='logsubject'" : "");
91 cgit_commit_link(info->subject, NULL, NULL, ctx.qry.head, 91 cgit_commit_link(info->subject, NULL, NULL, ctx.qry.head,
92 sha1_to_hex(commit->object.sha1)); 92 sha1_to_hex(commit->object.sha1), 0);
93 show_commit_decorations(commit); 93 show_commit_decorations(commit);
94 html("</td><td>"); 94 html("</td><td>");
95 html_txt(info->author); 95 html_txt(info->author);
96 if (ctx.repo->enable_log_filecount) { 96 if (ctx.repo->enable_log_filecount) {
97 files = 0; 97 files = 0;
98 add_lines = 0; 98 add_lines = 0;
99 rem_lines = 0; 99 rem_lines = 0;
100 cgit_diff_commit(commit, inspect_files); 100 cgit_diff_commit(commit, inspect_files);
101 html("</td><td>"); 101 html("</td><td>");
102 htmlf("%d", files); 102 htmlf("%d", files);
103 if (ctx.repo->enable_log_linecount) { 103 if (ctx.repo->enable_log_linecount) {
104 html("</td><td>"); 104 html("</td><td>");
105 htmlf("-%d/+%d", rem_lines, add_lines); 105 htmlf("-%d/+%d", rem_lines, add_lines);
106 } 106 }
107 } 107 }
108 html("</td></tr>\n"); 108 html("</td></tr>\n");
109 if (ctx.qry.showmsg) { 109 if (ctx.qry.showmsg) {
110 if (ctx.repo->enable_log_filecount) { 110 if (ctx.repo->enable_log_filecount) {
111 cols++; 111 cols++;
112 if (ctx.repo->enable_log_linecount) 112 if (ctx.repo->enable_log_linecount)
113 cols++; 113 cols++;
114 } 114 }
115 htmlf("<tr class='nohover'><td/><td colspan='%d' class='logmsg'>", 115 htmlf("<tr class='nohover'><td/><td colspan='%d' class='logmsg'>",
116 cols); 116 cols);
117 html_txt(info->msg); 117 html_txt(info->msg);
118 html("</td></tr>\n"); 118 html("</td></tr>\n");
119 } 119 }
120 cgit_free_commitinfo(info); 120 cgit_free_commitinfo(info);
121} 121}
122 122
123static const char *disambiguate_ref(const char *ref) 123static const char *disambiguate_ref(const char *ref)
124{ 124{
125 unsigned char sha1[20]; 125 unsigned char sha1[20];
126 const char *longref; 126 const char *longref;
127 127
128 longref = fmt("refs/heads/%s", ref); 128 longref = fmt("refs/heads/%s", ref);
129 if (get_sha1(longref, sha1) == 0) 129 if (get_sha1(longref, sha1) == 0)
130 return longref; 130 return longref;
131 131
132 return ref; 132 return ref;
133} 133}
134 134
135void cgit_print_log(const char *tip, int ofs, int cnt, char *grep, char *pattern, 135void cgit_print_log(const char *tip, int ofs, int cnt, char *grep, char *pattern,
136 char *path, int pager) 136 char *path, int pager)
137{ 137{
138 struct rev_info rev; 138 struct rev_info rev;
139 struct commit *commit; 139 struct commit *commit;
140 const char *argv[] = {NULL, NULL, NULL, NULL, NULL}; 140 const char *argv[] = {NULL, NULL, NULL, NULL, NULL};
diff --git a/ui-refs.c b/ui-refs.c
index d3b4f6e..33d9bec 100644
--- a/ui-refs.c
+++ b/ui-refs.c
@@ -29,97 +29,97 @@ static int cmp_age(int age1, int age2)
29static int cmp_ref_name(const void *a, const void *b) 29static int cmp_ref_name(const void *a, const void *b)
30{ 30{
31 struct refinfo *r1 = *(struct refinfo **)a; 31 struct refinfo *r1 = *(struct refinfo **)a;
32 struct refinfo *r2 = *(struct refinfo **)b; 32 struct refinfo *r2 = *(struct refinfo **)b;
33 33
34 return strcmp(r1->refname, r2->refname); 34 return strcmp(r1->refname, r2->refname);
35} 35}
36 36
37static int cmp_branch_age(const void *a, const void *b) 37static int cmp_branch_age(const void *a, const void *b)
38{ 38{
39 struct refinfo *r1 = *(struct refinfo **)a; 39 struct refinfo *r1 = *(struct refinfo **)a;
40 struct refinfo *r2 = *(struct refinfo **)b; 40 struct refinfo *r2 = *(struct refinfo **)b;
41 41
42 return cmp_age(r1->commit->committer_date, r2->commit->committer_date); 42 return cmp_age(r1->commit->committer_date, r2->commit->committer_date);
43} 43}
44 44
45static int cmp_tag_age(const void *a, const void *b) 45static int cmp_tag_age(const void *a, const void *b)
46{ 46{
47 struct refinfo *r1 = *(struct refinfo **)a; 47 struct refinfo *r1 = *(struct refinfo **)a;
48 struct refinfo *r2 = *(struct refinfo **)b; 48 struct refinfo *r2 = *(struct refinfo **)b;
49 int r1date, r2date; 49 int r1date, r2date;
50 50
51 if (r1->object->type != OBJ_COMMIT) 51 if (r1->object->type != OBJ_COMMIT)
52 r1date = r1->tag->tagger_date; 52 r1date = r1->tag->tagger_date;
53 else 53 else
54 r1date = r1->commit->committer_date; 54 r1date = r1->commit->committer_date;
55 55
56 if (r2->object->type != OBJ_COMMIT) 56 if (r2->object->type != OBJ_COMMIT)
57 r2date = r2->tag->tagger_date; 57 r2date = r2->tag->tagger_date;
58 else 58 else
59 r2date = r2->commit->committer_date; 59 r2date = r2->commit->committer_date;
60 60
61 return cmp_age(r1date, r2date); 61 return cmp_age(r1date, r2date);
62} 62}
63 63
64static int print_branch(struct refinfo *ref) 64static int print_branch(struct refinfo *ref)
65{ 65{
66 struct commitinfo *info = ref->commit; 66 struct commitinfo *info = ref->commit;
67 char *name = (char *)ref->refname; 67 char *name = (char *)ref->refname;
68 68
69 if (!info) 69 if (!info)
70 return 1; 70 return 1;
71 html("<tr><td>"); 71 html("<tr><td>");
72 cgit_log_link(name, NULL, NULL, name, NULL, NULL, 0, NULL, NULL, 72 cgit_log_link(name, NULL, NULL, name, NULL, NULL, 0, NULL, NULL,
73 ctx.qry.showmsg); 73 ctx.qry.showmsg);
74 html("</td><td>"); 74 html("</td><td>");
75 75
76 if (ref->object->type == OBJ_COMMIT) { 76 if (ref->object->type == OBJ_COMMIT) {
77 cgit_commit_link(info->subject, NULL, NULL, name, NULL); 77 cgit_commit_link(info->subject, NULL, NULL, name, NULL, 0);
78 html("</td><td>"); 78 html("</td><td>");
79 html_txt(info->author); 79 html_txt(info->author);
80 html("</td><td colspan='2'>"); 80 html("</td><td colspan='2'>");
81 cgit_print_age(info->commit->date, -1, NULL); 81 cgit_print_age(info->commit->date, -1, NULL);
82 } else { 82 } else {
83 html("</td><td></td><td>"); 83 html("</td><td></td><td>");
84 cgit_object_link(ref->object); 84 cgit_object_link(ref->object);
85 } 85 }
86 html("</td></tr>\n"); 86 html("</td></tr>\n");
87 return 0; 87 return 0;
88} 88}
89 89
90static void print_tag_header() 90static void print_tag_header()
91{ 91{
92 html("<tr class='nohover'><th class='left'>Tag</th>" 92 html("<tr class='nohover'><th class='left'>Tag</th>"
93 "<th class='left'>Download</th>" 93 "<th class='left'>Download</th>"
94 "<th class='left'>Author</th>" 94 "<th class='left'>Author</th>"
95 "<th class='left' colspan='2'>Age</th></tr>\n"); 95 "<th class='left' colspan='2'>Age</th></tr>\n");
96 header = 1; 96 header = 1;
97} 97}
98 98
99static void print_tag_downloads(const struct cgit_repo *repo, const char *ref) 99static void print_tag_downloads(const struct cgit_repo *repo, const char *ref)
100{ 100{
101 const struct cgit_snapshot_format* f; 101 const struct cgit_snapshot_format* f;
102 char *filename; 102 char *filename;
103 const char *basename; 103 const char *basename;
104 104
105 if (!ref || strlen(ref) < 2) 105 if (!ref || strlen(ref) < 2)
106 return; 106 return;
107 107
108 basename = cgit_repobasename(repo->url); 108 basename = cgit_repobasename(repo->url);
109 if (prefixcmp(ref, basename) != 0) { 109 if (prefixcmp(ref, basename) != 0) {
110 if ((ref[0] == 'v' || ref[0] == 'V') && isdigit(ref[1])) 110 if ((ref[0] == 'v' || ref[0] == 'V') && isdigit(ref[1]))
111 ref++; 111 ref++;
112 if (isdigit(ref[0])) 112 if (isdigit(ref[0]))
113 ref = xstrdup(fmt("%s-%s", basename, ref)); 113 ref = xstrdup(fmt("%s-%s", basename, ref));
114 } 114 }
115 115
116 for (f = cgit_snapshot_formats; f->suffix; f++) { 116 for (f = cgit_snapshot_formats; f->suffix; f++) {
117 if (!(repo->snapshots & f->bit)) 117 if (!(repo->snapshots & f->bit))
118 continue; 118 continue;
119 filename = fmt("%s%s", ref, f->suffix); 119 filename = fmt("%s%s", ref, f->suffix);
120 cgit_snapshot_link(filename, NULL, NULL, NULL, NULL, filename); 120 cgit_snapshot_link(filename, NULL, NULL, NULL, NULL, filename);
121 html("&nbsp;&nbsp;"); 121 html("&nbsp;&nbsp;");
122 } 122 }
123} 123}
124static int print_tag(struct refinfo *ref) 124static int print_tag(struct refinfo *ref)
125{ 125{
diff --git a/ui-shared.c b/ui-shared.c
index 07d5dd4..de55eff 100644
--- a/ui-shared.c
+++ b/ui-shared.c
@@ -272,163 +272,185 @@ void cgit_tree_link(char *name, char *title, char *class, char *head,
272{ 272{
273 reporevlink("tree", name, title, class, head, rev, path); 273 reporevlink("tree", name, title, class, head, rev, path);
274} 274}
275 275
276void cgit_plain_link(char *name, char *title, char *class, char *head, 276void cgit_plain_link(char *name, char *title, char *class, char *head,
277 char *rev, char *path) 277 char *rev, char *path)
278{ 278{
279 reporevlink("plain", name, title, class, head, rev, path); 279 reporevlink("plain", name, title, class, head, rev, path);
280} 280}
281 281
282void cgit_log_link(char *name, char *title, char *class, char *head, 282void cgit_log_link(char *name, char *title, char *class, char *head,
283 char *rev, char *path, int ofs, char *grep, char *pattern, 283 char *rev, char *path, int ofs, char *grep, char *pattern,
284 int showmsg) 284 int showmsg)
285{ 285{
286 char *delim; 286 char *delim;
287 287
288 delim = repolink(title, class, "log", head, path); 288 delim = repolink(title, class, "log", head, path);
289 if (rev && strcmp(rev, ctx.qry.head)) { 289 if (rev && strcmp(rev, ctx.qry.head)) {
290 html(delim); 290 html(delim);
291 html("id="); 291 html("id=");
292 html_url_arg(rev); 292 html_url_arg(rev);
293 delim = "&"; 293 delim = "&";
294 } 294 }
295 if (grep && pattern) { 295 if (grep && pattern) {
296 html(delim); 296 html(delim);
297 html("qt="); 297 html("qt=");
298 html_url_arg(grep); 298 html_url_arg(grep);
299 delim = "&"; 299 delim = "&";
300 html(delim); 300 html(delim);
301 html("q="); 301 html("q=");
302 html_url_arg(pattern); 302 html_url_arg(pattern);
303 } 303 }
304 if (ofs > 0) { 304 if (ofs > 0) {
305 html(delim); 305 html(delim);
306 html("ofs="); 306 html("ofs=");
307 htmlf("%d", ofs); 307 htmlf("%d", ofs);
308 delim = "&"; 308 delim = "&";
309 } 309 }
310 if (showmsg) { 310 if (showmsg) {
311 html(delim); 311 html(delim);
312 html("showmsg=1"); 312 html("showmsg=1");
313 } 313 }
314 html("'>"); 314 html("'>");
315 html_txt(name); 315 html_txt(name);
316 html("</a>"); 316 html("</a>");
317} 317}
318 318
319void cgit_commit_link(char *name, char *title, char *class, char *head, 319void cgit_commit_link(char *name, char *title, char *class, char *head,
320 char *rev) 320 char *rev, int toggle_ssdiff)
321{ 321{
322 if (strlen(name) > ctx.cfg.max_msg_len && ctx.cfg.max_msg_len >= 15) { 322 if (strlen(name) > ctx.cfg.max_msg_len && ctx.cfg.max_msg_len >= 15) {
323 name[ctx.cfg.max_msg_len] = '\0'; 323 name[ctx.cfg.max_msg_len] = '\0';
324 name[ctx.cfg.max_msg_len - 1] = '.'; 324 name[ctx.cfg.max_msg_len - 1] = '.';
325 name[ctx.cfg.max_msg_len - 2] = '.'; 325 name[ctx.cfg.max_msg_len - 2] = '.';
326 name[ctx.cfg.max_msg_len - 3] = '.'; 326 name[ctx.cfg.max_msg_len - 3] = '.';
327 } 327 }
328 reporevlink("commit", name, title, class, head, rev, NULL); 328
329 char *delim;
330
331 delim = repolink(title, class, "commit", head, NULL);
332 if (rev && strcmp(rev, ctx.qry.head)) {
333 html(delim);
334 html("id=");
335 html_url_arg(rev);
336 delim = "&amp;";
337 }
338 if ((ctx.qry.ssdiff && !toggle_ssdiff) || (!ctx.qry.ssdiff && toggle_ssdiff)) {
339 html(delim);
340 html("ss=1");
341 }
342 html("'>");
343 html_txt(name);
344 html("</a>");
329} 345}
330 346
331void cgit_refs_link(char *name, char *title, char *class, char *head, 347void cgit_refs_link(char *name, char *title, char *class, char *head,
332 char *rev, char *path) 348 char *rev, char *path)
333{ 349{
334 reporevlink("refs", name, title, class, head, rev, path); 350 reporevlink("refs", name, title, class, head, rev, path);
335} 351}
336 352
337void cgit_snapshot_link(char *name, char *title, char *class, char *head, 353void cgit_snapshot_link(char *name, char *title, char *class, char *head,
338 char *rev, char *archivename) 354 char *rev, char *archivename)
339{ 355{
340 reporevlink("snapshot", name, title, class, head, rev, archivename); 356 reporevlink("snapshot", name, title, class, head, rev, archivename);
341} 357}
342 358
343void cgit_diff_link(char *name, char *title, char *class, char *head, 359void cgit_diff_link(char *name, char *title, char *class, char *head,
344 char *new_rev, char *old_rev, char *path) 360 char *new_rev, char *old_rev, char *path,
361 int toggle_ssdiff)
345{ 362{
346 char *delim; 363 char *delim;
347 364
348 delim = repolink(title, class, "diff", head, path); 365 delim = repolink(title, class, "diff", head, path);
349 if (new_rev && strcmp(new_rev, ctx.qry.head)) { 366 if (new_rev && strcmp(new_rev, ctx.qry.head)) {
350 html(delim); 367 html(delim);
351 html("id="); 368 html("id=");
352 html_url_arg(new_rev); 369 html_url_arg(new_rev);
353 delim = "&amp;"; 370 delim = "&amp;";
354 } 371 }
355 if (old_rev) { 372 if (old_rev) {
356 html(delim); 373 html(delim);
357 html("id2="); 374 html("id2=");
358 html_url_arg(old_rev); 375 html_url_arg(old_rev);
376 delim = "&amp;";
377 }
378 if ((ctx.qry.ssdiff && !toggle_ssdiff) || (!ctx.qry.ssdiff && toggle_ssdiff)) {
379 html(delim);
380 html("ss=1");
359 } 381 }
360 html("'>"); 382 html("'>");
361 html_txt(name); 383 html_txt(name);
362 html("</a>"); 384 html("</a>");
363} 385}
364 386
365void cgit_patch_link(char *name, char *title, char *class, char *head, 387void cgit_patch_link(char *name, char *title, char *class, char *head,
366 char *rev) 388 char *rev)
367{ 389{
368 reporevlink("patch", name, title, class, head, rev, NULL); 390 reporevlink("patch", name, title, class, head, rev, NULL);
369} 391}
370 392
371void cgit_stats_link(char *name, char *title, char *class, char *head, 393void cgit_stats_link(char *name, char *title, char *class, char *head,
372 char *path) 394 char *path)
373{ 395{
374 reporevlink("stats", name, title, class, head, NULL, path); 396 reporevlink("stats", name, title, class, head, NULL, path);
375} 397}
376 398
377void cgit_object_link(struct object *obj) 399void cgit_object_link(struct object *obj)
378{ 400{
379 char *page, *shortrev, *fullrev, *name; 401 char *page, *shortrev, *fullrev, *name;
380 402
381 fullrev = sha1_to_hex(obj->sha1); 403 fullrev = sha1_to_hex(obj->sha1);
382 shortrev = xstrdup(fullrev); 404 shortrev = xstrdup(fullrev);
383 shortrev[10] = '\0'; 405 shortrev[10] = '\0';
384 if (obj->type == OBJ_COMMIT) { 406 if (obj->type == OBJ_COMMIT) {
385 cgit_commit_link(fmt("commit %s...", shortrev), NULL, NULL, 407 cgit_commit_link(fmt("commit %s...", shortrev), NULL, NULL,
386 ctx.qry.head, fullrev); 408 ctx.qry.head, fullrev, 0);
387 return; 409 return;
388 } else if (obj->type == OBJ_TREE) 410 } else if (obj->type == OBJ_TREE)
389 page = "tree"; 411 page = "tree";
390 else if (obj->type == OBJ_TAG) 412 else if (obj->type == OBJ_TAG)
391 page = "tag"; 413 page = "tag";
392 else 414 else
393 page = "blob"; 415 page = "blob";
394 name = fmt("%s %s...", typename(obj->type), shortrev); 416 name = fmt("%s %s...", typename(obj->type), shortrev);
395 reporevlink(page, name, NULL, NULL, ctx.qry.head, fullrev, NULL); 417 reporevlink(page, name, NULL, NULL, ctx.qry.head, fullrev, NULL);
396} 418}
397 419
398void cgit_print_date(time_t secs, char *format, int local_time) 420void cgit_print_date(time_t secs, char *format, int local_time)
399{ 421{
400 char buf[64]; 422 char buf[64];
401 struct tm *time; 423 struct tm *time;
402 424
403 if (!secs) 425 if (!secs)
404 return; 426 return;
405 if(local_time) 427 if(local_time)
406 time = localtime(&secs); 428 time = localtime(&secs);
407 else 429 else
408 time = gmtime(&secs); 430 time = gmtime(&secs);
409 strftime(buf, sizeof(buf)-1, format, time); 431 strftime(buf, sizeof(buf)-1, format, time);
410 html_txt(buf); 432 html_txt(buf);
411} 433}
412 434
413void cgit_print_age(time_t t, time_t max_relative, char *format) 435void cgit_print_age(time_t t, time_t max_relative, char *format)
414{ 436{
415 time_t now, secs; 437 time_t now, secs;
416 438
417 if (!t) 439 if (!t)
418 return; 440 return;
419 time(&now); 441 time(&now);
420 secs = now - t; 442 secs = now - t;
421 443
422 if (secs > max_relative && max_relative >= 0) { 444 if (secs > max_relative && max_relative >= 0) {
423 cgit_print_date(t, format, ctx.cfg.local_time); 445 cgit_print_date(t, format, ctx.cfg.local_time);
424 return; 446 return;
425 } 447 }
426 448
427 if (secs < TM_HOUR * 2) { 449 if (secs < TM_HOUR * 2) {
428 htmlf("<span class='age-mins'>%.0f min.</span>", 450 htmlf("<span class='age-mins'>%.0f min.</span>",
429 secs * 1.0 / TM_MIN); 451 secs * 1.0 / TM_MIN);
430 return; 452 return;
431 } 453 }
432 if (secs < TM_DAY * 2) { 454 if (secs < TM_DAY * 2) {
433 htmlf("<span class='age-hours'>%.0f hours</span>", 455 htmlf("<span class='age-hours'>%.0f hours</span>",
434 secs * 1.0 / TM_HOUR); 456 secs * 1.0 / TM_HOUR);
@@ -650,99 +672,99 @@ static void print_header(struct cgit_context *ctx)
650 html("</td><td class='form'>"); 672 html("</td><td class='form'>");
651 html("<form method='get' action=''>\n"); 673 html("<form method='get' action=''>\n");
652 cgit_add_hidden_formfields(0, 1, ctx->qry.page); 674 cgit_add_hidden_formfields(0, 1, ctx->qry.page);
653 html("<select name='h' onchange='this.form.submit();'>\n"); 675 html("<select name='h' onchange='this.form.submit();'>\n");
654 for_each_branch_ref(print_branch_option, ctx->qry.head); 676 for_each_branch_ref(print_branch_option, ctx->qry.head);
655 html("</select> "); 677 html("</select> ");
656 html("<input type='submit' name='' value='switch'/>"); 678 html("<input type='submit' name='' value='switch'/>");
657 html("</form>"); 679 html("</form>");
658 } else 680 } else
659 html_txt(ctx->cfg.root_title); 681 html_txt(ctx->cfg.root_title);
660 html("</td></tr>\n"); 682 html("</td></tr>\n");
661 683
662 html("<tr><td class='sub'>"); 684 html("<tr><td class='sub'>");
663 if (ctx->repo) { 685 if (ctx->repo) {
664 html_txt(ctx->repo->desc); 686 html_txt(ctx->repo->desc);
665 html("</td><td class='sub right'>"); 687 html("</td><td class='sub right'>");
666 html_txt(ctx->repo->owner); 688 html_txt(ctx->repo->owner);
667 } else { 689 } else {
668 if (ctx->cfg.root_desc) 690 if (ctx->cfg.root_desc)
669 html_txt(ctx->cfg.root_desc); 691 html_txt(ctx->cfg.root_desc);
670 else if (ctx->cfg.index_info) 692 else if (ctx->cfg.index_info)
671 html_include(ctx->cfg.index_info); 693 html_include(ctx->cfg.index_info);
672 } 694 }
673 html("</td></tr></table>\n"); 695 html("</td></tr></table>\n");
674} 696}
675 697
676void cgit_print_pageheader(struct cgit_context *ctx) 698void cgit_print_pageheader(struct cgit_context *ctx)
677{ 699{
678 struct cgit_cmd *cmd = cgit_get_cmd(ctx); 700 struct cgit_cmd *cmd = cgit_get_cmd(ctx);
679 701
680 if (!cmd && ctx->repo) 702 if (!cmd && ctx->repo)
681 fallback_cmd = "summary"; 703 fallback_cmd = "summary";
682 704
683 html("<div id='cgit'>"); 705 html("<div id='cgit'>");
684 if (!ctx->cfg.noheader) 706 if (!ctx->cfg.noheader)
685 print_header(ctx); 707 print_header(ctx);
686 708
687 html("<table class='tabs'><tr><td>\n"); 709 html("<table class='tabs'><tr><td>\n");
688 if (ctx->repo) { 710 if (ctx->repo) {
689 cgit_summary_link("summary", NULL, hc(cmd, "summary"), 711 cgit_summary_link("summary", NULL, hc(cmd, "summary"),
690 ctx->qry.head); 712 ctx->qry.head);
691 cgit_refs_link("refs", NULL, hc(cmd, "refs"), ctx->qry.head, 713 cgit_refs_link("refs", NULL, hc(cmd, "refs"), ctx->qry.head,
692 ctx->qry.sha1, NULL); 714 ctx->qry.sha1, NULL);
693 cgit_log_link("log", NULL, hc(cmd, "log"), ctx->qry.head, 715 cgit_log_link("log", NULL, hc(cmd, "log"), ctx->qry.head,
694 NULL, NULL, 0, NULL, NULL, ctx->qry.showmsg); 716 NULL, NULL, 0, NULL, NULL, ctx->qry.showmsg);
695 cgit_tree_link("tree", NULL, hc(cmd, "tree"), ctx->qry.head, 717 cgit_tree_link("tree", NULL, hc(cmd, "tree"), ctx->qry.head,
696 ctx->qry.sha1, NULL); 718 ctx->qry.sha1, NULL);
697 cgit_commit_link("commit", NULL, hc(cmd, "commit"), 719 cgit_commit_link("commit", NULL, hc(cmd, "commit"),
698 ctx->qry.head, ctx->qry.sha1); 720 ctx->qry.head, ctx->qry.sha1, 0);
699 cgit_diff_link("diff", NULL, hc(cmd, "diff"), ctx->qry.head, 721 cgit_diff_link("diff", NULL, hc(cmd, "diff"), ctx->qry.head,
700 ctx->qry.sha1, ctx->qry.sha2, NULL); 722 ctx->qry.sha1, ctx->qry.sha2, NULL, 0);
701 if (ctx->repo->max_stats) 723 if (ctx->repo->max_stats)
702 cgit_stats_link("stats", NULL, hc(cmd, "stats"), 724 cgit_stats_link("stats", NULL, hc(cmd, "stats"),
703 ctx->qry.head, NULL); 725 ctx->qry.head, NULL);
704 if (ctx->repo->readme) 726 if (ctx->repo->readme)
705 reporevlink("about", "about", NULL, 727 reporevlink("about", "about", NULL,
706 hc(cmd, "about"), ctx->qry.head, NULL, 728 hc(cmd, "about"), ctx->qry.head, NULL,
707 NULL); 729 NULL);
708 html("</td><td class='form'>"); 730 html("</td><td class='form'>");
709 html("<form class='right' method='get' action='"); 731 html("<form class='right' method='get' action='");
710 if (ctx->cfg.virtual_root) 732 if (ctx->cfg.virtual_root)
711 html_url_path(cgit_fileurl(ctx->qry.repo, "log", 733 html_url_path(cgit_fileurl(ctx->qry.repo, "log",
712 ctx->qry.path, NULL)); 734 ctx->qry.path, NULL));
713 html("'>\n"); 735 html("'>\n");
714 cgit_add_hidden_formfields(1, 0, "log"); 736 cgit_add_hidden_formfields(1, 0, "log");
715 html("<select name='qt'>\n"); 737 html("<select name='qt'>\n");
716 html_option("grep", "log msg", ctx->qry.grep); 738 html_option("grep", "log msg", ctx->qry.grep);
717 html_option("author", "author", ctx->qry.grep); 739 html_option("author", "author", ctx->qry.grep);
718 html_option("committer", "committer", ctx->qry.grep); 740 html_option("committer", "committer", ctx->qry.grep);
719 html("</select>\n"); 741 html("</select>\n");
720 html("<input class='txt' type='text' size='10' name='q' value='"); 742 html("<input class='txt' type='text' size='10' name='q' value='");
721 html_attr(ctx->qry.search); 743 html_attr(ctx->qry.search);
722 html("'/>\n"); 744 html("'/>\n");
723 html("<input type='submit' value='search'/>\n"); 745 html("<input type='submit' value='search'/>\n");
724 html("</form>\n"); 746 html("</form>\n");
725 } else { 747 } else {
726 site_link(NULL, "index", NULL, hc(cmd, "repolist"), NULL, 0); 748 site_link(NULL, "index", NULL, hc(cmd, "repolist"), NULL, 0);
727 if (ctx->cfg.root_readme) 749 if (ctx->cfg.root_readme)
728 site_link("about", "about", NULL, hc(cmd, "about"), 750 site_link("about", "about", NULL, hc(cmd, "about"),
729 NULL, 0); 751 NULL, 0);
730 html("</td><td class='form'>"); 752 html("</td><td class='form'>");
731 html("<form method='get' action='"); 753 html("<form method='get' action='");
732 html_attr(cgit_rooturl()); 754 html_attr(cgit_rooturl());
733 html("'>\n"); 755 html("'>\n");
734 html("<input type='text' name='q' size='10' value='"); 756 html("<input type='text' name='q' size='10' value='");
735 html_attr(ctx->qry.search); 757 html_attr(ctx->qry.search);
736 html("'/>\n"); 758 html("'/>\n");
737 html("<input type='submit' value='search'/>\n"); 759 html("<input type='submit' value='search'/>\n");
738 html("</form>"); 760 html("</form>");
739 } 761 }
740 html("</td></tr></table>\n"); 762 html("</td></tr></table>\n");
741 html("<div class='content'>"); 763 html("<div class='content'>");
742} 764}
743 765
744void cgit_print_filemode(unsigned short mode) 766void cgit_print_filemode(unsigned short mode)
745{ 767{
746 if (S_ISDIR(mode)) 768 if (S_ISDIR(mode))
747 html("d"); 769 html("d");
748 else if (S_ISLNK(mode)) 770 else if (S_ISLNK(mode))
diff --git a/ui-shared.h b/ui-shared.h
index bff4826..166246d 100644
--- a/ui-shared.h
+++ b/ui-shared.h
@@ -1,50 +1,51 @@
1#ifndef UI_SHARED_H 1#ifndef UI_SHARED_H
2#define UI_SHARED_H 2#define UI_SHARED_H
3 3
4extern char *cgit_httpscheme(); 4extern char *cgit_httpscheme();
5extern char *cgit_hosturl(); 5extern char *cgit_hosturl();
6extern char *cgit_repourl(const char *reponame); 6extern char *cgit_repourl(const char *reponame);
7extern char *cgit_fileurl(const char *reponame, const char *pagename, 7extern char *cgit_fileurl(const char *reponame, const char *pagename,
8 const char *filename, const char *query); 8 const char *filename, const char *query);
9extern char *cgit_pageurl(const char *reponame, const char *pagename, 9extern char *cgit_pageurl(const char *reponame, const char *pagename,
10 const char *query); 10 const char *query);
11 11
12extern void cgit_index_link(char *name, char *title, char *class, 12extern void cgit_index_link(char *name, char *title, char *class,
13 char *pattern, int ofs); 13 char *pattern, int ofs);
14extern void cgit_summary_link(char *name, char *title, char *class, char *head); 14extern void cgit_summary_link(char *name, char *title, char *class, char *head);
15extern void cgit_tag_link(char *name, char *title, char *class, char *head, 15extern void cgit_tag_link(char *name, char *title, char *class, char *head,
16 char *rev); 16 char *rev);
17extern void cgit_tree_link(char *name, char *title, char *class, char *head, 17extern void cgit_tree_link(char *name, char *title, char *class, char *head,
18 char *rev, char *path); 18 char *rev, char *path);
19extern void cgit_plain_link(char *name, char *title, char *class, char *head, 19extern void cgit_plain_link(char *name, char *title, char *class, char *head,
20 char *rev, char *path); 20 char *rev, char *path);
21extern void cgit_log_link(char *name, char *title, char *class, char *head, 21extern void cgit_log_link(char *name, char *title, char *class, char *head,
22 char *rev, char *path, int ofs, char *grep, 22 char *rev, char *path, int ofs, char *grep,
23 char *pattern, int showmsg); 23 char *pattern, int showmsg);
24extern void cgit_commit_link(char *name, char *title, char *class, char *head, 24extern void cgit_commit_link(char *name, char *title, char *class, char *head,
25 char *rev); 25 char *rev, int toggle_ssdiff);
26extern void cgit_patch_link(char *name, char *title, char *class, char *head, 26extern void cgit_patch_link(char *name, char *title, char *class, char *head,
27 char *rev); 27 char *rev);
28extern void cgit_refs_link(char *name, char *title, char *class, char *head, 28extern void cgit_refs_link(char *name, char *title, char *class, char *head,
29 char *rev, char *path); 29 char *rev, char *path);
30extern void cgit_snapshot_link(char *name, char *title, char *class, 30extern void cgit_snapshot_link(char *name, char *title, char *class,
31 char *head, char *rev, char *archivename); 31 char *head, char *rev, char *archivename);
32extern void cgit_diff_link(char *name, char *title, char *class, char *head, 32extern void cgit_diff_link(char *name, char *title, char *class, char *head,
33 char *new_rev, char *old_rev, char *path); 33 char *new_rev, char *old_rev, char *path,
34 int toggle_ssdiff);
34extern void cgit_stats_link(char *name, char *title, char *class, char *head, 35extern void cgit_stats_link(char *name, char *title, char *class, char *head,
35 char *path); 36 char *path);
36extern void cgit_object_link(struct object *obj); 37extern void cgit_object_link(struct object *obj);
37 38
38extern void cgit_print_error(char *msg); 39extern void cgit_print_error(char *msg);
39extern void cgit_print_date(time_t secs, char *format, int local_time); 40extern void cgit_print_date(time_t secs, char *format, int local_time);
40extern void cgit_print_age(time_t t, time_t max_relative, char *format); 41extern void cgit_print_age(time_t t, time_t max_relative, char *format);
41extern void cgit_print_http_headers(struct cgit_context *ctx); 42extern void cgit_print_http_headers(struct cgit_context *ctx);
42extern void cgit_print_docstart(struct cgit_context *ctx); 43extern void cgit_print_docstart(struct cgit_context *ctx);
43extern void cgit_print_docend(); 44extern void cgit_print_docend();
44extern void cgit_print_pageheader(struct cgit_context *ctx); 45extern void cgit_print_pageheader(struct cgit_context *ctx);
45extern void cgit_print_filemode(unsigned short mode); 46extern void cgit_print_filemode(unsigned short mode);
46extern void cgit_print_snapshot_links(const char *repo, const char *head, 47extern void cgit_print_snapshot_links(const char *repo, const char *head,
47 const char *hex, int snapshots); 48 const char *hex, int snapshots);
48extern void cgit_add_hidden_formfields(int incl_head, int incl_search, 49extern void cgit_add_hidden_formfields(int incl_head, int incl_search,
49 char *page); 50 char *page);
50#endif /* UI_SHARED_H */ 51#endif /* UI_SHARED_H */