{"thread":{"id":"35736","subject":"[PATCH v3 02/17] trailer: process trailers from file and arguments","startedAt":"2014-01-26T16:59:53Z","lastAt":"2014-02-02T11:22:14Z","messageCount":26,"participants":["Christian Couder","Eric Sunshine","Junio C Hamano"],"isPatch":true,"patchVersion":3,"patchTotal":17},"messages":[{"id":"233788","messageId":"20140126165018.24291.47716.chriscool@tuxfamily.org","threadId":"35736","inReplyTo":null,"subject":"[PATCH v3 00/17] Add interpret-trailers builtin","fromName":"Christian Couder","fromEmail":"chriscool@tuxfamily.org","sentAt":"2014-01-26T16:59:53Z","receivedAt":"2014-01-26T16:59:53Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"This patch series implements a new command:\n\n        git interpret-trailers\n\nand an infrastructure to process trailers that can be reused,\nfor example in \"commit.c\".\n\n1) Rationale:\n\nThis command should help with RFC 822 style headers, called\n\"trailers\", that are found at the end of commit messages.\n\n(Note that these headers do not follow and are not intended to\nfollow many rules that are in RFC 822. For example they do not\nfollow the line breaking rules, the encoding rules and probably\nmany other rules.)\n\nFor a long time, these trailers have become a de facto standard\nway to add helpful information into commit messages.\n\nUntil now git commit has only supported the well known\n\"Signed-off-by: \" trailer, that is used by many projects like\nthe Linux kernel and Git.\n\nIt is better to implement features for these trailers first in a\nnew command rather than in builtin/commit.c, because this way the\nprepare-commit-msg and commit-msg hooks can reuse this command.\n\n2) Current state:\n\nCurrently the usage string of this command is:\n\ngit interpret-trailers [--trim-empty] [--infile=file] [<token[=value]>...]\n\nThe following features are implemented:\n\n        - the result is printed on stdout\n        - the [<token[=value]>...] arguments are interpreted\n        - a commit message passed using the \"--infile=file\" option is interpreted\n        - if \"--infile\" is not used, a commit message is read from stdin\n        - the \"trailer.<token>.key\" options in the config are interpreted\n        - the \"trailer.<token>.where\" options are interpreted\n        - the \"trailer.<token>.ifExist\" options are interpreted\n        - the \"trailer.<token>.ifMissing\" options are interpreted\n        - the \"trailer.<token>.command\" config works\n\t- $ARG can be used in commands\n        - ditto for GIT_{AUTHOR,COMMITTER}_{NAME,EMAIL} env variables\n        - there are some tests\n        - (new) there is some documentation\n\nThe following features are planned but not yet implemented:\n\t- add more tests related to commands\n\t- add examples in documentation\n        - integration with \"git commit\"\n\nPossible improvements:\n        - support GIT_COMMIT_PROTO env variable in commands\n\n3) Changes since version 2:\n\n* the declared after statement are fixed\n* the style issues in for loops are fixed\n* there is some documentation\n\n\nChristian Couder (17):\n  Add data structures and basic functions for commit trailers\n  trailer: process trailers from file and arguments\n  trailer: read and process config information\n  trailer: process command line trailer arguments\n  strbuf: add strbuf_isspace()\n  trailer: parse trailers from input file\n  trailer: put all the processing together and print\n  trailer: add interpret-trailers command\n  trailer: add tests for \"git interpret-trailers\"\n  trailer: if no input file is passed, read from stdin\n  trailer: add new_trailer_item() function\n  strbuf: add strbuf_replace()\n  trailer: execute command from 'trailer.<name>.command'\n  trailer: add tests for trailer command\n  trailer: set author and committer env variables\n  trailer: add tests for commands using env variables\n  Documentation: add documentation for 'git interpret-trailers'\n\n .gitignore                               |   1 +\n Documentation/git-interpret-trailers.txt | 137 +++++++\n Makefile                                 |   2 +\n builtin.h                                |   1 +\n builtin/interpret-trailers.c             |  36 ++\n git.c                                    |   1 +\n strbuf.c                                 |  14 +\n strbuf.h                                 |   4 +\n t/t7513-interpret-trailers.sh            | 262 +++++++++++++\n trailer.c                                | 638 +++++++++++++++++++++++++++++++\n trailer.h                                |   6 +\n 11 files changed, 1102 insertions(+)\n create mode 100644 Documentation/git-interpret-trailers.txt\n create mode 100644 builtin/interpret-trailers.c\n create mode 100755 t/t7513-interpret-trailers.sh\n create mode 100644 trailer.c\n create mode 100644 trailer.h\n\n-- \n1.8.5.2.201.gacc5987\n"},{"id":"233791","messageId":"20140126170011.24291.88306.chriscool@tuxfamily.org","threadId":"35736","inReplyTo":"20140126165018.24291.47716.chriscool@tuxfamily.org","subject":"[PATCH v3 01/17] Add data structures and basic functions for commit trailers","fromName":"Christian Couder","fromEmail":"chriscool@tuxfamily.org","sentAt":"2014-01-26T16:59:54Z","receivedAt":"2014-01-26T16:59:54Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"We will use a doubly linked list to store all information\nabout trailers and their configuration.\n\nThis way we can easily remove or add trailers to or from\ntrailer lists while traversing the lists in either direction.\n\nSigned-off-by: Christian Couder <chriscool@tuxfamily.org>\n---\n Makefile  |  1 +\n trailer.c | 47 +++++++++++++++++++++++++++++++++++++++++++++++\n 2 files changed, 48 insertions(+)\n create mode 100644 trailer.c\n\ndiff --git a/Makefile b/Makefile\nindex b4af1e2..ec90feb 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -871,6 +871,7 @@ LIB_OBJS += submodule.o\n LIB_OBJS += symlinks.o\n LIB_OBJS += tag.o\n LIB_OBJS += trace.o\n+LIB_OBJS += trailer.o\n LIB_OBJS += transport.o\n LIB_OBJS += transport-helper.o\n LIB_OBJS += tree-diff.o\ndiff --git a/trailer.c b/trailer.c\nnew file mode 100644\nindex 0000000..d80d047\n--- /dev/null\n+++ b/trailer.c\n@@ -0,0 +1,47 @@\n+#include \"cache.h\"\n+/*\n+ * Copyright (c) 2013 Christian Couder <chriscool@tuxfamily.org>\n+ */\n+\n+enum action_where { WHERE_AFTER, WHERE_BEFORE };\n+enum action_if_exist { EXIST_ADD_IF_DIFFERENT, EXIST_ADD_IF_DIFFERENT_NEIGHBOR,\n+\t\t       EXIST_ADD, EXIST_OVERWRITE, EXIST_DO_NOTHING };\n+enum action_if_missing { MISSING_ADD, MISSING_DO_NOTHING };\n+\n+struct conf_info {\n+\tchar *name;\n+\tchar *key;\n+\tchar *command;\n+\tenum action_where where;\n+\tenum action_if_exist if_exist;\n+\tenum action_if_missing if_missing;\n+};\n+\n+struct trailer_item {\n+\tstruct trailer_item *previous;\n+\tstruct trailer_item *next;\n+\tconst char *token;\n+\tconst char *value;\n+\tstruct conf_info *conf;\n+};\n+\n+static int same_token(struct trailer_item *a, struct trailer_item *b, int alnum_len)\n+{\n+\treturn !strncasecmp(a->token, b->token, alnum_len);\n+}\n+\n+static int same_value(struct trailer_item *a, struct trailer_item *b)\n+{\n+\treturn !strcasecmp(a->value, b->value);\n+}\n+\n+static int same_trailer(struct trailer_item *a, struct trailer_item *b, int alnum_len)\n+{\n+\treturn same_token(a, b, alnum_len) && same_value(a, b);\n+}\n+\n+/* Get the length of buf from its beginning until its last alphanumeric character */\n+static size_t alnum_len(const char *buf, size_t len) {\n+\twhile (--len >= 0 && !isalnum(buf[len]));\n+\treturn len + 1;\n+}\n-- \n1.8.5.2.201.gacc5987\n"},{"id":"233775","messageId":"20140126170011.24291.72969.chriscool@tuxfamily.org","threadId":"35736","inReplyTo":"20140126165018.24291.47716.chriscool@tuxfamily.org","subject":"[PATCH v3 02/17] trailer: process trailers from file and arguments","fromName":"Christian Couder","fromEmail":"chriscool@tuxfamily.org","sentAt":"2014-01-26T16:59:55Z","receivedAt":"2014-01-26T16:59:55Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"This patch implements the logic that process trailers\nfrom file and arguments.\n\nAt the beginning trailers from file are in their own\ninfile_tok doubly linked list, and trailers from\narguments are in their own arg_tok doubly linked list.\n\nThe lists are traversed and when an arg_tok should be\n\"applied\", it is removed from its list and inserted\ninto the infile_tok list.\n\nSigned-off-by: Christian Couder <chriscool@tuxfamily.org>\n---\n trailer.c | 189 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++\n 1 file changed, 189 insertions(+)\n\ndiff --git a/trailer.c b/trailer.c\nindex d80d047..d88de3f 100644\n--- a/trailer.c\n+++ b/trailer.c\n@@ -45,3 +45,192 @@ static size_t alnum_len(const char *buf, size_t len) {\n \twhile (--len >= 0 && !isalnum(buf[len]));\n \treturn len + 1;\n }\n+\n+static void add_arg_to_infile(struct trailer_item *infile_tok,\n+\t\t\t      struct trailer_item *arg_tok)\n+{\n+\tif (arg_tok->conf->where == WHERE_AFTER) {\n+\t\targ_tok->next = infile_tok->next;\n+\t\tinfile_tok->next = arg_tok;\n+\t\targ_tok->previous = infile_tok;\n+\t\tif (arg_tok->next)\n+\t\t\targ_tok->next->previous = arg_tok;\n+\t} else {\n+\t\targ_tok->previous = infile_tok->previous;\n+\t\tinfile_tok->previous = arg_tok;\n+\t\targ_tok->next = infile_tok;\n+\t\tif (arg_tok->previous)\n+\t\t\targ_tok->previous->next = arg_tok;\n+\t}\n+}\n+\n+static int check_if_different(struct trailer_item *infile_tok,\n+\t\t\t      struct trailer_item *arg_tok,\n+\t\t\t      int alnum_len, int check_all)\n+{\n+\tenum action_where where = arg_tok->conf->where;\n+\tdo {\n+\t\tif (!infile_tok)\n+\t\t\treturn 1;\n+\t\tif (same_trailer(infile_tok, arg_tok, alnum_len))\n+\t\t\treturn 0;\n+\t\t/*\n+\t\t * if we want to add a trailer after another one,\n+\t\t * we have to check those before this one\n+\t\t */\n+\t\tinfile_tok = (where == WHERE_AFTER) ? infile_tok->previous : infile_tok->next;\n+\t} while (check_all);\n+\treturn 1;\n+}\n+\n+static void apply_arg_if_exist(struct trailer_item *infile_tok,\n+\t\t\t       struct trailer_item *arg_tok,\n+\t\t\t       int alnum_len)\n+{\n+\tswitch(arg_tok->conf->if_exist) {\n+\tcase EXIST_DO_NOTHING:\n+\t\tfree(arg_tok);\n+\t\tbreak;\n+\tcase EXIST_OVERWRITE:\n+\t\tfree((char *)infile_tok->value);\n+\t\tinfile_tok->value = xstrdup(arg_tok->value);\n+\t\tfree(arg_tok);\n+\t\tbreak;\n+\tcase EXIST_ADD:\n+\t\tadd_arg_to_infile(infile_tok, arg_tok);\n+\t\tbreak;\n+\tcase EXIST_ADD_IF_DIFFERENT:\n+\t\tif (check_if_different(infile_tok, arg_tok, alnum_len, 1))\n+\t\t\tadd_arg_to_infile(infile_tok, arg_tok);\n+\t\telse\n+\t\t\tfree(arg_tok);\n+\t\tbreak;\n+\tcase EXIST_ADD_IF_DIFFERENT_NEIGHBOR:\n+\t\tif (check_if_different(infile_tok, arg_tok, alnum_len, 0))\n+\t\t\tadd_arg_to_infile(infile_tok, arg_tok);\n+\t\telse\n+\t\t\tfree(arg_tok);\n+\t\tbreak;\n+\t}\n+}\n+\n+static void remove_from_list(struct trailer_item *item,\n+\t\t\t     struct trailer_item **first)\n+{\n+\tif (item->next)\n+\t\titem->next->previous = item->previous;\n+\tif (item->previous)\n+\t\titem->previous->next = item->next;\n+\telse\n+\t\t*first = item->next;\n+}\n+\n+static struct trailer_item *remove_first(struct trailer_item **first)\n+{\n+\tstruct trailer_item *item = *first;\n+\t*first = item->next;\n+\tif (item->next) {\n+\t\titem->next->previous = NULL;\n+\t\titem->next = NULL;\n+\t}\n+\treturn item;\n+}\n+\n+static void process_infile_tok(struct trailer_item *infile_tok,\n+\t\t\t       struct trailer_item **arg_tok_first,\n+\t\t\t       enum action_where where)\n+{\n+\tstruct trailer_item *arg_tok;\n+\tstruct trailer_item *next_arg;\n+\n+\tint tok_alnum_len = alnum_len(infile_tok->token, strlen(infile_tok->token));\n+\tfor (arg_tok = *arg_tok_first; arg_tok; arg_tok = next_arg) {\n+\t\tnext_arg = arg_tok->next;\n+\t\tif (same_token(infile_tok, arg_tok, tok_alnum_len) &&\n+\t\t    arg_tok->conf->where == where) {\n+\t\t\t/* Remove arg_tok from list */\n+\t\t\tremove_from_list(arg_tok, arg_tok_first);\n+\t\t\t/* Apply arg */\n+\t\t\tapply_arg_if_exist(infile_tok, arg_tok, tok_alnum_len);\n+\t\t\t/*\n+\t\t\t * If arg has been added to infile,\n+\t\t\t * then we need to process it too now.\n+\t\t\t */\n+\t\t\tif ((where == WHERE_AFTER ? infile_tok->next : infile_tok->previous) == arg_tok)\n+\t\t\t\tinfile_tok = arg_tok;\n+\t\t}\n+\t}\n+}\n+\n+static void update_last(struct trailer_item **last)\n+{\n+\tif (*last)\n+\t\twhile((*last)->next != NULL)\n+\t\t\t*last = (*last)->next;\n+}\n+\n+static void update_first(struct trailer_item **first)\n+{\n+\tif (*first)\n+\t\twhile((*first)->previous != NULL)\n+\t\t\t*first = (*first)->previous;\n+}\n+\n+static void apply_arg_if_missing(struct trailer_item **infile_tok_first,\n+\t\t\t\t struct trailer_item **infile_tok_last,\n+\t\t\t\t struct trailer_item *arg_tok)\n+{\n+\tstruct trailer_item **infile_tok;\n+\tenum action_where where;\n+\n+\tswitch(arg_tok->conf->if_missing) {\n+\tcase MISSING_DO_NOTHING:\n+\t\tfree(arg_tok);\n+\t\tbreak;\n+\tcase MISSING_ADD:\n+\t\twhere = arg_tok->conf->where;\n+\t\tinfile_tok = (where == WHERE_AFTER) ? infile_tok_last : infile_tok_first;\n+\t\tif (*infile_tok) {\n+\t\t\tadd_arg_to_infile(*infile_tok, arg_tok);\n+\t\t\t*infile_tok = arg_tok;\n+\t\t} else {\n+\t\t\t*infile_tok_first = arg_tok;\n+\t\t\t*infile_tok_last = arg_tok;\n+\t\t}\n+\t\tbreak;\n+\t}\n+}\n+\n+static void process_trailers_lists(struct trailer_item **infile_tok_first,\n+\t\t\t\t   struct trailer_item **infile_tok_last,\n+\t\t\t\t   struct trailer_item **arg_tok_first)\n+{\n+\tstruct trailer_item *infile_tok;\n+\tstruct trailer_item *arg_tok;\n+\n+\tif (!*arg_tok_first)\n+\t\treturn;\n+\n+\t/* Process infile from end to start */\n+\tfor (infile_tok = *infile_tok_last; infile_tok; infile_tok = infile_tok->previous) {\n+\t\tprocess_infile_tok(infile_tok, arg_tok_first, WHERE_AFTER);\n+\t}\n+\n+\tupdate_last(infile_tok_last);\n+\n+\tif (!*arg_tok_first)\n+\t\treturn;\n+\n+\t/* Process infile from start to end */\n+\tfor (infile_tok = *infile_tok_first; infile_tok; infile_tok = infile_tok->next) {\n+\t\tprocess_infile_tok(infile_tok, arg_tok_first, WHERE_BEFORE);\n+\t}\n+\n+\tupdate_first(infile_tok_first);\n+\n+\t/* Process args left */\n+\twhile (*arg_tok_first) {\n+\t\targ_tok = remove_first(arg_tok_first);\n+\t\tapply_arg_if_missing(infile_tok_first, infile_tok_last, arg_tok);\n+\t}\n+}\n-- \n1.8.5.2.201.gacc5987\n"},{"id":"233792","messageId":"20140126170011.24291.37366.chriscool@tuxfamily.org","threadId":"35736","inReplyTo":"20140126165018.24291.47716.chriscool@tuxfamily.org","subject":"[PATCH v3 03/17] trailer: read and process config information","fromName":"Christian Couder","fromEmail":"chriscool@tuxfamily.org","sentAt":"2014-01-26T16:59:56Z","receivedAt":"2014-01-26T16:59:56Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"This patch implements reading the configuration\nto get trailer information, and then processing\nit and storing it in a doubly linked list.\n\nThe config information is stored in the list\nwhose first item is pointed to by:\n\nstatic struct trailer_item *first_conf_item;\n\nSigned-off-by: Christian Couder <chriscool@tuxfamily.org>\n---\n trailer.c | 129 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++\n 1 file changed, 129 insertions(+)\n\ndiff --git a/trailer.c b/trailer.c\nindex d88de3f..e7d8244 100644\n--- a/trailer.c\n+++ b/trailer.c\n@@ -25,6 +25,8 @@ struct trailer_item {\n \tstruct conf_info *conf;\n };\n \n+static struct trailer_item *first_conf_item;\n+\n static int same_token(struct trailer_item *a, struct trailer_item *b, int alnum_len)\n {\n \treturn !strncasecmp(a->token, b->token, alnum_len);\n@@ -234,3 +236,130 @@ static void process_trailers_lists(struct trailer_item **infile_tok_first,\n \t\tapply_arg_if_missing(infile_tok_first, infile_tok_last, arg_tok);\n \t}\n }\n+\n+static int set_where(struct conf_info *item, const char *value)\n+{\n+\tif (!strcasecmp(\"after\", value)) {\n+\t\titem->where = WHERE_AFTER;\n+\t} else if (!strcasecmp(\"before\", value)) {\n+\t\titem->where = WHERE_BEFORE;\n+\t} else\n+\t\treturn 1;\n+\treturn 0;\n+}\n+\n+static int set_if_exist(struct conf_info *item, const char *value)\n+{\n+\tif (!strcasecmp(\"addIfDifferent\", value)) {\n+\t\titem->if_exist = EXIST_ADD_IF_DIFFERENT;\n+\t} else if (!strcasecmp(\"addIfDifferentNeighbor\", value)) {\n+\t\titem->if_exist = EXIST_ADD_IF_DIFFERENT_NEIGHBOR;\n+\t} else if (!strcasecmp(\"add\", value)) {\n+\t\titem->if_exist = EXIST_ADD;\n+\t} else if (!strcasecmp(\"overwrite\", value)) {\n+\t\titem->if_exist = EXIST_OVERWRITE;\n+\t} else if (!strcasecmp(\"doNothing\", value)) {\n+\t\titem->if_exist = EXIST_DO_NOTHING;\n+\t} else\n+\t\treturn 1;\n+\treturn 0;\n+}\n+\n+static int set_if_missing(struct conf_info *item, const char *value)\n+{\n+\tif (!strcasecmp(\"doNothing\", value)) {\n+\t\titem->if_missing = MISSING_DO_NOTHING;\n+\t} else if (!strcasecmp(\"add\", value)) {\n+\t\titem->if_missing = MISSING_ADD;\n+\t} else\n+\t\treturn 1;\n+\treturn 0;\n+}\n+\n+enum trailer_info_type { TRAILER_VALUE, TRAILER_COMMAND, TRAILER_WHERE,\n+\t\t\t TRAILER_IF_EXIST, TRAILER_IF_MISSING };\n+\n+static int set_name_and_type(const char *conf_key, const char *suffix,\n+\t\t\t     enum trailer_info_type type,\n+\t\t\t     char **pname, enum trailer_info_type *ptype)\n+{\n+\tint ret = ends_with(conf_key, suffix);\n+\tif (ret) {\n+\t\t*pname = xstrndup(conf_key, strlen(conf_key) - strlen(suffix));\n+\t\t*ptype = type;\n+\t}\n+\treturn ret;\n+}\n+\n+static struct trailer_item *get_conf_item(char *name)\n+{\n+\tstruct trailer_item *item;\n+\tstruct trailer_item *previous;\n+\n+\t/* Look up item with same name */\n+\tfor (previous = NULL, item = first_conf_item;\n+\t     item;\n+\t     previous = item, item = item->next)\n+\t{\n+\t\tif (!strcasecmp(item->conf->name, name))\n+\t\t\treturn item;\n+\t}\n+\n+\t/* Item does not already exists, create it */\n+\titem = xcalloc(sizeof(struct trailer_item), 1);\n+\titem->conf = xcalloc(sizeof(struct conf_info), 1);\n+\titem->conf->name = xstrdup(name);\n+\n+\tif (!previous) {\n+\t\tfirst_conf_item = item;\n+\t} else {\n+\t\tprevious->next = item;\n+\t\titem->previous = previous;\n+\t}\n+\n+\treturn item;\n+}\n+\n+static int git_trailer_config(const char *conf_key, const char *value, void *cb)\n+{\n+\tif (starts_with(conf_key, \"trailer.\")) {\n+\t\tconst char *orig_conf_key = conf_key;\n+\t\tstruct trailer_item *item;\n+\t\tstruct conf_info *conf;\n+\t\tchar *name;\n+\t\tenum trailer_info_type type;\n+\n+\t\tconf_key += 8;\n+\t\tif (!set_name_and_type(conf_key, \".key\", TRAILER_VALUE, &name, &type) &&\n+\t\t    !set_name_and_type(conf_key, \".command\", TRAILER_COMMAND, &name, &type) &&\n+\t\t    !set_name_and_type(conf_key, \".where\", TRAILER_WHERE, &name, &type) &&\n+\t\t    !set_name_and_type(conf_key, \".ifexist\", TRAILER_IF_EXIST, &name, &type) &&\n+\t\t    !set_name_and_type(conf_key, \".ifmissing\", TRAILER_IF_MISSING, &name, &type))\n+\t\t\treturn 0;\n+\n+\t\titem = get_conf_item(name);\n+\t\tconf = item->conf;\n+\n+\t\tif (type == TRAILER_VALUE) {\n+\t\t\tif (conf->key)\n+\t\t\t\twarning(_(\"more than one %s\"), orig_conf_key);\n+\t\t\tconf->key = xstrdup(value);\n+\t\t} else if (type == TRAILER_COMMAND) {\n+\t\t\tif (conf->command)\n+\t\t\t\twarning(_(\"more than one %s\"), orig_conf_key);\n+\t\t\tconf->command = xstrdup(value);\n+\t\t} else if (type == TRAILER_WHERE) {\n+\t\t\tif (set_where(conf, value))\n+\t\t\t\twarning(_(\"unknown value '%s' for key '%s'\"), value, orig_conf_key);\n+\t\t} else if (type == TRAILER_IF_EXIST) {\n+\t\t\tif (set_if_exist(conf, value))\n+\t\t\t\twarning(_(\"unknown value '%s' for key '%s'\"), value, orig_conf_key);\n+\t\t} else if (type == TRAILER_IF_MISSING) {\n+\t\t\tif (set_if_missing(conf, value))\n+\t\t\t\twarning(_(\"unknown value '%s' for key '%s'\"), value, orig_conf_key);\n+\t\t} else {\n+\t\t\tdie(\"internal bug in trailer.c\");\n+\t\t}\n+\t}\n+\treturn 0;\n+}\n-- \n1.8.5.2.201.gacc5987\n"},{"id":"233790","messageId":"20140126170011.24291.14120.chriscool@tuxfamily.org","threadId":"35736","inReplyTo":"20140126165018.24291.47716.chriscool@tuxfamily.org","subject":"[PATCH v3 04/17] trailer: process command line trailer arguments","fromName":"Christian Couder","fromEmail":"chriscool@tuxfamily.org","sentAt":"2014-01-26T16:59:57Z","receivedAt":"2014-01-26T16:59:57Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"This patch parses the trailer command line arguments\nand put the result into an arg_tok doubly linked\nlist.\n\nSigned-off-by: Christian Couder <chriscool@tuxfamily.org>\n---\n trailer.c | 77 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++\n 1 file changed, 77 insertions(+)\n\ndiff --git a/trailer.c b/trailer.c\nindex e7d8244..89377f2 100644\n--- a/trailer.c\n+++ b/trailer.c\n@@ -363,3 +363,80 @@ static int git_trailer_config(const char *conf_key, const char *value, void *cb)\n \t}\n \treturn 0;\n }\n+\n+static void parse_trailer(struct strbuf *tok, struct strbuf *val, const char *trailer)\n+{\n+\tchar *end = strchr(trailer, '=');\n+\tif (!end)\n+\t\tend = strchr(trailer, ':');\n+\tif (end) {\n+\t\tstrbuf_add(tok, trailer, end - trailer);\n+\t\tstrbuf_trim(tok);\n+\t\tstrbuf_addstr(val, end + 1);\n+\t\tstrbuf_trim(val);\n+\t} else {\n+\t\tstrbuf_addstr(tok, trailer);\n+\t\tstrbuf_trim(tok);\n+\t}\n+}\n+\n+static struct trailer_item *create_trailer_item(const char *string)\n+{\n+\tstruct strbuf tok = STRBUF_INIT;\n+\tstruct strbuf val = STRBUF_INIT;\n+\tstruct trailer_item *new;\n+\tstruct trailer_item *item;\n+\tint tok_alnum_len;\n+\n+\tparse_trailer(&tok, &val, string);\n+\n+\ttok_alnum_len = alnum_len(tok.buf, tok.len);\n+\n+\t/* Lookup if the token matches something in the config */\n+\tfor (item = first_conf_item; item; item = item->next) {\n+\t\tif (!strncasecmp(tok.buf, item->conf->key, tok_alnum_len) ||\n+\t\t    !strncasecmp(tok.buf, item->conf->name, tok_alnum_len)) {\n+\t\t\tnew = xcalloc(sizeof(struct trailer_item), 1);\n+\t\t\tnew->conf = item->conf;\n+\t\t\tnew->token = xstrdup(item->conf->key);\n+\t\t\tnew->value = strbuf_detach(&val, NULL);\n+\t\t\tstrbuf_release(&tok);\n+\t\t\treturn new;\n+\t\t}\n+\t}\n+\n+\tnew = xcalloc(sizeof(struct trailer_item), 1);\n+\tnew->conf = xcalloc(sizeof(struct conf_info), 1);\n+\tnew->token = strbuf_detach(&tok, NULL);\n+\tnew->value = strbuf_detach(&val, NULL);\n+\n+\treturn new;\n+}\n+\n+static void add_trailer_item(struct trailer_item **first,\n+\t\t\t     struct trailer_item **last,\n+\t\t\t     struct trailer_item *new)\n+{\n+\tif (!*last) {\n+\t\t*first = new;\n+\t\t*last = new;\n+\t} else {\n+\t\t(*last)->next = new;\n+\t\tnew->previous = *last;\n+\t\t*last = new;\n+\t}\n+}\n+\n+static struct trailer_item *process_command_line_args(int argc, const char **argv)\n+{\n+\tint i;\n+\tstruct trailer_item *arg_tok_first = NULL;\n+\tstruct trailer_item *arg_tok_last = NULL;\n+\n+\tfor (i = 0; i < argc; i++) {\n+\t\tstruct trailer_item *new = create_trailer_item(argv[i]);\n+\t\tadd_trailer_item(&arg_tok_first, &arg_tok_last, new);\n+\t}\n+\n+\treturn arg_tok_first;\n+}\n-- \n1.8.5.2.201.gacc5987\n"},{"id":"233789","messageId":"20140126170011.24291.54182.chriscool@tuxfamily.org","threadId":"35736","inReplyTo":"20140126165018.24291.47716.chriscool@tuxfamily.org","subject":"[PATCH v3 05/17] strbuf: add strbuf_isspace()","fromName":"Christian Couder","fromEmail":"chriscool@tuxfamily.org","sentAt":"2014-01-26T16:59:58Z","receivedAt":"2014-01-26T16:59:58Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"This helper function checks if a strbuf\ncontains only space chars or not.\n\nSigned-off-by: Christian Couder <chriscool@tuxfamily.org>\n---\n strbuf.c | 7 +++++++\n strbuf.h | 1 +\n 2 files changed, 8 insertions(+)\n\ndiff --git a/strbuf.c b/strbuf.c\nindex 83caf4a..2124bb8 100644\n--- a/strbuf.c\n+++ b/strbuf.c\n@@ -124,6 +124,13 @@ void strbuf_ltrim(struct strbuf *sb)\n \tsb->buf[sb->len] = '\\0';\n }\n \n+int strbuf_isspace(struct strbuf *sb)\n+{\n+\tchar *b;\n+\tfor (b = sb->buf; *b && isspace(*b); b++);\n+\treturn !*b;\n+}\n+\n struct strbuf **strbuf_split_buf(const char *str, size_t slen,\n \t\t\t\t int terminator, int max)\n {\ndiff --git a/strbuf.h b/strbuf.h\nindex 73e80ce..02bff3a 100644\n--- a/strbuf.h\n+++ b/strbuf.h\n@@ -42,6 +42,7 @@ static inline void strbuf_setlen(struct strbuf *sb, size_t len) {\n extern void strbuf_trim(struct strbuf *);\n extern void strbuf_rtrim(struct strbuf *);\n extern void strbuf_ltrim(struct strbuf *);\n+extern int strbuf_isspace(struct strbuf *);\n extern int strbuf_cmp(const struct strbuf *, const struct strbuf *);\n \n /*\n-- \n1.8.5.2.201.gacc5987\n"},{"id":"233777","messageId":"20140126170011.24291.61923.chriscool@tuxfamily.org","threadId":"35736","inReplyTo":"20140126165018.24291.47716.chriscool@tuxfamily.org","subject":"[PATCH v3 06/17] trailer: parse trailers from input file","fromName":"Christian Couder","fromEmail":"chriscool@tuxfamily.org","sentAt":"2014-01-26T16:59:59Z","receivedAt":"2014-01-26T16:59:59Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"This patch reads trailers from an input file, parses\nthem and puts the result into a doubly linked list.\n\nSigned-off-by: Christian Couder <chriscool@tuxfamily.org>\n---\n trailer.c | 62 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++\n 1 file changed, 62 insertions(+)\n\ndiff --git a/trailer.c b/trailer.c\nindex 89377f2..9ea08a7 100644\n--- a/trailer.c\n+++ b/trailer.c\n@@ -440,3 +440,65 @@ static struct trailer_item *process_command_line_args(int argc, const char **arg\n \n \treturn arg_tok_first;\n }\n+\n+static struct strbuf **read_input_file(const char *infile)\n+{\n+\tstruct strbuf sb = STRBUF_INIT;\n+\n+\tif (strbuf_read_file(&sb, infile, 0) < 0)\n+\t\tdie_errno(_(\"could not read input file '%s'\"), infile);\n+\n+\treturn strbuf_split(&sb, '\\n');\n+}\n+\n+/*\n+ * Return the the (0 based) index of the first trailer line\n+ * or the line count if there are no trailers.\n+ */\n+static int find_trailer_start(struct strbuf **lines)\n+{\n+\tint count, start, empty = 1;\n+\n+\t/* Get the line count */\n+\tfor (count = 0; lines[count]; count++);\n+\n+\t/*\n+\t * Get the start of the trailers by looking starting from the end\n+\t * for a line with only spaces before lines with one ':'.\n+\t */\n+\tfor (start = count - 1; start >= 0; start--) {\n+\t\tif (strbuf_isspace(lines[start])) {\n+\t\t\tif (empty)\n+\t\t\t\tcontinue;\n+\t\t\treturn start + 1;\n+\t\t}\n+\t\tif (strchr(lines[start]->buf, ':')) {\n+\t\t\tif (empty)\n+\t\t\t\tempty = 0;\n+\t\t\tcontinue;\n+\t\t}\n+\t\treturn count;\n+\t}\n+\n+\treturn empty ? count : start + 1;\n+}\n+\n+static void process_input_file(const char *infile,\n+\t\t\t       struct trailer_item **infile_tok_first,\n+\t\t\t       struct trailer_item **infile_tok_last)\n+{\n+\tstruct strbuf **lines = read_input_file(infile);\n+\tint start = find_trailer_start(lines);\n+\tint i;\n+\n+\t/* Print non trailer lines as is */\n+\tfor (i = 0; lines[i] && i < start; i++) {\n+\t\tprintf(\"%s\", lines[i]->buf);\n+\t}\n+\n+\t/* Parse trailer lines */\n+\tfor (i = start; lines[i]; i++) {\n+\t\tstruct trailer_item *new = create_trailer_item(lines[i]->buf);\n+\t\tadd_trailer_item(infile_tok_first, infile_tok_last, new);\n+\t}\n+}\n-- \n1.8.5.2.201.gacc5987\n"},{"id":"233776","messageId":"20140126170011.24291.23462.chriscool@tuxfamily.org","threadId":"35736","inReplyTo":"20140126165018.24291.47716.chriscool@tuxfamily.org","subject":"[PATCH v3 07/17] trailer: put all the processing together and print","fromName":"Christian Couder","fromEmail":"chriscool@tuxfamily.org","sentAt":"2014-01-26T17:00:00Z","receivedAt":"2014-01-26T17:00:00Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"This patch adds the process_trailers() function that\ncalls all the previously added processing functions\nand then prints the results on the standard output.\n\nSigned-off-by: Christian Couder <chriscool@tuxfamily.org>\n---\n trailer.c | 40 ++++++++++++++++++++++++++++++++++++++++\n 1 file changed, 40 insertions(+)\n\ndiff --git a/trailer.c b/trailer.c\nindex 9ea08a7..2678b3e 100644\n--- a/trailer.c\n+++ b/trailer.c\n@@ -48,6 +48,26 @@ static size_t alnum_len(const char *buf, size_t len) {\n \treturn len + 1;\n }\n \n+static void print_tok_val(const char *tok, const char *val)\n+{\n+\tchar c = tok[strlen(tok) - 1];\n+\tif (isalnum(c))\n+\t\tprintf(\"%s: %s\\n\", tok, val);\n+\telse if (isspace(c) || c == '#')\n+\t\tprintf(\"%s%s\\n\", tok, val);\n+\telse\n+\t\tprintf(\"%s %s\\n\", tok, val);\n+}\n+\n+static void print_all(struct trailer_item *first, int trim_empty)\n+{\n+\tstruct trailer_item *item;\n+\tfor (item = first; item; item = item->next) {\n+\t\tif (!trim_empty || strlen(item->value) > 0)\n+\t\t\tprint_tok_val(item->token, item->value);\n+\t}\n+}\n+\n static void add_arg_to_infile(struct trailer_item *infile_tok,\n \t\t\t      struct trailer_item *arg_tok)\n {\n@@ -502,3 +522,23 @@ static void process_input_file(const char *infile,\n \t\tadd_trailer_item(infile_tok_first, infile_tok_last, new);\n \t}\n }\n+\n+void process_trailers(const char *infile, int trim_empty, int argc, const char **argv)\n+{\n+\tstruct trailer_item *infile_tok_first = NULL;\n+\tstruct trailer_item *infile_tok_last = NULL;\n+\tstruct trailer_item *arg_tok_first;\n+\n+\tgit_config(git_trailer_config, NULL);\n+\n+\t/* Print the non trailer part of infile */\n+\tif (infile) {\n+\t\tprocess_input_file(infile, &infile_tok_first, &infile_tok_last);\n+\t}\n+\n+\targ_tok_first = process_command_line_args(argc, argv);\n+\n+\tprocess_trailers_lists(&infile_tok_first, &infile_tok_last, &arg_tok_first);\n+\n+\tprint_all(infile_tok_first, trim_empty);\n+}\n-- \n1.8.5.2.201.gacc5987\n"},{"id":"233786","messageId":"20140126170011.24291.3373.chriscool@tuxfamily.org","threadId":"35736","inReplyTo":"20140126165018.24291.47716.chriscool@tuxfamily.org","subject":"[PATCH v3 08/17] trailer: add interpret-trailers command","fromName":"Christian Couder","fromEmail":"chriscool@tuxfamily.org","sentAt":"2014-01-26T17:00:01Z","receivedAt":"2014-01-26T17:00:01Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"This patch adds the \"git interpret-trailers\" command.\nThis command uses the previously added process_trailers()\nfunction in trailer.c.\n\nSigned-off-by: Christian Couder <chriscool@tuxfamily.org>\n---\n .gitignore                   |  1 +\n Makefile                     |  1 +\n builtin.h                    |  1 +\n builtin/interpret-trailers.c | 36 ++++++++++++++++++++++++++++++++++++\n git.c                        |  1 +\n trailer.h                    |  6 ++++++\n 6 files changed, 46 insertions(+)\n create mode 100644 builtin/interpret-trailers.c\n create mode 100644 trailer.h\n\ndiff --git a/.gitignore b/.gitignore\nindex b5f9def..c870ada 100644\n--- a/.gitignore\n+++ b/.gitignore\n@@ -74,6 +74,7 @@\n /git-index-pack\n /git-init\n /git-init-db\n+/git-interpret-trailers\n /git-instaweb\n /git-log\n /git-ls-files\ndiff --git a/Makefile b/Makefile\nindex ec90feb..a91465e 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -935,6 +935,7 @@ BUILTIN_OBJS += builtin/hash-object.o\n BUILTIN_OBJS += builtin/help.o\n BUILTIN_OBJS += builtin/index-pack.o\n BUILTIN_OBJS += builtin/init-db.o\n+BUILTIN_OBJS += builtin/interpret-trailers.o\n BUILTIN_OBJS += builtin/log.o\n BUILTIN_OBJS += builtin/ls-files.o\n BUILTIN_OBJS += builtin/ls-remote.o\ndiff --git a/builtin.h b/builtin.h\nindex d4afbfe..30f4c30 100644\n--- a/builtin.h\n+++ b/builtin.h\n@@ -71,6 +71,7 @@ extern int cmd_hash_object(int argc, const char **argv, const char *prefix);\n extern int cmd_help(int argc, const char **argv, const char *prefix);\n extern int cmd_index_pack(int argc, const char **argv, const char *prefix);\n extern int cmd_init_db(int argc, const char **argv, const char *prefix);\n+extern int cmd_interpret_trailers(int argc, const char **argv, const char *prefix);\n extern int cmd_log(int argc, const char **argv, const char *prefix);\n extern int cmd_log_reflog(int argc, const char **argv, const char *prefix);\n extern int cmd_ls_files(int argc, const char **argv, const char *prefix);\ndiff --git a/builtin/interpret-trailers.c b/builtin/interpret-trailers.c\nnew file mode 100644\nindex 0000000..f79bffa\n--- /dev/null\n+++ b/builtin/interpret-trailers.c\n@@ -0,0 +1,36 @@\n+/*\n+ * Builtin \"git interpret-trailers\"\n+ *\n+ * Copyright (c) 2013 Christian Couder <chriscool@tuxfamily.org>\n+ *\n+ */\n+\n+#include \"cache.h\"\n+#include \"builtin.h\"\n+#include \"parse-options.h\"\n+#include \"strbuf.h\"\n+#include \"trailer.h\"\n+\n+static const char * const git_interpret_trailers_usage[] = {\n+\tN_(\"git interpret-trailers [--trim-empty] [--infile=file] [<token[=value]>...]\"),\n+\tNULL\n+};\n+\n+int cmd_interpret_trailers(int argc, const char **argv, const char *prefix)\n+{\n+\tconst char *infile = NULL;\n+\tint trim_empty = 0;\n+\n+\tstruct option options[] = {\n+\t\tOPT_BOOL(0, \"trim-empty\", &trim_empty, N_(\"trim empty trailers\")),\n+\t\tOPT_FILENAME(0, \"infile\", &infile, N_(\"use message from file\")),\n+\t\tOPT_END()\n+\t};\n+\n+\targc = parse_options(argc, argv, prefix, options,\n+\t\t\t     git_interpret_trailers_usage, 0);\n+\n+\tprocess_trailers(infile, trim_empty, argc, argv);\n+\n+\treturn 0;\n+}\ndiff --git a/git.c b/git.c\nindex 3799514..1420b58 100644\n--- a/git.c\n+++ b/git.c\n@@ -383,6 +383,7 @@ static void handle_internal_command(int argc, const char **argv)\n \t\t{ \"index-pack\", cmd_index_pack, RUN_SETUP_GENTLY },\n \t\t{ \"init\", cmd_init_db },\n \t\t{ \"init-db\", cmd_init_db },\n+\t\t{ \"interpret-trailers\", cmd_interpret_trailers, RUN_SETUP },\n \t\t{ \"log\", cmd_log, RUN_SETUP },\n \t\t{ \"ls-files\", cmd_ls_files, RUN_SETUP },\n \t\t{ \"ls-remote\", cmd_ls_remote, RUN_SETUP_GENTLY },\ndiff --git a/trailer.h b/trailer.h\nnew file mode 100644\nindex 0000000..9db4459\n--- /dev/null\n+++ b/trailer.h\n@@ -0,0 +1,6 @@\n+#ifndef TRAILER_H\n+#define TRAILER_H\n+\n+void process_trailers(const char *infile, int trim_empty, int argc, const char **argv);\n+\n+#endif /* TRAILER_H */\n-- \n1.8.5.2.201.gacc5987\n"},{"id":"233787","messageId":"20140126170011.24291.47211.chriscool@tuxfamily.org","threadId":"35736","inReplyTo":"20140126165018.24291.47716.chriscool@tuxfamily.org","subject":"[PATCH v3 09/17] trailer: add tests for \"git interpret-trailers\"","fromName":"Christian Couder","fromEmail":"chriscool@tuxfamily.org","sentAt":"2014-01-26T17:00:02Z","receivedAt":"2014-01-26T17:00:02Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"Signed-off-by: Christian Couder <chriscool@tuxfamily.org>\n---\n t/t7513-interpret-trailers.sh | 208 ++++++++++++++++++++++++++++++++++++++++++\n 1 file changed, 208 insertions(+)\n create mode 100755 t/t7513-interpret-trailers.sh\n\ndiff --git a/t/t7513-interpret-trailers.sh b/t/t7513-interpret-trailers.sh\nnew file mode 100755\nindex 0000000..8be333c\n--- /dev/null\n+++ b/t/t7513-interpret-trailers.sh\n@@ -0,0 +1,208 @@\n+#!/bin/sh\n+#\n+# Copyright (c) 2013 Christian Couder\n+#\n+\n+test_description='git interpret-trailers'\n+\n+. ./test-lib.sh\n+\n+cat >basic_message <<'EOF'\n+subject\n+\n+body\n+EOF\n+\n+cat >complex_message_body <<'EOF'\n+my subject\n+\n+my body which is long\n+and contains some special\n+chars like : = ? !\n+\n+EOF\n+\n+# We want one trailing space at the end of each line.\n+# Let's use sed to make sure that these spaces are not removed\n+# by any automatic tool.\n+sed -e 's/ Z$/ /' >complex_message_trailers <<-\\EOF\n+Fixes: Z\n+Acked-by: Z\n+Reviewed-by: Z\n+Signed-off-by: Z\n+EOF\n+\n+test_expect_success 'without config' '\n+\tprintf \"ack: Peff\\nReviewed-by: \\nAcked-by: Johan\\n\" >expected &&\n+\tgit interpret-trailers \"ack = Peff\" \"Reviewed-by\" \"Acked-by: Johan\" >actual &&\n+\ttest_cmp expected actual\n+'\n+\n+test_expect_success '--trim-empty without config' '\n+\tprintf \"ack: Peff\\nAcked-by: Johan\\n\" >expected &&\n+\tgit interpret-trailers --trim-empty \"ack = Peff\" \"Reviewed-by\" \"Acked-by: Johan\" \"sob:\" >actual &&\n+\ttest_cmp expected actual\n+'\n+\n+test_expect_success 'with config setup' '\n+\tgit config trailer.ack.key \"Acked-by: \" &&\n+\tprintf \"Acked-by: Peff\\n\" >expected &&\n+\tgit interpret-trailers --trim-empty \"ack = Peff\" >actual &&\n+\ttest_cmp expected actual &&\n+\tgit interpret-trailers --trim-empty \"Acked-by = Peff\" >actual &&\n+\ttest_cmp expected actual &&\n+\tgit interpret-trailers --trim-empty \"Acked-by :Peff\" >actual &&\n+\ttest_cmp expected actual\n+'\n+\n+test_expect_success 'with config setup and = sign' '\n+\tgit config trailer.ack.key \"Acked-by= \" &&\n+\tprintf \"Acked-by= Peff\\n\" >expected &&\n+\tgit interpret-trailers --trim-empty \"ack = Peff\" >actual &&\n+\ttest_cmp expected actual &&\n+\tgit interpret-trailers --trim-empty \"Acked-by= Peff\" >actual &&\n+\ttest_cmp expected actual &&\n+\tgit interpret-trailers --trim-empty \"Acked-by : Peff\" >actual &&\n+\ttest_cmp expected actual\n+'\n+\n+test_expect_success 'with config setup and # sign' '\n+\tgit config trailer.bug.key \"Bug #\" &&\n+\tprintf \"Bug #42\\n\" >expected &&\n+\tgit interpret-trailers --trim-empty \"bug = 42\" >actual &&\n+\ttest_cmp expected actual\n+'\n+\n+test_expect_success 'with commit basic message' '\n+\tgit interpret-trailers --infile basic_message >actual &&\n+\ttest_cmp basic_message actual\n+'\n+\n+test_expect_success 'with commit complex message' '\n+\tcat complex_message_body complex_message_trailers >complex_message &&\n+\tcat complex_message_body >expected &&\n+\tprintf \"Fixes: \\nAcked-by= \\nReviewed-by: \\nSigned-off-by: \\n\" >>expected &&\n+\tgit interpret-trailers --infile complex_message >actual &&\n+\ttest_cmp expected actual\n+'\n+\n+test_expect_success 'with commit complex message and args' '\n+\tcat complex_message_body >expected &&\n+\tprintf \"Fixes: \\nAcked-by= \\nAcked-by= Peff\\nReviewed-by: \\nSigned-off-by: \\nBug #42\\n\" >>expected &&\n+\tgit interpret-trailers --infile complex_message \"ack: Peff\" \"bug: 42\" >actual &&\n+\ttest_cmp expected actual\n+'\n+\n+test_expect_success 'with commit complex message, args and --trim-empty' '\n+\tcat complex_message_body >expected &&\n+\tprintf \"Acked-by= Peff\\nBug #42\\n\" >>expected &&\n+\tgit interpret-trailers --trim-empty --infile complex_message \"ack: Peff\" \"bug: 42\" >actual &&\n+\ttest_cmp expected actual\n+'\n+\n+test_expect_success 'using \"where = before\"' '\n+\tgit config trailer.bug.where \"before\" &&\n+\tcat complex_message_body >expected &&\n+\tprintf \"Bug #42\\nFixes: \\nAcked-by= \\nAcked-by= Peff\\nReviewed-by: \\nSigned-off-by: \\n\" >>expected &&\n+\tgit interpret-trailers --infile complex_message \"ack: Peff\" \"bug: 42\" >actual &&\n+\ttest_cmp expected actual\n+'\n+\n+test_expect_success 'using \"where = before\" for a token in the middle of infile' '\n+\tgit config trailer.review.key \"Reviewed-by:\" &&\n+\tgit config trailer.review.where \"before\" &&\n+\tcat complex_message_body >expected &&\n+\tprintf \"Bug #42\\nFixes: \\nAcked-by= \\nAcked-by= Peff\\nReviewed-by: Johan\\nReviewed-by: \\nSigned-off-by: \\n\" >>expected &&\n+\tgit interpret-trailers --infile complex_message \"ack: Peff\" \"bug: 42\" \"review: Johan\" >actual &&\n+\ttest_cmp expected actual\n+'\n+\n+test_expect_success 'using \"where = before\" and --trim-empty' '\n+\tcat complex_message_body >expected &&\n+\tprintf \"Bug #46\\nBug #42\\nAcked-by= Peff\\nReviewed-by: Johan\\n\" >>expected &&\n+\tgit interpret-trailers --infile complex_message --trim-empty \"ack: Peff\" \"bug: 42\" \"review: Johan\" \"Bug: 46\"  >actual &&\n+\ttest_cmp expected actual\n+'\n+\n+test_expect_success 'the default is \"ifExist = addIfDifferent\"' '\n+\tcat complex_message_body >expected &&\n+\tprintf \"Bug #42\\nFixes: \\nAcked-by= \\nAcked-by= Peff\\nReviewed-by: \\nSigned-off-by: \\n\" >>expected &&\n+\tgit interpret-trailers --infile complex_message \"ack: Peff\" \"review:\" \"bug: 42\" \"ack: Peff\" >actual &&\n+\ttest_cmp expected actual\n+'\n+\n+test_expect_success 'using \"ifExist = addIfDifferent\"' '\n+\tgit config trailer.review.ifExist \"addIfDifferent\" &&\n+\tcat complex_message_body >expected &&\n+\tprintf \"Bug #42\\nFixes: \\nAcked-by= \\nAcked-by= Peff\\nReviewed-by: \\nSigned-off-by: \\n\" >>expected &&\n+\tgit interpret-trailers --infile complex_message \"ack: Peff\" \"review:\" \"bug: 42\" \"ack: Peff\" >actual &&\n+\ttest_cmp expected actual\n+'\n+\n+test_expect_success 'using \"ifExist = addIfDifferentNeighbor\"' '\n+\tgit config trailer.ack.ifExist \"addIfDifferentNeighbor\" &&\n+\tcat complex_message_body >expected &&\n+\tprintf \"Bug #42\\nFixes: \\nAcked-by= \\nAcked-by= Peff\\nAcked-by= Junio\\nAcked-by= Peff\\nReviewed-by: \\nSigned-off-by: \\n\" >>expected &&\n+\tgit interpret-trailers --infile complex_message \"ack: Peff\" \"review:\" \"ack: Junio\" \"bug: 42\" \"ack: Peff\" >actual &&\n+\ttest_cmp expected actual\n+'\n+\n+test_expect_success 'using \"ifExist = addIfDifferentNeighbor\" and --trim-empty' '\n+\tgit config trailer.ack.ifExist \"addIfDifferentNeighbor\" &&\n+\tcat complex_message_body >expected &&\n+\tprintf \"Bug #42\\nAcked-by= Peff\\nAcked-by= Junio\\nAcked-by= Peff\\n\" >>expected &&\n+\tgit interpret-trailers --infile complex_message --trim-empty \"ack: Peff\" \"Acked-by= Peff\" \"review:\" \"ack: Junio\" \"bug: 42\" \"ack: Peff\" >actual &&\n+\ttest_cmp expected actual\n+'\n+\n+test_expect_success 'using \"ifExist = add\"' '\n+\tgit config trailer.ack.ifExist \"add\" &&\n+\tcat complex_message_body >expected &&\n+\tprintf \"Bug #42\\nFixes: \\nAcked-by= \\nAcked-by= Peff\\nAcked-by= Peff\\nAcked-by= Junio\\nAcked-by= Peff\\nReviewed-by: \\nSigned-off-by: \\n\" >>expected &&\n+\tgit interpret-trailers --infile complex_message \"ack: Peff\" \"Acked-by= Peff\" \"review:\" \"ack: Junio\" \"bug: 42\" \"ack: Peff\" >actual &&\n+\ttest_cmp expected actual\n+'\n+\n+test_expect_success 'using \"ifExist = overwrite\"' '\n+\tgit config trailer.fix.key \"Fixes:\" &&\n+\tgit config trailer.fix.ifExist \"overwrite\" &&\n+\tcat complex_message_body >expected &&\n+\tprintf \"Bug #42\\nFixes: 22\\nAcked-by= \\nAcked-by= Junio\\nAcked-by= Peff\\nReviewed-by: \\nSigned-off-by: \\n\" >>expected &&\n+\tgit interpret-trailers --infile complex_message \"review:\" \"fix=53\" \"ack: Junio\" \"fix=22\" \"bug: 42\" \"ack: Peff\" >actual &&\n+\ttest_cmp expected actual\n+'\n+\n+test_expect_success 'using \"ifExist = doNothing\"' '\n+\tgit config trailer.fix.ifExist \"doNothing\" &&\n+\tcat complex_message_body >expected &&\n+\tprintf \"Bug #42\\nFixes: \\nAcked-by= \\nAcked-by= Junio\\nAcked-by= Peff\\nReviewed-by: \\nSigned-off-by: \\n\" >>expected &&\n+\tgit interpret-trailers --infile complex_message \"review:\" \"fix=53\" \"ack: Junio\" \"fix=22\" \"bug: 42\" \"ack: Peff\" >actual &&\n+\ttest_cmp expected actual\n+'\n+\n+test_expect_success 'the default is \"ifMissing = add\"' '\n+\tgit config trailer.cc.key \"Cc: \" &&\n+\tgit config trailer.Cc.where \"before\" &&\n+\tcat complex_message_body >expected &&\n+\tprintf \"Bug #42\\nCc: Linus\\nFixes: \\nAcked-by= \\nAcked-by= Junio\\nAcked-by= Peff\\nReviewed-by: \\nSigned-off-by: \\n\" >>expected &&\n+\tgit interpret-trailers --infile complex_message \"review:\" \"fix=53\" \"cc=Linus\" \"ack: Junio\" \"fix=22\" \"bug: 42\" \"ack: Peff\" >actual &&\n+\ttest_cmp expected actual\n+'\n+\n+test_expect_success 'using \"ifMissing = add\"' '\n+\tgit config trailer.Cc.ifMissing \"add\" &&\n+\tcat complex_message_body >expected &&\n+\tprintf \"Cc: Linus\\nBug #42\\nFixes: \\nAcked-by= \\nAcked-by= Junio\\nAcked-by= Peff\\nReviewed-by: \\nSigned-off-by: \\n\" >>expected &&\n+\tgit interpret-trailers --infile complex_message \"review:\" \"fix=53\" \"ack: Junio\" \"fix=22\" \"bug: 42\" \"cc=Linus\" \"ack: Peff\" >actual &&\n+\ttest_cmp expected actual\n+'\n+\n+test_expect_success 'using \"ifMissing = doNothing\"' '\n+\tgit config trailer.Cc.ifMissing \"doNothing\" &&\n+\tcat complex_message_body >expected &&\n+\tprintf \"Bug #42\\nFixes: \\nAcked-by= \\nAcked-by= Junio\\nAcked-by= Peff\\nReviewed-by: \\nSigned-off-by: \\n\" >>expected &&\n+\tgit interpret-trailers --infile complex_message \"review:\" \"fix=53\" \"cc=Linus\" \"ack: Junio\" \"fix=22\" \"bug: 42\" \"ack: Peff\" >actual &&\n+\ttest_cmp expected actual\n+'\n+\n+test_done\n-- \n1.8.5.2.201.gacc5987\n"},{"id":"233785","messageId":"20140126170011.24291.56679.chriscool@tuxfamily.org","threadId":"35736","inReplyTo":"20140126165018.24291.47716.chriscool@tuxfamily.org","subject":"[PATCH v3 10/17] trailer: if no input file is passed, read from stdin","fromName":"Christian Couder","fromEmail":"chriscool@tuxfamily.org","sentAt":"2014-01-26T17:00:03Z","receivedAt":"2014-01-26T17:00:03Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"It is simpler and more natural if the \"git interpret-trailers\"\nis made a filter as its output already goes to sdtout.\n\nSigned-off-by: Christian Couder <chriscool@tuxfamily.org>\n---\n builtin/interpret-trailers.c  |  2 +-\n t/t7513-interpret-trailers.sh |  7 +++++++\n trailer.c                     | 15 +++++++++------\n 3 files changed, 17 insertions(+), 7 deletions(-)\n\ndiff --git a/builtin/interpret-trailers.c b/builtin/interpret-trailers.c\nindex f79bffa..37237f7 100644\n--- a/builtin/interpret-trailers.c\n+++ b/builtin/interpret-trailers.c\n@@ -23,7 +23,7 @@ int cmd_interpret_trailers(int argc, const char **argv, const char *prefix)\n \n \tstruct option options[] = {\n \t\tOPT_BOOL(0, \"trim-empty\", &trim_empty, N_(\"trim empty trailers\")),\n-\t\tOPT_FILENAME(0, \"infile\", &infile, N_(\"use message from file\")),\n+\t\tOPT_FILENAME(0, \"infile\", &infile, N_(\"use message from file, instead of stdin\")),\n \t\tOPT_END()\n \t};\n \ndiff --git a/t/t7513-interpret-trailers.sh b/t/t7513-interpret-trailers.sh\nindex 8be333c..f5ef81f 100755\n--- a/t/t7513-interpret-trailers.sh\n+++ b/t/t7513-interpret-trailers.sh\n@@ -205,4 +205,11 @@ test_expect_success 'using \"ifMissing = doNothing\"' '\n \ttest_cmp expected actual\n '\n \n+test_expect_success 'with input from stdin' '\n+\tcat complex_message_body >expected &&\n+\tprintf \"Bug #42\\nFixes: \\nAcked-by= \\nAcked-by= Junio\\nAcked-by= Peff\\nReviewed-by: \\nSigned-off-by: \\n\" >>expected &&\n+\tgit interpret-trailers \"review:\" \"fix=53\" \"cc=Linus\" \"ack: Junio\" \"fix=22\" \"bug: 42\" \"ack: Peff\" < complex_message >actual &&\n+\ttest_cmp expected actual\n+'\n+\n test_done\ndiff --git a/trailer.c b/trailer.c\nindex 2678b3e..d581371 100644\n--- a/trailer.c\n+++ b/trailer.c\n@@ -465,8 +465,13 @@ static struct strbuf **read_input_file(const char *infile)\n {\n \tstruct strbuf sb = STRBUF_INIT;\n \n-\tif (strbuf_read_file(&sb, infile, 0) < 0)\n-\t\tdie_errno(_(\"could not read input file '%s'\"), infile);\n+\tif (infile) {\n+\t\tif (strbuf_read_file(&sb, infile, 0) < 0)\n+\t\t\tdie_errno(_(\"could not read input file '%s'\"), infile);\n+\t} else {\n+\t\tif (strbuf_read(&sb, fileno(stdin), 0) < 0)\n+\t\t\tdie_errno(_(\"could not read from stdin\"));\n+\t}\n \n \treturn strbuf_split(&sb, '\\n');\n }\n@@ -531,10 +536,8 @@ void process_trailers(const char *infile, int trim_empty, int argc, const char *\n \n \tgit_config(git_trailer_config, NULL);\n \n-\t/* Print the non trailer part of infile */\n-\tif (infile) {\n-\t\tprocess_input_file(infile, &infile_tok_first, &infile_tok_last);\n-\t}\n+\t/* Print the non trailer part of infile (or stdin if infile is NULL) */\n+\tprocess_input_file(infile, &infile_tok_first, &infile_tok_last);\n \n \targ_tok_first = process_command_line_args(argc, argv);\n \n-- \n1.8.5.2.201.gacc5987\n"},{"id":"233783","messageId":"20140126170011.24291.94439.chriscool@tuxfamily.org","threadId":"35736","inReplyTo":"20140126165018.24291.47716.chriscool@tuxfamily.org","subject":"[PATCH v3 11/17] trailer: add new_trailer_item() function","fromName":"Christian Couder","fromEmail":"chriscool@tuxfamily.org","sentAt":"2014-01-26T17:00:04Z","receivedAt":"2014-01-26T17:00:04Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"This is a small refactoring to prepare for the next steps.\n\nSigned-off-by: Christian Couder <chriscool@tuxfamily.org>\n---\n trailer.c | 31 +++++++++++++++++++------------\n 1 file changed, 19 insertions(+), 12 deletions(-)\n\ndiff --git a/trailer.c b/trailer.c\nindex d581371..6eec3ce 100644\n--- a/trailer.c\n+++ b/trailer.c\n@@ -400,11 +400,27 @@ static void parse_trailer(struct strbuf *tok, struct strbuf *val, const char *tr\n \t}\n }\n \n+static struct trailer_item *new_trailer_item(struct trailer_item *conf_item,\n+\t\t\t\t\t     const char* tok, const char* val)\n+{\n+\tstruct trailer_item *new = xcalloc(sizeof(struct trailer_item), 1);\n+\tnew->value = val;\n+\n+\tif (conf_item) {\n+\t\tnew->conf = conf_item->conf;\n+\t\tnew->token = xstrdup(conf_item->conf->key);\n+\t} else {\n+\t\tnew->conf = xcalloc(sizeof(struct conf_info), 1);\n+\t\tnew->token = tok;\n+\t}\n+\n+\treturn new;\n+}\n+\n static struct trailer_item *create_trailer_item(const char *string)\n {\n \tstruct strbuf tok = STRBUF_INIT;\n \tstruct strbuf val = STRBUF_INIT;\n-\tstruct trailer_item *new;\n \tstruct trailer_item *item;\n \tint tok_alnum_len;\n \n@@ -416,21 +432,12 @@ static struct trailer_item *create_trailer_item(const char *string)\n \tfor (item = first_conf_item; item; item = item->next) {\n \t\tif (!strncasecmp(tok.buf, item->conf->key, tok_alnum_len) ||\n \t\t    !strncasecmp(tok.buf, item->conf->name, tok_alnum_len)) {\n-\t\t\tnew = xcalloc(sizeof(struct trailer_item), 1);\n-\t\t\tnew->conf = item->conf;\n-\t\t\tnew->token = xstrdup(item->conf->key);\n-\t\t\tnew->value = strbuf_detach(&val, NULL);\n \t\t\tstrbuf_release(&tok);\n-\t\t\treturn new;\n+\t\t\treturn new_trailer_item(item, NULL, strbuf_detach(&val, NULL));\n \t\t}\n \t}\n \n-\tnew = xcalloc(sizeof(struct trailer_item), 1);\n-\tnew->conf = xcalloc(sizeof(struct conf_info), 1);\n-\tnew->token = strbuf_detach(&tok, NULL);\n-\tnew->value = strbuf_detach(&val, NULL);\n-\n-\treturn new;\n+\treturn new_trailer_item(NULL, strbuf_detach(&tok, NULL), strbuf_detach(&val, NULL));;\n }\n \n static void add_trailer_item(struct trailer_item **first,\n-- \n1.8.5.2.201.gacc5987\n"},{"id":"233779","messageId":"20140126170011.24291.50133.chriscool@tuxfamily.org","threadId":"35736","inReplyTo":"20140126165018.24291.47716.chriscool@tuxfamily.org","subject":"[PATCH v3 12/17] strbuf: add strbuf_replace()","fromName":"Christian Couder","fromEmail":"chriscool@tuxfamily.org","sentAt":"2014-01-26T17:00:05Z","receivedAt":"2014-01-26T17:00:05Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"Signed-off-by: Christian Couder <chriscool@tuxfamily.org>\n---\n strbuf.c | 7 +++++++\n strbuf.h | 3 +++\n 2 files changed, 10 insertions(+)\n\ndiff --git a/strbuf.c b/strbuf.c\nindex 2124bb8..e45e513 100644\n--- a/strbuf.c\n+++ b/strbuf.c\n@@ -197,6 +197,13 @@ void strbuf_splice(struct strbuf *sb, size_t pos, size_t len,\n \tstrbuf_setlen(sb, sb->len + dlen - len);\n }\n \n+void strbuf_replace(struct strbuf *sb, const char *a, const char *b)\n+{\n+\tchar *ptr = strstr(sb->buf, a);\n+\tif (ptr)\n+\t\tstrbuf_splice(sb, ptr - sb->buf, strlen(a), b, strlen(b));\n+}\n+\n void strbuf_insert(struct strbuf *sb, size_t pos, const void *data, size_t len)\n {\n \tstrbuf_splice(sb, pos, 0, data, len);\ndiff --git a/strbuf.h b/strbuf.h\nindex 02bff3a..38faf70 100644\n--- a/strbuf.h\n+++ b/strbuf.h\n@@ -111,6 +111,9 @@ extern void strbuf_remove(struct strbuf *, size_t pos, size_t len);\n extern void strbuf_splice(struct strbuf *, size_t pos, size_t len,\n                           const void *, size_t);\n \n+/* first occurence of a replaced with b */\n+extern void strbuf_replace(struct strbuf *, const char *a, const char *b);\n+\n extern void strbuf_add_commented_lines(struct strbuf *out, const char *buf, size_t size);\n \n extern void strbuf_add(struct strbuf *, const void *, size_t);\n-- \n1.8.5.2.201.gacc5987\n"},{"id":"233781","messageId":"20140126170011.24291.18469.chriscool@tuxfamily.org","threadId":"35736","inReplyTo":"20140126165018.24291.47716.chriscool@tuxfamily.org","subject":"[PATCH v3 13/17] trailer: execute command from 'trailer.<name>.command'","fromName":"Christian Couder","fromEmail":"chriscool@tuxfamily.org","sentAt":"2014-01-26T17:00:06Z","receivedAt":"2014-01-26T17:00:06Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"Signed-off-by: Christian Couder <chriscool@tuxfamily.org>\n---\n trailer.c | 56 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++\n 1 file changed, 56 insertions(+)\n\ndiff --git a/trailer.c b/trailer.c\nindex 6eec3ce..dc81a01 100644\n--- a/trailer.c\n+++ b/trailer.c\n@@ -1,4 +1,5 @@\n #include \"cache.h\"\n+#include \"run-command.h\"\n /*\n  * Copyright (c) 2013 Christian Couder <chriscool@tuxfamily.org>\n  */\n@@ -12,11 +13,14 @@ struct conf_info {\n \tchar *name;\n \tchar *key;\n \tchar *command;\n+\tunsigned command_uses_arg : 1;\n \tenum action_where where;\n \tenum action_if_exist if_exist;\n \tenum action_if_missing if_missing;\n };\n \n+#define TRAILER_ARG_STRING \"$ARG\"\n+\n struct trailer_item {\n \tstruct trailer_item *previous;\n \tstruct trailer_item *next;\n@@ -368,6 +372,7 @@ static int git_trailer_config(const char *conf_key, const char *value, void *cb)\n \t\t\tif (conf->command)\n \t\t\t\twarning(_(\"more than one %s\"), orig_conf_key);\n \t\t\tconf->command = xstrdup(value);\n+\t\t\tconf->command_uses_arg = !!strstr(conf->command, TRAILER_ARG_STRING);\n \t\t} else if (type == TRAILER_WHERE) {\n \t\t\tif (set_where(conf, value))\n \t\t\t\twarning(_(\"unknown value '%s' for key '%s'\"), value, orig_conf_key);\n@@ -400,6 +405,45 @@ static void parse_trailer(struct strbuf *tok, struct strbuf *val, const char *tr\n \t}\n }\n \n+static int read_from_command(struct child_process *cp, struct strbuf *buf)\n+{\n+\tif (run_command(cp))\n+\t\treturn error(\"running trailer command '%s' failed\", cp->argv[0]);\n+\tif (strbuf_read(buf, cp->out, 1024) < 1)\n+\t\treturn error(\"reading from trailer command '%s' failed\", cp->argv[0]);\n+\tstrbuf_trim(buf);\n+\treturn 0;\n+}\n+\n+static const char *apply_command(const char *command, const char *arg)\n+{\n+\tstruct strbuf cmd = STRBUF_INIT;\n+\tstruct strbuf buf = STRBUF_INIT;\n+\tstruct child_process cp;\n+\tconst char *argv[] = {NULL, NULL};\n+\tconst char *result = \"\";\n+\n+\tstrbuf_addstr(&cmd, command);\n+\tif (arg)\n+\t\tstrbuf_replace(&cmd, TRAILER_ARG_STRING, arg);\n+\n+\targv[0] = cmd.buf;\n+\tmemset(&cp, 0, sizeof(cp));\n+\tcp.argv = argv;\n+\tcp.env = local_repo_env;\n+\tcp.no_stdin = 1;\n+\tcp.out = -1;\n+\tcp.use_shell = 1;\n+\n+\tif (read_from_command(&cp, &buf))\n+\t\tstrbuf_release(&buf);\n+\telse\n+\t\tresult = strbuf_detach(&buf, NULL);\n+\n+\tstrbuf_release(&cmd);\n+\treturn result;\n+}\n+\n static struct trailer_item *new_trailer_item(struct trailer_item *conf_item,\n \t\t\t\t\t     const char* tok, const char* val)\n {\n@@ -409,6 +453,8 @@ static struct trailer_item *new_trailer_item(struct trailer_item *conf_item,\n \tif (conf_item) {\n \t\tnew->conf = conf_item->conf;\n \t\tnew->token = xstrdup(conf_item->conf->key);\n+\t\tif (conf_item->conf->command_uses_arg || !val)\n+\t\t\tnew->value = apply_command(conf_item->conf->command, val);\n \t} else {\n \t\tnew->conf = xcalloc(sizeof(struct conf_info), 1);\n \t\tnew->token = tok;\n@@ -459,12 +505,22 @@ static struct trailer_item *process_command_line_args(int argc, const char **arg\n \tint i;\n \tstruct trailer_item *arg_tok_first = NULL;\n \tstruct trailer_item *arg_tok_last = NULL;\n+\tstruct trailer_item *item;\n \n \tfor (i = 0; i < argc; i++) {\n \t\tstruct trailer_item *new = create_trailer_item(argv[i]);\n \t\tadd_trailer_item(&arg_tok_first, &arg_tok_last, new);\n \t}\n \n+\t/* Add conf commands that don't use $ARG */\n+\tfor (item = first_conf_item; item; item = item->next) {\n+\t\tif (item->conf->command && !item->conf->command_uses_arg)\n+\t\t{\n+\t\t\tstruct trailer_item *new = new_trailer_item(item, NULL, NULL);\n+\t\t\tadd_trailer_item(&arg_tok_first, &arg_tok_last, new);\n+\t\t}\n+\t}\n+\n \treturn arg_tok_first;\n }\n \n-- \n1.8.5.2.201.gacc5987\n"},{"id":"233778","messageId":"20140126170011.24291.81040.chriscool@tuxfamily.org","threadId":"35736","inReplyTo":"20140126165018.24291.47716.chriscool@tuxfamily.org","subject":"[PATCH v3 14/17] trailer: add tests for trailer command","fromName":"Christian Couder","fromEmail":"chriscool@tuxfamily.org","sentAt":"2014-01-26T17:00:07Z","receivedAt":"2014-01-26T17:00:07Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"Signed-off-by: Christian Couder <chriscool@tuxfamily.org>\n---\n t/t7513-interpret-trailers.sh | 27 +++++++++++++++++++++++++++\n 1 file changed, 27 insertions(+)\n\ndiff --git a/t/t7513-interpret-trailers.sh b/t/t7513-interpret-trailers.sh\nindex f5ef81f..2d50b7a 100755\n--- a/t/t7513-interpret-trailers.sh\n+++ b/t/t7513-interpret-trailers.sh\n@@ -212,4 +212,31 @@ test_expect_success 'with input from stdin' '\n \ttest_cmp expected actual\n '\n \n+test_expect_success 'with simple command' '\n+\tgit config trailer.sign.key \"Signed-off-by: \" &&\n+\tgit config trailer.sign.where \"after\" &&\n+\tgit config trailer.sign.ifExist \"addIfDifferentNeighbor\" &&\n+\tgit config trailer.sign.command \"echo \\\"A U Thor <author@example.com>\\\"\" &&\n+\tcat complex_message_body >expected &&\n+\tprintf \"Fixes: \\nAcked-by= \\nReviewed-by: \\nSigned-off-by: \\nSigned-off-by: A U Thor <author@example.com>\\n\" >>expected &&\n+\tgit interpret-trailers \"review:\" \"fix=22\" < complex_message >actual &&\n+\ttest_cmp expected actual\n+'\n+\n+test_expect_success 'setup a commit' '\n+\techo \"Content of the first commit.\" > a.txt &&\n+\tgit add a.txt &&\n+\tgit commit -m \"Add file a.txt\"\n+'\n+\n+test_expect_success 'with command using $ARG' '\n+\tgit config trailer.fix.ifExist \"overwrite\" &&\n+\tgit config trailer.fix.command \"git log -1 --oneline --format=\\\"%h (%s)\\\" --abbrev-commit --abbrev=14 \\$ARG\" &&\n+\tFIXED=$(git log -1 --oneline --format=\"%h (%s)\" --abbrev-commit --abbrev=14 HEAD) &&\n+\tcat complex_message_body >expected &&\n+\tprintf \"Fixes: $FIXED\\nAcked-by= \\nReviewed-by: \\nSigned-off-by: \\nSigned-off-by: A U Thor <author@example.com>\\n\" >>expected &&\n+\tgit interpret-trailers \"review:\" \"fix=HEAD\" < complex_message >actual &&\n+\ttest_cmp expected actual\n+'\n+\n test_done\n-- \n1.8.5.2.201.gacc5987\n"},{"id":"233784","messageId":"20140126170011.24291.63913.chriscool@tuxfamily.org","threadId":"35736","inReplyTo":"20140126165018.24291.47716.chriscool@tuxfamily.org","subject":"[PATCH v3 15/17] trailer: set author and committer env variables","fromName":"Christian Couder","fromEmail":"chriscool@tuxfamily.org","sentAt":"2014-01-26T17:00:08Z","receivedAt":"2014-01-26T17:00:08Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"Signed-off-by: Christian Couder <chriscool@tuxfamily.org>\n---\n trailer.c | 30 +++++++++++++++++++++++++++++-\n 1 file changed, 29 insertions(+), 1 deletion(-)\n\ndiff --git a/trailer.c b/trailer.c\nindex dc81a01..6c2a2b9 100644\n--- a/trailer.c\n+++ b/trailer.c\n@@ -1,5 +1,6 @@\n #include \"cache.h\"\n #include \"run-command.h\"\n+#include \"argv-array.h\"\n /*\n  * Copyright (c) 2013 Christian Couder <chriscool@tuxfamily.org>\n  */\n@@ -415,14 +416,40 @@ static int read_from_command(struct child_process *cp, struct strbuf *buf)\n \treturn 0;\n }\n \n+static void setup_ac_env(struct argv_array *env, const char *ac_name, const char *ac_mail, const char *(*read)(int))\n+{\n+\tif (!getenv(ac_name) || !getenv(ac_mail)) {\n+\t\tstruct ident_split ident;\n+\t\tconst char *namebuf, *mailbuf;\n+\t\tint namelen, maillen;\n+\t\tconst char *ac_info = read(IDENT_NO_DATE);\n+\n+\t\tif (split_ident_line(&ident, ac_info, strlen(ac_info)))\n+\t\t\treturn;\n+\n+\t\tnamelen = ident.name_end - ident.name_begin;\n+\t\tnamebuf = ident.name_begin;\n+\n+\t\tmaillen = ident.mail_end - ident.mail_begin;\n+\t\tmailbuf = ident.mail_begin;\n+\n+\t\targv_array_pushf(env, \"%s=%.*s\", ac_name, namelen, namebuf);\n+\t\targv_array_pushf(env, \"%s=%.*s\", ac_mail, maillen, mailbuf);\n+\t}\n+}\n+\n static const char *apply_command(const char *command, const char *arg)\n {\n+\tstruct argv_array env = ARGV_ARRAY_INIT;\n \tstruct strbuf cmd = STRBUF_INIT;\n \tstruct strbuf buf = STRBUF_INIT;\n \tstruct child_process cp;\n \tconst char *argv[] = {NULL, NULL};\n \tconst char *result = \"\";\n \n+\tsetup_ac_env(&env, \"GIT_AUTHOR_NAME\", \"GIT_AUTHOR_EMAIL\", git_author_info);\n+\tsetup_ac_env(&env, \"GIT_COMMITTER_NAME\", \"GIT_COMMITTER_EMAIL\", git_committer_info);\n+\n \tstrbuf_addstr(&cmd, command);\n \tif (arg)\n \t\tstrbuf_replace(&cmd, TRAILER_ARG_STRING, arg);\n@@ -430,7 +457,7 @@ static const char *apply_command(const char *command, const char *arg)\n \targv[0] = cmd.buf;\n \tmemset(&cp, 0, sizeof(cp));\n \tcp.argv = argv;\n-\tcp.env = local_repo_env;\n+\tcp.env = env.argv;\n \tcp.no_stdin = 1;\n \tcp.out = -1;\n \tcp.use_shell = 1;\n@@ -441,6 +468,7 @@ static const char *apply_command(const char *command, const char *arg)\n \t\tresult = strbuf_detach(&buf, NULL);\n \n \tstrbuf_release(&cmd);\n+\targv_array_clear(&env);\n \treturn result;\n }\n \n-- \n1.8.5.2.201.gacc5987\n"},{"id":"233780","messageId":"20140126170011.24291.74796.chriscool@tuxfamily.org","threadId":"35736","inReplyTo":"20140126165018.24291.47716.chriscool@tuxfamily.org","subject":"[PATCH v3 16/17] trailer: add tests for commands using env variables","fromName":"Christian Couder","fromEmail":"chriscool@tuxfamily.org","sentAt":"2014-01-26T17:00:09Z","receivedAt":"2014-01-26T17:00:09Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"Signed-off-by: Christian Couder <chriscool@tuxfamily.org>\n---\n t/t7513-interpret-trailers.sh | 20 ++++++++++++++++++++\n 1 file changed, 20 insertions(+)\n\ndiff --git a/t/t7513-interpret-trailers.sh b/t/t7513-interpret-trailers.sh\nindex 2d50b7a..00894a8 100755\n--- a/t/t7513-interpret-trailers.sh\n+++ b/t/t7513-interpret-trailers.sh\n@@ -223,6 +223,26 @@ test_expect_success 'with simple command' '\n \ttest_cmp expected actual\n '\n \n+test_expect_success 'with command using commiter information' '\n+\tgit config trailer.sign.ifExist \"addIfDifferent\" &&\n+\tgit config trailer.sign.command \"echo \\\"\\$GIT_COMMITTER_NAME <\\$GIT_COMMITTER_EMAIL>\\\"\" &&\n+\tcat complex_message_body >expected &&\n+\tprintf \"Fixes: \\nAcked-by= \\nReviewed-by: \\nSigned-off-by: \\nSigned-off-by: C O Mitter <committer@example.com>\\n\" >>expected &&\n+\tgit interpret-trailers \"review:\" \"fix=22\" < complex_message >actual &&\n+\ttest_cmp expected actual\n+'\n+\n+test_expect_success 'with command using author information' '\n+\tgit config trailer.sign.key \"Signed-off-by: \" &&\n+\tgit config trailer.sign.where \"after\" &&\n+\tgit config trailer.sign.ifExist \"addIfDifferentNeighbor\" &&\n+\tgit config trailer.sign.command \"echo \\\"\\$GIT_AUTHOR_NAME <\\$GIT_AUTHOR_EMAIL>\\\"\" &&\n+\tcat complex_message_body >expected &&\n+\tprintf \"Fixes: \\nAcked-by= \\nReviewed-by: \\nSigned-off-by: \\nSigned-off-by: A U Thor <author@example.com>\\n\" >>expected &&\n+\tgit interpret-trailers \"review:\" \"fix=22\" < complex_message >actual &&\n+\ttest_cmp expected actual\n+'\n+\n test_expect_success 'setup a commit' '\n \techo \"Content of the first commit.\" > a.txt &&\n \tgit add a.txt &&\n-- \n1.8.5.2.201.gacc5987\n"},{"id":"233782","messageId":"20140126170011.24291.26146.chriscool@tuxfamily.org","threadId":"35736","inReplyTo":"20140126165018.24291.47716.chriscool@tuxfamily.org","subject":"[PATCH v3 17/17] Documentation: add documentation for 'git interpret-trailers'","fromName":"Christian Couder","fromEmail":"chriscool@tuxfamily.org","sentAt":"2014-01-26T17:00:10Z","receivedAt":"2014-01-26T17:00:10Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"Signed-off-by: Christian Couder <chriscool@tuxfamily.org>\n---\n Documentation/git-interpret-trailers.txt | 137 +++++++++++++++++++++++++++++++\n 1 file changed, 137 insertions(+)\n create mode 100644 Documentation/git-interpret-trailers.txt\n\ndiff --git a/Documentation/git-interpret-trailers.txt b/Documentation/git-interpret-trailers.txt\nnew file mode 100644\nindex 0000000..f74843e\n--- /dev/null\n+++ b/Documentation/git-interpret-trailers.txt\n@@ -0,0 +1,137 @@\n+git-interpret-trailers(1)\n+=========================\n+\n+NAME\n+----\n+git-interpret-trailers - help add stuctured information into commit messages\n+\n+SYNOPSIS\n+--------\n+[verse]\n+'git interpret-trailers' [--trim-empty] [--infile=file] [<token[=value]>...]\n+\n+DESCRIPTION\n+-----------\n+Help add RFC 822 like headers, called 'trailers', at the end of the\n+otherwise free-form part of a commit message.\n+\n+Unless `--infile=file` is used, this command is a filter. It reads the\n+standard input for a commit message and apply the `token` arguments,\n+if any, to this message. The resulting message is emited on the\n+standard output.\n+\n+Some configuration variables control the way the `token` arguments are\n+applied to the message and the way any existing trailer in the message\n+is changed. They also make it possible to automatically add some\n+trailers.\n+\n+By default, a 'token=value' or 'token:value' argument will be added\n+only if no trailer with the same (token, value) pair is already in the\n+message. The 'token' and 'value' parts will be trimmed to remove\n+starting and trailing white spaces, and the resulting trimmed 'token'\n+and 'value' will appear in the message like this:\n+\n+------------------------------------------------\n+token: value\n+------------------------------------------------\n+\n+By default, if there are already trailers with the same 'token' the\n+trailer will appear just after the last trailer with the same\n+'token'. Otherwise it will appear at the end of the message.\n+\n+Note that 'trailers' do not follow and are not intended to follow many\n+rules that are in RFC 822. For example they do not follow the line\n+breaking rules, the encoding rules and probably many other rules.\n+\n+Trailers have become a de facto standard way to add helpful structured\n+information into commit messages. For example the well known\n+\"Signed-off-by: \" trailer is used by many projects like the Linux\n+kernel and Git.\n+\n+OPTIONS\n+-------\n+--trim-empty::\n+\tIf the 'value' part of any trailer contains onlywhite spaces,\n+\tthe whole trailer will be removed from the resulting message.\n+\n+----infile=file::\n+\tRead the commit message from `file` instead of the standard\n+\tinput.\n+\n+CONFIGURATION VARIABLES\n+-----------------------\n+\n+trailer.<token>.key::\n+\tThis 'key' will be used instead of 'token' in the\n+\ttrailer. After some alphanumeric characters, it can contain\n+\tsome non alphanumeric characters like ':', '=' or '#' that will\n+\tbe used instead of ':' to separate the token from the value in\n+\tthe trailer, though the default ':' is more standard.\n+\n+trailer.<token>.where::\n+\tThis can be either `after`, which is the default, or\n+\t`before`. If it is `before`, then a trailer with the specified\n+\ttoken, will appear before, instead of after, other trailers\n+\twith the same token, or otherwise at the beginning, instead of\n+\tat the end, of all the trailers.\n+\n+trailer.<token>.ifexist::\n+\tThis option makes it possible to chose what action will be\n+\tperformed when there is already at least one trailer with the\n+\tsame token in the message.\n++\n+The valid values for this option are: `addIfDifferent` (this is the\n+default), `addIfDifferentNeighbor`, `add`, `overwrite` or `doNothing`.\n++\n+With `addIfDifferent`, a new trailer will be added only if no trailer\n+with the same (token, value) pair is already in the message.\n++\n+With `addIfDifferentNeighbor`, a new trailer will be added only if no\n+trailer with the same (token, value) pair is above or below the line\n+where the new trailer will be added.\n++\n+With `add`, a new trailer will be added, even if some trailers with\n+the same (token, value) pair are already in the message.\n++\n+With `overwrite`, the new trailer will overwrite an existing trailer\n+with the same token.\n++\n+With `doNothing`, nothing will be done, that is no new trailer will be\n+added if there is already one with the same token in the message.\n+\n+trailer.<token>.ifmissing::\n+\tThis option makes it possible to chose what action will be\n+\tperformed when there is not yet any trailer with the same\n+\ttoken in the message.\n++\n+The valid values for this option are: `add` (this is the default) and\n+`doNothing`.\n++\n+With `add`, a new trailer will be added.\n++\n+With `doNothing`, nothing will be done.\n+\n+trailer.<token>.command::\n+\tThis option can be used to specify a shell command that will\n+\tbe used to automatically add or modify a trailer with the\n+\tspecified 'token'.\n++\n+When this option is specified, it is like if a special 'token=value'\n+argument is added at the end of the command line, where 'value' will\n+be given by the standard output of the specified command.\n++\n+If the command contains the `$ARG` string, this string will be\n+replaced with the 'value' part of an existing trailer with the same\n+token, if any, before the command is launched.\n++\n+The following environment variables are set when the command is run:\n+GIT_AUTHOR_NAME, GIT_AUTHOR_EMAIL, GIT_COMMITTER_NAME,\n+GIT_COMMITTER_EMAIL.\n+\n+SEE ALSO\n+--------\n+linkgit:git-commit[1]\n+\n+GIT\n+---\n+Part of the linkgit:git[1] suite\n-- \n1.8.5.2.201.gacc5987\n"},{"id":"233806","messageId":"CAPig+cQgq_2h+n8OeDsrmk_NqAA4RDNzkBAtNCNjOAGMrFN4jg@mail.gmail.com","threadId":"35736","inReplyTo":"20140126170011.24291.26146.chriscool@tuxfamily.org","subject":"Re: [PATCH v3 17/17] Documentation: add documentation for 'git interpret-trailers'","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2014-01-27T00:52:59Z","receivedAt":"2014-01-27T00:52:59Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"On Sun, Jan 26, 2014 at 12:00 PM, Christian Couder\n<chriscool@tuxfamily.org> wrote:\n> Signed-off-by: Christian Couder <chriscool@tuxfamily.org>\n> ---\n> diff --git a/Documentation/git-interpret-trailers.txt b/Documentation/git-interpret-trailers.txt\n> new file mode 100644\n> index 0000000..f74843e\n> --- /dev/null\n> +++ b/Documentation/git-interpret-trailers.txt\n> @@ -0,0 +1,137 @@\n> +git-interpret-trailers(1)\n> +=========================\n> +\n> +NAME\n> +----\n> +git-interpret-trailers - help add stuctured information into commit messages\n> +\n> +SYNOPSIS\n> +--------\n> +[verse]\n> +'git interpret-trailers' [--trim-empty] [--infile=file] [<token[=value]>...]\n\nWould it be more consistent with existing documentation to format this as so?\n\n  [--infile=<file>] [<token>[=<value>]]...\n\n> +DESCRIPTION\n> +-----------\n> +Help add RFC 822 like headers, called 'trailers', at the end of the\n\ns/822 like/822-like/\n\nWas the suggestion, early in the discussion, to use \"footer\" rather\nthan \"trailer\" dismissed?\n\n> +otherwise free-form part of a commit message.\n> +\n> +Unless `--infile=file` is used, this command is a filter. It reads the\n> +standard input for a commit message and apply the `token` arguments,\n\ns/apply/applies/\n\n> +if any, to this message. The resulting message is emited on the\n> +standard output.\n> +\n> +Some configuration variables control the way the `token` arguments are\n> +applied to the message and the way any existing trailer in the message\n> +is changed. They also make it possible to automatically add some\n> +trailers.\n> +\n> +By default, a 'token=value' or 'token:value' argument will be added\n> +only if no trailer with the same (token, value) pair is already in the\n> +message. The 'token' and 'value' parts will be trimmed to remove\n> +starting and trailing white spaces, and the resulting trimmed 'token'\n\nOther git documentation uniformly spells it as \"whitespace\" rather\nthan \"white spaces\".\n\n> +and 'value' will appear in the message like this:\n> +\n> +------------------------------------------------\n> +token: value\n> +------------------------------------------------\n> +\n> +By default, if there are already trailers with the same 'token' the\n> +trailer will appear just after the last trailer with the same\n\nIt might be a bit clearer to say \"the _new_ trailer will appear...\".\n\n> +'token'. Otherwise it will appear at the end of the message.\n> +\n> +Note that 'trailers' do not follow and are not intended to follow many\n> +rules that are in RFC 822. For example they do not follow the line\n> +breaking rules, the encoding rules and probably many other rules.\n> +\n> +Trailers have become a de facto standard way to add helpful structured\n> +information into commit messages. For example the well known\n> +\"Signed-off-by: \" trailer is used by many projects like the Linux\n> +kernel and Git.\n\nThis \"justification\" paragraph might make more sense near or at the\nvery the top of the Description section.\n\n> +OPTIONS\n> +-------\n> +--trim-empty::\n> +       If the 'value' part of any trailer contains onlywhite spaces,\n\ns/onlywhite spaces/only whitespace/\n\n> +       the whole trailer will be removed from the resulting message.\n> +\n> +----infile=file::\n> +       Read the commit message from `file` instead of the standard\n> +       input.\n> +\n> +CONFIGURATION VARIABLES\n> +-----------------------\n> +\n> +trailer.<token>.key::\n> +       This 'key' will be used instead of 'token' in the\n> +       trailer. After some alphanumeric characters, it can contain\n> +       some non alphanumeric characters like ':', '=' or '#' that will\n> +       be used instead of ':' to separate the token from the value in\n> +       the trailer, though the default ':' is more standard.\n> +\n> +trailer.<token>.where::\n> +       This can be either `after`, which is the default, or\n> +       `before`. If it is `before`, then a trailer with the specified\n> +       token, will appear before, instead of after, other trailers\n> +       with the same token, or otherwise at the beginning, instead of\n> +       at the end, of all the trailers.\n> +\n> +trailer.<token>.ifexist::\n> +       This option makes it possible to chose what action will be\n\ns/chose/choose/\n\n> +       performed when there is already at least one trailer with the\n> +       same token in the message.\n> ++\n> +The valid values for this option are: `addIfDifferent` (this is the\n> +default), `addIfDifferentNeighbor`, `add`, `overwrite` or `doNothing`.\n> ++\n> +With `addIfDifferent`, a new trailer will be added only if no trailer\n> +with the same (token, value) pair is already in the message.\n> ++\n> +With `addIfDifferentNeighbor`, a new trailer will be added only if no\n> +trailer with the same (token, value) pair is above or below the line\n> +where the new trailer will be added.\n> ++\n> +With `add`, a new trailer will be added, even if some trailers with\n> +the same (token, value) pair are already in the message.\n> ++\n> +With `overwrite`, the new trailer will overwrite an existing trailer\n> +with the same token.\n> ++\n> +With `doNothing`, nothing will be done, that is no new trailer will be\n> +added if there is already one with the same token in the message.\n> +\n> +trailer.<token>.ifmissing::\n> +       This option makes it possible to chose what action will be\n\ns/chose/choose/\n\n> +       performed when there is not yet any trailer with the same\n> +       token in the message.\n> ++\n> +The valid values for this option are: `add` (this is the default) and\n> +`doNothing`.\n> ++\n> +With `add`, a new trailer will be added.\n> ++\n> +With `doNothing`, nothing will be done.\n> +\n> +trailer.<token>.command::\n> +       This option can be used to specify a shell command that will\n> +       be used to automatically add or modify a trailer with the\n> +       specified 'token'.\n> ++\n> +When this option is specified, it is like if a special 'token=value'\n> +argument is added at the end of the command line, where 'value' will\n> +be given by the standard output of the specified command.\n\nWhat happens if the text returned by the command has multiple lines?\nShould the documentation say something about this?\n\n> +If the command contains the `$ARG` string, this string will be\n> +replaced with the 'value' part of an existing trailer with the same\n> +token, if any, before the command is launched.\n> ++\n> +The following environment variables are set when the command is run:\n> +GIT_AUTHOR_NAME, GIT_AUTHOR_EMAIL, GIT_COMMITTER_NAME,\n> +GIT_COMMITTER_EMAIL.\n> +\n> +SEE ALSO\n> +--------\n> +linkgit:git-commit[1]\n> +\n> +GIT\n> +---\n> +Part of the linkgit:git[1] suite\n> --\n> 1.8.5.2.201.gacc5987\n"},{"id":"233837","messageId":"20140127.213344.212708599170084659.chriscool@tuxfamily.org","threadId":"35736","inReplyTo":"CAPig+cQgq_2h+n8OeDsrmk_NqAA4RDNzkBAtNCNjOAGMrFN4jg@mail.gmail.com","subject":"Re: [PATCH v3 17/17] Documentation: add documentation for 'git interpret-trailers'","fromName":"Christian Couder","fromEmail":"chriscool@tuxfamily.org","sentAt":"2014-01-27T20:33:44Z","receivedAt":"2014-01-27T20:33:44Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"From: Eric Sunshine <sunshine@sunshineco.com>\n>\n> On Sun, Jan 26, 2014 at 12:00 PM, Christian Couder\n> <chriscool@tuxfamily.org> wrote:\n>> Signed-off-by: Christian Couder <chriscool@tuxfamily.org>\n>> ---\n>> diff --git a/Documentation/git-interpret-trailers.txt b/Documentation/git-interpret-trailers.txt\n>> new file mode 100644\n>> index 0000000..f74843e\n>> --- /dev/null\n>> +++ b/Documentation/git-interpret-trailers.txt\n>> @@ -0,0 +1,137 @@\n>> +git-interpret-trailers(1)\n>> +=========================\n>> +\n>> +NAME\n>> +----\n>> +git-interpret-trailers - help add stuctured information into commit messages\n>> +\n>> +SYNOPSIS\n>> +--------\n>> +[verse]\n>> +'git interpret-trailers' [--trim-empty] [--infile=file] [<token[=value]>...]\n> \n> Would it be more consistent with existing documentation to format this as so?\n> \n>   [--infile=<file>] [<token>[=<value>]]...\n\nNo, it would be very inconsistent:\n\n$ grep '\\.\\.\\.\\]' *.txt | wc -l\n103\n$ grep '\\]\\.\\.\\.' *.txt | wc -l\n0\n\n>> +DESCRIPTION\n>> +-----------\n>> +Help add RFC 822 like headers, called 'trailers', at the end of the\n> \n> s/822 like/822-like/\n\nOk.\n\n> Was the suggestion, early in the discussion, to use \"footer\" rather\n> than \"trailer\" dismissed?\n\nDuring the early discussions people initially talked about \"footer\"\nwhile Junio and others talked about \"trailer\".\n\nSee:\n\nhttp://thread.gmane.org/gmane.comp.version-control.git/236429/\nhttp://thread.gmane.org/gmane.comp.version-control.git/236770/\n\nI have no preference for one or the other, but by default I use what\nJunio uses.\n\n>> +otherwise free-form part of a commit message.\n>> +\n>> +Unless `--infile=file` is used, this command is a filter. It reads the\n>> +standard input for a commit message and apply the `token` arguments,\n> \n> s/apply/applies/\n\nOk.\n\n>> +if any, to this message. The resulting message is emited on the\n>> +standard output.\n>> +\n>> +Some configuration variables control the way the `token` arguments are\n>> +applied to the message and the way any existing trailer in the message\n>> +is changed. They also make it possible to automatically add some\n>> +trailers.\n>> +\n>> +By default, a 'token=value' or 'token:value' argument will be added\n>> +only if no trailer with the same (token, value) pair is already in the\n>> +message. The 'token' and 'value' parts will be trimmed to remove\n>> +starting and trailing white spaces, and the resulting trimmed 'token'\n> \n> Other git documentation uniformly spells it as \"whitespace\" rather\n> than \"white spaces\".\n\nYou are right I will change that.\n\n>> +and 'value' will appear in the message like this:\n>> +\n>> +------------------------------------------------\n>> +token: value\n>> +------------------------------------------------\n>> +\n>> +By default, if there are already trailers with the same 'token' the\n>> +trailer will appear just after the last trailer with the same\n> \n> It might be a bit clearer to say \"the _new_ trailer will appear...\".\n\nOk.\n\n>> +'token'. Otherwise it will appear at the end of the message.\n>> +\n>> +Note that 'trailers' do not follow and are not intended to follow many\n>> +rules that are in RFC 822. For example they do not follow the line\n>> +breaking rules, the encoding rules and probably many other rules.\n>> +\n>> +Trailers have become a de facto standard way to add helpful structured\n>> +information into commit messages. For example the well known\n>> +\"Signed-off-by: \" trailer is used by many projects like the Linux\n>> +kernel and Git.\n> \n> This \"justification\" paragraph might make more sense near or at the\n> very the top of the Description section.\n\nYeah, or maybe I can remove it entirely.\n\n>> +OPTIONS\n>> +-------\n>> +--trim-empty::\n>> +       If the 'value' part of any trailer contains onlywhite spaces,\n> \n> s/onlywhite spaces/only whitespace/\n\nOk.\n\n>> +       the whole trailer will be removed from the resulting message.\n>> +\n>> +----infile=file::\n>> +       Read the commit message from `file` instead of the standard\n>> +       input.\n>> +\n>> +CONFIGURATION VARIABLES\n>> +-----------------------\n>> +\n>> +trailer.<token>.key::\n>> +       This 'key' will be used instead of 'token' in the\n>> +       trailer. After some alphanumeric characters, it can contain\n>> +       some non alphanumeric characters like ':', '=' or '#' that will\n>> +       be used instead of ':' to separate the token from the value in\n>> +       the trailer, though the default ':' is more standard.\n>> +\n>> +trailer.<token>.where::\n>> +       This can be either `after`, which is the default, or\n>> +       `before`. If it is `before`, then a trailer with the specified\n>> +       token, will appear before, instead of after, other trailers\n>> +       with the same token, or otherwise at the beginning, instead of\n>> +       at the end, of all the trailers.\n>> +\n>> +trailer.<token>.ifexist::\n>> +       This option makes it possible to chose what action will be\n> \n> s/chose/choose/\n\nOk.\n\n>> +       performed when there is already at least one trailer with the\n>> +       same token in the message.\n>> ++\n>> +The valid values for this option are: `addIfDifferent` (this is the\n>> +default), `addIfDifferentNeighbor`, `add`, `overwrite` or `doNothing`.\n>> ++\n>> +With `addIfDifferent`, a new trailer will be added only if no trailer\n>> +with the same (token, value) pair is already in the message.\n>> ++\n>> +With `addIfDifferentNeighbor`, a new trailer will be added only if no\n>> +trailer with the same (token, value) pair is above or below the line\n>> +where the new trailer will be added.\n>> ++\n>> +With `add`, a new trailer will be added, even if some trailers with\n>> +the same (token, value) pair are already in the message.\n>> ++\n>> +With `overwrite`, the new trailer will overwrite an existing trailer\n>> +with the same token.\n>> ++\n>> +With `doNothing`, nothing will be done, that is no new trailer will be\n>> +added if there is already one with the same token in the message.\n>> +\n>> +trailer.<token>.ifmissing::\n>> +       This option makes it possible to chose what action will be\n> \n> s/chose/choose/\n\nOk.\n\n>> +       performed when there is not yet any trailer with the same\n>> +       token in the message.\n>> ++\n>> +The valid values for this option are: `add` (this is the default) and\n>> +`doNothing`.\n>> ++\n>> +With `add`, a new trailer will be added.\n>> ++\n>> +With `doNothing`, nothing will be done.\n>> +\n>> +trailer.<token>.command::\n>> +       This option can be used to specify a shell command that will\n>> +       be used to automatically add or modify a trailer with the\n>> +       specified 'token'.\n>> ++\n>> +When this option is specified, it is like if a special 'token=value'\n>> +argument is added at the end of the command line, where 'value' will\n>> +be given by the standard output of the specified command.\n> \n> What happens if the text returned by the command has multiple lines?\n\nThe 'value' part of trailer will be on multiple lines.\n\n> Should the documentation say something about this?\n\nI think it is better left as unspecified because we might want to\nprint a warning, or perhaps error out in this case.\n\nThanks,\nChristian.\n"},{"id":"233839","messageId":"xmqqk3dlnnil.fsf@gitster.dls.corp.google.com","threadId":"35736","inReplyTo":"20140126165018.24291.47716.chriscool@tuxfamily.org","subject":"Re: [PATCH v3 00/17] Add interpret-trailers builtin","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2014-01-27T21:05:54Z","receivedAt":"2014-01-27T21:05:54Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Is this from the same Christian?\n\nThe series seems to have unusually high rate of style violations\ncompared to the usual submission, like these:\n\nERROR: open brace '{' following function declarations go on the next line\n#78: FILE: trailer.c:44:\n+static size_t alnum_len(const char *buf, size_t len) {\n\nERROR: trailing statements should be on next line\n#79: FILE: trailer.c:45:\n+\twhile (--len >= 0 && !isalnum(buf[len]));\n\nERROR: space required before the open parenthesis '('\n#70: FILE: trailer.c:90:\n+\tswitch(arg_tok->conf->if_exist) {\n\nWARNING: braces {} are not necessary for any arm of this statement\n#66: FILE: trailer.c:270:\n+\tif (!strcasecmp(\"doNothing\", value)) {\n[...]\n+\t} else if (!strcasecmp(\"add\", value)) {\n[...]\n+\t} else\n[...]\n\nERROR: that open brace { should be on the previous line\n#96: FILE: trailer.c:300:\n+\tfor (previous = NULL, item = first_conf_item;\n+\t     item;\n+\t     previous = item, item = item->next)\n+\t{\n\nJust trying to see if there is an impersonation ;-)\n"},{"id":"233840","messageId":"xmqqfvo9nmul.fsf@gitster.dls.corp.google.com","threadId":"35736","inReplyTo":"20140127.213344.212708599170084659.chriscool@tuxfamily.org","subject":"Re: [PATCH v3 17/17] Documentation: add documentation for 'git interpret-trailers'","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2014-01-27T21:20:18Z","receivedAt":"2014-01-27T21:20:18Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Christian Couder <chriscool@tuxfamily.org> writes:\n\n>>> +'git interpret-trailers' [--trim-empty] [--infile=file] [<token[=value]>...]\n>> \n>> Would it be more consistent with existing documentation to format this as so?\n>> \n>>   [--infile=<file>] [<token>[=<value>]]...\n>\n> No, it would be very inconsistent:\n>\n> $ grep '\\.\\.\\.\\]' *.txt | wc -l\n> 103\n> $ grep '\\]\\.\\.\\.' *.txt | wc -l\n> 0\n\nI have a feeling that you are missing the point Eric is making.  The\nvalue given to the --infile option can be anything, i.e. 'file'\nthere is a placeholder, hence \"--infile=<file>\" not \"--infile=file\"\nas you wrote.\n\nAlso I think \"[<token>[=<value>]]...\" is the correct way to spell\nthat there can be 0 or more \"<token>[=<value>]\".  \"<token[=value]>\"\nin the original does not make any sense, as <> is meant to say \"this\nthing is a placeholder\", and we do not try to say, with the string\ninside <>, what shape that placeholder takes.  In fact '=' part is\n_not_ a placeholder but is required syntactically when you want to\nsupply a value to the token, so the original doubly is incorrect.\n\nI find it a bad taste to allow unbound set of <token> on the LHS of\n'=' on the command line, but that is a separate issue in the design,\nnot in the documentation of the design.\n\nThanks.\n"},{"id":"233898","messageId":"20140129.202705.1633041266139066645.chriscool@tuxfamily.org","threadId":"35736","inReplyTo":"xmqqk3dlnnil.fsf@gitster.dls.corp.google.com","subject":"Re: [PATCH v3 00/17] Add interpret-trailers builtin","fromName":"Christian Couder","fromEmail":"chriscool@tuxfamily.org","sentAt":"2014-01-29T19:27:05Z","receivedAt":"2014-01-29T19:27:05Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"From: Junio C Hamano <gitster@pobox.com>\n>\n> Is this from the same Christian?\n\nYes, ...\n\n> The series seems to have unusually high rate of style violations\n> compared to the usual submission, like these:\n> \n> ERROR: open brace '{' following function declarations go on the next line\n> #78: FILE: trailer.c:44:\n> +static size_t alnum_len(const char *buf, size_t len) {\n> \n> ERROR: trailing statements should be on next line\n> #79: FILE: trailer.c:45:\n> +\twhile (--len >= 0 && !isalnum(buf[len]));\n> \n> ERROR: space required before the open parenthesis '('\n> #70: FILE: trailer.c:90:\n> +\tswitch(arg_tok->conf->if_exist) {\n> \n> WARNING: braces {} are not necessary for any arm of this statement\n> #66: FILE: trailer.c:270:\n> +\tif (!strcasecmp(\"doNothing\", value)) {\n> [...]\n> +\t} else if (!strcasecmp(\"add\", value)) {\n> [...]\n> +\t} else\n> [...]\n> \n> ERROR: that open brace { should be on the previous line\n> #96: FILE: trailer.c:300:\n> +\tfor (previous = NULL, item = first_conf_item;\n> +\t     item;\n> +\t     previous = item, item = item->next)\n> +\t{\n> \n> Just trying to see if there is an impersonation ;-)\n\n... I guess the artistic part of my mind is trying to come out somehow.\n\nThanks,\nChristian.\n"},{"id":"233899","messageId":"20140129.210102.2157679962537036887.chriscool@tuxfamily.org","threadId":"35736","inReplyTo":"xmqqfvo9nmul.fsf@gitster.dls.corp.google.com","subject":"Re: [PATCH v3 17/17] Documentation: add documentation for 'git interpret-trailers'","fromName":"Christian Couder","fromEmail":"chriscool@tuxfamily.org","sentAt":"2014-01-29T20:01:02Z","receivedAt":"2014-01-29T20:01:02Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"From: Junio C Hamano <gitster@pobox.com>\nSubject: Re: [PATCH v3 17/17] Documentation: add documentation for 'git interpret-trailers'\nDate: Mon, 27 Jan 2014 13:20:18 -0800\n\n> Christian Couder <chriscool@tuxfamily.org> writes:\n> \n>>>> +'git interpret-trailers' [--trim-empty] [--infile=file] [<token[=value]>...]\n>>> \n>>> Would it be more consistent with existing documentation to format this as so?\n>>> \n>>>   [--infile=<file>] [<token>[=<value>]]...\n>>\n>> No, it would be very inconsistent:\n>>\n>> $ grep '\\.\\.\\.\\]' *.txt | wc -l\n>> 103\n>> $ grep '\\]\\.\\.\\.' *.txt | wc -l\n>> 0\n> \n> I have a feeling that you are missing the point Eric is making.\n\nYeah I realize I missed some of his points. Sorry about that.\n\n> The\n> value given to the --infile option can be anything, i.e. 'file'\n> there is a placeholder, hence \"--infile=<file>\" not \"--infile=file\"\n> as you wrote.\n\nOk, I agree with that.\n\n> Also I think \"[<token>[=<value>]]...\" is the correct way to spell\n> that there can be 0 or more \"<token>[=<value>]\".\n\nPerhaps but it is not very consistent with what there is in other\nsynopsis strings. For example the commands that accept a path have a\nsynopsis that ends with \"[--] [<path>...]\" and not \"[--] [<path>]...\".\n\nPerhaps [(<token>[=<value>])...] is more correct, but not worth the\nadded complexity.\n\n> \"<token[=value]>\"\n> in the original does not make any sense, as <> is meant to say \"this\n> thing is a placeholder\", and we do not try to say, with the string\n> inside <>, what shape that placeholder takes.  In fact '=' part is\n> _not_ a placeholder but is required syntactically when you want to\n> supply a value to the token, so the original doubly is incorrect.\n\nYeah perhaps, except that in the code ':' is accepted instead of '=',\nand the documention says that.\n\nSo perhaps [(<token>[(=|:)<value>])...] is even more correct.\n\n> I find it a bad taste to allow unbound set of <token> on the LHS of\n> '=' on the command line, but that is a separate issue in the design,\n> not in the documentation of the design.\n\nI don't understand this sentence, sorry.\n\nThanks,\nChristian.\n"},{"id":"233901","messageId":"xmqq1tzqjzu3.fsf@gitster.dls.corp.google.com","threadId":"35736","inReplyTo":"20140129.210102.2157679962537036887.chriscool@tuxfamily.org","subject":"Re: [PATCH v3 17/17] Documentation: add documentation for 'git interpret-trailers'","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2014-01-29T20:30:12Z","receivedAt":"2014-01-29T20:30:12Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Christian Couder <chriscool@tuxfamily.org> writes:\n\n>> I find it a bad taste to allow unbound set of <token> on the LHS of\n>> '=' on the command line, but that is a separate issue in the design,\n>> not in the documentation of the design.\n>\n> I don't understand this sentence, sorry.\n\nIt is a bad design taste to structure the command line argument in\nsuch a way that it takes\n\n\tgit cmd xyzzy=frotz nitfol=rezrov some other args\n\nwhere these 'xyzzy', 'nitfol', etc. form an unbound set.  It\nprevents future enhancements to the command from allowing anything\nthat contain '=' in \"some other args\" part.\n\nAllowing not just '=' but also ':' makes it even worse.\n\nBut these are issues in the design itself.  Not the issue in the\npatch 17/17 that is trying to document the design.  This patch under\ndiscussion documents the bad design correctly ;-)\n"},{"id":"234038","messageId":"CAPig+cRJ3yVVooJymc5m=wHGrdn06A9wE-4GOa0xODz5Pmg1+A@mail.gmail.com","threadId":"35736","inReplyTo":"20140127.213344.212708599170084659.chriscool@tuxfamily.org","subject":"Re: [PATCH v3 17/17] Documentation: add documentation for 'git interpret-trailers'","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2014-02-02T11:22:14Z","receivedAt":"2014-02-02T11:22:14Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"Patch 17/17 of v4 failed to arrive in my inbox for some reason, so\nI'll just reply to v3 since there's another error I noticed which is\nstill present in v4, plus a comment specific to v4 (see below).\n\nOn Mon, Jan 27, 2014 at 3:33 PM, Christian Couder\n<chriscool@tuxfamily.org> wrote:\n> From: Eric Sunshine <sunshine@sunshineco.com>\n>> On Sun, Jan 26, 2014 at 12:00 PM, Christian Couder\n>> <chriscool@tuxfamily.org> wrote:\n>>> +By default, a 'token=value' or 'token:value' argument will be added\n>>> +only if no trailer with the same (token, value) pair is already in the\n>>> +message. The 'token' and 'value' parts will be trimmed to remove\n>>> +starting and trailing white spaces, and the resulting trimmed 'token'\n>>\n>> Other git documentation uniformly spells it as \"whitespace\" rather\n>> than \"white spaces\".\n>\n> You are right I will change that.\n\nThe rest of git documentation consistently spells it \"whitespace\", but\nv4 uses \"whitespaces\".\n\n>>> +----infile=file::\n>>> +       Read the commit message from `file` instead of the standard\n>>> +       input.\n\nI didn't notice this when reviewing v3, and the same text appears in\nv4. There are a couple extra hyphens before 'infile', and you want <\nand > around \"file\", so:\n\ns/----infile=file/--infile=<file>/\n"}]}