ref-filter: add new "describe" atom

Duplicate the logic of %(describe) and friends from pretty to
ref-filter. In the future, this change helps in unifying both the
formats as ref-filter will be able to do everything that pretty is doing
and we can have a single interface.

The new atom "describe" and its friends are equivalent to the existing
pretty formats with the same name.

Helped-by: Junio C Hamano <gitster@pobox.com>
Mentored-by: Christian Couder <christian.couder@gmail.com>
Mentored-by: Hariom Verma <hariom18599@gmail.com>
Signed-off-by: Kousik Sanagavarapu <five231003@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
This commit is contained in:
Kousik Sanagavarapu 2023-07-23 21:49:59 +05:30 committed by Junio C Hamano
parent f46094a5e6
commit f5d18f8c0e
3 changed files with 286 additions and 0 deletions

View File

@ -258,6 +258,29 @@ ahead-behind:<committish>::
commits ahead and behind, respectively, when comparing the output
ref to the `<committish>` specified in the format.
describe[:options]::
A human-readable name, like linkgit:git-describe[1];
empty string for undescribable commits. The `describe` string may
be followed by a colon and one or more comma-separated options.
+
--
tags=<bool-value>;;
Instead of only considering annotated tags, consider
lightweight tags as well; see the corresponding option in
linkgit:git-describe[1] for details.
abbrev=<number>;;
Use at least <number> hexadecimal digits; see the corresponding
option in linkgit:git-describe[1] for details.
match=<pattern>;;
Only consider tags matching the given `glob(7)` pattern,
excluding the "refs/tags/" prefix; see the corresponding option
in linkgit:git-describe[1] for details.
exclude=<pattern>;;
Do not consider tags matching the given `glob(7)` pattern,
excluding the "refs/tags/" prefix; see the corresponding option
in linkgit:git-describe[1] for details.
--
In addition to the above, for commit and tag objects, the header
field names (`tree`, `parent`, `object`, `type`, and `tag`) can
be used to specify the value in the header field.

View File

