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
This commit is contained in:
commit
06cf4f2d87
@ -3,24 +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 <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, 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
|
||||
@ -103,6 +106,22 @@ OPTIONS
|
||||
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
|
||||
-----------------------
|
||||
|
||||
|
@ -205,7 +205,10 @@ endif::git-rev-list[]
|
||||
- '%><(<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
|
||||
|
@ -73,15 +73,24 @@ static int option_parse_trailer(const struct option *opt,
|
||||
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;
|
||||
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),
|
||||
@ -90,6 +99,11 @@ int cmd_interpret_trailers(int argc, const char **argv, const char *prefix)
|
||||
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_CALLBACK(0, "trailer", &trailers, N_("trailer"),
|
||||
N_("trailer(s) to add"), option_parse_trailer),
|
||||
OPT_END()
|
||||
@ -98,14 +112,20 @@ int cmd_interpret_trailers(int argc, const char **argv, const char *prefix)
|
||||
argc = parse_options(argc, argv, prefix, options,
|
||||
git_interpret_trailers_usage, 0);
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
new_trailers_clear(&trailers);
|
||||
|
26
pretty.c
26
pretty.c
@ -871,16 +871,6 @@ const char *format_subject(struct strbuf *sb, const char *msg,
|
||||
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;
|
||||
@ -1074,6 +1064,7 @@ static size_t format_commit_one(struct strbuf *sb, /* in UTF-8 */
|
||||
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 */
|
||||
@ -1292,9 +1283,18 @@ static size_t format_commit_one(struct strbuf *sb, /* in UTF-8 */
|
||||
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 */
|
||||
|
@ -539,25 +539,62 @@ cat >trailers <<EOF
|
||||
Signed-off-by: A U Thor <author@example.com>
|
||||
Acked-by: A U Thor <author@example.com>
|
||||
[ v2 updated patch description ]
|
||||
Signed-off-by: A U Thor <author@example.com>
|
||||
Signed-off-by: A U Thor
|
||||
<author@example.com>
|
||||
EOF
|
||||
|
||||
test_expect_success 'pretty format %(trailers) shows trailers' '
|
||||
unfold () {
|
||||
perl -0pe 's/\n\s+/ /'
|
||||
}
|
||||
|
||||
test_expect_success 'set up trailer tests' '
|
||||
echo "Some contents" >trailerfile &&
|
||||
git add trailerfile &&
|
||||
git commit -F - <<-EOF &&
|
||||
git commit -F - <<-EOF
|
||||
trailers: this commit message has trailers
|
||||
|
||||
This commit is a test commit with trailers at the end. We parse this
|
||||
message and display the trailers using %bT
|
||||
message and display the trailers using %(trailers).
|
||||
|
||||
$(cat trailers)
|
||||
EOF
|
||||
'
|
||||
|
||||
test_expect_success 'pretty format %(trailers) shows trailers' '
|
||||
git log --no-walk --pretty="%(trailers)" >actual &&
|
||||
cat >expect <<-EOF &&
|
||||
$(cat trailers)
|
||||
{
|
||||
cat trailers &&
|
||||
echo
|
||||
} >expect &&
|
||||
test_cmp expect actual
|
||||
'
|
||||
|
||||
EOF
|
||||
test_expect_success '%(trailers:only) shows only "key: value" trailers' '
|
||||
git log --no-walk --pretty="%(trailers:only)" >actual &&
|
||||
{
|
||||
grep -v patch.description <trailers &&
|
||||
echo
|
||||
} >expect &&
|
||||
test_cmp expect actual
|
||||
'
|
||||
|
||||
test_expect_success '%(trailers:unfold) unfolds trailers' '
|
||||
git log --no-walk --pretty="%(trailers:unfold)" >actual &&
|
||||
{
|
||||
unfold <trailers &&
|
||||
echo
|
||||
} >expect &&
|
||||
test_cmp expect actual
|
||||
'
|
||||
|
||||
test_expect_success ':only and :unfold work together' '
|
||||
git log --no-walk --pretty="%(trailers:only:unfold)" >actual &&
|
||||
git log --no-walk --pretty="%(trailers:unfold:only)" >reverse &&
|
||||
test_cmp actual reverse &&
|
||||
{
|
||||
grep -v patch.description <trailers | unfold &&
|
||||
echo
|
||||
} >expect &&
|
||||
test_cmp expect actual
|
||||
'
|
||||
|
||||
|
@ -1341,4 +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 <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
|
||||
|
114
trailer.c
114
trailer.c
@ -159,13 +159,15 @@ 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 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);
|
||||
}
|
||||
}
|
||||
@ -910,9 +912,37 @@ static int ends_with_blank_line(const char *buf, size_t len)
|
||||
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 +952,10 @@ static int process_input_file(FILE *outfile,
|
||||
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 +967,12 @@ static int process_input_file(FILE *outfile,
|
||||
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 +1026,11 @@ static FILE *create_in_place_tempfile(const char *file)
|
||||
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 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 +1039,27 @@ void process_trailers(const char *file, int in_place, int trim_empty,
|
||||
|
||||
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);
|
||||
trailer_end = process_input_file(outfile, sb.buf, &head, opts);
|
||||
|
||||
process_command_line_args(&arg_head, new_trailer_head);
|
||||
if (!opts->only_input) {
|
||||
LIST_HEAD(arg_head);
|
||||
process_command_line_args(&arg_head, new_trailer_head);
|
||||
process_trailers_lists(&head, &arg_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 +1116,49 @@ void trailer_info_release(struct trailer_info *info)
|
||||
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);
|
||||
}
|
||||
|
27
trailer.h
27
trailer.h
@ -63,11 +63,36 @@ struct new_trailer_item {
|
||||
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 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 */
|
||||
|
Loading…
Reference in New Issue
Block a user