Merge git://ozlabs.org/~paulus/gitk
[gitweb.git] / pretty.c
index c11624832bf09d9b4630d8a9b5e213fe2de69c7f..9e431545d8e6c14401faea5e76296974ca1d6649 100644 (file)
--- a/pretty.c
+++ b/pretty.c
@@ -410,10 +410,7 @@ void pp_user_info(const struct pretty_print_context *pp,
                  const char *what, struct strbuf *sb,
                  const char *line, const char *encoding)
 {
-       struct strbuf name;
-       struct strbuf mail;
        struct ident_split ident;
-       int linelen;
        char *line_end;
        const char *mailbuf, *namebuf;
        size_t namelen, maillen;
@@ -422,18 +419,10 @@ void pp_user_info(const struct pretty_print_context *pp,
        if (pp->fmt == CMIT_FMT_ONELINE)
                return;
 
-       line_end = strchr(line, '\n');
-       if (!line_end) {
-               line_end = strchr(line, '\0');
-               if (!line_end)
-                       return;
-       }
-
-       linelen = ++line_end - line;
-       if (split_ident_line(&ident, line, linelen))
+       line_end = strchrnul(line, '\n');
+       if (split_ident_line(&ident, line, line_end - line))
                return;
 
-
        mailbuf = ident.mail_begin;
        maillen = ident.mail_end - ident.mail_begin;
        namebuf = ident.name_begin;
@@ -442,43 +431,33 @@ void pp_user_info(const struct pretty_print_context *pp,
        if (pp->mailmap)
                map_user(pp->mailmap, &mailbuf, &maillen, &namebuf, &namelen);
 
-       strbuf_init(&mail, 0);
-       strbuf_init(&name, 0);
-
-       strbuf_add(&mail, mailbuf, maillen);
-       strbuf_add(&name, namebuf, namelen);
-
-       namelen = name.len + mail.len + 3; /* ' ' + '<' + '>' */
-
        if (pp->fmt == CMIT_FMT_EMAIL) {
                strbuf_addstr(sb, "From: ");
-               if (needs_rfc2047_encoding(name.buf, name.len, RFC2047_ADDRESS)) {
-                       add_rfc2047(sb, name.buf, name.len,
+               if (needs_rfc2047_encoding(namebuf, namelen, RFC2047_ADDRESS)) {
+                       add_rfc2047(sb, namebuf, namelen,
                                    encoding, RFC2047_ADDRESS);
                        max_length = 76; /* per rfc2047 */
-               } else if (needs_rfc822_quoting(name.buf, name.len)) {
+               } else if (needs_rfc822_quoting(namebuf, namelen)) {
                        struct strbuf quoted = STRBUF_INIT;
-                       add_rfc822_quoted(&quoted, name.buf, name.len);
+                       add_rfc822_quoted(&quoted, namebuf, namelen);
                        strbuf_add_wrapped_bytes(sb, quoted.buf, quoted.len,
                                                        -6, 1, max_length);
                        strbuf_release(&quoted);
                } else {
-                       strbuf_add_wrapped_bytes(sb, name.buf, name.len,
+                       strbuf_add_wrapped_bytes(sb, namebuf, namelen,
                                                 -6, 1, max_length);
                }
-               if (namelen - name.len + last_line_length(sb) > max_length)
-                       strbuf_addch(sb, '\n');
 
-               strbuf_addf(sb, " <%s>\n", mail.buf);
+               if (max_length <
+                   last_line_length(sb) + strlen(" <") + maillen + strlen(">"))
+                       strbuf_addch(sb, '\n');
+               strbuf_addf(sb, " <%.*s>\n", (int)maillen, mailbuf);
        } else {
-               strbuf_addf(sb, "%s: %.*s%s <%s>\n", what,
-                             (pp->fmt == CMIT_FMT_FULLER) ? 4 : 0,
-                             "    ", name.buf, mail.buf);
+               strbuf_addf(sb, "%s: %.*s%.*s <%.*s>\n", what,
+                           (pp->fmt == CMIT_FMT_FULLER) ? 4 : 0, "    ",
+                           (int)namelen, namebuf, (int)maillen, mailbuf);
        }
 
-       strbuf_release(&mail);
-       strbuf_release(&name);
-
        switch (pp->fmt) {
        case CMIT_FMT_MEDIUM:
                strbuf_addf(sb, "Date:   %s\n",
@@ -606,6 +585,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 +607,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 +656,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 +751,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 +829,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 +909,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 +955,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 +1072,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 +1123,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 +1135,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 +1184,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 +1264,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 +1291,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 +1424,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 +1478,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 +1667,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;