diff --git a/Documentation/pretty-formats.txt b/Documentation/pretty-formats.txt index 76e2dbdb71..4cfea4c9d6 100644 --- a/Documentation/pretty-formats.txt +++ b/Documentation/pretty-formats.txt @@ -239,6 +239,15 @@ endif::git-rev-list[] `false`, `off`, `no` to show the non-trailer lines. If option is given without value it is enabled. If given multiple times the last value is used. +** 'separator=': specify a separator inserted between trailer + lines. When this option is not given each trailer line is + terminated with a line feed character. The string SEP may contain + the literal formatting codes described above. To use comma as + separator one must use `%x2C` as it would otherwise be parsed as + next option. If separator option is given multiple times only the + last one is used. E.g., `%(trailers:key=Ticket,separator=%x2C )` + shows all trailer lines whose key is "Ticket" separated by a comma + and a space. ** 'unfold[=val]': make it behave as if interpret-trailer's `--unfold` option was given. In same way as to for `only` it can be followed by an equal sign and explicit value. E.g., diff --git a/pretty.c b/pretty.c index 2c66698b89..6d58b21aff 100644 --- a/pretty.c +++ b/pretty.c @@ -1361,6 +1361,7 @@ static size_t format_commit_one(struct strbuf *sb, /* in UTF-8 */ if (skip_prefix(placeholder, "(trailers", &arg)) { struct process_trailer_options opts = PROCESS_TRAILER_OPTIONS_INIT; struct string_list filter_list = STRING_LIST_INIT_NODUP; + struct strbuf sepbuf = STRBUF_INIT; size_t ret = 0; opts.no_divider = 1; @@ -1384,6 +1385,14 @@ static size_t format_commit_one(struct strbuf *sb, /* in UTF-8 */ opts.filter = format_trailer_match_cb; opts.filter_data = &filter_list; opts.only_trailers = 1; + } else if (match_placeholder_arg_value(arg, "separator", &arg, &argval, &arglen)) { + char *fmt; + + strbuf_reset(&sepbuf); + fmt = xstrndup(argval, arglen); + strbuf_expand(&sepbuf, fmt, strbuf_expand_literal_cb, NULL); + free(fmt); + opts.separator = &sepbuf; } else if (!match_placeholder_bool_arg(arg, "only", &arg, &opts.only_trailers) && !match_placeholder_bool_arg(arg, "unfold", &arg, &opts.unfold) && !match_placeholder_bool_arg(arg, "valueonly", &arg, &opts.value_only)) @@ -1396,6 +1405,7 @@ static size_t format_commit_one(struct strbuf *sb, /* in UTF-8 */ } trailer_out: string_list_clear(&filter_list, 0); + strbuf_release(&sepbuf); return ret; } diff --git a/t/t4205-log-pretty-formats.sh b/t/t4205-log-pretty-formats.sh index 1ad6834781..99f50fa401 100755 --- a/t/t4205-log-pretty-formats.sh +++ b/t/t4205-log-pretty-formats.sh @@ -679,6 +679,42 @@ test_expect_success '%(trailers:key=foo,valueonly) shows only value' ' test_cmp expect actual ' +test_expect_success 'pretty format %(trailers:separator) changes separator' ' + git log --no-walk --pretty=format:"X%(trailers:separator=%x00,unfold)X" >actual && + printf "XSigned-off-by: A U Thor \0Acked-by: A U Thor \0[ v2 updated patch description ]\0Signed-off-by: A U Thor X" >expect && + test_cmp expect actual +' + +test_expect_success 'pretty format %(trailers) combining separator/key/valueonly' ' + git commit --allow-empty -F - <<-\EOF && + Important fix + + The fix is explained here + + Closes: #1234 + EOF + + git commit --allow-empty -F - <<-\EOF && + Another fix + + The fix is explained here + + Closes: #567 + Closes: #890 + EOF + + git commit --allow-empty -F - <<-\EOF && + Does not close any tickets + EOF + + git log --pretty="%s% (trailers:separator=%x2c%x20,key=Closes,valueonly)" HEAD~3.. >actual && + test_write_lines \ + "Does not close any tickets" \ + "Another fix #567, #890" \ + "Important fix #1234" >expect && + test_cmp expect actual +' + test_expect_success 'trailer parsing not fooled by --- line' ' git commit --allow-empty -F - <<-\EOF && this is the subject diff --git a/trailer.c b/trailer.c index d0d9e91631..0c414f2fed 100644 --- a/trailer.c +++ b/trailer.c @@ -1129,10 +1129,11 @@ static void format_trailer_info(struct strbuf *out, const struct trailer_info *info, const struct process_trailer_options *opts) { + size_t origlen = out->len; size_t i; /* If we want the whole block untouched, we can take the fast path. */ - if (!opts->only_trailers && !opts->unfold && !opts->filter) { + if (!opts->only_trailers && !opts->unfold && !opts->filter && !opts->separator) { strbuf_add(out, info->trailer_start, info->trailer_end - info->trailer_start); return; @@ -1150,16 +1151,26 @@ static void format_trailer_info(struct strbuf *out, if (!opts->filter || opts->filter(&tok, opts->filter_data)) { if (opts->unfold) unfold_value(&val); + + if (opts->separator && out->len != origlen) + strbuf_addbuf(out, opts->separator); if (!opts->value_only) strbuf_addf(out, "%s: ", tok.buf); strbuf_addbuf(out, &val); - strbuf_addch(out, '\n'); + if (!opts->separator) + strbuf_addch(out, '\n'); } strbuf_release(&tok); strbuf_release(&val); } else if (!opts->only_trailers) { + if (opts->separator && out->len != origlen) { + strbuf_addbuf(out, opts->separator); + } strbuf_addstr(out, trailer); + if (opts->separator) { + strbuf_rtrim(out); + } } } diff --git a/trailer.h b/trailer.h index 06d417fe93..203acf4ee1 100644 --- a/trailer.h +++ b/trailer.h @@ -73,6 +73,7 @@ struct process_trailer_options { int unfold; int no_divider; int value_only; + const struct strbuf *separator; int (*filter)(const struct strbuf *, void *); void *filter_data; };