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. Mentored-by: Christian Couder <christian.couder@xxxxxxxxx> Mentored-by: Hariom Verma <hariom18599@xxxxxxxxx> Signed-off-by: Kousik Sanagavarapu <five231003@xxxxxxxxx> --- Documentation/git-for-each-ref.txt | 19 ++++ ref-filter.c | 144 +++++++++++++++++++++++++++++ t/t6300-for-each-ref.sh | 85 +++++++++++++++++ 3 files changed, 248 insertions(+) diff --git a/Documentation/git-for-each-ref.txt b/Documentation/git-for-each-ref.txt index 1e215d4e73..4ac5c3dac4 100644 --- a/Documentation/git-for-each-ref.txt +++ b/Documentation/git-for-each-ref.txt @@ -231,6 +231,25 @@ ahead-behind:<committish>:: commits ahead and behind, respectively, when comparing the output ref to the `<committish>` specified in the format. +describe[:options]:: human-readable name, like + link-git:git-describe[1]; empty string for + undescribable commits. The `describe` string may be + followed by a colon and zero or more comma-separated + options. Descriptions can be inconsistent when tags + are added or removed at the same time. ++ +** tags=<bool-value>: Instead of only considering annotated tags, consider + lightweight tags as well. +** abbrev=<number>: Instead of using the default number of hexadecimal digits + (which will vary according to the number of objects in the + repository with a default of 7) of the abbreviated + object name, use <number> digits, or as many digits as + needed to form a unique object name. +** match=<pattern>: Only consider tags matching the given `glob(7)` pattern, + excluding the "refs/tags/" prefix. +** exclude=<pattern>: Do not consider tags matching the given `glob(7)` + pattern,excluding the "refs/tags/" prefix. + 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. diff --git a/ref-filter.c b/ref-filter.c index e0d03a9f8e..6ec647c81f 100644 --- a/ref-filter.c +++ b/ref-filter.c @@ -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, @@ -215,6 +217,13 @@ static struct used_atom { struct email_option { enum { EO_RAW, EO_TRIM, EO_LOCALPART } option; } email_option; + struct { + enum { D_BARE, D_TAGS, D_ABBREV, D_EXCLUDE, + D_MATCH } option; + unsigned int tagbool; + unsigned int length; + char *pattern; + } describe; struct refname_atom refname; char *head; } u; @@ -462,6 +471,66 @@ static int contents_atom_parser(struct ref_format *format, struct used_atom *ato return 0; } +static int parse_describe_option(const char *arg) +{ + if (!arg) + return D_BARE; + else if (starts_with(arg, "tags")) + return D_TAGS; + else if (starts_with(arg, "abbrev")) + return D_ABBREV; + else if(starts_with(arg, "exclude")) + return D_EXCLUDE; + else if (starts_with(arg, "match")) + return D_MATCH; + return -1; +} + +static int describe_atom_parser(struct ref_format *format UNUSED, + struct used_atom *atom, + const char *arg, struct strbuf *err) +{ + int opt = parse_describe_option(arg); + + switch (opt) { + case D_BARE: + break; + case D_TAGS: + /* + * It is also possible to just use describe:tags, which + * is just treated as describe:tags=1 + */ + if (skip_prefix(arg, "tags=", &arg)) { + if (strtoul_ui(arg, 10, &atom->u.describe.tagbool)) + return strbuf_addf_ret(err, -1, _("boolean value " + "expected describe:tags=%s"), arg); + + } else { + atom->u.describe.tagbool = 1; + } + break; + case D_ABBREV: + skip_prefix(arg, "abbrev=", &arg); + if (strtoul_ui(arg, 10, &atom->u.describe.length)) + return strbuf_addf_ret(err, -1, _("positive value " + "expected describe:abbrev=%s"), arg); + break; + case D_EXCLUDE: + skip_prefix(arg, "exclude=", &arg); + atom->u.describe.pattern = xstrdup(arg); + break; + case D_MATCH: + skip_prefix(arg, "match=", &arg); + atom->u.describe.pattern = xstrdup(arg); + break; + default: + return err_bad_arg(err, "describe", arg); + break; + } + atom->u.describe.option = opt; + return 0; +} + static int raw_atom_parser(struct ref_format *format UNUSED, struct used_atom *atom, const char *arg, struct strbuf *err) @@ -664,6 +733,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 }, @@ -1483,6 +1553,78 @@ 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]; + const char *name = atom->name; + struct atom_value *v = &val[i]; + int opt; + + struct child_process cmd = CHILD_PROCESS_INIT; + struct strbuf out = STRBUF_INIT; + struct strbuf err = STRBUF_INIT; + + if (!!deref != (*name == '*')) + continue; + if (deref) + name++; + + if (!skip_prefix(name, "describe", &name) || + (*name && *name != ':')) + continue; + if (!*name) + name = NULL; + else + name++; + + opt = parse_describe_option(name); + if (opt < 0) + continue; + + cmd.git_cmd = 1; + strvec_push(&cmd.args, "describe"); + + switch(opt) { + case D_BARE: + break; + case D_TAGS: + if (atom->u.describe.tagbool) + strvec_push(&cmd.args, "--tags"); + else + strvec_push(&cmd.args, "--no-tags"); + break; + case D_ABBREV: + strvec_pushf(&cmd.args, "--abbrev=%d", + atom->u.describe.length); + break; + case D_EXCLUDE: + strvec_pushf(&cmd.args, "--exclude=%s", + atom->u.describe.pattern); + break; + case D_MATCH: + strvec_pushf(&cmd.args, "--match=%s", + atom->u.describe.pattern); + break; + } + + 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) { @@ -1592,12 +1734,14 @@ 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); grab_sub_body_contents(val, deref, data); grab_person("author", val, deref, buf); grab_person("committer", val, deref, buf); + grab_describe_values(val, deref, obj); break; case OBJ_TREE: /* grab_tree_values(val, deref, obj, buf, sz); */ diff --git a/t/t6300-for-each-ref.sh b/t/t6300-for-each-ref.sh index 5c00607608..98ea37d336 100755 --- a/t/t6300-for-each-ref.sh +++ b/t/t6300-for-each-ref.sh @@ -561,6 +561,91 @@ test_expect_success 'color.ui=always does not override tty check' ' test_cmp expected.bare actual ' +test_expect_success 'describe atom vs git describe' ' + test_when_finished "rm -rf describe-repo" && + + git init 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 && + + git for-each-ref refs/tags/ --format="%(objectname)" >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' ' + test_when_finished "git tag -d tagname" && + git tag tagname && + git describe --tags >expect && + git for-each-ref --format="%(describe:tags)" refs/heads/ >actual && + test_cmp expect actual +' + +test_expect_success 'describe:abbrev=... vs describe --abbrev=...' ' + test_when_finished "git tag -d tagname" && + + # 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/ >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/ >actual && + test_cmp expect actual && + test tagname = $(cat actual) +' + +test_expect_success 'describe:match=... vs describe --match ...' ' + test_when_finished "git tag -d tag-match" && + git tag -a -m "tag match" tag-match && + git describe --match "*-match" >expect && + git for-each-ref --format="%(describe:match="*-match")" \ + refs/heads/ >actual && + test_cmp expect actual +' + +test_expect_success 'describe:exclude:... vs describe --exclude ...' ' + test_when_finished "git tag -d tag-exclude" && + git tag -a -m "tag exclude" tag-exclude && + git describe --exclude "*-exclude" >expect && + git for-each-ref --format="%(describe:exclude="*-exclude")" \ + refs/heads/ >actual && + test_cmp expect actual +' + cat >expected <<\EOF heads/main tags/main -- 2.41.0.237.g2d10a112d6.dirty