{"thread":{"id":"10650","subject":"[PATCH 0/3] Make user formatted commit listing less expensive","startedAt":"2007-11-04T19:14:02Z","lastAt":"2007-11-09T00:49:19Z","messageCount":24,"participants":["Johannes Schindelin","Junio C Hamano","René Scharfe","Jon Loeliger","Pierre Habouzit"],"isPatch":true,"patchVersion":1,"patchTotal":3},"messages":[{"id":"58295","messageId":"Pine.LNX.4.64.0711041912190.4362@racer.site","threadId":"10650","inReplyTo":null,"subject":"[PATCH 0/3] Make user formatted commit listing less expensive","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2007-11-04T19:14:02Z","receivedAt":"2007-11-04T19:14:02Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi,\n\nthis series of three splits off the formatting code from commit.c, adds \nthe function interp_find_active() to interpolate.[ch], and then uses it in \nthe obvious way.\n\nCiao,\nDscho\n"},{"id":"58296","messageId":"Pine.LNX.4.64.0711041914310.4362@racer.site","threadId":"10650","inReplyTo":"Pine.LNX.4.64.0711041912190.4362@racer.site","subject":"[PATCH 1/3] Split off the pretty print stuff into its own file","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2007-11-04T19:15:06Z","receivedAt":"2007-11-04T19:15:06Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"\nThe file commit.c got quite large, but it does not have to be: the\ncode concerning pretty printing is pretty well contained.  In fact,\nthis commit just splits it off into pretty.c, leaving commit.c with\njust 672 lines.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n\n\tI know, I suggested using \"format-patch -C -C\" for this case, but\n\tthe response was correct in that it seems funny.\n\n\tAFAICT this is a verbatim move of two hunks from commit.c to\n\tpretty.c, and the usual #include mantra in front of the latter to \n\tmake it\tcompile.\n\n Makefile |    2 +-\n commit.c |  718 -------------------------------------------------------------\n pretty.c |  723 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++\n 3 files changed, 724 insertions(+), 719 deletions(-)\n create mode 100644 pretty.c\n\ndiff --git a/Makefile b/Makefile\nindex 19a48f5..4c5f864 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -299,7 +299,7 @@ DIFF_OBJS = \\\n LIB_OBJS = \\\n \tblob.o commit.o connect.o csum-file.o cache-tree.o base85.o \\\n \tdate.o diff-delta.o entry.o exec_cmd.o ident.o \\\n-\tinterpolate.o hash.o \\\n+\tpretty.o interpolate.o hash.o \\\n \tlockfile.o \\\n \tpatch-ids.o \\\n \tobject.o pack-check.o pack-write.o patch-delta.o path.o pkt-line.o \\\ndiff --git a/commit.c b/commit.c\nindex ab4eb8b..f074811 100644\n--- a/commit.c\n+++ b/commit.c\n@@ -3,7 +3,6 @@\n #include \"commit.h\"\n #include \"pkt-line.h\"\n #include \"utf8.h\"\n-#include \"interpolate.h\"\n #include \"diff.h\"\n #include \"revision.h\"\n \n@@ -11,46 +10,6 @@ int save_commit_buffer = 1;\n \n const char *commit_type = \"commit\";\n \n-static struct cmt_fmt_map {\n-\tconst char *n;\n-\tsize_t cmp_len;\n-\tenum cmit_fmt v;\n-} cmt_fmts[] = {\n-\t{ \"raw\",\t1,\tCMIT_FMT_RAW },\n-\t{ \"medium\",\t1,\tCMIT_FMT_MEDIUM },\n-\t{ \"short\",\t1,\tCMIT_FMT_SHORT },\n-\t{ \"email\",\t1,\tCMIT_FMT_EMAIL },\n-\t{ \"full\",\t5,\tCMIT_FMT_FULL },\n-\t{ \"fuller\",\t5,\tCMIT_FMT_FULLER },\n-\t{ \"oneline\",\t1,\tCMIT_FMT_ONELINE },\n-\t{ \"format:\",\t7,\tCMIT_FMT_USERFORMAT},\n-};\n-\n-static char *user_format;\n-\n-enum cmit_fmt get_commit_format(const char *arg)\n-{\n-\tint i;\n-\n-\tif (!arg || !*arg)\n-\t\treturn CMIT_FMT_DEFAULT;\n-\tif (*arg == '=')\n-\t\targ++;\n-\tif (!prefixcmp(arg, \"format:\")) {\n-\t\tif (user_format)\n-\t\t\tfree(user_format);\n-\t\tuser_format = xstrdup(arg + 7);\n-\t\treturn CMIT_FMT_USERFORMAT;\n-\t}\n-\tfor (i = 0; i < ARRAY_SIZE(cmt_fmts); i++) {\n-\t\tif (!strncmp(arg, cmt_fmts[i].n, cmt_fmts[i].cmp_len) &&\n-\t\t    !strncmp(arg, cmt_fmts[i].n, strlen(arg)))\n-\t\t\treturn cmt_fmts[i].v;\n-\t}\n-\n-\tdie(\"invalid --pretty format: %s\", arg);\n-}\n-\n static struct commit *check_commit(struct object *obj,\n \t\t\t\t   const unsigned char *sha1,\n \t\t\t\t   int quiet)\n@@ -444,683 +403,6 @@ void clear_commit_marks(struct commit *commit, unsigned int mark)\n \t}\n }\n \n-/*\n- * Generic support for pretty-printing the header\n- */\n-static int get_one_line(const char *msg)\n-{\n-\tint ret = 0;\n-\n-\tfor (;;) {\n-\t\tchar c = *msg++;\n-\t\tif (!c)\n-\t\t\tbreak;\n-\t\tret++;\n-\t\tif (c == '\\n')\n-\t\t\tbreak;\n-\t}\n-\treturn ret;\n-}\n-\n-/* High bit set, or ISO-2022-INT */\n-int non_ascii(int ch)\n-{\n-\tch = (ch & 0xff);\n-\treturn ((ch & 0x80) || (ch == 0x1b));\n-}\n-\n-static int is_rfc2047_special(char ch)\n-{\n-\treturn (non_ascii(ch) || (ch == '=') || (ch == '?') || (ch == '_'));\n-}\n-\n-static void add_rfc2047(struct strbuf *sb, const char *line, int len,\n-\t\t       const char *encoding)\n-{\n-\tint i, last;\n-\n-\tfor (i = 0; i < len; i++) {\n-\t\tint ch = line[i];\n-\t\tif (non_ascii(ch))\n-\t\t\tgoto needquote;\n-\t\tif ((i + 1 < len) && (ch == '=' && line[i+1] == '?'))\n-\t\t\tgoto needquote;\n-\t}\n-\tstrbuf_add(sb, line, len);\n-\treturn;\n-\n-needquote:\n-\tstrbuf_grow(sb, len * 3 + strlen(encoding) + 100);\n-\tstrbuf_addf(sb, \"=?%s?q?\", encoding);\n-\tfor (i = last = 0; i < len; i++) {\n-\t\tunsigned ch = line[i] & 0xFF;\n-\t\t/*\n-\t\t * We encode ' ' using '=20' even though rfc2047\n-\t\t * allows using '_' for readability.  Unfortunately,\n-\t\t * many programs do not understand this and just\n-\t\t * leave the underscore in place.\n-\t\t */\n-\t\tif (is_rfc2047_special(ch) || ch == ' ') {\n-\t\t\tstrbuf_add(sb, line + last, i - last);\n-\t\t\tstrbuf_addf(sb, \"=%02X\", ch);\n-\t\t\tlast = i + 1;\n-\t\t}\n-\t}\n-\tstrbuf_add(sb, line + last, len - last);\n-\tstrbuf_addstr(sb, \"?=\");\n-}\n-\n-static void add_user_info(const char *what, enum cmit_fmt fmt, struct strbuf *sb,\n-\t\t\t const char *line, enum date_mode dmode,\n-\t\t\t const char *encoding)\n-{\n-\tchar *date;\n-\tint namelen;\n-\tunsigned long time;\n-\tint tz;\n-\tconst char *filler = \"    \";\n-\n-\tif (fmt == CMIT_FMT_ONELINE)\n-\t\treturn;\n-\tdate = strchr(line, '>');\n-\tif (!date)\n-\t\treturn;\n-\tnamelen = ++date - line;\n-\ttime = strtoul(date, &date, 10);\n-\ttz = strtol(date, NULL, 10);\n-\n-\tif (fmt == CMIT_FMT_EMAIL) {\n-\t\tchar *name_tail = strchr(line, '<');\n-\t\tint display_name_length;\n-\t\tif (!name_tail)\n-\t\t\treturn;\n-\t\twhile (line < name_tail && isspace(name_tail[-1]))\n-\t\t\tname_tail--;\n-\t\tdisplay_name_length = name_tail - line;\n-\t\tfiller = \"\";\n-\t\tstrbuf_addstr(sb, \"From: \");\n-\t\tadd_rfc2047(sb, line, display_name_length, encoding);\n-\t\tstrbuf_add(sb, name_tail, namelen - display_name_length);\n-\t\tstrbuf_addch(sb, '\\n');\n-\t} else {\n-\t\tstrbuf_addf(sb, \"%s: %.*s%.*s\\n\", what,\n-\t\t\t      (fmt == CMIT_FMT_FULLER) ? 4 : 0,\n-\t\t\t      filler, namelen, line);\n-\t}\n-\tswitch (fmt) {\n-\tcase CMIT_FMT_MEDIUM:\n-\t\tstrbuf_addf(sb, \"Date:   %s\\n\", show_date(time, tz, dmode));\n-\t\tbreak;\n-\tcase CMIT_FMT_EMAIL:\n-\t\tstrbuf_addf(sb, \"Date: %s\\n\", show_date(time, tz, DATE_RFC2822));\n-\t\tbreak;\n-\tcase CMIT_FMT_FULLER:\n-\t\tstrbuf_addf(sb, \"%sDate: %s\\n\", what, show_date(time, tz, dmode));\n-\t\tbreak;\n-\tdefault:\n-\t\t/* notin' */\n-\t\tbreak;\n-\t}\n-}\n-\n-static int is_empty_line(const char *line, int *len_p)\n-{\n-\tint len = *len_p;\n-\twhile (len && isspace(line[len-1]))\n-\t\tlen--;\n-\t*len_p = len;\n-\treturn !len;\n-}\n-\n-static void add_merge_info(enum cmit_fmt fmt, struct strbuf *sb,\n-\t\t\tconst struct commit *commit, int abbrev)\n-{\n-\tstruct commit_list *parent = commit->parents;\n-\n-\tif ((fmt == CMIT_FMT_ONELINE) || (fmt == CMIT_FMT_EMAIL) ||\n-\t    !parent || !parent->next)\n-\t\treturn;\n-\n-\tstrbuf_addstr(sb, \"Merge:\");\n-\n-\twhile (parent) {\n-\t\tstruct commit *p = parent->item;\n-\t\tconst char *hex = NULL;\n-\t\tconst char *dots;\n-\t\tif (abbrev)\n-\t\t\thex = find_unique_abbrev(p->object.sha1, abbrev);\n-\t\tif (!hex)\n-\t\t\thex = sha1_to_hex(p->object.sha1);\n-\t\tdots = (abbrev && strlen(hex) != 40) ?  \"...\" : \"\";\n-\t\tparent = parent->next;\n-\n-\t\tstrbuf_addf(sb, \" %s%s\", hex, dots);\n-\t}\n-\tstrbuf_addch(sb, '\\n');\n-}\n-\n-static char *get_header(const struct commit *commit, const char *key)\n-{\n-\tint key_len = strlen(key);\n-\tconst char *line = commit->buffer;\n-\n-\tfor (;;) {\n-\t\tconst char *eol = strchr(line, '\\n'), *next;\n-\n-\t\tif (line == eol)\n-\t\t\treturn NULL;\n-\t\tif (!eol) {\n-\t\t\teol = line + strlen(line);\n-\t\t\tnext = NULL;\n-\t\t} else\n-\t\t\tnext = eol + 1;\n-\t\tif (eol - line > key_len &&\n-\t\t    !strncmp(line, key, key_len) &&\n-\t\t    line[key_len] == ' ') {\n-\t\t\treturn xmemdupz(line + key_len + 1, eol - line - key_len - 1);\n-\t\t}\n-\t\tline = next;\n-\t}\n-}\n-\n-static char *replace_encoding_header(char *buf, const char *encoding)\n-{\n-\tstruct strbuf tmp;\n-\tsize_t start, len;\n-\tchar *cp = buf;\n-\n-\t/* guess if there is an encoding header before a \\n\\n */\n-\twhile (strncmp(cp, \"encoding \", strlen(\"encoding \"))) {\n-\t\tcp = strchr(cp, '\\n');\n-\t\tif (!cp || *++cp == '\\n')\n-\t\t\treturn buf;\n-\t}\n-\tstart = cp - buf;\n-\tcp = strchr(cp, '\\n');\n-\tif (!cp)\n-\t\treturn buf; /* should not happen but be defensive */\n-\tlen = cp + 1 - (buf + start);\n-\n-\tstrbuf_init(&tmp, 0);\n-\tstrbuf_attach(&tmp, buf, strlen(buf), strlen(buf) + 1);\n-\tif (is_encoding_utf8(encoding)) {\n-\t\t/* we have re-coded to UTF-8; drop the header */\n-\t\tstrbuf_remove(&tmp, start, len);\n-\t} else {\n-\t\t/* just replaces XXXX in 'encoding XXXX\\n' */\n-\t\tstrbuf_splice(&tmp, start + strlen(\"encoding \"),\n-\t\t\t\t\t  len - strlen(\"encoding \\n\"),\n-\t\t\t\t\t  encoding, strlen(encoding));\n-\t}\n-\treturn strbuf_detach(&tmp, NULL);\n-}\n-\n-static char *logmsg_reencode(const struct commit *commit,\n-\t\t\t     const char *output_encoding)\n-{\n-\tstatic const char *utf8 = \"utf-8\";\n-\tconst char *use_encoding;\n-\tchar *encoding;\n-\tchar *out;\n-\n-\tif (!*output_encoding)\n-\t\treturn NULL;\n-\tencoding = get_header(commit, \"encoding\");\n-\tuse_encoding = encoding ? encoding : utf8;\n-\tif (!strcmp(use_encoding, output_encoding))\n-\t\tif (encoding) /* we'll strip encoding header later */\n-\t\t\tout = xstrdup(commit->buffer);\n-\t\telse\n-\t\t\treturn NULL; /* nothing to do */\n-\telse\n-\t\tout = reencode_string(commit->buffer,\n-\t\t\t\t      output_encoding, use_encoding);\n-\tif (out)\n-\t\tout = replace_encoding_header(out, output_encoding);\n-\n-\tfree(encoding);\n-\treturn out;\n-}\n-\n-static void fill_person(struct interp *table, const char *msg, int len)\n-{\n-\tint start, end, tz = 0;\n-\tunsigned long date;\n-\tchar *ep;\n-\n-\t/* parse name */\n-\tfor (end = 0; end < len && msg[end] != '<'; end++)\n-\t\t; /* do nothing */\n-\tstart = end + 1;\n-\twhile (end > 0 && isspace(msg[end - 1]))\n-\t\tend--;\n-\ttable[0].value = xmemdupz(msg, end);\n-\n-\tif (start >= len)\n-\t\treturn;\n-\n-\t/* parse email */\n-\tfor (end = start + 1; end < len && msg[end] != '>'; end++)\n-\t\t; /* do nothing */\n-\n-\tif (end >= len)\n-\t\treturn;\n-\n-\ttable[1].value = xmemdupz(msg + start, end - start);\n-\n-\t/* parse date */\n-\tfor (start = end + 1; start < len && isspace(msg[start]); start++)\n-\t\t; /* do nothing */\n-\tif (start >= len)\n-\t\treturn;\n-\tdate = strtoul(msg + start, &ep, 10);\n-\tif (msg + start == ep)\n-\t\treturn;\n-\n-\ttable[5].value = xmemdupz(msg + start, ep - (msg + start));\n-\n-\t/* parse tz */\n-\tfor (start = ep - msg + 1; start < len && isspace(msg[start]); start++)\n-\t\t; /* do nothing */\n-\tif (start + 1 < len) {\n-\t\ttz = strtoul(msg + start + 1, NULL, 10);\n-\t\tif (msg[start] == '-')\n-\t\t\ttz = -tz;\n-\t}\n-\n-\tinterp_set_entry(table, 2, show_date(date, tz, DATE_NORMAL));\n-\tinterp_set_entry(table, 3, show_date(date, tz, DATE_RFC2822));\n-\tinterp_set_entry(table, 4, show_date(date, tz, DATE_RELATIVE));\n-\tinterp_set_entry(table, 6, show_date(date, tz, DATE_ISO8601));\n-}\n-\n-void format_commit_message(const struct commit *commit,\n-                           const void *format, struct strbuf *sb)\n-{\n-\tstruct interp table[] = {\n-\t\t{ \"%H\" },\t/* commit hash */\n-\t\t{ \"%h\" },\t/* abbreviated commit hash */\n-\t\t{ \"%T\" },\t/* tree hash */\n-\t\t{ \"%t\" },\t/* abbreviated tree hash */\n-\t\t{ \"%P\" },\t/* parent hashes */\n-\t\t{ \"%p\" },\t/* abbreviated parent hashes */\n-\t\t{ \"%an\" },\t/* author name */\n-\t\t{ \"%ae\" },\t/* author email */\n-\t\t{ \"%ad\" },\t/* author date */\n-\t\t{ \"%aD\" },\t/* author date, RFC2822 style */\n-\t\t{ \"%ar\" },\t/* author date, relative */\n-\t\t{ \"%at\" },\t/* author date, UNIX timestamp */\n-\t\t{ \"%ai\" },\t/* author date, ISO 8601 */\n-\t\t{ \"%cn\" },\t/* committer name */\n-\t\t{ \"%ce\" },\t/* committer email */\n-\t\t{ \"%cd\" },\t/* committer date */\n-\t\t{ \"%cD\" },\t/* committer date, RFC2822 style */\n-\t\t{ \"%cr\" },\t/* committer date, relative */\n-\t\t{ \"%ct\" },\t/* committer date, UNIX timestamp */\n-\t\t{ \"%ci\" },\t/* committer date, ISO 8601 */\n-\t\t{ \"%e\" },\t/* encoding */\n-\t\t{ \"%s\" },\t/* subject */\n-\t\t{ \"%b\" },\t/* body */\n-\t\t{ \"%Cred\" },\t/* red */\n-\t\t{ \"%Cgreen\" },\t/* green */\n-\t\t{ \"%Cblue\" },\t/* blue */\n-\t\t{ \"%Creset\" },\t/* reset color */\n-\t\t{ \"%n\" },\t/* newline */\n-\t\t{ \"%m\" },\t/* left/right/bottom */\n-\t};\n-\tenum interp_index {\n-\t\tIHASH = 0, IHASH_ABBREV,\n-\t\tITREE, ITREE_ABBREV,\n-\t\tIPARENTS, IPARENTS_ABBREV,\n-\t\tIAUTHOR_NAME, IAUTHOR_EMAIL,\n-\t\tIAUTHOR_DATE, IAUTHOR_DATE_RFC2822, IAUTHOR_DATE_RELATIVE,\n-\t\tIAUTHOR_TIMESTAMP, IAUTHOR_ISO8601,\n-\t\tICOMMITTER_NAME, ICOMMITTER_EMAIL,\n-\t\tICOMMITTER_DATE, ICOMMITTER_DATE_RFC2822,\n-\t\tICOMMITTER_DATE_RELATIVE, ICOMMITTER_TIMESTAMP,\n-\t\tICOMMITTER_ISO8601,\n-\t\tIENCODING,\n-\t\tISUBJECT,\n-\t\tIBODY,\n-\t\tIRED, IGREEN, IBLUE, IRESET_COLOR,\n-\t\tINEWLINE,\n-\t\tILEFT_RIGHT,\n-\t};\n-\tstruct commit_list *p;\n-\tchar parents[1024];\n-\tunsigned long len;\n-\tint i;\n-\tenum { HEADER, SUBJECT, BODY } state;\n-\tconst char *msg = commit->buffer;\n-\n-\tif (ILEFT_RIGHT + 1 != ARRAY_SIZE(table))\n-\t\tdie(\"invalid interp table!\");\n-\n-\t/* these are independent of the commit */\n-\tinterp_set_entry(table, IRED, \"\\033[31m\");\n-\tinterp_set_entry(table, IGREEN, \"\\033[32m\");\n-\tinterp_set_entry(table, IBLUE, \"\\033[34m\");\n-\tinterp_set_entry(table, IRESET_COLOR, \"\\033[m\");\n-\tinterp_set_entry(table, INEWLINE, \"\\n\");\n-\n-\t/* these depend on the commit */\n-\tif (!commit->object.parsed)\n-\t\tparse_object(commit->object.sha1);\n-\tinterp_set_entry(table, IHASH, sha1_to_hex(commit->object.sha1));\n-\tinterp_set_entry(table, IHASH_ABBREV,\n-\t\t\tfind_unique_abbrev(commit->object.sha1,\n-\t\t\t\tDEFAULT_ABBREV));\n-\tinterp_set_entry(table, ITREE, sha1_to_hex(commit->tree->object.sha1));\n-\tinterp_set_entry(table, ITREE_ABBREV,\n-\t\t\tfind_unique_abbrev(commit->tree->object.sha1,\n-\t\t\t\tDEFAULT_ABBREV));\n-\tinterp_set_entry(table, ILEFT_RIGHT,\n-\t\t\t (commit->object.flags & BOUNDARY)\n-\t\t\t ? \"-\"\n-\t\t\t : (commit->object.flags & SYMMETRIC_LEFT)\n-\t\t\t ? \"<\"\n-\t\t\t : \">\");\n-\n-\tparents[1] = 0;\n-\tfor (i = 0, p = commit->parents;\n-\t\t\tp && i < sizeof(parents) - 1;\n-\t\t\tp = p->next)\n-\t\ti += snprintf(parents + i, sizeof(parents) - i - 1, \" %s\",\n-\t\t\tsha1_to_hex(p->item->object.sha1));\n-\tinterp_set_entry(table, IPARENTS, parents + 1);\n-\n-\tparents[1] = 0;\n-\tfor (i = 0, p = commit->parents;\n-\t\t\tp && i < sizeof(parents) - 1;\n-\t\t\tp = p->next)\n-\t\ti += snprintf(parents + i, sizeof(parents) - i - 1, \" %s\",\n-\t\t\tfind_unique_abbrev(p->item->object.sha1,\n-\t\t\t\tDEFAULT_ABBREV));\n-\tinterp_set_entry(table, IPARENTS_ABBREV, parents + 1);\n-\n-\tfor (i = 0, state = HEADER; msg[i] && state < BODY; i++) {\n-\t\tint eol;\n-\t\tfor (eol = i; msg[eol] && msg[eol] != '\\n'; eol++)\n-\t\t\t; /* do nothing */\n-\n-\t\tif (state == SUBJECT) {\n-\t\t\ttable[ISUBJECT].value = xmemdupz(msg + i, eol - i);\n-\t\t\ti = eol;\n-\t\t}\n-\t\tif (i == eol) {\n-\t\t\tstate++;\n-\t\t\t/* strip empty lines */\n-\t\t\twhile (msg[eol + 1] == '\\n')\n-\t\t\t\teol++;\n-\t\t} else if (!prefixcmp(msg + i, \"author \"))\n-\t\t\tfill_person(table + IAUTHOR_NAME,\n-\t\t\t\t\tmsg + i + 7, eol - i - 7);\n-\t\telse if (!prefixcmp(msg + i, \"committer \"))\n-\t\t\tfill_person(table + ICOMMITTER_NAME,\n-\t\t\t\t\tmsg + i + 10, eol - i - 10);\n-\t\telse if (!prefixcmp(msg + i, \"encoding \"))\n-\t\t\ttable[IENCODING].value =\n-\t\t\t\txmemdupz(msg + i + 9, eol - i - 9);\n-\t\ti = eol;\n-\t}\n-\tif (msg[i])\n-\t\ttable[IBODY].value = xstrdup(msg + i);\n-\n-\tlen = interpolate(sb->buf + sb->len, strbuf_avail(sb),\n-\t\t\t\tformat, table, ARRAY_SIZE(table));\n-\tif (len > strbuf_avail(sb)) {\n-\t\tstrbuf_grow(sb, len);\n-\t\tinterpolate(sb->buf + sb->len, strbuf_avail(sb) + 1,\n-\t\t\t\t\tformat, table, ARRAY_SIZE(table));\n-\t}\n-\tstrbuf_setlen(sb, sb->len + len);\n-\tinterp_clear_table(table, ARRAY_SIZE(table));\n-}\n-\n-static void pp_header(enum cmit_fmt fmt,\n-\t\t      int abbrev,\n-\t\t      enum date_mode dmode,\n-\t\t      const char *encoding,\n-\t\t      const struct commit *commit,\n-\t\t      const char **msg_p,\n-\t\t      struct strbuf *sb)\n-{\n-\tint parents_shown = 0;\n-\n-\tfor (;;) {\n-\t\tconst char *line = *msg_p;\n-\t\tint linelen = get_one_line(*msg_p);\n-\n-\t\tif (!linelen)\n-\t\t\treturn;\n-\t\t*msg_p += linelen;\n-\n-\t\tif (linelen == 1)\n-\t\t\t/* End of header */\n-\t\t\treturn;\n-\n-\t\tif (fmt == CMIT_FMT_RAW) {\n-\t\t\tstrbuf_add(sb, line, linelen);\n-\t\t\tcontinue;\n-\t\t}\n-\n-\t\tif (!memcmp(line, \"parent \", 7)) {\n-\t\t\tif (linelen != 48)\n-\t\t\t\tdie(\"bad parent line in commit\");\n-\t\t\tcontinue;\n-\t\t}\n-\n-\t\tif (!parents_shown) {\n-\t\t\tstruct commit_list *parent;\n-\t\t\tint num;\n-\t\t\tfor (parent = commit->parents, num = 0;\n-\t\t\t     parent;\n-\t\t\t     parent = parent->next, num++)\n-\t\t\t\t;\n-\t\t\t/* with enough slop */\n-\t\t\tstrbuf_grow(sb, num * 50 + 20);\n-\t\t\tadd_merge_info(fmt, sb, commit, abbrev);\n-\t\t\tparents_shown = 1;\n-\t\t}\n-\n-\t\t/*\n-\t\t * MEDIUM == DEFAULT shows only author with dates.\n-\t\t * FULL shows both authors but not dates.\n-\t\t * FULLER shows both authors and dates.\n-\t\t */\n-\t\tif (!memcmp(line, \"author \", 7)) {\n-\t\t\tstrbuf_grow(sb, linelen + 80);\n-\t\t\tadd_user_info(\"Author\", fmt, sb, line + 7, dmode, encoding);\n-\t\t}\n-\t\tif (!memcmp(line, \"committer \", 10) &&\n-\t\t    (fmt == CMIT_FMT_FULL || fmt == CMIT_FMT_FULLER)) {\n-\t\t\tstrbuf_grow(sb, linelen + 80);\n-\t\t\tadd_user_info(\"Commit\", fmt, sb, line + 10, dmode, encoding);\n-\t\t}\n-\t}\n-}\n-\n-static void pp_title_line(enum cmit_fmt fmt,\n-\t\t\t  const char **msg_p,\n-\t\t\t  struct strbuf *sb,\n-\t\t\t  const char *subject,\n-\t\t\t  const char *after_subject,\n-\t\t\t  const char *encoding,\n-\t\t\t  int plain_non_ascii)\n-{\n-\tstruct strbuf title;\n-\n-\tstrbuf_init(&title, 80);\n-\n-\tfor (;;) {\n-\t\tconst char *line = *msg_p;\n-\t\tint linelen = get_one_line(line);\n-\n-\t\t*msg_p += linelen;\n-\t\tif (!linelen || is_empty_line(line, &linelen))\n-\t\t\tbreak;\n-\n-\t\tstrbuf_grow(&title, linelen + 2);\n-\t\tif (title.len) {\n-\t\t\tif (fmt == CMIT_FMT_EMAIL) {\n-\t\t\t\tstrbuf_addch(&title, '\\n');\n-\t\t\t}\n-\t\t\tstrbuf_addch(&title, ' ');\n-\t\t}\n-\t\tstrbuf_add(&title, line, linelen);\n-\t}\n-\n-\tstrbuf_grow(sb, title.len + 1024);\n-\tif (subject) {\n-\t\tstrbuf_addstr(sb, subject);\n-\t\tadd_rfc2047(sb, title.buf, title.len, encoding);\n-\t} else {\n-\t\tstrbuf_addbuf(sb, &title);\n-\t}\n-\tstrbuf_addch(sb, '\\n');\n-\n-\tif (plain_non_ascii) {\n-\t\tconst char *header_fmt =\n-\t\t\t\"MIME-Version: 1.0\\n\"\n-\t\t\t\"Content-Type: text/plain; charset=%s\\n\"\n-\t\t\t\"Content-Transfer-Encoding: 8bit\\n\";\n-\t\tstrbuf_addf(sb, header_fmt, encoding);\n-\t}\n-\tif (after_subject) {\n-\t\tstrbuf_addstr(sb, after_subject);\n-\t}\n-\tif (fmt == CMIT_FMT_EMAIL) {\n-\t\tstrbuf_addch(sb, '\\n');\n-\t}\n-\tstrbuf_release(&title);\n-}\n-\n-static void pp_remainder(enum cmit_fmt fmt,\n-\t\t\t const char **msg_p,\n-\t\t\t struct strbuf *sb,\n-\t\t\t int indent)\n-{\n-\tint first = 1;\n-\tfor (;;) {\n-\t\tconst char *line = *msg_p;\n-\t\tint linelen = get_one_line(line);\n-\t\t*msg_p += linelen;\n-\n-\t\tif (!linelen)\n-\t\t\tbreak;\n-\n-\t\tif (is_empty_line(line, &linelen)) {\n-\t\t\tif (first)\n-\t\t\t\tcontinue;\n-\t\t\tif (fmt == CMIT_FMT_SHORT)\n-\t\t\t\tbreak;\n-\t\t}\n-\t\tfirst = 0;\n-\n-\t\tstrbuf_grow(sb, linelen + indent + 20);\n-\t\tif (indent) {\n-\t\t\tmemset(sb->buf + sb->len, ' ', indent);\n-\t\t\tstrbuf_setlen(sb, sb->len + indent);\n-\t\t}\n-\t\tstrbuf_add(sb, line, linelen);\n-\t\tstrbuf_addch(sb, '\\n');\n-\t}\n-}\n-\n-void pretty_print_commit(enum cmit_fmt fmt, const struct commit *commit,\n-\t\t\t\t  struct strbuf *sb, int abbrev,\n-\t\t\t\t  const char *subject, const char *after_subject,\n-\t\t\t\t  enum date_mode dmode, int plain_non_ascii)\n-{\n-\tunsigned long beginning_of_body;\n-\tint indent = 4;\n-\tconst char *msg = commit->buffer;\n-\tchar *reencoded;\n-\tconst char *encoding;\n-\n-\tif (fmt == CMIT_FMT_USERFORMAT) {\n-\t\tformat_commit_message(commit, user_format, sb);\n-\t\treturn;\n-\t}\n-\n-\tencoding = (git_log_output_encoding\n-\t\t    ? git_log_output_encoding\n-\t\t    : git_commit_encoding);\n-\tif (!encoding)\n-\t\tencoding = \"utf-8\";\n-\treencoded = logmsg_reencode(commit, encoding);\n-\tif (reencoded) {\n-\t\tmsg = reencoded;\n-\t}\n-\n-\tif (fmt == CMIT_FMT_ONELINE || fmt == CMIT_FMT_EMAIL)\n-\t\tindent = 0;\n-\n-\t/* After-subject is used to pass in Content-Type: multipart\n-\t * MIME header; in that case we do not have to do the\n-\t * plaintext content type even if the commit message has\n-\t * non 7-bit ASCII character.  Otherwise, check if we need\n-\t * to say this is not a 7-bit ASCII.\n-\t */\n-\tif (fmt == CMIT_FMT_EMAIL && !after_subject) {\n-\t\tint i, ch, in_body;\n-\n-\t\tfor (in_body = i = 0; (ch = msg[i]); i++) {\n-\t\t\tif (!in_body) {\n-\t\t\t\t/* author could be non 7-bit ASCII but\n-\t\t\t\t * the log may be so; skip over the\n-\t\t\t\t * header part first.\n-\t\t\t\t */\n-\t\t\t\tif (ch == '\\n' && msg[i+1] == '\\n')\n-\t\t\t\t\tin_body = 1;\n-\t\t\t}\n-\t\t\telse if (non_ascii(ch)) {\n-\t\t\t\tplain_non_ascii = 1;\n-\t\t\t\tbreak;\n-\t\t\t}\n-\t\t}\n-\t}\n-\n-\tpp_header(fmt, abbrev, dmode, encoding, commit, &msg, sb);\n-\tif (fmt != CMIT_FMT_ONELINE && !subject) {\n-\t\tstrbuf_addch(sb, '\\n');\n-\t}\n-\n-\t/* Skip excess blank lines at the beginning of body, if any... */\n-\tfor (;;) {\n-\t\tint linelen = get_one_line(msg);\n-\t\tint ll = linelen;\n-\t\tif (!linelen)\n-\t\t\tbreak;\n-\t\tif (!is_empty_line(msg, &ll))\n-\t\t\tbreak;\n-\t\tmsg += linelen;\n-\t}\n-\n-\t/* These formats treat the title line specially. */\n-\tif (fmt == CMIT_FMT_ONELINE || fmt == CMIT_FMT_EMAIL)\n-\t\tpp_title_line(fmt, &msg, sb, subject,\n-\t\t\t      after_subject, encoding, plain_non_ascii);\n-\n-\tbeginning_of_body = sb->len;\n-\tif (fmt != CMIT_FMT_ONELINE)\n-\t\tpp_remainder(fmt, &msg, sb, indent);\n-\tstrbuf_rtrim(sb);\n-\n-\t/* Make sure there is an EOLN for the non-oneline case */\n-\tif (fmt != CMIT_FMT_ONELINE)\n-\t\tstrbuf_addch(sb, '\\n');\n-\n-\t/*\n-\t * The caller may append additional body text in e-mail\n-\t * format.  Make sure we did not strip the blank line\n-\t * between the header and the body.\n-\t */\n-\tif (fmt == CMIT_FMT_EMAIL && sb->len <= beginning_of_body)\n-\t\tstrbuf_addch(sb, '\\n');\n-\tfree(reencoded);\n-}\n-\n struct commit *pop_commit(struct commit_list **stack)\n {\n \tstruct commit_list *top = *stack;\ndiff --git a/pretty.c b/pretty.c\nnew file mode 100644\nindex 0000000..490cede\n--- /dev/null\n+++ b/pretty.c\n@@ -0,0 +1,723 @@\n+#include \"cache.h\"\n+#include \"commit.h\"\n+#include \"interpolate.h\"\n+#include \"utf8.h\"\n+#include \"diff.h\"\n+#include \"revision.h\"\n+\n+static struct cmt_fmt_map {\n+\tconst char *n;\n+\tsize_t cmp_len;\n+\tenum cmit_fmt v;\n+} cmt_fmts[] = {\n+\t{ \"raw\",\t1,\tCMIT_FMT_RAW },\n+\t{ \"medium\",\t1,\tCMIT_FMT_MEDIUM },\n+\t{ \"short\",\t1,\tCMIT_FMT_SHORT },\n+\t{ \"email\",\t1,\tCMIT_FMT_EMAIL },\n+\t{ \"full\",\t5,\tCMIT_FMT_FULL },\n+\t{ \"fuller\",\t5,\tCMIT_FMT_FULLER },\n+\t{ \"oneline\",\t1,\tCMIT_FMT_ONELINE },\n+\t{ \"format:\",\t7,\tCMIT_FMT_USERFORMAT},\n+};\n+\n+static char *user_format;\n+\n+enum cmit_fmt get_commit_format(const char *arg)\n+{\n+\tint i;\n+\n+\tif (!arg || !*arg)\n+\t\treturn CMIT_FMT_DEFAULT;\n+\tif (*arg == '=')\n+\t\targ++;\n+\tif (!prefixcmp(arg, \"format:\")) {\n+\t\tif (user_format)\n+\t\t\tfree(user_format);\n+\t\tuser_format = xstrdup(arg + 7);\n+\t\treturn CMIT_FMT_USERFORMAT;\n+\t}\n+\tfor (i = 0; i < ARRAY_SIZE(cmt_fmts); i++) {\n+\t\tif (!strncmp(arg, cmt_fmts[i].n, cmt_fmts[i].cmp_len) &&\n+\t\t    !strncmp(arg, cmt_fmts[i].n, strlen(arg)))\n+\t\t\treturn cmt_fmts[i].v;\n+\t}\n+\n+\tdie(\"invalid --pretty format: %s\", arg);\n+}\n+\n+/*\n+ * Generic support for pretty-printing the header\n+ */\n+static int get_one_line(const char *msg)\n+{\n+\tint ret = 0;\n+\n+\tfor (;;) {\n+\t\tchar c = *msg++;\n+\t\tif (!c)\n+\t\t\tbreak;\n+\t\tret++;\n+\t\tif (c == '\\n')\n+\t\t\tbreak;\n+\t}\n+\treturn ret;\n+}\n+\n+/* High bit set, or ISO-2022-INT */\n+int non_ascii(int ch)\n+{\n+\tch = (ch & 0xff);\n+\treturn ((ch & 0x80) || (ch == 0x1b));\n+}\n+\n+static int is_rfc2047_special(char ch)\n+{\n+\treturn (non_ascii(ch) || (ch == '=') || (ch == '?') || (ch == '_'));\n+}\n+\n+static void add_rfc2047(struct strbuf *sb, const char *line, int len,\n+\t\t       const char *encoding)\n+{\n+\tint i, last;\n+\n+\tfor (i = 0; i < len; i++) {\n+\t\tint ch = line[i];\n+\t\tif (non_ascii(ch))\n+\t\t\tgoto needquote;\n+\t\tif ((i + 1 < len) && (ch == '=' && line[i+1] == '?'))\n+\t\t\tgoto needquote;\n+\t}\n+\tstrbuf_add(sb, line, len);\n+\treturn;\n+\n+needquote:\n+\tstrbuf_grow(sb, len * 3 + strlen(encoding) + 100);\n+\tstrbuf_addf(sb, \"=?%s?q?\", encoding);\n+\tfor (i = last = 0; i < len; i++) {\n+\t\tunsigned ch = line[i] & 0xFF;\n+\t\t/*\n+\t\t * We encode ' ' using '=20' even though rfc2047\n+\t\t * allows using '_' for readability.  Unfortunately,\n+\t\t * many programs do not understand this and just\n+\t\t * leave the underscore in place.\n+\t\t */\n+\t\tif (is_rfc2047_special(ch) || ch == ' ') {\n+\t\t\tstrbuf_add(sb, line + last, i - last);\n+\t\t\tstrbuf_addf(sb, \"=%02X\", ch);\n+\t\t\tlast = i + 1;\n+\t\t}\n+\t}\n+\tstrbuf_add(sb, line + last, len - last);\n+\tstrbuf_addstr(sb, \"?=\");\n+}\n+\n+static void add_user_info(const char *what, enum cmit_fmt fmt, struct strbuf *sb,\n+\t\t\t const char *line, enum date_mode dmode,\n+\t\t\t const char *encoding)\n+{\n+\tchar *date;\n+\tint namelen;\n+\tunsigned long time;\n+\tint tz;\n+\tconst char *filler = \"    \";\n+\n+\tif (fmt == CMIT_FMT_ONELINE)\n+\t\treturn;\n+\tdate = strchr(line, '>');\n+\tif (!date)\n+\t\treturn;\n+\tnamelen = ++date - line;\n+\ttime = strtoul(date, &date, 10);\n+\ttz = strtol(date, NULL, 10);\n+\n+\tif (fmt == CMIT_FMT_EMAIL) {\n+\t\tchar *name_tail = strchr(line, '<');\n+\t\tint display_name_length;\n+\t\tif (!name_tail)\n+\t\t\treturn;\n+\t\twhile (line < name_tail && isspace(name_tail[-1]))\n+\t\t\tname_tail--;\n+\t\tdisplay_name_length = name_tail - line;\n+\t\tfiller = \"\";\n+\t\tstrbuf_addstr(sb, \"From: \");\n+\t\tadd_rfc2047(sb, line, display_name_length, encoding);\n+\t\tstrbuf_add(sb, name_tail, namelen - display_name_length);\n+\t\tstrbuf_addch(sb, '\\n');\n+\t} else {\n+\t\tstrbuf_addf(sb, \"%s: %.*s%.*s\\n\", what,\n+\t\t\t      (fmt == CMIT_FMT_FULLER) ? 4 : 0,\n+\t\t\t      filler, namelen, line);\n+\t}\n+\tswitch (fmt) {\n+\tcase CMIT_FMT_MEDIUM:\n+\t\tstrbuf_addf(sb, \"Date:   %s\\n\", show_date(time, tz, dmode));\n+\t\tbreak;\n+\tcase CMIT_FMT_EMAIL:\n+\t\tstrbuf_addf(sb, \"Date: %s\\n\", show_date(time, tz, DATE_RFC2822));\n+\t\tbreak;\n+\tcase CMIT_FMT_FULLER:\n+\t\tstrbuf_addf(sb, \"%sDate: %s\\n\", what, show_date(time, tz, dmode));\n+\t\tbreak;\n+\tdefault:\n+\t\t/* notin' */\n+\t\tbreak;\n+\t}\n+}\n+\n+static int is_empty_line(const char *line, int *len_p)\n+{\n+\tint len = *len_p;\n+\twhile (len && isspace(line[len-1]))\n+\t\tlen--;\n+\t*len_p = len;\n+\treturn !len;\n+}\n+\n+static void add_merge_info(enum cmit_fmt fmt, struct strbuf *sb,\n+\t\t\tconst struct commit *commit, int abbrev)\n+{\n+\tstruct commit_list *parent = commit->parents;\n+\n+\tif ((fmt == CMIT_FMT_ONELINE) || (fmt == CMIT_FMT_EMAIL) ||\n+\t    !parent || !parent->next)\n+\t\treturn;\n+\n+\tstrbuf_addstr(sb, \"Merge:\");\n+\n+\twhile (parent) {\n+\t\tstruct commit *p = parent->item;\n+\t\tconst char *hex = NULL;\n+\t\tconst char *dots;\n+\t\tif (abbrev)\n+\t\t\thex = find_unique_abbrev(p->object.sha1, abbrev);\n+\t\tif (!hex)\n+\t\t\thex = sha1_to_hex(p->object.sha1);\n+\t\tdots = (abbrev && strlen(hex) != 40) ?  \"...\" : \"\";\n+\t\tparent = parent->next;\n+\n+\t\tstrbuf_addf(sb, \" %s%s\", hex, dots);\n+\t}\n+\tstrbuf_addch(sb, '\\n');\n+}\n+\n+static char *get_header(const struct commit *commit, const char *key)\n+{\n+\tint key_len = strlen(key);\n+\tconst char *line = commit->buffer;\n+\n+\tfor (;;) {\n+\t\tconst char *eol = strchr(line, '\\n'), *next;\n+\n+\t\tif (line == eol)\n+\t\t\treturn NULL;\n+\t\tif (!eol) {\n+\t\t\teol = line + strlen(line);\n+\t\t\tnext = NULL;\n+\t\t} else\n+\t\t\tnext = eol + 1;\n+\t\tif (eol - line > key_len &&\n+\t\t    !strncmp(line, key, key_len) &&\n+\t\t    line[key_len] == ' ') {\n+\t\t\treturn xmemdupz(line + key_len + 1, eol - line - key_len - 1);\n+\t\t}\n+\t\tline = next;\n+\t}\n+}\n+\n+static char *replace_encoding_header(char *buf, const char *encoding)\n+{\n+\tstruct strbuf tmp;\n+\tsize_t start, len;\n+\tchar *cp = buf;\n+\n+\t/* guess if there is an encoding header before a \\n\\n */\n+\twhile (strncmp(cp, \"encoding \", strlen(\"encoding \"))) {\n+\t\tcp = strchr(cp, '\\n');\n+\t\tif (!cp || *++cp == '\\n')\n+\t\t\treturn buf;\n+\t}\n+\tstart = cp - buf;\n+\tcp = strchr(cp, '\\n');\n+\tif (!cp)\n+\t\treturn buf; /* should not happen but be defensive */\n+\tlen = cp + 1 - (buf + start);\n+\n+\tstrbuf_init(&tmp, 0);\n+\tstrbuf_attach(&tmp, buf, strlen(buf), strlen(buf) + 1);\n+\tif (is_encoding_utf8(encoding)) {\n+\t\t/* we have re-coded to UTF-8; drop the header */\n+\t\tstrbuf_remove(&tmp, start, len);\n+\t} else {\n+\t\t/* just replaces XXXX in 'encoding XXXX\\n' */\n+\t\tstrbuf_splice(&tmp, start + strlen(\"encoding \"),\n+\t\t\t\t\t  len - strlen(\"encoding \\n\"),\n+\t\t\t\t\t  encoding, strlen(encoding));\n+\t}\n+\treturn strbuf_detach(&tmp, NULL);\n+}\n+\n+static char *logmsg_reencode(const struct commit *commit,\n+\t\t\t     const char *output_encoding)\n+{\n+\tstatic const char *utf8 = \"utf-8\";\n+\tconst char *use_encoding;\n+\tchar *encoding;\n+\tchar *out;\n+\n+\tif (!*output_encoding)\n+\t\treturn NULL;\n+\tencoding = get_header(commit, \"encoding\");\n+\tuse_encoding = encoding ? encoding : utf8;\n+\tif (!strcmp(use_encoding, output_encoding))\n+\t\tif (encoding) /* we'll strip encoding header later */\n+\t\t\tout = xstrdup(commit->buffer);\n+\t\telse\n+\t\t\treturn NULL; /* nothing to do */\n+\telse\n+\t\tout = reencode_string(commit->buffer,\n+\t\t\t\t      output_encoding, use_encoding);\n+\tif (out)\n+\t\tout = replace_encoding_header(out, output_encoding);\n+\n+\tfree(encoding);\n+\treturn out;\n+}\n+\n+static void fill_person(struct interp *table, const char *msg, int len)\n+{\n+\tint start, end, tz = 0;\n+\tunsigned long date;\n+\tchar *ep;\n+\n+\t/* parse name */\n+\tfor (end = 0; end < len && msg[end] != '<'; end++)\n+\t\t; /* do nothing */\n+\tstart = end + 1;\n+\twhile (end > 0 && isspace(msg[end - 1]))\n+\t\tend--;\n+\ttable[0].value = xmemdupz(msg, end);\n+\n+\tif (start >= len)\n+\t\treturn;\n+\n+\t/* parse email */\n+\tfor (end = start + 1; end < len && msg[end] != '>'; end++)\n+\t\t; /* do nothing */\n+\n+\tif (end >= len)\n+\t\treturn;\n+\n+\ttable[1].value = xmemdupz(msg + start, end - start);\n+\n+\t/* parse date */\n+\tfor (start = end + 1; start < len && isspace(msg[start]); start++)\n+\t\t; /* do nothing */\n+\tif (start >= len)\n+\t\treturn;\n+\tdate = strtoul(msg + start, &ep, 10);\n+\tif (msg + start == ep)\n+\t\treturn;\n+\n+\ttable[5].value = xmemdupz(msg + start, ep - (msg + start));\n+\n+\t/* parse tz */\n+\tfor (start = ep - msg + 1; start < len && isspace(msg[start]); start++)\n+\t\t; /* do nothing */\n+\tif (start + 1 < len) {\n+\t\ttz = strtoul(msg + start + 1, NULL, 10);\n+\t\tif (msg[start] == '-')\n+\t\t\ttz = -tz;\n+\t}\n+\n+\tinterp_set_entry(table, 2, show_date(date, tz, DATE_NORMAL));\n+\tinterp_set_entry(table, 3, show_date(date, tz, DATE_RFC2822));\n+\tinterp_set_entry(table, 4, show_date(date, tz, DATE_RELATIVE));\n+\tinterp_set_entry(table, 6, show_date(date, tz, DATE_ISO8601));\n+}\n+\n+void format_commit_message(const struct commit *commit,\n+                           const void *format, struct strbuf *sb)\n+{\n+\tstruct interp table[] = {\n+\t\t{ \"%H\" },\t/* commit hash */\n+\t\t{ \"%h\" },\t/* abbreviated commit hash */\n+\t\t{ \"%T\" },\t/* tree hash */\n+\t\t{ \"%t\" },\t/* abbreviated tree hash */\n+\t\t{ \"%P\" },\t/* parent hashes */\n+\t\t{ \"%p\" },\t/* abbreviated parent hashes */\n+\t\t{ \"%an\" },\t/* author name */\n+\t\t{ \"%ae\" },\t/* author email */\n+\t\t{ \"%ad\" },\t/* author date */\n+\t\t{ \"%aD\" },\t/* author date, RFC2822 style */\n+\t\t{ \"%ar\" },\t/* author date, relative */\n+\t\t{ \"%at\" },\t/* author date, UNIX timestamp */\n+\t\t{ \"%ai\" },\t/* author date, ISO 8601 */\n+\t\t{ \"%cn\" },\t/* committer name */\n+\t\t{ \"%ce\" },\t/* committer email */\n+\t\t{ \"%cd\" },\t/* committer date */\n+\t\t{ \"%cD\" },\t/* committer date, RFC2822 style */\n+\t\t{ \"%cr\" },\t/* committer date, relative */\n+\t\t{ \"%ct\" },\t/* committer date, UNIX timestamp */\n+\t\t{ \"%ci\" },\t/* committer date, ISO 8601 */\n+\t\t{ \"%e\" },\t/* encoding */\n+\t\t{ \"%s\" },\t/* subject */\n+\t\t{ \"%b\" },\t/* body */\n+\t\t{ \"%Cred\" },\t/* red */\n+\t\t{ \"%Cgreen\" },\t/* green */\n+\t\t{ \"%Cblue\" },\t/* blue */\n+\t\t{ \"%Creset\" },\t/* reset color */\n+\t\t{ \"%n\" },\t/* newline */\n+\t\t{ \"%m\" },\t/* left/right/bottom */\n+\t};\n+\tenum interp_index {\n+\t\tIHASH = 0, IHASH_ABBREV,\n+\t\tITREE, ITREE_ABBREV,\n+\t\tIPARENTS, IPARENTS_ABBREV,\n+\t\tIAUTHOR_NAME, IAUTHOR_EMAIL,\n+\t\tIAUTHOR_DATE, IAUTHOR_DATE_RFC2822, IAUTHOR_DATE_RELATIVE,\n+\t\tIAUTHOR_TIMESTAMP, IAUTHOR_ISO8601,\n+\t\tICOMMITTER_NAME, ICOMMITTER_EMAIL,\n+\t\tICOMMITTER_DATE, ICOMMITTER_DATE_RFC2822,\n+\t\tICOMMITTER_DATE_RELATIVE, ICOMMITTER_TIMESTAMP,\n+\t\tICOMMITTER_ISO8601,\n+\t\tIENCODING,\n+\t\tISUBJECT,\n+\t\tIBODY,\n+\t\tIRED, IGREEN, IBLUE, IRESET_COLOR,\n+\t\tINEWLINE,\n+\t\tILEFT_RIGHT,\n+\t};\n+\tstruct commit_list *p;\n+\tchar parents[1024];\n+\tunsigned long len;\n+\tint i;\n+\tenum { HEADER, SUBJECT, BODY } state;\n+\tconst char *msg = commit->buffer;\n+\n+\tif (ILEFT_RIGHT + 1 != ARRAY_SIZE(table))\n+\t\tdie(\"invalid interp table!\");\n+\n+\t/* these are independent of the commit */\n+\tinterp_set_entry(table, IRED, \"\\033[31m\");\n+\tinterp_set_entry(table, IGREEN, \"\\033[32m\");\n+\tinterp_set_entry(table, IBLUE, \"\\033[34m\");\n+\tinterp_set_entry(table, IRESET_COLOR, \"\\033[m\");\n+\tinterp_set_entry(table, INEWLINE, \"\\n\");\n+\n+\t/* these depend on the commit */\n+\tif (!commit->object.parsed)\n+\t\tparse_object(commit->object.sha1);\n+\tinterp_set_entry(table, IHASH, sha1_to_hex(commit->object.sha1));\n+\tinterp_set_entry(table, IHASH_ABBREV,\n+\t\t\tfind_unique_abbrev(commit->object.sha1,\n+\t\t\t\tDEFAULT_ABBREV));\n+\tinterp_set_entry(table, ITREE, sha1_to_hex(commit->tree->object.sha1));\n+\tinterp_set_entry(table, ITREE_ABBREV,\n+\t\t\tfind_unique_abbrev(commit->tree->object.sha1,\n+\t\t\t\tDEFAULT_ABBREV));\n+\tinterp_set_entry(table, ILEFT_RIGHT,\n+\t\t\t (commit->object.flags & BOUNDARY)\n+\t\t\t ? \"-\"\n+\t\t\t : (commit->object.flags & SYMMETRIC_LEFT)\n+\t\t\t ? \"<\"\n+\t\t\t : \">\");\n+\n+\tparents[1] = 0;\n+\tfor (i = 0, p = commit->parents;\n+\t\t\tp && i < sizeof(parents) - 1;\n+\t\t\tp = p->next)\n+\t\ti += snprintf(parents + i, sizeof(parents) - i - 1, \" %s\",\n+\t\t\tsha1_to_hex(p->item->object.sha1));\n+\tinterp_set_entry(table, IPARENTS, parents + 1);\n+\n+\tparents[1] = 0;\n+\tfor (i = 0, p = commit->parents;\n+\t\t\tp && i < sizeof(parents) - 1;\n+\t\t\tp = p->next)\n+\t\ti += snprintf(parents + i, sizeof(parents) - i - 1, \" %s\",\n+\t\t\tfind_unique_abbrev(p->item->object.sha1,\n+\t\t\t\tDEFAULT_ABBREV));\n+\tinterp_set_entry(table, IPARENTS_ABBREV, parents + 1);\n+\n+\tfor (i = 0, state = HEADER; msg[i] && state < BODY; i++) {\n+\t\tint eol;\n+\t\tfor (eol = i; msg[eol] && msg[eol] != '\\n'; eol++)\n+\t\t\t; /* do nothing */\n+\n+\t\tif (state == SUBJECT) {\n+\t\t\ttable[ISUBJECT].value = xmemdupz(msg + i, eol - i);\n+\t\t\ti = eol;\n+\t\t}\n+\t\tif (i == eol) {\n+\t\t\tstate++;\n+\t\t\t/* strip empty lines */\n+\t\t\twhile (msg[eol + 1] == '\\n')\n+\t\t\t\teol++;\n+\t\t} else if (!prefixcmp(msg + i, \"author \"))\n+\t\t\tfill_person(table + IAUTHOR_NAME,\n+\t\t\t\t\tmsg + i + 7, eol - i - 7);\n+\t\telse if (!prefixcmp(msg + i, \"committer \"))\n+\t\t\tfill_person(table + ICOMMITTER_NAME,\n+\t\t\t\t\tmsg + i + 10, eol - i - 10);\n+\t\telse if (!prefixcmp(msg + i, \"encoding \"))\n+\t\t\ttable[IENCODING].value =\n+\t\t\t\txmemdupz(msg + i + 9, eol - i - 9);\n+\t\ti = eol;\n+\t}\n+\tif (msg[i])\n+\t\ttable[IBODY].value = xstrdup(msg + i);\n+\n+\tlen = interpolate(sb->buf + sb->len, strbuf_avail(sb),\n+\t\t\t\tformat, table, ARRAY_SIZE(table));\n+\tif (len > strbuf_avail(sb)) {\n+\t\tstrbuf_grow(sb, len);\n+\t\tinterpolate(sb->buf + sb->len, strbuf_avail(sb) + 1,\n+\t\t\t\t\tformat, table, ARRAY_SIZE(table));\n+\t}\n+\tstrbuf_setlen(sb, sb->len + len);\n+\tinterp_clear_table(table, ARRAY_SIZE(table));\n+}\n+\n+static void pp_header(enum cmit_fmt fmt,\n+\t\t      int abbrev,\n+\t\t      enum date_mode dmode,\n+\t\t      const char *encoding,\n+\t\t      const struct commit *commit,\n+\t\t      const char **msg_p,\n+\t\t      struct strbuf *sb)\n+{\n+\tint parents_shown = 0;\n+\n+\tfor (;;) {\n+\t\tconst char *line = *msg_p;\n+\t\tint linelen = get_one_line(*msg_p);\n+\n+\t\tif (!linelen)\n+\t\t\treturn;\n+\t\t*msg_p += linelen;\n+\n+\t\tif (linelen == 1)\n+\t\t\t/* End of header */\n+\t\t\treturn;\n+\n+\t\tif (fmt == CMIT_FMT_RAW) {\n+\t\t\tstrbuf_add(sb, line, linelen);\n+\t\t\tcontinue;\n+\t\t}\n+\n+\t\tif (!memcmp(line, \"parent \", 7)) {\n+\t\t\tif (linelen != 48)\n+\t\t\t\tdie(\"bad parent line in commit\");\n+\t\t\tcontinue;\n+\t\t}\n+\n+\t\tif (!parents_shown) {\n+\t\t\tstruct commit_list *parent;\n+\t\t\tint num;\n+\t\t\tfor (parent = commit->parents, num = 0;\n+\t\t\t     parent;\n+\t\t\t     parent = parent->next, num++)\n+\t\t\t\t;\n+\t\t\t/* with enough slop */\n+\t\t\tstrbuf_grow(sb, num * 50 + 20);\n+\t\t\tadd_merge_info(fmt, sb, commit, abbrev);\n+\t\t\tparents_shown = 1;\n+\t\t}\n+\n+\t\t/*\n+\t\t * MEDIUM == DEFAULT shows only author with dates.\n+\t\t * FULL shows both authors but not dates.\n+\t\t * FULLER shows both authors and dates.\n+\t\t */\n+\t\tif (!memcmp(line, \"author \", 7)) {\n+\t\t\tstrbuf_grow(sb, linelen + 80);\n+\t\t\tadd_user_info(\"Author\", fmt, sb, line + 7, dmode, encoding);\n+\t\t}\n+\t\tif (!memcmp(line, \"committer \", 10) &&\n+\t\t    (fmt == CMIT_FMT_FULL || fmt == CMIT_FMT_FULLER)) {\n+\t\t\tstrbuf_grow(sb, linelen + 80);\n+\t\t\tadd_user_info(\"Commit\", fmt, sb, line + 10, dmode, encoding);\n+\t\t}\n+\t}\n+}\n+\n+static void pp_title_line(enum cmit_fmt fmt,\n+\t\t\t  const char **msg_p,\n+\t\t\t  struct strbuf *sb,\n+\t\t\t  const char *subject,\n+\t\t\t  const char *after_subject,\n+\t\t\t  const char *encoding,\n+\t\t\t  int plain_non_ascii)\n+{\n+\tstruct strbuf title;\n+\n+\tstrbuf_init(&title, 80);\n+\n+\tfor (;;) {\n+\t\tconst char *line = *msg_p;\n+\t\tint linelen = get_one_line(line);\n+\n+\t\t*msg_p += linelen;\n+\t\tif (!linelen || is_empty_line(line, &linelen))\n+\t\t\tbreak;\n+\n+\t\tstrbuf_grow(&title, linelen + 2);\n+\t\tif (title.len) {\n+\t\t\tif (fmt == CMIT_FMT_EMAIL) {\n+\t\t\t\tstrbuf_addch(&title, '\\n');\n+\t\t\t}\n+\t\t\tstrbuf_addch(&title, ' ');\n+\t\t}\n+\t\tstrbuf_add(&title, line, linelen);\n+\t}\n+\n+\tstrbuf_grow(sb, title.len + 1024);\n+\tif (subject) {\n+\t\tstrbuf_addstr(sb, subject);\n+\t\tadd_rfc2047(sb, title.buf, title.len, encoding);\n+\t} else {\n+\t\tstrbuf_addbuf(sb, &title);\n+\t}\n+\tstrbuf_addch(sb, '\\n');\n+\n+\tif (plain_non_ascii) {\n+\t\tconst char *header_fmt =\n+\t\t\t\"MIME-Version: 1.0\\n\"\n+\t\t\t\"Content-Type: text/plain; charset=%s\\n\"\n+\t\t\t\"Content-Transfer-Encoding: 8bit\\n\";\n+\t\tstrbuf_addf(sb, header_fmt, encoding);\n+\t}\n+\tif (after_subject) {\n+\t\tstrbuf_addstr(sb, after_subject);\n+\t}\n+\tif (fmt == CMIT_FMT_EMAIL) {\n+\t\tstrbuf_addch(sb, '\\n');\n+\t}\n+\tstrbuf_release(&title);\n+}\n+\n+static void pp_remainder(enum cmit_fmt fmt,\n+\t\t\t const char **msg_p,\n+\t\t\t struct strbuf *sb,\n+\t\t\t int indent)\n+{\n+\tint first = 1;\n+\tfor (;;) {\n+\t\tconst char *line = *msg_p;\n+\t\tint linelen = get_one_line(line);\n+\t\t*msg_p += linelen;\n+\n+\t\tif (!linelen)\n+\t\t\tbreak;\n+\n+\t\tif (is_empty_line(line, &linelen)) {\n+\t\t\tif (first)\n+\t\t\t\tcontinue;\n+\t\t\tif (fmt == CMIT_FMT_SHORT)\n+\t\t\t\tbreak;\n+\t\t}\n+\t\tfirst = 0;\n+\n+\t\tstrbuf_grow(sb, linelen + indent + 20);\n+\t\tif (indent) {\n+\t\t\tmemset(sb->buf + sb->len, ' ', indent);\n+\t\t\tstrbuf_setlen(sb, sb->len + indent);\n+\t\t}\n+\t\tstrbuf_add(sb, line, linelen);\n+\t\tstrbuf_addch(sb, '\\n');\n+\t}\n+}\n+\n+void pretty_print_commit(enum cmit_fmt fmt, const struct commit *commit,\n+\t\t\t\t  struct strbuf *sb, int abbrev,\n+\t\t\t\t  const char *subject, const char *after_subject,\n+\t\t\t\t  enum date_mode dmode, int plain_non_ascii)\n+{\n+\tunsigned long beginning_of_body;\n+\tint indent = 4;\n+\tconst char *msg = commit->buffer;\n+\tchar *reencoded;\n+\tconst char *encoding;\n+\n+\tif (fmt == CMIT_FMT_USERFORMAT) {\n+\t\tformat_commit_message(commit, user_format, sb);\n+\t\treturn;\n+\t}\n+\n+\tencoding = (git_log_output_encoding\n+\t\t    ? git_log_output_encoding\n+\t\t    : git_commit_encoding);\n+\tif (!encoding)\n+\t\tencoding = \"utf-8\";\n+\treencoded = logmsg_reencode(commit, encoding);\n+\tif (reencoded) {\n+\t\tmsg = reencoded;\n+\t}\n+\n+\tif (fmt == CMIT_FMT_ONELINE || fmt == CMIT_FMT_EMAIL)\n+\t\tindent = 0;\n+\n+\t/* After-subject is used to pass in Content-Type: multipart\n+\t * MIME header; in that case we do not have to do the\n+\t * plaintext content type even if the commit message has\n+\t * non 7-bit ASCII character.  Otherwise, check if we need\n+\t * to say this is not a 7-bit ASCII.\n+\t */\n+\tif (fmt == CMIT_FMT_EMAIL && !after_subject) {\n+\t\tint i, ch, in_body;\n+\n+\t\tfor (in_body = i = 0; (ch = msg[i]); i++) {\n+\t\t\tif (!in_body) {\n+\t\t\t\t/* author could be non 7-bit ASCII but\n+\t\t\t\t * the log may be so; skip over the\n+\t\t\t\t * header part first.\n+\t\t\t\t */\n+\t\t\t\tif (ch == '\\n' && msg[i+1] == '\\n')\n+\t\t\t\t\tin_body = 1;\n+\t\t\t}\n+\t\t\telse if (non_ascii(ch)) {\n+\t\t\t\tplain_non_ascii = 1;\n+\t\t\t\tbreak;\n+\t\t\t}\n+\t\t}\n+\t}\n+\n+\tpp_header(fmt, abbrev, dmode, encoding, commit, &msg, sb);\n+\tif (fmt != CMIT_FMT_ONELINE && !subject) {\n+\t\tstrbuf_addch(sb, '\\n');\n+\t}\n+\n+\t/* Skip excess blank lines at the beginning of body, if any... */\n+\tfor (;;) {\n+\t\tint linelen = get_one_line(msg);\n+\t\tint ll = linelen;\n+\t\tif (!linelen)\n+\t\t\tbreak;\n+\t\tif (!is_empty_line(msg, &ll))\n+\t\t\tbreak;\n+\t\tmsg += linelen;\n+\t}\n+\n+\t/* These formats treat the title line specially. */\n+\tif (fmt == CMIT_FMT_ONELINE || fmt == CMIT_FMT_EMAIL)\n+\t\tpp_title_line(fmt, &msg, sb, subject,\n+\t\t\t      after_subject, encoding, plain_non_ascii);\n+\n+\tbeginning_of_body = sb->len;\n+\tif (fmt != CMIT_FMT_ONELINE)\n+\t\tpp_remainder(fmt, &msg, sb, indent);\n+\tstrbuf_rtrim(sb);\n+\n+\t/* Make sure there is an EOLN for the non-oneline case */\n+\tif (fmt != CMIT_FMT_ONELINE)\n+\t\tstrbuf_addch(sb, '\\n');\n+\n+\t/*\n+\t * The caller may append additional body text in e-mail\n+\t * format.  Make sure we did not strip the blank line\n+\t * between the header and the body.\n+\t */\n+\tif (fmt == CMIT_FMT_EMAIL && sb->len <= beginning_of_body)\n+\t\tstrbuf_addch(sb, '\\n');\n+\tfree(reencoded);\n+}\n-- \n1.5.3.5.1549.g91a3\n"},{"id":"58297","messageId":"Pine.LNX.4.64.0711041915120.4362@racer.site","threadId":"10650","inReplyTo":"Pine.LNX.4.64.0711041912190.4362@racer.site","subject":"[PATCH 2/3] interpolate.[ch]: Add a function to find which interpolations are active.","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2007-11-04T19:15:22Z","receivedAt":"2007-11-04T19:15:22Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"\nSome substitutions require pretty expensive operations.  So it make\nsense to find out which are needed to begin with.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n interpolate.c |   20 ++++++++++++++++++++\n interpolate.h |    2 ++\n 2 files changed, 22 insertions(+), 0 deletions(-)\n\ndiff --git a/interpolate.c b/interpolate.c\nindex 6ef53f2..80eeb36 100644\n--- a/interpolate.c\n+++ b/interpolate.c\n@@ -102,3 +102,23 @@ unsigned long interpolate(char *result, unsigned long reslen,\n \t\t*dest = '\\0';\n \treturn newlen;\n }\n+\n+char *interp_find_active(const char *orig,\n+\t\tconst struct interp *interps, int ninterps)\n+{\n+\tchar *result = xcalloc(1, ninterps);\n+\tchar c;\n+\tint i;\n+\n+\twhile ((c = *(orig++)))\n+\t\tif (c == '%')\n+\t\t\t/* Try to match an interpolation string. */\n+\t\t\tfor (i = 0; i < ninterps; i++)\n+\t\t\t\tif (!prefixcmp(orig, interps[i].name + 1)) {\n+\t\t\t\t\tresult[i] = 1;\n+\t\t\t\t\torig += strlen(interps[i].name + 1);\n+\t\t\t\t\tbreak;\n+\t\t\t\t}\n+\n+\treturn result;\n+}\ndiff --git a/interpolate.h b/interpolate.h\nindex 77407e6..2d197c5 100644\n--- a/interpolate.h\n+++ b/interpolate.h\n@@ -22,5 +22,7 @@ extern void interp_clear_table(struct interp *table, int ninterps);\n extern unsigned long interpolate(char *result, unsigned long reslen,\n \t\t\t\t const char *orig,\n \t\t\t\t const struct interp *interps, int ninterps);\n+extern char *interp_find_active(const char *orig,\n+\t\t\t\tconst struct interp *interps, int ninterps);\n \n #endif /* INTERPOLATE_H */\n-- \n1.5.3.5.1549.g91a3\n"},{"id":"58298","messageId":"Pine.LNX.4.64.0711041915290.4362@racer.site","threadId":"10650","inReplyTo":"Pine.LNX.4.64.0711041912190.4362@racer.site","subject":"[PATCH 3/3] pretty=format: Avoid some expensive calculations when not needed","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2007-11-04T19:15:44Z","receivedAt":"2007-11-04T19:15:44Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"\nUse the new function interp_find_active() to avoid calculating the\nunique hash names, and other things, when they are not even asked for.\n\nUnfortunately, we cannot reuse the result of that function, which\nwould be cleaner: there are more users than just git log.  Most\nnotably, git-archive with \"$Format:...$\" substitution.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n\tSo I found another reason why the function has to be called\n\teverytime.  But this reason appeals to me much more.\n\n\tOriginally, I wanted to do this differently, by providing a\n\tfunction which generates the substitutions, but the header\n\tparsing makes that infeasible.\n\n pretty.c |   55 ++++++++++++++++++++++++++++++++++---------------------\n 1 files changed, 34 insertions(+), 21 deletions(-)\n\ndiff --git a/pretty.c b/pretty.c\nindex 490cede..241e91c 100644\n--- a/pretty.c\n+++ b/pretty.c\n@@ -393,6 +393,7 @@ void format_commit_message(const struct commit *commit,\n \tint i;\n \tenum { HEADER, SUBJECT, BODY } state;\n \tconst char *msg = commit->buffer;\n+\tchar *active = interp_find_active(format, table, ARRAY_SIZE(table));\n \n \tif (ILEFT_RIGHT + 1 != ARRAY_SIZE(table))\n \t\tdie(\"invalid interp table!\");\n@@ -407,12 +408,18 @@ void format_commit_message(const struct commit *commit,\n \t/* these depend on the commit */\n \tif (!commit->object.parsed)\n \t\tparse_object(commit->object.sha1);\n-\tinterp_set_entry(table, IHASH, sha1_to_hex(commit->object.sha1));\n-\tinterp_set_entry(table, IHASH_ABBREV,\n+\tif (active[IHASH])\n+\t\tinterp_set_entry(table, IHASH,\n+\t\t\t\tsha1_to_hex(commit->object.sha1));\n+\tif (active[IHASH_ABBREV])\n+\t\tinterp_set_entry(table, IHASH_ABBREV,\n \t\t\tfind_unique_abbrev(commit->object.sha1,\n \t\t\t\tDEFAULT_ABBREV));\n-\tinterp_set_entry(table, ITREE, sha1_to_hex(commit->tree->object.sha1));\n-\tinterp_set_entry(table, ITREE_ABBREV,\n+\tif (active[ITREE])\n+\t\tinterp_set_entry(table, ITREE,\n+\t\t\t\tsha1_to_hex(commit->tree->object.sha1));\n+\tif (active[ITREE_ABBREV])\n+\t\tinterp_set_entry(table, ITREE_ABBREV,\n \t\t\tfind_unique_abbrev(commit->tree->object.sha1,\n \t\t\t\tDEFAULT_ABBREV));\n \tinterp_set_entry(table, ILEFT_RIGHT,\n@@ -422,22 +429,27 @@ void format_commit_message(const struct commit *commit,\n \t\t\t ? \"<\"\n \t\t\t : \">\");\n \n-\tparents[1] = 0;\n-\tfor (i = 0, p = commit->parents;\n-\t\t\tp && i < sizeof(parents) - 1;\n-\t\t\tp = p->next)\n-\t\ti += snprintf(parents + i, sizeof(parents) - i - 1, \" %s\",\n-\t\t\tsha1_to_hex(p->item->object.sha1));\n-\tinterp_set_entry(table, IPARENTS, parents + 1);\n-\n-\tparents[1] = 0;\n-\tfor (i = 0, p = commit->parents;\n-\t\t\tp && i < sizeof(parents) - 1;\n-\t\t\tp = p->next)\n-\t\ti += snprintf(parents + i, sizeof(parents) - i - 1, \" %s\",\n-\t\t\tfind_unique_abbrev(p->item->object.sha1,\n-\t\t\t\tDEFAULT_ABBREV));\n-\tinterp_set_entry(table, IPARENTS_ABBREV, parents + 1);\n+\tif (active[IPARENTS]) {\n+\t\tparents[1] = 0;\n+\t\tfor (i = 0, p = commit->parents;\n+\t\t\t\tp && i < sizeof(parents) - 1;\n+\t\t\t\tp = p->next)\n+\t\t\ti += snprintf(parents + i, sizeof(parents) - i - 1,\n+\t\t\t\t\" %s\", sha1_to_hex(p->item->object.sha1));\n+\t\tinterp_set_entry(table, IPARENTS, parents + 1);\n+\t}\n+\n+\tif (active[IPARENTS_ABBREV]) {\n+\t\tparents[1] = 0;\n+\t\tfor (i = 0, p = commit->parents;\n+\t\t\t\tp && i < sizeof(parents) - 1;\n+\t\t\t\tp = p->next)\n+\t\t\ti += snprintf(parents + i, sizeof(parents) - i - 1,\n+\t\t\t\t\" %s\",\n+\t\t\t\tfind_unique_abbrev(p->item->object.sha1,\n+\t\t\t\t\tDEFAULT_ABBREV));\n+\t\tinterp_set_entry(table, IPARENTS_ABBREV, parents + 1);\n+\t}\n \n \tfor (i = 0, state = HEADER; msg[i] && state < BODY; i++) {\n \t\tint eol;\n@@ -464,7 +476,7 @@ void format_commit_message(const struct commit *commit,\n \t\t\t\txmemdupz(msg + i + 9, eol - i - 9);\n \t\ti = eol;\n \t}\n-\tif (msg[i])\n+\tif (active[IBODY] && msg[i])\n \t\ttable[IBODY].value = xstrdup(msg + i);\n \n \tlen = interpolate(sb->buf + sb->len, strbuf_avail(sb),\n@@ -476,6 +488,7 @@ void format_commit_message(const struct commit *commit,\n \t}\n \tstrbuf_setlen(sb, sb->len + len);\n \tinterp_clear_table(table, ARRAY_SIZE(table));\n+\tfree(active);\n }\n \n static void pp_header(enum cmit_fmt fmt,\n-- \n1.5.3.5.1549.g91a3\n"},{"id":"58408","messageId":"7v8x5cqxn0.fsf@gitster.siamese.dyndns.org","threadId":"10650","inReplyTo":"Pine.LNX.4.64.0711041915290.4362@racer.site","subject":"Re: [PATCH 3/3] pretty=format: Avoid some expensive calculations when not needed","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2007-11-05T19:51:15Z","receivedAt":"2007-11-05T19:51:15Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Johannes Schindelin <Johannes.Schindelin@gmx.de> writes:\n\n> Unfortunately, we cannot reuse the result of that function, which\n> would be cleaner: there are more users than just git log.  Most\n> notably, git-archive with \"$Format:...$\" substitution.\n\nThat makes sense.\n\n\n> diff --git a/pretty.c b/pretty.c\n> index 490cede..241e91c 100644\n> --- a/pretty.c\n> +++ b/pretty.c\n> @@ -393,6 +393,7 @@ void format_commit_message(const struct commit *commit,\n>  \tint i;\n>  \tenum { HEADER, SUBJECT, BODY } state;\n>  \tconst char *msg = commit->buffer;\n> +\tchar *active = interp_find_active(format, table, ARRAY_SIZE(table));\n> ...\n> +\tif (active[IHASH])\n> +\t\tinterp_set_entry(table, IHASH,\n> +\t\t\t\tsha1_to_hex(commit->object.sha1));\n> +\tif (active[IHASH_ABBREV])\n> +\t\tinterp_set_entry(table, IHASH_ABBREV,\n>  \t\t\tfind_unique_abbrev(commit->object.sha1,\n>  \t\t\t\tDEFAULT_ABBREV));\n\nInstead of allocating a separate array and freeing at the end,\nwouldn't it make more sense to have a bitfield that records what\nis used by the format string inside the array elements?\n"},{"id":"58410","messageId":"472F7B2F.4050608@lsrfire.ath.cx","threadId":"10650","inReplyTo":"7v8x5cqxn0.fsf@gitster.siamese.dyndns.org","subject":"Re: [PATCH 3/3] pretty=format: Avoid some expensive calculations when not needed","fromName":"René Scharfe","fromEmail":"rene.scharfe@lsrfire.ath.cx","sentAt":"2007-11-05T20:21:03Z","receivedAt":"2007-11-05T20:21:03Z","isPatch":true,"sender":{"key":"l.s.r@web.de","avatar":"https://avatars.githubusercontent.com/u/26122331?v=4"},"body":"Junio C Hamano schrieb:\n> Johannes Schindelin <Johannes.Schindelin@gmx.de> writes:\n> \n>> Unfortunately, we cannot reuse the result of that function, which\n>> would be cleaner: there are more users than just git log.  Most\n>> notably, git-archive with \"$Format:...$\" substitution.\n> \n> That makes sense.\n> \n> \n>> diff --git a/pretty.c b/pretty.c\n>> index 490cede..241e91c 100644\n>> --- a/pretty.c\n>> +++ b/pretty.c\n>> @@ -393,6 +393,7 @@ void format_commit_message(const struct commit *commit,\n>>  \tint i;\n>>  \tenum { HEADER, SUBJECT, BODY } state;\n>>  \tconst char *msg = commit->buffer;\n>> +\tchar *active = interp_find_active(format, table, ARRAY_SIZE(table));\n>> ...\n>> +\tif (active[IHASH])\n>> +\t\tinterp_set_entry(table, IHASH,\n>> +\t\t\t\tsha1_to_hex(commit->object.sha1));\n>> +\tif (active[IHASH_ABBREV])\n>> +\t\tinterp_set_entry(table, IHASH_ABBREV,\n>>  \t\t\tfind_unique_abbrev(commit->object.sha1,\n>>  \t\t\t\tDEFAULT_ABBREV));\n> \n> Instead of allocating a separate array and freeing at the end,\n> wouldn't it make more sense to have a bitfield that records what\n> is used by the format string inside the array elements?\n\nHow about (ab)using the value field?  Let interp_find_active() mark\nunneeded entries with NULL, and the rest with some cookie.  All table\nentries with non-NULL values need to be initialized.  interp_set_entry()\nneeds to be aware of this cookie, as it mustn't free() it.  The cookie\ncould be the address of a static char* in interpolate.c.\n"},{"id":"58412","messageId":"1194294351.7158.41.camel@ld0161-tx32","threadId":"10650","inReplyTo":"472F7B2F.4050608@lsrfire.ath.cx","subject":"Re: [PATCH 3/3] pretty=format: Avoid some expensive calculations when not needed","fromName":"Jon Loeliger","fromEmail":"jdl@freescale.com","sentAt":"2007-11-05T20:25:51Z","receivedAt":"2007-11-05T20:25:51Z","isPatch":true,"sender":{"key":"jdl@jdl.com","avatar":"https://gravatar.com/avatar/75ce9a10b151acd2c28ec4ab2136dba7b2ff1634530bd04b155981a749d08a64?d=mp&s=160"},"body":"On Mon, 2007-11-05 at 14:21, René Scharfe wrote:\n\n> How about (ab)using the value field?  Let interp_find_active() mark\n> unneeded entries with NULL, and the rest with some cookie.  All table\n> entries with non-NULL values need to be initialized.  interp_set_entry()\n> needs to be aware of this cookie, as it mustn't free() it.  The cookie\n> could be the address of a static char* in interpolate.c.\n\nOr adding a \"flags/properties\" field and having it\nnote things like static entries, active, etc...?\n\njdl\n"},{"id":"58417","messageId":"7v3avkpf3r.fsf@gitster.siamese.dyndns.org","threadId":"10650","inReplyTo":"Pine.LNX.4.64.0711041914310.4362@racer.site","subject":"Re: [PATCH 1/3] Split off the pretty print stuff into its own file","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2007-11-05T21:16:56Z","receivedAt":"2007-11-05T21:16:56Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"I'll take the [1/3] to 'master'.  I will see how discussion\ngoes about the rest on the list for now.\n"},{"id":"58458","messageId":"Pine.LNX.4.64.0711052348390.4362@racer.site","threadId":"10650","inReplyTo":"472F7B2F.4050608@lsrfire.ath.cx","subject":"Re: [PATCH 3/3] pretty=format: Avoid some expensive calculations when not needed","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2007-11-05T23:53:04Z","receivedAt":"2007-11-05T23:53:04Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi,\n\nOn Mon, 5 Nov 2007, Ren? Scharfe wrote:\n\n> Junio C Hamano schrieb:\n> > Johannes Schindelin <Johannes.Schindelin@gmx.de> writes:\n> > \n> >> Unfortunately, we cannot reuse the result of that function, which\n> >> would be cleaner: there are more users than just git log.  Most\n> >> notably, git-archive with \"$Format:...$\" substitution.\n> > \n> > That makes sense.\n> > \n> > \n> >> diff --git a/pretty.c b/pretty.c\n> >> index 490cede..241e91c 100644\n> >> --- a/pretty.c\n> >> +++ b/pretty.c\n> >> @@ -393,6 +393,7 @@ void format_commit_message(const struct commit *commit,\n> >>  \tint i;\n> >>  \tenum { HEADER, SUBJECT, BODY } state;\n> >>  \tconst char *msg = commit->buffer;\n> >> +\tchar *active = interp_find_active(format, table, ARRAY_SIZE(table));\n> >> ...\n> >> +\tif (active[IHASH])\n> >> +\t\tinterp_set_entry(table, IHASH,\n> >> +\t\t\t\tsha1_to_hex(commit->object.sha1));\n> >> +\tif (active[IHASH_ABBREV])\n> >> +\t\tinterp_set_entry(table, IHASH_ABBREV,\n> >>  \t\t\tfind_unique_abbrev(commit->object.sha1,\n> >>  \t\t\t\tDEFAULT_ABBREV));\n> > \n> > Instead of allocating a separate array and freeing at the end,\n> > wouldn't it make more sense to have a bitfield that records what\n> > is used by the format string inside the array elements?\n> \n> How about (ab)using the value field?  Let interp_find_active() mark\n> unneeded entries with NULL, and the rest with some cookie.  All table\n> entries with non-NULL values need to be initialized.  interp_set_entry()\n> needs to be aware of this cookie, as it mustn't free() it.  The cookie\n> could be the address of a static char* in interpolate.c.\n\nYeah, something like this on top of my earlier patch (and obviously the \ncorresponding change from \"if (active[IHASH])\" to\n\"if (table[IHASH].value)\"):\n\n---\n\n interpolate.c |   10 ++++------\n interpolate.h |    2 +-\n 2 files changed, 5 insertions(+), 7 deletions(-)\n\ndiff --git a/interpolate.c b/interpolate.c\nindex 80eeb36..05a22e1 100644\n--- a/interpolate.c\n+++ b/interpolate.c\n@@ -5,13 +5,14 @@\n #include \"git-compat-util.h\"\n #include \"interpolate.h\"\n \n+static const char *empty_value = \"\";\n \n void interp_set_entry(struct interp *table, int slot, const char *value)\n {\n \tchar *oldval = table[slot].value;\n \tchar *newval = NULL;\n \n-\tif (oldval)\n+\tif (oldval && oldval != empty_value)\n \t\tfree(oldval);\n \n \tif (value)\n@@ -103,10 +104,9 @@ unsigned long interpolate(char *result, unsigned long reslen,\n \treturn newlen;\n }\n \n-char *interp_find_active(const char *orig,\n+void interp_find_active(const char *orig,\n \t\tconst struct interp *interps, int ninterps)\n {\n-\tchar *result = xcalloc(1, ninterps);\n \tchar c;\n \tint i;\n \n@@ -115,10 +115,8 @@ char *interp_find_active(const char *orig,\n \t\t\t/* Try to match an interpolation string. */\n \t\t\tfor (i = 0; i < ninterps; i++)\n \t\t\t\tif (!prefixcmp(orig, interps[i].name + 1)) {\n-\t\t\t\t\tresult[i] = 1;\n+\t\t\t\t\tinterps[i].value = empty_value;\n \t\t\t\t\torig += strlen(interps[i].name + 1);\n \t\t\t\t\tbreak;\n \t\t\t\t}\n-\n-\treturn result;\n }\ndiff --git a/interpolate.h b/interpolate.h\nindex 2d197c5..19b7ebe 100644\n--- a/interpolate.h\n+++ b/interpolate.h\n@@ -22,7 +22,7 @@ extern void interp_clear_table(struct interp *table, int ninterps);\n extern unsigned long interpolate(char *result, unsigned long reslen,\n \t\t\t\t const char *orig,\n \t\t\t\t const struct interp *interps, int ninterps);\n-extern char *interp_find_active(const char *orig,\n+extern void interp_find_active(const char *orig,\n \t\t\t\tconst struct interp *interps, int ninterps);\n \n #endif /* INTERPOLATE_H */\n\n\nHmm?\n\nCiao,\nDscho\n"},{"id":"58470","messageId":"7vejf4kwry.fsf@gitster.siamese.dyndns.org","threadId":"10650","inReplyTo":"472F7B2F.4050608@lsrfire.ath.cx","subject":"Re: [PATCH 3/3] pretty=format: Avoid some expensive calculations when not needed","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2007-11-06T01:06:25Z","receivedAt":"2007-11-06T01:06:25Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"René Scharfe <rene.scharfe@lsrfire.ath.cx> writes:\n\n> Junio C Hamano schrieb:\n> ...\n>> Instead of allocating a separate array and freeing at the end,\n>> wouldn't it make more sense to have a bitfield that records what\n>> is used by the format string inside the array elements?\n>\n> How about (ab)using the value field?  Let interp_find_active() mark\n> unneeded entries with NULL, and the rest with some cookie.  All table\n> entries with non-NULL values need to be initialized.  interp_set_entry()\n> needs to be aware of this cookie, as it mustn't free() it.  The cookie\n> could be the address of a static char* in interpolate.c.\n\nSorry, where is this aversion to making the struct a bit larger\ncoming from?\n"},{"id":"58604","messageId":"4730EB4E.4080903@lsrfire.ath.cx","threadId":"10650","inReplyTo":"7vejf4kwry.fsf@gitster.siamese.dyndns.org","subject":"Re: [PATCH 3/3] pretty=format: Avoid some expensive calculations when not needed","fromName":"René Scharfe","fromEmail":"rene.scharfe@lsrfire.ath.cx","sentAt":"2007-11-06T22:31:42Z","receivedAt":"2007-11-06T22:31:42Z","isPatch":true,"sender":{"key":"l.s.r@web.de","avatar":"https://avatars.githubusercontent.com/u/26122331?v=4"},"body":"Junio C Hamano schrieb:\n> René Scharfe <rene.scharfe@lsrfire.ath.cx> writes:\n> \n>> Junio C Hamano schrieb: ...\n>>> Instead of allocating a separate array and freeing at the end, \n>>> wouldn't it make more sense to have a bitfield that records what \n>>> is used by the format string inside the array elements?\n>> How about (ab)using the value field?  Let interp_find_active() mark\n>>  unneeded entries with NULL, and the rest with some cookie.  All\n>> table entries with non-NULL values need to be initialized.\n>> interp_set_entry() needs to be aware of this cookie, as it mustn't\n>> free() it.  The cookie could be the address of a static char* in\n>> interpolate.c.\n> \n> Sorry, where is this aversion to making the struct a bit larger \n> coming from?\n\nNot from the rational part of my brain, for sure.  The following on\ntop of Dscho's second patch?  (A char would be smaller, but a bitfield\ndocuments the intent better.)\n\n\ndiff --git a/interpolate.c b/interpolate.c\nindex 80eeb36..1e4ccaf 100644\n--- a/interpolate.c\n+++ b/interpolate.c\n@@ -103,22 +103,21 @@ unsigned long interpolate(char *result, unsigned long reslen,\n \treturn newlen;\n }\n \n-char *interp_find_active(const char *orig,\n-\t\tconst struct interp *interps, int ninterps)\n+void interp_find_active(const char *orig, struct interp *interps, int ninterps)\n {\n-\tchar *result = xcalloc(1, ninterps);\n \tchar c;\n \tint i;\n \n+\tfor (i = 0; i < ninterps; i++)\n+\t\tinterps[i].active = 0;\n+\n \twhile ((c = *(orig++)))\n \t\tif (c == '%')\n \t\t\t/* Try to match an interpolation string. */\n \t\t\tfor (i = 0; i < ninterps; i++)\n \t\t\t\tif (!prefixcmp(orig, interps[i].name + 1)) {\n-\t\t\t\t\tresult[i] = 1;\n+\t\t\t\t\tinterps[i].active = 1;\n \t\t\t\t\torig += strlen(interps[i].name + 1);\n \t\t\t\t\tbreak;\n \t\t\t\t}\n-\n-\treturn result;\n }\ndiff --git a/interpolate.h b/interpolate.h\nindex 2d197c5..a8ee6b9 100644\n--- a/interpolate.h\n+++ b/interpolate.h\n@@ -14,6 +14,7 @@\n struct interp {\n \tconst char *name;\n \tchar *value;\n+\tunsigned active:1;\n };\n \n extern void interp_set_entry(struct interp *table, int slot, const char *value);\n@@ -22,7 +23,6 @@ extern void interp_clear_table(struct interp *table, int ninterps);\n extern unsigned long interpolate(char *result, unsigned long reslen,\n \t\t\t\t const char *orig,\n \t\t\t\t const struct interp *interps, int ninterps);\n-extern char *interp_find_active(const char *orig,\n-\t\t\t\tconst struct interp *interps, int ninterps);\n+extern void interp_find_active(const char *orig, struct interp *interps, int ninterps);\n \n #endif /* INTERPOLATE_H */\n"},{"id":"58609","messageId":"4730F5FA.3030705@lsrfire.ath.cx","threadId":"10650","inReplyTo":"4730EB4E.4080903@lsrfire.ath.cx","subject":"Re: [PATCH 3/3] pretty=format: Avoid some expensive calculations when not needed","fromName":"René Scharfe","fromEmail":"rene.scharfe@lsrfire.ath.cx","sentAt":"2007-11-06T23:17:14Z","receivedAt":"2007-11-06T23:17:14Z","isPatch":true,"sender":{"key":"l.s.r@web.de","avatar":"https://avatars.githubusercontent.com/u/26122331?v=4"},"body":"By the way, the more intrusive surgery required when using strbuf_expand()\nleads to even faster operation.  Here my measurements of most of Paul's\ntest cases (best of three runs):\n\n# master\n$ time git log --pretty=oneline >/dev/null\n\nreal    0m0.390s\nuser    0m0.340s\nsys     0m0.040s\n\n# master\n$ time git log --pretty=raw >/dev/null\n\nreal    0m0.434s\nuser    0m0.408s\nsys     0m0.016s\n\n# master\n$ time git log --pretty=\"format:%H {%P} %ct\" >/dev/null\n\nreal    0m1.347s\nuser    0m0.080s\nsys     0m1.256s\n\n# interp_find_active\n$ time ./git log --pretty=\"format:%H {%P} %ct\" >/dev/null\n\nreal    0m0.694s\nuser    0m0.020s\nsys     0m0.672s\n\n# strbuf_expand\n$ time ./git log --pretty=\"format:%H {%P} %ct\" >/dev/null\n\nreal    0m0.395s\nuser    0m0.352s\nsys     0m0.028s\n\nI haven't seen any comments on strbuf_expand.  Is it too far out?\nHere it is again, adjusted for current master and with the changes\nto strbuf.[ch] coming first:\n\n\n strbuf.c |   22 +++++\n strbuf.h |    3 \n pretty.c |  276 ++++++++++++++++++++++++++++++++++-----------------------------\n 3 files changed, 178 insertions(+), 123 deletions(-)\n\ndiff --git a/strbuf.c b/strbuf.c\nindex f4201e1..b71da99 100644\n--- a/strbuf.c\n+++ b/strbuf.c\n@@ -129,6 +129,28 @@ void strbuf_addf(struct strbuf *sb, const char *fmt, ...)\n \tstrbuf_setlen(sb, sb->len + len);\n }\n \n+void strbuf_expand(struct strbuf *sb, const char *fmt,\n+                   const char **placeholders, expand_fn_t fn, void *context)\n+{\n+\tchar c;\n+\tconst char **p;\n+\n+\twhile ((c = *fmt++)) {\n+\t\tif (c != '%') {\n+\t\t\tstrbuf_addch(sb, c);\n+\t\t\tcontinue;\n+\t\t}\n+\n+\t\tfor (p = placeholders; *p; p++) {\n+\t\t\tif (!prefixcmp(fmt, *p)) {\n+\t\t\t\tfn(sb, *p, context);\n+\t\t\t\tfmt += strlen(*p);\n+\t\t\t\tbreak;\n+\t\t\t}\n+\t\t}\n+\t}\n+}\n+\n size_t strbuf_fread(struct strbuf *sb, size_t size, FILE *f)\n {\n \tsize_t res;\ndiff --git a/strbuf.h b/strbuf.h\nindex cd7f295..95071d5 100644\n--- a/strbuf.h\n+++ b/strbuf.h\n@@ -102,6 +102,9 @@ static inline void strbuf_addbuf(struct strbuf *sb, struct strbuf *sb2) {\n \tstrbuf_add(sb, sb2->buf, sb2->len);\n }\n \n+typedef void (*expand_fn_t) (struct strbuf *sb, const char *placeholder, void *context);\n+extern void strbuf_expand(struct strbuf *sb, const char *fmt, const char **placeholders, expand_fn_t fn, void *context);\n+\n __attribute__((format(printf,2,3)))\n extern void strbuf_addf(struct strbuf *sb, const char *fmt, ...);\n \ndiff --git a/pretty.c b/pretty.c\nindex 490cede..ea644bb 100644\n--- a/pretty.c\n+++ b/pretty.c\n@@ -1,6 +1,5 @@\n #include \"cache.h\"\n #include \"commit.h\"\n-#include \"interpolate.h\"\n #include \"utf8.h\"\n #include \"diff.h\"\n #include \"revision.h\"\n@@ -283,7 +282,8 @@ static char *logmsg_reencode(const struct commit *commit,\n \treturn out;\n }\n \n-static void fill_person(struct interp *table, const char *msg, int len)\n+static void format_person_part(struct strbuf *sb, char part,\n+                               const char *msg, int len)\n {\n \tint start, end, tz = 0;\n \tunsigned long date;\n@@ -295,7 +295,10 @@ static void fill_person(struct interp *table, const char *msg, int len)\n \tstart = end + 1;\n \twhile (end > 0 && isspace(msg[end - 1]))\n \t\tend--;\n-\ttable[0].value = xmemdupz(msg, end);\n+\tif (part == 'n') {\t/* name */\n+\t\tstrbuf_add(sb, msg, end);\n+\t\treturn;\n+\t}\n \n \tif (start >= len)\n \t\treturn;\n@@ -307,7 +310,10 @@ static void fill_person(struct interp *table, const char *msg, int len)\n \tif (end >= len)\n \t\treturn;\n \n-\ttable[1].value = xmemdupz(msg + start, end - start);\n+\tif (part == 'e') {\t/* email */\n+\t\tstrbuf_add(sb, msg + start, end - start);\n+\t\treturn;\n+\t}\n \n \t/* parse date */\n \tfor (start = end + 1; start < len && isspace(msg[start]); start++)\n@@ -318,7 +324,10 @@ static void fill_person(struct interp *table, const char *msg, int len)\n \tif (msg + start == ep)\n \t\treturn;\n \n-\ttable[5].value = xmemdupz(msg + start, ep - (msg + start));\n+\tif (part == 't') {\t/* date, UNIX timestamp */\n+\t\tstrbuf_add(sb, msg + start, ep - (msg + start));\n+\t\treturn;\n+\t}\n \n \t/* parse tz */\n \tfor (start = ep - msg + 1; start < len && isspace(msg[start]); start++)\n@@ -329,123 +338,107 @@ static void fill_person(struct interp *table, const char *msg, int len)\n \t\t\ttz = -tz;\n \t}\n \n-\tinterp_set_entry(table, 2, show_date(date, tz, DATE_NORMAL));\n-\tinterp_set_entry(table, 3, show_date(date, tz, DATE_RFC2822));\n-\tinterp_set_entry(table, 4, show_date(date, tz, DATE_RELATIVE));\n-\tinterp_set_entry(table, 6, show_date(date, tz, DATE_ISO8601));\n+\tswitch (part) {\n+\tcase 'd':\t/* date */\n+\t\tstrbuf_addstr(sb, show_date(date, tz, DATE_NORMAL));\n+\t\treturn;\n+\tcase 'D':\t/* date, RFC2822 style */\n+\t\tstrbuf_addstr(sb, show_date(date, tz, DATE_RFC2822));\n+\t\treturn;\n+\tcase 'r':\t/* date, relative */\n+\t\tstrbuf_addstr(sb, show_date(date, tz, DATE_RELATIVE));\n+\t\treturn;\n+\tcase 'i':\t/* date, ISO 8601 */\n+\t\tstrbuf_addstr(sb, show_date(date, tz, DATE_ISO8601));\n+\t\treturn;\n+\t}\n }\n \n-void format_commit_message(const struct commit *commit,\n-                           const void *format, struct strbuf *sb)\n+static void format_commit_item(struct strbuf *sb, const char *placeholder,\n+                               void *context)\n {\n-\tstruct interp table[] = {\n-\t\t{ \"%H\" },\t/* commit hash */\n-\t\t{ \"%h\" },\t/* abbreviated commit hash */\n-\t\t{ \"%T\" },\t/* tree hash */\n-\t\t{ \"%t\" },\t/* abbreviated tree hash */\n-\t\t{ \"%P\" },\t/* parent hashes */\n-\t\t{ \"%p\" },\t/* abbreviated parent hashes */\n-\t\t{ \"%an\" },\t/* author name */\n-\t\t{ \"%ae\" },\t/* author email */\n-\t\t{ \"%ad\" },\t/* author date */\n-\t\t{ \"%aD\" },\t/* author date, RFC2822 style */\n-\t\t{ \"%ar\" },\t/* author date, relative */\n-\t\t{ \"%at\" },\t/* author date, UNIX timestamp */\n-\t\t{ \"%ai\" },\t/* author date, ISO 8601 */\n-\t\t{ \"%cn\" },\t/* committer name */\n-\t\t{ \"%ce\" },\t/* committer email */\n-\t\t{ \"%cd\" },\t/* committer date */\n-\t\t{ \"%cD\" },\t/* committer date, RFC2822 style */\n-\t\t{ \"%cr\" },\t/* committer date, relative */\n-\t\t{ \"%ct\" },\t/* committer date, UNIX timestamp */\n-\t\t{ \"%ci\" },\t/* committer date, ISO 8601 */\n-\t\t{ \"%e\" },\t/* encoding */\n-\t\t{ \"%s\" },\t/* subject */\n-\t\t{ \"%b\" },\t/* body */\n-\t\t{ \"%Cred\" },\t/* red */\n-\t\t{ \"%Cgreen\" },\t/* green */\n-\t\t{ \"%Cblue\" },\t/* blue */\n-\t\t{ \"%Creset\" },\t/* reset color */\n-\t\t{ \"%n\" },\t/* newline */\n-\t\t{ \"%m\" },\t/* left/right/bottom */\n-\t};\n-\tenum interp_index {\n-\t\tIHASH = 0, IHASH_ABBREV,\n-\t\tITREE, ITREE_ABBREV,\n-\t\tIPARENTS, IPARENTS_ABBREV,\n-\t\tIAUTHOR_NAME, IAUTHOR_EMAIL,\n-\t\tIAUTHOR_DATE, IAUTHOR_DATE_RFC2822, IAUTHOR_DATE_RELATIVE,\n-\t\tIAUTHOR_TIMESTAMP, IAUTHOR_ISO8601,\n-\t\tICOMMITTER_NAME, ICOMMITTER_EMAIL,\n-\t\tICOMMITTER_DATE, ICOMMITTER_DATE_RFC2822,\n-\t\tICOMMITTER_DATE_RELATIVE, ICOMMITTER_TIMESTAMP,\n-\t\tICOMMITTER_ISO8601,\n-\t\tIENCODING,\n-\t\tISUBJECT,\n-\t\tIBODY,\n-\t\tIRED, IGREEN, IBLUE, IRESET_COLOR,\n-\t\tINEWLINE,\n-\t\tILEFT_RIGHT,\n-\t};\n+\tconst struct commit *commit = context;\n \tstruct commit_list *p;\n-\tchar parents[1024];\n-\tunsigned long len;\n \tint i;\n \tenum { HEADER, SUBJECT, BODY } state;\n \tconst char *msg = commit->buffer;\n \n-\tif (ILEFT_RIGHT + 1 != ARRAY_SIZE(table))\n-\t\tdie(\"invalid interp table!\");\n-\n \t/* these are independent of the commit */\n-\tinterp_set_entry(table, IRED, \"\\033[31m\");\n-\tinterp_set_entry(table, IGREEN, \"\\033[32m\");\n-\tinterp_set_entry(table, IBLUE, \"\\033[34m\");\n-\tinterp_set_entry(table, IRESET_COLOR, \"\\033[m\");\n-\tinterp_set_entry(table, INEWLINE, \"\\n\");\n+\tswitch (placeholder[0]) {\n+\tcase 'C':\n+\t\tswitch (placeholder[3]) {\n+\t\tcase 'd':\t/* red */\n+\t\t\tstrbuf_addstr(sb, \"\\033[31m\");\n+\t\t\treturn;\n+\t\tcase 'e':\t/* green */\n+\t\t\tstrbuf_addstr(sb, \"\\033[32m\");\n+\t\t\treturn;\n+\t\tcase 'u':\t/* blue */\n+\t\t\tstrbuf_addstr(sb, \"\\033[34m\");\n+\t\t\treturn;\n+\t\tcase 's':\t/* reset color */\n+\t\t\tstrbuf_addstr(sb, \"\\033[m\");\n+\t\t\treturn;\n+\t\t}\n+\tcase 'n':\t\t/* newline */\n+\t\tstrbuf_addch(sb, '\\n');\n+\t\treturn;\n+\t}\n \n \t/* these depend on the commit */\n \tif (!commit->object.parsed)\n \t\tparse_object(commit->object.sha1);\n-\tinterp_set_entry(table, IHASH, sha1_to_hex(commit->object.sha1));\n-\tinterp_set_entry(table, IHASH_ABBREV,\n-\t\t\tfind_unique_abbrev(commit->object.sha1,\n-\t\t\t\tDEFAULT_ABBREV));\n-\tinterp_set_entry(table, ITREE, sha1_to_hex(commit->tree->object.sha1));\n-\tinterp_set_entry(table, ITREE_ABBREV,\n-\t\t\tfind_unique_abbrev(commit->tree->object.sha1,\n-\t\t\t\tDEFAULT_ABBREV));\n-\tinterp_set_entry(table, ILEFT_RIGHT,\n-\t\t\t (commit->object.flags & BOUNDARY)\n-\t\t\t ? \"-\"\n-\t\t\t : (commit->object.flags & SYMMETRIC_LEFT)\n-\t\t\t ? \"<\"\n-\t\t\t : \">\");\n-\n-\tparents[1] = 0;\n-\tfor (i = 0, p = commit->parents;\n-\t\t\tp && i < sizeof(parents) - 1;\n-\t\t\tp = p->next)\n-\t\ti += snprintf(parents + i, sizeof(parents) - i - 1, \" %s\",\n-\t\t\tsha1_to_hex(p->item->object.sha1));\n-\tinterp_set_entry(table, IPARENTS, parents + 1);\n-\n-\tparents[1] = 0;\n-\tfor (i = 0, p = commit->parents;\n-\t\t\tp && i < sizeof(parents) - 1;\n-\t\t\tp = p->next)\n-\t\ti += snprintf(parents + i, sizeof(parents) - i - 1, \" %s\",\n-\t\t\tfind_unique_abbrev(p->item->object.sha1,\n-\t\t\t\tDEFAULT_ABBREV));\n-\tinterp_set_entry(table, IPARENTS_ABBREV, parents + 1);\n \n+\tswitch (placeholder[0]) {\n+\tcase 'H':\t\t/* commit hash */\n+\t\tstrbuf_addstr(sb, sha1_to_hex(commit->object.sha1));\n+\t\treturn;\n+\tcase 'h':\t\t/* abbreviated commit hash */\n+\t\tstrbuf_addstr(sb, find_unique_abbrev(commit->object.sha1,\n+\t\t                                     DEFAULT_ABBREV));\n+\t\treturn;\n+\tcase 'T':\t\t/* tree hash */\n+\t\tstrbuf_addstr(sb, sha1_to_hex(commit->tree->object.sha1));\n+\t\treturn;\n+\tcase 't':\t\t/* abbreviated tree hash */\n+\t\tstrbuf_addstr(sb, find_unique_abbrev(commit->tree->object.sha1,\n+\t\t                                     DEFAULT_ABBREV));\n+\t\treturn;\n+\tcase 'P':\t\t/* parent hashes */\n+\t\tfor (p = commit->parents; p; p = p->next) {\n+\t\t\tif (p != commit->parents)\n+\t\t\t\tstrbuf_addch(sb, ' ');\n+\t\t\tstrbuf_addstr(sb, sha1_to_hex(p->item->object.sha1));\n+\t\t}\n+\t\treturn;\n+\tcase 'p':\t\t/* abbreviated parent hashes */\n+\t\tfor (p = commit->parents; p; p = p->next) {\n+\t\t\tif (p != commit->parents)\n+\t\t\t\tstrbuf_addch(sb, ' ');\n+\t\t\tstrbuf_addstr(sb, find_unique_abbrev(\n+\t\t\t\t\tp->item->object.sha1, DEFAULT_ABBREV));\n+\t\t}\n+\t\treturn;\n+\tcase 'm':\t\t/* left/right/bottom */\n+\t\tstrbuf_addch(sb, (commit->object.flags & BOUNDARY)\n+\t\t                 ? '-'\n+\t\t                 : (commit->object.flags & SYMMETRIC_LEFT)\n+\t\t                 ? '<'\n+\t\t                 : '>');\n+\t\treturn;\n+\t}\n+\n+\t/* For the rest we have to parse the commit header. */\n \tfor (i = 0, state = HEADER; msg[i] && state < BODY; i++) {\n \t\tint eol;\n \t\tfor (eol = i; msg[eol] && msg[eol] != '\\n'; eol++)\n \t\t\t; /* do nothing */\n \n \t\tif (state == SUBJECT) {\n-\t\t\ttable[ISUBJECT].value = xmemdupz(msg + i, eol - i);\n+\t\t\tif (placeholder[0] == 's') {\n+\t\t\t\tstrbuf_add(sb, msg + i, eol - i);\n+\t\t\t\treturn;\n+\t\t\t}\n \t\t\ti = eol;\n \t\t}\n \t\tif (i == eol) {\n@@ -453,29 +446,66 @@ void format_commit_message(const struct commit *commit,\n \t\t\t/* strip empty lines */\n \t\t\twhile (msg[eol + 1] == '\\n')\n \t\t\t\teol++;\n-\t\t} else if (!prefixcmp(msg + i, \"author \"))\n-\t\t\tfill_person(table + IAUTHOR_NAME,\n-\t\t\t\t\tmsg + i + 7, eol - i - 7);\n-\t\telse if (!prefixcmp(msg + i, \"committer \"))\n-\t\t\tfill_person(table + ICOMMITTER_NAME,\n-\t\t\t\t\tmsg + i + 10, eol - i - 10);\n-\t\telse if (!prefixcmp(msg + i, \"encoding \"))\n-\t\t\ttable[IENCODING].value =\n-\t\t\t\txmemdupz(msg + i + 9, eol - i - 9);\n+\t\t} else if (!prefixcmp(msg + i, \"author \")) {\n+\t\t\tif (placeholder[0] == 'a') {\n+\t\t\t\tformat_person_part(sb, placeholder[1],\n+\t\t\t\t                   msg + i + 7, eol - i - 7);\n+\t\t\t\treturn;\n+\t\t\t}\n+\t\t} else if (!prefixcmp(msg + i, \"committer \")) {\n+\t\t\tif (placeholder[0] == 'c') {\n+\t\t\t\tformat_person_part(sb, placeholder[1],\n+\t\t\t\t                   msg + i + 10, eol - i - 10);\n+\t\t\t\treturn;\n+\t\t\t}\n+\t\t} else if (!prefixcmp(msg + i, \"encoding \")) {\n+\t\t\tif (placeholder[0] == 'e') {\n+\t\t\t\tstrbuf_add(sb, msg + i + 9, eol - i - 9);\n+\t\t\t\treturn;\n+\t\t\t}\n+\t\t}\n \t\ti = eol;\n \t}\n-\tif (msg[i])\n-\t\ttable[IBODY].value = xstrdup(msg + i);\n-\n-\tlen = interpolate(sb->buf + sb->len, strbuf_avail(sb),\n-\t\t\t\tformat, table, ARRAY_SIZE(table));\n-\tif (len > strbuf_avail(sb)) {\n-\t\tstrbuf_grow(sb, len);\n-\t\tinterpolate(sb->buf + sb->len, strbuf_avail(sb) + 1,\n-\t\t\t\t\tformat, table, ARRAY_SIZE(table));\n-\t}\n-\tstrbuf_setlen(sb, sb->len + len);\n-\tinterp_clear_table(table, ARRAY_SIZE(table));\n+\tif (msg[i] && placeholder[0] == 'b')\t/* body */\n+\t\tstrbuf_addstr(sb, msg + i);\n+}\n+\n+void format_commit_message(const struct commit *commit,\n+                           const void *format, struct strbuf *sb)\n+{\n+\tconst char *placeholders[] = {\n+\t\t\"H\",\t\t/* commit hash */\n+\t\t\"h\",\t\t/* abbreviated commit hash */\n+\t\t\"T\",\t\t/* tree hash */\n+\t\t\"t\",\t\t/* abbreviated tree hash */\n+\t\t\"P\",\t\t/* parent hashes */\n+\t\t\"p\",\t\t/* abbreviated parent hashes */\n+\t\t\"an\",\t\t/* author name */\n+\t\t\"ae\",\t\t/* author email */\n+\t\t\"ad\",\t\t/* author date */\n+\t\t\"aD\",\t\t/* author date, RFC2822 style */\n+\t\t\"ar\",\t\t/* author date, relative */\n+\t\t\"at\",\t\t/* author date, UNIX timestamp */\n+\t\t\"ai\",\t\t/* author date, ISO 8601 */\n+\t\t\"cn\",\t\t/* committer name */\n+\t\t\"ce\",\t\t/* committer email */\n+\t\t\"cd\",\t\t/* committer date */\n+\t\t\"cD\",\t\t/* committer date, RFC2822 style */\n+\t\t\"cr\",\t\t/* committer date, relative */\n+\t\t\"ct\",\t\t/* committer date, UNIX timestamp */\n+\t\t\"ci\",\t\t/* committer date, ISO 8601 */\n+\t\t\"e\",\t\t/* encoding */\n+\t\t\"s\",\t\t/* subject */\n+\t\t\"b\",\t\t/* body */\n+\t\t\"Cred\",\t\t/* red */\n+\t\t\"Cgreen\",\t/* green */\n+\t\t\"Cblue\",\t/* blue */\n+\t\t\"Creset\",\t/* reset color */\n+\t\t\"n\",\t\t/* newline */\n+\t\t\"m\",\t\t/* left/right/bottom */\n+\t\tNULL\n+\t};\n+\tstrbuf_expand(sb, format, placeholders, format_commit_item, (void *)commit);\n }\n \n static void pp_header(enum cmit_fmt fmt,\n"},{"id":"58614","messageId":"Pine.LNX.4.64.0711062335150.4362@racer.site","threadId":"10650","inReplyTo":"4730EB4E.4080903@lsrfire.ath.cx","subject":"Re: [PATCH 3/3] pretty=format: Avoid some expensive calculations when not needed","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2007-11-06T23:36:44Z","receivedAt":"2007-11-06T23:36:44Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi,\n\nOn Tue, 6 Nov 2007, René Scharfe wrote:\n\n> -char *interp_find_active(const char *orig,\n> -\t\tconst struct interp *interps, int ninterps)\n> +void interp_find_active(const char *orig, struct interp *interps, int ninterps)\n>  {\n> -\tchar *result = xcalloc(1, ninterps);\n>  \tchar c;\n>  \tint i;\n>  \n> +\tfor (i = 0; i < ninterps; i++)\n> +\t\tinterps[i].active = 0;\n> +\n> [...]\n\nFunny.\n\nI have the _exact_ same change in my repository.  I only forgot to send \nit, it seems.\n\nCiao,\nDscho\n"},{"id":"58616","messageId":"Pine.LNX.4.64.0711062336510.4362@racer.site","threadId":"10650","inReplyTo":"Pine.LNX.4.64.0711062335150.4362@racer.site","subject":"[PATCH 1/2] interpolate.[ch]: Add a function to find which interpolations are active.","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2007-11-06T23:38:03Z","receivedAt":"2007-11-06T23:38:03Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"\nSome substitutions require pretty expensive operations.  So it makes\nsense to find out which are needed to begin with.  Call\ninterp_find_active() to set the new \"active\" field in the interpolation\ntable.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n\n\tThis is the patch that Rene proposed, but squashed into my earlier\n\tpatch.\n\n interpolate.c |   20 ++++++++++++++++++++\n interpolate.h |    3 +++\n 2 files changed, 23 insertions(+), 0 deletions(-)\n\ndiff --git a/interpolate.c b/interpolate.c\nindex 6ef53f2..bbd89bb 100644\n--- a/interpolate.c\n+++ b/interpolate.c\n@@ -102,3 +102,23 @@ unsigned long interpolate(char *result, unsigned long reslen,\n \t\t*dest = '\\0';\n \treturn newlen;\n }\n+\n+void interp_find_active(const char *orig,\n+\t\tstruct interp *interps, int ninterps)\n+{\n+\tchar c;\n+\tint i;\n+\n+\tfor (i = 0; i < ninterps; i++)\n+\t\tinterps[i].active = 0;\n+\n+\twhile ((c = *(orig++)))\n+\t\tif (c == '%')\n+\t\t\t/* Try to match an interpolation string. */\n+\t\t\tfor (i = 0; i < ninterps; i++)\n+\t\t\t\tif (!prefixcmp(orig, interps[i].name + 1)) {\n+\t\t\t\t\tinterps[i].active = 1;\n+\t\t\t\t\torig += strlen(interps[i].name + 1);\n+\t\t\t\t\tbreak;\n+\t\t\t\t}\n+}\ndiff --git a/interpolate.h b/interpolate.h\nindex 77407e6..d23531a 100644\n--- a/interpolate.h\n+++ b/interpolate.h\n@@ -14,6 +14,7 @@\n struct interp {\n \tconst char *name;\n \tchar *value;\n+\tint active:1;\n };\n \n extern void interp_set_entry(struct interp *table, int slot, const char *value);\n@@ -22,5 +23,7 @@ extern void interp_clear_table(struct interp *table, int ninterps);\n extern unsigned long interpolate(char *result, unsigned long reslen,\n \t\t\t\t const char *orig,\n \t\t\t\t const struct interp *interps, int ninterps);\n+extern void interp_find_active(const char *orig,\n+\t\t\t\tstruct interp *interps, int ninterps);\n \n #endif /* INTERPOLATE_H */\n-- \n1.5.3.5.1597.g7191\n"},{"id":"58617","messageId":"Pine.LNX.4.64.0711062338170.4362@racer.site","threadId":"10650","inReplyTo":"Pine.LNX.4.64.0711062335150.4362@racer.site","subject":"[PATCH 2/2] pretty=format: Avoid some expensive calculations when not needed","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2007-11-06T23:38:25Z","receivedAt":"2007-11-06T23:38:25Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"\nUse the new function interp_find_active() to avoid calculating the\nunique hash names, and other things, when they are not even asked for.\n\nUnfortunately, we cannot reuse the result of that function, which\nwould be cleaner: there are more users than just git log.  Most\nnotably, git-archive with \"$Format:...$\" substitution.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n pretty.c |   55 ++++++++++++++++++++++++++++++++++---------------------\n 1 files changed, 34 insertions(+), 21 deletions(-)\n\ndiff --git a/pretty.c b/pretty.c\nindex 490cede..590de4c 100644\n--- a/pretty.c\n+++ b/pretty.c\n@@ -394,6 +394,8 @@ void format_commit_message(const struct commit *commit,\n \tenum { HEADER, SUBJECT, BODY } state;\n \tconst char *msg = commit->buffer;\n \n+\tinterp_find_active(format, table, ARRAY_SIZE(table));\n+\n \tif (ILEFT_RIGHT + 1 != ARRAY_SIZE(table))\n \t\tdie(\"invalid interp table!\");\n \n@@ -407,12 +409,18 @@ void format_commit_message(const struct commit *commit,\n \t/* these depend on the commit */\n \tif (!commit->object.parsed)\n \t\tparse_object(commit->object.sha1);\n-\tinterp_set_entry(table, IHASH, sha1_to_hex(commit->object.sha1));\n-\tinterp_set_entry(table, IHASH_ABBREV,\n+\tif (table[IHASH].active)\n+\t\tinterp_set_entry(table, IHASH,\n+\t\t\t\tsha1_to_hex(commit->object.sha1));\n+\tif (table[IHASH_ABBREV].active)\n+\t\tinterp_set_entry(table, IHASH_ABBREV,\n \t\t\tfind_unique_abbrev(commit->object.sha1,\n \t\t\t\tDEFAULT_ABBREV));\n-\tinterp_set_entry(table, ITREE, sha1_to_hex(commit->tree->object.sha1));\n-\tinterp_set_entry(table, ITREE_ABBREV,\n+\tif (table[ITREE].active)\n+\t\tinterp_set_entry(table, ITREE,\n+\t\t\t\tsha1_to_hex(commit->tree->object.sha1));\n+\tif (table[ITREE_ABBREV].active)\n+\t\tinterp_set_entry(table, ITREE_ABBREV,\n \t\t\tfind_unique_abbrev(commit->tree->object.sha1,\n \t\t\t\tDEFAULT_ABBREV));\n \tinterp_set_entry(table, ILEFT_RIGHT,\n@@ -422,22 +430,27 @@ void format_commit_message(const struct commit *commit,\n \t\t\t ? \"<\"\n \t\t\t : \">\");\n \n-\tparents[1] = 0;\n-\tfor (i = 0, p = commit->parents;\n-\t\t\tp && i < sizeof(parents) - 1;\n-\t\t\tp = p->next)\n-\t\ti += snprintf(parents + i, sizeof(parents) - i - 1, \" %s\",\n-\t\t\tsha1_to_hex(p->item->object.sha1));\n-\tinterp_set_entry(table, IPARENTS, parents + 1);\n-\n-\tparents[1] = 0;\n-\tfor (i = 0, p = commit->parents;\n-\t\t\tp && i < sizeof(parents) - 1;\n-\t\t\tp = p->next)\n-\t\ti += snprintf(parents + i, sizeof(parents) - i - 1, \" %s\",\n-\t\t\tfind_unique_abbrev(p->item->object.sha1,\n-\t\t\t\tDEFAULT_ABBREV));\n-\tinterp_set_entry(table, IPARENTS_ABBREV, parents + 1);\n+\tif (table[IPARENTS].active) {\n+\t\tparents[1] = 0;\n+\t\tfor (i = 0, p = commit->parents;\n+\t\t\t\tp && i < sizeof(parents) - 1;\n+\t\t\t\tp = p->next)\n+\t\t\ti += snprintf(parents + i, sizeof(parents) - i - 1,\n+\t\t\t\t\" %s\", sha1_to_hex(p->item->object.sha1));\n+\t\tinterp_set_entry(table, IPARENTS, parents + 1);\n+\t}\n+\n+\tif (table[IPARENTS_ABBREV].active) {\n+\t\tparents[1] = 0;\n+\t\tfor (i = 0, p = commit->parents;\n+\t\t\t\tp && i < sizeof(parents) - 1;\n+\t\t\t\tp = p->next)\n+\t\t\ti += snprintf(parents + i, sizeof(parents) - i - 1,\n+\t\t\t\t\" %s\",\n+\t\t\t\tfind_unique_abbrev(p->item->object.sha1,\n+\t\t\t\t\tDEFAULT_ABBREV));\n+\t\tinterp_set_entry(table, IPARENTS_ABBREV, parents + 1);\n+\t}\n \n \tfor (i = 0, state = HEADER; msg[i] && state < BODY; i++) {\n \t\tint eol;\n@@ -464,7 +477,7 @@ void format_commit_message(const struct commit *commit,\n \t\t\t\txmemdupz(msg + i + 9, eol - i - 9);\n \t\ti = eol;\n \t}\n-\tif (msg[i])\n+\tif (table[IBODY].active && msg[i])\n \t\ttable[IBODY].value = xstrdup(msg + i);\n \n \tlen = interpolate(sb->buf + sb->len, strbuf_avail(sb),\n-- \n1.5.3.5.1597.g7191\n"},{"id":"58618","messageId":"Pine.LNX.4.64.0711062343050.4362@racer.site","threadId":"10650","inReplyTo":"4730F5FA.3030705@lsrfire.ath.cx","subject":"Re: [PATCH 3/3] pretty=format: Avoid some expensive calculations when not needed","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2007-11-06T23:45:41Z","receivedAt":"2007-11-06T23:45:41Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi,\n\nOn Wed, 7 Nov 2007, René Scharfe wrote:\n\n> By the way, the more intrusive surgery required when using strbuf_expand()\n> leads to even faster operation.  Here my measurements of most of Paul's\n> test cases (best of three runs):\n>\n> [...]\n\nimpressive timings.  Although I wonder where the time comes from, as the \nother substitutions should not be _that_ expensive.\n\nIn any case, your approach seems much more sensible, now that we have \nstrbuf.\n\n> diff --git a/strbuf.h b/strbuf.h\n> index cd7f295..95071d5 100644\n> --- a/strbuf.h\n> +++ b/strbuf.h\n> @@ -102,6 +102,9 @@ static inline void strbuf_addbuf(struct strbuf *sb, struct strbuf *sb2) {\n>  \tstrbuf_add(sb, sb2->buf, sb2->len);\n>  }\n>  \n> +typedef void (*expand_fn_t) (struct strbuf *sb, const char *placeholder, void *context);\n> +extern void strbuf_expand(struct strbuf *sb, const char *fmt, const char **placeholders, expand_fn_t fn, void *context);\n\nI wonder if it would even faster (but maybe not half as readable) if \nexpand_fd_t got the placeholder_index instead of the placeholder.\n\nCiao,\nDscho"},{"id":"58621","messageId":"20071107001112.GD4382@artemis.corp","threadId":"10650","inReplyTo":"4730F5FA.3030705@lsrfire.ath.cx","subject":"Re: [PATCH 3/3] pretty=format: Avoid some expensive calculations when not needed","fromName":"Pierre Habouzit","fromEmail":"madcoder@debian.org","sentAt":"2007-11-07T00:11:12Z","receivedAt":"2007-11-07T00:11:12Z","isPatch":true,"sender":{"key":"madcoder@debian.org","avatar":"https://avatars.githubusercontent.com/u/44708?v=4"},"body":"On Tue, Nov 06, 2007 at 11:17:14PM +0000, René Scharfe wrote:\n> I haven't seen any comments on strbuf_expand.  Is it too far out?\n> Here it is again, adjusted for current master and with the changes\n> to strbuf.[ch] coming first:\n\n  I have one.\n\n>  strbuf.c |   22 +++++\n>  strbuf.h |    3 \n>  pretty.c |  276 ++++++++++++++++++++++++++++++++++-----------------------------\n>  3 files changed, 178 insertions(+), 123 deletions(-)\n> \n> diff --git a/strbuf.c b/strbuf.c\n> index f4201e1..b71da99 100644\n> --- a/strbuf.c\n> +++ b/strbuf.c\n> @@ -129,6 +129,28 @@ void strbuf_addf(struct strbuf *sb, const char *fmt, ...)\n>  \tstrbuf_setlen(sb, sb->len + len);\n>  }\n>  \n> +void strbuf_expand(struct strbuf *sb, const char *fmt,\n> +                   const char **placeholders, expand_fn_t fn, void *context)\n> +{\n> +\tchar c;\n> +\tconst char **p;\n> +\n> +\twhile ((c = *fmt++)) {\n> +\t\tif (c != '%') {\n> +\t\t\tstrbuf_addch(sb, c);\n> +\t\t\tcontinue;\n> +\t\t}\n\nstrbuf_addch is pretty inneficient as it puts NULs each time. rather\ndo that (sketchy) :\n\n{\n    for (;;) {\n        const char *percent = strchr(fmt, '%');\n        if (!percent)\n            break;\n        strbuf_add(sb, fmt, percent - fmt);\n        fmt = percent + 1;\n\n        /* do your stuff */\n    }\n    strbuf_addstr(sb, fmt);\n}\n\nOf course it's a detail as formats will probably be short. But it's a good\nexample to show to people wanting to write new strbuf functions.\n\nThis nitpicking apart, the timings are impressive.\n\n-- \n·O·  Pierre Habouzit\n··O                                                madcoder@debian.org\nOOO                                                http://www.madism.org\n"},{"id":"58622","messageId":"20071107001458.GE4382@artemis.corp","threadId":"10650","inReplyTo":"20071107001112.GD4382@artemis.corp","subject":"Re: [PATCH 3/3] pretty=format: Avoid some expensive calculations when not needed","fromName":"Pierre Habouzit","fromEmail":"madcoder@debian.org","sentAt":"2007-11-07T00:14:58Z","receivedAt":"2007-11-07T00:14:58Z","isPatch":true,"sender":{"key":"madcoder@debian.org","avatar":"https://avatars.githubusercontent.com/u/44708?v=4"},"body":"On Wed, Nov 07, 2007 at 12:11:12AM +0000, Pierre Habouzit wrote:\n> On Tue, Nov 06, 2007 at 11:17:14PM +0000, René Scharfe wrote:\n> > I haven't seen any comments on strbuf_expand.  Is it too far out?\n> > Here it is again, adjusted for current master and with the changes\n> > to strbuf.[ch] coming first:\n> \n>   I have one.\n> \n> >  strbuf.c |   22 +++++\n> >  strbuf.h |    3 \n> >  pretty.c |  276 ++++++++++++++++++++++++++++++++++-----------------------------\n> >  3 files changed, 178 insertions(+), 123 deletions(-)\n> > \n> > diff --git a/strbuf.c b/strbuf.c\n> > index f4201e1..b71da99 100644\n> > --- a/strbuf.c\n> > +++ b/strbuf.c\n> > @@ -129,6 +129,28 @@ void strbuf_addf(struct strbuf *sb, const char *fmt, ...)\n> >  \tstrbuf_setlen(sb, sb->len + len);\n> >  }\n> >  \n> > +void strbuf_expand(struct strbuf *sb, const char *fmt,\n> > +                   const char **placeholders, expand_fn_t fn, void *context)\n> > +{\n> > +\tchar c;\n> > +\tconst char **p;\n> > +\n> > +\twhile ((c = *fmt++)) {\n> > +\t\tif (c != '%') {\n> > +\t\t\tstrbuf_addch(sb, c);\n> > +\t\t\tcontinue;\n> > +\t\t}\n> \n> strbuf_addch is pretty inneficient as it puts NULs each time. rather\n> do that (sketchy) :\n> \n> {\n>     for (;;) {\n>         const char *percent = strchr(fmt, '%');\n>         if (!percent)\n>             break;\n>         strbuf_add(sb, fmt, percent - fmt);\n>         fmt = percent + 1;\n> \n>         /* do your stuff */\n>     }\n>     strbuf_addstr(sb, fmt);\n> }\n\n\nOr if we are at this level of micro-optimization:\n\n{\n    const char *percent = strchrnul(fmt, '%');\n    while (*percent) {\n        strbuf_add(sb, fmt, percent - fmt);\n        fmt = percent + 1;\n\n        /* do your stuff */\n\n        percent = strchrnul(fmt, '%');\n    }\n    strbuf_add(sb, fmt, percent - fmt);\n}\n\n\nWhich would require strchrnul, but it's trivial compat/ material for sure.\n\n\n-- \n·O·  Pierre Habouzit\n··O                                                madcoder@debian.org\nOOO                                                http://www.madism.org\n"},{"id":"58721","messageId":"7vhcjxaire.fsf@gitster.siamese.dyndns.org","threadId":"10650","inReplyTo":"4730F5FA.3030705@lsrfire.ath.cx","subject":"Re: [PATCH 3/3] pretty=format: Avoid some expensive calculations when not needed","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2007-11-07T20:43:49Z","receivedAt":"2007-11-07T20:43:49Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"René Scharfe <rene.scharfe@lsrfire.ath.cx> writes:\n\n> I haven't seen any comments on strbuf_expand.  Is it too far out?\n> Here it is again, adjusted for current master and with the changes\n> to strbuf.[ch] coming first:\n\nNumbers talk ;-).\n\nIn your previous round, you alluded that the strbuf_expand()\ninterface could allow caching of the return value of fn(), but I\ndo not think strbuf_expand() in this patch has anything to\ndirectly support that notion.\n\nNor I would expect to --- fn() could keep the really expensive\ninformation cached, keyed with context value, if it wanted to,\nbut in practice for the purpose of format_commit_item() I do not\noffhand see anything cacheable and reusable, unless the user did\nstupid things (e.g. use more than one %h in the format string).\n\nI added a few paragraphs to describe the API in the commit log\nmessage, and rewrote \"# master\" to \"(master)\" etc.\n\n-- >8 --\npretty=format: Avoid some expensive calculations when not needed\n\nThis rewrites the custom format prettyprinter for commit objects\nby introducing a new strbuf API function \"strbuf_expand()\".\n\nThe function takes a format string, list of placeholder strings,\na user supplied function 'fn', and an opaque pointer 'context'\nto tell 'fn' what thingy to operate on.\n\nThe function 'fn' is expected to accept a strbuf, a parsed\nplaceholder string and the 'context' pointer, and append the\ninterpolated value for the 'context' thingy, according to the\nformat specified by the placeholder.\n\nHere my measurements of most of Paul's test cases (best of\nthree runs):\n\n(master)\n$ time git log --pretty=oneline >/dev/null\n\n...\n"},{"id":"58758","messageId":"47324810.2010507@lsrfire.ath.cx","threadId":"10650","inReplyTo":"Pine.LNX.4.64.0711062343050.4362@racer.site","subject":"Re: [PATCH 3/3] pretty=format: Avoid some expensive calculations when not needed","fromName":"René Scharfe","fromEmail":"rene.scharfe@lsrfire.ath.cx","sentAt":"2007-11-07T23:19:44Z","receivedAt":"2007-11-07T23:19:44Z","isPatch":true,"sender":{"key":"l.s.r@web.de","avatar":"https://avatars.githubusercontent.com/u/26122331?v=4"},"body":"Johannes Schindelin schrieb:\n> Hi,\n> \n> On Wed, 7 Nov 2007, René Scharfe wrote:\n> \n>> By the way, the more intrusive surgery required when using strbuf_expand()\n>> leads to even faster operation.  Here my measurements of most of Paul's\n>> test cases (best of three runs):\n>>\n>> [...]\n> \n> impressive timings.  Although I wonder where the time comes from, as the \n> other substitutions should not be _that_ expensive.\n\nI haven't run a profiler, but my two suspects are the malloc()s and\nfree()s done by interp_set_entry(), and the fact that\nformat_commit_message() calls interpolate() twice.\n\n> In any case, your approach seems much more sensible, now that we have \n> strbuf.\n> \n>> diff --git a/strbuf.h b/strbuf.h\n>> index cd7f295..95071d5 100644\n>> --- a/strbuf.h\n>> +++ b/strbuf.h\n>> @@ -102,6 +102,9 @@ static inline void strbuf_addbuf(struct strbuf *sb, struct strbuf *sb2) {\n>>  \tstrbuf_add(sb, sb2->buf, sb2->len);\n>>  }\n>>  \n>> +typedef void (*expand_fn_t) (struct strbuf *sb, const char *placeholder, void *context);\n>> +extern void strbuf_expand(struct strbuf *sb, const char *fmt, const char **placeholders, expand_fn_t fn, void *context);\n> \n> I wonder if it would even faster (but maybe not half as readable) if \n> expand_fd_t got the placeholder_index instead of the placeholder.\n\nI doubt it.  All this would save is one pointer dereference per\nplaceholder.  I haven't tried and measured this, though.\n\nRené\n"},{"id":"58759","messageId":"4732487A.4050100@lsrfire.ath.cx","threadId":"10650","inReplyTo":"20071107001458.GE4382@artemis.corp","subject":"Re: [PATCH 3/3] pretty=format: Avoid some expensive calculations when not needed","fromName":"René Scharfe","fromEmail":"rene.scharfe@lsrfire.ath.cx","sentAt":"2007-11-07T23:21:30Z","receivedAt":"2007-11-07T23:21:30Z","isPatch":true,"sender":{"key":"l.s.r@web.de","avatar":"https://avatars.githubusercontent.com/u/26122331?v=4"},"body":"Pierre Habouzit schrieb:\n> {\n>     const char *percent = strchrnul(fmt, '%');\n>     while (*percent) {\n>         strbuf_add(sb, fmt, percent - fmt);\n>         fmt = percent + 1;\n> \n>         /* do your stuff */\n> \n>         percent = strchrnul(fmt, '%');\n>     }\n>     strbuf_add(sb, fmt, percent - fmt);\n> }\n> \n> \n> Which would require strchrnul, but it's trivial compat/ material for sure.\n\nGrepping through the source I see several places that can be simplified\nby converting them to strchrnul(), so I think introducing this GNU\nextension is a good idea in any case.\n\nUsing strchr()/strchrnul() instead of strbuf_addch()'ing is sensible, of\ncourse.  I don't like the duplicate code in your sketch above, though.\nI'll try to look into it later today.\n\nThanks!\nRené\n"},{"id":"58762","messageId":"20071107233145.GB5483@artemis.corp","threadId":"10650","inReplyTo":"4732487A.4050100@lsrfire.ath.cx","subject":"Re: [PATCH 3/3] pretty=format: Avoid some expensive calculations when not needed","fromName":"Pierre Habouzit","fromEmail":"madcoder@debian.org","sentAt":"2007-11-07T23:31:45Z","receivedAt":"2007-11-07T23:31:45Z","isPatch":true,"sender":{"key":"madcoder@debian.org","avatar":"https://avatars.githubusercontent.com/u/44708?v=4"},"body":"On Wed, Nov 07, 2007 at 11:21:30PM +0000, René Scharfe wrote:\n> Pierre Habouzit schrieb:\n> > {\n> >     const char *percent = strchrnul(fmt, '%');\n> >     while (*percent) {\n> >         strbuf_add(sb, fmt, percent - fmt);\n> >         fmt = percent + 1;\n> > \n> >         /* do your stuff */\n> > \n> >         percent = strchrnul(fmt, '%');\n> >     }\n> >     strbuf_add(sb, fmt, percent - fmt);\n> > }\n> > \n> > \n> > Which would require strchrnul, but it's trivial compat/ material for sure.\n> \n> Grepping through the source I see several places that can be simplified\n> by converting them to strchrnul(), so I think introducing this GNU\n> extension is a good idea in any case.\n\n  Yes, strchrnul is _very_ useful for parsing.\n\n> Using strchr()/strchrnul() instead of strbuf_addch()'ing is sensible, of\n> course.  I don't like the duplicate code in your sketch above, though.\n> I'll try to look into it later today.\n\nWell you can alternatively write it this way of course:\n\nfor (;;) {\n    const char *percent = strchrnul(fmt, '%');\n    strbuf_add(sb, fmt, percent - fmt);\n    if (!*percent)\n        break /* or return probably */;\n    fmt = percent + 1;\n\n    /* do your stuff */\n}\n\n-- \n·O·  Pierre Habouzit\n··O                                                madcoder@debian.org\nOOO                                                http://www.madism.org\n"},{"id":"58772","messageId":"Pine.LNX.4.64.0711080012510.4362@racer.site","threadId":"10650","inReplyTo":"47324810.2010507@lsrfire.ath.cx","subject":"Re: [PATCH 3/3] pretty=format: Avoid some expensive calculations when not needed","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2007-11-08T00:14:51Z","receivedAt":"2007-11-08T00:14:51Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi,\n\nOn Thu, 8 Nov 2007, René Scharfe wrote:\n\n> Johannes Schindelin schrieb:\n> \n> > On Wed, 7 Nov 2007, René Scharfe wrote:\n> > \n> >> By the way, the more intrusive surgery required when using \n> >> strbuf_expand() leads to even faster operation.  Here my measurements \n> >> of most of Paul's test cases (best of three runs):\n> >>\n> >> [...]\n> > \n> > impressive timings.  Although I wonder where the time comes from, as \n> > the other substitutions should not be _that_ expensive.\n> \n> I haven't run a profiler, but my two suspects are the malloc()s and \n> free()s done by interp_set_entry(), and the fact that \n> format_commit_message() calls interpolate() twice.\n\nBut of course! *slapsthehead*\n\n> > In any case, your approach seems much more sensible, now that we have \n> > strbuf.\n> > \n> >> diff --git a/strbuf.h b/strbuf.h\n> >> index cd7f295..95071d5 100644\n> >> --- a/strbuf.h\n> >> +++ b/strbuf.h\n> >> @@ -102,6 +102,9 @@ static inline void strbuf_addbuf(struct strbuf *sb, struct strbuf *sb2) {\n> >>  \tstrbuf_add(sb, sb2->buf, sb2->len);\n> >>  }\n> >>  \n> >> +typedef void (*expand_fn_t) (struct strbuf *sb, const char *placeholder, void *context);\n> >> +extern void strbuf_expand(struct strbuf *sb, const char *fmt, const char **placeholders, expand_fn_t fn, void *context);\n> > \n> > I wonder if it would even faster (but maybe not half as readable) if \n> > expand_fd_t got the placeholder_index instead of the placeholder.\n> \n> I doubt it.  All this would save is one pointer dereference per\n> placeholder.  I haven't tried and measured this, though.\n\nMy thinko.  I thought that you save one prefixcmp(), but strbuf_expand() \noffloads that to the callback function.\n\nThanks,\nDscho\n"},{"id":"58960","messageId":"4733AE8F.4080403@lsrfire.ath.cx","threadId":"10650","inReplyTo":"7vhcjxaire.fsf@gitster.siamese.dyndns.org","subject":"Re: [PATCH 3/3] pretty=format: Avoid some expensive calculations when not needed","fromName":"René Scharfe","fromEmail":"rene.scharfe@lsrfire.ath.cx","sentAt":"2007-11-09T00:49:19Z","receivedAt":"2007-11-09T00:49:19Z","isPatch":true,"sender":{"key":"l.s.r@web.de","avatar":"https://avatars.githubusercontent.com/u/26122331?v=4"},"body":"Junio C Hamano schrieb:\n> René Scharfe <rene.scharfe@lsrfire.ath.cx> writes:\n> \n>> I haven't seen any comments on strbuf_expand.  Is it too far out?\n>> Here it is again, adjusted for current master and with the changes\n>> to strbuf.[ch] coming first:\n> \n> Numbers talk ;-).\n\n:-)  I just hope someone else has compared the times, too..\n\n> In your previous round, you alluded that the strbuf_expand()\n> interface could allow caching of the return value of fn(), but I\n> do not think strbuf_expand() in this patch has anything to\n> directly support that notion.\n> \n> Nor I would expect to --- fn() could keep the really expensive\n> information cached, keyed with context value, if it wanted to,\n> but in practice for the purpose of format_commit_item() I do not\n> offhand see anything cacheable and reusable, unless the user did\n> stupid things (e.g. use more than one %h in the format string).\n\nYes, that's what I arrived at, too, when I actually wrote and used\nthe function.\n\n> I added a few paragraphs to describe the API in the commit log\n> message, and rewrote \"# master\" to \"(master)\" etc.\n\nThanks!  I'll send a slightly enhanced version in a two-patch\nseries -- with an actual commit message, including your API\ndesciption -- in just a few seconds.\n\nRené\n"}]}