Merge branch 'pb/trailers-from-command-line'

"git interpret-trailers" learned to take the trailer specifications
from the command line that overrides the configured values.

* pb/trailers-from-command-line:
  interpret-trailers: fix documentation typo
  interpret-trailers: add options for actions
  trailers: introduce struct new_trailer_item
  trailers: export action enums and corresponding lookup functions
This commit is contained in:
Junio C Hamano
2017-08-26 22:55:04 -07:00
5 changed files with 274 additions and 53 deletions

View File

@ -80,6 +80,29 @@ OPTIONS
trailer to the input messages. See the description of this trailer to the input messages. See the description of this
command. 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'.
CONFIGURATION VARIABLES CONFIGURATION VARIABLES
----------------------- -----------------------
@ -170,8 +193,8 @@ trailer.<token>.where::
configuration variable and it overrides what is specified by configuration variable and it overrides what is specified by
that option for trailers with the specified <token>. that option for trailers with the specified <token>.
trailer.<token>.ifexist:: trailer.<token>.ifexists::
This option takes the same values as the 'trailer.ifexist' This option takes the same values as the 'trailer.ifexists'
configuration variable and it overrides what is specified by configuration variable and it overrides what is specified by
that option for trailers with the specified <token>. that option for trailers with the specified <token>.

View File

@ -16,17 +16,82 @@ static const char * const git_interpret_trailers_usage[] = {
NULL 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;
}
int cmd_interpret_trailers(int argc, const char **argv, const char *prefix) int cmd_interpret_trailers(int argc, const char **argv, const char *prefix)
{ {
int in_place = 0; int in_place = 0;
int trim_empty = 0; int trim_empty = 0;
struct string_list trailers = STRING_LIST_INIT_NODUP; LIST_HEAD(trailers);
struct option options[] = { struct option options[] = {
OPT_BOOL(0, "in-place", &in_place, N_("edit files in place")), 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, "trim-empty", &trim_empty, N_("trim empty trailers")),
OPT_STRING_LIST(0, "trailer", &trailers, N_("trailer"),
N_("trailer(s) to add")), 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_CALLBACK(0, "trailer", &trailers, N_("trailer"),
N_("trailer(s) to add"), option_parse_trailer),
OPT_END() OPT_END()
}; };
@ -43,7 +108,7 @@ int cmd_interpret_trailers(int argc, const char **argv, const char *prefix)
process_trailers(NULL, in_place, trim_empty, &trailers); process_trailers(NULL, in_place, trim_empty, &trailers);
} }
string_list_clear(&trailers, 0); new_trailers_clear(&trailers);
return 0; return 0;
} }

View File

@ -681,6 +681,36 @@ test_expect_success 'using "where = before"' '
test_cmp expected actual 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"' ' test_expect_success 'using "where = after"' '
git config trailer.ack.where "after" && git config trailer.ack.where "after" &&
cat complex_message_body >expected && cat complex_message_body >expected &&
@ -947,6 +977,23 @@ test_expect_success 'using "ifExists = add" with "where = after"' '
test_cmp expected actual 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"' ' test_expect_success 'using "ifExists = replace"' '
git config trailer.fix.key "Fixes: " && git config trailer.fix.key "Fixes: " &&
git config trailer.fix.ifExists "replace" && git config trailer.fix.ifExists "replace" &&
@ -1026,6 +1073,25 @@ test_expect_success 'the default is "ifMissing = add"' '
test_cmp expected actual 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"' ' test_expect_success 'when default "ifMissing" is "doNothing"' '
git config trailer.ifmissing "doNothing" && git config trailer.ifmissing "doNothing" &&
cat complex_message_body >expected && cat complex_message_body >expected &&

118
trailer.c
View File

