Merge branch 'jc/pretty-lf'
authorJunio C Hamano <gitster@pobox.com>
Mon, 30 Nov 2009 22:44:22 +0000 (14:44 -0800)
committerJunio C Hamano <gitster@pobox.com>
Mon, 30 Nov 2009 22:44:22 +0000 (14:44 -0800)
Conflicts:
pretty.c
t/t6006-rev-list-format.sh

1  2 
Documentation/pretty-formats.txt
pretty.c
t/t6006-rev-list-format.sh
index 0683fb3a3d34e89c67c6e37f2ee96adcd2821ea0,ca9c6d1f8078900f3f08b24cd47ba802c5e80ba1..53a9168ba7d8959e65c2442f6a078155d6f24c71
@@@ -123,10 -123,6 +123,10 @@@ The placeholders are
  - '%s': subject
  - '%f': sanitized subject line, suitable for a filename
  - '%b': body
 +- '%N': commit notes
 +- '%gD': reflog selector, e.g., `refs/stash@\{1\}`
 +- '%gd': shortened reflog selector, e.g., `stash@\{1\}`
 +- '%gs': reflog subject
  - '%Cred': switch color to red
  - '%Cgreen': switch color to green
  - '%Cblue': switch color to blue
  - '%m': left, right or boundary mark
  - '%n': newline
  - '%x00': print a byte from a hex code
 +- '%w([<w>[,<i1>[,<i2>]]])': switch line wrapping, like the -w option of
 +  linkgit:git-shortlog[1].
 +
 +NOTE: Some placeholders may depend on other options given to the
 +revision traversal engine. For example, the `%g*` reflog options will
 +insert an empty string unless we are traversing reflog entries (e.g., by
 +`git log -g`). The `%d` placeholder will use the "short" decoration
 +format if `--decorate` was not already provided on the command line.
  
+ If you add a `{plus}` (plus sign) after '%' of a placeholder, a line-feed
+ is inserted immediately before the expansion if and only if the
+ placeholder expands to a non-empty string.
+ If you add a `-` (minus sign) after '%' of a placeholder, line-feeds that
+ immediately precede the expansion are deleted if and only if the
+ placeholder expands to an empty string.
  * 'tformat:'
  +
  The 'tformat:' format works exactly like 'format:', except that it
diff --combined pretty.c
index 5661cba5952e2e68dd067a8afebe144bc1ca63af,081feb66026a473060a7beaf40de2618b792ac0f..8f5bd1ab7f119715564fb13cb74de3937d1a3774
+++ b/pretty.c
@@@ -6,9 -6,7 +6,9 @@@
  #include "string-list.h"
  #include "mailmap.h"
  #include "log-tree.h"
 +#include "notes.h"
  #include "color.h"
 +#include "reflog-walk.h"
  
  static char *user_format;
  
@@@ -444,10 -442,9 +444,10 @@@ struct chunk 
  
  struct format_commit_context {
        const struct commit *commit;
 -      enum date_mode dmode;
 +      const struct pretty_print_context *pretty_ctx;
        unsigned commit_header_parsed:1;
        unsigned commit_message_parsed:1;
 +      size_t width, indent1, indent2;
  
        /* These offsets are relative to the start of the commit message. */
        struct chunk author;
        struct chunk abbrev_commit_hash;
        struct chunk abbrev_tree_hash;
        struct chunk abbrev_parent_hashes;
 +      size_t wrap_start;
  };
  
  static int add_again(struct strbuf *sb, struct chunk *chunk)
@@@ -599,37 -595,8 +599,37 @@@ static void format_decoration(struct st
                strbuf_addch(sb, ')');
  }
  
- static size_t format_commit_item(struct strbuf *sb, const char *placeholder,
-                                void *context)
 +static void strbuf_wrap(struct strbuf *sb, size_t pos,
 +                      size_t width, size_t indent1, size_t indent2)
 +{
 +      struct strbuf tmp = STRBUF_INIT;
 +
 +      if (pos)
 +              strbuf_add(&tmp, sb->buf, pos);
 +      strbuf_add_wrapped_text(&tmp, sb->buf + pos,
 +                              (int) indent1, (int) indent2, (int) width);
 +      strbuf_swap(&tmp, sb);
 +      strbuf_release(&tmp);
 +}
 +
 +static void rewrap_message_tail(struct strbuf *sb,
 +                              struct format_commit_context *c,
 +                              size_t new_width, size_t new_indent1,
 +                              size_t new_indent2)
 +{
 +      if (c->width == new_width && c->indent1 == new_indent1 &&
 +          c->indent2 == new_indent2)
 +              return;
 +      if (c->wrap_start < sb->len)
 +              strbuf_wrap(sb, c->wrap_start, c->width, c->indent1, c->indent2);
 +      c->wrap_start = sb->len;
 +      c->width = new_width;
 +      c->indent1 = new_indent1;
 +      c->indent2 = new_indent2;
 +}
 +
