Merge branch 'nd/pretty-formats'
authorJunio C Hamano <gitster@pobox.com>
Tue, 23 Apr 2013 18:22:48 +0000 (11:22 -0700)
committerJunio C Hamano <gitster@pobox.com>
Tue, 23 Apr 2013 18:22:48 +0000 (11:22 -0700)
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

1  2 
Documentation/pretty-formats.txt
builtin/blame.c
pretty.c
revision.c
index f5b50dcba2b97d9293cf2332346e778706bda589,c96ff41fd51ffa10ff0cbcf5ef448f8141c68695..1d174fd0b6fa7f9fae3eb1523c8c6e2192e71e45
@@@ -75,7 -75,7 +75,7 @@@ This is designed to be as compact as po
  * 'raw'
  +
  The 'raw' format shows the entire commit exactly as
 -stored in the commit object.  Notably, the SHA1s are
 +stored in the commit object.  Notably, the SHA-1s are
  displayed in full, regardless of whether --abbrev or
  --no-abbrev are used, and 'parents' information show the
  true parent commits, without taking grafts nor history
@@@ -106,18 -106,22 +106,22 @@@ The placeholders are
  - '%P': parent hashes
  - '%p': abbreviated parent hashes
  - '%an': author name
- - '%aN': author name (respecting .mailmap, see linkgit:git-shortlog[1] or linkgit:git-blame[1])
+ - '%aN': author name (respecting .mailmap, see linkgit:git-shortlog[1]
+   or linkgit:git-blame[1])
  - '%ae': author email
- - '%aE': author email (respecting .mailmap, see linkgit:git-shortlog[1] or linkgit:git-blame[1])
+ - '%aE': author email (respecting .mailmap, see
+   linkgit:git-shortlog[1] or linkgit:git-blame[1])
  - '%ad': author date (format respects --date= option)
  - '%aD': author date, RFC2822 style
  - '%ar': author date, relative
  - '%at': author date, UNIX timestamp
  - '%ai': author date, ISO 8601 format
  - '%cn': committer name
- - '%cN': committer name (respecting .mailmap, see linkgit:git-shortlog[1] or linkgit:git-blame[1])
+ - '%cN': committer name (respecting .mailmap, see
+   linkgit:git-shortlog[1] or linkgit:git-blame[1])
  - '%ce': committer email
- - '%cE': committer email (respecting .mailmap, see linkgit:git-shortlog[1] or linkgit:git-blame[1])
+ - '%cE': committer email (respecting .mailmap, see
+   linkgit:git-shortlog[1] or linkgit:git-blame[1])
  - '%cd': committer date
  - '%cD': committer date, RFC2822 style
  - '%cr': committer date, relative
  - '%gD': reflog selector, e.g., `refs/stash@{1}`
  - '%gd': shortened reflog selector, e.g., `stash@{1}`
  - '%gn': reflog identity name
- - '%gN': reflog identity name (respecting .mailmap, see linkgit:git-shortlog[1] or linkgit:git-blame[1])
+ - '%gN': reflog identity name (respecting .mailmap, see
+   linkgit:git-shortlog[1] or linkgit:git-blame[1])
  - '%ge': reflog identity email
- - '%gE': reflog identity email (respecting .mailmap, see linkgit:git-shortlog[1] or linkgit:git-blame[1])
+ - '%gE': reflog identity email (respecting .mailmap, see
+   linkgit:git-shortlog[1] or linkgit:git-blame[1])
  - '%gs': reflog subject
  - '%Cred': switch color to red
  - '%Cgreen': switch color to green
    adding `auto,` at the beginning will emit color only when colors are
    enabled for log output (by `color.diff`, `color.ui`, or `--color`, and
    respecting the `auto` settings of the former if we are going to a
-   terminal)
+   terminal). `auto` alone (i.e. `%C(auto)`) will turn on auto coloring
+   on the next placeholders until the color is switched again.
  - '%m': left, right or boundary mark
  - '%n': newline
  - '%%': a raw '%'
  - '%x00': print a byte from a hex code
  - '%w([<w>[,<i1>[,<i2>]]])': switch line wrapping, like the -w option of
    linkgit:git-shortlog[1].
