{"thread":{"id":"35569","subject":"[PATCH 0/9] Add interpret-trailers builtin","startedAt":"2013-12-24T06:37:16Z","lastAt":"2013-12-30T22:27:19Z","messageCount":17,"participants":["Christian Couder","Junio C Hamano","Josh Triplett"],"isPatch":true,"patchVersion":1,"patchTotal":9},"messages":[{"id":"232366","messageId":"20131224061541.19560.17773.chriscool@tuxfamily.org","threadId":"35569","inReplyTo":null,"subject":"[PATCH 0/9] Add interpret-trailers builtin","fromName":"Christian Couder","fromEmail":"chriscool@tuxfamily.org","sentAt":"2013-12-24T06:37:16Z","receivedAt":"2013-12-24T06:37:16Z","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        - 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        - 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\t- there are a lot of tests\n\nThe following features are planned but not yet implemented:\n\t- making \"git interpret-trailers\" a filter\n        - some documentation\n        - the \"trailer.<token>.command\" config option\n\n3) Notes:\n\n* I chose \"trailer.<token>.key\" over \"trailer.<token>.value\" and\n\"trailer.<token>.trailer\", as I think it is clearer.\n\n* I focused on a cleaner and more complete implementation than the\nPATCH/RFC I sent previously.\n\nHappy end of year!\n\nChristian Couder (9):\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\n .gitignore                    |   1 +\n Makefile                      |   2 +\n builtin.h                     |   1 +\n builtin/interpret-trailers.c  |  36 +++\n git.c                         |   1 +\n strbuf.c                      |   7 +\n strbuf.h                      |   1 +\n t/t7513-interpret-trailers.sh | 206 ++++++++++++++++\n trailer.c                     | 544 ++++++++++++++++++++++++++++++++++++++++++\n trailer.h                     |   6 +\n 10 files changed, 805 insertions(+)\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.4.1.616.g07f5c81\n"},{"id":"232371","messageId":"20131224063726.19560.38180.chriscool@tuxfamily.org","threadId":"35569","inReplyTo":"20131224061541.19560.17773.chriscool@tuxfamily.org","subject":"[PATCH 1/9] Add data structures and basic functions for commit trailers","fromName":"Christian Couder","fromEmail":"chriscool@tuxfamily.org","sentAt":"2013-12-24T06:37:17Z","receivedAt":"2013-12-24T06:37:17Z","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..ccbcfb0\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 { AFTER, 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.4.1.616.g07f5c81\n"},{"id":"232374","messageId":"20131224063726.19560.26042.chriscool@tuxfamily.org","threadId":"35569","inReplyTo":"20131224061541.19560.17773.chriscool@tuxfamily.org","subject":"[PATCH 2/9] trailer: process trailers from file and arguments","fromName":"Christian Couder","fromEmail":"chriscool@tuxfamily.org","sentAt":"2013-12-24T06:37:18Z","receivedAt":"2013-12-24T06:37:18Z","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 ccbcfb0..bd6595d 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 == 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 == 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 == 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 == 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, 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, 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.4.1.616.g07f5c81\n"},{"id":"232372","messageId":"20131224063726.19560.89108.chriscool@tuxfamily.org","threadId":"35569","inReplyTo":"20131224061541.19560.17773.chriscool@tuxfamily.org","subject":"[PATCH 3/9] trailer: read and process config information","fromName":"Christian Couder","fromEmail":"chriscool@tuxfamily.org","sentAt":"2013-12-24T06:37:19Z","receivedAt":"2013-12-24T06:37:19Z","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 bd6595d..6aba40e 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 = AFTER;\n+\t} else if (!strcasecmp(\"before\", value)) {\n+\t\titem->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(_(\"unknow 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(_(\"unknow 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(_(\"unknow 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.4.1.616.g07f5c81\n"},{"id":"232368","messageId":"20131224063726.19560.28979.chriscool@tuxfamily.org","threadId":"35569","inReplyTo":"20131224061541.19560.17773.chriscool@tuxfamily.org","subject":"[PATCH 4/9] trailer: process command line trailer arguments","fromName":"Christian Couder","fromEmail":"chriscool@tuxfamily.org","sentAt":"2013-12-24T06:37:20Z","receivedAt":"2013-12-24T06:37:20Z","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 6aba40e..b572c44 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+\n+\tparse_trailer(&tok, &val, string);\n+\n+\tint tok_alnum_len = alnum_len(tok.buf, tok.len);\n+\n+\t/* Lookup if the token matches something in the config */\n+\tstruct trailer_item *item;\n+\tfor (item = first_conf_item; item; item = item->next)\n+\t{\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.4.1.616.g07f5c81\n"},{"id":"232375","messageId":"20131224063726.19560.38673.chriscool@tuxfamily.org","threadId":"35569","inReplyTo":"20131224061541.19560.17773.chriscool@tuxfamily.org","subject":"[PATCH 5/9] strbuf: add strbuf_isspace()","fromName":"Christian Couder","fromEmail":"chriscool@tuxfamily.org","sentAt":"2013-12-24T06:37:21Z","receivedAt":"2013-12-24T06:37:21Z","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.4.1.616.g07f5c81\n"},{"id":"232373","messageId":"20131224063726.19560.28407.chriscool@tuxfamily.org","threadId":"35569","inReplyTo":"20131224061541.19560.17773.chriscool@tuxfamily.org","subject":"[PATCH 6/9] trailer: parse trailers from input file","fromName":"Christian Couder","fromEmail":"chriscool@tuxfamily.org","sentAt":"2013-12-24T06:37:22Z","receivedAt":"2013-12-24T06:37:22Z","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 b572c44..22d3ea3 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.4.1.616.g07f5c81\n"},{"id":"232370","messageId":"20131224063726.19560.3793.chriscool@tuxfamily.org","threadId":"35569","inReplyTo":"20131224061541.19560.17773.chriscool@tuxfamily.org","subject":"[PATCH 7/9] trailer: put all the processing together and print","fromName":"Christian Couder","fromEmail":"chriscool@tuxfamily.org","sentAt":"2013-12-24T06:37:23Z","receivedAt":"2013-12-24T06:37:23Z","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 22d3ea3..33aa907 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.4.1.616.g07f5c81\n"},{"id":"232369","messageId":"20131224063726.19560.9968.chriscool@tuxfamily.org","threadId":"35569","inReplyTo":"20131224061541.19560.17773.chriscool@tuxfamily.org","subject":"[PATCH 8/9] trailer: add interpret-trailers command","fromName":"Christian Couder","fromEmail":"chriscool@tuxfamily.org","sentAt":"2013-12-24T06:37:24Z","receivedAt":"2013-12-24T06:37:24Z","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.4.1.616.g07f5c81\n"},{"id":"232367","messageId":"20131224063726.19560.61859.chriscool@tuxfamily.org","threadId":"35569","inReplyTo":"20131224061541.19560.17773.chriscool@tuxfamily.org","subject":"[PATCH 9/9] trailer: add tests for \"git interpret-trailers\"","fromName":"Christian Couder","fromEmail":"chriscool@tuxfamily.org","sentAt":"2013-12-24T06:37:25Z","receivedAt":"2013-12-24T06:37:25Z","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 | 206 ++++++++++++++++++++++++++++++++++++++++++\n 1 file changed, 206 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..beb58fe\n--- /dev/null\n+++ b/t/t7513-interpret-trailers.sh\n@@ -0,0 +1,206 @@\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+# Do not remove trailing spaces below!\n+cat >complex_message_trailers <<'EOF'\n+Fixes: \n+Acked-by: \n+Reviewed-by: \n+Signed-off-by: \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.4.1.616.g07f5c81\n"},{"id":"232377","messageId":"CAP8UFD105z8YrgWPAzsaj7=Zx9x+DqfXBK1FUAmcFYY2rdzvBg@mail.gmail.com","threadId":"35569","inReplyTo":"20131224063726.19560.89108.chriscool@tuxfamily.org","subject":"Re: [PATCH 3/9] trailer: read and process config information","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2013-12-24T13:47:41Z","receivedAt":"2013-12-24T13:47:41Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"On Tue, Dec 24, 2013 at 7:37 AM, Christian Couder\n<chriscool@tuxfamily.org> wrote:\n>\n> +static int git_trailer_config(const char *conf_key, const char *value, void *cb)\n> +{\n> +       if (starts_with(conf_key, \"trailer.\")) {\n> +               const char *orig_conf_key = conf_key;\n> +               struct trailer_item *item;\n> +               struct conf_info *conf;\n> +               char *name;\n> +               enum trailer_info_type type;\n> +\n> +               conf_key += 8;\n> +               if (!set_name_and_type(conf_key, \".key\", TRAILER_VALUE, &name, &type) &&\n> +                   !set_name_and_type(conf_key, \".command\", TRAILER_COMMAND, &name, &type) &&\n> +                   !set_name_and_type(conf_key, \".where\", TRAILER_WHERE, &name, &type) &&\n> +                   !set_name_and_type(conf_key, \".ifexist\", TRAILER_IF_EXIST, &name, &type) &&\n> +                   !set_name_and_type(conf_key, \".ifmissing\", TRAILER_IF_MISSING, &name, &type))\n> +                       return 0;\n> +\n> +               item = get_conf_item(name);\n> +               conf = item->conf;\n> +\n> +               if (type == TRAILER_VALUE) {\n> +                       if (conf->key)\n> +                               warning(_(\"more than one %s\"), orig_conf_key);\n> +                       conf->key = xstrdup(value);\n> +               } else if (type == TRAILER_COMMAND) {\n> +                       if (conf->command)\n> +                               warning(_(\"more than one %s\"), orig_conf_key);\n> +                       conf->command = xstrdup(value);\n> +               } else if (type == TRAILER_WHERE) {\n> +                       if (set_where(conf, value))\n> +                               warning(_(\"unknow value '%s' for key '%s'\"), value, orig_conf_key);\n\nI realize that I forgot to s/unknow/unknown/.\nSorry about that. It will be in the next version.\n\n> +               } else if (type == TRAILER_IF_EXIST) {\n> +                       if (set_if_exist(conf, value))\n> +                               warning(_(\"unknow value '%s' for key '%s'\"), value, orig_conf_key);\n> +               } else if (type == TRAILER_IF_MISSING) {\n> +                       if (set_if_missing(conf, value))\n> +                               warning(_(\"unknow value '%s' for key '%s'\"), value, orig_conf_key);\n> +               } else {\n> +                       die(\"internal bug in trailer.c\");\n> +               }\n> +       }\n> +       return 0;\n> +}\n"},{"id":"232514","messageId":"xmqq1u0utfwk.fsf@gitster.dls.corp.google.com","threadId":"35569","inReplyTo":"20131224063726.19560.61859.chriscool@tuxfamily.org","subject":"Re: [PATCH 9/9] trailer: add tests for \"git interpret-trailers\"","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2013-12-30T17:19:55Z","receivedAt":"2013-12-30T17:19:55Z","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> +# Do not remove trailing spaces below!\n> +cat >complex_message_trailers <<'EOF'\n> +Fixes: \n> +Acked-by: \n> +Reviewed-by: \n> +Signed-off-by: \n> +EOF\n\nJust a hint.  I think it is far safer and robust over time to do\nsomething like this:\n\n\tsed -e 's/ Z$/ /' <<-\\EOF\n        Fixes: Z\n        Acked-by: Z\n        EOF\n\ninstead of a comment, which can warn human developers but does not\ndo anything to prevent their editors' auto-fix features from kicking\nin.\n"},{"id":"232525","messageId":"20131230202042.GJ27213@leaf","threadId":"35569","inReplyTo":"xmqq1u0utfwk.fsf@gitster.dls.corp.google.com","subject":"Re: [PATCH 9/9] trailer: add tests for \"git interpret-trailers\"","fromName":"Josh Triplett","fromEmail":"josh@joshtriplett.org","sentAt":"2013-12-30T20:20:42Z","receivedAt":"2013-12-30T20:20:42Z","isPatch":true,"sender":{"key":"josh@joshtriplett.org","avatar":"https://avatars.githubusercontent.com/u/162737?v=4"},"body":"On Mon, Dec 30, 2013 at 09:19:55AM -0800, Junio C Hamano wrote:\n> Christian Couder <chriscool@tuxfamily.org> writes:\n> \n> > +# Do not remove trailing spaces below!\n> > +cat >complex_message_trailers <<'EOF'\n> > +Fixes: \n> > +Acked-by: \n> > +Reviewed-by: \n> > +Signed-off-by: \n> > +EOF\n> \n> Just a hint.  I think it is far safer and robust over time to do\n> something like this:\n> \n> \tsed -e 's/ Z$/ /' <<-\\EOF\n>         Fixes: Z\n>         Acked-by: Z\n>         EOF\n> \n> instead of a comment, which can warn human developers but does not\n> do anything to prevent their editors' auto-fix features from kicking\n> in.\n\nThis, but for simplicity, since every line needs the trailing space, why\nnot just use 's/$/ /' and drop the ' Z' on every line?\n\n</bikeshed>\n\n- Josh Triplett\n"},{"id":"232528","messageId":"xmqqk3emqd7a.fsf@gitster.dls.corp.google.com","threadId":"35569","inReplyTo":"20131230202042.GJ27213@leaf","subject":"Re: [PATCH 9/9] trailer: add tests for \"git interpret-trailers\"","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2013-12-30T20:46:33Z","receivedAt":"2013-12-30T20:46:33Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Josh Triplett <josh@joshtriplett.org> writes:\n\n> On Mon, Dec 30, 2013 at 09:19:55AM -0800, Junio C Hamano wrote:\n>> Christian Couder <chriscool@tuxfamily.org> writes:\n>> \n>> > +# Do not remove trailing spaces below!\n>> > +cat >complex_message_trailers <<'EOF'\n>> > +Fixes: \n>> > +Acked-by: \n>> > +Reviewed-by: \n>> > +Signed-off-by: \n>> > +EOF\n>> \n>> Just a hint.  I think it is far safer and robust over time to do\n>> something like this:\n>> \n>> \tsed -e 's/ Z$/ /' <<-\\EOF\n>>         Fixes: Z\n>>         Acked-by: Z\n>>         EOF\n>> \n>> instead of a comment, which can warn human developers but does not\n>> do anything to prevent their editors' auto-fix features from kicking\n>> in.\n>\n> This, but for simplicity, since every line needs the trailing space, why\n> not just use 's/$/ /' and drop the ' Z' on every line?\n>\n> </bikeshed>\n>\n> - Josh Triplett\n\nA few reasons:\n\n - The \"everybody will have a single SP at the end\" may or may not\n   last forever;\n\n - With your scheme, if you already had _one_ trailing SPs in the\n   input, it would be hard to spot in the source;\n\n - It makes it visually very clear that we expect a SP after these\n   colons.  This is especially true if you replace 'Z' with\n   something more readable (e.g. '|').\n"},{"id":"232529","messageId":"20131230205234.GU27213@leaf","threadId":"35569","inReplyTo":"xmqqk3emqd7a.fsf@gitster.dls.corp.google.com","subject":"Re: [PATCH 9/9] trailer: add tests for \"git interpret-trailers\"","fromName":"Josh Triplett","fromEmail":"josh@joshtriplett.org","sentAt":"2013-12-30T20:52:34Z","receivedAt":"2013-12-30T20:52:34Z","isPatch":true,"sender":{"key":"josh@joshtriplett.org","avatar":"https://avatars.githubusercontent.com/u/162737?v=4"},"body":"On Mon, Dec 30, 2013 at 12:46:33PM -0800, Junio C Hamano wrote:\n> Josh Triplett <josh@joshtriplett.org> writes:\n> \n> > On Mon, Dec 30, 2013 at 09:19:55AM -0800, Junio C Hamano wrote:\n> >> Christian Couder <chriscool@tuxfamily.org> writes:\n> >> \n> >> > +# Do not remove trailing spaces below!\n> >> > +cat >complex_message_trailers <<'EOF'\n> >> > +Fixes: \n> >> > +Acked-by: \n> >> > +Reviewed-by: \n> >> > +Signed-off-by: \n> >> > +EOF\n> >> \n> >> Just a hint.  I think it is far safer and robust over time to do\n> >> something like this:\n> >> \n> >> \tsed -e 's/ Z$/ /' <<-\\EOF\n> >>         Fixes: Z\n> >>         Acked-by: Z\n> >>         EOF\n> >> \n> >> instead of a comment, which can warn human developers but does not\n> >> do anything to prevent their editors' auto-fix features from kicking\n> >> in.\n> >\n> > This, but for simplicity, since every line needs the trailing space, why\n> > not just use 's/$/ /' and drop the ' Z' on every line?\n> >\n> > </bikeshed>\n> >\n> > - Josh Triplett\n> \n> A few reasons:\n> \n>  - The \"everybody will have a single SP at the end\" may or may not\n>    last forever;\n\nTrivially fixed if that ever changes, but given the nature of all of\nthese, that seems unlikely.\n\n>  - With your scheme, if you already had _one_ trailing SPs in the\n>    input, it would be hard to spot in the source;\n\nGit makes them quite difficult to miss. :)\n\n- Josh Triplett\n"},{"id":"232530","messageId":"xmqqfvpaqcbu.fsf@gitster.dls.corp.google.com","threadId":"35569","inReplyTo":"20131230205234.GU27213@leaf","subject":"Re: [PATCH 9/9] trailer: add tests for \"git interpret-trailers\"","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2013-12-30T21:05:25Z","receivedAt":"2013-12-30T21:05:25Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Josh Triplett <josh@joshtriplett.org> writes:\n\n>>  - The \"everybody will have a single SP at the end\" may or may not\n>>    last forever;\n>\n> Trivially fixed if that ever changes, but given the nature of all of\n> these, that seems unlikely.\n\nWhy?  Because we encourage to write tests that are expected to find\nbreakages, some of these test vector lines may have to show a broken\nline that lacks SP after label + colon.\n\n>>  - With your scheme, if you already had _one_ trailing SPs in the\n>>    input, it would be hard to spot in the source;\n>\n> Git makes them quite difficult to miss. :)\n\nThat is irrelevant, isn't it?\n\nThis is about protecting the source in the editor, before you run\n\"git show --whitespace=trailing-space\", \"git diff --check\", etc.\n"},{"id":"232533","messageId":"20131230222719.GN6840@leaf","threadId":"35569","inReplyTo":"xmqqfvpaqcbu.fsf@gitster.dls.corp.google.com","subject":"Re: [PATCH 9/9] trailer: add tests for \"git interpret-trailers\"","fromName":"Josh Triplett","fromEmail":"josh@joshtriplett.org","sentAt":"2013-12-30T22:27:19Z","receivedAt":"2013-12-30T22:27:19Z","isPatch":true,"sender":{"key":"josh@joshtriplett.org","avatar":"https://avatars.githubusercontent.com/u/162737?v=4"},"body":"On Mon, Dec 30, 2013 at 01:05:25PM -0800, Junio C Hamano wrote:\n> Josh Triplett <josh@joshtriplett.org> writes:\n> >>  - With your scheme, if you already had _one_ trailing SPs in the\n> >>    input, it would be hard to spot in the source;\n> >\n> > Git makes them quite difficult to miss. :)\n> \n> That is irrelevant, isn't it?\n> \n> This is about protecting the source in the editor, before you run\n> \"git show --whitespace=trailing-space\", \"git diff --check\", etc.\n\nThat was exactly my point: such lines shouldn't exist, and rather than\nincluding the trailing space and following it with a character that then\nneeds removing, it seems more sensible to me to omit the trailing space\nand insert it via an almost identical sed line.  Git already helps\nensure that trailing space won't exist on *any* line, including those; I\ndon't see how an extra character after the space (making it no longer\ntrailing space) makes it any more or less likely that those lines would\nhave trailing space.\n\nIn any case, I don't care enough to argue the point further; it was just\na style suggestion.\n\n- Josh Triplett\n"}]}