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 <token>[(=|:)<value>])...] [<file>...]
+ 'git interpret-trailers' [options] [(--trailer <token>[(=|:)<value>])...] [<file>...]
+ 'git interpret-trailers' [options] [--parse] [<file>...]
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
- <file> arguments or the standard input if no <file> 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.
+ <file> arguments or the standard input if no <file> is specified. If
+ `--parse` is specified, the output consists of the parsed trailers.
+
+ Otherwise, the 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
trailer to the input messages. See the description of this
command.
+--where <placement>::
+--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 <action>::
+--no-if-exists::
+ Specify what action will be performed when there is already at
+ least one trailer with the same <token> 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 <action>::
+--no-if-missing::
+ Specify what action will be performed when there is no other
+ trailer with the same <token> 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
-----------------------
configuration variable and it overrides what is specified by
that option for trailers with the specified <token>.
-trailer.<token>.ifexist::
- This option takes the same values as the 'trailer.ifexist'
+trailer.<token>.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 <token>.
- '%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 '%'
- '%><(<N>)', '%><|(<N>)': similar to '% <(<N>)', '%<|(<N>)'
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
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;
}
#include "cache.h"
+#include "config.h"
#include "commit.h"
#include "utf8.h"
#include "diff.h"
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 {
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;
struct format_commit_context *c)
{
const char *rest = placeholder;
+ const char *basic_color = NULL;
if (placeholder[1] == '(') {
const char *begin = placeholder + 2;
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;
}
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 */
/* 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 */
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 */
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 &&
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" \
+ <complex_message >actual &&
+ test_cmp expected actual
+'
+
test_expect_success 'using "ifExists = replace"' '
git config trailer.fix.key "Fixes: " &&
git config trailer.fix.ifExists "replace" &&
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" \
+ <complex_message >actual &&
+ test_cmp expected actual
+'
+
test_expect_success 'when default "ifMissing" is "doNothing"' '
git config trailer.ifmissing "doNothing" &&
cat complex_message_body >expected &&
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 <nobody@nowhere>
+ Signed-off-by: somebody <somebody@somewhere>
+ 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 <nobody@nowhere>
+ this is not a trailer
+ Signed-off-by: somebody <somebody@somewhere>
+ 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
#include "cache.h"
+#include "config.h"
#include "string-list.h"
#include "run-command.h"
#include "commit.h"
* Copyright (c) 2013, 2014 Christian Couder <chriscool@tuxfamily.org>
*/
-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;
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);
}
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);
}
}
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))
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) {
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);
}
}
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;
}
}
-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;
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")) {
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:
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;
}
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;
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);
}
}
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;
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++) {
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,
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;
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);
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);
+ }
#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
size_t trailer_nr;
};
- void process_trailers(const char *file, int in_place, int trim_empty,
+/*
+ * 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;
+};
+
- struct string_list *trailers);
+ 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 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 */