+ static size_t format_commit_one(struct strbuf *sb, const char *placeholder,
+                               void *context)
  {
        struct format_commit_context *c = context;
        const struct commit *commit = c->commit;
                        return 3;
                } else
                        return 0;
 +      case 'w':
 +              if (placeholder[1] == '(') {
 +                      unsigned long width = 0, indent1 = 0, indent2 = 0;
 +                      char *next;
 +                      const char *start = placeholder + 2;
 +                      const char *end = strchr(start, ')');
 +                      if (!end)
 +                              return 0;
 +                      if (end > start) {
 +                              width = strtoul(start, &next, 10);
 +                              if (*next == ',') {
 +                                      indent1 = strtoul(next + 1, &next, 10);
 +                                      if (*next == ',') {
 +                                              indent2 = strtoul(next + 1,
 +                                                               &next, 10);
 +                                      }
 +                              }
 +                              if (*next != ')')
 +                                      return 0;
 +                      }
 +                      rewrap_message_tail(sb, c, width, indent1, indent2);
 +                      return end - placeholder + 1;
 +              } else
 +                      return 0;
        }
  
        /* these depend on the commit */
        case 'd':
                format_decoration(sb, commit);
                return 1;
 +      case 'g':               /* reflog info */
 +              switch(placeholder[1]) {
 +              case 'd':       /* reflog selector */
 +              case 'D':
 +                      if (c->pretty_ctx->reflog_info)
 +                              get_reflog_selector(sb,
 +                                                  c->pretty_ctx->reflog_info,
 +                                                  c->pretty_ctx->date_mode,
 +                                                  (placeholder[1] == 'd'));
 +                      return 2;
 +              case 's':       /* reflog message */
 +                      if (c->pretty_ctx->reflog_info)
 +                              get_reflog_message(sb, c->pretty_ctx->reflog_info);
 +                      return 2;
 +              }
 +              return 0;       /* unknown %g placeholder */
 +      case 'N':
 +              get_commit_notes(commit, sb, git_log_output_encoding ?
 +                           git_log_output_encoding : git_commit_encoding, 0);
 +              return 1;
        }
  
        /* For the rest we have to parse the commit header. */
        case 'a':       /* author ... */
                return format_person_part(sb, placeholder[1],
                                   msg + c->author.off, c->author.len,
 -                                 c->dmode);
 +                                 c->pretty_ctx->date_mode);
        case 'c':       /* committer ... */
                return format_person_part(sb, placeholder[1],
                                   msg + c->committer.off, c->committer.len,
 -                                 c->dmode);
 +                                 c->pretty_ctx->date_mode);
        case 'e':       /* encoding */
                strbuf_add(sb, msg + c->encoding.off, c->encoding.len);
                return 1;
        return 0;       /* unknown placeholder */
  }
  
