2007-11-05 03:15:06 +08:00
|
|
|
#include "cache.h"
|
|
|
|
#include "commit.h"
|
|
|
|
#include "utf8.h"
|
|
|
|
#include "diff.h"
|
|
|
|
#include "revision.h"
|
2008-07-22 02:03:49 +08:00
|
|
|
#include "string-list.h"
|
2008-07-12 07:28:18 +08:00
|
|
|
#include "mailmap.h"
|
2008-09-05 05:40:03 +08:00
|
|
|
#include "log-tree.h"
|
2009-10-09 18:21:57 +08:00
|
|
|
#include "notes.h"
|
expand --pretty=format color options
Currently, the only colors available to --pretty=format
users are red, green, and blue. Rather than expand it with a
few new colors, this patch makes the usual config color
syntax available, including more colors, backgrounds, and
attributes.
Because colors are no longer bounded to a single word (e.g.,
%Cred), this uses a more advanced syntax that features a
beginning and end delimiter (but the old syntax still
works). So you can now do:
git log --pretty=tformat:'%C(yellow)%h%C(reset) %s'
to emulate --pretty=oneline, or even
git log --pretty=tformat:'%C(cyan magenta bold)%s%C(reset)'
if you want to relive the awesomeness of 4-color CGA.
Signed-off-by: Jeff King <peff@peff.net>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2009-01-17 23:38:46 +08:00
|
|
|
#include "color.h"
|
2009-10-19 23:48:10 +08:00
|
|
|
#include "reflog-walk.h"
|
2011-10-22 12:06:02 +08:00
|
|
|
#include "gpg-interface.h"
|
2007-11-05 03:15:06 +08:00
|
|
|
|
|
|
|
static char *user_format;
|
2010-05-02 19:00:42 +08:00
|
|
|
static struct cmt_fmt_map {
|
|
|
|
const char *name;
|
|
|
|
enum cmit_fmt format;
|
|
|
|
int is_tformat;
|
2010-05-02 19:00:43 +08:00
|
|
|
int is_alias;
|
|
|
|
const char *user_format;
|
2010-05-02 19:00:42 +08:00
|
|
|
} *commit_formats;
|
2010-05-02 19:00:44 +08:00
|
|
|
static size_t builtin_formats_len;
|
2010-05-02 19:00:42 +08:00
|
|
|
static size_t commit_formats_len;
|
2010-05-02 19:00:44 +08:00
|
|
|
static size_t commit_formats_alloc;
|
2010-05-02 19:00:42 +08:00
|
|
|
static struct cmt_fmt_map *find_commit_format(const char *sought);
|
2007-11-05 03:15:06 +08:00
|
|
|
|
2009-02-24 17:59:15 +08:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2010-05-02 19:00:44 +08:00
|
|
|
static int git_pretty_formats_config(const char *var, const char *value, void *cb)
|
2007-11-05 03:15:06 +08:00
|
|
|
{
|
2010-05-02 19:00:44 +08:00
|
|
|
struct cmt_fmt_map *commit_format = NULL;
|
|
|
|
const char *name;
|
|
|
|
const char *fmt;
|
2007-11-05 03:15:06 +08:00
|
|
|
int i;
|
2010-05-02 19:00:44 +08:00
|
|
|
|
2014-06-19 03:48:29 +08:00
|
|
|
if (!skip_prefix(var, "pretty.", &name))
|
2010-05-02 19:00:44 +08:00
|
|
|
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];
|
2010-05-09 05:07:39 +08:00
|
|
|
memset(commit_format, 0, sizeof(*commit_format));
|
2010-05-02 19:00:44 +08:00
|
|
|
commit_formats_len++;
|
|
|
|
}
|
|
|
|
|
|
|
|
commit_format->name = xstrdup(name);
|
|
|
|
commit_format->format = CMIT_FMT_USERFORMAT;
|
|
|
|
git_config_string(&fmt, var, value);
|
2013-12-01 04:55:40 +08:00
|
|
|
if (starts_with(fmt, "format:") || starts_with(fmt, "tformat:")) {
|
2010-05-02 19:00:44 +08:00
|
|
|
commit_format->is_tformat = fmt[0] == 't';
|
|
|
|
fmt = strchr(fmt, ':') + 1;
|
|
|
|
} else if (strchr(fmt, '%'))
|
|
|
|
commit_format->is_tformat = 1;
|
|
|
|
else
|
|
|
|
commit_format->is_alias = 1;
|
|
|
|
commit_format->user_format = fmt;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2010-05-02 19:00:42 +08:00
|
|
|
static void setup_commit_formats(void)
|
2007-11-05 03:15:06 +08:00
|
|
|
{
|
2010-05-02 19:00:42 +08:00
|
|
|
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 }
|
2008-04-08 08:11:34 +08:00
|
|
|
};
|
2010-05-02 19:00:42 +08:00
|
|
|
commit_formats_len = ARRAY_SIZE(builtin_formats);
|
2010-05-02 19:00:44 +08:00
|
|
|
builtin_formats_len = commit_formats_len;
|
|
|
|
ALLOC_GROW(commit_formats, commit_formats_len, commit_formats_alloc);
|
2010-05-02 19:00:42 +08:00
|
|
|
memcpy(commit_formats, builtin_formats,
|
|
|
|
sizeof(*builtin_formats)*ARRAY_SIZE(builtin_formats));
|
2010-05-02 19:00:44 +08:00
|
|
|
|
|
|
|
git_config(git_pretty_formats_config, NULL);
|
2010-05-02 19:00:42 +08:00
|
|
|
}
|
|
|
|
|
2010-05-02 19:00:43 +08:00
|
|
|
static struct cmt_fmt_map *find_commit_format_recursive(const char *sought,
|
|
|
|
const char *original,
|
|
|
|
int num_redirections)
|
2010-05-02 19:00:42 +08:00
|
|
|
{
|
|
|
|
struct cmt_fmt_map *found = NULL;
|
|
|
|
size_t found_match_len = 0;
|
|
|
|
int i;
|
|
|
|
|
2010-05-02 19:00:43 +08:00
|
|
|
if (num_redirections >= commit_formats_len)
|
|
|
|
die("invalid --pretty format: "
|
|
|
|
"'%s' references an alias which points to itself",
|
|
|
|
original);
|
2010-05-02 19:00:42 +08:00
|
|
|
|
|
|
|
for (i = 0; i < commit_formats_len; i++) {
|
|
|
|
size_t match_len;
|
|
|
|
|
2013-12-01 04:55:40 +08:00
|
|
|
if (!starts_with(commit_formats[i].name, sought))
|
2010-05-02 19:00:42 +08:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
}
|
2010-05-02 19:00:43 +08:00
|
|
|
|
|
|
|
if (found && found->is_alias) {
|
|
|
|
found = find_commit_format_recursive(found->user_format,
|
|
|
|
original,
|
|
|
|
num_redirections+1);
|
|
|
|
}
|
|
|
|
|
2010-05-02 19:00:42 +08:00
|
|
|
return found;
|
|
|
|
}
|
|
|
|
|
2010-05-02 19:00:43 +08:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
2010-05-02 19:00:42 +08:00
|
|
|
void get_commit_format(const char *arg, struct rev_info *rev)
|
|
|
|
{
|
|
|
|
struct cmt_fmt_map *commit_format;
|
2007-11-05 03:15:06 +08:00
|
|
|
|
2008-04-08 08:11:34 +08:00
|
|
|
rev->use_terminator = 0;
|
|
|
|
if (!arg || !*arg) {
|
|
|
|
rev->commit_format = CMIT_FMT_DEFAULT;
|
|
|
|
return;
|
|
|
|
}
|
2013-12-01 04:55:40 +08:00
|
|
|
if (starts_with(arg, "format:") || starts_with(arg, "tformat:")) {
|
2009-02-24 17:59:15 +08:00
|
|
|
save_user_format(rev, strchr(arg, ':') + 1, arg[0] == 't');
|
2008-04-08 08:11:34 +08:00
|
|
|
return;
|
2007-11-05 03:15:06 +08:00
|
|
|
}
|
2010-05-02 19:00:42 +08:00
|
|
|
|
2009-02-24 17:59:15 +08:00
|
|
|
if (strchr(arg, '%')) {
|
|
|
|
save_user_format(rev, arg, 1);
|
|
|
|
return;
|
|
|
|
}
|
2007-11-05 03:15:06 +08:00
|
|
|
|
2010-05-02 19:00:42 +08:00
|
|
|
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;
|
2010-05-02 19:00:44 +08:00
|
|
|
if (commit_format->format == CMIT_FMT_USERFORMAT) {
|
|
|
|
save_user_format(rev, commit_format->user_format,
|
|
|
|
commit_format->is_tformat);
|
|
|
|
}
|
2007-11-05 03:15:06 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* 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 */
|
2010-01-12 14:23:35 +08:00
|
|
|
static int non_ascii(int ch)
|
2007-11-05 03:15:06 +08:00
|
|
|
{
|
2009-03-07 21:06:49 +08:00
|
|
|
return !isascii(ch) || ch == '\033';
|
2007-11-05 03:15:06 +08:00
|
|
|
}
|
|
|
|
|
2009-08-11 00:22:18 +08:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2011-04-09 06:40:36 +08:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2012-10-18 22:43:33 +08:00
|
|
|
static int needs_rfc822_quoting(const char *s, int len)
|
2011-04-09 06:40:36 +08:00
|
|
|
{
|
|
|
|
int i;
|
|
|
|
for (i = 0; i < len; i++)
|
|
|
|
if (is_rfc822_special(s[i]))
|
|
|
|
return 1;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2012-10-18 22:43:31 +08:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
2011-04-09 06:40:36 +08:00
|
|
|
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, '"');
|
|
|
|
}
|
|
|
|
|
2012-10-18 22:43:32 +08:00
|
|
|
enum rfc2047_type {
|
|
|
|
RFC2047_SUBJECT,
|
2014-07-03 02:24:05 +08:00
|
|
|
RFC2047_ADDRESS
|
2012-10-18 22:43:32 +08:00
|
|
|
};
|
|
|
|
|
|
|
|
static int is_rfc2047_special(char ch, enum rfc2047_type type)
|
2007-11-05 03:15:06 +08:00
|
|
|
{
|
2012-10-18 22:43:32 +08:00
|
|
|
/*
|
|
|
|
* 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 == '_')
|
2012-10-18 22:43:30 +08:00
|
|
|
return 1;
|
|
|
|
|
2012-10-18 22:43:32 +08:00
|
|
|
/*
|
|
|
|
* 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 == '/');
|
2007-11-05 03:15:06 +08:00
|
|
|
}
|
|
|
|
|
2012-10-18 22:43:33 +08:00
|
|
|
static int needs_rfc2047_encoding(const char *line, int len,
|
|
|
|
enum rfc2047_type type)
|
2007-11-05 03:15:06 +08:00
|
|
|
{
|
2011-02-23 17:58:41 +08:00
|
|
|
int i;
|
2007-11-05 03:15:06 +08:00
|
|
|
|
|
|
|
for (i = 0; i < len; i++) {
|
|
|
|
int ch = line[i];
|
2011-02-23 17:59:18 +08:00
|
|
|
if (non_ascii(ch) || ch == '\n')
|
2012-10-18 22:43:33 +08:00
|
|
|
return 1;
|
2007-11-05 03:15:06 +08:00
|
|
|
if ((i + 1 < len) && (ch == '=' && line[i+1] == '?'))
|
2012-10-18 22:43:33 +08:00
|
|
|
return 1;
|
2007-11-05 03:15:06 +08:00
|
|
|
}
|
|
|
|
|
2012-10-18 22:43:33 +08:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2013-03-07 18:55:07 +08:00
|
|
|
static void add_rfc2047(struct strbuf *sb, const char *line, size_t len,
|
2012-10-18 22:43:33 +08:00
|
|
|
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);
|
|
|
|
|
2007-11-05 03:15:06 +08:00
|
|
|
strbuf_grow(sb, len * 3 + strlen(encoding) + 100);
|
|
|
|
strbuf_addf(sb, "=?%s?q?", encoding);
|
2011-02-23 17:58:41 +08:00
|
|
|
line_len += strlen(encoding) + 5; /* 5 for =??q? */
|
2013-03-07 18:55:07 +08:00
|
|
|
|
|
|
|
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;
|
2012-10-18 22:43:30 +08:00
|
|
|
|
|
|
|
/*
|
|
|
|
* 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.
|
|
|
|
*/
|
2011-02-23 17:58:41 +08:00
|
|
|
|
2013-03-07 18:55:07 +08:00
|
|
|
if (line_len + encoded_len + 2 > max_encoded_length) {
|
|
|
|
/* It won't fit with trailing "?=" --- break the line */
|
2011-02-23 17:58:41 +08:00
|
|
|
strbuf_addf(sb, "?=\n =?%s?q?", encoding);
|
|
|
|
line_len = strlen(encoding) + 5 + 1; /* =??q? plus SP */
|
|
|
|
}
|
|
|
|
|
2013-03-07 18:55:07 +08:00
|
|
|
for (i = 0; i < chrlen; i++)
|
|
|
|
strbuf_addf(sb, encoded_fmt, p[i]);
|
|
|
|
line_len += encoded_len;
|
2007-11-05 03:15:06 +08:00
|
|
|
}
|
|
|
|
strbuf_addstr(sb, "?=");
|
|
|
|
}
|
|
|
|
|
2014-05-02 09:07:22 +08:00
|
|
|
const char *show_ident_date(const struct ident_split *ident,
|
|
|
|
enum date_mode mode)
|
2013-04-18 02:33:35 +08:00
|
|
|
{
|
|
|
|
unsigned long date = 0;
|
2014-03-08 01:15:01 +08:00
|
|
|
long tz = 0;
|
2013-04-18 02:33:35 +08:00
|
|
|
|
|
|
|
if (ident->date_begin && ident->date_end)
|
|
|
|
date = strtoul(ident->date_begin, NULL, 10);
|
log: handle integer overflow in timestamps
If an ident line has a ridiculous date value like (2^64)+1,
we currently just pass ULONG_MAX along to the date code,
which can produce nonsensical dates.
On systems with a signed long time_t (e.g., 64-bit glibc
systems), this actually doesn't end up too bad. The
ULONG_MAX is converted to -1, we apply the timezone field to
that, and the result ends up somewhere between Dec 31, 1969
and Jan 1, 1970.
However, there is still a few good reasons to detect the
overflow explicitly:
1. On systems where "unsigned long" is smaller than
time_t, we get a nonsensical date in the future.
2. Even where it would produce "Dec 31, 1969", it's easier
to recognize "midnight Jan 1" as a consistent sentinel
value for "we could not parse this".
3. Values which do not overflow strtoul but do overflow a
signed time_t produce nonsensical values in the past.
For example, on a 64-bit system with a signed long
time_t, a timestamp of 18446744073000000000 produces a
date in 1947.
We also recognize overflow in the timezone field, which
could produce nonsensical results. In this case we show the
parsed date, but in UTC.
Signed-off-by: Jeff King <peff@peff.net>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2014-02-24 15:46:37 +08:00
|
|
|
if (date_overflows(date))
|
|
|
|
date = 0;
|
|
|
|
else {
|
|
|
|
if (ident->tz_begin && ident->tz_end)
|
|
|
|
tz = strtol(ident->tz_begin, NULL, 10);
|
2014-03-08 01:15:01 +08:00
|
|
|
if (tz >= INT_MAX || tz <= INT_MIN)
|
log: handle integer overflow in timestamps
If an ident line has a ridiculous date value like (2^64)+1,
we currently just pass ULONG_MAX along to the date code,
which can produce nonsensical dates.
On systems with a signed long time_t (e.g., 64-bit glibc
systems), this actually doesn't end up too bad. The
ULONG_MAX is converted to -1, we apply the timezone field to
that, and the result ends up somewhere between Dec 31, 1969
and Jan 1, 1970.
However, there is still a few good reasons to detect the
overflow explicitly:
1. On systems where "unsigned long" is smaller than
time_t, we get a nonsensical date in the future.
2. Even where it would produce "Dec 31, 1969", it's easier
to recognize "midnight Jan 1" as a consistent sentinel
value for "we could not parse this".
3. Values which do not overflow strtoul but do overflow a
signed time_t produce nonsensical values in the past.
For example, on a 64-bit system with a signed long
time_t, a timestamp of 18446744073000000000 produces a
date in 1947.
We also recognize overflow in the timezone field, which
could produce nonsensical results. In this case we show the
parsed date, but in UTC.
Signed-off-by: Jeff King <peff@peff.net>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2014-02-24 15:46:37 +08:00
|
|
|
tz = 0;
|
|
|
|
}
|
2013-04-18 02:33:35 +08:00
|
|
|
return show_date(date, tz, mode);
|
|
|
|
}
|
|
|
|
|
2013-07-03 15:07:48 +08:00
|
|
|
void pp_user_info(struct pretty_print_context *pp,
|
2011-05-27 06:27:49 +08:00
|
|
|
const char *what, struct strbuf *sb,
|
|
|
|
const char *line, const char *encoding)
|
2007-11-05 03:15:06 +08:00
|
|
|
{
|
2013-01-06 05:26:38 +08:00
|
|
|
struct ident_split ident;
|
2013-04-18 02:33:35 +08:00
|
|
|
char *line_end;
|
2013-01-06 05:26:42 +08:00
|
|
|
const char *mailbuf, *namebuf;
|
|
|
|
size_t namelen, maillen;
|
2012-10-18 22:43:33 +08:00
|
|
|
int max_length = 78; /* per rfc2822 */
|
2007-11-05 03:15:06 +08:00
|
|
|
|
2011-05-27 06:27:49 +08:00
|
|
|
if (pp->fmt == CMIT_FMT_ONELINE)
|
2007-11-05 03:15:06 +08:00
|
|
|
return;
|
2013-01-06 05:26:38 +08:00
|
|
|
|
2013-04-26 03:40:25 +08:00
|
|
|
line_end = strchrnul(line, '\n');
|
|
|
|
if (split_ident_line(&ident, line, line_end - line))
|
2007-11-05 03:15:06 +08:00
|
|
|
return;
|
2013-01-06 05:26:38 +08:00
|
|
|
|
2013-01-06 05:26:42 +08:00
|
|
|
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);
|
|
|
|
|
2011-05-27 06:27:49 +08:00
|
|
|
if (pp->fmt == CMIT_FMT_EMAIL) {
|
2013-09-20 18:16:28 +08:00
|
|
|
if (pp->from_ident && ident_cmp(pp->from_ident, &ident)) {
|
teach format-patch to place other authors into in-body "From"
Format-patch generates emails with the "From" address set to the
author of each patch. If you are going to send the emails, however,
you would want to replace the author identity with yours (if they
are not the same), and bump the author identity to an in-body
header.
Normally this is handled by git-send-email, which does the
transformation before sending out the emails. However, some
workflows may not use send-email (e.g., imap-send, or a custom
script which feeds the mbox to a non-git MUA). They could each
implement this feature themselves, but getting it right is
non-trivial (one must canonicalize the identities by reversing any
RFC2047 encoding or RFC822 quoting of the headers, which has caused
many bugs in send-email over the years).
This patch takes a different approach: it teaches format-patch a
"--from" option which handles the ident check and in-body header
while it is writing out the email. It's much simpler to do at this
level (because we haven't done any quoting yet), and any workflow
based on format-patch can easily turn it on.
Signed-off-by: Jeff King <peff@peff.net>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2013-07-03 15:08:22 +08:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2007-11-05 03:15:06 +08:00
|
|
|
strbuf_addstr(sb, "From: ");
|
2013-04-26 03:43:56 +08:00
|
|
|
if (needs_rfc2047_encoding(namebuf, namelen, RFC2047_ADDRESS)) {
|
|
|
|
add_rfc2047(sb, namebuf, namelen,
|
2013-01-06 05:26:42 +08:00
|
|
|
encoding, RFC2047_ADDRESS);
|
2012-10-18 22:43:33 +08:00
|
|
|
max_length = 76; /* per rfc2047 */
|
2013-04-26 03:43:56 +08:00
|
|
|
} else if (needs_rfc822_quoting(namebuf, namelen)) {
|
2011-04-09 06:40:36 +08:00
|
|
|
struct strbuf quoted = STRBUF_INIT;
|
2013-04-26 03:43:56 +08:00
|
|
|
add_rfc822_quoted("ed, namebuf, namelen);
|
2012-10-18 22:43:33 +08:00
|
|
|
strbuf_add_wrapped_bytes(sb, quoted.buf, quoted.len,
|
|
|
|
-6, 1, max_length);
|
2011-04-09 06:40:36 +08:00
|
|
|
strbuf_release("ed);
|
2012-10-18 22:43:33 +08:00
|
|
|
} else {
|
2013-04-26 03:43:56 +08:00
|
|
|
strbuf_add_wrapped_bytes(sb, namebuf, namelen,
|
2013-01-06 05:26:42 +08:00
|
|
|
-6, 1, max_length);
|
2011-04-09 06:40:36 +08:00
|
|
|
}
|
2013-01-06 05:26:42 +08:00
|
|
|
|
2013-04-26 03:41:57 +08:00
|
|
|
if (max_length <
|
|
|
|
last_line_length(sb) + strlen(" <") + maillen + strlen(">"))
|
|
|
|
strbuf_addch(sb, '\n');
|
2013-04-26 03:43:56 +08:00
|
|
|
strbuf_addf(sb, " <%.*s>\n", (int)maillen, mailbuf);
|
2007-11-05 03:15:06 +08:00
|
|
|
} else {
|
2013-04-26 03:43:56 +08:00
|
|
|
strbuf_addf(sb, "%s: %.*s%.*s <%.*s>\n", what,
|
|
|
|
(pp->fmt == CMIT_FMT_FULLER) ? 4 : 0, " ",
|
|
|
|
(int)namelen, namebuf, (int)maillen, mailbuf);
|
2007-11-05 03:15:06 +08:00
|
|
|
}
|
2013-01-06 05:26:42 +08:00
|
|
|
|
2011-05-27 06:27:49 +08:00
|
|
|
switch (pp->fmt) {
|
2007-11-05 03:15:06 +08:00
|
|
|
case CMIT_FMT_MEDIUM:
|
2013-04-18 02:33:35 +08:00
|
|
|
strbuf_addf(sb, "Date: %s\n",
|
|
|
|
show_ident_date(&ident, pp->date_mode));
|
2007-11-05 03:15:06 +08:00
|
|
|
break;
|
|
|
|
case CMIT_FMT_EMAIL:
|
2013-04-18 02:33:35 +08:00
|
|
|
strbuf_addf(sb, "Date: %s\n",
|
|
|
|
show_ident_date(&ident, DATE_RFC2822));
|
2007-11-05 03:15:06 +08:00
|
|
|
break;
|
|
|
|
case CMIT_FMT_FULLER:
|
2013-04-18 02:33:35 +08:00
|
|
|
strbuf_addf(sb, "%sDate: %s\n", what,
|
|
|
|
show_ident_date(&ident, pp->date_mode));
|
2007-11-05 03:15:06 +08:00
|
|
|
break;
|
|
|
|
default:
|
|
|
|
/* notin' */
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static int is_empty_line(const char *line, int *len_p)
|
|
|
|
{
|
|
|
|
int len = *len_p;
|
2013-10-31 17:25:42 +08:00
|
|
|
while (len && isspace(line[len - 1]))
|
2007-11-05 03:15:06 +08:00
|
|
|
len--;
|
|
|
|
*len_p = len;
|
|
|
|
return !len;
|
|
|
|
}
|
|
|
|
|
2008-12-27 08:32:49 +08:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2011-05-27 06:27:49 +08:00
|
|
|
static void add_merge_info(const struct pretty_print_context *pp,
|
|
|
|
struct strbuf *sb, const struct commit *commit)
|
2007-11-05 03:15:06 +08:00
|
|
|
{
|
|
|
|
struct commit_list *parent = commit->parents;
|
|
|
|
|
2011-05-27 06:27:49 +08:00
|
|
|
if ((pp->fmt == CMIT_FMT_ONELINE) || (pp->fmt == CMIT_FMT_EMAIL) ||
|
2007-11-05 03:15:06 +08:00
|
|
|
!parent || !parent->next)
|
|
|
|
return;
|
|
|
|
|
|
|
|
strbuf_addstr(sb, "Merge:");
|
|
|
|
|
|
|
|
while (parent) {
|
|
|
|
struct commit *p = parent->item;
|
|
|
|
const char *hex = NULL;
|
2011-05-27 06:27:49 +08:00
|
|
|
if (pp->abbrev)
|
|
|
|
hex = find_unique_abbrev(p->object.sha1, pp->abbrev);
|
2007-11-05 03:15:06 +08:00
|
|
|
if (!hex)
|
|
|
|
hex = sha1_to_hex(p->object.sha1);
|
|
|
|
parent = parent->next;
|
|
|
|
|
2009-02-14 06:10:41 +08:00
|
|
|
strbuf_addf(sb, " %s", hex);
|
2007-11-05 03:15:06 +08:00
|
|
|
}
|
|
|
|
strbuf_addch(sb, '\n');
|
|
|
|
}
|
|
|
|
|
2013-01-26 17:44:06 +08:00
|
|
|
static char *get_header(const struct commit *commit, const char *msg,
|
|
|
|
const char *key)
|
2007-11-05 03:15:06 +08:00
|
|
|
{
|
|
|
|
int key_len = strlen(key);
|
2013-01-26 17:44:06 +08:00
|
|
|
const char *line = msg;
|
2007-11-05 03:15:06 +08:00
|
|
|
|
2012-05-22 12:52:17 +08:00
|
|
|
while (line) {
|
2014-03-08 14:48:31 +08:00
|
|
|
const char *eol = strchrnul(line, '\n'), *next;
|
2007-11-05 03:15:06 +08:00
|
|
|
|
|
|
|
if (line == eol)
|
|
|
|
return NULL;
|
2014-03-08 14:48:31 +08:00
|
|
|
if (!*eol) {
|
2012-05-22 12:52:17 +08:00
|
|
|
warning("malformed commit (header is missing newline): %s",
|
|
|
|
sha1_to_hex(commit->object.sha1));
|
2007-11-05 03:15:06 +08:00
|
|
|
next = NULL;
|
|
|
|
} else
|
|
|
|
next = eol + 1;
|
|
|
|
if (eol - line > key_len &&
|
|
|
|
!strncmp(line, key, key_len) &&
|
|
|
|
line[key_len] == ' ') {
|
|
|
|
return xmemdupz(line + key_len + 1, eol - line - key_len - 1);
|
|
|
|
}
|
|
|
|
line = next;
|
|
|
|
}
|
2012-05-22 12:52:17 +08:00
|
|
|
return NULL;
|
2007-11-05 03:15:06 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
static char *replace_encoding_header(char *buf, const char *encoding)
|
|
|
|
{
|
2008-10-10 03:12:12 +08:00
|
|
|
struct strbuf tmp = STRBUF_INIT;
|
2007-11-05 03:15:06 +08:00
|
|
|
size_t start, len;
|
|
|
|
char *cp = buf;
|
|
|
|
|
|
|
|
/* guess if there is an encoding header before a \n\n */
|
|
|
|
while (strncmp(cp, "encoding ", strlen("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);
|
|
|
|
}
|
|
|
|
|
2014-06-11 05:39:30 +08:00
|
|
|
const char *logmsg_reencode(const struct commit *commit,
|
|
|
|
char **commit_encoding,
|
|
|
|
const char *output_encoding)
|
2007-11-05 03:15:06 +08:00
|
|
|
{
|
2009-05-19 07:44:39 +08:00
|
|
|
static const char *utf8 = "UTF-8";
|
2007-11-05 03:15:06 +08:00
|
|
|
const char *use_encoding;
|
|
|
|
char *encoding;
|
2014-06-11 05:44:13 +08:00
|
|
|
const char *msg = get_commit_buffer(commit, NULL);
|
2007-11-05 03:15:06 +08:00
|
|
|
char *out;
|
|
|
|
|
2013-04-19 07:08:40 +08:00
|
|
|
if (!output_encoding || !*output_encoding) {
|
|
|
|
if (commit_encoding)
|
|
|
|
*commit_encoding =
|
|
|
|
get_header(commit, msg, "encoding");
|
2013-01-26 17:44:06 +08:00
|
|
|
return msg;
|
2013-04-19 07:08:40 +08:00
|
|
|
}
|
2013-01-26 17:44:06 +08:00
|
|
|
encoding = get_header(commit, msg, "encoding");
|
2013-04-19 07:08:40 +08:00
|
|
|
if (commit_encoding)
|
|
|
|
*commit_encoding = encoding;
|
2007-11-05 03:15:06 +08:00
|
|
|
use_encoding = encoding ? encoding : utf8;
|
logmsg_reencode: lazily load missing commit buffers
Usually a commit that makes it to logmsg_reencode will have
been parsed, and the commit->buffer struct member will be
valid. However, some code paths will free commit buffers
after having used them (for example, the log traversal
machinery will do so to keep memory usage down).
Most of the time this is fine; log should only show a commit
once, and then exits. However, there are some code paths
where this does not work. At least two are known:
1. A commit may be shown as part of a regular ref, and
then it may be shown again as part of a submodule diff
(e.g., if a repo contains refs to both the superproject
and subproject).
2. A notes-cache commit may be shown during "log --all",
and then later used to access a textconv cache during a
diff.
Lazily loading in logmsg_reencode does not necessarily catch
all such cases, but it should catch most of them. Users of
the commit buffer tend to be either parsing for structure
(in which they will call parse_commit, and either we will
already have parsed, or we will load commit->buffer lazily
there), or outputting (either to the user, or fetching a
part of the commit message via format_commit_message). In
the latter case, we should always be using logmsg_reencode
anyway (and typically we do so via the pretty-print
machinery).
If there are any cases that this misses, we can fix them up
to use logmsg_reencode (or handle them on a case-by-case
basis if that is inappropriate).
Signed-off-by: Jeff King <peff@peff.net>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2013-01-26 17:44:28 +08:00
|
|
|
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
|
2014-06-11 05:41:39 +08:00
|
|
|
* the cached copy from get_commit_buffer, we need to duplicate it
|
|
|
|
* to avoid munging the cached copy.
|
logmsg_reencode: lazily load missing commit buffers
Usually a commit that makes it to logmsg_reencode will have
been parsed, and the commit->buffer struct member will be
valid. However, some code paths will free commit buffers
after having used them (for example, the log traversal
machinery will do so to keep memory usage down).
Most of the time this is fine; log should only show a commit
once, and then exits. However, there are some code paths
where this does not work. At least two are known:
1. A commit may be shown as part of a regular ref, and
then it may be shown again as part of a submodule diff
(e.g., if a repo contains refs to both the superproject
and subproject).
2. A notes-cache commit may be shown during "log --all",
and then later used to access a textconv cache during a
diff.
Lazily loading in logmsg_reencode does not necessarily catch
all such cases, but it should catch most of them. Users of
the commit buffer tend to be either parsing for structure
(in which they will call parse_commit, and either we will
already have parsed, or we will load commit->buffer lazily
there), or outputting (either to the user, or fetching a
part of the commit message via format_commit_message). In
the latter case, we should always be using logmsg_reencode
anyway (and typically we do so via the pretty-print
machinery).
If there are any cases that this misses, we can fix them up
to use logmsg_reencode (or handle them on a case-by-case
basis if that is inappropriate).
Signed-off-by: Jeff King <peff@peff.net>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2013-01-26 17:44:28 +08:00
|
|
|
*/
|
2014-06-11 05:44:13 +08:00
|
|
|
if (msg == get_cached_commit_buffer(commit, NULL))
|
2014-06-11 05:41:39 +08:00
|
|
|
out = xstrdup(msg);
|
|
|
|
else
|
|
|
|
out = (char *)msg;
|
logmsg_reencode: lazily load missing commit buffers
Usually a commit that makes it to logmsg_reencode will have
been parsed, and the commit->buffer struct member will be
valid. However, some code paths will free commit buffers
after having used them (for example, the log traversal
machinery will do so to keep memory usage down).
Most of the time this is fine; log should only show a commit
once, and then exits. However, there are some code paths
where this does not work. At least two are known:
1. A commit may be shown as part of a regular ref, and
then it may be shown again as part of a submodule diff
(e.g., if a repo contains refs to both the superproject
and subproject).
2. A notes-cache commit may be shown during "log --all",
and then later used to access a textconv cache during a
diff.
Lazily loading in logmsg_reencode does not necessarily catch
all such cases, but it should catch most of them. Users of
the commit buffer tend to be either parsing for structure
(in which they will call parse_commit, and either we will
already have parsed, or we will load commit->buffer lazily
there), or outputting (either to the user, or fetching a
part of the commit message via format_commit_message). In
the latter case, we should always be using logmsg_reencode
anyway (and typically we do so via the pretty-print
machinery).
If there are any cases that this misses, we can fix them up
to use logmsg_reencode (or handle them on a case-by-case
basis if that is inappropriate).
Signed-off-by: Jeff King <peff@peff.net>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2013-01-26 17:44:28 +08:00
|
|
|
}
|
|
|
|
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);
|
2014-06-11 05:41:39 +08:00
|
|
|
if (out)
|
|
|
|
unuse_commit_buffer(commit, msg);
|
logmsg_reencode: lazily load missing commit buffers
Usually a commit that makes it to logmsg_reencode will have
been parsed, and the commit->buffer struct member will be
valid. However, some code paths will free commit buffers
after having used them (for example, the log traversal
machinery will do so to keep memory usage down).
Most of the time this is fine; log should only show a commit
once, and then exits. However, there are some code paths
where this does not work. At least two are known:
1. A commit may be shown as part of a regular ref, and
then it may be shown again as part of a submodule diff
(e.g., if a repo contains refs to both the superproject
and subproject).
2. A notes-cache commit may be shown during "log --all",
and then later used to access a textconv cache during a
diff.
Lazily loading in logmsg_reencode does not necessarily catch
all such cases, but it should catch most of them. Users of
the commit buffer tend to be either parsing for structure
(in which they will call parse_commit, and either we will
already have parsed, or we will load commit->buffer lazily
there), or outputting (either to the user, or fetching a
part of the commit message via format_commit_message). In
the latter case, we should always be using logmsg_reencode
anyway (and typically we do so via the pretty-print
machinery).
If there are any cases that this misses, we can fix them up
to use logmsg_reencode (or handle them on a case-by-case
basis if that is inappropriate).
Signed-off-by: Jeff King <peff@peff.net>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2013-01-26 17:44:28 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* This replacement actually consumes the buffer we hand it, so we do
|
|
|
|
* not have to worry about freeing the old "out" here.
|
|
|
|
*/
|
2007-11-05 03:15:06 +08:00
|
|
|
if (out)
|
|
|
|
out = replace_encoding_header(out, output_encoding);
|
|
|
|
|
2013-04-19 07:08:40 +08:00
|
|
|
if (!commit_encoding)
|
|
|
|
free(encoding);
|
2013-01-26 17:44:06 +08:00
|
|
|
/*
|
|
|
|
* If the re-encoding failed, out might be NULL here; in that
|
|
|
|
* case we just return the commit message verbatim.
|
|
|
|
*/
|
|
|
|
return out ? out : msg;
|
|
|
|
}
|
|
|
|
|
2013-01-06 05:26:40 +08:00
|
|
|
static int mailmap_name(const char **email, size_t *email_len,
|
|
|
|
const char **name, size_t *name_len)
|
2008-07-12 07:28:18 +08:00
|
|
|
{
|
2008-07-22 02:03:49 +08:00
|
|
|
static struct string_list *mail_map;
|
2008-07-12 07:28:18 +08:00
|
|
|
if (!mail_map) {
|
|
|
|
mail_map = xcalloc(1, sizeof(*mail_map));
|
2009-02-08 22:34:27 +08:00
|
|
|
read_mailmap(mail_map, NULL);
|
2008-07-12 07:28:18 +08:00
|
|
|
}
|
2009-02-08 22:34:30 +08:00
|
|
|
return mail_map->nr && map_user(mail_map, email, email_len, name, name_len);
|
2008-07-12 07:28:18 +08:00
|
|
|
}
|
|
|
|
|
2008-02-09 22:40:19 +08:00
|
|
|
static size_t format_person_part(struct strbuf *sb, char part,
|
2008-08-29 08:54:59 +08:00
|
|
|
const char *msg, int len, enum date_mode dmode)
|
2007-11-05 03:15:06 +08:00
|
|
|
{
|
2008-02-09 22:40:19 +08:00
|
|
|
/* currently all placeholders have same length */
|
|
|
|
const int placeholder_len = 2;
|
2012-03-11 17:25:43 +08:00
|
|
|
struct ident_split s;
|
2013-01-06 05:26:40 +08:00
|
|
|
const char *name, *mail;
|
|
|
|
size_t maillen, namelen;
|
2007-11-05 03:15:06 +08:00
|
|
|
|
2012-03-11 17:25:43 +08:00
|
|
|
if (split_ident_line(&s, msg, len) < 0)
|
2008-02-09 22:40:19 +08:00
|
|
|
goto skip;
|
|
|
|
|
2013-01-06 05:26:40 +08:00
|
|
|
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);
|
2008-07-12 07:28:18 +08:00
|
|
|
if (part == 'n' || part == 'N') { /* name */
|
2013-01-06 05:26:40 +08:00
|
|
|
strbuf_add(sb, name, namelen);
|
2008-02-09 22:40:19 +08:00
|
|
|
return placeholder_len;
|
2007-11-09 08:49:42 +08:00
|
|
|
}
|
2009-02-08 22:34:30 +08:00
|
|
|
if (part == 'e' || part == 'E') { /* email */
|
2013-01-06 05:26:40 +08:00
|
|
|
strbuf_add(sb, mail, maillen);
|
2008-02-09 22:40:19 +08:00
|
|
|
return placeholder_len;
|
2007-11-09 08:49:42 +08:00
|
|
|
}
|
2007-11-05 03:15:06 +08:00
|
|
|
|
2012-03-11 17:25:43 +08:00
|
|
|
if (!s.date_begin)
|
2008-02-09 22:40:19 +08:00
|
|
|
goto skip;
|
2007-11-05 03:15:06 +08:00
|
|
|
|
2007-11-09 08:49:42 +08:00
|
|
|
if (part == 't') { /* date, UNIX timestamp */
|
2012-03-11 17:25:43 +08:00
|
|
|
strbuf_add(sb, s.date_begin, s.date_end - s.date_begin);
|
2008-02-09 22:40:19 +08:00
|
|
|
return placeholder_len;
|
2007-11-09 08:49:42 +08:00
|
|
|
}
|
2007-11-05 03:15:06 +08:00
|
|
|
|
2007-11-09 08:49:42 +08:00
|
|
|
switch (part) {
|
|
|
|
case 'd': /* date */
|
2013-04-18 02:33:35 +08:00
|
|
|
strbuf_addstr(sb, show_ident_date(&s, dmode));
|
2008-02-09 22:40:19 +08:00
|
|
|
return placeholder_len;
|
2007-11-09 08:49:42 +08:00
|
|
|
case 'D': /* date, RFC2822 style */
|
2013-04-18 02:33:35 +08:00
|
|
|
strbuf_addstr(sb, show_ident_date(&s, DATE_RFC2822));
|
2008-02-09 22:40:19 +08:00
|
|
|
return placeholder_len;
|
2007-11-09 08:49:42 +08:00
|
|
|
case 'r': /* date, relative */
|
2013-04-18 02:33:35 +08:00
|
|
|
strbuf_addstr(sb, show_ident_date(&s, DATE_RELATIVE));
|
2008-02-09 22:40:19 +08:00
|
|
|
return placeholder_len;
|
2007-11-09 08:49:42 +08:00
|
|
|
case 'i': /* date, ISO 8601 */
|
2013-04-18 02:33:35 +08:00
|
|
|
strbuf_addstr(sb, show_ident_date(&s, DATE_ISO8601));
|
2008-02-09 22:40:19 +08:00
|
|
|
return placeholder_len;
|
2007-11-09 08:49:42 +08:00
|
|
|
}
|
2008-02-09 22:40:19 +08:00
|
|
|
|
|
|
|
skip:
|
|
|
|
/*
|
2012-03-11 17:25:43 +08:00
|
|
|
* 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.
|
2008-02-09 22:40:19 +08:00
|
|
|
*/
|
|
|
|
if (part == 'n' || part == 'e' || part == 't' || part == 'd'
|
|
|
|
|| part == 'D' || part == 'r' || part == 'i')
|
|
|
|
return placeholder_len;
|
|
|
|
|
|
|
|
return 0; /* unknown placeholder */
|
2007-11-05 03:15:06 +08:00
|
|
|
}
|
|
|
|
|
2007-11-10 19:14:20 +08:00
|
|
|
struct chunk {
|
|
|
|
size_t off;
|
|
|
|
size_t len;
|
|
|
|
};
|
|
|
|
|
2013-04-19 07:08:50 +08:00
|
|
|
enum flush_type {
|
|
|
|
no_flush,
|
|
|
|
flush_right,
|
|
|
|
flush_left,
|
2013-04-19 07:08:52 +08:00
|
|
|
flush_left_and_steal,
|
2013-04-19 07:08:50 +08:00
|
|
|
flush_both
|
|
|
|
};
|
|
|
|
|
2013-04-19 07:08:51 +08:00
|
|
|
enum trunc_type {
|
|
|
|
trunc_none,
|
|
|
|
trunc_left,
|
|
|
|
trunc_middle,
|
|
|
|
trunc_right
|
|
|
|
};
|
|
|
|
|
2007-11-10 19:14:20 +08:00
|
|
|
struct format_commit_context {
|
|
|
|
const struct commit *commit;
|
2009-10-19 23:48:08 +08:00
|
|
|
const struct pretty_print_context *pretty_ctx;
|
2008-12-27 08:49:21 +08:00
|
|
|
unsigned commit_header_parsed:1;
|
|
|
|
unsigned commit_message_parsed:1;
|
2013-04-01 00:00:14 +08:00
|
|
|
struct signature_check signature_check;
|
2013-04-19 07:08:50 +08:00
|
|
|
enum flush_type flush_type;
|
2013-04-19 07:08:51 +08:00
|
|
|
enum trunc_type truncate;
|
2014-06-11 05:39:30 +08:00
|
|
|
const char *message;
|
2013-04-19 07:08:41 +08:00
|
|
|
char *commit_encoding;
|
2009-10-18 05:04:19 +08:00
|
|
|
size_t width, indent1, indent2;
|
2013-04-19 07:08:49 +08:00
|
|
|
int auto_color;
|
2013-04-19 07:08:50 +08:00
|
|
|
int padding;
|
2007-11-10 19:14:20 +08:00
|
|
|
|
|
|
|
/* These offsets are relative to the start of the commit message. */
|
|
|
|
struct chunk author;
|
|
|
|
struct chunk committer;
|
2008-12-27 08:49:21 +08:00
|
|
|
size_t message_off;
|
|
|
|
size_t subject_off;
|
2007-11-10 19:14:20 +08:00
|
|
|
size_t body_off;
|
2007-11-10 19:18:26 +08:00
|
|
|
|
|
|
|
/* 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;
|
2009-10-18 05:04:19 +08:00
|
|
|
size_t wrap_start;
|
2007-11-10 19:14:20 +08:00
|
|
|
};
|
|
|
|
|
2007-11-10 19:18:26 +08:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2007-11-10 19:14:20 +08:00
|
|
|
static void parse_commit_header(struct format_commit_context *context)
|
2007-11-05 03:15:06 +08:00
|
|
|
{
|
2010-11-03 03:59:08 +08:00
|
|
|
const char *msg = context->message;
|
2007-11-05 03:15:06 +08:00
|
|
|
int i;
|
2007-11-10 19:14:20 +08:00
|
|
|
|
2008-12-27 08:49:21 +08:00
|
|
|
for (i = 0; msg[i]; i++) {
|
2007-11-10 19:14:20 +08:00
|
|
|
int eol;
|
|
|
|
for (eol = i; msg[eol] && msg[eol] != '\n'; eol++)
|
|
|
|
; /* do nothing */
|
|
|
|
|
|
|
|
if (i == eol) {
|
2008-12-27 08:49:21 +08:00
|
|
|
break;
|
2013-12-01 04:55:40 +08:00
|
|
|
} else if (starts_with(msg + i, "author ")) {
|
2007-11-10 19:14:20 +08:00
|
|
|
context->author.off = i + 7;
|
|
|
|
context->author.len = eol - i - 7;
|
2013-12-01 04:55:40 +08:00
|
|
|
} else if (starts_with(msg + i, "committer ")) {
|
2007-11-10 19:14:20 +08:00
|
|
|
context->committer.off = i + 10;
|
|
|
|
context->committer.len = eol - i - 10;
|
|
|
|
}
|
|
|
|
i = eol;
|
|
|
|
}
|
2008-12-27 08:49:21 +08:00
|
|
|
context->message_off = i;
|
2007-11-10 19:14:20 +08:00
|
|
|
context->commit_header_parsed = 1;
|
|
|
|
}
|
|
|
|
|
2009-03-23 10:14:01 +08:00
|
|
|
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;
|
2009-04-01 07:24:38 +08:00
|
|
|
size_t start_len = sb->len;
|
2009-03-23 10:14:01 +08:00
|
|
|
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;
|
2009-04-01 07:24:38 +08:00
|
|
|
while (sb->len - trimlen > start_len &&
|
|
|
|
(sb->buf[sb->len - 1 - trimlen] == '.'
|
|
|
|
|| sb->buf[sb->len - 1 - trimlen] == '-'))
|
2009-03-23 10:14:01 +08:00
|
|
|
trimlen++;
|
|
|
|
strbuf_remove(sb, sb->len - trimlen, trimlen);
|
|
|
|
}
|
|
|
|
|
2009-01-07 04:41:06 +08:00
|
|
|
const char *format_subject(struct strbuf *sb, const char *msg,
|
|
|
|
const char *line_separator)
|
2008-12-27 08:39:35 +08:00
|
|
|
{
|
|
|
|
int first = 1;
|
|
|
|
|
|
|
|
for (;;) {
|
|
|
|
const char *line = msg;
|
|
|
|
int linelen = get_one_line(line);
|
|
|
|
|
|
|
|
msg += linelen;
|
|
|
|
if (!linelen || is_empty_line(line, &linelen))
|
|
|
|
break;
|
|
|
|
|
2008-12-27 08:49:21 +08:00
|
|
|
if (!sb)
|
|
|
|
continue;
|
2008-12-27 08:39:35 +08:00
|
|
|
strbuf_grow(sb, linelen + 2);
|
|
|
|
if (!first)
|
|
|
|
strbuf_addstr(sb, line_separator);
|
|
|
|
strbuf_add(sb, line, linelen);
|
|
|
|
first = 0;
|
|
|
|
}
|
|
|
|
return msg;
|
|
|
|
}
|
|
|
|
|
2008-12-27 08:49:21 +08:00
|
|
|
static void parse_commit_message(struct format_commit_context *c)
|
|
|
|
{
|
2010-11-03 03:59:08 +08:00
|
|
|
const char *msg = c->message + c->message_off;
|
|
|
|
const char *start = c->message;
|
2008-12-27 08:49:21 +08:00
|
|
|
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2009-10-18 05:04:19 +08:00
|
|
|
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;
|
2009-11-08 09:04:21 +08:00
|
|
|
if (c->wrap_start < sb->len)
|
2009-10-18 05:04:19 +08:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2011-12-16 19:40:24 +08:00
|
|
|
static int format_reflog_person(struct strbuf *sb,
|
|
|
|
char part,
|
|
|
|
struct reflog_walk_info *log,
|
|
|
|
enum date_mode dmode)
|
|
|
|
{
|
|
|
|
const char *ident;
|
|
|
|
|
|
|
|
if (!log)
|
|
|
|
return 2;
|
|
|
|
|
|
|
|
ident = get_reflog_ident(log);
|
|
|
|
if (!ident)
|
|
|
|
return 2;
|
|
|
|
|
|
|
|
return format_person_part(sb, part, ident, strlen(ident), dmode);
|
|
|
|
}
|
|
|
|
|
2013-04-19 07:08:48 +08:00
|
|
|
static size_t parse_color(struct strbuf *sb, /* in UTF-8 */
|
|
|
|
const char *placeholder,
|
|
|
|
struct format_commit_context *c)
|
|
|
|
{
|
|
|
|
if (placeholder[1] == '(') {
|
|
|
|
const char *begin = placeholder + 2;
|
|
|
|
const char *end = strchr(begin, ')');
|
|
|
|
char color[COLOR_MAXLEN];
|
|
|
|
|
|
|
|
if (!end)
|
|
|
|
return 0;
|
2013-12-01 04:55:40 +08:00
|
|
|
if (starts_with(begin, "auto,")) {
|
2013-04-19 07:08:48 +08:00
|
|
|
if (!want_color(c->pretty_ctx->color))
|
|
|
|
return end - placeholder + 1;
|
|
|
|
begin += 5;
|
|
|
|
}
|
|
|
|
color_parse_mem(begin,
|
|
|
|
end - begin,
|
|
|
|
"--pretty format", color);
|
|
|
|
strbuf_addstr(sb, color);
|
|
|
|
return end - placeholder + 1;
|
|
|
|
}
|
2013-12-01 04:55:40 +08:00
|
|
|
if (starts_with(placeholder + 1, "red")) {
|
2013-04-19 07:08:48 +08:00
|
|
|
strbuf_addstr(sb, GIT_COLOR_RED);
|
|
|
|
return 4;
|
2013-12-01 04:55:40 +08:00
|
|
|
} else if (starts_with(placeholder + 1, "green")) {
|
2013-04-19 07:08:48 +08:00
|
|
|
strbuf_addstr(sb, GIT_COLOR_GREEN);
|
|
|
|
return 6;
|
2013-12-01 04:55:40 +08:00
|
|
|
} else if (starts_with(placeholder + 1, "blue")) {
|
2013-04-19 07:08:48 +08:00
|
|
|
strbuf_addstr(sb, GIT_COLOR_BLUE);
|
|
|
|
return 5;
|
2013-12-01 04:55:40 +08:00
|
|
|
} else if (starts_with(placeholder + 1, "reset")) {
|
2013-04-19 07:08:48 +08:00
|
|
|
strbuf_addstr(sb, GIT_COLOR_RESET);
|
|
|
|
return 6;
|
|
|
|
} else
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2013-04-19 07:08:50 +08:00
|
|
|
static size_t parse_padding_placeholder(struct strbuf *sb,
|
|
|
|
const char *placeholder,
|
|
|
|
struct format_commit_context *c)
|
|
|
|
{
|
|
|
|
const char *ch = placeholder;
|
|
|
|
enum flush_type flush_type;
|
|
|
|
int to_column = 0;
|
|
|
|
|
|
|
|
switch (*ch++) {
|
|
|
|
case '<':
|
|
|
|
flush_type = flush_right;
|
|
|
|
break;
|
|
|
|
case '>':
|
|
|
|
if (*ch == '<') {
|
|
|
|
flush_type = flush_both;
|
|
|
|
ch++;
|
2013-04-19 07:08:52 +08:00
|
|
|
} else if (*ch == '>') {
|
|
|
|
flush_type = flush_left_and_steal;
|
|
|
|
ch++;
|
2013-04-19 07:08:50 +08:00
|
|
|
} else
|
|
|
|
flush_type = flush_left;
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* the next value means "wide enough to that column" */
|
|
|
|
if (*ch == '|') {
|
|
|
|
to_column = 1;
|
|
|
|
ch++;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (*ch == '(') {
|
|
|
|
const char *start = ch + 1;
|
2013-04-19 07:08:51 +08:00
|
|
|
const char *end = start + strcspn(start, ",)");
|
2013-04-19 07:08:50 +08:00
|
|
|
char *next;
|
|
|
|
int width;
|
|
|
|
if (!end || end == start)
|
|
|
|
return 0;
|
|
|
|
width = strtoul(start, &next, 10);
|
|
|
|
if (next == start || width == 0)
|
|
|
|
return 0;
|
|
|
|
c->padding = to_column ? -width : width;
|
|
|
|
c->flush_type = flush_type;
|
2013-04-19 07:08:51 +08:00
|
|
|
|
|
|
|
if (*end == ',') {
|
|
|
|
start = end + 1;
|
|
|
|
end = strchr(start, ')');
|
|
|
|
if (!end || end == start)
|
|
|
|
return 0;
|
2013-12-01 04:55:40 +08:00
|
|
|
if (starts_with(start, "trunc)"))
|
2013-04-19 07:08:51 +08:00
|
|
|
c->truncate = trunc_right;
|
2013-12-01 04:55:40 +08:00
|
|
|
else if (starts_with(start, "ltrunc)"))
|
2013-04-19 07:08:51 +08:00
|
|
|
c->truncate = trunc_left;
|
2013-12-01 04:55:40 +08:00
|
|
|
else if (starts_with(start, "mtrunc)"))
|
2013-04-19 07:08:51 +08:00
|
|
|
c->truncate = trunc_middle;
|
|
|
|
else
|
|
|
|
return 0;
|
|
|
|
} else
|
|
|
|
c->truncate = trunc_none;
|
|
|
|
|
2013-04-19 07:08:50 +08:00
|
|
|
return end - placeholder + 1;
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2013-04-19 07:08:47 +08:00
|
|
|
static size_t format_commit_one(struct strbuf *sb, /* in UTF-8 */
|
|
|
|
const char *placeholder,
|
2009-10-05 14:43:32 +08:00
|
|
|
void *context)
|
2007-11-10 19:14:20 +08:00
|
|
|
{
|
|
|
|
struct format_commit_context *c = context;
|
|
|
|
const struct commit *commit = c->commit;
|
2010-11-03 03:59:08 +08:00
|
|
|
const char *msg = c->message;
|
2007-11-10 19:14:20 +08:00
|
|
|
struct commit_list *p;
|
2008-03-21 23:05:06 +08:00
|
|
|
int h1, h2;
|
2007-11-05 03:15:06 +08:00
|
|
|
|
|
|
|
/* these are independent of the commit */
|
2007-11-09 08:49:42 +08:00
|
|
|
switch (placeholder[0]) {
|
|
|
|
case 'C':
|
2013-12-01 04:55:40 +08:00
|
|
|
if (starts_with(placeholder + 1, "(auto)")) {
|
2013-04-19 07:08:49 +08:00
|
|
|
c->auto_color = 1;
|
|
|
|
return 7; /* consumed 7 bytes, "C(auto)" */
|
|
|
|
} else {
|
|
|
|
int ret = parse_color(sb, placeholder, c);
|
|
|
|
if (ret)
|
|
|
|
c->auto_color = 0;
|
|
|
|
/*
|
|
|
|
* Otherwise, we decided to treat %C<unknown>
|
|
|
|
* as a literal string, and the previous
|
|
|
|
* %C(auto) is still valid.
|
|
|
|
*/
|
|
|
|
return ret;
|
expand --pretty=format color options
Currently, the only colors available to --pretty=format
users are red, green, and blue. Rather than expand it with a
few new colors, this patch makes the usual config color
syntax available, including more colors, backgrounds, and
attributes.
Because colors are no longer bounded to a single word (e.g.,
%Cred), this uses a more advanced syntax that features a
beginning and end delimiter (but the old syntax still
works). So you can now do:
git log --pretty=tformat:'%C(yellow)%h%C(reset) %s'
to emulate --pretty=oneline, or even
git log --pretty=tformat:'%C(cyan magenta bold)%s%C(reset)'
if you want to relive the awesomeness of 4-color CGA.
Signed-off-by: Jeff King <peff@peff.net>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2009-01-17 23:38:46 +08:00
|
|
|
}
|
2007-11-09 08:49:42 +08:00
|
|
|
case 'n': /* newline */
|
|
|
|
strbuf_addch(sb, '\n');
|
2008-02-09 22:40:19 +08:00
|
|
|
return 1;
|
2008-03-21 23:05:06 +08:00
|
|
|
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
|
|
|
|
return 0;
|
2009-10-18 05:04:19 +08:00
|
|
|
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;
|
2013-04-19 07:08:50 +08:00
|
|
|
|
|
|
|
case '<':
|
|
|
|
case '>':
|
|
|
|
return parse_padding_placeholder(sb, placeholder, c);
|
2007-11-09 08:49:42 +08:00
|
|
|
}
|
2007-11-05 03:15:06 +08:00
|
|
|
|
|
|
|
/* these depend on the commit */
|
|
|
|
if (!commit->object.parsed)
|
|
|
|
parse_object(commit->object.sha1);
|
|
|
|
|
2007-11-09 08:49:42 +08:00
|
|
|
switch (placeholder[0]) {
|
|
|
|
case 'H': /* commit hash */
|
2013-04-19 07:08:49 +08:00
|
|
|
strbuf_addstr(sb, diff_get_color(c->auto_color, DIFF_COMMIT));
|
2007-11-09 08:49:42 +08:00
|
|
|
strbuf_addstr(sb, sha1_to_hex(commit->object.sha1));
|
2013-04-19 07:08:49 +08:00
|
|
|
strbuf_addstr(sb, diff_get_color(c->auto_color, DIFF_RESET));
|
2008-02-09 22:40:19 +08:00
|
|
|
return 1;
|
2007-11-09 08:49:42 +08:00
|
|
|
case 'h': /* abbreviated commit hash */
|
2013-04-19 07:08:49 +08:00
|
|
|
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));
|
2008-02-09 22:40:19 +08:00
|
|
|
return 1;
|
2013-04-19 07:08:49 +08:00
|
|
|
}
|
2007-11-09 08:49:42 +08:00
|
|
|
strbuf_addstr(sb, find_unique_abbrev(commit->object.sha1,
|
2010-05-04 11:18:57 +08:00
|
|
|
c->pretty_ctx->abbrev));
|
2013-04-19 07:08:49 +08:00
|
|
|
strbuf_addstr(sb, diff_get_color(c->auto_color, DIFF_RESET));
|
2007-11-10 19:18:26 +08:00
|
|
|
c->abbrev_commit_hash.len = sb->len - c->abbrev_commit_hash.off;
|
2008-02-09 22:40:19 +08:00
|
|
|
return 1;
|
2007-11-09 08:49:42 +08:00
|
|
|
case 'T': /* tree hash */
|
|
|
|
strbuf_addstr(sb, sha1_to_hex(commit->tree->object.sha1));
|
2008-02-09 22:40:19 +08:00
|
|
|
return 1;
|
2007-11-09 08:49:42 +08:00
|
|
|
case 't': /* abbreviated tree hash */
|
2007-11-10 19:18:26 +08:00
|
|
|
if (add_again(sb, &c->abbrev_tree_hash))
|
2008-02-09 22:40:19 +08:00
|
|
|
return 1;
|
2007-11-09 08:49:42 +08:00
|
|
|
strbuf_addstr(sb, find_unique_abbrev(commit->tree->object.sha1,
|
2010-05-04 11:18:57 +08:00
|
|
|
c->pretty_ctx->abbrev));
|
2007-11-10 19:18:26 +08:00
|
|
|
c->abbrev_tree_hash.len = sb->len - c->abbrev_tree_hash.off;
|
2008-02-09 22:40:19 +08:00
|
|
|
return 1;
|
2007-11-09 08:49:42 +08:00
|
|
|
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));
|
|
|
|
}
|
2008-02-09 22:40:19 +08:00
|
|
|
return 1;
|
2007-11-09 08:49:42 +08:00
|
|
|
case 'p': /* abbreviated parent hashes */
|
2007-11-10 19:18:26 +08:00
|
|
|
if (add_again(sb, &c->abbrev_parent_hashes))
|
2008-02-09 22:40:19 +08:00
|
|
|
return 1;
|
2007-11-09 08:49:42 +08:00
|
|
|
for (p = commit->parents; p; p = p->next) {
|
|
|
|
if (p != commit->parents)
|
|
|
|
strbuf_addch(sb, ' ');
|
|
|
|
strbuf_addstr(sb, find_unique_abbrev(
|
2010-05-04 11:18:57 +08:00
|
|
|
p->item->object.sha1,
|
|
|
|
c->pretty_ctx->abbrev));
|
2007-11-09 08:49:42 +08:00
|
|
|
}
|
2007-11-10 19:18:26 +08:00
|
|
|
c->abbrev_parent_hashes.len = sb->len -
|
|
|
|
c->abbrev_parent_hashes.off;
|
2008-02-09 22:40:19 +08:00
|
|
|
return 1;
|
2007-11-09 08:49:42 +08:00
|
|
|
case 'm': /* left/right/bottom */
|
2011-03-07 20:31:39 +08:00
|
|
|
strbuf_addstr(sb, get_revision_mark(NULL, commit));
|
2008-02-09 22:40:19 +08:00
|
|
|
return 1;
|
2008-09-05 05:40:03 +08:00
|
|
|
case 'd':
|
2013-04-19 07:08:43 +08:00
|
|
|
load_ref_decorations(DECORATE_SHORT_REFS);
|
2013-04-19 07:08:49 +08:00
|
|
|
format_decorations(sb, commit, c->auto_color);
|
2008-09-05 05:40:03 +08:00
|
|
|
return 1;
|
2009-10-19 23:48:10 +08:00
|
|
|
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,
|
2012-05-08 05:11:32 +08:00
|
|
|
c->pretty_ctx->date_mode_explicit,
|
2009-10-19 23:48:10 +08:00
|
|
|
(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;
|
2011-12-16 19:40:24 +08:00
|
|
|
case 'n':
|
|
|
|
case 'N':
|
|
|
|
case 'e':
|
|
|
|
case 'E':
|
|
|
|
return format_reflog_person(sb,
|
|
|
|
placeholder[1],
|
|
|
|
c->pretty_ctx->reflog_info,
|
|
|
|
c->pretty_ctx->date_mode);
|
2009-10-19 23:48:10 +08:00
|
|
|
}
|
|
|
|
return 0; /* unknown %g placeholder */
|
2009-10-09 18:22:05 +08:00
|
|
|
case 'N':
|
2012-10-18 09:51:47 +08:00
|
|
|
if (c->pretty_ctx->notes_message) {
|
|
|
|
strbuf_addstr(sb, c->pretty_ctx->notes_message);
|
2010-04-14 04:31:12 +08:00
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
return 0;
|
2007-11-09 08:49:42 +08:00
|
|
|
}
|
|
|
|
|
2011-10-22 12:06:02 +08:00
|
|
|
if (placeholder[0] == 'G') {
|
2013-04-01 00:00:14 +08:00
|
|
|
if (!c->signature_check.result)
|
|
|
|
check_commit_signature(c->commit, &(c->signature_check));
|
2011-10-22 12:06:02 +08:00
|
|
|
switch (placeholder[1]) {
|
|
|
|
case 'G':
|
2013-04-01 00:00:14 +08:00
|
|
|
if (c->signature_check.gpg_output)
|
|
|
|
strbuf_addstr(sb, c->signature_check.gpg_output);
|
2011-10-22 12:06:02 +08:00
|
|
|
break;
|
|
|
|
case '?':
|
2013-04-01 00:00:14 +08:00
|
|
|
switch (c->signature_check.result) {
|
2011-10-22 12:06:02 +08:00
|
|
|
case 'G':
|
|
|
|
case 'B':
|
2013-04-01 00:03:22 +08:00
|
|
|
case 'U':
|
|
|
|
case 'N':
|
2013-04-01 00:00:14 +08:00
|
|
|
strbuf_addch(sb, c->signature_check.result);
|
2011-10-22 12:06:02 +08:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
case 'S':
|
2013-04-01 00:00:14 +08:00
|
|
|
if (c->signature_check.signer)
|
|
|
|
strbuf_addstr(sb, c->signature_check.signer);
|
2011-10-22 12:06:02 +08:00
|
|
|
break;
|
2013-02-15 00:04:46 +08:00
|
|
|
case 'K':
|
2013-04-01 00:00:14 +08:00
|
|
|
if (c->signature_check.key)
|
|
|
|
strbuf_addstr(sb, c->signature_check.key);
|
2013-02-15 00:04:46 +08:00
|
|
|
break;
|
2014-06-17 08:07:07 +08:00
|
|
|
default:
|
|
|
|
return 0;
|
2011-10-22 12:06:02 +08:00
|
|
|
}
|
|
|
|
return 2;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2007-11-09 08:49:42 +08:00
|
|
|
/* For the rest we have to parse the commit header. */
|
2007-11-10 19:14:20 +08:00
|
|
|
if (!c->commit_header_parsed)
|
|
|
|
parse_commit_header(c);
|
2007-11-05 03:15:06 +08:00
|
|
|
|
2007-11-10 19:14:20 +08:00
|
|
|
switch (placeholder[0]) {
|
2008-02-09 22:40:19 +08:00
|
|
|
case 'a': /* author ... */
|
|
|
|
return format_person_part(sb, placeholder[1],
|
2008-08-29 08:54:59 +08:00
|
|
|
msg + c->author.off, c->author.len,
|
2009-10-19 23:48:08 +08:00
|
|
|
c->pretty_ctx->date_mode);
|
2008-02-09 22:40:19 +08:00
|
|
|
case 'c': /* committer ... */
|
|
|
|
return format_person_part(sb, placeholder[1],
|
2008-08-29 08:54:59 +08:00
|
|
|
msg + c->committer.off, c->committer.len,
|
2009-10-19 23:48:08 +08:00
|
|
|
c->pretty_ctx->date_mode);
|
2008-02-09 22:40:19 +08:00
|
|
|
case 'e': /* encoding */
|
2013-04-19 07:08:41 +08:00
|
|
|
if (c->commit_encoding)
|
|
|
|
strbuf_addstr(sb, c->commit_encoding);
|
2008-02-09 22:40:19 +08:00
|
|
|
return 1;
|
2010-03-25 10:51:52 +08:00
|
|
|
case 'B': /* raw body */
|
|
|
|
/* message_off is always left at the initial newline */
|
|
|
|
strbuf_addstr(sb, msg + c->message_off + 1);
|
|
|
|
return 1;
|
2008-12-27 08:49:21 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
/* 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;
|
2009-03-23 10:14:01 +08:00
|
|
|
case 'f': /* sanitized subject */
|
|
|
|
format_sanitized_subject(sb, msg + c->subject_off);
|
|
|
|
return 1;
|
2008-02-09 22:40:19 +08:00
|
|
|
case 'b': /* body */
|
2007-11-10 19:14:20 +08:00
|
|
|
strbuf_addstr(sb, msg + c->body_off);
|
2008-02-09 22:40:19 +08:00
|
|
|
return 1;
|
2007-11-05 03:15:06 +08:00
|
|
|
}
|
2008-02-09 22:40:19 +08:00
|
|
|
return 0; /* unknown placeholder */
|
2007-11-09 08:49:42 +08:00
|
|
|
}
|
|
|
|
|
2013-04-19 07:08:50 +08:00
|
|
|
static size_t format_and_pad_commit(struct strbuf *sb, /* in UTF-8 */
|
|
|
|
const char *placeholder,
|
|
|
|
struct format_commit_context *c)
|
|
|
|
{
|
|
|
|
struct strbuf local_sb = STRBUF_INIT;
|
|
|
|
int total_consumed = 0, len, padding = c->padding;
|
|
|
|
if (padding < 0) {
|
|
|
|
const char *start = strrchr(sb->buf, '\n');
|
|
|
|
int occupied;
|
|
|
|
if (!start)
|
|
|
|
start = sb->buf;
|
|
|
|
occupied = utf8_strnwidth(start, -1, 1);
|
|
|
|
padding = (-padding) - occupied;
|
|
|
|
}
|
|
|
|
while (1) {
|
|
|
|
int modifier = *placeholder == 'C';
|
|
|
|
int consumed = format_commit_one(&local_sb, placeholder, c);
|
|
|
|
total_consumed += consumed;
|
|
|
|
|
|
|
|
if (!modifier)
|
|
|
|
break;
|
|
|
|
|
|
|
|
placeholder += consumed;
|
|
|
|
if (*placeholder != '%')
|
|
|
|
break;
|
|
|
|
placeholder++;
|
|
|
|
total_consumed++;
|
|
|
|
}
|
|
|
|
len = utf8_strnwidth(local_sb.buf, -1, 1);
|
2013-04-19 07:08:52 +08:00
|
|
|
|
|
|
|
if (c->flush_type == flush_left_and_steal) {
|
|
|
|
const char *ch = sb->buf + sb->len - 1;
|
|
|
|
while (len > padding && ch > sb->buf) {
|
|
|
|
const char *p;
|
|
|
|
if (*ch == ' ') {
|
|
|
|
ch--;
|
|
|
|
padding++;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
/* check for trailing ansi sequences */
|
|
|
|
if (*ch != 'm')
|
|
|
|
break;
|
|
|
|
p = ch - 1;
|
|
|
|
while (ch - p < 10 && *p != '\033')
|
|
|
|
p--;
|
|
|
|
if (*p != '\033' ||
|
|
|
|
ch + 1 - p != display_mode_esc_sequence_len(p))
|
|
|
|
break;
|
|
|
|
/*
|
|
|
|
* got a good ansi sequence, put it back to
|
|
|
|
* local_sb as we're cutting sb
|
|
|
|
*/
|
|
|
|
strbuf_insert(&local_sb, 0, p, ch + 1 - p);
|
|
|
|
ch = p - 1;
|
|
|
|
}
|
|
|
|
strbuf_setlen(sb, ch + 1 - sb->buf);
|
|
|
|
c->flush_type = flush_left;
|
|
|
|
}
|
|
|
|
|
2013-04-19 07:08:51 +08:00
|
|
|
if (len > padding) {
|
|
|
|
switch (c->truncate) {
|
|
|
|
case trunc_left:
|
|
|
|
strbuf_utf8_replace(&local_sb,
|
|
|
|
0, len - (padding - 2),
|
|
|
|
"..");
|
|
|
|
break;
|
|
|
|
case trunc_middle:
|
|
|
|
strbuf_utf8_replace(&local_sb,
|
|
|
|
padding / 2 - 1,
|
|
|
|
len - (padding - 2),
|
|
|
|
"..");
|
|
|
|
break;
|
|
|
|
case trunc_right:
|
|
|
|
strbuf_utf8_replace(&local_sb,
|
|
|
|
padding - 2, len - (padding - 2),
|
|
|
|
"..");
|
|
|
|
break;
|
|
|
|
case trunc_none:
|
|
|
|
break;
|
|
|
|
}
|
2013-04-19 07:08:50 +08:00
|
|
|
strbuf_addstr(sb, local_sb.buf);
|
2013-04-19 07:08:51 +08:00
|
|
|
} else {
|
2013-04-19 07:08:50 +08:00
|
|
|
int sb_len = sb->len, offset = 0;
|
|
|
|
if (c->flush_type == flush_left)
|
|
|
|
offset = padding - len;
|
|
|
|
else if (c->flush_type == flush_both)
|
|
|
|
offset = (padding - len) / 2;
|
|
|
|
/*
|
|
|
|
* we calculate padding in columns, now
|
|
|
|
* convert it back to chars
|
|
|
|
*/
|
|
|
|
padding = padding - len + local_sb.len;
|
|
|
|
strbuf_grow(sb, padding);
|
|
|
|
strbuf_setlen(sb, sb_len + padding);
|
|
|
|
memset(sb->buf + sb_len, ' ', sb->len - sb_len);
|
|
|
|
memcpy(sb->buf + sb_len + offset, local_sb.buf,
|
|
|
|
local_sb.len);
|
|
|
|
}
|
|
|
|
strbuf_release(&local_sb);
|
|
|
|
c->flush_type = no_flush;
|
|
|
|
return total_consumed;
|
|
|
|
}
|
|
|
|
|
2013-04-19 07:08:47 +08:00
|
|
|
static size_t format_commit_item(struct strbuf *sb, /* in UTF-8 */
|
|
|
|
const char *placeholder,
|
2009-10-05 14:43:32 +08:00
|
|
|
void *context)
|
|
|
|
{
|
|
|
|
int consumed;
|
|
|
|
size_t orig_len;
|
|
|
|
enum {
|
|
|
|
NO_MAGIC,
|
|
|
|
ADD_LF_BEFORE_NON_EMPTY,
|
|
|
|
DEL_LF_BEFORE_EMPTY,
|
2010-06-23 00:45:22 +08:00
|
|
|
ADD_SP_BEFORE_NON_EMPTY
|
2009-10-05 14:43:32 +08:00
|
|
|
} magic = NO_MAGIC;
|
|
|
|
|
|
|
|
switch (placeholder[0]) {
|
|
|
|
case '-':
|
|
|
|
magic = DEL_LF_BEFORE_EMPTY;
|
|
|
|
break;
|
|
|
|
case '+':
|
|
|
|
magic = ADD_LF_BEFORE_NON_EMPTY;
|
|
|
|
break;
|
2010-06-15 00:12:29 +08:00
|
|
|
case ' ':
|
|
|
|
magic = ADD_SP_BEFORE_NON_EMPTY;
|
|
|
|
break;
|
2009-10-05 14:43:32 +08:00
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
if (magic != NO_MAGIC)
|
|
|
|
placeholder++;
|
|
|
|
|
|
|
|
orig_len = sb->len;
|
2013-04-19 07:08:50 +08:00
|
|
|
if (((struct format_commit_context *)context)->flush_type != no_flush)
|
|
|
|
consumed = format_and_pad_commit(sb, placeholder, context);
|
|
|
|
else
|
|
|
|
consumed = format_commit_one(sb, placeholder, context);
|
2009-10-05 14:43:32 +08:00
|
|
|
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);
|
2010-06-15 00:12:29 +08:00
|
|
|
} else if (orig_len != sb->len) {
|
|
|
|
if (magic == ADD_LF_BEFORE_NON_EMPTY)
|
|
|
|
strbuf_insert(sb, orig_len, "\n", 1);
|
|
|
|
else if (magic == ADD_SP_BEFORE_NON_EMPTY)
|
|
|
|
strbuf_insert(sb, orig_len, " ", 1);
|
2009-10-05 14:43:32 +08:00
|
|
|
}
|
|
|
|
return consumed + 1;
|
|
|
|
}
|
|
|
|
|
2010-04-14 04:31:12 +08:00
|
|
|
static size_t userformat_want_item(struct strbuf *sb, const char *placeholder,
|
|
|
|
void *context)
|
|
|
|
{
|
|
|
|
struct userformat_want *w = context;
|
|
|
|
|
2010-06-15 00:12:29 +08:00
|
|
|
if (*placeholder == '+' || *placeholder == '-' || *placeholder == ' ')
|
2010-04-14 04:31:12 +08:00
|
|
|
placeholder++;
|
|
|
|
|
|
|
|
switch (*placeholder) {
|
|
|
|
case 'N':
|
|
|
|
w->notes = 1;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
void userformat_find_requirements(const char *fmt, struct userformat_want *w)
|
|
|
|
{
|
|
|
|
struct strbuf dummy = STRBUF_INIT;
|
|
|
|
|
|
|
|
if (!fmt) {
|
|
|
|
if (!user_format)
|
|
|
|
return;
|
|
|
|
fmt = user_format;
|
|
|
|
}
|
2011-05-26 03:23:44 +08:00
|
|
|
strbuf_expand(&dummy, fmt, userformat_want_item, w);
|
2010-04-14 04:31:12 +08:00
|
|
|
strbuf_release(&dummy);
|
|
|
|
}
|
|
|
|
|
2007-11-09 08:49:42 +08:00
|
|
|
void format_commit_message(const struct commit *commit,
|
2009-10-16 13:59:41 +08:00
|
|
|
const char *format, struct strbuf *sb,
|
2009-10-19 23:48:08 +08:00
|
|
|
const struct pretty_print_context *pretty_ctx)
|
2007-11-09 08:49:42 +08:00
|
|
|
{
|
2007-11-10 19:14:20 +08:00
|
|
|
struct format_commit_context context;
|
2010-11-03 03:59:08 +08:00
|
|
|
const char *output_enc = pretty_ctx->output_encoding;
|
2013-04-19 07:08:47 +08:00
|
|
|
const char *utf8 = "UTF-8";
|
2007-11-10 19:14:20 +08:00
|
|
|
|
|
|
|
memset(&context, 0, sizeof(context));
|
|
|
|
context.commit = commit;
|
2009-10-19 23:48:08 +08:00
|
|
|
context.pretty_ctx = pretty_ctx;
|
2009-10-18 05:04:19 +08:00
|
|
|
context.wrap_start = sb->len;
|
2014-05-21 21:20:07 +08:00
|
|
|
/*
|
|
|
|
* convert a commit message to UTF-8 first
|
|
|
|
* as far as 'format_commit_item' assumes it in UTF-8
|
|
|
|
*/
|
2013-04-19 07:08:41 +08:00
|
|
|
context.message = logmsg_reencode(commit,
|
|
|
|
&context.commit_encoding,
|
2014-05-21 21:20:07 +08:00
|
|
|
utf8);
|
2010-11-03 03:59:08 +08:00
|
|
|
|
2008-02-09 22:40:19 +08:00
|
|
|
strbuf_expand(sb, format, format_commit_item, &context);
|
2009-10-18 05:04:19 +08:00
|
|
|
rewrap_message_tail(sb, &context, 0, 0, 0);
|
2010-11-03 03:59:08 +08:00
|
|
|
|
2014-05-21 21:20:07 +08:00
|
|
|
/* then convert a commit message to an actual output encoding */
|
2013-04-19 07:08:47 +08:00
|
|
|
if (output_enc) {
|
|
|
|
if (same_encoding(utf8, output_enc))
|
|
|
|
output_enc = NULL;
|
|
|
|
} else {
|
|
|
|
if (context.commit_encoding &&
|
|
|
|
!same_encoding(context.commit_encoding, utf8))
|
|
|
|
output_enc = context.commit_encoding;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (output_enc) {
|
|
|
|
int outsz;
|
|
|
|
char *out = reencode_string_len(sb->buf, sb->len,
|
|
|
|
output_enc, utf8, &outsz);
|
|
|
|
if (out)
|
|
|
|
strbuf_attach(sb, out, outsz, outsz + 1);
|
|
|
|
}
|
|
|
|
|
2013-04-19 07:08:41 +08:00
|
|
|
free(context.commit_encoding);
|
2014-06-11 05:41:39 +08:00
|
|
|
unuse_commit_buffer(commit, context.message);
|
2013-04-01 00:00:14 +08:00
|
|
|
free(context.signature_check.gpg_output);
|
|
|
|
free(context.signature_check.signer);
|
2007-11-05 03:15:06 +08:00
|
|
|
}
|
|
|
|
|
2013-07-03 15:07:48 +08:00
|
|
|
static void pp_header(struct pretty_print_context *pp,
|
2007-11-05 03:15:06 +08:00
|
|
|
const char *encoding,
|
|
|
|
const struct commit *commit,
|
|
|
|
const char **msg_p,
|
|
|
|
struct strbuf *sb)
|
|
|
|
{
|
|
|
|
int parents_shown = 0;
|
|
|
|
|
|
|
|
for (;;) {
|
|
|
|
const char *line = *msg_p;
|
|
|
|
int linelen = get_one_line(*msg_p);
|
|
|
|
|
|
|
|
if (!linelen)
|
|
|
|
return;
|
|
|
|
*msg_p += linelen;
|
|
|
|
|
|
|
|
if (linelen == 1)
|
|
|
|
/* End of header */
|
|
|
|
return;
|
|
|
|
|
2011-05-27 06:27:49 +08:00
|
|
|
if (pp->fmt == CMIT_FMT_RAW) {
|
2007-11-05 03:15:06 +08:00
|
|
|
strbuf_add(sb, line, linelen);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2013-12-01 04:55:40 +08:00
|
|
|
if (starts_with(line, "parent ")) {
|
2007-11-05 03:15:06 +08:00
|
|
|
if (linelen != 48)
|
|
|
|
die("bad parent line in commit");
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!parents_shown) {
|
|
|
|
struct commit_list *parent;
|
|
|
|
int num;
|
|
|
|
for (parent = commit->parents, num = 0;
|
|
|
|
parent;
|
|
|
|
parent = parent->next, num++)
|
|
|
|
;
|
|
|
|
/* with enough slop */
|
|
|
|
strbuf_grow(sb, num * 50 + 20);
|
2011-05-27 06:27:49 +08:00
|
|
|
add_merge_info(pp, sb, commit);
|
2007-11-05 03:15:06 +08:00
|
|
|
parents_shown = 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* MEDIUM == DEFAULT shows only author with dates.
|
|
|
|
* FULL shows both authors but not dates.
|
|
|
|
* FULLER shows both authors and dates.
|
|
|
|
*/
|
2013-12-01 04:55:40 +08:00
|
|
|
if (starts_with(line, "author ")) {
|
2007-11-05 03:15:06 +08:00
|
|
|
strbuf_grow(sb, linelen + 80);
|
2011-05-27 06:27:49 +08:00
|
|
|
pp_user_info(pp, "Author", sb, line + 7, encoding);
|
2007-11-05 03:15:06 +08:00
|
|
|
}
|
2013-12-01 04:55:40 +08:00
|
|
|
if (starts_with(line, "committer ") &&
|
2011-05-27 06:27:49 +08:00
|
|
|
(pp->fmt == CMIT_FMT_FULL || pp->fmt == CMIT_FMT_FULLER)) {
|
2007-11-05 03:15:06 +08:00
|
|
|
strbuf_grow(sb, linelen + 80);
|
2011-05-27 06:27:49 +08:00
|
|
|
pp_user_info(pp, "Commit", sb, line + 10, encoding);
|
2007-11-05 03:15:06 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-07-03 15:07:48 +08:00
|
|
|
void pp_title_line(struct pretty_print_context *pp,
|
2008-02-19 11:56:08 +08:00
|
|
|
const char **msg_p,
|
|
|
|
struct strbuf *sb,
|
|
|
|
const char *encoding,
|
2008-03-15 15:09:20 +08:00
|
|
|
int need_8bit_cte)
|
2007-11-05 03:15:06 +08:00
|
|
|
{
|
2012-10-18 22:43:33 +08:00
|
|
|
static const int max_length = 78; /* per rfc2047 */
|
2007-11-05 03:15:06 +08:00
|
|
|
struct strbuf title;
|
|
|
|
|
|
|
|
strbuf_init(&title, 80);
|
2011-05-27 06:28:17 +08:00
|
|
|
*msg_p = format_subject(&title, *msg_p,
|
|
|
|
pp->preserve_subject ? "\n" : " ");
|
2007-11-05 03:15:06 +08:00
|
|
|
|
|
|
|
strbuf_grow(sb, title.len + 1024);
|
2011-05-27 06:27:49 +08:00
|
|
|
if (pp->subject) {
|
|
|
|
strbuf_addstr(sb, pp->subject);
|
2012-10-18 22:43:33 +08:00
|
|
|
if (needs_rfc2047_encoding(title.buf, title.len, RFC2047_SUBJECT))
|
|
|
|
add_rfc2047(sb, title.buf, title.len,
|
|
|
|
encoding, RFC2047_SUBJECT);
|
|
|
|
else
|
|
|
|
strbuf_add_wrapped_bytes(sb, title.buf, title.len,
|
|
|
|
-last_line_length(sb), 1, max_length);
|
2007-11-05 03:15:06 +08:00
|
|
|
} else {
|
|
|
|
strbuf_addbuf(sb, &title);
|
|
|
|
}
|
|
|
|
strbuf_addch(sb, '\n');
|
|
|
|
|
teach format-patch to place other authors into in-body "From"
Format-patch generates emails with the "From" address set to the
author of each patch. If you are going to send the emails, however,
you would want to replace the author identity with yours (if they
are not the same), and bump the author identity to an in-body
header.
Normally this is handled by git-send-email, which does the
transformation before sending out the emails. However, some
workflows may not use send-email (e.g., imap-send, or a custom
script which feeds the mbox to a non-git MUA). They could each
implement this feature themselves, but getting it right is
non-trivial (one must canonicalize the identities by reversing any
RFC2047 encoding or RFC822 quoting of the headers, which has caused
many bugs in send-email over the years).
This patch takes a different approach: it teaches format-patch a
"--from" option which handles the ident check and in-body header
while it is writing out the email. It's much simpler to do at this
level (because we haven't done any quoting yet), and any workflow
based on format-patch can easily turn it on.
Signed-off-by: Jeff King <peff@peff.net>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2013-07-03 15:08:22 +08:00
|
|
|
if (need_8bit_cte == 0) {
|
|
|
|
int i;
|
|
|
|
for (i = 0; i < pp->in_body_headers.nr; i++) {
|
|
|
|
if (has_non_ascii(pp->in_body_headers.items[i].string)) {
|
|
|
|
need_8bit_cte = 1;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2008-03-15 08:10:09 +08:00
|
|
|
if (need_8bit_cte > 0) {
|
2007-11-05 03:15:06 +08:00
|
|
|
const char *header_fmt =
|
|
|
|
"MIME-Version: 1.0\n"
|
|
|
|
"Content-Type: text/plain; charset=%s\n"
|
|
|
|
"Content-Transfer-Encoding: 8bit\n";
|
|
|
|
strbuf_addf(sb, header_fmt, encoding);
|
|
|
|
}
|
2011-05-27 06:27:49 +08:00
|
|
|
if (pp->after_subject) {
|
|
|
|
strbuf_addstr(sb, pp->after_subject);
|
2007-11-05 03:15:06 +08:00
|
|
|
}
|
2011-05-27 06:27:49 +08:00
|
|
|
if (pp->fmt == CMIT_FMT_EMAIL) {
|
2007-11-05 03:15:06 +08:00
|
|
|
strbuf_addch(sb, '\n');
|
|
|
|
}
|
teach format-patch to place other authors into in-body "From"
Format-patch generates emails with the "From" address set to the
author of each patch. If you are going to send the emails, however,
you would want to replace the author identity with yours (if they
are not the same), and bump the author identity to an in-body
header.
Normally this is handled by git-send-email, which does the
transformation before sending out the emails. However, some
workflows may not use send-email (e.g., imap-send, or a custom
script which feeds the mbox to a non-git MUA). They could each
implement this feature themselves, but getting it right is
non-trivial (one must canonicalize the identities by reversing any
RFC2047 encoding or RFC822 quoting of the headers, which has caused
many bugs in send-email over the years).
This patch takes a different approach: it teaches format-patch a
"--from" option which handles the ident check and in-body header
while it is writing out the email. It's much simpler to do at this
level (because we haven't done any quoting yet), and any workflow
based on format-patch can easily turn it on.
Signed-off-by: Jeff King <peff@peff.net>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2013-07-03 15:08:22 +08:00
|
|
|
|
|
|
|
if (pp->in_body_headers.nr) {
|
|
|
|
int i;
|
|
|
|
for (i = 0; i < pp->in_body_headers.nr; i++) {
|
|
|
|
strbuf_addstr(sb, pp->in_body_headers.items[i].string);
|
|
|
|
free(pp->in_body_headers.items[i].string);
|
|
|
|
}
|
|
|
|
string_list_clear(&pp->in_body_headers, 0);
|
|
|
|
strbuf_addch(sb, '\n');
|
|
|
|
}
|
|
|
|
|
2007-11-05 03:15:06 +08:00
|
|
|
strbuf_release(&title);
|
|
|
|
}
|
|
|
|
|
2013-07-03 15:07:48 +08:00
|
|
|
void pp_remainder(struct pretty_print_context *pp,
|
2008-02-19 11:56:08 +08:00
|
|
|
const char **msg_p,
|
|
|
|
struct strbuf *sb,
|
|
|
|
int indent)
|
2007-11-05 03:15:06 +08:00
|
|
|
{
|
|
|
|
int first = 1;
|
|
|
|
for (;;) {
|
|
|
|
const char *line = *msg_p;
|
|
|
|
int linelen = get_one_line(line);
|
|
|
|
*msg_p += linelen;
|
|
|
|
|
|
|
|
if (!linelen)
|
|
|
|
break;
|
|
|
|
|
|
|
|
if (is_empty_line(line, &linelen)) {
|
|
|
|
if (first)
|
|
|
|
continue;
|
2011-05-27 06:27:49 +08:00
|
|
|
if (pp->fmt == CMIT_FMT_SHORT)
|
2007-11-05 03:15:06 +08:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
first = 0;
|
|
|
|
|
|
|
|
strbuf_grow(sb, linelen + indent + 20);
|
|
|
|
if (indent) {
|
|
|
|
memset(sb->buf + sb->len, ' ', indent);
|
|
|
|
strbuf_setlen(sb, sb->len + indent);
|
|
|
|
}
|
|
|
|
strbuf_add(sb, line, linelen);
|
|
|
|
strbuf_addch(sb, '\n');
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-07-03 15:07:48 +08:00
|
|
|
void pretty_print_commit(struct pretty_print_context *pp,
|
2011-05-27 06:27:49 +08:00
|
|
|
const struct commit *commit,
|
|
|
|
struct strbuf *sb)
|
2007-11-05 03:15:06 +08:00
|
|
|
{
|
|
|
|
unsigned long beginning_of_body;
|
|
|
|
int indent = 4;
|
2013-01-26 17:44:06 +08:00
|
|
|
const char *msg;
|
2014-06-11 05:39:30 +08:00
|
|
|
const char *reencoded;
|
2007-11-05 03:15:06 +08:00
|
|
|
const char *encoding;
|
2011-05-27 06:27:49 +08:00
|
|
|
int need_8bit_cte = pp->need_8bit_cte;
|
2007-11-05 03:15:06 +08:00
|
|
|
|
2011-05-27 06:27:49 +08:00
|
|
|
if (pp->fmt == CMIT_FMT_USERFORMAT) {
|
|
|
|
format_commit_message(commit, user_format, sb, pp);
|
2007-11-05 03:15:06 +08:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2012-10-18 08:12:55 +08:00
|
|
|
encoding = get_log_output_encoding();
|
2013-04-19 07:08:40 +08:00
|
|
|
msg = reencoded = logmsg_reencode(commit, NULL, encoding);
|
2007-11-05 03:15:06 +08:00
|
|
|
|
2011-05-27 06:27:49 +08:00
|
|
|
if (pp->fmt == CMIT_FMT_ONELINE || pp->fmt == CMIT_FMT_EMAIL)
|
2007-11-05 03:15:06 +08:00
|
|
|
indent = 0;
|
|
|
|
|
2008-03-15 08:10:09 +08:00
|
|
|
/*
|
|
|
|
* We need to check and emit Content-type: to mark it
|
|
|
|
* as 8-bit if we haven't done so.
|
2007-11-05 03:15:06 +08:00
|
|
|
*/
|
2011-05-27 06:27:49 +08:00
|
|
|
if (pp->fmt == CMIT_FMT_EMAIL && need_8bit_cte == 0) {
|
2007-11-05 03:15:06 +08:00
|
|
|
int i, ch, in_body;
|
|
|
|
|
|
|
|
for (in_body = i = 0; (ch = msg[i]); i++) {
|
|
|
|
if (!in_body) {
|
|
|
|
/* author could be non 7-bit ASCII but
|
|
|
|
* the log may be so; skip over the
|
|
|
|
* header part first.
|
|
|
|
*/
|
|
|
|
if (ch == '\n' && msg[i+1] == '\n')
|
|
|
|
in_body = 1;
|
|
|
|
}
|
|
|
|
else if (non_ascii(ch)) {
|
2008-03-15 08:10:09 +08:00
|
|
|
need_8bit_cte = 1;
|
2007-11-05 03:15:06 +08:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2011-05-27 06:27:49 +08:00
|
|
|
pp_header(pp, encoding, commit, &msg, sb);
|
|
|
|
if (pp->fmt != CMIT_FMT_ONELINE && !pp->subject) {
|
2007-11-05 03:15:06 +08:00
|
|
|
strbuf_addch(sb, '\n');
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Skip excess blank lines at the beginning of body, if any... */
|
2008-12-27 08:32:49 +08:00
|
|
|
msg = skip_empty_lines(msg);
|
2007-11-05 03:15:06 +08:00
|
|
|
|
|
|
|
/* These formats treat the title line specially. */
|
2011-05-27 06:27:49 +08:00
|
|
|
if (pp->fmt == CMIT_FMT_ONELINE || pp->fmt == CMIT_FMT_EMAIL)
|
|
|
|
pp_title_line(pp, &msg, sb, encoding, need_8bit_cte);
|
2007-11-05 03:15:06 +08:00
|
|
|
|
|
|
|
beginning_of_body = sb->len;
|
2011-05-27 06:27:49 +08:00
|
|
|
if (pp->fmt != CMIT_FMT_ONELINE)
|
|
|
|
pp_remainder(pp, &msg, sb, indent);
|
2007-11-05 03:15:06 +08:00
|
|
|
strbuf_rtrim(sb);
|
|
|
|
|
|
|
|
/* Make sure there is an EOLN for the non-oneline case */
|
2011-05-27 06:27:49 +08:00
|
|
|
if (pp->fmt != CMIT_FMT_ONELINE)
|
2007-11-05 03:15:06 +08:00
|
|
|
strbuf_addch(sb, '\n');
|
|
|
|
|
|
|
|
/*
|
|
|
|
* The caller may append additional body text in e-mail
|
|
|
|
* format. Make sure we did not strip the blank line
|
|
|
|
* between the header and the body.
|
|
|
|
*/
|
2011-05-27 06:27:49 +08:00
|
|
|
if (pp->fmt == CMIT_FMT_EMAIL && sb->len <= beginning_of_body)
|
2007-11-05 03:15:06 +08:00
|
|
|
strbuf_addch(sb, '\n');
|
2009-10-09 18:21:57 +08:00
|
|
|
|
2014-06-11 05:41:39 +08:00
|
|
|
unuse_commit_buffer(commit, reencoded);
|
2007-11-05 03:15:06 +08:00
|
|
|
}
|
2011-05-27 06:27:24 +08:00
|
|
|
|
|
|
|
void pp_commit_easy(enum cmit_fmt fmt, const struct commit *commit,
|
|
|
|
struct strbuf *sb)
|
|
|
|
{
|
|
|
|
struct pretty_print_context pp = {0};
|
2011-05-27 06:27:49 +08:00
|
|
|
pp.fmt = fmt;
|
|
|
|
pretty_print_commit(&pp, commit, sb);
|
2011-05-27 06:27:24 +08:00
|
|
|
}
|