Tidy up git mergetool's backup file behaviour
[gitweb.git] / pretty.c
index 490cede263020dc15f4d48af06faf8d77552883f..703f52176bf0b5cbf52cf1f77ba55ba6d87fac94 100644 (file)
--- a/pretty.c
+++ b/pretty.c
@@ -1,6 +1,5 @@
 #include "cache.h"
 #include "commit.h"
-#include "interpolate.h"
 #include "utf8.h"
 #include "diff.h"
 #include "revision.h"
@@ -31,8 +30,7 @@ enum cmit_fmt get_commit_format(const char *arg)
        if (*arg == '=')
                arg++;
        if (!prefixcmp(arg, "format:")) {
-               if (user_format)
-                       free(user_format);
+               free(user_format);
                user_format = xstrdup(arg + 7);
                return CMIT_FMT_USERFORMAT;
        }
@@ -111,9 +109,9 @@ static void add_rfc2047(struct strbuf *sb, const char *line, int len,
        strbuf_addstr(sb, "?=");
 }
 
-static void add_user_info(const char *what, enum cmit_fmt fmt, struct strbuf *sb,
-                        const char *line, enum date_mode dmode,
-                        const char *encoding)
+void pp_user_info(const char *what, enum cmit_fmt fmt, struct strbuf *sb,
+                 const char *line, enum date_mode dmode,
+                 const char *encoding)
 {
        char *date;
        int namelen;
@@ -283,42 +281,60 @@ static char *logmsg_reencode(const struct commit *commit,
        return out;
 }
 
-static void fill_person(struct interp *table, const char *msg, int len)
+static size_t format_person_part(struct strbuf *sb, char part,
+                               const char *msg, int len)
 {
+       /* currently all placeholders have same length */
+       const int placeholder_len = 2;
        int start, end, tz = 0;
-       unsigned long date;
+       unsigned long date = 0;
        char *ep;
 
-       /* parse name */
+       /* advance 'end' to point to email start delimiter */
        for (end = 0; end < len && msg[end] != '<'; end++)
                ; /* do nothing */
-       start = end + 1;
-       while (end > 0 && isspace(msg[end - 1]))
-               end--;
-       table[0].value = xmemdupz(msg, end);
 
-       if (start >= len)
-               return;
+       /*
+        * When end points at the '<' that we found, it should have
+        * matching '>' later, which means 'end' must be strictly
+        * below len - 1.
+        */
+       if (end >= len - 2)
+               goto skip;
+
+       if (part == 'n') {      /* name */
+               while (end > 0 && isspace(msg[end - 1]))
+                       end--;
+               strbuf_add(sb, msg, end);
+               return placeholder_len;
+       }
+       start = ++end; /* save email start position */
 
-       /* parse email */
-       for (end = start + 1; end < len && msg[end] != '>'; end++)
+       /* advance 'end' to point to email end delimiter */
+       for ( ; end < len && msg[end] != '>'; end++)
                ; /* do nothing */
 
        if (end >= len)
-               return;
+               goto skip;
 
-       table[1].value = xmemdupz(msg + start, end - start);
+       if (part == 'e') {      /* email */
+               strbuf_add(sb, msg + start, end - start);
+               return placeholder_len;
+       }
 
-       /* parse date */
+       /* advance 'start' to point to date start delimiter */
        for (start = end + 1; start < len && isspace(msg[start]); start++)
                ; /* do nothing */
        if (start >= len)
-               return;
+               goto skip;
        date = strtoul(msg + start, &ep, 10);
        if (msg + start == ep)
-               return;
+               goto skip;
 
-       table[5].value = xmemdupz(msg + start, ep - (msg + start));
+       if (part == 't') {      /* date, UNIX timestamp */
+               strbuf_add(sb, msg + start, ep - (msg + start));
+               return placeholder_len;
+       }
 
        /* parse tz */
        for (start = ep - msg + 1; start < len && isspace(msg[start]); start++)
@@ -329,115 +345,77 @@ static void fill_person(struct interp *table, const char *msg, int len)
                        tz = -tz;
        }
 
-       interp_set_entry(table, 2, show_date(date, tz, DATE_NORMAL));
-       interp_set_entry(table, 3, show_date(date, tz, DATE_RFC2822));
-       interp_set_entry(table, 4, show_date(date, tz, DATE_RELATIVE));
-       interp_set_entry(table, 6, show_date(date, tz, DATE_ISO8601));
+       switch (part) {
+       case 'd':       /* date */
+               strbuf_addstr(sb, show_date(date, tz, DATE_NORMAL));
+               return placeholder_len;
+       case 'D':       /* date, RFC2822 style */
+               strbuf_addstr(sb, show_date(date, tz, DATE_RFC2822));
+               return placeholder_len;
+       case 'r':       /* date, relative */
+               strbuf_addstr(sb, show_date(date, tz, DATE_RELATIVE));
+               return placeholder_len;
+       case 'i':       /* date, ISO 8601 */
+               strbuf_addstr(sb, show_date(date, tz, DATE_ISO8601));
+               return placeholder_len;
+       }
+
+skip:
+       /*
+        * bogus commit, 'sb' cannot be updated, but we still need to
+        * compute a valid return value.
+        */
+       if (part == 'n' || part == 'e' || part == 't' || part == 'd'
+           || part == 'D' || part == 'r' || part == 'i')
+               return placeholder_len;
+
+       return 0; /* unknown placeholder */
 }
 