+ static size_t format_commit_item(struct strbuf *sb, const char *placeholder,
+                                void *context)
+ {
+       int consumed;
+       size_t orig_len;
+       enum {
+               NO_MAGIC,
+               ADD_LF_BEFORE_NON_EMPTY,
+               DEL_LF_BEFORE_EMPTY,
+       } magic = NO_MAGIC;
+       switch (placeholder[0]) {
+       case '-':
+               magic = DEL_LF_BEFORE_EMPTY;
+               break;
+       case '+':
+               magic = ADD_LF_BEFORE_NON_EMPTY;
+               break;
+       default:
+               break;
+       }
+       if (magic != NO_MAGIC)
+               placeholder++;
+       orig_len = sb->len;
+       consumed = format_commit_one(sb, placeholder, context);
+       if (magic == NO_MAGIC)
+               return consumed;
+       if ((orig_len == sb->len) && magic == DEL_LF_BEFORE_EMPTY) {
+               while (sb->len && sb->buf[sb->len - 1] == '\n')
+                       strbuf_setlen(sb, sb->len - 1);
+       } else if ((orig_len != sb->len) && magic == ADD_LF_BEFORE_NON_EMPTY) {
+               strbuf_insert(sb, orig_len, "\n", 1);
+       }
+       return consumed + 1;
+ }
  void format_commit_message(const struct commit *commit,
 -                         const void *format, struct strbuf *sb,
 -                         enum date_mode dmode)
 +                         const char *format, struct strbuf *sb,
 +                         const struct pretty_print_context *pretty_ctx)
  {
        struct format_commit_context context;
  
        memset(&context, 0, sizeof(context));
        context.commit = commit;
 -      context.dmode = dmode;
 +      context.pretty_ctx = pretty_ctx;
 +      context.wrap_start = sb->len;
        strbuf_expand(sb, format, format_commit_item, &context);
 +      rewrap_message_tail(sb, &context, 0, 0, 0);
  }
  
  static void pp_header(enum cmit_fmt fmt,
@@@ -979,18 -938,18 +1017,18 @@@ char *reencode_commit_message(const str
  }
  
  void pretty_print_commit(enum cmit_fmt fmt, const struct commit *commit,
 -                       struct strbuf *sb, int abbrev,
 -                       const char *subject, const char *after_subject,
 -                       enum date_mode dmode, int need_8bit_cte)
 +                       struct strbuf *sb,
 +                       const struct pretty_print_context *context)
  {
        unsigned long beginning_of_body;
        int indent = 4;
        const char *msg = commit->buffer;
        char *reencoded;
        const char *encoding;
 +      int need_8bit_cte = context->need_8bit_cte;
  
        if (fmt == CMIT_FMT_USERFORMAT) {
 -              format_commit_message(commit, user_format, sb, dmode);
 +              format_commit_message(commit, user_format, sb, context);
                return;
        }
  
                }
        }
  
 -      pp_header(fmt, abbrev, dmode, encoding, commit, &msg, sb);
 -      if (fmt != CMIT_FMT_ONELINE && !subject) {
 +      pp_header(fmt, context->abbrev, context->date_mode, encoding,
 +                commit, &msg, sb);
 +      if (fmt != CMIT_FMT_ONELINE && !context->subject) {
                strbuf_addch(sb, '\n');
        }
  
  
        /* These formats treat the title line specially. */
        if (fmt == CMIT_FMT_ONELINE || fmt == CMIT_FMT_EMAIL)
 -              pp_title_line(fmt, &msg, sb, subject,
 -                            after_subject, encoding, need_8bit_cte);
 +              pp_title_line(fmt, &msg, sb, context->subject,
 +                            context->after_subject, encoding, need_8bit_cte);
  
        beginning_of_body = sb->len;
        if (fmt != CMIT_FMT_ONELINE)
         */
        if (fmt == CMIT_FMT_EMAIL && sb->len <= beginning_of_body)
                strbuf_addch(sb, '\n');
 +
 +      if (fmt != CMIT_FMT_ONELINE)
 +              get_commit_notes(commit, sb, encoding,
 +                               NOTES_SHOW_HEADER | NOTES_INDENT);
 +
        free(reencoded);
  }
index 7f61ab0e522fd28c5e10594f03ed9076f9f5ce42,18a77a739ed326ab9cc17ca3838b3859becddabe..571931588eda5799efbf2b0789abd10b8770a0a7
@@@ -162,22 -162,26 +162,44 @@@ test_expect_success 'empty email' 
        }
  '
  
+ test_expect_success 'del LF before empty (1)' '
+       git show -s --pretty=format:"%s%n%-b%nThanks%n" HEAD^^ >actual &&
+       test $(wc -l <actual) = 2
+ '
+ test_expect_success 'del LF before empty (2)' '
+       git show -s --pretty=format:"%s%n%-b%nThanks%n" HEAD >actual &&
+       test $(wc -l <actual) = 6 &&
+       grep "^$" actual
+ '
+ test_expect_success 'add LF before non-empty (1)' '
+       git show -s --pretty=format:"%s%+b%nThanks%n" HEAD^^ >actual &&
+       test $(wc -l <actual) = 2
+ '
+ test_expect_success 'add LF before non-empty (2)' '
+       git show -s --pretty=format:"%s%+b%nThanks%n" HEAD >actual &&
+       test $(wc -l <actual) = 6 &&
+       grep "^$" actual
+ '
 +test_expect_success '"%h %gD: %gs" is same as git-reflog' '
 +      git reflog >expect &&
 +      git log -g --format="%h %gD: %gs" >actual &&
 +      test_cmp expect actual
 +'
 +
 +test_expect_success '"%h %gD: %gs" is same as git-reflog (with date)' '
 +      git reflog --date=raw >expect &&
 +      git log -g --format="%h %gD: %gs" --date=raw >actual &&
 +      test_cmp expect actual
 +'
 +
 +test_expect_success '%gd shortens ref name' '
 +      echo "master@{0}" >expect.gd-short &&
 +      git log -g -1 --format=%gd refs/heads/master >actual.gd-short &&
 +      test_cmp expect.gd-short actual.gd-short
 +'
 +
  test_done