#include "cache.h"
+#include "config.h"
#include "commit.h"
#include "utf8.h"
#include "diff.h"
#include "color.h"
#include "reflog-walk.h"
#include "gpg-interface.h"
+#include "trailer.h"
static char *user_format;
static struct cmt_fmt_map {
const char *name;
enum cmit_fmt format;
int is_tformat;
+ int expand_tabs_in_log;
int is_alias;
const char *user_format;
} *commit_formats;
static void setup_commit_formats(void)
{
struct cmt_fmt_map builtin_formats[] = {
- { "raw", CMIT_FMT_RAW, 0 },
- { "medium", CMIT_FMT_MEDIUM, 0 },
- { "short", CMIT_FMT_SHORT, 0 },
- { "email", CMIT_FMT_EMAIL, 0 },
- { "fuller", CMIT_FMT_FULLER, 0 },
- { "full", CMIT_FMT_FULL, 0 },
- { "oneline", CMIT_FMT_ONELINE, 1 }
+ { "raw", CMIT_FMT_RAW, 0, 0 },
+ { "medium", CMIT_FMT_MEDIUM, 0, 8 },
+ { "short", CMIT_FMT_SHORT, 0, 0 },
+ { "email", CMIT_FMT_EMAIL, 0, 0 },
+ { "mboxrd", CMIT_FMT_MBOXRD, 0, 0 },
+ { "fuller", CMIT_FMT_FULLER, 0, 8 },
+ { "full", CMIT_FMT_FULL, 0, 8 },
+ { "oneline", CMIT_FMT_ONELINE, 1, 0 }
};
commit_formats_len = ARRAY_SIZE(builtin_formats);
builtin_formats_len = commit_formats_len;
rev->commit_format = commit_format->format;
rev->use_terminator = commit_format->is_tformat;
+ rev->expand_tabs_in_log_default = commit_format->expand_tabs_in_log;
if (commit_format->format == CMIT_FMT_USERFORMAT) {
save_user_format(rev, commit_format->user_format,
commit_format->is_tformat);
const char *show_ident_date(const struct ident_split *ident,
const struct date_mode *mode)
{
- unsigned long date = 0;
+ timestamp_t date = 0;
long tz = 0;
if (ident->date_begin && ident->date_end)
- date = strtoul(ident->date_begin, NULL, 10);
+ date = parse_timestamp(ident->date_begin, NULL, 10);
if (date_overflows(date))
date = 0;
else {
if (pp->mailmap)
map_user(pp->mailmap, &mailbuf, &maillen, &namebuf, &namelen);
- if (pp->fmt == CMIT_FMT_EMAIL) {
+ if (cmit_fmt_is_mail(pp->fmt)) {
if (pp->from_ident && ident_cmp(pp->from_ident, &ident)) {
struct strbuf buf = STRBUF_INIT;
show_ident_date(&ident, &pp->date_mode));
break;
case CMIT_FMT_EMAIL:
+ case CMIT_FMT_MBOXRD:
strbuf_addf(sb, "Date: %s\n",
show_ident_date(&ident, DATE_MODE(RFC2822)));
break;
}
}
-static int is_empty_line(const char *line, int *len_p)
+static int is_blank_line(const char *line, int *len_p)
{
int len = *len_p;
while (len && isspace(line[len - 1]))
return !len;
}
-static const char *skip_empty_lines(const char *msg)
+const char *skip_blank_lines(const char *msg)
{
for (;;) {
int linelen = get_one_line(msg);
int ll = linelen;
if (!linelen)
break;
- if (!is_empty_line(msg, &ll))
+ if (!is_blank_line(msg, &ll))
break;
msg += linelen;
}
{
struct commit_list *parent = commit->parents;
- if ((pp->fmt == CMIT_FMT_ONELINE) || (pp->fmt == CMIT_FMT_EMAIL) ||
+ if ((pp->fmt == CMIT_FMT_ONELINE) || (cmit_fmt_is_mail(pp->fmt)) ||
!parent || !parent->next)
return;
strbuf_addstr(sb, "Merge:");
while (parent) {
- struct commit *p = parent->item;
- const char *hex = NULL;
+ struct object_id *oidp = &parent->item->object.oid;
+ strbuf_addch(sb, ' ');
if (pp->abbrev)
- hex = find_unique_abbrev(p->object.sha1, pp->abbrev);
- if (!hex)
- hex = sha1_to_hex(p->object.sha1);
+ strbuf_add_unique_abbrev(sb, oidp->hash, pp->abbrev);
+ else
+ strbuf_addstr(sb, oid_to_hex(oidp));
parent = parent->next;
-
- strbuf_addf(sb, " %s", hex);
}
strbuf_addch(sb, '\n');
}
size_t body_off;
/* The following ones are relative to the result struct strbuf. */
- struct chunk abbrev_commit_hash;
- struct chunk abbrev_tree_hash;
- struct chunk abbrev_parent_hashes;
size_t wrap_start;
};
-static int add_again(struct strbuf *sb, struct chunk *chunk)
-{
- if (chunk->len) {
- strbuf_adddup(sb, chunk->off, chunk->len);
- return 1;
- }
-
- /*
- * We haven't seen this chunk before. Our caller is surely
- * going to add it the hard way now. Remember the most likely
- * start of the to-be-added chunk: the current end of the
- * struct strbuf.
- */
- chunk->off = sb->len;
- return 0;
-}
-
static void parse_commit_header(struct format_commit_context *context)
{
const char *msg = context->message;
int linelen = get_one_line(line);
msg += linelen;
- if (!linelen || is_empty_line(line, &linelen))
+ if (!linelen || is_blank_line(line, &linelen))
break;
if (!sb)
return msg;
}
+static void format_trailers(struct strbuf *sb, const char *msg)
+{
+ struct trailer_info info;
+
+ trailer_info_get(&info, msg);
+ strbuf_add(sb, info.trailer_start,
+ info.trailer_end - info.trailer_start);
+ trailer_info_release(&info);
+}
+
static void parse_commit_message(struct format_commit_context *c)
{
const char *msg = c->message + c->message_off;
const char *start = c->message;
- msg = skip_empty_lines(msg);
+ msg = skip_blank_lines(msg);
c->subject_off = msg - start;
msg = format_subject(NULL, msg, NULL);
- msg = skip_empty_lines(msg);
+ msg = skip_blank_lines(msg);
c->body_off = msg - start;
c->commit_message_parsed = 1;
struct format_commit_context *c)
{
const char *rest = placeholder;
+ const char *basic_color = NULL;
if (placeholder[1] == '(') {
const char *begin = placeholder + 2;
if (!end)
return 0;
+
if (skip_prefix(begin, "auto,", &begin)) {
if (!want_color(c->pretty_ctx->color))
return end - placeholder + 1;
+ } else if (skip_prefix(begin, "always,", &begin)) {
+ /* nothing to do; we do not respect want_color at all */
+ } else {
+ /* the default is the same as "auto" */
+ if (!want_color(c->pretty_ctx->color))
+ return end - placeholder + 1;
}
+
if (color_parse_mem(begin, end - begin, color) < 0)
die(_("unable to parse --pretty format"));
strbuf_addstr(sb, color);
return end - placeholder + 1;
}
+
+ /*
+ * We handle things like "%C(red)" above; for historical reasons, there
+ * are a few colors that can be specified without parentheses (and
+ * they cannot support things like "auto" or "always" at all).
+ */
if (skip_prefix(placeholder + 1, "red", &rest))
- strbuf_addstr(sb, GIT_COLOR_RED);
+ basic_color = GIT_COLOR_RED;
else if (skip_prefix(placeholder + 1, "green", &rest))
- strbuf_addstr(sb, GIT_COLOR_GREEN);
+ basic_color = GIT_COLOR_GREEN;
else if (skip_prefix(placeholder + 1, "blue", &rest))
- strbuf_addstr(sb, GIT_COLOR_BLUE);
+ basic_color = GIT_COLOR_BLUE;
else if (skip_prefix(placeholder + 1, "reset", &rest))
- strbuf_addstr(sb, GIT_COLOR_RESET);
+ basic_color = GIT_COLOR_RESET;
+
+ if (basic_color && want_color(c->pretty_ctx->color))
+ strbuf_addstr(sb, basic_color);
+
return rest - placeholder;
}
int width;
if (!end || end == start)
return 0;
- width = strtoul(start, &next, 10);
+ width = strtol(start, &next, 10);
if (next == start || width == 0)
return 0;
+ if (width < 0) {
+ if (to_column)
+ width += term_columns();
+ if (width < 0)
+ return 0;
+ }
c->padding = to_column ? -width : width;
c->flush_type = flush_type;
const struct commit *commit = c->commit;
const char *msg = c->message;
struct commit_list *p;
- int h1, h2;
+ int ch;
/* these are independent of the commit */
switch (placeholder[0]) {
case 'C':
if (starts_with(placeholder + 1, "(auto)")) {
- c->auto_color = 1;
+ c->auto_color = want_color(c->pretty_ctx->color);
+ if (c->auto_color && sb->len)
+ strbuf_addstr(sb, GIT_COLOR_RESET);
return 7; /* consumed 7 bytes, "C(auto)" */
} else {
int ret = parse_color(sb, placeholder, c);
return 1;
case 'x':
/* %x00 == NUL, %x0a == LF, etc. */
- if (0 <= (h1 = hexval_table[0xff & placeholder[1]]) &&
- h1 <= 16 &&
- 0 <= (h2 = hexval_table[0xff & placeholder[2]]) &&
- h2 <= 16) {
- strbuf_addch(sb, (h1<<4)|h2);
- return 3;
- } else
+ ch = hex2chr(placeholder + 1);
+ if (ch < 0)
return 0;
+ strbuf_addch(sb, ch);
+ return 3;
case 'w':
if (placeholder[1] == '(') {
unsigned long width = 0, indent1 = 0, indent2 = 0;
/* these depend on the commit */
if (!commit->object.parsed)
- parse_object(commit->object.sha1);
+ parse_object(&commit->object.oid);
switch (placeholder[0]) {
case 'H': /* commit hash */
strbuf_addstr(sb, diff_get_color(c->auto_color, DIFF_COMMIT));
- strbuf_addstr(sb, sha1_to_hex(commit->object.sha1));
+ strbuf_addstr(sb, oid_to_hex(&commit->object.oid));
strbuf_addstr(sb, diff_get_color(c->auto_color, DIFF_RESET));
return 1;
case 'h': /* abbreviated commit hash */
strbuf_addstr(sb, diff_get_color(c->auto_color, DIFF_COMMIT));
- if (add_again(sb, &c->abbrev_commit_hash)) {
- strbuf_addstr(sb, diff_get_color(c->auto_color, DIFF_RESET));
- return 1;
- }
- strbuf_addstr(sb, find_unique_abbrev(commit->object.sha1,
- c->pretty_ctx->abbrev));
+ strbuf_add_unique_abbrev(sb, commit->object.oid.hash,
+ c->pretty_ctx->abbrev);
strbuf_addstr(sb, diff_get_color(c->auto_color, DIFF_RESET));
- c->abbrev_commit_hash.len = sb->len - c->abbrev_commit_hash.off;
return 1;
case 'T': /* tree hash */
- strbuf_addstr(sb, sha1_to_hex(commit->tree->object.sha1));
+ strbuf_addstr(sb, oid_to_hex(&commit->tree->object.oid));
return 1;
case 't': /* abbreviated tree hash */
- if (add_again(sb, &c->abbrev_tree_hash))
- return 1;
- strbuf_addstr(sb, find_unique_abbrev(commit->tree->object.sha1,
- c->pretty_ctx->abbrev));
- c->abbrev_tree_hash.len = sb->len - c->abbrev_tree_hash.off;
+ strbuf_add_unique_abbrev(sb, commit->tree->object.oid.hash,
+ c->pretty_ctx->abbrev);
return 1;
case 'P': /* parent hashes */
for (p = commit->parents; p; p = p->next) {
if (p != commit->parents)
strbuf_addch(sb, ' ');
- strbuf_addstr(sb, sha1_to_hex(p->item->object.sha1));
+ strbuf_addstr(sb, oid_to_hex(&p->item->object.oid));
}
return 1;
case 'p': /* abbreviated parent hashes */
- if (add_again(sb, &c->abbrev_parent_hashes))
- return 1;
for (p = commit->parents; p; p = p->next) {
if (p != commit->parents)
strbuf_addch(sb, ' ');
- strbuf_addstr(sb, find_unique_abbrev(
- p->item->object.sha1,
- c->pretty_ctx->abbrev));
+ strbuf_add_unique_abbrev(sb, p->item->object.oid.hash,
+ c->pretty_ctx->abbrev);
}
- c->abbrev_parent_hashes.len = sb->len -
- c->abbrev_parent_hashes.off;
return 1;
case 'm': /* left/right/bottom */
strbuf_addstr(sb, get_revision_mark(NULL, commit));
switch (c->signature_check.result) {
case 'G':
case 'B':
+ case 'E':
case 'U':
case 'N':
+ case 'X':
+ case 'Y':
+ case 'R':
strbuf_addch(sb, c->signature_check.result);
}
break;
strbuf_addstr(sb, msg + c->body_off);
return 1;
}
+
+ if (starts_with(placeholder, "(trailers)")) {
+ format_trailers(sb, msg + c->subject_off);
+ return strlen("(trailers)");
+ }
+
return 0; /* unknown placeholder */
}
if (!start)
start = sb->buf;
occupied = utf8_strnwidth(start, -1, 1);
+ occupied += c->pretty_ctx->graph_width;
padding = (-padding) - occupied;
}
while (1) {
pp->preserve_subject ? "\n" : " ");
strbuf_grow(sb, title.len + 1024);
- if (pp->subject) {
- strbuf_addstr(sb, pp->subject);
+ if (pp->print_email_subject) {
+ if (pp->rev)
+ fmt_output_email_subject(sb, pp->rev);
if (needs_rfc2047_encoding(title.buf, title.len, RFC2047_SUBJECT))
add_rfc2047(sb, title.buf, title.len,
encoding, RFC2047_SUBJECT);
if (pp->after_subject) {
strbuf_addstr(sb, pp->after_subject);
}
- if (pp->fmt == CMIT_FMT_EMAIL) {
+ if (cmit_fmt_is_mail(pp->fmt)) {
strbuf_addch(sb, '\n');
}
strbuf_release(&title);
}
+static int pp_utf8_width(const char *start, const char *end)
+{
+ int width = 0;
+ size_t remain = end - start;
+
+ while (remain) {
+ int n = utf8_width(&start, &remain);
+ if (n < 0 || !start)
+ return -1;
+ width += n;
+ }
+ return width;
+}
+
+static void strbuf_add_tabexpand(struct strbuf *sb, int tabwidth,
+ const char *line, int linelen)
+{
+ const char *tab;
+
+ while ((tab = memchr(line, '\t', linelen)) != NULL) {
+ int width = pp_utf8_width(line, tab);
+
+ /*
+ * If it wasn't well-formed utf8, or it
+ * had characters with badly defined
+ * width (control characters etc), just
+ * give up on trying to align things.
+ */
+ if (width < 0)
+ break;
+
+ /* Output the data .. */
+ strbuf_add(sb, line, tab - line);
+
+ /* .. and the de-tabified tab */
+ strbuf_addchars(sb, ' ', tabwidth - (width % tabwidth));
+
+ /* Skip over the printed part .. */
+ linelen -= tab + 1 - line;
+ line = tab + 1;
+ }
+
+ /*
+ * Print out everything after the last tab without
+ * worrying about width - there's nothing more to
+ * align.
+ */
+ strbuf_add(sb, line, linelen);
+}
+
+/*
+ * pp_handle_indent() prints out the intendation, and
+ * the whole line (without the final newline), after
+ * de-tabifying.
+ */
+static void pp_handle_indent(struct pretty_print_context *pp,
+ struct strbuf *sb, int indent,
+ const char *line, int linelen)
+{
+ strbuf_addchars(sb, ' ', indent);
+ if (pp->expand_tabs_in_log)
+ strbuf_add_tabexpand(sb, pp->expand_tabs_in_log, line, linelen);
+ else
+ strbuf_add(sb, line, linelen);
+}
+
+static int is_mboxrd_from(const char *line, int len)
+{
+ /*
+ * a line matching /^From $/ here would only have len == 4
+ * at this point because is_empty_line would've trimmed all
+ * trailing space
+ */
+ return len > 4 && starts_with(line + strspn(line, ">"), "From ");
+}
+
void pp_remainder(struct pretty_print_context *pp,
const char **msg_p,
struct strbuf *sb,
if (!linelen)
break;
- if (is_empty_line(line, &linelen)) {
+ if (is_blank_line(line, &linelen)) {
if (first)
continue;
if (pp->fmt == CMIT_FMT_SHORT)
strbuf_grow(sb, linelen + indent + 20);
if (indent)
- strbuf_addchars(sb, ' ', indent);
- strbuf_add(sb, line, linelen);
+ pp_handle_indent(pp, sb, indent, line, linelen);
+ else if (pp->expand_tabs_in_log)
+ strbuf_add_tabexpand(sb, pp->expand_tabs_in_log,
+ line, linelen);
+ else {
+ if (pp->fmt == CMIT_FMT_MBOXRD &&
+ is_mboxrd_from(line, linelen))
+ strbuf_addch(sb, '>');
+
+ strbuf_add(sb, line, linelen);
+ }
strbuf_addch(sb, '\n');
}
}
encoding = get_log_output_encoding();
msg = reencoded = logmsg_reencode(commit, NULL, encoding);
- if (pp->fmt == CMIT_FMT_ONELINE || pp->fmt == CMIT_FMT_EMAIL)
+ if (pp->fmt == CMIT_FMT_ONELINE || cmit_fmt_is_mail(pp->fmt))
indent = 0;
/*
* We need to check and emit Content-type: to mark it
* as 8-bit if we haven't done so.
*/
- if (pp->fmt == CMIT_FMT_EMAIL && need_8bit_cte == 0) {
+ if (cmit_fmt_is_mail(pp->fmt) && need_8bit_cte == 0) {
int i, ch, in_body;
for (in_body = i = 0; (ch = msg[i]); i++) {
}
pp_header(pp, encoding, commit, &msg, sb);
- if (pp->fmt != CMIT_FMT_ONELINE && !pp->subject) {
+ if (pp->fmt != CMIT_FMT_ONELINE && !pp->print_email_subject) {
strbuf_addch(sb, '\n');
}
/* Skip excess blank lines at the beginning of body, if any... */
- msg = skip_empty_lines(msg);
+ msg = skip_blank_lines(msg);
/* These formats treat the title line specially. */
- if (pp->fmt == CMIT_FMT_ONELINE || pp->fmt == CMIT_FMT_EMAIL)
+ if (pp->fmt == CMIT_FMT_ONELINE || cmit_fmt_is_mail(pp->fmt))
pp_title_line(pp, &msg, sb, encoding, need_8bit_cte);
beginning_of_body = sb->len;
* format. Make sure we did not strip the blank line
* between the header and the body.
*/
- if (pp->fmt == CMIT_FMT_EMAIL && sb->len <= beginning_of_body)
+ if (cmit_fmt_is_mail(pp->fmt) && sb->len <= beginning_of_body)
strbuf_addch(sb, '\n');
unuse_commit_buffer(commit, reencoded);