+ - '%<(<N>[,trunc|ltrunc|mtrunc])': make the next placeholder take at
+   least N columns, padding spaces on the right if necessary.
+   Optionally truncate at the beginning (ltrunc), the middle (mtrunc)
+   or the end (trunc) if the output is longer than N columns.
+   Note that truncating only works correctly with N >= 2.
+ - '%<|(<N>)': make the next placeholder take at least until Nth
+   columns, padding spaces on the right if necessary
+ - '%>(<N>)', '%>|(<N>)': similar to '%<(<N>)', '%<|(<N>)'
+   respectively, but padding spaces on the left
+ - '%>>(<N>)', '%>>|(<N>)': similar to '%>(<N>)', '%>|(<N>)'
+   respectively, except that if the next placeholder takes more spaces
+   than given and there are spaces on its left, use those spaces
+ - '%><(<N>)', '%><|(<N>)': similar to '% <(<N>)', '%<|(<N>)'
+   respectively, but padding both sides (i.e. the text is centered)
  
  NOTE: Some placeholders may depend on other options given to the
  revision traversal engine. For example, the `%g*` reflog options will
diff --combined builtin/blame.c
index 77707819afa03703b3ee110345a79eec3d3d9d6f,104a948e0c15361a0d62f85328a225d9ad9a0ec4..57a487e052fccc9ad45fa90a68a74fd54a1cdf72
@@@ -1375,15 -1375,10 +1375,15 @@@ static void get_ac_line(const char *inb
        maillen = ident.mail_end - ident.mail_begin;
        mailbuf = ident.mail_begin;
  
 -      *time = strtoul(ident.date_begin, NULL, 10);
 +      if (ident.date_begin && ident.date_end)
 +              *time = strtoul(ident.date_begin, NULL, 10);
 +      else
 +              *time = 0;
  
 -      len = ident.tz_end - ident.tz_begin;
 -      strbuf_add(tz, ident.tz_begin, len);
 +      if (ident.tz_begin && ident.tz_end)
 +              strbuf_add(tz, ident.tz_begin, ident.tz_end - ident.tz_begin);
 +      else
 +              strbuf_addstr(tz, "(unknown)");
  
        /*
         * Now, convert both name and e-mail using mailmap
@@@ -1430,7 -1425,7 +1430,7 @@@ static void get_commit_info(struct comm
        commit_info_init(ret);
  
        encoding = get_log_output_encoding();
-       message = logmsg_reencode(commit, encoding);
+       message = logmsg_reencode(commit, NULL, encoding);
        get_ac_line(message, "\nauthor ",
                    &ret->author, &ret->author_mail,
                    &ret->author_time, &ret->author_tz);
diff --combined pretty.c
index c11624832bf09d9b4630d8a9b5e213fe2de69c7f,d59579a2c0190a397fd0ceb5e051d241f731f5ed..ba3148181eff960ff6de68cafffe77e37719fd1d
+++ b/pretty.c
@@@ -393,19 -393,6 +393,19 @@@ static void add_rfc2047(struct strbuf *
        strbuf_addstr(sb, "?=");
  }
  
 +static const char *show_ident_date(const struct ident_split *ident,
 +                                 enum date_mode mode)
 +{
 +      unsigned long date = 0;
 +      int tz = 0;
 +
 +      if (ident->date_begin && ident->date_end)
 +              date = strtoul(ident->date_begin, NULL, 10);
 +      if (ident->tz_begin && ident->tz_end)
 +              tz = strtol(ident->tz_begin, NULL, 10);
 +      return show_date(date, tz, mode);
 +}
 +
  void pp_user_info(const struct pretty_print_context *pp,
                  const char *what, struct strbuf *sb,
                  const char *line, const char *encoding)
        struct strbuf mail;
        struct ident_split ident;
        int linelen;
 -      char *line_end, *date;
 +      char *line_end;
        const char *mailbuf, *namebuf;
        size_t namelen, maillen;
        int max_length = 78; /* per rfc2822 */
 -      unsigned long time;
 -      int tz;
  
        if (pp->fmt == CMIT_FMT_ONELINE)
                return;
        strbuf_add(&name, namebuf, namelen);
  
        namelen = name.len + mail.len + 3; /* ' ' + '<' + '>' */
 -      time = strtoul(ident.date_begin, &date, 10);
 -      tz = strtol(date, NULL, 10);
  
        if (pp->fmt == CMIT_FMT_EMAIL) {
                strbuf_addstr(sb, "From: ");
  
        switch (pp->fmt) {
        case CMIT_FMT_MEDIUM:
 -              strbuf_addf(sb, "Date:   %s\n", show_date(time, tz, pp->date_mode));
 +              strbuf_addf(sb, "Date:   %s\n",
 +                          show_ident_date(&ident, pp->date_mode));
                break;
        case CMIT_FMT_EMAIL:
 -              strbuf_addf(sb, "Date: %s\n", show_date(time, tz, DATE_RFC2822));
 +              strbuf_addf(sb, "Date: %s\n",
 +                          show_ident_date(&ident, DATE_RFC2822));
                break;
        case CMIT_FMT_FULLER:
 -              strbuf_addf(sb, "%sDate: %s\n", what, show_date(time, tz, pp->date_mode));
 +              strbuf_addf(sb, "%sDate: %s\n", what,
 +                          show_ident_date(&ident, pp->date_mode));
                break;
        default:
                /* notin' */
@@@ -606,6 -594,7 +606,7 @@@ static char *replace_encoding_header(ch
  }
  
  char *logmsg_reencode(const struct commit *commit,
+                     char **commit_encoding,
                      const char *output_encoding)
  {
        static const char *utf8 = "UTF-8";
                            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)) {
                /*
        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.
@@@ -700,6 -696,8 +708,6 @@@ static size_t format_person_part(struc
  {
        /* currently all placeholders have same length */
        const int placeholder_len = 2;
 -      int tz;
 -      unsigned long date = 0;
        struct ident_split s;
        const char *name, *mail;
        size_t maillen, namelen;
        if (!s.date_begin)
                goto skip;
  
 -      date = strtoul(s.date_begin, NULL, 10);
 -
        if (part == 't') {      /* date, UNIX timestamp */
                strbuf_add(sb, s.date_begin, s.date_end - s.date_begin);
                return placeholder_len;
        }
  
 -      /* parse tz */
 -      tz = strtoul(s.tz_begin + 1, NULL, 10);
 -      if (*s.tz_begin == '-')
 -              tz = -tz;
 -
        switch (part) {
        case 'd':       /* date */
 -              strbuf_addstr(sb, show_date(date, tz, dmode));
 +              strbuf_addstr(sb, show_ident_date(&s, dmode));
                return placeholder_len;
        case 'D':       /* date, RFC2822 style */
 -              strbuf_addstr(sb, show_date(date, tz, DATE_RFC2822));
 +              strbuf_addstr(sb, show_ident_date(&s, DATE_RFC2822));
                return placeholder_len;
        case 'r':       /* date, relative */
 -              strbuf_addstr(sb, show_date(date, tz, DATE_RELATIVE));
 +              strbuf_addstr(sb, show_ident_date(&s, DATE_RELATIVE));
                return placeholder_len;
        case 'i':       /* date, ISO 8601 */
 -              strbuf_addstr(sb, show_date(date, tz, DATE_ISO8601));
 +              strbuf_addstr(sb, show_ident_date(&s, DATE_ISO8601));
                return placeholder_len;
        }
  
@@@ -764,19 -769,38 +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 -847,6 +850,6 @@@ static void parse_commit_header(struct 
                } 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 -927,6 +930,6 @@@ static void parse_commit_message(struc
        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 -973,112 +976,112 @@@ static int format_reflog_person(struct 
        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;
        /* 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;
                        return end - placeholder + 1;
                } else
                        return 0;
+       case '<':
+       case '>':
+               return parse_padding_placeholder(sb, placeholder, c);
        }
  
        /* these depend on the commit */
  
        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 */
                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]) {
                                   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 */
        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;
                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 -1496,37 +1499,37 @@@ void format_commit_message(const struc
  {
        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 -1685,7 +1688,7 @@@ void pretty_print_commit(const struct p
        }
  
        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;
diff --combined revision.c
index 56d666d69a8a961d33651c0658f7009967e0b7ab,d739211f9be0375deb3dbab81d9c0391916dfd65..a67b615bfc7001ac28e339db1d91ea46a1b125b5
@@@ -1276,8 -1276,7 +1276,8 @@@ static void read_revisions_from_stdin(s
                        }
                        die("options not supported in --stdin mode");
                }
 -              if (handle_revision_arg(sb.buf, revs, 0, REVARG_CANNOT_BE_FILENAME))
 +              if (handle_revision_arg(xstrdup(sb.buf), revs, 0,
 +                                      REVARG_CANNOT_BE_FILENAME))
                        die("bad revision '%s'", sb.buf);
        }
        if (seen_dashdash)
@@@ -2292,7 -2291,7 +2292,7 @@@ static int commit_match(struct commit *
         * in it.
         */
        encoding = get_log_output_encoding();
-       message = logmsg_reencode(commit, encoding);
+       message = logmsg_reencode(commit, NULL, encoding);
  
        /* Copy the commit to temporary if we are using "fake" headers */
        if (buf.len)