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