{"thread":{"id":"44285","subject":"[PATCH 1/5] trailer: use singly-linked list, not doubly","startedAt":"2016-10-12T01:28:52Z","lastAt":"2016-10-22T00:06:09Z","messageCount":48,"participants":["Jonathan Tan","Junio C Hamano","Christian Couder","Jeff King","Jakub Narębski","Stefan Beller"],"isPatch":true,"patchVersion":1,"patchTotal":5},"messages":[{"id":"303953","messageId":"8e12e0954f0a23d7c7905c58a3f7d8084d9338be.1476232683.git.jonathantanmy@google.com","threadId":"44285","inReplyTo":"cover.1476232683.git.jonathantanmy@google.com","subject":"[PATCH 1/5] trailer: use singly-linked list, not doubly","fromName":"Jonathan Tan","fromEmail":"jonathantanmy@google.com","sentAt":"2016-10-12T01:23:25Z","receivedAt":"2016-10-12T01:28:52Z","isPatch":true,"sender":{"key":"jonathantanmy@fastmail.com","avatar":null},"body":"Use singly-linked lists (instead of doubly-linked lists) in trailer to\nkeep track of arguments (whether implicit from configuration or explicit\nfrom the command line) and trailer items.\n\nThis change significantly reduces the code length and simplifies the code.\nThere are now fewer pointers to be manipulated, but most trailer\nmanipulations now require seeking from beginning to end, so there might\nbe a slight net decrease in performance; however the number of trailers\nis usually small (10 to 15 at the most) so this should not cause a big\nimpact.\n---\n trailer.c | 357 ++++++++++++++++++++++----------------------------------------\n 1 file changed, 125 insertions(+), 232 deletions(-)\n\ndiff --git a/trailer.c b/trailer.c\nindex c6ea9ac..bf3d7d0 100644\n--- a/trailer.c\n+++ b/trailer.c\n@@ -25,7 +25,6 @@ struct conf_info {\n static struct conf_info default_conf_info;\n \n struct trailer_item {\n-\tstruct trailer_item *previous;\n \tstruct trailer_item *next;\n \tconst char *token;\n \tconst char *value;\n@@ -56,7 +55,8 @@ static size_t token_len_without_separator(const char *token, size_t len)\n \treturn len;\n }\n \n-static int same_token(struct trailer_item *a, struct trailer_item *b)\n+static int same_token(const struct trailer_item *a,\n+\t\t      const struct trailer_item *b)\n {\n \tsize_t a_len = token_len_without_separator(a->token, strlen(a->token));\n \tsize_t b_len = token_len_without_separator(b->token, strlen(b->token));\n@@ -65,12 +65,14 @@ static int same_token(struct trailer_item *a, struct trailer_item *b)\n \treturn !strncasecmp(a->token, b->token, min_len);\n }\n \n-static int same_value(struct trailer_item *a, struct trailer_item *b)\n+static int same_value(const struct trailer_item *a,\n+\t\t      const 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)\n+static int same_trailer(const struct trailer_item *a,\n+\t\t\tconst struct trailer_item *b)\n {\n \treturn same_token(a, b) && same_value(a, b);\n }\n@@ -129,92 +131,6 @@ static void print_all(FILE *outfile, struct trailer_item *first, int trim_empty)\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 add_arg_to_input_list(struct trailer_item *on_tok,\n-\t\t\t\t  struct trailer_item *arg_tok,\n-\t\t\t\t  struct trailer_item **first,\n-\t\t\t\t  struct trailer_item **last)\n-{\n-\tif (after_or_end(arg_tok->conf.where)) {\n-\t\targ_tok->next = on_tok->next;\n-\t\ton_tok->next = arg_tok;\n-\t\targ_tok->previous = on_tok;\n-\t\tif (arg_tok->next)\n-\t\t\targ_tok->next->previous = arg_tok;\n-\t\tupdate_last(last);\n-\t} else {\n-\t\targ_tok->previous = on_tok->previous;\n-\t\ton_tok->previous = arg_tok;\n-\t\targ_tok->next = on_tok;\n-\t\tif (arg_tok->previous)\n-\t\t\targ_tok->previous->next = arg_tok;\n-\t\tupdate_first(first);\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 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))\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 = after_or_end(where) ? in_tok->previous : in_tok->next;\n-\t} while (check_all);\n-\treturn 1;\n-}\n-\n-static void remove_from_list(struct trailer_item *item,\n-\t\t\t     struct trailer_item **first,\n-\t\t\t     struct trailer_item **last)\n-{\n-\tstruct trailer_item *next = item->next;\n-\tstruct trailer_item *previous = item->previous;\n-\n-\tif (next) {\n-\t\titem->next->previous = previous;\n-\t\titem->next = NULL;\n-\t} else if (last)\n-\t\t*last = previous;\n-\n-\tif (previous) {\n-\t\titem->previous->next = next;\n-\t\titem->previous = NULL;\n-\t} else if (first)\n-\t\t*first = 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 const char *apply_command(const char *command, const char *arg)\n {\n \tstruct strbuf cmd = STRBUF_INIT;\n@@ -263,122 +179,116 @@ static void apply_item_command(struct trailer_item *in_tok, struct trailer_item\n \t}\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\tstruct trailer_item *on_tok,\n-\t\t\t\tstruct trailer_item **in_tok_first,\n-\t\t\t\tstruct trailer_item **in_tok_last)\n+static void apply_existing_arg(struct trailer_item **found_next,\n+\t\t\t       struct trailer_item *arg_tok,\n+\t\t\t       struct trailer_item **position_to_add,\n+\t\t\t       const struct trailer_item *in_tok_head,\n+\t\t\t       const struct trailer_item *neighbor)\n {\n-\tswitch (arg_tok->conf.if_exists) {\n-\tcase EXISTS_DO_NOTHING:\n+\tif (arg_tok->conf.if_exists == EXISTS_DO_NOTHING) {\n \t\tfree_trailer_item(arg_tok);\n-\t\tbreak;\n-\tcase EXISTS_REPLACE:\n-\t\tapply_item_command(in_tok, arg_tok);\n-\t\tadd_arg_to_input_list(on_tok, arg_tok,\n-\t\t\t\t      in_tok_first, in_tok_last);\n-\t\tremove_from_list(in_tok, in_tok_first, in_tok_last);\n-\t\tfree_trailer_item(in_tok);\n-\t\tbreak;\n-\tcase EXISTS_ADD:\n-\t\tapply_item_command(in_tok, arg_tok);\n-\t\tadd_arg_to_input_list(on_tok, arg_tok,\n-\t\t\t\t      in_tok_first, in_tok_last);\n-\t\tbreak;\n-\tcase EXISTS_ADD_IF_DIFFERENT:\n-\t\tapply_item_command(in_tok, arg_tok);\n-\t\tif (check_if_different(in_tok, arg_tok, 1))\n-\t\t\tadd_arg_to_input_list(on_tok, arg_tok,\n-\t\t\t\t\t      in_tok_first, in_tok_last);\n-\t\telse\n-\t\t\tfree_trailer_item(arg_tok);\n-\t\tbreak;\n-\tcase EXISTS_ADD_IF_DIFFERENT_NEIGHBOR:\n-\t\tapply_item_command(in_tok, arg_tok);\n-\t\tif (check_if_different(on_tok, arg_tok, 0))\n-\t\t\tadd_arg_to_input_list(on_tok, arg_tok,\n-\t\t\t\t\t      in_tok_first, in_tok_last);\n-\t\telse\n+\t\treturn;\n+\t}\n+\n+\tapply_item_command(*found_next, arg_tok);\n+\n+\tif (arg_tok->conf.if_exists == EXISTS_ADD_IF_DIFFERENT_NEIGHBOR) {\n+\t\tif (neighbor && same_trailer(neighbor, arg_tok)) {\n \t\t\tfree_trailer_item(arg_tok);\n-\t\tbreak;\n+\t\t\treturn;\n+\t\t}\n+\t} else if (arg_tok->conf.if_exists == EXISTS_ADD_IF_DIFFERENT) {\n+\t\tconst struct trailer_item *in_tok;\n+\t\tfor (in_tok = in_tok_head; in_tok; in_tok = in_tok->next) {\n+\t\t\tif (same_trailer(in_tok, arg_tok)) {\n+\t\t\t\tfree_trailer_item(arg_tok);\n+\t\t\t\treturn;\n+\t\t\t}\n+\t\t}\n+\t}\n+\n+\targ_tok->next = *position_to_add;\n+\t*position_to_add = arg_tok;\n+\n+\tif (arg_tok->conf.if_exists == EXISTS_REPLACE) {\n+\t\tstruct trailer_item *new_next = (*found_next)->next;\n+\t\tfree_trailer_item(*found_next);\n+\t\t*found_next = new_next;\n \t}\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+static void apply_missing_arg(struct trailer_item *arg_tok,\n+\t\t\t      struct trailer_item **position_to_add)\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+\tif (arg_tok->conf.if_missing == 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 = after_or_end(where) ? in_tok_last : in_tok_first;\n-\t\tapply_item_command(NULL, arg_tok);\n-\t\tif (*in_tok) {\n-\t\t\tadd_arg_to_input_list(*in_tok, arg_tok,\n-\t\t\t\t\t      in_tok_first, in_tok_last);\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\treturn;\n \t}\n+\n+\tapply_item_command(NULL, arg_tok);\n+\n+\targ_tok->next = *position_to_add;\n+\t*position_to_add = arg_tok;\n }\n \n-static int find_same_and_apply_arg(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+static void apply_arg(struct trailer_item **in_tok_head,\n+\t\t      struct trailer_item *arg_tok)\n {\n-\tstruct trailer_item *in_tok;\n-\tstruct trailer_item *on_tok;\n-\tstruct trailer_item *following_tok;\n-\n+\tstruct trailer_item **next, **found_next = NULL, *last = NULL;\n \tenum action_where where = arg_tok->conf.where;\n-\tint middle = (where == WHERE_AFTER) || (where == WHERE_BEFORE);\n \tint backwards = after_or_end(where);\n-\tstruct trailer_item *start_tok = backwards ? *in_tok_last : *in_tok_first;\n \n-\tfor (in_tok = start_tok; in_tok; in_tok = following_tok) {\n-\t\tfollowing_tok = backwards ? in_tok->previous : in_tok->next;\n-\t\tif (!same_token(in_tok, arg_tok))\n-\t\t\tcontinue;\n-\t\ton_tok = middle ? in_tok : start_tok;\n-\t\tapply_arg_if_exists(in_tok, arg_tok, on_tok,\n-\t\t\t\t    in_tok_first, in_tok_last);\n-\t\treturn 1;\n+\tfor (next = in_tok_head; *next; next = &(*next)->next) {\n+\t\tlast = *next;\n+\t\tif ((!found_next || backwards) &&\n+\t\t    same_token(*next, arg_tok))\n+\t\t\tfound_next = next;\n+\t}\n+\n+\tif (found_next) {\n+\t\tstruct trailer_item **position_to_add, *neighbor;\n+\t\tswitch (where) {\n+\t\tcase WHERE_START:\n+\t\t\tposition_to_add = in_tok_head;\n+\t\t\tneighbor = *in_tok_head;\n+\t\t\tbreak;\n+\t\tcase WHERE_BEFORE:\n+\t\t\tposition_to_add = found_next;\n+\t\t\tneighbor = *found_next;\n+\t\t\tbreak;\n+\t\tcase WHERE_AFTER:\n+\t\t\tposition_to_add = &(*found_next)->next;\n+\t\t\tneighbor = *found_next;\n+\t\t\tbreak;\n+\t\tdefault:\n+\t\t\tposition_to_add = next;\n+\t\t\tneighbor = last;\n+\t\t\tbreak;\n+\t\t}\n+\t\tapply_existing_arg(found_next, arg_tok, position_to_add,\n+\t\t\t\t   *in_tok_head, neighbor);\n+\t} else {\n+\t\tstruct trailer_item **position_to_add;\n+\t\tswitch (where) {\n+\t\tcase WHERE_START:\n+\t\tcase WHERE_BEFORE:\n+\t\t\tposition_to_add = in_tok_head;\n+\t\t\tbreak;\n+\t\tdefault:\n+\t\t\tposition_to_add = next;\n+\t\t\tbreak;\n+\t\t}\n+\t\tapply_missing_arg(arg_tok, position_to_add);\n \t}\n-\treturn 0;\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+static void process_trailers_lists(struct trailer_item **in_tok_head,\n+\t\t\t\t   struct trailer_item *arg_tok_head)\n {\n-\tstruct trailer_item *arg_tok;\n-\tstruct trailer_item *next_arg;\n-\n-\tif (!*arg_tok_first)\n-\t\treturn;\n-\n-\tfor (arg_tok = *arg_tok_first; arg_tok; arg_tok = next_arg) {\n-\t\tint applied = 0;\n-\n-\t\tnext_arg = arg_tok->next;\n-\t\tremove_from_list(arg_tok, arg_tok_first, NULL);\n-\n-\t\tapplied = find_same_and_apply_arg(in_tok_first,\n-\t\t\t\t\t\t  in_tok_last,\n-\t\t\t\t\t\t  arg_tok);\n-\n-\t\tif (!applied)\n-\t\t\tapply_arg_if_missing(in_tok_first,\n-\t\t\t\t\t     in_tok_last,\n-\t\t\t\t\t     arg_tok);\n+\tstruct trailer_item *arg_tok, *next;\n+\tfor (arg_tok = arg_tok_head; arg_tok; arg_tok = next) {\n+\t\tnext = arg_tok->next;\n+\t\tapply_arg(in_tok_head, arg_tok);\n \t}\n }\n \n@@ -438,29 +348,19 @@ static void duplicate_conf(struct conf_info *dst, struct conf_info *src)\n \n static struct trailer_item *get_conf_item(const char *name)\n {\n+\tstruct trailer_item **next = &first_conf_item;\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+\tfor (next = &first_conf_item; *next; next = &(*next)->next)\n+\t\tif (!strcasecmp((*next)->conf.name, name))\n+\t\t\treturn *next;\n \n \t/* Item does not already exists, create it */\n \titem = xcalloc(sizeof(struct trailer_item), 1);\n \tduplicate_conf(&item->conf, &default_conf_info);\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+\t*next = item;\n \treturn item;\n }\n \n@@ -652,26 +552,19 @@ static struct trailer_item *create_trailer_item(const char *string)\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+static void add_trailer_item(struct trailer_item ***tail,\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+\t**tail = new;\n+\t*tail = &new->next;\n }\n \n static struct trailer_item *process_command_line_args(struct string_list *trailers)\n {\n-\tstruct trailer_item *arg_tok_first = NULL;\n-\tstruct trailer_item *arg_tok_last = NULL;\n+\tstruct trailer_item *arg_tok_head = NULL;\n+\tstruct trailer_item **arg_tok_tail = &arg_tok_head;\n \tstruct string_list_item *tr;\n \tstruct trailer_item *item;\n \n@@ -679,17 +572,17 @@ static struct trailer_item *process_command_line_args(struct string_list *traile\n \tfor (item = first_conf_item; item; item = item->next) {\n \t\tif (item->conf.command) {\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\tadd_trailer_item(&arg_tok_tail, new);\n \t\t}\n \t}\n \n \t/* Add a trailer item for each trailer on the command line */\n \tfor_each_string_list_item(tr, trailers) {\n \t\tstruct trailer_item *new = create_trailer_item(tr->string);\n-\t\tadd_trailer_item(&arg_tok_first, &arg_tok_last, new);\n+\t\tadd_trailer_item(&arg_tok_tail, new);\n \t}\n \n-\treturn arg_tok_first;\n+\treturn arg_tok_head;\n }\n \n static struct strbuf **read_input_file(const char *file)\n@@ -805,8 +698,7 @@ static void print_lines(FILE *outfile, struct strbuf **lines, int start, int end\n \n static int process_input_file(FILE *outfile,\n \t\t\t      struct strbuf **lines,\n-\t\t\t      struct trailer_item **in_tok_first,\n-\t\t\t      struct trailer_item **in_tok_last)\n+\t\t\t      struct trailer_item ***in_tok_tail)\n {\n \tint count = 0;\n \tint patch_start, trailer_start, trailer_end, i;\n@@ -829,18 +721,19 @@ static int process_input_file(FILE *outfile,\n \tfor (i = trailer_start; i < trailer_end; i++) {\n \t\tif (lines[i]->buf[0] != comment_line_char) {\n \t\t\tstruct trailer_item *new = create_trailer_item(lines[i]->buf);\n-\t\t\tadd_trailer_item(in_tok_first, in_tok_last, new);\n+\t\t\tadd_trailer_item(in_tok_tail, new);\n \t\t}\n \t}\n \n \treturn trailer_end;\n }\n \n-static void free_all(struct trailer_item **first)\n+static void free_all(struct trailer_item *head)\n {\n-\twhile (*first) {\n-\t\tstruct trailer_item *item = remove_first(first);\n-\t\tfree_trailer_item(item);\n+\twhile (head) {\n+\t\tstruct trailer_item *next = head->next;\n+\t\tfree_trailer_item(head);\n+\t\thead = next;\n \t}\n }\n \n@@ -877,9 +770,9 @@ static FILE *create_in_place_tempfile(const char *file)\n \n void process_trailers(const char *file, int in_place, int trim_empty, struct string_list *trailers)\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+\tstruct trailer_item *in_tok_head = NULL;\n+\tstruct trailer_item **in_tok_tail = &in_tok_head;\n+\tstruct trailer_item *arg_tok_head;\n \tstruct strbuf **lines;\n \tint trailer_end;\n \tFILE *outfile = stdout;\n@@ -894,15 +787,15 @@ void process_trailers(const char *file, int in_place, int trim_empty, struct str\n \t\toutfile = create_in_place_tempfile(file);\n \n \t/* Print the lines before the trailers */\n-\ttrailer_end = process_input_file(outfile, lines, &in_tok_first, &in_tok_last);\n+\ttrailer_end = process_input_file(outfile, lines, &in_tok_tail);\n \n-\targ_tok_first = process_command_line_args(trailers);\n+\targ_tok_head = process_command_line_args(trailers);\n \n-\tprocess_trailers_lists(&in_tok_first, &in_tok_last, &arg_tok_first);\n+\tprocess_trailers_lists(&in_tok_head, arg_tok_head);\n \n-\tprint_all(outfile, in_tok_first, trim_empty);\n+\tprint_all(outfile, in_tok_head, trim_empty);\n \n-\tfree_all(&in_tok_first);\n+\tfree_all(in_tok_head);\n \n \t/* Print the lines after the trailers as is */\n \tprint_lines(outfile, lines, trailer_end, INT_MAX);\n-- \n2.8.0.rc3.226.g39d4020\n\n"},{"id":"303954","messageId":"cover.1476232683.git.jonathantanmy@google.com","threadId":"44285","inReplyTo":null,"subject":"[PATCH 0/5] allow non-trailers and multiple-line trailers","fromName":"Jonathan Tan","fromEmail":"jonathantanmy@google.com","sentAt":"2016-10-12T01:23:24Z","receivedAt":"2016-10-12T01:29:15Z","isPatch":true,"sender":{"key":"jonathantanmy@fastmail.com","avatar":null},"body":"In [1], Junio explained a possible redesign of trailer support in\ninterpret-trailers and cherry-pick, something along these lines:\n\n 1. Support non-trailers and multi-line trailers in interpret-trailers\n 2. Create a helper function so that the new interpretation can be used\n    elsewhere (e.g. in sequencer)\n 3. Modify \"Signed-off-by\" and \"(cherry picked by\" to use the helper\n    function\n\nMy current plans for step 1 and step 2 require relatively significant\nrefactoring in trailer.c, so I thought that I should send out a patch\nset covering only step 1 first for discussion, before continuing with my\nwork on top of this patch set.\n\nSupport for steps 2 and 3, including my original use case of being\nlooser in the definition of a trailer when invoking \"cherry-pick -x\"\n(and thus suppressing the insertion of a newline) will come in a\nsubsequent patch set.\n\n[1] Message ID <xmqqwphouivf.fsf@gitster.mtv.corp.google.com>\n\nJonathan Tan (5):\n  trailer: use singly-linked list, not doubly\n  trailer: streamline trailer item create and add\n  trailer: make args have their own struct\n  trailer: allow non-trailers in trailer block\n  trailer: support values folded to multiple lines\n\n Documentation/git-interpret-trailers.txt |  10 +-\n t/t7513-interpret-trailers.sh            | 174 +++++++++\n trailer.c                                | 638 +++++++++++++++----------------\n 3 files changed, 480 insertions(+), 342 deletions(-)\n\n-- \n2.8.0.rc3.226.g39d4020\n\n"},{"id":"303955","messageId":"4b8616732b719ede04b90c87ab240c29b4e3a0bb.1476232683.git.jonathantanmy@google.com","threadId":"44285","inReplyTo":"cover.1476232683.git.jonathantanmy@google.com","subject":"[PATCH 5/5] trailer: support values folded to multiple lines","fromName":"Jonathan Tan","fromEmail":"jonathantanmy@google.com","sentAt":"2016-10-12T01:23:29Z","receivedAt":"2016-10-12T01:29:19Z","isPatch":true,"sender":{"key":"jonathantanmy@fastmail.com","avatar":null},"body":"Currently, interpret-trailers requires that a trailer be only on 1 line.\nFor example:\n\n  a: first line\n     second line\n\nwould be interpreted as one trailer line followed by one non-trailer line.\n\nMake interpret-trailers support RFC 822-style folding, treating those\nlines as one logical trailer.\n---\n Documentation/git-interpret-trailers.txt |   7 +-\n t/t7513-interpret-trailers.sh            | 139 +++++++++++++++++++++++++++++++\n trailer.c                                |  40 ++++++---\n 3 files changed, 170 insertions(+), 16 deletions(-)\n\ndiff --git a/Documentation/git-interpret-trailers.txt b/Documentation/git-interpret-trailers.txt\nindex c480da6..cfec636 100644\n--- a/Documentation/git-interpret-trailers.txt\n+++ b/Documentation/git-interpret-trailers.txt\n@@ -57,11 +57,12 @@ minus signs start the patch part of the message.\n \n When reading trailers, there can be whitespaces before and after the\n token, the separator and the value. There can also be whitespaces\n-inside the token and the value.\n+inside the token and the value. The value may be split over multiple lines with\n+each subsequent line starting with whitespace, like the \"folding\" in RFC 822.\n \n Note that 'trailers' do not follow and are not intended to follow many\n-rules for RFC 822 headers. For example they do not follow the line\n-folding rules, the encoding rules and probably many other rules.\n+rules for RFC 822 headers. For example they do not follow\n+the encoding rules and probably many other rules.\n \n OPTIONS\n -------\ndiff --git a/t/t7513-interpret-trailers.sh b/t/t7513-interpret-trailers.sh\nindex 7f5cd2a..195cdd3 100755\n--- a/t/t7513-interpret-trailers.sh\n+++ b/t/t7513-interpret-trailers.sh\n@@ -157,6 +157,145 @@ test_expect_success 'with non-trailer lines only' '\n \ttest_cmp expected actual\n '\n \n+test_expect_success 'multiline field treated as atomic for placement' '\n+\tq_to_tab >patch <<-\\EOF &&\n+\n+\t\tanother: trailer\n+\t\tname: value on\n+\t\tQmultiple lines\n+\t\tanother: trailer\n+\tEOF\n+\tq_to_tab >expected <<-\\EOF &&\n+\n+\t\tanother: trailer\n+\t\tname: value on\n+\t\tQmultiple lines\n+\t\tname: value\n+\t\tanother: trailer\n+\tEOF\n+\ttest_config trailer.name.where after &&\n+\tgit interpret-trailers --trailer \"name: value\" patch >actual &&\n+\ttest_cmp expected actual\n+'\n+\n+test_expect_success 'multiline field treated as atomic for replacement' '\n+\tq_to_tab >patch <<-\\EOF &&\n+\n+\t\tanother: trailer\n+\t\tname: value on\n+\t\tQmultiple lines\n+\t\tanother: trailer\n+\tEOF\n+\tq_to_tab >expected <<-\\EOF &&\n+\n+\t\tanother: trailer\n+\t\tanother: trailer\n+\t\tname: value\n+\tEOF\n+\ttest_config trailer.name.ifexists replace &&\n+\tgit interpret-trailers --trailer \"name: value\" patch >actual &&\n+\ttest_cmp expected actual\n+'\n+\n+test_expect_success 'multiline field treated as atomic for difference check' '\n+\tq_to_tab >patch <<-\\EOF &&\n+\n+\t\tanother: trailer\n+\t\tname: first line\n+\t\tQsecond line\n+\t\tanother: trailer\n+\tEOF\n+\ttest_config trailer.name.ifexists addIfDifferent &&\n+\n+\tq_to_tab >trailer <<-\\EOF &&\n+\t\tname: first line\n+\t\tQsecond line\n+\tEOF\n+\tq_to_tab >expected <<-\\EOF &&\n+\n+\t\tanother: trailer\n+\t\tname: first line\n+\t\tQsecond line\n+\t\tanother: trailer\n+\tEOF\n+\tgit interpret-trailers --trailer \"$(cat trailer)\" patch >actual &&\n+\ttest_cmp expected actual &&\n+\n+\tq_to_tab >trailer <<-\\EOF &&\n+\t\tname: first line\n+\t\tQsecond line *DIFFERENT*\n+\tEOF\n+\tq_to_tab >expected <<-\\EOF &&\n+\n+\t\tanother: trailer\n+\t\tname: first line\n+\t\tQsecond line\n+\t\tanother: trailer\n+\t\tname: first line\n+\t\tQsecond line *DIFFERENT*\n+\tEOF\n+\tgit interpret-trailers --trailer \"$(cat trailer)\" patch >actual &&\n+\ttest_cmp expected actual &&\n+\n+\tq_to_tab >trailer <<-\\EOF &&\n+\t\tname: first line *DIFFERENT*\n+\t\tQsecond line\n+\tEOF\n+\tq_to_tab >expected <<-\\EOF &&\n+\n+\t\tanother: trailer\n+\t\tname: first line\n+\t\tQsecond line\n+\t\tanother: trailer\n+\t\tname: first line *DIFFERENT*\n+\t\tQsecond line\n+\tEOF\n+\tgit interpret-trailers --trailer \"$(cat trailer)\" patch >actual &&\n+\ttest_cmp expected actual\n+'\n+\n+test_expect_success 'multiline field treated as atomic for neighbor check' '\n+\tq_to_tab >patch <<-\\EOF &&\n+\n+\t\tanother: trailer\n+\t\tname: first line\n+\t\tQsecond line\n+\t\tanother: trailer\n+\tEOF\n+\ttest_config trailer.name.where after &&\n+\ttest_config trailer.name.ifexists addIfDifferentNeighbor &&\n+\n+\tq_to_tab >trailer <<-\\EOF &&\n+\t\tname: first line\n+\t\tQsecond line\n+\tEOF\n+\tq_to_tab >expected <<-\\EOF &&\n+\n+\t\tanother: trailer\n+\t\tname: first line\n+\t\tQsecond line\n+\t\tanother: trailer\n+\tEOF\n+\tgit interpret-trailers --trailer \"$(cat trailer)\" patch >actual &&\n+\ttest_cmp expected actual &&\n+\n+\tq_to_tab >trailer <<-\\EOF &&\n+\t\tname: first line\n+\t\tQsecond line *DIFFERENT*\n+\tEOF\n+\tq_to_tab >expected <<-\\EOF &&\n+\n+\t\tanother: trailer\n+\t\tname: first line\n+\t\tQsecond line\n+\t\tname: first line\n+\t\tQsecond line *DIFFERENT*\n+\t\tanother: trailer\n+\tEOF\n+\tgit interpret-trailers --trailer \"$(cat trailer)\" patch >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 &&\ndiff --git a/trailer.c b/trailer.c\nindex 97e96a9..907baa0 100644\n--- a/trailer.c\n+++ b/trailer.c\n@@ -31,7 +31,7 @@ struct trailer_item {\n \t * (excluding the terminating newline) and token is NULL.\n \t */\n \tchar *token;\n-\tchar *value;\n+\tstruct strbuf value;\n };\n \n struct arg_item {\n@@ -81,7 +81,7 @@ static int same_token(const struct trailer_item *a, const struct arg_item *b)\n \n static int same_value(const struct trailer_item *a, const struct arg_item *b)\n {\n-\treturn !strcasecmp(a->value, b->value);\n+\treturn !strcasecmp(a->value.buf, b->value);\n }\n \n static int same_trailer(const struct trailer_item *a, const struct arg_item *b)\n@@ -107,7 +107,7 @@ static inline void strbuf_replace(struct strbuf *sb, const char *a, const char *\n static void free_trailer_item(struct trailer_item *item)\n {\n \tfree(item->token);\n-\tfree(item->value);\n+\tstrbuf_release(&item->value);\n \tfree(item);\n }\n \n@@ -152,8 +152,8 @@ static void print_all(FILE *outfile, 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(outfile, item->token, item->value);\n+\t\tif (!trim_empty || item->value.len > 0)\n+\t\t\tprint_tok_val(outfile, item->token, item->value.buf);\n \t}\n }\n \n@@ -195,8 +195,8 @@ static void apply_item_command(struct trailer_item *in_tok, struct arg_item *arg\n \t\tif (arg_tok->value && arg_tok->value[0]) {\n \t\t\targ = arg_tok->value;\n \t\t} else {\n-\t\t\tif (in_tok && in_tok->value)\n-\t\t\t\targ = xstrdup(in_tok->value);\n+\t\t\tif (in_tok)\n+\t\t\t\targ = xstrdup(in_tok->value.buf);\n \t\t\telse\n \t\t\t\targ = xstrdup(\"\");\n \t\t}\n@@ -209,7 +209,9 @@ static struct trailer_item *trailer_from_arg(struct arg_item *arg_tok)\n {\n \tstruct trailer_item *new = xcalloc(sizeof(*new), 1);\n \tnew->token = arg_tok->token;\n-\tnew->value = arg_tok->value;\n+\tstrbuf_init(&new->value, 0);\n+\tstrbuf_attach(&new->value, arg_tok->value, strlen(arg_tok->value),\n+\t\t      strlen(arg_tok->value));\n \targ_tok->token = arg_tok->value = NULL;\n \tfree_arg_item(arg_tok);\n \treturn new;\n@@ -587,14 +589,17 @@ static int parse_trailer(struct strbuf *tok, struct strbuf *val,\n \treturn 0;\n }\n \n-static void add_trailer_item(struct trailer_item ***tail, char *tok, char *val)\n+static struct trailer_item *add_trailer_item(struct trailer_item ***tail, char *tok,\n+\t\t\t\t\t     char *val)\n {\n \tstruct trailer_item *new = xcalloc(sizeof(*new), 1);\n \tnew->token = tok;\n-\tnew->value = val;\n+\tstrbuf_init(&new->value, 0);\n+\tstrbuf_attach(&new->value, val, strlen(val), strlen(val));\n \n \t**tail = new;\n \t*tail = &new->next;\n+\treturn new;\n }\n \n static void add_arg_item(struct arg_item ***tail, char *tok, char *val,\n@@ -750,6 +755,7 @@ static int process_input_file(FILE *outfile,\n \tint patch_start, trailer_start, trailer_end, i;\n \tstruct strbuf tok = STRBUF_INIT;\n \tstruct strbuf val = STRBUF_INIT;\n+\tstruct trailer_item *last = NULL;\n \n \t/* Get the line count */\n \twhile (lines[count])\n@@ -767,16 +773,24 @@ static int process_input_file(FILE *outfile,\n \n \t/* Parse trailer lines */\n \tfor (i = trailer_start; i < trailer_end; i++) {\n+\t\tif (last && isspace(lines[i]->buf[0])) {\n+\t\t\t/* continuation line of the last trailer item */\n+\t\t\tstrbuf_addch(&last->value, '\\n');\n+\t\t\tstrbuf_addbuf(&last->value, lines[i]);\n+\t\t\tstrbuf_strip_suffix(&last->value, \"\\n\");\n+\t\t\tcontinue;\n+\t\t}\n \t\tif (!parse_trailer(&tok, &val, NULL, lines[i]->buf, 0))\n-\t\t\tadd_trailer_item(in_tok_tail,\n-\t\t\t\t\t strbuf_detach(&tok, NULL),\n-\t\t\t\t\t strbuf_detach(&val, NULL));\n+\t\t\tlast = add_trailer_item(in_tok_tail,\n+\t\t\t\t\t\tstrbuf_detach(&tok, NULL),\n+\t\t\t\t\t\tstrbuf_detach(&val, NULL));\n \t\telse {\n \t\t\tstrbuf_addbuf(&val, lines[i]);\n \t\t\tstrbuf_strip_suffix(&val, \"\\n\");\n \t\t\tadd_trailer_item(in_tok_tail,\n \t\t\t\t\t NULL,\n \t\t\t\t\t strbuf_detach(&val, NULL));\n+\t\t\tlast = NULL;\n \t\t}\n \t}\n \n-- \n2.8.0.rc3.226.g39d4020\n\n"},{"id":"303956","messageId":"99b6b334e61ac12e93cdc76a8c60e87d7d1c39cc.1476232683.git.jonathantanmy@google.com","threadId":"44285","inReplyTo":"cover.1476232683.git.jonathantanmy@google.com","subject":"[PATCH 2/5] trailer: streamline trailer item create and add","fromName":"Jonathan Tan","fromEmail":"jonathantanmy@google.com","sentAt":"2016-10-12T01:23:26Z","receivedAt":"2016-10-12T01:31:58Z","isPatch":true,"sender":{"key":"jonathantanmy@fastmail.com","avatar":null},"body":"Currently, creation and addition (to a list) of trailer items are spread\nacross multiple functions. Streamline this by only having 2 functions:\none to parse the user-supplied string, and one to add the parsed\ninformation to a list.\n---\n trailer.c | 135 +++++++++++++++++++++++++++++---------------------------------\n 1 file changed, 62 insertions(+), 73 deletions(-)\n\ndiff --git a/trailer.c b/trailer.c\nindex bf3d7d0..e8b1bfb 100644\n--- a/trailer.c\n+++ b/trailer.c\n@@ -335,7 +335,7 @@ static int set_if_missing(struct conf_info *item, const char *value)\n \treturn 0;\n }\n \n-static void duplicate_conf(struct conf_info *dst, struct conf_info *src)\n+static void duplicate_conf(struct conf_info *dst, const struct conf_info *src)\n {\n \t*dst = *src;\n \tif (src->name)\n@@ -467,10 +467,30 @@ static int git_trailer_config(const char *conf_key, const char *value, void *cb)\n \treturn 0;\n }\n \n-static int parse_trailer(struct strbuf *tok, struct strbuf *val, const char *trailer)\n+static const char *token_from_item(struct trailer_item *item, char *tok)\n+{\n+\tif (item->conf.key)\n+\t\treturn item->conf.key;\n+\tif (tok)\n+\t\treturn tok;\n+\treturn item->conf.name;\n+}\n+\n+static int token_matches_item(const char *tok, struct trailer_item *item, int tok_len)\n+{\n+\tif (!strncasecmp(tok, item->conf.name, tok_len))\n+\t\treturn 1;\n+\treturn item->conf.key ? !strncasecmp(tok, item->conf.key, tok_len) : 0;\n+}\n+\n+static int parse_trailer(struct strbuf *tok, struct strbuf *val,\n+\t\t\t const struct conf_info **conf, const char *trailer)\n {\n \tsize_t len;\n \tstruct strbuf seps = STRBUF_INIT;\n+\tstruct trailer_item *item;\n+\tint tok_len;\n+\n \tstrbuf_addstr(&seps, separators);\n \tstrbuf_addch(&seps, '=');\n \tlen = strcspn(trailer, seps.buf);\n@@ -490,73 +510,31 @@ static int parse_trailer(struct strbuf *tok, struct strbuf *val, const char *tra\n \t\tstrbuf_addstr(tok, trailer);\n \t\tstrbuf_trim(tok);\n \t}\n-\treturn 0;\n-}\n-\n-static const char *token_from_item(struct trailer_item *item, char *tok)\n-{\n-\tif (item->conf.key)\n-\t\treturn item->conf.key;\n-\tif (tok)\n-\t\treturn tok;\n-\treturn item->conf.name;\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 ? val : xstrdup(\"\");\n-\n-\tif (conf_item) {\n-\t\tduplicate_conf(&new->conf, &conf_item->conf);\n-\t\tnew->token = xstrdup(token_from_item(conf_item, tok));\n-\t\tfree(tok);\n-\t} else {\n-\t\tduplicate_conf(&new->conf, &default_conf_info);\n-\t\tnew->token = tok;\n-\t}\n-\n-\treturn new;\n-}\n-\n-static int token_matches_item(const char *tok, struct trailer_item *item, int tok_len)\n-{\n-\tif (!strncasecmp(tok, item->conf.name, tok_len))\n-\t\treturn 1;\n-\treturn item->conf.key ? !strncasecmp(tok, item->conf.key, tok_len) : 0;\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_len;\n-\n-\tif (parse_trailer(&tok, &val, string))\n-\t\treturn NULL;\n-\n-\ttok_len = token_len_without_separator(tok.buf, tok.len);\n \n \t/* Lookup if the token matches something in the config */\n+\ttok_len = token_len_without_separator(tok->buf, tok->len);\n+\t*conf = &default_conf_info;\n \tfor (item = first_conf_item; item; item = item->next) {\n-\t\tif (token_matches_item(tok.buf, item, tok_len))\n-\t\t\treturn new_trailer_item(item,\n-\t\t\t\t\t\tstrbuf_detach(&tok, NULL),\n-\t\t\t\t\t\tstrbuf_detach(&val, NULL));\n+\t\tif (token_matches_item(tok->buf, item, tok_len)) {\n+\t\t\tchar *tok_buf = strbuf_detach(tok, NULL);\n+\t\t\t*conf = &item->conf;\n+\t\t\tstrbuf_addstr(tok, token_from_item(item, tok_buf));\n+\t\t\tfree(tok_buf);\n+\t\t\tbreak;\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+\treturn 0;\n }\n \n-static void add_trailer_item(struct trailer_item ***tail,\n-\t\t\t     struct trailer_item *new)\n+static void add_trailer_item(struct trailer_item ***tail, char *tok, char *val,\n+\t\t\t     const struct conf_info *conf)\n {\n-\tif (!new)\n-\t\treturn;\n+\tstruct trailer_item *new = xcalloc(sizeof(*new), 1);\n+\tnew->token = tok;\n+\tnew->value = val;\n+\tduplicate_conf(&new->conf, conf);\n+\n \t**tail = new;\n \t*tail = &new->next;\n }\n@@ -567,19 +545,25 @@ static struct trailer_item *process_command_line_args(struct string_list *traile\n \tstruct trailer_item **arg_tok_tail = &arg_tok_head;\n \tstruct string_list_item *tr;\n \tstruct trailer_item *item;\n+\tstruct strbuf tok = STRBUF_INIT;\n+\tstruct strbuf val = STRBUF_INIT;\n+\tconst struct conf_info *conf;\n \n \t/* Add a trailer item for each configured trailer with a command */\n-\tfor (item = first_conf_item; item; item = item->next) {\n-\t\tif (item->conf.command) {\n-\t\t\tstruct trailer_item *new = new_trailer_item(item, NULL, NULL);\n-\t\t\tadd_trailer_item(&arg_tok_tail, new);\n-\t\t}\n-\t}\n+\tfor (item = first_conf_item; item; item = item->next)\n+\t\tif (item->conf.command)\n+\t\t\tadd_trailer_item(&arg_tok_tail,\n+\t\t\t\t\t xstrdup(token_from_item(item, NULL)),\n+\t\t\t\t\t xstrdup(\"\"),\n+\t\t\t\t\t &item->conf);\n \n \t/* Add a trailer item for each trailer on the command line */\n \tfor_each_string_list_item(tr, trailers) {\n-\t\tstruct trailer_item *new = create_trailer_item(tr->string);\n-\t\tadd_trailer_item(&arg_tok_tail, new);\n+\t\tif (!parse_trailer(&tok, &val, &conf, tr->string))\n+\t\t\tadd_trailer_item(&arg_tok_tail,\n+\t\t\t\t\t strbuf_detach(&tok, NULL),\n+\t\t\t\t\t strbuf_detach(&val, NULL),\n+\t\t\t\t\t conf);\n \t}\n \n \treturn arg_tok_head;\n@@ -702,6 +686,9 @@ static int process_input_file(FILE *outfile,\n {\n \tint count = 0;\n \tint patch_start, trailer_start, trailer_end, i;\n+\tstruct strbuf tok = STRBUF_INIT;\n+\tstruct strbuf val = STRBUF_INIT;\n+\tconst struct conf_info *conf;\n \n \t/* Get the line count */\n \twhile (lines[count])\n@@ -719,10 +706,12 @@ static int process_input_file(FILE *outfile,\n \n \t/* Parse trailer lines */\n \tfor (i = trailer_start; i < trailer_end; i++) {\n-\t\tif (lines[i]->buf[0] != comment_line_char) {\n-\t\t\tstruct trailer_item *new = create_trailer_item(lines[i]->buf);\n-\t\t\tadd_trailer_item(in_tok_tail, new);\n-\t\t}\n+\t\tif (lines[i]->buf[0] != comment_line_char &&\n+\t\t    !parse_trailer(&tok, &val, &conf, lines[i]->buf))\n+\t\t\tadd_trailer_item(in_tok_tail,\n+\t\t\t\t\t strbuf_detach(&tok, NULL),\n+\t\t\t\t\t strbuf_detach(&val, NULL),\n+\t\t\t\t\t conf);\n \t}\n \n \treturn trailer_end;\n-- \n2.8.0.rc3.226.g39d4020\n\n"},{"id":"303957","messageId":"f7f39d1a968f96e1f7f40ac9a3cfb34908e9681a.1476232683.git.jonathantanmy@google.com","threadId":"44285","inReplyTo":"cover.1476232683.git.jonathantanmy@google.com","subject":"[PATCH 4/5] trailer: allow non-trailers in trailer block","fromName":"Jonathan Tan","fromEmail":"jonathantanmy@google.com","sentAt":"2016-10-12T01:23:28Z","receivedAt":"2016-10-12T01:32:02Z","isPatch":true,"sender":{"key":"jonathantanmy@fastmail.com","avatar":null},"body":"Currently, interpret-trailers requires all lines of a trailer block to\nbe trailers (or comments) - if not it would not identify that block as a\ntrailer block, and thus create its own trailer block, inserting a blank\nline.  For example:\n\n  echo -e \"\\na: b\\nnot trailer\" |\n  git interpret-trailers --trailer \"c: d\"\n\nwould result in:\n\n  a: b\n  not trailer\n\n  c: d\n\nRelax the definition of a trailer block to only require 1 trailer, so\nthat trailers can be directly added to such blocks, resulting in:\n\n  a: b\n  not trailer\n  c: d\n\nThis allows arbitrary lines to be included in trailer blocks, like those\nin [1], and still allow interpret-trailers to be used.\n\nThis change also makes comments in the trailer block be treated as any\nother non-trailer line, preserving them in the output of\ninterpret-trailers.\n\n[1]\nhttps://kernel.googlesource.com/pub/scm/linux/kernel/git/stable/linux-stable/+/e7d316a02f683864a12389f8808570e37fb90aa3\n---\n\nThere are some possible inaccuracies in the master branch's\nfind_trailer_start (in particular, handling of lines that *start* with a\nseparator, which should not be considered a trailer line) - this patch\npreserves the existing behavior.\n\n Documentation/git-interpret-trailers.txt |  3 +-\n t/t7513-interpret-trailers.sh            | 35 +++++++++++++++\n trailer.c                                | 77 ++++++++++++++++++++++----------\n 3 files changed, 90 insertions(+), 25 deletions(-)\n\ndiff --git a/Documentation/git-interpret-trailers.txt b/Documentation/git-interpret-trailers.txt\nindex 93d1db6..c480da6 100644\n--- a/Documentation/git-interpret-trailers.txt\n+++ b/Documentation/git-interpret-trailers.txt\n@@ -48,7 +48,8 @@ with only spaces at the end of the commit message part, one blank line\n will be added before the new trailer.\n \n Existing trailers are extracted from the input message by looking for\n-a group of one or more lines that contain a colon (by default), where\n+a group of one or more lines in which at least one line contains a \n+colon (by default), where\n the group is preceded by one or more empty (or whitespace-only) lines.\n The group must either be at the end of the message or be the last\n non-whitespace lines before a line that starts with '---'. Such three\ndiff --git a/t/t7513-interpret-trailers.sh b/t/t7513-interpret-trailers.sh\nindex aee785c..7f5cd2a 100755\n--- a/t/t7513-interpret-trailers.sh\n+++ b/t/t7513-interpret-trailers.sh\n@@ -126,6 +126,37 @@ test_expect_success 'with multiline title in the message' '\n \ttest_cmp expected actual\n '\n \n+test_expect_success 'with non-trailer lines mixed with trailer lines' '\n+\tcat >patch <<-\\EOF &&\n+\n+\t\tthis: is a trailer\n+\t\tthis is not a trailer\n+\tEOF\n+\tcat >expected <<-\\EOF &&\n+\n+\t\tthis: is a trailer\n+\t\tthis is not a trailer\n+\t\ttoken: value\n+\tEOF\n+\tgit interpret-trailers --trailer \"token: value\" patch >actual &&\n+\ttest_cmp expected actual\n+'\n+\n+test_expect_success 'with non-trailer lines only' '\n+\tcat >patch <<-\\EOF &&\n+\n+\t\tthis is not a trailer\n+\tEOF\n+\tcat >expected <<-\\EOF &&\n+\n+\t\tthis is not a trailer\n+\n+\t\ttoken: value\n+\tEOF\n+\tgit interpret-trailers --trailer \"token: value\" patch >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@@ -257,6 +288,8 @@ test_expect_success 'with message that has comments' '\n \tcat >>expected <<-\\EOF &&\n \t\t# comment\n \n+\t\t# other comment\n+\t\t# yet another comment\n \t\tReviewed-by: Johan\n \t\tCc: Peff\n \t\t# last comment\n@@ -286,6 +319,8 @@ test_expect_success 'with message that has an old style conflict block' '\n \tcat >>expected <<-\\EOF &&\n \t\t# comment\n \n+\t\t# other comment\n+\t\t# yet another comment\n \t\tReviewed-by: Johan\n \t\tCc: Peff\n \t\t# last comment\ndiff --git a/trailer.c b/trailer.c\nindex 167b2fd..97e96a9 100644\n--- a/trailer.c\n+++ b/trailer.c\n@@ -26,6 +26,10 @@ static struct conf_info default_conf_info;\n \n struct trailer_item {\n \tstruct trailer_item *next;\n+\t/*\n+\t * If this is not a trailer line, the line is stored in value\n+\t * (excluding the terminating newline) and token is NULL.\n+\t */\n \tchar *token;\n \tchar *value;\n };\n@@ -63,9 +67,14 @@ static size_t token_len_without_separator(const char *token, size_t len)\n \n static int same_token(const struct trailer_item *a, const struct arg_item *b)\n {\n-\tsize_t a_len = token_len_without_separator(a->token, strlen(a->token));\n-\tsize_t b_len = token_len_without_separator(b->token, strlen(b->token));\n-\tsize_t min_len = (a_len > b_len) ? b_len : a_len;\n+\tsize_t a_len, b_len, min_len;\n+\n+\tif (!a->token)\n+\t\treturn 0;\n+\n+\ta_len = token_len_without_separator(a->token, strlen(a->token));\n+\tb_len = token_len_without_separator(b->token, strlen(b->token));\n+\tmin_len = (a_len > b_len) ? b_len : a_len;\n \n \treturn !strncasecmp(a->token, b->token, min_len);\n }\n@@ -123,7 +132,14 @@ static char last_non_space_char(const char *s)\n \n static void print_tok_val(FILE *outfile, const char *tok, const char *val)\n {\n-\tchar c = last_non_space_char(tok);\n+\tchar c;\n+\n+\tif (!tok) {\n+\t\tfprintf(outfile, \"%s\\n\", val);\n+\t\treturn;\n+\t}\n+\n+\tc = last_non_space_char(tok);\n \tif (!c)\n \t\treturn;\n \tif (strchr(separators, c))\n@@ -510,8 +526,16 @@ static int token_matches_item(const char *tok, struct arg_item *item, int tok_le\n \treturn item->conf.key ? !strncasecmp(tok, item->conf.key, tok_len) : 0;\n }\n \n+/*\n+ * Parse the given trailer into token and value parts.\n+ *\n+ * If the given trailer does not have a separator (and thus cannot be separated\n+ * into token and value parts), it is treated as a token (if parse_as_arg) or\n+ * as a non-trailer line (if not parse_as_arg).\n+ */\n static int parse_trailer(struct strbuf *tok, struct strbuf *val,\n-\t\t\t const struct conf_info **conf, const char *trailer)\n+\t\t\t const struct conf_info **conf, const char *trailer,\n+\t\t\t int parse_as_arg)\n {\n \tsize_t len;\n \tstruct strbuf seps = STRBUF_INIT;\n@@ -523,11 +547,18 @@ static int parse_trailer(struct strbuf *tok, struct strbuf *val,\n \tlen = strcspn(trailer, seps.buf);\n \tstrbuf_release(&seps);\n \tif (len == 0) {\n-\t\tint l = strlen(trailer);\n+\t\tint l;\n+\t\tif (!parse_as_arg)\n+\t\t\treturn -1;\n+\n+\t\tl = strlen(trailer);\n \t\twhile (l > 0 && isspace(trailer[l - 1]))\n \t\t\tl--;\n \t\treturn error(_(\"empty trailer token in trailer '%.*s'\"), l, trailer);\n \t}\n+\tif (!parse_as_arg && len == strlen(trailer))\n+\t\treturn -1;\n+\n \tif (len < strlen(trailer)) {\n \t\tstrbuf_add(tok, trailer, len);\n \t\tstrbuf_trim(tok);\n@@ -598,7 +629,7 @@ static struct arg_item *process_command_line_args(struct string_list *trailers)\n \n \t/* Add an arg item for each trailer on the command line */\n \tfor_each_string_list_item(tr, trailers) {\n-\t\tif (!parse_trailer(&tok, &val, &conf, tr->string))\n+\t\tif (!parse_trailer(&tok, &val, &conf, tr->string, 1))\n \t\t\tadd_arg_item(&arg_tok_tail,\n \t\t\t\t     strbuf_detach(&tok, NULL),\n \t\t\t\t     strbuf_detach(&val, NULL),\n@@ -652,7 +683,7 @@ static int find_patch_start(struct strbuf **lines, int count)\n  */\n static int find_trailer_start(struct strbuf **lines, int count)\n {\n-\tint start, end_of_title, only_spaces = 1;\n+\tint start, end_of_title, only_spaces = 1, trailer_found = 0;\n \n \t/* The first paragraph is the title and cannot be trailers */\n \tfor (start = 0; start < count; start++) {\n@@ -668,22 +699,17 @@ static int find_trailer_start(struct strbuf **lines, int count)\n \t * for a line with only spaces before lines with one separator.\n \t */\n \tfor (start = count - 1; start >= end_of_title; start--) {\n-\t\tif (lines[start]->buf[0] == comment_line_char)\n-\t\t\tcontinue;\n \t\tif (contains_only_spaces(lines[start]->buf)) {\n \t\t\tif (only_spaces)\n \t\t\t\tcontinue;\n-\t\t\treturn start + 1;\n+\t\t\treturn trailer_found ? start + 1 : count;\n \t\t}\n-\t\tif (strcspn(lines[start]->buf, separators) < lines[start]->len) {\n-\t\t\tif (only_spaces)\n-\t\t\t\tonly_spaces = 0;\n-\t\t\tcontinue;\n-\t\t}\n-\t\treturn count;\n+\t\tonly_spaces = 0;\n+\t\tif (strcspn(lines[start]->buf, separators) < lines[start]->len)\n+\t\t\ttrailer_found = 1;\n \t}\n \n-\treturn only_spaces ? count : 0;\n+\treturn count;\n }\n \n /* Get the index of the end of the trailers */\n@@ -704,11 +730,8 @@ static int find_trailer_end(struct strbuf **lines, int patch_start)\n \n static int has_blank_line_before(struct strbuf **lines, int start)\n {\n-\tfor (;start >= 0; start--) {\n-\t\tif (lines[start]->buf[0] == comment_line_char)\n-\t\t\tcontinue;\n+\tif (start >= 0)\n \t\treturn contains_only_spaces(lines[start]->buf);\n-\t}\n \treturn 0;\n }\n \n@@ -744,11 +767,17 @@ static int process_input_file(FILE *outfile,\n \n \t/* Parse trailer lines */\n \tfor (i = trailer_start; i < trailer_end; i++) {\n-\t\tif (lines[i]->buf[0] != comment_line_char &&\n-\t\t    !parse_trailer(&tok, &val, NULL, lines[i]->buf))\n+\t\tif (!parse_trailer(&tok, &val, NULL, lines[i]->buf, 0))\n \t\t\tadd_trailer_item(in_tok_tail,\n \t\t\t\t\t strbuf_detach(&tok, NULL),\n \t\t\t\t\t strbuf_detach(&val, NULL));\n+\t\telse {\n+\t\t\tstrbuf_addbuf(&val, lines[i]);\n+\t\t\tstrbuf_strip_suffix(&val, \"\\n\");\n+\t\t\tadd_trailer_item(in_tok_tail,\n+\t\t\t\t\t NULL,\n+\t\t\t\t\t strbuf_detach(&val, NULL));\n+\t\t}\n \t}\n \n \treturn trailer_end;\n-- \n2.8.0.rc3.226.g39d4020\n\n"},{"id":"303959","messageId":"a4fdcf820a1d4c2bd9fe8bf157eab833c1e02044.1476232683.git.jonathantanmy@google.com","threadId":"44285","inReplyTo":"cover.1476232683.git.jonathantanmy@google.com","subject":"[PATCH 3/5] trailer: make args have their own struct","fromName":"Jonathan Tan","fromEmail":"jonathantanmy@google.com","sentAt":"2016-10-12T01:23:27Z","receivedAt":"2016-10-12T01:51:18Z","isPatch":true,"sender":{"key":"jonathantanmy@fastmail.com","avatar":null},"body":"Improve type safety by making arguments (whether from configuration or\nfrom the command line) have their own \"struct arg_item\" type, separate\nfrom the \"struct trailer_item\" type used to represent the trailers in\nthe buffer being manipulated.\n\nAlso take the opportunity to refine the \"struct trailer_item\" definition\nby removing the conf (which is used only by arguments) and by removing\nconst on the string fields, since those fields are owned by the struct.\n\nThis change also prepares \"struct trailer_item\" to be further\ndifferentiated from \"struct arg_item\" in future patches.\n---\n trailer.c | 161 ++++++++++++++++++++++++++++++++++++++------------------------\n 1 file changed, 99 insertions(+), 62 deletions(-)\n\ndiff --git a/trailer.c b/trailer.c\nindex e8b1bfb..167b2fd 100644\n--- a/trailer.c\n+++ b/trailer.c\n@@ -26,12 +26,18 @@ static struct conf_info default_conf_info;\n \n struct trailer_item {\n \tstruct trailer_item *next;\n-\tconst char *token;\n-\tconst char *value;\n+\tchar *token;\n+\tchar *value;\n+};\n+\n+struct arg_item {\n+\tstruct arg_item *next;\n+\tchar *token;\n+\tchar *value;\n \tstruct conf_info conf;\n };\n \n-static struct trailer_item *first_conf_item;\n+static struct arg_item *first_conf_item;\n \n static char *separators = \":\";\n \n@@ -55,8 +61,7 @@ static size_t token_len_without_separator(const char *token, size_t len)\n \treturn len;\n }\n \n-static int same_token(const struct trailer_item *a,\n-\t\t      const struct trailer_item *b)\n+static int same_token(const struct trailer_item *a, const struct arg_item *b)\n {\n \tsize_t a_len = token_len_without_separator(a->token, strlen(a->token));\n \tsize_t b_len = token_len_without_separator(b->token, strlen(b->token));\n@@ -65,14 +70,12 @@ static int same_token(const struct trailer_item *a,\n \treturn !strncasecmp(a->token, b->token, min_len);\n }\n \n-static int same_value(const struct trailer_item *a,\n-\t\t      const struct trailer_item *b)\n+static int same_value(const struct trailer_item *a, const struct arg_item *b)\n {\n \treturn !strcasecmp(a->value, b->value);\n }\n \n-static int same_trailer(const struct trailer_item *a,\n-\t\t\tconst struct trailer_item *b)\n+static int same_trailer(const struct trailer_item *a, const struct arg_item *b)\n {\n \treturn same_token(a, b) && same_value(a, b);\n }\n@@ -94,11 +97,18 @@ static inline void strbuf_replace(struct strbuf *sb, const char *a, const char *\n \n static void free_trailer_item(struct trailer_item *item)\n {\n+\tfree(item->token);\n+\tfree(item->value);\n+\tfree(item);\n+}\n+\n+static void free_arg_item(struct arg_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->token);\n+\tfree(item->value);\n \tfree(item);\n }\n \n@@ -131,13 +141,13 @@ static void print_all(FILE *outfile, struct trailer_item *first, int trim_empty)\n \t}\n }\n \n-static const char *apply_command(const char *command, const char *arg)\n+static 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 = CHILD_PROCESS_INIT;\n \tconst char *argv[] = {NULL, NULL};\n-\tconst char *result;\n+\tchar *result;\n \n \tstrbuf_addstr(&cmd, command);\n \tif (arg)\n@@ -162,7 +172,7 @@ static const char *apply_command(const char *command, const char *arg)\n \treturn result;\n }\n \n-static void apply_item_command(struct trailer_item *in_tok, struct trailer_item *arg_tok)\n+static void apply_item_command(struct trailer_item *in_tok, struct arg_item *arg_tok)\n {\n \tif (arg_tok->conf.command) {\n \t\tconst char *arg;\n@@ -179,60 +189,77 @@ static void apply_item_command(struct trailer_item *in_tok, struct trailer_item\n \t}\n }\n \n+static struct trailer_item *trailer_from_arg(struct arg_item *arg_tok)\n+{\n+\tstruct trailer_item *new = xcalloc(sizeof(*new), 1);\n+\tnew->token = arg_tok->token;\n+\tnew->value = arg_tok->value;\n+\targ_tok->token = arg_tok->value = NULL;\n+\tfree_arg_item(arg_tok);\n+\treturn new;\n+}\n+\n static void apply_existing_arg(struct trailer_item **found_next,\n-\t\t\t       struct trailer_item *arg_tok,\n+\t\t\t       struct arg_item *arg_tok,\n \t\t\t       struct trailer_item **position_to_add,\n \t\t\t       const struct trailer_item *in_tok_head,\n \t\t\t       const struct trailer_item *neighbor)\n {\n-\tif (arg_tok->conf.if_exists == EXISTS_DO_NOTHING) {\n-\t\tfree_trailer_item(arg_tok);\n+\tenum action_if_exists if_exists = arg_tok->conf.if_exists;\n+\tstruct trailer_item *to_add;\n+\n+\tif (if_exists == EXISTS_DO_NOTHING) {\n+\t\tfree_arg_item(arg_tok);\n \t\treturn;\n \t}\n \n \tapply_item_command(*found_next, arg_tok);\n \n-\tif (arg_tok->conf.if_exists == EXISTS_ADD_IF_DIFFERENT_NEIGHBOR) {\n+\tif (if_exists == EXISTS_ADD_IF_DIFFERENT_NEIGHBOR) {\n \t\tif (neighbor && same_trailer(neighbor, arg_tok)) {\n-\t\t\tfree_trailer_item(arg_tok);\n+\t\t\tfree_arg_item(arg_tok);\n \t\t\treturn;\n \t\t}\n-\t} else if (arg_tok->conf.if_exists == EXISTS_ADD_IF_DIFFERENT) {\n+\t} else if (if_exists == EXISTS_ADD_IF_DIFFERENT) {\n \t\tconst struct trailer_item *in_tok;\n \t\tfor (in_tok = in_tok_head; in_tok; in_tok = in_tok->next) {\n \t\t\tif (same_trailer(in_tok, arg_tok)) {\n-\t\t\t\tfree_trailer_item(arg_tok);\n+\t\t\t\tfree_arg_item(arg_tok);\n \t\t\t\treturn;\n \t\t\t}\n \t\t}\n \t}\n \n-\targ_tok->next = *position_to_add;\n-\t*position_to_add = arg_tok;\n+\tto_add = trailer_from_arg(arg_tok);\n+\tto_add->next = *position_to_add;\n+\t*position_to_add = to_add;\n \n-\tif (arg_tok->conf.if_exists == EXISTS_REPLACE) {\n+\tif (if_exists == EXISTS_REPLACE) {\n \t\tstruct trailer_item *new_next = (*found_next)->next;\n \t\tfree_trailer_item(*found_next);\n \t\t*found_next = new_next;\n \t}\n }\n \n-static void apply_missing_arg(struct trailer_item *arg_tok,\n+static void apply_missing_arg(struct arg_item *arg_tok,\n \t\t\t      struct trailer_item **position_to_add)\n {\n+\tstruct trailer_item *to_add;\n+\n \tif (arg_tok->conf.if_missing == MISSING_DO_NOTHING) {\n-\t\tfree_trailer_item(arg_tok);\n+\t\tfree_arg_item(arg_tok);\n \t\treturn;\n \t}\n \n \tapply_item_command(NULL, arg_tok);\n \n-\targ_tok->next = *position_to_add;\n-\t*position_to_add = arg_tok;\n+\tto_add = trailer_from_arg(arg_tok);\n+\tto_add->next = *position_to_add;\n+\t*position_to_add = to_add;\n }\n \n static void apply_arg(struct trailer_item **in_tok_head,\n-\t\t      struct trailer_item *arg_tok)\n+\t\t      struct arg_item *arg_tok)\n {\n \tstruct trailer_item **next, **found_next = NULL, *last = NULL;\n \tenum action_where where = arg_tok->conf.where;\n@@ -283,9 +310,9 @@ static void apply_arg(struct trailer_item **in_tok_head,\n }\n \n static void process_trailers_lists(struct trailer_item **in_tok_head,\n-\t\t\t\t   struct trailer_item *arg_tok_head)\n+\t\t\t\t   struct arg_item *arg_tok_head)\n {\n-\tstruct trailer_item *arg_tok, *next;\n+\tstruct arg_item *arg_tok, *next;\n \tfor (arg_tok = arg_tok_head; arg_tok; arg_tok = next) {\n \t\tnext = arg_tok->next;\n \t\tapply_arg(in_tok_head, arg_tok);\n@@ -346,10 +373,10 @@ static void duplicate_conf(struct conf_info *dst, const struct conf_info *src)\n \t\tdst->command = xstrdup(src->command);\n }\n \n-static struct trailer_item *get_conf_item(const char *name)\n+static struct arg_item *get_conf_item(const char *name)\n {\n-\tstruct trailer_item **next = &first_conf_item;\n-\tstruct trailer_item *item;\n+\tstruct arg_item **next = &first_conf_item;\n+\tstruct arg_item *item;\n \n \t/* Look up item with same name */\n \tfor (next = &first_conf_item; *next; next = &(*next)->next)\n@@ -357,7 +384,7 @@ static struct trailer_item *get_conf_item(const char *name)\n \t\t\treturn *next;\n \n \t/* Item does not already exists, create it */\n-\titem = xcalloc(sizeof(struct trailer_item), 1);\n+\titem = xcalloc(sizeof(*item), 1);\n \tduplicate_conf(&item->conf, &default_conf_info);\n \titem->conf.name = xstrdup(name);\n \t*next = item;\n@@ -409,7 +436,7 @@ static int git_trailer_default_config(const char *conf_key, const char *value, v\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 arg_item *item;\n \tstruct conf_info *conf;\n \tchar *name = NULL;\n \tenum trailer_info_type type;\n@@ -467,7 +494,7 @@ static int git_trailer_config(const char *conf_key, const char *value, void *cb)\n \treturn 0;\n }\n \n-static const char *token_from_item(struct trailer_item *item, char *tok)\n+static const char *token_from_item(struct arg_item *item, char *tok)\n {\n \tif (item->conf.key)\n \t\treturn item->conf.key;\n@@ -476,7 +503,7 @@ static const char *token_from_item(struct trailer_item *item, char *tok)\n \treturn item->conf.name;\n }\n \n-static int token_matches_item(const char *tok, struct trailer_item *item, int tok_len)\n+static int token_matches_item(const char *tok, struct arg_item *item, int tok_len)\n {\n \tif (!strncasecmp(tok, item->conf.name, tok_len))\n \t\treturn 1;\n@@ -488,7 +515,7 @@ static int parse_trailer(struct strbuf *tok, struct strbuf *val,\n {\n \tsize_t len;\n \tstruct strbuf seps = STRBUF_INIT;\n-\tstruct trailer_item *item;\n+\tstruct arg_item *item;\n \tint tok_len;\n \n \tstrbuf_addstr(&seps, separators);\n@@ -513,11 +540,13 @@ static int parse_trailer(struct strbuf *tok, struct strbuf *val,\n \n \t/* Lookup if the token matches something in the config */\n \ttok_len = token_len_without_separator(tok->buf, tok->len);\n-\t*conf = &default_conf_info;\n+\tif (conf)\n+\t\t*conf = &default_conf_info;\n \tfor (item = first_conf_item; item; item = item->next) {\n \t\tif (token_matches_item(tok->buf, item, tok_len)) {\n \t\t\tchar *tok_buf = strbuf_detach(tok, NULL);\n-\t\t\t*conf = &item->conf;\n+\t\t\tif (conf)\n+\t\t\t\t*conf = &item->conf;\n \t\t\tstrbuf_addstr(tok, token_from_item(item, tok_buf));\n \t\t\tfree(tok_buf);\n \t\t\tbreak;\n@@ -527,43 +556,53 @@ static int parse_trailer(struct strbuf *tok, struct strbuf *val,\n \treturn 0;\n }\n \n-static void add_trailer_item(struct trailer_item ***tail, char *tok, char *val,\n-\t\t\t     const struct conf_info *conf)\n+static void add_trailer_item(struct trailer_item ***tail, char *tok, char *val)\n {\n \tstruct trailer_item *new = xcalloc(sizeof(*new), 1);\n \tnew->token = tok;\n \tnew->value = val;\n+\n+\t**tail = new;\n+\t*tail = &new->next;\n+}\n+\n+static void add_arg_item(struct arg_item ***tail, char *tok, char *val,\n+\t\t\t const struct conf_info *conf)\n+{\n+\tstruct arg_item *new = xcalloc(sizeof(*new), 1);\n+\tnew->token = tok;\n+\tnew->value = val;\n \tduplicate_conf(&new->conf, conf);\n \n \t**tail = new;\n \t*tail = &new->next;\n }\n \n-static struct trailer_item *process_command_line_args(struct string_list *trailers)\n+static struct arg_item *process_command_line_args(struct string_list *trailers)\n {\n-\tstruct trailer_item *arg_tok_head = NULL;\n-\tstruct trailer_item **arg_tok_tail = &arg_tok_head;\n+\tstruct arg_item *arg_tok_head = NULL;\n+\tstruct arg_item **arg_tok_tail = &arg_tok_head;\n \tstruct string_list_item *tr;\n-\tstruct trailer_item *item;\n+\tstruct arg_item *item;\n \tstruct strbuf tok = STRBUF_INIT;\n \tstruct strbuf val = STRBUF_INIT;\n \tconst struct conf_info *conf;\n \n-\t/* Add a trailer item for each configured trailer with a command */\n+\t/* Add an arg item for each configured trailer with a command */\n \tfor (item = first_conf_item; item; item = item->next)\n \t\tif (item->conf.command)\n-\t\t\tadd_trailer_item(&arg_tok_tail,\n-\t\t\t\t\t xstrdup(token_from_item(item, NULL)),\n-\t\t\t\t\t xstrdup(\"\"),\n-\t\t\t\t\t &item->conf);\n+\t\t\tadd_arg_item(&arg_tok_tail,\n+\t\t\t\t     xstrdup(token_from_item(item, NULL)),\n+\t\t\t\t     xstrdup(\"\"),\n+\t\t\t\t     &item->conf);\n \n-\t/* Add a trailer item for each trailer on the command line */\n+\t/* Add an arg item for each trailer on the command line */\n \tfor_each_string_list_item(tr, trailers) {\n \t\tif (!parse_trailer(&tok, &val, &conf, tr->string))\n-\t\t\tadd_trailer_item(&arg_tok_tail,\n-\t\t\t\t\t strbuf_detach(&tok, NULL),\n-\t\t\t\t\t strbuf_detach(&val, NULL),\n-\t\t\t\t\t conf);\n+\t\t\tadd_arg_item(&arg_tok_tail,\n+\t\t\t\t     strbuf_detach(&tok, NULL),\n+\t\t\t\t     strbuf_detach(&val, NULL),\n+\t\t\t\t     conf);\n \t}\n \n \treturn arg_tok_head;\n@@ -688,7 +727,6 @@ static int process_input_file(FILE *outfile,\n \tint patch_start, trailer_start, trailer_end, i;\n \tstruct strbuf tok = STRBUF_INIT;\n \tstruct strbuf val = STRBUF_INIT;\n-\tconst struct conf_info *conf;\n \n \t/* Get the line count */\n \twhile (lines[count])\n@@ -707,11 +745,10 @@ static int process_input_file(FILE *outfile,\n \t/* Parse trailer lines */\n \tfor (i = trailer_start; i < trailer_end; i++) {\n \t\tif (lines[i]->buf[0] != comment_line_char &&\n-\t\t    !parse_trailer(&tok, &val, &conf, lines[i]->buf))\n+\t\t    !parse_trailer(&tok, &val, NULL, lines[i]->buf))\n \t\t\tadd_trailer_item(in_tok_tail,\n \t\t\t\t\t strbuf_detach(&tok, NULL),\n-\t\t\t\t\t strbuf_detach(&val, NULL),\n-\t\t\t\t\t conf);\n+\t\t\t\t\t strbuf_detach(&val, NULL));\n \t}\n \n \treturn trailer_end;\n@@ -761,7 +798,7 @@ void process_trailers(const char *file, int in_place, int trim_empty, struct str\n {\n \tstruct trailer_item *in_tok_head = NULL;\n \tstruct trailer_item **in_tok_tail = &in_tok_head;\n-\tstruct trailer_item *arg_tok_head;\n+\tstruct arg_item *arg_tok_head;\n \tstruct strbuf **lines;\n \tint trailer_end;\n \tFILE *outfile = stdout;\n-- \n2.8.0.rc3.226.g39d4020\n\n"},{"id":"303964","messageId":"xmqqr37m5bmz.fsf@gitster.mtv.corp.google.com","threadId":"44285","inReplyTo":"4b8616732b719ede04b90c87ab240c29b4e3a0bb.1476232683.git.jonathantanmy@google.com","subject":"Re: [PATCH 5/5] trailer: support values folded to multiple lines","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2016-10-12T06:23:16Z","receivedAt":"2016-10-12T06:23:24Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Jonathan Tan <jonathantanmy@google.com> writes:\n\n> Currently, interpret-trailers requires that a trailer be only on 1 line.\n> For example:\n>\n>   a: first line\n>      second line\n>\n> would be interpreted as one trailer line followed by one non-trailer line.\n>\n> Make interpret-trailers support RFC 822-style folding, treating those\n> lines as one logical trailer.\n\nLet's see how the code handles one minor detail when we see 822\nfolding, namely, \"what happens to the leading whitespace that signals\nthe beginning of the second and subsequent lines?\".\n\n> diff --git a/trailer.c b/trailer.c\n> index 97e96a9..907baa0 100644\n> --- a/trailer.c\n> +++ b/trailer.c\n> @@ -31,7 +31,7 @@ struct trailer_item {\n>  \t * (excluding the terminating newline) and token is NULL.\n>  \t */\n>  \tchar *token;\n> -\tchar *value;\n> +\tstruct strbuf value;\n>  };\n\nIs the length of value very frequently used once the list of trailer\nlines are fully parsed?  If not, I'd rather not to have \"struct\nstrbuf\" in a long-living structure like this one and instead prefer\nkeeping it a simple and stupid \"char *value\".\n\nYes, I know the existing code in trailers overuses strbuf when there\nis no need, primarily because it uses the lazy \"split into an array\nof strbufs\" function.  We shouldn't make it worse.\n\n> @@ -767,16 +773,24 @@ static int process_input_file(FILE *outfile,\n>  \n>  \t/* Parse trailer lines */\n>  \tfor (i = trailer_start; i < trailer_end; i++) {\n> +\t\tif (last && isspace(lines[i]->buf[0])) {\n\nIt is convenient if \"value\" is a strbuf to do this,\n\n> +\t\t\t/* continuation line of the last trailer item */\n> +\t\t\tstrbuf_addch(&last->value, '\\n');\n> +\t\t\tstrbuf_addbuf(&last->value, lines[i]);\n> +\t\t\tstrbuf_strip_suffix(&last->value, \"\\n\");\n\nbut it is easy to introduce a temporary strbuf in this scope and use\nit only to create the final value and detach it to last->value, i.e.\n\n\t\tif (last && isspace(*lines[i]->buf)) {\n\t\t\tstruct strbuf buf = STRBUF_INIT;\n\t\t\tstrbuf_addf(&buf, \"%s\\n%s\", last->value, lines[i]->buf);\n\t\t\tstrbuf_strip_suffix(&buf, \"\\n\");\n\t\t\tfree(last->value);\n\t\t\tlast->value = strbuf_detach(&buf, NULL);\n\nBy the way, I now see that the code handles the \"minor detail\" to\nkeep the leading whitespace, which is good.\n\nThanks.\n"},{"id":"303967","messageId":"xmqqmvia5bkf.fsf@gitster.mtv.corp.google.com","threadId":"44285","inReplyTo":"8e12e0954f0a23d7c7905c58a3f7d8084d9338be.1476232683.git.jonathantanmy@google.com","subject":"Re: [PATCH 1/5] trailer: use singly-linked list, not doubly","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2016-10-12T06:24:48Z","receivedAt":"2016-10-12T07:00:36Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Jonathan Tan <jonathantanmy@google.com> writes:\n\n> Use singly-linked lists (instead of doubly-linked lists) in trailer to\n> keep track of arguments (whether implicit from configuration or explicit\n> from the command line) and trailer items.\n>\n> This change significantly reduces the code length and simplifies the code.\n> There are now fewer pointers to be manipulated, but most trailer\n> manipulations now require seeking from beginning to end, so there might\n> be a slight net decrease in performance; however the number of trailers\n> is usually small (10 to 15 at the most) so this should not cause a big\n> impact.\n\nIt is overall a very good change, but can you split this into two\nindependent patches?  s/struct trailer_item/const &/ sprinkled all\nover the place is more or less unrelated change and it is very\ndistracting to see the primary change of the way lists are handled.\n"},{"id":"303998","messageId":"CAP8UFD03sOgVVb5-VEgHxHaXBBUPJ9UTB0=Y=th8YnUCk+Aa4Q@mail.gmail.com","threadId":"44285","inReplyTo":"8e12e0954f0a23d7c7905c58a3f7d8084d9338be.1476232683.git.jonathantanmy@google.com","subject":"Re: [PATCH 1/5] trailer: use singly-linked list, not doubly","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2016-10-12T15:38:14Z","receivedAt":"2016-10-12T15:38:53Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"On Wed, Oct 12, 2016 at 3:23 AM, Jonathan Tan <jonathantanmy@google.com> wrote:\n> Use singly-linked lists (instead of doubly-linked lists) in trailer to\n> keep track of arguments (whether implicit from configuration or explicit\n> from the command line) and trailer items.\n>\n> This change significantly reduces the code length and simplifies the code.\n\nIt's true that the code can be simplified a lot by using a\nsingly-linked list, but if we already had and used some generic\nfunctions or macros to handle doubly-linked list, I doubt there would\nbe a significant simplification (as the generic code could not be\ndeleted in this case).\n\n> There are now fewer pointers to be manipulated, but most trailer\n> manipulations now require seeking from beginning to end, so there might\n> be a slight net decrease in performance; however the number of trailers\n> is usually small (10 to 15 at the most) so this should not cause a big\n> impact.\n\nBy default we append new trailers at the end of the trailer list, so a\nsingly-linked list is theoretically not well suited at all for\nhandling trailer lists.\n\nYou say that at most there is a small number of trailers, and that may\nbe true indeed for the Linux kernel and most projects these days, but\nI am not sure it is a good assumption to make in general.\n\nFor example if some projects use or start using \"CC:  *\" trailers and\ntools to automatically append such trailers (perhaps using 'git\ninterpret-trailers' for that purpose by the way) based on people who\ntouched the same code, then it could very well be a common thing to\nhave 20 or more trailers on patches/commits for these projects.\n\nThere could also be automated testing tools that add their own\n\"Tested-by: *\" trailers, and projects that require many people to add\ntheir \"Reviewed-by: *\" trailers to each patch/commit. And in general\nwith millions of users, it is not very safe to make assumptions that\nthey will all be \"reasonable\" in the way they use a feature.\n\nAnother thing is that when Git started, I doubt many people would have\nthought that there would often be more than just one or two\n\"Signed-off-by: *\" trailer, and now 10 or 15 doesn't seem unthinkable.\nIn fact 'git interpret-trailers' has been made precisely because there\nare more and more trailers, so there is more and more a need for a\ntool to properly add and manage them.\n\nWe recently had a discussion on the list to increase the default\nabbreviation length because it was not foreseen at the beginning of\nGit that people would need a larger number of characters as projects\ngrow.\n\nSo even for the Linux kernel, I am not sure that it is safe to assume\nthat the number of trailers will not grow much over the years,\nespecially if we work on the tool that can make it easy to easily and\nautomatically add them.\n\nThanks,\nChristian.\n"},{"id":"304015","messageId":"20161012172605.tudfyg657cn4e5sj@sigill.intra.peff.net","threadId":"44285","inReplyTo":"CAP8UFD03sOgVVb5-VEgHxHaXBBUPJ9UTB0=Y=th8YnUCk+Aa4Q@mail.gmail.com","subject":"Re: [PATCH 1/5] trailer: use singly-linked list, not doubly","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2016-10-12T17:26:06Z","receivedAt":"2016-10-12T17:26:32Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Wed, Oct 12, 2016 at 05:38:14PM +0200, Christian Couder wrote:\n\n> On Wed, Oct 12, 2016 at 3:23 AM, Jonathan Tan <jonathantanmy@google.com> wrote:\n> > Use singly-linked lists (instead of doubly-linked lists) in trailer to\n> > keep track of arguments (whether implicit from configuration or explicit\n> > from the command line) and trailer items.\n> >\n> > This change significantly reduces the code length and simplifies the code.\n> \n> It's true that the code can be simplified a lot by using a\n> singly-linked list, but if we already had and used some generic\n> functions or macros to handle doubly-linked list, I doubt there would\n> be a significant simplification (as the generic code could not be\n> deleted in this case).\n\nWe didn't have such generic macros when you wrote the trailer code\noriginally, but we do now, in list.h (they come from the kernel's\ndoubly-linked list implementation). I used them recently in a series and\nfound them pretty pleasant and complete.\n\nMaybe it's worth trying the conversion here to see if it simplifies the\ncode.\n\n> > There are now fewer pointers to be manipulated, but most trailer\n> > manipulations now require seeking from beginning to end, so there might\n> > be a slight net decrease in performance; however the number of trailers\n> > is usually small (10 to 15 at the most) so this should not cause a big\n> > impact.\n> \n> By default we append new trailers at the end of the trailer list, so a\n> singly-linked list is theoretically not well suited at all for\n> handling trailer lists.\n> \n> You say that at most there is a small number of trailers, and that may\n> be true indeed for the Linux kernel and most projects these days, but\n> I am not sure it is a good assumption to make in general.\n\nI agree. As somebody who has fixed quite a number of accidentally\nquadratic cases in the number of refs, width of the commit graph, etc,\nover the years, these things have a way of creeping up or finding\npathological cases.\n\nYou _can_ append to a singly linked list in O(1) if you keep a tail\npointer, but I think using list.h would be even nicer.\n\n-Peff\n"},{"id":"304049","messageId":"cover.1476314576.git.jonathantanmy@google.com","threadId":"44285","inReplyTo":"cover.1476232683.git.jonathantanmy@google.com","subject":"[PATCH v2 0/6] allow non-trailers and multiple-line trailers","fromName":"Jonathan Tan","fromEmail":"jonathantanmy@google.com","sentAt":"2016-10-12T23:40:15Z","receivedAt":"2016-10-12T23:40:30Z","isPatch":true,"sender":{"key":"jonathantanmy@fastmail.com","avatar":null},"body":"Thanks, Peff, for the pointer to list.h. Using list.h does simplify the\ncode by a similar amount to switching it to a singly-linked list, so I\nhave done that (replacing my earlier \"trailer: use singly-linked list,\nnot doubly\" patch). Another advantage is that I no longer need to change\nthe algorithm, making for a smaller patch.\n\n(There are some quirks resulting from list.h implementing a circular\nlist, like needing to pass \"head\" as a sentinel when iterating from the\nmiddle of the list, but those are minor, and my original singly-linked\nlist implementation had quirks too anyway like needing to pass a pointer\nto the next pointer.)\n\nUpdates:\n (-> 1/6)\n - Added separate patch for const correctness changes\n (1/5 -> 2/6)\n - Dropped singly-linked list patch, instead replacing existing\n   doubly-linked list implementation with list.h\n (5/5 -> 6/6)\n - Used \"char *\" instead of \"struct strbuf\"\n - Modified test slightly to test whitespace at beginning of line\n\nJonathan Tan (6):\n  trailer: improve const correctness\n  trailer: use list.h for doubly-linked list\n  trailer: streamline trailer item create and add\n  trailer: make args have their own struct\n  trailer: allow non-trailers in trailer block\n  trailer: support values folded to multiple lines\n\n Documentation/git-interpret-trailers.txt |  10 +-\n t/t7513-interpret-trailers.sh            | 174 ++++++++++\n trailer.c                                | 538 +++++++++++++++----------------\n 3 files changed, 444 insertions(+), 278 deletions(-)\n\n-- \n2.8.0.rc3.226.g39d4020\n\n"},{"id":"304050","messageId":"4300c7b0aca2c07a9d65ae5a715ec66536461d1b.1476314576.git.jonathantanmy@google.com","threadId":"44285","inReplyTo":"cover.1476314576.git.jonathantanmy@google.com","subject":"[PATCH v2 1/6] trailer: improve const correctness","fromName":"Jonathan Tan","fromEmail":"jonathantanmy@google.com","sentAt":"2016-10-12T23:40:16Z","receivedAt":"2016-10-12T23:40:34Z","isPatch":true,"sender":{"key":"jonathantanmy@fastmail.com","avatar":null},"body":"Change \"const char *\" to \"char *\" in struct trailer_item and in the\nreturn value of apply_command (since those strings are owned strings).\n\nChange \"struct conf_info *\" to \"const struct conf_info *\" (since that\nstruct is not modified).\n---\n trailer.c | 14 +++++++-------\n 1 file changed, 7 insertions(+), 7 deletions(-)\n\ndiff --git a/trailer.c b/trailer.c\nindex c6ea9ac..1f191b2 100644\n--- a/trailer.c\n+++ b/trailer.c\n@@ -27,8 +27,8 @@ static struct conf_info default_conf_info;\n struct trailer_item {\n \tstruct trailer_item *previous;\n \tstruct trailer_item *next;\n-\tconst char *token;\n-\tconst char *value;\n+\tchar *token;\n+\tchar *value;\n \tstruct conf_info conf;\n };\n \n@@ -95,8 +95,8 @@ static void free_trailer_item(struct trailer_item *item)\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->token);\n+\tfree(item->value);\n \tfree(item);\n }\n \n@@ -215,13 +215,13 @@ static struct trailer_item *remove_first(struct trailer_item **first)\n \treturn item;\n }\n \n-static const char *apply_command(const char *command, const char *arg)\n+static 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 = CHILD_PROCESS_INIT;\n \tconst char *argv[] = {NULL, NULL};\n-\tconst char *result;\n+\tchar *result;\n \n \tstrbuf_addstr(&cmd, command);\n \tif (arg)\n@@ -425,7 +425,7 @@ static int set_if_missing(struct conf_info *item, const char *value)\n \treturn 0;\n }\n \n-static void duplicate_conf(struct conf_info *dst, struct conf_info *src)\n+static void duplicate_conf(struct conf_info *dst, const struct conf_info *src)\n {\n \t*dst = *src;\n \tif (src->name)\n-- \n2.8.0.rc3.226.g39d4020\n\n"},{"id":"304051","messageId":"a7b9f79ea1b61b80d1177740d5d78fcca848a4f6.1476314576.git.jonathantanmy@google.com","threadId":"44285","inReplyTo":"cover.1476314576.git.jonathantanmy@google.com","subject":"[PATCH v2 2/6] trailer: use list.h for doubly-linked list","fromName":"Jonathan Tan","fromEmail":"jonathantanmy@google.com","sentAt":"2016-10-12T23:40:17Z","receivedAt":"2016-10-12T23:40:38Z","isPatch":true,"sender":{"key":"jonathantanmy@fastmail.com","avatar":null},"body":"Replace the existing handwritten implementation of a doubly-linked list\nin trailer.c with the functions and macros from list.h. This\nsignificantly simplifies the code.\n---\n trailer.c | 258 ++++++++++++++++++++++----------------------------------------\n 1 file changed, 91 insertions(+), 167 deletions(-)\n\ndiff --git a/trailer.c b/trailer.c\nindex 1f191b2..0afa240 100644\n--- a/trailer.c\n+++ b/trailer.c\n@@ -4,6 +4,7 @@\n #include \"commit.h\"\n #include \"tempfile.h\"\n #include \"trailer.h\"\n+#include \"list.h\"\n /*\n  * Copyright (c) 2013, 2014 Christian Couder <chriscool@tuxfamily.org>\n  */\n@@ -25,19 +26,24 @@ struct conf_info {\n static struct conf_info default_conf_info;\n \n struct trailer_item {\n-\tstruct trailer_item *previous;\n-\tstruct trailer_item *next;\n+\tstruct list_head list;\n \tchar *token;\n \tchar *value;\n \tstruct conf_info conf;\n };\n \n-static struct trailer_item *first_conf_item;\n+LIST_HEAD(conf_head);\n \n static char *separators = \":\";\n \n #define TRAILER_ARG_STRING \"$ARG\"\n \n+/* Iterate over the elements of the list. */\n+#define list_for_each_dir(pos, head, is_reverse) \\\n+\tfor (pos = is_reverse ? (head)->prev : (head)->next; \\\n+\t\tpos != (head); \\\n+\t\tpos = is_reverse ? pos->prev : pos->next)\n+\n static int after_or_end(enum action_where where)\n {\n \treturn (where == WHERE_AFTER) || (where == WHERE_END);\n@@ -120,101 +126,49 @@ static void print_tok_val(FILE *outfile, const char *tok, const char *val)\n \t\tfprintf(outfile, \"%s%c %s\\n\", tok, separators[0], val);\n }\n \n-static void print_all(FILE *outfile, struct trailer_item *first, int trim_empty)\n+static void print_all(FILE *outfile, struct list_head *head, int trim_empty)\n {\n+\tstruct list_head *pos;\n \tstruct trailer_item *item;\n-\tfor (item = first; item; item = item->next) {\n+\tlist_for_each(pos, head) {\n+\t\titem = list_entry(pos, struct trailer_item, list);\n \t\tif (!trim_empty || strlen(item->value) > 0)\n \t\t\tprint_tok_val(outfile, item->token, item->value);\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 add_arg_to_input_list(struct trailer_item *on_tok,\n-\t\t\t\t  struct trailer_item *arg_tok,\n-\t\t\t\t  struct trailer_item **first,\n-\t\t\t\t  struct trailer_item **last)\n-{\n-\tif (after_or_end(arg_tok->conf.where)) {\n-\t\targ_tok->next = on_tok->next;\n-\t\ton_tok->next = arg_tok;\n-\t\targ_tok->previous = on_tok;\n-\t\tif (arg_tok->next)\n-\t\t\targ_tok->next->previous = arg_tok;\n-\t\tupdate_last(last);\n-\t} else {\n-\t\targ_tok->previous = on_tok->previous;\n-\t\ton_tok->previous = arg_tok;\n-\t\targ_tok->next = on_tok;\n-\t\tif (arg_tok->previous)\n-\t\t\targ_tok->previous->next = arg_tok;\n-\t\tupdate_first(first);\n-\t}\n+\t\t\t\t  struct trailer_item *arg_tok)\n+{\n+\tif (after_or_end(arg_tok->conf.where))\n+\t\tlist_add(&arg_tok->list, &on_tok->list);\n+\telse\n+\t\tlist_add_tail(&arg_tok->list, &on_tok->list);\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 check_all)\n+\t\t\t      int check_all,\n+\t\t\t      struct list_head *head)\n {\n \tenum action_where where = arg_tok->conf.where;\n+\tstruct list_head *next_head;\n \tdo {\n-\t\tif (!in_tok)\n-\t\t\treturn 1;\n \t\tif (same_trailer(in_tok, arg_tok))\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 = after_or_end(where) ? in_tok->previous : in_tok->next;\n+\t\tnext_head = after_or_end(where) ? in_tok->list.prev\n+\t\t\t\t\t\t: in_tok->list.next;\n+\t\tif (next_head == head)\n+\t\t\tbreak;\n+\t\tin_tok = list_entry(next_head, struct trailer_item, list);\n \t} while (check_all);\n \treturn 1;\n }\n \n-static void remove_from_list(struct trailer_item *item,\n-\t\t\t     struct trailer_item **first,\n-\t\t\t     struct trailer_item **last)\n-{\n-\tstruct trailer_item *next = item->next;\n-\tstruct trailer_item *previous = item->previous;\n-\n-\tif (next) {\n-\t\titem->next->previous = previous;\n-\t\titem->next = NULL;\n-\t} else if (last)\n-\t\t*last = previous;\n-\n-\tif (previous) {\n-\t\titem->previous->next = next;\n-\t\titem->previous = NULL;\n-\t} else if (first)\n-\t\t*first = 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 char *apply_command(const char *command, const char *arg)\n {\n \tstruct strbuf cmd = STRBUF_INIT;\n@@ -266,8 +220,7 @@ static void apply_item_command(struct trailer_item *in_tok, struct trailer_item\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\tstruct trailer_item *on_tok,\n-\t\t\t\tstruct trailer_item **in_tok_first,\n-\t\t\t\tstruct trailer_item **in_tok_last)\n+\t\t\t\tstruct list_head *head)\n {\n \tswitch (arg_tok->conf.if_exists) {\n \tcase EXISTS_DO_NOTHING:\n@@ -275,40 +228,34 @@ static void apply_arg_if_exists(struct trailer_item *in_tok,\n \t\tbreak;\n \tcase EXISTS_REPLACE:\n \t\tapply_item_command(in_tok, arg_tok);\n-\t\tadd_arg_to_input_list(on_tok, arg_tok,\n-\t\t\t\t      in_tok_first, in_tok_last);\n-\t\tremove_from_list(in_tok, in_tok_first, in_tok_last);\n+\t\tadd_arg_to_input_list(on_tok, arg_tok);\n+\t\tlist_del(&in_tok->list);\n \t\tfree_trailer_item(in_tok);\n \t\tbreak;\n \tcase EXISTS_ADD:\n \t\tapply_item_command(in_tok, arg_tok);\n-\t\tadd_arg_to_input_list(on_tok, arg_tok,\n-\t\t\t\t      in_tok_first, in_tok_last);\n+\t\tadd_arg_to_input_list(on_tok, arg_tok);\n \t\tbreak;\n \tcase EXISTS_ADD_IF_DIFFERENT:\n \t\tapply_item_command(in_tok, arg_tok);\n-\t\tif (check_if_different(in_tok, arg_tok, 1))\n-\t\t\tadd_arg_to_input_list(on_tok, arg_tok,\n-\t\t\t\t\t      in_tok_first, in_tok_last);\n+\t\tif (check_if_different(in_tok, arg_tok, 1, head))\n+\t\t\tadd_arg_to_input_list(on_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\tapply_item_command(in_tok, arg_tok);\n-\t\tif (check_if_different(on_tok, arg_tok, 0))\n-\t\t\tadd_arg_to_input_list(on_tok, arg_tok,\n-\t\t\t\t\t      in_tok_first, in_tok_last);\n+\t\tif (check_if_different(on_tok, arg_tok, 0, head))\n+\t\t\tadd_arg_to_input_list(on_tok, arg_tok);\n \t\telse\n \t\t\tfree_trailer_item(arg_tok);\n \t\tbreak;\n \t}\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+static void apply_arg_if_missing(struct list_head *head,\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@@ -317,68 +264,60 @@ static void apply_arg_if_missing(struct trailer_item **in_tok_first,\n \t\tbreak;\n \tcase MISSING_ADD:\n \t\twhere = arg_tok->conf.where;\n-\t\tin_tok = after_or_end(where) ? in_tok_last : in_tok_first;\n \t\tapply_item_command(NULL, arg_tok);\n-\t\tif (*in_tok) {\n-\t\t\tadd_arg_to_input_list(*in_tok, arg_tok,\n-\t\t\t\t\t      in_tok_first, in_tok_last);\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\tif (after_or_end(where))\n+\t\t\tlist_add_tail(&arg_tok->list, head);\n+\t\telse\n+\t\t\tlist_add(&arg_tok->list, head);\n \t}\n }\n \n-static int find_same_and_apply_arg(struct trailer_item **in_tok_first,\n-\t\t\t\t   struct trailer_item **in_tok_last,\n+static int find_same_and_apply_arg(struct list_head *head,\n \t\t\t\t   struct trailer_item *arg_tok)\n {\n+\tstruct list_head *pos;\n \tstruct trailer_item *in_tok;\n \tstruct trailer_item *on_tok;\n-\tstruct trailer_item *following_tok;\n \n \tenum action_where where = arg_tok->conf.where;\n \tint middle = (where == WHERE_AFTER) || (where == WHERE_BEFORE);\n \tint backwards = after_or_end(where);\n-\tstruct trailer_item *start_tok = backwards ? *in_tok_last : *in_tok_first;\n+\tstruct trailer_item *start_tok;\n \n-\tfor (in_tok = start_tok; in_tok; in_tok = following_tok) {\n-\t\tfollowing_tok = backwards ? in_tok->previous : in_tok->next;\n+\tif (list_empty(head))\n+\t\treturn 0;\n+\n+\tstart_tok = list_entry(backwards ? head->prev : head->next,\n+\t\t\t       struct trailer_item,\n+\t\t\t       list);\n+\n+\tlist_for_each_dir(pos, head, backwards) {\n+\t\tin_tok = list_entry(pos, struct trailer_item, list);\n \t\tif (!same_token(in_tok, arg_tok))\n \t\t\tcontinue;\n \t\ton_tok = middle ? in_tok : start_tok;\n-\t\tapply_arg_if_exists(in_tok, arg_tok, on_tok,\n-\t\t\t\t    in_tok_first, in_tok_last);\n+\t\tapply_arg_if_exists(in_tok, arg_tok, on_tok, head);\n \t\treturn 1;\n \t}\n \treturn 0;\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+static void process_trailers_lists(struct list_head *head,\n+\t\t\t\t   struct list_head *arg_head)\n {\n+\tstruct list_head *pos, *p;\n \tstruct trailer_item *arg_tok;\n-\tstruct trailer_item *next_arg;\n-\n-\tif (!*arg_tok_first)\n-\t\treturn;\n \n-\tfor (arg_tok = *arg_tok_first; arg_tok; arg_tok = next_arg) {\n+\tlist_for_each_safe(pos, p, arg_head) {\n \t\tint applied = 0;\n+\t\targ_tok = list_entry(pos, struct trailer_item, list);\n \n-\t\tnext_arg = arg_tok->next;\n-\t\tremove_from_list(arg_tok, arg_tok_first, NULL);\n+\t\tlist_del(pos);\n \n-\t\tapplied = find_same_and_apply_arg(in_tok_first,\n-\t\t\t\t\t\t  in_tok_last,\n-\t\t\t\t\t\t  arg_tok);\n+\t\tapplied = find_same_and_apply_arg(head, arg_tok);\n \n \t\tif (!applied)\n-\t\t\tapply_arg_if_missing(in_tok_first,\n-\t\t\t\t\t     in_tok_last,\n-\t\t\t\t\t     arg_tok);\n+\t\t\tapply_arg_if_missing(head, arg_tok);\n \t}\n }\n \n@@ -438,13 +377,12 @@ static void duplicate_conf(struct conf_info *dst, const struct conf_info *src)\n \n static struct trailer_item *get_conf_item(const char *name)\n {\n+\tstruct list_head *pos;\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+\tlist_for_each(pos, &conf_head) {\n+\t\titem = list_entry(pos, struct trailer_item, list);\n \t\tif (!strcasecmp(item->conf.name, name))\n \t\t\treturn item;\n \t}\n@@ -454,12 +392,7 @@ static struct trailer_item *get_conf_item(const char *name)\n \tduplicate_conf(&item->conf, &default_conf_info);\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+\tlist_add_tail(&item->list, &conf_head);\n \n \treturn item;\n }\n@@ -633,6 +566,7 @@ static struct trailer_item *create_trailer_item(const char *string)\n \tstruct strbuf val = STRBUF_INIT;\n \tstruct trailer_item *item;\n \tint tok_len;\n+\tstruct list_head *pos;\n \n \tif (parse_trailer(&tok, &val, string))\n \t\treturn NULL;\n@@ -640,7 +574,8 @@ static struct trailer_item *create_trailer_item(const char *string)\n \ttok_len = token_len_without_separator(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+\tlist_for_each(pos, &conf_head) {\n+\t\titem = list_entry(pos, struct trailer_item, list);\n \t\tif (token_matches_item(tok.buf, item, tok_len))\n \t\t\treturn new_trailer_item(item,\n \t\t\t\t\t\tstrbuf_detach(&tok, NULL),\n@@ -652,44 +587,34 @@ static struct trailer_item *create_trailer_item(const char *string)\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+static void add_trailer_item(struct list_head *head, 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+\tlist_add_tail(&new->list, head);\n }\n \n-static struct trailer_item *process_command_line_args(struct string_list *trailers)\n+static void process_command_line_args(struct list_head *arg_head, \n+\t\t\t\t      struct string_list *trailers)\n {\n-\tstruct trailer_item *arg_tok_first = NULL;\n-\tstruct trailer_item *arg_tok_last = NULL;\n \tstruct string_list_item *tr;\n \tstruct trailer_item *item;\n+\tstruct list_head *pos;\n \n \t/* Add a trailer item for each configured trailer with a command */\n-\tfor (item = first_conf_item; item; item = item->next) {\n+\tlist_for_each(pos, &conf_head) {\n+\t\titem = list_entry(pos, struct trailer_item, list);\n \t\tif (item->conf.command) {\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\tadd_trailer_item(arg_head, new);\n \t\t}\n \t}\n \n \t/* Add a trailer item for each trailer on the command line */\n \tfor_each_string_list_item(tr, trailers) {\n \t\tstruct trailer_item *new = create_trailer_item(tr->string);\n-\t\tadd_trailer_item(&arg_tok_first, &arg_tok_last, new);\n+\t\tadd_trailer_item(arg_head, new);\n \t}\n-\n-\treturn arg_tok_first;\n }\n \n static struct strbuf **read_input_file(const char *file)\n@@ -805,8 +730,7 @@ static void print_lines(FILE *outfile, struct strbuf **lines, int start, int end\n \n static int process_input_file(FILE *outfile,\n \t\t\t      struct strbuf **lines,\n-\t\t\t      struct trailer_item **in_tok_first,\n-\t\t\t      struct trailer_item **in_tok_last)\n+\t\t\t      struct list_head *head)\n {\n \tint count = 0;\n \tint patch_start, trailer_start, trailer_end, i;\n@@ -829,18 +753,19 @@ static int process_input_file(FILE *outfile,\n \tfor (i = trailer_start; i < trailer_end; i++) {\n \t\tif (lines[i]->buf[0] != comment_line_char) {\n \t\t\tstruct trailer_item *new = create_trailer_item(lines[i]->buf);\n-\t\t\tadd_trailer_item(in_tok_first, in_tok_last, new);\n+\t\t\tadd_trailer_item(head, new);\n \t\t}\n \t}\n \n \treturn trailer_end;\n }\n \n-static void free_all(struct trailer_item **first)\n+static void free_all(struct list_head *head)\n {\n-\twhile (*first) {\n-\t\tstruct trailer_item *item = remove_first(first);\n-\t\tfree_trailer_item(item);\n+\tstruct list_head *pos, *p;\n+\tlist_for_each_safe(pos, p, head) {\n+\t\tlist_del(pos);\n+\t\tfree_trailer_item(list_entry(pos, struct trailer_item, list));\n \t}\n }\n \n@@ -877,9 +802,8 @@ static FILE *create_in_place_tempfile(const char *file)\n \n void process_trailers(const char *file, int in_place, int trim_empty, struct string_list *trailers)\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+\tLIST_HEAD(head);\n+\tLIST_HEAD(arg_head);\n \tstruct strbuf **lines;\n \tint trailer_end;\n \tFILE *outfile = stdout;\n@@ -894,15 +818,15 @@ void process_trailers(const char *file, int in_place, int trim_empty, struct str\n \t\toutfile = create_in_place_tempfile(file);\n \n \t/* Print the lines before the trailers */\n-\ttrailer_end = process_input_file(outfile, lines, &in_tok_first, &in_tok_last);\n+\ttrailer_end = process_input_file(outfile, lines, &head);\n \n-\targ_tok_first = process_command_line_args(trailers);\n+\tprocess_command_line_args(&arg_head, trailers);\n \n-\tprocess_trailers_lists(&in_tok_first, &in_tok_last, &arg_tok_first);\n+\tprocess_trailers_lists(&head, &arg_head);\n \n-\tprint_all(outfile, in_tok_first, trim_empty);\n+\tprint_all(outfile, &head, trim_empty);\n \n-\tfree_all(&in_tok_first);\n+\tfree_all(&head);\n \n \t/* Print the lines after the trailers as is */\n \tprint_lines(outfile, lines, trailer_end, INT_MAX);\n-- \n2.8.0.rc3.226.g39d4020\n\n"},{"id":"304052","messageId":"566f5aa5aa94d77adaa5e592dcc97c76c57ee09f.1476314576.git.jonathantanmy@google.com","threadId":"44285","inReplyTo":"cover.1476314576.git.jonathantanmy@google.com","subject":"[PATCH v2 3/6] trailer: streamline trailer item create and add","fromName":"Jonathan Tan","fromEmail":"jonathantanmy@google.com","sentAt":"2016-10-12T23:40:18Z","receivedAt":"2016-10-12T23:40:42Z","isPatch":true,"sender":{"key":"jonathantanmy@fastmail.com","avatar":null},"body":"Currently, creation and addition (to a list) of trailer items are spread\nacross multiple functions. Streamline this by only having 2 functions:\none to parse the user-supplied string, and one to add the parsed\ninformation to a list.\n---\n trailer.c | 130 +++++++++++++++++++++++++++++---------------------------------\n 1 file changed, 60 insertions(+), 70 deletions(-)\n\ndiff --git a/trailer.c b/trailer.c\nindex 0afa240..54cc930 100644\n--- a/trailer.c\n+++ b/trailer.c\n@@ -500,10 +500,31 @@ static int git_trailer_config(const char *conf_key, const char *value, void *cb)\n \treturn 0;\n }\n \n-static int parse_trailer(struct strbuf *tok, struct strbuf *val, const char *trailer)\n+static const char *token_from_item(struct trailer_item *item, char *tok)\n+{\n+\tif (item->conf.key)\n+\t\treturn item->conf.key;\n+\tif (tok)\n+\t\treturn tok;\n+\treturn item->conf.name;\n+}\n+\n+static int token_matches_item(const char *tok, struct trailer_item *item, int tok_len)\n+{\n+\tif (!strncasecmp(tok, item->conf.name, tok_len))\n+\t\treturn 1;\n+\treturn item->conf.key ? !strncasecmp(tok, item->conf.key, tok_len) : 0;\n+}\n+\n+static int parse_trailer(struct strbuf *tok, struct strbuf *val,\n+\t\t\t const struct conf_info **conf, const char *trailer)\n {\n \tsize_t len;\n \tstruct strbuf seps = STRBUF_INIT;\n+\tstruct trailer_item *item;\n+\tint tok_len;\n+\tstruct list_head *pos;\n+\n \tstrbuf_addstr(&seps, separators);\n \tstrbuf_addch(&seps, '=');\n \tlen = strcspn(trailer, seps.buf);\n@@ -523,74 +544,31 @@ static int parse_trailer(struct strbuf *tok, struct strbuf *val, const char *tra\n \t\tstrbuf_addstr(tok, trailer);\n \t\tstrbuf_trim(tok);\n \t}\n-\treturn 0;\n-}\n-\n-static const char *token_from_item(struct trailer_item *item, char *tok)\n-{\n-\tif (item->conf.key)\n-\t\treturn item->conf.key;\n-\tif (tok)\n-\t\treturn tok;\n-\treturn item->conf.name;\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 ? val : xstrdup(\"\");\n-\n-\tif (conf_item) {\n-\t\tduplicate_conf(&new->conf, &conf_item->conf);\n-\t\tnew->token = xstrdup(token_from_item(conf_item, tok));\n-\t\tfree(tok);\n-\t} else {\n-\t\tduplicate_conf(&new->conf, &default_conf_info);\n-\t\tnew->token = tok;\n-\t}\n-\n-\treturn new;\n-}\n-\n-static int token_matches_item(const char *tok, struct trailer_item *item, int tok_len)\n-{\n-\tif (!strncasecmp(tok, item->conf.name, tok_len))\n-\t\treturn 1;\n-\treturn item->conf.key ? !strncasecmp(tok, item->conf.key, tok_len) : 0;\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_len;\n-\tstruct list_head *pos;\n-\n-\tif (parse_trailer(&tok, &val, string))\n-\t\treturn NULL;\n-\n-\ttok_len = token_len_without_separator(tok.buf, tok.len);\n \n \t/* Lookup if the token matches something in the config */\n+\ttok_len = token_len_without_separator(tok->buf, tok->len);\n+\t*conf = &default_conf_info;\n \tlist_for_each(pos, &conf_head) {\n \t\titem = list_entry(pos, struct trailer_item, list);\n-\t\tif (token_matches_item(tok.buf, item, tok_len))\n-\t\t\treturn new_trailer_item(item,\n-\t\t\t\t\t\tstrbuf_detach(&tok, NULL),\n-\t\t\t\t\t\tstrbuf_detach(&val, NULL));\n+\t\tif (token_matches_item(tok->buf, item, tok_len)) {\n+\t\t\tchar *tok_buf = strbuf_detach(tok, NULL);\n+\t\t\t*conf = &item->conf;\n+\t\t\tstrbuf_addstr(tok, token_from_item(item, tok_buf));\n+\t\t\tfree(tok_buf);\n+\t\t\tbreak;\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+\treturn 0;\n }\n \n-static void add_trailer_item(struct list_head *head, struct trailer_item *new)\n+static void add_trailer_item(struct list_head *head, char *tok, char *val,\n+\t\t\t     const struct conf_info *conf)\n {\n-\tif (!new)\n-\t\treturn;\n+\tstruct trailer_item *new = xcalloc(sizeof(*new), 1);\n+\tnew->token = tok;\n+\tnew->value = val;\n+\tduplicate_conf(&new->conf, conf);\n \tlist_add_tail(&new->list, head);\n }\n \n@@ -599,21 +577,28 @@ static void process_command_line_args(struct list_head *arg_head,\n {\n \tstruct string_list_item *tr;\n \tstruct trailer_item *item;\n+\tstruct strbuf tok = STRBUF_INIT;\n+\tstruct strbuf val = STRBUF_INIT;\n+\tconst struct conf_info *conf;\n \tstruct list_head *pos;\n \n \t/* Add a trailer item for each configured trailer with a command */\n \tlist_for_each(pos, &conf_head) {\n \t\titem = list_entry(pos, struct trailer_item, list);\n-\t\tif (item->conf.command) {\n-\t\t\tstruct trailer_item *new = new_trailer_item(item, NULL, NULL);\n-\t\t\tadd_trailer_item(arg_head, new);\n-\t\t}\n+\t\tif (item->conf.command)\n+\t\t\tadd_trailer_item(arg_head,\n+\t\t\t\t\t xstrdup(token_from_item(item, NULL)),\n+\t\t\t\t\t xstrdup(\"\"),\n+\t\t\t\t\t &item->conf);\n \t}\n \n \t/* Add a trailer item for each trailer on the command line */\n \tfor_each_string_list_item(tr, trailers) {\n-\t\tstruct trailer_item *new = create_trailer_item(tr->string);\n-\t\tadd_trailer_item(arg_head, new);\n+\t\tif (!parse_trailer(&tok, &val, &conf, tr->string))\n+\t\t\tadd_trailer_item(arg_head,\n+\t\t\t\t\t strbuf_detach(&tok, NULL),\n+\t\t\t\t\t strbuf_detach(&val, NULL),\n+\t\t\t\t\t conf);\n \t}\n }\n \n@@ -734,6 +719,9 @@ static int process_input_file(FILE *outfile,\n {\n \tint count = 0;\n \tint patch_start, trailer_start, trailer_end, i;\n+\tstruct strbuf tok = STRBUF_INIT;\n+\tstruct strbuf val = STRBUF_INIT;\n+\tconst struct conf_info *conf;\n \n \t/* Get the line count */\n \twhile (lines[count])\n@@ -751,10 +739,12 @@ static int process_input_file(FILE *outfile,\n \n \t/* Parse trailer lines */\n \tfor (i = trailer_start; i < trailer_end; i++) {\n-\t\tif (lines[i]->buf[0] != comment_line_char) {\n-\t\t\tstruct trailer_item *new = create_trailer_item(lines[i]->buf);\n-\t\t\tadd_trailer_item(head, new);\n-\t\t}\n+\t\tif (lines[i]->buf[0] != comment_line_char &&\n+\t\t    !parse_trailer(&tok, &val, &conf, lines[i]->buf))\n+\t\t\tadd_trailer_item(head,\n+\t\t\t\t\t strbuf_detach(&tok, NULL),\n+\t\t\t\t\t strbuf_detach(&val, NULL),\n+\t\t\t\t\t conf);\n \t}\n \n \treturn trailer_end;\n-- \n2.8.0.rc3.226.g39d4020\n\n"},{"id":"304053","messageId":"7cb8e79a13c6ea328840c42008ad0f9f97efb90d.1476314576.git.jonathantanmy@google.com","threadId":"44285","inReplyTo":"cover.1476314576.git.jonathantanmy@google.com","subject":"[PATCH v2 4/6] trailer: make args have their own struct","fromName":"Jonathan Tan","fromEmail":"jonathantanmy@google.com","sentAt":"2016-10-12T23:40:19Z","receivedAt":"2016-10-12T23:40:46Z","isPatch":true,"sender":{"key":"jonathantanmy@fastmail.com","avatar":null},"body":"Improve type safety by making arguments (whether from configuration or\nfrom the command line) have their own \"struct arg_item\" type, separate\nfrom the \"struct trailer_item\" type used to represent the trailers in\nthe buffer being manipulated.\n\nThis change also prepares \"struct trailer_item\" to be further\ndifferentiated from \"struct arg_item\" in future patches.\n---\n trailer.c | 135 +++++++++++++++++++++++++++++++++++++++-----------------------\n 1 file changed, 85 insertions(+), 50 deletions(-)\n\ndiff --git a/trailer.c b/trailer.c\nindex 54cc930..a9ed3f8 100644\n--- a/trailer.c\n+++ b/trailer.c\n@@ -29,6 +29,12 @@ struct trailer_item {\n \tstruct list_head list;\n \tchar *token;\n \tchar *value;\n+};\n+\n+struct arg_item {\n+\tstruct list_head list;\n+\tchar *token;\n+\tchar *value;\n \tstruct conf_info conf;\n };\n \n@@ -62,7 +68,7 @@ static size_t token_len_without_separator(const char *token, size_t len)\n \treturn len;\n }\n \n-static int same_token(struct trailer_item *a, struct trailer_item *b)\n+static int same_token(struct trailer_item *a, struct arg_item *b)\n {\n \tsize_t a_len = token_len_without_separator(a->token, strlen(a->token));\n \tsize_t b_len = token_len_without_separator(b->token, strlen(b->token));\n@@ -71,12 +77,12 @@ static int same_token(struct trailer_item *a, struct trailer_item *b)\n \treturn !strncasecmp(a->token, b->token, min_len);\n }\n \n-static int same_value(struct trailer_item *a, struct trailer_item *b)\n+static int same_value(struct trailer_item *a, struct arg_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)\n+static int same_trailer(struct trailer_item *a, struct arg_item *b)\n {\n \treturn same_token(a, b) && same_value(a, b);\n }\n@@ -98,6 +104,13 @@ static inline void strbuf_replace(struct strbuf *sb, const char *a, const char *\n \n static void free_trailer_item(struct trailer_item *item)\n {\n+\tfree(item->token);\n+\tfree(item->value);\n+\tfree(item);\n+}\n+\n+static void free_arg_item(struct arg_item *item)\n+{\n \tfree(item->conf.name);\n \tfree(item->conf.key);\n \tfree(item->conf.command);\n@@ -137,17 +150,29 @@ static void print_all(FILE *outfile, struct list_head *head, int trim_empty)\n \t}\n }\n \n+static struct trailer_item *trailer_from_arg(struct arg_item *arg_tok)\n+{\n+\tstruct trailer_item *new = xcalloc(sizeof(*new), 1);\n+\tnew->token = arg_tok->token;\n+\tnew->value = arg_tok->value;\n+\targ_tok->token = arg_tok->value = NULL;\n+\tfree_arg_item(arg_tok);\n+\treturn new;\n+}\n+\n static void add_arg_to_input_list(struct trailer_item *on_tok,\n-\t\t\t\t  struct trailer_item *arg_tok)\n+\t\t\t\t  struct arg_item *arg_tok)\n {\n-\tif (after_or_end(arg_tok->conf.where))\n-\t\tlist_add(&arg_tok->list, &on_tok->list);\n+\tint aoe = after_or_end(arg_tok->conf.where);\n+\tstruct trailer_item *to_add = trailer_from_arg(arg_tok);\n+\tif (aoe)\n+\t\tlist_add(&to_add->list, &on_tok->list);\n \telse\n-\t\tlist_add_tail(&arg_tok->list, &on_tok->list);\n+\t\tlist_add_tail(&to_add->list, &on_tok->list);\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      struct arg_item *arg_tok,\n \t\t\t      int check_all,\n \t\t\t      struct list_head *head)\n {\n@@ -200,7 +225,7 @@ static char *apply_command(const char *command, const char *arg)\n \treturn result;\n }\n \n-static void apply_item_command(struct trailer_item *in_tok, struct trailer_item *arg_tok)\n+static void apply_item_command(struct trailer_item *in_tok, struct arg_item *arg_tok)\n {\n \tif (arg_tok->conf.command) {\n \t\tconst char *arg;\n@@ -218,13 +243,13 @@ static void apply_item_command(struct trailer_item *in_tok, struct trailer_item\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\tstruct arg_item *arg_tok,\n \t\t\t\tstruct trailer_item *on_tok,\n \t\t\t\tstruct list_head *head)\n {\n \tswitch (arg_tok->conf.if_exists) {\n \tcase EXISTS_DO_NOTHING:\n-\t\tfree_trailer_item(arg_tok);\n+\t\tfree_arg_item(arg_tok);\n \t\tbreak;\n \tcase EXISTS_REPLACE:\n \t\tapply_item_command(in_tok, arg_tok);\n@@ -241,39 +266,41 @@ static void apply_arg_if_exists(struct trailer_item *in_tok,\n \t\tif (check_if_different(in_tok, arg_tok, 1, head))\n \t\t\tadd_arg_to_input_list(on_tok, arg_tok);\n \t\telse\n-\t\t\tfree_trailer_item(arg_tok);\n+\t\t\tfree_arg_item(arg_tok);\n \t\tbreak;\n \tcase EXISTS_ADD_IF_DIFFERENT_NEIGHBOR:\n \t\tapply_item_command(in_tok, arg_tok);\n \t\tif (check_if_different(on_tok, arg_tok, 0, head))\n \t\t\tadd_arg_to_input_list(on_tok, arg_tok);\n \t\telse\n-\t\t\tfree_trailer_item(arg_tok);\n+\t\t\tfree_arg_item(arg_tok);\n \t\tbreak;\n \t}\n }\n \n static void apply_arg_if_missing(struct list_head *head,\n-\t\t\t\t struct trailer_item *arg_tok)\n+\t\t\t\t struct arg_item *arg_tok)\n {\n \tenum action_where where;\n+\tstruct trailer_item *to_add;\n \n \tswitch (arg_tok->conf.if_missing) {\n \tcase MISSING_DO_NOTHING:\n-\t\tfree_trailer_item(arg_tok);\n+\t\tfree_arg_item(arg_tok);\n \t\tbreak;\n \tcase MISSING_ADD:\n \t\twhere = arg_tok->conf.where;\n \t\tapply_item_command(NULL, arg_tok);\n+\t\tto_add = trailer_from_arg(arg_tok);\n \t\tif (after_or_end(where))\n-\t\t\tlist_add_tail(&arg_tok->list, head);\n+\t\t\tlist_add_tail(&to_add->list, head);\n \t\telse\n-\t\t\tlist_add(&arg_tok->list, head);\n+\t\t\tlist_add(&to_add->list, head);\n \t}\n }\n \n static int find_same_and_apply_arg(struct list_head *head,\n-\t\t\t\t   struct trailer_item *arg_tok)\n+\t\t\t\t   struct arg_item *arg_tok)\n {\n \tstruct list_head *pos;\n \tstruct trailer_item *in_tok;\n@@ -306,11 +333,11 @@ static void process_trailers_lists(struct list_head *head,\n \t\t\t\t   struct list_head *arg_head)\n {\n \tstruct list_head *pos, *p;\n-\tstruct trailer_item *arg_tok;\n+\tstruct arg_item *arg_tok;\n \n \tlist_for_each_safe(pos, p, arg_head) {\n \t\tint applied = 0;\n-\t\targ_tok = list_entry(pos, struct trailer_item, list);\n+\t\targ_tok = list_entry(pos, struct arg_item, list);\n \n \t\tlist_del(pos);\n \n@@ -375,20 +402,20 @@ static void duplicate_conf(struct conf_info *dst, const struct conf_info *src)\n \t\tdst->command = xstrdup(src->command);\n }\n \n-static struct trailer_item *get_conf_item(const char *name)\n+static struct arg_item *get_conf_item(const char *name)\n {\n \tstruct list_head *pos;\n-\tstruct trailer_item *item;\n+\tstruct arg_item *item;\n \n \t/* Look up item with same name */\n \tlist_for_each(pos, &conf_head) {\n-\t\titem = list_entry(pos, struct trailer_item, list);\n+\t\titem = list_entry(pos, struct arg_item, list);\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 = xcalloc(sizeof(*item), 1);\n \tduplicate_conf(&item->conf, &default_conf_info);\n \titem->conf.name = xstrdup(name);\n \n@@ -442,7 +469,7 @@ static int git_trailer_default_config(const char *conf_key, const char *value, v\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 arg_item *item;\n \tstruct conf_info *conf;\n \tchar *name = NULL;\n \tenum trailer_info_type type;\n@@ -500,7 +527,7 @@ static int git_trailer_config(const char *conf_key, const char *value, void *cb)\n \treturn 0;\n }\n \n-static const char *token_from_item(struct trailer_item *item, char *tok)\n+static const char *token_from_item(struct arg_item *item, char *tok)\n {\n \tif (item->conf.key)\n \t\treturn item->conf.key;\n@@ -509,7 +536,7 @@ static const char *token_from_item(struct trailer_item *item, char *tok)\n \treturn item->conf.name;\n }\n \n-static int token_matches_item(const char *tok, struct trailer_item *item, int tok_len)\n+static int token_matches_item(const char *tok, struct arg_item *item, int tok_len)\n {\n \tif (!strncasecmp(tok, item->conf.name, tok_len))\n \t\treturn 1;\n@@ -521,7 +548,7 @@ static int parse_trailer(struct strbuf *tok, struct strbuf *val,\n {\n \tsize_t len;\n \tstruct strbuf seps = STRBUF_INIT;\n-\tstruct trailer_item *item;\n+\tstruct arg_item *item;\n \tint tok_len;\n \tstruct list_head *pos;\n \n@@ -547,12 +574,14 @@ static int parse_trailer(struct strbuf *tok, struct strbuf *val,\n \n \t/* Lookup if the token matches something in the config */\n \ttok_len = token_len_without_separator(tok->buf, tok->len);\n-\t*conf = &default_conf_info;\n+\tif (conf)\n+\t\t*conf = &default_conf_info;\n \tlist_for_each(pos, &conf_head) {\n-\t\titem = list_entry(pos, struct trailer_item, list);\n+\t\titem = list_entry(pos, struct arg_item, list);\n \t\tif (token_matches_item(tok->buf, item, tok_len)) {\n \t\t\tchar *tok_buf = strbuf_detach(tok, NULL);\n-\t\t\t*conf = &item->conf;\n+\t\t\tif (conf)\n+\t\t\t\t*conf = &item->conf;\n \t\t\tstrbuf_addstr(tok, token_from_item(item, tok_buf));\n \t\t\tfree(tok_buf);\n \t\t\tbreak;\n@@ -562,43 +591,51 @@ static int parse_trailer(struct strbuf *tok, struct strbuf *val,\n \treturn 0;\n }\n \n-static void add_trailer_item(struct list_head *head, char *tok, char *val,\n-\t\t\t     const struct conf_info *conf)\n+static void add_trailer_item(struct list_head *head, char *tok, char *val)\n {\n \tstruct trailer_item *new = xcalloc(sizeof(*new), 1);\n \tnew->token = tok;\n \tnew->value = val;\n-\tduplicate_conf(&new->conf, conf);\n \tlist_add_tail(&new->list, head);\n }\n \n+static void add_arg_item(struct list_head *arg_head, char *tok, char *val,\n+\t\t\t const struct conf_info *conf)\n+{\n+\tstruct arg_item *new = xcalloc(sizeof(*new), 1);\n+\tnew->token = tok;\n+\tnew->value = val;\n+\tduplicate_conf(&new->conf, conf);\n+\tlist_add_tail(&new->list, arg_head);\n+}\n+\n static void process_command_line_args(struct list_head *arg_head, \n \t\t\t\t      struct string_list *trailers)\n {\n \tstruct string_list_item *tr;\n-\tstruct trailer_item *item;\n+\tstruct arg_item *item;\n \tstruct strbuf tok = STRBUF_INIT;\n \tstruct strbuf val = STRBUF_INIT;\n \tconst struct conf_info *conf;\n \tstruct list_head *pos;\n \n-\t/* Add a trailer item for each configured trailer with a command */\n+\t/* Add an arg item for each configured trailer with a command */\n \tlist_for_each(pos, &conf_head) {\n-\t\titem = list_entry(pos, struct trailer_item, list);\n+\t\titem = list_entry(pos, struct arg_item, list);\n \t\tif (item->conf.command)\n-\t\t\tadd_trailer_item(arg_head,\n-\t\t\t\t\t xstrdup(token_from_item(item, NULL)),\n-\t\t\t\t\t xstrdup(\"\"),\n-\t\t\t\t\t &item->conf);\n+\t\t\tadd_arg_item(arg_head,\n+\t\t\t\t     xstrdup(token_from_item(item, NULL)),\n+\t\t\t\t     xstrdup(\"\"),\n+\t\t\t\t     &item->conf);\n \t}\n \n-\t/* Add a trailer item for each trailer on the command line */\n+\t/* Add an arg item for each trailer on the command line */\n \tfor_each_string_list_item(tr, trailers) {\n \t\tif (!parse_trailer(&tok, &val, &conf, tr->string))\n-\t\t\tadd_trailer_item(arg_head,\n-\t\t\t\t\t strbuf_detach(&tok, NULL),\n-\t\t\t\t\t strbuf_detach(&val, NULL),\n-\t\t\t\t\t conf);\n+\t\t\tadd_arg_item(arg_head,\n+\t\t\t\t     strbuf_detach(&tok, NULL),\n+\t\t\t\t     strbuf_detach(&val, NULL),\n+\t\t\t\t     conf);\n \t}\n }\n \n@@ -721,7 +758,6 @@ static int process_input_file(FILE *outfile,\n \tint patch_start, trailer_start, trailer_end, i;\n \tstruct strbuf tok = STRBUF_INIT;\n \tstruct strbuf val = STRBUF_INIT;\n-\tconst struct conf_info *conf;\n \n \t/* Get the line count */\n \twhile (lines[count])\n@@ -740,11 +776,10 @@ static int process_input_file(FILE *outfile,\n \t/* Parse trailer lines */\n \tfor (i = trailer_start; i < trailer_end; i++) {\n \t\tif (lines[i]->buf[0] != comment_line_char &&\n-\t\t    !parse_trailer(&tok, &val, &conf, lines[i]->buf))\n+\t\t    !parse_trailer(&tok, &val, NULL, lines[i]->buf))\n \t\t\tadd_trailer_item(head,\n \t\t\t\t\t strbuf_detach(&tok, NULL),\n-\t\t\t\t\t strbuf_detach(&val, NULL),\n-\t\t\t\t\t conf);\n+\t\t\t\t\t strbuf_detach(&val, NULL));\n \t}\n \n \treturn trailer_end;\n-- \n2.8.0.rc3.226.g39d4020\n\n"},{"id":"304054","messageId":"5b6e6ec430d3b9c75e3bf0e7138ab508c91857b7.1476314576.git.jonathantanmy@google.com","threadId":"44285","inReplyTo":"cover.1476314576.git.jonathantanmy@google.com","subject":"[PATCH v2 5/6] trailer: allow non-trailers in trailer block","fromName":"Jonathan Tan","fromEmail":"jonathantanmy@google.com","sentAt":"2016-10-12T23:40:20Z","receivedAt":"2016-10-12T23:40:52Z","isPatch":true,"sender":{"key":"jonathantanmy@fastmail.com","avatar":null},"body":"Currently, interpret-trailers requires all lines of a trailer block to\nbe trailers (or comments) - if not it would not identify that block as a\ntrailer block, and thus create its own trailer block, inserting a blank\nline.  For example:\n\n  echo -e \"\\na: b\\nnot trailer\" |\n  git interpret-trailers --trailer \"c: d\"\n\nwould result in:\n\n  a: b\n  not trailer\n\n  c: d\n\nRelax the definition of a trailer block to only require 1 trailer, so\nthat trailers can be directly added to such blocks, resulting in:\n\n  a: b\n  not trailer\n  c: d\n\nThis allows arbitrary lines to be included in trailer blocks, like those\nin [1], and still allow interpret-trailers to be used.\n\nThis change also makes comments in the trailer block be treated as any\nother non-trailer line, preserving them in the output of\ninterpret-trailers.\n\n[1]\nhttps://kernel.googlesource.com/pub/scm/linux/kernel/git/stable/linux-stable/+/e7d316a02f683864a12389f8808570e37fb90aa3\n---\n Documentation/git-interpret-trailers.txt |  3 +-\n t/t7513-interpret-trailers.sh            | 35 +++++++++++++++\n trailer.c                                | 77 ++++++++++++++++++++++----------\n 3 files changed, 90 insertions(+), 25 deletions(-)\n\ndiff --git a/Documentation/git-interpret-trailers.txt b/Documentation/git-interpret-trailers.txt\nindex 93d1db6..c480da6 100644\n--- a/Documentation/git-interpret-trailers.txt\n+++ b/Documentation/git-interpret-trailers.txt\n@@ -48,7 +48,8 @@ with only spaces at the end of the commit message part, one blank line\n will be added before the new trailer.\n \n Existing trailers are extracted from the input message by looking for\n-a group of one or more lines that contain a colon (by default), where\n+a group of one or more lines in which at least one line contains a \n+colon (by default), where\n the group is preceded by one or more empty (or whitespace-only) lines.\n The group must either be at the end of the message or be the last\n non-whitespace lines before a line that starts with '---'. Such three\ndiff --git a/t/t7513-interpret-trailers.sh b/t/t7513-interpret-trailers.sh\nindex aee785c..7f5cd2a 100755\n--- a/t/t7513-interpret-trailers.sh\n+++ b/t/t7513-interpret-trailers.sh\n@@ -126,6 +126,37 @@ test_expect_success 'with multiline title in the message' '\n \ttest_cmp expected actual\n '\n \n+test_expect_success 'with non-trailer lines mixed with trailer lines' '\n+\tcat >patch <<-\\EOF &&\n+\n+\t\tthis: is a trailer\n+\t\tthis is not a trailer\n+\tEOF\n+\tcat >expected <<-\\EOF &&\n+\n+\t\tthis: is a trailer\n+\t\tthis is not a trailer\n+\t\ttoken: value\n+\tEOF\n+\tgit interpret-trailers --trailer \"token: value\" patch >actual &&\n+\ttest_cmp expected actual\n+'\n+\n+test_expect_success 'with non-trailer lines only' '\n+\tcat >patch <<-\\EOF &&\n+\n+\t\tthis is not a trailer\n+\tEOF\n+\tcat >expected <<-\\EOF &&\n+\n+\t\tthis is not a trailer\n+\n+\t\ttoken: value\n+\tEOF\n+\tgit interpret-trailers --trailer \"token: value\" patch >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@@ -257,6 +288,8 @@ test_expect_success 'with message that has comments' '\n \tcat >>expected <<-\\EOF &&\n \t\t# comment\n \n+\t\t# other comment\n+\t\t# yet another comment\n \t\tReviewed-by: Johan\n \t\tCc: Peff\n \t\t# last comment\n@@ -286,6 +319,8 @@ test_expect_success 'with message that has an old style conflict block' '\n \tcat >>expected <<-\\EOF &&\n \t\t# comment\n \n+\t\t# other comment\n+\t\t# yet another comment\n \t\tReviewed-by: Johan\n \t\tCc: Peff\n \t\t# last comment\ndiff --git a/trailer.c b/trailer.c\nindex a9ed3f8..d6dfc7a 100644\n--- a/trailer.c\n+++ b/trailer.c\n@@ -27,6 +27,10 @@ static struct conf_info default_conf_info;\n \n struct trailer_item {\n \tstruct list_head list;\n+\t/*\n+\t * If this is not a trailer line, the line is stored in value\n+\t * (excluding the terminating newline) and token is NULL.\n+\t */\n \tchar *token;\n \tchar *value;\n };\n@@ -70,9 +74,14 @@ static size_t token_len_without_separator(const char *token, size_t len)\n \n static int same_token(struct trailer_item *a, struct arg_item *b)\n {\n-\tsize_t a_len = token_len_without_separator(a->token, strlen(a->token));\n-\tsize_t b_len = token_len_without_separator(b->token, strlen(b->token));\n-\tsize_t min_len = (a_len > b_len) ? b_len : a_len;\n+\tsize_t a_len, b_len, min_len;\n+\n+\tif (!a->token)\n+\t\treturn 0;\n+\n+\ta_len = token_len_without_separator(a->token, strlen(a->token));\n+\tb_len = token_len_without_separator(b->token, strlen(b->token));\n+\tmin_len = (a_len > b_len) ? b_len : a_len;\n \n \treturn !strncasecmp(a->token, b->token, min_len);\n }\n@@ -130,7 +139,14 @@ static char last_non_space_char(const char *s)\n \n static void print_tok_val(FILE *outfile, const char *tok, const char *val)\n {\n-\tchar c = last_non_space_char(tok);\n+\tchar c;\n+\n+\tif (!tok) {\n+\t\tfprintf(outfile, \"%s\\n\", val);\n+\t\treturn;\n+\t}\n+\n+\tc = last_non_space_char(tok);\n \tif (!c)\n \t\treturn;\n \tif (strchr(separators, c))\n@@ -543,8 +559,16 @@ static int token_matches_item(const char *tok, struct arg_item *item, int tok_le\n \treturn item->conf.key ? !strncasecmp(tok, item->conf.key, tok_len) : 0;\n }\n \n+/*\n+ * Parse the given trailer into token and value parts.\n+ *\n+ * If the given trailer does not have a separator (and thus cannot be separated\n+ * into token and value parts), it is treated as a token (if parse_as_arg) or\n+ * as a non-trailer line (if not parse_as_arg).\n+ */\n static int parse_trailer(struct strbuf *tok, struct strbuf *val,\n-\t\t\t const struct conf_info **conf, const char *trailer)\n+\t\t\t const struct conf_info **conf, const char *trailer,\n+\t\t\t int parse_as_arg)\n {\n \tsize_t len;\n \tstruct strbuf seps = STRBUF_INIT;\n@@ -557,11 +581,18 @@ static int parse_trailer(struct strbuf *tok, struct strbuf *val,\n \tlen = strcspn(trailer, seps.buf);\n \tstrbuf_release(&seps);\n \tif (len == 0) {\n-\t\tint l = strlen(trailer);\n+\t\tint l;\n+\t\tif (!parse_as_arg)\n+\t\t\treturn -1;\n+\n+\t\tl = strlen(trailer);\n \t\twhile (l > 0 && isspace(trailer[l - 1]))\n \t\t\tl--;\n \t\treturn error(_(\"empty trailer token in trailer '%.*s'\"), l, trailer);\n \t}\n+\tif (!parse_as_arg && len == strlen(trailer))\n+\t\treturn -1;\n+\n \tif (len < strlen(trailer)) {\n \t\tstrbuf_add(tok, trailer, len);\n \t\tstrbuf_trim(tok);\n@@ -631,7 +662,7 @@ static void process_command_line_args(struct list_head *arg_head,\n \n \t/* Add an arg item for each trailer on the command line */\n \tfor_each_string_list_item(tr, trailers) {\n-\t\tif (!parse_trailer(&tok, &val, &conf, tr->string))\n+\t\tif (!parse_trailer(&tok, &val, &conf, tr->string, 1))\n \t\t\tadd_arg_item(arg_head,\n \t\t\t\t     strbuf_detach(&tok, NULL),\n \t\t\t\t     strbuf_detach(&val, NULL),\n@@ -683,7 +714,7 @@ static int find_patch_start(struct strbuf **lines, int count)\n  */\n static int find_trailer_start(struct strbuf **lines, int count)\n {\n-\tint start, end_of_title, only_spaces = 1;\n+\tint start, end_of_title, only_spaces = 1, trailer_found = 0;\n \n \t/* The first paragraph is the title and cannot be trailers */\n \tfor (start = 0; start < count; start++) {\n@@ -699,22 +730,17 @@ static int find_trailer_start(struct strbuf **lines, int count)\n \t * for a line with only spaces before lines with one separator.\n \t */\n \tfor (start = count - 1; start >= end_of_title; start--) {\n-\t\tif (lines[start]->buf[0] == comment_line_char)\n-\t\t\tcontinue;\n \t\tif (contains_only_spaces(lines[start]->buf)) {\n \t\t\tif (only_spaces)\n \t\t\t\tcontinue;\n-\t\t\treturn start + 1;\n+\t\t\treturn trailer_found ? start + 1 : count;\n \t\t}\n-\t\tif (strcspn(lines[start]->buf, separators) < lines[start]->len) {\n-\t\t\tif (only_spaces)\n-\t\t\t\tonly_spaces = 0;\n-\t\t\tcontinue;\n-\t\t}\n-\t\treturn count;\n+\t\tonly_spaces = 0;\n+\t\tif (strcspn(lines[start]->buf, separators) < lines[start]->len)\n+\t\t\ttrailer_found = 1;\n \t}\n \n-\treturn only_spaces ? count : 0;\n+\treturn count;\n }\n \n /* Get the index of the end of the trailers */\n@@ -735,11 +761,8 @@ static int find_trailer_end(struct strbuf **lines, int patch_start)\n \n static int has_blank_line_before(struct strbuf **lines, int start)\n {\n-\tfor (;start >= 0; start--) {\n-\t\tif (lines[start]->buf[0] == comment_line_char)\n-\t\t\tcontinue;\n+\tif (start >= 0)\n \t\treturn contains_only_spaces(lines[start]->buf);\n-\t}\n \treturn 0;\n }\n \n@@ -775,11 +798,17 @@ static int process_input_file(FILE *outfile,\n \n \t/* Parse trailer lines */\n \tfor (i = trailer_start; i < trailer_end; i++) {\n-\t\tif (lines[i]->buf[0] != comment_line_char &&\n-\t\t    !parse_trailer(&tok, &val, NULL, lines[i]->buf))\n+\t\tif (!parse_trailer(&tok, &val, NULL, lines[i]->buf, 0))\n \t\t\tadd_trailer_item(head,\n \t\t\t\t\t strbuf_detach(&tok, NULL),\n \t\t\t\t\t strbuf_detach(&val, NULL));\n+\t\telse {\n+\t\t\tstrbuf_addbuf(&val, lines[i]);\n+\t\t\tstrbuf_strip_suffix(&val, \"\\n\");\n+\t\t\tadd_trailer_item(head,\n+\t\t\t\t\t NULL,\n+\t\t\t\t\t strbuf_detach(&val, NULL));\n+\t\t}\n \t}\n \n \treturn trailer_end;\n-- \n2.8.0.rc3.226.g39d4020\n\n"},{"id":"304055","messageId":"351bb8e1f64457c68b3439f82ac8261033757e17.1476314576.git.jonathantanmy@google.com","threadId":"44285","inReplyTo":"cover.1476314576.git.jonathantanmy@google.com","subject":"[PATCH v2 6/6] trailer: support values folded to multiple lines","fromName":"Jonathan Tan","fromEmail":"jonathantanmy@google.com","sentAt":"2016-10-12T23:40:21Z","receivedAt":"2016-10-12T23:40:55Z","isPatch":true,"sender":{"key":"jonathantanmy@fastmail.com","avatar":null},"body":"Currently, interpret-trailers requires that a trailer be only on 1 line.\nFor example:\n\n  a: first line\n     second line\n\nwould be interpreted as one trailer line followed by one non-trailer line.\n\nMake interpret-trailers support RFC 822-style folding, treating those\nlines as one logical trailer.\n---\n Documentation/git-interpret-trailers.txt |   7 +-\n t/t7513-interpret-trailers.sh            | 139 +++++++++++++++++++++++++++++++\n trailer.c                                |  22 +++--\n 3 files changed, 160 insertions(+), 8 deletions(-)\n\ndiff --git a/Documentation/git-interpret-trailers.txt b/Documentation/git-interpret-trailers.txt\nindex c480da6..cfec636 100644\n--- a/Documentation/git-interpret-trailers.txt\n+++ b/Documentation/git-interpret-trailers.txt\n@@ -57,11 +57,12 @@ minus signs start the patch part of the message.\n \n When reading trailers, there can be whitespaces before and after the\n token, the separator and the value. There can also be whitespaces\n-inside the token and the value.\n+inside the token and the value. The value may be split over multiple lines with\n+each subsequent line starting with whitespace, like the \"folding\" in RFC 822.\n \n Note that 'trailers' do not follow and are not intended to follow many\n-rules for RFC 822 headers. For example they do not follow the line\n-folding rules, the encoding rules and probably many other rules.\n+rules for RFC 822 headers. For example they do not follow\n+the encoding rules and probably many other rules.\n \n OPTIONS\n -------\ndiff --git a/t/t7513-interpret-trailers.sh b/t/t7513-interpret-trailers.sh\nindex 7f5cd2a..31db699 100755\n--- a/t/t7513-interpret-trailers.sh\n+++ b/t/t7513-interpret-trailers.sh\n@@ -157,6 +157,145 @@ test_expect_success 'with non-trailer lines only' '\n \ttest_cmp expected actual\n '\n \n+test_expect_success 'multiline field treated as atomic for placement' '\n+\tq_to_tab >patch <<-\\EOF &&\n+\n+\t\tanother: trailer\n+\t\tname: value on\n+\t\tQmultiple lines\n+\t\tanother: trailer\n+\tEOF\n+\tq_to_tab >expected <<-\\EOF &&\n+\n+\t\tanother: trailer\n+\t\tname: value on\n+\t\tQmultiple lines\n+\t\tname: value\n+\t\tanother: trailer\n+\tEOF\n+\ttest_config trailer.name.where after &&\n+\tgit interpret-trailers --trailer \"name: value\" patch >actual &&\n+\ttest_cmp expected actual\n+'\n+\n+test_expect_success 'multiline field treated as atomic for replacement' '\n+\tq_to_tab >patch <<-\\EOF &&\n+\n+\t\tanother: trailer\n+\t\tname: value on\n+\t\tQmultiple lines\n+\t\tanother: trailer\n+\tEOF\n+\tq_to_tab >expected <<-\\EOF &&\n+\n+\t\tanother: trailer\n+\t\tanother: trailer\n+\t\tname: value\n+\tEOF\n+\ttest_config trailer.name.ifexists replace &&\n+\tgit interpret-trailers --trailer \"name: value\" patch >actual &&\n+\ttest_cmp expected actual\n+'\n+\n+test_expect_success 'multiline field treated as atomic for difference check' '\n+\tq_to_tab >patch <<-\\EOF &&\n+\n+\t\tanother: trailer\n+\t\tname: first line\n+\t\tQsecond line\n+\t\tanother: trailer\n+\tEOF\n+\ttest_config trailer.name.ifexists addIfDifferent &&\n+\n+\tq_to_tab >trailer <<-\\EOF &&\n+\t\tname: first line\n+\t\tQsecond line\n+\tEOF\n+\tq_to_tab >expected <<-\\EOF &&\n+\n+\t\tanother: trailer\n+\t\tname: first line\n+\t\tQsecond line\n+\t\tanother: trailer\n+\tEOF\n+\tgit interpret-trailers --trailer \"$(cat trailer)\" patch >actual &&\n+\ttest_cmp expected actual &&\n+\n+\tq_to_tab >trailer <<-\\EOF &&\n+\t\tname: first line\n+\t\tQQQQQsecond line\n+\tEOF\n+\tq_to_tab >expected <<-\\EOF &&\n+\n+\t\tanother: trailer\n+\t\tname: first line\n+\t\tQsecond line\n+\t\tanother: trailer\n+\t\tname: first line\n+\t\tQQQQQsecond line\n+\tEOF\n+\tgit interpret-trailers --trailer \"$(cat trailer)\" patch >actual &&\n+\ttest_cmp expected actual &&\n+\n+\tq_to_tab >trailer <<-\\EOF &&\n+\t\tname: first line *DIFFERENT*\n+\t\tQsecond line\n+\tEOF\n+\tq_to_tab >expected <<-\\EOF &&\n+\n+\t\tanother: trailer\n+\t\tname: first line\n+\t\tQsecond line\n+\t\tanother: trailer\n+\t\tname: first line *DIFFERENT*\n+\t\tQsecond line\n+\tEOF\n+\tgit interpret-trailers --trailer \"$(cat trailer)\" patch >actual &&\n+\ttest_cmp expected actual\n+'\n+\n+test_expect_success 'multiline field treated as atomic for neighbor check' '\n+\tq_to_tab >patch <<-\\EOF &&\n+\n+\t\tanother: trailer\n+\t\tname: first line\n+\t\tQsecond line\n+\t\tanother: trailer\n+\tEOF\n+\ttest_config trailer.name.where after &&\n+\ttest_config trailer.name.ifexists addIfDifferentNeighbor &&\n+\n+\tq_to_tab >trailer <<-\\EOF &&\n+\t\tname: first line\n+\t\tQsecond line\n+\tEOF\n+\tq_to_tab >expected <<-\\EOF &&\n+\n+\t\tanother: trailer\n+\t\tname: first line\n+\t\tQsecond line\n+\t\tanother: trailer\n+\tEOF\n+\tgit interpret-trailers --trailer \"$(cat trailer)\" patch >actual &&\n+\ttest_cmp expected actual &&\n+\n+\tq_to_tab >trailer <<-\\EOF &&\n+\t\tname: first line\n+\t\tQQQQQsecond line\n+\tEOF\n+\tq_to_tab >expected <<-\\EOF &&\n+\n+\t\tanother: trailer\n+\t\tname: first line\n+\t\tQsecond line\n+\t\tname: first line\n+\t\tQQQQQsecond line\n+\t\tanother: trailer\n+\tEOF\n+\tgit interpret-trailers --trailer \"$(cat trailer)\" patch >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 &&\ndiff --git a/trailer.c b/trailer.c\nindex d6dfc7a..ef276e6 100644\n--- a/trailer.c\n+++ b/trailer.c\n@@ -248,7 +248,7 @@ static void apply_item_command(struct trailer_item *in_tok, struct arg_item *arg\n \t\tif (arg_tok->value && arg_tok->value[0]) {\n \t\t\targ = arg_tok->value;\n \t\t} else {\n-\t\t\tif (in_tok && in_tok->value)\n+\t\t\tif (in_tok)\n \t\t\t\targ = xstrdup(in_tok->value);\n \t\t\telse\n \t\t\t\targ = xstrdup(\"\");\n@@ -622,12 +622,14 @@ static int parse_trailer(struct strbuf *tok, struct strbuf *val,\n \treturn 0;\n }\n \n-static void add_trailer_item(struct list_head *head, char *tok, char *val)\n+static struct trailer_item *add_trailer_item(struct list_head *head, char *tok,\n+\t\t\t\t\t     char *val)\n {\n \tstruct trailer_item *new = xcalloc(sizeof(*new), 1);\n \tnew->token = tok;\n \tnew->value = val;\n \tlist_add_tail(&new->list, head);\n+\treturn new;\n }\n \n static void add_arg_item(struct list_head *arg_head, char *tok, char *val,\n@@ -781,6 +783,7 @@ static int process_input_file(FILE *outfile,\n \tint patch_start, trailer_start, trailer_end, i;\n \tstruct strbuf tok = STRBUF_INIT;\n \tstruct strbuf val = STRBUF_INIT;\n+\tstruct trailer_item *last = NULL;\n \n \t/* Get the line count */\n \twhile (lines[count])\n@@ -798,16 +801,25 @@ static int process_input_file(FILE *outfile,\n \n \t/* Parse trailer lines */\n \tfor (i = trailer_start; i < trailer_end; i++) {\n+\t\tif (last && isspace(lines[i]->buf[0])) {\n+\t\t\tstruct strbuf sb = STRBUF_INIT;\n+\t\t\tstrbuf_addf(&sb, \"%s\\n%s\", last->value, lines[i]->buf);\n+\t\t\tstrbuf_strip_suffix(&sb, \"\\n\");\n+\t\t\tfree(last->value);\n+\t\t\tlast->value = strbuf_detach(&sb, NULL);\n+\t\t\tcontinue;\n+\t\t}\n \t\tif (!parse_trailer(&tok, &val, NULL, lines[i]->buf, 0))\n-\t\t\tadd_trailer_item(head,\n-\t\t\t\t\t strbuf_detach(&tok, NULL),\n-\t\t\t\t\t strbuf_detach(&val, NULL));\n+\t\t\tlast = add_trailer_item(head,\n+\t\t\t\t\t\tstrbuf_detach(&tok, NULL),\n+\t\t\t\t\t\tstrbuf_detach(&val, NULL));\n \t\telse {\n \t\t\tstrbuf_addbuf(&val, lines[i]);\n \t\t\tstrbuf_strip_suffix(&val, \"\\n\");\n \t\t\tadd_trailer_item(head,\n \t\t\t\t\t NULL,\n \t\t\t\t\t strbuf_detach(&val, NULL));\n+\t\t\tlast = NULL;\n \t\t}\n \t}\n \n-- \n2.8.0.rc3.226.g39d4020\n\n"},{"id":"304177","messageId":"cover.1476466609.git.jonathantanmy@google.com","threadId":"44285","inReplyTo":"cover.1476232683.git.jonathantanmy@google.com","subject":"[PATCH v3 0/6] allow non-trailers and multiple-line trailers","fromName":"Jonathan Tan","fromEmail":"jonathantanmy@google.com","sentAt":"2016-10-14T17:37:57Z","receivedAt":"2016-10-14T17:38:12Z","isPatch":true,"sender":{"key":"jonathantanmy@fastmail.com","avatar":null},"body":"Ah, I knew I forgot something. These are exactly the same as v2, except\nthat these are signed off.\n\nJonathan Tan (6):\n  trailer: improve const correctness\n  trailer: use list.h for doubly-linked list\n  trailer: streamline trailer item create and add\n  trailer: make args have their own struct\n  trailer: allow non-trailers in trailer block\n  trailer: support values folded to multiple lines\n\n Documentation/git-interpret-trailers.txt |  10 +-\n t/t7513-interpret-trailers.sh            | 174 ++++++++++\n trailer.c                                | 538 +++++++++++++++----------------\n 3 files changed, 444 insertions(+), 278 deletions(-)\n\n-- \n2.8.0.rc3.226.g39d4020\n\n"},{"id":"304178","messageId":"701ba7164b587a97ef380bc981c95c5d51db6781.1476466609.git.jonathantanmy@google.com","threadId":"44285","inReplyTo":"cover.1476466609.git.jonathantanmy@google.com","subject":"[PATCH v3 1/6] trailer: improve const correctness","fromName":"Jonathan Tan","fromEmail":"jonathantanmy@google.com","sentAt":"2016-10-14T17:37:58Z","receivedAt":"2016-10-14T17:38:15Z","isPatch":true,"sender":{"key":"jonathantanmy@fastmail.com","avatar":null},"body":"Change \"const char *\" to \"char *\" in struct trailer_item and in the\nreturn value of apply_command (since those strings are owned strings).\n\nChange \"struct conf_info *\" to \"const struct conf_info *\" (since that\nstruct is not modified).\n\nSigned-off-by: Jonathan Tan <jonathantanmy@google.com>\n---\n trailer.c | 14 +++++++-------\n 1 file changed, 7 insertions(+), 7 deletions(-)\n\ndiff --git a/trailer.c b/trailer.c\nindex c6ea9ac..1f191b2 100644\n--- a/trailer.c\n+++ b/trailer.c\n@@ -27,8 +27,8 @@ static struct conf_info default_conf_info;\n struct trailer_item {\n \tstruct trailer_item *previous;\n \tstruct trailer_item *next;\n-\tconst char *token;\n-\tconst char *value;\n+\tchar *token;\n+\tchar *value;\n \tstruct conf_info conf;\n };\n \n@@ -95,8 +95,8 @@ static void free_trailer_item(struct trailer_item *item)\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->token);\n+\tfree(item->value);\n \tfree(item);\n }\n \n@@ -215,13 +215,13 @@ static struct trailer_item *remove_first(struct trailer_item **first)\n \treturn item;\n }\n \n-static const char *apply_command(const char *command, const char *arg)\n+static 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 = CHILD_PROCESS_INIT;\n \tconst char *argv[] = {NULL, NULL};\n-\tconst char *result;\n+\tchar *result;\n \n \tstrbuf_addstr(&cmd, command);\n \tif (arg)\n@@ -425,7 +425,7 @@ static int set_if_missing(struct conf_info *item, const char *value)\n \treturn 0;\n }\n \n-static void duplicate_conf(struct conf_info *dst, struct conf_info *src)\n+static void duplicate_conf(struct conf_info *dst, const struct conf_info *src)\n {\n \t*dst = *src;\n \tif (src->name)\n-- \n2.8.0.rc3.226.g39d4020\n\n"},{"id":"304179","messageId":"03086e210fb19ecd011fd507721430d5beb9bb7a.1476466609.git.jonathantanmy@google.com","threadId":"44285","inReplyTo":"cover.1476466609.git.jonathantanmy@google.com","subject":"[PATCH v3 2/6] trailer: use list.h for doubly-linked list","fromName":"Jonathan Tan","fromEmail":"jonathantanmy@google.com","sentAt":"2016-10-14T17:37:59Z","receivedAt":"2016-10-14T17:38:24Z","isPatch":true,"sender":{"key":"jonathantanmy@fastmail.com","avatar":null},"body":"Replace the existing handwritten implementation of a doubly-linked list\nin trailer.c with the functions and macros from list.h. This\nsignificantly simplifies the code.\n\nSigned-off-by: Jonathan Tan <jonathantanmy@google.com>\n---\n trailer.c | 258 ++++++++++++++++++++++----------------------------------------\n 1 file changed, 91 insertions(+), 167 deletions(-)\n\ndiff --git a/trailer.c b/trailer.c\nindex 1f191b2..0afa240 100644\n--- a/trailer.c\n+++ b/trailer.c\n@@ -4,6 +4,7 @@\n #include \"commit.h\"\n #include \"tempfile.h\"\n #include \"trailer.h\"\n+#include \"list.h\"\n /*\n  * Copyright (c) 2013, 2014 Christian Couder <chriscool@tuxfamily.org>\n  */\n@@ -25,19 +26,24 @@ struct conf_info {\n static struct conf_info default_conf_info;\n \n struct trailer_item {\n-\tstruct trailer_item *previous;\n-\tstruct trailer_item *next;\n+\tstruct list_head list;\n \tchar *token;\n \tchar *value;\n \tstruct conf_info conf;\n };\n \n-static struct trailer_item *first_conf_item;\n+LIST_HEAD(conf_head);\n \n static char *separators = \":\";\n \n #define TRAILER_ARG_STRING \"$ARG\"\n \n+/* Iterate over the elements of the list. */\n+#define list_for_each_dir(pos, head, is_reverse) \\\n+\tfor (pos = is_reverse ? (head)->prev : (head)->next; \\\n+\t\tpos != (head); \\\n+\t\tpos = is_reverse ? pos->prev : pos->next)\n+\n static int after_or_end(enum action_where where)\n {\n \treturn (where == WHERE_AFTER) || (where == WHERE_END);\n@@ -120,101 +126,49 @@ static void print_tok_val(FILE *outfile, const char *tok, const char *val)\n \t\tfprintf(outfile, \"%s%c %s\\n\", tok, separators[0], val);\n }\n \n-static void print_all(FILE *outfile, struct trailer_item *first, int trim_empty)\n+static void print_all(FILE *outfile, struct list_head *head, int trim_empty)\n {\n+\tstruct list_head *pos;\n \tstruct trailer_item *item;\n-\tfor (item = first; item; item = item->next) {\n+\tlist_for_each(pos, head) {\n+\t\titem = list_entry(pos, struct trailer_item, list);\n \t\tif (!trim_empty || strlen(item->value) > 0)\n \t\t\tprint_tok_val(outfile, item->token, item->value);\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 add_arg_to_input_list(struct trailer_item *on_tok,\n-\t\t\t\t  struct trailer_item *arg_tok,\n-\t\t\t\t  struct trailer_item **first,\n-\t\t\t\t  struct trailer_item **last)\n-{\n-\tif (after_or_end(arg_tok->conf.where)) {\n-\t\targ_tok->next = on_tok->next;\n-\t\ton_tok->next = arg_tok;\n-\t\targ_tok->previous = on_tok;\n-\t\tif (arg_tok->next)\n-\t\t\targ_tok->next->previous = arg_tok;\n-\t\tupdate_last(last);\n-\t} else {\n-\t\targ_tok->previous = on_tok->previous;\n-\t\ton_tok->previous = arg_tok;\n-\t\targ_tok->next = on_tok;\n-\t\tif (arg_tok->previous)\n-\t\t\targ_tok->previous->next = arg_tok;\n-\t\tupdate_first(first);\n-\t}\n+\t\t\t\t  struct trailer_item *arg_tok)\n+{\n+\tif (after_or_end(arg_tok->conf.where))\n+\t\tlist_add(&arg_tok->list, &on_tok->list);\n+\telse\n+\t\tlist_add_tail(&arg_tok->list, &on_tok->list);\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 check_all)\n+\t\t\t      int check_all,\n+\t\t\t      struct list_head *head)\n {\n \tenum action_where where = arg_tok->conf.where;\n+\tstruct list_head *next_head;\n \tdo {\n-\t\tif (!in_tok)\n-\t\t\treturn 1;\n \t\tif (same_trailer(in_tok, arg_tok))\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 = after_or_end(where) ? in_tok->previous : in_tok->next;\n+\t\tnext_head = after_or_end(where) ? in_tok->list.prev\n+\t\t\t\t\t\t: in_tok->list.next;\n+\t\tif (next_head == head)\n+\t\t\tbreak;\n+\t\tin_tok = list_entry(next_head, struct trailer_item, list);\n \t} while (check_all);\n \treturn 1;\n }\n \n-static void remove_from_list(struct trailer_item *item,\n-\t\t\t     struct trailer_item **first,\n-\t\t\t     struct trailer_item **last)\n-{\n-\tstruct trailer_item *next = item->next;\n-\tstruct trailer_item *previous = item->previous;\n-\n-\tif (next) {\n-\t\titem->next->previous = previous;\n-\t\titem->next = NULL;\n-\t} else if (last)\n-\t\t*last = previous;\n-\n-\tif (previous) {\n-\t\titem->previous->next = next;\n-\t\titem->previous = NULL;\n-\t} else if (first)\n-\t\t*first = 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 char *apply_command(const char *command, const char *arg)\n {\n \tstruct strbuf cmd = STRBUF_INIT;\n@@ -266,8 +220,7 @@ static void apply_item_command(struct trailer_item *in_tok, struct trailer_item\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\tstruct trailer_item *on_tok,\n-\t\t\t\tstruct trailer_item **in_tok_first,\n-\t\t\t\tstruct trailer_item **in_tok_last)\n+\t\t\t\tstruct list_head *head)\n {\n \tswitch (arg_tok->conf.if_exists) {\n \tcase EXISTS_DO_NOTHING:\n@@ -275,40 +228,34 @@ static void apply_arg_if_exists(struct trailer_item *in_tok,\n \t\tbreak;\n \tcase EXISTS_REPLACE:\n \t\tapply_item_command(in_tok, arg_tok);\n-\t\tadd_arg_to_input_list(on_tok, arg_tok,\n-\t\t\t\t      in_tok_first, in_tok_last);\n-\t\tremove_from_list(in_tok, in_tok_first, in_tok_last);\n+\t\tadd_arg_to_input_list(on_tok, arg_tok);\n+\t\tlist_del(&in_tok->list);\n \t\tfree_trailer_item(in_tok);\n \t\tbreak;\n \tcase EXISTS_ADD:\n \t\tapply_item_command(in_tok, arg_tok);\n-\t\tadd_arg_to_input_list(on_tok, arg_tok,\n-\t\t\t\t      in_tok_first, in_tok_last);\n+\t\tadd_arg_to_input_list(on_tok, arg_tok);\n \t\tbreak;\n \tcase EXISTS_ADD_IF_DIFFERENT:\n \t\tapply_item_command(in_tok, arg_tok);\n-\t\tif (check_if_different(in_tok, arg_tok, 1))\n-\t\t\tadd_arg_to_input_list(on_tok, arg_tok,\n-\t\t\t\t\t      in_tok_first, in_tok_last);\n+\t\tif (check_if_different(in_tok, arg_tok, 1, head))\n+\t\t\tadd_arg_to_input_list(on_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\tapply_item_command(in_tok, arg_tok);\n-\t\tif (check_if_different(on_tok, arg_tok, 0))\n-\t\t\tadd_arg_to_input_list(on_tok, arg_tok,\n-\t\t\t\t\t      in_tok_first, in_tok_last);\n+\t\tif (check_if_different(on_tok, arg_tok, 0, head))\n+\t\t\tadd_arg_to_input_list(on_tok, arg_tok);\n \t\telse\n \t\t\tfree_trailer_item(arg_tok);\n \t\tbreak;\n \t}\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+static void apply_arg_if_missing(struct list_head *head,\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@@ -317,68 +264,60 @@ static void apply_arg_if_missing(struct trailer_item **in_tok_first,\n \t\tbreak;\n \tcase MISSING_ADD:\n \t\twhere = arg_tok->conf.where;\n-\t\tin_tok = after_or_end(where) ? in_tok_last : in_tok_first;\n \t\tapply_item_command(NULL, arg_tok);\n-\t\tif (*in_tok) {\n-\t\t\tadd_arg_to_input_list(*in_tok, arg_tok,\n-\t\t\t\t\t      in_tok_first, in_tok_last);\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\tif (after_or_end(where))\n+\t\t\tlist_add_tail(&arg_tok->list, head);\n+\t\telse\n+\t\t\tlist_add(&arg_tok->list, head);\n \t}\n }\n \n-static int find_same_and_apply_arg(struct trailer_item **in_tok_first,\n-\t\t\t\t   struct trailer_item **in_tok_last,\n+static int find_same_and_apply_arg(struct list_head *head,\n \t\t\t\t   struct trailer_item *arg_tok)\n {\n+\tstruct list_head *pos;\n \tstruct trailer_item *in_tok;\n \tstruct trailer_item *on_tok;\n-\tstruct trailer_item *following_tok;\n \n \tenum action_where where = arg_tok->conf.where;\n \tint middle = (where == WHERE_AFTER) || (where == WHERE_BEFORE);\n \tint backwards = after_or_end(where);\n-\tstruct trailer_item *start_tok = backwards ? *in_tok_last : *in_tok_first;\n+\tstruct trailer_item *start_tok;\n \n-\tfor (in_tok = start_tok; in_tok; in_tok = following_tok) {\n-\t\tfollowing_tok = backwards ? in_tok->previous : in_tok->next;\n+\tif (list_empty(head))\n+\t\treturn 0;\n+\n+\tstart_tok = list_entry(backwards ? head->prev : head->next,\n+\t\t\t       struct trailer_item,\n+\t\t\t       list);\n+\n+\tlist_for_each_dir(pos, head, backwards) {\n+\t\tin_tok = list_entry(pos, struct trailer_item, list);\n \t\tif (!same_token(in_tok, arg_tok))\n \t\t\tcontinue;\n \t\ton_tok = middle ? in_tok : start_tok;\n-\t\tapply_arg_if_exists(in_tok, arg_tok, on_tok,\n-\t\t\t\t    in_tok_first, in_tok_last);\n+\t\tapply_arg_if_exists(in_tok, arg_tok, on_tok, head);\n \t\treturn 1;\n \t}\n \treturn 0;\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+static void process_trailers_lists(struct list_head *head,\n+\t\t\t\t   struct list_head *arg_head)\n {\n+\tstruct list_head *pos, *p;\n \tstruct trailer_item *arg_tok;\n-\tstruct trailer_item *next_arg;\n-\n-\tif (!*arg_tok_first)\n-\t\treturn;\n \n-\tfor (arg_tok = *arg_tok_first; arg_tok; arg_tok = next_arg) {\n+\tlist_for_each_safe(pos, p, arg_head) {\n \t\tint applied = 0;\n+\t\targ_tok = list_entry(pos, struct trailer_item, list);\n \n-\t\tnext_arg = arg_tok->next;\n-\t\tremove_from_list(arg_tok, arg_tok_first, NULL);\n+\t\tlist_del(pos);\n \n-\t\tapplied = find_same_and_apply_arg(in_tok_first,\n-\t\t\t\t\t\t  in_tok_last,\n-\t\t\t\t\t\t  arg_tok);\n+\t\tapplied = find_same_and_apply_arg(head, arg_tok);\n \n \t\tif (!applied)\n-\t\t\tapply_arg_if_missing(in_tok_first,\n-\t\t\t\t\t     in_tok_last,\n-\t\t\t\t\t     arg_tok);\n+\t\t\tapply_arg_if_missing(head, arg_tok);\n \t}\n }\n \n@@ -438,13 +377,12 @@ static void duplicate_conf(struct conf_info *dst, const struct conf_info *src)\n \n static struct trailer_item *get_conf_item(const char *name)\n {\n+\tstruct list_head *pos;\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+\tlist_for_each(pos, &conf_head) {\n+\t\titem = list_entry(pos, struct trailer_item, list);\n \t\tif (!strcasecmp(item->conf.name, name))\n \t\t\treturn item;\n \t}\n@@ -454,12 +392,7 @@ static struct trailer_item *get_conf_item(const char *name)\n \tduplicate_conf(&item->conf, &default_conf_info);\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+\tlist_add_tail(&item->list, &conf_head);\n \n \treturn item;\n }\n@@ -633,6 +566,7 @@ static struct trailer_item *create_trailer_item(const char *string)\n \tstruct strbuf val = STRBUF_INIT;\n \tstruct trailer_item *item;\n \tint tok_len;\n+\tstruct list_head *pos;\n \n \tif (parse_trailer(&tok, &val, string))\n \t\treturn NULL;\n@@ -640,7 +574,8 @@ static struct trailer_item *create_trailer_item(const char *string)\n \ttok_len = token_len_without_separator(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+\tlist_for_each(pos, &conf_head) {\n+\t\titem = list_entry(pos, struct trailer_item, list);\n \t\tif (token_matches_item(tok.buf, item, tok_len))\n \t\t\treturn new_trailer_item(item,\n \t\t\t\t\t\tstrbuf_detach(&tok, NULL),\n@@ -652,44 +587,34 @@ static struct trailer_item *create_trailer_item(const char *string)\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+static void add_trailer_item(struct list_head *head, 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+\tlist_add_tail(&new->list, head);\n }\n \n-static struct trailer_item *process_command_line_args(struct string_list *trailers)\n+static void process_command_line_args(struct list_head *arg_head, \n+\t\t\t\t      struct string_list *trailers)\n {\n-\tstruct trailer_item *arg_tok_first = NULL;\n-\tstruct trailer_item *arg_tok_last = NULL;\n \tstruct string_list_item *tr;\n \tstruct trailer_item *item;\n+\tstruct list_head *pos;\n \n \t/* Add a trailer item for each configured trailer with a command */\n-\tfor (item = first_conf_item; item; item = item->next) {\n+\tlist_for_each(pos, &conf_head) {\n+\t\titem = list_entry(pos, struct trailer_item, list);\n \t\tif (item->conf.command) {\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\tadd_trailer_item(arg_head, new);\n \t\t}\n \t}\n \n \t/* Add a trailer item for each trailer on the command line */\n \tfor_each_string_list_item(tr, trailers) {\n \t\tstruct trailer_item *new = create_trailer_item(tr->string);\n-\t\tadd_trailer_item(&arg_tok_first, &arg_tok_last, new);\n+\t\tadd_trailer_item(arg_head, new);\n \t}\n-\n-\treturn arg_tok_first;\n }\n \n static struct strbuf **read_input_file(const char *file)\n@@ -805,8 +730,7 @@ static void print_lines(FILE *outfile, struct strbuf **lines, int start, int end\n \n static int process_input_file(FILE *outfile,\n \t\t\t      struct strbuf **lines,\n-\t\t\t      struct trailer_item **in_tok_first,\n-\t\t\t      struct trailer_item **in_tok_last)\n+\t\t\t      struct list_head *head)\n {\n \tint count = 0;\n \tint patch_start, trailer_start, trailer_end, i;\n@@ -829,18 +753,19 @@ static int process_input_file(FILE *outfile,\n \tfor (i = trailer_start; i < trailer_end; i++) {\n \t\tif (lines[i]->buf[0] != comment_line_char) {\n \t\t\tstruct trailer_item *new = create_trailer_item(lines[i]->buf);\n-\t\t\tadd_trailer_item(in_tok_first, in_tok_last, new);\n+\t\t\tadd_trailer_item(head, new);\n \t\t}\n \t}\n \n \treturn trailer_end;\n }\n \n-static void free_all(struct trailer_item **first)\n+static void free_all(struct list_head *head)\n {\n-\twhile (*first) {\n-\t\tstruct trailer_item *item = remove_first(first);\n-\t\tfree_trailer_item(item);\n+\tstruct list_head *pos, *p;\n+\tlist_for_each_safe(pos, p, head) {\n+\t\tlist_del(pos);\n+\t\tfree_trailer_item(list_entry(pos, struct trailer_item, list));\n \t}\n }\n \n@@ -877,9 +802,8 @@ static FILE *create_in_place_tempfile(const char *file)\n \n void process_trailers(const char *file, int in_place, int trim_empty, struct string_list *trailers)\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+\tLIST_HEAD(head);\n+\tLIST_HEAD(arg_head);\n \tstruct strbuf **lines;\n \tint trailer_end;\n \tFILE *outfile = stdout;\n@@ -894,15 +818,15 @@ void process_trailers(const char *file, int in_place, int trim_empty, struct str\n \t\toutfile = create_in_place_tempfile(file);\n \n \t/* Print the lines before the trailers */\n-\ttrailer_end = process_input_file(outfile, lines, &in_tok_first, &in_tok_last);\n+\ttrailer_end = process_input_file(outfile, lines, &head);\n \n-\targ_tok_first = process_command_line_args(trailers);\n+\tprocess_command_line_args(&arg_head, trailers);\n \n-\tprocess_trailers_lists(&in_tok_first, &in_tok_last, &arg_tok_first);\n+\tprocess_trailers_lists(&head, &arg_head);\n \n-\tprint_all(outfile, in_tok_first, trim_empty);\n+\tprint_all(outfile, &head, trim_empty);\n \n-\tfree_all(&in_tok_first);\n+\tfree_all(&head);\n \n \t/* Print the lines after the trailers as is */\n \tprint_lines(outfile, lines, trailer_end, INT_MAX);\n-- \n2.8.0.rc3.226.g39d4020\n\n"},{"id":"304180","messageId":"b09fb7693069a14d2ed9ec47545e49294ac3015e.1476466609.git.jonathantanmy@google.com","threadId":"44285","inReplyTo":"cover.1476466609.git.jonathantanmy@google.com","subject":"[PATCH v3 3/6] trailer: streamline trailer item create and add","fromName":"Jonathan Tan","fromEmail":"jonathantanmy@google.com","sentAt":"2016-10-14T17:38:00Z","receivedAt":"2016-10-14T17:38:29Z","isPatch":true,"sender":{"key":"jonathantanmy@fastmail.com","avatar":null},"body":"Currently, creation and addition (to a list) of trailer items are spread\nacross multiple functions. Streamline this by only having 2 functions:\none to parse the user-supplied string, and one to add the parsed\ninformation to a list.\n\nSigned-off-by: Jonathan Tan <jonathantanmy@google.com>\n---\n trailer.c | 130 +++++++++++++++++++++++++++++---------------------------------\n 1 file changed, 60 insertions(+), 70 deletions(-)\n\ndiff --git a/trailer.c b/trailer.c\nindex 0afa240..54cc930 100644\n--- a/trailer.c\n+++ b/trailer.c\n@@ -500,10 +500,31 @@ static int git_trailer_config(const char *conf_key, const char *value, void *cb)\n \treturn 0;\n }\n \n-static int parse_trailer(struct strbuf *tok, struct strbuf *val, const char *trailer)\n+static const char *token_from_item(struct trailer_item *item, char *tok)\n+{\n+\tif (item->conf.key)\n+\t\treturn item->conf.key;\n+\tif (tok)\n+\t\treturn tok;\n+\treturn item->conf.name;\n+}\n+\n+static int token_matches_item(const char *tok, struct trailer_item *item, int tok_len)\n+{\n+\tif (!strncasecmp(tok, item->conf.name, tok_len))\n+\t\treturn 1;\n+\treturn item->conf.key ? !strncasecmp(tok, item->conf.key, tok_len) : 0;\n+}\n+\n+static int parse_trailer(struct strbuf *tok, struct strbuf *val,\n+\t\t\t const struct conf_info **conf, const char *trailer)\n {\n \tsize_t len;\n \tstruct strbuf seps = STRBUF_INIT;\n+\tstruct trailer_item *item;\n+\tint tok_len;\n+\tstruct list_head *pos;\n+\n \tstrbuf_addstr(&seps, separators);\n \tstrbuf_addch(&seps, '=');\n \tlen = strcspn(trailer, seps.buf);\n@@ -523,74 +544,31 @@ static int parse_trailer(struct strbuf *tok, struct strbuf *val, const char *tra\n \t\tstrbuf_addstr(tok, trailer);\n \t\tstrbuf_trim(tok);\n \t}\n-\treturn 0;\n-}\n-\n-static const char *token_from_item(struct trailer_item *item, char *tok)\n-{\n-\tif (item->conf.key)\n-\t\treturn item->conf.key;\n-\tif (tok)\n-\t\treturn tok;\n-\treturn item->conf.name;\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 ? val : xstrdup(\"\");\n-\n-\tif (conf_item) {\n-\t\tduplicate_conf(&new->conf, &conf_item->conf);\n-\t\tnew->token = xstrdup(token_from_item(conf_item, tok));\n-\t\tfree(tok);\n-\t} else {\n-\t\tduplicate_conf(&new->conf, &default_conf_info);\n-\t\tnew->token = tok;\n-\t}\n-\n-\treturn new;\n-}\n-\n-static int token_matches_item(const char *tok, struct trailer_item *item, int tok_len)\n-{\n-\tif (!strncasecmp(tok, item->conf.name, tok_len))\n-\t\treturn 1;\n-\treturn item->conf.key ? !strncasecmp(tok, item->conf.key, tok_len) : 0;\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_len;\n-\tstruct list_head *pos;\n-\n-\tif (parse_trailer(&tok, &val, string))\n-\t\treturn NULL;\n-\n-\ttok_len = token_len_without_separator(tok.buf, tok.len);\n \n \t/* Lookup if the token matches something in the config */\n+\ttok_len = token_len_without_separator(tok->buf, tok->len);\n+\t*conf = &default_conf_info;\n \tlist_for_each(pos, &conf_head) {\n \t\titem = list_entry(pos, struct trailer_item, list);\n-\t\tif (token_matches_item(tok.buf, item, tok_len))\n-\t\t\treturn new_trailer_item(item,\n-\t\t\t\t\t\tstrbuf_detach(&tok, NULL),\n-\t\t\t\t\t\tstrbuf_detach(&val, NULL));\n+\t\tif (token_matches_item(tok->buf, item, tok_len)) {\n+\t\t\tchar *tok_buf = strbuf_detach(tok, NULL);\n+\t\t\t*conf = &item->conf;\n+\t\t\tstrbuf_addstr(tok, token_from_item(item, tok_buf));\n+\t\t\tfree(tok_buf);\n+\t\t\tbreak;\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+\treturn 0;\n }\n \n-static void add_trailer_item(struct list_head *head, struct trailer_item *new)\n+static void add_trailer_item(struct list_head *head, char *tok, char *val,\n+\t\t\t     const struct conf_info *conf)\n {\n-\tif (!new)\n-\t\treturn;\n+\tstruct trailer_item *new = xcalloc(sizeof(*new), 1);\n+\tnew->token = tok;\n+\tnew->value = val;\n+\tduplicate_conf(&new->conf, conf);\n \tlist_add_tail(&new->list, head);\n }\n \n@@ -599,21 +577,28 @@ static void process_command_line_args(struct list_head *arg_head,\n {\n \tstruct string_list_item *tr;\n \tstruct trailer_item *item;\n+\tstruct strbuf tok = STRBUF_INIT;\n+\tstruct strbuf val = STRBUF_INIT;\n+\tconst struct conf_info *conf;\n \tstruct list_head *pos;\n \n \t/* Add a trailer item for each configured trailer with a command */\n \tlist_for_each(pos, &conf_head) {\n \t\titem = list_entry(pos, struct trailer_item, list);\n-\t\tif (item->conf.command) {\n-\t\t\tstruct trailer_item *new = new_trailer_item(item, NULL, NULL);\n-\t\t\tadd_trailer_item(arg_head, new);\n-\t\t}\n+\t\tif (item->conf.command)\n+\t\t\tadd_trailer_item(arg_head,\n+\t\t\t\t\t xstrdup(token_from_item(item, NULL)),\n+\t\t\t\t\t xstrdup(\"\"),\n+\t\t\t\t\t &item->conf);\n \t}\n \n \t/* Add a trailer item for each trailer on the command line */\n \tfor_each_string_list_item(tr, trailers) {\n-\t\tstruct trailer_item *new = create_trailer_item(tr->string);\n-\t\tadd_trailer_item(arg_head, new);\n+\t\tif (!parse_trailer(&tok, &val, &conf, tr->string))\n+\t\t\tadd_trailer_item(arg_head,\n+\t\t\t\t\t strbuf_detach(&tok, NULL),\n+\t\t\t\t\t strbuf_detach(&val, NULL),\n+\t\t\t\t\t conf);\n \t}\n }\n \n@@ -734,6 +719,9 @@ static int process_input_file(FILE *outfile,\n {\n \tint count = 0;\n \tint patch_start, trailer_start, trailer_end, i;\n+\tstruct strbuf tok = STRBUF_INIT;\n+\tstruct strbuf val = STRBUF_INIT;\n+\tconst struct conf_info *conf;\n \n \t/* Get the line count */\n \twhile (lines[count])\n@@ -751,10 +739,12 @@ static int process_input_file(FILE *outfile,\n \n \t/* Parse trailer lines */\n \tfor (i = trailer_start; i < trailer_end; i++) {\n-\t\tif (lines[i]->buf[0] != comment_line_char) {\n-\t\t\tstruct trailer_item *new = create_trailer_item(lines[i]->buf);\n-\t\t\tadd_trailer_item(head, new);\n-\t\t}\n+\t\tif (lines[i]->buf[0] != comment_line_char &&\n+\t\t    !parse_trailer(&tok, &val, &conf, lines[i]->buf))\n+\t\t\tadd_trailer_item(head,\n+\t\t\t\t\t strbuf_detach(&tok, NULL),\n+\t\t\t\t\t strbuf_detach(&val, NULL),\n+\t\t\t\t\t conf);\n \t}\n \n \treturn trailer_end;\n-- \n2.8.0.rc3.226.g39d4020\n\n"},{"id":"304181","messageId":"1b3fe84e4b6126884a801e721d0a93c41fcb4184.1476466609.git.jonathantanmy@google.com","threadId":"44285","inReplyTo":"cover.1476466609.git.jonathantanmy@google.com","subject":"[PATCH v3 5/6] trailer: allow non-trailers in trailer block","fromName":"Jonathan Tan","fromEmail":"jonathantanmy@google.com","sentAt":"2016-10-14T17:38:02Z","receivedAt":"2016-10-14T17:38:30Z","isPatch":true,"sender":{"key":"jonathantanmy@fastmail.com","avatar":null},"body":"Currently, interpret-trailers requires all lines of a trailer block to\nbe trailers (or comments) - if not it would not identify that block as a\ntrailer block, and thus create its own trailer block, inserting a blank\nline.  For example:\n\n  echo -e \"\\na: b\\nnot trailer\" |\n  git interpret-trailers --trailer \"c: d\"\n\nwould result in:\n\n  a: b\n  not trailer\n\n  c: d\n\nRelax the definition of a trailer block to only require 1 trailer, so\nthat trailers can be directly added to such blocks, resulting in:\n\n  a: b\n  not trailer\n  c: d\n\nThis allows arbitrary lines to be included in trailer blocks, like those\nin [1], and still allow interpret-trailers to be used.\n\nThis change also makes comments in the trailer block be treated as any\nother non-trailer line, preserving them in the output of\ninterpret-trailers.\n\n[1]\nhttps://kernel.googlesource.com/pub/scm/linux/kernel/git/stable/linux-stable/+/e7d316a02f683864a12389f8808570e37fb90aa3\n\nSigned-off-by: Jonathan Tan <jonathantanmy@google.com>\n---\n Documentation/git-interpret-trailers.txt |  3 +-\n t/t7513-interpret-trailers.sh            | 35 +++++++++++++++\n trailer.c                                | 77 ++++++++++++++++++++++----------\n 3 files changed, 90 insertions(+), 25 deletions(-)\n\ndiff --git a/Documentation/git-interpret-trailers.txt b/Documentation/git-interpret-trailers.txt\nindex 93d1db6..c480da6 100644\n--- a/Documentation/git-interpret-trailers.txt\n+++ b/Documentation/git-interpret-trailers.txt\n@@ -48,7 +48,8 @@ with only spaces at the end of the commit message part, one blank line\n will be added before the new trailer.\n \n Existing trailers are extracted from the input message by looking for\n-a group of one or more lines that contain a colon (by default), where\n+a group of one or more lines in which at least one line contains a \n+colon (by default), where\n the group is preceded by one or more empty (or whitespace-only) lines.\n The group must either be at the end of the message or be the last\n non-whitespace lines before a line that starts with '---'. Such three\ndiff --git a/t/t7513-interpret-trailers.sh b/t/t7513-interpret-trailers.sh\nindex aee785c..7f5cd2a 100755\n--- a/t/t7513-interpret-trailers.sh\n+++ b/t/t7513-interpret-trailers.sh\n@@ -126,6 +126,37 @@ test_expect_success 'with multiline title in the message' '\n \ttest_cmp expected actual\n '\n \n+test_expect_success 'with non-trailer lines mixed with trailer lines' '\n+\tcat >patch <<-\\EOF &&\n+\n+\t\tthis: is a trailer\n+\t\tthis is not a trailer\n+\tEOF\n+\tcat >expected <<-\\EOF &&\n+\n+\t\tthis: is a trailer\n+\t\tthis is not a trailer\n+\t\ttoken: value\n+\tEOF\n+\tgit interpret-trailers --trailer \"token: value\" patch >actual &&\n+\ttest_cmp expected actual\n+'\n+\n+test_expect_success 'with non-trailer lines only' '\n+\tcat >patch <<-\\EOF &&\n+\n+\t\tthis is not a trailer\n+\tEOF\n+\tcat >expected <<-\\EOF &&\n+\n+\t\tthis is not a trailer\n+\n+\t\ttoken: value\n+\tEOF\n+\tgit interpret-trailers --trailer \"token: value\" patch >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@@ -257,6 +288,8 @@ test_expect_success 'with message that has comments' '\n \tcat >>expected <<-\\EOF &&\n \t\t# comment\n \n+\t\t# other comment\n+\t\t# yet another comment\n \t\tReviewed-by: Johan\n \t\tCc: Peff\n \t\t# last comment\n@@ -286,6 +319,8 @@ test_expect_success 'with message that has an old style conflict block' '\n \tcat >>expected <<-\\EOF &&\n \t\t# comment\n \n+\t\t# other comment\n+\t\t# yet another comment\n \t\tReviewed-by: Johan\n \t\tCc: Peff\n \t\t# last comment\ndiff --git a/trailer.c b/trailer.c\nindex a9ed3f8..d6dfc7a 100644\n--- a/trailer.c\n+++ b/trailer.c\n@@ -27,6 +27,10 @@ static struct conf_info default_conf_info;\n \n struct trailer_item {\n \tstruct list_head list;\n+\t/*\n+\t * If this is not a trailer line, the line is stored in value\n+\t * (excluding the terminating newline) and token is NULL.\n+\t */\n \tchar *token;\n \tchar *value;\n };\n@@ -70,9 +74,14 @@ static size_t token_len_without_separator(const char *token, size_t len)\n \n static int same_token(struct trailer_item *a, struct arg_item *b)\n {\n-\tsize_t a_len = token_len_without_separator(a->token, strlen(a->token));\n-\tsize_t b_len = token_len_without_separator(b->token, strlen(b->token));\n-\tsize_t min_len = (a_len > b_len) ? b_len : a_len;\n+\tsize_t a_len, b_len, min_len;\n+\n+\tif (!a->token)\n+\t\treturn 0;\n+\n+\ta_len = token_len_without_separator(a->token, strlen(a->token));\n+\tb_len = token_len_without_separator(b->token, strlen(b->token));\n+\tmin_len = (a_len > b_len) ? b_len : a_len;\n \n \treturn !strncasecmp(a->token, b->token, min_len);\n }\n@@ -130,7 +139,14 @@ static char last_non_space_char(const char *s)\n \n static void print_tok_val(FILE *outfile, const char *tok, const char *val)\n {\n-\tchar c = last_non_space_char(tok);\n+\tchar c;\n+\n+\tif (!tok) {\n+\t\tfprintf(outfile, \"%s\\n\", val);\n+\t\treturn;\n+\t}\n+\n+\tc = last_non_space_char(tok);\n \tif (!c)\n \t\treturn;\n \tif (strchr(separators, c))\n@@ -543,8 +559,16 @@ static int token_matches_item(const char *tok, struct arg_item *item, int tok_le\n \treturn item->conf.key ? !strncasecmp(tok, item->conf.key, tok_len) : 0;\n }\n \n+/*\n+ * Parse the given trailer into token and value parts.\n+ *\n+ * If the given trailer does not have a separator (and thus cannot be separated\n+ * into token and value parts), it is treated as a token (if parse_as_arg) or\n+ * as a non-trailer line (if not parse_as_arg).\n+ */\n static int parse_trailer(struct strbuf *tok, struct strbuf *val,\n-\t\t\t const struct conf_info **conf, const char *trailer)\n+\t\t\t const struct conf_info **conf, const char *trailer,\n+\t\t\t int parse_as_arg)\n {\n \tsize_t len;\n \tstruct strbuf seps = STRBUF_INIT;\n@@ -557,11 +581,18 @@ static int parse_trailer(struct strbuf *tok, struct strbuf *val,\n \tlen = strcspn(trailer, seps.buf);\n \tstrbuf_release(&seps);\n \tif (len == 0) {\n-\t\tint l = strlen(trailer);\n+\t\tint l;\n+\t\tif (!parse_as_arg)\n+\t\t\treturn -1;\n+\n+\t\tl = strlen(trailer);\n \t\twhile (l > 0 && isspace(trailer[l - 1]))\n \t\t\tl--;\n \t\treturn error(_(\"empty trailer token in trailer '%.*s'\"), l, trailer);\n \t}\n+\tif (!parse_as_arg && len == strlen(trailer))\n+\t\treturn -1;\n+\n \tif (len < strlen(trailer)) {\n \t\tstrbuf_add(tok, trailer, len);\n \t\tstrbuf_trim(tok);\n@@ -631,7 +662,7 @@ static void process_command_line_args(struct list_head *arg_head,\n \n \t/* Add an arg item for each trailer on the command line */\n \tfor_each_string_list_item(tr, trailers) {\n-\t\tif (!parse_trailer(&tok, &val, &conf, tr->string))\n+\t\tif (!parse_trailer(&tok, &val, &conf, tr->string, 1))\n \t\t\tadd_arg_item(arg_head,\n \t\t\t\t     strbuf_detach(&tok, NULL),\n \t\t\t\t     strbuf_detach(&val, NULL),\n@@ -683,7 +714,7 @@ static int find_patch_start(struct strbuf **lines, int count)\n  */\n static int find_trailer_start(struct strbuf **lines, int count)\n {\n-\tint start, end_of_title, only_spaces = 1;\n+\tint start, end_of_title, only_spaces = 1, trailer_found = 0;\n \n \t/* The first paragraph is the title and cannot be trailers */\n \tfor (start = 0; start < count; start++) {\n@@ -699,22 +730,17 @@ static int find_trailer_start(struct strbuf **lines, int count)\n \t * for a line with only spaces before lines with one separator.\n \t */\n \tfor (start = count - 1; start >= end_of_title; start--) {\n-\t\tif (lines[start]->buf[0] == comment_line_char)\n-\t\t\tcontinue;\n \t\tif (contains_only_spaces(lines[start]->buf)) {\n \t\t\tif (only_spaces)\n \t\t\t\tcontinue;\n-\t\t\treturn start + 1;\n+\t\t\treturn trailer_found ? start + 1 : count;\n \t\t}\n-\t\tif (strcspn(lines[start]->buf, separators) < lines[start]->len) {\n-\t\t\tif (only_spaces)\n-\t\t\t\tonly_spaces = 0;\n-\t\t\tcontinue;\n-\t\t}\n-\t\treturn count;\n+\t\tonly_spaces = 0;\n+\t\tif (strcspn(lines[start]->buf, separators) < lines[start]->len)\n+\t\t\ttrailer_found = 1;\n \t}\n \n-\treturn only_spaces ? count : 0;\n+\treturn count;\n }\n \n /* Get the index of the end of the trailers */\n@@ -735,11 +761,8 @@ static int find_trailer_end(struct strbuf **lines, int patch_start)\n \n static int has_blank_line_before(struct strbuf **lines, int start)\n {\n-\tfor (;start >= 0; start--) {\n-\t\tif (lines[start]->buf[0] == comment_line_char)\n-\t\t\tcontinue;\n+\tif (start >= 0)\n \t\treturn contains_only_spaces(lines[start]->buf);\n-\t}\n \treturn 0;\n }\n \n@@ -775,11 +798,17 @@ static int process_input_file(FILE *outfile,\n \n \t/* Parse trailer lines */\n \tfor (i = trailer_start; i < trailer_end; i++) {\n-\t\tif (lines[i]->buf[0] != comment_line_char &&\n-\t\t    !parse_trailer(&tok, &val, NULL, lines[i]->buf))\n+\t\tif (!parse_trailer(&tok, &val, NULL, lines[i]->buf, 0))\n \t\t\tadd_trailer_item(head,\n \t\t\t\t\t strbuf_detach(&tok, NULL),\n \t\t\t\t\t strbuf_detach(&val, NULL));\n+\t\telse {\n+\t\t\tstrbuf_addbuf(&val, lines[i]);\n+\t\t\tstrbuf_strip_suffix(&val, \"\\n\");\n+\t\t\tadd_trailer_item(head,\n+\t\t\t\t\t NULL,\n+\t\t\t\t\t strbuf_detach(&val, NULL));\n+\t\t}\n \t}\n \n \treturn trailer_end;\n-- \n2.8.0.rc3.226.g39d4020\n\n"},{"id":"304182","messageId":"0a187897454a5bce946d675f6f6c93283b411f31.1476466609.git.jonathantanmy@google.com","threadId":"44285","inReplyTo":"cover.1476466609.git.jonathantanmy@google.com","subject":"[PATCH v3 6/6] trailer: support values folded to multiple lines","fromName":"Jonathan Tan","fromEmail":"jonathantanmy@google.com","sentAt":"2016-10-14T17:38:03Z","receivedAt":"2016-10-14T17:38:33Z","isPatch":true,"sender":{"key":"jonathantanmy@fastmail.com","avatar":null},"body":"Currently, interpret-trailers requires that a trailer be only on 1 line.\nFor example:\n\n  a: first line\n     second line\n\nwould be interpreted as one trailer line followed by one non-trailer line.\n\nMake interpret-trailers support RFC 822-style folding, treating those\nlines as one logical trailer.\n\nSigned-off-by: Jonathan Tan <jonathantanmy@google.com>\n---\n Documentation/git-interpret-trailers.txt |   7 +-\n t/t7513-interpret-trailers.sh            | 139 +++++++++++++++++++++++++++++++\n trailer.c                                |  22 +++--\n 3 files changed, 160 insertions(+), 8 deletions(-)\n\ndiff --git a/Documentation/git-interpret-trailers.txt b/Documentation/git-interpret-trailers.txt\nindex c480da6..cfec636 100644\n--- a/Documentation/git-interpret-trailers.txt\n+++ b/Documentation/git-interpret-trailers.txt\n@@ -57,11 +57,12 @@ minus signs start the patch part of the message.\n \n When reading trailers, there can be whitespaces before and after the\n token, the separator and the value. There can also be whitespaces\n-inside the token and the value.\n+inside the token and the value. The value may be split over multiple lines with\n+each subsequent line starting with whitespace, like the \"folding\" in RFC 822.\n \n Note that 'trailers' do not follow and are not intended to follow many\n-rules for RFC 822 headers. For example they do not follow the line\n-folding rules, the encoding rules and probably many other rules.\n+rules for RFC 822 headers. For example they do not follow\n+the encoding rules and probably many other rules.\n \n OPTIONS\n -------\ndiff --git a/t/t7513-interpret-trailers.sh b/t/t7513-interpret-trailers.sh\nindex 7f5cd2a..31db699 100755\n--- a/t/t7513-interpret-trailers.sh\n+++ b/t/t7513-interpret-trailers.sh\n@@ -157,6 +157,145 @@ test_expect_success 'with non-trailer lines only' '\n \ttest_cmp expected actual\n '\n \n+test_expect_success 'multiline field treated as atomic for placement' '\n+\tq_to_tab >patch <<-\\EOF &&\n+\n+\t\tanother: trailer\n+\t\tname: value on\n+\t\tQmultiple lines\n+\t\tanother: trailer\n+\tEOF\n+\tq_to_tab >expected <<-\\EOF &&\n+\n+\t\tanother: trailer\n+\t\tname: value on\n+\t\tQmultiple lines\n+\t\tname: value\n+\t\tanother: trailer\n+\tEOF\n+\ttest_config trailer.name.where after &&\n+\tgit interpret-trailers --trailer \"name: value\" patch >actual &&\n+\ttest_cmp expected actual\n+'\n+\n+test_expect_success 'multiline field treated as atomic for replacement' '\n+\tq_to_tab >patch <<-\\EOF &&\n+\n+\t\tanother: trailer\n+\t\tname: value on\n+\t\tQmultiple lines\n+\t\tanother: trailer\n+\tEOF\n+\tq_to_tab >expected <<-\\EOF &&\n+\n+\t\tanother: trailer\n+\t\tanother: trailer\n+\t\tname: value\n+\tEOF\n+\ttest_config trailer.name.ifexists replace &&\n+\tgit interpret-trailers --trailer \"name: value\" patch >actual &&\n+\ttest_cmp expected actual\n+'\n+\n+test_expect_success 'multiline field treated as atomic for difference check' '\n+\tq_to_tab >patch <<-\\EOF &&\n+\n+\t\tanother: trailer\n+\t\tname: first line\n+\t\tQsecond line\n+\t\tanother: trailer\n+\tEOF\n+\ttest_config trailer.name.ifexists addIfDifferent &&\n+\n+\tq_to_tab >trailer <<-\\EOF &&\n+\t\tname: first line\n+\t\tQsecond line\n+\tEOF\n+\tq_to_tab >expected <<-\\EOF &&\n+\n+\t\tanother: trailer\n+\t\tname: first line\n+\t\tQsecond line\n+\t\tanother: trailer\n+\tEOF\n+\tgit interpret-trailers --trailer \"$(cat trailer)\" patch >actual &&\n+\ttest_cmp expected actual &&\n+\n+\tq_to_tab >trailer <<-\\EOF &&\n+\t\tname: first line\n+\t\tQQQQQsecond line\n+\tEOF\n+\tq_to_tab >expected <<-\\EOF &&\n+\n+\t\tanother: trailer\n+\t\tname: first line\n+\t\tQsecond line\n+\t\tanother: trailer\n+\t\tname: first line\n+\t\tQQQQQsecond line\n+\tEOF\n+\tgit interpret-trailers --trailer \"$(cat trailer)\" patch >actual &&\n+\ttest_cmp expected actual &&\n+\n+\tq_to_tab >trailer <<-\\EOF &&\n+\t\tname: first line *DIFFERENT*\n+\t\tQsecond line\n+\tEOF\n+\tq_to_tab >expected <<-\\EOF &&\n+\n+\t\tanother: trailer\n+\t\tname: first line\n+\t\tQsecond line\n+\t\tanother: trailer\n+\t\tname: first line *DIFFERENT*\n+\t\tQsecond line\n+\tEOF\n+\tgit interpret-trailers --trailer \"$(cat trailer)\" patch >actual &&\n+\ttest_cmp expected actual\n+'\n+\n+test_expect_success 'multiline field treated as atomic for neighbor check' '\n+\tq_to_tab >patch <<-\\EOF &&\n+\n+\t\tanother: trailer\n+\t\tname: first line\n+\t\tQsecond line\n+\t\tanother: trailer\n+\tEOF\n+\ttest_config trailer.name.where after &&\n+\ttest_config trailer.name.ifexists addIfDifferentNeighbor &&\n+\n+\tq_to_tab >trailer <<-\\EOF &&\n+\t\tname: first line\n+\t\tQsecond line\n+\tEOF\n+\tq_to_tab >expected <<-\\EOF &&\n+\n+\t\tanother: trailer\n+\t\tname: first line\n+\t\tQsecond line\n+\t\tanother: trailer\n+\tEOF\n+\tgit interpret-trailers --trailer \"$(cat trailer)\" patch >actual &&\n+\ttest_cmp expected actual &&\n+\n+\tq_to_tab >trailer <<-\\EOF &&\n+\t\tname: first line\n+\t\tQQQQQsecond line\n+\tEOF\n+\tq_to_tab >expected <<-\\EOF &&\n+\n+\t\tanother: trailer\n+\t\tname: first line\n+\t\tQsecond line\n+\t\tname: first line\n+\t\tQQQQQsecond line\n+\t\tanother: trailer\n+\tEOF\n+\tgit interpret-trailers --trailer \"$(cat trailer)\" patch >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 &&\ndiff --git a/trailer.c b/trailer.c\nindex d6dfc7a..ef276e6 100644\n--- a/trailer.c\n+++ b/trailer.c\n@@ -248,7 +248,7 @@ static void apply_item_command(struct trailer_item *in_tok, struct arg_item *arg\n \t\tif (arg_tok->value && arg_tok->value[0]) {\n \t\t\targ = arg_tok->value;\n \t\t} else {\n-\t\t\tif (in_tok && in_tok->value)\n+\t\t\tif (in_tok)\n \t\t\t\targ = xstrdup(in_tok->value);\n \t\t\telse\n \t\t\t\targ = xstrdup(\"\");\n@@ -622,12 +622,14 @@ static int parse_trailer(struct strbuf *tok, struct strbuf *val,\n \treturn 0;\n }\n \n-static void add_trailer_item(struct list_head *head, char *tok, char *val)\n+static struct trailer_item *add_trailer_item(struct list_head *head, char *tok,\n+\t\t\t\t\t     char *val)\n {\n \tstruct trailer_item *new = xcalloc(sizeof(*new), 1);\n \tnew->token = tok;\n \tnew->value = val;\n \tlist_add_tail(&new->list, head);\n+\treturn new;\n }\n \n static void add_arg_item(struct list_head *arg_head, char *tok, char *val,\n@@ -781,6 +783,7 @@ static int process_input_file(FILE *outfile,\n \tint patch_start, trailer_start, trailer_end, i;\n \tstruct strbuf tok = STRBUF_INIT;\n \tstruct strbuf val = STRBUF_INIT;\n+\tstruct trailer_item *last = NULL;\n \n \t/* Get the line count */\n \twhile (lines[count])\n@@ -798,16 +801,25 @@ static int process_input_file(FILE *outfile,\n \n \t/* Parse trailer lines */\n \tfor (i = trailer_start; i < trailer_end; i++) {\n+\t\tif (last && isspace(lines[i]->buf[0])) {\n+\t\t\tstruct strbuf sb = STRBUF_INIT;\n+\t\t\tstrbuf_addf(&sb, \"%s\\n%s\", last->value, lines[i]->buf);\n+\t\t\tstrbuf_strip_suffix(&sb, \"\\n\");\n+\t\t\tfree(last->value);\n+\t\t\tlast->value = strbuf_detach(&sb, NULL);\n+\t\t\tcontinue;\n+\t\t}\n \t\tif (!parse_trailer(&tok, &val, NULL, lines[i]->buf, 0))\n-\t\t\tadd_trailer_item(head,\n-\t\t\t\t\t strbuf_detach(&tok, NULL),\n-\t\t\t\t\t strbuf_detach(&val, NULL));\n+\t\t\tlast = add_trailer_item(head,\n+\t\t\t\t\t\tstrbuf_detach(&tok, NULL),\n+\t\t\t\t\t\tstrbuf_detach(&val, NULL));\n \t\telse {\n \t\t\tstrbuf_addbuf(&val, lines[i]);\n \t\t\tstrbuf_strip_suffix(&val, \"\\n\");\n \t\t\tadd_trailer_item(head,\n \t\t\t\t\t NULL,\n \t\t\t\t\t strbuf_detach(&val, NULL));\n+\t\t\tlast = NULL;\n \t\t}\n \t}\n \n-- \n2.8.0.rc3.226.g39d4020\n\n"},{"id":"304183","messageId":"2357e9e6418fc5b30a60efe16dd8f72bf7544fef.1476466609.git.jonathantanmy@google.com","threadId":"44285","inReplyTo":"cover.1476466609.git.jonathantanmy@google.com","subject":"[PATCH v3 4/6] trailer: make args have their own struct","fromName":"Jonathan Tan","fromEmail":"jonathantanmy@google.com","sentAt":"2016-10-14T17:38:01Z","receivedAt":"2016-10-14T17:38:36Z","isPatch":true,"sender":{"key":"jonathantanmy@fastmail.com","avatar":null},"body":"Improve type safety by making arguments (whether from configuration or\nfrom the command line) have their own \"struct arg_item\" type, separate\nfrom the \"struct trailer_item\" type used to represent the trailers in\nthe buffer being manipulated.\n\nThis change also prepares \"struct trailer_item\" to be further\ndifferentiated from \"struct arg_item\" in future patches.\n\nSigned-off-by: Jonathan Tan <jonathantanmy@google.com>\n---\n trailer.c | 135 +++++++++++++++++++++++++++++++++++++++-----------------------\n 1 file changed, 85 insertions(+), 50 deletions(-)\n\ndiff --git a/trailer.c b/trailer.c\nindex 54cc930..a9ed3f8 100644\n--- a/trailer.c\n+++ b/trailer.c\n@@ -29,6 +29,12 @@ struct trailer_item {\n \tstruct list_head list;\n \tchar *token;\n \tchar *value;\n+};\n+\n+struct arg_item {\n+\tstruct list_head list;\n+\tchar *token;\n+\tchar *value;\n \tstruct conf_info conf;\n };\n \n@@ -62,7 +68,7 @@ static size_t token_len_without_separator(const char *token, size_t len)\n \treturn len;\n }\n \n-static int same_token(struct trailer_item *a, struct trailer_item *b)\n+static int same_token(struct trailer_item *a, struct arg_item *b)\n {\n \tsize_t a_len = token_len_without_separator(a->token, strlen(a->token));\n \tsize_t b_len = token_len_without_separator(b->token, strlen(b->token));\n@@ -71,12 +77,12 @@ static int same_token(struct trailer_item *a, struct trailer_item *b)\n \treturn !strncasecmp(a->token, b->token, min_len);\n }\n \n-static int same_value(struct trailer_item *a, struct trailer_item *b)\n+static int same_value(struct trailer_item *a, struct arg_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)\n+static int same_trailer(struct trailer_item *a, struct arg_item *b)\n {\n \treturn same_token(a, b) && same_value(a, b);\n }\n@@ -98,6 +104,13 @@ static inline void strbuf_replace(struct strbuf *sb, const char *a, const char *\n \n static void free_trailer_item(struct trailer_item *item)\n {\n+\tfree(item->token);\n+\tfree(item->value);\n+\tfree(item);\n+}\n+\n+static void free_arg_item(struct arg_item *item)\n+{\n \tfree(item->conf.name);\n \tfree(item->conf.key);\n \tfree(item->conf.command);\n@@ -137,17 +150,29 @@ static void print_all(FILE *outfile, struct list_head *head, int trim_empty)\n \t}\n }\n \n+static struct trailer_item *trailer_from_arg(struct arg_item *arg_tok)\n+{\n+\tstruct trailer_item *new = xcalloc(sizeof(*new), 1);\n+\tnew->token = arg_tok->token;\n+\tnew->value = arg_tok->value;\n+\targ_tok->token = arg_tok->value = NULL;\n+\tfree_arg_item(arg_tok);\n+\treturn new;\n+}\n+\n static void add_arg_to_input_list(struct trailer_item *on_tok,\n-\t\t\t\t  struct trailer_item *arg_tok)\n+\t\t\t\t  struct arg_item *arg_tok)\n {\n-\tif (after_or_end(arg_tok->conf.where))\n-\t\tlist_add(&arg_tok->list, &on_tok->list);\n+\tint aoe = after_or_end(arg_tok->conf.where);\n+\tstruct trailer_item *to_add = trailer_from_arg(arg_tok);\n+\tif (aoe)\n+\t\tlist_add(&to_add->list, &on_tok->list);\n \telse\n-\t\tlist_add_tail(&arg_tok->list, &on_tok->list);\n+\t\tlist_add_tail(&to_add->list, &on_tok->list);\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      struct arg_item *arg_tok,\n \t\t\t      int check_all,\n \t\t\t      struct list_head *head)\n {\n@@ -200,7 +225,7 @@ static char *apply_command(const char *command, const char *arg)\n \treturn result;\n }\n \n-static void apply_item_command(struct trailer_item *in_tok, struct trailer_item *arg_tok)\n+static void apply_item_command(struct trailer_item *in_tok, struct arg_item *arg_tok)\n {\n \tif (arg_tok->conf.command) {\n \t\tconst char *arg;\n@@ -218,13 +243,13 @@ static void apply_item_command(struct trailer_item *in_tok, struct trailer_item\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\tstruct arg_item *arg_tok,\n \t\t\t\tstruct trailer_item *on_tok,\n \t\t\t\tstruct list_head *head)\n {\n \tswitch (arg_tok->conf.if_exists) {\n \tcase EXISTS_DO_NOTHING:\n-\t\tfree_trailer_item(arg_tok);\n+\t\tfree_arg_item(arg_tok);\n \t\tbreak;\n \tcase EXISTS_REPLACE:\n \t\tapply_item_command(in_tok, arg_tok);\n@@ -241,39 +266,41 @@ static void apply_arg_if_exists(struct trailer_item *in_tok,\n \t\tif (check_if_different(in_tok, arg_tok, 1, head))\n \t\t\tadd_arg_to_input_list(on_tok, arg_tok);\n \t\telse\n-\t\t\tfree_trailer_item(arg_tok);\n+\t\t\tfree_arg_item(arg_tok);\n \t\tbreak;\n \tcase EXISTS_ADD_IF_DIFFERENT_NEIGHBOR:\n \t\tapply_item_command(in_tok, arg_tok);\n \t\tif (check_if_different(on_tok, arg_tok, 0, head))\n \t\t\tadd_arg_to_input_list(on_tok, arg_tok);\n \t\telse\n-\t\t\tfree_trailer_item(arg_tok);\n+\t\t\tfree_arg_item(arg_tok);\n \t\tbreak;\n \t}\n }\n \n static void apply_arg_if_missing(struct list_head *head,\n-\t\t\t\t struct trailer_item *arg_tok)\n+\t\t\t\t struct arg_item *arg_tok)\n {\n \tenum action_where where;\n+\tstruct trailer_item *to_add;\n \n \tswitch (arg_tok->conf.if_missing) {\n \tcase MISSING_DO_NOTHING:\n-\t\tfree_trailer_item(arg_tok);\n+\t\tfree_arg_item(arg_tok);\n \t\tbreak;\n \tcase MISSING_ADD:\n \t\twhere = arg_tok->conf.where;\n \t\tapply_item_command(NULL, arg_tok);\n+\t\tto_add = trailer_from_arg(arg_tok);\n \t\tif (after_or_end(where))\n-\t\t\tlist_add_tail(&arg_tok->list, head);\n+\t\t\tlist_add_tail(&to_add->list, head);\n \t\telse\n-\t\t\tlist_add(&arg_tok->list, head);\n+\t\t\tlist_add(&to_add->list, head);\n \t}\n }\n \n static int find_same_and_apply_arg(struct list_head *head,\n-\t\t\t\t   struct trailer_item *arg_tok)\n+\t\t\t\t   struct arg_item *arg_tok)\n {\n \tstruct list_head *pos;\n \tstruct trailer_item *in_tok;\n@@ -306,11 +333,11 @@ static void process_trailers_lists(struct list_head *head,\n \t\t\t\t   struct list_head *arg_head)\n {\n \tstruct list_head *pos, *p;\n-\tstruct trailer_item *arg_tok;\n+\tstruct arg_item *arg_tok;\n \n \tlist_for_each_safe(pos, p, arg_head) {\n \t\tint applied = 0;\n-\t\targ_tok = list_entry(pos, struct trailer_item, list);\n+\t\targ_tok = list_entry(pos, struct arg_item, list);\n \n \t\tlist_del(pos);\n \n@@ -375,20 +402,20 @@ static void duplicate_conf(struct conf_info *dst, const struct conf_info *src)\n \t\tdst->command = xstrdup(src->command);\n }\n \n-static struct trailer_item *get_conf_item(const char *name)\n+static struct arg_item *get_conf_item(const char *name)\n {\n \tstruct list_head *pos;\n-\tstruct trailer_item *item;\n+\tstruct arg_item *item;\n \n \t/* Look up item with same name */\n \tlist_for_each(pos, &conf_head) {\n-\t\titem = list_entry(pos, struct trailer_item, list);\n+\t\titem = list_entry(pos, struct arg_item, list);\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 = xcalloc(sizeof(*item), 1);\n \tduplicate_conf(&item->conf, &default_conf_info);\n \titem->conf.name = xstrdup(name);\n \n@@ -442,7 +469,7 @@ static int git_trailer_default_config(const char *conf_key, const char *value, v\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 arg_item *item;\n \tstruct conf_info *conf;\n \tchar *name = NULL;\n \tenum trailer_info_type type;\n@@ -500,7 +527,7 @@ static int git_trailer_config(const char *conf_key, const char *value, void *cb)\n \treturn 0;\n }\n \n-static const char *token_from_item(struct trailer_item *item, char *tok)\n+static const char *token_from_item(struct arg_item *item, char *tok)\n {\n \tif (item->conf.key)\n \t\treturn item->conf.key;\n@@ -509,7 +536,7 @@ static const char *token_from_item(struct trailer_item *item, char *tok)\n \treturn item->conf.name;\n }\n \n-static int token_matches_item(const char *tok, struct trailer_item *item, int tok_len)\n+static int token_matches_item(const char *tok, struct arg_item *item, int tok_len)\n {\n \tif (!strncasecmp(tok, item->conf.name, tok_len))\n \t\treturn 1;\n@@ -521,7 +548,7 @@ static int parse_trailer(struct strbuf *tok, struct strbuf *val,\n {\n \tsize_t len;\n \tstruct strbuf seps = STRBUF_INIT;\n-\tstruct trailer_item *item;\n+\tstruct arg_item *item;\n \tint tok_len;\n \tstruct list_head *pos;\n \n@@ -547,12 +574,14 @@ static int parse_trailer(struct strbuf *tok, struct strbuf *val,\n \n \t/* Lookup if the token matches something in the config */\n \ttok_len = token_len_without_separator(tok->buf, tok->len);\n-\t*conf = &default_conf_info;\n+\tif (conf)\n+\t\t*conf = &default_conf_info;\n \tlist_for_each(pos, &conf_head) {\n-\t\titem = list_entry(pos, struct trailer_item, list);\n+\t\titem = list_entry(pos, struct arg_item, list);\n \t\tif (token_matches_item(tok->buf, item, tok_len)) {\n \t\t\tchar *tok_buf = strbuf_detach(tok, NULL);\n-\t\t\t*conf = &item->conf;\n+\t\t\tif (conf)\n+\t\t\t\t*conf = &item->conf;\n \t\t\tstrbuf_addstr(tok, token_from_item(item, tok_buf));\n \t\t\tfree(tok_buf);\n \t\t\tbreak;\n@@ -562,43 +591,51 @@ static int parse_trailer(struct strbuf *tok, struct strbuf *val,\n \treturn 0;\n }\n \n-static void add_trailer_item(struct list_head *head, char *tok, char *val,\n-\t\t\t     const struct conf_info *conf)\n+static void add_trailer_item(struct list_head *head, char *tok, char *val)\n {\n \tstruct trailer_item *new = xcalloc(sizeof(*new), 1);\n \tnew->token = tok;\n \tnew->value = val;\n-\tduplicate_conf(&new->conf, conf);\n \tlist_add_tail(&new->list, head);\n }\n \n+static void add_arg_item(struct list_head *arg_head, char *tok, char *val,\n+\t\t\t const struct conf_info *conf)\n+{\n+\tstruct arg_item *new = xcalloc(sizeof(*new), 1);\n+\tnew->token = tok;\n+\tnew->value = val;\n+\tduplicate_conf(&new->conf, conf);\n+\tlist_add_tail(&new->list, arg_head);\n+}\n+\n static void process_command_line_args(struct list_head *arg_head, \n \t\t\t\t      struct string_list *trailers)\n {\n \tstruct string_list_item *tr;\n-\tstruct trailer_item *item;\n+\tstruct arg_item *item;\n \tstruct strbuf tok = STRBUF_INIT;\n \tstruct strbuf val = STRBUF_INIT;\n \tconst struct conf_info *conf;\n \tstruct list_head *pos;\n \n-\t/* Add a trailer item for each configured trailer with a command */\n+\t/* Add an arg item for each configured trailer with a command */\n \tlist_for_each(pos, &conf_head) {\n-\t\titem = list_entry(pos, struct trailer_item, list);\n+\t\titem = list_entry(pos, struct arg_item, list);\n \t\tif (item->conf.command)\n-\t\t\tadd_trailer_item(arg_head,\n-\t\t\t\t\t xstrdup(token_from_item(item, NULL)),\n-\t\t\t\t\t xstrdup(\"\"),\n-\t\t\t\t\t &item->conf);\n+\t\t\tadd_arg_item(arg_head,\n+\t\t\t\t     xstrdup(token_from_item(item, NULL)),\n+\t\t\t\t     xstrdup(\"\"),\n+\t\t\t\t     &item->conf);\n \t}\n \n-\t/* Add a trailer item for each trailer on the command line */\n+\t/* Add an arg item for each trailer on the command line */\n \tfor_each_string_list_item(tr, trailers) {\n \t\tif (!parse_trailer(&tok, &val, &conf, tr->string))\n-\t\t\tadd_trailer_item(arg_head,\n-\t\t\t\t\t strbuf_detach(&tok, NULL),\n-\t\t\t\t\t strbuf_detach(&val, NULL),\n-\t\t\t\t\t conf);\n+\t\t\tadd_arg_item(arg_head,\n+\t\t\t\t     strbuf_detach(&tok, NULL),\n+\t\t\t\t     strbuf_detach(&val, NULL),\n+\t\t\t\t     conf);\n \t}\n }\n \n@@ -721,7 +758,6 @@ static int process_input_file(FILE *outfile,\n \tint patch_start, trailer_start, trailer_end, i;\n \tstruct strbuf tok = STRBUF_INIT;\n \tstruct strbuf val = STRBUF_INIT;\n-\tconst struct conf_info *conf;\n \n \t/* Get the line count */\n \twhile (lines[count])\n@@ -740,11 +776,10 @@ static int process_input_file(FILE *outfile,\n \t/* Parse trailer lines */\n \tfor (i = trailer_start; i < trailer_end; i++) {\n \t\tif (lines[i]->buf[0] != comment_line_char &&\n-\t\t    !parse_trailer(&tok, &val, &conf, lines[i]->buf))\n+\t\t    !parse_trailer(&tok, &val, NULL, lines[i]->buf))\n \t\t\tadd_trailer_item(head,\n \t\t\t\t\t strbuf_detach(&tok, NULL),\n-\t\t\t\t\t strbuf_detach(&val, NULL),\n-\t\t\t\t\t conf);\n+\t\t\t\t\t strbuf_detach(&val, NULL));\n \t}\n \n \treturn trailer_end;\n-- \n2.8.0.rc3.226.g39d4020\n\n"},{"id":"304191","messageId":"xmqq7f9azsze.fsf@gitster.mtv.corp.google.com","threadId":"44285","inReplyTo":"a7b9f79ea1b61b80d1177740d5d78fcca848a4f6.1476314576.git.jonathantanmy@google.com","subject":"Re: [PATCH v2 2/6] trailer: use list.h for doubly-linked list","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2016-10-14T18:27:17Z","receivedAt":"2016-10-16T00:33:40Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Jonathan Tan <jonathantanmy@google.com> writes:\n\n> Replace the existing handwritten implementation of a doubly-linked list\n> in trailer.c with the functions and macros from list.h. This\n> significantly simplifies the code.\n> ---\n\nThe handcrafted one in trailer.c somehow did not use the common\npractice of using a doubly-linked cycle as a doubly-linked list with\na designated fake element as the pointers to the first and to the\nlast elements of the list (instead it used NULL as the \"this is the\nend in this direction\" convention just like a common singly-linked\nlist), and this update removes the need for special cases handling\nthe elements at the beginning and at the end that comes from that\nchoice by switching to list.h macros.  update_last/update_first can\ngo, two parameters that were passed to point at the variables for\nthe beginning and the end can go, the special cases for the initial\ncondition in add_trailer_item() can go, all thanks to this change.\n\nVery nice.\n\n>  trailer.c | 258 ++++++++++++++++++++++----------------------------------------\n>  1 file changed, 91 insertions(+), 167 deletions(-)\n"},{"id":"304216","messageId":"2e688562-56d2-96d5-0fe7-bf140a7f7cba@gmail.com","threadId":"44285","inReplyTo":"a7b9f79ea1b61b80d1177740d5d78fcca848a4f6.1476314576.git.jonathantanmy@google.com","subject":"Re: [PATCH v2 2/6] trailer: use list.h for doubly-linked list","fromName":"Jakub Narębski","fromEmail":"jnareb@gmail.com","sentAt":"2016-10-14T17:29:13Z","receivedAt":"2016-10-16T00:34:27Z","isPatch":true,"sender":{"key":"jnareb@gmail.com","avatar":"https://avatars.githubusercontent.com/u/2706?v=4"},"body":"W dniu 13.10.2016 o 01:40, Jonathan Tan pisze:\n> Replace the existing handwritten implementation of a doubly-linked list\n> in trailer.c with the functions and macros from list.h. This\n> significantly simplifies the code.\n> ---\n>  trailer.c | 258 ++++++++++++++++++++++----------------------------------------\n>  1 file changed, 91 insertions(+), 167 deletions(-)\n\nVery nice gains!\n\nBTW. could you sign (Signed-off-by) your patches? TIA.\n\nBest,\n-- \nJakub Narębski\n\n"},{"id":"304319","messageId":"CAGZ79kY80ysSfM-fsZwpHq=SUSmvfc3YUZSR54mRN2q-1OzOVA@mail.gmail.com","threadId":"44285","inReplyTo":"701ba7164b587a97ef380bc981c95c5d51db6781.1476466609.git.jonathantanmy@google.com","subject":"Re: [PATCH v3 1/6] trailer: improve const correctness","fromName":"Stefan Beller","fromEmail":"sbeller@google.com","sentAt":"2016-10-17T22:49:21Z","receivedAt":"2016-10-17T22:49:26Z","isPatch":true,"sender":{"key":"stefanbeller@gmail.com","avatar":"https://avatars.githubusercontent.com/u/455868?v=4"},"body":"On Fri, Oct 14, 2016 at 10:37 AM, Jonathan Tan <jonathantanmy@google.com> wrote:\n> Change \"const char *\" to \"char *\" in struct trailer_item and in the\n> return value of apply_command (since those strings are owned strings).\n>\n> Change \"struct conf_info *\" to \"const struct conf_info *\" (since that\n> struct is not modified).\n>\n> Signed-off-by: Jonathan Tan <jonathantanmy@google.com>\n\nReviewed-by Stefan Beller <sbeller@google.com>\n\n> ---\n>  trailer.c | 14 +++++++-------\n>  1 file changed, 7 insertions(+), 7 deletions(-)\n>\n> diff --git a/trailer.c b/trailer.c\n> index c6ea9ac..1f191b2 100644\n> --- a/trailer.c\n> +++ b/trailer.c\n> @@ -27,8 +27,8 @@ static struct conf_info default_conf_info;\n>  struct trailer_item {\n>         struct trailer_item *previous;\n>         struct trailer_item *next;\n> -       const char *token;\n> -       const char *value;\n> +       char *token;\n> +       char *value;\n>         struct conf_info conf;\n>  };\n>\n> @@ -95,8 +95,8 @@ static void free_trailer_item(struct trailer_item *item)\n>         free(item->conf.name);\n>         free(item->conf.key);\n>         free(item->conf.command);\n> -       free((char *)item->token);\n> -       free((char *)item->value);\n> +       free(item->token);\n> +       free(item->value);\n>         free(item);\n>  }\n>\n> @@ -215,13 +215,13 @@ static struct trailer_item *remove_first(struct trailer_item **first)\n>         return item;\n>  }\n>\n> -static const char *apply_command(const char *command, const char *arg)\n> +static char *apply_command(const char *command, const char *arg)\n>  {\n>         struct strbuf cmd = STRBUF_INIT;\n>         struct strbuf buf = STRBUF_INIT;\n>         struct child_process cp = CHILD_PROCESS_INIT;\n>         const char *argv[] = {NULL, NULL};\n> -       const char *result;\n> +       char *result;\n>\n>         strbuf_addstr(&cmd, command);\n>         if (arg)\n> @@ -425,7 +425,7 @@ static int set_if_missing(struct conf_info *item, const char *value)\n>         return 0;\n>  }\n>\n> -static void duplicate_conf(struct conf_info *dst, struct conf_info *src)\n> +static void duplicate_conf(struct conf_info *dst, const struct conf_info *src)\n>  {\n>         *dst = *src;\n>         if (src->name)\n> --\n> 2.8.0.rc3.226.g39d4020\n>\n"},{"id":"304321","messageId":"CAGZ79kZWQ7tBck1V_feYiAa-sTW0UC9j51AKzO3QrJBw-jdzvw@mail.gmail.com","threadId":"44285","inReplyTo":"b09fb7693069a14d2ed9ec47545e49294ac3015e.1476466609.git.jonathantanmy@google.com","subject":"Re: [PATCH v3 3/6] trailer: streamline trailer item create and add","fromName":"Stefan Beller","fromEmail":"sbeller@google.com","sentAt":"2016-10-17T23:01:43Z","receivedAt":"2016-10-17T23:02:13Z","isPatch":true,"sender":{"key":"stefanbeller@gmail.com","avatar":"https://avatars.githubusercontent.com/u/455868?v=4"},"body":"On Fri, Oct 14, 2016 at 10:38 AM, Jonathan Tan <jonathantanmy@google.com> wrote:\n> Currently, creation and addition (to a list) of trailer items are spread\n> across multiple functions. Streamline this by only having 2 functions:\n> one to parse the user-supplied string, and one to add the parsed\n> information to a list.\n>\n> Signed-off-by: Jonathan Tan <jonathantanmy@google.com>\n\nReviewed-by: Stefan Beller <sbeller@google.com>\n"},{"id":"304322","messageId":"CAGZ79kYuaHvO-aXeCocDseTLe13g=PesKxQgOrcOREtM1CsWcg@mail.gmail.com","threadId":"44285","inReplyTo":"2357e9e6418fc5b30a60efe16dd8f72bf7544fef.1476466609.git.jonathantanmy@google.com","subject":"Re: [PATCH v3 4/6] trailer: make args have their own struct","fromName":"Stefan Beller","fromEmail":"sbeller@google.com","sentAt":"2016-10-17T23:20:05Z","receivedAt":"2016-10-17T23:20:12Z","isPatch":true,"sender":{"key":"stefanbeller@gmail.com","avatar":"https://avatars.githubusercontent.com/u/455868?v=4"},"body":"On Fri, Oct 14, 2016 at 10:38 AM, Jonathan Tan <jonathantanmy@google.com> wrote:\n> Improve type safety by making arguments (whether from configuration or\n> from the command line) have their own \"struct arg_item\" type, separate\n> from the \"struct trailer_item\" type used to represent the trailers in\n> the buffer being manipulated.\n>\n> This change also prepares \"struct trailer_item\" to be further\n> differentiated from \"struct arg_item\" in future patches.\n>\n> Signed-off-by: Jonathan Tan <jonathantanmy@google.com>\n> ---\n>  trailer.c | 135 +++++++++++++++++++++++++++++++++++++++-----------------------\n>  1 file changed, 85 insertions(+), 50 deletions(-)\n>\n> diff --git a/trailer.c b/trailer.c\n> index 54cc930..a9ed3f8 100644\n> --- a/trailer.c\n> +++ b/trailer.c\n> @@ -29,6 +29,12 @@ struct trailer_item {\n>         struct list_head list;\n>         char *token;\n>         char *value;\n> +};\n> +\n> +struct arg_item {\n> +       struct list_head list;\n> +       char *token;\n> +       char *value;\n>         struct conf_info conf;\n>  };\n\n(Unrelated side note:) When first seeing this diff, I assumed the diff\nheuristic is going wild, because it doesn't add a full struct.\nBut on a second closer look, I realize this is the only correct diff,\nbecause we do not account for moved lines from one struct to the\nother.\n\n\n>  static void add_arg_to_input_list(struct trailer_item *on_tok,\n> -                                 struct trailer_item *arg_tok)\n> +                                 struct arg_item *arg_tok)\n>  {\n> -       if (after_or_end(arg_tok->conf.where))\n> -               list_add(&arg_tok->list, &on_tok->list);\n> +       int aoe = after_or_end(arg_tok->conf.where);\n> +       struct trailer_item *to_add = trailer_from_arg(arg_tok);\n> +       if (aoe)\n\nThe use of an extra variable here is more readable than my\nimagined version of inlining to_add into the list_add calls\njust to save aoe.\n\nLooks good to me.\n\nStefan\n"},{"id":"304323","messageId":"CAGZ79kYLq1qA4_Qg2x5Fiu5AmGBZdozm4zk6K7LkU+uJ1LNUTw@mail.gmail.com","threadId":"44285","inReplyTo":"1b3fe84e4b6126884a801e721d0a93c41fcb4184.1476466609.git.jonathantanmy@google.com","subject":"Re: [PATCH v3 5/6] trailer: allow non-trailers in trailer block","fromName":"Stefan Beller","fromEmail":"sbeller@google.com","sentAt":"2016-10-18T00:49:19Z","receivedAt":"2016-10-18T00:49:26Z","isPatch":true,"sender":{"key":"stefanbeller@gmail.com","avatar":"https://avatars.githubusercontent.com/u/455868?v=4"},"body":"On Fri, Oct 14, 2016 at 10:38 AM, Jonathan Tan <jonathantanmy@google.com> wrote:\n>\n>  Existing trailers are extracted from the input message by looking for\n> -a group of one or more lines that contain a colon (by default), where\n> +a group of one or more lines in which at least one line contains a\n> +colon (by default), where\n\nPlease see commit\n578e6021c0819d7be1179e05e7ce0e6fdb2a01b7\nfor an example where I think this is overly broad.\n\nAnother made up example, that I'd want to feed\nin commit -s eventually:\n\n--8<--\ndemonstrate colons in Java\n\nFirst paragraph is not interesting.\n\nAlso if using another Language such as Java, where I point out\nClass::function() to be problematic\n--8<--\n\nThis would lack the white space between the last paragraph and\nthe Sign off ?\n\nSo for this patch I am mostly concerned about false positives hidden\nin actual text.\n"},{"id":"304324","messageId":"CAGZ79kYiTNVRCzk--97qPLLWq2O4xsLPgGCLwJyKAvjvGMu66A@mail.gmail.com","threadId":"44285","inReplyTo":"0a187897454a5bce946d675f6f6c93283b411f31.1476466609.git.jonathantanmy@google.com","subject":"Re: [PATCH v3 6/6] trailer: support values folded to multiple lines","fromName":"Stefan Beller","fromEmail":"sbeller@google.com","sentAt":"2016-10-18T00:55:57Z","receivedAt":"2016-10-18T00:56:03Z","isPatch":true,"sender":{"key":"stefanbeller@gmail.com","avatar":"https://avatars.githubusercontent.com/u/455868?v=4"},"body":"On Fri, Oct 14, 2016 at 10:38 AM, Jonathan Tan <jonathantanmy@google.com> wrote:\n> Currently, interpret-trailers requires that a trailer be only on 1 line.\n> For example:\n>\n>   a: first line\n>      second line\n>\n> would be interpreted as one trailer line followed by one non-trailer line.\n>\n> Make interpret-trailers support RFC 822-style folding, treating those\n> lines as one logical trailer.\n>\n> Signed-off-by: Jonathan Tan <jonathantanmy@google.com>\n> ---\n\nLooks good,\n\nThanks,\nStefan\n"},{"id":"304325","messageId":"xmqq7f96sa9i.fsf@gitster.mtv.corp.google.com","threadId":"44285","inReplyTo":"CAGZ79kYLq1qA4_Qg2x5Fiu5AmGBZdozm4zk6K7LkU+uJ1LNUTw@mail.gmail.com","subject":"Re: [PATCH v3 5/6] trailer: allow non-trailers in trailer block","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2016-10-18T01:42:33Z","receivedAt":"2016-10-18T01:42:43Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Stefan Beller <sbeller@google.com> writes:\n\n> On Fri, Oct 14, 2016 at 10:38 AM, Jonathan Tan <jonathantanmy@google.com> wrote:\n>>\n>>  Existing trailers are extracted from the input message by looking for\n>> -a group of one or more lines that contain a colon (by default), where\n>> +a group of one or more lines in which at least one line contains a\n>> +colon (by default), where\n>\n> Please see commit\n> 578e6021c0819d7be1179e05e7ce0e6fdb2a01b7\n> for an example where I think this is overly broad.\n\nHmph.  That's a merge.\n\n    Merge branch 'rs/c-auto-resets-attributes'\n\n    When \"%C(auto)\" appears at the very beginning of the pretty format\n    string, it did not need to issue the reset sequence, but it did.\n\n    * rs/c-auto-resets-attributes:\n      pretty: avoid adding reset for %C(auto) if output is empty\n\nAnd neither of the two colon containing line remotely resembles how\na typical RFC-822 header is formatted.  So that may serve as a hint\nto how we can tighten it without introducing false negative.\n\n> Another made up example, that I'd want to feed\n> in commit -s eventually:\n>\n> --8<--\n> demonstrate colons in Java\n>\n> First paragraph is not interesting.\n>\n> Also if using another Language such as Java, where I point out\n> Class::function() to be problematic\n> --8<--\n>\n> This would lack the white space between the last paragraph and\n> the Sign off ?\n>\n> So for this patch I am mostly concerned about false positives hidden\n> in actual text.\n\nYes.  \n\nThese are exactly why I mentioned \"if certian number or percentage\"\nin my earlier suggestion.\n\nI think in practice, \"A paragraph with at least one Signed-off-by:\nline, and has no more than 3/4 of the (logical) lines that do not\nresemble how a typical RFC-822 header is formatted\" or something\nalong that line would give us a reasonable safety.  \n\nYour Java example will fail the criteria in two ways, so we'd be\nsafe ;-)\n"},{"id":"304326","messageId":"cb622421-1857-d136-2618-ec62d438790d@google.com","threadId":"44285","inReplyTo":"xmqq7f96sa9i.fsf@gitster.mtv.corp.google.com","subject":"Re: [PATCH v3 5/6] trailer: allow non-trailers in trailer block","fromName":"Jonathan Tan","fromEmail":"jonathantanmy@google.com","sentAt":"2016-10-18T02:02:46Z","receivedAt":"2016-10-18T02:03:16Z","isPatch":true,"sender":{"key":"jonathantanmy@fastmail.com","avatar":null},"body":"On 10/17/2016 06:42 PM, Junio C Hamano wrote:\n> Stefan Beller <sbeller@google.com> writes:\n>\n>> On Fri, Oct 14, 2016 at 10:38 AM, Jonathan Tan <jonathantanmy@google.com> wrote:\n>>>\n>>>  Existing trailers are extracted from the input message by looking for\n>>> -a group of one or more lines that contain a colon (by default), where\n>>> +a group of one or more lines in which at least one line contains a\n>>> +colon (by default), where\n>>\n>> Please see commit\n>> 578e6021c0819d7be1179e05e7ce0e6fdb2a01b7\n>> for an example where I think this is overly broad.\n>\n> Hmph.  That's a merge.\n>\n>     Merge branch 'rs/c-auto-resets-attributes'\n>\n>     When \"%C(auto)\" appears at the very beginning of the pretty format\n>     string, it did not need to issue the reset sequence, but it did.\n>\n>     * rs/c-auto-resets-attributes:\n>       pretty: avoid adding reset for %C(auto) if output is empty\n>\n> And neither of the two colon containing line remotely resembles how\n> a typical RFC-822 header is formatted.  So that may serve as a hint\n> to how we can tighten it without introducing false negative.\n\nThe only \"offending\" character is the space (according to RFC 822), but \nthat sounds like a good rule to have.\n\n>> Another made up example, that I'd want to feed\n>> in commit -s eventually:\n>>\n>> --8<--\n>> demonstrate colons in Java\n>>\n>> First paragraph is not interesting.\n>>\n>> Also if using another Language such as Java, where I point out\n>> Class::function() to be problematic\n>> --8<--\n>>\n>> This would lack the white space between the last paragraph and\n>> the Sign off ?\n>>\n>> So for this patch I am mostly concerned about false positives hidden\n>> in actual text.\n>\n> Yes.\n>\n> These are exactly why I mentioned \"if certian number or percentage\"\n> in my earlier suggestion.\n>\n> I think in practice, \"A paragraph with at least one Signed-off-by:\n> line, and has no more than 3/4 of the (logical) lines that do not\n> resemble how a typical RFC-822 header is formatted\" or something\n> along that line would give us a reasonable safety.\n\nI think that \"Signed-off-by:\" is not guaranteed to be present. Defining \na trailer line as \"a line starting with a token, then optional \nwhitespace, then separator\", maybe the following rule:\n- at least one trailer line generated by Git (\"(cherry picked by\" or \n\"Signed-off-by\") or configured in the \"trailer\" section in gitconfig\nOR\n- at least 3/4 logical trailer lines (I'm wondering if this should be \n100% trailer lines)\n\n?\n\n> Your Java example will fail the criteria in two ways, so we'd be\n> safe ;-)\n"},{"id":"304344","messageId":"xmqqh989r6b8.fsf@gitster.mtv.corp.google.com","threadId":"44285","inReplyTo":"CAGZ79kYuaHvO-aXeCocDseTLe13g=PesKxQgOrcOREtM1CsWcg@mail.gmail.com","subject":"Re: [PATCH v3 4/6] trailer: make args have their own struct","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2016-10-18T16:05:31Z","receivedAt":"2016-10-18T16:05:41Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Stefan Beller <sbeller@google.com> writes:\n\n>> @@ -29,6 +29,12 @@ struct trailer_item {\n>>         struct list_head list;\n>>         char *token;\n>>         char *value;\n>> +};\n>> +\n>> +struct arg_item {\n>> +       struct list_head list;\n>> +       char *token;\n>> +       char *value;\n>>         struct conf_info conf;\n>>  };\n>\n> (Unrelated side note:) When first seeing this diff, I assumed the diff\n> heuristic is going wild, because it doesn't add a full struct.\n> But on a second closer look, I realize this is the only correct diff,\n> because we do not account for moved lines from one struct to the\n> other.\n\nIt probably is not \"the only\" correct diff, as you actually could\nshift it the other way by one to three lines.  I am not sure which\none among four possible diff is the easiest to grok, though.  Both\nthe above (picking the highest possible position) and the below (the\nother extreme) are probably easier to read than anything in between.\n\n struct trailer_item {\n+\tstruct list_head list;\n+\tchar *token;\n+\tchar *value;\n+};\n+\n+struct arg_item {\n \tstruct list_head list;\n \tchar *token;\n \tchar *value;\n \tstruct conf_info conf;\n };\n \n"},{"id":"304345","messageId":"xmqqbmyhr4vt.fsf@gitster.mtv.corp.google.com","threadId":"44285","inReplyTo":"cb622421-1857-d136-2618-ec62d438790d@google.com","subject":"Re: [PATCH v3 5/6] trailer: allow non-trailers in trailer block","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2016-10-18T16:36:22Z","receivedAt":"2016-10-18T16:36:32Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Jonathan Tan <jonathantanmy@google.com> writes:\n\n>>     * rs/c-auto-resets-attributes:\n>>       pretty: avoid adding reset for %C(auto) if output is empty\n>>\n>> And neither of the two colon containing line remotely resembles how\n>> a typical RFC-822 header is formatted.  So that may serve as a hint\n>> to how we can tighten it without introducing false negative.\n>\n> The only \"offending\" character is the space (according to RFC 822),\n> but that sounds like a good rule to have.\n\nI suspect that we should be willing to deviate from the letter of\nRFC to reject misidentification.  I saw things like\n\n\tThanks to: Jonathan Tan <jt@host.xz>\n\tSigned-off-by: A U Thor <au@th.or>\n\nin the wild (notice the SP between Thanks and to), for example.\nRejecting leading whitespace as a line that does *not* start the\nheader (hence its colon does not count) may be a good compromise.\n\n> I think that \"Signed-off-by:\" is not guaranteed to be\n> present.\n\nBut do we really care about that case where there is no S-o-b:?  I\npersonally do not think so.\n\n> Defining a trailer line as \"a line starting with a token,\n> then optional whitespace, then separator\", maybe the following rule:\n> - at least one trailer line generated by Git (\"(cherry picked by\" or\n> \"Signed-off-by\") or configured in the \"trailer\" section in gitconfig\n> OR\n> - at least 3/4 logical trailer lines (I'm wondering if this should be\n> 100% trailer lines)\n\nI'd strongly suggest turning that OR to AND.  We will not safely be\nable to write a commit log message that describes how S-o-b lines\nare handled in its last paragraph otherwise.\n\nI do not care too deeply about 3/4, but I meant to allow 75% cruft\nbut no more than that, and the fact that the threashold is set at\nway more than 50% is important.  IOW, if you have\n\n\tOrdinary log message here...\n\n\tS-o-b: A U Thor <au@th.or>\n\t[a short description that is typically a single liner\n        in the real world use pattern we saw in the world, but\n\tcould overflow to become multi line cruft]\n\tS-o-b: R E Layer <re@lay.er>\n\n\"last paragraph\" is 5 lines long, among which 60% are cruft that is\nbelow the 75% threshold, and \"am -s\" can still add the S-o-b of the\ncommitter at the end of that existing last paragraph.  Making it too\nstrict would raise the false negative ratio.\n"},{"id":"304405","messageId":"31cae41d-701c-c657-f04e-3b617c0b1d39@google.com","threadId":"44285","inReplyTo":"xmqqbmyhr4vt.fsf@gitster.mtv.corp.google.com","subject":"Re: [PATCH v3 5/6] trailer: allow non-trailers in trailer block","fromName":"Jonathan Tan","fromEmail":"jonathantanmy@google.com","sentAt":"2016-10-19T18:00:45Z","receivedAt":"2016-10-19T18:00:55Z","isPatch":true,"sender":{"key":"jonathantanmy@fastmail.com","avatar":null},"body":"On 10/18/2016 09:36 AM, Junio C Hamano wrote:\n> Jonathan Tan <jonathantanmy@google.com> writes:\n>\n>>>     * rs/c-auto-resets-attributes:\n>>>       pretty: avoid adding reset for %C(auto) if output is empty\n>>>\n>>> And neither of the two colon containing line remotely resembles how\n>>> a typical RFC-822 header is formatted.  So that may serve as a hint\n>>> to how we can tighten it without introducing false negative.\n>>\n>> The only \"offending\" character is the space (according to RFC 822),\n>> but that sounds like a good rule to have.\n>\n> I suspect that we should be willing to deviate from the letter of\n> RFC to reject misidentification.  I saw things like\n>\n> \tThanks to: Jonathan Tan <jt@host.xz>\n> \tSigned-off-by: A U Thor <au@th.or>\n>\n> in the wild (notice the SP between Thanks and to), for example.\n> Rejecting leading whitespace as a line that does *not* start the\n> header (hence its colon does not count) may be a good compromise.\n\nGood point.\n\n>> I think that \"Signed-off-by:\" is not guaranteed to be\n>> present.\n>\n> But do we really care about that case where there is no S-o-b:?  I\n> personally do not think so.\n\nI think we should - the use case I had in mind when I started this is \nthe Android Git repository, which does not use Signed-off-by. For \nexample, I quoted this commit in an earlier e-mail [1]:\n\nhttps://android.googlesource.com/platform/frameworks/base/+/4c5281862f750cbc9d7355a07ef1a5545b9b3523\n\nwhich has the footer:\n\n   Bug: http://b/30562229\n   Test: readelf --dyn-sym app_process32 and check that bsd_signal is \nexported\n         readelf --dyn-sym app_process64 and check that bsd_signal is \nnot exported\n   Change-Id: Iec584070b42bc7fa43b114c0f884aff2db5a6858\n\n>> Defining a trailer line as \"a line starting with a token,\n>> then optional whitespace, then separator\", maybe the following rule:\n>> - at least one trailer line generated by Git (\"(cherry picked by\" or\n>> \"Signed-off-by\") or configured in the \"trailer\" section in gitconfig\n>> OR\n>> - at least 3/4 logical trailer lines (I'm wondering if this should be\n>> 100% trailer lines)\n>\n> I'd strongly suggest turning that OR to AND.  We will not safely be\n> able to write a commit log message that describes how S-o-b lines\n> are handled in its last paragraph otherwise.\n>\n> I do not care too deeply about 3/4, but I meant to allow 75% cruft\n> but no more than that, and the fact that the threashold is set at\n> way more than 50% is important.  IOW, if you have\n>\n> \tOrdinary log message here...\n>\n> \tS-o-b: A U Thor <au@th.or>\n> \t[a short description that is typically a single liner\n>         in the real world use pattern we saw in the world, but\n> \tcould overflow to become multi line cruft]\n> \tS-o-b: R E Layer <re@lay.er>\n>\n> \"last paragraph\" is 5 lines long, among which 60% are cruft that is\n> below the 75% threshold, and \"am -s\" can still add the S-o-b of the\n> committer at the end of that existing last paragraph.  Making it too\n> strict would raise the false negative ratio.\n\nAh, sorry, I misread your original suggestion.\n\nWould this work then:\n- at least one trailer line generated by Git (\"(cherry picked by\" or\n   \"Signed-off-by: \") or configured in the \"trailer\" section in\n   git config AND at least 25% logical trailer lines\nOR\n- 100% logical trailer lines\n\nThe first part is your original suggestion except that I think that it \nis more consistent to allow other trailer lines as well (but I do not \nfeel too strongly about this). The second part would satisfy the Android \nGit use case above, and also retain existing behavior when \n\"Signed-off-by\" (for example) is added to an existing footer that does \nnot contain \"Signed-off-by\" yet.\n\nWhat do you think?\n\n[1] Message ID <29cb0f55-f729-80af-cdca-64e927fa97c0@google.com>\n"},{"id":"304409","messageId":"xmqqpomwkuqs.fsf@gitster.mtv.corp.google.com","threadId":"44285","inReplyTo":"31cae41d-701c-c657-f04e-3b617c0b1d39@google.com","subject":"Re: [PATCH v3 5/6] trailer: allow non-trailers in trailer block","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2016-10-19T19:24:11Z","receivedAt":"2016-10-19T19:24:19Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Jonathan Tan <jonathantanmy@google.com> writes:\n\n> Would this work then:\n> - at least one trailer line generated by Git (\"(cherry picked by\" or\n>   \"Signed-off-by: \") or configured in the \"trailer\" section in\n>   git config AND at least 25% logical trailer lines\n> OR\n> - 100% logical trailer lines\n\nSure.  \n\nAt that point, I doubt that the latter \"100% logical trailer\" makes\nmuch difference, though, because at least one of them is likely to\nbe in the former set, or the users can easily make it so by throwing\nwhat they use like \"Bug: \", \"Test: \" and \"Change-ID: \" in the\n\"configured in the trailer section\" category.  FWIW, I do not think\nwe mind terribly to tweak the definition of \"generated by Git\" class\nto \"commonly used in Git managed projects\" to include \"Change-ID:\"\nand friends.\n"},{"id":"304625","messageId":"cover.1477072247.git.jonathantanmy@google.com","threadId":"44285","inReplyTo":"cover.1476232683.git.jonathantanmy@google.com","subject":"[PATCH v5 0/8] allow non-trailers and multiple-line trailers","fromName":"Jonathan Tan","fromEmail":"jonathantanmy@google.com","sentAt":"2016-10-21T17:54:55Z","receivedAt":"2016-10-21T17:55:16Z","isPatch":true,"sender":{"key":"jonathantanmy@fastmail.com","avatar":null},"body":"I've updated patch 5/8 to use strcspn and to pass in the list of\nseparators, meaning that we no longer accept '=' in file input (and also\nupdated its commit message accordingly).\n\nWe also discussed inlining find_separator, but after looking at the\ncode, I think that it is more convenient if find_separator returns -1\nwhen there is no separator, because of the 3 times it is used (as\nof 8/8), it is checked twice with '>= 1' (since both \"no separator\" and\n\"string begins with separator\" are handled in the same way - treating\nthem as a non-trailer line). So I have left it as its own function.\n\nNo other updates.\n\nJonathan Tan (8):\n  trailer: improve const correctness\n  trailer: use list.h for doubly-linked list\n  trailer: streamline trailer item create and add\n  trailer: make args have their own struct\n  trailer: clarify failure modes in parse_trailer\n  trailer: allow non-trailers in trailer block\n  trailer: forbid leading whitespace in trailers\n  trailer: support values folded to multiple lines\n\n Documentation/git-interpret-trailers.txt |  14 +-\n t/t7513-interpret-trailers.sh            | 299 +++++++++++++++\n trailer.c                                | 620 +++++++++++++++++--------------\n 3 files changed, 654 insertions(+), 279 deletions(-)\n\n-- \n2.8.0.rc3.226.g39d4020\n\n"},{"id":"304626","messageId":"701ba7164b587a97ef380bc981c95c5d51db6781.1477072247.git.jonathantanmy@google.com","threadId":"44285","inReplyTo":"cover.1477072247.git.jonathantanmy@google.com","subject":"[PATCH v5 1/8] trailer: improve const correctness","fromName":"Jonathan Tan","fromEmail":"jonathantanmy@google.com","sentAt":"2016-10-21T17:54:56Z","receivedAt":"2016-10-21T17:55:19Z","isPatch":true,"sender":{"key":"jonathantanmy@fastmail.com","avatar":null},"body":"Change \"const char *\" to \"char *\" in struct trailer_item and in the\nreturn value of apply_command (since those strings are owned strings).\n\nChange \"struct conf_info *\" to \"const struct conf_info *\" (since that\nstruct is not modified).\n\nSigned-off-by: Jonathan Tan <jonathantanmy@google.com>\n---\n trailer.c | 14 +++++++-------\n 1 file changed, 7 insertions(+), 7 deletions(-)\n\ndiff --git a/trailer.c b/trailer.c\nindex c6ea9ac..1f191b2 100644\n--- a/trailer.c\n+++ b/trailer.c\n@@ -27,8 +27,8 @@ static struct conf_info default_conf_info;\n struct trailer_item {\n \tstruct trailer_item *previous;\n \tstruct trailer_item *next;\n-\tconst char *token;\n-\tconst char *value;\n+\tchar *token;\n+\tchar *value;\n \tstruct conf_info conf;\n };\n \n@@ -95,8 +95,8 @@ static void free_trailer_item(struct trailer_item *item)\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->token);\n+\tfree(item->value);\n \tfree(item);\n }\n \n@@ -215,13 +215,13 @@ static struct trailer_item *remove_first(struct trailer_item **first)\n \treturn item;\n }\n \n-static const char *apply_command(const char *command, const char *arg)\n+static 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 = CHILD_PROCESS_INIT;\n \tconst char *argv[] = {NULL, NULL};\n-\tconst char *result;\n+\tchar *result;\n \n \tstrbuf_addstr(&cmd, command);\n \tif (arg)\n@@ -425,7 +425,7 @@ static int set_if_missing(struct conf_info *item, const char *value)\n \treturn 0;\n }\n \n-static void duplicate_conf(struct conf_info *dst, struct conf_info *src)\n+static void duplicate_conf(struct conf_info *dst, const struct conf_info *src)\n {\n \t*dst = *src;\n \tif (src->name)\n-- \n2.8.0.rc3.226.g39d4020\n\n"},{"id":"304627","messageId":"a2bd43b8b0c5846455afb59be713218f774410e9.1477072247.git.jonathantanmy@google.com","threadId":"44285","inReplyTo":"cover.1477072247.git.jonathantanmy@google.com","subject":"[PATCH v5 5/8] trailer: clarify failure modes in parse_trailer","fromName":"Jonathan Tan","fromEmail":"jonathantanmy@google.com","sentAt":"2016-10-21T17:55:00Z","receivedAt":"2016-10-21T17:55:45Z","isPatch":true,"sender":{"key":"jonathantanmy@fastmail.com","avatar":null},"body":"The parse_trailer function has a few modes of operation, all depending\non whether the separator is present in its input, and if yes, the\nseparator's position. Some of these modes are failure modes, and these\nfailure modes are handled differently depending on whether the trailer\nline was sourced from a file or from a command-line argument.\n\nExtract a function to find the separator, allowing the invokers of\nparse_trailer to determine how to handle the failure modes instead of\nmaking parse_trailer do it. In this function, also take in the list of\nseparators, so that we can distinguish between command line arguments\n(which allow '=' as separator) and file input (which does not allow '='\nas separator).\n\nSigned-off-by: Jonathan Tan <jonathantanmy@google.com>\n---\n trailer.c | 75 ++++++++++++++++++++++++++++++++++++++++++++-------------------\n 1 file changed, 53 insertions(+), 22 deletions(-)\n\ndiff --git a/trailer.c b/trailer.c\nindex 99018f8..aff858b 100644\n--- a/trailer.c\n+++ b/trailer.c\n@@ -543,29 +543,37 @@ static int token_matches_item(const char *tok, struct arg_item *item, int tok_le\n \treturn item->conf.key ? !strncasecmp(tok, item->conf.key, tok_len) : 0;\n }\n \n-static int parse_trailer(struct strbuf *tok, struct strbuf *val,\n-\t\t\t const struct conf_info **conf, const char *trailer)\n+/*\n+ * Return the location of the first separator in line, or -1 if there is no\n+ * separator.\n+ */\n+static int find_separator(const char *line, const char *separators)\n+{\n+\tint loc = strcspn(line, separators);\n+\tif (!line[loc])\n+\t\treturn -1;\n+\treturn loc;\n+}\n+\n+/*\n+ * Obtain the token, value, and conf from the given trailer.\n+ *\n+ * separator_pos must not be 0, since the token cannot be an empty string.\n+ *\n+ * If separator_pos is -1, interpret the whole trailer as a token.\n+ */\n+static void parse_trailer(struct strbuf *tok, struct strbuf *val,\n+\t\t\t const struct conf_info **conf, const char *trailer,\n+\t\t\t int separator_pos)\n {\n-\tsize_t len;\n-\tstruct strbuf seps = STRBUF_INIT;\n \tstruct arg_item *item;\n \tint tok_len;\n \tstruct list_head *pos;\n \n-\tstrbuf_addstr(&seps, separators);\n-\tstrbuf_addch(&seps, '=');\n-\tlen = strcspn(trailer, seps.buf);\n-\tstrbuf_release(&seps);\n-\tif (len == 0) {\n-\t\tint l = strlen(trailer);\n-\t\twhile (l > 0 && isspace(trailer[l - 1]))\n-\t\t\tl--;\n-\t\treturn error(_(\"empty trailer token in trailer '%.*s'\"), l, trailer);\n-\t}\n-\tif (len < strlen(trailer)) {\n-\t\tstrbuf_add(tok, trailer, len);\n+\tif (separator_pos != -1) {\n+\t\tstrbuf_add(tok, trailer, separator_pos);\n \t\tstrbuf_trim(tok);\n-\t\tstrbuf_addstr(val, trailer + len + 1);\n+\t\tstrbuf_addstr(val, trailer + separator_pos + 1);\n \t\tstrbuf_trim(val);\n \t} else {\n \t\tstrbuf_addstr(tok, trailer);\n@@ -587,8 +595,6 @@ static int parse_trailer(struct strbuf *tok, struct strbuf *val,\n \t\t\tbreak;\n \t\t}\n \t}\n-\n-\treturn 0;\n }\n \n static void add_trailer_item(struct list_head *head, char *tok, char *val)\n@@ -619,6 +625,12 @@ static void process_command_line_args(struct list_head *arg_head,\n \tconst struct conf_info *conf;\n \tstruct list_head *pos;\n \n+\t/*\n+\t * In command-line arguments, '=' is accepted (in addition to the\n+\t * separators that are defined).\n+\t */\n+\tchar *cl_separators = xstrfmt(\"=%s\", separators);\n+\n \t/* Add an arg item for each configured trailer with a command */\n \tlist_for_each(pos, &conf_head) {\n \t\titem = list_entry(pos, struct arg_item, list);\n@@ -631,12 +643,25 @@ static void process_command_line_args(struct list_head *arg_head,\n \n \t/* Add an arg item for each trailer on the command line */\n \tfor_each_string_list_item(tr, trailers) {\n-\t\tif (!parse_trailer(&tok, &val, &conf, tr->string))\n+\t\tint separator_pos = find_separator(tr->string, cl_separators);\n+\t\tif (separator_pos == 0) {\n+\t\t\tstruct strbuf sb = STRBUF_INIT;\n+\t\t\tstrbuf_addstr(&sb, tr->string);\n+\t\t\tstrbuf_trim(&sb);\n+\t\t\terror(_(\"empty trailer token in trailer '%.*s'\"),\n+\t\t\t      (int) sb.len, sb.buf);\n+\t\t\tstrbuf_release(&sb);\n+\t\t} else {\n+\t\t\tparse_trailer(&tok, &val, &conf, tr->string,\n+\t\t\t\t      separator_pos);\n \t\t\tadd_arg_item(arg_head,\n \t\t\t\t     strbuf_detach(&tok, NULL),\n \t\t\t\t     strbuf_detach(&val, NULL),\n \t\t\t\t     conf);\n+\t\t}\n \t}\n+\n+\tfree(cl_separators);\n }\n \n static struct strbuf **read_input_file(const char *file)\n@@ -775,11 +800,17 @@ static int process_input_file(FILE *outfile,\n \n \t/* Parse trailer lines */\n \tfor (i = trailer_start; i < trailer_end; i++) {\n-\t\tif (lines[i]->buf[0] != comment_line_char &&\n-\t\t    !parse_trailer(&tok, &val, NULL, lines[i]->buf))\n+\t\tint separator_pos;\n+\t\tif (lines[i]->buf[0] == comment_line_char)\n+\t\t\tcontinue;\n+\t\tseparator_pos = find_separator(lines[i]->buf, separators);\n+\t\tif (separator_pos >= 1) {\n+\t\t\tparse_trailer(&tok, &val, NULL, lines[i]->buf,\n+\t\t\t\t      separator_pos);\n \t\t\tadd_trailer_item(head,\n \t\t\t\t\t strbuf_detach(&tok, NULL),\n \t\t\t\t\t strbuf_detach(&val, NULL));\n+\t\t}\n \t}\n \n \treturn trailer_end;\n-- \n2.8.0.rc3.226.g39d4020\n\n"},{"id":"304628","messageId":"6d5608bb3dadc9b767a0c48216418931f4602f72.1477072247.git.jonathantanmy@google.com","threadId":"44285","inReplyTo":"cover.1477072247.git.jonathantanmy@google.com","subject":"[PATCH v5 4/8] trailer: make args have their own struct","fromName":"Jonathan Tan","fromEmail":"jonathantanmy@google.com","sentAt":"2016-10-21T17:54:59Z","receivedAt":"2016-10-21T17:55:48Z","isPatch":true,"sender":{"key":"jonathantanmy@fastmail.com","avatar":null},"body":"Improve type safety by making arguments (whether from configuration or\nfrom the command line) have their own \"struct arg_item\" type, separate\nfrom the \"struct trailer_item\" type used to represent the trailers in\nthe buffer being manipulated.\n\nThis change also prepares \"struct trailer_item\" to be further\ndifferentiated from \"struct arg_item\" in future patches.\n\nSigned-off-by: Jonathan Tan <jonathantanmy@google.com>\n---\n trailer.c | 135 +++++++++++++++++++++++++++++++++++++++-----------------------\n 1 file changed, 85 insertions(+), 50 deletions(-)\n\ndiff --git a/trailer.c b/trailer.c\nindex ae3972a..99018f8 100644\n--- a/trailer.c\n+++ b/trailer.c\n@@ -29,6 +29,12 @@ struct trailer_item {\n \tstruct list_head list;\n \tchar *token;\n \tchar *value;\n+};\n+\n+struct arg_item {\n+\tstruct list_head list;\n+\tchar *token;\n+\tchar *value;\n \tstruct conf_info conf;\n };\n \n@@ -62,7 +68,7 @@ static size_t token_len_without_separator(const char *token, size_t len)\n \treturn len;\n }\n \n-static int same_token(struct trailer_item *a, struct trailer_item *b)\n+static int same_token(struct trailer_item *a, struct arg_item *b)\n {\n \tsize_t a_len = token_len_without_separator(a->token, strlen(a->token));\n \tsize_t b_len = token_len_without_separator(b->token, strlen(b->token));\n@@ -71,12 +77,12 @@ static int same_token(struct trailer_item *a, struct trailer_item *b)\n \treturn !strncasecmp(a->token, b->token, min_len);\n }\n \n-static int same_value(struct trailer_item *a, struct trailer_item *b)\n+static int same_value(struct trailer_item *a, struct arg_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)\n+static int same_trailer(struct trailer_item *a, struct arg_item *b)\n {\n \treturn same_token(a, b) && same_value(a, b);\n }\n@@ -98,6 +104,13 @@ static inline void strbuf_replace(struct strbuf *sb, const char *a, const char *\n \n static void free_trailer_item(struct trailer_item *item)\n {\n+\tfree(item->token);\n+\tfree(item->value);\n+\tfree(item);\n+}\n+\n+static void free_arg_item(struct arg_item *item)\n+{\n \tfree(item->conf.name);\n \tfree(item->conf.key);\n \tfree(item->conf.command);\n@@ -137,17 +150,29 @@ static void print_all(FILE *outfile, struct list_head *head, int trim_empty)\n \t}\n }\n \n+static struct trailer_item *trailer_from_arg(struct arg_item *arg_tok)\n+{\n+\tstruct trailer_item *new = xcalloc(sizeof(*new), 1);\n+\tnew->token = arg_tok->token;\n+\tnew->value = arg_tok->value;\n+\targ_tok->token = arg_tok->value = NULL;\n+\tfree_arg_item(arg_tok);\n+\treturn new;\n+}\n+\n static void add_arg_to_input_list(struct trailer_item *on_tok,\n-\t\t\t\t  struct trailer_item *arg_tok)\n+\t\t\t\t  struct arg_item *arg_tok)\n {\n-\tif (after_or_end(arg_tok->conf.where))\n-\t\tlist_add(&arg_tok->list, &on_tok->list);\n+\tint aoe = after_or_end(arg_tok->conf.where);\n+\tstruct trailer_item *to_add = trailer_from_arg(arg_tok);\n+\tif (aoe)\n+\t\tlist_add(&to_add->list, &on_tok->list);\n \telse\n-\t\tlist_add_tail(&arg_tok->list, &on_tok->list);\n+\t\tlist_add_tail(&to_add->list, &on_tok->list);\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      struct arg_item *arg_tok,\n \t\t\t      int check_all,\n \t\t\t      struct list_head *head)\n {\n@@ -200,7 +225,7 @@ static char *apply_command(const char *command, const char *arg)\n \treturn result;\n }\n \n-static void apply_item_command(struct trailer_item *in_tok, struct trailer_item *arg_tok)\n+static void apply_item_command(struct trailer_item *in_tok, struct arg_item *arg_tok)\n {\n \tif (arg_tok->conf.command) {\n \t\tconst char *arg;\n@@ -218,13 +243,13 @@ static void apply_item_command(struct trailer_item *in_tok, struct trailer_item\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\tstruct arg_item *arg_tok,\n \t\t\t\tstruct trailer_item *on_tok,\n \t\t\t\tstruct list_head *head)\n {\n \tswitch (arg_tok->conf.if_exists) {\n \tcase EXISTS_DO_NOTHING:\n-\t\tfree_trailer_item(arg_tok);\n+\t\tfree_arg_item(arg_tok);\n \t\tbreak;\n \tcase EXISTS_REPLACE:\n \t\tapply_item_command(in_tok, arg_tok);\n@@ -241,39 +266,41 @@ static void apply_arg_if_exists(struct trailer_item *in_tok,\n \t\tif (check_if_different(in_tok, arg_tok, 1, head))\n \t\t\tadd_arg_to_input_list(on_tok, arg_tok);\n \t\telse\n-\t\t\tfree_trailer_item(arg_tok);\n+\t\t\tfree_arg_item(arg_tok);\n \t\tbreak;\n \tcase EXISTS_ADD_IF_DIFFERENT_NEIGHBOR:\n \t\tapply_item_command(in_tok, arg_tok);\n \t\tif (check_if_different(on_tok, arg_tok, 0, head))\n \t\t\tadd_arg_to_input_list(on_tok, arg_tok);\n \t\telse\n-\t\t\tfree_trailer_item(arg_tok);\n+\t\t\tfree_arg_item(arg_tok);\n \t\tbreak;\n \t}\n }\n \n static void apply_arg_if_missing(struct list_head *head,\n-\t\t\t\t struct trailer_item *arg_tok)\n+\t\t\t\t struct arg_item *arg_tok)\n {\n \tenum action_where where;\n+\tstruct trailer_item *to_add;\n \n \tswitch (arg_tok->conf.if_missing) {\n \tcase MISSING_DO_NOTHING:\n-\t\tfree_trailer_item(arg_tok);\n+\t\tfree_arg_item(arg_tok);\n \t\tbreak;\n \tcase MISSING_ADD:\n \t\twhere = arg_tok->conf.where;\n \t\tapply_item_command(NULL, arg_tok);\n+\t\tto_add = trailer_from_arg(arg_tok);\n \t\tif (after_or_end(where))\n-\t\t\tlist_add_tail(&arg_tok->list, head);\n+\t\t\tlist_add_tail(&to_add->list, head);\n \t\telse\n-\t\t\tlist_add(&arg_tok->list, head);\n+\t\t\tlist_add(&to_add->list, head);\n \t}\n }\n \n static int find_same_and_apply_arg(struct list_head *head,\n-\t\t\t\t   struct trailer_item *arg_tok)\n+\t\t\t\t   struct arg_item *arg_tok)\n {\n \tstruct list_head *pos;\n \tstruct trailer_item *in_tok;\n@@ -306,11 +333,11 @@ static void process_trailers_lists(struct list_head *head,\n \t\t\t\t   struct list_head *arg_head)\n {\n \tstruct list_head *pos, *p;\n-\tstruct trailer_item *arg_tok;\n+\tstruct arg_item *arg_tok;\n \n \tlist_for_each_safe(pos, p, arg_head) {\n \t\tint applied = 0;\n-\t\targ_tok = list_entry(pos, struct trailer_item, list);\n+\t\targ_tok = list_entry(pos, struct arg_item, list);\n \n \t\tlist_del(pos);\n \n@@ -375,20 +402,20 @@ static void duplicate_conf(struct conf_info *dst, const struct conf_info *src)\n \t\tdst->command = xstrdup(src->command);\n }\n \n-static struct trailer_item *get_conf_item(const char *name)\n+static struct arg_item *get_conf_item(const char *name)\n {\n \tstruct list_head *pos;\n-\tstruct trailer_item *item;\n+\tstruct arg_item *item;\n \n \t/* Look up item with same name */\n \tlist_for_each(pos, &conf_head) {\n-\t\titem = list_entry(pos, struct trailer_item, list);\n+\t\titem = list_entry(pos, struct arg_item, list);\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 = xcalloc(sizeof(*item), 1);\n \tduplicate_conf(&item->conf, &default_conf_info);\n \titem->conf.name = xstrdup(name);\n \n@@ -442,7 +469,7 @@ static int git_trailer_default_config(const char *conf_key, const char *value, v\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 arg_item *item;\n \tstruct conf_info *conf;\n \tchar *name = NULL;\n \tenum trailer_info_type type;\n@@ -500,7 +527,7 @@ static int git_trailer_config(const char *conf_key, const char *value, void *cb)\n \treturn 0;\n }\n \n-static const char *token_from_item(struct trailer_item *item, char *tok)\n+static const char *token_from_item(struct arg_item *item, char *tok)\n {\n \tif (item->conf.key)\n \t\treturn item->conf.key;\n@@ -509,7 +536,7 @@ static const char *token_from_item(struct trailer_item *item, char *tok)\n \treturn item->conf.name;\n }\n \n-static int token_matches_item(const char *tok, struct trailer_item *item, int tok_len)\n+static int token_matches_item(const char *tok, struct arg_item *item, int tok_len)\n {\n \tif (!strncasecmp(tok, item->conf.name, tok_len))\n \t\treturn 1;\n@@ -521,7 +548,7 @@ static int parse_trailer(struct strbuf *tok, struct strbuf *val,\n {\n \tsize_t len;\n \tstruct strbuf seps = STRBUF_INIT;\n-\tstruct trailer_item *item;\n+\tstruct arg_item *item;\n \tint tok_len;\n \tstruct list_head *pos;\n \n@@ -547,12 +574,14 @@ static int parse_trailer(struct strbuf *tok, struct strbuf *val,\n \n \t/* Lookup if the token matches something in the config */\n \ttok_len = token_len_without_separator(tok->buf, tok->len);\n-\t*conf = &default_conf_info;\n+\tif (conf)\n+\t\t*conf = &default_conf_info;\n \tlist_for_each(pos, &conf_head) {\n-\t\titem = list_entry(pos, struct trailer_item, list);\n+\t\titem = list_entry(pos, struct arg_item, list);\n \t\tif (token_matches_item(tok->buf, item, tok_len)) {\n \t\t\tchar *tok_buf = strbuf_detach(tok, NULL);\n-\t\t\t*conf = &item->conf;\n+\t\t\tif (conf)\n+\t\t\t\t*conf = &item->conf;\n \t\t\tstrbuf_addstr(tok, token_from_item(item, tok_buf));\n \t\t\tfree(tok_buf);\n \t\t\tbreak;\n@@ -562,43 +591,51 @@ static int parse_trailer(struct strbuf *tok, struct strbuf *val,\n \treturn 0;\n }\n \n-static void add_trailer_item(struct list_head *head, char *tok, char *val,\n-\t\t\t     const struct conf_info *conf)\n+static void add_trailer_item(struct list_head *head, char *tok, char *val)\n {\n \tstruct trailer_item *new = xcalloc(sizeof(*new), 1);\n \tnew->token = tok;\n \tnew->value = val;\n-\tduplicate_conf(&new->conf, conf);\n \tlist_add_tail(&new->list, head);\n }\n \n+static void add_arg_item(struct list_head *arg_head, char *tok, char *val,\n+\t\t\t const struct conf_info *conf)\n+{\n+\tstruct arg_item *new = xcalloc(sizeof(*new), 1);\n+\tnew->token = tok;\n+\tnew->value = val;\n+\tduplicate_conf(&new->conf, conf);\n+\tlist_add_tail(&new->list, arg_head);\n+}\n+\n static void process_command_line_args(struct list_head *arg_head, \n \t\t\t\t      struct string_list *trailers)\n {\n \tstruct string_list_item *tr;\n-\tstruct trailer_item *item;\n+\tstruct arg_item *item;\n \tstruct strbuf tok = STRBUF_INIT;\n \tstruct strbuf val = STRBUF_INIT;\n \tconst struct conf_info *conf;\n \tstruct list_head *pos;\n \n-\t/* Add a trailer item for each configured trailer with a command */\n+\t/* Add an arg item for each configured trailer with a command */\n \tlist_for_each(pos, &conf_head) {\n-\t\titem = list_entry(pos, struct trailer_item, list);\n+\t\titem = list_entry(pos, struct arg_item, list);\n \t\tif (item->conf.command)\n-\t\t\tadd_trailer_item(arg_head,\n-\t\t\t\t\t xstrdup(token_from_item(item, NULL)),\n-\t\t\t\t\t xstrdup(\"\"),\n-\t\t\t\t\t &item->conf);\n+\t\t\tadd_arg_item(arg_head,\n+\t\t\t\t     xstrdup(token_from_item(item, NULL)),\n+\t\t\t\t     xstrdup(\"\"),\n+\t\t\t\t     &item->conf);\n \t}\n \n-\t/* Add a trailer item for each trailer on the command line */\n+\t/* Add an arg item for each trailer on the command line */\n \tfor_each_string_list_item(tr, trailers) {\n \t\tif (!parse_trailer(&tok, &val, &conf, tr->string))\n-\t\t\tadd_trailer_item(arg_head,\n-\t\t\t\t\t strbuf_detach(&tok, NULL),\n-\t\t\t\t\t strbuf_detach(&val, NULL),\n-\t\t\t\t\t conf);\n+\t\t\tadd_arg_item(arg_head,\n+\t\t\t\t     strbuf_detach(&tok, NULL),\n+\t\t\t\t     strbuf_detach(&val, NULL),\n+\t\t\t\t     conf);\n \t}\n }\n \n@@ -721,7 +758,6 @@ static int process_input_file(FILE *outfile,\n \tint patch_start, trailer_start, trailer_end, i;\n \tstruct strbuf tok = STRBUF_INIT;\n \tstruct strbuf val = STRBUF_INIT;\n-\tconst struct conf_info *conf;\n \n \t/* Get the line count */\n \twhile (lines[count])\n@@ -740,11 +776,10 @@ static int process_input_file(FILE *outfile,\n \t/* Parse trailer lines */\n \tfor (i = trailer_start; i < trailer_end; i++) {\n \t\tif (lines[i]->buf[0] != comment_line_char &&\n-\t\t    !parse_trailer(&tok, &val, &conf, lines[i]->buf))\n+\t\t    !parse_trailer(&tok, &val, NULL, lines[i]->buf))\n \t\t\tadd_trailer_item(head,\n \t\t\t\t\t strbuf_detach(&tok, NULL),\n-\t\t\t\t\t strbuf_detach(&val, NULL),\n-\t\t\t\t\t conf);\n+\t\t\t\t\t strbuf_detach(&val, NULL));\n \t}\n \n \treturn trailer_end;\n-- \n2.8.0.rc3.226.g39d4020\n\n"},{"id":"304629","messageId":"8ed371ada381da782c3e296fe8987da0a380a7f1.1477072247.git.jonathantanmy@google.com","threadId":"44285","inReplyTo":"cover.1477072247.git.jonathantanmy@google.com","subject":"[PATCH v5 7/8] trailer: forbid leading whitespace in trailers","fromName":"Jonathan Tan","fromEmail":"jonathantanmy@google.com","sentAt":"2016-10-21T17:55:02Z","receivedAt":"2016-10-21T17:55:49Z","isPatch":true,"sender":{"key":"jonathantanmy@fastmail.com","avatar":null},"body":"Currently, interpret-trailers allows leading whitespace in trailer\nlines. This leads to false positives, especially for quoted lines or\nbullet lists.\n\nForbid leading whitespace in trailers.\n\nSigned-off-by: Jonathan Tan <jonathantanmy@google.com>\n---\n Documentation/git-interpret-trailers.txt |  2 +-\n t/t7513-interpret-trailers.sh            | 15 +++++++++++++++\n trailer.c                                |  2 +-\n 3 files changed, 17 insertions(+), 2 deletions(-)\n\ndiff --git a/Documentation/git-interpret-trailers.txt b/Documentation/git-interpret-trailers.txt\nindex cf4c5ea..4966b5b 100644\n--- a/Documentation/git-interpret-trailers.txt\n+++ b/Documentation/git-interpret-trailers.txt\n@@ -55,7 +55,7 @@ The group must either be at the end of the message or be the last\n non-whitespace lines before a line that starts with '---'. Such three\n minus signs start the patch part of the message.\n \n-When reading trailers, there can be whitespaces before and after the\n+When reading trailers, there can be whitespaces after the\n token, the separator and the value. There can also be whitespaces\n inside the token and the value.\n \ndiff --git a/t/t7513-interpret-trailers.sh b/t/t7513-interpret-trailers.sh\nindex 003e90f..3d94b3a 100755\n--- a/t/t7513-interpret-trailers.sh\n+++ b/t/t7513-interpret-trailers.sh\n@@ -241,6 +241,21 @@ test_expect_success 'with non-trailer lines only' '\n \ttest_cmp expected actual\n '\n \n+test_expect_success 'line with leading whitespace is not trailer' '\n+\tq_to_tab >patch <<-\\EOF &&\n+\n+\t\tQtoken: value\n+\tEOF\n+\tq_to_tab >expected <<-\\EOF &&\n+\n+\t\tQtoken: value\n+\n+\t\ttoken: value\n+\tEOF\n+\tgit interpret-trailers --trailer \"token: value\" patch >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 &&\ndiff --git a/trailer.c b/trailer.c\nindex 199f86a..d978437 100644\n--- a/trailer.c\n+++ b/trailer.c\n@@ -775,7 +775,7 @@ static int find_trailer_start(struct strbuf **lines, int count)\n \t\t}\n \n \t\tseparator_pos = find_separator(lines[start]->buf);\n-\t\tif (separator_pos >= 1) {\n+\t\tif (separator_pos >= 1 && !isspace(lines[start]->buf[0])) {\n \t\t\tstruct list_head *pos;\n \n \t\t\ttrailer_lines++;\n-- \n2.8.0.rc3.226.g39d4020\n\n"},{"id":"304630","messageId":"b7ff1a633688f3523cb641e9c4faf80094d6a435.1477072247.git.jonathantanmy@google.com","threadId":"44285","inReplyTo":"cover.1477072247.git.jonathantanmy@google.com","subject":"[PATCH v5 6/8] trailer: allow non-trailers in trailer block","fromName":"Jonathan Tan","fromEmail":"jonathantanmy@google.com","sentAt":"2016-10-21T17:55:01Z","receivedAt":"2016-10-21T17:55:50Z","isPatch":true,"sender":{"key":"jonathantanmy@fastmail.com","avatar":null},"body":"Currently, interpret-trailers requires all lines of a trailer block to\nbe trailers (or comments) - if not it would not identify that block as a\ntrailer block, and thus create its own trailer block, inserting a blank\nline.  For example:\n\n  echo -e \"\\nSigned-off-by: x\\nnot trailer\" |\n  git interpret-trailers --trailer \"c: d\"\n\nwould result in:\n\n  Signed-off-by: x\n  not trailer\n\n  c: d\n\nRelax the definition of a trailer block to require that the trailers (i)\nare all trailers, or (ii) contain at least one Git-generated trailer and\nconsists of at least 25% trailers.\n\n  Signed-off-by: x\n  not trailer\n  c: d\n\n(i) is the existing functionality. (ii) allows arbitrary lines to be\nincluded in trailer blocks, like those in [1], and still allow\ninterpret-trailers to be used.\n\n[1]\nhttps://kernel.googlesource.com/pub/scm/linux/kernel/git/stable/linux-stable/+/e7d316a02f683864a12389f8808570e37fb90aa3\n\nSigned-off-by: Jonathan Tan <jonathantanmy@google.com>\n---\n Documentation/git-interpret-trailers.txt |   5 +-\n t/t7513-interpret-trailers.sh            | 115 +++++++++++++++++++++++++++++++\n trailer.c                                |  89 ++++++++++++++++++++----\n 3 files changed, 194 insertions(+), 15 deletions(-)\n\ndiff --git a/Documentation/git-interpret-trailers.txt b/Documentation/git-interpret-trailers.txt\nindex 93d1db6..cf4c5ea 100644\n--- a/Documentation/git-interpret-trailers.txt\n+++ b/Documentation/git-interpret-trailers.txt\n@@ -48,8 +48,9 @@ with only spaces at the end of the commit message part, one blank line\n will be added before the new trailer.\n \n Existing trailers are extracted from the input message by looking for\n-a group of one or more lines that contain a colon (by default), where\n-the group is preceded by one or more empty (or whitespace-only) lines.\n+a group of one or more lines that (i) are all trailers, or (ii) contains at\n+least one Git-generated trailer and consists of at least 25% trailers.\n+The group must be preceded by one or more empty (or whitespace-only) lines.\n The group must either be at the end of the message or be the last\n non-whitespace lines before a line that starts with '---'. Such three\n minus signs start the patch part of the message.\ndiff --git a/t/t7513-interpret-trailers.sh b/t/t7513-interpret-trailers.sh\nindex aee785c..003e90f 100755\n--- a/t/t7513-interpret-trailers.sh\n+++ b/t/t7513-interpret-trailers.sh\n@@ -126,6 +126,121 @@ test_expect_success 'with multiline title in the message' '\n \ttest_cmp expected actual\n '\n \n+test_expect_success 'with non-trailer lines mixed with Signed-off-by' '\n+\tcat >patch <<-\\EOF &&\n+\n+\t\tthis is not a trailer\n+\t\tthis is not a trailer\n+\t\tSigned-off-by: a <a@example.com>\n+\t\tthis is not a trailer\n+\tEOF\n+\tcat >expected <<-\\EOF &&\n+\n+\t\tthis is not a trailer\n+\t\tthis is not a trailer\n+\t\tSigned-off-by: a <a@example.com>\n+\t\tthis is not a trailer\n+\t\ttoken: value\n+\tEOF\n+\tgit interpret-trailers --trailer \"token: value\" patch >actual &&\n+\ttest_cmp expected actual\n+'\n+\n+test_expect_success 'with non-trailer lines mixed with cherry picked from' '\n+\tcat >patch <<-\\EOF &&\n+\n+\t\tthis is not a trailer\n+\t\tthis is not a trailer\n+\t\t(cherry picked from commit x)\n+\t\tthis is not a trailer\n+\tEOF\n+\tcat >expected <<-\\EOF &&\n+\n+\t\tthis is not a trailer\n+\t\tthis is not a trailer\n+\t\t(cherry picked from commit x)\n+\t\tthis is not a trailer\n+\t\ttoken: value\n+\tEOF\n+\tgit interpret-trailers --trailer \"token: value\" patch >actual &&\n+\ttest_cmp expected actual\n+'\n+\n+test_expect_success 'with non-trailer lines mixed with a configured trailer' '\n+\tcat >patch <<-\\EOF &&\n+\n+\t\tthis is not a trailer\n+\t\tthis is not a trailer\n+\t\tMy-trailer: x\n+\t\tthis is not a trailer\n+\tEOF\n+\tcat >expected <<-\\EOF &&\n+\n+\t\tthis is not a trailer\n+\t\tthis is not a trailer\n+\t\tMy-trailer: x\n+\t\tthis is not a trailer\n+\t\ttoken: value\n+\tEOF\n+\ttest_config trailer.my.key \"My-trailer: \" &&\n+\tgit interpret-trailers --trailer \"token: value\" patch >actual &&\n+\ttest_cmp expected actual\n+'\n+\n+test_expect_success 'with non-trailer lines mixed with a non-configured trailer' '\n+\tcat >patch <<-\\EOF &&\n+\n+\t\tthis is not a trailer\n+\t\tthis is not a trailer\n+\t\tI-am-not-configured: x\n+\t\tthis is not a trailer\n+\tEOF\n+\tcat >expected <<-\\EOF &&\n+\n+\t\tthis is not a trailer\n+\t\tthis is not a trailer\n+\t\tI-am-not-configured: x\n+\t\tthis is not a trailer\n+\n+\t\ttoken: value\n+\tEOF\n+\ttest_config trailer.my.key \"My-trailer: \" &&\n+\tgit interpret-trailers --trailer \"token: value\" patch >actual &&\n+\ttest_cmp expected actual\n+'\n+\n+test_expect_success 'with all non-configured trailers' '\n+\tcat >patch <<-\\EOF &&\n+\n+\t\tI-am-not-configured: x\n+\t\tI-am-also-not-configured: x\n+\tEOF\n+\tcat >expected <<-\\EOF &&\n+\n+\t\tI-am-not-configured: x\n+\t\tI-am-also-not-configured: x\n+\t\ttoken: value\n+\tEOF\n+\ttest_config trailer.my.key \"My-trailer: \" &&\n+\tgit interpret-trailers --trailer \"token: value\" patch >actual &&\n+\ttest_cmp expected actual\n+'\n+\n+test_expect_success 'with non-trailer lines only' '\n+\tcat >patch <<-\\EOF &&\n+\n+\t\tthis is not a trailer\n+\tEOF\n+\tcat >expected <<-\\EOF &&\n+\n+\t\tthis is not a trailer\n+\n+\t\ttoken: value\n+\tEOF\n+\tgit interpret-trailers --trailer \"token: value\" patch >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 &&\ndiff --git a/trailer.c b/trailer.c\nindex aff858b..199f86a 100644\n--- a/trailer.c\n+++ b/trailer.c\n@@ -27,6 +27,10 @@ static struct conf_info default_conf_info;\n \n struct trailer_item {\n \tstruct list_head list;\n+\t/*\n+\t * If this is not a trailer line, the line is stored in value\n+\t * (excluding the terminating newline) and token is NULL.\n+\t */\n \tchar *token;\n \tchar *value;\n };\n@@ -44,6 +48,12 @@ static char *separators = \":\";\n \n #define TRAILER_ARG_STRING \"$ARG\"\n \n+static const char *git_generated_prefixes[] = {\n+\t\"Signed-off-by: \",\n+\t\"(cherry picked from commit \",\n+\tNULL\n+};\n+\n /* Iterate over the elements of the list. */\n #define list_for_each_dir(pos, head, is_reverse) \\\n \tfor (pos = is_reverse ? (head)->prev : (head)->next; \\\n@@ -70,9 +80,14 @@ static size_t token_len_without_separator(const char *token, size_t len)\n \n static int same_token(struct trailer_item *a, struct arg_item *b)\n {\n-\tsize_t a_len = token_len_without_separator(a->token, strlen(a->token));\n-\tsize_t b_len = token_len_without_separator(b->token, strlen(b->token));\n-\tsize_t min_len = (a_len > b_len) ? b_len : a_len;\n+\tsize_t a_len, b_len, min_len;\n+\n+\tif (!a->token)\n+\t\treturn 0;\n+\n+\ta_len = token_len_without_separator(a->token, strlen(a->token));\n+\tb_len = token_len_without_separator(b->token, strlen(b->token));\n+\tmin_len = (a_len > b_len) ? b_len : a_len;\n \n \treturn !strncasecmp(a->token, b->token, min_len);\n }\n@@ -130,7 +145,14 @@ static char last_non_space_char(const char *s)\n \n static void print_tok_val(FILE *outfile, const char *tok, const char *val)\n {\n-\tchar c = last_non_space_char(tok);\n+\tchar c;\n+\n+\tif (!tok) {\n+\t\tfprintf(outfile, \"%s\\n\", val);\n+\t\treturn;\n+\t}\n+\n+\tc = last_non_space_char(tok);\n \tif (!c)\n \t\treturn;\n \tif (strchr(separators, c))\n@@ -709,6 +731,7 @@ static int find_patch_start(struct strbuf **lines, int count)\n static int find_trailer_start(struct strbuf **lines, int count)\n {\n \tint start, end_of_title, only_spaces = 1;\n+\tint recognized_prefix = 0, trailer_lines = 0, non_trailer_lines = 0;\n \n \t/* The first paragraph is the title and cannot be trailers */\n \tfor (start = 0; start < count; start++) {\n@@ -720,26 +743,60 @@ static int find_trailer_start(struct strbuf **lines, int count)\n \tend_of_title = start;\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 separator.\n+\t * Get the start of the trailers by looking starting from the end for a\n+\t * blank line before a set of non-blank lines that (i) are all\n+\t * trailers, or (ii) contains at least one Git-generated trailer and\n+\t * consists of at least 25% trailers.\n \t */\n \tfor (start = count - 1; start >= end_of_title; start--) {\n+\t\tconst char **p;\n+\t\tint separator_pos;\n+\n \t\tif (lines[start]->buf[0] == comment_line_char)\n \t\t\tcontinue;\n \t\tif (contains_only_spaces(lines[start]->buf)) {\n \t\t\tif (only_spaces)\n \t\t\t\tcontinue;\n-\t\t\treturn start + 1;\n+\t\t\tif (recognized_prefix &&\n+\t\t\t    trailer_lines * 3 >= non_trailer_lines)\n+\t\t\t\treturn start + 1;\n+\t\t\tif (trailer_lines && !non_trailer_lines)\n+\t\t\t\treturn start + 1;\n+\t\t\treturn count;\n \t\t}\n-\t\tif (strcspn(lines[start]->buf, separators) < lines[start]->len) {\n-\t\t\tif (only_spaces)\n-\t\t\t\tonly_spaces = 0;\n-\t\t\tcontinue;\n+\t\tonly_spaces = 0;\n+\n+\t\tfor (p = git_generated_prefixes; *p; p++) {\n+\t\t\tif (starts_with(lines[start]->buf, *p)) {\n+\t\t\t\ttrailer_lines++;\n+\t\t\t\trecognized_prefix = 1;\n+\t\t\t\tgoto continue_outer_loop;\n+\t\t\t}\n \t\t}\n-\t\treturn count;\n+\n+\t\tseparator_pos = find_separator(lines[start]->buf);\n+\t\tif (separator_pos >= 1) {\n+\t\t\tstruct list_head *pos;\n+\n+\t\t\ttrailer_lines++;\n+\t\t\tif (recognized_prefix)\n+\t\t\t\tcontinue;\n+\t\t\tlist_for_each(pos, &conf_head) {\n+\t\t\t\tstruct arg_item *item;\n+\t\t\t\titem = list_entry(pos, struct arg_item, list);\n+\t\t\t\tif (token_matches_item(lines[start]->buf, item,\n+\t\t\t\t\t\t       separator_pos)) {\n+\t\t\t\t\trecognized_prefix = 1;\n+\t\t\t\t\tbreak;\n+\t\t\t\t}\n+\t\t\t}\n+\t\t} else\n+\t\t\tnon_trailer_lines++;\n+continue_outer_loop:\n+\t\t;\n \t}\n \n-\treturn only_spaces ? count : 0;\n+\treturn count;\n }\n \n /* Get the index of the end of the trailers */\n@@ -810,6 +867,12 @@ static int process_input_file(FILE *outfile,\n \t\t\tadd_trailer_item(head,\n \t\t\t\t\t strbuf_detach(&tok, NULL),\n \t\t\t\t\t strbuf_detach(&val, NULL));\n+\t\t} else {\n+\t\t\tstrbuf_addbuf(&val, lines[i]);\n+\t\t\tstrbuf_strip_suffix(&val, \"\\n\");\n+\t\t\tadd_trailer_item(head,\n+\t\t\t\t\t NULL,\n+\t\t\t\t\t strbuf_detach(&val, NULL));\n \t\t}\n \t}\n \n-- \n2.8.0.rc3.226.g39d4020\n\n"},{"id":"304631","messageId":"b75fc95acb015dd69d8326bf69201965a3d18c87.1477072247.git.jonathantanmy@google.com","threadId":"44285","inReplyTo":"cover.1477072247.git.jonathantanmy@google.com","subject":"[PATCH v5 8/8] trailer: support values folded to multiple lines","fromName":"Jonathan Tan","fromEmail":"jonathantanmy@google.com","sentAt":"2016-10-21T17:55:03Z","receivedAt":"2016-10-21T17:56:05Z","isPatch":true,"sender":{"key":"jonathantanmy@fastmail.com","avatar":null},"body":"Currently, interpret-trailers requires that a trailer be only on 1 line.\nFor example:\n\na: first line\n   second line\n\nwould be interpreted as one trailer line followed by one non-trailer line.\n\nMake interpret-trailers support RFC 822-style folding, treating those\nlines as one logical trailer.\n\nSigned-off-by: Jonathan Tan <jonathantanmy@google.com>\n---\n Documentation/git-interpret-trailers.txt |   7 +-\n t/t7513-interpret-trailers.sh            | 169 +++++++++++++++++++++++++++++++\n trailer.c                                |  45 ++++++--\n 3 files changed, 211 insertions(+), 10 deletions(-)\n\ndiff --git a/Documentation/git-interpret-trailers.txt b/Documentation/git-interpret-trailers.txt\nindex 4966b5b..e99bda6 100644\n--- a/Documentation/git-interpret-trailers.txt\n+++ b/Documentation/git-interpret-trailers.txt\n@@ -57,11 +57,12 @@ minus signs start the patch part of the message.\n \n When reading trailers, there can be whitespaces after the\n token, the separator and the value. There can also be whitespaces\n-inside the token and the value.\n+inside the token and the value. The value may be split over multiple lines with\n+each subsequent line starting with whitespace, like the \"folding\" in RFC 822.\n \n Note that 'trailers' do not follow and are not intended to follow many\n-rules for RFC 822 headers. For example they do not follow the line\n-folding rules, the encoding rules and probably many other rules.\n+rules for RFC 822 headers. For example they do not follow\n+the encoding rules and probably many other rules.\n \n OPTIONS\n -------\ndiff --git a/t/t7513-interpret-trailers.sh b/t/t7513-interpret-trailers.sh\nindex 3d94b3a..4dd1d7c 100755\n--- a/t/t7513-interpret-trailers.sh\n+++ b/t/t7513-interpret-trailers.sh\n@@ -256,6 +256,175 @@ test_expect_success 'line with leading whitespace is not trailer' '\n \ttest_cmp expected actual\n '\n \n+test_expect_success 'multiline field treated as one trailer for 25% check' '\n+\tq_to_tab >patch <<-\\EOF &&\n+\n+\t\tSigned-off-by: a <a@example.com>\n+\t\tname: value on\n+\t\tQmultiple lines\n+\t\tthis is not a trailer\n+\t\tthis is not a trailer\n+\t\tthis is not a trailer\n+\t\tthis is not a trailer\n+\t\tthis is not a trailer\n+\t\tthis is not a trailer\n+\tEOF\n+\tq_to_tab >expected <<-\\EOF &&\n+\n+\t\tSigned-off-by: a <a@example.com>\n+\t\tname: value on\n+\t\tQmultiple lines\n+\t\tthis is not a trailer\n+\t\tthis is not a trailer\n+\t\tthis is not a trailer\n+\t\tthis is not a trailer\n+\t\tthis is not a trailer\n+\t\tthis is not a trailer\n+\t\tname: value\n+\tEOF\n+\tgit interpret-trailers --trailer \"name: value\" patch >actual &&\n+\ttest_cmp expected actual\n+'\n+\n+test_expect_success 'multiline field treated as atomic for placement' '\n+\tq_to_tab >patch <<-\\EOF &&\n+\n+\t\tanother: trailer\n+\t\tname: value on\n+\t\tQmultiple lines\n+\t\tanother: trailer\n+\tEOF\n+\tq_to_tab >expected <<-\\EOF &&\n+\n+\t\tanother: trailer\n+\t\tname: value on\n+\t\tQmultiple lines\n+\t\tname: value\n+\t\tanother: trailer\n+\tEOF\n+\ttest_config trailer.name.where after &&\n+\tgit interpret-trailers --trailer \"name: value\" patch >actual &&\n+\ttest_cmp expected actual\n+'\n+\n+test_expect_success 'multiline field treated as atomic for replacement' '\n+\tq_to_tab >patch <<-\\EOF &&\n+\n+\t\tanother: trailer\n+\t\tname: value on\n+\t\tQmultiple lines\n+\t\tanother: trailer\n+\tEOF\n+\tq_to_tab >expected <<-\\EOF &&\n+\n+\t\tanother: trailer\n+\t\tanother: trailer\n+\t\tname: value\n+\tEOF\n+\ttest_config trailer.name.ifexists replace &&\n+\tgit interpret-trailers --trailer \"name: value\" patch >actual &&\n+\ttest_cmp expected actual\n+'\n+\n+test_expect_success 'multiline field treated as atomic for difference check' '\n+\tq_to_tab >patch <<-\\EOF &&\n+\n+\t\tanother: trailer\n+\t\tname: first line\n+\t\tQsecond line\n+\t\tanother: trailer\n+\tEOF\n+\ttest_config trailer.name.ifexists addIfDifferent &&\n+\n+\tq_to_tab >trailer <<-\\EOF &&\n+\t\tname: first line\n+\t\tQsecond line\n+\tEOF\n+\tq_to_tab >expected <<-\\EOF &&\n+\n+\t\tanother: trailer\n+\t\tname: first line\n+\t\tQsecond line\n+\t\tanother: trailer\n+\tEOF\n+\tgit interpret-trailers --trailer \"$(cat trailer)\" patch >actual &&\n+\ttest_cmp expected actual &&\n+\n+\tq_to_tab >trailer <<-\\EOF &&\n+\t\tname: first line\n+\t\tQQQQQsecond line\n+\tEOF\n+\tq_to_tab >expected <<-\\EOF &&\n+\n+\t\tanother: trailer\n+\t\tname: first line\n+\t\tQsecond line\n+\t\tanother: trailer\n+\t\tname: first line\n+\t\tQQQQQsecond line\n+\tEOF\n+\tgit interpret-trailers --trailer \"$(cat trailer)\" patch >actual &&\n+\ttest_cmp expected actual &&\n+\n+\tq_to_tab >trailer <<-\\EOF &&\n+\t\tname: first line *DIFFERENT*\n+\t\tQsecond line\n+\tEOF\n+\tq_to_tab >expected <<-\\EOF &&\n+\n+\t\tanother: trailer\n+\t\tname: first line\n+\t\tQsecond line\n+\t\tanother: trailer\n+\t\tname: first line *DIFFERENT*\n+\t\tQsecond line\n+\tEOF\n+\tgit interpret-trailers --trailer \"$(cat trailer)\" patch >actual &&\n+\ttest_cmp expected actual\n+'\n+\n+test_expect_success 'multiline field treated as atomic for neighbor check' '\n+\tq_to_tab >patch <<-\\EOF &&\n+\n+\t\tanother: trailer\n+\t\tname: first line\n+\t\tQsecond line\n+\t\tanother: trailer\n+\tEOF\n+\ttest_config trailer.name.where after &&\n+\ttest_config trailer.name.ifexists addIfDifferentNeighbor &&\n+\n+\tq_to_tab >trailer <<-\\EOF &&\n+\t\tname: first line\n+\t\tQsecond line\n+\tEOF\n+\tq_to_tab >expected <<-\\EOF &&\n+\n+\t\tanother: trailer\n+\t\tname: first line\n+\t\tQsecond line\n+\t\tanother: trailer\n+\tEOF\n+\tgit interpret-trailers --trailer \"$(cat trailer)\" patch >actual &&\n+\ttest_cmp expected actual &&\n+\n+\tq_to_tab >trailer <<-\\EOF &&\n+\t\tname: first line\n+\t\tQQQQQsecond line\n+\tEOF\n+\tq_to_tab >expected <<-\\EOF &&\n+\n+\t\tanother: trailer\n+\t\tname: first line\n+\t\tQsecond line\n+\t\tname: first line\n+\t\tQQQQQsecond line\n+\t\tanother: trailer\n+\tEOF\n+\tgit interpret-trailers --trailer \"$(cat trailer)\" patch >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 &&\ndiff --git a/trailer.c b/trailer.c\nindex d978437..65866d0 100644\n--- a/trailer.c\n+++ b/trailer.c\n@@ -619,12 +619,14 @@ static void parse_trailer(struct strbuf *tok, struct strbuf *val,\n \t}\n }\n \n-static void add_trailer_item(struct list_head *head, char *tok, char *val)\n+static struct trailer_item *add_trailer_item(struct list_head *head, char *tok,\n+\t\t\t\t\t     char *val)\n {\n \tstruct trailer_item *new = xcalloc(sizeof(*new), 1);\n \tnew->token = tok;\n \tnew->value = val;\n \tlist_add_tail(&new->list, head);\n+\treturn new;\n }\n \n static void add_arg_item(struct list_head *arg_head, char *tok, char *val,\n@@ -732,6 +734,14 @@ static int find_trailer_start(struct strbuf **lines, int count)\n {\n \tint start, end_of_title, only_spaces = 1;\n \tint recognized_prefix = 0, trailer_lines = 0, non_trailer_lines = 0;\n+\t/*\n+\t * Number of possible continuation lines encountered. This will be\n+\t * reset to 0 if we encounter a trailer (since those lines are to be\n+\t * considered continuations of that trailer), and added to\n+\t * non_trailer_lines if we encounter a non-trailer (since those lines\n+\t * are to be considered non-trailers).\n+\t */\n+\tint possible_continuation_lines = 0;\n \n \t/* The first paragraph is the title and cannot be trailers */\n \tfor (start = 0; start < count; start++) {\n@@ -752,11 +762,15 @@ static int find_trailer_start(struct strbuf **lines, int count)\n \t\tconst char **p;\n \t\tint separator_pos;\n \n-\t\tif (lines[start]->buf[0] == comment_line_char)\n+\t\tif (lines[start]->buf[0] == comment_line_char) {\n+\t\t\tnon_trailer_lines += possible_continuation_lines;\n+\t\t\tpossible_continuation_lines = 0;\n \t\t\tcontinue;\n+\t\t}\n \t\tif (contains_only_spaces(lines[start]->buf)) {\n \t\t\tif (only_spaces)\n \t\t\t\tcontinue;\n+\t\t\tnon_trailer_lines += possible_continuation_lines;\n \t\t\tif (recognized_prefix &&\n \t\t\t    trailer_lines * 3 >= non_trailer_lines)\n \t\t\t\treturn start + 1;\n@@ -769,16 +783,18 @@ static int find_trailer_start(struct strbuf **lines, int count)\n \t\tfor (p = git_generated_prefixes; *p; p++) {\n \t\t\tif (starts_with(lines[start]->buf, *p)) {\n \t\t\t\ttrailer_lines++;\n+\t\t\t\tpossible_continuation_lines = 0;\n \t\t\t\trecognized_prefix = 1;\n \t\t\t\tgoto continue_outer_loop;\n \t\t\t}\n \t\t}\n \n-\t\tseparator_pos = find_separator(lines[start]->buf);\n+\t\tseparator_pos = find_separator(lines[start]->buf, separators);\n \t\tif (separator_pos >= 1 && !isspace(lines[start]->buf[0])) {\n \t\t\tstruct list_head *pos;\n \n \t\t\ttrailer_lines++;\n+\t\t\tpossible_continuation_lines = 0;\n \t\t\tif (recognized_prefix)\n \t\t\t\tcontinue;\n \t\t\tlist_for_each(pos, &conf_head) {\n@@ -790,8 +806,13 @@ static int find_trailer_start(struct strbuf **lines, int count)\n \t\t\t\t\tbreak;\n \t\t\t\t}\n \t\t\t}\n-\t\t} else\n+\t\t} else if (isspace(lines[start]->buf[0]))\n+\t\t\tpossible_continuation_lines++;\n+\t\telse {\n \t\t\tnon_trailer_lines++;\n+\t\t\tnon_trailer_lines += possible_continuation_lines;\n+\t\t\tpossible_continuation_lines = 0;\n+\t\t}\n continue_outer_loop:\n \t\t;\n \t}\n@@ -840,6 +861,7 @@ static int process_input_file(FILE *outfile,\n \tint patch_start, trailer_start, trailer_end, i;\n \tstruct strbuf tok = STRBUF_INIT;\n \tstruct strbuf val = STRBUF_INIT;\n+\tstruct trailer_item *last = NULL;\n \n \t/* Get the line count */\n \twhile (lines[count])\n@@ -860,19 +882,28 @@ static int process_input_file(FILE *outfile,\n \t\tint separator_pos;\n \t\tif (lines[i]->buf[0] == comment_line_char)\n \t\t\tcontinue;\n+\t\tif (last && isspace(lines[i]->buf[0])) {\n+\t\t\tstruct strbuf sb = STRBUF_INIT;\n+\t\t\tstrbuf_addf(&sb, \"%s\\n%s\", last->value, lines[i]->buf);\n+\t\t\tstrbuf_strip_suffix(&sb, \"\\n\");\n+\t\t\tfree(last->value);\n+\t\t\tlast->value = strbuf_detach(&sb, NULL);\n+\t\t\tcontinue;\n+\t\t}\n \t\tseparator_pos = find_separator(lines[i]->buf, separators);\n \t\tif (separator_pos >= 1) {\n \t\t\tparse_trailer(&tok, &val, NULL, lines[i]->buf,\n \t\t\t\t      separator_pos);\n-\t\t\tadd_trailer_item(head,\n-\t\t\t\t\t strbuf_detach(&tok, NULL),\n-\t\t\t\t\t strbuf_detach(&val, NULL));\n+\t\t\tlast = add_trailer_item(head,\n+\t\t\t\t\t\tstrbuf_detach(&tok, NULL),\n+\t\t\t\t\t\tstrbuf_detach(&val, NULL));\n \t\t} else {\n \t\t\tstrbuf_addbuf(&val, lines[i]);\n \t\t\tstrbuf_strip_suffix(&val, \"\\n\");\n \t\t\tadd_trailer_item(head,\n \t\t\t\t\t NULL,\n \t\t\t\t\t strbuf_detach(&val, NULL));\n+\t\t\tlast = NULL;\n \t\t}\n \t}\n \n-- \n2.8.0.rc3.226.g39d4020\n\n"},{"id":"304632","messageId":"c821f75f4000fc3e224b23633cce983c33b6d4d6.1477072247.git.jonathantanmy@google.com","threadId":"44285","inReplyTo":"cover.1477072247.git.jonathantanmy@google.com","subject":"[PATCH v5 3/8] trailer: streamline trailer item create and add","fromName":"Jonathan Tan","fromEmail":"jonathantanmy@google.com","sentAt":"2016-10-21T17:54:58Z","receivedAt":"2016-10-21T17:56:08Z","isPatch":true,"sender":{"key":"jonathantanmy@fastmail.com","avatar":null},"body":"Currently, creation and addition (to a list) of trailer items are spread\nacross multiple functions. Streamline this by only having 2 functions:\none to parse the user-supplied string, and one to add the parsed\ninformation to a list.\n\nSigned-off-by: Jonathan Tan <jonathantanmy@google.com>\n---\n trailer.c | 130 +++++++++++++++++++++++++++++---------------------------------\n 1 file changed, 60 insertions(+), 70 deletions(-)\n\ndiff --git a/trailer.c b/trailer.c\nindex 4e85aae..ae3972a 100644\n--- a/trailer.c\n+++ b/trailer.c\n@@ -500,10 +500,31 @@ static int git_trailer_config(const char *conf_key, const char *value, void *cb)\n \treturn 0;\n }\n \n-static int parse_trailer(struct strbuf *tok, struct strbuf *val, const char *trailer)\n+static const char *token_from_item(struct trailer_item *item, char *tok)\n+{\n+\tif (item->conf.key)\n+\t\treturn item->conf.key;\n+\tif (tok)\n+\t\treturn tok;\n+\treturn item->conf.name;\n+}\n+\n+static int token_matches_item(const char *tok, struct trailer_item *item, int tok_len)\n+{\n+\tif (!strncasecmp(tok, item->conf.name, tok_len))\n+\t\treturn 1;\n+\treturn item->conf.key ? !strncasecmp(tok, item->conf.key, tok_len) : 0;\n+}\n+\n+static int parse_trailer(struct strbuf *tok, struct strbuf *val,\n+\t\t\t const struct conf_info **conf, const char *trailer)\n {\n \tsize_t len;\n \tstruct strbuf seps = STRBUF_INIT;\n+\tstruct trailer_item *item;\n+\tint tok_len;\n+\tstruct list_head *pos;\n+\n \tstrbuf_addstr(&seps, separators);\n \tstrbuf_addch(&seps, '=');\n \tlen = strcspn(trailer, seps.buf);\n@@ -523,74 +544,31 @@ static int parse_trailer(struct strbuf *tok, struct strbuf *val, const char *tra\n \t\tstrbuf_addstr(tok, trailer);\n \t\tstrbuf_trim(tok);\n \t}\n-\treturn 0;\n-}\n-\n-static const char *token_from_item(struct trailer_item *item, char *tok)\n-{\n-\tif (item->conf.key)\n-\t\treturn item->conf.key;\n-\tif (tok)\n-\t\treturn tok;\n-\treturn item->conf.name;\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 ? val : xstrdup(\"\");\n-\n-\tif (conf_item) {\n-\t\tduplicate_conf(&new->conf, &conf_item->conf);\n-\t\tnew->token = xstrdup(token_from_item(conf_item, tok));\n-\t\tfree(tok);\n-\t} else {\n-\t\tduplicate_conf(&new->conf, &default_conf_info);\n-\t\tnew->token = tok;\n-\t}\n-\n-\treturn new;\n-}\n-\n-static int token_matches_item(const char *tok, struct trailer_item *item, int tok_len)\n-{\n-\tif (!strncasecmp(tok, item->conf.name, tok_len))\n-\t\treturn 1;\n-\treturn item->conf.key ? !strncasecmp(tok, item->conf.key, tok_len) : 0;\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_len;\n-\tstruct list_head *pos;\n-\n-\tif (parse_trailer(&tok, &val, string))\n-\t\treturn NULL;\n-\n-\ttok_len = token_len_without_separator(tok.buf, tok.len);\n \n \t/* Lookup if the token matches something in the config */\n+\ttok_len = token_len_without_separator(tok->buf, tok->len);\n+\t*conf = &default_conf_info;\n \tlist_for_each(pos, &conf_head) {\n \t\titem = list_entry(pos, struct trailer_item, list);\n-\t\tif (token_matches_item(tok.buf, item, tok_len))\n-\t\t\treturn new_trailer_item(item,\n-\t\t\t\t\t\tstrbuf_detach(&tok, NULL),\n-\t\t\t\t\t\tstrbuf_detach(&val, NULL));\n+\t\tif (token_matches_item(tok->buf, item, tok_len)) {\n+\t\t\tchar *tok_buf = strbuf_detach(tok, NULL);\n+\t\t\t*conf = &item->conf;\n+\t\t\tstrbuf_addstr(tok, token_from_item(item, tok_buf));\n+\t\t\tfree(tok_buf);\n+\t\t\tbreak;\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+\treturn 0;\n }\n \n-static void add_trailer_item(struct list_head *head, struct trailer_item *new)\n+static void add_trailer_item(struct list_head *head, char *tok, char *val,\n+\t\t\t     const struct conf_info *conf)\n {\n-\tif (!new)\n-\t\treturn;\n+\tstruct trailer_item *new = xcalloc(sizeof(*new), 1);\n+\tnew->token = tok;\n+\tnew->value = val;\n+\tduplicate_conf(&new->conf, conf);\n \tlist_add_tail(&new->list, head);\n }\n \n@@ -599,21 +577,28 @@ static void process_command_line_args(struct list_head *arg_head,\n {\n \tstruct string_list_item *tr;\n \tstruct trailer_item *item;\n+\tstruct strbuf tok = STRBUF_INIT;\n+\tstruct strbuf val = STRBUF_INIT;\n+\tconst struct conf_info *conf;\n \tstruct list_head *pos;\n \n \t/* Add a trailer item for each configured trailer with a command */\n \tlist_for_each(pos, &conf_head) {\n \t\titem = list_entry(pos, struct trailer_item, list);\n-\t\tif (item->conf.command) {\n-\t\t\tstruct trailer_item *new = new_trailer_item(item, NULL, NULL);\n-\t\t\tadd_trailer_item(arg_head, new);\n-\t\t}\n+\t\tif (item->conf.command)\n+\t\t\tadd_trailer_item(arg_head,\n+\t\t\t\t\t xstrdup(token_from_item(item, NULL)),\n+\t\t\t\t\t xstrdup(\"\"),\n+\t\t\t\t\t &item->conf);\n \t}\n \n \t/* Add a trailer item for each trailer on the command line */\n \tfor_each_string_list_item(tr, trailers) {\n-\t\tstruct trailer_item *new = create_trailer_item(tr->string);\n-\t\tadd_trailer_item(arg_head, new);\n+\t\tif (!parse_trailer(&tok, &val, &conf, tr->string))\n+\t\t\tadd_trailer_item(arg_head,\n+\t\t\t\t\t strbuf_detach(&tok, NULL),\n+\t\t\t\t\t strbuf_detach(&val, NULL),\n+\t\t\t\t\t conf);\n \t}\n }\n \n@@ -734,6 +719,9 @@ static int process_input_file(FILE *outfile,\n {\n \tint count = 0;\n \tint patch_start, trailer_start, trailer_end, i;\n+\tstruct strbuf tok = STRBUF_INIT;\n+\tstruct strbuf val = STRBUF_INIT;\n+\tconst struct conf_info *conf;\n \n \t/* Get the line count */\n \twhile (lines[count])\n@@ -751,10 +739,12 @@ static int process_input_file(FILE *outfile,\n \n \t/* Parse trailer lines */\n \tfor (i = trailer_start; i < trailer_end; i++) {\n-\t\tif (lines[i]->buf[0] != comment_line_char) {\n-\t\t\tstruct trailer_item *new = create_trailer_item(lines[i]->buf);\n-\t\t\tadd_trailer_item(head, new);\n-\t\t}\n+\t\tif (lines[i]->buf[0] != comment_line_char &&\n+\t\t    !parse_trailer(&tok, &val, &conf, lines[i]->buf))\n+\t\t\tadd_trailer_item(head,\n+\t\t\t\t\t strbuf_detach(&tok, NULL),\n+\t\t\t\t\t strbuf_detach(&val, NULL),\n+\t\t\t\t\t conf);\n \t}\n \n \treturn trailer_end;\n-- \n2.8.0.rc3.226.g39d4020\n\n"},{"id":"304633","messageId":"f0acfc7d23ce76a2562b9125af6f8b270baf7b8c.1477072247.git.jonathantanmy@google.com","threadId":"44285","inReplyTo":"cover.1477072247.git.jonathantanmy@google.com","subject":"[PATCH v5 2/8] trailer: use list.h for doubly-linked list","fromName":"Jonathan Tan","fromEmail":"jonathantanmy@google.com","sentAt":"2016-10-21T17:54:57Z","receivedAt":"2016-10-21T17:56:09Z","isPatch":true,"sender":{"key":"jonathantanmy@fastmail.com","avatar":null},"body":"Replace the existing handwritten implementation of a doubly-linked list\nin trailer.c with the functions and macros from list.h. This\nsignificantly simplifies the code.\n\nSigned-off-by: Jonathan Tan <jonathantanmy@google.com>\nSigned-off-by: Ramsay Jones <ramsay@ramsayjones.plus.com>\n---\n trailer.c | 258 ++++++++++++++++++++++----------------------------------------\n 1 file changed, 91 insertions(+), 167 deletions(-)\n\ndiff --git a/trailer.c b/trailer.c\nindex 1f191b2..4e85aae 100644\n--- a/trailer.c\n+++ b/trailer.c\n@@ -4,6 +4,7 @@\n #include \"commit.h\"\n #include \"tempfile.h\"\n #include \"trailer.h\"\n+#include \"list.h\"\n /*\n  * Copyright (c) 2013, 2014 Christian Couder <chriscool@tuxfamily.org>\n  */\n@@ -25,19 +26,24 @@ struct conf_info {\n static struct conf_info default_conf_info;\n \n struct trailer_item {\n-\tstruct trailer_item *previous;\n-\tstruct trailer_item *next;\n+\tstruct list_head list;\n \tchar *token;\n \tchar *value;\n \tstruct conf_info conf;\n };\n \n-static struct trailer_item *first_conf_item;\n+static LIST_HEAD(conf_head);\n \n static char *separators = \":\";\n \n #define TRAILER_ARG_STRING \"$ARG\"\n \n+/* Iterate over the elements of the list. */\n+#define list_for_each_dir(pos, head, is_reverse) \\\n+\tfor (pos = is_reverse ? (head)->prev : (head)->next; \\\n+\t\tpos != (head); \\\n+\t\tpos = is_reverse ? pos->prev : pos->next)\n+\n static int after_or_end(enum action_where where)\n {\n \treturn (where == WHERE_AFTER) || (where == WHERE_END);\n@@ -120,101 +126,49 @@ static void print_tok_val(FILE *outfile, const char *tok, const char *val)\n \t\tfprintf(outfile, \"%s%c %s\\n\", tok, separators[0], val);\n }\n \n-static void print_all(FILE *outfile, struct trailer_item *first, int trim_empty)\n+static void print_all(FILE *outfile, struct list_head *head, int trim_empty)\n {\n+\tstruct list_head *pos;\n \tstruct trailer_item *item;\n-\tfor (item = first; item; item = item->next) {\n+\tlist_for_each(pos, head) {\n+\t\titem = list_entry(pos, struct trailer_item, list);\n \t\tif (!trim_empty || strlen(item->value) > 0)\n \t\t\tprint_tok_val(outfile, item->token, item->value);\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 add_arg_to_input_list(struct trailer_item *on_tok,\n-\t\t\t\t  struct trailer_item *arg_tok,\n-\t\t\t\t  struct trailer_item **first,\n-\t\t\t\t  struct trailer_item **last)\n-{\n-\tif (after_or_end(arg_tok->conf.where)) {\n-\t\targ_tok->next = on_tok->next;\n-\t\ton_tok->next = arg_tok;\n-\t\targ_tok->previous = on_tok;\n-\t\tif (arg_tok->next)\n-\t\t\targ_tok->next->previous = arg_tok;\n-\t\tupdate_last(last);\n-\t} else {\n-\t\targ_tok->previous = on_tok->previous;\n-\t\ton_tok->previous = arg_tok;\n-\t\targ_tok->next = on_tok;\n-\t\tif (arg_tok->previous)\n-\t\t\targ_tok->previous->next = arg_tok;\n-\t\tupdate_first(first);\n-\t}\n+\t\t\t\t  struct trailer_item *arg_tok)\n+{\n+\tif (after_or_end(arg_tok->conf.where))\n+\t\tlist_add(&arg_tok->list, &on_tok->list);\n+\telse\n+\t\tlist_add_tail(&arg_tok->list, &on_tok->list);\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 check_all)\n+\t\t\t      int check_all,\n+\t\t\t      struct list_head *head)\n {\n \tenum action_where where = arg_tok->conf.where;\n+\tstruct list_head *next_head;\n \tdo {\n-\t\tif (!in_tok)\n-\t\t\treturn 1;\n \t\tif (same_trailer(in_tok, arg_tok))\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 = after_or_end(where) ? in_tok->previous : in_tok->next;\n+\t\tnext_head = after_or_end(where) ? in_tok->list.prev\n+\t\t\t\t\t\t: in_tok->list.next;\n+\t\tif (next_head == head)\n+\t\t\tbreak;\n+\t\tin_tok = list_entry(next_head, struct trailer_item, list);\n \t} while (check_all);\n \treturn 1;\n }\n \n-static void remove_from_list(struct trailer_item *item,\n-\t\t\t     struct trailer_item **first,\n-\t\t\t     struct trailer_item **last)\n-{\n-\tstruct trailer_item *next = item->next;\n-\tstruct trailer_item *previous = item->previous;\n-\n-\tif (next) {\n-\t\titem->next->previous = previous;\n-\t\titem->next = NULL;\n-\t} else if (last)\n-\t\t*last = previous;\n-\n-\tif (previous) {\n-\t\titem->previous->next = next;\n-\t\titem->previous = NULL;\n-\t} else if (first)\n-\t\t*first = 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 char *apply_command(const char *command, const char *arg)\n {\n \tstruct strbuf cmd = STRBUF_INIT;\n@@ -266,8 +220,7 @@ static void apply_item_command(struct trailer_item *in_tok, struct trailer_item\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\tstruct trailer_item *on_tok,\n-\t\t\t\tstruct trailer_item **in_tok_first,\n-\t\t\t\tstruct trailer_item **in_tok_last)\n+\t\t\t\tstruct list_head *head)\n {\n \tswitch (arg_tok->conf.if_exists) {\n \tcase EXISTS_DO_NOTHING:\n@@ -275,40 +228,34 @@ static void apply_arg_if_exists(struct trailer_item *in_tok,\n \t\tbreak;\n \tcase EXISTS_REPLACE:\n \t\tapply_item_command(in_tok, arg_tok);\n-\t\tadd_arg_to_input_list(on_tok, arg_tok,\n-\t\t\t\t      in_tok_first, in_tok_last);\n-\t\tremove_from_list(in_tok, in_tok_first, in_tok_last);\n+\t\tadd_arg_to_input_list(on_tok, arg_tok);\n+\t\tlist_del(&in_tok->list);\n \t\tfree_trailer_item(in_tok);\n \t\tbreak;\n \tcase EXISTS_ADD:\n \t\tapply_item_command(in_tok, arg_tok);\n-\t\tadd_arg_to_input_list(on_tok, arg_tok,\n-\t\t\t\t      in_tok_first, in_tok_last);\n+\t\tadd_arg_to_input_list(on_tok, arg_tok);\n \t\tbreak;\n \tcase EXISTS_ADD_IF_DIFFERENT:\n \t\tapply_item_command(in_tok, arg_tok);\n-\t\tif (check_if_different(in_tok, arg_tok, 1))\n-\t\t\tadd_arg_to_input_list(on_tok, arg_tok,\n-\t\t\t\t\t      in_tok_first, in_tok_last);\n+\t\tif (check_if_different(in_tok, arg_tok, 1, head))\n+\t\t\tadd_arg_to_input_list(on_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\tapply_item_command(in_tok, arg_tok);\n-\t\tif (check_if_different(on_tok, arg_tok, 0))\n-\t\t\tadd_arg_to_input_list(on_tok, arg_tok,\n-\t\t\t\t\t      in_tok_first, in_tok_last);\n+\t\tif (check_if_different(on_tok, arg_tok, 0, head))\n+\t\t\tadd_arg_to_input_list(on_tok, arg_tok);\n \t\telse\n \t\t\tfree_trailer_item(arg_tok);\n \t\tbreak;\n \t}\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+static void apply_arg_if_missing(struct list_head *head,\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@@ -317,68 +264,60 @@ static void apply_arg_if_missing(struct trailer_item **in_tok_first,\n \t\tbreak;\n \tcase MISSING_ADD:\n \t\twhere = arg_tok->conf.where;\n-\t\tin_tok = after_or_end(where) ? in_tok_last : in_tok_first;\n \t\tapply_item_command(NULL, arg_tok);\n-\t\tif (*in_tok) {\n-\t\t\tadd_arg_to_input_list(*in_tok, arg_tok,\n-\t\t\t\t\t      in_tok_first, in_tok_last);\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\tif (after_or_end(where))\n+\t\t\tlist_add_tail(&arg_tok->list, head);\n+\t\telse\n+\t\t\tlist_add(&arg_tok->list, head);\n \t}\n }\n \n-static int find_same_and_apply_arg(struct trailer_item **in_tok_first,\n-\t\t\t\t   struct trailer_item **in_tok_last,\n+static int find_same_and_apply_arg(struct list_head *head,\n \t\t\t\t   struct trailer_item *arg_tok)\n {\n+\tstruct list_head *pos;\n \tstruct trailer_item *in_tok;\n \tstruct trailer_item *on_tok;\n-\tstruct trailer_item *following_tok;\n \n \tenum action_where where = arg_tok->conf.where;\n \tint middle = (where == WHERE_AFTER) || (where == WHERE_BEFORE);\n \tint backwards = after_or_end(where);\n-\tstruct trailer_item *start_tok = backwards ? *in_tok_last : *in_tok_first;\n+\tstruct trailer_item *start_tok;\n \n-\tfor (in_tok = start_tok; in_tok; in_tok = following_tok) {\n-\t\tfollowing_tok = backwards ? in_tok->previous : in_tok->next;\n+\tif (list_empty(head))\n+\t\treturn 0;\n+\n+\tstart_tok = list_entry(backwards ? head->prev : head->next,\n+\t\t\t       struct trailer_item,\n+\t\t\t       list);\n+\n+\tlist_for_each_dir(pos, head, backwards) {\n+\t\tin_tok = list_entry(pos, struct trailer_item, list);\n \t\tif (!same_token(in_tok, arg_tok))\n \t\t\tcontinue;\n \t\ton_tok = middle ? in_tok : start_tok;\n-\t\tapply_arg_if_exists(in_tok, arg_tok, on_tok,\n-\t\t\t\t    in_tok_first, in_tok_last);\n+\t\tapply_arg_if_exists(in_tok, arg_tok, on_tok, head);\n \t\treturn 1;\n \t}\n \treturn 0;\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+static void process_trailers_lists(struct list_head *head,\n+\t\t\t\t   struct list_head *arg_head)\n {\n+\tstruct list_head *pos, *p;\n \tstruct trailer_item *arg_tok;\n-\tstruct trailer_item *next_arg;\n-\n-\tif (!*arg_tok_first)\n-\t\treturn;\n \n-\tfor (arg_tok = *arg_tok_first; arg_tok; arg_tok = next_arg) {\n+\tlist_for_each_safe(pos, p, arg_head) {\n \t\tint applied = 0;\n+\t\targ_tok = list_entry(pos, struct trailer_item, list);\n \n-\t\tnext_arg = arg_tok->next;\n-\t\tremove_from_list(arg_tok, arg_tok_first, NULL);\n+\t\tlist_del(pos);\n \n-\t\tapplied = find_same_and_apply_arg(in_tok_first,\n-\t\t\t\t\t\t  in_tok_last,\n-\t\t\t\t\t\t  arg_tok);\n+\t\tapplied = find_same_and_apply_arg(head, arg_tok);\n \n \t\tif (!applied)\n-\t\t\tapply_arg_if_missing(in_tok_first,\n-\t\t\t\t\t     in_tok_last,\n-\t\t\t\t\t     arg_tok);\n+\t\t\tapply_arg_if_missing(head, arg_tok);\n \t}\n }\n \n@@ -438,13 +377,12 @@ static void duplicate_conf(struct conf_info *dst, const struct conf_info *src)\n \n static struct trailer_item *get_conf_item(const char *name)\n {\n+\tstruct list_head *pos;\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+\tlist_for_each(pos, &conf_head) {\n+\t\titem = list_entry(pos, struct trailer_item, list);\n \t\tif (!strcasecmp(item->conf.name, name))\n \t\t\treturn item;\n \t}\n@@ -454,12 +392,7 @@ static struct trailer_item *get_conf_item(const char *name)\n \tduplicate_conf(&item->conf, &default_conf_info);\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+\tlist_add_tail(&item->list, &conf_head);\n \n \treturn item;\n }\n@@ -633,6 +566,7 @@ static struct trailer_item *create_trailer_item(const char *string)\n \tstruct strbuf val = STRBUF_INIT;\n \tstruct trailer_item *item;\n \tint tok_len;\n+\tstruct list_head *pos;\n \n \tif (parse_trailer(&tok, &val, string))\n \t\treturn NULL;\n@@ -640,7 +574,8 @@ static struct trailer_item *create_trailer_item(const char *string)\n \ttok_len = token_len_without_separator(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+\tlist_for_each(pos, &conf_head) {\n+\t\titem = list_entry(pos, struct trailer_item, list);\n \t\tif (token_matches_item(tok.buf, item, tok_len))\n \t\t\treturn new_trailer_item(item,\n \t\t\t\t\t\tstrbuf_detach(&tok, NULL),\n@@ -652,44 +587,34 @@ static struct trailer_item *create_trailer_item(const char *string)\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+static void add_trailer_item(struct list_head *head, 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+\tlist_add_tail(&new->list, head);\n }\n \n-static struct trailer_item *process_command_line_args(struct string_list *trailers)\n+static void process_command_line_args(struct list_head *arg_head, \n+\t\t\t\t      struct string_list *trailers)\n {\n-\tstruct trailer_item *arg_tok_first = NULL;\n-\tstruct trailer_item *arg_tok_last = NULL;\n \tstruct string_list_item *tr;\n \tstruct trailer_item *item;\n+\tstruct list_head *pos;\n \n \t/* Add a trailer item for each configured trailer with a command */\n-\tfor (item = first_conf_item; item; item = item->next) {\n+\tlist_for_each(pos, &conf_head) {\n+\t\titem = list_entry(pos, struct trailer_item, list);\n \t\tif (item->conf.command) {\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\tadd_trailer_item(arg_head, new);\n \t\t}\n \t}\n \n \t/* Add a trailer item for each trailer on the command line */\n \tfor_each_string_list_item(tr, trailers) {\n \t\tstruct trailer_item *new = create_trailer_item(tr->string);\n-\t\tadd_trailer_item(&arg_tok_first, &arg_tok_last, new);\n+\t\tadd_trailer_item(arg_head, new);\n \t}\n-\n-\treturn arg_tok_first;\n }\n \n static struct strbuf **read_input_file(const char *file)\n@@ -805,8 +730,7 @@ static void print_lines(FILE *outfile, struct strbuf **lines, int start, int end\n \n static int process_input_file(FILE *outfile,\n \t\t\t      struct strbuf **lines,\n-\t\t\t      struct trailer_item **in_tok_first,\n-\t\t\t      struct trailer_item **in_tok_last)\n+\t\t\t      struct list_head *head)\n {\n \tint count = 0;\n \tint patch_start, trailer_start, trailer_end, i;\n@@ -829,18 +753,19 @@ static int process_input_file(FILE *outfile,\n \tfor (i = trailer_start; i < trailer_end; i++) {\n \t\tif (lines[i]->buf[0] != comment_line_char) {\n \t\t\tstruct trailer_item *new = create_trailer_item(lines[i]->buf);\n-\t\t\tadd_trailer_item(in_tok_first, in_tok_last, new);\n+\t\t\tadd_trailer_item(head, new);\n \t\t}\n \t}\n \n \treturn trailer_end;\n }\n \n-static void free_all(struct trailer_item **first)\n+static void free_all(struct list_head *head)\n {\n-\twhile (*first) {\n-\t\tstruct trailer_item *item = remove_first(first);\n-\t\tfree_trailer_item(item);\n+\tstruct list_head *pos, *p;\n+\tlist_for_each_safe(pos, p, head) {\n+\t\tlist_del(pos);\n+\t\tfree_trailer_item(list_entry(pos, struct trailer_item, list));\n \t}\n }\n \n@@ -877,9 +802,8 @@ static FILE *create_in_place_tempfile(const char *file)\n \n void process_trailers(const char *file, int in_place, int trim_empty, struct string_list *trailers)\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+\tLIST_HEAD(head);\n+\tLIST_HEAD(arg_head);\n \tstruct strbuf **lines;\n \tint trailer_end;\n \tFILE *outfile = stdout;\n@@ -894,15 +818,15 @@ void process_trailers(const char *file, int in_place, int trim_empty, struct str\n \t\toutfile = create_in_place_tempfile(file);\n \n \t/* Print the lines before the trailers */\n-\ttrailer_end = process_input_file(outfile, lines, &in_tok_first, &in_tok_last);\n+\ttrailer_end = process_input_file(outfile, lines, &head);\n \n-\targ_tok_first = process_command_line_args(trailers);\n+\tprocess_command_line_args(&arg_head, trailers);\n \n-\tprocess_trailers_lists(&in_tok_first, &in_tok_last, &arg_tok_first);\n+\tprocess_trailers_lists(&head, &arg_head);\n \n-\tprint_all(outfile, in_tok_first, trim_empty);\n+\tprint_all(outfile, &head, trim_empty);\n \n-\tfree_all(&in_tok_first);\n+\tfree_all(&head);\n \n \t/* Print the lines after the trailers as is */\n \tprint_lines(outfile, lines, trailer_end, INT_MAX);\n-- \n2.8.0.rc3.226.g39d4020\n\n"},{"id":"304658","messageId":"CAPc5daX-bdSBAxy60zG2ZuGbrjGwRcsvHFktCqKw_o2QuuWTEg@mail.gmail.com","threadId":"44285","inReplyTo":"cover.1477072247.git.jonathantanmy@google.com","subject":"Re: [PATCH v5 0/8] allow non-trailers and multiple-line trailers","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2016-10-21T23:59:34Z","receivedAt":"2016-10-22T00:00:06Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"On Fri, Oct 21, 2016 at 10:54 AM, Jonathan Tan <jonathantanmy@google.com> wrote:\n> I've updated patch 5/8 to use strcspn and to pass in the list of\n> separators, meaning that we no longer accept '=' in file input (and also\n> updated its commit message accordingly).\n\nThanks for a pleasant read. Queued.\n\nHopefully this is ready for 'next' now.\n"},{"id":"304662","messageId":"CAGZ79kbjoNTJUX=UBh4JCgBgA5qOiTz-OdxP3QFZndVZiPHaMw@mail.gmail.com","threadId":"44285","inReplyTo":"CAPc5daX-bdSBAxy60zG2ZuGbrjGwRcsvHFktCqKw_o2QuuWTEg@mail.gmail.com","subject":"Re: [PATCH v5 0/8] allow non-trailers and multiple-line trailers","fromName":"Stefan Beller","fromEmail":"sbeller@google.com","sentAt":"2016-10-22T00:06:03Z","receivedAt":"2016-10-22T00:06:09Z","isPatch":true,"sender":{"key":"stefanbeller@gmail.com","avatar":"https://avatars.githubusercontent.com/u/455868?v=4"},"body":"On Fri, Oct 21, 2016 at 4:59 PM, Junio C Hamano <gitster@pobox.com> wrote:\n> On Fri, Oct 21, 2016 at 10:54 AM, Jonathan Tan <jonathantanmy@google.com> wrote:\n>> I've updated patch 5/8 to use strcspn and to pass in the list of\n>> separators, meaning that we no longer accept '=' in file input (and also\n>> updated its commit message accordingly).\n>\n> Thanks for a pleasant read. Queued.\n>\n> Hopefully this is ready for 'next' now.\n\nI also just read through and was about to say the same.\n"}]}