Merge branch 'nd/pretty-formats'
pretty-printing body of the commit that is stored in non UTF-8 encoding did not work well. The early part of this series fixes it. And then it adds %C(auto) specifier that turns the coloring on when we are emitting to the terminal, and adds column-aligning format directives. * nd/pretty-formats: pretty: support %>> that steal trailing spaces pretty: support truncating in %>, %< and %>< pretty: support padding placeholders, %< %> and %>< pretty: add %C(auto) for auto-coloring pretty: split color parsing into a separate function pretty: two phase conversion for non utf-8 commits utf8.c: add reencode_string_len() that can handle NULs in string utf8.c: add utf8_strnwidth() with the ability to skip ansi sequences utf8.c: move display_mode_esc_sequence_len() for use by other functions pretty: share code between format_decoration and show_decorations pretty-formats.txt: wrap long lines pretty: get the correct encoding for --pretty:format=%e pretty: save commit encoding from logmsg_reencode if the caller needs it
This commit is contained in:
358
pretty.c
358
pretty.c
@ -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,
|
||||
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,
|
||||
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 {
|
||||
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)
|
||||
} 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)
|
||||
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,
|
||||
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,
|
||||
/* 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,
|
||||
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,
|
||||
|
||||
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,
|
||||
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,
|
||||
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,
|
||||
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,
|
||||
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,
|
||||
{
|
||||
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,
|
||||
}
|
||||
|
||||
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;
|
||||
|
Reference in New Issue
Block a user