{"thread":{"id":"26896","subject":"[PATCH/RFC 0/9] add long forms for format placeholders","startedAt":"2011-03-28T23:17:22Z","lastAt":"2011-03-29T07:27:00Z","messageCount":14,"participants":["Will Palmer","Junio C Hamano","Michael J Gruber"],"isPatch":true,"patchVersion":1,"patchTotal":9},"messages":[{"id":"164503","messageId":"1301354251-23380-1-git-send-email-wmpalmer@gmail.com","threadId":"26896","inReplyTo":null,"subject":"[PATCH/RFC 0/9] add long forms for format placeholders","fromName":"Will Palmer","fromEmail":"wmpalmer@gmail.com","sentAt":"2011-03-28T23:17:22Z","receivedAt":"2011-03-28T23:17:22Z","isPatch":true,"sender":{"key":"wmpalmer@gmail.com","avatar":"https://avatars.githubusercontent.com/u/357044?v=4"},"body":"I've been kicking around this series for a while now as part of a larger\neffort of refactoring the pretty formats. A recent discussion on the\nlist has lead me to believe that this smaller subset may be of use\nsooner, rather than later.\n\nThis series attempts to add \"long forms\" for common format placeholders\nin the \"git log\" family of commands, making the way for yet more\nplaceholders to be added without needing to worry too much about the\nincreasingly limited set of available one-letter mnemonics. It also\nmoves towards the possibility of eventual unification with the format\noptions in for-each-ref.\n\nFor example: after this series, in place of %ad, %at, etc you would be\nable to use %(authordate), %(authordate:unix), etc.\n\nThere are some changes in this series which may not seem justified at\nthis point, most noticeably the splitting of user-defined format\nprocessing into separate \"parse\" and \"format\" stages. This is done to\nallow for more-complicated formats to be defined, especially conditional\nformats. Examples of these are not yet included in this series, because\nthis series only includes those parts related to allowing for the\n\"long form\" placeholders. If this series needs to go through a few\ncycles before it's ready for inclusion, examples more-complicated\nformats may find their way into the series.\n\nThe primary purpose of the series is to allow new placeholders to be\ndefined without worrying about the increasingly diminishing set of\navailable one-or-two-letter mnemonics.\n\nCc: Jeff King <peff@peff.net>\nCc: Junio C Hamano <gitster@pobox.com>\n\nWill Palmer (9):\n  mention --date=raw in rev-list and blame help\n  add support for --date=unix to complement %at\n  interpret %C(invalid) as we would %%C(invalid)\n  add sanity length check to format_person_part\n  refactor pretty.c into \"parse\" and \"format\" steps\n  add long-form %(wrap:...) for %w(...)\n  add long form %(color:...) for %C(...)\n  add long forms %(authordate) and %(committerdate)\n  add long forms for author and committer identity\n\n .gitignore                         |    1 +\n Documentation/blame-options.txt    |    4 +-\n Documentation/git-rev-list.txt     |    2 +-\n Documentation/pretty-formats.txt   |   51 ++-\n Documentation/rev-list-options.txt |    4 +-\n Makefile                           |    1 +\n builtin/blame.c                    |    3 +\n cache.h                            |    5 +-\n color.c                            |   17 +-\n color.h                            |    1 +\n commit.h                           |   84 ++++\n date.c                             |   61 ++-\n pretty.c                           |  947 +++++++++++++++++++++++++++---------\n test-pretty.c                      |  250 ++++++++++\n 14 files changed, 1164 insertions(+), 267 deletions(-)\n create mode 100644 test-pretty.c\n\n-- \n1.7.4.2\n"},{"id":"164505","messageId":"1301354251-23380-2-git-send-email-wmpalmer@gmail.com","threadId":"26896","inReplyTo":"1301354251-23380-1-git-send-email-wmpalmer@gmail.com","subject":"[PATCH/RFC 1/9] mention --date=raw in rev-list and blame help","fromName":"Will Palmer","fromEmail":"wmpalmer@gmail.com","sentAt":"2011-03-28T23:17:23Z","receivedAt":"2011-03-28T23:17:23Z","isPatch":true,"sender":{"key":"wmpalmer@gmail.com","avatar":"https://avatars.githubusercontent.com/u/357044?v=4"},"body":"Very trivial: the documentation for \"blame\" and \"rev-list\" failed to\nmention the --date=raw option in the summary of the --date option.\nHere we correct that.\n\nSigned-off-by: Will Palmer <wmpalmer@gmail.com>\n---\n Documentation/blame-options.txt |    4 ++--\n Documentation/git-rev-list.txt  |    2 +-\n 2 files changed, 3 insertions(+), 3 deletions(-)\n\ndiff --git a/Documentation/blame-options.txt b/Documentation/blame-options.txt\nindex 16e3c68..e11a3da 100644\n--- a/Documentation/blame-options.txt\n+++ b/Documentation/blame-options.txt\n@@ -72,8 +72,8 @@ of lines before or after the line given by <start>.\n \n --date <format>::\n \tThe value is one of the following alternatives:\n-\t{relative,local,default,iso,rfc,short}. If --date is not\n-\tprovided, the value of the blame.date config variable is\n+\t{relative,local,default,iso,rfc,short,raw}. If --date is\n+\tnot provided, the value of the blame.date config variable is\n \tused. If the blame.date config variable is also not set, the\n \tiso format is used. For more information, See the discussion\n \tof the --date option at linkgit:git-log[1].\ndiff --git a/Documentation/git-rev-list.txt b/Documentation/git-rev-list.txt\nindex 8e1e329..ce3692c 100644\n--- a/Documentation/git-rev-list.txt\n+++ b/Documentation/git-rev-list.txt\n@@ -37,7 +37,7 @@ SYNOPSIS\n \t     [ \\--regexp-ignore-case | -i ]\n \t     [ \\--extended-regexp | -E ]\n \t     [ \\--fixed-strings | -F ]\n-\t     [ \\--date=(local|relative|default|iso|rfc|short) ]\n+\t     [ \\--date=(local|relative|default|iso|rfc|short|raw) ]\n \t     [ [\\--objects | \\--objects-edge] [ \\--unpacked ] ]\n \t     [ \\--pretty | \\--header ]\n \t     [ \\--bisect ]\n-- \n1.7.4.2\n"},{"id":"164504","messageId":"1301354251-23380-3-git-send-email-wmpalmer@gmail.com","threadId":"26896","inReplyTo":"1301354251-23380-1-git-send-email-wmpalmer@gmail.com","subject":"[PATCH/RFC 2/9] add support for --date=unix to complement %at","fromName":"Will Palmer","fromEmail":"wmpalmer@gmail.com","sentAt":"2011-03-28T23:17:24Z","receivedAt":"2011-03-28T23:17:24Z","isPatch":true,"sender":{"key":"wmpalmer@gmail.com","avatar":"https://avatars.githubusercontent.com/u/357044?v=4"},"body":"this adds support for --date=unix to the \"git log\" family of commands,\nwhich would cause %ad and %cd to act like %at and %ct respectively.\n\nSigned-off-by: Will Palmer <wmpalmer@gmail.com>\n---\n Documentation/blame-options.txt    |    2 +-\n Documentation/git-rev-list.txt     |    2 +-\n Documentation/rev-list-options.txt |    4 +++-\n builtin/blame.c                    |    3 +++\n cache.h                            |    3 ++-\n date.c                             |    7 +++++++\n 6 files changed, 17 insertions(+), 4 deletions(-)\n\ndiff --git a/Documentation/blame-options.txt b/Documentation/blame-options.txt\nindex e11a3da..2586606 100644\n--- a/Documentation/blame-options.txt\n+++ b/Documentation/blame-options.txt\n@@ -72,7 +72,7 @@ of lines before or after the line given by <start>.\n \n --date <format>::\n \tThe value is one of the following alternatives:\n-\t{relative,local,default,iso,rfc,short,raw}. If --date is\n+\t{relative,local,default,iso,rfc,short,unix,raw}. If --date is\n \tnot provided, the value of the blame.date config variable is\n \tused. If the blame.date config variable is also not set, the\n \tiso format is used. For more information, See the discussion\ndiff --git a/Documentation/git-rev-list.txt b/Documentation/git-rev-list.txt\nindex ce3692c..7e1d4bf 100644\n--- a/Documentation/git-rev-list.txt\n+++ b/Documentation/git-rev-list.txt\n@@ -37,7 +37,7 @@ SYNOPSIS\n \t     [ \\--regexp-ignore-case | -i ]\n \t     [ \\--extended-regexp | -E ]\n \t     [ \\--fixed-strings | -F ]\n-\t     [ \\--date=(local|relative|default|iso|rfc|short|raw) ]\n+\t     [ \\--date=(local|relative|default|iso|rfc|short|unix|raw) ]\n \t     [ [\\--objects | \\--objects-edge] [ \\--unpacked ] ]\n \t     [ \\--pretty | \\--header ]\n \t     [ \\--bisect ]\ndiff --git a/Documentation/rev-list-options.txt b/Documentation/rev-list-options.txt\nindex 9c47ad8..9ad68a7 100644\n--- a/Documentation/rev-list-options.txt\n+++ b/Documentation/rev-list-options.txt\n@@ -13,7 +13,7 @@ include::pretty-options.txt[]\n \n \tSynonym for `--date=relative`.\n \n---date=(relative|local|default|iso|rfc|short|raw)::\n+--date=(relative|local|default|iso|rfc|short|unix|raw)::\n \n \tOnly takes effect for dates shown in human-readable format, such\n \tas when using \"--pretty\". `log.date` config variable sets a default\n@@ -31,6 +31,8 @@ format, often found in E-mail messages.\n +\n `--date=short` shows only date but not time, in `YYYY-MM-DD` format.\n +\n+`--date=unix` shows the date as a UNIX timestamp, ignoring timezone.\n++\n `--date=raw` shows the date in the internal raw git format `%s %z` format.\n +\n `--date=default` shows timestamps in the original timezone\ndiff --git a/builtin/blame.c b/builtin/blame.c\nindex f6b03f7..c6e43a0 100644\n--- a/builtin/blame.c\n+++ b/builtin/blame.c\n@@ -2366,6 +2366,9 @@ parse_done:\n \tcase DATE_RAW:\n \t\tblame_date_width = sizeof(\"1161298804 -0700\");\n \t\tbreak;\n+\tcase DATE_UNIX:\n+\t\tblame_date_width = sizeof(\"1161298804\");\n+\t\tbreak;\n \tcase DATE_SHORT:\n \t\tblame_date_width = sizeof(\"2006-10-19\");\n \t\tbreak;\ndiff --git a/cache.h b/cache.h\nindex f2dabef..fa564fa 100644\n--- a/cache.h\n+++ b/cache.h\n@@ -814,7 +814,8 @@ enum date_mode {\n \tDATE_LOCAL,\n \tDATE_ISO8601,\n \tDATE_RFC2822,\n-\tDATE_RAW\n+\tDATE_RAW,\n+\tDATE_UNIX\n };\n \n const char *show_date(unsigned long time, int timezone, enum date_mode mode);\ndiff --git a/date.c b/date.c\nindex 00f9eb5..ce48220 100644\n--- a/date.c\n+++ b/date.c\n@@ -157,6 +157,11 @@ const char *show_date(unsigned long time, int tz, enum date_mode mode)\n \t\treturn timebuf;\n \t}\n \n+\tif (mode == DATE_UNIX) {\n+\t\tsnprintf(timebuf, sizeof(timebuf), \"%lu\", time);\n+\t\treturn timebuf;\n+\t}\n+\n \tif (mode == DATE_RELATIVE) {\n \t\tstruct timeval now;\n \t\tgettimeofday(&now, NULL);\n@@ -672,6 +677,8 @@ enum date_mode parse_date_format(const char *format)\n \t\treturn DATE_NORMAL;\n \telse if (!strcmp(format, \"raw\"))\n \t\treturn DATE_RAW;\n+\telse if (!strcmp(format, \"unix\"))\n+\t\treturn DATE_UNIX;\n \telse\n \t\tdie(\"unknown date format %s\", format);\n }\n-- \n1.7.4.2\n"},{"id":"164507","messageId":"1301354251-23380-4-git-send-email-wmpalmer@gmail.com","threadId":"26896","inReplyTo":"1301354251-23380-1-git-send-email-wmpalmer@gmail.com","subject":"[PATCH/RFC 3/9] interpret %C(invalid) as we would %%C(invalid)","fromName":"Will Palmer","fromEmail":"wmpalmer@gmail.com","sentAt":"2011-03-28T23:17:25Z","receivedAt":"2011-03-28T23:17:25Z","isPatch":true,"sender":{"key":"wmpalmer@gmail.com","avatar":"https://avatars.githubusercontent.com/u/357044?v=4"},"body":"%C(...) had the distinction of being the only format placeholder which\ncould trigger a die(), a side-effect of its ancestry in calling the\nexisting color_parse_mem(...). This was good, because it gave users an\nexplanation of what went wrong. It was also inconsistent, since every\nother \"unknown placeholder\" was interpreted as a literal.\n\nThis removes the inconsistency by interpreting %C(invalid) as a literal.\nPerhaps this is a step in the wrong direction.\n\nSigned-off-by: Will Palmer <wmpalmer@gmail.com>\n---\n color.c  |   17 +++++++++++------\n color.h  |    1 +\n pretty.c |    7 +++----\n 3 files changed, 15 insertions(+), 10 deletions(-)\n\ndiff --git a/color.c b/color.c\nindex 6a5a54e..9bc190b 100644\n--- a/color.c\n+++ b/color.c\n@@ -45,6 +45,13 @@ void color_parse(const char *value, const char *var, char *dst)\n void color_parse_mem(const char *value, int value_len, const char *var,\n \t\tchar *dst)\n {\n+\tif (color_parse_len(value, value_len, dst))\n+\t\treturn;\n+\tdie(\"bad color value '%.*s' for variable '%s'\", value_len, value, var);\n+}\n+\n+int color_parse_len(const char *value, int value_len, char *dst)\n+{\n \tconst char *ptr = value;\n \tint len = value_len;\n \tunsigned int attr = 0;\n@@ -53,7 +60,7 @@ void color_parse_mem(const char *value, int value_len, const char *var,\n \n \tif (!strncasecmp(value, \"reset\", len)) {\n \t\tstrcpy(dst, GIT_COLOR_RESET);\n-\t\treturn;\n+\t\treturn 1;\n \t}\n \n \t/* [fg [bg]] [attr]... */\n@@ -82,13 +89,13 @@ void color_parse_mem(const char *value, int value_len, const char *var,\n \t\t\t\tbg = val;\n \t\t\t\tcontinue;\n \t\t\t}\n-\t\t\tgoto bad;\n+\t\t\treturn 0;\n \t\t}\n \t\tval = parse_attr(word, wordlen);\n \t\tif (0 <= val)\n \t\t\tattr |= (1 << val);\n \t\telse\n-\t\t\tgoto bad;\n+\t\t\treturn 0;\n \t}\n \n \tif (attr || fg >= 0 || bg >= 0) {\n@@ -130,9 +137,7 @@ void color_parse_mem(const char *value, int value_len, const char *var,\n \t\t*dst++ = 'm';\n \t}\n \t*dst = 0;\n-\treturn;\n-bad:\n-\tdie(\"bad color value '%.*s' for variable '%s'\", value_len, value, var);\n+\treturn 1;\n }\n \n int git_config_colorbool(const char *var, const char *value, int stdout_is_tty)\ndiff --git a/color.h b/color.h\nindex 170ff40..084d85d 100644\n--- a/color.h\n+++ b/color.h\n@@ -59,6 +59,7 @@ int git_color_default_config(const char *var, const char *value, void *cb);\n \n int git_config_colorbool(const char *var, const char *value, int stdout_is_tty);\n void color_parse(const char *value, const char *var, char *dst);\n+int color_parse_len(const char *value, int len, char *dst);\n void color_parse_mem(const char *value, int len, const char *var, char *dst);\n __attribute__((format (printf, 3, 4)))\n int color_fprintf(FILE *fp, const char *color, const char *fmt, ...);\ndiff --git a/pretty.c b/pretty.c\nindex 8549934..d5a724f 100644\n--- a/pretty.c\n+++ b/pretty.c\n@@ -752,11 +752,10 @@ static size_t format_commit_one(struct strbuf *sb, const char *placeholder,\n \t\tif (placeholder[1] == '(') {\n \t\t\tconst char *end = strchr(placeholder + 2, ')');\n \t\t\tchar color[COLOR_MAXLEN];\n-\t\t\tif (!end)\n+\t\t\tif (!end || !color_parse_len(placeholder + 2,\n+\t\t\t\t\t\t     end - (placeholder + 2),\n+\t\t\t\t\t\t     color))\n \t\t\t\treturn 0;\n-\t\t\tcolor_parse_mem(placeholder + 2,\n-\t\t\t\t\tend - (placeholder + 2),\n-\t\t\t\t\t\"--pretty format\", color);\n \t\t\tstrbuf_addstr(sb, color);\n \t\t\treturn end - placeholder + 1;\n \t\t}\n-- \n1.7.4.2\n"},{"id":"164510","messageId":"1301354251-23380-5-git-send-email-wmpalmer@gmail.com","threadId":"26896","inReplyTo":"1301354251-23380-1-git-send-email-wmpalmer@gmail.com","subject":"[PATCH/RFC 4/9] add sanity length check to format_person_part","fromName":"Will Palmer","fromEmail":"wmpalmer@gmail.com","sentAt":"2011-03-28T23:17:26Z","receivedAt":"2011-03-28T23:17:26Z","isPatch":true,"sender":{"key":"wmpalmer@gmail.com","avatar":"https://avatars.githubusercontent.com/u/357044?v=4"},"body":"previously we were relying on the length check from ident.c, ie: relying\non having been given a good object to parse in the first place. If the\nobject should have triggered the \"Impossibly long personal identifier\"\ncheck at commit-time, for example, it would have resulted in an overflow\nhere.\n\nThis is another one which, due to the input constraints, would not have\nbeen a real-world problem for regular usage. I was able to cause an\noverflow by viewing the log of a commit with an impossibly-long Author,\ncreated via hash-object. This is admittedly a pretty far-fetched\nscenario, though it could potentially be considered a security issue.\n\nIn any case, the lack of it rubbed me the wrong way when I was\nrefactoring this section, and it is trivial to add the sanity check.\n\nSigned-off-by: Will Palmer <wmpalmer@gmail.com>\n---\n pretty.c |   20 ++++++++++++++------\n 1 files changed, 14 insertions(+), 6 deletions(-)\n\ndiff --git a/pretty.c b/pretty.c\nindex d5a724f..8a288f0 100644\n--- a/pretty.c\n+++ b/pretty.c\n@@ -449,6 +449,7 @@ static size_t format_person_part(struct strbuf *sb, char part,\n \tunsigned long date = 0;\n \tchar *ep;\n \tconst char *name_start, *name_end, *mail_start, *mail_end, *msg_end = msg+len;\n+\tsize_t name_len, mail_len;\n \tchar person_name[1024];\n \tchar person_mail[1024];\n \n@@ -469,29 +470,36 @@ static size_t format_person_part(struct strbuf *sb, char part,\n \tname_end = msg+end;\n \twhile (name_end > name_start && isspace(*(name_end-1)))\n \t\tname_end--;\n+\tname_len = name_end-name_start;\n+\tif (name_len >= sizeof(person_name))\n+\t\tgoto skip;\n \tmail_start = msg+end+1;\n \tmail_end = mail_start;\n \twhile (mail_end < msg_end && *mail_end != '>')\n \t\tmail_end++;\n+\tmail_len = mail_end-mail_start;\n+\tif (mail_len >= sizeof(person_mail))\n+\t\tgoto skip;\n \tif (mail_end == msg_end)\n \t\tgoto skip;\n \tend = mail_end-msg;\n \n \tif (part == 'N' || part == 'E') { /* mailmap lookup */\n-\t\tstrlcpy(person_name, name_start, name_end-name_start+1);\n-\t\tstrlcpy(person_mail, mail_start, mail_end-mail_start+1);\n+\t\t/* copy up to, and including, the end delimiter */\n+\t\tstrlcpy(person_name, name_start, name_len+1);\n+\t\tstrlcpy(person_mail, mail_start, mail_len+1);\n \t\tmailmap_name(person_mail, sizeof(person_mail), person_name, sizeof(person_name));\n \t\tname_start = person_name;\n-\t\tname_end = name_start + strlen(person_name);\n+\t\tname_len = strlen(person_name);\n \t\tmail_start = person_mail;\n-\t\tmail_end = mail_start +  strlen(person_mail);\n+\t\tmail_len = strlen(person_mail);\n \t}\n \tif (part == 'n' || part == 'N') {\t/* name */\n-\t\tstrbuf_add(sb, name_start, name_end-name_start);\n+\t\tstrbuf_add(sb, name_start, name_len);\n \t\treturn placeholder_len;\n \t}\n \tif (part == 'e' || part == 'E') {\t/* email */\n-\t\tstrbuf_add(sb, mail_start, mail_end-mail_start);\n+\t\tstrbuf_add(sb, mail_start, mail_len);\n \t\treturn placeholder_len;\n \t}\n \n-- \n1.7.4.2\n"},{"id":"164508","messageId":"1301354251-23380-6-git-send-email-wmpalmer@gmail.com","threadId":"26896","inReplyTo":"1301354251-23380-1-git-send-email-wmpalmer@gmail.com","subject":"[PATCH/RFC 5/9] refactor pretty.c into \"parse\" and \"format\" steps","fromName":"Will Palmer","fromEmail":"wmpalmer@gmail.com","sentAt":"2011-03-28T23:17:27Z","receivedAt":"2011-03-28T23:17:27Z","isPatch":true,"sender":{"key":"wmpalmer@gmail.com","avatar":"https://avatars.githubusercontent.com/u/357044?v=4"},"body":"Previously we parsed-out the user-defined \"pretty\" formats and outputted\nthe result in one pass, once for every commit. This was perfectly\nreasonable, as the performance penalties for doing so are almost\nnonexistant. However, it did make it cumbersome to define more-complex\nplaceholders.\n\nHere we split the bulk of pretty.c into separate \"parse\" and \"format\"\nsteps, adding the bulk of the back-end for more-complicated formatting\nrules in the process.\n\nThis also adds test-pretty, for debugging the parser. It prints out a\nhuman-readable dump of the parsed format.\n\nSigned-off-by: Will Palmer <wmpalmer@gmail.com>\n---\n .gitignore    |    1 +\n Makefile      |    1 +\n commit.h      |   86 ++++++\n pretty.c      |  802 ++++++++++++++++++++++++++++++++++++++++-----------------\n test-pretty.c |  232 +++++++++++++++++\n 5 files changed, 889 insertions(+), 233 deletions(-)\n create mode 100644 test-pretty.c\n\ndiff --git a/.gitignore b/.gitignore\nindex 3dd6ef7..c3b32e8 100644\n--- a/.gitignore\n+++ b/.gitignore\n@@ -173,6 +173,7 @@\n /test-obj-pool\n /test-parse-options\n /test-path-utils\n+/test-pretty\n /test-run-command\n /test-sha1\n /test-sigchain\ndiff --git a/Makefile b/Makefile\nindex 5c2b797..9cecf40 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -436,6 +436,7 @@ TEST_PROGRAMS_NEED_X += test-svn-fe\n TEST_PROGRAMS_NEED_X += test-treap\n TEST_PROGRAMS_NEED_X += test-index-version\n TEST_PROGRAMS_NEED_X += test-mktemp\n+TEST_PROGRAMS_NEED_X += test-pretty\n \n TEST_PROGRAMS = $(patsubst %,%$X,$(TEST_PROGRAMS_NEED_X))\n \ndiff --git a/commit.h b/commit.h\nindex 4198513..fff1225 100644\n--- a/commit.h\n+++ b/commit.h\n@@ -83,17 +83,103 @@ struct userformat_want {\n \tunsigned notes:1;\n };\n \n+enum format_part_type {\n+\tFORMAT_PART_UNKNOWN = 0,\n+\tFORMAT_PART_LITERAL,\n+\n+\tFORMAT_PART_COMMIT_HASH,\n+\tFORMAT_PART_COMMIT_HASH_ABBREV,\n+\tFORMAT_PART_PARENT_HASHES,\n+\tFORMAT_PART_PARENT_HASHES_ABBREV,\n+\tFORMAT_PART_TREE_HASH,\n+\tFORMAT_PART_TREE_HASH_ABBREV,\n+\n+\tFORMAT_PART_AUTHOR_NAME,\n+\tFORMAT_PART_AUTHOR_NAME_MAILMAP,\n+\tFORMAT_PART_AUTHOR_EMAIL,\n+\tFORMAT_PART_AUTHOR_EMAIL_MAILMAP,\n+\tFORMAT_PART_AUTHOR_DATE,\n+\tFORMAT_PART_COMMITTER_NAME,\n+\tFORMAT_PART_COMMITTER_NAME_MAILMAP,\n+\tFORMAT_PART_COMMITTER_EMAIL,\n+\tFORMAT_PART_COMMITTER_EMAIL_MAILMAP,\n+\tFORMAT_PART_COMMITTER_DATE,\n+\n+\tFORMAT_PART_DECORATE,\n+\tFORMAT_PART_ENCODING,\n+\tFORMAT_PART_SUBJECT,\n+\tFORMAT_PART_SUBJECT_SANITIZED,\n+\tFORMAT_PART_BODY,\n+\tFORMAT_PART_RAW_BODY,\n+\tFORMAT_PART_NOTES,\n+\n+\tFORMAT_PART_REFLOG_SELECTOR,\n+\tFORMAT_PART_REFLOG_SELECTOR_SHORT,\n+\tFORMAT_PART_REFLOG_SUBJECT,\n+\n+\tFORMAT_PART_MARK,\n+\tFORMAT_PART_WRAP\n+};\n+\n+enum format_part_magic {\n+\tNO_MAGIC,\n+\tADD_LF_BEFORE_NON_EMPTY,\n+\tDEL_LF_BEFORE_EMPTY,\n+\tADD_SP_BEFORE_NON_EMPTY\n+};\n+\n+enum format_arg_type {\n+\tFORMAT_ARG_UINT,\n+\tFORMAT_ARG_DATE_MODE\n+};\n+\n+struct format_arg {\n+\tenum format_arg_type type;\n+\tunion {\n+\t\tunsigned long uint;\n+\t\tenum date_mode dmode;\n+\t};\n+};\n+\n+struct format_part;\n+struct format_parts {\n+\tsize_t\t\t\tformat_len;\n+\tstruct userformat_want\twant;\n+\tsize_t\t\t\tlen;\n+\tsize_t\t\t\talloc;\n+\tstruct format_part\t*part;\n+};\n+\n+struct format_part {\n+\tenum format_part_type\ttype;\n+\tenum format_part_magic\tmagic;\n+\n+\tsize_t\t\t\tformat_len;\n+\n+\tsize_t\t\t\tliteral_len;\n+\tchar\t\t\t*literal;\n+\n+\tsize_t\t\t\targc;\n+\tsize_t\t\t\targs_alloc;\n+\tstruct format_arg\t*args;\n+};\n+\n extern int has_non_ascii(const char *text);\n struct rev_info; /* in revision.h, it circularly uses enum cmit_fmt */\n extern char *logmsg_reencode(const struct commit *commit,\n \t\t\t     const char *output_encoding);\n extern char *reencode_commit_message(const struct commit *commit,\n \t\t\t\t     const char **encoding_p);\n+extern struct format_parts *parse_format(const char *unparsed);\n extern void get_commit_format(const char *arg, struct rev_info *);\n extern void userformat_find_requirements(const char *fmt, struct userformat_want *w);\n extern void format_commit_message(const struct commit *commit,\n \t\t\t\t  const char *format, struct strbuf *sb,\n \t\t\t\t  const struct pretty_print_context *context);\n+extern void format_commit_message_parsed(const struct commit *commit,\n+\t\t\t\t\t const struct format_parts *parsed_format,\n+\t\t\t\t\t struct strbuf *sb,\n+\t\t\t\t\t const struct pretty_print_context *context);\n extern void pretty_print_commit(enum cmit_fmt fmt, const struct commit *commit,\n \t\t\t\tstruct strbuf *sb,\n \t\t\t\tconst struct pretty_print_context *context);\ndiff --git a/pretty.c b/pretty.c\nindex 8a288f0..cb02879 100644\n--- a/pretty.c\n+++ b/pretty.c\n@@ -10,7 +10,7 @@\n #include \"color.h\"\n #include \"reflog-walk.h\"\n \n-static char *user_format;\n+static struct format_parts *user_format;\n static struct cmt_fmt_map {\n \tconst char *name;\n \tenum cmit_fmt format;\n@@ -23,10 +23,408 @@ static size_t commit_formats_len;\n static size_t commit_formats_alloc;\n static struct cmt_fmt_map *find_commit_format(const char *sought);\n \n+#define WHITESPACE \" \\t\\r\\n\"\n+#define format_parts_alloc() \\\n+\t((struct format_parts*)xcalloc(1, sizeof(struct format_parts)))\n+#define format_part_alloc() \\\n+\t((struct format_part*)xcalloc(1, sizeof(struct format_part)))\n+static void format_part_free(struct format_part **part);\n+static void format_parts_free(struct format_parts **parts)\n+{\n+\tif((*parts)->part)\n+\t\tfree((*parts)->part);\n+\tfree(*parts);\n+\t*parts = NULL;\n+}\n+static void format_part_free(struct format_part **part)\n+{\n+\tif ((*part)->literal)\n+\t\tfree((*part)->literal);\n+\tif ((*part)->args)\n+\t\tfree((*part)->args);\n+\tfree(*part);\n+\t*part = NULL;\n+}\n+\n+static struct format_part *parts_add(struct format_parts *parts,\n+\t\t\t\t     enum format_part_type type)\n+{\n+\tALLOC_GROW(parts->part, parts->len+1, parts->alloc);\n+\tmemset(&parts->part[parts->len], 0,\n+\t       sizeof(parts->part[parts->len]));\n+\tparts->part[parts->len].type = type;\n+\tparts->len++;\n+\treturn &parts->part[parts->len-1];\n+}\n+\n+static struct format_part *parts_add_part(struct format_parts *parts,\n+\t\t\t\t\t   struct format_part *part)\n+{\n+\tstruct format_part *dst = parts_add(parts, FORMAT_PART_UNKNOWN);\n+\tmemcpy(dst, part, sizeof(*dst));\n+\tif (part->type == FORMAT_PART_NOTES)\n+\t\tparts->want.notes = 1;\n+\treturn dst;\n+}\n+\n+static void parts_add_nliteral(struct format_parts *parts, const char *literal,\n+\t\t\t       size_t len)\n+{\n+\tif (len == 0)\n+\t\treturn;\n+\tparts_add(parts, FORMAT_PART_LITERAL);\n+\tparts->part[parts->len-1].literal = xmemdupz(literal, len);\n+\tparts->part[parts->len-1].literal_len = len;\n+\tparts->part[parts->len-1].format_len = len;\n+\treturn;\n+}\n+\n+static void part_add_arg_date_mode(struct format_part *part,\n+\t\t\t\t   enum date_mode dmode)\n+{\n+\tpart->args = xrealloc(part->args,\n+\t\t\t      sizeof(struct format_arg) * (part->argc+1));\n+\tpart->args[part->argc].type = FORMAT_ARG_DATE_MODE;\n+\tpart->args[part->argc].dmode = dmode;\n+\tpart->argc++;\n+\treturn;\n+}\n+\n+/*\n+* Parse a single argument of an extended format, up to the next delimiter\n+* ie: up to ',' or ')'\n+* The return value is the position of the found delimiter within *unparsed,\n+* or NULL if the argument was invalid.\n+*/\n+const char *parse_arg(struct format_part *part, enum format_arg_type type,\n+\t\t      const char *unparsed)\n+{\n+\tstruct format_arg arg = {0};\n+\tconst char *c = unparsed;\n+\tchar *t;\n+\n+\tif (type != FORMAT_ARG_UINT)\n+\t\treturn NULL;\n+\targ.type = type;\n+\n+\tc += strspn(c, WHITESPACE);\n+\tif (!isdigit(*c))\n+\t\treturn NULL;\n+\targ.uint = strtoul(c, &t, 10);\n+\tif (t == c)\n+\t\treturn NULL;\n+\tc = t + strspn(t, WHITESPACE);\n+\tif (*c == ',' || *c == ')'){\n+\t\tALLOC_GROW(part->args, part->argc+1, part->args_alloc);\n+\t\tmemcpy(&(part->args[part->argc]), &arg,\n+\t\t       sizeof(struct format_arg));\n+\t\tpart->argc++;\n+\t\treturn c;\n+\t}\n+\treturn NULL;\n+}\n+\n+static struct format_part *parse_special(const char *unparsed)\n+{\n+\tstruct format_part *part = NULL;\n+\tint h1,h2;\n+\tchar c;\n+\tconst char *s, *e;\n+\n+\t/* these allocate their own part */\n+\tswitch (unparsed[1]) {\n+\tcase '-':\n+\tcase '+':\n+\tcase ' ':\n+\t\tif (*unparsed != '%')\n+\t\t\tgoto fail;\n+\n+\t\tpart = parse_special(unparsed + 1);\n+\t\tif (part) {\n+\t\t\tpart->format_len++;\n+\n+\t\t\tswitch (unparsed[1]) {\n+\t\t\tcase '-':\n+\t\t\t\tpart->magic = DEL_LF_BEFORE_EMPTY;\n+\t\t\t\tbreak;\n+\t\t\tcase '+':\n+\t\t\t\tpart->magic = ADD_LF_BEFORE_NON_EMPTY;\n+\t\t\t\tbreak;\n+\t\t\tcase ' ':\n+\t\t\t\tpart->magic = ADD_SP_BEFORE_NON_EMPTY;\n+\t\t\t\tbreak;\n+\t\t\t}\n+\t\t}\n+\t\treturn part;\n+\t}\n+\n+\tpart = format_part_alloc();\n+\n+\t/* most placeholders are 2 characters long */\n+\tpart->format_len = 2;\n+\n+\tswitch (unparsed[1]) {\n+\tcase 'h':\n+\t\tpart->type = FORMAT_PART_COMMIT_HASH_ABBREV;\n+\t\treturn part;\n+\tcase 'H':\n+\t\tpart->type = FORMAT_PART_COMMIT_HASH;\n+\t\treturn part;\n+\tcase 'p':\n+\t\tpart->type = FORMAT_PART_PARENT_HASHES_ABBREV;\n+\t\treturn part;\n+\tcase 'P':\n+\t\tpart->type = FORMAT_PART_PARENT_HASHES;\n+\t\treturn part;\n+\tcase 't':\n+\t\tpart->type = FORMAT_PART_TREE_HASH_ABBREV;\n+\t\treturn part;\n+\tcase 'T':\n+\t\tpart->type = FORMAT_PART_TREE_HASH;\n+\t\treturn part;\n+\tcase 'a':\n+\t\tpart->format_len++;\n+\t\tswitch (unparsed[2]) {\n+\t\tcase 'n':\n+\t\t\tpart->type = FORMAT_PART_AUTHOR_NAME;\n+\t\t\treturn part;\n+\t\tcase 'N':\n+\t\t\tpart->type = FORMAT_PART_AUTHOR_NAME_MAILMAP;\n+\t\t\treturn part;\n+\t\tcase 'e':\n+\t\t\tpart->type = FORMAT_PART_AUTHOR_EMAIL;\n+\t\t\treturn part;\n+\t\tcase 'E':\n+\t\t\tpart->type = FORMAT_PART_AUTHOR_EMAIL_MAILMAP;\n+\t\t\treturn part;\n+\t\tcase 'd':\n+\t\t\tpart->type = FORMAT_PART_AUTHOR_DATE;\n+\t\t\treturn part;\n+\t\tcase 'D':\n+\t\t\tpart->type = FORMAT_PART_AUTHOR_DATE;\n+\t\t\tpart_add_arg_date_mode(part, DATE_RFC2822);\n+\t\t\treturn part;\n+\t\tcase 'r':\n+\t\t\tpart->type = FORMAT_PART_AUTHOR_DATE;\n+\t\t\tpart_add_arg_date_mode(part, DATE_RELATIVE);\n+\t\t\treturn part;\n+\t\tcase 't':\n+\t\t\tpart->type = FORMAT_PART_AUTHOR_DATE;\n+\t\t\tpart_add_arg_date_mode(part, DATE_UNIX);\n+\t\t\treturn part;\n+\t\tcase 'i':\n+\t\t\tpart->type = FORMAT_PART_AUTHOR_DATE;\n+\t\t\tpart_add_arg_date_mode(part, DATE_ISO8601);\n+\t\t\treturn part;\n+\t\t}\n+\t\tgoto fail;\n+\tcase 'c':\n+\t\tpart->format_len++;\n+\t\tswitch (unparsed[2]) {\n+\t\tcase 'n':\n+\t\t\tpart->type = FORMAT_PART_COMMITTER_NAME;\n+\t\t\treturn part;\n+\t\tcase 'N':\n+\t\t\tpart->type = FORMAT_PART_COMMITTER_NAME_MAILMAP;\n+\t\t\treturn part;\n+\t\tcase 'e':\n+\t\t\tpart->type = FORMAT_PART_COMMITTER_EMAIL;\n+\t\t\treturn part;\n+\t\tcase 'E':\n+\t\t\tpart->type = FORMAT_PART_COMMITTER_EMAIL_MAILMAP;\n+\t\t\treturn part;\n+\t\tcase 'd':\n+\t\t\tpart->type = FORMAT_PART_COMMITTER_DATE;\n+\t\t\treturn part;\n+\t\tcase 'D':\n+\t\t\tpart->type = FORMAT_PART_COMMITTER_DATE;\n+\t\t\tpart_add_arg_date_mode(part, DATE_RFC2822);\n+\t\t\treturn part;\n+\t\tcase 'r':\n+\t\t\tpart->type = FORMAT_PART_COMMITTER_DATE;\n+\t\t\tpart_add_arg_date_mode(part, DATE_RELATIVE);\n+\t\t\treturn part;\n+\t\tcase 't':\n+\t\t\tpart->type = FORMAT_PART_COMMITTER_DATE;\n+\t\t\tpart_add_arg_date_mode(part, DATE_UNIX);\n+\t\t\treturn part;\n+\t\tcase 'i':\n+\t\t\tpart->type = FORMAT_PART_COMMITTER_DATE;\n+\t\t\tpart_add_arg_date_mode(part, DATE_ISO8601);\n+\t\t\treturn part;\n+\t\t}\n+\t\tgoto fail;\n+\tcase 'd':\n+\t\tpart->type = FORMAT_PART_DECORATE;\n+\t\treturn part;\n+\tcase 'e':\n+\t\tpart->type = FORMAT_PART_ENCODING;\n+\t\treturn part;\n+\tcase 's':\n+\t\tpart->type = FORMAT_PART_SUBJECT;\n+\t\treturn part;\n+\tcase 'f':\n+\t\tpart->type = FORMAT_PART_SUBJECT_SANITIZED;\n+\t\treturn part;\n+\tcase 'b':\n+\t\tpart->type = FORMAT_PART_BODY;\n+\t\treturn part;\n+\tcase 'B':\n+\t\tpart->type = FORMAT_PART_RAW_BODY;\n+\t\treturn part;\n+\tcase 'N':\n+\t\tpart->type = FORMAT_PART_NOTES;\n+\t\treturn part;\n+\tcase 'g':\n+\t\tpart->format_len++;\n+\t\tswitch (unparsed[2]) {\n+\t\tcase 'D':\n+\t\t\tpart->type = FORMAT_PART_REFLOG_SELECTOR;\n+\t\t\treturn part;\n+\t\tcase 'd':\n+\t\t\tpart->type = FORMAT_PART_REFLOG_SELECTOR_SHORT;\n+\t\t\treturn part;\n+\t\tcase 's':\n+\t\t\tpart->type = FORMAT_PART_REFLOG_SUBJECT;\n+\t\t\treturn part;\n+\t\t}\n+\t\tgoto fail;\n+\tcase 'C':\n+\t\tpart->type = FORMAT_PART_LITERAL;\n+\t\tif (unparsed[2] == '(') {\n+\t\t\te = strchr(unparsed + 3, ')');\n+\t\t\tpart->literal = xcalloc(1, COLOR_MAXLEN);\n+\t\t\tif (!e || !color_parse_len(unparsed + 3,\n+\t\t\t\t\t\t  e - (unparsed + 3),\n+\t\t\t\t\t\t  part->literal))\n+\t\t\t\tgoto fail;\n+\t\t\tpart->literal_len = strlen(part->literal);\n+\t\t\tpart->format_len = e - unparsed + 1;\n+\t\t\treturn part;\n+\t\t}\n+\n+\t\tif (!prefixcmp(&unparsed[2], \"red\")) {\n+\t\t\tpart->literal = GIT_COLOR_RED;\n+\t\t\tpart->literal_len = strlen(GIT_COLOR_RED);\n+\t\t\tpart->format_len = 5;\n+\t\t} else if (!prefixcmp(&unparsed[2], \"green\")) {\n+\t\t\tpart->literal = GIT_COLOR_GREEN;\n+\t\t\tpart->literal_len = strlen(GIT_COLOR_GREEN);\n+\t\t\tpart->format_len = 7;\n+\t\t} else if (!prefixcmp(&unparsed[2], \"blue\")) {\n+\t\t\tpart->literal = GIT_COLOR_BLUE;\n+\t\t\tpart->literal_len = strlen(GIT_COLOR_BLUE);\n+\t\t\tpart->format_len = 6;\n+\t\t} else if (!prefixcmp(&unparsed[2], \"reset\")) {\n+\t\t\tpart->literal = GIT_COLOR_RESET;\n+\t\t\tpart->literal_len = strlen(GIT_COLOR_RESET);\n+\t\t\tpart->format_len = 7;\n+\t\t}\n+\n+\t\tif (part->literal)\n+\t\t\treturn part;\n+\t\tgoto fail;\n+\tcase 'm':\n+\t\tpart->type = FORMAT_PART_MARK;\n+\t\treturn part;\n+\tcase 'w':\n+\t\tif (unparsed[2] != '(')\n+\t\t\tgoto fail;\n+\n+\t\tpart->type = FORMAT_PART_WRAP;\n+\n+\t\ts = unparsed + 3;\n+\t\twhile (part->argc <= 3) {\n+\t\t\ts += strspn(s, WHITESPACE);\n+\t\t\tif (*s == ')'){\n+\t\t\t\tpart->format_len = s - unparsed + 1;\n+\t\t\t\treturn part;\n+\t\t\t}\n+\t\t\tif (part->argc) {\n+\t\t\t\tif (*s != ',')\n+\t\t\t\t\tgoto fail;\n+\t\t\t\ts++;\n+\t\t\t}\n+\t\t\tif (part->argc == 3)\n+\t\t\t\tgoto fail;\n+\n+\t\t\ts = parse_arg(part, FORMAT_ARG_UINT, s);\n+\t\t\tif (!s)\n+\t\t\t\tgoto fail;\n+\t\t}\n+\t\tgoto fail;\n+\tcase 'x':\n+\t\t/* %x00 == NUL, %x0a == LF, etc. */\n+\t\tif (0 <= (h1 = hexval_table[0xff & unparsed[2]]) &&\n+\t\t    h1 <= 16 &&\n+\t\t    0 <= (h2 = hexval_table[0xff & unparsed[3]]) &&\n+\t\t    h2 <= 16) {\n+\t\t\tpart->type = FORMAT_PART_LITERAL;\n+\t\t\tpart->format_len = 4;\n+\t\t\tc = (h1<<4)|h2;\n+\t\t\tpart->literal = xmemdupz(&c, 1);\n+\t\t\tpart->literal_len = 1;\n+\t\t\treturn part;\n+\t\t}\n+\t\tgoto fail;\n+\tcase 'n':\n+\t\tpart->type = FORMAT_PART_LITERAL;\n+\t\tpart->literal = \"\\n\";\n+\t\tpart->literal_len = 1;\n+\t\treturn part;\n+\tcase '%':\n+\t\tpart->type = FORMAT_PART_LITERAL;\n+\t\tpart->literal = xstrndup(&unparsed[1], 1);\n+\t\tpart->literal_len = 1;\n+\t\treturn part;\n+\t}\n+\n+fail:\n+\tif (part)\n+\t\tformat_part_free(&part);\n+\treturn NULL;\n+}\n+\n+struct format_parts *parse_format(const char *unparsed)\n+{\n+\tstruct format_parts *parts = format_parts_alloc();\n+\tstruct format_part *part;\n+\tconst char *c = unparsed;\n+\tconst char *last = NULL;\n+\n+\twhile (*c) {\n+\t\tif (!last)\n+\t\t\tlast = c;\n+\n+\t\tc += strcspn(c, \"%\");\n+\t\tif (!*c)\n+\t\t\tbreak;\n+\n+\t\tpart = parse_special(c);\n+\t\tif (part) {\n+\t\t\tparts_add_nliteral(parts, last, c - last);\n+\t\t\tlast = NULL;\n+\n+\t\t\tparts_add_part(parts, part);\n+\t\t\tc += part->format_len;\n+\t\t\tfree(part);\n+\t\t\tcontinue;\n+\t\t}\n+\t\tc++;\n+\t}\n+\n+\tif (last)\n+\t\tparts_add_nliteral(parts, last, c - last);\n+\n+\tparts->format_len = c - unparsed + 1;\n+\treturn parts;\n+}\n+\n static void save_user_format(struct rev_info *rev, const char *cp, int is_tformat)\n {\n-\tfree(user_format);\n-\tuser_format = xstrdup(cp);\n+\tif (user_format)\n+\t\tformat_parts_free(&user_format);\n+\tuser_format = parse_format(cp);\n \tif (is_tformat)\n \t\trev->use_terminator = 1;\n \trev->commit_format = CMIT_FMT_USERFORMAT;\n@@ -440,11 +838,9 @@ static int mailmap_name(char *email, int email_len, char *name, int name_len)\n \treturn mail_map->nr && map_user(mail_map, email, email_len, name, name_len);\n }\n \n-static size_t format_person_part(struct strbuf *sb, char part,\n-\t\t\t\t const char *msg, int len, enum date_mode dmode)\n+static void format_person_part(struct strbuf *sb, struct format_part *part,\n+\t\t\t       const char *msg, int len, enum date_mode dmode)\n {\n-\t/* currently all placeholders have same length */\n-\tconst int placeholder_len = 2;\n \tint start, end, tz = 0;\n \tunsigned long date = 0;\n \tchar *ep;\n@@ -463,7 +859,7 @@ static size_t format_person_part(struct strbuf *sb, char part,\n \t * below len - 1.\n \t */\n \tif (end >= len - 2)\n-\t\tgoto skip;\n+\t\treturn;\n \n \t/* Seek for both name and email part */\n \tname_start = msg;\n@@ -472,19 +868,22 @@ static size_t format_person_part(struct strbuf *sb, char part,\n \t\tname_end--;\n \tname_len = name_end-name_start;\n \tif (name_len >= sizeof(person_name))\n-\t\tgoto skip;\n+\t\treturn;\n \tmail_start = msg+end+1;\n \tmail_end = mail_start;\n \twhile (mail_end < msg_end && *mail_end != '>')\n \t\tmail_end++;\n \tmail_len = mail_end-mail_start;\n \tif (mail_len >= sizeof(person_mail))\n-\t\tgoto skip;\n+\t\treturn;\n \tif (mail_end == msg_end)\n-\t\tgoto skip;\n+\t\treturn;\n \tend = mail_end-msg;\n \n-\tif (part == 'N' || part == 'E') { /* mailmap lookup */\n+\tif (part->type == FORMAT_PART_AUTHOR_NAME_MAILMAP ||\n+\t    part->type == FORMAT_PART_AUTHOR_EMAIL_MAILMAP ||\n+\t    part->type == FORMAT_PART_COMMITTER_NAME_MAILMAP ||\n+\t    part->type == FORMAT_PART_COMMITTER_EMAIL_MAILMAP) {\n \t\t/* copy up to, and including, the end delimiter */\n \t\tstrlcpy(person_name, name_start, name_len+1);\n \t\tstrlcpy(person_mail, mail_start, mail_len+1);\n@@ -494,27 +893,37 @@ static size_t format_person_part(struct strbuf *sb, char part,\n \t\tmail_start = person_mail;\n \t\tmail_len = strlen(person_mail);\n \t}\n-\tif (part == 'n' || part == 'N') {\t/* name */\n+\tif (part->type == FORMAT_PART_AUTHOR_NAME ||\n+\t    part->type == FORMAT_PART_AUTHOR_NAME_MAILMAP ||\n+\t    part->type == FORMAT_PART_COMMITTER_NAME ||\n+\t    part->type == FORMAT_PART_COMMITTER_NAME_MAILMAP) {\n \t\tstrbuf_add(sb, name_start, name_len);\n-\t\treturn placeholder_len;\n+\t\treturn;\n \t}\n-\tif (part == 'e' || part == 'E') {\t/* email */\n+\tif (part->type == FORMAT_PART_AUTHOR_EMAIL ||\n+\t    part->type == FORMAT_PART_AUTHOR_EMAIL_MAILMAP ||\n+\t    part->type == FORMAT_PART_COMMITTER_EMAIL ||\n+\t    part->type == FORMAT_PART_COMMITTER_EMAIL_MAILMAP) {\n \t\tstrbuf_add(sb, mail_start, mail_len);\n-\t\treturn placeholder_len;\n+\t\treturn;\n \t}\n \n \t/* advance 'start' to point to date start delimiter */\n \tfor (start = end + 1; start < len && isspace(msg[start]); start++)\n \t\t; /* do nothing */\n \tif (start >= len)\n-\t\tgoto skip;\n+\t\treturn;\n \tdate = strtoul(msg + start, &ep, 10);\n \tif (msg + start == ep)\n-\t\tgoto skip;\n+\t\treturn;\n \n-\tif (part == 't') {\t/* date, UNIX timestamp */\n+\tif (part->type != FORMAT_PART_AUTHOR_DATE &&\n+\t    part->type != FORMAT_PART_COMMITTER_DATE)\n+\t\treturn;\n+\n+\tif (part->argc && part->args[0].dmode == DATE_UNIX) {\n \t\tstrbuf_add(sb, msg + start, ep - (msg + start));\n-\t\treturn placeholder_len;\n+\t\treturn;\n \t}\n \n \t/* parse tz */\n@@ -526,31 +935,11 @@ static size_t format_person_part(struct strbuf *sb, char part,\n \t\t\ttz = -tz;\n \t}\n \n-\tswitch (part) {\n-\tcase 'd':\t/* date */\n+\tif (part->argc)\n+\t\tstrbuf_addstr(sb, show_date(date, tz, part->args[0].dmode));\n+\telse\n \t\tstrbuf_addstr(sb, show_date(date, tz, dmode));\n-\t\treturn placeholder_len;\n-\tcase 'D':\t/* date, RFC2822 style */\n-\t\tstrbuf_addstr(sb, show_date(date, tz, DATE_RFC2822));\n-\t\treturn placeholder_len;\n-\tcase 'r':\t/* date, relative */\n-\t\tstrbuf_addstr(sb, show_date(date, tz, DATE_RELATIVE));\n-\t\treturn placeholder_len;\n-\tcase 'i':\t/* date, ISO 8601 */\n-\t\tstrbuf_addstr(sb, show_date(date, tz, DATE_ISO8601));\n-\t\treturn placeholder_len;\n-\t}\n-\n-skip:\n-\t/*\n-\t * bogus commit, 'sb' cannot be updated, but we still need to\n-\t * compute a valid return value.\n-\t */\n-\tif (part == 'n' || part == 'e' || part == 't' || part == 'd'\n-\t    || part == 'D' || part == 'r' || part == 'i')\n-\t\treturn placeholder_len;\n-\n-\treturn 0; /* unknown placeholder */\n+\treturn;\n }\n \n struct chunk {\n@@ -745,116 +1134,65 @@ static void rewrap_message_tail(struct strbuf *sb,\n \tc->indent2 = new_indent2;\n }\n \n-static size_t format_commit_one(struct strbuf *sb, const char *placeholder,\n-\t\t\t\tvoid *context)\n+void format_commit_message_part(struct format_part *part,\n+\t\t\t\tstruct strbuf *sb, void *context)\n {\n \tstruct format_commit_context *c = context;\n \tconst struct commit *commit = c->commit;\n-\tconst char *msg = c->message;\n+\tconst char *msg = commit->buffer;\n \tstruct commit_list *p;\n-\tint h1, h2;\n+\tunsigned long width = 0, indent1 = 0, indent2 = 0;\n \n \t/* these are independent of the commit */\n-\tswitch (placeholder[0]) {\n-\tcase 'C':\n-\t\tif (placeholder[1] == '(') {\n-\t\t\tconst char *end = strchr(placeholder + 2, ')');\n-\t\t\tchar color[COLOR_MAXLEN];\n-\t\t\tif (!end || !color_parse_len(placeholder + 2,\n-\t\t\t\t\t\t     end - (placeholder + 2),\n-\t\t\t\t\t\t     color))\n-\t\t\t\treturn 0;\n-\t\t\tstrbuf_addstr(sb, color);\n-\t\t\treturn end - placeholder + 1;\n-\t\t}\n-\t\tif (!prefixcmp(placeholder + 1, \"red\")) {\n-\t\t\tstrbuf_addstr(sb, GIT_COLOR_RED);\n-\t\t\treturn 4;\n-\t\t} else if (!prefixcmp(placeholder + 1, \"green\")) {\n-\t\t\tstrbuf_addstr(sb, GIT_COLOR_GREEN);\n-\t\t\treturn 6;\n-\t\t} else if (!prefixcmp(placeholder + 1, \"blue\")) {\n-\t\t\tstrbuf_addstr(sb, GIT_COLOR_BLUE);\n-\t\t\treturn 5;\n-\t\t} else if (!prefixcmp(placeholder + 1, \"reset\")) {\n-\t\t\tstrbuf_addstr(sb, GIT_COLOR_RESET);\n-\t\t\treturn 6;\n-\t\t} else\n-\t\t\treturn 0;\n-\tcase 'n':\t\t/* newline */\n-\t\tstrbuf_addch(sb, '\\n');\n-\t\treturn 1;\n-\tcase 'x':\n-\t\t/* %x00 == NUL, %x0a == LF, etc. */\n-\t\tif (0 <= (h1 = hexval_table[0xff & placeholder[1]]) &&\n-\t\t    h1 <= 16 &&\n-\t\t    0 <= (h2 = hexval_table[0xff & placeholder[2]]) &&\n-\t\t    h2 <= 16) {\n-\t\t\tstrbuf_addch(sb, (h1<<4)|h2);\n-\t\t\treturn 3;\n-\t\t} else\n-\t\t\treturn 0;\n-\tcase 'w':\n-\t\tif (placeholder[1] == '(') {\n-\t\t\tunsigned long width = 0, indent1 = 0, indent2 = 0;\n-\t\t\tchar *next;\n-\t\t\tconst char *start = placeholder + 2;\n-\t\t\tconst char *end = strchr(start, ')');\n-\t\t\tif (!end)\n-\t\t\t\treturn 0;\n-\t\t\tif (end > start) {\n-\t\t\t\twidth = strtoul(start, &next, 10);\n-\t\t\t\tif (*next == ',') {\n-\t\t\t\t\tindent1 = strtoul(next + 1, &next, 10);\n-\t\t\t\t\tif (*next == ',') {\n-\t\t\t\t\t\tindent2 = strtoul(next + 1,\n-\t\t\t\t\t\t\t\t &next, 10);\n-\t\t\t\t\t}\n-\t\t\t\t}\n-\t\t\t\tif (*next != ')')\n-\t\t\t\t\treturn 0;\n-\t\t\t}\n-\t\t\trewrap_message_tail(sb, c, width, indent1, indent2);\n-\t\t\treturn end - placeholder + 1;\n-\t\t} else\n-\t\t\treturn 0;\n+\tswitch (part->type) {\n+\tcase FORMAT_PART_LITERAL:\n+\t\tstrbuf_add(sb, part->literal, part->literal_len);\n+\t\treturn;\n+\tcase FORMAT_PART_WRAP:\n+\t\twidth = (part->argc > 0) ? part->args[0].uint : 0;\n+\t\tindent1 = (part->argc > 1) ? part->args[1].uint : 0;\n+\t\tindent2 = (part->argc > 2) ? part->args[2].uint : 0;\n+\t\trewrap_message_tail(sb, c, width, indent1, indent2);\n+\t\treturn;\n+\tdefault:\n+\t\tbreak;\n \t}\n \n \t/* these depend on the commit */\n \tif (!commit->object.parsed)\n \t\tparse_object(commit->object.sha1);\n \n-\tswitch (placeholder[0]) {\n-\tcase 'H':\t\t/* commit hash */\n+\tswitch (part->type) {\n+\tcase FORMAT_PART_COMMIT_HASH:\n \t\tstrbuf_addstr(sb, sha1_to_hex(commit->object.sha1));\n-\t\treturn 1;\n-\tcase 'h':\t\t/* abbreviated commit hash */\n+\t\treturn;\n+\tcase FORMAT_PART_COMMIT_HASH_ABBREV:\n \t\tif (add_again(sb, &c->abbrev_commit_hash))\n-\t\t\treturn 1;\n+\t\t\treturn;\n \t\tstrbuf_addstr(sb, find_unique_abbrev(commit->object.sha1,\n-\t\t\t\t\t\t     c->pretty_ctx->abbrev));\n+\t\t\t\t\t     c->pretty_ctx->abbrev));\n \t\tc->abbrev_commit_hash.len = sb->len - c->abbrev_commit_hash.off;\n-\t\treturn 1;\n-\tcase 'T':\t\t/* tree hash */\n+\t\treturn;\n+\tcase FORMAT_PART_TREE_HASH:\n \t\tstrbuf_addstr(sb, sha1_to_hex(commit->tree->object.sha1));\n-\t\treturn 1;\n-\tcase 't':\t\t/* abbreviated tree hash */\n+\t\treturn;\n+\tcase FORMAT_PART_TREE_HASH_ABBREV:\n \t\tif (add_again(sb, &c->abbrev_tree_hash))\n-\t\t\treturn 1;\n+\t\t\treturn;\n \t\tstrbuf_addstr(sb, find_unique_abbrev(commit->tree->object.sha1,\n \t\t\t\t\t\t     c->pretty_ctx->abbrev));\n \t\tc->abbrev_tree_hash.len = sb->len - c->abbrev_tree_hash.off;\n-\t\treturn 1;\n-\tcase 'P':\t\t/* parent hashes */\n+\t\treturn;\n+\tcase FORMAT_PART_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 1;\n-\tcase 'p':\t\t/* abbreviated parent hashes */\n+\t\treturn;\n+\tcase FORMAT_PART_PARENT_HASHES_ABBREV:\n \t\tif (add_again(sb, &c->abbrev_parent_hashes))\n-\t\t\treturn 1;\n+\t\t\treturn;\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@@ -864,159 +1202,138 @@ static size_t format_commit_one(struct strbuf *sb, const char *placeholder,\n \t\t}\n \t\tc->abbrev_parent_hashes.len = sb->len -\n \t\t                              c->abbrev_parent_hashes.off;\n-\t\treturn 1;\n-\tcase 'm':\t\t/* left/right/bottom */\n+\t\treturn;\n+\tcase FORMAT_PART_MARK:\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 1;\n-\tcase 'd':\n+\t\treturn;\n+\tcase FORMAT_PART_DECORATE:\n \t\tformat_decoration(sb, commit);\n-\t\treturn 1;\n-\tcase 'g':\t\t/* reflog info */\n-\t\tswitch(placeholder[1]) {\n-\t\tcase 'd':\t/* reflog selector */\n-\t\tcase 'D':\n-\t\t\tif (c->pretty_ctx->reflog_info)\n-\t\t\t\tget_reflog_selector(sb,\n-\t\t\t\t\t\t    c->pretty_ctx->reflog_info,\n-\t\t\t\t\t\t    c->pretty_ctx->date_mode,\n-\t\t\t\t\t\t    (placeholder[1] == 'd'));\n-\t\t\treturn 2;\n-\t\tcase 's':\t/* reflog message */\n-\t\t\tif (c->pretty_ctx->reflog_info)\n-\t\t\t\tget_reflog_message(sb, c->pretty_ctx->reflog_info);\n-\t\t\treturn 2;\n+\t\treturn;\n+\tcase FORMAT_PART_REFLOG_SELECTOR:\n+\tcase FORMAT_PART_REFLOG_SELECTOR_SHORT:\n+\t\tif (c->pretty_ctx->reflog_info) {\n+\t\t\tget_reflog_selector(sb,\n+\t\t\t\t\t    c->pretty_ctx->reflog_info,\n+\t\t\t\t\t    c->pretty_ctx->date_mode,\n+\t\t\t\t\t    (part->type == FORMAT_PART_REFLOG_SELECTOR_SHORT));\n \t\t}\n-\t\treturn 0;\t/* unknown %g placeholder */\n-\tcase 'N':\n+\t\treturn;\n+\tcase FORMAT_PART_REFLOG_SUBJECT:\n+\t\tif (c->pretty_ctx->reflog_info)\n+\t\t\tget_reflog_message(sb, c->pretty_ctx->reflog_info);\n+\t\treturn;\n+\tcase FORMAT_PART_NOTES:\n \t\tif (c->pretty_ctx->show_notes) {\n \t\t\tformat_display_notes(commit->object.sha1, sb,\n \t\t\t\t    get_log_output_encoding(), 0);\n-\t\t\treturn 1;\n \t\t}\n-\t\treturn 0;\n+\t\treturn;\n+\tdefault:\n+\t\tbreak;\n \t}\n \n \t/* For the rest we have to parse the commit header. */\n \tif (!c->commit_header_parsed)\n \t\tparse_commit_header(c);\n \n-\tswitch (placeholder[0]) {\n-\tcase 'a':\t/* author ... */\n-\t\treturn format_person_part(sb, placeholder[1],\n-\t\t\t\t   msg + c->author.off, c->author.len,\n-\t\t\t\t   c->pretty_ctx->date_mode);\n-\tcase 'c':\t/* committer ... */\n-\t\treturn format_person_part(sb, placeholder[1],\n-\t\t\t\t   msg + c->committer.off, c->committer.len,\n-\t\t\t\t   c->pretty_ctx->date_mode);\n-\tcase 'e':\t/* encoding */\n+\tswitch (part->type) {\n+\tcase FORMAT_PART_AUTHOR_NAME:\n+\tcase FORMAT_PART_AUTHOR_NAME_MAILMAP:\n+\tcase FORMAT_PART_AUTHOR_EMAIL:\n+\tcase FORMAT_PART_AUTHOR_EMAIL_MAILMAP:\n+\tcase FORMAT_PART_AUTHOR_DATE:\n+\t\tformat_person_part(sb, part, commit->buffer + c->author.off,\n+\t\t\t\t   c->author.len, c->pretty_ctx->date_mode);\n+\t\treturn;\n+\tcase FORMAT_PART_COMMITTER_NAME:\n+\tcase FORMAT_PART_COMMITTER_NAME_MAILMAP:\n+\tcase FORMAT_PART_COMMITTER_EMAIL:\n+\tcase FORMAT_PART_COMMITTER_EMAIL_MAILMAP:\n+\tcase FORMAT_PART_COMMITTER_DATE:\n+\t\tformat_person_part(sb, part, commit->buffer + c->committer.off,\n+\t\t\t\t   c->committer.len, c->pretty_ctx->date_mode);\n+\t\treturn;\n+\tcase FORMAT_PART_ENCODING:\n \t\tstrbuf_add(sb, msg + c->encoding.off, c->encoding.len);\n-\t\treturn 1;\n-\tcase 'B':\t/* raw body */\n+\t\treturn;\n+\tcase FORMAT_PART_RAW_BODY:\n \t\t/* message_off is always left at the initial newline */\n \t\tstrbuf_addstr(sb, msg + c->message_off + 1);\n-\t\treturn 1;\n+\t\treturn;\n+\tdefault:\n+\t\tbreak;\n \t}\n \n \t/* Now we need to parse the commit message. */\n \tif (!c->commit_message_parsed)\n \t\tparse_commit_message(c);\n \n-\tswitch (placeholder[0]) {\n-\tcase 's':\t/* subject */\n+\tswitch (part->type) {\n+\tcase FORMAT_PART_SUBJECT:\n \t\tformat_subject(sb, msg + c->subject_off, \" \");\n-\t\treturn 1;\n-\tcase 'f':\t/* sanitized subject */\n+\t\treturn;\n+\tcase FORMAT_PART_SUBJECT_SANITIZED:\n \t\tformat_sanitized_subject(sb, msg + c->subject_off);\n-\t\treturn 1;\n-\tcase 'b':\t/* body */\n+\t\treturn;\n+\tcase FORMAT_PART_BODY:\n \t\tstrbuf_addstr(sb, msg + c->body_off);\n-\t\treturn 1;\n-\t}\n-\treturn 0;\t/* unknown placeholder */\n-}\n-\n-static size_t format_commit_item(struct strbuf *sb, const char *placeholder,\n-\t\t\t\t void *context)\n-{\n-\tint consumed;\n-\tsize_t orig_len;\n-\tenum {\n-\t\tNO_MAGIC,\n-\t\tADD_LF_BEFORE_NON_EMPTY,\n-\t\tDEL_LF_BEFORE_EMPTY,\n-\t\tADD_SP_BEFORE_NON_EMPTY\n-\t} magic = NO_MAGIC;\n-\n-\tswitch (placeholder[0]) {\n-\tcase '-':\n-\t\tmagic = DEL_LF_BEFORE_EMPTY;\n-\t\tbreak;\n-\tcase '+':\n-\t\tmagic = ADD_LF_BEFORE_NON_EMPTY;\n-\t\tbreak;\n-\tcase ' ':\n-\t\tmagic = ADD_SP_BEFORE_NON_EMPTY;\n-\t\tbreak;\n+\t\treturn;\n \tdefault:\n \t\tbreak;\n \t}\n-\tif (magic != NO_MAGIC)\n-\t\tplaceholder++;\n-\n-\torig_len = sb->len;\n-\tconsumed = format_commit_one(sb, placeholder, context);\n-\tif (magic == NO_MAGIC)\n-\t\treturn consumed;\n-\n-\tif ((orig_len == sb->len) && magic == DEL_LF_BEFORE_EMPTY) {\n-\t\twhile (sb->len && sb->buf[sb->len - 1] == '\\n')\n-\t\t\tstrbuf_setlen(sb, sb->len - 1);\n-\t} else if (orig_len != sb->len) {\n-\t\tif (magic == ADD_LF_BEFORE_NON_EMPTY)\n-\t\t\tstrbuf_insert(sb, orig_len, \"\\n\", 1);\n-\t\telse if (magic == ADD_SP_BEFORE_NON_EMPTY)\n-\t\t\tstrbuf_insert(sb, orig_len, \" \", 1);\n-\t}\n-\treturn consumed + 1;\n+\treturn;\n }\n \n-static size_t userformat_want_item(struct strbuf *sb, const char *placeholder,\n-\t\t\t\t   void *context)\n+void format_commit_message_parts(const struct format_parts *parsed,\n+\t\t\t\t struct strbuf *sb, void *context)\n {\n-\tstruct userformat_want *w = context;\n+\tsize_t i, orig_len;\n+\tenum format_part_magic magic;\n \n-\tif (*placeholder == '+' || *placeholder == '-' || *placeholder == ' ')\n-\t\tplaceholder++;\n+\tfor (i = 0; i < parsed->len; i++) {\n+\t\torig_len = sb->len;\n+\t\tmagic = parsed->part[i].magic;\n+\t\tformat_commit_message_part(&parsed->part[i], sb, context);\n \n-\tswitch (*placeholder) {\n-\tcase 'N':\n-\t\tw->notes = 1;\n-\t\tbreak;\n+\t\tif (magic == NO_MAGIC)\n+\t\t\tcontinue;\n+\n+\t\tif ((orig_len == sb->len) && magic == DEL_LF_BEFORE_EMPTY) {\n+\t\t\twhile (sb->len && sb->buf[sb->len - 1] == '\\n')\n+\t\t\t\tstrbuf_setlen(sb, sb->len - 1);\n+\t\t} else if (orig_len != sb->len) {\n+\t\t\tif (magic == ADD_LF_BEFORE_NON_EMPTY)\n+\t\t\t\tstrbuf_insert(sb, orig_len, \"\\n\", 1);\n+\t\t\telse if (magic == ADD_SP_BEFORE_NON_EMPTY)\n+\t\t\t\tstrbuf_insert(sb, orig_len, \" \", 1);\n+\t\t}\n \t}\n-\treturn 0;\n }\n \n void userformat_find_requirements(const char *fmt, struct userformat_want *w)\n {\n-\tstruct strbuf dummy = STRBUF_INIT;\n+\tstruct format_parts *dummy;\n \n \tif (!fmt) {\n \t\tif (!user_format)\n \t\t\treturn;\n-\t\tfmt = user_format;\n+\t\tmemcpy(w, &user_format->want, sizeof(*w));\n+\t\treturn;\n \t}\n-\tstrbuf_expand(&dummy, user_format, userformat_want_item, w);\n-\tstrbuf_release(&dummy);\n+\n+\tdummy = parse_format(fmt);\n+\tmemcpy(w, &dummy->want, sizeof(*w));\n+\tformat_parts_free(&dummy);\n }\n \n-void format_commit_message(const struct commit *commit,\n-\t\t\t   const char *format, struct strbuf *sb,\n-\t\t\t   const struct pretty_print_context *pretty_ctx)\n+void format_commit_message_parsed(const struct commit *commit,\n+\t\t\t\t  const struct format_parts *parsed_format,\n+\t\t\t\t  struct strbuf *sb,\n+\t\t\t\t  const struct pretty_print_context *pretty_ctx)\n {\n \tstruct format_commit_context context;\n \tstatic const char utf8[] = \"UTF-8\";\n@@ -1035,13 +1352,32 @@ void format_commit_message(const struct commit *commit,\n \t\t\tcontext.message = logmsg_reencode(commit, output_enc);\n \t}\n \n-\tstrbuf_expand(sb, format, format_commit_item, &context);\n+\tformat_commit_message_parts(parsed_format, sb, &context);\n \trewrap_message_tail(sb, &context, 0, 0, 0);\n \n \tif (context.message != commit->buffer)\n \t\tfree(context.message);\n }\n \n+void format_commit_message(const struct commit *commit,\n+\t\t\t   const char *format, struct strbuf *sb,\n+\t\t\t   const struct pretty_print_context *pretty_ctx)\n+{\n+\tstatic char *last = NULL;\n+\tstatic struct format_parts *parsed = NULL;\n+\n+\tif( !parsed || strcmp(last, format) ){\n+\t\tif (parsed){\n+\t\t\tformat_parts_free(&parsed);\n+\t\t\tfree(last);\n+\t\t}\n+\t\tparsed = parse_format(format);\n+\t\tlast = xstrdup(format);\n+\t}\n+\n+\tformat_commit_message_parsed(commit, parsed, sb, pretty_ctx);\n+}\n+\n static void pp_header(enum cmit_fmt fmt,\n \t\t      int abbrev,\n \t\t      enum date_mode dmode,\n@@ -1198,7 +1534,7 @@ void pretty_print_commit(enum cmit_fmt fmt, const struct commit *commit,\n \tint need_8bit_cte = context->need_8bit_cte;\n \n \tif (fmt == CMIT_FMT_USERFORMAT) {\n-\t\tformat_commit_message(commit, user_format, sb, context);\n+\t\tformat_commit_message_parsed(commit, user_format, sb, context);\n \t\treturn;\n \t}\n \ndiff --git a/test-pretty.c b/test-pretty.c\nnew file mode 100644\nindex 0000000..57c1c65\n--- /dev/null\n+++ b/test-pretty.c\n@@ -0,0 +1,232 @@\n+#include <ctype.h>\n+#include \"cache.h\"\n+#include \"utf8.h\"\n+#include \"commit.h\"\n+\n+static const char *usage_msg = \"\\n\"\n+\"  test-pretty <format>\\n\"\n+\"  test-pretty -a\\n\"\n+\"  test-pretty -- <format>\\n\";\n+\n+static const char *all = \"a\"\n+\"%-h%+h% h\"\n+\"%h%H%p%P%t%T\"\n+\"%an%aN%ae%aE%ad%aD%ar%at%ai\"\n+\"%cn%cN%ce%cE%cd%cD%cr%ct%ci\"\n+\"%d%e%s%f%b%B%N\"\n+\"%gD%gd%gs\"\n+\"%Cred%Cgreen%Cblue%Creset%C(reset)\"\n+\"%m%w()%w(1)%w(1,2)%w(1,2,3)\"\n+\"%x0a%n%%%@\";\n+\n+static struct strbuf *parts_debug(struct format_parts *parts,\n+\t\t\t\t  const char *unparsed)\n+{\n+\tstruct format_part *part;\n+\tstruct strbuf *buf = xcalloc(1, sizeof(*buf));\n+\tsize_t indent = 0;\n+\tstruct {enum format_part_type type; char *label;} labels[] = {\n+\t\t{FORMAT_PART_LITERAL, \"LITERAL\"},\n+\t\t{FORMAT_PART_COMMIT_HASH, \"COMMIT_HASH\"},\n+\t\t{FORMAT_PART_COMMIT_HASH_ABBREV, \"COMMIT_HASH_ABBREV\"},\n+\t\t{FORMAT_PART_PARENT_HASHES, \"PARENT_HASHES\"},\n+\t\t{FORMAT_PART_PARENT_HASHES_ABBREV, \"PARENT_HASHES_ABBREV\"},\n+\t\t{FORMAT_PART_TREE_HASH, \"TREE_HASH\"},\n+\t\t{FORMAT_PART_TREE_HASH_ABBREV, \"TREE_HASH_ABBREV\"},\n+\t\t{FORMAT_PART_AUTHOR_NAME, \"AUTHOR_NAME\"},\n+\t\t{FORMAT_PART_AUTHOR_NAME_MAILMAP, \"AUTHOR_NAME_MAILMAP\"},\n+\t\t{FORMAT_PART_AUTHOR_EMAIL, \"AUTHOR_EMAIL\"},\n+\t\t{FORMAT_PART_AUTHOR_EMAIL_MAILMAP, \"AUTHOR_EMAIL_MAILMAP\"},\n+\t\t{FORMAT_PART_AUTHOR_DATE, \"AUTHOR_DATE\"},\n+\t\t{FORMAT_PART_COMMITTER_NAME, \"COMMITTER_NAME\"},\n+\t\t{FORMAT_PART_COMMITTER_NAME_MAILMAP, \"COMMITTER_NAME_MAILMAP\"},\n+\t\t{FORMAT_PART_COMMITTER_EMAIL, \"COMMITTER_EMAIL\"},\n+\t\t{FORMAT_PART_COMMITTER_EMAIL_MAILMAP, \"COMMITTER_EMAIL_MAILMAP\"},\n+\t\t{FORMAT_PART_COMMITTER_DATE, \"COMMITTER_DATE\"},\n+\n+\t\t{FORMAT_PART_DECORATE, \"DECORATE\"},\n+\t\t{FORMAT_PART_ENCODING, \"ENCODING\"},\n+\t\t{FORMAT_PART_SUBJECT, \"SUBJECT\"},\n+\t\t{FORMAT_PART_SUBJECT_SANITIZED, \"SUBJECT_SANITIZED\"},\n+\t\t{FORMAT_PART_BODY, \"BODY\"},\n+\t\t{FORMAT_PART_RAW_BODY, \"RAW_BODY\"},\n+\t\t{FORMAT_PART_NOTES, \"NOTES\"},\n+\n+\t\t{FORMAT_PART_REFLOG_SELECTOR, \"REFLOG_SELECTOR\"},\n+\t\t{FORMAT_PART_REFLOG_SELECTOR_SHORT, \"REFLOG_SELECTOR_SHORT\"},\n+\t\t{FORMAT_PART_REFLOG_SUBJECT, \"REFLOG_SUBJECT\"},\n+\n+\t\t{FORMAT_PART_MARK, \"MARK\"},\n+\t\t{FORMAT_PART_WRAP, \"WRAP\"}\n+\t};\n+\tchar *label;\n+\tsize_t i,j,t = 0;\n+\tstrbuf_init(buf, 0);\n+\n+\tstrbuf_add_wrapped_text(buf, \"{[PARTS:\", indent++, 0, 0);\n+\tstrbuf_addf(buf, \"%li]\\n\", parts->len);\n+\tstrbuf_add_wrapped_text(buf, \"[FORMAT:\", indent, 0, 0);\n+\tstrbuf_addf(buf, \"%s]\\n\", unparsed);\n+\tstrbuf_add_wrapped_text(buf, \"(REMADE:\", indent, 0, 0);\n+\tfor (i = 0; i < parts->len; i++) {\n+\t\tstrbuf_add(buf, unparsed + t, parts->part[i].format_len);\n+\t\tt += parts->part[i].format_len;\n+\t}\n+\tstrbuf_addstr(buf, \")\\n\");\n+\n+\tfor (i = 0; i < parts->len; i++) {\n+\t\tpart = &parts->part[i];\n+\t\tlabel = \"UNKNOWN\";\n+\t\tfor (j = 0; j < ARRAY_SIZE(labels); j++) {\n+\t\t\tif (labels[j].type == part->type) {\n+\t\t\t\tlabel = labels[j].label;\n+\t\t\t}\n+\t\t}\n+\n+\t\tstrbuf_add_wrapped_text(buf, \"{[\", indent, 0, 0);\n+\t\tstrbuf_add(buf, unparsed, part->format_len);\n+\t\tunparsed += part->format_len;\n+\t\tstrbuf_add(buf, \"] \", 2);\n+\t\tstrbuf_addstr(buf, label);\n+\n+\t\tswitch(part->magic){\n+\t\tcase NO_MAGIC:\n+\t\t\tbreak;\n+\t\tcase ADD_LF_BEFORE_NON_EMPTY:\n+\t\t\tstrbuf_addstr(buf, \" (ADD_LF_BEFORE_NON_EMPTY)\");\n+\t\t\tbreak;\n+\t\tcase DEL_LF_BEFORE_EMPTY:\n+\t\t\tstrbuf_addstr(buf, \" (DEL_LF_BEFORE_EMPTY)\");\n+\t\t\tbreak;\n+\t\tcase ADD_SP_BEFORE_NON_EMPTY:\n+\t\t\tstrbuf_addstr(buf, \" (ADD_SP_BEFORE_NON_EMPTY)\");\n+\t\t\tbreak;\n+\t\t}\n+\n+\t\tif (part->literal) {\n+\t\t\tstrbuf_addstr(buf, \" \\\"\");\n+\t\t\tt = 0;\n+\t\t\twhile (t < part->literal_len) {\n+\t\t\t\tswitch (part->literal[t]) {\n+\t\t\t\tcase '\\n':\n+\t\t\t\t\tstrbuf_addstr(buf, \"\\\\n\");\n+\t\t\t\t\tbreak;\n+\t\t\t\tcase '\\r':\n+\t\t\t\t\tstrbuf_addstr(buf, \"\\\\r\");\n+\t\t\t\t\tbreak;\n+\t\t\t\tcase '\\t':\n+\t\t\t\t\tstrbuf_addstr(buf, \"\\\\t\");\n+\t\t\t\t\tbreak;\n+\t\t\t\tcase '\\\\':\n+\t\t\t\t\tstrbuf_addstr(buf, \"\\\\\\\\\");\n+\t\t\t\t\tbreak;\n+\t\t\t\tdefault:\n+\t\t\t\t\tif (isprint(part->literal[t]))\n+\t\t\t\t\t\tstrbuf_add(buf, &part->literal[t],\n+\t\t\t\t\t\t\t   1);\n+\t\t\t\t\telse\n+\t\t\t\t\t\tstrbuf_addf(buf, \"\\\\x%02x\",\n+\t\t\t\t\t\t\t    part->literal[t]);\n+\t\t\t\t\tbreak;\n+\t\t\t\t}\n+\t\t\t\tt++;\n+\t\t\t}\n+\t\t\tstrbuf_addstr(buf, \"\\\"\");\n+\t\t}\n+\n+\t\tif (part->argc) {\n+\t\t\tstrbuf_addstr(buf, \"\\n\");\n+\t\t\tstrbuf_add_wrapped_text(buf, \"ARGS: [\", indent+1, 0, 0);\n+\t\t\tfor (j = 0; j < part->argc; j++) {\n+\t\t\t\tswitch(part->args[j].type){\n+\t\t\t\tcase FORMAT_ARG_UINT:\n+\t\t\t\t\tstrbuf_addstr(buf, \"UINT:\");\n+\t\t\t\t\tstrbuf_addf(buf, \"%lu\", part->args[j].uint);\n+\t\t\t\t\tbreak;\n+\t\t\t\tcase FORMAT_ARG_DATE_MODE:\n+\t\t\t\t\tstrbuf_addstr(buf, \"DATE_MODE:\");\n+\t\t\t\t\tswitch(part->args[j].dmode){\n+\t\t\t\t\tcase DATE_NORMAL:\n+\t\t\t\t\t\tstrbuf_addstr(buf, \"DATE_NORMAL\");\n+\t\t\t\t\t\tbreak;\n+\t\t\t\t\tcase DATE_RELATIVE:\n+\t\t\t\t\t\tstrbuf_addstr(buf, \"DATE_RELATIVE\");\n+\t\t\t\t\t\tbreak;\n+\t\t\t\t\tcase DATE_SHORT:\n+\t\t\t\t\t\tstrbuf_addstr(buf, \"DATE_SHORT\");\n+\t\t\t\t\t\tbreak;\n+\t\t\t\t\tcase DATE_LOCAL:\n+\t\t\t\t\t\tstrbuf_addstr(buf, \"DATE_LOCAL\");\n+\t\t\t\t\t\tbreak;\n+\t\t\t\t\tcase DATE_ISO8601:\n+\t\t\t\t\t\tstrbuf_addstr(buf, \"DATE_ISO8601\");\n+\t\t\t\t\t\tbreak;\n+\t\t\t\t\tcase DATE_RFC2822:\n+\t\t\t\t\t\tstrbuf_addstr(buf, \"DATE_RFC2822\");\n+\t\t\t\t\t\tbreak;\n+\t\t\t\t\tcase DATE_RAW:\n+\t\t\t\t\t\tstrbuf_addstr(buf, \"DATE_RAW\");\n+\t\t\t\t\t\tbreak;\n+\t\t\t\t\tcase DATE_UNIX:\n+\t\t\t\t\t\tstrbuf_addstr(buf, \"DATE_UNIX\");\n+\t\t\t\t\t\tbreak;\n+\t\t\t\t\tdefault:\n+\t\t\t\t\t\tstrbuf_addf(buf, \"(UNKNOWN:%u)\",\n+\t\t\t\t\t\t\t    part->args[j].dmode);\n+\t\t\t\t\t\tbreak;\n+\t\t\t\t\t}\n+\t\t\t\t\tbreak;\n+\t\t\t\tdefault:\n+\t\t\t\t\tstrbuf_addstr(buf, \"(UNKNOWN)\");\n+\t\t\t\t\tbreak;\n+\t\t\t\t}\n+\n+\t\t\t\tif (j < part->argc - 1)\n+\t\t\t\t\tstrbuf_addstr(buf, \", \");\n+\t\t\t}\n+\t\t\tstrbuf_addstr(buf, \"]\\n\");\n+\t\t}\n+\n+\t\tif (part->argc)\n+\t\t\tstrbuf_add_wrapped_text(buf, \"}\\n\", indent, 0, 0);\n+\t\telse\n+\t\t\tstrbuf_addstr(buf, \"}\\n\");\n+\t}\n+\tstrbuf_add_wrapped_text(buf, \"}\\n\", --indent, 0, 0);\n+\n+\tif (!indent) {\n+\t\tprintf(\"%s\", buf->buf);\n+\t\tstrbuf_release(buf);\n+\t\tfree(buf);\n+\t\treturn NULL;\n+\t}\n+\treturn buf;\n+}\n+\n+int main(int argc, char **argv)\n+{\n+\tconst char *unparsed = NULL;\n+\tstruct format_parts *parsed;\n+\n+\tif (argc < 2) {\n+\t\tusage(usage_msg);\n+\t\treturn 1;\n+\t}\n+\n+\tif (*argv[1] == '-') {\n+\t\tif (argv[1][1] == 'a' && argc == 2)\n+\t\t\tunparsed = all;\n+\t\tif (argv[1][1] == '-' && !argv[1][2] && argc == 3)\n+\t\t\tunparsed = argv[2];\n+\t} else\n+\t\tunparsed = argv[1];\n+\n+\tif (!unparsed) {\n+\t\tusage(usage_msg);\n+\t\treturn 1;\n+\t}\n+\n+\tparsed = parse_format(unparsed);\n+\tparts_debug(parsed, unparsed);\n+\treturn 0;\n+}\n-- \n1.7.4.2\n"},{"id":"164509","messageId":"1301354251-23380-7-git-send-email-wmpalmer@gmail.com","threadId":"26896","inReplyTo":"1301354251-23380-1-git-send-email-wmpalmer@gmail.com","subject":"[PATCH/RFC 6/9] add long-form %(wrap:...) for %w(...)","fromName":"Will Palmer","fromEmail":"wmpalmer@gmail.com","sentAt":"2011-03-28T23:17:28Z","receivedAt":"2011-03-28T23:17:28Z","isPatch":true,"sender":{"key":"wmpalmer@gmail.com","avatar":"https://avatars.githubusercontent.com/u/357044?v=4"},"body":"the list of user-defined format placeholders has grown steadily longer\nsince they were first introduced. We currently have about forty\nplaceholders, and the room for new mnemonics is growing scarce. To make\nmore room, we introduce \"long forms\" for placeholders, which take the\nform:\n\t'%(' <label> [ ':' <arg> [ ',' <arg> ]* ] ')'\neg:\n\t%(wrap: 80, 0, 4)\n\nWe start by adding a long-form to the %w(...) placeholder, mostly\nbecause as it already takes multiple arguments, it is a good example.\n\nSigned-off-by: Will Palmer <wmpalmer@gmail.com>\n---\n Documentation/pretty-formats.txt |    1 +\n pretty.c                         |   47 +++++++++++++++++++++++++++++++++-----\n test-pretty.c                    |    1 +\n 3 files changed, 43 insertions(+), 6 deletions(-)\n\ndiff --git a/Documentation/pretty-formats.txt b/Documentation/pretty-formats.txt\nindex 561cc9f..c9f3fb6 100644\n--- a/Documentation/pretty-formats.txt\n+++ b/Documentation/pretty-formats.txt\n@@ -144,6 +144,7 @@ The placeholders are:\n - '%x00': print a byte from a hex code\n - '%w([<w>[,<i1>[,<i2>]]])': switch line wrapping, like the -w option of\n   linkgit:git-shortlog[1].\n+- '%(wrap:[<w>[,<i1>[,<i2>]]])': alternative form of %w(...)\n \n NOTE: Some placeholders may depend on other options given to the\n revision traversal engine. For example, the `%g*` reflog options will\ndiff --git a/pretty.c b/pretty.c\nindex cb02879..8301008 100644\n--- a/pretty.c\n+++ b/pretty.c\n@@ -108,12 +108,10 @@ const char *parse_arg(struct format_part *part, enum format_arg_type type,\n \targ.type = type;\n \n \tc += strspn(c, WHITESPACE);\n-\tif (!isdigit(*c))\n-\t\treturn NULL;\n-\targ.uint = strtoul(c, &t, 10);\n-\tif (t == c)\n-\t\treturn NULL;\n-\tc = t + strspn(t, WHITESPACE);\n+\tif (isdigit(*c)) {\n+\t\targ.uint = strtoul(c, &t, 10);\n+\t\tc = t + strspn(t, WHITESPACE);\n+\t}\n \tif (*c == ',' || *c == ')'){\n \t\tALLOC_GROW(part->args, part->argc+1, part->args_alloc);\n \t\tmemcpy(&(part->args[part->argc]), &arg,\n@@ -124,6 +122,41 @@ const char *parse_arg(struct format_part *part, enum format_arg_type type,\n \treturn NULL;\n }\n \n+static struct format_part *parse_extended(const char *unparsed)\n+{\n+\tstruct format_part *part = format_part_alloc();\n+\tconst char *c = unparsed + 2; /* \"%(...\" + strlen(\"%(\") */\n+\n+\tc += strspn(c, WHITESPACE);\n+\n+\tif (!prefixcmp(c, \"wrap\")) {\n+\t\tpart->type = FORMAT_PART_WRAP;\n+\t\tc += 4;\n+\t\twhile(part->argc <= 3){\n+\t\t\tc += strspn(c, WHITESPACE);\n+\t\t\tif (*c == ')')\n+\t\t\t\tgoto success;\n+\t\t\tif (*c != (part->argc ? ',' : ':'))\n+\t\t\t\tgoto fail;\n+\t\t\tif (part->argc == 3)\n+\t\t\t\tgoto fail;\n+\n+\t\t\tc = parse_arg(part, FORMAT_ARG_UINT, c+1);\n+\t\t\tif (!c)\n+\t\t\t\tgoto fail;\n+\t\t}\n+\t\tgoto fail;\n+\t}\n+\n+fail:\n+\tformat_part_free(&part);\n+\treturn NULL;\n+\n+success:\n+\tpart->format_len = c - unparsed + 1;\n+\treturn part;\n+}\n+\n static struct format_part *parse_special(const char *unparsed)\n {\n \tstruct format_part *part = NULL;\n@@ -156,6 +189,8 @@ static struct format_part *parse_special(const char *unparsed)\n \t\t\t}\n \t\t}\n \t\treturn part;\n+\tcase '(':\n+\t\treturn parse_extended(unparsed);\n \t}\n \n \tpart = format_part_alloc();\ndiff --git a/test-pretty.c b/test-pretty.c\nindex 57c1c65..64a8218 100644\n--- a/test-pretty.c\n+++ b/test-pretty.c\n@@ -17,6 +17,7 @@ static const char *all = \"a\"\n \"%gD%gd%gs\"\n \"%Cred%Cgreen%Cblue%Creset%C(reset)\"\n \"%m%w()%w(1)%w(1,2)%w(1,2,3)\"\n+\"%(wrap)%(wrap:1)%(wrap:1,2)%(wrap:1,2,3)\"\n \"%x0a%n%%%@\";\n \n static struct strbuf *parts_debug(struct format_parts *parts,\n-- \n1.7.4.2\n"},{"id":"164506","messageId":"1301354251-23380-8-git-send-email-wmpalmer@gmail.com","threadId":"26896","inReplyTo":"1301354251-23380-1-git-send-email-wmpalmer@gmail.com","subject":"[PATCH/RFC 7/9] add long form %(color:...) for %C(...)","fromName":"Will Palmer","fromEmail":"wmpalmer@gmail.com","sentAt":"2011-03-28T23:17:29Z","receivedAt":"2011-03-28T23:17:29Z","isPatch":true,"sender":{"key":"wmpalmer@gmail.com","avatar":"https://avatars.githubusercontent.com/u/357044?v=4"},"body":"this adds %(color:...) as an alternative to %C(...) in the \"git log\"\nfamily of commands. We now have a \"long form\" for all of the existing\ncomplex placeholders.\n\nSigned-off-by: Will Palmer <wmpalmer@gmail.com>\n---\n Documentation/pretty-formats.txt |    1 +\n pretty.c                         |   22 ++++++++++++++++++++++\n test-pretty.c                    |    1 +\n 3 files changed, 24 insertions(+), 0 deletions(-)\n\ndiff --git a/Documentation/pretty-formats.txt b/Documentation/pretty-formats.txt\nindex c9f3fb6..d987102 100644\n--- a/Documentation/pretty-formats.txt\n+++ b/Documentation/pretty-formats.txt\n@@ -138,6 +138,7 @@ The placeholders are:\n - '%Cblue': switch color to blue\n - '%Creset': reset color\n - '%C(...)': color specification, as described in color.branch.* config option\n+- '%(color:...)': alternative form of %C(...)\n - '%m': left, right or boundary mark\n - '%n': newline\n - '%%': a raw '%'\ndiff --git a/pretty.c b/pretty.c\nindex 8301008..616b857 100644\n--- a/pretty.c\n+++ b/pretty.c\n@@ -126,9 +126,31 @@ static struct format_part *parse_extended(const char *unparsed)\n {\n \tstruct format_part *part = format_part_alloc();\n \tconst char *c = unparsed + 2; /* \"%(...\" + strlen(\"%(\") */\n+\tconst char *e;\n \n \tc += strspn(c, WHITESPACE);\n \n+\tif (!prefixcmp(c, \"color\")) {\n+\t\tpart->type = FORMAT_PART_LITERAL;\n+\t\tc += 5 + strspn(c + 5, WHITESPACE);\n+\t\tif (*c == ')') {\n+\t\t\tpart->literal = xstrdup(GIT_COLOR_RESET);\n+\t\t\tpart->literal_len = strlen(part->literal);\n+\t\t\tgoto success;\n+\t\t}\n+\t\tif (*c != ':')\n+\t\t\tgoto fail;\n+\t\tc++;\n+\t\te = strchr(c, ')');\n+\t\tpart->literal = xcalloc(1, COLOR_MAXLEN);\n+\t\tif (!e || !color_parse_len(c, e - c,\n+\t\t\t\t\t   part->literal))\n+\t\t\tgoto fail;\n+\t\tpart->literal_len = strlen(part->literal);\n+\t\tc = e;\n+\t\tgoto success;\n+\t}\n+\n \tif (!prefixcmp(c, \"wrap\")) {\n \t\tpart->type = FORMAT_PART_WRAP;\n \t\tc += 4;\ndiff --git a/test-pretty.c b/test-pretty.c\nindex 64a8218..eb88e3a 100644\n--- a/test-pretty.c\n+++ b/test-pretty.c\n@@ -18,6 +18,7 @@ static const char *all = \"a\"\n \"%Cred%Cgreen%Cblue%Creset%C(reset)\"\n \"%m%w()%w(1)%w(1,2)%w(1,2,3)\"\n \"%(wrap)%(wrap:1)%(wrap:1,2)%(wrap:1,2,3)\"\n+\"%(color)%(color:red)%(color:red bold)%(color:red green bold)\"\n \"%x0a%n%%%@\";\n \n static struct strbuf *parts_debug(struct format_parts *parts,\n-- \n1.7.4.2\n"},{"id":"164512","messageId":"1301354251-23380-9-git-send-email-wmpalmer@gmail.com","threadId":"26896","inReplyTo":"1301354251-23380-1-git-send-email-wmpalmer@gmail.com","subject":"[PATCH/RFC 8/9] add long forms %(authordate) and %(committerdate)","fromName":"Will Palmer","fromEmail":"wmpalmer@gmail.com","sentAt":"2011-03-28T23:17:30Z","receivedAt":"2011-03-28T23:17:30Z","isPatch":true,"sender":{"key":"wmpalmer@gmail.com","avatar":"https://avatars.githubusercontent.com/u/357044?v=4"},"body":"this adds the long forms %(authordate) and %(committerdate) to\ncomplement the existing placeholders %ad, %aD, %ar, %at, %ai, %cd, %cC,\n%cr, %ct, and %ci. The specific format is specified as with --date, eg:\n\t%(committerdate:rfc2822)\n\nThis change exists mostly to give a place for new date formatting\noptions to go, though it also has the benefit of matching the format of\nthe date placeholders in git for-each-ref.\n\nSigned-off-by: Will Palmer <wmpalmer@gmail.com>\n---\n Documentation/pretty-formats.txt |    4 ++\n cache.h                          |    2 +\n date.c                           |   58 +++++++++++++++++++++++++------------\n pretty.c                         |   51 +++++++++++++++++++++++++++++----\n test-pretty.c                    |   10 ++++++\n 5 files changed, 100 insertions(+), 25 deletions(-)\n\ndiff --git a/Documentation/pretty-formats.txt b/Documentation/pretty-formats.txt\nindex d987102..dfb81a7 100644\n--- a/Documentation/pretty-formats.txt\n+++ b/Documentation/pretty-formats.txt\n@@ -114,6 +114,8 @@ The placeholders are:\n - '%ar': author date, relative\n - '%at': author date, UNIX timestamp\n - '%ai': author date, ISO 8601 format\n+- '%(authordate[:<format>])': author date. Without a <format>, the --date= option is respected.\n+  Otherwise, a format of the type which can be specified via the --date= option is taken.\n - '%cn': committer name\n - '%cN': committer name (respecting .mailmap, see linkgit:git-shortlog[1] or linkgit:git-blame[1])\n - '%ce': committer email\n@@ -123,6 +125,8 @@ The placeholders are:\n - '%cr': committer date, relative\n - '%ct': committer date, UNIX timestamp\n - '%ci': committer date, ISO 8601 format\n+- '%(committerdate[:<format>])': committer date. Without a <format>, the --date= option is respected.\n+  Otherwise, a format of the type which can be specified via the --date= option is taken.\n - '%d': ref names, like the --decorate option of linkgit:git-log[1]\n - '%e': encoding\n - '%s': subject\ndiff --git a/cache.h b/cache.h\nindex fa564fa..b769cf8 100644\n--- a/cache.h\n+++ b/cache.h\n@@ -829,6 +829,8 @@ void datestamp(char *buf, int bufsize);\n #define approxidate(s) approxidate_careful((s), NULL)\n unsigned long approxidate_careful(const char *, int *);\n unsigned long approxidate_relative(const char *date, const struct timeval *now);\n+#define DATE_FORMAT_MAX 9 /* strlen(\"relative\") + 1 */\n+size_t parse_date_format_len(const char *format, enum date_mode *dmode);\n enum date_mode parse_date_format(const char *format);\n \n #define IDENT_WARN_ON_NO_NAME  1\ndiff --git a/date.c b/date.c\nindex ce48220..1834ab6 100644\n--- a/date.c\n+++ b/date.c\n@@ -659,28 +659,48 @@ int parse_date(const char *date, char *result, int maxlen)\n \treturn date_string(timestamp, offset, result, maxlen);\n }\n \n+size_t parse_date_format_len(const char *format, enum date_mode *dmode)\n+{\n+\tif (!strcmp(format, \"relative\")) {\n+\t\t*dmode = DATE_RELATIVE;\n+\t\treturn 8;\n+\t} else if (!strcmp(format, \"iso8601\")) {\n+\t\t*dmode = DATE_ISO8601;\n+\t\treturn 7;\n+\t} else if (!strcmp(format, \"iso\")) {\n+\t\t*dmode = DATE_ISO8601;\n+\t\treturn 3;\n+\t} else if (!strcmp(format, \"rfc2822\")) {\n+\t\t*dmode = DATE_RFC2822;\n+\t\treturn 7;\n+\t} else if (!strcmp(format, \"rfc\")) {\n+\t\t*dmode = DATE_RFC2822;\n+\t\treturn 3;\n+\t} else if (!strcmp(format, \"short\")) {\n+\t\t*dmode = DATE_SHORT;\n+\t\treturn 5;\n+\t} else if (!strcmp(format, \"local\")) {\n+\t\t*dmode = DATE_LOCAL;\n+\t\treturn 5;\n+\t} else if (!strcmp(format, \"default\")) {\n+\t\t*dmode = DATE_NORMAL;\n+\t\treturn 7;\n+\t} else if (!strcmp(format, \"raw\")) {\n+\t\t*dmode = DATE_RAW;\n+\t\treturn 3;\n+\t} else if (!strcmp(format, \"unix\")) {\n+\t\t*dmode = DATE_UNIX;\n+\t\treturn 4;\n+\t} else\n+\t\treturn 0;\n+}\n+\n enum date_mode parse_date_format(const char *format)\n {\n-\tif (!strcmp(format, \"relative\"))\n-\t\treturn DATE_RELATIVE;\n-\telse if (!strcmp(format, \"iso8601\") ||\n-\t\t !strcmp(format, \"iso\"))\n-\t\treturn DATE_ISO8601;\n-\telse if (!strcmp(format, \"rfc2822\") ||\n-\t\t !strcmp(format, \"rfc\"))\n-\t\treturn DATE_RFC2822;\n-\telse if (!strcmp(format, \"short\"))\n-\t\treturn DATE_SHORT;\n-\telse if (!strcmp(format, \"local\"))\n-\t\treturn DATE_LOCAL;\n-\telse if (!strcmp(format, \"default\"))\n-\t\treturn DATE_NORMAL;\n-\telse if (!strcmp(format, \"raw\"))\n-\t\treturn DATE_RAW;\n-\telse if (!strcmp(format, \"unix\"))\n-\t\treturn DATE_UNIX;\n-\telse\n+\tenum date_mode dmode;\n+\tif (!parse_date_format_len(format, &dmode))\n \t\tdie(\"unknown date format %s\", format);\n+\treturn dmode;\n }\n \n void datestamp(char *buf, int bufsize)\ndiff --git a/pretty.c b/pretty.c\nindex 616b857..006bbe3 100644\n--- a/pretty.c\n+++ b/pretty.c\n@@ -102,19 +102,38 @@ const char *parse_arg(struct format_part *part, enum format_arg_type type,\n \tstruct format_arg arg = {0};\n \tconst char *c = unparsed;\n \tchar *t;\n+\tsize_t len;\n+\tchar date_format[DATE_FORMAT_MAX];\n \n-\tif (type != FORMAT_ARG_UINT)\n-\t\treturn NULL;\n \targ.type = type;\n \n \tc += strspn(c, WHITESPACE);\n-\tif (isdigit(*c)) {\n-\t\targ.uint = strtoul(c, &t, 10);\n-\t\tc = t + strspn(t, WHITESPACE);\n+\n+\tswitch (type){\n+\tcase FORMAT_ARG_UINT:\n+\t\tif (isdigit(*c)) {\n+\t\t\targ.uint = strtoul(c, &t, 10);\n+\t\t\tc = t;\n+\t\t}\n+\t\tbreak;\n+\tcase FORMAT_ARG_DATE_MODE:\n+\t\tlen = strcspn(c, WHITESPACE \",)\");\n+\t\tif (len >= DATE_FORMAT_MAX)\n+\t\t\treturn NULL;\n+\t\tstrncpy(date_format, c, len);\n+\t\tlen = parse_date_format_len(date_format, &arg.dmode);\n+\t\tif (!len)\n+\t\t\treturn NULL;\n+\t\tc += len;\n+\t\tbreak;\n+\tdefault:\n+\t\treturn NULL;\n \t}\n+\n+\tc += strspn(c, WHITESPACE);\n \tif (*c == ',' || *c == ')'){\n \t\tALLOC_GROW(part->args, part->argc+1, part->args_alloc);\n-\t\tmemcpy(&(part->args[part->argc]), &arg,\n+\t\tmemcpy(&part->args[part->argc], &arg,\n \t\t       sizeof(struct format_arg));\n \t\tpart->argc++;\n \t\treturn c;\n@@ -130,6 +149,26 @@ static struct format_part *parse_extended(const char *unparsed)\n \n \tc += strspn(c, WHITESPACE);\n \n+\tif (!prefixcmp(c, \"author\") || !prefixcmp(c, \"committer\")) {\n+\t\te = c;\n+\t\tc += (*e == 'a') ? 6 : 9;\n+\t\tif (!prefixcmp(c, \"date\")) {\n+\t\t\tpart->type = (*e == 'a') ? FORMAT_PART_AUTHOR_DATE :\n+\t\t\t\t\t\t   FORMAT_PART_COMMITTER_DATE;\n+\t\t\tc += 4 + strspn(c + 4, WHITESPACE);\n+\t\t\tif (*c == ')')\n+\t\t\t\tgoto success;\n+\t\t\tif (*c != ':')\n+\t\t\t\tgoto fail;\n+\t\t\tc = parse_arg(part, FORMAT_ARG_DATE_MODE, c+1);\n+\t\t\tif (!c)\n+\t\t\t\tgoto fail;\n+\t\t\tgoto success;\n+\t\t}\n+\n+\t\tc = e;\n+\t}\n+\n \tif (!prefixcmp(c, \"color\")) {\n \t\tpart->type = FORMAT_PART_LITERAL;\n \t\tc += 5 + strspn(c + 5, WHITESPACE);\ndiff --git a/test-pretty.c b/test-pretty.c\nindex eb88e3a..6a92c65 100644\n--- a/test-pretty.c\n+++ b/test-pretty.c\n@@ -19,6 +19,16 @@ static const char *all = \"a\"\n \"%m%w()%w(1)%w(1,2)%w(1,2,3)\"\n \"%(wrap)%(wrap:1)%(wrap:1,2)%(wrap:1,2,3)\"\n \"%(color)%(color:red)%(color:red bold)%(color:red green bold)\"\n+\"%(authordate)%(authordate:default)%(authordate:relative)\"\n+\"%(authordate:short)%(authordate:local)\"\n+\"%(authordate:iso8601)%(authordate:iso)\"\n+\"%(authordate:rfc2822)%(authordate:rfc)\"\n+\"%(authordate:unix)%(authordate:raw)\"\n+\"%(committerdate)%(committerdate:default)%(committerdate:relative)\"\n+\"%(committerdate:short)%(committerdate:local)\"\n+\"%(committerdate:iso8601)%(committerdate:iso)\"\n+\"%(committerdate:rfc2822)%(committerdate:rfc)\"\n+\"%(committerdate:unix)%(committerdate:raw)\"\n \"%x0a%n%%%@\";\n \n static struct strbuf *parts_debug(struct format_parts *parts,\n-- \n1.7.4.2\n"},{"id":"164511","messageId":"1301354251-23380-10-git-send-email-wmpalmer@gmail.com","threadId":"26896","inReplyTo":"1301354251-23380-1-git-send-email-wmpalmer@gmail.com","subject":"[PATCH/RFC 9/9] add long forms for author and committer identity","fromName":"Will Palmer","fromEmail":"wmpalmer@gmail.com","sentAt":"2011-03-28T23:17:31Z","receivedAt":"2011-03-28T23:17:31Z","isPatch":true,"sender":{"key":"wmpalmer@gmail.com","avatar":"https://avatars.githubusercontent.com/u/357044?v=4"},"body":"this adds the long forms %(authorname), %(committername),\n%(authoremail), and %(committeremail) to complement the existing\nplaceholders %an, %ae, %cn, and %ce; and, :mailmap forms of each, eg:\n    %(authoremail:mailmap)\n\nThe main purpose of this change is to match the format of the\nplaceholders supported in git for-each-ref, though the optional :mailmap\nparameter seemed like a sensible extension.\n\nAt this point we have enough \"long form\" placeholders to justify giving\nthem their own section in the documentation, so we do.\n\nSigned-off-by: Will Palmer <wmpalmer@gmail.com>\n---\n Documentation/pretty-formats.txt |   57 ++++++++++++++++++++++++++++----\n commit.h                         |    6 +--\n pretty.c                         |   68 ++++++++++++++++++++++++++++----------\n test-pretty.c                    |   14 +++++--\n 4 files changed, 112 insertions(+), 33 deletions(-)\n\ndiff --git a/Documentation/pretty-formats.txt b/Documentation/pretty-formats.txt\nindex dfb81a7..e9d6634 100644\n--- a/Documentation/pretty-formats.txt\n+++ b/Documentation/pretty-formats.txt\n@@ -97,7 +97,13 @@ The title was >>t4119: test autocomputing -p<n> for traditional diff input.<<\n \n --------\n +\n-The placeholders are:\n+The placeholders are divided into two categories, \"short forms\",\n+for quickly typing the more-common placeholders, and \"long forms\",\n+which may be more readable, and may support various additional\n+options.\n++\n+--\n+Short forms:\n \n - '%H': commit hash\n - '%h': abbreviated commit hash\n@@ -114,8 +120,6 @@ The placeholders are:\n - '%ar': author date, relative\n - '%at': author date, UNIX timestamp\n - '%ai': author date, ISO 8601 format\n-- '%(authordate[:<format>])': author date. Without a <format>, the --date= option is respected.\n-  Otherwise, a format of the type which can be specified via the --date= option is taken.\n - '%cn': committer name\n - '%cN': committer name (respecting .mailmap, see linkgit:git-shortlog[1] or linkgit:git-blame[1])\n - '%ce': committer email\n@@ -125,8 +129,6 @@ The placeholders are:\n - '%cr': committer date, relative\n - '%ct': committer date, UNIX timestamp\n - '%ci': committer date, ISO 8601 format\n-- '%(committerdate[:<format>])': committer date. Without a <format>, the --date= option is respected.\n-  Otherwise, a format of the type which can be specified via the --date= option is taken.\n - '%d': ref names, like the --decorate option of linkgit:git-log[1]\n - '%e': encoding\n - '%s': subject\n@@ -142,20 +144,61 @@ The placeholders are:\n - '%Cblue': switch color to blue\n - '%Creset': reset color\n - '%C(...)': color specification, as described in color.branch.* config option\n-- '%(color:...)': alternative form of %C(...)\n - '%m': left, right or boundary mark\n - '%n': newline\n - '%%': a raw '%'\n - '%x00': print a byte from a hex code\n - '%w([<w>[,<i1>[,<i2>]]])': switch line wrapping, like the -w option of\n   linkgit:git-shortlog[1].\n-- '%(wrap:[<w>[,<i1>[,<i2>]]])': alternative form of %w(...)\n+\n+Long forms:\n+\n+- '%(authorname[:mailmap])':\n++\n+author name, optionally respecting .mailmap (see linkgit:git-shortlog[1]\n+or linkgit:git-blame[1]).\n+\n+- '%(authoremail[:mailmap])':\n++\n+author email, optionally respecting .mailmap (see linkgit:git-shortlog[1]\n+or linkgit:git-blame[1]).\n+\n+- '%(authordate[:<format>])':\n++\n+author date. Without a <format>, the --date= option is respected.\n+Otherwise, a format of the type which can be specified via the --date=\n+option is taken.\n+\n+- '%(committername[:mailmap])':\n++\n+committer name, optionally respecting .mailmap (see\n+linkgit:git-shortlog[1] or linkgit:git-blame[1]).\n+\n+- '%(committeremail[:mailmap])':\n++\n+committer email, optionally respecting .mailmap (see\n+linkgit:git-shortlog[1] or linkgit:git-blame[1]).\n+\n+- '%(committerdate[:<format>])':\n++\n+committer date. Without a <format>, the --date= option is respected.\n+Otherwise, a format of the type which can be specified via the --date=\n+option is taken.\n+\n+- '%(color:[<spec>])':\n++\n+color specification, as described in color.branch.* config option.\n+\n+- '%(wrap:[<w>[,<i1>[,<i2>]]])':\n++\n+switch line wrapping, like the -w option of linkgit:git-shortlog[1].\n \n NOTE: Some placeholders may depend on other options given to the\n revision traversal engine. For example, the `%g*` reflog options will\n insert an empty string unless we are traversing reflog entries (e.g., by\n `git log -g`). The `%d` placeholder will use the \"short\" decoration\n format if `--decorate` was not already provided on the command line.\n+--\n \n If you add a `{plus}` (plus sign) after '%' of a placeholder, a line-feed\n is inserted immediately before the expansion if and only if the\ndiff --git a/commit.h b/commit.h\nindex fff1225..a175444 100644\n--- a/commit.h\n+++ b/commit.h\n@@ -95,14 +95,10 @@ enum format_part_type {\n \tFORMAT_PART_TREE_HASH_ABBREV,\n \n \tFORMAT_PART_AUTHOR_NAME,\n-\tFORMAT_PART_AUTHOR_NAME_MAILMAP,\n \tFORMAT_PART_AUTHOR_EMAIL,\n-\tFORMAT_PART_AUTHOR_EMAIL_MAILMAP,\n \tFORMAT_PART_AUTHOR_DATE,\n \tFORMAT_PART_COMMITTER_NAME,\n-\tFORMAT_PART_COMMITTER_NAME_MAILMAP,\n \tFORMAT_PART_COMMITTER_EMAIL,\n-\tFORMAT_PART_COMMITTER_EMAIL_MAILMAP,\n \tFORMAT_PART_COMMITTER_DATE,\n \n \tFORMAT_PART_DECORATE,\n@@ -130,6 +126,7 @@ enum format_part_magic {\n \n enum format_arg_type {\n \tFORMAT_ARG_UINT,\n+\tFORMAT_ARG_BOOLEAN,\n \tFORMAT_ARG_DATE_MODE\n };\n \n@@ -137,6 +134,7 @@ struct format_arg {\n \tenum format_arg_type type;\n \tunion {\n \t\tunsigned long uint;\n+\t\tint boolean : 1;\n \t\tenum date_mode dmode;\n \t};\n };\ndiff --git a/pretty.c b/pretty.c\nindex 006bbe3..ef6c3c1 100644\n--- a/pretty.c\n+++ b/pretty.c\n@@ -90,6 +90,16 @@ static void part_add_arg_date_mode(struct format_part *part,\n \treturn;\n }\n \n+static void part_add_arg_boolean(struct format_part *part, int value)\n+{\n+\tpart->args = xrealloc(part->args,\n+\t\t\t      sizeof(struct format_arg) * (part->argc+1));\n+\tpart->args[part->argc].type = FORMAT_ARG_BOOLEAN;\n+\tpart->args[part->argc].boolean = value ? 1 : 0;\n+\tpart->argc++;\n+\treturn;\n+}\n+\n /*\n * Parse a single argument of an extended format, up to the next delimiter\n * ie: up to ',' or ')'\n@@ -165,6 +175,31 @@ static struct format_part *parse_extended(const char *unparsed)\n \t\t\t\tgoto fail;\n \t\t\tgoto success;\n \t\t}\n+\t\tif (!prefixcmp(c, \"name\") || !prefixcmp(c, \"email\")) {\n+\t\t\tif (*c == 'n') { /* name */\n+\t\t\t\tpart->type = (*e == 'a') ? FORMAT_PART_AUTHOR_NAME :\n+\t\t\t\t\t\t\t   FORMAT_PART_COMMITTER_NAME;\n+\t\t\t\tc += 4;\n+\t\t\t} else { /* email */\n+\t\t\t\tpart->type = (*e == 'a') ? FORMAT_PART_AUTHOR_EMAIL :\n+\t\t\t\t\t\t\t   FORMAT_PART_COMMITTER_EMAIL;\n+\t\t\t\tc += 5;\n+\t\t\t}\n+\n+\t\t\tstrspn(c, WHITESPACE);\n+\t\t\tif (*c == ')')\n+\t\t\t\tgoto success;\n+\t\t\tif (*c != ':')\n+\t\t\t\tgoto fail;\n+\t\t\tc += 1 + strspn(c + 1, WHITESPACE);\n+\t\t\tif (!prefixcmp(c, \"mailmap\")) {\n+\t\t\t\tpart_add_arg_boolean(part, 1);\n+\t\t\t\tc += 7 + strspn(c + 7, WHITESPACE);\n+\t\t\t\tif (*c == ')')\n+\t\t\t\t\tgoto success;\n+\t\t\t}\n+\t\t\tgoto fail;\n+\t\t}\n \n \t\tc = e;\n \t}\n@@ -285,13 +320,15 @@ static struct format_part *parse_special(const char *unparsed)\n \t\t\tpart->type = FORMAT_PART_AUTHOR_NAME;\n \t\t\treturn part;\n \t\tcase 'N':\n-\t\t\tpart->type = FORMAT_PART_AUTHOR_NAME_MAILMAP;\n+\t\t\tpart->type = FORMAT_PART_AUTHOR_NAME;\n+\t\t\tpart_add_arg_boolean(part, 1);\n \t\t\treturn part;\n \t\tcase 'e':\n \t\t\tpart->type = FORMAT_PART_AUTHOR_EMAIL;\n \t\t\treturn part;\n \t\tcase 'E':\n-\t\t\tpart->type = FORMAT_PART_AUTHOR_EMAIL_MAILMAP;\n+\t\t\tpart->type = FORMAT_PART_AUTHOR_EMAIL;\n+\t\t\tpart_add_arg_boolean(part, 1);\n \t\t\treturn part;\n \t\tcase 'd':\n \t\t\tpart->type = FORMAT_PART_AUTHOR_DATE;\n@@ -321,13 +358,15 @@ static struct format_part *parse_special(const char *unparsed)\n \t\t\tpart->type = FORMAT_PART_COMMITTER_NAME;\n \t\t\treturn part;\n \t\tcase 'N':\n-\t\t\tpart->type = FORMAT_PART_COMMITTER_NAME_MAILMAP;\n+\t\t\tpart->type = FORMAT_PART_COMMITTER_NAME;\n+\t\t\tpart_add_arg_boolean(part, 1);\n \t\t\treturn part;\n \t\tcase 'e':\n \t\t\tpart->type = FORMAT_PART_COMMITTER_EMAIL;\n \t\t\treturn part;\n \t\tcase 'E':\n-\t\t\tpart->type = FORMAT_PART_COMMITTER_EMAIL_MAILMAP;\n+\t\t\tpart->type = FORMAT_PART_COMMITTER_EMAIL;\n+\t\t\tpart_add_arg_boolean(part, 1);\n \t\t\treturn part;\n \t\tcase 'd':\n \t\t\tpart->type = FORMAT_PART_COMMITTER_DATE;\n@@ -976,10 +1015,11 @@ static void format_person_part(struct strbuf *sb, struct format_part *part,\n \t\treturn;\n \tend = mail_end-msg;\n \n-\tif (part->type == FORMAT_PART_AUTHOR_NAME_MAILMAP ||\n-\t    part->type == FORMAT_PART_AUTHOR_EMAIL_MAILMAP ||\n-\t    part->type == FORMAT_PART_COMMITTER_NAME_MAILMAP ||\n-\t    part->type == FORMAT_PART_COMMITTER_EMAIL_MAILMAP) {\n+\tif ((part->type == FORMAT_PART_AUTHOR_NAME ||\n+\t     part->type == FORMAT_PART_AUTHOR_EMAIL ||\n+\t     part->type == FORMAT_PART_COMMITTER_NAME ||\n+\t     part->type == FORMAT_PART_COMMITTER_EMAIL) &&\n+\t    part->argc && part->args[0].boolean) { /* mailmap */\n \t\t/* copy up to, and including, the end delimiter */\n \t\tstrlcpy(person_name, name_start, name_len+1);\n \t\tstrlcpy(person_mail, mail_start, mail_len+1);\n@@ -990,16 +1030,12 @@ static void format_person_part(struct strbuf *sb, struct format_part *part,\n \t\tmail_len = strlen(person_mail);\n \t}\n \tif (part->type == FORMAT_PART_AUTHOR_NAME ||\n-\t    part->type == FORMAT_PART_AUTHOR_NAME_MAILMAP ||\n-\t    part->type == FORMAT_PART_COMMITTER_NAME ||\n-\t    part->type == FORMAT_PART_COMMITTER_NAME_MAILMAP) {\n+\t    part->type == FORMAT_PART_COMMITTER_NAME) {\n \t\tstrbuf_add(sb, name_start, name_len);\n \t\treturn;\n \t}\n \tif (part->type == FORMAT_PART_AUTHOR_EMAIL ||\n-\t    part->type == FORMAT_PART_AUTHOR_EMAIL_MAILMAP ||\n-\t    part->type == FORMAT_PART_COMMITTER_EMAIL ||\n-\t    part->type == FORMAT_PART_COMMITTER_EMAIL_MAILMAP) {\n+\t    part->type == FORMAT_PART_COMMITTER_EMAIL) {\n \t\tstrbuf_add(sb, mail_start, mail_len);\n \t\treturn;\n \t}\n@@ -1338,17 +1374,13 @@ void format_commit_message_part(struct format_part *part,\n \n \tswitch (part->type) {\n \tcase FORMAT_PART_AUTHOR_NAME:\n-\tcase FORMAT_PART_AUTHOR_NAME_MAILMAP:\n \tcase FORMAT_PART_AUTHOR_EMAIL:\n-\tcase FORMAT_PART_AUTHOR_EMAIL_MAILMAP:\n \tcase FORMAT_PART_AUTHOR_DATE:\n \t\tformat_person_part(sb, part, commit->buffer + c->author.off,\n \t\t\t\t   c->author.len, c->pretty_ctx->date_mode);\n \t\treturn;\n \tcase FORMAT_PART_COMMITTER_NAME:\n-\tcase FORMAT_PART_COMMITTER_NAME_MAILMAP:\n \tcase FORMAT_PART_COMMITTER_EMAIL:\n-\tcase FORMAT_PART_COMMITTER_EMAIL_MAILMAP:\n \tcase FORMAT_PART_COMMITTER_DATE:\n \t\tformat_person_part(sb, part, commit->buffer + c->committer.off,\n \t\t\t\t   c->committer.len, c->pretty_ctx->date_mode);\ndiff --git a/test-pretty.c b/test-pretty.c\nindex 6a92c65..f9d44fa 100644\n--- a/test-pretty.c\n+++ b/test-pretty.c\n@@ -19,11 +19,15 @@ static const char *all = \"a\"\n \"%m%w()%w(1)%w(1,2)%w(1,2,3)\"\n \"%(wrap)%(wrap:1)%(wrap:1,2)%(wrap:1,2,3)\"\n \"%(color)%(color:red)%(color:red bold)%(color:red green bold)\"\n+\"%(authorname)%(authorname:mailmap)\"\n+\"%(authoremail)%(authoremail:mailmap)\"\n \"%(authordate)%(authordate:default)%(authordate:relative)\"\n \"%(authordate:short)%(authordate:local)\"\n \"%(authordate:iso8601)%(authordate:iso)\"\n \"%(authordate:rfc2822)%(authordate:rfc)\"\n \"%(authordate:unix)%(authordate:raw)\"\n+\"%(committername)%(committername:mailmap)\"\n+\"%(committeremail)%(committeremail:mailmap)\"\n \"%(committerdate)%(committerdate:default)%(committerdate:relative)\"\n \"%(committerdate:short)%(committerdate:local)\"\n \"%(committerdate:iso8601)%(committerdate:iso)\"\n@@ -46,14 +50,10 @@ static struct strbuf *parts_debug(struct format_parts *parts,\n \t\t{FORMAT_PART_TREE_HASH, \"TREE_HASH\"},\n \t\t{FORMAT_PART_TREE_HASH_ABBREV, \"TREE_HASH_ABBREV\"},\n \t\t{FORMAT_PART_AUTHOR_NAME, \"AUTHOR_NAME\"},\n-\t\t{FORMAT_PART_AUTHOR_NAME_MAILMAP, \"AUTHOR_NAME_MAILMAP\"},\n \t\t{FORMAT_PART_AUTHOR_EMAIL, \"AUTHOR_EMAIL\"},\n-\t\t{FORMAT_PART_AUTHOR_EMAIL_MAILMAP, \"AUTHOR_EMAIL_MAILMAP\"},\n \t\t{FORMAT_PART_AUTHOR_DATE, \"AUTHOR_DATE\"},\n \t\t{FORMAT_PART_COMMITTER_NAME, \"COMMITTER_NAME\"},\n-\t\t{FORMAT_PART_COMMITTER_NAME_MAILMAP, \"COMMITTER_NAME_MAILMAP\"},\n \t\t{FORMAT_PART_COMMITTER_EMAIL, \"COMMITTER_EMAIL\"},\n-\t\t{FORMAT_PART_COMMITTER_EMAIL_MAILMAP, \"COMMITTER_EMAIL_MAILMAP\"},\n \t\t{FORMAT_PART_COMMITTER_DATE, \"COMMITTER_DATE\"},\n \n \t\t{FORMAT_PART_DECORATE, \"DECORATE\"},\n@@ -155,6 +155,12 @@ static struct strbuf *parts_debug(struct format_parts *parts,\n \t\t\t\t\tstrbuf_addstr(buf, \"UINT:\");\n \t\t\t\t\tstrbuf_addf(buf, \"%lu\", part->args[j].uint);\n \t\t\t\t\tbreak;\n+\t\t\t\tcase FORMAT_ARG_BOOLEAN:\n+\t\t\t\t\tstrbuf_addstr(buf, \"BOOLEAN:\");\n+\t\t\t\t\tstrbuf_addstr(buf,\n+\t\t\t\t\t\t      part->args[j].boolean ?\n+\t\t\t\t\t\t       \"TRUE\" : \"FALSE\");\n+\t\t\t\t\tbreak;\n \t\t\t\tcase FORMAT_ARG_DATE_MODE:\n \t\t\t\t\tstrbuf_addstr(buf, \"DATE_MODE:\");\n \t\t\t\t\tswitch(part->args[j].dmode){\n-- \n1.7.4.2\n"},{"id":"164514","messageId":"7vei5qvkgw.fsf@alter.siamese.dyndns.org","threadId":"26896","inReplyTo":"1301354251-23380-1-git-send-email-wmpalmer@gmail.com","subject":"Re: [PATCH/RFC 0/9] add long forms for format placeholders","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2011-03-29T00:28:15Z","receivedAt":"2011-03-29T00:28:15Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Will Palmer <wmpalmer@gmail.com> writes:\n\n> I've been kicking around this series for a while now as part of a larger\n> effort of refactoring the pretty formats. A recent discussion on the\n> list has lead me to believe that this smaller subset may be of use\n> sooner, rather than later.\n>\n> This series attempts to add \"long forms\" for common format placeholders\n> in the \"git log\" family of commands, making the way for yet more\n> placeholders to be added without needing to worry too much about the\n> increasingly limited set of available one-letter mnemonics. It also\n> moves towards the possibility of eventual unification with the format\n> options in for-each-ref.\n\nI don't claim that I read 1300+ long [PATCH 5/9] carefully, but I like the\ndirection in which this topic is going very much.\n\nExcept that [PATCH 2/9] looked quite out of place---more like \"I wanted to\nsneak this feature in\" than \"this was needed to keep the resulting code\nbackward compatible\" or anything like that.\n\nOff the top of my head, I don't think of a reason to say that [PATCH 3/9]\nis going in a wrong direction---is there a reason to make you worried in\nthe particular change?\n"},{"id":"164533","messageId":"1301381072.2335.26.camel@dreddbeard","threadId":"26896","inReplyTo":"7vei5qvkgw.fsf@alter.siamese.dyndns.org","subject":"Re: [PATCH/RFC 0/9] add long forms for format placeholders","fromName":"Will Palmer","fromEmail":"wmpalmer@gmail.com","sentAt":"2011-03-29T06:44:32Z","receivedAt":"2011-03-29T06:44:32Z","isPatch":true,"sender":{"key":"wmpalmer@gmail.com","avatar":"https://avatars.githubusercontent.com/u/357044?v=4"},"body":"On Mon, 2011-03-28 at 17:28 -0700, Junio C Hamano wrote:\n> Will Palmer <wmpalmer@gmail.com> writes:\n> \n> > I've been kicking around this series for a while now as part of a larger\n> > effort of refactoring the pretty formats. A recent discussion on the\n> > list has lead me to believe that this smaller subset may be of use\n> > sooner, rather than later.\n> >\n> > This series attempts to add \"long forms\" for common format placeholders\n> > in the \"git log\" family of commands, making the way for yet more\n> > placeholders to be added without needing to worry too much about the\n> > increasingly limited set of available one-letter mnemonics. It also\n> > moves towards the possibility of eventual unification with the format\n> > options in for-each-ref.\n> \n> I don't claim that I read 1300+ long [PATCH 5/9] carefully, but I like the\n> direction in which this topic is going very much.\n> \n> Except that [PATCH 2/9] looked quite out of place---more like \"I wanted to\n> sneak this feature in\" than \"this was needed to keep the resulting code\n> backward compatible\" or anything like that.\n\njust for context, we're talking about:\n [PATCH/RFC 2/9] add support for --date=unix to complement %at\n\nI was warned that I should tweak that message!\n\nThis one is actually in there to make the later [PATCH 5/9] more\nconsistent in how it handled dates, as: {AUTHOR,COMMITTER}_DATE: <TYPE>,\nrather than having a special case just for _UNIX.\n\nWhen I added DATE_UNIX to the enum, gcc started complaining about\nunhandled enum values in switch()es. To get around those (and noticing\nthat %at was the only format that wasn't available as a --date= switch)\n--date=unix was added. It seemed like a good idea to move that change to\nearlier in the series, rather than \"sneaking it in\" as part of [PATCH\n5/9]\n\nOf course, [PATCH 1/9] is only in there to make the documentation tweaks\nin [PATCH 2/9] more readable.\n\n> \n> Off the top of my head, I don't think of a reason to say that [PATCH 3/9]\n> is going in a wrong direction---is there a reason to make you worried in\n> the particular change?\n\n [PATCH/RFC 3/9] interpret %C(invalid) as we would %%C(invalid)\n\nThis one I was iffy on. On the one hand, it's inconsistent to treat\n%C(invalid) any differently from %Z(doesn't even exist), but on the\nother hand we lose feedback of telling the user why it's actually not\nworking as intended.\n\nThe real purpose of it was to prevent strange messages later on in the\nlarger series, which adds support for %(alias:<aliasname>). Seeing the\nmessage \"bad color value 'bkue' for variable '--pretty format'\" when\nwhat you actually typed was:\n    commit %h%+(alias:mergeline)%+(alias:message)\ncould be confusing.\n\n> --\n> To unsubscribe from this list: send the line \"unsubscribe git\" in\n> the body of a message to majordomo@vger.kernel.org\n> More majordomo info at  http://vger.kernel.org/majordomo-info.html\n\n-- \n-- Will\n"},{"id":"164536","messageId":"4D918032.3010608@drmicha.warpmail.net","threadId":"26896","inReplyTo":"7vei5qvkgw.fsf@alter.siamese.dyndns.org","subject":"Re: [PATCH/RFC 0/9] add long forms for format placeholders","fromName":"Michael J Gruber","fromEmail":"git@drmicha.warpmail.net","sentAt":"2011-03-29T06:46:10Z","receivedAt":"2011-03-29T06:46:10Z","isPatch":true,"sender":{"key":"git@grubix.eu","avatar":"https://avatars.githubusercontent.com/u/233215?v=4"},"body":"Junio C Hamano venit, vidit, dixit 29.03.2011 02:28:\n> Will Palmer <wmpalmer@gmail.com> writes:\n> \n>> I've been kicking around this series for a while now as part of a larger\n>> effort of refactoring the pretty formats. A recent discussion on the\n>> list has lead me to believe that this smaller subset may be of use\n>> sooner, rather than later.\n>>\n>> This series attempts to add \"long forms\" for common format placeholders\n>> in the \"git log\" family of commands, making the way for yet more\n>> placeholders to be added without needing to worry too much about the\n>> increasingly limited set of available one-letter mnemonics. It also\n>> moves towards the possibility of eventual unification with the format\n>> options in for-each-ref.\n> \n> I don't claim that I read 1300+ long [PATCH 5/9] carefully, but I like the\n> direction in which this topic is going very much.\n> \n> Except that [PATCH 2/9] looked quite out of place---more like \"I wanted to\n> sneak this feature in\" than \"this was needed to keep the resulting code\n> backward compatible\" or anything like that.\n> \n> Off the top of my head, I don't think of a reason to say that [PATCH 3/9]\n> is going in a wrong direction---is there a reason to make you worried in\n> the particular change?\n\nI'm wondering how much of this could and should be shared with\nfor-each-ref. Notable differences that I'm aware of:\n\n- for-each-ref is about (named) refs which can point to any type of\nobject; rev-list/log is about commit objects\n\n- for-each-ref deals with \"few\" objects typically, rev-list/log with many\n\nSo, other than %(refname), %(upstream) and %(tagger...), all\nfor-each-ref placeholders make sense for rev-list/log.\n\nSharing the parser would serve several purposes:\n\n- reduced code\n- increased test coverage (for-each-ref tests would test the parser)\n- speed up for for-each-ref (due to your nice separation)\n- short placeholders for for-each-ref\n- automatic consistency between the two\n\nMichael\n"},{"id":"164545","messageId":"1301383620.2335.50.camel@dreddbeard","threadId":"26896","inReplyTo":"4D918032.3010608@drmicha.warpmail.net","subject":"Re: [PATCH/RFC 0/9] add long forms for format placeholders","fromName":"Will Palmer","fromEmail":"wmpalmer@gmail.com","sentAt":"2011-03-29T07:27:00Z","receivedAt":"2011-03-29T07:27:00Z","isPatch":true,"sender":{"key":"wmpalmer@gmail.com","avatar":"https://avatars.githubusercontent.com/u/357044?v=4"},"body":"On Tue, 2011-03-29 at 08:46 +0200, Michael J Gruber wrote:\n> Junio C Hamano venit, vidit, dixit 29.03.2011 02:28:\n> > Will Palmer <wmpalmer@gmail.com> writes:\n> > \n> >> I've been kicking around this series for a while now as part of a larger\n> >> effort of refactoring the pretty formats. A recent discussion on the\n> >> list has lead me to believe that this smaller subset may be of use\n> >> sooner, rather than later.\n> >>\n> >> This series attempts to add \"long forms\" for common format placeholders\n> >> in the \"git log\" family of commands, making the way for yet more\n> >> placeholders to be added without needing to worry too much about the\n> >> increasingly limited set of available one-letter mnemonics. It also\n> >> moves towards the possibility of eventual unification with the format\n> >> options in for-each-ref.\n> > \n> > I don't claim that I read 1300+ long [PATCH 5/9] carefully, but I like the\n> > direction in which this topic is going very much.\n> > \n> > Except that [PATCH 2/9] looked quite out of place---more like \"I wanted to\n> > sneak this feature in\" than \"this was needed to keep the resulting code\n> > backward compatible\" or anything like that.\n> > \n> > Off the top of my head, I don't think of a reason to say that [PATCH 3/9]\n> > is going in a wrong direction---is there a reason to make you worried in\n> > the particular change?\n> \n> I'm wondering how much of this could and should be shared with\n> for-each-ref. ......................................\n\nI agree with this.\nNot only that, but I think the \"list\" modes of branch and tag should\nalso call for-each-ref internally, and I hope that some of the\nconditional formats that this series is moving slowly towards will help\nwith that.\n\n> ............. Notable differences that I'm aware of:\n> \n> - for-each-ref is about (named) refs which can point to any type of\n> object; rev-list/log is about commit objects\n> \n> - for-each-ref deals with \"few\" objects typically, rev-list/log with many\n> \n> So, other than %(refname), %(upstream) and %(tagger...), all\n> for-each-ref placeholders make sense for rev-list/log.\n\nI think the \"right thing to do\" here is to allow the parser to accept\nany of the for-each-ref specifications, but for the formatter to return\nan empty string for anything that doesn't make sense in context. This is\nwhat for-each-ref currently does. for-each-ref also gives an empty\nstring for some invalid specifications, such as %(tree:short), but I\nassume this is a bug.\n\nI'm not sure what the implications are in terms of what additional\nstructures we'll need to pass in to the formatter, as I haven't looked\nmuch at the for-each-ref code. It may also be that there are some\ncommit-related things which for-each-ref doesn't currently bother to\ngrab, since its placeholder list is comparatively smaller than the\nrev-list one.\n\n> \n> Sharing the parser would serve several purposes:\n> \n> - reduced code\n> - increased test coverage (for-each-ref tests would test the parser)\n> - speed up for for-each-ref (due to your nice separation)\n> - short placeholders for for-each-ref\n> - automatic consistency between the two\n> \n\nThis is already a part of my longer-term plans, though those were mostly\nas a \"I bet it would be fairly simple to do this once the rest is done\".\nWhat I'm actually working towards is strictly related to the --pretty=\nformats, so I expect it will be a while before I get to anything like\nfor-each-ref unification. It may also be worth noting that the last part\nof this work I submitted, \"pretty aliases\", was sent nearly a year ago.\nI am not going to be working on any of this full-time.\nThe point here is: I would not be offended if someone were to snatch\nfor-each-ref unification up from me, since I really don't know when I\nwould get to it myself.\n\n\n> Michael\n\n-- \n-- Will\n"}]}