#include "revision.h"
#include "string-list.h"
#include "mailmap.h"
+#include "log-tree.h"
+#include "notes.h"
+#include "color.h"
+#include "reflog-walk.h"
static char *user_format;
+static void save_user_format(struct rev_info *rev, const char *cp, int is_tformat)
+{
+ free(user_format);
+ user_format = xstrdup(cp);
+ if (is_tformat)
+ rev->use_terminator = 1;
+ rev->commit_format = CMIT_FMT_USERFORMAT;
+}
+
void get_commit_format(const char *arg, struct rev_info *rev)
{
int i;
return;
}
if (!prefixcmp(arg, "format:") || !prefixcmp(arg, "tformat:")) {
- const char *cp = strchr(arg, ':') + 1;
- free(user_format);
- user_format = xstrdup(cp);
- if (arg[0] == 't')
- rev->use_terminator = 1;
- rev->commit_format = CMIT_FMT_USERFORMAT;
+ save_user_format(rev, strchr(arg, ':') + 1, arg[0] == 't');
return;
}
for (i = 0; i < ARRAY_SIZE(cmt_fmts); i++) {
return;
}
}
+ if (strchr(arg, '%')) {
+ save_user_format(rev, arg, 1);
+ return;
+ }
die("invalid --pretty format: %s", arg);
}
}
/* High bit set, or ISO-2022-INT */
-int non_ascii(int ch)
+static int non_ascii(int ch)
{
- ch = (ch & 0xff);
- return ((ch & 0x80) || (ch == 0x1b));
+ return !isascii(ch) || ch == '\033';
+}
+
+int has_non_ascii(const char *s)
+{
+ int ch;
+ if (!s)
+ return 0;
+ while ((ch = *s++) != '\0') {
+ if (non_ascii(ch))
+ return 1;
+ }
+ return 0;
}
static int is_rfc2047_special(char ch)
int namelen;
unsigned long time;
int tz;
- const char *filler = " ";
if (fmt == CMIT_FMT_ONELINE)
return;
while (line < name_tail && isspace(name_tail[-1]))
name_tail--;
display_name_length = name_tail - line;
- filler = "";
strbuf_addstr(sb, "From: ");
add_rfc2047(sb, line, display_name_length, encoding);
strbuf_add(sb, name_tail, namelen - display_name_length);
} else {
strbuf_addf(sb, "%s: %.*s%.*s\n", what,
(fmt == CMIT_FMT_FULLER) ? 4 : 0,
- filler, namelen, line);
+ " ", namelen, line);
}
switch (fmt) {
case CMIT_FMT_MEDIUM:
return !len;
}
+static const char *skip_empty_lines(const char *msg)
+{
+ for (;;) {
+ int linelen = get_one_line(msg);
+ int ll = linelen;
+ if (!linelen)
+ break;
+ if (!is_empty_line(msg, &ll))
+ break;
+ msg += linelen;
+ }
+ return msg;
+}
+
static void add_merge_info(enum cmit_fmt fmt, struct strbuf *sb,
const struct commit *commit, int abbrev)
{
while (parent) {
struct commit *p = parent->item;
const char *hex = NULL;
- const char *dots;
if (abbrev)
hex = find_unique_abbrev(p->object.sha1, abbrev);
if (!hex)
hex = sha1_to_hex(p->object.sha1);
- dots = (abbrev && strlen(hex) != 40) ? "..." : "";
parent = parent->next;
- strbuf_addf(sb, " %s%s", hex, dots);
+ strbuf_addf(sb, " %s", hex);
}
strbuf_addch(sb, '\n');
}
static char *replace_encoding_header(char *buf, const char *encoding)
{
- struct strbuf tmp;
+ struct strbuf tmp = STRBUF_INIT;
size_t start, len;
char *cp = buf;
return buf; /* should not happen but be defensive */
len = cp + 1 - (buf + start);
- strbuf_init(&tmp, 0);
strbuf_attach(&tmp, buf, strlen(buf), strlen(buf) + 1);
if (is_encoding_utf8(encoding)) {
/* we have re-coded to UTF-8; drop the header */
static char *logmsg_reencode(const struct commit *commit,
const char *output_encoding)
{
- static const char *utf8 = "utf-8";
+ static const char *utf8 = "UTF-8";
const char *use_encoding;
char *encoding;
char *out;
return out;
}
-static int mailmap_name(struct strbuf *sb, const char *email)
+static int mailmap_name(char *email, int email_len, char *name, int name_len)
{
static struct string_list *mail_map;
- char buffer[1024];
-
if (!mail_map) {
mail_map = xcalloc(1, sizeof(*mail_map));
- read_mailmap(mail_map, ".mailmap", NULL);
+ read_mailmap(mail_map, NULL);
}
-
- if (!mail_map->nr)
- return -1;
-
- if (!map_email(mail_map, email, buffer, sizeof(buffer)))
- return -1;
- strbuf_addstr(sb, buffer);
- return 0;
+ return mail_map->nr && map_user(mail_map, email, email_len, name, name_len);
}
static size_t format_person_part(struct strbuf *sb, char part,
int start, end, tz = 0;
unsigned long date = 0;
char *ep;
+ const char *name_start, *name_end, *mail_start, *mail_end, *msg_end = msg+len;
+ char person_name[1024];
+ char person_mail[1024];
/* advance 'end' to point to email start delimiter */
for (end = 0; end < len && msg[end] != '<'; end++)
if (end >= len - 2)
goto skip;
+ /* Seek for both name and email part */
+ name_start = msg;
+ name_end = msg+end;
+ while (name_end > name_start && isspace(*(name_end-1)))
+ name_end--;
+ mail_start = msg+end+1;
+ mail_end = mail_start;
+ while (mail_end < msg_end && *mail_end != '>')
+ mail_end++;
+ if (mail_end == msg_end)
+ goto skip;
+ end = mail_end-msg;
+
+ if (part == 'N' || part == 'E') { /* mailmap lookup */
+ strlcpy(person_name, name_start, name_end-name_start+1);
+ strlcpy(person_mail, mail_start, mail_end-mail_start+1);
+ mailmap_name(person_mail, sizeof(person_mail), person_name, sizeof(person_name));
+ name_start = person_name;
+ name_end = name_start + strlen(person_name);
+ mail_start = person_mail;
+ mail_end = mail_start + strlen(person_mail);
+ }
if (part == 'n' || part == 'N') { /* name */
- while (end > 0 && isspace(msg[end - 1]))
- end--;
- if (part != 'N' || !msg[end] || !msg[end + 1] ||
- mailmap_name(sb, msg + end + 2) < 0)
- strbuf_add(sb, msg, end);
+ strbuf_add(sb, name_start, name_end-name_start);
return placeholder_len;
}
- start = ++end; /* save email start position */
-
- /* advance 'end' to point to email end delimiter */
- for ( ; end < len && msg[end] != '>'; end++)
- ; /* do nothing */
-
- if (end >= len)
- goto skip;
-
- if (part == 'e') { /* email */
- strbuf_add(sb, msg + start, end - start);
+ if (part == 'e' || part == 'E') { /* email */
+ strbuf_add(sb, mail_start, mail_end-mail_start);
return placeholder_len;
}
struct format_commit_context {
const struct commit *commit;
- enum date_mode dmode;
+ const struct pretty_print_context *pretty_ctx;
+ unsigned commit_header_parsed:1;
+ unsigned commit_message_parsed:1;
+ size_t width, indent1, indent2;
/* These offsets are relative to the start of the commit message. */
- int commit_header_parsed;
- struct chunk subject;
struct chunk author;
struct chunk committer;
struct chunk encoding;
+ size_t message_off;
+ size_t subject_off;
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)
{
const char *msg = context->commit->buffer;
int i;
- enum { HEADER, SUBJECT, BODY } state;
- for (i = 0, state = HEADER; msg[i] && state < BODY; i++) {
+ for (i = 0; msg[i]; i++) {
int eol;
for (eol = i; msg[eol] && msg[eol] != '\n'; eol++)
; /* do nothing */
- if (state == SUBJECT) {
- context->subject.off = i;
- context->subject.len = eol - i;
- i = eol;
- }
if (i == eol) {
- state++;
- /* strip empty lines */
- while (msg[eol] == '\n' && msg[eol + 1] == '\n')
- eol++;
+ break;
} else if (!prefixcmp(msg + i, "author ")) {
context->author.off = i + 7;
context->author.len = eol - i - 7;
context->encoding.len = eol - i - 9;
}
i = eol;
- if (!msg[i])
- break;
}
- context->body_off = i;
+ context->message_off = i;
context->commit_header_parsed = 1;
}
-static size_t format_commit_item(struct strbuf *sb, const char *placeholder,
- void *context)
+static int istitlechar(char c)
+{
+ return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') ||
+ (c >= '0' && c <= '9') || c == '.' || c == '_';
+}
+
+static void format_sanitized_subject(struct strbuf *sb, const char *msg)
+{
+ size_t trimlen;
+ size_t start_len = sb->len;
+ int space = 2;
+
+ for (; *msg && *msg != '\n'; msg++) {
+ if (istitlechar(*msg)) {
+ if (space == 1)
+ strbuf_addch(sb, '-');
+ space = 0;
+ strbuf_addch(sb, *msg);
+ if (*msg == '.')
+ while (*(msg+1) == '.')
+ msg++;
+ } else
+ space |= 1;
+ }
+
+ /* trim any trailing '.' or '-' characters */
+ trimlen = 0;
+ while (sb->len - trimlen > start_len &&
+ (sb->buf[sb->len - 1 - trimlen] == '.'
+ || sb->buf[sb->len - 1 - trimlen] == '-'))
+ trimlen++;
+ strbuf_remove(sb, sb->len - trimlen, trimlen);
+}
+
+const char *format_subject(struct strbuf *sb, const char *msg,
+ const char *line_separator)
+{
+ int first = 1;
+
+ for (;;) {
+ const char *line = msg;
+ int linelen = get_one_line(line);
+
+ msg += linelen;
+ if (!linelen || is_empty_line(line, &linelen))
+ break;
+
+ if (!sb)
+ continue;
+ strbuf_grow(sb, linelen + 2);
+ if (!first)
+ strbuf_addstr(sb, line_separator);
+ strbuf_add(sb, line, linelen);
+ first = 0;
+ }
+ return msg;
+}
+
+static void parse_commit_message(struct format_commit_context *c)
+{
+ const char *msg = c->commit->buffer + c->message_off;
+ const char *start = c->commit->buffer;
+
+ msg = skip_empty_lines(msg);
+ c->subject_off = msg - start;
+
+ msg = format_subject(NULL, msg, NULL);
+ msg = skip_empty_lines(msg);
+ c->body_off = msg - start;
+
+ c->commit_message_parsed = 1;
+}
+
+static void format_decoration(struct strbuf *sb, const struct commit *commit)
+{
+ struct name_decoration *d;
+ const char *prefix = " (";
+
+ load_ref_decorations(DECORATE_SHORT_REFS);
+ d = lookup_decoration(&name_decoration, &commit->object);
+ while (d) {
+ strbuf_addstr(sb, prefix);
+ prefix = ", ";
+ strbuf_addstr(sb, d->name);
+ d = d->next;
+ }
+ if (prefix[0] == ',')
+ strbuf_addch(sb, ')');
+}
+
+static void strbuf_wrap(struct strbuf *sb, size_t pos,
+ size_t width, size_t indent1, size_t indent2)
+{
+ struct strbuf tmp = STRBUF_INIT;
+
+ if (pos)
+ strbuf_add(&tmp, sb->buf, pos);
+ strbuf_add_wrapped_text(&tmp, sb->buf + pos,
+ (int) indent1, (int) indent2, (int) width);
+ strbuf_swap(&tmp, sb);
+ strbuf_release(&tmp);
+}
+
+static void rewrap_message_tail(struct strbuf *sb,
+ struct format_commit_context *c,
+ size_t new_width, size_t new_indent1,
+ size_t new_indent2)
+{
+ if (c->width == new_width && c->indent1 == new_indent1 &&
+ c->indent2 == new_indent2)
+ return;
+ if (c->wrap_start < sb->len)
+ strbuf_wrap(sb, c->wrap_start, c->width, c->indent1, c->indent2);
+ c->wrap_start = sb->len;
+ c->width = new_width;
+ c->indent1 = new_indent1;
+ c->indent2 = new_indent2;
+}
+
+static size_t format_commit_one(struct strbuf *sb, const char *placeholder,
+ void *context)
{
struct format_commit_context *c = context;
const struct commit *commit = c->commit;
/* these are independent of the commit */
switch (placeholder[0]) {
case 'C':
+ if (placeholder[1] == '(') {
+ const char *end = strchr(placeholder + 2, ')');
+ char color[COLOR_MAXLEN];
+ if (!end)
+ return 0;
+ color_parse_mem(placeholder + 2,
+ end - (placeholder + 2),
+ "--pretty format", color);
+ strbuf_addstr(sb, color);
+ return end - placeholder + 1;
+ }
if (!prefixcmp(placeholder + 1, "red")) {
- strbuf_addstr(sb, "\033[31m");
+ strbuf_addstr(sb, GIT_COLOR_RED);
return 4;
} else if (!prefixcmp(placeholder + 1, "green")) {
- strbuf_addstr(sb, "\033[32m");
+ strbuf_addstr(sb, GIT_COLOR_GREEN);
return 6;
} else if (!prefixcmp(placeholder + 1, "blue")) {
- strbuf_addstr(sb, "\033[34m");
+ strbuf_addstr(sb, GIT_COLOR_BLUE);
return 5;
} else if (!prefixcmp(placeholder + 1, "reset")) {
- strbuf_addstr(sb, "\033[m");
+ strbuf_addstr(sb, GIT_COLOR_RESET);
return 6;
} else
return 0;
return 3;
} else
return 0;
+ case 'w':
+ if (placeholder[1] == '(') {
+ unsigned long width = 0, indent1 = 0, indent2 = 0;
+ char *next;
+ const char *start = placeholder + 2;
+ const char *end = strchr(start, ')');
+ if (!end)
+ return 0;
+ if (end > start) {
+ width = strtoul(start, &next, 10);
+ if (*next == ',') {
+ indent1 = strtoul(next + 1, &next, 10);
+ if (*next == ',') {
+ indent2 = strtoul(next + 1,
+ &next, 10);
+ }
+ }
+ if (*next != ')')
+ return 0;
+ }
+ rewrap_message_tail(sb, c, width, indent1, indent2);
+ return end - placeholder + 1;
+ } else
+ return 0;
}
/* these depend on the commit */
? '<'
: '>');
return 1;
+ case 'd':
+ format_decoration(sb, commit);
+ return 1;
+ case 'g': /* reflog info */
+ switch(placeholder[1]) {
+ case 'd': /* reflog selector */
+ case 'D':
+ if (c->pretty_ctx->reflog_info)
+ get_reflog_selector(sb,
+ c->pretty_ctx->reflog_info,
+ c->pretty_ctx->date_mode,
+ (placeholder[1] == 'd'));
+ return 2;
+ case 's': /* reflog message */
+ if (c->pretty_ctx->reflog_info)
+ get_reflog_message(sb, c->pretty_ctx->reflog_info);
+ return 2;
+ }
+ return 0; /* unknown %g placeholder */
+ case 'N':
+ get_commit_notes(commit, sb, git_log_output_encoding ?
+ git_log_output_encoding : git_commit_encoding, 0);
+ return 1;
}
/* For the rest we have to parse the commit header. */
parse_commit_header(c);
switch (placeholder[0]) {
- case 's': /* subject */
- strbuf_add(sb, msg + c->subject.off, c->subject.len);
- return 1;
case 'a': /* author ... */
return format_person_part(sb, placeholder[1],
msg + c->author.off, c->author.len,
- c->dmode);
+ c->pretty_ctx->date_mode);
case 'c': /* committer ... */
return format_person_part(sb, placeholder[1],
msg + c->committer.off, c->committer.len,
- c->dmode);
+ c->pretty_ctx->date_mode);
case 'e': /* encoding */
strbuf_add(sb, msg + c->encoding.off, c->encoding.len);
return 1;
+ }
+
+ /* Now we need to parse the commit message. */
+ if (!c->commit_message_parsed)
+ parse_commit_message(c);
+
+ switch (placeholder[0]) {
+ case 's': /* subject */
+ format_subject(sb, msg + c->subject_off, " ");
+ return 1;
+ case 'f': /* sanitized subject */
+ format_sanitized_subject(sb, msg + c->subject_off);
+ return 1;
case 'b': /* body */
strbuf_addstr(sb, msg + c->body_off);
return 1;
return 0; /* unknown placeholder */
}
+static size_t format_commit_item(struct strbuf *sb, const char *placeholder,
+ void *context)
+{
+ int consumed;
+ size_t orig_len;
+ enum {
+ NO_MAGIC,
+ ADD_LF_BEFORE_NON_EMPTY,
+ DEL_LF_BEFORE_EMPTY,
+ } magic = NO_MAGIC;
+
+ switch (placeholder[0]) {
+ case '-':
+ magic = DEL_LF_BEFORE_EMPTY;
+ break;
+ case '+':
+ magic = ADD_LF_BEFORE_NON_EMPTY;
+ break;
+ default:
+ break;
+ }
+ if (magic != NO_MAGIC)
+ placeholder++;
+
+ orig_len = sb->len;
+ consumed = format_commit_one(sb, placeholder, context);
+ if (magic == NO_MAGIC)
+ return consumed;
+
+ if ((orig_len == sb->len) && magic == DEL_LF_BEFORE_EMPTY) {
+ while (sb->len && sb->buf[sb->len - 1] == '\n')
+ strbuf_setlen(sb, sb->len - 1);
+ } else if ((orig_len != sb->len) && magic == ADD_LF_BEFORE_NON_EMPTY) {
+ strbuf_insert(sb, orig_len, "\n", 1);
+ }
+ return consumed + 1;
+}
+
void format_commit_message(const struct commit *commit,
- const void *format, struct strbuf *sb,
- enum date_mode dmode)
+ const char *format, struct strbuf *sb,
+ const struct pretty_print_context *pretty_ctx)
{
struct format_commit_context context;
memset(&context, 0, sizeof(context));
context.commit = commit;
- context.dmode = dmode;
+ context.pretty_ctx = pretty_ctx;
+ context.wrap_start = sb->len;
strbuf_expand(sb, format, format_commit_item, &context);
+ rewrap_message_tail(sb, &context, 0, 0, 0);
}
static void pp_header(enum cmit_fmt fmt,
const char *encoding,
int need_8bit_cte)
{
+ const char *line_separator = (fmt == CMIT_FMT_EMAIL) ? "\n " : " ";
struct strbuf title;
strbuf_init(&title, 80);
-
- for (;;) {
- const char *line = *msg_p;
- int linelen = get_one_line(line);
-
- *msg_p += linelen;
- if (!linelen || is_empty_line(line, &linelen))
- break;
-
- strbuf_grow(&title, linelen + 2);
- if (title.len) {
- if (fmt == CMIT_FMT_EMAIL) {
- strbuf_addch(&title, '\n');
- }
- strbuf_addch(&title, ' ');
- }
- strbuf_add(&title, line, linelen);
- }
+ *msg_p = format_subject(&title, *msg_p, line_separator);
strbuf_grow(sb, title.len + 1024);
if (subject) {
}
}
+char *reencode_commit_message(const struct commit *commit, const char **encoding_p)
+{
+ const char *encoding;
+
+ encoding = (git_log_output_encoding
+ ? git_log_output_encoding
+ : git_commit_encoding);
+ if (!encoding)
+ encoding = "UTF-8";
+ if (encoding_p)
+ *encoding_p = encoding;
+ return logmsg_reencode(commit, encoding);
+}
+
void pretty_print_commit(enum cmit_fmt fmt, const struct commit *commit,
- struct strbuf *sb, int abbrev,
- const char *subject, const char *after_subject,
- enum date_mode dmode, int need_8bit_cte)
+ struct strbuf *sb,
+ const struct pretty_print_context *context)
{
unsigned long beginning_of_body;
int indent = 4;
const char *msg = commit->buffer;
char *reencoded;
const char *encoding;
+ int need_8bit_cte = context->need_8bit_cte;
if (fmt == CMIT_FMT_USERFORMAT) {
- format_commit_message(commit, user_format, sb, dmode);
+ format_commit_message(commit, user_format, sb, context);
return;
}
- encoding = (git_log_output_encoding
- ? git_log_output_encoding
- : git_commit_encoding);
- if (!encoding)
- encoding = "utf-8";
- reencoded = logmsg_reencode(commit, encoding);
+ reencoded = reencode_commit_message(commit, &encoding);
if (reencoded) {
msg = reencoded;
}
}
}
- pp_header(fmt, abbrev, dmode, encoding, commit, &msg, sb);
- if (fmt != CMIT_FMT_ONELINE && !subject) {
+ pp_header(fmt, context->abbrev, context->date_mode, encoding,
+ commit, &msg, sb);
+ if (fmt != CMIT_FMT_ONELINE && !context->subject) {
strbuf_addch(sb, '\n');
}
/* Skip excess blank lines at the beginning of body, if any... */
- for (;;) {
- int linelen = get_one_line(msg);
- int ll = linelen;
- if (!linelen)
- break;
- if (!is_empty_line(msg, &ll))
- break;
- msg += linelen;
- }
+ msg = skip_empty_lines(msg);
/* These formats treat the title line specially. */
if (fmt == CMIT_FMT_ONELINE || fmt == CMIT_FMT_EMAIL)
- pp_title_line(fmt, &msg, sb, subject,
- after_subject, encoding, need_8bit_cte);
+ pp_title_line(fmt, &msg, sb, context->subject,
+ context->after_subject, encoding, need_8bit_cte);
beginning_of_body = sb->len;
if (fmt != CMIT_FMT_ONELINE)
*/
if (fmt == CMIT_FMT_EMAIL && sb->len <= beginning_of_body)
strbuf_addch(sb, '\n');
+
+ if (fmt != CMIT_FMT_ONELINE)
+ get_commit_notes(commit, sb, encoding,
+ NOTES_SHOW_HEADER | NOTES_INDENT);
+
free(reencoded);
}