Author: Lars Hjemli <hjemli@gmail.com>
Merge branch 'jh/graph' * jh/graph: ui-log: Move 'Age' column when commit graph is present ui-log: Line-wrap long commit subjects when showmsg is enabled ui-log: Colorize commit graph ui-log: Implement support for commit graphs ui-log: Change display of full commit messages (and notes) Conflicts: cgit.css
cgit.c | 6 + cgit.css | 40 ++++++++--- cgit.h | 3 cgitrc.5.txt | 15 +++ cmd.c | 3 shared.c | 1 ui-log.c | 195 ++++++++++++++++++++++++++++++++++++++++++++--------- ui-log.h | 3 ui-summary.c | 2
diff --git a/cgit.c b/cgit.c index 412fbf0c32c4c0d2be3b0973ae5b5804c85059d6..53ab68da2d7ff8f8b54deafcfdb3de47cf0c89b0 100644 --- a/cgit.c +++ b/cgit.c @@ -57,6 +57,8 @@ else if (!strcmp(name, "defbranch")) repo->defbranch = xstrdup(value); else if (!strcmp(name, "snapshots")) repo->snapshots = ctx.cfg.snapshots & cgit_parse_snapshots_mask(value); + else if (!strcmp(name, "enable-commit-graph")) + repo->enable_commit_graph = ctx.cfg.enable_commit_graph * atoi(value); else if (!strcmp(name, "enable-log-filecount")) repo->enable_log_filecount = ctx.cfg.enable_log_filecount * atoi(value); else if (!strcmp(name, "enable-log-linecount")) @@ -141,6 +143,8 @@ else if (!strcmp(name, "enable-gitweb-owner")) ctx.cfg.enable_gitweb_owner = atoi(value); else if (!strcmp(name, "enable-index-links")) ctx.cfg.enable_index_links = atoi(value); + else if (!strcmp(name, "enable-commit-graph")) + ctx.cfg.enable_commit_graph = atoi(value); else if (!strcmp(name, "enable-log-filecount")) ctx.cfg.enable_log_filecount = atoi(value); else if (!strcmp(name, "enable-log-linecount")) @@ -540,6 +544,8 @@ if (repo->section) fprintf(f, "repo.section=%s\n", repo->section); if (repo->clone_url) fprintf(f, "repo.clone-url=%s\n", repo->clone_url); + fprintf(f, "repo.enable-commit-graph=%d\n", + repo->enable_commit_graph); fprintf(f, "repo.enable-log-filecount=%d\n", repo->enable_log_filecount); fprintf(f, "repo.enable-log-linecount=%d\n", diff --git a/cgit.css b/cgit.css index a2a685b412d83a2672cb9c90c1b6efe561390b3d..008cff8e0c766fa4bf85a6f7fbaee7e644335b60 100644 --- a/cgit.css +++ b/cgit.css @@ -153,26 +153,44 @@ border: none; padding: 0.1em 0.5em 0.1em 0.5em; } -table.list td.logsubject { +table.list td.commitgraph { font-family: monospace; - font-weight: bold; + white-space: pre; } -table.list td.logmsg { - font-family: monospace; - white-space: pre; - padding: 1em 0.5em 2em 0.5em; +table.list td.commitgraph .column1 { + color: #a00; } -table.list td.lognotes-label { - text-align:right; - vertical-align:top; +table.list td.commitgraph .column2 { + color: #0a0; } -table.list td.lognotes { +table.list td.commitgraph .column3 { + color: #aa0; +} + +table.list td.commitgraph .column4 { + color: #00a; +} + +table.list td.commitgraph .column5 { + color: #a0a; +} + +table.list td.commitgraph .column6 { + color: #0aa; +} + +table.list td.logsubject { + font-family: monospace; + font-weight: bold; +} + +table.list td.logmsg { font-family: monospace; white-space: pre; - padding: 0em 0.5em 2em 0.5em; + padding: 0 0.5em; } table.list td a { diff --git a/cgit.h b/cgit.h index f5f68ac74c76d33110354645aeba464827dec736..bed770b11baffb6f5927ec3f3629fa7bf960088d 100644 --- a/cgit.h +++ b/cgit.h @@ -20,6 +20,7 @@ #include#include <xdiff/xdiff.h> #include <utf8.h> #include <notes.h> +#include <graph.h> /* @@ -71,6 +72,7 @@ char *readme; char *section; char *clone_url; int snapshots; + int enable_commit_graph; int enable_log_filecount; int enable_log_linecount; int enable_remote_branches; @@ -188,6 +190,7 @@ int embedded; int enable_filter_overrides; int enable_gitweb_owner; int enable_index_links; + int enable_commit_graph; int enable_log_filecount; int enable_log_linecount; int enable_remote_branches; diff --git a/cgitrc.5.txt b/cgitrc.5.txt index 8e51ca5766945c68c23b1f988062b865cf46397a..3c20fe1a075f88da8c51a5ab790d2ce20c43d02f 100644 --- a/cgitrc.5.txt +++ b/cgitrc.5.txt @@ -90,7 +90,12 @@ embedded:: Flag which, when set to "1", will make cgit generate a html fragment suitable for embedding in other html pages. Default value: none. See also: "noheader". - + +enable-commit-graph:: + Flag which, when set to "1", will make cgit print an ASCII-art commit + history graph to the left of the commit messages in the repository + log page. 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. @@ -354,6 +359,10 @@ repo.desc:: The value to show as repository description. Default value: none. +repo.enable-commit-graph:: + A flag which can be used to disable the global setting + `enable-commit-graph'. Default value: none. + repo.enable-log-filecount:: A flag which can be used to disable the global setting `enable-log-filecount'. Default value: none. @@ -439,6 +448,10 @@ # Show extra links for each repository on the index page enable-index-links=1 + + +# Enable ASCII art commit history graph on the log pages +enable-commit-graph=1 # Show number of affected files per commit on the log pages diff --git a/cmd.c b/cmd.c index 6dc9f5ea0757ff38d7bad5981279ca7ade2eec48..536515b7bb135dafefc5b5ed4e55c0dc7d2eb98a 100644 --- a/cmd.c +++ b/cmd.c @@ -67,7 +67,8 @@ static void log_fn(struct cgit_context *ctx) { cgit_print_log(ctx->qry.sha1, ctx->qry.ofs, ctx->cfg.max_commit_count, - ctx->qry.grep, ctx->qry.search, ctx->qry.path, 1); + ctx->qry.grep, ctx->qry.search, ctx->qry.path, 1, + ctx->repo->enable_commit_graph); } static void ls_cache_fn(struct cgit_context *ctx) diff --git a/shared.c b/shared.c index 765cd276dd9b0eb947bed9820793d98a18a45a16..7ec2e19296e2bfbc7226849163b8bdb05bb56e94 100644 --- a/shared.c +++ b/shared.c @@ -56,6 +56,7 @@ ret->owner = NULL; ret->section = ctx.cfg.section; ret->defbranch = "master"; ret->snapshots = ctx.cfg.snapshots; + ret->enable_commit_graph = ctx.cfg.enable_commit_graph; ret->enable_log_filecount = ctx.cfg.enable_log_filecount; ret->enable_log_linecount = ctx.cfg.enable_log_linecount; ret->enable_remote_branches = ctx.cfg.enable_remote_branches; diff --git a/ui-log.c b/ui-log.c index 27f5a1a111cef792b45b9bb35b399a25c53e1a58..8add66aa0f41c16081a2ead29ba2c19a8f41ace5 100644 --- a/ui-log.c +++ b/ui-log.c @@ -13,6 +13,21 @@ #include "vector.h" int files, add_lines, rem_lines; +/* + * The list of available column colors in the commit graph. + */ +static const char *column_colors_html[] = { + "<span class='column1'>", + "<span class='column2'>", + "<span class='column3'>", + "<span class='column4'>", + "<span class='column5'>", + "<span class='column6'>", + "</span>", +}; + +#define COLUMN_COLORS_HTML_MAX (ARRAY_SIZE(column_colors_html) - 1) + void count_lines(char *line, int size) { if (size <= 0) @@ -77,27 +92,93 @@ deco = deco->next; } } -void print_commit(struct commit *commit) +void print_commit(struct commit *commit, struct rev_info *revs) { struct commitinfo *info; char *tmp; - int cols = 2; + int cols = revs->graph ? 3 : 2; + struct strbuf graphbuf = STRBUF_INIT; + struct strbuf msgbuf = STRBUF_INIT; + + if (ctx.repo->enable_log_filecount) { + cols++; + if (ctx.repo->enable_log_linecount) + cols++; + } + + if (revs->graph) { + /* Advance graph until current commit */ + while (!graph_next_line(revs->graph, &graphbuf)) { + /* Print graph segment in otherwise empty table row */ + html("<tr class='nohover'><td class='commitgraph'>"); + html(graphbuf.buf); + htmlf("</td><td colspan='%d' /></tr>\n", cols); + strbuf_setlen(&graphbuf, 0); + } + /* Current commit's graph segment is now ready in graphbuf */ + } info = cgit_parse_commit(commit); - htmlf("<tr%s><td>", - ctx.qry.showmsg ? " class='logheader'" : ""); - tmp = fmt("id=%s", sha1_to_hex(commit->object.sha1)); - tmp = cgit_fileurl(ctx.repo->url, "commit", ctx.qry.vpath, tmp); - html_link_open(tmp, NULL, NULL); - cgit_print_age(commit->date, TM_WEEK * 2, FMT_SHORTDATE); - html_link_close(); - htmlf("</td><td%s>", - ctx.qry.showmsg ? " class='logsubject'" : ""); + htmlf("<tr%s>", ctx.qry.showmsg ? " class='logheader'" : ""); + + if (revs->graph) { + /* Print graph segment for current commit */ + html("<td class='commitgraph'>"); + html(graphbuf.buf); + html("</td>"); + strbuf_setlen(&graphbuf, 0); + } + else { + html("<td>"); + tmp = fmt("id=%s", sha1_to_hex(commit->object.sha1)); + tmp = cgit_fileurl(ctx.repo->url, "commit", ctx.qry.vpath, tmp); + html_link_open(tmp, NULL, NULL); + cgit_print_age(commit->date, TM_WEEK * 2, FMT_SHORTDATE); + html_link_close(); + html("</td>"); + } + + htmlf("<td%s>", ctx.qry.showmsg ? " class='logsubject'" : ""); + if (ctx.qry.showmsg) { + /* line-wrap long commit subjects instead of truncating them */ + size_t subject_len = strlen(info->subject); + + if (subject_len > ctx.cfg.max_msg_len && + ctx.cfg.max_msg_len >= 15) { + /* symbol for signaling line-wrap (in PAGE_ENCODING) */ + const char wrap_symbol[] = { ' ', 0xE2, 0x86, 0xB5, 0 }; + int i = ctx.cfg.max_msg_len - strlen(wrap_symbol); + + /* Rewind i to preceding space character */ + while (i > 0 && !isspace(info->subject[i])) + --i; + if (!i) /* Oops, zero spaces. Reset i */ + i = ctx.cfg.max_msg_len - strlen(wrap_symbol); + + /* add remainder starting at i to msgbuf */ + strbuf_add(&msgbuf, info->subject + i, subject_len - i); + strbuf_trim(&msgbuf); + strbuf_add(&msgbuf, "\n\n", 2); + + /* Place wrap_symbol at position i in info->subject */ + strcpy(info->subject + i, wrap_symbol); + } + } cgit_commit_link(info->subject, NULL, NULL, ctx.qry.head, sha1_to_hex(commit->object.sha1), ctx.qry.vpath, 0); show_commit_decorations(commit); html("</td><td>"); html_txt(info->author); + + if (revs->graph) { + html("</td><td>"); + tmp = fmt("id=%s", sha1_to_hex(commit->object.sha1)); + tmp = cgit_fileurl(ctx.repo->url, "commit", ctx.qry.vpath, tmp); + html_link_open(tmp, NULL, NULL); + cgit_print_age(commit->date, TM_WEEK * 2, FMT_SHORTDATE); + html_link_close(); + } + if (ctx.repo->enable_log_filecount) { files = 0; add_lines = 0; @@ -111,29 +192,61 @@ htmlf("-%d/+%d", rem_lines, add_lines); } } html("</td></tr>\n"); - if (ctx.qry.showmsg) { - struct strbuf notes = STRBUF_INIT; - format_note(NULL, commit->object.sha1, ¬es, PAGE_ENCODING, 0); + + if (revs->graph || ctx.qry.showmsg) { /* Print a second table row */ + html("<tr class='nohover'>"); + + if (ctx.qry.showmsg) { + /* Concatenate commit message + notes in msgbuf */ + if (info->msg && *(info->msg)) { + strbuf_addstr(&msgbuf, info->msg); + strbuf_addch(&msgbuf, '\n'); + } + format_note(NULL, commit->object.sha1, &msgbuf, + PAGE_ENCODING, + NOTES_SHOW_HEADER | NOTES_INDENT); + strbuf_addch(&msgbuf, '\n'); + strbuf_ltrim(&msgbuf); + } + + if (revs->graph) { + int lines = 0; + + /* Calculate graph padding */ + if (ctx.qry.showmsg) { + /* Count #lines in commit message + notes */ + const char *p = msgbuf.buf; + lines = 1; + while ((p = strchr(p, '\n'))) { + p++; + lines++; + } + } - if (ctx.repo->enable_log_filecount) { - cols++; - if (ctx.repo->enable_log_linecount) - cols++; + /* Print graph padding */ + html("<td class='commitgraph'>"); + while (lines > 0 || !graph_is_commit_finished(revs->graph)) { + if (graphbuf.len) + html("\n"); + strbuf_setlen(&graphbuf, 0); + graph_next_line(revs->graph, &graphbuf); + html(graphbuf.buf); + lines--; + } + html("</td>\n"); } - htmlf("<tr class='nohover'><td/><td colspan='%d' class='logmsg'>", - cols); - html_txt(info->msg); + else + html("<td/>"); /* Empty 'Age' column */ + + /* Print msgbuf into remainder of table row */ + htmlf("<td colspan='%d'%s>\n", cols, + ctx.qry.showmsg ? " class='logmsg'" : ""); + html_txt(msgbuf.buf); html("</td></tr>\n"); - if (notes.len != 0) { - html("<tr class='nohover'>"); - html("<td class='lognotes-label'>Notes:</td>"); - htmlf("<td colspan='%d' class='lognotes'>", - cols); - html_txt(notes.buf); - html("</td></tr>\n"); - } - strbuf_release(¬es); } + + strbuf_release(&msgbuf); + strbuf_release(&graphbuf); cgit_free_commitinfo(info); } @@ -172,7 +285,7 @@ return result; } void cgit_print_log(const char *tip, int ofs, int cnt, char *grep, char *pattern, - char *path, int pager) + char *path, int pager, int commit_graph) { struct rev_info rev; struct commit *commit; @@ -211,6 +324,14 @@ } vector_push(&vec, &arg, 0); } } + } + if (commit_graph) { + static const char *graph_arg = "--graph"; + static const char *color_arg = "--color"; + vector_push(&vec, &graph_arg, 0); + vector_push(&vec, &color_arg, 0); + graph_set_column_colors(column_colors_html, + COLUMN_COLORS_HTML_MAX); } if (path) { @@ -238,8 +359,12 @@ if (pager) html("<table class='list nowrap'>"); - html("<tr class='nohover'><th class='left'>Age</th>" - "<th class='left'>Commit message"); + html("<tr class='nohover'>"); + if (commit_graph) + html("<th></th>"); + else + html("<th class='left'>Age</th>"); + html("<th class='left'>Commit message"); if (pager) { html(" ("); cgit_log_link(ctx.qry.showmsg ? "Collapse" : "Expand", NULL, @@ -249,6 +374,8 @@ ctx.qry.search, ctx.qry.showmsg ? 0 : 1); html(")"); } html("</th><th class='left'>Author</th>"); + if (commit_graph) + html("<th class='left'>Age</th>"); if (ctx.repo->enable_log_filecount) { html("<th class='left'>Files</th>"); columns++; @@ -270,7 +397,7 @@ commit->parents = NULL; } for (i = 0; i < cnt && (commit = get_revision(&rev)) != NULL; i++) { - print_commit(commit); + print_commit(commit, &rev); free(commit->buffer); commit->buffer = NULL; free_commit_list(commit->parents); diff --git a/ui-log.h b/ui-log.h index 603405569e90de064d5f70c5aa1454952aedde9d..d0cb7790688b2a16cc51284f434f296d6a43ceb3 100644 --- a/ui-log.h +++ b/ui-log.h @@ -2,7 +2,8 @@ #ifndef UI_LOG_H #define UI_LOG_H extern void cgit_print_log(const char *tip, int ofs, int cnt, char *grep, - char *pattern, char *path, int pager); + char *pattern, char *path, int pager, + int commit_graph); extern void show_commit_decorations(struct commit *commit); #endif /* UI_LOG_H */ diff --git a/ui-summary.c b/ui-summary.c index b203bccfabeab67d3e4043d1b26344775aca419c..5be2545c398ca7f7ac715991e58b90b52f32e636 100644 --- a/ui-summary.c +++ b/ui-summary.c @@ -59,7 +59,7 @@ cgit_print_tags(ctx.cfg.summary_tags); if (ctx.cfg.summary_log > 0) { html("<tr class='nohover'><td colspan='4'> </td></tr>"); cgit_print_log(ctx.qry.head, 0, ctx.cfg.summary_log, NULL, - NULL, NULL, 0); + NULL, NULL, 0, 0); } if (ctx.repo->clone_url) print_urls(ctx.repo->clone_url, NULL);