aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--cgit.c4
-rw-r--r--cgit.h2
-rw-r--r--cgitrc.5.txt4
-rw-r--r--ui-diff.c35
-rw-r--r--ui-log.c131
-rw-r--r--ui-refs.c2
-rw-r--r--ui-repolist.c2
-rw-r--r--ui-shared.c28
-rw-r--r--ui-shared.h2
-rw-r--r--ui-tree.c2
10 files changed, 194 insertions, 18 deletions
diff --git a/cgit.c b/cgit.c
index baad1c8..d84b4be 100644
--- a/cgit.c
+++ b/cgit.c
@@ -152,6 +152,8 @@ static void config_cb(const char *name, const char *value)
152 ctx.cfg.snapshots = cgit_parse_snapshots_mask(value); 152 ctx.cfg.snapshots = cgit_parse_snapshots_mask(value);
153 else if (!strcmp(name, "enable-filter-overrides")) 153 else if (!strcmp(name, "enable-filter-overrides"))
154 ctx.cfg.enable_filter_overrides = atoi(value); 154 ctx.cfg.enable_filter_overrides = atoi(value);
155 else if (!strcmp(name, "enable-follow-links"))
156 ctx.cfg.enable_follow_links = atoi(value);
155 else if (!strcmp(name, "enable-http-clone")) 157 else if (!strcmp(name, "enable-http-clone"))
156 ctx.cfg.enable_http_clone = atoi(value); 158 ctx.cfg.enable_http_clone = atoi(value);
157 else if (!strcmp(name, "enable-index-links")) 159 else if (!strcmp(name, "enable-index-links"))
@@ -333,6 +335,8 @@ static void querystring_cb(const char *name, const char *value)
333 ctx.qry.context = atoi(value); 335 ctx.qry.context = atoi(value);
334 } else if (!strcmp(name, "ignorews")) { 336 } else if (!strcmp(name, "ignorews")) {
335 ctx.qry.ignorews = atoi(value); 337 ctx.qry.ignorews = atoi(value);
338 } else if (!strcmp(name, "follow")) {
339 ctx.qry.follow = atoi(value);
336 } 340 }
337} 341}
338 342
diff --git a/cgit.h b/cgit.h
index b2253d2..3120562 100644
--- a/cgit.h
+++ b/cgit.h
@@ -179,6 +179,7 @@ struct cgit_query {
179 int show_all; 179 int show_all;
180 int context; 180 int context;
181 int ignorews; 181 int ignorews;
182 int follow;
182 char *vpath; 183 char *vpath;
183}; 184};
184 185
@@ -221,6 +222,7 @@ struct cgit_config {
221 int case_sensitive_sort; 222 int case_sensitive_sort;
222 int embedded; 223 int embedded;
223 int enable_filter_overrides; 224 int enable_filter_overrides;
225 int enable_follow_links;
224 int enable_http_clone; 226 int enable_http_clone;
225 int enable_index_links; 227 int enable_index_links;
226 int enable_index_owner; 228 int enable_index_owner;
diff --git a/cgitrc.5.txt b/cgitrc.5.txt
index e21ece9..759f353 100644
--- a/cgitrc.5.txt
+++ b/cgitrc.5.txt
@@ -150,6 +150,10 @@ enable-filter-overrides::
150 Flag which, when set to "1", allows all filter settings to be 150 Flag which, when set to "1", allows all filter settings to be
151 overridden in repository-specific cgitrc files. Default value: none. 151 overridden in repository-specific cgitrc files. Default value: none.
152 152
153enable-follow-links::
154 Flag which, when set to "1", allows users to follow a file in the log
155 view. Default value: "0".
156
153enable-http-clone:: 157enable-http-clone::
154 If set to "1", cgit will act as an dumb HTTP endpoint for git clones. 158 If set to "1", cgit will act as an dumb HTTP endpoint for git clones.
155 You can add "http://$HTTP_HOST$SCRIPT_NAME/$CGIT_REPO_URL" to clone-url 159 You can add "http://$HTTP_HOST$SCRIPT_NAME/$CGIT_REPO_URL" to clone-url
diff --git a/ui-diff.c b/ui-diff.c
index 1cf2ce0..caebd5d 100644
--- a/ui-diff.c
+++ b/ui-diff.c
@@ -36,6 +36,7 @@ static struct fileinfo {
36 36
37static int use_ssdiff = 0; 37static int use_ssdiff = 0;
38static struct diff_filepair *current_filepair; 38static struct diff_filepair *current_filepair;
39static const char *current_prefix;
39 40
40struct diff_filespec *cgit_get_current_old_file(void) 41struct diff_filespec *cgit_get_current_old_file(void)
41{ 42{
@@ -132,11 +133,30 @@ static void count_diff_lines(char *line, int len)
132 } 133 }
133} 134}
134 135
136static int show_filepair(struct diff_filepair *pair)
137{
138 /* Always show if we have no limiting prefix. */
139 if (!current_prefix)
140 return 1;
141
142 /* Show if either path in the pair begins with the prefix. */
143 if (starts_with(pair->one->path, current_prefix) ||
144 starts_with(pair->two->path, current_prefix))
145 return 1;
146
147 /* Otherwise we don't want to show this filepair. */
148 return 0;
149}
150
135static void inspect_filepair(struct diff_filepair *pair) 151static void inspect_filepair(struct diff_filepair *pair)
136{ 152{
137 int binary = 0; 153 int binary = 0;
138 unsigned long old_size = 0; 154 unsigned long old_size = 0;
139 unsigned long new_size = 0; 155 unsigned long new_size = 0;
156
157 if (!show_filepair(pair))
158 return;
159
140 files++; 160 files++;
141 lines_added = 0; 161 lines_added = 0;
142 lines_removed = 0; 162 lines_removed = 0;
@@ -279,6 +299,9 @@ static void filepair_cb(struct diff_filepair *pair)
279 int binary = 0; 299 int binary = 0;
280 linediff_fn print_line_fn = print_line; 300 linediff_fn print_line_fn = print_line;
281 301
302 if (!show_filepair(pair))
303 return;
304
282 current_filepair = pair; 305 current_filepair = pair;
283 if (use_ssdiff) { 306 if (use_ssdiff) {
284 cgit_ssdiff_header_begin(); 307 cgit_ssdiff_header_begin();
@@ -365,6 +388,18 @@ void cgit_print_diff(const char *new_rev, const char *old_rev,
365 const unsigned char *old_tree_sha1, *new_tree_sha1; 388 const unsigned char *old_tree_sha1, *new_tree_sha1;
366 diff_type difftype; 389 diff_type difftype;
367 390
391 /*
392 * If "follow" is set then the diff machinery needs to examine the
393 * entire commit to detect renames so we must limit the paths in our
394 * own callbacks and not pass the prefix to the diff machinery.
395 */
396 if (ctx.qry.follow && ctx.cfg.enable_follow_links) {
397 current_prefix = prefix;
398 prefix = "";
399 } else {
400 current_prefix = NULL;
401 }
402
368 if (!new_rev) 403 if (!new_rev)
369 new_rev = ctx.qry.head; 404 new_rev = ctx.qry.head;
370 if (get_sha1(new_rev, new_rev_sha1)) { 405 if (get_sha1(new_rev, new_rev_sha1)) {
diff --git a/ui-log.c b/ui-log.c
index 8028b27..ff832ce 100644
--- a/ui-log.c
+++ b/ui-log.c
@@ -12,7 +12,7 @@
12#include "ui-shared.h" 12#include "ui-shared.h"
13#include "argv-array.h" 13#include "argv-array.h"
14 14
15static int files, add_lines, rem_lines; 15static int files, add_lines, rem_lines, lines_counted;
16 16
17/* 17/*
18 * The list of available column colors in the commit graph. 18 * The list of available column colors in the commit graph.
@@ -67,7 +67,7 @@ void show_commit_decorations(struct commit *commit)
67 strncpy(buf, deco->name + 11, sizeof(buf) - 1); 67 strncpy(buf, deco->name + 11, sizeof(buf) - 1);
68 cgit_log_link(buf, NULL, "branch-deco", buf, NULL, 68 cgit_log_link(buf, NULL, "branch-deco", buf, NULL,
69 ctx.qry.vpath, 0, NULL, NULL, 69 ctx.qry.vpath, 0, NULL, NULL,
70 ctx.qry.showmsg); 70 ctx.qry.showmsg, 0);
71 } 71 }
72 else if (starts_with(deco->name, "tag: refs/tags/")) { 72 else if (starts_with(deco->name, "tag: refs/tags/")) {
73 strncpy(buf, deco->name + 15, sizeof(buf) - 1); 73 strncpy(buf, deco->name + 15, sizeof(buf) - 1);
@@ -84,7 +84,7 @@ void show_commit_decorations(struct commit *commit)
84 cgit_log_link(buf, NULL, "remote-deco", NULL, 84 cgit_log_link(buf, NULL, "remote-deco", NULL,
85 sha1_to_hex(commit->object.sha1), 85 sha1_to_hex(commit->object.sha1),
86 ctx.qry.vpath, 0, NULL, NULL, 86 ctx.qry.vpath, 0, NULL, NULL,
87 ctx.qry.showmsg); 87 ctx.qry.showmsg, 0);
88 } 88 }
89 else { 89 else {
90 strncpy(buf, deco->name, sizeof(buf) - 1); 90 strncpy(buf, deco->name, sizeof(buf) - 1);
@@ -98,6 +98,74 @@ next:
98 html("</span>"); 98 html("</span>");
99} 99}
100 100
101static void handle_rename(struct diff_filepair *pair)
102{
103 /*
104 * After we have seen a rename, we generate links to the previous
105 * name of the file so that commit & diff views get fed the path
106 * that is correct for the commit they are showing, avoiding the
107 * need to walk the entire history leading back to every commit we
108 * show in order detect renames.
109 */
110 if (0 != strcmp(ctx.qry.vpath, pair->two->path)) {
111 free(ctx.qry.vpath);
112 ctx.qry.vpath = xstrdup(pair->two->path);
113 }
114 inspect_files(pair);
115}
116
117static int show_commit(struct commit *commit, struct rev_info *revs)
118{
119 struct commit_list *parents = commit->parents;
120 struct commit *parent;
121 int found = 0, saved_fmt;
122 unsigned saved_flags = revs->diffopt.flags;
123
124
125 /* Always show if we're not in "follow" mode with a single file. */
126 if (!ctx.qry.follow)
127 return 1;
128
129 /*
130 * In "follow" mode, we don't show merges. This is consistent with
131 * "git log --follow -- <file>".
132 */
133 if (parents && parents->next)
134 return 0;
135
136 /*
137 * If this is the root commit, do what rev_info tells us.
138 */
139 if (!parents)
140 return revs->show_root_diff;
141
142 /* When we get here we have precisely one parent. */
143 parent = parents->item;
144 parse_commit(parent);
145
146 files = 0;
147 add_lines = 0;
148 rem_lines = 0;
149
150 DIFF_OPT_SET(&revs->diffopt, RECURSIVE);
151 diff_tree_sha1(parent->tree->object.sha1,
152 commit->tree->object.sha1,
153 "", &revs->diffopt);
154 diffcore_std(&revs->diffopt);
155
156 found = !diff_queue_is_empty();
157 saved_fmt = revs->diffopt.output_format;
158 revs->diffopt.output_format = DIFF_FORMAT_CALLBACK;
159 revs->diffopt.format_callback = cgit_diff_tree_cb;
160 revs->diffopt.format_callback_data = handle_rename;
161 diff_flush(&revs->diffopt);
162 revs->diffopt.output_format = saved_fmt;
163 revs->diffopt.flags = saved_flags;
164
165 lines_counted = 1;
166 return found;
167}
168
101static void print_commit(struct commit *commit, struct rev_info *revs) 169static void print_commit(struct commit *commit, struct rev_info *revs)
102{ 170{
103 struct commitinfo *info; 171 struct commitinfo *info;
@@ -177,7 +245,8 @@ static void print_commit(struct commit *commit, struct rev_info *revs)
177 cgit_print_age(commit->date, TM_WEEK * 2, FMT_SHORTDATE); 245 cgit_print_age(commit->date, TM_WEEK * 2, FMT_SHORTDATE);
178 } 246 }
179 247
180 if (ctx.repo->enable_log_filecount || ctx.repo->enable_log_linecount) { 248 if (!lines_counted && (ctx.repo->enable_log_filecount ||
249 ctx.repo->enable_log_linecount)) {
181 files = 0; 250 files = 0;
182 add_lines = 0; 251 add_lines = 0;
183 rem_lines = 0; 252 rem_lines = 0;
@@ -325,7 +394,17 @@ void cgit_print_log(const char *tip, int ofs, int cnt, char *grep, char *pattern
325 } 394 }
326 } 395 }
327 } 396 }
328 if (commit_graph) { 397
398 if (!path || !ctx.cfg.enable_follow_links) {
399 /*
400 * If we don't have a path, "follow" is a no-op so make sure
401 * the variable is set to false to avoid needing to check
402 * both this and whether we have a path everywhere.
403 */
404 ctx.qry.follow = 0;
405 }
406
407 if (commit_graph && !ctx.qry.follow) {
329 argv_array_push(&rev_argv, "--graph"); 408 argv_array_push(&rev_argv, "--graph");
330 argv_array_push(&rev_argv, "--color"); 409 argv_array_push(&rev_argv, "--color");
331 graph_set_column_colors(column_colors_html, 410 graph_set_column_colors(column_colors_html,
@@ -337,6 +416,8 @@ void cgit_print_log(const char *tip, int ofs, int cnt, char *grep, char *pattern
337 else if (commit_sort == 2) 416 else if (commit_sort == 2)
338 argv_array_push(&rev_argv, "--topo-order"); 417 argv_array_push(&rev_argv, "--topo-order");
339 418
419 if (path && ctx.qry.follow)
420 argv_array_push(&rev_argv, "--follow");
340 argv_array_push(&rev_argv, "--"); 421 argv_array_push(&rev_argv, "--");
341 if (path) 422 if (path)
342 argv_array_push(&rev_argv, path); 423 argv_array_push(&rev_argv, path);
@@ -347,10 +428,17 @@ void cgit_print_log(const char *tip, int ofs, int cnt, char *grep, char *pattern
347 rev.verbose_header = 1; 428 rev.verbose_header = 1;
348 rev.show_root_diff = 0; 429 rev.show_root_diff = 0;
349 rev.ignore_missing = 1; 430 rev.ignore_missing = 1;
431 rev.simplify_history = 1;
350 setup_revisions(rev_argv.argc, rev_argv.argv, &rev, NULL); 432 setup_revisions(rev_argv.argc, rev_argv.argv, &rev, NULL);
351 load_ref_decorations(DECORATE_FULL_REFS); 433 load_ref_decorations(DECORATE_FULL_REFS);
352 rev.show_decorations = 1; 434 rev.show_decorations = 1;
353 rev.grep_filter.regflags |= REG_ICASE; 435 rev.grep_filter.regflags |= REG_ICASE;
436
437 rev.diffopt.detect_rename = 1;
438 rev.diffopt.rename_limit = ctx.cfg.renamelimit;
439 if (ctx.qry.ignorews)
440 DIFF_XDL_SET(&rev.diffopt, IGNORE_WHITESPACE);
441
354 compile_grep_patterns(&rev.grep_filter); 442 compile_grep_patterns(&rev.grep_filter);
355 prepare_revision_walk(&rev); 443 prepare_revision_walk(&rev);
356 444
@@ -368,11 +456,12 @@ void cgit_print_log(const char *tip, int ofs, int cnt, char *grep, char *pattern
368 cgit_log_link(ctx.qry.showmsg ? "Collapse" : "Expand", NULL, 456 cgit_log_link(ctx.qry.showmsg ? "Collapse" : "Expand", NULL,
369 NULL, ctx.qry.head, ctx.qry.sha1, 457 NULL, ctx.qry.head, ctx.qry.sha1,
370 ctx.qry.vpath, ctx.qry.ofs, ctx.qry.grep, 458 ctx.qry.vpath, ctx.qry.ofs, ctx.qry.grep,
371 ctx.qry.search, ctx.qry.showmsg ? 0 : 1); 459 ctx.qry.search, ctx.qry.showmsg ? 0 : 1,
460 ctx.qry.follow);
372 html(")"); 461 html(")");
373 } 462 }
374 html("</th><th class='left'>Author</th>"); 463 html("</th><th class='left'>Author</th>");
375 if (commit_graph) 464 if (rev.graph)
376 html("<th class='left'>Age</th>"); 465 html("<th class='left'>Age</th>");
377 if (ctx.repo->enable_log_filecount) { 466 if (ctx.repo->enable_log_filecount) {
378 html("<th class='left'>Files</th>"); 467 html("<th class='left'>Files</th>");
@@ -388,13 +477,30 @@ void cgit_print_log(const char *tip, int ofs, int cnt, char *grep, char *pattern
388 ofs = 0; 477 ofs = 0;
389 478
390 for (i = 0; i < ofs && (commit = get_revision(&rev)) != NULL; i++) { 479 for (i = 0; i < ofs && (commit = get_revision(&rev)) != NULL; i++) {
480 if (show_commit(commit, &rev))
481 i++;
391 free_commit_buffer(commit); 482 free_commit_buffer(commit);
392 free_commit_list(commit->parents); 483 free_commit_list(commit->parents);
393 commit->parents = NULL; 484 commit->parents = NULL;
394 } 485 }
395 486
396 for (i = 0; i < cnt && (commit = get_revision(&rev)) != NULL; i++) { 487 for (i = 0; i < cnt && (commit = get_revision(&rev)) != NULL; i++) {
397 print_commit(commit, &rev); 488 /*
489 * In "follow" mode, we must count the files and lines the
490 * first time we invoke diff on a given commit, and we need
491 * to do that to see if the commit touches the path we care
492 * about, so we do it in show_commit. Hence we must clear
493 * lines_counted here.
494 *
495 * This has the side effect of avoiding running diff twice
496 * when we are both following renames and showing file
497 * and/or line counts.
498 */
499 lines_counted = 0;
500 if (show_commit(commit, &rev)) {
501 i++;
502 print_commit(commit, &rev);
503 }
398 free_commit_buffer(commit); 504 free_commit_buffer(commit);
399 free_commit_list(commit->parents); 505 free_commit_list(commit->parents);
400 commit->parents = NULL; 506 commit->parents = NULL;
@@ -406,7 +512,8 @@ void cgit_print_log(const char *tip, int ofs, int cnt, char *grep, char *pattern
406 cgit_log_link("[prev]", NULL, NULL, ctx.qry.head, 512 cgit_log_link("[prev]", NULL, NULL, ctx.qry.head,
407 ctx.qry.sha1, ctx.qry.vpath, 513 ctx.qry.sha1, ctx.qry.vpath,
408 ofs - cnt, ctx.qry.grep, 514 ofs - cnt, ctx.qry.grep,
409 ctx.qry.search, ctx.qry.showmsg); 515 ctx.qry.search, ctx.qry.showmsg,
516 ctx.qry.follow);
410 html("</li>"); 517 html("</li>");
411 } 518 }
412 if ((commit = get_revision(&rev)) != NULL) { 519 if ((commit = get_revision(&rev)) != NULL) {
@@ -414,14 +521,16 @@ void cgit_print_log(const char *tip, int ofs, int cnt, char *grep, char *pattern
414 cgit_log_link("[next]", NULL, NULL, ctx.qry.head, 521 cgit_log_link("[next]", NULL, NULL, ctx.qry.head,
415 ctx.qry.sha1, ctx.qry.vpath, 522 ctx.qry.sha1, ctx.qry.vpath,
416 ofs + cnt, ctx.qry.grep, 523 ofs + cnt, ctx.qry.grep,
417 ctx.qry.search, ctx.qry.showmsg); 524 ctx.qry.search, ctx.qry.showmsg,
525 ctx.qry.follow);
418 html("</li>"); 526 html("</li>");
419 } 527 }
420 html("</ul>"); 528 html("</ul>");
421 } else if ((commit = get_revision(&rev)) != NULL) { 529 } else if ((commit = get_revision(&rev)) != NULL) {
422 htmlf("<tr class='nohover'><td colspan='%d'>", columns); 530 htmlf("<tr class='nohover'><td colspan='%d'>", columns);
423 cgit_log_link("[...]", NULL, NULL, ctx.qry.head, NULL, 531 cgit_log_link("[...]", NULL, NULL, ctx.qry.head, NULL,
424 ctx.qry.vpath, 0, NULL, NULL, ctx.qry.showmsg); 532 ctx.qry.vpath, 0, NULL, NULL, ctx.qry.showmsg,
533 ctx.qry.follow);
425 html("</td></tr>\n"); 534 html("</td></tr>\n");
426 } 535 }
427 536
diff --git a/ui-refs.c b/ui-refs.c
index d3d71dd..73a187b 100644
--- a/ui-refs.c
+++ b/ui-refs.c
@@ -63,7 +63,7 @@ static int print_branch(struct refinfo *ref)
63 return 1; 63 return 1;
64 html("<tr><td>"); 64 html("<tr><td>");
65 cgit_log_link(name, NULL, NULL, name, NULL, NULL, 0, NULL, NULL, 65 cgit_log_link(name, NULL, NULL, name, NULL, NULL, 0, NULL, NULL,
66 ctx.qry.showmsg); 66 ctx.qry.showmsg, 0);
67 html("</td><td>"); 67 html("</td><td>");
68 68
69 if (ref->object->type == OBJ_COMMIT) { 69 if (ref->object->type == OBJ_COMMIT) {
diff --git a/ui-repolist.c b/ui-repolist.c
index 2453a7f..edefc4c 100644
--- a/ui-repolist.c
+++ b/ui-repolist.c
@@ -330,7 +330,7 @@ void cgit_print_repolist(void)
330 html("<td>"); 330 html("<td>");
331 cgit_summary_link("summary", NULL, "button", NULL); 331 cgit_summary_link("summary", NULL, "button", NULL);
332 cgit_log_link("log", NULL, "button", NULL, NULL, NULL, 332 cgit_log_link("log", NULL, "button", NULL, NULL, NULL,
333 0, NULL, NULL, ctx.qry.showmsg); 333 0, NULL, NULL, ctx.qry.showmsg, 0);
334 cgit_tree_link("tree", NULL, "button", NULL, NULL, NULL); 334 cgit_tree_link("tree", NULL, "button", NULL, NULL, NULL);
335 html("</td>"); 335 html("</td>");
336 } 336 }
diff --git a/ui-shared.c b/ui-shared.c
index 4f84b7c..6be0c2e 100644
--- a/ui-shared.c
+++ b/ui-shared.c
@@ -303,7 +303,8 @@ void cgit_plain_link(const char *name, const char *title, const char *class,
303 303
304void cgit_log_link(const char *name, const char *title, const char *class, 304void cgit_log_link(const char *name, const char *title, const char *class,
305 const char *head, const char *rev, const char *path, 305 const char *head, const char *rev, const char *path,
306 int ofs, const char *grep, const char *pattern, int showmsg) 306 int ofs, const char *grep, const char *pattern, int showmsg,
307 int follow)
307{ 308{
308 char *delim; 309 char *delim;
309 310
@@ -332,6 +333,11 @@ void cgit_log_link(const char *name, const char *title, const char *class,
332 if (showmsg) { 333 if (showmsg) {
333 html(delim); 334 html(delim);
334 html("showmsg=1"); 335 html("showmsg=1");
336 delim = "&amp;";
337 }
338 if (follow) {
339 html(delim);
340 html("follow=1");
335 } 341 }
336 html("'>"); 342 html("'>");
337 html_txt(name); 343 html_txt(name);
@@ -373,6 +379,10 @@ void cgit_commit_link(char *name, const char *title, const char *class,
373 html("ignorews=1"); 379 html("ignorews=1");
374 delim = "&amp;"; 380 delim = "&amp;";
375 } 381 }
382 if (ctx.qry.follow) {
383 html(delim);
384 html("follow=1");
385 }
376 html("'>"); 386 html("'>");
377 if (name[0] != '\0') 387 if (name[0] != '\0')
378 html_txt(name); 388 html_txt(name);
@@ -429,6 +439,10 @@ void cgit_diff_link(const char *name, const char *title, const char *class,
429 html("ignorews=1"); 439 html("ignorews=1");
430 delim = "&amp;"; 440 delim = "&amp;";
431 } 441 }
442 if (ctx.qry.follow) {
443 html(delim);
444 html("follow=1");
445 }
432 html("'>"); 446 html("'>");
433 html_txt(name); 447 html_txt(name);
434 html("</a>"); 448 html("</a>");
@@ -469,7 +483,7 @@ static void cgit_self_link(char *name, const char *title, const char *class)
469 ctx.qry.has_sha1 ? ctx.qry.sha1 : NULL, 483 ctx.qry.has_sha1 ? ctx.qry.sha1 : NULL,
470 ctx.qry.path, ctx.qry.ofs, 484 ctx.qry.path, ctx.qry.ofs,
471 ctx.qry.grep, ctx.qry.search, 485 ctx.qry.grep, ctx.qry.search,
472 ctx.qry.showmsg); 486 ctx.qry.showmsg, ctx.qry.follow);
473 else if (!strcmp(ctx.qry.page, "commit")) 487 else if (!strcmp(ctx.qry.page, "commit"))
474 cgit_commit_link(name, title, class, ctx.qry.head, 488 cgit_commit_link(name, title, class, ctx.qry.head,
475 ctx.qry.has_sha1 ? ctx.qry.sha1 : NULL, 489 ctx.qry.has_sha1 ? ctx.qry.sha1 : NULL,
@@ -945,7 +959,7 @@ void cgit_print_pageheader(void)
945 ctx.qry.sha1, NULL); 959 ctx.qry.sha1, NULL);
946 cgit_log_link("log", NULL, hc("log"), ctx.qry.head, 960 cgit_log_link("log", NULL, hc("log"), ctx.qry.head,
947 NULL, ctx.qry.vpath, 0, NULL, NULL, 961 NULL, ctx.qry.vpath, 0, NULL, NULL,
948 ctx.qry.showmsg); 962 ctx.qry.showmsg, ctx.qry.follow);
949 cgit_tree_link("tree", NULL, hc("tree"), ctx.qry.head, 963 cgit_tree_link("tree", NULL, hc("tree"), ctx.qry.head,
950 ctx.qry.sha1, ctx.qry.vpath); 964 ctx.qry.sha1, ctx.qry.vpath);
951 cgit_commit_link("commit", NULL, hc("commit"), 965 cgit_commit_link("commit", NULL, hc("commit"),
@@ -993,6 +1007,14 @@ void cgit_print_pageheader(void)
993 html("<div class='path'>"); 1007 html("<div class='path'>");
994 html("path: "); 1008 html("path: ");
995 cgit_print_path_crumbs(ctx.qry.vpath); 1009 cgit_print_path_crumbs(ctx.qry.vpath);
1010 if (ctx.cfg.enable_follow_links && !strcmp(ctx.qry.page, "log")) {
1011 html(" (");
1012 ctx.qry.follow = !ctx.qry.follow;
1013 cgit_self_link(ctx.qry.follow ? "follow" : "unfollow",
1014 NULL, NULL);
1015 ctx.qry.follow = !ctx.qry.follow;
1016 html(")");
1017 }
996 html("</div>"); 1018 html("</div>");
997 } 1019 }
998 html("<div class='content'>"); 1020 html("<div class='content'>");
diff --git a/ui-shared.h b/ui-shared.h
index 43d0fa6..788b1bc 100644
--- a/ui-shared.h
+++ b/ui-shared.h
@@ -31,7 +31,7 @@ extern void cgit_plain_link(const char *name, const char *title,
31extern void cgit_log_link(const char *name, const char *title, 31extern void cgit_log_link(const char *name, const char *title,
32 const char *class, const char *head, const char *rev, 32 const char *class, const char *head, const char *rev,
33 const char *path, int ofs, const char *grep, 33 const char *path, int ofs, const char *grep,
34 const char *pattern, int showmsg); 34 const char *pattern, int showmsg, int follow);
35extern void cgit_commit_link(char *name, const char *title, 35extern void cgit_commit_link(char *name, const char *title,
36 const char *class, const char *head, 36 const char *class, const char *head,
37 const char *rev, const char *path); 37 const char *rev, const char *path);
diff --git a/ui-tree.c b/ui-tree.c
index bbc468e..c8d24f6 100644
--- a/ui-tree.c
+++ b/ui-tree.c
@@ -166,7 +166,7 @@ static int ls_item(const unsigned char *sha1, struct strbuf *base,
166 html("<td>"); 166 html("<td>");
167 cgit_log_link("log", NULL, "button", ctx.qry.head, 167 cgit_log_link("log", NULL, "button", ctx.qry.head,
168 walk_tree_ctx->curr_rev, fullpath.buf, 0, NULL, NULL, 168 walk_tree_ctx->curr_rev, fullpath.buf, 0, NULL, NULL,
169 ctx.qry.showmsg); 169 ctx.qry.showmsg, 0);
170 if (ctx.repo->max_stats) 170 if (ctx.repo->max_stats)
171 cgit_stats_link("stats", NULL, "button", ctx.qry.head, 171 cgit_stats_link("stats", NULL, "button", ctx.qry.head,
172 fullpath.buf); 172 fullpath.buf);