From c1cd290d1f83d3d1c2d081d734e8d213f12cc06b Mon Sep 17 00:00:00 2001 From: Jeff Smith Date: Sun, 1 Oct 2017 23:39:08 -0500 Subject: ui-blame: add blame UI Implement a page which provides the blame view of a specified file. This feature is controlled by a new config variable, "enable-blame", which is disabled by default. Signed-off-by: Jeff Smith Reviewed-by: John Keeping --- ui-blame.c | 227 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 227 insertions(+) create mode 100644 ui-blame.c (limited to 'ui-blame.c') diff --git a/ui-blame.c b/ui-blame.c new file mode 100644 index 0000000..62cf431 --- /dev/null +++ b/ui-blame.c @@ -0,0 +1,227 @@ +/* ui-blame.c: functions for blame output + * + * Copyright (C) 2006-2017 cgit Development Team + * + * Licensed under GNU General Public License v2 + * (see COPYING for full license text) + */ + +#include "cgit.h" +#include "ui-blame.h" +#include "html.h" +#include "ui-shared.h" +#include "argv-array.h" +#include "blame.h" + + +static char *emit_suspect_detail(struct blame_origin *suspect) +{ + struct commitinfo *info; + struct strbuf detail = STRBUF_INIT; + + info = cgit_parse_commit(suspect->commit); + + strbuf_addf(&detail, "author %s", info->author); + if (!ctx.cfg.noplainemail) + strbuf_addf(&detail, " %s", info->author_email); + strbuf_addf(&detail, " %s\n", + show_date(info->author_date, info->author_tz, + cgit_date_mode(DATE_ISO8601))); + + strbuf_addf(&detail, "committer %s", info->committer); + if (!ctx.cfg.noplainemail) + strbuf_addf(&detail, " %s", info->committer_email); + strbuf_addf(&detail, " %s\n\n", + show_date(info->committer_date, info->committer_tz, + cgit_date_mode(DATE_ISO8601))); + + strbuf_addstr(&detail, info->subject); + + cgit_free_commitinfo(info); + return strbuf_detach(&detail, NULL); +} + +static void emit_blame_entry(struct blame_scoreboard *sb, + struct blame_entry *ent) +{ + struct blame_origin *suspect = ent->suspect; + struct object_id *oid = &suspect->commit->object.oid; + const char *numberfmt = "%1$d\n"; + const char *cp, *cpend; + + char *detail = emit_suspect_detail(suspect); + + html(""); + cgit_commit_link(find_unique_abbrev(oid->hash, DEFAULT_ABBREV), detail, + NULL, ctx.qry.head, oid_to_hex(oid), suspect->path); + html("\n"); + + free(detail); + + if (ctx.cfg.enable_tree_linenumbers) { + unsigned long lineno = ent->lno; + html("
");
+		while (lineno < ent->lno + ent->num_lines)
+			htmlf(numberfmt, ++lineno);
+		html("
\n"); + } + + cp = blame_nth_line(sb, ent->lno); + cpend = blame_nth_line(sb, ent->lno + ent->num_lines); + + html("
");
+	html_ntxt(cp, cpend - cp);
+	html("
\n"); +} + +struct walk_tree_context { + char *curr_rev; + int match_baselen; + int state; +}; + +static void print_object(const unsigned char *sha1, const char *path, + const char *basename, const char *rev) +{ + enum object_type type; + unsigned long size; + struct argv_array rev_argv = ARGV_ARRAY_INIT; + struct rev_info revs; + struct blame_scoreboard sb; + struct blame_origin *o; + struct blame_entry *ent = NULL; + + type = sha1_object_info(sha1, &size); + if (type == OBJ_BAD) { + cgit_print_error_page(404, "Not found", "Bad object name: %s", + sha1_to_hex(sha1)); + return; + } + + argv_array_push(&rev_argv, "blame"); + argv_array_push(&rev_argv, rev); + init_revisions(&revs, NULL); + DIFF_OPT_SET(&revs.diffopt, ALLOW_TEXTCONV); + setup_revisions(rev_argv.argc, rev_argv.argv, &revs, NULL); + init_scoreboard(&sb); + sb.revs = &revs; + setup_scoreboard(&sb, path, &o); + o->suspects = blame_entry_prepend(NULL, 0, sb.num_lines, o); + prio_queue_put(&sb.commits, o->commit); + blame_origin_decref(o); + sb.ent = NULL; + sb.path = path; + assign_blame(&sb, 0); + blame_sort_final(&sb); + blame_coalesce(&sb); + + cgit_set_title_from_path(path); + + cgit_print_layout_start(); + htmlf("blob: %s (", sha1_to_hex(sha1)); + cgit_plain_link("plain", NULL, NULL, ctx.qry.head, rev, path); + html(") ("); + cgit_tree_link("tree", NULL, NULL, ctx.qry.head, rev, path); + html(")\n"); + + if (ctx.cfg.max_blob_size && size / 1024 > ctx.cfg.max_blob_size) { + htmlf("
blob size (%ldKB)" + " exceeds display size limit (%dKB).
", + size / 1024, ctx.cfg.max_blob_size); + return; + } + + html(""); + for (ent = sb.ent; ent; ) { + struct blame_entry *e = ent->next; + emit_blame_entry(&sb, ent); + free(ent); + ent = e; + } + html("
\n"); + free((void *)sb.final_buf); + + cgit_print_layout_end(); +} + +static int walk_tree(const unsigned char *sha1, struct strbuf *base, + const char *pathname, unsigned mode, int stage, + void *cbdata) +{ + struct walk_tree_context *walk_tree_ctx = cbdata; + + if (base->len == walk_tree_ctx->match_baselen) { + if (S_ISREG(mode)) { + struct strbuf buffer = STRBUF_INIT; + strbuf_addbuf(&buffer, base); + strbuf_addstr(&buffer, pathname); + print_object(sha1, buffer.buf, pathname, + walk_tree_ctx->curr_rev); + strbuf_release(&buffer); + walk_tree_ctx->state = 1; + } else if (S_ISDIR(mode)) { + walk_tree_ctx->state = 2; + } + } else if (base->len < INT_MAX + && (int)base->len > walk_tree_ctx->match_baselen) { + walk_tree_ctx->state = 2; + } else if (S_ISDIR(mode)) { + return READ_TREE_RECURSIVE; + } + return 0; +} + +static int basedir_len(const char *path) +{ + char *p = strrchr(path, '/'); + if (p) + return p - path + 1; + return 0; +} + +void cgit_print_blame(void) +{ + const char *rev = ctx.qry.sha1; + struct object_id oid; + struct commit *commit; + struct pathspec_item path_items = { + .match = ctx.qry.path, + .len = ctx.qry.path ? strlen(ctx.qry.path) : 0 + }; + struct pathspec paths = { + .nr = 1, + .items = &path_items + }; + struct walk_tree_context walk_tree_ctx = { + .state = 0 + }; + + if (!rev) + rev = ctx.qry.head; + + if (get_oid(rev, &oid)) { + cgit_print_error_page(404, "Not found", + "Invalid revision name: %s", rev); + return; + } + commit = lookup_commit_reference(&oid); + if (!commit || parse_commit(commit)) { + cgit_print_error_page(404, "Not found", + "Invalid commit reference: %s", rev); + return; + } + + walk_tree_ctx.curr_rev = xstrdup(rev); + walk_tree_ctx.match_baselen = (path_items.match) ? + basedir_len(path_items.match) : -1; + + read_tree_recursive(commit->tree, "", 0, 0, &paths, walk_tree, + &walk_tree_ctx); + if (!walk_tree_ctx.state) + cgit_print_error_page(404, "Not found", "Not found"); + else if (walk_tree_ctx.state == 2) + cgit_print_error_page(404, "No blame for folders", + "Blame is not available for folders."); + + free(walk_tree_ctx.curr_rev); +} -- cgit v1.2.3-24-g4f1b From 1dd53e3a2ffec730ec27ebe15b3d63e0b417a544 Mon Sep 17 00:00:00 2001 From: Christian Hesse Date: Thu, 18 Jan 2018 09:19:31 +0100 Subject: git: update to v2.16.0 Update to git version v2.16.0: * refs: convert resolve_ref_unsafe to struct object_id (49e61479be913f67e66bb3fdf8de9475c41b58bd) * diff: remove DIFF_OPT_SET macro (23dcf77f48feb49c54bad09210f093a799816334) * log: add option to choose which refs to decorate (65516f586b69307f977cd67cc45513a296cabc25) * diff: convert flags to be stored in bitfields (02f2f56bc377c287c411947d0e1482aac888f8db) Signed-off-by: Christian Hesse --- ui-blame.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'ui-blame.c') diff --git a/ui-blame.c b/ui-blame.c index 62cf431..d4a4534 100644 --- a/ui-blame.c +++ b/ui-blame.c @@ -101,7 +101,7 @@ static void print_object(const unsigned char *sha1, const char *path, argv_array_push(&rev_argv, "blame"); argv_array_push(&rev_argv, rev); init_revisions(&revs, NULL); - DIFF_OPT_SET(&revs.diffopt, ALLOW_TEXTCONV); + revs.diffopt.flags.allow_textconv = 1; setup_revisions(rev_argv.argc, rev_argv.argv, &revs, NULL); init_scoreboard(&sb); sb.revs = &revs; -- cgit v1.2.3-24-g4f1b From 6b5b655f6d2449fe33d8f48f6e98d5e421bf3ff9 Mon Sep 17 00:00:00 2001 From: Jeff Smith Date: Tue, 17 Oct 2017 23:17:32 -0500 Subject: ui-blame: Distinguish hashes column from lines column Signed-off-by: Jeff Smith Reviewed-by: John Keeping --- ui-blame.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'ui-blame.c') diff --git a/ui-blame.c b/ui-blame.c index d4a4534..62647a8 100644 --- a/ui-blame.c +++ b/ui-blame.c @@ -51,7 +51,7 @@ static void emit_blame_entry(struct blame_scoreboard *sb, char *detail = emit_suspect_detail(suspect); - html(""); + html(""); cgit_commit_link(find_unique_abbrev(oid->hash, DEFAULT_ABBREV), detail, NULL, ctx.qry.head, oid_to_hex(oid), suspect->path); html("\n"); -- cgit v1.2.3-24-g4f1b From 2b95c9d49c8581e2b19efca1613ada292f56bf08 Mon Sep 17 00:00:00 2001 From: Jeff Smith Date: Tue, 17 Oct 2017 23:17:33 -0500 Subject: ui-blame: Break out emit_blame_entry into component methods Signed-off-by: Jeff Smith Reviewed-by: John Keeping --- ui-blame.c | 44 ++++++++++++++++++++++++++++++-------------- 1 file changed, 30 insertions(+), 14 deletions(-) (limited to 'ui-blame.c') diff --git a/ui-blame.c b/ui-blame.c index 62647a8..bbaad1c 100644 --- a/ui-blame.c +++ b/ui-blame.c @@ -41,36 +41,52 @@ static char *emit_suspect_detail(struct blame_origin *suspect) return strbuf_detach(&detail, NULL); } -static void emit_blame_entry(struct blame_scoreboard *sb, - struct blame_entry *ent) +static void emit_blame_entry_hash(struct blame_entry *ent) { struct blame_origin *suspect = ent->suspect; struct object_id *oid = &suspect->commit->object.oid; + + char *detail = emit_suspect_detail(suspect); + cgit_commit_link(find_unique_abbrev(oid->hash, DEFAULT_ABBREV), detail, + NULL, ctx.qry.head, oid_to_hex(oid), suspect->path); + free(detail); +} + +static void emit_blame_entry_linenumber(struct blame_entry *ent) +{ const char *numberfmt = "%1$d\n"; + + unsigned long lineno = ent->lno; + while (lineno < ent->lno + ent->num_lines) + htmlf(numberfmt, ++lineno); +} + +static void emit_blame_entry_line(struct blame_scoreboard *sb, + struct blame_entry *ent) +{ const char *cp, *cpend; - char *detail = emit_suspect_detail(suspect); + cp = blame_nth_line(sb, ent->lno); + cpend = blame_nth_line(sb, ent->lno + ent->num_lines); + + html_ntxt(cp, cpend - cp); +} +static void emit_blame_entry(struct blame_scoreboard *sb, + struct blame_entry *ent) +{ html(""); - cgit_commit_link(find_unique_abbrev(oid->hash, DEFAULT_ABBREV), detail, - NULL, ctx.qry.head, oid_to_hex(oid), suspect->path); + emit_blame_entry_hash(ent); html("\n"); - free(detail); - if (ctx.cfg.enable_tree_linenumbers) { - unsigned long lineno = ent->lno; html("
");
-		while (lineno < ent->lno + ent->num_lines)
-			htmlf(numberfmt, ++lineno);
+		emit_blame_entry_linenumber(ent);
 		html("
\n"); } - cp = blame_nth_line(sb, ent->lno); - cpend = blame_nth_line(sb, ent->lno + ent->num_lines); - html("
");
-	html_ntxt(cp, cpend - cp);
+	emit_blame_entry_line(sb, ent);
 	html("
\n"); } -- cgit v1.2.3-24-g4f1b From aafc42d8089437db5105feb12d540c33fe9f9e16 Mon Sep 17 00:00:00 2001 From: Jeff Smith Date: Tue, 17 Oct 2017 23:17:34 -0500 Subject: ui-blame: Make each column into a single table cell Signed-off-by: Jeff Smith Reviewed-by: John Keeping --- ui-blame.c | 58 +++++++++++++++++++++++++++++++++++++--------------------- 1 file changed, 37 insertions(+), 21 deletions(-) (limited to 'ui-blame.c') diff --git a/ui-blame.c b/ui-blame.c index bbaad1c..d565fff 100644 --- a/ui-blame.c +++ b/ui-blame.c @@ -45,11 +45,17 @@ static void emit_blame_entry_hash(struct blame_entry *ent) { struct blame_origin *suspect = ent->suspect; struct object_id *oid = &suspect->commit->object.oid; + unsigned long line = 0; char *detail = emit_suspect_detail(suspect); + html(""); cgit_commit_link(find_unique_abbrev(oid->hash, DEFAULT_ABBREV), detail, NULL, ctx.qry.head, oid_to_hex(oid), suspect->path); + html(""); free(detail); + + while (line++ < ent->num_lines) + html("\n"); } static void emit_blame_entry_linenumber(struct blame_entry *ent) @@ -72,24 +78,6 @@ static void emit_blame_entry_line(struct blame_scoreboard *sb, html_ntxt(cp, cpend - cp); } -static void emit_blame_entry(struct blame_scoreboard *sb, - struct blame_entry *ent) -{ - html(""); - emit_blame_entry_hash(ent); - html("\n"); - - if (ctx.cfg.enable_tree_linenumbers) { - html("
");
-		emit_blame_entry_linenumber(ent);
-		html("
\n"); - } - - html("
");
-	emit_blame_entry_line(sb, ent);
-	html("
\n"); -} - struct walk_tree_context { char *curr_rev; int match_baselen; @@ -147,16 +135,44 @@ static void print_object(const unsigned char *sha1, const char *path, return; } - html(""); + html("
\n\n"); + + /* Commit hashes */ + html("\n"); + + /* Line numbers */ + if (ctx.cfg.enable_tree_linenumbers) { + html("\n"); + } + + /* Lines */ + html("
"); + for (ent = sb.ent; ent; ent = ent->next) { + html("
");
+		emit_blame_entry_hash(ent);
+		html("
"); + } + html("
"); + for (ent = sb.ent; ent; ent = ent->next) { + html("
");
+			emit_blame_entry_linenumber(ent);
+			html("
"); + } + html("
"); for (ent = sb.ent; ent; ) { struct blame_entry *e = ent->next; - emit_blame_entry(&sb, ent); + html("
");
+		emit_blame_entry_line(&sb, ent);
+		html("
"); free(ent); ent = e; } - html("
\n"); + html("\n"); + free((void *)sb.final_buf); + html("\n\n"); + cgit_print_layout_end(); } -- cgit v1.2.3-24-g4f1b From dbaee2672be14374acb17266477c19294c6155f3 Mon Sep 17 00:00:00 2001 From: Jeff Smith Date: Sat, 28 Oct 2017 21:43:26 -0500 Subject: ui-blame: Allow syntax highlighting Place file contents into a single block so that syntax highlighting can be applied in the usual fashion. Place the alternating color bars behind the file contents. Force the default syntax highlighting background to transparent. Signed-off-by: Jeff Smith Reviewed-by: John Keeping --- ui-blame.c | 63 ++++++++++++++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 51 insertions(+), 12 deletions(-) (limited to 'ui-blame.c') diff --git a/ui-blame.c b/ui-blame.c index d565fff..17e2d60 100644 --- a/ui-blame.c +++ b/ui-blame.c @@ -67,15 +67,29 @@ static void emit_blame_entry_linenumber(struct blame_entry *ent) htmlf(numberfmt, ++lineno); } -static void emit_blame_entry_line(struct blame_scoreboard *sb, - struct blame_entry *ent) +static void emit_blame_entry_line_background(struct blame_scoreboard *sb, + struct blame_entry *ent) { - const char *cp, *cpend; + unsigned long line; + size_t len, maxlen = 2; + const char* pos, *endpos; - cp = blame_nth_line(sb, ent->lno); - cpend = blame_nth_line(sb, ent->lno + ent->num_lines); + for (line = ent->lno; line < ent->lno + ent->num_lines; line++) { + html("\n"); + pos = blame_nth_line(sb, line); + endpos = blame_nth_line(sb, line + 1); + len = 0; + while (pos < endpos) { + len++; + if (*pos++ == '\t') + len = (len + 7) & ~7; + } + if (len > maxlen) + maxlen = len; + } - html_ntxt(cp, cpend - cp); + for (len = 0; len < maxlen - 1; len++) + html(" "); } struct walk_tree_context { @@ -88,6 +102,7 @@ static void print_object(const unsigned char *sha1, const char *path, const char *basename, const char *rev) { enum object_type type; + char *buf; unsigned long size; struct argv_array rev_argv = ARGV_ARRAY_INIT; struct rev_info revs; @@ -102,6 +117,13 @@ static void print_object(const unsigned char *sha1, const char *path, return; } + buf = read_sha1_file(sha1, &type, &size); + if (!buf) { + cgit_print_error_page(500, "Internal server error", + "Error reading object %s", sha1_to_hex(sha1)); + return; + } + argv_array_push(&rev_argv, "blame"); argv_array_push(&rev_argv, rev); init_revisions(&revs, NULL); @@ -157,20 +179,37 @@ static void print_object(const unsigned char *sha1, const char *path, html("\n"); } - /* Lines */ - html(""); + html("
"); + + /* Colored bars behind lines */ + html("
"); for (ent = sb.ent; ent; ) { struct blame_entry *e = ent->next; - html("
");
-		emit_blame_entry_line(&sb, ent);
-		html("
"); + html("
");
+		emit_blame_entry_line_background(&sb, ent);
+		html("
"); free(ent); ent = e; } - html("\n"); + html("
"); free((void *)sb.final_buf); + /* Lines */ + html("
");
+	if (ctx.repo->source_filter) {
+		char *filter_arg = xstrdup(basename);
+		cgit_open_filter(ctx.repo->source_filter, filter_arg);
+		html_raw(buf, size);
+		cgit_close_filter(ctx.repo->source_filter);
+		free(filter_arg);
+	} else {
+		html_txt(buf);
+	}
+	html("
"); + + html("
\n"); + html("\n\n"); cgit_print_layout_end(); -- cgit v1.2.3-24-g4f1b