Permalink
Switch branches/tags
Find file
Fetching contributors…
Cannot retrieve contributors at this time
1848 lines (1632 sloc) 44.9 KB
#include "cache.h"
#include "config.h"
#include "commit.h"
#include "utf8.h"
#include "diff.h"
#include "revision.h"
#include "string-list.h"
#include "mailmap.h"
#include "log-tree.h"
#include "notes.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 size_t builtin_formats_len;
static size_t commit_formats_len;
static size_t commit_formats_alloc;
static struct cmt_fmt_map *find_commit_format(const char *sought);
int commit_format_is_empty(enum cmit_fmt fmt)
{
return fmt == CMIT_FMT_USERFORMAT && !*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;
}
static int git_pretty_formats_config(const char *var, const char *value, void *cb)
{
struct cmt_fmt_map *commit_format = NULL;
const char *name;
const char *fmt;
int i;
if (!skip_prefix(var, "pretty.", &name))
return 0;
for (i = 0; i < builtin_formats_len; i++) {
if (!strcmp(commit_formats[i].name, name))
return 0;
}
for (i = builtin_formats_len; i < commit_formats_len; i++) {
if (!strcmp(commit_formats[i].name, name)) {
commit_format = &commit_formats[i];
break;
}
}
if (!commit_format) {
ALLOC_GROW(commit_formats, commit_formats_len+1,
commit_formats_alloc);
commit_format = &commit_formats[commit_formats_len];
memset(commit_format, 0, sizeof(*commit_format));
commit_formats_len++;
}
commit_format->name = xstrdup(name);
commit_format->format = CMIT_FMT_USERFORMAT;
if (git_config_string(&fmt, var, value))
return -1;
if (skip_prefix(fmt, "format:", &fmt))
commit_format->is_tformat = 0;
else if (skip_prefix(fmt, "tformat:", &fmt) || strchr(fmt, '%'))
commit_format->is_tformat = 1;
else
commit_format->is_alias = 1;
commit_format->user_format = fmt;
return 0;
}
static void setup_commit_formats(void)
{
struct cmt_fmt_map builtin_formats[] = {
{ "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;
ALLOC_GROW(commit_formats, commit_formats_len, commit_formats_alloc);
memcpy(commit_formats, builtin_formats,
sizeof(*builtin_formats)*ARRAY_SIZE(builtin_formats));
git_config(git_pretty_formats_config, NULL);
}
static struct cmt_fmt_map *find_commit_format_recursive(const char *sought,
const char *original,
int num_redirections)
{
struct cmt_fmt_map *found = NULL;
size_t found_match_len = 0;
int i;
if (num_redirections >= commit_formats_len)
die("invalid --pretty format: "
"'%s' references an alias which points to itself",
original);
for (i = 0; i < commit_formats_len; i++) {
size_t match_len;
if (!starts_with(commit_formats[i].name, sought))
continue;
match_len = strlen(commit_formats[i].name);
if (found == NULL || found_match_len > match_len) {
found = &commit_formats[i];
found_match_len = match_len;
}
}
if (found && found->is_alias) {
found = find_commit_format_recursive(found->user_format,
original,
num_redirections+1);
}
return found;
}
static struct cmt_fmt_map *find_commit_format(const char *sought)
{
if (!commit_formats)
setup_commit_formats();
return find_commit_format_recursive(sought, sought, 0);
}
void get_commit_format(const char *arg, struct rev_info *rev)
{
struct cmt_fmt_map *commit_format;
rev->use_terminator = 0;
if (!arg) {
rev->commit_format = CMIT_FMT_DEFAULT;
return;
}
if (skip_prefix(arg, "format:", &arg)) {
save_user_format(rev, arg, 0);
return;
}
if (!*arg || skip_prefix(arg, "tformat:", &arg) || strchr(arg, '%')) {
save_user_format(rev, arg, 1);
return;
}
commit_format = find_commit_format(arg);
if (!commit_format)
die("invalid --pretty format: %s", arg);
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);
}
}
/*
* Generic support for pretty-printing the header
*/
static int get_one_line(const char *msg)
{
int ret = 0;
for (;;) {
char c = *msg++;
if (!c)
break;
ret++;
if (c == '\n')
break;
}
return ret;
}
/* High bit set, or ISO-2022-INT */
static int non_ascii(int ch)
{
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_rfc822_special(char ch)
{
switch (ch) {
case '(':
case ')':
case '<':
case '>':
case '[':
case ']':
case ':':
case ';':
case '@':
case ',':
case '.':
case '"':
case '\\':
return 1;
default:
return 0;
}
}
static int needs_rfc822_quoting(const char *s, int len)
{
int i;
for (i = 0; i < len; i++)
if (is_rfc822_special(s[i]))
return 1;
return 0;
}
static int last_line_length(struct strbuf *sb)
{
int i;
/* How many bytes are already used on the last line? */
for (i = sb->len - 1; i >= 0; i--)
if (sb->buf[i] == '\n')
break;
return sb->len - (i + 1);
}
static void add_rfc822_quoted(struct strbuf *out, const char *s, int len)
{
int i;
/* just a guess, we may have to also backslash-quote */
strbuf_grow(out, len + 2);
strbuf_addch(out, '"');
for (i = 0; i < len; i++) {
switch (s[i]) {
case '"':
case '\\':
strbuf_addch(out, '\\');
/* fall through */
default:
strbuf_addch(out, s[i]);
}
}
strbuf_addch(out, '"');
}
enum rfc2047_type {
RFC2047_SUBJECT,
RFC2047_ADDRESS
};
static int is_rfc2047_special(char ch, enum rfc2047_type type)
{
/*
* rfc2047, section 4.2:
*
* 8-bit values which correspond to printable ASCII characters other
* than "=", "?", and "_" (underscore), MAY be represented as those
* characters. (But see section 5 for restrictions.) In
* particular, SPACE and TAB MUST NOT be represented as themselves
* within encoded words.
*/
/*
* rule out non-ASCII characters and non-printable characters (the
* non-ASCII check should be redundant as isprint() is not localized
* and only knows about ASCII, but be defensive about that)
*/
if (non_ascii(ch) || !isprint(ch))
return 1;
/*
* rule out special printable characters (' ' should be the only
* whitespace character considered printable, but be defensive and use
* isspace())
*/
if (isspace(ch) || ch == '=' || ch == '?' || ch == '_')
return 1;
/*
* rfc2047, section 5.3:
*
* As a replacement for a 'word' entity within a 'phrase', for example,
* one that precedes an address in a From, To, or Cc header. The ABNF
* definition for 'phrase' from RFC 822 thus becomes:
*
* phrase = 1*( encoded-word / word )
*
* In this case the set of characters that may be used in a "Q"-encoded
* 'encoded-word' is restricted to: <upper and lower case ASCII
* letters, decimal digits, "!", "*", "+", "-", "/", "=", and "_"
* (underscore, ASCII 95.)>. An 'encoded-word' that appears within a
* 'phrase' MUST be separated from any adjacent 'word', 'text' or
* 'special' by 'linear-white-space'.
*/
if (type != RFC2047_ADDRESS)
return 0;
/* '=' and '_' are special cases and have been checked above */
return !(isalnum(ch) || ch == '!' || ch == '*' || ch == '+' || ch == '-' || ch == '/');
}
static int needs_rfc2047_encoding(const char *line, int len,
enum rfc2047_type type)
{
int i;
for (i = 0; i < len; i++) {
int ch = line[i];
if (non_ascii(ch) || ch == '\n')
return 1;
if ((i + 1 < len) && (ch == '=' && line[i+1] == '?'))
return 1;
}
return 0;
}
static void add_rfc2047(struct strbuf *sb, const char *line, size_t len,
const char *encoding, enum rfc2047_type type)
{
static const int max_encoded_length = 76; /* per rfc2047 */
int i;
int line_len = last_line_length(sb);
strbuf_grow(sb, len * 3 + strlen(encoding) + 100);
strbuf_addf(sb, "=?%s?q?", encoding);
line_len += strlen(encoding) + 5; /* 5 for =??q? */
while (len) {
/*
* RFC 2047, section 5 (3):
*
* Each 'encoded-word' MUST represent an integral number of
* characters. A multi-octet character may not be split across
* adjacent 'encoded- word's.
*/
const unsigned char *p = (const unsigned char *)line;
int chrlen = mbs_chrlen(&line, &len, encoding);
int is_special = (chrlen > 1) || is_rfc2047_special(*p, type);
/* "=%02X" * chrlen, or the byte itself */
const char *encoded_fmt = is_special ? "=%02X" : "%c";
int encoded_len = is_special ? 3 * chrlen : 1;
/*
* According to RFC 2047, we could encode the special character
* ' ' (space) with '_' (underscore) for readability. But many
* programs do not understand this and just leave the
* underscore in place. Thus, we do nothing special here, which
* causes ' ' to be encoded as '=20', avoiding this problem.
*/
if (line_len + encoded_len + 2 > max_encoded_length) {
/* It won't fit with trailing "?=" --- break the line */
strbuf_addf(sb, "?=\n =?%s?q?", encoding);
line_len = strlen(encoding) + 5 + 1; /* =??q? plus SP */
}
for (i = 0; i < chrlen; i++)
strbuf_addf(sb, encoded_fmt, p[i]);
line_len += encoded_len;
}
strbuf_addstr(sb, "?=");
}
const char *show_ident_date(const struct ident_split *ident,
const struct date_mode *mode)
{
timestamp_t date = 0;
long tz = 0;
if (ident->date_begin && ident->date_end)
date = parse_timestamp(ident->date_begin, NULL, 10);
if (date_overflows(date))
date = 0;
else {
if (ident->tz_begin && ident->tz_end)
tz = strtol(ident->tz_begin, NULL, 10);
if (tz >= INT_MAX || tz <= INT_MIN)
tz = 0;
}
return show_date(date, tz, mode);
}
void pp_user_info(struct pretty_print_context *pp,
const char *what, struct strbuf *sb,
const char *line, const char *encoding)
{
struct ident_split ident;
char *line_end;
const char *mailbuf, *namebuf;
size_t namelen, maillen;
int max_length = 78; /* per rfc2822 */
if (pp->fmt == CMIT_FMT_ONELINE)
return;
line_end = strchrnul(line, '\n');
if (split_ident_line(&ident, line, line_end - line))
return;
mailbuf = ident.mail_begin;
maillen = ident.mail_end - ident.mail_begin;
namebuf = ident.name_begin;
namelen = ident.name_end - ident.name_begin;
if (pp->mailmap)
map_user(pp->mailmap, &mailbuf, &maillen, &namebuf, &namelen);
if (cmit_fmt_is_mail(pp->fmt)) {
if (pp->from_ident && ident_cmp(pp->from_ident, &ident)) {
struct strbuf buf = STRBUF_INIT;
strbuf_addstr(&buf, "From: ");
strbuf_add(&buf, namebuf, namelen);
strbuf_addstr(&buf, " <");
strbuf_add(&buf, mailbuf, maillen);
strbuf_addstr(&buf, ">\n");
string_list_append(&pp->in_body_headers,
strbuf_detach(&buf, NULL));
mailbuf = pp->from_ident->mail_begin;
maillen = pp->from_ident->mail_end - mailbuf;
namebuf = pp->from_ident->name_begin;
namelen = pp->from_ident->name_end - namebuf;
}
strbuf_addstr(sb, "From: ");
if (needs_rfc2047_encoding(namebuf, namelen, RFC2047_ADDRESS)) {
add_rfc2047(sb, namebuf, namelen,
encoding, RFC2047_ADDRESS);
max_length = 76; /* per rfc2047 */
} else if (needs_rfc822_quoting(namebuf, namelen)) {
struct strbuf quoted = STRBUF_INIT;
add_rfc822_quoted(&quoted, namebuf, namelen);
strbuf_add_wrapped_bytes(sb, quoted.buf, quoted.len,
-6, 1, max_length);
strbuf_release(&quoted);
} else {
strbuf_add_wrapped_bytes(sb, namebuf, namelen,
-6, 1, max_length);
}
if (max_length <
last_line_length(sb) + strlen(" <") + maillen + strlen(">"))
strbuf_addch(sb, '\n');
strbuf_addf(sb, " <%.*s>\n", (int)maillen, mailbuf);
} else {
strbuf_addf(sb, "%s: %.*s%.*s <%.*s>\n", what,
(pp->fmt == CMIT_FMT_FULLER) ? 4 : 0, " ",
(int)namelen, namebuf, (int)maillen, mailbuf);
}
switch (pp->fmt) {
case CMIT_FMT_MEDIUM:
strbuf_addf(sb, "Date: %s\n",
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;
case CMIT_FMT_FULLER:
strbuf_addf(sb, "%sDate: %s\n", what,
show_ident_date(&ident, &pp->date_mode));
break;
default:
/* notin' */
break;
}
}
static int is_blank_line(const char *line, int *len_p)
{
int len = *len_p;
while (len && isspace(line[len - 1]))
len--;
*len_p = len;
return !len;
}
const char *skip_blank_lines(const char *msg)
{
for (;;) {
int linelen = get_one_line(msg);
int ll = linelen;
if (!linelen)
break;
if (!is_blank_line(msg, &ll))
break;
msg += linelen;
}
return msg;
}
static void add_merge_info(const struct pretty_print_context *pp,
struct strbuf *sb, const struct commit *commit)
{
struct commit_list *parent = commit->parents;
if ((pp->fmt == CMIT_FMT_ONELINE) || (cmit_fmt_is_mail(pp->fmt)) ||
!parent || !parent->next)
return;
strbuf_addstr(sb, "Merge:");
while (parent) {
struct object_id *oidp = &parent->item->object.oid;
strbuf_addch(sb, ' ');
if (pp->abbrev)
strbuf_add_unique_abbrev(sb, oidp->hash, pp->abbrev);
else
strbuf_addstr(sb, oid_to_hex(oidp));
parent = parent->next;
}
strbuf_addch(sb, '\n');
}
static char *get_header(const char *msg, const char *key)
{
size_t len;
const char *v = find_commit_header(msg, key, &len);
return v ? xmemdupz(v, len) : NULL;
}
static char *replace_encoding_header(char *buf, const char *encoding)
{
struct strbuf tmp = STRBUF_INIT;
size_t start, len;
char *cp = buf;
/* guess if there is an encoding header before a \n\n */
while (!starts_with(cp, "encoding ")) {
cp = strchr(cp, '\n');
if (!cp || *++cp == '\n')
return buf;
}
start = cp - buf;
cp = strchr(cp, '\n');
if (!cp)
return buf; /* should not happen but be defensive */
len = cp + 1 - (buf + start);
strbuf_attach(&tmp, buf, strlen(buf), strlen(buf) + 1);
if (is_encoding_utf8(encoding)) {
/* we have re-coded to UTF-8; drop the header */
strbuf_remove(&tmp, start, len);
} else {
/* just replaces XXXX in 'encoding XXXX\n' */
strbuf_splice(&tmp, start + strlen("encoding "),
len - strlen("encoding \n"),
encoding, strlen(encoding));
}
return strbuf_detach(&tmp, NULL);
}
const char *logmsg_reencode(const struct commit *commit,
char **commit_encoding,
const char *output_encoding)
{
static const char *utf8 = "UTF-8";
const char *use_encoding;
char *encoding;
const char *msg = get_commit_buffer(commit, NULL);
char *out;
if (!output_encoding || !*output_encoding) {
if (commit_encoding)
*commit_encoding = get_header(msg, "encoding");
return msg;
}
encoding = get_header(msg, "encoding");
if (commit_encoding)
*commit_encoding = encoding;
use_encoding = encoding ? encoding : utf8;
if (same_encoding(use_encoding, output_encoding)) {
/*
* No encoding work to be done. If we have no encoding header
* at all, then there's nothing to do, and we can return the
* message verbatim (whether newly allocated or not).
*/
if (!encoding)
return msg;
/*
* Otherwise, we still want to munge the encoding header in the
* result, which will be done by modifying the buffer. If we
* are using a fresh copy, we can reuse it. But if we are using
* the cached copy from get_commit_buffer, we need to duplicate it
* to avoid munging the cached copy.
*/
if (msg == get_cached_commit_buffer(commit, NULL))
out = xstrdup(msg);
else
out = (char *)msg;
}
else {
/*
* There's actual encoding work to do. Do the reencoding, which
* still leaves the header to be replaced in the next step. At
* this point, we are done with msg. If we allocated a fresh
* copy, we can free it.
*/
out = reencode_string(msg, output_encoding, use_encoding);
if (out)
unuse_commit_buffer(commit, msg);
}
/*
* This replacement actually consumes the buffer we hand it, so we do
* not have to worry about freeing the old "out" here.
*/
if (out)
out = replace_encoding_header(out, output_encoding);
if (!commit_encoding)
free(encoding);
/*
* If the re-encoding failed, out might be NULL here; in that
* case we just return the commit message verbatim.
*/
return out ? out : msg;
}
static int mailmap_name(const char **email, size_t *email_len,
const char **name, size_t *name_len)
{
static struct string_list *mail_map;
if (!mail_map) {
mail_map = xcalloc(1, sizeof(*mail_map));
read_mailmap(mail_map, NULL);
}
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,
const char *msg, int len,
const struct date_mode *dmode)
{
/* currently all placeholders have same length */
const int placeholder_len = 2;
struct ident_split s;
const char *name, *mail;
size_t maillen, namelen;
if (split_ident_line(&s, msg, len) < 0)
goto skip;
name = s.name_begin;
namelen = s.name_end - s.name_begin;
mail = s.mail_begin;
maillen = s.mail_end - s.mail_begin;
if (part == 'N' || part == 'E') /* mailmap lookup */
mailmap_name(&mail, &maillen, &name, &namelen);
if (part == 'n' || part == 'N') { /* name */
strbuf_add(sb, name, namelen);
return placeholder_len;
}
if (part == 'e' || part == 'E') { /* email */
strbuf_add(sb, mail, maillen);
return placeholder_len;
}
if (!s.date_begin)
goto skip;
if (part == 't') { /* date, UNIX timestamp */
strbuf_add(sb, s.date_begin, s.date_end - s.date_begin);
return placeholder_len;
}
switch (part) {
case 'd': /* date */
strbuf_addstr(sb, show_ident_date(&s, dmode));
return placeholder_len;
case 'D': /* date, RFC2822 style */
strbuf_addstr(sb, show_ident_date(&s, DATE_MODE(RFC2822)));
return placeholder_len;
case 'r': /* date, relative */
strbuf_addstr(sb, show_ident_date(&s, DATE_MODE(RELATIVE)));
return placeholder_len;
case 'i': /* date, ISO 8601-like */
strbuf_addstr(sb, show_ident_date(&s, DATE_MODE(ISO8601)));
return placeholder_len;
case 'I': /* date, ISO 8601 strict */
strbuf_addstr(sb, show_ident_date(&s, DATE_MODE(ISO8601_STRICT)));
return placeholder_len;
}
skip:
/*
* reading from either a bogus commit, or a reflog entry with
* %gn, %ge, etc.; 'sb' cannot be updated, but we still need
* to compute a valid return value.
*/
if (part == 'n' || part == 'e' || part == 't' || part == 'd'
|| part == 'D' || part == 'r' || part == 'i')
return placeholder_len;
return 0; /* unknown placeholder */
}
struct chunk {
size_t off;
size_t len;
};
enum flush_type {
no_flush,
flush_right,
flush_left,
flush_left_and_steal,
flush_both
};