{"thread":{"id":"36334","subject":"[PATCH v9 03/11] trailer: read and process config information","startedAt":"2014-04-01T19:20:11Z","lastAt":"2014-04-07T22:00:11Z","messageCount":24,"participants":["Christian Couder","Junio C Hamano","Jonathan Nieder"],"isPatch":true,"patchVersion":9,"patchTotal":11},"messages":[{"id":"238248","messageId":"20140401191831.353.99271.chriscool@tuxfamily.org","threadId":"36334","inReplyTo":null,"subject":"[PATCH v9 00/11] Add interpret-trailers builtin","fromName":"Christian Couder","fromEmail":"chriscool@tuxfamily.org","sentAt":"2014-04-01T19:20:11Z","receivedAt":"2014-04-01T19:20:11Z","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 keep builtin/commit.c uncontaminated by any more\nhard-wired logic, like what we have for the signed-off-by line.  Any\nnew things can and should be doable in hooks, and this filter would\nhelp writing these hooks.\n\nAnd that is why the design goal of the filter is to make it at least\nas powerful as the built-in logic we have for signed-off-by lines;\nthat would allow us to later eject the hard-wired logic for\nsigned-off-by line from the main codepath, if/when we wanted to.\n\nAlternatively, we could build a library-ish API around this filter\ncode and replace the hard-wired logic for signed-off-by line with a\ncall into that API, if/when we wanted to, but that requires (in\naddition to the \"at least as powerful as the built-in logic\") that\nthe implementation of this stand-alone filter can be cleanly made\ninto a reusable library, so that is a bit higher bar to cross than\n\"everything can be doable with hooks\" alternative.\n\n2) Current state:\n\nCurrently the usage string of this command is:\n\ngit interpret-trailers [--trim-empty] [(<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 read from stdin is interpreted\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        - $ARG can be used in commands\n        - there are some tests\n        - there is some documentation\n\nThe following features are planned but not yet implemented:\n        - add more tests related to commands\n        - 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 8, thanks to Junio:\n\n* improve the rationale section above\n* improve commit message in patch 1/11\n* remove patch (3/12) that moved some lowercase\n  functions to wrapper.c\n* use strcmp() instead of strcasecmp() to compare\n  some values from the config file matching a fixed set\n\nThis means code changes only in patch 3/11 and commit\nmessage changes only in patch 1/11.\n\n\nChristian Couder (11):\n  trailer: add data structures and basic functions\n  trailer: process trailers from stdin and arguments\n  trailer: read and process config information\n  trailer: process command line trailer arguments\n  trailer: parse trailers from stdin\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: execute command from 'trailer.<name>.command'\n  trailer: add tests for commands in config file\n  Documentation: add documentation for 'git interpret-trailers'\n\n .gitignore                               |   1 +\n Documentation/git-interpret-trailers.txt | 123 ++++++\n Makefile                                 |   2 +\n builtin.h                                |   1 +\n builtin/interpret-trailers.c             |  33 ++\n git.c                                    |   1 +\n t/t7513-interpret-trailers.sh            | 407 ++++++++++++++++++\n trailer.c                                | 684 +++++++++++++++++++++++++++++++\n trailer.h                                |   6 +\n 9 files changed, 1258 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.9.0.164.g3aa33cd.dirty\n"},{"id":"238246","messageId":"20140401192023.353.73135.chriscool@tuxfamily.org","threadId":"36334","inReplyTo":"20140401191831.353.99271.chriscool@tuxfamily.org","subject":"[PATCH v9 01/11] trailer: add data structures and basic functions","fromName":"Christian Couder","fromEmail":"chriscool@tuxfamily.org","sentAt":"2014-04-01T19:20:12Z","receivedAt":"2014-04-01T19:20:12Z","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 | 49 +++++++++++++++++++++++++++++++++++++++++++++++++\n 2 files changed, 50 insertions(+)\n create mode 100644 trailer.c\n\ndiff --git a/Makefile b/Makefile\nindex d4ce53a..ec67ae1 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -883,6 +883,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..db93a63\n--- /dev/null\n+++ b/trailer.c\n@@ -0,0 +1,49 @@\n+#include \"cache.h\"\n+/*\n+ * Copyright (c) 2013, 2014 Christian Couder <chriscool@tuxfamily.org>\n+ */\n+\n+enum action_where { WHERE_AFTER, WHERE_BEFORE };\n+enum action_if_exists { EXISTS_ADD_IF_DIFFERENT, EXISTS_ADD_IF_DIFFERENT_NEIGHBOR,\n+\t\t\tEXISTS_ADD, EXISTS_OVERWRITE, EXISTS_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_exists if_exists;\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+{\n+\twhile (len > 0 && !isalnum(buf[len - 1]))\n+\t\tlen--;\n+\treturn len;\n+}\n-- \n1.9.0.164.g3aa33cd.dirty\n"},{"id":"238243","messageId":"20140401192023.353.48293.chriscool@tuxfamily.org","threadId":"36334","inReplyTo":"20140401191831.353.99271.chriscool@tuxfamily.org","subject":"[PATCH v9 02/11] trailer: process trailers from stdin and arguments","fromName":"Christian Couder","fromEmail":"chriscool@tuxfamily.org","sentAt":"2014-04-01T19:20:13Z","receivedAt":"2014-04-01T19:20:13Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"Implement the logic to process trailers from stdin and arguments.\n\nAt the beginning trailers from stdin are in their own in_tok\ndoubly linked list, and trailers from arguments are in their own\narg_tok doubly linked list.\n\nThe lists are traversed and when an arg_tok should be \"applied\",\nit is removed from its list and inserted into the in_tok list.\n\nSigned-off-by: Christian Couder <chriscool@tuxfamily.org>\n---\n trailer.c | 198 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++\n 1 file changed, 198 insertions(+)\n\ndiff --git a/trailer.c b/trailer.c\nindex db93a63..52108c2 100644\n--- a/trailer.c\n+++ b/trailer.c\n@@ -47,3 +47,201 @@ static size_t alnum_len(const char *buf, size_t len)\n \t\tlen--;\n \treturn len;\n }\n+\n+static void free_trailer_item(struct trailer_item *item)\n+{\n+\tfree(item->conf.name);\n+\tfree(item->conf.key);\n+\tfree(item->conf.command);\n+\tfree((char *)item->token);\n+\tfree((char *)item->value);\n+\tfree(item);\n+}\n+\n+static void add_arg_to_input_list(struct trailer_item *in_tok,\n+\t\t\t\t  struct trailer_item *arg_tok)\n+{\n+\tif (arg_tok->conf.where == WHERE_AFTER) {\n+\t\targ_tok->next = in_tok->next;\n+\t\tin_tok->next = arg_tok;\n+\t\targ_tok->previous = in_tok;\n+\t\tif (arg_tok->next)\n+\t\t\targ_tok->next->previous = arg_tok;\n+\t} else {\n+\t\targ_tok->previous = in_tok->previous;\n+\t\tin_tok->previous = arg_tok;\n+\t\targ_tok->next = in_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 *in_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 (!in_tok)\n+\t\t\treturn 1;\n+\t\tif (same_trailer(in_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\tin_tok = (where == WHERE_AFTER) ? in_tok->previous : in_tok->next;\n+\t} while (check_all);\n+\treturn 1;\n+}\n+\n+static void apply_arg_if_exists(struct trailer_item *in_tok,\n+\t\t\t\tstruct trailer_item *arg_tok,\n+\t\t\t\tint alnum_len)\n+{\n+\tswitch (arg_tok->conf.if_exists) {\n+\tcase EXISTS_DO_NOTHING:\n+\t\tfree_trailer_item(arg_tok);\n+\t\tbreak;\n+\tcase EXISTS_OVERWRITE:\n+\t\tfree((char *)in_tok->value);\n+\t\tin_tok->value = xstrdup(arg_tok->value);\n+\t\tfree_trailer_item(arg_tok);\n+\t\tbreak;\n+\tcase EXISTS_ADD:\n+\t\tadd_arg_to_input_list(in_tok, arg_tok);\n+\t\tbreak;\n+\tcase EXISTS_ADD_IF_DIFFERENT:\n+\t\tif (check_if_different(in_tok, arg_tok, alnum_len, 1))\n+\t\t\tadd_arg_to_input_list(in_tok, arg_tok);\n+\t\telse\n+\t\t\tfree_trailer_item(arg_tok);\n+\t\tbreak;\n+\tcase EXISTS_ADD_IF_DIFFERENT_NEIGHBOR:\n+\t\tif (check_if_different(in_tok, arg_tok, alnum_len, 0))\n+\t\t\tadd_arg_to_input_list(in_tok, arg_tok);\n+\t\telse\n+\t\t\tfree_trailer_item(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_input_token(struct trailer_item *in_tok,\n+\t\t\t\tstruct trailer_item **arg_tok_first,\n+\t\t\t\tenum action_where where)\n+{\n+\tstruct trailer_item *arg_tok;\n+\tstruct trailer_item *next_arg;\n+\n+\tint after = where == WHERE_AFTER;\n+\tint tok_alnum_len = alnum_len(in_tok->token, strlen(in_tok->token));\n+\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(in_tok, arg_tok, tok_alnum_len))\n+\t\t\tcontinue;\n+\t\tif (arg_tok->conf.where != where)\n+\t\t\tcontinue;\n+\t\tremove_from_list(arg_tok, arg_tok_first);\n+\t\tapply_arg_if_exists(in_tok, arg_tok, tok_alnum_len);\n+\t\t/*\n+\t\t * If arg has been added to input,\n+\t\t * then we need to process it too now.\n+\t\t */\n+\t\tif ((after ? in_tok->next : in_tok->previous) == arg_tok)\n+\t\t\tin_tok = arg_tok;\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 **in_tok_first,\n+\t\t\t\t struct trailer_item **in_tok_last,\n+\t\t\t\t struct trailer_item *arg_tok)\n+{\n+\tstruct trailer_item **in_tok;\n+\tenum action_where where;\n+\n+\tswitch (arg_tok->conf.if_missing) {\n+\tcase MISSING_DO_NOTHING:\n+\t\tfree_trailer_item(arg_tok);\n+\t\tbreak;\n+\tcase MISSING_ADD:\n+\t\twhere = arg_tok->conf.where;\n+\t\tin_tok = (where == WHERE_AFTER) ? in_tok_last : in_tok_first;\n+\t\tif (*in_tok) {\n+\t\t\tadd_arg_to_input_list(*in_tok, arg_tok);\n+\t\t\t*in_tok = arg_tok;\n+\t\t} else {\n+\t\t\t*in_tok_first = arg_tok;\n+\t\t\t*in_tok_last = arg_tok;\n+\t\t}\n+\t\tbreak;\n+\t}\n+}\n+\n+static void process_trailers_lists(struct trailer_item **in_tok_first,\n+\t\t\t\t   struct trailer_item **in_tok_last,\n+\t\t\t\t   struct trailer_item **arg_tok_first)\n+{\n+\tstruct trailer_item *in_tok;\n+\tstruct trailer_item *arg_tok;\n+\n+\tif (!*arg_tok_first)\n+\t\treturn;\n+\n+\t/* Process input from end to start */\n+\tfor (in_tok = *in_tok_last; in_tok; in_tok = in_tok->previous)\n+\t\tprocess_input_token(in_tok, arg_tok_first, WHERE_AFTER);\n+\n+\tupdate_last(in_tok_last);\n+\n+\tif (!*arg_tok_first)\n+\t\treturn;\n+\n+\t/* Process input from start to end */\n+\tfor (in_tok = *in_tok_first; in_tok; in_tok = in_tok->next)\n+\t\tprocess_input_token(in_tok, arg_tok_first, WHERE_BEFORE);\n+\n+\tupdate_first(in_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(in_tok_first, in_tok_last, arg_tok);\n+\t}\n+}\n-- \n1.9.0.164.g3aa33cd.dirty\n"},{"id":"238238","messageId":"20140401192023.353.78359.chriscool@tuxfamily.org","threadId":"36334","inReplyTo":"20140401191831.353.99271.chriscool@tuxfamily.org","subject":"[PATCH v9 03/11] trailer: read and process config information","fromName":"Christian Couder","fromEmail":"chriscool@tuxfamily.org","sentAt":"2014-04-01T19:20:14Z","receivedAt":"2014-04-01T19:20:14Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"Read the configuration to get trailer information, and then process\nit and storing it in a doubly linked list.\n\nThe config information is stored in the list whose first item is\npointed to by:\n\nstatic struct trailer_item *first_conf_item;\n\nSigned-off-by: Christian Couder <chriscool@tuxfamily.org>\n---\n trailer.c | 146 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++\n 1 file changed, 146 insertions(+)\n\ndiff --git a/trailer.c b/trailer.c\nindex 52108c2..f7714d5 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@@ -245,3 +247,147 @@ static void process_trailers_lists(struct trailer_item **in_tok_first,\n \t\tapply_arg_if_missing(in_tok_first, in_tok_last, arg_tok);\n \t}\n }\n+\n+static int set_where(struct conf_info *item, const char *value)\n+{\n+\tif (!strcmp(\"after\", value))\n+\t\titem->where = WHERE_AFTER;\n+\telse if (!strcmp(\"before\", value))\n+\t\titem->where = WHERE_BEFORE;\n+\telse\n+\t\treturn -1;\n+\treturn 0;\n+}\n+\n+static int set_if_exists(struct conf_info *item, const char *value)\n+{\n+\tif (!strcmp(\"addIfDifferent\", value))\n+\t\titem->if_exists = EXISTS_ADD_IF_DIFFERENT;\n+\telse if (!strcmp(\"addIfDifferentNeighbor\", value))\n+\t\titem->if_exists = EXISTS_ADD_IF_DIFFERENT_NEIGHBOR;\n+\telse if (!strcmp(\"add\", value))\n+\t\titem->if_exists = EXISTS_ADD;\n+\telse if (!strcmp(\"overwrite\", value))\n+\t\titem->if_exists = EXISTS_OVERWRITE;\n+\telse if (!strcmp(\"doNothing\", value))\n+\t\titem->if_exists = EXISTS_DO_NOTHING;\n+\telse\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 (!strcmp(\"doNothing\", value))\n+\t\titem->if_missing = MISSING_DO_NOTHING;\n+\telse if (!strcmp(\"add\", value))\n+\t\titem->if_missing = MISSING_ADD;\n+\telse\n+\t\treturn -1;\n+\treturn 0;\n+}\n+\n+static struct trailer_item *get_conf_item(const 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\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.name = xstrdup(name);\n+\n+\tif (!previous)\n+\t\tfirst_conf_item = item;\n+\telse {\n+\t\tprevious->next = item;\n+\t\titem->previous = previous;\n+\t}\n+\n+\treturn item;\n+}\n+\n+enum trailer_info_type { TRAILER_KEY, TRAILER_COMMAND, TRAILER_WHERE,\n+\t\t\t TRAILER_IF_EXISTS, TRAILER_IF_MISSING };\n+\n+static struct {\n+\tconst char *name;\n+\tenum trailer_info_type type;\n+} trailer_config_items[] = {\n+\t{ \"key\", TRAILER_KEY },\n+\t{ \"command\", TRAILER_COMMAND },\n+\t{ \"where\", TRAILER_WHERE },\n+\t{ \"ifexists\", TRAILER_IF_EXISTS },\n+\t{ \"ifmissing\", TRAILER_IF_MISSING }\n+};\n+\n+static int git_trailer_config(const char *conf_key, const char *value, void *cb)\n+{\n+\tconst char *trailer_item, *variable_name;\n+\tstruct trailer_item *item;\n+\tstruct conf_info *conf;\n+\tchar *name = NULL;\n+\tenum trailer_info_type type;\n+\tint i;\n+\n+\ttrailer_item = skip_prefix(conf_key, \"trailer.\");\n+\tif (!trailer_item)\n+\t\treturn 0;\n+\n+\tvariable_name = strrchr(trailer_item, '.');\n+\tif (!variable_name) {\n+\t\twarning(_(\"two level trailer config variable %s\"), conf_key);\n+\t\treturn 0;\n+\t}\n+\n+\tvariable_name++;\n+\tfor (i = 0; i < ARRAY_SIZE(trailer_config_items); i++) {\n+\t\tif (strcmp(trailer_config_items[i].name, variable_name))\n+\t\t\tcontinue;\n+\t\tname = xstrndup(trailer_item,  variable_name - trailer_item -1);\n+\t\ttype = trailer_config_items[i].type;\n+\t\tbreak;\n+\t}\n+\n+\tif (!name)\n+\t\treturn 0;\n+\n+\titem = get_conf_item(name);\n+\tconf = &item->conf;\n+\tfree(name);\n+\n+\tswitch (type) {\n+\tcase TRAILER_KEY:\n+\t\tif (conf->key)\n+\t\t\twarning(_(\"more than one %s\"), conf_key);\n+\t\tconf->key = xstrdup(value);\n+\t\tbreak;\n+\tcase TRAILER_COMMAND:\n+\t\tif (conf->command)\n+\t\t\twarning(_(\"more than one %s\"), conf_key);\n+\t\tconf->command = xstrdup(value);\n+\t\tbreak;\n+\tcase TRAILER_WHERE:\n+\t\tif (set_where(conf, value))\n+\t\t\twarning(_(\"unknown value '%s' for key '%s'\"), value, conf_key);\n+\t\tbreak;\n+\tcase TRAILER_IF_EXISTS:\n+\t\tif (set_if_exists(conf, value))\n+\t\t\twarning(_(\"unknown value '%s' for key '%s'\"), value, conf_key);\n+\t\tbreak;\n+\tcase TRAILER_IF_MISSING:\n+\t\tif (set_if_missing(conf, value))\n+\t\t\twarning(_(\"unknown value '%s' for key '%s'\"), value, conf_key);\n+\t\tbreak;\n+\tdefault:\n+\t\tdie(\"internal bug in trailer.c\");\n+\t}\n+\treturn 0;\n+}\n-- \n1.9.0.164.g3aa33cd.dirty\n"},{"id":"238249","messageId":"20140401192023.353.25828.chriscool@tuxfamily.org","threadId":"36334","inReplyTo":"20140401191831.353.99271.chriscool@tuxfamily.org","subject":"[PATCH v9 04/11] trailer: process command line trailer arguments","fromName":"Christian Couder","fromEmail":"chriscool@tuxfamily.org","sentAt":"2014-04-01T19:20:15Z","receivedAt":"2014-04-01T19:20:15Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"Parse the trailer command line arguments and put\nthe result into an arg_tok doubly linked list.\n\nSigned-off-by: Christian Couder <chriscool@tuxfamily.org>\n---\n trailer.c | 103 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++\n 1 file changed, 103 insertions(+)\n\ndiff --git a/trailer.c b/trailer.c\nindex f7714d5..743ff23 100644\n--- a/trailer.c\n+++ b/trailer.c\n@@ -391,3 +391,106 @@ static int git_trailer_config(const char *conf_key, const char *value, void *cb)\n \t}\n \treturn 0;\n }\n+\n+static int parse_trailer(struct strbuf *tok, struct strbuf *val, const char *trailer)\n+{\n+\tsize_t len = strcspn(trailer, \"=:\");\n+\tif (len == 0)\n+\t\treturn error(_(\"empty trailer token in trailer '%s'\"), trailer);\n+\tif (len < strlen(trailer)) {\n+\t\tstrbuf_add(tok, trailer, len);\n+\t\tstrbuf_trim(tok);\n+\t\tstrbuf_addstr(val, trailer + len + 1);\n+\t\tstrbuf_trim(val);\n+\t} else {\n+\t\tstrbuf_addstr(tok, trailer);\n+\t\tstrbuf_trim(tok);\n+\t}\n+\treturn 0;\n+}\n+\n+\n+static void duplicate_conf(struct conf_info *dst, struct conf_info *src)\n+{\n+\t*dst = *src;\n+\tif (src->name)\n+\t\tdst->name = xstrdup(src->name);\n+\tif (src->key)\n+\t\tdst->key = xstrdup(src->key);\n+\tif (src->command)\n+\t\tdst->command = xstrdup(src->command);\n+}\n+\n+static struct trailer_item *new_trailer_item(struct trailer_item *conf_item,\n+\t\t\t\t\t     char *tok, char *val)\n+{\n+\tstruct trailer_item *new = xcalloc(sizeof(*new), 1);\n+\tnew->value = val;\n+\n+\tif (conf_item) {\n+\t\tduplicate_conf(&new->conf, &conf_item->conf);\n+\t\tnew->token = xstrdup(conf_item->conf.key);\n+\t\tfree(tok);\n+\t} else\n+\t\tnew->token = tok;\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 *item;\n+\tint tok_alnum_len;\n+\n+\tif (parse_trailer(&tok, &val, string))\n+\t\treturn NULL;\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\tstrbuf_release(&tok);\n+\t\t\treturn new_trailer_item(item,\n+\t\t\t\t\t\tNULL,\n+\t\t\t\t\t\tstrbuf_detach(&val, NULL));\n+\t\t}\n+\t}\n+\n+\treturn new_trailer_item(NULL,\n+\t\t\t\tstrbuf_detach(&tok, NULL),\n+\t\t\t\tstrbuf_detach(&val, NULL));\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 (!new)\n+\t\treturn;\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.9.0.164.g3aa33cd.dirty\n"},{"id":"238247","messageId":"20140401192023.353.81287.chriscool@tuxfamily.org","threadId":"36334","inReplyTo":"20140401191831.353.99271.chriscool@tuxfamily.org","subject":"[PATCH v9 05/11] trailer: parse trailers from stdin","fromName":"Christian Couder","fromEmail":"chriscool@tuxfamily.org","sentAt":"2014-04-01T19:20:16Z","receivedAt":"2014-04-01T19:20:16Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"Read trailers from stdin, parse them and put the result into a doubly linked\nlist.\n\nSigned-off-by: Christian Couder <chriscool@tuxfamily.org>\n---\n trailer.c | 76 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++\n 1 file changed, 76 insertions(+)\n\ndiff --git a/trailer.c b/trailer.c\nindex 743ff23..953ae51 100644\n--- a/trailer.c\n+++ b/trailer.c\n@@ -50,6 +50,14 @@ static size_t alnum_len(const char *buf, size_t len)\n \treturn len;\n }\n \n+static inline int contains_only_spaces(const char *str)\n+{\n+\tconst char *s = str;\n+\twhile (*s && isspace(*s))\n+\t\ts++;\n+\treturn !*s;\n+}\n+\n static void free_trailer_item(struct trailer_item *item)\n {\n \tfree(item->conf.name);\n@@ -494,3 +502,71 @@ 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_stdin(void)\n+{\n+\tstruct strbuf **lines;\n+\tstruct strbuf sb = STRBUF_INIT;\n+\n+\tif (strbuf_read(&sb, fileno(stdin), 0) < 0)\n+\t\tdie_errno(_(\"could not read from stdin\"));\n+\n+\tlines = strbuf_split(&sb, '\\n');\n+\n+\tstrbuf_release(&sb);\n+\n+\treturn lines;\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 start, empty = 1, count = 0;\n+\n+\t/* Get the line count */\n+\twhile (lines[count])\n+\t\tcount++;\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 (contains_only_spaces(lines[start]->buf)) {\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_stdin(struct trailer_item **in_tok_first,\n+\t\t\t  struct trailer_item **in_tok_last)\n+{\n+\tstruct strbuf **lines = read_stdin();\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+\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(in_tok_first, in_tok_last, new);\n+\t}\n+\n+\tstrbuf_list_free(lines);\n+}\n-- \n1.9.0.164.g3aa33cd.dirty\n"},{"id":"238245","messageId":"20140401192023.353.45651.chriscool@tuxfamily.org","threadId":"36334","inReplyTo":"20140401191831.353.99271.chriscool@tuxfamily.org","subject":"[PATCH v9 06/11] trailer: put all the processing together and print","fromName":"Christian Couder","fromEmail":"chriscool@tuxfamily.org","sentAt":"2014-04-01T19:20:17Z","receivedAt":"2014-04-01T19:20:17Z","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 | 49 +++++++++++++++++++++++++++++++++++++++++++++++++\n trailer.h |  6 ++++++\n 2 files changed, 55 insertions(+)\n create mode 100644 trailer.h\n\ndiff --git a/trailer.c b/trailer.c\nindex 953ae51..5d2b3ad 100644\n--- a/trailer.c\n+++ b/trailer.c\n@@ -1,4 +1,5 @@\n #include \"cache.h\"\n+#include \"trailer.h\"\n /*\n  * Copyright (c) 2013, 2014 Christian Couder <chriscool@tuxfamily.org>\n  */\n@@ -68,6 +69,26 @@ static void free_trailer_item(struct trailer_item *item)\n \tfree(item);\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_input_list(struct trailer_item *in_tok,\n \t\t\t\t  struct trailer_item *arg_tok)\n {\n@@ -570,3 +591,31 @@ static void process_stdin(struct trailer_item **in_tok_first,\n \n \tstrbuf_list_free(lines);\n }\n+\n+static void free_all(struct trailer_item **first)\n+{\n+\twhile (*first) {\n+\t\tstruct trailer_item *item = remove_first(first);\n+\t\tfree_trailer_item(item);\n+\t}\n+}\n+\n+void process_trailers(int trim_empty, int argc, const char **argv)\n+{\n+\tstruct trailer_item *in_tok_first = NULL;\n+\tstruct trailer_item *in_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 stdin */\n+\tprocess_stdin(&in_tok_first, &in_tok_last);\n+\n+\targ_tok_first = process_command_line_args(argc, argv);\n+\n+\tprocess_trailers_lists(&in_tok_first, &in_tok_last, &arg_tok_first);\n+\n+\tprint_all(in_tok_first, trim_empty);\n+\n+\tfree_all(&in_tok_first);\n+}\ndiff --git a/trailer.h b/trailer.h\nnew file mode 100644\nindex 0000000..9323b1e\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(int trim_empty, int argc, const char **argv);\n+\n+#endif /* TRAILER_H */\n-- \n1.9.0.164.g3aa33cd.dirty\n"},{"id":"238244","messageId":"20140401192023.353.4997.chriscool@tuxfamily.org","threadId":"36334","inReplyTo":"20140401191831.353.99271.chriscool@tuxfamily.org","subject":"[PATCH v9 07/11] trailer: add interpret-trailers command","fromName":"Christian Couder","fromEmail":"chriscool@tuxfamily.org","sentAt":"2014-04-01T19:20:18Z","receivedAt":"2014-04-01T19:20:18Z","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 | 33 +++++++++++++++++++++++++++++++++\n git.c                        |  1 +\n 5 files changed, 37 insertions(+)\n create mode 100644 builtin/interpret-trailers.c\n\ndiff --git a/.gitignore b/.gitignore\nindex dc600f9..c2a0b19 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 ec67ae1..94f7e95 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -947,6 +947,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 c47c110..8ca0065 100644\n--- a/builtin.h\n+++ b/builtin.h\n@@ -73,6 +73,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..0c8ca72\n--- /dev/null\n+++ b/builtin/interpret-trailers.c\n@@ -0,0 +1,33 @@\n+/*\n+ * Builtin \"git interpret-trailers\"\n+ *\n+ * Copyright (c) 2013, 2014 Christian Couder <chriscool@tuxfamily.org>\n+ *\n+ */\n+\n+#include \"cache.h\"\n+#include \"builtin.h\"\n+#include \"parse-options.h\"\n+#include \"trailer.h\"\n+\n+static const char * const git_interpret_trailers_usage[] = {\n+\tN_(\"git interpret-trailers [--trim-empty] [(<token>[(=|:)<value>])...]\"),\n+\tNULL\n+};\n+\n+int cmd_interpret_trailers(int argc, const char **argv, const char *prefix)\n+{\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_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(trim_empty, argc, argv);\n+\n+\treturn 0;\n+}\ndiff --git a/git.c b/git.c\nindex 7cf2953..63a03eb 100644\n--- a/git.c\n+++ b/git.c\n@@ -380,6 +380,7 @@ static struct cmd_struct commands[] = {\n \t{ \"index-pack\", cmd_index_pack, RUN_SETUP_GENTLY },\n \t{ \"init\", cmd_init_db },\n \t{ \"init-db\", cmd_init_db },\n+\t{ \"interpret-trailers\", cmd_interpret_trailers, RUN_SETUP },\n \t{ \"log\", cmd_log, RUN_SETUP },\n \t{ \"ls-files\", cmd_ls_files, RUN_SETUP },\n \t{ \"ls-remote\", cmd_ls_remote, RUN_SETUP_GENTLY },\n-- \n1.9.0.164.g3aa33cd.dirty\n"},{"id":"238241","messageId":"20140401192023.353.56221.chriscool@tuxfamily.org","threadId":"36334","inReplyTo":"20140401191831.353.99271.chriscool@tuxfamily.org","subject":"[PATCH v9 08/11] trailer: add tests for \"git interpret-trailers\"","fromName":"Christian Couder","fromEmail":"chriscool@tuxfamily.org","sentAt":"2014-04-01T19:20:19Z","receivedAt":"2014-04-01T19:20:19Z","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 | 336 ++++++++++++++++++++++++++++++++++++++++++\n 1 file changed, 336 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..417a4f3\n--- /dev/null\n+++ b/t/t7513-interpret-trailers.sh\n@@ -0,0 +1,336 @@\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+# When we want one trailing space at the end of each line, let's use sed\n+# to make sure that these spaces are not removed by any automatic tool.\n+\n+test_expect_success 'setup' '\n+\tcat >basic_message <<-\\EOF &&\n+\t\tsubject\n+\n+\t\tbody\n+\tEOF\n+\tcat >complex_message_body <<-\\EOF &&\n+\t\tmy subject\n+\n+\t\tmy body which is long\n+\t\tand contains some special\n+\t\tchars like : = ? !\n+\n+\tEOF\n+\tsed -e \"s/ Z\\$/ /\" >complex_message_trailers <<-\\EOF\n+\t\tFixes: Z\n+\t\tAcked-by: Z\n+\t\tReviewed-by: Z\n+\t\tSigned-off-by: Z\n+\tEOF\n+'\n+\n+test_expect_success 'without config' '\n+\tsed -e \"s/ Z\\$/ /\" >expected <<-\\EOF &&\n+\t\tack: Peff\n+\t\tReviewed-by: Z\n+\t\tAcked-by: Johan\n+\tEOF\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+\tcat >expected <<-\\EOF &&\n+\t\tack: Peff\n+\t\tAcked-by: Johan\n+\tEOF\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+\tcat >expected <<-\\EOF &&\n+\t\tAcked-by: Peff\n+\tEOF\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+\tcat >expected <<-\\EOF &&\n+\t\tAcked-by= Peff\n+\tEOF\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+\tcat >expected <<-\\EOF &&\n+\t\tBug #42\n+\tEOF\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 <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+\tsed -e \"s/ Z\\$/ /\" >>expected <<-\\EOF &&\n+\t\tFixes: Z\n+\t\tAcked-by= Z\n+\t\tReviewed-by: Z\n+\t\tSigned-off-by: Z\n+\tEOF\n+\tgit interpret-trailers <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+\tsed -e \"s/ Z\\$/ /\" >>expected <<-\\EOF &&\n+\t\tFixes: Z\n+\t\tAcked-by= Z\n+\t\tAcked-by= Peff\n+\t\tReviewed-by: Z\n+\t\tSigned-off-by: Z\n+\t\tBug #42\n+\tEOF\n+\tgit interpret-trailers \"ack: Peff\" \"bug: 42\" <complex_message >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+\tcat >>expected <<-\\EOF &&\n+\t\tAcked-by= Peff\n+\t\tBug #42\n+\tEOF\n+\tgit interpret-trailers --trim-empty \"ack: Peff\" \"bug: 42\" <complex_message >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+\tsed -e \"s/ Z\\$/ /\" >>expected <<-\\EOF &&\n+\t\tBug #42\n+\t\tFixes: Z\n+\t\tAcked-by= Z\n+\t\tAcked-by= Peff\n+\t\tReviewed-by: Z\n+\t\tSigned-off-by: Z\n+\tEOF\n+\tgit interpret-trailers \"ack: Peff\" \"bug: 42\" <complex_message >actual &&\n+\ttest_cmp expected actual\n+'\n+\n+test_expect_success 'using \"where = before\" for a token in the middle of the message' '\n+\tgit config trailer.review.key \"Reviewed-by:\" &&\n+\tgit config trailer.review.where \"before\" &&\n+\tcat complex_message_body >expected &&\n+\tsed -e \"s/ Z\\$/ /\" >>expected <<-\\EOF &&\n+\t\tBug #42\n+\t\tFixes: Z\n+\t\tAcked-by= Z\n+\t\tAcked-by= Peff\n+\t\tReviewed-by: Johan\n+\t\tReviewed-by: Z\n+\t\tSigned-off-by: Z\n+\tEOF\n+\tgit interpret-trailers \"ack: Peff\" \"bug: 42\" \"review: Johan\" <complex_message >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+\tcat >>expected <<-\\EOF &&\n+\t\tBug #46\n+\t\tBug #42\n+\t\tAcked-by= Peff\n+\t\tReviewed-by: Johan\n+\tEOF\n+\tgit interpret-trailers --trim-empty \"ack: Peff\" \"bug: 42\" \"review: Johan\" \"Bug: 46\" <complex_message >actual &&\n+\ttest_cmp expected actual\n+'\n+\n+test_expect_success 'the default is \"ifExists = addIfDifferent\"' '\n+\tcat complex_message_body >expected &&\n+\tsed -e \"s/ Z\\$/ /\" >>expected <<-\\EOF &&\n+\t\tBug #42\n+\t\tFixes: Z\n+\t\tAcked-by= Z\n+\t\tAcked-by= Peff\n+\t\tReviewed-by: Z\n+\t\tSigned-off-by: Z\n+\tEOF\n+\tgit interpret-trailers \"ack: Peff\" \"review:\" \"bug: 42\" \"ack: Peff\" <complex_message >actual &&\n+\ttest_cmp expected actual\n+'\n+\n+test_expect_success 'using \"ifExists = addIfDifferent\"' '\n+\tgit config trailer.review.ifExists \"addIfDifferent\" &&\n+\tcat complex_message_body >expected &&\n+\tsed -e \"s/ Z\\$/ /\" >>expected <<-\\EOF &&\n+\t\tBug #42\n+\t\tFixes: Z\n+\t\tAcked-by= Z\n+\t\tAcked-by= Peff\n+\t\tReviewed-by: Z\n+\t\tSigned-off-by: Z\n+\tEOF\n+\tgit interpret-trailers \"ack: Peff\" \"review:\" \"bug: 42\" \"ack: Peff\" <complex_message >actual &&\n+\ttest_cmp expected actual\n+'\n+\n+test_expect_success 'using \"ifExists = addIfDifferentNeighbor\"' '\n+\tgit config trailer.ack.ifExists \"addIfDifferentNeighbor\" &&\n+\tcat complex_message_body >expected &&\n+\tsed -e \"s/ Z\\$/ /\" >>expected <<-\\EOF &&\n+\t\tBug #42\n+\t\tFixes: Z\n+\t\tAcked-by= Z\n+\t\tAcked-by= Peff\n+\t\tAcked-by= Junio\n+\t\tAcked-by= Peff\n+\t\tReviewed-by: Z\n+\t\tSigned-off-by: Z\n+\tEOF\n+\tgit interpret-trailers \"ack: Peff\" \"review:\" \"ack: Junio\" \"bug: 42\" \"ack: Peff\" <complex_message >actual &&\n+\ttest_cmp expected actual\n+'\n+\n+test_expect_success 'using \"ifExists = addIfDifferentNeighbor\" and --trim-empty' '\n+\tgit config trailer.ack.ifExists \"addIfDifferentNeighbor\" &&\n+\tcat complex_message_body >expected &&\n+\tcat >>expected <<-\\EOF &&\n+\t\tBug #42\n+\t\tAcked-by= Peff\n+\t\tAcked-by= Junio\n+\t\tAcked-by= Peff\n+\tEOF\n+\tgit interpret-trailers --trim-empty \"ack: Peff\" \"Acked-by= Peff\" \"review:\" \"ack: Junio\" \"bug: 42\" \"ack: Peff\" <complex_message >actual &&\n+\ttest_cmp expected actual\n+'\n+\n+test_expect_success 'using \"ifExists = add\"' '\n+\tgit config trailer.ack.ifExists \"add\" &&\n+\tcat complex_message_body >expected &&\n+\tsed -e \"s/ Z\\$/ /\" >>expected <<-\\EOF &&\n+\t\tBug #42\n+\t\tFixes: Z\n+\t\tAcked-by= Z\n+\t\tAcked-by= Peff\n+\t\tAcked-by= Peff\n+\t\tAcked-by= Junio\n+\t\tAcked-by= Peff\n+\t\tReviewed-by: Z\n+\t\tSigned-off-by: Z\n+\tEOF\n+\tgit interpret-trailers \"ack: Peff\" \"Acked-by= Peff\" \"review:\" \"ack: Junio\" \"bug: 42\" \"ack: Peff\" <complex_message >actual &&\n+\ttest_cmp expected actual\n+'\n+\n+test_expect_success 'using \"ifExists = overwrite\"' '\n+\tgit config trailer.fix.key \"Fixes:\" &&\n+\tgit config trailer.fix.ifExists \"overwrite\" &&\n+\tcat complex_message_body >expected &&\n+\tsed -e \"s/ Z\\$/ /\" >>expected <<-\\EOF &&\n+\t\tBug #42\n+\t\tFixes: 22\n+\t\tAcked-by= Z\n+\t\tAcked-by= Junio\n+\t\tAcked-by= Peff\n+\t\tReviewed-by: Z\n+\t\tSigned-off-by: Z\n+\tEOF\n+\tgit interpret-trailers \"review:\" \"fix=53\" \"ack: Junio\" \"fix=22\" \"bug: 42\" \"ack: Peff\" <complex_message >actual &&\n+\ttest_cmp expected actual\n+'\n+\n+test_expect_success 'using \"ifExists = doNothing\"' '\n+\tgit config trailer.fix.ifExists \"doNothing\" &&\n+\tcat complex_message_body >expected &&\n+\tsed -e \"s/ Z\\$/ /\" >>expected <<-\\EOF &&\n+\t\tBug #42\n+\t\tFixes: Z\n+\t\tAcked-by= Z\n+\t\tAcked-by= Junio\n+\t\tAcked-by= Peff\n+\t\tReviewed-by: Z\n+\t\tSigned-off-by: Z\n+\tEOF\n+\tgit interpret-trailers \"review:\" \"fix=53\" \"ack: Junio\" \"fix=22\" \"bug: 42\" \"ack: Peff\" <complex_message >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+\tsed -e \"s/ Z\\$/ /\" >>expected <<-\\EOF &&\n+\t\tBug #42\n+\t\tCc: Linus\n+\t\tFixes: Z\n+\t\tAcked-by= Z\n+\t\tAcked-by= Junio\n+\t\tAcked-by= Peff\n+\t\tReviewed-by: Z\n+\t\tSigned-off-by: Z\n+\tEOF\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_expect_success 'using \"ifMissing = add\"' '\n+\tgit config trailer.cc.ifMissing \"add\" &&\n+\tcat complex_message_body >expected &&\n+\tsed -e \"s/ Z\\$/ /\" >>expected <<-\\EOF &&\n+\t\tCc: Linus\n+\t\tBug #42\n+\t\tFixes: Z\n+\t\tAcked-by= Z\n+\t\tAcked-by= Junio\n+\t\tAcked-by= Peff\n+\t\tReviewed-by: Z\n+\t\tSigned-off-by: Z\n+\tEOF\n+\tgit interpret-trailers \"review:\" \"fix=53\" \"ack: Junio\" \"fix=22\" \"bug: 42\" \"cc=Linus\" \"ack: Peff\" <complex_message >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+\tsed -e \"s/ Z\\$/ /\" >>expected <<-\\EOF &&\n+\t\tBug #42\n+\t\tFixes: Z\n+\t\tAcked-by= Z\n+\t\tAcked-by= Junio\n+\t\tAcked-by= Peff\n+\t\tReviewed-by: Z\n+\t\tSigned-off-by: Z\n+\tEOF\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\n-- \n1.9.0.164.g3aa33cd.dirty\n"},{"id":"238239","messageId":"20140401192023.353.8875.chriscool@tuxfamily.org","threadId":"36334","inReplyTo":"20140401191831.353.99271.chriscool@tuxfamily.org","subject":"[PATCH v9 09/11] trailer: execute command from 'trailer.<name>.command'","fromName":"Christian Couder","fromEmail":"chriscool@tuxfamily.org","sentAt":"2014-04-01T19:20:20Z","receivedAt":"2014-04-01T19:20:20Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"Let the user specify a command that will give on its standard output\nthe value to use for the specified trailer.\n\nSigned-off-by: Christian Couder <chriscool@tuxfamily.org>\n---\n trailer.c | 63 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++\n 1 file changed, 63 insertions(+)\n\ndiff --git a/trailer.c b/trailer.c\nindex 5d2b3ad..bc96643 100644\n--- a/trailer.c\n+++ b/trailer.c\n@@ -1,4 +1,5 @@\n #include \"cache.h\"\n+#include \"run-command.h\"\n #include \"trailer.h\"\n /*\n  * Copyright (c) 2013, 2014 Christian Couder <chriscool@tuxfamily.org>\n@@ -13,11 +14,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_exists if_exists;\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@@ -59,6 +63,13 @@ static inline int contains_only_spaces(const char *str)\n \treturn !*s;\n }\n \n+static inline void strbuf_replace(struct strbuf *sb, const char *a, const char *b)\n+{\n+\tconst char *ptr = strstr(sb->buf, a);\n+\tif (ptr)\n+\t\tstrbuf_splice(sb, ptr - sb->buf, strlen(a), b, strlen(b));\n+}\n+\n static void free_trailer_item(struct trailer_item *item)\n {\n \tfree(item->conf.name);\n@@ -402,6 +413,7 @@ static int git_trailer_config(const char *conf_key, const char *value, void *cb)\n \t\tif (conf->command)\n \t\t\twarning(_(\"more than one %s\"), conf_key);\n \t\tconf->command = xstrdup(value);\n+\t\tconf->command_uses_arg = !!strstr(conf->command, TRAILER_ARG_STRING);\n \t\tbreak;\n \tcase TRAILER_WHERE:\n \t\tif (set_where(conf, value))\n@@ -438,6 +450,44 @@ static int parse_trailer(struct strbuf *tok, struct strbuf *val, const char *tra\n \treturn 0;\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 void duplicate_conf(struct conf_info *dst, struct conf_info *src)\n {\n@@ -460,6 +510,10 @@ static struct trailer_item *new_trailer_item(struct trailer_item *conf_item,\n \t\tduplicate_conf(&new->conf, &conf_item->conf);\n \t\tnew->token = xstrdup(conf_item->conf.key);\n \t\tfree(tok);\n+\t\tif (conf_item->conf.command_uses_arg || !val) {\n+\t\t\tnew->value = apply_command(conf_item->conf.command, val);\n+\t\t\tfree(val);\n+\t\t}\n \t} else\n \t\tnew->token = tok;\n \n@@ -515,12 +569,21 @@ 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\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.9.0.164.g3aa33cd.dirty\n"},{"id":"238242","messageId":"20140401192023.353.53841.chriscool@tuxfamily.org","threadId":"36334","inReplyTo":"20140401191831.353.99271.chriscool@tuxfamily.org","subject":"[PATCH v9 10/11] trailer: add tests for commands in config file","fromName":"Christian Couder","fromEmail":"chriscool@tuxfamily.org","sentAt":"2014-04-01T19:20:21Z","receivedAt":"2014-04-01T19:20:21Z","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 | 71 +++++++++++++++++++++++++++++++++++++++++++\n 1 file changed, 71 insertions(+)\n\ndiff --git a/t/t7513-interpret-trailers.sh b/t/t7513-interpret-trailers.sh\nindex 417a4f3..0a1f3b6 100755\n--- a/t/t7513-interpret-trailers.sh\n+++ b/t/t7513-interpret-trailers.sh\n@@ -333,4 +333,75 @@ test_expect_success 'using \"ifMissing = doNothing\"' '\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.ifExists \"addIfDifferentNeighbor\" &&\n+\tgit config trailer.sign.command \"echo \\\"A U Thor <author@example.com>\\\"\" &&\n+\tcat complex_message_body >expected &&\n+\tsed -e \"s/ Z\\$/ /\" >>expected <<-\\EOF &&\n+\t\tFixes: Z\n+\t\tAcked-by= Z\n+\t\tReviewed-by: Z\n+\t\tSigned-off-by: Z\n+\t\tSigned-off-by: A U Thor <author@example.com>\n+\tEOF\n+\tgit interpret-trailers \"review:\" \"fix=22\" <complex_message >actual &&\n+\ttest_cmp expected actual\n+'\n+\n+test_expect_success 'with command using commiter information' '\n+\tgit config trailer.sign.ifExists \"addIfDifferent\" &&\n+\tgit config trailer.sign.command \"echo \\\"\\$GIT_COMMITTER_NAME <\\$GIT_COMMITTER_EMAIL>\\\"\" &&\n+\tcat complex_message_body >expected &&\n+\tsed -e \"s/ Z\\$/ /\" >>expected <<-\\EOF &&\n+\t\tFixes: Z\n+\t\tAcked-by= Z\n+\t\tReviewed-by: Z\n+\t\tSigned-off-by: Z\n+\t\tSigned-off-by: C O Mitter <committer@example.com>\n+\tEOF\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.ifExists \"addIfDifferentNeighbor\" &&\n+\tgit config trailer.sign.command \"echo \\\"\\$GIT_AUTHOR_NAME <\\$GIT_AUTHOR_EMAIL>\\\"\" &&\n+\tcat complex_message_body >expected &&\n+\tsed -e \"s/ Z\\$/ /\" >>expected <<-\\EOF &&\n+\t\tFixes: Z\n+\t\tAcked-by= Z\n+\t\tReviewed-by: Z\n+\t\tSigned-off-by: Z\n+\t\tSigned-off-by: A U Thor <author@example.com>\n+\tEOF\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.ifExists \"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+\tsed -e \"s/ Z\\$/ /\" >>expected <<-EOF &&\n+\t\tFixes: $FIXED\n+\t\tAcked-by= Z\n+\t\tReviewed-by: Z\n+\t\tSigned-off-by: Z\n+\t\tSigned-off-by: A U Thor <author@example.com>\n+\tEOF\n+\tgit interpret-trailers \"review:\" \"fix=HEAD\" <complex_message >actual &&\n+\ttest_cmp expected actual\n+'\n+\n test_done\n-- \n1.9.0.164.g3aa33cd.dirty\n"},{"id":"238240","messageId":"20140401192023.353.34477.chriscool@tuxfamily.org","threadId":"36334","inReplyTo":"20140401191831.353.99271.chriscool@tuxfamily.org","subject":"[PATCH v9 11/11] Documentation: add documentation for 'git interpret-trailers'","fromName":"Christian Couder","fromEmail":"chriscool@tuxfamily.org","sentAt":"2014-04-01T19:20:22Z","receivedAt":"2014-04-01T19:20:22Z","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 | 123 +++++++++++++++++++++++++++++++\n 1 file changed, 123 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..75ae386\n--- /dev/null\n+++ b/Documentation/git-interpret-trailers.txt\n@@ -0,0 +1,123 @@\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] [(<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+This command is a filter. It reads the standard input for a commit\n+message and applies the `token` arguments, if any, to this\n+message. The resulting message is emited on the 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 whitespace, 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+new 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+OPTIONS\n+-------\n+--trim-empty::\n+\tIf the 'value' part of any trailer contains only whitespace,\n+\tthe whole trailer will be removed from the resulting message.\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 choose 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 choose 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+SEE ALSO\n+--------\n+linkgit:git-commit[1]\n+\n+GIT\n+---\n+Part of the linkgit:git[1] suite\n-- \n1.9.0.164.g3aa33cd.dirty\n"},{"id":"238281","messageId":"xmqqha6cy86t.fsf@gitster.dls.corp.google.com","threadId":"36334","inReplyTo":"20140401192023.353.56221.chriscool@tuxfamily.org","subject":"Re: [PATCH v9 08/11] trailer: add tests for \"git interpret-trailers\"","fromName":"Junio C Hamano","fromEmail":"junio@pobox.com","sentAt":"2014-04-01T22:51:54Z","receivedAt":"2014-04-01T22:51:54Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Junio C Hamano <gitster@pobox.com> writes:\n\n> Christian Couder <chriscool@tuxfamily.org> writes:\n>\n>> +test_expect_success '--trim-empty without config' '\n>> +\tcat >expected <<-\\EOF &&\n>> +\t\tack: Peff\n>> +\t\tAcked-by: Johan\n>> +\tEOF\n>> +\tgit interpret-trailers --trim-empty \"ack = Peff\" \"Reviewed-by\" \"Acked-by: Johan\" \"sob:\" >actual &&\n>> +\ttest_cmp expected actual\n>> +'\n>\n> Let's avoid these overlong and unreadable lines by doing something\n> like this (just one hunk shown for illustration):\n>\n>  \t\tack: Peff\n>  \t\tAcked-by: Johan\n>  \tEOF\n> -\tgit interpret-trailers --trim-empty \"ack = Peff\" \"Reviewed-by\" \"Acked-by: Johan\" \"sob:\" >actual &&\n> +\tgit interpret-trailers --trim-empty \"ack = Peff\" \\\n> +\t\t\"Reviewed-by\" \"Acked-by: Johan\" \"sob:\" >actual &&\n>  \ttest_cmp expected actual\n>  '\n>  \n\nI'll squash in the line-chopping fix to this, and also fill a\nmissing whitespace in 3/11 before pushing it out to 'pu'.  Hopefully\nthe result can be later merged to 'next' without further rerolls.\n\nThanks.\n"},{"id":"238280","messageId":"xmqqa9c4y823.fsf@gitster.dls.corp.google.com","threadId":"36334","inReplyTo":"20140401192023.353.56221.chriscool@tuxfamily.org","subject":"Re: [PATCH v9 08/11] trailer: add tests for \"git interpret-trailers\"","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2014-04-01T22:54:44Z","receivedAt":"2014-04-01T22:54:44Z","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> +test_expect_success '--trim-empty without config' '\n> +\tcat >expected <<-\\EOF &&\n> +\t\tack: Peff\n> +\t\tAcked-by: Johan\n> +\tEOF\n> +\tgit interpret-trailers --trim-empty \"ack = Peff\" \"Reviewed-by\" \"Acked-by: Johan\" \"sob:\" >actual &&\n> +\ttest_cmp expected actual\n> +'\n\nLet's avoid these overlong and unreadable lines by doing something\nlike this (just one hunk shown for illustration):\n\n \t\tack: Peff\n \t\tAcked-by: Johan\n \tEOF\n-\tgit interpret-trailers --trim-empty \"ack = Peff\" \"Reviewed-by\" \"Acked-by: Johan\" \"sob:\" >actual &&\n+\tgit interpret-trailers --trim-empty \"ack = Peff\" \\\n+\t\t\"Reviewed-by\" \"Acked-by: Johan\" \"sob:\" >actual &&\n \ttest_cmp expected actual\n '\n \n\nI've queued the series on 'pu' with the 'chop-overlong-lines' and\nanother minor fix squashed in; hopefully we can merge to 'next'\nsoonish.\n\nThanks.\n"},{"id":"238286","messageId":"20140402003938.GE6851@google.com","threadId":"36334","inReplyTo":"20140401192023.353.34477.chriscool@tuxfamily.org","subject":"Re: [PATCH v9 11/11] Documentation: add documentation for 'git interpret-trailers'","fromName":"Jonathan Nieder","fromEmail":"jrnieder@gmail.com","sentAt":"2014-04-02T00:39:38Z","receivedAt":"2014-04-02T00:39:38Z","isPatch":true,"sender":{"key":"jrnieder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/281595?v=4"},"body":"(culling cc list)\nHi,\n\nChristian Couder wrote:\n\n> [Subject: Documentation: add documentation for 'git interpret-trailers']\n>\n> Signed-off-by: Christian Couder <chriscool@tuxfamily.org>\n\nThis should be squashed into the patch that introduces the\ninterpret-trailers command, IMHO (or if it should be reviewed\nseparately, it can be an earlier patch).  That way, someone looking at\nwhen the command was introduced and wanting to understand what it was\noriginally meant to do has the information close by.\n\nThanks for picking up the 'git commit --fixes' topic and your steady\nwork improving the series.\n\n[...]\n> --- /dev/null\n> +++ b/Documentation/git-interpret-trailers.txt\n> @@ -0,0 +1,123 @@\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] [(<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> +This command is a filter. It reads the standard input for a commit\n> +message and applies the `token` arguments, if any, to this\n> +message. The resulting message is emited on the standard output.\n\nDo you have an example?  Does it work like this?\n\n\t$ git interpret-trailers 'signoff=Jonathan Nieder <jrnieder@gmail.com>' <<EOF\n\t> foo bar baz qux\n\t> EOF\n\tfoo bar baz qux\n\n\tSigned-off-by: Jonathan Nieder <jrnieder@gmail.com>\n\t$\n\nA short EXAMPLES section could help.\n\nIf I am understanding it correctly, would a name like 'git add-trailers'\nwork?  How do I read back the trailers later?\n\n[...]\n> +By default, a 'token=value' or 'token:value' argument will be added\n> +only if\n\nWhy support both '=' and ':'?  Using just one would make it easier to\ngrep through scripts to see who is adding signoffs.\n\nHope that helps,\nJonathan\n"},{"id":"238308","messageId":"CAP8UFD1hrXDdwKokLH_j=vwWoViC9sSJHf0gTFubh-oFQao4MA@mail.gmail.com","threadId":"36334","inReplyTo":"20140402003938.GE6851@google.com","subject":"Re: [PATCH v9 11/11] Documentation: add documentation for 'git interpret-trailers'","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2014-04-02T06:01:00Z","receivedAt":"2014-04-02T06:01:00Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"Hi,\n\nOn Wed, Apr 2, 2014 at 2:39 AM, Jonathan Nieder <jrnieder@gmail.com> wrote:\n> (culling cc list)\n> Hi,\n>\n> Christian Couder wrote:\n>\n>> [Subject: Documentation: add documentation for 'git interpret-trailers']\n>>\n>> Signed-off-by: Christian Couder <chriscool@tuxfamily.org>\n>\n> This should be squashed into the patch that introduces the\n> interpret-trailers command, IMHO (or if it should be reviewed\n> separately, it can be an earlier patch).  That way, someone looking at\n> when the command was introduced and wanting to understand what it was\n> originally meant to do has the information close by.\n\nWell, the series is not very long, so this patch is quite close to the\nbeginning anyway.\n\n> Thanks for picking up the 'git commit --fixes' topic and your steady\n> work improving the series.\n>\n> [...]\n>> --- /dev/null\n>> +++ b/Documentation/git-interpret-trailers.txt\n>> @@ -0,0 +1,123 @@\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] [(<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>> +This command is a filter. It reads the standard input for a commit\n>> +message and applies the `token` arguments, if any, to this\n>> +message. The resulting message is emited on the standard output.\n>\n> Do you have an example?  Does it work like this?\n>\n>         $ git interpret-trailers 'signoff=Jonathan Nieder <jrnieder@gmail.com>' <<EOF\n>         > foo bar baz qux\n>         > EOF\n>         foo bar baz qux\n>\n>         Signed-off-by: Jonathan Nieder <jrnieder@gmail.com>\n>         $\n\nYeah, that's the idea. But you need to run something like:\n\n$ git config trailer.signoff.key \"Signed-off-by:\"\n\nto configure it properly first.\n\nBy the way trying your example, I found that it is not currently\nadding an empty line before the s-o-b.\nI will have a look.\n\n> A short EXAMPLES section could help.\n\nYeah, it is planned, but not yet implemented, as written in patch 0/11:\n\n\"The following features are planned but not yet implemented:\n        - add more tests related to commands\n        - add examples in documentation\n        - integration with \"git commit\"\"\n\n> If I am understanding it correctly, would a name like 'git add-trailers'\n> work?\n\nIt could work but it can modify, not just add trailers.\n\n> How do I read back the trailers later?\n\nWhy do you want to read them back?\nRight now it should be used in hooks to modify commit messages.\n\n> [...]\n>> +By default, a 'token=value' or 'token:value' argument will be added\n>> +only if\n>\n> Why support both '=' and ':'?  Using just one would make it easier to\n> grep through scripts to see who is adding signoffs.\n\nThat was already discussed previously.\nThe reason is that people are used to \"token=value\" for command line\narguments, but trailers appears in the result as \"token: value\", so it\nis better for the user if we support both.\n\nThanks,\nChristian.\n"},{"id":"238324","messageId":"CAP8UFD3dr=p-=zf5Dt4YZA0DEGaqMtFd3GuAzJnC1fPyvUn2Kw@mail.gmail.com","threadId":"36334","inReplyTo":"xmqqa9c4y823.fsf@gitster.dls.corp.google.com","subject":"Re: [PATCH v9 08/11] trailer: add tests for \"git interpret-trailers\"","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2014-04-02T06:02:35Z","receivedAt":"2014-04-02T06:02:35Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"On Wed, Apr 2, 2014 at 12:54 AM, Junio C Hamano <gitster@pobox.com> wrote:\n>\n>> +test_expect_success '--trim-empty without config' '\n>> +     cat >expected <<-\\EOF &&\n>> +             ack: Peff\n>> +             Acked-by: Johan\n>> +     EOF\n>> +     git interpret-trailers --trim-empty \"ack = Peff\" \"Reviewed-by\" \"Acked-by: Johan\" \"sob:\" >actual &&\n>> +     test_cmp expected actual\n>> +'\n>\n> Let's avoid these overlong and unreadable lines by doing something\n> like this (just one hunk shown for illustration):\n>\n>                 ack: Peff\n>                 Acked-by: Johan\n>         EOF\n> -       git interpret-trailers --trim-empty \"ack = Peff\" \"Reviewed-by\" \"Acked-by: Johan\" \"sob:\" >actual &&\n> +       git interpret-trailers --trim-empty \"ack = Peff\" \\\n> +               \"Reviewed-by\" \"Acked-by: Johan\" \"sob:\" >actual &&\n>         test_cmp expected actual\n>  '\n\nOk.\n\n> I've queued the series on 'pu' with the 'chop-overlong-lines' and\n> another minor fix squashed in; hopefully we can merge to 'next'\n> soonish.\n\nGreat!\n\nThanks,\nChristian.\n"},{"id":"238384","messageId":"xmqqob0gohc2.fsf@gitster.dls.corp.google.com","threadId":"36334","inReplyTo":"CAP8UFD1hrXDdwKokLH_j=vwWoViC9sSJHf0gTFubh-oFQao4MA@mail.gmail.com","subject":"Re: [PATCH v9 11/11] Documentation: add documentation for 'git interpret-trailers'","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2014-04-04T22:33:33Z","receivedAt":"2014-04-04T22:33:33Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Christian Couder <christian.couder@gmail.com> writes:\n\n> \"The following features are planned but not yet implemented:\n>         - add more tests related to commands\n>         - add examples in documentation\n>         - integration with \"git commit\"\"\n\nI was planning to merge the series to 'next', but perhaps we should\nwait at least for the first two items (I do not think the third one\nis necessary to block the series).\n\n>>> +By default, a 'token=value' or 'token:value' argument will be added\n>>> +only if\n>>\n>> Why support both '=' and ':'?  Using just one would make it easier to\n>> grep through scripts to see who is adding signoffs.\n>\n> That was already discussed previously.\n\nI do recall it was discussed previously, but given that a new reader\nposed the same question, I am not sure if the end result in this\npatch under discussion sufficiently answers the question in a\nsatisfactory way.\n\nThanks.\n"},{"id":"238385","messageId":"xmqqk3b4ogwu.fsf@gitster.dls.corp.google.com","threadId":"36334","inReplyTo":"xmqqob0gohc2.fsf@gitster.dls.corp.google.com","subject":"Re: [PATCH v9 11/11] Documentation: add documentation for 'git interpret-trailers'","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2014-04-04T22:42:41Z","receivedAt":"2014-04-04T22:42:41Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Junio C Hamano <gitster@pobox.com> writes:\n\n> Christian Couder <christian.couder@gmail.com> writes:\n> ...\n>>> Why support both '=' and ':'?  Using just one would make it easier to\n>>> grep through scripts to see who is adding signoffs.\n>>\n>> That was already discussed previously.\n>\n> I do recall it was discussed previously, but given that a new reader\n> posed the same question, I am not sure if the end result in this\n> patch under discussion sufficiently answers the question in a\n> satisfactory way.\n>\n> Thanks.\n\nSorry, I sent out only a half-response before finishing.\n\n>> The reason is that people are used to \"token=value\" for command line\n>> arguments, but trailers appears in the result as \"token: value\", so it\n>> is better for the user if we support both.\n\nWhile I do understand the part before \", so\" on the second line, I\ndo not see why that leads to the conclusion at all.\n\nYes, because it is a well-established convention to separate option\nname with its parameter with '=', accepting \"--option=parameter\"\nmakes sense.  That may result in a string \"Option: parameter\" added\nto the output from the command.  I am not sure why that output has\nanything to do with how the command line should be specified.\n"},{"id":"238437","messageId":"CAP8UFD38TE=5zxvkDvLRsDTpC6zDo6EN5q_HJMQPbUBcfJVsSg@mail.gmail.com","threadId":"36334","inReplyTo":"xmqqk3b4ogwu.fsf@gitster.dls.corp.google.com","subject":"Re: [PATCH v9 11/11] Documentation: add documentation for 'git interpret-trailers'","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2014-04-07T09:39:01Z","receivedAt":"2014-04-07T09:39:01Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"On Sat, Apr 5, 2014 at 12:42 AM, Junio C Hamano <gitster@pobox.com> wrote:\n> Junio C Hamano <gitster@pobox.com> writes:\n>\n>> Christian Couder <christian.couder@gmail.com> writes:\n>> \"The following features are planned but not yet implemented:\n>>         - add more tests related to commands\n>>         - add examples in documentation\n>>         - integration with \"git commit\"\"\n>\n> I was planning to merge the series to 'next', but perhaps we should\n> wait at least for the first two items (I do not think the third one\n> is necessary to block the series).\n\nI will send soon a new version of the series with more tests and fixes.\nIt will also contains a patch that adds an empty line before the\ntrailers in the output if there is not already one.\nAfter that I plan to work on adding examples to the documentation.\n\n>>> Why support both '=' and ':'?  Using just one would make it easier to\n>>> grep through scripts to see who is adding signoffs.\n>>\n>> That was already discussed previously.\n>\n> I do recall it was discussed previously, but given that a new reader\n> posed the same question, I am not sure if the end result in this\n> patch under discussion sufficiently answers the question in a\n> satisfactory way.\n>\n>> The reason is that people are used to \"token=value\" for command line\n>> arguments, but trailers appears in the result as \"token: value\", so it\n>> is better for the user if we support both.\n>\n> While I do understand the part before \", so\" on the second line, I\n> do not see why that leads to the conclusion at all.\n>\n> Yes, because it is a well-established convention to separate option\n> name with its parameter with '=', accepting \"--option=parameter\"\n> makes sense.  That may result in a string \"Option: parameter\" added\n> to the output from the command.  I am not sure why that output has\n> anything to do with how the command line should be specified.\n\nFirst accepting both ':' and '=' means one can see the \"git\ninterpret-trailers\" as acting on trailers only. Not just on trailers\nfrom the intput message and option parameters from the command line.\nBut from trailers both from the input message and being passed as\narguments.\nIn my opinion it is good if it can be seen this way, as it may\nsimplifies the user's mental model of how the command works.\n\nAnd second there is also a practical advantage, as the user can\ncopy-paste trailers directly from other messages into the command line\nto pass them as arguments to \"git interpret-trailers\" without the need\nto replace the ':' with '='. Even if this command is not often used\ndirectly by users, it might simplify scripts using it.\n\nThird there is a technical advantage which is that the code that\nparses arguments from the command line can be the same as the code\nthat parses trailers from the input message.\n\nThanks,\nChristian.\n"},{"id":"238473","messageId":"xmqq4n25nkt0.fsf@gitster.dls.corp.google.com","threadId":"36334","inReplyTo":"CAP8UFD38TE=5zxvkDvLRsDTpC6zDo6EN5q_HJMQPbUBcfJVsSg@mail.gmail.com","subject":"Re: [PATCH v9 11/11] Documentation: add documentation for 'git interpret-trailers'","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2014-04-07T16:52:59Z","receivedAt":"2014-04-07T16:52:59Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Christian Couder <christian.couder@gmail.com> writes:\n\n> On Sat, Apr 5, 2014 at 12:42 AM, Junio C Hamano <gitster@pobox.com> wrote:\n>> Junio C Hamano <gitster@pobox.com> writes:\n>>\n>>> Christian Couder <christian.couder@gmail.com> writes:\n>>> \"The following features are planned but not yet implemented:\n>>>         - add more tests related to commands\n>>>         - add examples in documentation\n>>>         - integration with \"git commit\"\"\n>>\n>> I was planning to merge the series to 'next', but perhaps we should\n>> wait at least for the first two items (I do not think the third one\n>> is necessary to block the series).\n>\n> I will send soon a new version of the series with more tests and fixes.\n> It will also contains a patch that adds an empty line before the\n> trailers in the output if there is not already one.\n\nAh, yes, that one was mentioned in the reviews, I remember.\n\n> After that I plan to work on adding examples to the documentation.\n\nOK, thanks.\n\n> First accepting both ':' and '=' means one can see the \"git\n> interpret-trailers\" as acting on trailers only. Not just on trailers\n> from the intput message and option parameters from the command line.\n\nSorry, you lost me.  What does \"acting on trailers only\" really\nmean?  Do you mean the command should/can be run without any command\nline options, pick up the existing \"Signed-off-by:\" and friends in\nits input and emit its output, somehow taking these existing ones as\nits instruction regarding how to transform the input to its output?\n\n> And second there is also a practical advantage, as the user can\n> copy-paste trailers directly from other messages into the command line\n> to pass them as arguments to \"git interpret-trailers\" without the need\n> to replace the ':' with '='. Even if this command is not often used\n> directly by users, it might simplify scripts using it.\n>\n> Third there is a technical advantage which is that the code that\n> parses arguments from the command line can be the same as the code\n> that parses trailers from the input message.\n\nI do not see these two as valid arguments to make the command line\nmore complex to the end users---who now need to know that only this\ncommand treats its command line in a funny way, accepting a colon in\nplace of an equal sign.\n\nA different way to sell a colon, e.g.\n\n    Consider the instruction sed takes on its command line.\n    (e.g. \"sed 's/frotz/nitfol/' <xyzzy\").  In the most general\n    form, you would always give it as the value of an '-e' option\n    (e.g. \"sed -e 's/frotz/nitfol' <xyzzy\"), but you are allowed to\n    be loose in limited occassions.  \"Key:value\" is like that, and\n    in the most general form, it actually needs to be spelled as\n    \"-e 'Key:value'\".\n\nis possible, but I do not think it is a particularly good analogy,\nbecause what you have as the alternative is \"Key=value\", and not\n\"-e 'Key:value'\", or \"--Key=value\" (the last would probably be the \nmost natural way to express this).\n"},{"id":"238497","messageId":"20140407.205800.839733420387302333.chriscool@tuxfamily.org","threadId":"36334","inReplyTo":"xmqq4n25nkt0.fsf@gitster.dls.corp.google.com","subject":"Re: [PATCH v9 11/11] Documentation: add documentation for 'git interpret-trailers'","fromName":"Christian Couder","fromEmail":"chriscool@tuxfamily.org","sentAt":"2014-04-07T18:58:00Z","receivedAt":"2014-04-07T18:58:00Z","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> Christian Couder <christian.couder@gmail.com> writes:\n> \n>> First accepting both ':' and '=' means one can see the \"git\n>> interpret-trailers\" as acting on trailers only. Not just on trailers\n>> from the intput message and option parameters from the command line.\n> \n> Sorry, you lost me.  What does \"acting on trailers only\" really\n> mean?\n\nIt means that the command can seen as processing only trailers, (from\nstdin and from its arguments), sorry if I used the wrong verb.\n\n> Do you mean the command should/can be run without any command\n> line options, pick up the existing \"Signed-off-by:\" and friends in\n> its input and emit its output, somehow taking these existing ones as\n> its instruction regarding how to transform the input to its output?\n>\n>> And second there is also a practical advantage, as the user can\n>> copy-paste trailers directly from other messages into the command line\n>> to pass them as arguments to \"git interpret-trailers\" without the need\n>> to replace the ':' with '='. Even if this command is not often used\n>> directly by users, it might simplify scripts using it.\n>>\n>> Third there is a technical advantage which is that the code that\n>> parses arguments from the command line can be the same as the code\n>> that parses trailers from the input message.\n> \n> I do not see these two as valid arguments to make the command line\n> more complex to the end users\n\nI don't think that it makes the command more complex to the end users.\n\n> ---who now need to know that only this\n> command treats its command line in a funny way, accepting a colon in\n> place of an equal sign.\n\nIt accepts both. So if they think that it is like a regular command,\nwhich uses '=' for (key, value) pairs, it will work, and if they think\nit works on trailers, which use ':' for (key, value) pairs, it will\nalso work.\n\n> A different way to sell a colon, e.g.\n> \n>     Consider the instruction sed takes on its command line.\n>     (e.g. \"sed 's/frotz/nitfol/' <xyzzy\").  In the most general\n>     form, you would always give it as the value of an '-e' option\n>     (e.g. \"sed -e 's/frotz/nitfol' <xyzzy\"), but you are allowed to\n>     be loose in limited occassions.  \"Key:value\" is like that, and\n>     in the most general form, it actually needs to be spelled as\n>     \"-e 'Key:value'\".\n> \n> is possible, but I do not think it is a particularly good analogy,\n> because what you have as the alternative is \"Key=value\", and not\n> \"-e 'Key:value'\", or \"--Key=value\" (the last would probably be the \n> most natural way to express this).\n\nThe analogy that I would use is rather that Perl lets people use\n's:foo:bar:' as well as 's=foo=bar=' instead of 's/foo/bar/' if they\nprefer.\n"},{"id":"238501","messageId":"xmqq4n25lyqr.fsf@gitster.dls.corp.google.com","threadId":"36334","inReplyTo":"20140407.205800.839733420387302333.chriscool@tuxfamily.org","subject":"Re: [PATCH v9 11/11] Documentation: add documentation for 'git interpret-trailers'","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2014-04-07T19:34:52Z","receivedAt":"2014-04-07T19:34:52Z","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 do not see these two as valid arguments to make the command line\n>> more complex to the end users\n>\n> I don't think that it makes the command more complex to the end users.\n>\n>> ---who now need to know that only this\n>> command treats its command line in a funny way, accepting a colon in\n>> place of an equal sign.\n\nI meant that it makes learning the \"command line syntax of Git\" more\ncomplex to new users.  If one is too narrowly focused on this single\ncommand, it may not seem that \"the command\" is not made more\ncomplex, but I am more interested in making sure that the entire Git\ncommand line experience does not get unnecessarily harder to learn.\n"},{"id":"238516","messageId":"xmqqr458ls0k.fsf@gitster.dls.corp.google.com","threadId":"36334","inReplyTo":"20140407.205800.839733420387302333.chriscool@tuxfamily.org","subject":"Re: [PATCH v9 11/11] Documentation: add documentation for 'git interpret-trailers'","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2014-04-07T22:00:11Z","receivedAt":"2014-04-07T22:00:11Z","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> From: Junio C Hamano <gitster@pobox.com>\n>>\n>> A different way to sell a colon, e.g.\n>> \n>>     Consider the instruction sed takes on its command line.\n>>     (e.g. \"sed 's/frotz/nitfol/' <xyzzy\").  In the most general\n>>     form, you would always give it as the value of an '-e' option\n>>     (e.g. \"sed -e 's/frotz/nitfol' <xyzzy\"), but you are allowed to\n>>     be loose in limited occassions.  \"Key:value\" is like that, and\n>>     in the most general form, it actually needs to be spelled as\n>>     \"-e 'Key:value'\".\n>> \n>> is possible, but I do not think it is a particularly good analogy,\n>> because what you have as the alternative is \"Key=value\", and not\n>> \"-e 'Key:value'\", or \"--Key=value\" (the last would probably be the \n>> most natural way to express this).\n>\n> The analogy that I would use is rather that Perl lets people use\n> 's:foo:bar:' as well as 's=foo=bar=' instead of 's/foo/bar/' if they\n> prefer.\n\nI could *almost* buy that, but that does not hold as you are not\nallowing (and I do not think you need to in this case) the user to\npick any termination character like \"s|foo|bar|\".  The only thing\nyou are doing is forbidding both \":\" and \"=\" from the set of allowed\ncharacters for labels.\n\nHowever.\n\nI think we could buy the syntax if the \"Key:value\" form were the\n*only* form, *without* accepting \"Key=value\".\n\nThe latter is a poor attempt to pretend as if it is a normal command\nline option, but because that form does not even take double-dashes\nat the beginning, it even fails to mimic as a command line option.\n\nIt would be one way to reduce the unnecessary cognitive load from\nthe users when learning the Git command line argument convention to\nreject the \"key=value\" form and only stick to \"key: value\" form.\nAfter all, because of the shape of the footer we add to the log\nmessage (i.e. a keyword label followed by a colon followed by a SP\nfollowed by the value), it is clear that we can use \":\" as the\nseparator without inconveniencing the users who want to use some\nunusual characters in the label part, but there is no strong reason\nto reject an equal sign in the label.\n"}]}