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/cgit.c16
-rw-r--r--source/cgit.h4
-rw-r--r--source/filter.c3
-rw-r--r--source/filter.h2
-rw-r--r--source/shared.c2
-rw-r--r--source/ui-commit.c128
6 files changed, 149 insertions, 6 deletions
diff --git a/source/cgit.c b/source/cgit.c
index e6e8613..ae91dd2 100644
--- a/source/cgit.c
+++ b/source/cgit.c
@@ -247,6 +247,8 @@ static void print_repo(FILE *f, struct cgit_repo *repo)
cgit_fprintf_filter(repo->source_filter, f, "repo.source-filter=");
if (repo->email_filter && repo->email_filter != ctx.cfg.email_filter)
cgit_fprintf_filter(repo->email_filter, f, "repo.email-filter=");
+ if (repo->trailer_filter && repo->trailer_filter != ctx.cfg.trailer_filter)
+ cgit_fprintf_filter(repo->trailer_filter, f, "repo.trailer-filter=");
if (repo->snapshots != ctx.cfg.snapshots) {
char *formats = build_snapshot_setting(repo->snapshots);
fprintf(f, "repo.snapshots=%s\n", formats ? formats : "");
@@ -262,6 +264,7 @@ static void print_repo(FILE *f, struct cgit_repo *repo)
fprintf(f, "repo.logo-link=%s\n", repo->logo_link);
fprintf(f, "repo.enable-remote-branches=%d\n", repo->enable_remote_branches);
fprintf(f, "repo.enable-subject-links=%d\n", repo->enable_subject_links);
+ fprintf(f, "repo.enable-trailers=%d\n", repo->enable_trailers);
fprintf(f, "repo.enable-html-serving=%d\n", repo->enable_html_serving);
if (repo->branch_sort == 1)
fprintf(f, "repo.branch-sort=age\n");
@@ -560,6 +563,8 @@ static void apply_config(const char *name, const char *value)
ctx.cfg.enable_remote_branches = atoi(value);
else if (!strcmp(name, "enable-subject-links"))
ctx.cfg.enable_subject_links = atoi(value);
+ else if (!strcmp(name, "enable-trailers"))
+ ctx.cfg.enable_trailers = atoi(value);
else if (!strcmp(name, "enable-html-serving"))
ctx.cfg.enable_html_serving = atoi(value);
else if (!strcmp(name, "enable-tree-linenumbers"))
@@ -598,6 +603,8 @@ static void apply_config(const char *name, const char *value)
ctx.cfg.commit_filter = cgit_new_filter(value, COMMIT);
else if (!strcmp(name, "email-filter"))
ctx.cfg.email_filter = cgit_new_filter(value, EMAIL);
+ else if (!strcmp(name, "trailer-filter"))
+ ctx.cfg.trailer_filter = cgit_new_filter(value, TRAILER);
else if (!strcmp(name, "auth-filter"))
ctx.cfg.auth_filter = cgit_new_filter(value, AUTH);
else if (!strcmp(name, "embedded"))
@@ -1165,6 +1172,8 @@ void cgit_repo_config(struct cgit_repo *repo, const char *name, const char *valu
repo->enable_remote_branches = atoi(value);
else if (!strcmp(name, "enable-subject-links"))
repo->enable_subject_links = atoi(value);
+ else if (!strcmp(name, "enable-trailers"))
+ repo->enable_trailers = atoi(value);
else if (!strcmp(name, "enable-html-serving"))
repo->enable_html_serving = atoi(value);
else if (!strcmp(name, "branch-sort")) {
@@ -1204,7 +1213,8 @@ void cgit_repo_config(struct cgit_repo *repo, const char *name, const char *valu
!strcmp(name, "about-filter") ||
!strcmp(name, "commit-filter") ||
!strcmp(name, "source-filter") ||
- !strcmp(name, "email-filter")
+ !strcmp(name, "email-filter") ||
+ !strcmp(name, "trailer-filter")
) {
if (!ctx.cfg.enable_filter_overrides)
fprintf(stderr, "[cgit] Ignoring repo %s: enable-filter-overrides is not set\n", name);
@@ -1214,8 +1224,10 @@ void cgit_repo_config(struct cgit_repo *repo, const char *name, const char *valu
repo->commit_filter = cgit_new_filter(value, COMMIT);
else if (!strcmp(name, "source-filter"))
repo->source_filter = cgit_new_filter(value, SOURCE);
- else
+ else if (!strcmp(name, "email-filter"))
repo->email_filter = cgit_new_filter(value, EMAIL);
+ else
+ repo->trailer_filter = cgit_new_filter(value, TRAILER);
} else
fprintf(stderr, "[cgit] Unknown repo config key: %s\n", name);
}
diff --git a/source/cgit.h b/source/cgit.h
index cc4ac02..d976906 100644
--- a/source/cgit.h
+++ b/source/cgit.h
@@ -90,6 +90,7 @@ struct cgit_repo {
int enable_log_linecount;
int enable_remote_branches;
int enable_subject_links;
+ int enable_trailers;
int enable_html_serving;
int max_stats;
int branch_sort;
@@ -99,6 +100,7 @@ struct cgit_repo {
struct cgit_filter *commit_filter;
struct cgit_filter *source_filter;
struct cgit_filter *email_filter;
+ struct cgit_filter *trailer_filter;
struct string_list submodules;
int hide;
int ignore;
@@ -228,6 +230,7 @@ struct cgit_config {
int enable_relative_dates;
int enable_remote_branches;
int enable_subject_links;
+ int enable_trailers;
int enable_html_serving;
int enable_tree_linenumbers;
int enable_tree_group_dirs;
@@ -263,6 +266,7 @@ struct cgit_config {
struct cgit_filter *commit_filter;
struct cgit_filter *source_filter;
struct cgit_filter *email_filter;
+ struct cgit_filter *trailer_filter;
struct cgit_filter *auth_filter;
};
diff --git a/source/filter.c b/source/filter.c
index 2731d58..81c4681 100644
--- a/source/filter.c
+++ b/source/filter.c
@@ -395,12 +395,14 @@ void cgit_cleanup_filters(void)
cleanup_filter(ctx.cfg.commit_filter);
cleanup_filter(ctx.cfg.source_filter);
cleanup_filter(ctx.cfg.email_filter);
+ cleanup_filter(ctx.cfg.trailer_filter);
cleanup_filter(ctx.cfg.auth_filter);
for (i = 0; i < cgit_repolist.count; ++i) {
cleanup_filter(cgit_repolist.repos[i].about_filter);
cleanup_filter(cgit_repolist.repos[i].commit_filter);
cleanup_filter(cgit_repolist.repos[i].source_filter);
cleanup_filter(cgit_repolist.repos[i].email_filter);
+ cleanup_filter(cgit_repolist.repos[i].trailer_filter);
}
}
@@ -438,6 +440,7 @@ struct cgit_filter *cgit_new_filter(const char *cmd, filter_type filtertype)
argument_count = 12;
break;
case EMAIL:
+ case TRAILER:
argument_count = 2;
break;
case SOURCE:
diff --git a/source/filter.h b/source/filter.h
index 861578c..fa43416 100644
--- a/source/filter.h
+++ b/source/filter.h
@@ -13,7 +13,7 @@
#include <stdio.h>
typedef enum {
- ABOUT, COMMIT, SOURCE, EMAIL, AUTH
+ ABOUT, COMMIT, SOURCE, EMAIL, TRAILER, AUTH
} filter_type;
struct cgit_filter {
diff --git a/source/shared.c b/source/shared.c
index beda48b..59712ce 100644
--- a/source/shared.c
+++ b/source/shared.c
@@ -215,6 +215,7 @@ struct cgit_repo *cgit_add_repo(const char *url)
repo->enable_log_linecount = ctx.cfg.enable_log_linecount;
repo->enable_remote_branches = ctx.cfg.enable_remote_branches;
repo->enable_subject_links = ctx.cfg.enable_subject_links;
+ repo->enable_trailers = ctx.cfg.enable_trailers;
repo->enable_html_serving = ctx.cfg.enable_html_serving;
repo->max_stats = ctx.cfg.max_stats;
repo->branch_sort = ctx.cfg.branch_sort;
@@ -226,6 +227,7 @@ struct cgit_repo *cgit_add_repo(const char *url)
repo->commit_filter = ctx.cfg.commit_filter;
repo->source_filter = ctx.cfg.source_filter;
repo->email_filter = ctx.cfg.email_filter;
+ repo->trailer_filter = ctx.cfg.trailer_filter;
repo->clone_url = ctx.cfg.clone_url;
repo->submodules.strdup_strings = 1;
repo->hide = repo->ignore = 0;
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'>");