/* * The commit page, which shows one commit on its own and is where the log and * the ref listings link. It renders the idents, the object ids, the message, * the trailer table when the repository asks for one and any note, then hands * off to ui-diff.c for the diff against the first parent. Free text and * addresses are written through the repository's commit, trailer and email * filters, so a site can rewrite any of them on the way out. */ #define USE_THE_REPOSITORY_VARIABLE #include "cgit.h" #include "filter.h" #include "html.h" #include "parsing.h" #include "shared.h" #include "ui-commit.h" #include "ui-diff.h" #include "ui-log.h" #include "ui-shared.h" #include #include #include // 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. #define OCTOPUS_PARENTS 3 static void print_ident_row(const char *role, const char *name, const char *email, timestamp_t date, int tz) { htmlf("%s", role); cgit_open_filter(ctx.repo->email_filter, email, "commit"); html_txt(name); if (ctx.cfg.enable_plain_email) { html(" "); html_txt(email); } cgit_close_filter(ctx.repo->email_filter); html("\n"); } static int print_parent_rows(struct commit *commit, const char *rev, const char *prefix) { struct commit_list *p; struct commit *parent; const char *parent_hex, *label; int parents = 0; for (p = commit->parents; p; p = p->next) { parent = lookup_commit_reference(the_repository, &p->item->object.oid); if (!parent) { html(""); cgit_print_error("Error reading parent commit"); html("\n"); continue; } html("parent"); parent_hex = label = oid_to_hex(&p->item->object.oid); if (ctx.repo->enable_subject_links) label = cgit_parse_commit(parent)->subject; cgit_commit_link(label, NULL, NULL, ctx.qry.head, parent_hex, prefix); html(" ("); cgit_diff_link("diff", NULL, NULL, ctx.qry.head, rev, oid_to_hex(&p->item->object.oid), prefix); html(")\n"); parents++; } return parents; } static void print_filtered_text(const char *text) { cgit_open_filter(ctx.repo->commit_filter); html_txt(text); cgit_close_filter(ctx.repo->commit_filter); } /* * A value shaped like an ident is written the way the author row is, through * the mailmap and the email filter, and anything after the address, such as a * role in parentheses, follows as text. 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 string_list *map; struct strbuf email = STRBUF_INIT; const char *name, *mail, *rest; size_t name_len, mail_len; if (!split_ident_line(&ident, value, strlen(value)) && memchr(ident.mail_begin, '@', ident.mail_end - ident.mail_begin)) { name = ident.name_begin; name_len = ident.name_end - ident.name_begin; mail = ident.mail_begin; mail_len = ident.mail_end - ident.mail_begin; map = cgit_mailmap(); if (map) map_user(map, &mail, &mail_len, &name, &name_len); strbuf_addch(&email, '<'); strbuf_add(&email, mail, mail_len); strbuf_addch(&email, '>'); cgit_open_filter(ctx.repo->email_filter, email.buf, "commit"); html_ntxt(name, name_len); if (ctx.cfg.enable_plain_email) { html(" "); html_txt(email.buf); } cgit_close_filter(ctx.repo->email_filter); strbuf_release(&email); rest = ident.mail_end + 1; while (isspace((unsigned char)*rest)) rest++; if (*rest) { html(" "); html_txt(rest); } } 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, compared * without regard to case, becomes a single row listing every value. A line git * kept in the block without a value, such as a cherry-pick note or a key with * nothing after its colon, gets a row of its own as written. */ static void print_trailers(const char *msg) { struct trailer_iterator iter; struct strbuf key = STRBUF_INIT, line = STRBUF_INIT; int open = 0; html("\n"); trailer_iterator_init(&iter, msg); while (trailer_iterator_advance(&iter)) { if (!iter.val.len) { if (open) html("\n"); open = 0; strbuf_reset(&line); strbuf_addstr(&line, iter.raw); strbuf_rtrim(&line); html("\n"); continue; } if (open && !strcasecmp(key.buf, iter.key.buf)) html("
"); else { if (open) html("\n"); strbuf_reset(&key); strbuf_addbuf(&key, &iter.key); html("\n"); html("
"); print_filtered_text(line.buf); html("
"); html_txt(key.buf); html(""); open = 1; } print_trailer_value(key.buf, iter.val.buf); } if (open) html("
\n"); trailer_iterator_release(&iter); strbuf_release(&key); strbuf_release(&line); } /* * 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("
"); print_filtered_text(body ? body : info->msg); html("
\n"); if (body) print_trailers(full.buf); free(body); strbuf_release(&full); } void cgit_print_commit(char *hex, const char *prefix) { struct commit *commit; struct commitinfo *info; struct strbuf notes = STRBUF_INIT; struct object_id oid; const char *commit_hex, *first_parent; char *tree_rev; int parents; if (!hex) hex = ctx.qry.head; if (repo_get_oid(the_repository, hex, &oid)) { cgit_print_error_page(400, "Bad Request", "Bad object id: %s", hex); return; } commit = lookup_commit_reference(the_repository, &oid); if (!commit) { cgit_print_error_page(404, "Not Found", "Bad commit reference: %s", hex); return; } info = cgit_parse_commit(commit); format_display_notes(&oid, ¬es, PAGE_ENCODING, 1); load_ref_decorations(NULL, DECORATE_FULL_REFS); ctx.page.title = cgit_fmtalloc("%s - %s", info->subject, ctx.page.title); cgit_print_layout_start(); cgit_print_diff_ctrls(); html("\n"); print_ident_row("author", info->author, info->author_email, info->author_date, info->author_tz); print_ident_row("committer", info->committer, info->committer_email, info->committer_date, info->committer_tz); html("\n"); // The parent rows follow the commit row so the commit hashes sit // together, leaving the tree beside the download links. parents = print_parent_rows(commit, hex, prefix); html("\n"); if (ctx.repo->snapshots) { html("\n"); } html("
commit"); commit_hex = oid_to_hex(&commit->object.oid); cgit_commit_link(commit_hex, NULL, NULL, ctx.qry.head, commit_hex, prefix); html(" ("); cgit_patch_link("patch", NULL, NULL, NULL, commit_hex, prefix); html(")
tree"); tree_rev = xstrdup(hex); cgit_tree_link(oid_to_hex(get_commit_tree_oid(commit)), NULL, NULL, ctx.qry.head, tree_rev, NULL); if (prefix) { html(" /"); cgit_tree_link(prefix, NULL, NULL, ctx.qry.head, tree_rev, prefix); } free(tree_rev); html("
download"); cgit_print_snapshot_links(ctx.repo, hex, "
"); html("
\n"); html("
"); print_filtered_text(info->subject); cgit_print_commit_decorations(commit); html("
\n"); print_message(info); if (notes.len != 0) { html("
Notes
\n"); html("
"); print_filtered_text(notes.buf); html("
\n"); html("\n"); } if (parents < OCTOPUS_PARENTS) { if (parents) first_parent = oid_to_hex(&commit->parents->item->object.oid); else first_parent = NULL; cgit_print_diff(ctx.qry.oid, first_parent, prefix, 0, 0); } strbuf_release(¬es); cgit_free_commitinfo(info); cgit_print_layout_end(); }