From d65fd424ad21adbc1ea11fcddae331698453d1e2 Mon Sep 17 00:00:00 2001 From: Jonathan Tan Date: Fri, 14 Oct 2016 10:37:58 -0700 Subject: trailer: improve const correctness Change "const char *" to "char *" in struct trailer_item and in the return value of apply_command (since those strings are owned strings). Change "struct conf_info *" to "const struct conf_info *" (since that struct is not modified). Signed-off-by: Jonathan Tan Signed-off-by: Junio C Hamano --- trailer.c | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/trailer.c b/trailer.c index c6ea9ac64d..1f191b20b2 100644 --- a/trailer.c +++ b/trailer.c @@ -27,8 +27,8 @@ static struct conf_info default_conf_info; struct trailer_item { struct trailer_item *previous; struct trailer_item *next; - const char *token; - const char *value; + char *token; + char *value; struct conf_info conf; }; @@ -95,8 +95,8 @@ static void free_trailer_item(struct trailer_item *item) free(item->conf.name); free(item->conf.key); free(item->conf.command); - free((char *)item->token); - free((char *)item->value); + free(item->token); + free(item->value); free(item); } @@ -215,13 +215,13 @@ static struct trailer_item *remove_first(struct trailer_item **first) return item; } -static const char *apply_command(const char *command, const char *arg) +static char *apply_command(const char *command, const char *arg) { struct strbuf cmd = STRBUF_INIT; struct strbuf buf = STRBUF_INIT; struct child_process cp = CHILD_PROCESS_INIT; const char *argv[] = {NULL, NULL}; - const char *result; + char *result; strbuf_addstr(&cmd, command); if (arg) @@ -425,7 +425,7 @@ static int set_if_missing(struct conf_info *item, const char *value) return 0; } -static void duplicate_conf(struct conf_info *dst, struct conf_info *src) +static void duplicate_conf(struct conf_info *dst, const struct conf_info *src) { *dst = *src; if (src->name) -- cgit v1.2.1 From 8966a39483fb2e823055b1c7f1a2ff9394d727e4 Mon Sep 17 00:00:00 2001 From: Jonathan Tan Date: Thu, 20 Oct 2016 14:39:47 -0700 Subject: trailer: use list.h for doubly-linked list Replace the existing handwritten implementation of a doubly-linked list in trailer.c with the functions and macros from list.h. This significantly simplifies the code. Signed-off-by: Jonathan Tan Signed-off-by: Ramsay Jones Signed-off-by: Junio C Hamano --- trailer.c | 258 ++++++++++++++++++++++---------------------------------------- 1 file changed, 91 insertions(+), 167 deletions(-) diff --git a/trailer.c b/trailer.c index 1f191b20b2..0b3716f514 100644 --- a/trailer.c +++ b/trailer.c @@ -4,6 +4,7 @@ #include "commit.h" #include "tempfile.h" #include "trailer.h" +#include "list.h" /* * Copyright (c) 2013, 2014 Christian Couder */ @@ -25,19 +26,24 @@ struct conf_info { static struct conf_info default_conf_info; struct trailer_item { - struct trailer_item *previous; - struct trailer_item *next; + struct list_head list; char *token; char *value; struct conf_info conf; }; -static struct trailer_item *first_conf_item; +static LIST_HEAD(conf_head); static char *separators = ":"; #define TRAILER_ARG_STRING "$ARG" +/* Iterate over the elements of the list. */ +#define list_for_each_dir(pos, head, is_reverse) \ + for (pos = is_reverse ? (head)->prev : (head)->next; \ + pos != (head); \ + pos = is_reverse ? pos->prev : pos->next) + static int after_or_end(enum action_where where) { return (where == WHERE_AFTER) || (where == WHERE_END); @@ -120,101 +126,49 @@ static void print_tok_val(FILE *outfile, const char *tok, const char *val) fprintf(outfile, "%s%c %s\n", tok, separators[0], val); } -static void print_all(FILE *outfile, struct trailer_item *first, int trim_empty) +static void print_all(FILE *outfile, struct list_head *head, int trim_empty) { + struct list_head *pos; struct trailer_item *item; - for (item = first; item; item = item->next) { + list_for_each(pos, head) { + item = list_entry(pos, struct trailer_item, list); if (!trim_empty || strlen(item->value) > 0) print_tok_val(outfile, item->token, item->value); } } -static void update_last(struct trailer_item **last) -{ - if (*last) - while ((*last)->next != NULL) - *last = (*last)->next; -} - -static void update_first(struct trailer_item **first) -{ - if (*first) - while ((*first)->previous != NULL) - *first = (*first)->previous; -} - static void add_arg_to_input_list(struct trailer_item *on_tok, - struct trailer_item *arg_tok, - struct trailer_item **first, - struct trailer_item **last) -{ - if (after_or_end(arg_tok->conf.where)) { - arg_tok->next = on_tok->next; - on_tok->next = arg_tok; - arg_tok->previous = on_tok; - if (arg_tok->next) - arg_tok->next->previous = arg_tok; - update_last(last); - } else { - arg_tok->previous = on_tok->previous; - on_tok->previous = arg_tok; - arg_tok->next = on_tok; - if (arg_tok->previous) - arg_tok->previous->next = arg_tok; - update_first(first); - } + struct trailer_item *arg_tok) +{ + if (after_or_end(arg_tok->conf.where)) + list_add(&arg_tok->list, &on_tok->list); + else + list_add_tail(&arg_tok->list, &on_tok->list); } static int check_if_different(struct trailer_item *in_tok, struct trailer_item *arg_tok, - int check_all) + int check_all, + struct list_head *head) { enum action_where where = arg_tok->conf.where; + struct list_head *next_head; do { - if (!in_tok) - return 1; if (same_trailer(in_tok, arg_tok)) return 0; /* * if we want to add a trailer after another one, * we have to check those before this one */ - in_tok = after_or_end(where) ? in_tok->previous : in_tok->next; + next_head = after_or_end(where) ? in_tok->list.prev + : in_tok->list.next; + if (next_head == head) + break; + in_tok = list_entry(next_head, struct trailer_item, list); } while (check_all); return 1; } -static void remove_from_list(struct trailer_item *item, - struct trailer_item **first, - struct trailer_item **last) -{ - struct trailer_item *next = item->next; - struct trailer_item *previous = item->previous; - - if (next) { - item->next->previous = previous; - item->next = NULL; - } else if (last) - *last = previous; - - if (previous) { - item->previous->next = next; - item->previous = NULL; - } else if (first) - *first = next; -} - -static struct trailer_item *remove_first(struct trailer_item **first) -{ - struct trailer_item *item = *first; - *first = item->next; - if (item->next) { - item->next->previous = NULL; - item->next = NULL; - } - return item; -} - static char *apply_command(const char *command, const char *arg) { struct strbuf cmd = STRBUF_INIT; @@ -266,8 +220,7 @@ static void apply_item_command(struct trailer_item *in_tok, struct trailer_item static void apply_arg_if_exists(struct trailer_item *in_tok, struct trailer_item *arg_tok, struct trailer_item *on_tok, - struct trailer_item **in_tok_first, - struct trailer_item **in_tok_last) + struct list_head *head) { switch (arg_tok->conf.if_exists) { case EXISTS_DO_NOTHING: @@ -275,40 +228,34 @@ static void apply_arg_if_exists(struct trailer_item *in_tok, break; case EXISTS_REPLACE: apply_item_command(in_tok, arg_tok); - add_arg_to_input_list(on_tok, arg_tok, - in_tok_first, in_tok_last); - remove_from_list(in_tok, in_tok_first, in_tok_last); + add_arg_to_input_list(on_tok, arg_tok); + list_del(&in_tok->list); free_trailer_item(in_tok); break; case EXISTS_ADD: apply_item_command(in_tok, arg_tok); - add_arg_to_input_list(on_tok, arg_tok, - in_tok_first, in_tok_last); + add_arg_to_input_list(on_tok, arg_tok); break; case EXISTS_ADD_IF_DIFFERENT: apply_item_command(in_tok, arg_tok); - if (check_if_different(in_tok, arg_tok, 1)) - add_arg_to_input_list(on_tok, arg_tok, - in_tok_first, in_tok_last); + if (check_if_different(in_tok, arg_tok, 1, head)) + add_arg_to_input_list(on_tok, arg_tok); else free_trailer_item(arg_tok); break; case EXISTS_ADD_IF_DIFFERENT_NEIGHBOR: apply_item_command(in_tok, arg_tok); - if (check_if_different(on_tok, arg_tok, 0)) - add_arg_to_input_list(on_tok, arg_tok, - in_tok_first, in_tok_last); + if (check_if_different(on_tok, arg_tok, 0, head)) + add_arg_to_input_list(on_tok, arg_tok); else free_trailer_item(arg_tok); break; } } -static void apply_arg_if_missing(struct trailer_item **in_tok_first, - struct trailer_item **in_tok_last, +static void apply_arg_if_missing(struct list_head *head, struct trailer_item *arg_tok) { - struct trailer_item **in_tok; enum action_where where; switch (arg_tok->conf.if_missing) { @@ -317,68 +264,60 @@ static void apply_arg_if_missing(struct trailer_item **in_tok_first, break; case MISSING_ADD: where = arg_tok->conf.where; - in_tok = after_or_end(where) ? in_tok_last : in_tok_first; apply_item_command(NULL, arg_tok); - if (*in_tok) { - add_arg_to_input_list(*in_tok, arg_tok, - in_tok_first, in_tok_last); - } else { - *in_tok_first = arg_tok; - *in_tok_last = arg_tok; - } - break; + if (after_or_end(where)) + list_add_tail(&arg_tok->list, head); + else + list_add(&arg_tok->list, head); } } -static int find_same_and_apply_arg(struct trailer_item **in_tok_first, - struct trailer_item **in_tok_last, +static int find_same_and_apply_arg(struct list_head *head, struct trailer_item *arg_tok) { + struct list_head *pos; struct trailer_item *in_tok; struct trailer_item *on_tok; - struct trailer_item *following_tok; enum action_where where = arg_tok->conf.where; int middle = (where == WHERE_AFTER) || (where == WHERE_BEFORE); int backwards = after_or_end(where); - struct trailer_item *start_tok = backwards ? *in_tok_last : *in_tok_first; + struct trailer_item *start_tok; - for (in_tok = start_tok; in_tok; in_tok = following_tok) { - following_tok = backwards ? in_tok->previous : in_tok->next; + if (list_empty(head)) + return 0; + + start_tok = list_entry(backwards ? head->prev : head->next, + struct trailer_item, + list); + + list_for_each_dir(pos, head, backwards) { + in_tok = list_entry(pos, struct trailer_item, list); if (!same_token(in_tok, arg_tok)) continue; on_tok = middle ? in_tok : start_tok; - apply_arg_if_exists(in_tok, arg_tok, on_tok, - in_tok_first, in_tok_last); + apply_arg_if_exists(in_tok, arg_tok, on_tok, head); return 1; } return 0; } -static void process_trailers_lists(struct trailer_item **in_tok_first, - struct trailer_item **in_tok_last, - struct trailer_item **arg_tok_first) +static void process_trailers_lists(struct list_head *head, + struct list_head *arg_head) { + struct list_head *pos, *p; struct trailer_item *arg_tok; - struct trailer_item *next_arg; - - if (!*arg_tok_first) - return; - for (arg_tok = *arg_tok_first; arg_tok; arg_tok = next_arg) { + list_for_each_safe(pos, p, arg_head) { int applied = 0; + arg_tok = list_entry(pos, struct trailer_item, list); - next_arg = arg_tok->next; - remove_from_list(arg_tok, arg_tok_first, NULL); + list_del(pos); - applied = find_same_and_apply_arg(in_tok_first, - in_tok_last, - arg_tok); + applied = find_same_and_apply_arg(head, arg_tok); if (!applied) - apply_arg_if_missing(in_tok_first, - in_tok_last, - arg_tok); + apply_arg_if_missing(head, arg_tok); } } @@ -438,13 +377,12 @@ static void duplicate_conf(struct conf_info *dst, const struct conf_info *src) static struct trailer_item *get_conf_item(const char *name) { + struct list_head *pos; struct trailer_item *item; - struct trailer_item *previous; /* Look up item with same name */ - for (previous = NULL, item = first_conf_item; - item; - previous = item, item = item->next) { + list_for_each(pos, &conf_head) { + item = list_entry(pos, struct trailer_item, list); if (!strcasecmp(item->conf.name, name)) return item; } @@ -454,12 +392,7 @@ static struct trailer_item *get_conf_item(const char *name) duplicate_conf(&item->conf, &default_conf_info); item->conf.name = xstrdup(name); - if (!previous) - first_conf_item = item; - else { - previous->next = item; - item->previous = previous; - } + list_add_tail(&item->list, &conf_head); return item; } @@ -633,6 +566,7 @@ static struct trailer_item *create_trailer_item(const char *string) struct strbuf val = STRBUF_INIT; struct trailer_item *item; int tok_len; + struct list_head *pos; if (parse_trailer(&tok, &val, string)) return NULL; @@ -640,7 +574,8 @@ static struct trailer_item *create_trailer_item(const char *string) tok_len = token_len_without_separator(tok.buf, tok.len); /* Lookup if the token matches something in the config */ - for (item = first_conf_item; item; item = item->next) { + list_for_each(pos, &conf_head) { + item = list_entry(pos, struct trailer_item, list); if (token_matches_item(tok.buf, item, tok_len)) return new_trailer_item(item, strbuf_detach(&tok, NULL), @@ -652,44 +587,34 @@ static struct trailer_item *create_trailer_item(const char *string) strbuf_detach(&val, NULL)); } -static void add_trailer_item(struct trailer_item **first, - struct trailer_item **last, - struct trailer_item *new) +static void add_trailer_item(struct list_head *head, struct trailer_item *new) { if (!new) return; - if (!*last) { - *first = new; - *last = new; - } else { - (*last)->next = new; - new->previous = *last; - *last = new; - } + list_add_tail(&new->list, head); } -static struct trailer_item *process_command_line_args(struct string_list *trailers) +static void process_command_line_args(struct list_head *arg_head, + struct string_list *trailers) { - struct trailer_item *arg_tok_first = NULL; - struct trailer_item *arg_tok_last = NULL; struct string_list_item *tr; struct trailer_item *item; + struct list_head *pos; /* Add a trailer item for each configured trailer with a command */ - for (item = first_conf_item; item; item = item->next) { + list_for_each(pos, &conf_head) { + item = list_entry(pos, struct trailer_item, list); if (item->conf.command) { struct trailer_item *new = new_trailer_item(item, NULL, NULL); - add_trailer_item(&arg_tok_first, &arg_tok_last, new); + add_trailer_item(arg_head, new); } } /* Add a trailer item for each trailer on the command line */ for_each_string_list_item(tr, trailers) { struct trailer_item *new = create_trailer_item(tr->string); - add_trailer_item(&arg_tok_first, &arg_tok_last, new); + add_trailer_item(arg_head, new); } - - return arg_tok_first; } static struct strbuf **read_input_file(const char *file) @@ -805,8 +730,7 @@ static void print_lines(FILE *outfile, struct strbuf **lines, int start, int end static int process_input_file(FILE *outfile, struct strbuf **lines, - struct trailer_item **in_tok_first, - struct trailer_item **in_tok_last) + struct list_head *head) { int count = 0; int patch_start, trailer_start, trailer_end, i; @@ -829,18 +753,19 @@ static int process_input_file(FILE *outfile, for (i = trailer_start; i < trailer_end; i++) { if (lines[i]->buf[0] != comment_line_char) { struct trailer_item *new = create_trailer_item(lines[i]->buf); - add_trailer_item(in_tok_first, in_tok_last, new); + add_trailer_item(head, new); } } return trailer_end; } -static void free_all(struct trailer_item **first) +static void free_all(struct list_head *head) { - while (*first) { - struct trailer_item *item = remove_first(first); - free_trailer_item(item); + struct list_head *pos, *p; + list_for_each_safe(pos, p, head) { + list_del(pos); + free_trailer_item(list_entry(pos, struct trailer_item, list)); } } @@ -877,9 +802,8 @@ static FILE *create_in_place_tempfile(const char *file) void process_trailers(const char *file, int in_place, int trim_empty, struct string_list *trailers) { - struct trailer_item *in_tok_first = NULL; - struct trailer_item *in_tok_last = NULL; - struct trailer_item *arg_tok_first; + LIST_HEAD(head); + LIST_HEAD(arg_head); struct strbuf **lines; int trailer_end; FILE *outfile = stdout; @@ -894,15 +818,15 @@ void process_trailers(const char *file, int in_place, int trim_empty, struct str outfile = create_in_place_tempfile(file); /* Print the lines before the trailers */ - trailer_end = process_input_file(outfile, lines, &in_tok_first, &in_tok_last); + trailer_end = process_input_file(outfile, lines, &head); - arg_tok_first = process_command_line_args(trailers); + process_command_line_args(&arg_head, trailers); - process_trailers_lists(&in_tok_first, &in_tok_last, &arg_tok_first); + process_trailers_lists(&head, &arg_head); - print_all(outfile, in_tok_first, trim_empty); + print_all(outfile, &head, trim_empty); - free_all(&in_tok_first); + free_all(&head); /* Print the lines after the trailers as is */ print_lines(outfile, lines, trailer_end, INT_MAX); -- cgit v1.2.1 From 63ab3f348458212c3e5d493465acd615303fb45a Mon Sep 17 00:00:00 2001 From: Jonathan Tan Date: Thu, 20 Oct 2016 14:39:48 -0700 Subject: trailer: streamline trailer item create and add Currently, creation and addition (to a list) of trailer items are spread across multiple functions. Streamline this by only having 2 functions: one to parse the user-supplied string, and one to add the parsed information to a list. Signed-off-by: Jonathan Tan Signed-off-by: Junio C Hamano --- trailer.c | 130 +++++++++++++++++++++++++++++--------------------------------- 1 file changed, 60 insertions(+), 70 deletions(-) diff --git a/trailer.c b/trailer.c index 0b3716f514..dd6462b500 100644 --- a/trailer.c +++ b/trailer.c @@ -500,10 +500,31 @@ static int git_trailer_config(const char *conf_key, const char *value, void *cb) return 0; } -static int parse_trailer(struct strbuf *tok, struct strbuf *val, const char *trailer) +static const char *token_from_item(struct trailer_item *item, char *tok) +{ + if (item->conf.key) + return item->conf.key; + if (tok) + return tok; + return item->conf.name; +} + +static int token_matches_item(const char *tok, struct trailer_item *item, int tok_len) +{ + if (!strncasecmp(tok, item->conf.name, tok_len)) + return 1; + return item->conf.key ? !strncasecmp(tok, item->conf.key, tok_len) : 0; +} + +static int parse_trailer(struct strbuf *tok, struct strbuf *val, + const struct conf_info **conf, const char *trailer) { size_t len; struct strbuf seps = STRBUF_INIT; + struct trailer_item *item; + int tok_len; + struct list_head *pos; + strbuf_addstr(&seps, separators); strbuf_addch(&seps, '='); len = strcspn(trailer, seps.buf); @@ -523,74 +544,31 @@ static int parse_trailer(struct strbuf *tok, struct strbuf *val, const char *tra strbuf_addstr(tok, trailer); strbuf_trim(tok); } - return 0; -} - -static const char *token_from_item(struct trailer_item *item, char *tok) -{ - if (item->conf.key) - return item->conf.key; - if (tok) - return tok; - return item->conf.name; -} - -static struct trailer_item *new_trailer_item(struct trailer_item *conf_item, - char *tok, char *val) -{ - struct trailer_item *new = xcalloc(sizeof(*new), 1); - new->value = val ? val : xstrdup(""); - - if (conf_item) { - duplicate_conf(&new->conf, &conf_item->conf); - new->token = xstrdup(token_from_item(conf_item, tok)); - free(tok); - } else { - duplicate_conf(&new->conf, &default_conf_info); - new->token = tok; - } - - return new; -} - -static int token_matches_item(const char *tok, struct trailer_item *item, int tok_len) -{ - if (!strncasecmp(tok, item->conf.name, tok_len)) - return 1; - return item->conf.key ? !strncasecmp(tok, item->conf.key, tok_len) : 0; -} - -static struct trailer_item *create_trailer_item(const char *string) -{ - struct strbuf tok = STRBUF_INIT; - struct strbuf val = STRBUF_INIT; - struct trailer_item *item; - int tok_len; - struct list_head *pos; - - if (parse_trailer(&tok, &val, string)) - return NULL; - - tok_len = token_len_without_separator(tok.buf, tok.len); /* Lookup if the token matches something in the config */ + tok_len = token_len_without_separator(tok->buf, tok->len); + *conf = &default_conf_info; list_for_each(pos, &conf_head) { item = list_entry(pos, struct trailer_item, list); - if (token_matches_item(tok.buf, item, tok_len)) - return new_trailer_item(item, - strbuf_detach(&tok, NULL), - strbuf_detach(&val, NULL)); + if (token_matches_item(tok->buf, item, tok_len)) { + char *tok_buf = strbuf_detach(tok, NULL); + *conf = &item->conf; + strbuf_addstr(tok, token_from_item(item, tok_buf)); + free(tok_buf); + break; + } } - return new_trailer_item(NULL, - strbuf_detach(&tok, NULL), - strbuf_detach(&val, NULL)); + return 0; } -static void add_trailer_item(struct list_head *head, struct trailer_item *new) +static void add_trailer_item(struct list_head *head, char *tok, char *val, + const struct conf_info *conf) { - if (!new) - return; + struct trailer_item *new = xcalloc(sizeof(*new), 1); + new->token = tok; + new->value = val; + duplicate_conf(&new->conf, conf); list_add_tail(&new->list, head); } @@ -599,21 +577,28 @@ static void process_command_line_args(struct list_head *arg_head, { struct string_list_item *tr; struct trailer_item *item; + struct strbuf tok = STRBUF_INIT; + struct strbuf val = STRBUF_INIT; + const struct conf_info *conf; struct list_head *pos; /* Add a trailer item for each configured trailer with a command */ list_for_each(pos, &conf_head) { item = list_entry(pos, struct trailer_item, list); - if (item->conf.command) { - struct trailer_item *new = new_trailer_item(item, NULL, NULL); - add_trailer_item(arg_head, new); - } + if (item->conf.command) + add_trailer_item(arg_head, + xstrdup(token_from_item(item, NULL)), + xstrdup(""), + &item->conf); } /* Add a trailer item for each trailer on the command line */ for_each_string_list_item(tr, trailers) { - struct trailer_item *new = create_trailer_item(tr->string); - add_trailer_item(arg_head, new); + if (!parse_trailer(&tok, &val, &conf, tr->string)) + add_trailer_item(arg_head, + strbuf_detach(&tok, NULL), + strbuf_detach(&val, NULL), + conf); } } @@ -734,6 +719,9 @@ static int process_input_file(FILE *outfile, { int count = 0; int patch_start, trailer_start, trailer_end, i; + struct strbuf tok = STRBUF_INIT; + struct strbuf val = STRBUF_INIT; + const struct conf_info *conf; /* Get the line count */ while (lines[count]) @@ -751,10 +739,12 @@ static int process_input_file(FILE *outfile, /* Parse trailer lines */ for (i = trailer_start; i < trailer_end; i++) { - if (lines[i]->buf[0] != comment_line_char) { - struct trailer_item *new = create_trailer_item(lines[i]->buf); - add_trailer_item(head, new); - } + if (lines[i]->buf[0] != comment_line_char && + !parse_trailer(&tok, &val, &conf, lines[i]->buf)) + add_trailer_item(head, + strbuf_detach(&tok, NULL), + strbuf_detach(&val, NULL), + conf); } return trailer_end; -- cgit v1.2.1 From cc71b0de115808835486bc10d094b49261128276 Mon Sep 17 00:00:00 2001 From: Jonathan Tan Date: Thu, 20 Oct 2016 14:39:49 -0700 Subject: trailer: make args have their own struct Improve type safety by making arguments (whether from configuration or from the command line) have their own "struct arg_item" type, separate from the "struct trailer_item" type used to represent the trailers in the buffer being manipulated. This change also prepares "struct trailer_item" to be further differentiated from "struct arg_item" in future patches. Signed-off-by: Jonathan Tan Signed-off-by: Junio C Hamano --- trailer.c | 135 +++++++++++++++++++++++++++++++++++++++----------------------- 1 file changed, 85 insertions(+), 50 deletions(-) diff --git a/trailer.c b/trailer.c index dd6462b500..ea6eeb032b 100644 --- a/trailer.c +++ b/trailer.c @@ -29,6 +29,12 @@ struct trailer_item { struct list_head list; char *token; char *value; +}; + +struct arg_item { + struct list_head list; + char *token; + char *value; struct conf_info conf; }; @@ -62,7 +68,7 @@ static size_t token_len_without_separator(const char *token, size_t len) return len; } -static int same_token(struct trailer_item *a, struct trailer_item *b) +static int same_token(struct trailer_item *a, struct arg_item *b) { size_t a_len = token_len_without_separator(a->token, strlen(a->token)); size_t b_len = token_len_without_separator(b->token, strlen(b->token)); @@ -71,12 +77,12 @@ static int same_token(struct trailer_item *a, struct trailer_item *b) return !strncasecmp(a->token, b->token, min_len); } -static int same_value(struct trailer_item *a, struct trailer_item *b) +static int same_value(struct trailer_item *a, struct arg_item *b) { return !strcasecmp(a->value, b->value); } -static int same_trailer(struct trailer_item *a, struct trailer_item *b) +static int same_trailer(struct trailer_item *a, struct arg_item *b) { return same_token(a, b) && same_value(a, b); } @@ -97,6 +103,13 @@ static inline void strbuf_replace(struct strbuf *sb, const char *a, const char * } static void free_trailer_item(struct trailer_item *item) +{ + free(item->token); + free(item->value); + free(item); +} + +static void free_arg_item(struct arg_item *item) { free(item->conf.name); free(item->conf.key); @@ -137,17 +150,29 @@ static void print_all(FILE *outfile, struct list_head *head, int trim_empty) } } +static struct trailer_item *trailer_from_arg(struct arg_item *arg_tok) +{ + struct trailer_item *new = xcalloc(sizeof(*new), 1); + new->token = arg_tok->token; + new->value = arg_tok->value; + arg_tok->token = arg_tok->value = NULL; + free_arg_item(arg_tok); + return new; +} + static void add_arg_to_input_list(struct trailer_item *on_tok, - struct trailer_item *arg_tok) + struct arg_item *arg_tok) { - if (after_or_end(arg_tok->conf.where)) - list_add(&arg_tok->list, &on_tok->list); + int aoe = after_or_end(arg_tok->conf.where); + struct trailer_item *to_add = trailer_from_arg(arg_tok); + if (aoe) + list_add(&to_add->list, &on_tok->list); else - list_add_tail(&arg_tok->list, &on_tok->list); + list_add_tail(&to_add->list, &on_tok->list); } static int check_if_different(struct trailer_item *in_tok, - struct trailer_item *arg_tok, + struct arg_item *arg_tok, int check_all, struct list_head *head) { @@ -200,7 +225,7 @@ static char *apply_command(const char *command, const char *arg) return result; } -static void apply_item_command(struct trailer_item *in_tok, struct trailer_item *arg_tok) +static void apply_item_command(struct trailer_item *in_tok, struct arg_item *arg_tok) { if (arg_tok->conf.command) { const char *arg; @@ -218,13 +243,13 @@ static void apply_item_command(struct trailer_item *in_tok, struct trailer_item } static void apply_arg_if_exists(struct trailer_item *in_tok, - struct trailer_item *arg_tok, + struct arg_item *arg_tok, struct trailer_item *on_tok, struct list_head *head) { switch (arg_tok->conf.if_exists) { case EXISTS_DO_NOTHING: - free_trailer_item(arg_tok); + free_arg_item(arg_tok); break; case EXISTS_REPLACE: apply_item_command(in_tok, arg_tok); @@ -241,39 +266,41 @@ static void apply_arg_if_exists(struct trailer_item *in_tok, if (check_if_different(in_tok, arg_tok, 1, head)) add_arg_to_input_list(on_tok, arg_tok); else - free_trailer_item(arg_tok); + free_arg_item(arg_tok); break; case EXISTS_ADD_IF_DIFFERENT_NEIGHBOR: apply_item_command(in_tok, arg_tok); if (check_if_different(on_tok, arg_tok, 0, head)) add_arg_to_input_list(on_tok, arg_tok); else - free_trailer_item(arg_tok); + free_arg_item(arg_tok); break; } } static void apply_arg_if_missing(struct list_head *head, - struct trailer_item *arg_tok) + struct arg_item *arg_tok) { enum action_where where; + struct trailer_item *to_add; switch (arg_tok->conf.if_missing) { case MISSING_DO_NOTHING: - free_trailer_item(arg_tok); + free_arg_item(arg_tok); break; case MISSING_ADD: where = arg_tok->conf.where; apply_item_command(NULL, arg_tok); + to_add = trailer_from_arg(arg_tok); if (after_or_end(where)) - list_add_tail(&arg_tok->list, head); + list_add_tail(&to_add->list, head); else - list_add(&arg_tok->list, head); + list_add(&to_add->list, head); } } static int find_same_and_apply_arg(struct list_head *head, - struct trailer_item *arg_tok) + struct arg_item *arg_tok) { struct list_head *pos; struct trailer_item *in_tok; @@ -306,11 +333,11 @@ static void process_trailers_lists(struct list_head *head, struct list_head *arg_head) { struct list_head *pos, *p; - struct trailer_item *arg_tok; + struct arg_item *arg_tok; list_for_each_safe(pos, p, arg_head) { int applied = 0; - arg_tok = list_entry(pos, struct trailer_item, list); + arg_tok = list_entry(pos, struct arg_item, list); list_del(pos); @@ -375,20 +402,20 @@ static void duplicate_conf(struct conf_info *dst, const struct conf_info *src) dst->command = xstrdup(src->command); } -static struct trailer_item *get_conf_item(const char *name) +static struct arg_item *get_conf_item(const char *name) { struct list_head *pos; - struct trailer_item *item; + struct arg_item *item; /* Look up item with same name */ list_for_each(pos, &conf_head) { - item = list_entry(pos, struct trailer_item, list); + item = list_entry(pos, struct arg_item, list); if (!strcasecmp(item->conf.name, name)) return item; } /* Item does not already exists, create it */ - item = xcalloc(sizeof(struct trailer_item), 1); + item = xcalloc(sizeof(*item), 1); duplicate_conf(&item->conf, &default_conf_info); item->conf.name = xstrdup(name); @@ -442,7 +469,7 @@ static int git_trailer_default_config(const char *conf_key, const char *value, v static int git_trailer_config(const char *conf_key, const char *value, void *cb) { const char *trailer_item, *variable_name; - struct trailer_item *item; + struct arg_item *item; struct conf_info *conf; char *name = NULL; enum trailer_info_type type; @@ -500,7 +527,7 @@ static int git_trailer_config(const char *conf_key, const char *value, void *cb) return 0; } -static const char *token_from_item(struct trailer_item *item, char *tok) +static const char *token_from_item(struct arg_item *item, char *tok) { if (item->conf.key) return item->conf.key; @@ -509,7 +536,7 @@ static const char *token_from_item(struct trailer_item *item, char *tok) return item->conf.name; } -static int token_matches_item(const char *tok, struct trailer_item *item, int tok_len) +static int token_matches_item(const char *tok, struct arg_item *item, int tok_len) { if (!strncasecmp(tok, item->conf.name, tok_len)) return 1; @@ -521,7 +548,7 @@ static int parse_trailer(struct strbuf *tok, struct strbuf *val, { size_t len; struct strbuf seps = STRBUF_INIT; - struct trailer_item *item; + struct arg_item *item; int tok_len; struct list_head *pos; @@ -547,12 +574,14 @@ static int parse_trailer(struct strbuf *tok, struct strbuf *val, /* Lookup if the token matches something in the config */ tok_len = token_len_without_separator(tok->buf, tok->len); - *conf = &default_conf_info; + if (conf) + *conf = &default_conf_info; list_for_each(pos, &conf_head) { - item = list_entry(pos, struct trailer_item, list); + item = list_entry(pos, struct arg_item, list); if (token_matches_item(tok->buf, item, tok_len)) { char *tok_buf = strbuf_detach(tok, NULL); - *conf = &item->conf; + if (conf) + *conf = &item->conf; strbuf_addstr(tok, token_from_item(item, tok_buf)); free(tok_buf); break; @@ -562,43 +591,51 @@ static int parse_trailer(struct strbuf *tok, struct strbuf *val, return 0; } -static void add_trailer_item(struct list_head *head, char *tok, char *val, - const struct conf_info *conf) +static void add_trailer_item(struct list_head *head, char *tok, char *val) { struct trailer_item *new = xcalloc(sizeof(*new), 1); new->token = tok; new->value = val; - duplicate_conf(&new->conf, conf); list_add_tail(&new->list, head); } +static void add_arg_item(struct list_head *arg_head, char *tok, char *val, + const struct conf_info *conf) +{ + struct arg_item *new = xcalloc(sizeof(*new), 1); + new->token = tok; + new->value = val; + duplicate_conf(&new->conf, conf); + list_add_tail(&new->list, arg_head); +} + static void process_command_line_args(struct list_head *arg_head, struct string_list *trailers) { struct string_list_item *tr; - struct trailer_item *item; + struct arg_item *item; struct strbuf tok = STRBUF_INIT; struct strbuf val = STRBUF_INIT; const struct conf_info *conf; struct list_head *pos; - /* Add a trailer item for each configured trailer with a command */ + /* Add an arg item for each configured trailer with a command */ list_for_each(pos, &conf_head) { - item = list_entry(pos, struct trailer_item, list); + item = list_entry(pos, struct arg_item, list); if (item->conf.command) - add_trailer_item(arg_head, - xstrdup(token_from_item(item, NULL)), - xstrdup(""), - &item->conf); + add_arg_item(arg_head, + xstrdup(token_from_item(item, NULL)), + xstrdup(""), + &item->conf); } - /* Add a trailer item for each trailer on the command line */ + /* Add an arg item for each trailer on the command line */ for_each_string_list_item(tr, trailers) { if (!parse_trailer(&tok, &val, &conf, tr->string)) - add_trailer_item(arg_head, - strbuf_detach(&tok, NULL), - strbuf_detach(&val, NULL), - conf); + add_arg_item(arg_head, + strbuf_detach(&tok, NULL), + strbuf_detach(&val, NULL), + conf); } } @@ -721,7 +758,6 @@ static int process_input_file(FILE *outfile, int patch_start, trailer_start, trailer_end, i; struct strbuf tok = STRBUF_INIT; struct strbuf val = STRBUF_INIT; - const struct conf_info *conf; /* Get the line count */ while (lines[count]) @@ -740,11 +776,10 @@ static int process_input_file(FILE *outfile, /* Parse trailer lines */ for (i = trailer_start; i < trailer_end; i++) { if (lines[i]->buf[0] != comment_line_char && - !parse_trailer(&tok, &val, &conf, lines[i]->buf)) + !parse_trailer(&tok, &val, NULL, lines[i]->buf)) add_trailer_item(head, strbuf_detach(&tok, NULL), - strbuf_detach(&val, NULL), - conf); + strbuf_detach(&val, NULL)); } return trailer_end; -- cgit v1.2.1 From fdbf4510aeb7e7b860c2ee77b8a4a3c5787fe182 Mon Sep 17 00:00:00 2001 From: Jonathan Tan Date: Fri, 21 Oct 2016 10:55:00 -0700 Subject: trailer: clarify failure modes in parse_trailer The parse_trailer function has a few modes of operation, all depending on whether the separator is present in its input, and if yes, the separator's position. Some of these modes are failure modes, and these failure modes are handled differently depending on whether the trailer line was sourced from a file or from a command-line argument. Extract a function to find the separator, allowing the invokers of parse_trailer to determine how to handle the failure modes instead of making parse_trailer do it. In this function, also take in the list of separators, so that we can distinguish between command line arguments (which allow '=' as separator) and file input (which does not allow '=' as separator). Signed-off-by: Jonathan Tan Signed-off-by: Junio C Hamano --- trailer.c | 75 ++++++++++++++++++++++++++++++++++++++++++++------------------- 1 file changed, 53 insertions(+), 22 deletions(-) diff --git a/trailer.c b/trailer.c index ea6eeb032b..eeaafc4976 100644 --- a/trailer.c +++ b/trailer.c @@ -543,29 +543,37 @@ static int token_matches_item(const char *tok, struct arg_item *item, int tok_le return item->conf.key ? !strncasecmp(tok, item->conf.key, tok_len) : 0; } -static int parse_trailer(struct strbuf *tok, struct strbuf *val, - const struct conf_info **conf, const char *trailer) +/* + * Return the location of the first separator in line, or -1 if there is no + * separator. + */ +static int find_separator(const char *line, const char *separators) +{ + int loc = strcspn(line, separators); + if (!line[loc]) + return -1; + return loc; +} + +/* + * Obtain the token, value, and conf from the given trailer. + * + * separator_pos must not be 0, since the token cannot be an empty string. + * + * If separator_pos is -1, interpret the whole trailer as a token. + */ +static void parse_trailer(struct strbuf *tok, struct strbuf *val, + const struct conf_info **conf, const char *trailer, + int separator_pos) { - size_t len; - struct strbuf seps = STRBUF_INIT; struct arg_item *item; int tok_len; struct list_head *pos; - strbuf_addstr(&seps, separators); - strbuf_addch(&seps, '='); - len = strcspn(trailer, seps.buf); - strbuf_release(&seps); - if (len == 0) { - int l = strlen(trailer); - while (l > 0 && isspace(trailer[l - 1])) - l--; - return error(_("empty trailer token in trailer '%.*s'"), l, trailer); - } - if (len < strlen(trailer)) { - strbuf_add(tok, trailer, len); + if (separator_pos != -1) { + strbuf_add(tok, trailer, separator_pos); strbuf_trim(tok); - strbuf_addstr(val, trailer + len + 1); + strbuf_addstr(val, trailer + separator_pos + 1); strbuf_trim(val); } else { strbuf_addstr(tok, trailer); @@ -587,8 +595,6 @@ static int parse_trailer(struct strbuf *tok, struct strbuf *val, break; } } - - return 0; } static void add_trailer_item(struct list_head *head, char *tok, char *val) @@ -619,6 +625,12 @@ static void process_command_line_args(struct list_head *arg_head, const struct conf_info *conf; struct list_head *pos; + /* + * In command-line arguments, '=' is accepted (in addition to the + * separators that are defined). + */ + char *cl_separators = xstrfmt("=%s", separators); + /* Add an arg item for each configured trailer with a command */ list_for_each(pos, &conf_head) { item = list_entry(pos, struct arg_item, list); @@ -631,12 +643,25 @@ static void process_command_line_args(struct list_head *arg_head, /* Add an arg item for each trailer on the command line */ for_each_string_list_item(tr, trailers) { - if (!parse_trailer(&tok, &val, &conf, tr->string)) + int separator_pos = find_separator(tr->string, cl_separators); + if (separator_pos == 0) { + struct strbuf sb = STRBUF_INIT; + strbuf_addstr(&sb, tr->string); + strbuf_trim(&sb); + error(_("empty trailer token in trailer '%.*s'"), + (int) sb.len, sb.buf); + strbuf_release(&sb); + } else { + parse_trailer(&tok, &val, &conf, tr->string, + separator_pos); add_arg_item(arg_head, strbuf_detach(&tok, NULL), strbuf_detach(&val, NULL), conf); + } } + + free(cl_separators); } static struct strbuf **read_input_file(const char *file) @@ -775,11 +800,17 @@ static int process_input_file(FILE *outfile, /* Parse trailer lines */ for (i = trailer_start; i < trailer_end; i++) { - if (lines[i]->buf[0] != comment_line_char && - !parse_trailer(&tok, &val, NULL, lines[i]->buf)) + int separator_pos; + if (lines[i]->buf[0] == comment_line_char) + continue; + separator_pos = find_separator(lines[i]->buf, separators); + if (separator_pos >= 1) { + parse_trailer(&tok, &val, NULL, lines[i]->buf, + separator_pos); add_trailer_item(head, strbuf_detach(&tok, NULL), strbuf_detach(&val, NULL)); + } } return trailer_end; -- cgit v1.2.1 From 146245063e286d5a38b146bd5a38da958bd3957b Mon Sep 17 00:00:00 2001 From: Jonathan Tan Date: Fri, 21 Oct 2016 10:55:01 -0700 Subject: trailer: allow non-trailers in trailer block Currently, interpret-trailers requires all lines of a trailer block to be trailers (or comments) - if not it would not identify that block as a trailer block, and thus create its own trailer block, inserting a blank line. For example: echo -e "\nSigned-off-by: x\nnot trailer" | git interpret-trailers --trailer "c: d" would result in: Signed-off-by: x not trailer c: d Relax the definition of a trailer block to require that the trailers (i) are all trailers, or (ii) contain at least one Git-generated trailer and consists of at least 25% trailers. Signed-off-by: x not trailer c: d (i) is the existing functionality. (ii) allows arbitrary lines to be included in trailer blocks, like those in [1], and still allow interpret-trailers to be used. [1] https://kernel.googlesource.com/pub/scm/linux/kernel/git/stable/linux-stable/+/e7d316a02f683864a12389f8808570e37fb90aa3 Signed-off-by: Jonathan Tan Signed-off-by: Junio C Hamano --- Documentation/git-interpret-trailers.txt | 5 +- t/t7513-interpret-trailers.sh | 115 +++++++++++++++++++++++++++++++ trailer.c | 89 ++++++++++++++++++++---- 3 files changed, 194 insertions(+), 15 deletions(-) diff --git a/Documentation/git-interpret-trailers.txt b/Documentation/git-interpret-trailers.txt index 93d1db6528..cf4c5ea45f 100644 --- a/Documentation/git-interpret-trailers.txt +++ b/Documentation/git-interpret-trailers.txt @@ -48,8 +48,9 @@ with only spaces at the end of the commit message part, one blank line will be added before the new trailer. Existing trailers are extracted from the input message by looking for -a group of one or more lines that contain a colon (by default), where -the group is preceded by one or more empty (or whitespace-only) lines. +a group of one or more lines that (i) are all trailers, or (ii) contains at +least one Git-generated trailer and consists of at least 25% trailers. +The group must be preceded by one or more empty (or whitespace-only) lines. The group must either be at the end of the message or be the last non-whitespace lines before a line that starts with '---'. Such three minus signs start the patch part of the message. diff --git a/t/t7513-interpret-trailers.sh b/t/t7513-interpret-trailers.sh index aee785cffa..003e90f8cf 100755 --- a/t/t7513-interpret-trailers.sh +++ b/t/t7513-interpret-trailers.sh @@ -126,6 +126,121 @@ test_expect_success 'with multiline title in the message' ' test_cmp expected actual ' +test_expect_success 'with non-trailer lines mixed with Signed-off-by' ' + cat >patch <<-\EOF && + + this is not a trailer + this is not a trailer + Signed-off-by: a + this is not a trailer + EOF + cat >expected <<-\EOF && + + this is not a trailer + this is not a trailer + Signed-off-by: a + this is not a trailer + token: value + EOF + git interpret-trailers --trailer "token: value" patch >actual && + test_cmp expected actual +' + +test_expect_success 'with non-trailer lines mixed with cherry picked from' ' + cat >patch <<-\EOF && + + this is not a trailer + this is not a trailer + (cherry picked from commit x) + this is not a trailer + EOF + cat >expected <<-\EOF && + + this is not a trailer + this is not a trailer + (cherry picked from commit x) + this is not a trailer + token: value + EOF + git interpret-trailers --trailer "token: value" patch >actual && + test_cmp expected actual +' + +test_expect_success 'with non-trailer lines mixed with a configured trailer' ' + cat >patch <<-\EOF && + + this is not a trailer + this is not a trailer + My-trailer: x + this is not a trailer + EOF + cat >expected <<-\EOF && + + this is not a trailer + this is not a trailer + My-trailer: x + this is not a trailer + token: value + EOF + test_config trailer.my.key "My-trailer: " && + git interpret-trailers --trailer "token: value" patch >actual && + test_cmp expected actual +' + +test_expect_success 'with non-trailer lines mixed with a non-configured trailer' ' + cat >patch <<-\EOF && + + this is not a trailer + this is not a trailer + I-am-not-configured: x + this is not a trailer + EOF + cat >expected <<-\EOF && + + this is not a trailer + this is not a trailer + I-am-not-configured: x + this is not a trailer + + token: value + EOF + test_config trailer.my.key "My-trailer: " && + git interpret-trailers --trailer "token: value" patch >actual && + test_cmp expected actual +' + +test_expect_success 'with all non-configured trailers' ' + cat >patch <<-\EOF && + + I-am-not-configured: x + I-am-also-not-configured: x + EOF + cat >expected <<-\EOF && + + I-am-not-configured: x + I-am-also-not-configured: x + token: value + EOF + test_config trailer.my.key "My-trailer: " && + git interpret-trailers --trailer "token: value" patch >actual && + test_cmp expected actual +' + +test_expect_success 'with non-trailer lines only' ' + cat >patch <<-\EOF && + + this is not a trailer + EOF + cat >expected <<-\EOF && + + this is not a trailer + + token: value + EOF + git interpret-trailers --trailer "token: value" patch >actual && + test_cmp expected actual +' + test_expect_success 'with config setup' ' git config trailer.ack.key "Acked-by: " && cat >expected <<-\EOF && diff --git a/trailer.c b/trailer.c index eeaafc4976..4c5b931bcc 100644 --- a/trailer.c +++ b/trailer.c @@ -27,6 +27,10 @@ static struct conf_info default_conf_info; struct trailer_item { struct list_head list; + /* + * If this is not a trailer line, the line is stored in value + * (excluding the terminating newline) and token is NULL. + */ char *token; char *value; }; @@ -44,6 +48,12 @@ static char *separators = ":"; #define TRAILER_ARG_STRING "$ARG" +static const char *git_generated_prefixes[] = { + "Signed-off-by: ", + "(cherry picked from commit ", + NULL +}; + /* Iterate over the elements of the list. */ #define list_for_each_dir(pos, head, is_reverse) \ for (pos = is_reverse ? (head)->prev : (head)->next; \ @@ -70,9 +80,14 @@ static size_t token_len_without_separator(const char *token, size_t len) static int same_token(struct trailer_item *a, struct arg_item *b) { - size_t a_len = token_len_without_separator(a->token, strlen(a->token)); - size_t b_len = token_len_without_separator(b->token, strlen(b->token)); - size_t min_len = (a_len > b_len) ? b_len : a_len; + size_t a_len, b_len, min_len; + + if (!a->token) + return 0; + + a_len = token_len_without_separator(a->token, strlen(a->token)); + b_len = token_len_without_separator(b->token, strlen(b->token)); + min_len = (a_len > b_len) ? b_len : a_len; return !strncasecmp(a->token, b->token, min_len); } @@ -130,7 +145,14 @@ static char last_non_space_char(const char *s) static void print_tok_val(FILE *outfile, const char *tok, const char *val) { - char c = last_non_space_char(tok); + char c; + + if (!tok) { + fprintf(outfile, "%s\n", val); + return; + } + + c = last_non_space_char(tok); if (!c) return; if (strchr(separators, c)) @@ -709,6 +731,7 @@ static int find_patch_start(struct strbuf **lines, int count) static int find_trailer_start(struct strbuf **lines, int count) { int start, end_of_title, only_spaces = 1; + int recognized_prefix = 0, trailer_lines = 0, non_trailer_lines = 0; /* The first paragraph is the title and cannot be trailers */ for (start = 0; start < count; start++) { @@ -720,26 +743,60 @@ static int find_trailer_start(struct strbuf **lines, int count) end_of_title = start; /* - * Get the start of the trailers by looking starting from the end - * for a line with only spaces before lines with one separator. + * Get the start of the trailers by looking starting from the end for a + * blank line before a set of non-blank lines that (i) are all + * trailers, or (ii) contains at least one Git-generated trailer and + * consists of at least 25% trailers. */ for (start = count - 1; start >= end_of_title; start--) { + const char **p; + int separator_pos; + if (lines[start]->buf[0] == comment_line_char) continue; if (contains_only_spaces(lines[start]->buf)) { if (only_spaces) continue; - return start + 1; + if (recognized_prefix && + trailer_lines * 3 >= non_trailer_lines) + return start + 1; + if (trailer_lines && !non_trailer_lines) + return start + 1; + return count; } - if (strcspn(lines[start]->buf, separators) < lines[start]->len) { - if (only_spaces) - only_spaces = 0; - continue; + only_spaces = 0; + + for (p = git_generated_prefixes; *p; p++) { + if (starts_with(lines[start]->buf, *p)) { + trailer_lines++; + recognized_prefix = 1; + goto continue_outer_loop; + } } - return count; + + separator_pos = find_separator(lines[start]->buf); + if (separator_pos >= 1) { + struct list_head *pos; + + trailer_lines++; + if (recognized_prefix) + continue; + list_for_each(pos, &conf_head) { + struct arg_item *item; + item = list_entry(pos, struct arg_item, list); + if (token_matches_item(lines[start]->buf, item, + separator_pos)) { + recognized_prefix = 1; + break; + } + } + } else + non_trailer_lines++; +continue_outer_loop: + ; } - return only_spaces ? count : 0; + return count; } /* Get the index of the end of the trailers */ @@ -810,6 +867,12 @@ static int process_input_file(FILE *outfile, add_trailer_item(head, strbuf_detach(&tok, NULL), strbuf_detach(&val, NULL)); + } else { + strbuf_addbuf(&val, lines[i]); + strbuf_strip_suffix(&val, "\n"); + add_trailer_item(head, + NULL, + strbuf_detach(&val, NULL)); } } -- cgit v1.2.1 From c463a6b28023dd3ad7ad4542147e20c27dbc83d6 Mon Sep 17 00:00:00 2001 From: Jonathan Tan Date: Fri, 21 Oct 2016 10:55:02 -0700 Subject: trailer: forbid leading whitespace in trailers Currently, interpret-trailers allows leading whitespace in trailer lines. This leads to false positives, especially for quoted lines or bullet lists. Forbid leading whitespace in trailers. Signed-off-by: Jonathan Tan Signed-off-by: Junio C Hamano --- Documentation/git-interpret-trailers.txt | 2 +- t/t7513-interpret-trailers.sh | 15 +++++++++++++++ trailer.c | 2 +- 3 files changed, 17 insertions(+), 2 deletions(-) diff --git a/Documentation/git-interpret-trailers.txt b/Documentation/git-interpret-trailers.txt index cf4c5ea45f..4966b5b104 100644 --- a/Documentation/git-interpret-trailers.txt +++ b/Documentation/git-interpret-trailers.txt @@ -55,7 +55,7 @@ The group must either be at the end of the message or be the last non-whitespace lines before a line that starts with '---'. Such three minus signs start the patch part of the message. -When reading trailers, there can be whitespaces before and after the +When reading trailers, there can be whitespaces after the token, the separator and the value. There can also be whitespaces inside the token and the value. diff --git a/t/t7513-interpret-trailers.sh b/t/t7513-interpret-trailers.sh index 003e90f8cf..3d94b3a2f7 100755 --- a/t/t7513-interpret-trailers.sh +++ b/t/t7513-interpret-trailers.sh @@ -241,6 +241,21 @@ test_expect_success 'with non-trailer lines only' ' test_cmp expected actual ' +test_expect_success 'line with leading whitespace is not trailer' ' + q_to_tab >patch <<-\EOF && + + Qtoken: value + EOF + q_to_tab >expected <<-\EOF && + + Qtoken: value + + token: value + EOF + git interpret-trailers --trailer "token: value" patch >actual && + test_cmp expected actual +' + test_expect_success 'with config setup' ' git config trailer.ack.key "Acked-by: " && cat >expected <<-\EOF && diff --git a/trailer.c b/trailer.c index 4c5b931bcc..84105ac181 100644 --- a/trailer.c +++ b/trailer.c @@ -775,7 +775,7 @@ static int find_trailer_start(struct strbuf **lines, int count) } separator_pos = find_separator(lines[start]->buf); - if (separator_pos >= 1) { + if (separator_pos >= 1 && !isspace(lines[start]->buf[0])) { struct list_head *pos; trailer_lines++; -- cgit v1.2.1 From 60ef86a16268bfb1efb28bcb3f1b646bfad1931c Mon Sep 17 00:00:00 2001 From: Jonathan Tan Date: Fri, 21 Oct 2016 10:55:03 -0700 Subject: trailer: support values folded to multiple lines Currently, interpret-trailers requires that a trailer be only on 1 line. For example: a: first line second line would be interpreted as one trailer line followed by one non-trailer line. Make interpret-trailers support RFC 822-style folding, treating those lines as one logical trailer. Signed-off-by: Jonathan Tan Signed-off-by: Junio C Hamano --- Documentation/git-interpret-trailers.txt | 7 +- t/t7513-interpret-trailers.sh | 169 +++++++++++++++++++++++++++++++ trailer.c | 45 ++++++-- 3 files changed, 211 insertions(+), 10 deletions(-) diff --git a/Documentation/git-interpret-trailers.txt b/Documentation/git-interpret-trailers.txt index 4966b5b104..e99bda6add 100644 --- a/Documentation/git-interpret-trailers.txt +++ b/Documentation/git-interpret-trailers.txt @@ -57,11 +57,12 @@ minus signs start the patch part of the message. When reading trailers, there can be whitespaces after the token, the separator and the value. There can also be whitespaces -inside the token and the value. +inside the token and the value. The value may be split over multiple lines with +each subsequent line starting with whitespace, like the "folding" in RFC 822. Note that 'trailers' do not follow and are not intended to follow many -rules for RFC 822 headers. For example they do not follow the line -folding rules, the encoding rules and probably many other rules. +rules for RFC 822 headers. For example they do not follow +the encoding rules and probably many other rules. OPTIONS ------- diff --git a/t/t7513-interpret-trailers.sh b/t/t7513-interpret-trailers.sh index 3d94b3a2f7..4dd1d7c520 100755 --- a/t/t7513-interpret-trailers.sh +++ b/t/t7513-interpret-trailers.sh @@ -256,6 +256,175 @@ test_expect_success 'line with leading whitespace is not trailer' ' test_cmp expected actual ' +test_expect_success 'multiline field treated as one trailer for 25% check' ' + q_to_tab >patch <<-\EOF && + + Signed-off-by: a + name: value on + Qmultiple lines + this is not a trailer + this is not a trailer + this is not a trailer + this is not a trailer + this is not a trailer + this is not a trailer + EOF + q_to_tab >expected <<-\EOF && + + Signed-off-by: a + name: value on + Qmultiple lines + this is not a trailer + this is not a trailer + this is not a trailer + this is not a trailer + this is not a trailer + this is not a trailer + name: value + EOF + git interpret-trailers --trailer "name: value" patch >actual && + test_cmp expected actual +' + +test_expect_success 'multiline field treated as atomic for placement' ' + q_to_tab >patch <<-\EOF && + + another: trailer + name: value on + Qmultiple lines + another: trailer + EOF + q_to_tab >expected <<-\EOF && + + another: trailer + name: value on + Qmultiple lines + name: value + another: trailer + EOF + test_config trailer.name.where after && + git interpret-trailers --trailer "name: value" patch >actual && + test_cmp expected actual +' + +test_expect_success 'multiline field treated as atomic for replacement' ' + q_to_tab >patch <<-\EOF && + + another: trailer + name: value on + Qmultiple lines + another: trailer + EOF + q_to_tab >expected <<-\EOF && + + another: trailer + another: trailer + name: value + EOF + test_config trailer.name.ifexists replace && + git interpret-trailers --trailer "name: value" patch >actual && + test_cmp expected actual +' + +test_expect_success 'multiline field treated as atomic for difference check' ' + q_to_tab >patch <<-\EOF && + + another: trailer + name: first line + Qsecond line + another: trailer + EOF + test_config trailer.name.ifexists addIfDifferent && + + q_to_tab >trailer <<-\EOF && + name: first line + Qsecond line + EOF + q_to_tab >expected <<-\EOF && + + another: trailer + name: first line + Qsecond line + another: trailer + EOF + git interpret-trailers --trailer "$(cat trailer)" patch >actual && + test_cmp expected actual && + + q_to_tab >trailer <<-\EOF && + name: first line + QQQQQsecond line + EOF + q_to_tab >expected <<-\EOF && + + another: trailer + name: first line + Qsecond line + another: trailer + name: first line + QQQQQsecond line + EOF + git interpret-trailers --trailer "$(cat trailer)" patch >actual && + test_cmp expected actual && + + q_to_tab >trailer <<-\EOF && + name: first line *DIFFERENT* + Qsecond line + EOF + q_to_tab >expected <<-\EOF && + + another: trailer + name: first line + Qsecond line + another: trailer + name: first line *DIFFERENT* + Qsecond line + EOF + git interpret-trailers --trailer "$(cat trailer)" patch >actual && + test_cmp expected actual +' + +test_expect_success 'multiline field treated as atomic for neighbor check' ' + q_to_tab >patch <<-\EOF && + + another: trailer + name: first line + Qsecond line + another: trailer + EOF + test_config trailer.name.where after && + test_config trailer.name.ifexists addIfDifferentNeighbor && + + q_to_tab >trailer <<-\EOF && + name: first line + Qsecond line + EOF + q_to_tab >expected <<-\EOF && + + another: trailer + name: first line + Qsecond line + another: trailer + EOF + git interpret-trailers --trailer "$(cat trailer)" patch >actual && + test_cmp expected actual && + + q_to_tab >trailer <<-\EOF && + name: first line + QQQQQsecond line + EOF + q_to_tab >expected <<-\EOF && + + another: trailer + name: first line + Qsecond line + name: first line + QQQQQsecond line + another: trailer + EOF + git interpret-trailers --trailer "$(cat trailer)" patch >actual && + test_cmp expected actual +' + test_expect_success 'with config setup' ' git config trailer.ack.key "Acked-by: " && cat >expected <<-\EOF && diff --git a/trailer.c b/trailer.c index 84105ac181..d19a92cd1c 100644 --- a/trailer.c +++ b/trailer.c @@ -619,12 +619,14 @@ static void parse_trailer(struct strbuf *tok, struct strbuf *val, } } -static void add_trailer_item(struct list_head *head, char *tok, char *val) +static struct trailer_item *add_trailer_item(struct list_head *head, char *tok, + char *val) { struct trailer_item *new = xcalloc(sizeof(*new), 1); new->token = tok; new->value = val; list_add_tail(&new->list, head); + return new; } static void add_arg_item(struct list_head *arg_head, char *tok, char *val, @@ -732,6 +734,14 @@ static int find_trailer_start(struct strbuf **lines, int count) { int start, end_of_title, only_spaces = 1; int recognized_prefix = 0, trailer_lines = 0, non_trailer_lines = 0; + /* + * Number of possible continuation lines encountered. This will be + * reset to 0 if we encounter a trailer (since those lines are to be + * considered continuations of that trailer), and added to + * non_trailer_lines if we encounter a non-trailer (since those lines + * are to be considered non-trailers). + */ + int possible_continuation_lines = 0; /* The first paragraph is the title and cannot be trailers */ for (start = 0; start < count; start++) { @@ -752,11 +762,15 @@ static int find_trailer_start(struct strbuf **lines, int count) const char **p; int separator_pos; - if (lines[start]->buf[0] == comment_line_char) + if (lines[start]->buf[0] == comment_line_char) { + non_trailer_lines += possible_continuation_lines; + possible_continuation_lines = 0; continue; + } if (contains_only_spaces(lines[start]->buf)) { if (only_spaces) continue; + non_trailer_lines += possible_continuation_lines; if (recognized_prefix && trailer_lines * 3 >= non_trailer_lines) return start + 1; @@ -769,16 +783,18 @@ static int find_trailer_start(struct strbuf **lines, int count) for (p = git_generated_prefixes; *p; p++) { if (starts_with(lines[start]->buf, *p)) { trailer_lines++; + possible_continuation_lines = 0; recognized_prefix = 1; goto continue_outer_loop; } } - separator_pos = find_separator(lines[start]->buf); + separator_pos = find_separator(lines[start]->buf, separators); if (separator_pos >= 1 && !isspace(lines[start]->buf[0])) { struct list_head *pos; trailer_lines++; + possible_continuation_lines = 0; if (recognized_prefix) continue; list_for_each(pos, &conf_head) { @@ -790,8 +806,13 @@ static int find_trailer_start(struct strbuf **lines, int count) break; } } - } else + } else if (isspace(lines[start]->buf[0])) + possible_continuation_lines++; + else { non_trailer_lines++; + non_trailer_lines += possible_continuation_lines; + possible_continuation_lines = 0; + } continue_outer_loop: ; } @@ -840,6 +861,7 @@ static int process_input_file(FILE *outfile, int patch_start, trailer_start, trailer_end, i; struct strbuf tok = STRBUF_INIT; struct strbuf val = STRBUF_INIT; + struct trailer_item *last = NULL; /* Get the line count */ while (lines[count]) @@ -860,19 +882,28 @@ static int process_input_file(FILE *outfile, int separator_pos; if (lines[i]->buf[0] == comment_line_char) continue; + if (last && isspace(lines[i]->buf[0])) { + struct strbuf sb = STRBUF_INIT; + strbuf_addf(&sb, "%s\n%s", last->value, lines[i]->buf); + strbuf_strip_suffix(&sb, "\n"); + free(last->value); + last->value = strbuf_detach(&sb, NULL); + continue; + } separator_pos = find_separator(lines[i]->buf, separators); if (separator_pos >= 1) { parse_trailer(&tok, &val, NULL, lines[i]->buf, separator_pos); - add_trailer_item(head, - strbuf_detach(&tok, NULL), - strbuf_detach(&val, NULL)); + last = add_trailer_item(head, + strbuf_detach(&tok, NULL), + strbuf_detach(&val, NULL)); } else { strbuf_addbuf(&val, lines[i]); strbuf_strip_suffix(&val, "\n"); add_trailer_item(head, NULL, strbuf_detach(&val, NULL)); + last = NULL; } } -- cgit v1.2.1