Merge branch 'ks/ref-filter-describe' into next
"git branch --list --format=<format>" and friends are taught a new "%(describe)" placeholder. * ks/ref-filter-describe: ref-filter: add new "describe" atom ref-filter: add multiple-option parsing functions
This commit is contained in:
@ -264,6 +264,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.
|
||||
|
230
ref-filter.c
230
ref-filter.c
@ -1,9 +1,11 @@
|
||||
#include "git-compat-util.h"
|
||||
#include "environment.h"
|
||||
#include "gettext.h"
|
||||
#include "config.h"
|
||||
#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"
|
||||
@ -145,6 +147,7 @@ enum atom_type {
|
||||
ATOM_TAGGERDATE,
|
||||
ATOM_CREATOR,
|
||||
ATOM_CREATORDATE,
|
||||
ATOM_DESCRIBE,
|
||||
ATOM_SUBJECT,
|
||||
ATOM_BODY,
|
||||
ATOM_TRAILERS,
|
||||
@ -219,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;
|
||||
@ -255,6 +259,110 @@ static int err_bad_arg(struct strbuf *sb, const char *name, const char *arg)
|
||||
return -1;
|
||||
}
|
||||
|
||||
/*
|
||||
* Parse option of name "candidate" in the option string "to_parse" of
|
||||
* the form
|
||||
*
|
||||
* "candidate1[=val1],candidate2[=val2],candidate3[=val3],..."
|
||||
*
|
||||
* The remaining part of "to_parse" is stored in "end" (if we are
|
||||
* parsing the last candidate, then this is NULL) and the value of
|
||||
* the candidate is stored in "valuestart" and its length in "valuelen",
|
||||
* that is the portion after "=". Since it is possible for a "candidate"
|
||||
* to not have a value, in such cases, "valuestart" is set to point to
|
||||
* NULL and "valuelen" to 0.
|
||||
*
|
||||
* The function returns 1 on success. It returns 0 if we don't find
|
||||
* "candidate" in "to_parse" or we find "candidate" but it is followed
|
||||
* by more chars (for example, "candidatefoo"), that is, we don't find
|
||||
* an exact match.
|
||||
*
|
||||
* This function only does the above for one "candidate" at a time. So
|
||||
* it has to be called each time trying to parse a "candidate" in the
|
||||
* option string "to_parse".
|
||||
*/
|
||||
static int match_atom_arg_value(const char *to_parse, const char *candidate,
|
||||
const char **end, const char **valuestart,
|
||||
size_t *valuelen)
|
||||
{
|
||||
const char *atom;
|
||||
|
||||
if (!skip_prefix(to_parse, candidate, &atom))
|
||||
return 0; /* definitely not "candidate" */
|
||||
|
||||
if (*atom == '=') {
|
||||
/* we just saw "candidate=" */
|
||||
*valuestart = atom + 1;
|
||||
atom = strchrnul(*valuestart, ',');
|
||||
*valuelen = atom - *valuestart;
|
||||
} else if (*atom != ',' && *atom != '\0') {
|
||||
/* key begins with "candidate" but has more chars */
|
||||
return 0;
|
||||
} else {
|
||||
/* just "candidate" without "=val" */
|
||||
*valuestart = NULL;
|
||||
*valuelen = 0;
|
||||
}
|
||||
|
||||
/* atom points at either the ',' or NUL after this key[=val] */
|
||||
if (*atom == ',')
|
||||
atom++;
|
||||
else if (*atom)
|
||||
BUG("Why is *atom not NULL yet?");
|
||||
|
||||
*end = atom;
|
||||
return 1;
|
||||
}
|
||||
|
||||
/*
|
||||
* Parse boolean option of name "candidate" in the option list "to_parse"
|
||||
* of the form
|
||||
*
|
||||
* "candidate1[=bool1],candidate2[=bool2],candidate3[=bool3],..."
|
||||
*
|
||||
* The remaining part of "to_parse" is stored in "end" (if we are parsing
|
||||
* the last candidate, then this is NULL) and the value (if given) is
|
||||
* parsed and stored in "val", so "val" always points to either 0 or 1.
|
||||
* If the value is not given, then "val" is set to point to 1.
|
||||
*
|
||||
* The boolean value is parsed using "git_parse_maybe_bool()", so the
|
||||
* accepted values are
|
||||
*
|
||||
* to set true - "1", "yes", "true"
|
||||
* to set false - "0", "no", "false"
|
||||
*
|
||||
* This function returns 1 on success. It returns 0 when we don't find
|
||||
* an exact match for "candidate" or when the boolean value given is
|
||||
* not valid.
|
||||
*/
|
||||
static int match_atom_bool_arg(const char *to_parse, const char *candidate,
|
||||
const char **end, int *val)
|
||||
{
|
||||
const char *argval;
|
||||
char *strval;
|
||||
size_t arglen;
|
||||
int v;
|
||||
|
||||
if (!match_atom_arg_value(to_parse, candidate, end, &argval, &arglen))
|
||||
return 0;
|
||||
|
||||
if (!argval) {
|
||||
*val = 1;
|
||||
return 1;
|
||||
}
|
||||
|
||||
strval = xstrndup(argval, arglen);
|
||||
v = git_parse_maybe_bool(strval);
|
||||
free(strval);
|
||||
|
||||
if (v == -1)
|
||||
return 0;
|
||||
|
||||
*val = v;
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int color_atom_parser(struct ref_format *format, struct used_atom *atom,
|
||||
const char *color_value, struct strbuf *err)
|
||||
{
|
||||
@ -495,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)
|
||||
@ -697,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 },
|
||||
@ -1603,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)
|
||||
{
|
||||
@ -1712,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);
|
||||
@ -1719,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); */
|
||||
|
@ -597,6 +597,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
|
||||
|
Reference in New Issue
Block a user