From mboxrd@z Thu Jan 1 00:00:00 1970 From: john at keeping.me.uk (John Keeping) Date: Wed, 12 Aug 2015 14:03:34 +0100 Subject: [PATCH 1/3] ui-blame: add blame UI In-Reply-To: References: Message-ID: <6cc40c0b0e4036863f4472790683a2e1f98048a5.1439384224.git.john@keeping.me.uk> This is disabled by default and needs to be turned on in the config file. Because libgit.a does not include the blame implementation (which lives in git/builtin/blame.c), this is implemented by executing a git-blame subprocess and parsing its output. Given how expensive the blame operation itself is, the overhead of using a separate process should not be noticeable. --- cgit.c | 4 +- cgit.css | 4 ++ cgit.h | 3 + cgit.mk | 1 + cgitrc.5.txt | 4 ++ cmd.c | 7 +++ ui-blame.c | 202 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ ui-blame.h | 6 ++ 8 files changed, 230 insertions(+), 1 deletion(-) create mode 100644 ui-blame.c create mode 100644 ui-blame.h diff --git a/cgit.c b/cgit.c index ae413c6..fe952d4 100644 --- a/cgit.c +++ b/cgit.c @@ -150,6 +150,8 @@ static void config_cb(const char *name, const char *value) ctx.cfg.noheader = atoi(value); else if (!strcmp(name, "snapshots")) ctx.cfg.snapshots = cgit_parse_snapshots_mask(value); + else if (!strcmp(name, "enable-blame")) + ctx.cfg.enable_blame = atoi(value); else if (!strcmp(name, "enable-filter-overrides")) ctx.cfg.enable_filter_overrides = atoi(value); else if (!strcmp(name, "enable-http-clone")) @@ -707,7 +709,7 @@ static void process_request(void) } cmd = cgit_get_cmd(); - if (!cmd) { + if (!cmd || (!ctx.cfg.enable_blame && !strcmp(cmd->name, "blame"))) { ctx.page.title = "cgit error"; ctx.page.status = 404; ctx.page.statusmsg = "Not found"; diff --git a/cgit.css b/cgit.css index 82c755c..39b8277 100644 --- a/cgit.css +++ b/cgit.css @@ -280,6 +280,10 @@ div#cgit table.blob td.lines { color: black; } +div#cgit table.blob td.noprevious { + font-weight: bold; +} + div#cgit table.blob td.linenumbers { margin: 0; padding: 0 0.5em 0 0.5em; vertical-align: top; diff --git a/cgit.h b/cgit.h index 16f8092..4347046 100644 --- a/cgit.h +++ b/cgit.h @@ -16,12 +16,14 @@ #include #include #include +#include #include #include #include #include #include #include +#include /* @@ -220,6 +222,7 @@ struct cgit_config { int cache_snapshot_ttl; int case_sensitive_sort; int embedded; + int enable_blame; int enable_filter_overrides; int enable_http_clone; int enable_index_links; diff --git a/cgit.mk b/cgit.mk index 1b50307..c427229 100644 --- a/cgit.mk +++ b/cgit.mk @@ -75,6 +75,7 @@ CGIT_OBJ_NAMES += parsing.o CGIT_OBJ_NAMES += scan-tree.o CGIT_OBJ_NAMES += shared.o CGIT_OBJ_NAMES += ui-atom.o +CGIT_OBJ_NAMES += ui-blame.o CGIT_OBJ_NAMES += ui-blob.o CGIT_OBJ_NAMES += ui-clone.o CGIT_OBJ_NAMES += ui-commit.o diff --git a/cgitrc.5.txt b/cgitrc.5.txt index e21ece9..a5a54ec 100644 --- a/cgitrc.5.txt +++ b/cgitrc.5.txt @@ -146,6 +146,10 @@ enable-commit-graph:: history graph to the left of the commit messages in the repository log page. Default value: "0". +enable-blame:: + Flag which, when set to "1", enables the blame UI. Default value: + "0". + enable-filter-overrides:: Flag which, when set to "1", allows all filter settings to be overridden in repository-specific cgitrc files. Default value: none. diff --git a/cmd.c b/cmd.c index 188cd56..ca27e8d 100644 --- a/cmd.c +++ b/cmd.c @@ -11,6 +11,7 @@ #include "cache.h" #include "ui-shared.h" #include "ui-atom.h" +#include "ui-blame.h" #include "ui-blob.h" #include "ui-clone.h" #include "ui-commit.h" @@ -44,6 +45,11 @@ static void about_fn(void) cgit_print_site_readme(); } +static void blame_fn(void) +{ + cgit_print_blame(); +} + static void blob_fn(void) { cgit_print_blob(ctx.qry.sha1, ctx.qry.path, ctx.qry.head, 0); @@ -145,6 +151,7 @@ struct cgit_cmd *cgit_get_cmd(void) def_cmd(HEAD, 1, 0, 0, 1), def_cmd(atom, 1, 0, 0, 0), def_cmd(about, 0, 1, 0, 0), + def_cmd(blame, 1, 1, 0, 0), def_cmd(blob, 1, 0, 0, 0), def_cmd(commit, 1, 1, 1, 0), def_cmd(diff, 1, 1, 1, 0), diff --git a/ui-blame.c b/ui-blame.c new file mode 100644 index 0000000..34c836a --- /dev/null +++ b/ui-blame.c @@ -0,0 +1,202 @@ +/* ui-blame.c: functions for showing blame output + * + * Copyright (C) 2015 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" + +static void start_output(void) +{ + html(""); +} + +static void end_output(void) +{ + html("
"); +} + +static int parse_init_line(const char *line, unsigned char sha1[20], + unsigned long *orig_line, unsigned long *final_line, + unsigned long *count) +{ + char *end; + if (get_sha1_hex(line, sha1) < 0) + return -1; + + *orig_line = strtoul(line + 41, &end, 10); + /* We already have a count, so there isn't one on this line. */ + if (*count) + return 0; + + end++; + *final_line = strtoul(end, &end, 10); + + if (!*end) + return -1; + end++; + *count = strtoul(end, NULL, 10); + + return 0; +} + +static const char *author_abbrev(const char *author) +{ + static char buf[4]; + size_t idx = 0; + const char *c = strrchr(author, ' '); + buf[idx++] = *author; + if (c && c != author) + buf[idx++] = c[1]; + buf[idx] = '\0'; + return buf; +} + +static int process_blame(FILE *out) +{ + /* + * The format is: + * [] + *
+ * ... + * + * + * STATE_INIT means we expect a SHA1, otherwise it's a header or + * content depending on whether or not the line starts with . + */ + enum { + STATE_INIT, + STATE_HEADER, + } state = STATE_INIT; + int output_started = 0; + int first = 0; + int previous = 0; + unsigned long line = 1; + unsigned long orig_line = 0; + unsigned long final_line = 0; + unsigned long count = 0; + unsigned char sha1[20]; + struct strbuf buf = STRBUF_INIT; + struct strbuf author = STRBUF_INIT; + time_t author_time = 0; + int author_tz; + + while (strbuf_getline(&buf, out, '\n') != EOF) { + const char *value; + if (ferror(out)) + break; + + switch (state) { + case STATE_INIT: + first = !count; + previous = 0; + + if (parse_init_line(buf.buf, sha1, &orig_line, &final_line, &count) < 0) + goto err; + + if (!output_started) { + start_output(); + output_started = 1; + } + html(""); + state++; + break; + + case STATE_HEADER: + if (buf.buf[0] == '\t') { + if (first) { + char date_buf[64]; + strftime(date_buf, sizeof(date_buf) - 1, FMT_LONGDATE, gmtime(&author_time)); + htmlf("
");
+					cgit_commit_link(fmt("%s", find_unique_abbrev(sha1, DEFAULT_ABBREV)),
+						NULL, NULL, NULL, sha1_to_hex(sha1), ctx.qry.path);
+					if (count > 1) {
+						html("
"); + html_txt(author_abbrev(author.buf)); + } + html("
"); + first = 0; + } + htmlf("%lu
",
+						line, line, line);
+				html_txt(buf.buf + 1);
+				html("
"); + + line++; + count--; + state = STATE_INIT; + } else if (skip_prefix(buf.buf, "author ", &value)) { + strbuf_reset(&author); + strbuf_addstr(&author, value); + } else if (skip_prefix(buf.buf, "author-time ", &value)) { + author_time = (time_t) strtoul(value, NULL, 10); + } else if (skip_prefix(buf.buf, "author-tz ", &value)) { + strtol_i(value, 10, &author_tz); + } else if (skip_prefix(buf.buf, "previous ", &value)) { + previous++; + } + break; + } + } + + return output_started; +err: + return -1; +} + +void cgit_print_blame(void) +{ + FILE *out; + int output_started = 0; + struct child_process proc = CHILD_PROCESS_INIT; + + if (!ctx.qry.path) { + cgit_print_error("Bad request"); + return; + } + + argv_array_push(&proc.args, "blame"); + argv_array_push(&proc.args, "--line-porcelain"); + if (ctx.qry.ignorews) + argv_array_push(&proc.args, "-w"); + argv_array_push(&proc.args, ctx.qry.sha1 ? ctx.qry.sha1 : ctx.qry.head); + argv_array_push(&proc.args, "--"); + argv_array_push(&proc.args, ctx.qry.path); + + proc.out = -1; + proc.no_stdin = 1; + proc.no_stderr = 1; + proc.git_cmd = 1; + + if (start_command(&proc) < 0) + goto err; + + out = fdopen(proc.out, "r"); + if (!out) + goto err; + + output_started = process_blame(out); + + if (finish_command(&proc) != 0) { + if (!output_started) { + cgit_print_error("Not found"); + return; + } + /* What to do? - we started getting data then something went wrong. */ + } + + if (output_started) + end_output(); + + return; +err: + cgit_print_error("Internal server error"); +} diff --git a/ui-blame.h b/ui-blame.h new file mode 100644 index 0000000..5b97e03 --- /dev/null +++ b/ui-blame.h @@ -0,0 +1,6 @@ +#ifndef UI_BLAME_H +#define UI_BLAME_H + +extern void cgit_print_blame(void); + +#endif /* UI_BLAME_H */ -- 2.5.0.466.g9af26fa