diff options
context:
space:
mode:
authorBryce Kwon <bryce@brycekwon.com>
committerBryce Kwon <bryce@brycekwon.com>
commit
parent
tree
download
Split trailers out of the commit message
Diffstat (limited to '')
-rw-r--r--source/ui-commit.c128
1 file changed, 125 insertions, 3 deletions
diff --git a/source/ui-commit.c b/source/ui-commit.c
index 6055509..b67f5db 100644
--- a/source/ui-commit.c
+++ b/source/ui-commit.c
@@ -18,6 +18,9 @@
#include "ui-log.h"
#include "ui-shared.h"
+#include <list.h>
+#include <trailer.h>
+
// The diff below the message is always taken against the first parent alone,
// which says little about a merge of many branches, so a commit with this
// many parents or more is shown without a diff at all.
@@ -76,6 +79,127 @@ static void print_filtered_text(const char *text)
cgit_close_filter(ctx.repo->commit_filter);
}
+/*
+ * A value shaped like an ident is written the way the author row is, through
+ * the email filter. Anything else goes through the trailer filter when one is
+ * set, and otherwise through the commit filter, so the links a site already
+ * draws in message text keep appearing on trailer lines.
+ */
+static void print_trailer_value(const char *key, const char *value)
+{
+ struct ident_split ident;
+ struct strbuf name = STRBUF_INIT, email = STRBUF_INIT;
+
+ if (!split_ident_line(&ident, value, strlen(value)) &&
+ memchr(ident.mail_begin, '@', ident.mail_end - ident.mail_begin)) {
+ strbuf_add(&name, ident.name_begin, ident.name_end - ident.name_begin);
+ strbuf_addch(&email, '<');
+ strbuf_add(&email, ident.mail_begin, ident.mail_end - ident.mail_begin);
+ strbuf_addch(&email, '>');
+ cgit_open_filter(ctx.repo->email_filter, email.buf, "commit");
+ html_txt(name.buf);
+ if (ctx.cfg.enable_plain_email) {
+ html(" ");
+ html_txt(email.buf);
+ }
+ cgit_close_filter(ctx.repo->email_filter);
+ strbuf_release(&name);
+ strbuf_release(&email);
+ } else if (ctx.repo->trailer_filter) {
+ cgit_open_filter(ctx.repo->trailer_filter, key, "commit");
+ html_txt(value);
+ cgit_close_filter(ctx.repo->trailer_filter);
+ } else
+ print_filtered_text(value);
+}
+
+/*
+ * One row per trailer, except that a run of trailers sharing a key becomes a
+ * single row listing every value. A line git kept in the block without being
+ * able to split it, such as a cherry-pick note, gets a row of its own.
+ */
+static void print_trailers(const char *msg)
+{
+ struct trailer_iterator iter;
+ struct strbuf key = STRBUF_INIT;
+ int open = 0;
+
+ html("<table class='commit-trailers'>\n");
+ trailer_iterator_init(&iter, msg);
+ while (trailer_iterator_advance(&iter)) {
+ if (!iter.val.len) {
+ if (open)
+ html("</td></tr>\n");
+ open = 0;
+ html("<tr><td colspan='2'>");
+ print_filtered_text(iter.key.buf);
+ html("</td></tr>\n");
+ continue;
+ }
+ if (open && !strcasecmp(key.buf, iter.key.buf))
+ html("<br>");
+ else {
+ if (open)
+ html("</td></tr>\n");
+ strbuf_reset(&key);
+ strbuf_addbuf(&key, &iter.key);
+ html("<tr><th>");
+ html_txt(key.buf);
+ html("</th><td>");
+ open = 1;
+ }
+ print_trailer_value(key.buf, iter.val.buf);
+ }
+ if (open)
+ html("</td></tr>\n");
+ html("</table>\n");
+ trailer_iterator_release(&iter);
+ strbuf_release(&key);
+}
+
+/*
+ * Git finds the trailer block by looking at the whole message, title included,
+ * so the subject goes back in front of the body before asking, and the offsets
+ * that come back are shifted by that prefix. The blank line that separated the
+ * body from its block is dropped along with the block.
+ */
+static void print_message(struct commitinfo *info)
+{
+ struct strbuf full = STRBUF_INIT;
+ struct process_trailer_options opts = PROCESS_TRAILER_OPTIONS_INIT;
+ struct trailer_block *block;
+ LIST_HEAD(items);
+ size_t prefix, start, end;
+ char *body = NULL;
+
+ if (ctx.repo->enable_trailers) {
+ strbuf_addf(&full, "%s\n\n%s", info->subject, info->msg);
+ prefix = full.len - strlen(info->msg);
+ opts.no_divider = 1;
+ block = parse_trailers(&opts, full.buf, &items);
+ start = trailer_block_start(block);
+ end = trailer_block_end(block);
+ free_trailers(&items);
+ trailer_block_release(block);
+ if (start >= prefix && start < end) {
+ size_t len = start - prefix;
+
+ while (len > 0 && info->msg[len - 1] == '\n')
+ len--;
+ body = xmemdupz(info->msg, len);
+ }
+ }
+
+ html("<div class='commit-msg'>");
+ print_filtered_text(body ? body : info->msg);
+ html("</div>\n");
+ if (body)
+ print_trailers(full.buf);
+
+ free(body);
+ strbuf_release(&full);
+}
+
void cgit_print_commit(char *hex, const char *prefix)
{
struct commit *commit;
@@ -145,9 +269,7 @@ void cgit_print_commit(char *hex, const char *prefix)
print_filtered_text(info->subject);
cgit_print_commit_decorations(commit);
html("</div>\n");
- html("<div class='commit-msg'>");
- print_filtered_text(info->msg);
- html("</div>\n");
+ print_message(info);
if (notes.len != 0) {
html("<div class='notes-header'>Notes</div>\n");
html("<div class='notes'>");