1#include "cache.h"2#include "diff.h"3#include "commit.h"4#include "log-tree.h"5#include "reflog-walk.h"67struct decoration name_decoration = { "object names" };89static void show_parents(struct commit *commit, int abbrev)10{11struct commit_list *p;12for (p = commit->parents; p ; p = p->next) {13struct commit *parent = p->item;14printf(" %s", diff_unique_abbrev(parent->object.sha1, abbrev));15}16}1718void show_decorations(struct commit *commit)19{20const char *prefix;21struct name_decoration *decoration;2223decoration = lookup_decoration(&name_decoration, &commit->object);24if (!decoration)25return;26prefix = " (";27while (decoration) {28printf("%s%s", prefix, decoration->name);29prefix = ", ";30decoration = decoration->next;31}32putchar(')');33}3435/*36* Search for "^[-A-Za-z]+: [^@]+@" pattern. It usually matches37* Signed-off-by: and Acked-by: lines.38*/39static int detect_any_signoff(char *letter, int size)40{41char ch, *cp;42int seen_colon = 0;43int seen_at = 0;44int seen_name = 0;45int seen_head = 0;4647cp = letter + size;48while (letter <= --cp && (ch = *cp) == '\n')49continue;5051while (letter <= cp) {52ch = *cp--;53if (ch == '\n')54break;5556if (!seen_at) {57if (ch == '@')58seen_at = 1;59continue;60}61if (!seen_colon) {62if (ch == '@')63return 0;64else if (ch == ':')65seen_colon = 1;66else67seen_name = 1;68continue;69}70if (('A' <= ch && ch <= 'Z') ||71('a' <= ch && ch <= 'z') ||72ch == '-') {73seen_head = 1;74continue;75}76/* no empty last line doesn't match */77return 0;78}79return seen_head && seen_name;80}8182static void append_signoff(struct strbuf *sb, const char *signoff)83{84static const char signed_off_by[] = "Signed-off-by: ";85size_t signoff_len = strlen(signoff);86int has_signoff = 0;87char *cp;8889cp = sb->buf;9091/* First see if we already have the sign-off by the signer */92while ((cp = strstr(cp, signed_off_by))) {9394has_signoff = 1;9596cp += strlen(signed_off_by);97if (cp + signoff_len >= sb->buf + sb->len)98break;99if (strncmp(cp, signoff, signoff_len))100continue;101if (!isspace(cp[signoff_len]))102continue;103/* we already have him */104return;105}106107if (!has_signoff)108has_signoff = detect_any_signoff(sb->buf, sb->len);109110if (!has_signoff)111strbuf_addch(sb, '\n');112113strbuf_addstr(sb, signed_off_by);114strbuf_add(sb, signoff, signoff_len);115strbuf_addch(sb, '\n');116}117118static unsigned int digits_in_number(unsigned int number)119{120unsigned int i = 10, result = 1;121while (i <= number) {122i *= 10;123result++;124}125return result;126}127128static int has_non_ascii(const char *s)129{130int ch;131if (!s)132return 0;133while ((ch = *s++) != '\0') {134if (non_ascii(ch))135return 1;136}137return 0;138}139140void show_log(struct rev_info *opt, const char *sep)141{142struct strbuf msgbuf;143struct log_info *log = opt->loginfo;144struct commit *commit = log->commit, *parent = log->parent;145int abbrev = opt->diffopt.abbrev;146int abbrev_commit = opt->abbrev_commit ? opt->abbrev : 40;147const char *extra;148const char *subject = NULL, *extra_headers = opt->extra_headers;149150opt->loginfo = NULL;151if (!opt->verbose_header) {152if (opt->left_right) {153if (commit->object.flags & BOUNDARY)154putchar('-');155else if (commit->object.flags & SYMMETRIC_LEFT)156putchar('<');157else158putchar('>');159}160fputs(diff_unique_abbrev(commit->object.sha1, abbrev_commit), stdout);161if (opt->parents)162show_parents(commit, abbrev_commit);163show_decorations(commit);164putchar(opt->diffopt.line_termination);165return;166}167168/*169* The "oneline" format has several special cases:170* - The pretty-printed commit lacks a newline at the end171* of the buffer, but we do want to make sure that we172* have a newline there. If the separator isn't already173* a newline, add an extra one.174* - unlike other log messages, the one-line format does175* not have an empty line between entries.176*/177extra = "";178if (*sep != '\n' && opt->commit_format == CMIT_FMT_ONELINE)179extra = "\n";180if (opt->shown_one && opt->commit_format != CMIT_FMT_ONELINE)181putchar(opt->diffopt.line_termination);182opt->shown_one = 1;183184/*185* Print header line of header..186*/187188if (opt->commit_format == CMIT_FMT_EMAIL) {189char *sha1 = sha1_to_hex(commit->object.sha1);190if (opt->total > 0) {191static char buffer[64];192snprintf(buffer, sizeof(buffer),193"Subject: [%s %0*d/%d] ",194opt->subject_prefix,195digits_in_number(opt->total),196opt->nr, opt->total);197subject = buffer;198} else if (opt->total == 0 && opt->subject_prefix && *opt->subject_prefix) {199static char buffer[256];200snprintf(buffer, sizeof(buffer),201"Subject: [%s] ",202opt->subject_prefix);203subject = buffer;204} else {205subject = "Subject: ";206}207208printf("From %s Mon Sep 17 00:00:00 2001\n", sha1);209if (opt->message_id)210printf("Message-Id: <%s>\n", opt->message_id);211if (opt->ref_message_id)212printf("In-Reply-To: <%s>\nReferences: <%s>\n",213opt->ref_message_id, opt->ref_message_id);214if (opt->mime_boundary) {215static char subject_buffer[1024];216static char buffer[1024];217snprintf(subject_buffer, sizeof(subject_buffer) - 1,218"%s"219"MIME-Version: 1.0\n"220"Content-Type: multipart/mixed;"221" boundary=\"%s%s\"\n"222"\n"223"This is a multi-part message in MIME "224"format.\n"225"--%s%s\n"226"Content-Type: text/plain; "227"charset=UTF-8; format=fixed\n"228"Content-Transfer-Encoding: 8bit\n\n",229extra_headers ? extra_headers : "",230mime_boundary_leader, opt->mime_boundary,231mime_boundary_leader, opt->mime_boundary);232extra_headers = subject_buffer;233234snprintf(buffer, sizeof(buffer) - 1,235"--%s%s\n"236"Content-Type: text/x-patch;"237" name=\"%s.diff\"\n"238"Content-Transfer-Encoding: 8bit\n"239"Content-Disposition: %s;"240" filename=\"%s.diff\"\n\n",241mime_boundary_leader, opt->mime_boundary,242sha1,243opt->no_inline ? "attachment" : "inline",244sha1);245opt->diffopt.stat_sep = buffer;246}247} else if (opt->commit_format != CMIT_FMT_USERFORMAT) {248fputs(diff_get_color(opt->diffopt.color_diff, DIFF_COMMIT),249stdout);250if (opt->commit_format != CMIT_FMT_ONELINE)251fputs("commit ", stdout);252if (commit->object.flags & BOUNDARY)253putchar('-');254else if (opt->left_right) {255if (commit->object.flags & SYMMETRIC_LEFT)256putchar('<');257else258putchar('>');259}260fputs(diff_unique_abbrev(commit->object.sha1, abbrev_commit),261stdout);262if (opt->parents)263show_parents(commit, abbrev_commit);264if (parent)265printf(" (from %s)",266diff_unique_abbrev(parent->object.sha1,267abbrev_commit));268show_decorations(commit);269printf("%s",270diff_get_color(opt->diffopt.color_diff, DIFF_RESET));271putchar(opt->commit_format == CMIT_FMT_ONELINE ? ' ' : '\n');272if (opt->reflog_info) {273show_reflog_message(opt->reflog_info,274opt->commit_format == CMIT_FMT_ONELINE,275opt->date_mode);276if (opt->commit_format == CMIT_FMT_ONELINE) {277printf("%s", sep);278return;279}280}281}282283/*284* And then the pretty-printed message itself285*/286strbuf_init(&msgbuf, 0);287pretty_print_commit(opt->commit_format, commit, &msgbuf,288abbrev, subject, extra_headers, opt->date_mode,289has_non_ascii(opt->add_signoff));290291if (opt->add_signoff)292append_signoff(&msgbuf, opt->add_signoff);293if (opt->show_log_size)294printf("log size %i\n", (int)msgbuf.len);295296if (msgbuf.len)297printf("%s%s%s", msgbuf.buf, extra, sep);298strbuf_release(&msgbuf);299}300301int log_tree_diff_flush(struct rev_info *opt)302{303diffcore_std(&opt->diffopt);304305if (diff_queue_is_empty()) {306int saved_fmt = opt->diffopt.output_format;307opt->diffopt.output_format = DIFF_FORMAT_NO_OUTPUT;308diff_flush(&opt->diffopt);309opt->diffopt.output_format = saved_fmt;310return 0;311}312313if (opt->loginfo && !opt->no_commit_id) {314/* When showing a verbose header (i.e. log message),315* and not in --pretty=oneline format, we would want316* an extra newline between the end of log and the317* output for readability.318*/319show_log(opt, opt->diffopt.msg_sep);320if ((opt->diffopt.output_format & ~DIFF_FORMAT_NO_OUTPUT) &&321opt->verbose_header &&322opt->commit_format != CMIT_FMT_ONELINE) {323int pch = DIFF_FORMAT_DIFFSTAT | DIFF_FORMAT_PATCH;324if ((pch & opt->diffopt.output_format) == pch)325printf("---");326putchar('\n');327}328}329diff_flush(&opt->diffopt);330return 1;331}332333static int do_diff_combined(struct rev_info *opt, struct commit *commit)334{335unsigned const char *sha1 = commit->object.sha1;336337diff_tree_combined_merge(sha1, opt->dense_combined_merges, opt);338return !opt->loginfo;339}340341/*342* Show the diff of a commit.343*344* Return true if we printed any log info messages345*/346static int log_tree_diff(struct rev_info *opt, struct commit *commit, struct log_info *log)347{348int showed_log;349struct commit_list *parents;350unsigned const char *sha1 = commit->object.sha1;351352if (!opt->diff)353return 0;354355/* Root commit? */356parents = commit->parents;357if (!parents) {358if (opt->show_root_diff) {359diff_root_tree_sha1(sha1, "", &opt->diffopt);360log_tree_diff_flush(opt);361}362return !opt->loginfo;363}364365/* More than one parent? */366if (parents && parents->next) {367if (opt->ignore_merges)368return 0;369else if (opt->combine_merges)370return do_diff_combined(opt, commit);371372/* If we show individual diffs, show the parent info */373log->parent = parents->item;374}375376showed_log = 0;377for (;;) {378struct commit *parent = parents->item;379380diff_tree_sha1(parent->object.sha1, sha1, "", &opt->diffopt);381log_tree_diff_flush(opt);382383showed_log |= !opt->loginfo;384385/* Set up the log info for the next parent, if any.. */386parents = parents->next;387if (!parents)388break;389log->parent = parents->item;390opt->loginfo = log;391}392return showed_log;393}394395int log_tree_commit(struct rev_info *opt, struct commit *commit)396{397struct log_info log;398int shown;399400log.commit = commit;401log.parent = NULL;402opt->loginfo = &log;403404shown = log_tree_diff(opt, commit, &log);405if (!shown && opt->loginfo && opt->always_show_header) {406log.parent = NULL;407show_log(opt, "");408shown = 1;409}410opt->loginfo = NULL;411maybe_flush_or_die(stdout, "stdout");412return shown;413}