@ -10,18 +10,13 @@
* Copyright (c) 2013, 2014 Christian Couder <chriscool@tuxfamily.org> * 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 { struct conf_info {
char *name; char *name;
char *key; char *key;
char *command; char *command;
enum action_where where; enum trailer_where where;
enum action_if_exists if_exists; enum trailer_if_exists if_exists;
enum action_if_missing if_missing; enum trailer_if_missing if_missing;
}; };
static struct conf_info default_conf_info; static struct conf_info default_conf_info;
@ -63,7 +58,7 @@ static const char *git_generated_prefixes[] = {
pos != (head); \ pos != (head); \
pos = is_reverse ? pos->prev : pos->next) 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); return (where == WHERE_AFTER) || (where == WHERE_END);
} }
@ -201,7 +196,7 @@ static int check_if_different(struct trailer_item *in_tok,
int check_all, int check_all,
struct list_head *head) 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; struct list_head *next_head;
do { do {
if (same_trailer(in_tok, arg_tok)) if (same_trailer(in_tok, arg_tok))
@ -300,13 +295,16 @@ static void apply_arg_if_exists(struct trailer_item *in_tok,
else else
free_arg_item(arg_tok); free_arg_item(arg_tok);
break; break;
default:
die("BUG: trailer.c: unhandled value %d",
arg_tok->conf.if_exists);
} }
} }
static void apply_arg_if_missing(struct list_head *head, static void apply_arg_if_missing(struct list_head *head,
struct arg_item *arg_tok) struct arg_item *arg_tok)
{ {
enum action_where where; enum trailer_where where;
struct trailer_item *to_add; struct trailer_item *to_add;
switch (arg_tok->conf.if_missing) { switch (arg_tok->conf.if_missing) {
@ -321,6 +319,10 @@ static void apply_arg_if_missing(struct list_head *head,
list_add_tail(&to_add->list, head); list_add_tail(&to_add->list, head);
else else
list_add(&to_add->list, head); list_add(&to_add->list, head);
break;
default:
die("BUG: trailer.c: unhandled value %d",
arg_tok->conf.if_missing);
} }
} }
@ -331,7 +333,7 @@ static int find_same_and_apply_arg(struct list_head *head,
struct trailer_item *in_tok; struct trailer_item *in_tok;
struct trailer_item *on_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 middle = (where == WHERE_AFTER) || (where == WHERE_BEFORE);
int backwards = after_or_end(where); int backwards = after_or_end(where);
struct trailer_item *start_tok; struct trailer_item *start_tok;
@ -373,44 +375,50 @@ static void process_trailers_lists(struct list_head *head,
} }
} }
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)) if (!value)
item->where = WHERE_AFTER; *item = WHERE_DEFAULT;
else if (!strcasecmp("after", value))
*item = WHERE_AFTER;
else if (!strcasecmp("before", value)) else if (!strcasecmp("before", value))
item->where = WHERE_BEFORE; *item = WHERE_BEFORE;
else if (!strcasecmp("end", value)) else if (!strcasecmp("end", value))
item->where = WHERE_END; *item = WHERE_END;
else if (!strcasecmp("start", value)) else if (!strcasecmp("start", value))
item->where = WHERE_START; *item = WHERE_START;
else else
return -1; return -1;
return 0; 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)) if (!value)
item->if_exists = EXISTS_ADD_IF_DIFFERENT; *item = EXISTS_DEFAULT;
else if (!strcasecmp("addIfDifferent", value))
*item = EXISTS_ADD_IF_DIFFERENT;
else if (!strcasecmp("addIfDifferentNeighbor", value)) else if (!strcasecmp("addIfDifferentNeighbor", value))
item->if_exists = EXISTS_ADD_IF_DIFFERENT_NEIGHBOR; *item = EXISTS_ADD_IF_DIFFERENT_NEIGHBOR;
else if (!strcasecmp("add", value)) else if (!strcasecmp("add", value))
item->if_exists = EXISTS_ADD; *item = EXISTS_ADD;
else if (!strcasecmp("replace", value)) else if (!strcasecmp("replace", value))
item->if_exists = EXISTS_REPLACE; *item = EXISTS_REPLACE;
else if (!strcasecmp("doNothing", value)) else if (!strcasecmp("doNothing", value))
item->if_exists = EXISTS_DO_NOTHING; *item = EXISTS_DO_NOTHING;
else else
return -1; return -1;
return 0; 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)) if (!value)
item->if_missing = MISSING_DO_NOTHING; *item = MISSING_DEFAULT;
else if (!strcasecmp("doNothing", value))
*item = MISSING_DO_NOTHING;
else if (!strcasecmp("add", value)) else if (!strcasecmp("add", value))
item->if_missing = MISSING_ADD; *item = MISSING_ADD;
else else
return -1; return -1;
return 0; return 0;
@ -470,15 +478,18 @@ static int git_trailer_default_config(const char *conf_key, const char *value, v
variable_name = strrchr(trailer_item, '.'); variable_name = strrchr(trailer_item, '.');
if (!variable_name) { if (!variable_name) {
if (!strcmp(trailer_item, "where")) { 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'"), warning(_("unknown value '%s' for key '%s'"),
value, conf_key); value, conf_key);
} else if (!strcmp(trailer_item, "ifexists")) { } 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'"), warning(_("unknown value '%s' for key '%s'"),
value, conf_key); value, conf_key);
} else if (!strcmp(trailer_item, "ifmissing")) { } 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'"), warning(_("unknown value '%s' for key '%s'"),
value, conf_key); value, conf_key);
} else if (!strcmp(trailer_item, "separators")) { } else if (!strcmp(trailer_item, "separators")) {
@ -532,15 +543,15 @@ static int git_trailer_config(const char *conf_key, const char *value, void *cb)
conf->command = xstrdup(value); conf->command = xstrdup(value);
break; break;
case TRAILER_WHERE: 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); warning(_("unknown value '%s' for key '%s'"), value, conf_key);
break; break;
case TRAILER_IF_EXISTS: 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); warning(_("unknown value '%s' for key '%s'"), value, conf_key);
break; break;
case TRAILER_IF_MISSING: 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); warning(_("unknown value '%s' for key '%s'"), value, conf_key);
break; break;
default: default:
@ -555,6 +566,9 @@ static void ensure_configured(void)
return; return;
/* Default config must be setup first */ /* 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_default_config, NULL);
git_config(git_trailer_config, NULL); git_config(git_trailer_config, NULL);
configured = 1; configured = 1;
@ -658,19 +672,27 @@ static struct trailer_item *add_trailer_item(struct list_head *head, char *tok,
} }
static void add_arg_item(struct list_head *arg_head, char *tok, char *val, 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); struct arg_item *new = xcalloc(sizeof(*new), 1);
new->token = tok; new->token = tok;
new->value = val; new->value = val;
duplicate_conf(&new->conf, conf); 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); list_add_tail(&new->list, arg_head);
} }
static void process_command_line_args(struct list_head *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 arg_item *item;
struct strbuf tok = STRBUF_INIT; struct strbuf tok = STRBUF_INIT;
struct strbuf val = STRBUF_INIT; struct strbuf val = STRBUF_INIT;
@ -690,26 +712,29 @@ static void process_command_line_args(struct list_head *arg_head,
add_arg_item(arg_head, add_arg_item(arg_head,
xstrdup(token_from_item(item, NULL)), xstrdup(token_from_item(item, NULL)),
xstrdup(""), xstrdup(""),
&item->conf); &item->conf, NULL);
} }
/* Add an arg 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) { list_for_each(pos, new_trailer_head) {
int separator_pos = find_separator(tr->string, cl_separators); 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) { if (separator_pos == 0) {
struct strbuf sb = STRBUF_INIT; struct strbuf sb = STRBUF_INIT;
strbuf_addstr(&sb, tr->string); strbuf_addstr(&sb, tr->text);
strbuf_trim(&sb); strbuf_trim(&sb);
error(_("empty trailer token in trailer '%.*s'"), error(_("empty trailer token in trailer '%.*s'"),
(int) sb.len, sb.buf); (int) sb.len, sb.buf);
strbuf_release(&sb); strbuf_release(&sb);
} else { } else {
parse_trailer(&tok, &val, &conf, tr->string, parse_trailer(&tok, &val, &conf, tr->text,
separator_pos); separator_pos);
add_arg_item(arg_head, add_arg_item(arg_head,
strbuf_detach(&tok, NULL), strbuf_detach(&tok, NULL),
strbuf_detach(&val, NULL), strbuf_detach(&val, NULL),
conf); conf, tr);
} }
} }
@ -968,7 +993,8 @@ static FILE *create_in_place_tempfile(const char *file)
return outfile; return outfile;
} }
void process_trailers(const char *file, int in_place, int trim_empty, struct string_list *trailers) void process_trailers(const char *file, int in_place, int trim_empty,
struct list_head *new_trailer_head)
{ {
LIST_HEAD(head); LIST_HEAD(head);
LIST_HEAD(arg_head); LIST_HEAD(arg_head);
@ -986,7 +1012,7 @@ void process_trailers(const char *file, int in_place, int trim_empty, struct str
/* Print the lines before the trailers */ /* Print the lines before the trailers */
trailer_end = process_input_file(outfile, sb.buf, &head); trailer_end = process_input_file(outfile, sb.buf, &head);
process_command_line_args(&arg_head, trailers); process_command_line_args(&arg_head, new_trailer_head);
process_trailers_lists(&head, &arg_head); process_trailers_lists(&head, &arg_head);

View File

@ -1,6 +1,33 @@
#ifndef TRAILER_H #ifndef TRAILER_H
#define 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 { struct trailer_info {
/* /*
* True if there is a blank line before the location pointed to by * True if there is a blank line before the location pointed to by
@ -22,8 +49,22 @@ struct trailer_info {
size_t trailer_nr; 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, void process_trailers(const char *file, int in_place, int trim_empty,
struct string_list *trailers); struct list_head *new_trailer_head);
void trailer_info_get(struct trailer_info *info, const char *str); void trailer_info_get(struct trailer_info *info, const char *str);