pretty: add merge and exclude options to %(describe)

Allow restricting the tags used by the placeholder %(describe) with the options match and exclude. E.g. the following command describes the current commit using official version tags, without those for release candidates: $ git log -1 --format='%(describe:match=v[0-9]*,exclude=*rc*)' Signed-off-by: René Scharfe <l.s.r@web.de> Signed-off-by: Junio C Hamano <gitster@pobox.com>

René Scharfe committed Feb 14, 2021 at 11:10 UTC b081547ec1de57162f2c1c7748aa09c861413d34
3 files changed +68 -4
Documentation/pretty-formats.txt
+11 -2
@@ -208,8 +208,17 @@ The placeholders are:
208 '%cs':: committer date, short format (`YYYY-MM-DD`)
209 '%d':: ref names, like the --decorate option of linkgit:git-log[1]
210 '%D':: ref names without the " (", ")" wrapping.
211 -'%(describe)':: human-readable name, like linkgit:git-describe[1];
212 - empty string for undescribable commits
211 +'%(describe[:options])':: human-readable name, like
212 + linkgit:git-describe[1]; empty string for
213 + undescribable commits. The `describe` string
214 + may be followed by a colon and zero or more
215 + comma-separated options.
216 ++
217 +** 'match=<pattern>': Only consider tags matching the given
218 + `glob(7)` pattern, excluding the "refs/tags/" prefix.
219 +** 'exclude=<pattern>': Do not consider tags matching the given
220 + `glob(7)` pattern, excluding the "refs/tags/" prefix.
221 +
222 '%S':: ref name given on the command line by which the commit was reached
223 (like `git log --source`), only works with `git log`
224 '%e':: encoding
pretty.c
+41 -2
@@ -1150,6 +1150,34 @@ static int format_trailer_match_cb(const struct strbuf *key, void *ud)
1150 return 0;
1151 }
1152
1153 +static size_t parse_describe_args(const char *start, struct strvec *args)
1154 +{
1155 + const char *options[] = { "match", "exclude" };
1156 + const char *arg = start;
1157 +
1158 + for (;;) {
1159 + const char *matched = NULL;
1160 + const char *argval;
1161 + size_t arglen = 0;
1162 + int i;
1163 +
1164 + for (i = 0; i < ARRAY_SIZE(options); i++) {
1165 + if (match_placeholder_arg_value(arg, options[i], &arg,
1166 + &argval, &arglen)) {
1167 + matched = options[i];
1168 + break;
1169 + }
1170 + }
1171 + if (!matched)
1172 + break;
1173 +
1174 + if (!arglen)
1175 + return 0;
1176 + strvec_pushf(args, "--%s=%.*s", matched, (int)arglen, argval);
1177 + }
1178 + return arg - start;
1179 +}
1180 +
1181 static size_t format_commit_one(struct strbuf *sb, /* in UTF-8 */
1182 const char *placeholder,
1183 void *context)
@@ -1215,20 +1243,31 @@ static size_t format_commit_one(struct strbuf *sb, /* in UTF-8 */
1243 return parse_padding_placeholder(placeholder, c);
1244 }
1245
1218 - if (skip_prefix(placeholder, "(describe)", &arg)) {
1246 + if (skip_prefix(placeholder, "(describe", &arg)) {
1247 struct child_process cmd = CHILD_PROCESS_INIT;
1248 struct strbuf out = STRBUF_INIT;
1249 struct strbuf err = STRBUF_INIT;
1250
1251 cmd.git_cmd = 1;
1252 strvec_push(&cmd.args, "describe");
1253 +
1254 + if (*arg == ':') {
1255 + arg++;
1256 + arg += parse_describe_args(arg, &cmd.args);
1257 + }
1258 +
1259 + if (*arg != ')') {
1260 + child_process_clear(&cmd);
1261 + return 0;
1262 + }
1263 +
1264 strvec_push(&cmd.args, oid_to_hex(&commit->object.oid));
1265 pipe_command(&cmd, NULL, 0, &out, 0, &err, 0);
1266 strbuf_rtrim(&out);
1267 strbuf_addbuf(sb, &out);
1268 strbuf_release(&out);
1269 strbuf_release(&err);
1231 - return arg - placeholder;
1270 + return arg - placeholder + 1;
1271 }
1272
1273 /* these depend on the commit */
t/t4205-log-pretty-formats.sh
+16
@@ -972,4 +972,20 @@ test_expect_success '%(describe) vs git describe' '
972 test_must_be_empty err
973 '
974
975 +test_expect_success '%(describe:match=...) vs git describe --match ...' '
976 + test_when_finished "git tag -d tag-match" &&
977 + git tag -a -m tagged tag-match&&
978 + git describe --match "*-match" >expect &&
979 + git log -1 --format="%(describe:match=*-match)" >actual &&
980 + test_cmp expect actual
981 +'
982 +
983 +test_expect_success '%(describe:exclude=...) vs git describe --exclude ...' '
984 + test_when_finished "git tag -d tag-exclude" &&
985 + git tag -a -m tagged tag-exclude &&
986 + git describe --exclude "*-exclude" >expect &&
987 + git log -1 --format="%(describe:exclude=*-exclude)" >actual &&
988 + test_cmp expect actual
989 +'
990 +
991 test_done