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>

Kousik Sanagavarapu committed Jul 23, 2023 at 21:49 UTC f5d18f8c0ef9cc3e62420268c2e72d1fd46b940c
3 files changed +286
Documentation/git-for-each-ref.txt
+23
@@ -258,6 +258,29 @@ ahead-behind:<committish>::
258 commits ahead and behind, respectively, when comparing the output
259 ref to the `<committish>` specified in the format.
260
261 +describe[:options]::
262 + A human-readable name, like linkgit:git-describe[1];
263 + empty string for undescribable commits. The `describe` string may
264 + be followed by a colon and one or more comma-separated options.
265 ++
266 +--
267 +tags=<bool-value>;;
268 + Instead of only considering annotated tags, consider
269 + lightweight tags as well; see the corresponding option in
270 + linkgit:git-describe[1] for details.
271 +abbrev=<number>;;
272 + Use at least <number> hexadecimal digits; see the corresponding
273 + option in linkgit:git-describe[1] for details.
274 +match=<pattern>;;
275 + Only consider tags matching the given `glob(7)` pattern,
276 + excluding the "refs/tags/" prefix; see the corresponding option
277 + in linkgit:git-describe[1] for details.
278 +exclude=<pattern>;;
279 + Do not consider tags matching the given `glob(7)` pattern,
280 + excluding the "refs/tags/" prefix; see the corresponding option
281 + in linkgit:git-describe[1] for details.
282 +--
283 +
284 In addition to the above, for commit and tag objects, the header
285 field names (`tree`, `parent`, `object`, `type`, and `tag`) can
286 be used to specify the value in the header field.
ref-filter.c
+125
@@ -5,6 +5,7 @@
5 #include "gpg-interface.h"
6 #include "hex.h"
7 #include "parse-options.h"
8 +#include "run-command.h"
9 #include "refs.h"
10 #include "wildmatch.h"
11 #include "object-name.h"
@@ -146,6 +147,7 @@ enum atom_type {
147 ATOM_TAGGERDATE,
148 ATOM_CREATOR,
149 ATOM_CREATORDATE,
150 + ATOM_DESCRIBE,
151 ATOM_SUBJECT,
152 ATOM_BODY,
153 ATOM_TRAILERS,
@@ -220,6 +222,7 @@ static struct used_atom {
222 enum { S_BARE, S_GRADE, S_SIGNER, S_KEY,
223 S_FINGERPRINT, S_PRI_KEY_FP, S_TRUST_LEVEL } option;
224 } signature;
225 + const char **describe_args;
226 struct refname_atom refname;
227 char *head;
228 } u;
@@ -600,6 +603,87 @@ static int contents_atom_parser(struct ref_format *format, struct used_atom *ato
603 return 0;
604 }
605
606 +static int describe_atom_option_parser(struct strvec *args, const char **arg,
607 + struct strbuf *err)
608 +{
609 + const char *argval;
610 + size_t arglen = 0;
611 + int optval = 0;
612 +
613 + if (match_atom_bool_arg(*arg, "tags", arg, &optval)) {
614 + if (!optval)
615 + strvec_push(args, "--no-tags");
616 + else
617 + strvec_push(args, "--tags");
618 + return 1;
619 + }
620 +
621 + if (match_atom_arg_value(*arg, "abbrev", arg, &argval, &arglen)) {
622 + char *endptr;
623 +
624 + if (!arglen)
625 + return strbuf_addf_ret(err, -1,
626 + _("argument expected for %s"),
627 + "describe:abbrev");
628 + if (strtol(argval, &endptr, 10) < 0)
629 + return strbuf_addf_ret(err, -1,
630 + _("positive value expected %s=%s"),
631 + "describe:abbrev", argval);
632 + if (endptr - argval != arglen)
633 + return strbuf_addf_ret(err, -1,
634 + _("cannot fully parse %s=%s"),
635 + "describe:abbrev", argval);
636 +
637 + strvec_pushf(args, "--abbrev=%.*s", (int)arglen, argval);
638 + return 1;
639 + }
640 +
641 + if (match_atom_arg_value(*arg, "match", arg, &argval, &arglen)) {
642 + if (!arglen)
643 + return strbuf_addf_ret(err, -1,
644 + _("value expected %s="),
645 + "describe:match");
646 +
647 + strvec_pushf(args, "--match=%.*s", (int)arglen, argval);
648 + return 1;
649 + }
650 +
651 + if (match_atom_arg_value(*arg, "exclude", arg, &argval, &arglen)) {
652 + if (!arglen)
653 + return strbuf_addf_ret(err, -1,
654 + _("value expected %s="),
655 + "describe:exclude");
656 +
657 + strvec_pushf(args, "--exclude=%.*s", (int)arglen, argval);
658 + return 1;
659 + }
660 +
661 + return 0;
662 +}
663 +
664 +static int describe_atom_parser(struct ref_format *format UNUSED,
665 + struct used_atom *atom,
666 + const char *arg, struct strbuf *err)
667 +{
668 + struct strvec args = STRVEC_INIT;
669 +
670 + for (;;) {
671 + int found = 0;
672 + const char *bad_arg = arg;
673 +
674 + if (!arg || !*arg)
675 + break;
676 +
677 + found = describe_atom_option_parser(&args, &arg, err);
678 + if (found < 0)
679 + return found;
680 + if (!found)
681 + return err_bad_arg(err, "describe", bad_arg);
682 + }
683 + atom->u.describe_args = strvec_detach(&args);
684 + return 0;
685 +}
686 +
687 static int raw_atom_parser(struct ref_format *format UNUSED,
688 struct used_atom *atom,
689 const char *arg, struct strbuf *err)
@@ -802,6 +886,7 @@ static struct {
886 [ATOM_TAGGERDATE] = { "taggerdate", SOURCE_OBJ, FIELD_TIME },
887 [ATOM_CREATOR] = { "creator", SOURCE_OBJ },
888 [ATOM_CREATORDATE] = { "creatordate", SOURCE_OBJ, FIELD_TIME },
889 + [ATOM_DESCRIBE] = { "describe", SOURCE_OBJ, FIELD_STR, describe_atom_parser },
890 [ATOM_SUBJECT] = { "subject", SOURCE_OBJ, FIELD_STR, subject_atom_parser },
891 [ATOM_BODY] = { "body", SOURCE_OBJ, FIELD_STR, body_atom_parser },
892 [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
1793 }
1794 }
1795
1796 +static void grab_describe_values(struct atom_value *val, int deref,
1797 + struct object *obj)
1798 +{
1799 + struct commit *commit = (struct commit *)obj;
1800 + int i;
1801 +
1802 + for (i = 0; i < used_atom_cnt; i++) {
1803 + struct used_atom *atom = &used_atom[i];
1804 + enum atom_type type = atom->atom_type;
1805 + const char *name = atom->name;
1806 + struct atom_value *v = &val[i];
1807 +
1808 + struct child_process cmd = CHILD_PROCESS_INIT;
1809 + struct strbuf out = STRBUF_INIT;
1810 + struct strbuf err = STRBUF_INIT;
1811 +
1812 + if (type != ATOM_DESCRIBE)
1813 + continue;
1814 +
1815 + if (!!deref != (*name == '*'))
1816 + continue;
1817 +
1818 + cmd.git_cmd = 1;
1819 + strvec_push(&cmd.args, "describe");
1820 + strvec_pushv(&cmd.args, atom->u.describe_args);
1821 + strvec_push(&cmd.args, oid_to_hex(&commit->object.oid));
1822 + if (pipe_command(&cmd, NULL, 0, &out, 0, &err, 0) < 0) {
1823 + error(_("failed to run 'describe'"));
1824 + v->s = xstrdup("");
1825 + continue;
1826 + }
1827 + strbuf_rtrim(&out);
1828 + v->s = strbuf_detach(&out, NULL);
1829 +
1830 + strbuf_release(&err);
1831 + }
1832 +}
1833 +
1834 /* See grab_values */
1835 static void grab_sub_body_contents(struct atom_value *val, int deref, struct expand_data *data)
1836 {
@@ -1817,6 +1940,7 @@ static void grab_values(struct atom_value *val, int deref, struct object *obj, s
1940 grab_tag_values(val, deref, obj);
1941 grab_sub_body_contents(val, deref, data);
1942 grab_person("tagger", val, deref, buf);
1943 + grab_describe_values(val, deref, obj);
1944 break;
1945 case OBJ_COMMIT:
1946 grab_commit_values(val, deref, obj);
@@ -1824,6 +1948,7 @@ static void grab_values(struct atom_value *val, int deref, struct object *obj, s
1948 grab_person("author", val, deref, buf);
1949 grab_person("committer", val, deref, buf);
1950 grab_signature(val, deref, obj);
1951 + grab_describe_values(val, deref, obj);
1952 break;
1953 case OBJ_TREE:
1954 /* grab_tree_values(val, deref, obj, buf, sz); */
t/t6300-for-each-ref.sh
+138
@@ -562,6 +562,144 @@ test_expect_success 'color.ui=always does not override tty check' '
562 test_cmp expected.bare actual
563 '
564
565 +test_expect_success 'setup for describe atom tests' '
566 + git init -b master describe-repo &&
567 + (
568 + cd describe-repo &&
569 +
570 + test_commit --no-tag one &&
571 + git tag tagone &&
572 +
573 + test_commit --no-tag two &&
574 + git tag -a -m "tag two" tagtwo
575 + )
576 +'
577 +
578 +test_expect_success 'describe atom vs git describe' '
579 + (
580 + cd describe-repo &&
581 +
582 + git for-each-ref --format="%(objectname)" \
583 + refs/tags/ >obj &&
584 + while read hash
585 + do
586 + if desc=$(git describe $hash)
587 + then
588 + : >expect-contains-good
589 + else
590 + : >expect-contains-bad
591 + fi &&
592 + echo "$hash $desc" || return 1
593 + done <obj >expect &&
594 + test_path_exists expect-contains-good &&
595 + test_path_exists expect-contains-bad &&
596 +
597 + git for-each-ref --format="%(objectname) %(describe)" \
598 + refs/tags/ >actual 2>err &&
599 + test_cmp expect actual &&
600 + test_must_be_empty err
601 + )
602 +'
603 +
604 +test_expect_success 'describe:tags vs describe --tags' '
605 + (
606 + cd describe-repo &&
607 + git describe --tags >expect &&
608 + git for-each-ref --format="%(describe:tags)" \
609 + refs/heads/master >actual &&
610 + test_cmp expect actual
611 + )
612 +'
613 +
614 +test_expect_success 'describe:abbrev=... vs describe --abbrev=...' '
615 + (
616 + cd describe-repo &&
617 +
618 + # Case 1: We have commits between HEAD and the most
619 + # recent tag reachable from it
620 + test_commit --no-tag file &&
621 + git describe --abbrev=14 >expect &&
622 + git for-each-ref --format="%(describe:abbrev=14)" \
623 + refs/heads/master >actual &&
624 + test_cmp expect actual &&
625 +
626 + # Make sure the hash used is atleast 14 digits long
627 + sed -e "s/^.*-g\([0-9a-f]*\)$/\1/" <actual >hexpart &&
628 + test 15 -le $(wc -c <hexpart) &&
629 +
630 + # Case 2: We have a tag at HEAD, describe directly gives
631 + # the name of the tag
632 + git tag -a -m tagged tagname &&
633 + git describe --abbrev=14 >expect &&
634 + git for-each-ref --format="%(describe:abbrev=14)" \
635 + refs/heads/master >actual &&
636 + test_cmp expect actual &&
637 + test tagname = $(cat actual)
638 + )
639 +'
640 +
641 +test_expect_success 'describe:match=... vs describe --match ...' '
642 + (
643 + cd describe-repo &&
644 + git tag -a -m "tag foo" tag-foo &&
645 + git describe --match "*-foo" >expect &&
646 + git for-each-ref --format="%(describe:match="*-foo")" \
647 + refs/heads/master >actual &&
648 + test_cmp expect actual
649 + )
650 +'
651 +
652 +test_expect_success 'describe:exclude:... vs describe --exclude ...' '
653 + (
654 + cd describe-repo &&
655 + git tag -a -m "tag bar" tag-bar &&
656 + git describe --exclude "*-bar" >expect &&
657 + git for-each-ref --format="%(describe:exclude="*-bar")" \
658 + refs/heads/master >actual &&
659 + test_cmp expect actual
660 + )
661 +'
662 +
663 +test_expect_success 'deref with describe atom' '
664 + (
665 + cd describe-repo &&
666 + cat >expect <<-\EOF &&
667 +
668 + tagname
669 + tagname
670 + tagname
671 +
672 + tagtwo
673 + EOF
674 + git for-each-ref --format="%(*describe)" >actual &&
675 + test_cmp expect actual
676 + )
677 +'
678 +
679 +test_expect_success 'err on bad describe atom arg' '
680 + (
681 + cd describe-repo &&
682 +
683 + # The bad arg is the only arg passed to describe atom
684 + cat >expect <<-\EOF &&
685 + fatal: unrecognized %(describe) argument: baz
686 + EOF
687 + test_must_fail git for-each-ref --format="%(describe:baz)" \
688 + refs/heads/master 2>actual &&
689 + test_cmp expect actual &&
690 +
691 + # The bad arg is in the middle of the option string
692 + # passed to the describe atom
693 + cat >expect <<-\EOF &&
694 + fatal: unrecognized %(describe) argument: qux=1,abbrev=14
695 + EOF
696 + test_must_fail git for-each-ref \
697 + --format="%(describe:tags,qux=1,abbrev=14)" \
698 + ref/heads/master 2>actual &&
699 + test_cmp expect actual
700 + )
701 +'
702 +
703 cat >expected <<\EOF
704 heads/main
705 tags/main