@ -5,6 +5,7 @@
#include "gpg-interface.h"
#include "hex.h"
#include "parse-options.h"
#include "run-command.h"
#include "refs.h"
#include "wildmatch.h"
#include "object-name.h"
@ -146,6 +147,7 @@ enum atom_type {
ATOM_TAGGERDATE,
ATOM_CREATOR,
ATOM_CREATORDATE,
ATOM_DESCRIBE,
ATOM_SUBJECT,
ATOM_BODY,
ATOM_TRAILERS,
@ -220,6 +222,7 @@ static struct used_atom {
enum { S_BARE, S_GRADE, S_SIGNER, S_KEY,
S_FINGERPRINT, S_PRI_KEY_FP, S_TRUST_LEVEL } option;
} signature;
const char **describe_args;
struct refname_atom refname;
char *head;
} u;
@ -600,6 +603,87 @@ static int contents_atom_parser(struct ref_format *format, struct used_atom *ato
return 0;
}
static int describe_atom_option_parser(struct strvec *args, const char **arg,
struct strbuf *err)
{
const char *argval;
size_t arglen = 0;
int optval = 0;
if (match_atom_bool_arg(*arg, "tags", arg, &optval)) {
if (!optval)
strvec_push(args, "--no-tags");
else
strvec_push(args, "--tags");
return 1;
}
if (match_atom_arg_value(*arg, "abbrev", arg, &argval, &arglen)) {
char *endptr;
if (!arglen)
return strbuf_addf_ret(err, -1,
_("argument expected for %s"),
"describe:abbrev");
if (strtol(argval, &endptr, 10) < 0)
return strbuf_addf_ret(err, -1,
_("positive value expected %s=%s"),
"describe:abbrev", argval);
if (endptr - argval != arglen)
return strbuf_addf_ret(err, -1,
_("cannot fully parse %s=%s"),
"describe:abbrev", argval);
strvec_pushf(args, "--abbrev=%.*s", (int)arglen, argval);
return 1;
}
if (match_atom_arg_value(*arg, "match", arg, &argval, &arglen)) {
if (!arglen)
return strbuf_addf_ret(err, -1,
_("value expected %s="),
"describe:match");
strvec_pushf(args, "--match=%.*s", (int)arglen, argval);
return 1;
}
if (match_atom_arg_value(*arg, "exclude", arg, &argval, &arglen)) {
if (!arglen)
return strbuf_addf_ret(err, -1,
_("value expected %s="),
"describe:exclude");
strvec_pushf(args, "--exclude=%.*s", (int)arglen, argval);
return 1;
}
return 0;
}
static int describe_atom_parser(struct ref_format *format UNUSED,
struct used_atom *atom,
const char *arg, struct strbuf *err)
{
struct strvec args = STRVEC_INIT;
for (;;) {
int found = 0;
const char *bad_arg = arg;
if (!arg || !*arg)
break;
found = describe_atom_option_parser(&args, &arg, err);
if (found < 0)
return found;
if (!found)
return err_bad_arg(err, "describe", bad_arg);
}
atom->u.describe_args = strvec_detach(&args);
return 0;
}
static int raw_atom_parser(struct ref_format *format UNUSED,
struct used_atom *atom,
const char *arg, struct strbuf *err)
@ -802,6 +886,7 @@ static struct {
[ATOM_TAGGERDATE] = { "taggerdate", SOURCE_OBJ, FIELD_TIME },
[ATOM_CREATOR] = { "creator", SOURCE_OBJ },
[ATOM_CREATORDATE] = { "creatordate", SOURCE_OBJ, FIELD_TIME },
[ATOM_DESCRIBE] = { "describe", SOURCE_OBJ, FIELD_STR, describe_atom_parser },
[ATOM_SUBJECT] = { "subject", SOURCE_OBJ, FIELD_STR, subject_atom_parser },
[ATOM_BODY] = { "body", SOURCE_OBJ, FIELD_STR, body_atom_parser },
[ATOM_TRAILERS] = { "trailers", SOURCE_OBJ, FIELD_STR, trailers_atom_parser },
@ -1708,6 +1793,44 @@ static void append_lines(struct strbuf *out, const char *buf, unsigned long size
}
}
static void grab_describe_values(struct atom_value *val, int deref,
struct object *obj)
{
struct commit *commit = (struct commit *)obj;
int i;
for (i = 0; i < used_atom_cnt; i++) {
struct used_atom *atom = &used_atom[i];
enum atom_type type = atom->atom_type;
const char *name = atom->name;
struct atom_value *v = &val[i];
struct child_process cmd = CHILD_PROCESS_INIT;
struct strbuf out = STRBUF_INIT;
struct strbuf err = STRBUF_INIT;
if (type != ATOM_DESCRIBE)
continue;
if (!!deref != (*name == '*'))
continue;
cmd.git_cmd = 1;
strvec_push(&cmd.args, "describe");
strvec_pushv(&cmd.args, atom->u.describe_args);
strvec_push(&cmd.args, oid_to_hex(&commit->object.oid));
if (pipe_command(&cmd, NULL, 0, &out, 0, &err, 0) < 0) {
error(_("failed to run 'describe'"));
v->s = xstrdup("");
continue;
}
strbuf_rtrim(&out);
v->s = strbuf_detach(&out, NULL);
strbuf_release(&err);
}
}
/* See grab_values */
static void grab_sub_body_contents(struct atom_value *val, int deref, struct expand_data *data)
{
@ -1817,6 +1940,7 @@ static void grab_values(struct atom_value *val, int deref, struct object *obj, s
grab_tag_values(val, deref, obj);
grab_sub_body_contents(val, deref, data);
grab_person("tagger", val, deref, buf);
grab_describe_values(val, deref, obj);
break;
case OBJ_COMMIT:
grab_commit_values(val, deref, obj);
@ -1824,6 +1948,7 @@ static void grab_values(struct atom_value *val, int deref, struct object *obj, s
grab_person("author", val, deref, buf);
grab_person("committer", val, deref, buf);
grab_signature(val, deref, obj);
grab_describe_values(val, deref, obj);
break;
case OBJ_TREE:
/* grab_tree_values(val, deref, obj, buf, sz); */

View File

@ -562,6 +562,144 @@ test_expect_success 'color.ui=always does not override tty check' '
test_cmp expected.bare actual
'
test_expect_success 'setup for describe atom tests' '
git init -b master describe-repo &&
(
cd describe-repo &&
test_commit --no-tag one &&
git tag tagone &&
test_commit --no-tag two &&
git tag -a -m "tag two" tagtwo
)
'
test_expect_success 'describe atom vs git describe' '
(
cd describe-repo &&
git for-each-ref --format="%(objectname)" \
refs/tags/ >obj &&
while read hash
do
if desc=$(git describe $hash)
then
: >expect-contains-good
else
: >expect-contains-bad
fi &&
echo "$hash $desc" || return 1
done <obj >expect &&
test_path_exists expect-contains-good &&
test_path_exists expect-contains-bad &&
git for-each-ref --format="%(objectname) %(describe)" \
refs/tags/ >actual 2>err &&
test_cmp expect actual &&
test_must_be_empty err
)
'
test_expect_success 'describe:tags vs describe --tags' '
(
cd describe-repo &&
git describe --tags >expect &&
git for-each-ref --format="%(describe:tags)" \
refs/heads/master >actual &&
test_cmp expect actual
)
'
test_expect_success 'describe:abbrev=... vs describe --abbrev=...' '
(
cd describe-repo &&
# Case 1: We have commits between HEAD and the most
# recent tag reachable from it
test_commit --no-tag file &&
git describe --abbrev=14 >expect &&
git for-each-ref --format="%(describe:abbrev=14)" \
refs/heads/master >actual &&
test_cmp expect actual &&
# Make sure the hash used is atleast 14 digits long
sed -e "s/^.*-g\([0-9a-f]*\)$/\1/" <actual >hexpart &&
test 15 -le $(wc -c <hexpart) &&
# Case 2: We have a tag at HEAD, describe directly gives
# the name of the tag
git tag -a -m tagged tagname &&
git describe --abbrev=14 >expect &&
git for-each-ref --format="%(describe:abbrev=14)" \
refs/heads/master >actual &&
test_cmp expect actual &&
test tagname = $(cat actual)
)
'
test_expect_success 'describe:match=... vs describe --match ...' '
(
cd describe-repo &&
git tag -a -m "tag foo" tag-foo &&
git describe --match "*-foo" >expect &&
git for-each-ref --format="%(describe:match="*-foo")" \
refs/heads/master >actual &&
test_cmp expect actual
)
'
test_expect_success 'describe:exclude:... vs describe --exclude ...' '
(
cd describe-repo &&
git tag -a -m "tag bar" tag-bar &&
git describe --exclude "*-bar" >expect &&
git for-each-ref --format="%(describe:exclude="*-bar")" \
refs/heads/master >actual &&
test_cmp expect actual
)
'
test_expect_success 'deref with describe atom' '
(
cd describe-repo &&
cat >expect <<-\EOF &&
tagname
tagname
tagname
tagtwo
EOF
git for-each-ref --format="%(*describe)" >actual &&
test_cmp expect actual
)
'
test_expect_success 'err on bad describe atom arg' '
(
cd describe-repo &&
# The bad arg is the only arg passed to describe atom
cat >expect <<-\EOF &&
fatal: unrecognized %(describe) argument: baz
EOF
test_must_fail git for-each-ref --format="%(describe:baz)" \
refs/heads/master 2>actual &&
test_cmp expect actual &&
# The bad arg is in the middle of the option string
# passed to the describe atom
cat >expect <<-\EOF &&
fatal: unrecognized %(describe) argument: qux=1,abbrev=14
EOF
test_must_fail git for-each-ref \
--format="%(describe:tags,qux=1,abbrev=14)" \
ref/heads/master 2>actual &&
test_cmp expect actual
)
'
cat >expected <<\EOF
heads/main
tags/main