-void format_commit_message(const struct commit *commit,
-                           const void *format, struct strbuf *sb)
-{
-       struct interp table[] = {
-               { "%H" },       /* commit hash */
-               { "%h" },       /* abbreviated commit hash */
-               { "%T" },       /* tree hash */
-               { "%t" },       /* abbreviated tree hash */
-               { "%P" },       /* parent hashes */
-               { "%p" },       /* abbreviated parent hashes */
-               { "%an" },      /* author name */
-               { "%ae" },      /* author email */
-               { "%ad" },      /* author date */
-               { "%aD" },      /* author date, RFC2822 style */
-               { "%ar" },      /* author date, relative */
-               { "%at" },      /* author date, UNIX timestamp */
-               { "%ai" },      /* author date, ISO 8601 */
-               { "%cn" },      /* committer name */
-               { "%ce" },      /* committer email */
-               { "%cd" },      /* committer date */
-               { "%cD" },      /* committer date, RFC2822 style */
-               { "%cr" },      /* committer date, relative */
-               { "%ct" },      /* committer date, UNIX timestamp */
-               { "%ci" },      /* committer date, ISO 8601 */
-               { "%e" },       /* encoding */
-               { "%s" },       /* subject */
-               { "%b" },       /* body */
-               { "%Cred" },    /* red */
-               { "%Cgreen" },  /* green */
-               { "%Cblue" },   /* blue */
-               { "%Creset" },  /* reset color */
-               { "%n" },       /* newline */
-               { "%m" },       /* left/right/bottom */
-       };
-       enum interp_index {
-               IHASH = 0, IHASH_ABBREV,
-               ITREE, ITREE_ABBREV,
-               IPARENTS, IPARENTS_ABBREV,
-               IAUTHOR_NAME, IAUTHOR_EMAIL,
-               IAUTHOR_DATE, IAUTHOR_DATE_RFC2822, IAUTHOR_DATE_RELATIVE,
-               IAUTHOR_TIMESTAMP, IAUTHOR_ISO8601,
-               ICOMMITTER_NAME, ICOMMITTER_EMAIL,
-               ICOMMITTER_DATE, ICOMMITTER_DATE_RFC2822,
-               ICOMMITTER_DATE_RELATIVE, ICOMMITTER_TIMESTAMP,
-               ICOMMITTER_ISO8601,
-               IENCODING,
-               ISUBJECT,
-               IBODY,
-               IRED, IGREEN, IBLUE, IRESET_COLOR,
-               INEWLINE,
-               ILEFT_RIGHT,
-       };
-       struct commit_list *p;
-       char parents[1024];
-       unsigned long len;
-       int i;
-       enum { HEADER, SUBJECT, BODY } state;
-       const char *msg = commit->buffer;
+struct chunk {
+       size_t off;
+       size_t len;
+};
 
-       if (ILEFT_RIGHT + 1 != ARRAY_SIZE(table))
-               die("invalid interp table!");
+struct format_commit_context {
+       const struct commit *commit;
+
+       /* These offsets are relative to the start of the commit message. */
+       int commit_header_parsed;
+       struct chunk subject;
+       struct chunk author;
+       struct chunk committer;
+       struct chunk encoding;
+       size_t body_off;
+
+       /* The following ones are relative to the result struct strbuf. */
+       struct chunk abbrev_commit_hash;
+       struct chunk abbrev_tree_hash;
+       struct chunk abbrev_parent_hashes;
+};
 
-       /* these are independent of the commit */
-       interp_set_entry(table, IRED, "\033[31m");
-       interp_set_entry(table, IGREEN, "\033[32m");
-       interp_set_entry(table, IBLUE, "\033[34m");
-       interp_set_entry(table, IRESET_COLOR, "\033[m");
-       interp_set_entry(table, INEWLINE, "\n");
+static int add_again(struct strbuf *sb, struct chunk *chunk)
+{
+       if (chunk->len) {
+               strbuf_adddup(sb, chunk->off, chunk->len);
+               return 1;
+       }
 
-       /* these depend on the commit */
-       if (!commit->object.parsed)
-               parse_object(commit->object.sha1);
-       interp_set_entry(table, IHASH, sha1_to_hex(commit->object.sha1));
-       interp_set_entry(table, IHASH_ABBREV,
-                       find_unique_abbrev(commit->object.sha1,
-                               DEFAULT_ABBREV));
-       interp_set_entry(table, ITREE, sha1_to_hex(commit->tree->object.sha1));
-       interp_set_entry(table, ITREE_ABBREV,
-                       find_unique_abbrev(commit->tree->object.sha1,
-                               DEFAULT_ABBREV));
-       interp_set_entry(table, ILEFT_RIGHT,
-                        (commit->object.flags & BOUNDARY)
-                        ? "-"
-                        : (commit->object.flags & SYMMETRIC_LEFT)
-                        ? "<"
-                        : ">");
-
-       parents[1] = 0;
-       for (i = 0, p = commit->parents;
-                       p && i < sizeof(parents) - 1;
-                       p = p->next)
-               i += snprintf(parents + i, sizeof(parents) - i - 1, " %s",
-                       sha1_to_hex(p->item->object.sha1));
-       interp_set_entry(table, IPARENTS, parents + 1);
-
-       parents[1] = 0;
-       for (i = 0, p = commit->parents;
-                       p && i < sizeof(parents) - 1;
-                       p = p->next)
-               i += snprintf(parents + i, sizeof(parents) - i - 1, " %s",
-                       find_unique_abbrev(p->item->object.sha1,
-                               DEFAULT_ABBREV));
-       interp_set_entry(table, IPARENTS_ABBREV, parents + 1);
+       /*
+        * We haven't seen this chunk before.  Our caller is surely
+        * going to add it the hard way now.  Remember the most likely
+        * start of the to-be-added chunk: the current end of the
+        * struct strbuf.
+        */
+       chunk->off = sb->len;
+       return 0;
+}
+
+static void parse_commit_header(struct format_commit_context *context)
+{
+       const char *msg = context->commit->buffer;
+       int i;
+       enum { HEADER, SUBJECT, BODY } state;
 
        for (i = 0, state = HEADER; msg[i] && state < BODY; i++) {
                int eol;
@@ -445,37 +423,148 @@ void format_commit_message(const struct commit *commit,
                        ; /* do nothing */
 
                if (state == SUBJECT) {
-                       table[ISUBJECT].value = xmemdupz(msg + i, eol - i);
+                       context->subject.off = i;
+                       context->subject.len = eol - i;
                        i = eol;
                }
                if (i == eol) {
                        state++;
                        /* strip empty lines */
-                       while (msg[eol + 1] == '\n')
+                       while (msg[eol] == '\n' && msg[eol + 1] == '\n')
                                eol++;
-               } else if (!prefixcmp(msg + i, "author "))
-                       fill_person(table + IAUTHOR_NAME,
-                                       msg + i + 7, eol - i - 7);
-               else if (!prefixcmp(msg + i, "committer "))
-                       fill_person(table + ICOMMITTER_NAME,
-                                       msg + i + 10, eol - i - 10);
-               else if (!prefixcmp(msg + i, "encoding "))
-                       table[IENCODING].value =
-                               xmemdupz(msg + i + 9, eol - i - 9);
+               } else if (!prefixcmp(msg + i, "author ")) {
+                       context->author.off = i + 7;
+                       context->author.len = eol - i - 7;
+               } 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;
+               if (!msg[i])
+                       break;
        }
-       if (msg[i])
-               table[IBODY].value = xstrdup(msg + i);
+       context->body_off = i;
+       context->commit_header_parsed = 1;
+}
 
-       len = interpolate(sb->buf + sb->len, strbuf_avail(sb),
-                               format, table, ARRAY_SIZE(table));
-       if (len > strbuf_avail(sb)) {
-               strbuf_grow(sb, len);
-               interpolate(sb->buf + sb->len, strbuf_avail(sb) + 1,
-                                       format, table, ARRAY_SIZE(table));
+static size_t format_commit_item(struct strbuf *sb, const char *placeholder,
+                               void *context)
+{
+       struct format_commit_context *c = context;
+       const struct commit *commit = c->commit;
+       const char *msg = commit->buffer;
+       struct commit_list *p;
+
+       /* these are independent of the commit */
+       switch (placeholder[0]) {
+       case 'C':
+               if (!prefixcmp(placeholder + 1, "red")) {
+                       strbuf_addstr(sb, "\033[31m");
+                       return 4;
+               } else if (!prefixcmp(placeholder + 1, "green")) {
+                       strbuf_addstr(sb, "\033[32m");
+                       return 6;
+               } else if (!prefixcmp(placeholder + 1, "blue")) {
+                       strbuf_addstr(sb, "\033[34m");
+                       return 5;
+               } else if (!prefixcmp(placeholder + 1, "reset")) {
+                       strbuf_addstr(sb, "\033[m");
+                       return 6;
+               } else
+                       return 0;
+       case 'n':               /* newline */
+               strbuf_addch(sb, '\n');
+               return 1;
        }
-       strbuf_setlen(sb, sb->len + len);
-       interp_clear_table(table, ARRAY_SIZE(table));
+
+       /* these depend on the commit */
+       if (!commit->object.parsed)
+               parse_object(commit->object.sha1);
+
+       switch (placeholder[0]) {
+       case 'H':               /* commit hash */
+               strbuf_addstr(sb, sha1_to_hex(commit->object.sha1));
+               return 1;
+       case 'h':               /* abbreviated commit hash */
+               if (add_again(sb, &c->abbrev_commit_hash))
+                       return 1;
+               strbuf_addstr(sb, find_unique_abbrev(commit->object.sha1,
+                                                    DEFAULT_ABBREV));
+               c->abbrev_commit_hash.len = sb->len - c->abbrev_commit_hash.off;
+               return 1;
+       case 'T':               /* tree hash */
+               strbuf_addstr(sb, sha1_to_hex(commit->tree->object.sha1));
+               return 1;
+       case 't':               /* abbreviated tree hash */
+               if (add_again(sb, &c->abbrev_tree_hash))
+                       return 1;
+               strbuf_addstr(sb, find_unique_abbrev(commit->tree->object.sha1,
+                                                    DEFAULT_ABBREV));
+               c->abbrev_tree_hash.len = sb->len - c->abbrev_tree_hash.off;
+               return 1;
+       case 'P':               /* parent hashes */
+               for (p = commit->parents; p; p = p->next) {
+                       if (p != commit->parents)
+                               strbuf_addch(sb, ' ');
+                       strbuf_addstr(sb, sha1_to_hex(p->item->object.sha1));
+               }
+               return 1;
+       case 'p':               /* abbreviated parent hashes */
+               if (add_again(sb, &c->abbrev_parent_hashes))
+                       return 1;
+               for (p = commit->parents; p; p = p->next) {
+                       if (p != commit->parents)
+                               strbuf_addch(sb, ' ');
+                       strbuf_addstr(sb, find_unique_abbrev(
+                                       p->item->object.sha1, DEFAULT_ABBREV));
+               }
+               c->abbrev_parent_hashes.len = sb->len -
+                                             c->abbrev_parent_hashes.off;
+               return 1;
+       case 'm':               /* left/right/bottom */
+               strbuf_addch(sb, (commit->object.flags & BOUNDARY)
+                                ? '-'
+                                : (commit->object.flags & SYMMETRIC_LEFT)
+                                ? '<'
+                                : '>');
+               return 1;
+       }
+
+       /* For the rest we have to parse the commit header. */
+       if (!c->commit_header_parsed)
+               parse_commit_header(c);
+
+       switch (placeholder[0]) {
+       case 's':       /* subject */
+               strbuf_add(sb, msg + c->subject.off, c->subject.len);
+               return 1;
+       case 'a':       /* author ... */
+               return format_person_part(sb, placeholder[1],
+                                  msg + c->author.off, c->author.len);
+       case 'c':       /* committer ... */
+               return format_person_part(sb, placeholder[1],
+                                  msg + c->committer.off, c->committer.len);
+       case 'e':       /* encoding */
+               strbuf_add(sb, msg + c->encoding.off, c->encoding.len);
+               return 1;
+       case 'b':       /* body */
+               strbuf_addstr(sb, msg + c->body_off);
+               return 1;
+       }
+       return 0;       /* unknown placeholder */
+}
+
+void format_commit_message(const struct commit *commit,
+                           const void *format, struct strbuf *sb)
+{
+       struct format_commit_context context;
+
+       memset(&context, 0, sizeof(context));
+       context.commit = commit;
+       strbuf_expand(sb, format, format_commit_item, &context);
 }
 
 static void pp_header(enum cmit_fmt fmt,
@@ -531,23 +620,23 @@ static void pp_header(enum cmit_fmt fmt,
                 */
                if (!memcmp(line, "author ", 7)) {
                        strbuf_grow(sb, linelen + 80);
-                       add_user_info("Author", fmt, sb, line + 7, dmode, encoding);
+                       pp_user_info("Author", fmt, sb, line + 7, dmode, encoding);
                }
                if (!memcmp(line, "committer ", 10) &&
                    (fmt == CMIT_FMT_FULL || fmt == CMIT_FMT_FULLER)) {
                        strbuf_grow(sb, linelen + 80);
-                       add_user_info("Commit", fmt, sb, line + 10, dmode, encoding);
+                       pp_user_info("Commit", fmt, sb, line + 10, dmode, encoding);
                }
        }
 }
 
-static void pp_title_line(enum cmit_fmt fmt,
-                         const char **msg_p,
-                         struct strbuf *sb,
-                         const char *subject,
-                         const char *after_subject,
-                         const char *encoding,
-                         int plain_non_ascii)
+void pp_title_line(enum cmit_fmt fmt,
+                  const char **msg_p,
+                  struct strbuf *sb,
+                  const char *subject,
+                  const char *after_subject,
+                  const char *encoding,
+                  int plain_non_ascii)
 {
        struct strbuf title;
 
@@ -596,10 +685,10 @@ static void pp_title_line(enum cmit_fmt fmt,
        strbuf_release(&title);
 }
 
-static void pp_remainder(enum cmit_fmt fmt,
-                        const char **msg_p,
-                        struct strbuf *sb,
-                        int indent)
+void pp_remainder(enum cmit_fmt fmt,
+                 const char **msg_p,
+                 struct strbuf *sb,
+                 int indent)
 {
        int first = 1;
        for (;;) {