From: Junio C Hamano Date: Sun, 27 Aug 2017 05:55:04 +0000 (-0700) Subject: Merge branch 'jk/trailers-parse' X-Git-Tag: v2.15.0-rc0~127 X-Git-Url: https://git.lorimer.id.au/gitweb.git/diff_plain/06cf4f2d87d670f6d49c208fa41933941205da90?ds=inline;hp=-c Merge branch 'jk/trailers-parse' "git interpret-trailers" has been taught a "--parse" and a few other options to make it easier for scripts to grab existing trailer lines from a commit log message. * jk/trailers-parse: doc/interpret-trailers: fix "the this" typo pretty: support normalization options for %(trailers) t4205: refactor %(trailers) tests pretty: move trailer formatting to trailer.c interpret-trailers: add --parse convenience option interpret-trailers: add an option to unfold values interpret-trailers: add an option to show only existing trailers interpret-trailers: add an option to show only the trailers trailer: put process_trailers() options into a struct --- 06cf4f2d87d670f6d49c208fa41933941205da90 diff --combined Documentation/git-interpret-trailers.txt index 0ef93204f1,2e22210734..9dd19a1dd9 --- a/Documentation/git-interpret-trailers.txt +++ b/Documentation/git-interpret-trailers.txt @@@ -3,24 -3,27 +3,27 @@@ git-interpret-trailers(1 NAME ---- - git-interpret-trailers - help add structured information into commit messages + git-interpret-trailers - add or parse structured information in commit messages SYNOPSIS -------- [verse] - 'git interpret-trailers' [--in-place] [--trim-empty] [(--trailer [(=|:)])...] [...] + 'git interpret-trailers' [options] [(--trailer [(=|:)])...] [...] + 'git interpret-trailers' [options] [--parse] [...] DESCRIPTION ----------- - Help adding 'trailers' lines, that look similar to RFC 822 e-mail + Help parsing or adding 'trailers' lines, that look similar to RFC 822 e-mail headers, at the end of the otherwise free-form part of a commit message. This command reads some patches or commit messages from either the - arguments or the standard input if no is specified. Then - this command applies the arguments passed using the `--trailer` - option, if any, to the commit message part of each input file. The - result is emitted on the standard output. + arguments or the standard input if no is specified. If + `--parse` is specified, the output consists of the parsed trailers. + + Otherwise, this command applies the arguments passed using the + `--trailer` option, if any, to the commit message part of each input + file. The result is emitted on the standard output. Some configuration variables control the way the `--trailer` arguments are applied to each commit message and the way any existing trailer in @@@ -80,29 -83,22 +83,45 @@@ OPTION trailer to the input messages. See the description of this command. +--where :: +--no-where:: + Specify where all new trailers will be added. A setting + provided with '--where' overrides all configuration variables + and applies to all '--trailer' options until the next occurrence of + '--where' or '--no-where'. + +--if-exists :: +--no-if-exists:: + Specify what action will be performed when there is already at + least one trailer with the same in the message. A setting + provided with '--if-exists' overrides all configuration variables + and applies to all '--trailer' options until the next occurrence of + '--if-exists' or '--no-if-exists'. + +--if-missing :: +--no-if-missing:: + Specify what action will be performed when there is no other + trailer with the same in the message. A setting + provided with '--if-missing' overrides all configuration variables + and applies to all '--trailer' options until the next occurrence of + '--if-missing' or '--no-if-missing'. + + --only-trailers:: + Output only the trailers, not any other parts of the input. + + --only-input:: + Output only trailers that exist in the input; do not add any + from the command-line or by following configured `trailer.*` + rules. + + --unfold:: + Remove any whitespace-continuation in trailers, so that each + trailer appears on a line by itself with its full content. + + --parse:: + A convenience alias for `--only-trailers --only-input + --unfold`. + CONFIGURATION VARIABLES ----------------------- @@@ -193,8 -189,8 +212,8 @@@ trailer..where: configuration variable and it overrides what is specified by that option for trailers with the specified . -trailer..ifexist:: - This option takes the same values as the 'trailer.ifexist' +trailer..ifexists:: + This option takes the same values as the 'trailer.ifexists' configuration variable and it overrides what is specified by that option for trailers with the specified . diff --combined Documentation/pretty-formats.txt index 973d19606b,efa67a716e..d433d50f81 --- a/Documentation/pretty-formats.txt +++ b/Documentation/pretty-formats.txt @@@ -173,17 -173,13 +173,17 @@@ endif::git-rev-list[ - '%Cblue': switch color to blue - '%Creset': reset color - '%C(...)': color specification, as described under Values in the - "CONFIGURATION FILE" section of linkgit:git-config[1]; - adding `auto,` at the beginning (e.g. `%C(auto,red)`) will emit - color only when colors are enabled for log output (by `color.diff`, - `color.ui`, or `--color`, and respecting the `auto` settings of the - former if we are going to a terminal). `auto` alone (i.e. - `%C(auto)`) will turn on auto coloring on the next placeholders - until the color is switched again. + "CONFIGURATION FILE" section of linkgit:git-config[1]. + By default, colors are shown only when enabled for log output (by + `color.diff`, `color.ui`, or `--color`, and respecting the `auto` + settings of the former if we are going to a terminal). `%C(auto,...)` + is accepted as a historical synonym for the default (e.g., + `%C(auto,red)`). Specifying `%C(always,...) will show the colors + even when color is not otherwise enabled (though consider + just using `--color=always` to enable color for the whole output, + including this format and anything else git might color). `auto` + alone (i.e. `%C(auto)`) will turn on auto coloring on the next + placeholders until the color is switched again. - '%m': left (`<`), right (`>`) or boundary (`-`) mark - '%n': newline - '%%': a raw '%' @@@ -205,7 -201,10 +205,10 @@@ - '%><()', '%><|()': similar to '% <()', '%<|()' respectively, but padding both sides (i.e. the text is centered) - %(trailers): display the trailers of the body as interpreted by - linkgit:git-interpret-trailers[1] + linkgit:git-interpret-trailers[1]. If the `:only` option is given, + omit non-trailer lines from the trailer block. If the `:unfold` + option is given, behave as if interpret-trailer's `--unfold` option + was given. E.g., `%(trailers:only:unfold)` to do both. NOTE: Some placeholders may depend on other options given to the revision traversal engine. For example, the `%g*` reflog options will diff --combined builtin/interpret-trailers.c index d7cbaf60f9,555111a078..b742539d4d --- a/builtin/interpret-trailers.c +++ b/builtin/interpret-trailers.c @@@ -16,99 -16,54 +16,119 @@@ static const char * const git_interpret NULL }; +static enum trailer_where where; +static enum trailer_if_exists if_exists; +static enum trailer_if_missing if_missing; + +static int option_parse_where(const struct option *opt, + const char *arg, int unset) +{ + return trailer_set_where(&where, arg); +} + +static int option_parse_if_exists(const struct option *opt, + const char *arg, int unset) +{ + return trailer_set_if_exists(&if_exists, arg); +} + +static int option_parse_if_missing(const struct option *opt, + const char *arg, int unset) +{ + return trailer_set_if_missing(&if_missing, arg); +} + +static void new_trailers_clear(struct list_head *trailers) +{ + struct list_head *pos, *tmp; + struct new_trailer_item *item; + + list_for_each_safe(pos, tmp, trailers) { + item = list_entry(pos, struct new_trailer_item, list); + list_del(pos); + free(item); + } +} + +static int option_parse_trailer(const struct option *opt, + const char *arg, int unset) +{ + struct list_head *trailers = opt->value; + struct new_trailer_item *item; + + if (unset) { + new_trailers_clear(trailers); + return 0; + } + + if (!arg) + return -1; + + item = xmalloc(sizeof(*item)); + item->text = arg; + item->where = where; + item->if_exists = if_exists; + item->if_missing = if_missing; + list_add_tail(&item->list, trailers); + return 0; +} + + static int parse_opt_parse(const struct option *opt, const char *arg, + int unset) + { + struct process_trailer_options *v = opt->value; + v->only_trailers = 1; + v->only_input = 1; + v->unfold = 1; + return 0; + } + int cmd_interpret_trailers(int argc, const char **argv, const char *prefix) { - int in_place = 0; - int trim_empty = 0; + struct process_trailer_options opts = PROCESS_TRAILER_OPTIONS_INIT; - struct string_list trailers = STRING_LIST_INIT_NODUP; + LIST_HEAD(trailers); struct option options[] = { - OPT_BOOL(0, "in-place", &in_place, N_("edit files in place")), - OPT_BOOL(0, "trim-empty", &trim_empty, N_("trim empty trailers")), + OPT_BOOL(0, "in-place", &opts.in_place, N_("edit files in place")), + OPT_BOOL(0, "trim-empty", &opts.trim_empty, N_("trim empty trailers")), + + OPT_CALLBACK(0, "where", NULL, N_("action"), + N_("where to place the new trailer"), option_parse_where), + OPT_CALLBACK(0, "if-exists", NULL, N_("action"), + N_("action if trailer already exists"), option_parse_if_exists), + OPT_CALLBACK(0, "if-missing", NULL, N_("action"), + N_("action if trailer is missing"), option_parse_if_missing), + + OPT_BOOL(0, "only-trailers", &opts.only_trailers, N_("output only the trailers")), + OPT_BOOL(0, "only-input", &opts.only_input, N_("do not apply config rules")), + OPT_BOOL(0, "unfold", &opts.unfold, N_("join whitespace-continued values")), + { OPTION_CALLBACK, 0, "parse", &opts, NULL, N_("set parsing options"), + PARSE_OPT_NOARG | PARSE_OPT_NONEG, parse_opt_parse }, - OPT_STRING_LIST(0, "trailer", &trailers, N_("trailer"), - N_("trailer(s) to add")), + OPT_CALLBACK(0, "trailer", &trailers, N_("trailer"), + N_("trailer(s) to add"), option_parse_trailer), OPT_END() }; argc = parse_options(argc, argv, prefix, options, git_interpret_trailers_usage, 0); - if (opts.only_input && trailers.nr) ++ if (opts.only_input && !list_empty(&trailers)) + usage_msg_opt( + _("--trailer with --only-input does not make sense"), + git_interpret_trailers_usage, + options); + if (argc) { int i; for (i = 0; i < argc; i++) - process_trailers(argv[i], in_place, trim_empty, &trailers); + process_trailers(argv[i], &opts, &trailers); } else { - if (in_place) + if (opts.in_place) die(_("no input file given for in-place editing")); - process_trailers(NULL, in_place, trim_empty, &trailers); + process_trailers(NULL, &opts, &trailers); } - string_list_clear(&trailers, 0); + new_trailers_clear(&trailers); return 0; } diff --combined pretty.c index 39cad5112b,0e23fe3c05..94eab5c89e --- a/pretty.c +++ b/pretty.c @@@ -1,5 -1,4 +1,5 @@@ #include "cache.h" +#include "config.h" #include "commit.h" #include "utf8.h" #include "diff.h" @@@ -406,11 -405,11 +406,11 @@@ static void add_rfc2047(struct strbuf * const char *show_ident_date(const struct ident_split *ident, const struct date_mode *mode) { - unsigned long date = 0; + timestamp_t date = 0; long tz = 0; if (ident->date_begin && ident->date_end) - date = strtoul(ident->date_begin, NULL, 10); + date = parse_timestamp(ident->date_begin, NULL, 10); if (date_overflows(date)) date = 0; else { @@@ -871,16 -870,6 +871,6 @@@ const char *format_subject(struct strbu return msg; } - static void format_trailers(struct strbuf *sb, const char *msg) - { - struct trailer_info info; - - trailer_info_get(&info, msg); - strbuf_add(sb, info.trailer_start, - info.trailer_end - info.trailer_start); - trailer_info_release(&info); - } - static void parse_commit_message(struct format_commit_context *c) { const char *msg = c->message + c->message_off; @@@ -947,7 -936,6 +937,7 @@@ static size_t parse_color(struct strbu struct format_commit_context *c) { const char *rest = placeholder; + const char *basic_color = NULL; if (placeholder[1] == '(') { const char *begin = placeholder + 2; @@@ -956,41 -944,23 +946,41 @@@ if (!end) return 0; + if (skip_prefix(begin, "auto,", &begin)) { if (!want_color(c->pretty_ctx->color)) return end - placeholder + 1; + } else if (skip_prefix(begin, "always,", &begin)) { + /* nothing to do; we do not respect want_color at all */ + } else { + /* the default is the same as "auto" */ + if (!want_color(c->pretty_ctx->color)) + return end - placeholder + 1; } + if (color_parse_mem(begin, end - begin, color) < 0) die(_("unable to parse --pretty format")); strbuf_addstr(sb, color); return end - placeholder + 1; } + + /* + * We handle things like "%C(red)" above; for historical reasons, there + * are a few colors that can be specified without parentheses (and + * they cannot support things like "auto" or "always" at all). + */ if (skip_prefix(placeholder + 1, "red", &rest)) - strbuf_addstr(sb, GIT_COLOR_RED); + basic_color = GIT_COLOR_RED; else if (skip_prefix(placeholder + 1, "green", &rest)) - strbuf_addstr(sb, GIT_COLOR_GREEN); + basic_color = GIT_COLOR_GREEN; else if (skip_prefix(placeholder + 1, "blue", &rest)) - strbuf_addstr(sb, GIT_COLOR_BLUE); + basic_color = GIT_COLOR_BLUE; else if (skip_prefix(placeholder + 1, "reset", &rest)) - strbuf_addstr(sb, GIT_COLOR_RESET); + basic_color = GIT_COLOR_RESET; + + if (basic_color && want_color(c->pretty_ctx->color)) + strbuf_addstr(sb, basic_color); + return rest - placeholder; } @@@ -1074,6 -1044,7 +1064,7 @@@ static size_t format_commit_one(struct const struct commit *commit = c->commit; const char *msg = c->message; struct commit_list *p; + const char *arg; int ch; /* these are independent of the commit */ @@@ -1137,7 -1108,7 +1128,7 @@@ /* these depend on the commit */ if (!commit->object.parsed) - parse_object(commit->object.oid.hash); + parse_object(&commit->object.oid); switch (placeholder[0]) { case 'H': /* commit hash */ @@@ -1292,9 -1263,18 +1283,18 @@@ return 1; } - if (starts_with(placeholder, "(trailers)")) { - format_trailers(sb, msg + c->subject_off); - return strlen("(trailers)"); + if (skip_prefix(placeholder, "(trailers", &arg)) { + struct process_trailer_options opts = PROCESS_TRAILER_OPTIONS_INIT; + while (*arg == ':') { + if (skip_prefix(arg, ":only", &arg)) + opts.only_trailers = 1; + else if (skip_prefix(arg, ":unfold", &arg)) + opts.unfold = 1; + } + if (*arg == ')') { + format_trailers_from_commit(sb, msg + c->subject_off, &opts); + return arg - placeholder + 1; + } } return 0; /* unknown placeholder */ diff --combined t/t7513-interpret-trailers.sh index adbdf54f8d,baf2feba98..164719d1c9 --- a/t/t7513-interpret-trailers.sh +++ b/t/t7513-interpret-trailers.sh @@@ -681,36 -681,6 +681,36 @@@ test_expect_success 'using "where = bef test_cmp expected actual ' +test_expect_success 'overriding configuration with "--where after"' ' + git config trailer.ack.where "before" && + cat complex_message_body >expected && + sed -e "s/ Z\$/ /" >>expected <<-\EOF && + Fixes: Z + Acked-by= Z + Acked-by= Peff + Reviewed-by: Z + Signed-off-by: Z + EOF + git interpret-trailers --where after --trailer "ack: Peff" \ + complex_message >actual && + test_cmp expected actual +' + +test_expect_success 'using "where = before" with "--no-where"' ' + cat complex_message_body >expected && + sed -e "s/ Z\$/ /" >>expected <<-\EOF && + Bug #42 + Fixes: Z + Acked-by= Peff + Acked-by= Z + Reviewed-by: Z + Signed-off-by: Z + EOF + git interpret-trailers --where after --no-where --trailer "ack: Peff" \ + --trailer "bug: 42" complex_message >actual && + test_cmp expected actual +' + test_expect_success 'using "where = after"' ' git config trailer.ack.where "after" && cat complex_message_body >expected && @@@ -977,23 -947,6 +977,23 @@@ test_expect_success 'using "ifExists = test_cmp expected actual ' +test_expect_success 'overriding configuration with "--if-exists replace"' ' + git config trailer.fix.key "Fixes: " && + git config trailer.fix.ifExists "add" && + cat complex_message_body >expected && + sed -e "s/ Z\$/ /" >>expected <<-\EOF && + Bug #42 + Acked-by= Z + Reviewed-by: + Signed-off-by: Z + Fixes: 22 + EOF + git interpret-trailers --if-exists replace --trailer "review:" \ + --trailer "fix=53" --trailer "fix=22" --trailer "bug: 42" \ + actual && + test_cmp expected actual +' + test_expect_success 'using "ifExists = replace"' ' git config trailer.fix.key "Fixes: " && git config trailer.fix.ifExists "replace" && @@@ -1073,25 -1026,6 +1073,25 @@@ test_expect_success 'the default is "if test_cmp expected actual ' +test_expect_success 'overriding configuration with "--if-missing doNothing"' ' + git config trailer.ifmissing "add" && + cat complex_message_body >expected && + sed -e "s/ Z\$/ /" >>expected <<-\EOF && + Fixes: Z + Acked-by= Z + Acked-by= Junio + Acked-by= Peff + Reviewed-by: + Signed-off-by: Z + EOF + git interpret-trailers --if-missing doNothing \ + --trailer "review:" --trailer "fix=53" \ + --trailer "cc=Linus" --trailer "ack: Junio" \ + --trailer "fix=22" --trailer "bug: 42" --trailer "ack: Peff" \ + actual && + test_cmp expected actual +' + test_expect_success 'when default "ifMissing" is "doNothing"' ' git config trailer.ifmissing "doNothing" && cat complex_message_body >expected && @@@ -1341,4 -1275,80 +1341,80 @@@ test_expect_success 'with cut line' test_cmp expected actual ' + test_expect_success 'only trailers' ' + git config trailer.sign.command "echo config-value" && + cat >expected <<-\EOF && + existing: existing-value + sign: config-value + added: added-value + EOF + git interpret-trailers \ + --trailer added:added-value \ + --only-trailers >actual <<-\EOF && + my subject + + my body + + existing: existing-value + EOF + test_cmp expected actual + ' + + test_expect_success 'only-trailers omits non-trailer in middle of block' ' + git config trailer.sign.command "echo config-value" && + cat >expected <<-\EOF && + Signed-off-by: nobody + Signed-off-by: somebody + sign: config-value + EOF + git interpret-trailers --only-trailers >actual <<-\EOF && + subject + + it is important that the trailers below are signed-off-by + so that they meet the "25% trailers Git knows about" heuristic + + Signed-off-by: nobody + this is not a trailer + Signed-off-by: somebody + EOF + test_cmp expected actual + ' + + test_expect_success 'only input' ' + git config trailer.sign.command "echo config-value" && + cat >expected <<-\EOF && + existing: existing-value + EOF + git interpret-trailers \ + --only-trailers --only-input >actual <<-\EOF && + my subject + + my body + + existing: existing-value + EOF + test_cmp expected actual + ' + + test_expect_success 'unfold' ' + cat >expected <<-\EOF && + foo: continued across several lines + EOF + # pass through tr to make leading and trailing whitespace more obvious + tr _ " " <<-\EOF | + my subject + + my body + + foo:_ + __continued + ___across + ____several + _____lines + ___ + EOF + git interpret-trailers --only-trailers --only-input --unfold >actual && + test_cmp expected actual + ' + test_done diff --combined trailer.c index d441cd9ac6,6ec5505dc4..c30e3a0c04 --- a/trailer.c +++ b/trailer.c @@@ -1,5 -1,4 +1,5 @@@ #include "cache.h" +#include "config.h" #include "string-list.h" #include "run-command.h" #include "commit.h" @@@ -10,13 -9,18 +10,13 @@@ * Copyright (c) 2013, 2014 Christian Couder */ -enum action_where { WHERE_END, WHERE_AFTER, WHERE_BEFORE, WHERE_START }; -enum action_if_exists { EXISTS_ADD_IF_DIFFERENT_NEIGHBOR, EXISTS_ADD_IF_DIFFERENT, - EXISTS_ADD, EXISTS_REPLACE, EXISTS_DO_NOTHING }; -enum action_if_missing { MISSING_ADD, MISSING_DO_NOTHING }; - struct conf_info { char *name; char *key; char *command; - enum action_where where; - enum action_if_exists if_exists; - enum action_if_missing if_missing; + enum trailer_where where; + enum trailer_if_exists if_exists; + enum trailer_if_missing if_missing; }; static struct conf_info default_conf_info; @@@ -58,7 -62,7 +58,7 @@@ static const char *git_generated_prefix pos != (head); \ pos = is_reverse ? pos->prev : pos->next) -static int after_or_end(enum action_where where) +static int after_or_end(enum trailer_where where) { return (where == WHERE_AFTER) || (where == WHERE_END); } @@@ -159,13 -163,15 +159,15 @@@ static void print_tok_val(FILE *outfile fprintf(outfile, "%s%c %s\n", tok, separators[0], val); } - static void print_all(FILE *outfile, struct list_head *head, int trim_empty) + static void print_all(FILE *outfile, struct list_head *head, + const struct process_trailer_options *opts) { struct list_head *pos; struct trailer_item *item; list_for_each(pos, head) { item = list_entry(pos, struct trailer_item, list); - if (!trim_empty || strlen(item->value) > 0) + if ((!opts->trim_empty || strlen(item->value) > 0) && + (!opts->only_trailers || item->token)) print_tok_val(outfile, item->token, item->value); } } @@@ -196,7 -202,7 +198,7 @@@ static int check_if_different(struct tr int check_all, struct list_head *head) { - enum action_where where = arg_tok->conf.where; + enum trailer_where where = arg_tok->conf.where; struct list_head *next_head; do { if (same_trailer(in_tok, arg_tok)) @@@ -295,16 -301,13 +297,16 @@@ static void apply_arg_if_exists(struct else free_arg_item(arg_tok); break; + default: + die("BUG: trailer.c: unhandled value %d", + arg_tok->conf.if_exists); } } static void apply_arg_if_missing(struct list_head *head, struct arg_item *arg_tok) { - enum action_where where; + enum trailer_where where; struct trailer_item *to_add; switch (arg_tok->conf.if_missing) { @@@ -319,10 -322,6 +321,10 @@@ list_add_tail(&to_add->list, head); else list_add(&to_add->list, head); + break; + default: + die("BUG: trailer.c: unhandled value %d", + arg_tok->conf.if_missing); } } @@@ -333,7 -332,7 +335,7 @@@ static int find_same_and_apply_arg(stru struct trailer_item *in_tok; struct trailer_item *on_tok; - enum action_where where = arg_tok->conf.where; + enum trailer_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; @@@ -375,50 -374,44 +377,50 @@@ static void process_trailers_lists(stru } } -static int set_where(struct conf_info *item, const char *value) +int trailer_set_where(enum trailer_where *item, const char *value) { - if (!strcasecmp("after", value)) - item->where = WHERE_AFTER; + if (!value) + *item = WHERE_DEFAULT; + else if (!strcasecmp("after", value)) + *item = WHERE_AFTER; else if (!strcasecmp("before", value)) - item->where = WHERE_BEFORE; + *item = WHERE_BEFORE; else if (!strcasecmp("end", value)) - item->where = WHERE_END; + *item = WHERE_END; else if (!strcasecmp("start", value)) - item->where = WHERE_START; + *item = WHERE_START; else return -1; return 0; } -static int set_if_exists(struct conf_info *item, const char *value) +int trailer_set_if_exists(enum trailer_if_exists *item, const char *value) { - if (!strcasecmp("addIfDifferent", value)) - item->if_exists = EXISTS_ADD_IF_DIFFERENT; + if (!value) + *item = EXISTS_DEFAULT; + else if (!strcasecmp("addIfDifferent", value)) + *item = EXISTS_ADD_IF_DIFFERENT; else if (!strcasecmp("addIfDifferentNeighbor", value)) - item->if_exists = EXISTS_ADD_IF_DIFFERENT_NEIGHBOR; + *item = EXISTS_ADD_IF_DIFFERENT_NEIGHBOR; else if (!strcasecmp("add", value)) - item->if_exists = EXISTS_ADD; + *item = EXISTS_ADD; else if (!strcasecmp("replace", value)) - item->if_exists = EXISTS_REPLACE; + *item = EXISTS_REPLACE; else if (!strcasecmp("doNothing", value)) - item->if_exists = EXISTS_DO_NOTHING; + *item = EXISTS_DO_NOTHING; else return -1; return 0; } -static int set_if_missing(struct conf_info *item, const char *value) +int trailer_set_if_missing(enum trailer_if_missing *item, const char *value) { - if (!strcasecmp("doNothing", value)) - item->if_missing = MISSING_DO_NOTHING; + if (!value) + *item = MISSING_DEFAULT; + else if (!strcasecmp("doNothing", value)) + *item = MISSING_DO_NOTHING; else if (!strcasecmp("add", value)) - item->if_missing = MISSING_ADD; + *item = MISSING_ADD; else return -1; return 0; @@@ -478,18 -471,15 +480,18 @@@ static int git_trailer_default_config(c variable_name = strrchr(trailer_item, '.'); if (!variable_name) { if (!strcmp(trailer_item, "where")) { - if (set_where(&default_conf_info, value) < 0) + if (trailer_set_where(&default_conf_info.where, + value) < 0) warning(_("unknown value '%s' for key '%s'"), value, conf_key); } else if (!strcmp(trailer_item, "ifexists")) { - if (set_if_exists(&default_conf_info, value) < 0) + if (trailer_set_if_exists(&default_conf_info.if_exists, + value) < 0) warning(_("unknown value '%s' for key '%s'"), value, conf_key); } else if (!strcmp(trailer_item, "ifmissing")) { - if (set_if_missing(&default_conf_info, value) < 0) + if (trailer_set_if_missing(&default_conf_info.if_missing, + value) < 0) warning(_("unknown value '%s' for key '%s'"), value, conf_key); } else if (!strcmp(trailer_item, "separators")) { @@@ -543,15 -533,15 +545,15 @@@ static int git_trailer_config(const cha conf->command = xstrdup(value); break; case TRAILER_WHERE: - if (set_where(conf, value)) + if (trailer_set_where(&conf->where, value)) warning(_("unknown value '%s' for key '%s'"), value, conf_key); break; case TRAILER_IF_EXISTS: - if (set_if_exists(conf, value)) + if (trailer_set_if_exists(&conf->if_exists, value)) warning(_("unknown value '%s' for key '%s'"), value, conf_key); break; case TRAILER_IF_MISSING: - if (set_if_missing(conf, value)) + if (trailer_set_if_missing(&conf->if_missing, value)) warning(_("unknown value '%s' for key '%s'"), value, conf_key); break; default: @@@ -566,9 -556,6 +568,9 @@@ static void ensure_configured(void return; /* Default config must be setup first */ + default_conf_info.where = WHERE_END; + default_conf_info.if_exists = EXISTS_ADD_IF_DIFFERENT_NEIGHBOR; + default_conf_info.if_missing = MISSING_ADD; git_config(git_trailer_default_config, NULL); git_config(git_trailer_config, NULL); configured = 1; @@@ -672,27 -659,19 +674,27 @@@ static struct trailer_item *add_trailer } static void add_arg_item(struct list_head *arg_head, char *tok, char *val, - const struct conf_info *conf) + const struct conf_info *conf, + const struct new_trailer_item *new_trailer_item) { struct arg_item *new = xcalloc(sizeof(*new), 1); new->token = tok; new->value = val; duplicate_conf(&new->conf, conf); + if (new_trailer_item) { + if (new_trailer_item->where != WHERE_DEFAULT) + new->conf.where = new_trailer_item->where; + if (new_trailer_item->if_exists != EXISTS_DEFAULT) + new->conf.if_exists = new_trailer_item->if_exists; + if (new_trailer_item->if_missing != MISSING_DEFAULT) + new->conf.if_missing = new_trailer_item->if_missing; + } list_add_tail(&new->list, arg_head); } static void process_command_line_args(struct list_head *arg_head, - struct string_list *trailers) + struct list_head *new_trailer_head) { - struct string_list_item *tr; struct arg_item *item; struct strbuf tok = STRBUF_INIT; struct strbuf val = STRBUF_INIT; @@@ -712,29 -691,26 +714,29 @@@ add_arg_item(arg_head, xstrdup(token_from_item(item, NULL)), xstrdup(""), - &item->conf); + &item->conf, NULL); } /* Add an arg item for each trailer on the command line */ - for_each_string_list_item(tr, trailers) { - int separator_pos = find_separator(tr->string, cl_separators); + list_for_each(pos, new_trailer_head) { + struct new_trailer_item *tr = + list_entry(pos, struct new_trailer_item, list); + int separator_pos = find_separator(tr->text, cl_separators); + if (separator_pos == 0) { struct strbuf sb = STRBUF_INIT; - strbuf_addstr(&sb, tr->string); + strbuf_addstr(&sb, tr->text); 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, + parse_trailer(&tok, &val, &conf, tr->text, separator_pos); add_arg_item(arg_head, strbuf_detach(&tok, NULL), strbuf_detach(&val, NULL), - conf); + conf, tr); } } @@@ -910,9 -886,37 +912,37 @@@ static int ends_with_blank_line(const c return is_blank_line(buf + ll); } + static void unfold_value(struct strbuf *val) + { + struct strbuf out = STRBUF_INIT; + size_t i; + + strbuf_grow(&out, val->len); + i = 0; + while (i < val->len) { + char c = val->buf[i++]; + if (c == '\n') { + /* Collapse continuation down to a single space. */ + while (i < val->len && isspace(val->buf[i])) + i++; + strbuf_addch(&out, ' '); + } else { + strbuf_addch(&out, c); + } + } + + /* Empty lines may have left us with whitespace cruft at the edges */ + strbuf_trim(&out); + + /* output goes back to val as if we modified it in-place */ + strbuf_swap(&out, val); + strbuf_release(&out); + } + static int process_input_file(FILE *outfile, const char *str, - struct list_head *head) + struct list_head *head, + const struct process_trailer_options *opts) { struct trailer_info info; struct strbuf tok = STRBUF_INIT; @@@ -922,9 -926,10 +952,10 @@@ trailer_info_get(&info, str); /* Print lines before the trailers as is */ - fwrite(str, 1, info.trailer_start - str, outfile); + if (!opts->only_trailers) + fwrite(str, 1, info.trailer_start - str, outfile); - if (!info.blank_line_before_trailer) + if (!opts->only_trailers && !info.blank_line_before_trailer) fprintf(outfile, "\n"); for (i = 0; i < info.trailer_nr; i++) { @@@ -936,10 -941,12 +967,12 @@@ if (separator_pos >= 1) { parse_trailer(&tok, &val, NULL, trailer, separator_pos); + if (opts->unfold) + unfold_value(&val); add_trailer_item(head, strbuf_detach(&tok, NULL), strbuf_detach(&val, NULL)); - } else { + } else if (!opts->only_trailers) { strbuf_addstr(&val, trailer); strbuf_strip_suffix(&val, "\n"); add_trailer_item(head, @@@ -993,11 -1000,11 +1026,11 @@@ static FILE *create_in_place_tempfile(c return outfile; } - void process_trailers(const char *file, int in_place, int trim_empty, + void process_trailers(const char *file, + const struct process_trailer_options *opts, - struct string_list *trailers) + struct list_head *new_trailer_head) { LIST_HEAD(head); - LIST_HEAD(arg_head); struct strbuf sb = STRBUF_INIT; int trailer_end; FILE *outfile = stdout; @@@ -1006,24 -1013,27 +1039,27 @@@ read_input_file(&sb, file); - if (in_place) + if (opts->in_place) outfile = create_in_place_tempfile(file); /* Print the lines before the trailers */ - trailer_end = process_input_file(outfile, sb.buf, &head); - - process_command_line_args(&arg_head, new_trailer_head); + trailer_end = process_input_file(outfile, sb.buf, &head, opts); - process_trailers_lists(&head, &arg_head); + if (!opts->only_input) { + LIST_HEAD(arg_head); - process_command_line_args(&arg_head, trailers); ++ process_command_line_args(&arg_head, new_trailer_head); + process_trailers_lists(&head, &arg_head); + } - print_all(outfile, &head, trim_empty); + print_all(outfile, &head, opts); free_all(&head); /* Print the lines after the trailers as is */ - fwrite(sb.buf + trailer_end, 1, sb.len - trailer_end, outfile); + if (!opts->only_trailers) + fwrite(sb.buf + trailer_end, 1, sb.len - trailer_end, outfile); - if (in_place) + if (opts->in_place) if (rename_tempfile(&trailers_tempfile, file)) die_errno(_("could not rename temporary file to %s"), file); @@@ -1080,3 -1090,49 +1116,49 @@@ void trailer_info_release(struct traile free(info->trailers[i]); free(info->trailers); } + + static void format_trailer_info(struct strbuf *out, + const struct trailer_info *info, + const struct process_trailer_options *opts) + { + int i; + + /* If we want the whole block untouched, we can take the fast path. */ + if (!opts->only_trailers && !opts->unfold) { + strbuf_add(out, info->trailer_start, + info->trailer_end - info->trailer_start); + return; + } + + for (i = 0; i < info->trailer_nr; i++) { + char *trailer = info->trailers[i]; + int separator_pos = find_separator(trailer, separators); + + if (separator_pos >= 1) { + struct strbuf tok = STRBUF_INIT; + struct strbuf val = STRBUF_INIT; + + parse_trailer(&tok, &val, NULL, trailer, separator_pos); + if (opts->unfold) + unfold_value(&val); + + strbuf_addf(out, "%s: %s\n", tok.buf, val.buf); + strbuf_release(&tok); + strbuf_release(&val); + + } else if (!opts->only_trailers) { + strbuf_addstr(out, trailer); + } + } + + } + + void format_trailers_from_commit(struct strbuf *out, const char *msg, + const struct process_trailer_options *opts) + { + struct trailer_info info; + + trailer_info_get(&info, msg); + format_trailer_info(out, &info, opts); + trailer_info_release(&info); + } diff --combined trailer.h index 973b533a1a,a172811022..6d7f8c2a52 --- a/trailer.h +++ b/trailer.h @@@ -1,33 -1,6 +1,33 @@@ #ifndef TRAILER_H #define TRAILER_H +#include "list.h" + +enum trailer_where { + WHERE_DEFAULT, + WHERE_END, + WHERE_AFTER, + WHERE_BEFORE, + WHERE_START +}; +enum trailer_if_exists { + EXISTS_DEFAULT, + EXISTS_ADD_IF_DIFFERENT_NEIGHBOR, + EXISTS_ADD_IF_DIFFERENT, + EXISTS_ADD, + EXISTS_REPLACE, + EXISTS_DO_NOTHING +}; +enum trailer_if_missing { + MISSING_DEFAULT, + MISSING_ADD, + MISSING_DO_NOTHING +}; + +int trailer_set_where(enum trailer_where *item, const char *value); +int trailer_set_if_exists(enum trailer_if_exists *item, const char *value); +int trailer_set_if_missing(enum trailer_if_missing *item, const char *value); + struct trailer_info { /* * True if there is a blank line before the location pointed to by @@@ -49,25 -22,36 +49,50 @@@ size_t trailer_nr; }; +/* + * A list that represents newly-added trailers, such as those provided + * with the --trailer command line option of git-interpret-trailers. + */ +struct new_trailer_item { + struct list_head list; + + const char *text; + + enum trailer_where where; + enum trailer_if_exists if_exists; + enum trailer_if_missing if_missing; +}; + - void process_trailers(const char *file, int in_place, int trim_empty, + struct process_trailer_options { + int in_place; + int trim_empty; + int only_trailers; + int only_input; + int unfold; + }; + + #define PROCESS_TRAILER_OPTIONS_INIT {0} + + void process_trailers(const char *file, + const struct process_trailer_options *opts, - struct string_list *trailers); + struct list_head *new_trailer_head); void trailer_info_get(struct trailer_info *info, const char *str); void trailer_info_release(struct trailer_info *info); + /* + * Format the trailers from the commit msg "msg" into the strbuf "out". + * Note two caveats about "opts": + * + * - this is primarily a helper for pretty.c, and not + * all of the flags are supported. + * + * - this differs from process_trailers slightly in that we always format + * only the trailer block itself, even if the "only_trailers" option is not + * set. + */ + void format_trailers_from_commit(struct strbuf *out, const char *msg, + const struct process_trailer_options *opts); + #endif /* TRAILER_H */