@ -606,6 +606,7 @@ static char *replace_encoding_header(char *buf, const char *encoding)
@@ -606,6 +606,7 @@ static char *replace_encoding_header(char *buf, const char *encoding)
}
char *logmsg_reencode(const struct commit *commit,
char **commit_encoding,
const char *output_encoding)
{
static const char *utf8 = "UTF-8";
@ -627,9 +628,15 @@ char *logmsg_reencode(const struct commit *commit,
@@ -627,9 +628,15 @@ char *logmsg_reencode(const struct commit *commit,
sha1_to_hex(commit->object.sha1), typename(type));
}
if (!output_encoding || !*output_encoding)
if (!output_encoding || !*output_encoding) {
if (commit_encoding)
*commit_encoding =
get_header(commit, msg, "encoding");
return msg;
}
encoding = get_header(commit, msg, "encoding");
if (commit_encoding)
*commit_encoding = encoding;
use_encoding = encoding ? encoding : utf8;
if (same_encoding(use_encoding, output_encoding)) {
/*
@ -670,7 +677,8 @@ char *logmsg_reencode(const struct commit *commit,
@@ -670,7 +677,8 @@ char *logmsg_reencode(const struct commit *commit,
if (out)
out = replace_encoding_header(out, output_encoding);
free(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.
@ -764,19 +772,38 @@ struct chunk {
@@ -764,19 +772,38 @@ struct chunk {
size_t len;
};
enum flush_type {
no_flush,
flush_right,
flush_left,
flush_left_and_steal,
flush_both
};
enum trunc_type {
trunc_none,
trunc_left,
trunc_middle,
trunc_right
};
struct format_commit_context {
const struct commit *commit;
const struct pretty_print_context *pretty_ctx;
unsigned commit_header_parsed:1;
unsigned commit_message_parsed:1;
struct signature_check signature_check;
enum flush_type flush_type;
enum trunc_type truncate;
char *message;
char *commit_encoding;
size_t width, indent1, indent2;
int auto_color;
int padding;
/* These offsets are relative to the start of the commit message. */
struct chunk author;
struct chunk committer;
struct chunk encoding;
size_t message_off;
size_t subject_off;
size_t body_off;
@ -823,9 +850,6 @@ static void parse_commit_header(struct format_commit_context *context)
@@ -823,9 +850,6 @@ static void parse_commit_header(struct format_commit_context *context)
} else if (!prefixcmp(msg + i, "committer ")) {
context->committer.off = i + 10;
context->committer.len = eol - i - 10;
} else if (!prefixcmp(msg + i, "encoding ")) {
context->encoding.off = i + 9;
context->encoding.len = eol - i - 9;
}
i = eol;
}
@ -906,23 +930,6 @@ static void parse_commit_message(struct format_commit_context *c)
@@ -906,23 +930,6 @@ static void parse_commit_message(struct format_commit_context *c)
c->commit_message_parsed = 1;
}
static void format_decoration(struct strbuf *sb, const struct commit *commit)
{
struct name_decoration *d;
const char *prefix = " (";
load_ref_decorations(DECORATE_SHORT_REFS);
d = lookup_decoration(&name_decoration, &commit->object);
while (d) {
strbuf_addstr(sb, prefix);
prefix = ", ";
strbuf_addstr(sb, d->name);
d = d->next;
}
if (prefix[0] == ',')
strbuf_addch(sb, ')');
}
static void strbuf_wrap(struct strbuf *sb, size_t pos,
size_t width, size_t indent1, size_t indent2)
{
@ -969,7 +976,112 @@ static int format_reflog_person(struct strbuf *sb,
@@ -969,7 +976,112 @@ static int format_reflog_person(struct strbuf *sb,
return format_person_part(sb, part, ident, strlen(ident), dmode);
}
static size_t format_commit_one(struct strbuf *sb, const char *placeholder,
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;
if (!prefixcmp(begin, "auto,")) {
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;
}
if (!prefixcmp(placeholder + 1, "red")) {
strbuf_addstr(sb, GIT_COLOR_RED);
return 4;
} else if (!prefixcmp(placeholder + 1, "green")) {
strbuf_addstr(sb, GIT_COLOR_GREEN);
return 6;
} else if (!prefixcmp(placeholder + 1, "blue")) {
strbuf_addstr(sb, GIT_COLOR_BLUE);
return 5;
} else if (!prefixcmp(placeholder + 1, "reset")) {
strbuf_addstr(sb, GIT_COLOR_RESET);
return 6;
} else
return 0;
}
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++;
} else if (*ch == '>') {
flush_type = flush_left_and_steal;
ch++;
} 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;
const char *end = start + strcspn(start, ",)");
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;
if (*end == ',') {
start = end + 1;
end = strchr(start, ')');
if (!end || end == start)
return 0;
if (!prefixcmp(start, "trunc)"))
c->truncate = trunc_right;
else if (!prefixcmp(start, "ltrunc)"))
c->truncate = trunc_left;
else if (!prefixcmp(start, "mtrunc)"))
c->truncate = trunc_middle;
else
return 0;
} else
c->truncate = trunc_none;
return end - placeholder + 1;
}
return 0;
}
static size_t format_commit_one(struct strbuf *sb, /* in UTF-8 */
const char *placeholder,
void *context)
{
struct format_commit_context *c = context;
@ -981,38 +1093,20 @@ static size_t format_commit_one(struct strbuf *sb, const char *placeholder,
@@ -981,38 +1093,20 @@ static size_t format_commit_one(struct strbuf *sb, const char *placeholder,
/* these are independent of the commit */
switch (placeholder[0]) {
case 'C':
if (placeholder[1] == '(') {
const char *begin = placeholder + 2;
const char *end = strchr(begin, ')');
char color[COLOR_MAXLEN];
if (!end)
return 0;
if (!prefixcmp(begin, "auto,")) {
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;
if (!prefixcmp(placeholder + 1, "(auto)")) {
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;
}
if (!prefixcmp(placeholder + 1, "red")) {
strbuf_addstr(sb, GIT_COLOR_RED);
return 4;
} else if (!prefixcmp(placeholder + 1, "green")) {
strbuf_addstr(sb, GIT_COLOR_GREEN);
return 6;
} else if (!prefixcmp(placeholder + 1, "blue")) {
strbuf_addstr(sb, GIT_COLOR_BLUE);
return 5;
} else if (!prefixcmp(placeholder + 1, "reset")) {
strbuf_addstr(sb, GIT_COLOR_RESET);
return 6;
} else
return 0;
case 'n': /* newline */
strbuf_addch(sb, '\n');
return 1;
@ -1050,6 +1144,10 @@ static size_t format_commit_one(struct strbuf *sb, const char *placeholder,
@@ -1050,6 +1144,10 @@ static size_t format_commit_one(struct strbuf *sb, const char *placeholder,
return end - placeholder + 1;
} else
return 0;
case '<':
case '>':
return parse_padding_placeholder(sb, placeholder, c);
}
/* these depend on the commit */
@ -1058,13 +1156,19 @@ static size_t format_commit_one(struct strbuf *sb, const char *placeholder,
@@ -1058,13 +1156,19 @@ static size_t format_commit_one(struct strbuf *sb, const char *placeholder,
switch (placeholder[0]) {
case 'H': /* commit hash */
strbuf_addstr(sb, diff_get_color(c->auto_color, DIFF_COMMIT));
strbuf_addstr(sb, sha1_to_hex(commit->object.sha1));
strbuf_addstr(sb, diff_get_color(c->auto_color, DIFF_RESET));
return 1;
case 'h': /* abbreviated commit hash */
if (add_again(sb, &c->abbrev_commit_hash))
strbuf_addstr(sb, diff_get_color(c->auto_color, DIFF_COMMIT));
if (add_again(sb, &c->abbrev_commit_hash)) {
strbuf_addstr(sb, diff_get_color(c->auto_color, DIFF_RESET));
return 1;
}
strbuf_addstr(sb, find_unique_abbrev(commit->object.sha1,
c->pretty_ctx->abbrev));
strbuf_addstr(sb, diff_get_color(c->auto_color, DIFF_RESET));
c->abbrev_commit_hash.len = sb->len - c->abbrev_commit_hash.off;
return 1;
case 'T': /* tree hash */
@ -1101,7 +1205,8 @@ static size_t format_commit_one(struct strbuf *sb, const char *placeholder,
@@ -1101,7 +1205,8 @@ static size_t format_commit_one(struct strbuf *sb, const char *placeholder,
strbuf_addstr(sb, get_revision_mark(NULL, commit));
return 1;
case 'd':
format_decoration(sb, commit);
load_ref_decorations(DECORATE_SHORT_REFS);
format_decorations(sb, commit, c->auto_color);
return 1;
case 'g': /* reflog info */
switch(placeholder[1]) {
@ -1180,7 +1285,8 @@ static size_t format_commit_one(struct strbuf *sb, const char *placeholder,
@@ -1180,7 +1285,8 @@ static size_t format_commit_one(struct strbuf *sb, const char *placeholder,
msg + c->committer.off, c->committer.len,
c->pretty_ctx->date_mode);
case 'e': /* encoding */
strbuf_add(sb, msg + c->encoding.off, c->encoding.len);
if (c->commit_encoding)
strbuf_addstr(sb, c->commit_encoding);
return 1;
case 'B': /* raw body */
/* message_off is always left at the initial newline */
@ -1206,7 +1312,111 @@ static size_t format_commit_one(struct strbuf *sb, const char *placeholder,
@@ -1206,7 +1312,111 @@ static size_t format_commit_one(struct strbuf *sb, const char *placeholder,
return 0; /* unknown placeholder */
}
static size_t format_commit_item(struct strbuf *sb, const char *placeholder,
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);
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;
}
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;
}
strbuf_addstr(sb, local_sb.buf);
} else {
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;
}
static size_t format_commit_item(struct strbuf *sb, /* in UTF-8 */
const char *placeholder,
void *context)
{
int consumed;
@ -1235,7 +1445,10 @@ static size_t format_commit_item(struct strbuf *sb, const char *placeholder,
@@ -1235,7 +1445,10 @@ static size_t format_commit_item(struct strbuf *sb, const char *placeholder,
placeholder++;
orig_len = sb->len;
consumed = format_commit_one(sb, placeholder, context);
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);
if (magic == NO_MAGIC)
return consumed;
@ -1286,16 +1499,37 @@ void format_commit_message(const struct commit *commit,
@@ -1286,16 +1499,37 @@ void format_commit_message(const struct commit *commit,
{
struct format_commit_context context;
const char *output_enc = pretty_ctx->output_encoding;
const char *utf8 = "UTF-8";
memset(&context, 0, sizeof(context));
context.commit = commit;
context.pretty_ctx = pretty_ctx;
context.wrap_start = sb->len;
context.message = logmsg_reencode(commit, output_enc);
context.message = logmsg_reencode(commit,
&context.commit_encoding,
output_enc);
strbuf_expand(sb, format, format_commit_item, &context);
rewrap_message_tail(sb, &context, 0, 0, 0);
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);
}
free(context.commit_encoding);
logmsg_free(context.message, commit);
free(context.signature_check.gpg_output);
free(context.signature_check.signer);
@ -1454,7 +1688,7 @@ void pretty_print_commit(const struct pretty_print_context *pp,
@@ -1454,7 +1688,7 @@ void pretty_print_commit(const struct pretty_print_context *pp,
}
encoding = get_log_output_encoding();
msg = reencoded = logmsg_reencode(commit, encoding);
msg = reencoded = logmsg_reencode(commit, NULL, encoding);
if (pp->fmt == CMIT_FMT_ONELINE || pp->fmt == CMIT_FMT_EMAIL)
indent = 0;