Merge branch 'jc/same-encoding'
authorJunio C Hamano <gitster@pobox.com>
Thu, 3 Jan 2013 18:29:08 +0000 (10:29 -0800)
committerJunio C Hamano <gitster@pobox.com>
Thu, 3 Jan 2013 18:29:09 +0000 (10:29 -0800)
Finishing touches to the series to unify "Do we need to reencode
between these two encodings?" logic.

* jc/same-encoding:
format_commit_message(): simplify calls to logmsg_reencode()

1  2 
pretty.c
diff --combined pretty.c
index 5bdc2e70bcd1e8fc0c7e625c3eac26397d8c96d2,732e2a26f328f99472d541ea4dad1bd7d4e444fd..91bb2d3ef675b12ac5425ae71600e13c1adc34f9
+++ b/pretty.c
@@@ -231,7 -231,7 +231,7 @@@ static int is_rfc822_special(char ch
        }
  }
  
 -static int has_rfc822_specials(const char *s, int len)
 +static int needs_rfc822_quoting(const char *s, int len)
  {
        int i;
        for (i = 0; i < len; i++)
        return 0;
  }
  
 +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);
 +}
 +
  static void add_rfc822_quoted(struct strbuf *out, const char *s, int len)
  {
        int i;
        strbuf_addch(out, '"');
  }
  
 -static int is_rfc2047_special(char ch)
 +enum rfc2047_type {
 +      RFC2047_SUBJECT,
 +      RFC2047_ADDRESS,
 +};
 +
 +static int is_rfc2047_special(char ch, enum rfc2047_type type)
  {
 -      return (non_ascii(ch) || (ch == '=') || (ch == '?') || (ch == '_'));
 +      /*
 +       * 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 == '_')
 +              return 1;
 +
 +      /*
 +       * 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 == '/');
  }
  
 -static void add_rfc2047(struct strbuf *sb, const char *line, int len,
 -                     const char *encoding)
 +static int needs_rfc2047_encoding(const char *line, int len,
 +                                enum rfc2047_type type)
  {
 -      static const int max_length = 78; /* per rfc2822 */
        int i;
 -      int line_len;
 -
 -      /* How many bytes are already used on the current line? */
 -      for (i = sb->len - 1; i >= 0; i--)
 -              if (sb->buf[i] == '\n')
 -                      break;
 -      line_len = sb->len - (i+1);
  
        for (i = 0; i < len; i++) {
                int ch = line[i];
                if (non_ascii(ch) || ch == '\n')
 -                      goto needquote;
 +                      return 1;
                if ((i + 1 < len) && (ch == '=' && line[i+1] == '?'))
 -                      goto needquote;
 +                      return 1;
        }
 -      strbuf_add_wrapped_bytes(sb, line, len, 0, 1, max_length - line_len);
 -      return;
  
 -needquote:
 +      return 0;
 +}
 +
 +static void add_rfc2047(struct strbuf *sb, const char *line, int len,
 +                     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);
 +
        strbuf_grow(sb, len * 3 + strlen(encoding) + 100);
        strbuf_addf(sb, "=?%s?q?", encoding);
        line_len += strlen(encoding) + 5; /* 5 for =??q? */
        for (i = 0; i < len; i++) {
                unsigned ch = line[i] & 0xFF;
 +              int is_special = is_rfc2047_special(ch, type);
  
 -              if (line_len >= max_length - 2) {
 +              /*
 +               * 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.
 +               */
 +
 +              if (line_len + 2 + (is_special ? 3 : 1) > max_encoded_length) {
                        strbuf_addf(sb, "?=\n =?%s?q?", encoding);
                        line_len = strlen(encoding) + 5 + 1; /* =??q? plus SP */
                }
  
 -              /*
 -               * We encode ' ' using '=20' even though rfc2047
 -               * allows using '_' for readability.  Unfortunately,
 -               * many programs do not understand this and just
 -               * leave the underscore in place.
 -               */
 -              if (is_rfc2047_special(ch) || ch == ' ' || ch == '\n') {
 +              if (is_special) {
                        strbuf_addf(sb, "=%02X", ch);
                        line_len += 3;
 -              }
 -              else {
 +              } else {
                        strbuf_addch(sb, ch);
                        line_len++;
                }
@@@ -387,7 -323,6 +387,7 @@@ void pp_user_info(const struct pretty_p
                  const char *what, struct strbuf *sb,
                  const char *line, const char *encoding)
  {
 +      int max_length = 78; /* per rfc2822 */
        char *date;
        int namelen;
        unsigned long time;
        if (pp->fmt == CMIT_FMT_EMAIL) {
                char *name_tail = strchr(line, '<');
                int display_name_length;
 -              int final_line;
                if (!name_tail)
                        return;
                while (line < name_tail && isspace(name_tail[-1]))
                        name_tail--;
                display_name_length = name_tail - line;
                strbuf_addstr(sb, "From: ");
 -              if (!has_rfc822_specials(line, display_name_length)) {
 -                      add_rfc2047(sb, line, display_name_length, encoding);
 -              } else {
 +              if (needs_rfc2047_encoding(line, display_name_length, RFC2047_ADDRESS)) {
 +                      add_rfc2047(sb, line, display_name_length,
 +                                              encoding, RFC2047_ADDRESS);
 +                      max_length = 76; /* per rfc2047 */
 +              } else if (needs_rfc822_quoting(line, display_name_length)) {
                        struct strbuf quoted = STRBUF_INIT;
                        add_rfc822_quoted(&quoted, line, display_name_length);
 -                      add_rfc2047(sb, quoted.buf, quoted.len, encoding);
 +                      strbuf_add_wrapped_bytes(sb, quoted.buf, quoted.len,
 +                                                      -6, 1, max_length);
                        strbuf_release(&quoted);
 +              } else {
 +                      strbuf_add_wrapped_bytes(sb, line, display_name_length,
 +                                                      -6, 1, max_length);
                }
 -              for (final_line = 0; final_line < sb->len; final_line++)
 -                      if (sb->buf[sb->len - final_line - 1] == '\n')
 -                              break;
 -              if (namelen - display_name_length + final_line > 78) {
 +              if (namelen - display_name_length + last_line_length(sb) > max_length) {
                        strbuf_addch(sb, '\n');
                        if (!isspace(name_tail[0]))
                                strbuf_addch(sb, ' ');
@@@ -567,7 -500,7 +567,7 @@@ char *logmsg_reencode(const struct comm
        char *encoding;
        char *out;
  
-       if (!*output_encoding)
+       if (!output_encoding || !*output_encoding)
                return NULL;
        encoding = get_header(commit, "encoding");
        use_encoding = encoding ? encoding : utf8;
@@@ -1100,8 -1033,9 +1100,8 @@@ static size_t format_commit_one(struct 
                }
                return 0;       /* unknown %g placeholder */
        case 'N':
 -              if (c->pretty_ctx->show_notes) {
 -                      format_display_notes(commit->object.sha1, sb,
 -                                  get_log_output_encoding(), 0);
 +              if (c->pretty_ctx->notes_message) {
 +                      strbuf_addstr(sb, c->pretty_ctx->notes_message);
                        return 1;
                }
                return 0;
@@@ -1250,23 -1184,15 +1250,15 @@@ void format_commit_message(const struc
                           const struct pretty_print_context *pretty_ctx)
  {
        struct format_commit_context context;
-       static const char utf8[] = "UTF-8";
        const char *output_enc = pretty_ctx->output_encoding;
  
        memset(&context, 0, sizeof(context));
        context.commit = commit;
        context.pretty_ctx = pretty_ctx;
        context.wrap_start = sb->len;
-       context.message = commit->buffer;
-       if (output_enc) {
-               char *enc = get_header(commit, "encoding");
-               if (strcmp(enc ? enc : utf8, output_enc)) {
-                       context.message = logmsg_reencode(commit, output_enc);
-                       if (!context.message)
-                               context.message = commit->buffer;
-               }
-               free(enc);
-       }
+       context.message = logmsg_reencode(commit, output_enc);
+       if (!context.message)
+               context.message = commit->buffer;
  
        strbuf_expand(sb, format, format_commit_item, &context);
        rewrap_message_tail(sb, &context, 0, 0, 0);
@@@ -1344,7 -1270,6 +1336,7 @@@ void pp_title_line(const struct pretty_
                   const char *encoding,
                   int need_8bit_cte)
  {
 +      static const int max_length = 78; /* per rfc2047 */
        struct strbuf title;
  
        strbuf_init(&title, 80);
        strbuf_grow(sb, title.len + 1024);
        if (pp->subject) {
                strbuf_addstr(sb, pp->subject);
 -              add_rfc2047(sb, title.buf, title.len, encoding);
 +              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);
        } else {
                strbuf_addbuf(sb, &title);
        }
@@@ -1413,6 -1333,16 +1405,6 @@@ void pp_remainder(const struct pretty_p
        }
  }
  
 -char *reencode_commit_message(const struct commit *commit, const char **encoding_p)
 -{
 -      const char *encoding;
 -
 -      encoding = get_log_output_encoding();
 -      if (encoding_p)
 -              *encoding_p = encoding;
 -      return logmsg_reencode(commit, encoding);
 -}
 -
  void pretty_print_commit(const struct pretty_print_context *pp,
                         const struct commit *commit,
                         struct strbuf *sb)
                return;
        }
  
 -      reencoded = reencode_commit_message(commit, &encoding);
 +      encoding = get_log_output_encoding();
 +      reencoded = logmsg_reencode(commit, encoding);
        if (reencoded) {
                msg = reencoded;
        }
        if (pp->fmt == CMIT_FMT_EMAIL && sb->len <= beginning_of_body)
                strbuf_addch(sb, '\n');
  
 -      if (pp->show_notes)
 -              format_display_notes(commit->object.sha1, sb, encoding,
 -                                   NOTES_SHOW_HEADER | NOTES_INDENT);
 -
        free(reencoded);
  }