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
@@ -153,161 +153,166 @@ void config_cb(const char *name, const char *value)
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
diff --git a/cgit.h b/cgit.h
index 6c6c460..b7b0adb 100644
--- a/cgit.h
+++ b/cgit.h
@@ -114,115 +114,117 @@ struct refinfo {
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
diff --git a/cgitrc.5.txt b/cgitrc.5.txt
index 4dc383d..252d546 100644
--- a/cgitrc.5.txt
+++ b/cgitrc.5.txt
@@ -209,64 +209,68 @@ renamelimit::
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.
diff --git a/ui-commit.c b/ui-commit.c
index f5b0ae5..b5e3c01 100644
--- a/ui-commit.c
+++ b/ui-commit.c
@@ -29,88 +29,93 @@ void cgit_print_commit(char *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 }
diff --git a/ui-diff.c b/ui-diff.c
index 0c6f8d7..42e81ac 100644
--- a/ui-diff.c
+++ b/ui-diff.c
@@ -56,65 +56,65 @@ static void print_fileinfo(struct fileinfo *info)
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
@@ -131,65 +131,65 @@ static void inspect_filepair(struct diff_filepair *pair)
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);
@@ -221,104 +221,122 @@ static void header(unsigned char *sha1, char *path1, int mode1,
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
@@ -37,88 +37,88 @@ void inspect_files(struct diff_filepair *pair)
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{
diff --git a/ui-refs.c b/ui-refs.c
index d3b4f6e..33d9bec 100644
--- a/ui-refs.c
+++ b/ui-refs.c
@@ -45,65 +45,65 @@ static int cmp_branch_age(const void *a, const void *b)
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) {
diff --git a/ui-shared.c b/ui-shared.c
index 07d5dd4..de55eff 100644
--- a/ui-shared.c
+++ b/ui-shared.c
@@ -288,131 +288,153 @@ void cgit_log_link(char *name, char *title, char *class, char *head,
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;
@@ -666,67 +688,67 @@ static void print_header(struct cgit_context *ctx)
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());
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 */