diff options
-rw-r--r-- | cgit.c | 4 | ||||
-rw-r--r-- | cgit.h | 2 | ||||
-rw-r--r-- | cgitrc.5.txt | 4 | ||||
-rw-r--r-- | ui-diff.c | 35 | ||||
-rw-r--r-- | ui-log.c | 136 | ||||
-rw-r--r-- | ui-refs.c | 2 | ||||
-rw-r--r-- | ui-repolist.c | 2 | ||||
-rw-r--r-- | ui-shared.c | 28 | ||||
-rw-r--r-- | ui-shared.h | 2 | ||||
-rw-r--r-- | ui-tree.c | 2 |
10 files changed, 197 insertions, 20 deletions
@@ -171,6 +171,8 @@ static void config_cb(const char *name, const char *value) ctx.cfg.snapshots = cgit_parse_snapshots_mask(value); else if (!strcmp(name, "enable-filter-overrides")) ctx.cfg.enable_filter_overrides = atoi(value); + else if (!strcmp(name, "enable-follow-links")) + ctx.cfg.enable_follow_links = atoi(value); else if (!strcmp(name, "enable-http-clone")) ctx.cfg.enable_http_clone = atoi(value); else if (!strcmp(name, "enable-index-links")) @@ -338,6 +340,8 @@ static void querystring_cb(const char *name, const char *value) ctx.qry.context = atoi(value); } else if (!strcmp(name, "ignorews")) { ctx.qry.ignorews = atoi(value); + } else if (!strcmp(name, "follow")) { + ctx.qry.follow = atoi(value); } } @@ -163,6 +163,7 @@ struct cgit_query { int show_all; int context; int ignorews; + int follow; char *vpath; }; @@ -203,6 +204,7 @@ struct cgit_config { int case_sensitive_sort; int embedded; int enable_filter_overrides; + int enable_follow_links; int enable_http_clone; int enable_index_links; int enable_index_owner; diff --git a/cgitrc.5.txt b/cgitrc.5.txt index 39b031e..48ef249 100644 --- a/cgitrc.5.txt +++ b/cgitrc.5.txt @@ -121,6 +121,10 @@ enable-filter-overrides:: Flag which, when set to "1", allows all filter settings to be overridden in repository-specific cgitrc files. Default value: none. +enable-follow-links:: + Flag which, when set to "1", allows users to follow a file in the log + view. Default value: "0". + enable-http-clone:: If set to "1", cgit will act as an dumb HTTP endpoint for git clones. If you use an alternate way of serving git repositories, you may wish @@ -36,6 +36,7 @@ static struct fileinfo { static int use_ssdiff = 0; static struct diff_filepair *current_filepair; +static const char *current_prefix; struct diff_filespec *cgit_get_current_old_file(void) { @@ -132,11 +133,30 @@ static void count_diff_lines(char *line, int len) } } +static int show_filepair(struct diff_filepair *pair) +{ + /* Always show if we have no limiting prefix. */ + if (!current_prefix) + return 1; + + /* Show if either path in the pair begins with the prefix. */ + if (!prefixcmp(pair->one->path, current_prefix) || + !prefixcmp(pair->two->path, current_prefix)) + return 1; + + /* Otherwise we don't want to show this filepair. */ + return 0; +} + static void inspect_filepair(struct diff_filepair *pair) { int binary = 0; unsigned long old_size = 0; unsigned long new_size = 0; + + if (!show_filepair(pair)) + return; + files++; lines_added = 0; lines_removed = 0; @@ -279,6 +299,9 @@ static void filepair_cb(struct diff_filepair *pair) int binary = 0; linediff_fn print_line_fn = print_line; + if (!show_filepair(pair)) + return; + current_filepair = pair; if (use_ssdiff) { cgit_ssdiff_header_begin(); @@ -364,6 +387,18 @@ void cgit_print_diff(const char *new_rev, const char *old_rev, unsigned long size; struct commit *commit, *commit2; + /* + * If "follow" is set then the diff machinery needs to examine the + * entire commit to detect renames so we must limit the paths in our + * own callbacks and not pass the prefix to the diff machinery. + */ + if (ctx.qry.follow && ctx.cfg.enable_follow_links) { + current_prefix = prefix; + prefix = ""; + } else { + current_prefix = NULL; + } + if (!new_rev) new_rev = ctx.qry.head; get_sha1(new_rev, new_rev_sha1); @@ -12,7 +12,7 @@ #include "ui-shared.h" #include "vector.h" -int files, add_lines, rem_lines; +int files, add_lines, rem_lines, lines_counted; /* * The list of available column colors in the commit graph. @@ -66,7 +66,7 @@ void show_commit_decorations(struct commit *commit) strncpy(buf, deco->name + 11, sizeof(buf) - 1); cgit_log_link(buf, NULL, "branch-deco", buf, NULL, ctx.qry.vpath, 0, NULL, NULL, - ctx.qry.showmsg); + ctx.qry.showmsg, 0); } else if (!prefixcmp(deco->name, "tag: refs/tags/")) { strncpy(buf, deco->name + 15, sizeof(buf) - 1); @@ -83,7 +83,7 @@ void show_commit_decorations(struct commit *commit) cgit_log_link(buf, NULL, "remote-deco", NULL, sha1_to_hex(commit->object.sha1), ctx.qry.vpath, 0, NULL, NULL, - ctx.qry.showmsg); + ctx.qry.showmsg, 0); } else { strncpy(buf, deco->name, sizeof(buf) - 1); @@ -96,6 +96,74 @@ next: } } +static void handle_rename(struct diff_filepair *pair) +{ + /* + * After we have seen a rename, we generate links to the previous + * name of the file so that commit & diff views get fed the path + * that is correct for the commit they are showing, avoiding the + * need to walk the entire history leading back to every commit we + * show in order detect renames. + */ + if (0 != strcmp(ctx.qry.vpath, pair->two->path)) { + free(ctx.qry.vpath); + ctx.qry.vpath = xstrdup(pair->two->path); + } + inspect_files(pair); +} + +static int show_commit(struct commit *commit, struct rev_info *revs) +{ + struct commit_list *parents = commit->parents; + struct commit *parent; + int found = 0, saved_fmt; + unsigned saved_flags = revs->diffopt.flags; + + + /* Always show if we're not in "follow" mode with a single file. */ + if (!ctx.qry.follow) + return 1; + + /* + * In "follow" mode, we don't show merges. This is consistent with + * "git log --follow -- <file>". + */ + if (parents && parents->next) + return 0; + + /* + * If this is the root commit, do what rev_info tells us. + */ + if (!parents) + return revs->show_root_diff; + + /* When we get here we have precisely one parent. */ + parent = parents->item; + parse_commit(parent); + + files = 0; + add_lines = 0; + rem_lines = 0; + + DIFF_OPT_SET(&revs->diffopt, RECURSIVE); + diff_tree_sha1(parent->tree->object.sha1, + commit->tree->object.sha1, + "", &revs->diffopt); + diffcore_std(&revs->diffopt); + + found = !diff_queue_is_empty(); + saved_fmt = revs->diffopt.output_format; + revs->diffopt.output_format = DIFF_FORMAT_CALLBACK; + revs->diffopt.format_callback = cgit_diff_tree_cb; + revs->diffopt.format_callback_data = handle_rename; + diff_flush(&revs->diffopt); + revs->diffopt.output_format = saved_fmt; + revs->diffopt.flags = saved_flags; + + lines_counted = 1; + return found; +} + static void print_commit(struct commit *commit, struct rev_info *revs) { struct commitinfo *info; @@ -173,7 +241,8 @@ static void print_commit(struct commit *commit, struct rev_info *revs) cgit_print_age(commit->date, TM_WEEK * 2, FMT_SHORTDATE); } - if (ctx.repo->enable_log_filecount || ctx.repo->enable_log_linecount) { + if (!lines_counted && (ctx.repo->enable_log_filecount || + ctx.repo->enable_log_linecount)) { files = 0; add_lines = 0; rem_lines = 0; @@ -324,7 +393,17 @@ void cgit_print_log(const char *tip, int ofs, int cnt, char *grep, char *pattern } } } - if (commit_graph) { + + if (!path || !ctx.cfg.enable_follow_links) { + /* + * If we don't have a path, "follow" is a no-op so make sure + * the variable is set to false to avoid needing to check + * both this and whether we have a path everywhere. + */ + ctx.qry.follow = 0; + } + + if (commit_graph && !ctx.qry.follow) { static const char *graph_arg = "--graph"; static const char *color_arg = "--color"; vector_push(&vec, &graph_arg, 0); @@ -342,7 +421,10 @@ void cgit_print_log(const char *tip, int ofs, int cnt, char *grep, char *pattern } if (path) { + static const char *follow_arg = "--follow"; static const char *double_dash_arg = "--"; + if (ctx.qry.follow) + vector_push(&vec, &follow_arg, 0); vector_push(&vec, &double_dash_arg, 0); vector_push(&vec, &path, 0); } @@ -356,10 +438,17 @@ void cgit_print_log(const char *tip, int ofs, int cnt, char *grep, char *pattern rev.commit_format = CMIT_FMT_DEFAULT; rev.verbose_header = 1; rev.show_root_diff = 0; + rev.simplify_history = 1; setup_revisions(vec.count, vec.data, &rev, NULL); load_ref_decorations(DECORATE_FULL_REFS); rev.show_decorations = 1; rev.grep_filter.regflags |= REG_ICASE; + + rev.diffopt.detect_rename = 1; + rev.diffopt.rename_limit = ctx.cfg.renamelimit; + if (ctx.qry.ignorews) + DIFF_XDL_SET(&rev.diffopt, IGNORE_WHITESPACE); + compile_grep_patterns(&rev.grep_filter); prepare_revision_walk(&rev); @@ -377,11 +466,12 @@ void cgit_print_log(const char *tip, int ofs, int cnt, char *grep, char *pattern cgit_log_link(ctx.qry.showmsg ? "Collapse" : "Expand", NULL, NULL, ctx.qry.head, ctx.qry.sha1, ctx.qry.vpath, ctx.qry.ofs, ctx.qry.grep, - ctx.qry.search, ctx.qry.showmsg ? 0 : 1); + ctx.qry.search, ctx.qry.showmsg ? 0 : 1, + ctx.qry.follow); html(")"); } html("</th><th class='left'>Author</th>"); - if (commit_graph) + if (rev.graph) html("<th class='left'>Age</th>"); if (ctx.repo->enable_log_filecount) { html("<th class='left'>Files</th>"); @@ -396,15 +486,32 @@ void cgit_print_log(const char *tip, int ofs, int cnt, char *grep, char *pattern if (ofs<0) ofs = 0; - for (i = 0; i < ofs && (commit = get_revision(&rev)) != NULL; i++) { + for (i = 0; i < ofs && (commit = get_revision(&rev)) != NULL;) { + if (show_commit(commit, &rev)) + i++; free(commit->buffer); commit->buffer = NULL; free_commit_list(commit->parents); commit->parents = NULL; } - for (i = 0; i < cnt && (commit = get_revision(&rev)) != NULL; i++) { - print_commit(commit, &rev); + for (i = 0; i < cnt && (commit = get_revision(&rev)) != NULL;) { + /* + * In "follow" mode, we must count the files and lines the + * first time we invoke diff on a given commit, and we need + * to do that to see if the commit touches the path we care + * about, so we do it in show_commit. Hence we must clear + * lines_counted here. + * + * This has the side effect of avoiding running diff twice + * when we are both following renames and showing file + * and/or line counts. + */ + lines_counted = 0; + if (show_commit(commit, &rev)) { + i++; + print_commit(commit, &rev); + } free(commit->buffer); commit->buffer = NULL; free_commit_list(commit->parents); @@ -417,7 +524,8 @@ void cgit_print_log(const char *tip, int ofs, int cnt, char *grep, char *pattern cgit_log_link("[prev]", NULL, NULL, ctx.qry.head, ctx.qry.sha1, ctx.qry.vpath, ofs - cnt, ctx.qry.grep, - ctx.qry.search, ctx.qry.showmsg); + ctx.qry.search, ctx.qry.showmsg, + ctx.qry.follow); html("</li>"); } if ((commit = get_revision(&rev)) != NULL) { @@ -425,14 +533,16 @@ void cgit_print_log(const char *tip, int ofs, int cnt, char *grep, char *pattern cgit_log_link("[next]", NULL, NULL, ctx.qry.head, ctx.qry.sha1, ctx.qry.vpath, ofs + cnt, ctx.qry.grep, - ctx.qry.search, ctx.qry.showmsg); + ctx.qry.search, ctx.qry.showmsg, + ctx.qry.follow); html("</li>"); } html("</ul>"); } else if ((commit = get_revision(&rev)) != NULL) { htmlf("<tr class='nohover'><td colspan='%d'>", columns); cgit_log_link("[...]", NULL, NULL, ctx.qry.head, NULL, - ctx.qry.vpath, 0, NULL, NULL, ctx.qry.showmsg); + ctx.qry.vpath, 0, NULL, NULL, ctx.qry.showmsg, + ctx.qry.follow); html("</td></tr>\n"); } @@ -71,7 +71,7 @@ static int print_branch(struct refinfo *ref) return 1; html("<tr><td>"); cgit_log_link(name, NULL, NULL, name, NULL, NULL, 0, NULL, NULL, - ctx.qry.showmsg); + ctx.qry.showmsg, 0); html("</td><td>"); if (ref->object->type == OBJ_COMMIT) { diff --git a/ui-repolist.c b/ui-repolist.c index 47ca997..d96f252 100644 --- a/ui-repolist.c +++ b/ui-repolist.c @@ -314,7 +314,7 @@ void cgit_print_repolist() html("<td>"); cgit_summary_link("summary", NULL, "button", NULL); cgit_log_link("log", NULL, "button", NULL, NULL, NULL, - 0, NULL, NULL, ctx.qry.showmsg); + 0, NULL, NULL, ctx.qry.showmsg, 0); cgit_tree_link("tree", NULL, "button", NULL, NULL, NULL); html("</td>"); } diff --git a/ui-shared.c b/ui-shared.c index 519eef7..04d0368 100644 --- a/ui-shared.c +++ b/ui-shared.c @@ -284,7 +284,8 @@ void cgit_plain_link(const char *name, const char *title, const char *class, void cgit_log_link(const char *name, const char *title, const char *class, const char *head, const char *rev, const char *path, - int ofs, const char *grep, const char *pattern, int showmsg) + int ofs, const char *grep, const char *pattern, int showmsg, + int follow) { char *delim; @@ -313,6 +314,11 @@ void cgit_log_link(const char *name, const char *title, const char *class, if (showmsg) { html(delim); html("showmsg=1"); + delim = "&"; + } + if (follow) { + html(delim); + html("follow=1"); } html("'>"); html_txt(name); @@ -355,6 +361,10 @@ void cgit_commit_link(char *name, const char *title, const char *class, html("ignorews=1"); delim = "&"; } + if (ctx.qry.follow) { + html(delim); + html("follow=1"); + } html("'>"); if (name[0] != '\0') html_txt(name); @@ -411,6 +421,10 @@ void cgit_diff_link(const char *name, const char *title, const char *class, html("ignorews=1"); delim = "&"; } + if (ctx.qry.follow) { + html(delim); + html("follow=1"); + } html("'>"); html_txt(name); html("</a>"); @@ -452,7 +466,7 @@ static void cgit_self_link(char *name, const char *title, const char *class, ctx->qry.has_sha1 ? ctx->qry.sha1 : NULL, ctx->qry.path, ctx->qry.ofs, ctx->qry.grep, ctx->qry.search, - ctx->qry.showmsg); + ctx->qry.showmsg, ctx->qry.follow); else if (!strcmp(ctx->qry.page, "commit")) cgit_commit_link(name, title, class, ctx->qry.head, ctx->qry.has_sha1 ? ctx->qry.sha1 : NULL, @@ -854,7 +868,7 @@ void cgit_print_pageheader(struct cgit_context *ctx) ctx->qry.sha1, NULL); cgit_log_link("log", NULL, hc(ctx, "log"), ctx->qry.head, NULL, ctx->qry.vpath, 0, NULL, NULL, - ctx->qry.showmsg); + ctx->qry.showmsg, ctx->qry.follow); cgit_tree_link("tree", NULL, hc(ctx, "tree"), ctx->qry.head, ctx->qry.sha1, ctx->qry.vpath); cgit_commit_link("commit", NULL, hc(ctx, "commit"), @@ -906,6 +920,14 @@ void cgit_print_pageheader(struct cgit_context *ctx) html("<div class='path'>"); html("path: "); cgit_print_path_crumbs(ctx, ctx->qry.vpath); + if (ctx->cfg.enable_follow_links && !strcmp(ctx->qry.page, "log")) { + html(" ("); + ctx->qry.follow = !ctx->qry.follow; + cgit_self_link(ctx->qry.follow ? "follow" : "unfollow", + NULL, NULL, ctx); + ctx->qry.follow = !ctx->qry.follow; + html(")"); + } html("</div>"); } html("<div class='content'>"); diff --git a/ui-shared.h b/ui-shared.h index 5987e77..3156846 100644 --- a/ui-shared.h +++ b/ui-shared.h @@ -26,7 +26,7 @@ extern void cgit_plain_link(const char *name, const char *title, extern void cgit_log_link(const char *name, const char *title, const char *class, const char *head, const char *rev, const char *path, int ofs, const char *grep, - const char *pattern, int showmsg); + const char *pattern, int showmsg, int follow); extern void cgit_commit_link(char *name, const char *title, const char *class, const char *head, const char *rev, const char *path, @@ -169,7 +169,7 @@ static int ls_item(const unsigned char *sha1, const char *base, int baselen, html("<td>"); cgit_log_link("log", NULL, "button", ctx.qry.head, walk_tree_ctx->curr_rev, fullpath.buf, 0, NULL, NULL, - ctx.qry.showmsg); + ctx.qry.showmsg, 0); if (ctx.repo->max_stats) cgit_stats_link("stats", NULL, "button", ctx.qry.head, fullpath.buf); |