diff mbox series

[v2,3/3] show-ref: add --symbolic-name option

Message ID a9e6644327a04f1d309eca812ace9c4159781353.1712597893.git.gitgitgadget@gmail.com (mailing list archive)
State New
Headers show
Series show-ref: add --symbolic-name option | expand

Commit Message

John Cai April 8, 2024, 5:38 p.m. UTC
From: John Cai <johncai86@gmail.com>

For reftable development, it would be handy to have a tool to provide
the direct value of any ref whether it be a symbolic ref or not.
Currently there is git-symbolic-ref, which only works for symbolic refs,
and git-rev-parse, which will resolve the ref. Let's teach show-ref a
--symbolic-name option that will cause git-show-ref(1) to print out the
value symbolic references points to.

Signed-off-by: John Cai <johncai86@gmail.com>
---
 Documentation/git-show-ref.txt | 21 ++++++++++++++++++-
 builtin/show-ref.c             | 38 ++++++++++++++++++++++++----------
 refs.c                         |  6 ++++++
 refs.h                         |  2 +-
 t/t1403-show-ref.sh            | 20 ++++++++++++++++++
 5 files changed, 74 insertions(+), 13 deletions(-)

Comments

Phillip Wood April 9, 2024, 3:25 p.m. UTC | #1
Hi John

On 08/04/2024 18:38, John Cai via GitGitGadget wrote:
 > +--symbolic-name::
 > +
 > +	Print out the value the reference points to without dereferencing. This
 > +	is useful to know the reference that a symbolic ref is pointing to.

It would be helpful to clarify that this prints the contents of the 
symbolic ref without dereferencing but also prints the OID after 
dereferencing the given ref.

 > +When using `--symbolic-name`, the output is in the format:
 > +
 > +-----------
 > +<oid> SP <ref> SP <symbolic-name>
 > +-----------
 > +
 > +For example,
 > +
 > 
+-----------------------------------------------------------------------------
 > +$ git show-ref --symbolic-name
 > +b75428bae1d090f60bdd4b67185f814bc8f0819d refs/heads/SYMBOLIC_REF 
ref:refs/heads/main

Do we really need the "ref:" prefix? It is not specified above and I 
think anyone calling this would have to remove the prefix before they 
could use the value.


> +test_expect_success '--symbolic-name with symbolic ref' '
> +	test_when_finished "git symbolic-ref -d refs/heads/SYMBOLIC_REF_A" &&
> +	commit_oid=$(git rev-parse refs/heads/$GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME) &&
> +	cat >expect <<-EOF &&
> +	$commit_oid refs/heads/SYMBOLIC_REF_A ref:refs/heads/$GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME
> +	EOF
> +	git symbolic-ref refs/heads/SYMBOLIC_REF_A refs/heads/$GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME &&
> +	git show-ref --symbolic-name SYMBOLIC_REF_A >actual &&
> +	test_cmp expect actual
> +'

I think it would be nice to see a test along the lines of

	git symbolic-ref refs/symref-c refs/heads/master
	git symbolic-ref refs/symref-b refs/symref-c &&
	git symbolic-ref refs/symref-a refs/symref-b &&
	git show-ref --symbolic-name refs/symref-a >actual &&
	cat >expect <<\EOF &&
	$(git show-ref -s --verify refs/heads/master) refs/heads/symref-a 
refs/heads/symref-b
	EOF
	test_cmp expect actual

to show what this command is expected to return when there is a chain of 
symbolic references.

Best Wishes

Phillip
Patrick Steinhardt April 10, 2024, 6:53 a.m. UTC | #2
On Mon, Apr 08, 2024 at 05:38:13PM +0000, John Cai via GitGitGadget wrote:
> From: John Cai <johncai86@gmail.com>
> 
> For reftable development, it would be handy to have a tool to provide
> the direct value of any ref whether it be a symbolic ref or not.
> Currently there is git-symbolic-ref, which only works for symbolic refs,
> and git-rev-parse, which will resolve the ref. Let's teach show-ref a
> --symbolic-name option that will cause git-show-ref(1) to print out the
> value symbolic references points to.

I think it was Peff who shared a way to achieve this without actually
introducing a new option via `git for-each-ref --format=`. Can we maybe
provide some benchmarks to demonstrate that this variant is preferable
over what's already possible?

Patrick

> Signed-off-by: John Cai <johncai86@gmail.com>
> ---
>  Documentation/git-show-ref.txt | 21 ++++++++++++++++++-
>  builtin/show-ref.c             | 38 ++++++++++++++++++++++++----------
>  refs.c                         |  6 ++++++
>  refs.h                         |  2 +-
>  t/t1403-show-ref.sh            | 20 ++++++++++++++++++
>  5 files changed, 74 insertions(+), 13 deletions(-)
> 
> diff --git a/Documentation/git-show-ref.txt b/Documentation/git-show-ref.txt
> index ba757470059..9627b34b37f 100644
> --- a/Documentation/git-show-ref.txt
> +++ b/Documentation/git-show-ref.txt
> @@ -10,7 +10,7 @@ SYNOPSIS
>  [verse]
>  'git show-ref' [--head] [-d | --dereference]
>  	     [-s | --hash[=<n>]] [--abbrev[=<n>]] [--tags]
> -	     [--heads] [--] [<pattern>...]
> +	     [--heads] [--symbolic-name] [--] [<pattern>...]
>  'git show-ref' --verify [-q | --quiet] [-d | --dereference]
>  	     [-s | --hash[=<n>]] [--abbrev[=<n>]]
>  	     [--] [<ref>...]
> @@ -58,6 +58,11 @@ OPTIONS
>  	Dereference tags into object IDs as well. They will be shown with `^{}`
>  	appended.
>  
> +--symbolic-name::
> +
> +	Print out the value the reference points to without dereferencing. This
> +	is useful to know the reference that a symbolic ref is pointing to.
> +
>  -s::
>  --hash[=<n>]::
>  
> @@ -146,6 +151,20 @@ $ git show-ref --heads --hash
>  ...
>  -----------------------------------------------------------------------------
>  
> +When using `--symbolic-name`, the output is in the format:
> +
> +-----------
> +<oid> SP <ref> SP <symbolic-name>
> +-----------
> +
> +For example,
> +
> +-----------------------------------------------------------------------------
> +$ git show-ref --symbolic-name
> +b75428bae1d090f60bdd4b67185f814bc8f0819d refs/heads/SYMBOLIC_REF ref:refs/heads/main
> +...
> +-----------------------------------------------------------------------------
> +
>  EXAMPLES
>  --------
>  
> diff --git a/builtin/show-ref.c b/builtin/show-ref.c
> index 1c15421e600..1d681505eac 100644
> --- a/builtin/show-ref.c
> +++ b/builtin/show-ref.c
> @@ -12,7 +12,7 @@
>  static const char * const show_ref_usage[] = {
>  	N_("git show-ref [--head] [-d | --dereference]\n"
>  	   "             [-s | --hash[=<n>]] [--abbrev[=<n>]] [--tags]\n"
> -	   "             [--heads] [--] [<pattern>...]"),
> +	   "             [--heads] [--symbolic-name] [--] [<pattern>...]"),
>  	N_("git show-ref --verify [-q | --quiet] [-d | --dereference]\n"
>  	   "             [-s | --hash[=<n>]] [--abbrev[=<n>]]\n"
>  	   "             [--] [<ref>...]"),
> @@ -26,10 +26,13 @@ struct show_one_options {
>  	int hash_only;
>  	int abbrev;
>  	int deref_tags;
> +	int symbolic_name;
>  };
>  
>  static void show_one(const struct show_one_options *opts,
> -		     const char *refname, const struct object_id *oid)
> +		     const char *refname,
> +		     const char *referent,
> +		     const struct object_id *oid, const int is_symref)
>  {
>  	const char *hex;
>  	struct object_id peeled;
> @@ -44,7 +47,9 @@ static void show_one(const struct show_one_options *opts,
>  	hex = repo_find_unique_abbrev(the_repository, oid, opts->abbrev);
>  	if (opts->hash_only)
>  		printf("%s\n", hex);
> -	else
> +	else if (opts->symbolic_name & is_symref) {
> +		printf("%s %s ref:%s\n", hex, refname, referent);
> +	} else
>  		printf("%s %s\n", hex, refname);
>  
>  	if (!opts->deref_tags)
> @@ -63,8 +68,11 @@ struct show_ref_data {
>  	int show_head;
>  };
>  
> -static int show_ref(const char *refname, const struct object_id *oid,
> -		    int flag UNUSED, void *cbdata)
> +static int show_ref_referent(struct repository *repo UNUSED,
> +			     const char *refname,
> +			     const char *referent,
> +			     const struct object_id *oid,
> +			     int flag, void *cbdata)
>  {
>  	struct show_ref_data *data = cbdata;
>  
> @@ -91,11 +99,17 @@ static int show_ref(const char *refname, const struct object_id *oid,
>  match:
>  	data->found_match++;
>  
> -	show_one(data->show_one_opts, refname, oid);
> +	show_one(data->show_one_opts, refname, referent, oid, flag & REF_ISSYMREF);
>  
>  	return 0;
>  }
>  
> +static int show_ref(const char *refname, const struct object_id *oid,
> +		    int flag, void *cbdata)
> +{
> +	return show_ref_referent(NULL, refname, NULL, oid, flag, cbdata);
> +}
> +
>  static int add_existing(const char *refname,
>  			const struct object_id *oid UNUSED,
>  			int flag UNUSED, void *cbdata)
> @@ -171,10 +185,11 @@ static int cmd_show_ref__verify(const struct show_one_options *show_one_opts,
>  
>  	while (*refs) {
>  		struct object_id oid;
> +		int flags = 0;
>  
>  		if ((starts_with(*refs, "refs/") || refname_is_safe(*refs)) &&
> -		    !read_ref(*refs, &oid)) {
> -			show_one(show_one_opts, *refs, &oid);
> +		    !read_ref_full(*refs, 0, &oid, &flags)) {
> +			show_one(show_one_opts, *refs, NULL, &oid, flags & REF_ISSYMREF);
>  		}
>  		else if (!show_one_opts->quiet)
>  			die("'%s' - not a valid ref", *refs);
> @@ -208,11 +223,11 @@ static int cmd_show_ref__patterns(const struct patterns_options *opts,
>  		head_ref(show_ref, &show_ref_data);
>  	if (opts->heads_only || opts->tags_only) {
>  		if (opts->heads_only)
> -			for_each_fullref_in("refs/heads/", show_ref, &show_ref_data);
> +			for_each_ref_all("refs/heads/", show_ref_referent, &show_ref_data);
>  		if (opts->tags_only)
> -			for_each_fullref_in("refs/tags/", show_ref, &show_ref_data);
> +			for_each_ref_all("refs/tags/", show_ref_referent, &show_ref_data);
>  	} else {
> -		for_each_ref(show_ref, &show_ref_data);
> +		for_each_ref_all("", show_ref_referent, &show_ref_data);
>  	}
>  	if (!show_ref_data.found_match)
>  		return 1;
> @@ -289,6 +304,7 @@ int cmd_show_ref(int argc, const char **argv, const char *prefix)
>  		OPT_BOOL(0, "tags", &patterns_opts.tags_only, N_("only show tags (can be combined with heads)")),
>  		OPT_BOOL(0, "heads", &patterns_opts.heads_only, N_("only show heads (can be combined with tags)")),
>  		OPT_BOOL(0, "exists", &exists, N_("check for reference existence without resolving")),
> +		OPT_BOOL(0, "symbolic-name", &show_one_opts.symbolic_name, N_("print out symbolic reference values")),
>  		OPT_BOOL(0, "verify", &verify, N_("stricter reference checking, "
>  			    "requires exact ref path")),
>  		OPT_HIDDEN_BOOL('h', NULL, &patterns_opts.show_head,
> diff --git a/refs.c b/refs.c
> index 77ae38ea214..9488ad9594d 100644
> --- a/refs.c
> +++ b/refs.c
> @@ -1734,6 +1734,12 @@ int for_each_replace_ref(struct repository *r, each_repo_ref_fn fn, void *cb_dat
>  				    DO_FOR_EACH_INCLUDE_BROKEN, cb_data);
>  }
>  
> +int for_each_ref_all(const char *prefix, each_repo_ref_fn fn, void *cb_data)
> +{
> +	return do_for_each_repo_ref(the_repository, prefix, fn, 0,
> +				    0, cb_data);
> +}
> +
>  int for_each_namespaced_ref(const char **exclude_patterns,
>  			    each_ref_fn fn, void *cb_data)
>  {
> diff --git a/refs.h b/refs.h
> index 23e5aaba2e9..54b459375be 100644
> --- a/refs.h
> +++ b/refs.h
> @@ -337,7 +337,6 @@ int refs_for_each_branch_ref(struct ref_store *refs,
>  			     each_ref_fn fn, void *cb_data);
>  int refs_for_each_remote_ref(struct ref_store *refs,
>  			     each_ref_fn fn, void *cb_data);
> -
>  /* just iterates the head ref. */
>  int head_ref(each_ref_fn fn, void *cb_data);
>  
> @@ -381,6 +380,7 @@ int for_each_tag_ref(each_ref_fn fn, void *cb_data);
>  int for_each_branch_ref(each_ref_fn fn, void *cb_data);
>  int for_each_remote_ref(each_ref_fn fn, void *cb_data);
>  int for_each_replace_ref(struct repository *r, each_repo_ref_fn fn, void *cb_data);
> +int for_each_ref_all(const char *prefix, each_repo_ref_fn fn, void *cb_data);
>  
>  /* iterates all refs that match the specified glob pattern. */
>  int for_each_glob_ref(each_ref_fn fn, const char *pattern, void *cb_data);
> diff --git a/t/t1403-show-ref.sh b/t/t1403-show-ref.sh
> index 33fb7a38fff..0aebe709dca 100755
> --- a/t/t1403-show-ref.sh
> +++ b/t/t1403-show-ref.sh
> @@ -286,4 +286,24 @@ test_expect_success '--exists with existing special ref' '
>  	git show-ref --exists FETCH_HEAD
>  '
>  
> +test_expect_success '--symbolic-name with a non symbolic ref' '
> +	commit_oid=$(git rev-parse refs/heads/$GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME) &&
> +	cat >expect <<-EOF &&
> +	$commit_oid refs/heads/$GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME
> +	EOF
> +	git show-ref --symbolic-name refs/heads/$GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME >actual &&
> +	test_cmp expect actual
> +'
> +
> +test_expect_success '--symbolic-name with symbolic ref' '
> +	test_when_finished "git symbolic-ref -d refs/heads/SYMBOLIC_REF_A" &&
> +	commit_oid=$(git rev-parse refs/heads/$GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME) &&
> +	cat >expect <<-EOF &&
> +	$commit_oid refs/heads/SYMBOLIC_REF_A ref:refs/heads/$GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME
> +	EOF
> +	git symbolic-ref refs/heads/SYMBOLIC_REF_A refs/heads/$GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME &&
> +	git show-ref --symbolic-name SYMBOLIC_REF_A >actual &&
> +	test_cmp expect actual
> +'
> +
>  test_done
> -- 
> gitgitgadget
Junio C Hamano April 10, 2024, 3:27 p.m. UTC | #3
Patrick Steinhardt <ps@pks.im> writes:

> On Mon, Apr 08, 2024 at 05:38:13PM +0000, John Cai via GitGitGadget wrote:
>> From: John Cai <johncai86@gmail.com>
>> 
>> For reftable development, it would be handy to have a tool to provide
>> the direct value of any ref whether it be a symbolic ref or not.
>> Currently there is git-symbolic-ref, which only works for symbolic refs,
>> and git-rev-parse, which will resolve the ref. Let's teach show-ref a
>> --symbolic-name option that will cause git-show-ref(1) to print out the
>> value symbolic references points to.
>
> I think it was Peff who shared a way to achieve this without actually
> introducing a new option via `git for-each-ref --format=`. Can we maybe
> provide some benchmarks to demonstrate that this variant is preferable
> over what's already possible?

Yes, I recall that discussion, and in fact I was a bit surprised to
see that this iteration still went to the show-ref route.

Thanks for bringing it up.
John Cai April 11, 2024, 7:57 p.m. UTC | #4
Hi Phillip,

On 9 Apr 2024, at 11:25, Phillip Wood wrote:

> Hi John
>
> On 08/04/2024 18:38, John Cai via GitGitGadget wrote:
>> +--symbolic-name::
>> +
>> +	Print out the value the reference points to without dereferencing. This
>> +	is useful to know the reference that a symbolic ref is pointing to.
>
> It would be helpful to clarify that this prints the contents of the symbolic ref without dereferencing but also prints the OID after dereferencing the given ref.

Thanks, will clarify in the next version.

>
>> +When using `--symbolic-name`, the output is in the format:
>> +
>> +-----------
>> +<oid> SP <ref> SP <symbolic-name>
>> +-----------
>> +
>> +For example,
>> +
>> +-----------------------------------------------------------------------------
>> +$ git show-ref --symbolic-name
>> +b75428bae1d090f60bdd4b67185f814bc8f0819d refs/heads/SYMBOLIC_REF ref:refs/heads/main
>
> Do we really need the "ref:" prefix? It is not specified above and I think anyone calling this would have to remove the prefix before they could use the value.

I can see how it would be more ergonimic to just have the value without the
"ref: " prefix. I kept it because that's how the refs are represented on disk
and git-symbolic-ref prints them out with the "ref: " prefix.

I don't have a strong preference, but I lean a bit towards keeping it consistent
with the output of other commands.

>
>
>> +test_expect_success '--symbolic-name with symbolic ref' '
>> +	test_when_finished "git symbolic-ref -d refs/heads/SYMBOLIC_REF_A" &&
>> +	commit_oid=$(git rev-parse refs/heads/$GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME) &&
>> +	cat >expect <<-EOF &&
>> +	$commit_oid refs/heads/SYMBOLIC_REF_A ref:refs/heads/$GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME
>> +	EOF
>> +	git symbolic-ref refs/heads/SYMBOLIC_REF_A refs/heads/$GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME &&
>> +	git show-ref --symbolic-name SYMBOLIC_REF_A >actual &&
>> +	test_cmp expect actual
>> +'
>
> I think it would be nice to see a test along the lines of
>
> 	git symbolic-ref refs/symref-c refs/heads/master
> 	git symbolic-ref refs/symref-b refs/symref-c &&
> 	git symbolic-ref refs/symref-a refs/symref-b &&
> 	git show-ref --symbolic-name refs/symref-a >actual &&
> 	cat >expect <<\EOF &&
> 	$(git show-ref -s --verify refs/heads/master) refs/heads/symref-a refs/heads/symref-b
> 	EOF
> 	test_cmp expect actual
>
> to show what this command is expected to return when there is a chain of symbolic references.

good point, will add this in the next series.

>
> Best Wishes
>
> Phillip

thanks for the review!
John
Phillip Wood April 12, 2024, 9:37 a.m. UTC | #5
Hi John

On 11/04/2024 20:57, John Cai wrote:
> On 9 Apr 2024, at 11:25, Phillip Wood wrote:
>>> +When using `--symbolic-name`, the output is in the format:
>>> +
>>> +-----------
>>> +<oid> SP <ref> SP <symbolic-name>
>>> +-----------
>>> +
>>> +For example,
>>> +
>>> +-----------------------------------------------------------------------------
>>> +$ git show-ref --symbolic-name
>>> +b75428bae1d090f60bdd4b67185f814bc8f0819d refs/heads/SYMBOLIC_REF ref:refs/heads/main
>>
>> Do we really need the "ref:" prefix? It is not specified above and I think anyone calling this would have to remove the prefix before they could use the value.
> 
> I can see how it would be more ergonimic to just have the value without the
> "ref: " prefix. I kept it because that's how the refs are represented on disk
> and git-symbolic-ref prints them out with the "ref: " prefix.
> 
> I don't have a strong preference, but I lean a bit towards keeping it consistent
> with the output of other commands.

I agree it is a good idea to keep things consistent, and dropping the 
"ref:" prefix is consistent with other commands:

$ git symbolic-ref HEAD
refs/heads/rebase-fix-signoff

Best Wishes

Phillip

> 
>>
>>
>>> +test_expect_success '--symbolic-name with symbolic ref' '
>>> +	test_when_finished "git symbolic-ref -d refs/heads/SYMBOLIC_REF_A" &&
>>> +	commit_oid=$(git rev-parse refs/heads/$GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME) &&
>>> +	cat >expect <<-EOF &&
>>> +	$commit_oid refs/heads/SYMBOLIC_REF_A ref:refs/heads/$GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME
>>> +	EOF
>>> +	git symbolic-ref refs/heads/SYMBOLIC_REF_A refs/heads/$GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME &&
>>> +	git show-ref --symbolic-name SYMBOLIC_REF_A >actual &&
>>> +	test_cmp expect actual
>>> +'
>>
>> I think it would be nice to see a test along the lines of
>>
>> 	git symbolic-ref refs/symref-c refs/heads/master
>> 	git symbolic-ref refs/symref-b refs/symref-c &&
>> 	git symbolic-ref refs/symref-a refs/symref-b &&
>> 	git show-ref --symbolic-name refs/symref-a >actual &&
>> 	cat >expect <<\EOF &&
>> 	$(git show-ref -s --verify refs/heads/master) refs/heads/symref-a refs/heads/symref-b
>> 	EOF
>> 	test_cmp expect actual
>>
>> to show what this command is expected to return when there is a chain of symbolic references.
> 
> good point, will add this in the next series.
> 
>>
>> Best Wishes
>>
>> Phillip
> 
> thanks for the review!
> John
John Cai April 12, 2024, 3:23 p.m. UTC | #6
Hi Patrick,

On 10 Apr 2024, at 2:53, Patrick Steinhardt wrote:

> On Mon, Apr 08, 2024 at 05:38:13PM +0000, John Cai via GitGitGadget wrote:
>> From: John Cai <johncai86@gmail.com>
>>
>> For reftable development, it would be handy to have a tool to provide
>> the direct value of any ref whether it be a symbolic ref or not.
>> Currently there is git-symbolic-ref, which only works for symbolic refs,
>> and git-rev-parse, which will resolve the ref. Let's teach show-ref a
>> --symbolic-name option that will cause git-show-ref(1) to print out the
>> value symbolic references points to.
>
> I think it was Peff who shared a way to achieve this without actually
> introducing a new option via `git for-each-ref --format=`. Can we maybe
> provide some benchmarks to demonstrate that this variant is preferable
> over what's already possible?

Yes, it would be better to not introduce a new option. I've done a quick
benchmark including my changes to add the unresolved symref to the iterator, and
some changes to integrate this into ref-filter.c. Here are the results:

$ hyperfine --warmup 5 "git for-each-ref \
--format='%(refname) %(objectname) %(symref)'" "./git for-each-ref \
--format='%(refname) %(objectname) %(symref)'"

Benchmark 1: git for-each-ref --format='%(refname) %(objectname) %(symref)'
  Time (mean ± σ):     213.5 ms ±   9.9 ms    [User: 7.5 ms, System: 14.3 ms]
  Range (min … max):   202.7 ms … 236.3 ms    14 runs

Benchmark 2: ./git for-each-ref --format='%(refname) %(objectname) %(symref)'
  Time (mean ± σ):      10.8 ms ±   1.3 ms    [User: 4.4 ms, System: 6.2 ms]
  Range (min … max):     9.5 ms …  17.5 ms    189 runs

Summary
  ./git for-each-ref --format='%(refname) %(objectname) %(symref)' ran
   19.72 ± 2.62 times faster than git for-each-ref --format='%(refname) %(objectname) %(symref)'

It looks like this gives us a nice speedup. I will send up a new version that improves git-for-each-ref
instead.

thanks
John

>
> Patrick
>
>> Signed-off-by: John Cai <johncai86@gmail.com>
>> ---
>>  Documentation/git-show-ref.txt | 21 ++++++++++++++++++-
>>  builtin/show-ref.c             | 38 ++++++++++++++++++++++++----------
>>  refs.c                         |  6 ++++++
>>  refs.h                         |  2 +-
>>  t/t1403-show-ref.sh            | 20 ++++++++++++++++++
>>  5 files changed, 74 insertions(+), 13 deletions(-)
>>
>> diff --git a/Documentation/git-show-ref.txt b/Documentation/git-show-ref.txt
>> index ba757470059..9627b34b37f 100644
>> --- a/Documentation/git-show-ref.txt
>> +++ b/Documentation/git-show-ref.txt
>> @@ -10,7 +10,7 @@ SYNOPSIS
>>  [verse]
>>  'git show-ref' [--head] [-d | --dereference]
>>  	     [-s | --hash[=<n>]] [--abbrev[=<n>]] [--tags]
>> -	     [--heads] [--] [<pattern>...]
>> +	     [--heads] [--symbolic-name] [--] [<pattern>...]
>>  'git show-ref' --verify [-q | --quiet] [-d | --dereference]
>>  	     [-s | --hash[=<n>]] [--abbrev[=<n>]]
>>  	     [--] [<ref>...]
>> @@ -58,6 +58,11 @@ OPTIONS
>>  	Dereference tags into object IDs as well. They will be shown with `^{}`
>>  	appended.
>>
>> +--symbolic-name::
>> +
>> +	Print out the value the reference points to without dereferencing. This
>> +	is useful to know the reference that a symbolic ref is pointing to.
>> +
>>  -s::
>>  --hash[=<n>]::
>>
>> @@ -146,6 +151,20 @@ $ git show-ref --heads --hash
>>  ...
>>  -----------------------------------------------------------------------------
>>
>> +When using `--symbolic-name`, the output is in the format:
>> +
>> +-----------
>> +<oid> SP <ref> SP <symbolic-name>
>> +-----------
>> +
>> +For example,
>> +
>> +-----------------------------------------------------------------------------
>> +$ git show-ref --symbolic-name
>> +b75428bae1d090f60bdd4b67185f814bc8f0819d refs/heads/SYMBOLIC_REF ref:refs/heads/main
>> +...
>> +-----------------------------------------------------------------------------
>> +
>>  EXAMPLES
>>  --------
>>
>> diff --git a/builtin/show-ref.c b/builtin/show-ref.c
>> index 1c15421e600..1d681505eac 100644
>> --- a/builtin/show-ref.c
>> +++ b/builtin/show-ref.c
>> @@ -12,7 +12,7 @@
>>  static const char * const show_ref_usage[] = {
>>  	N_("git show-ref [--head] [-d | --dereference]\n"
>>  	   "             [-s | --hash[=<n>]] [--abbrev[=<n>]] [--tags]\n"
>> -	   "             [--heads] [--] [<pattern>...]"),
>> +	   "             [--heads] [--symbolic-name] [--] [<pattern>...]"),
>>  	N_("git show-ref --verify [-q | --quiet] [-d | --dereference]\n"
>>  	   "             [-s | --hash[=<n>]] [--abbrev[=<n>]]\n"
>>  	   "             [--] [<ref>...]"),
>> @@ -26,10 +26,13 @@ struct show_one_options {
>>  	int hash_only;
>>  	int abbrev;
>>  	int deref_tags;
>> +	int symbolic_name;
>>  };
>>
>>  static void show_one(const struct show_one_options *opts,
>> -		     const char *refname, const struct object_id *oid)
>> +		     const char *refname,
>> +		     const char *referent,
>> +		     const struct object_id *oid, const int is_symref)
>>  {
>>  	const char *hex;
>>  	struct object_id peeled;
>> @@ -44,7 +47,9 @@ static void show_one(const struct show_one_options *opts,
>>  	hex = repo_find_unique_abbrev(the_repository, oid, opts->abbrev);
>>  	if (opts->hash_only)
>>  		printf("%s\n", hex);
>> -	else
>> +	else if (opts->symbolic_name & is_symref) {
>> +		printf("%s %s ref:%s\n", hex, refname, referent);
>> +	} else
>>  		printf("%s %s\n", hex, refname);
>>
>>  	if (!opts->deref_tags)
>> @@ -63,8 +68,11 @@ struct show_ref_data {
>>  	int show_head;
>>  };
>>
>> -static int show_ref(const char *refname, const struct object_id *oid,
>> -		    int flag UNUSED, void *cbdata)
>> +static int show_ref_referent(struct repository *repo UNUSED,
>> +			     const char *refname,
>> +			     const char *referent,
>> +			     const struct object_id *oid,
>> +			     int flag, void *cbdata)
>>  {
>>  	struct show_ref_data *data = cbdata;
>>
>> @@ -91,11 +99,17 @@ static int show_ref(const char *refname, const struct object_id *oid,
>>  match:
>>  	data->found_match++;
>>
>> -	show_one(data->show_one_opts, refname, oid);
>> +	show_one(data->show_one_opts, refname, referent, oid, flag & REF_ISSYMREF);
>>
>>  	return 0;
>>  }
>>
>> +static int show_ref(const char *refname, const struct object_id *oid,
>> +		    int flag, void *cbdata)
>> +{
>> +	return show_ref_referent(NULL, refname, NULL, oid, flag, cbdata);
>> +}
>> +
>>  static int add_existing(const char *refname,
>>  			const struct object_id *oid UNUSED,
>>  			int flag UNUSED, void *cbdata)
>> @@ -171,10 +185,11 @@ static int cmd_show_ref__verify(const struct show_one_options *show_one_opts,
>>
>>  	while (*refs) {
>>  		struct object_id oid;
>> +		int flags = 0;
>>
>>  		if ((starts_with(*refs, "refs/") || refname_is_safe(*refs)) &&
>> -		    !read_ref(*refs, &oid)) {
>> -			show_one(show_one_opts, *refs, &oid);
>> +		    !read_ref_full(*refs, 0, &oid, &flags)) {
>> +			show_one(show_one_opts, *refs, NULL, &oid, flags & REF_ISSYMREF);
>>  		}
>>  		else if (!show_one_opts->quiet)
>>  			die("'%s' - not a valid ref", *refs);
>> @@ -208,11 +223,11 @@ static int cmd_show_ref__patterns(const struct patterns_options *opts,
>>  		head_ref(show_ref, &show_ref_data);
>>  	if (opts->heads_only || opts->tags_only) {
>>  		if (opts->heads_only)
>> -			for_each_fullref_in("refs/heads/", show_ref, &show_ref_data);
>> +			for_each_ref_all("refs/heads/", show_ref_referent, &show_ref_data);
>>  		if (opts->tags_only)
>> -			for_each_fullref_in("refs/tags/", show_ref, &show_ref_data);
>> +			for_each_ref_all("refs/tags/", show_ref_referent, &show_ref_data);
>>  	} else {
>> -		for_each_ref(show_ref, &show_ref_data);
>> +		for_each_ref_all("", show_ref_referent, &show_ref_data);
>>  	}
>>  	if (!show_ref_data.found_match)
>>  		return 1;
>> @@ -289,6 +304,7 @@ int cmd_show_ref(int argc, const char **argv, const char *prefix)
>>  		OPT_BOOL(0, "tags", &patterns_opts.tags_only, N_("only show tags (can be combined with heads)")),
>>  		OPT_BOOL(0, "heads", &patterns_opts.heads_only, N_("only show heads (can be combined with tags)")),
>>  		OPT_BOOL(0, "exists", &exists, N_("check for reference existence without resolving")),
>> +		OPT_BOOL(0, "symbolic-name", &show_one_opts.symbolic_name, N_("print out symbolic reference values")),
>>  		OPT_BOOL(0, "verify", &verify, N_("stricter reference checking, "
>>  			    "requires exact ref path")),
>>  		OPT_HIDDEN_BOOL('h', NULL, &patterns_opts.show_head,
>> diff --git a/refs.c b/refs.c
>> index 77ae38ea214..9488ad9594d 100644
>> --- a/refs.c
>> +++ b/refs.c
>> @@ -1734,6 +1734,12 @@ int for_each_replace_ref(struct repository *r, each_repo_ref_fn fn, void *cb_dat
>>  				    DO_FOR_EACH_INCLUDE_BROKEN, cb_data);
>>  }
>>
>> +int for_each_ref_all(const char *prefix, each_repo_ref_fn fn, void *cb_data)
>> +{
>> +	return do_for_each_repo_ref(the_repository, prefix, fn, 0,
>> +				    0, cb_data);
>> +}
>> +
>>  int for_each_namespaced_ref(const char **exclude_patterns,
>>  			    each_ref_fn fn, void *cb_data)
>>  {
>> diff --git a/refs.h b/refs.h
>> index 23e5aaba2e9..54b459375be 100644
>> --- a/refs.h
>> +++ b/refs.h
>> @@ -337,7 +337,6 @@ int refs_for_each_branch_ref(struct ref_store *refs,
>>  			     each_ref_fn fn, void *cb_data);
>>  int refs_for_each_remote_ref(struct ref_store *refs,
>>  			     each_ref_fn fn, void *cb_data);
>> -
>>  /* just iterates the head ref. */
>>  int head_ref(each_ref_fn fn, void *cb_data);
>>
>> @@ -381,6 +380,7 @@ int for_each_tag_ref(each_ref_fn fn, void *cb_data);
>>  int for_each_branch_ref(each_ref_fn fn, void *cb_data);
>>  int for_each_remote_ref(each_ref_fn fn, void *cb_data);
>>  int for_each_replace_ref(struct repository *r, each_repo_ref_fn fn, void *cb_data);
>> +int for_each_ref_all(const char *prefix, each_repo_ref_fn fn, void *cb_data);
>>
>>  /* iterates all refs that match the specified glob pattern. */
>>  int for_each_glob_ref(each_ref_fn fn, const char *pattern, void *cb_data);
>> diff --git a/t/t1403-show-ref.sh b/t/t1403-show-ref.sh
>> index 33fb7a38fff..0aebe709dca 100755
>> --- a/t/t1403-show-ref.sh
>> +++ b/t/t1403-show-ref.sh
>> @@ -286,4 +286,24 @@ test_expect_success '--exists with existing special ref' '
>>  	git show-ref --exists FETCH_HEAD
>>  '
>>
>> +test_expect_success '--symbolic-name with a non symbolic ref' '
>> +	commit_oid=$(git rev-parse refs/heads/$GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME) &&
>> +	cat >expect <<-EOF &&
>> +	$commit_oid refs/heads/$GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME
>> +	EOF
>> +	git show-ref --symbolic-name refs/heads/$GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME >actual &&
>> +	test_cmp expect actual
>> +'
>> +
>> +test_expect_success '--symbolic-name with symbolic ref' '
>> +	test_when_finished "git symbolic-ref -d refs/heads/SYMBOLIC_REF_A" &&
>> +	commit_oid=$(git rev-parse refs/heads/$GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME) &&
>> +	cat >expect <<-EOF &&
>> +	$commit_oid refs/heads/SYMBOLIC_REF_A ref:refs/heads/$GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME
>> +	EOF
>> +	git symbolic-ref refs/heads/SYMBOLIC_REF_A refs/heads/$GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME &&
>> +	git show-ref --symbolic-name SYMBOLIC_REF_A >actual &&
>> +	test_cmp expect actual
>> +'
>> +
>>  test_done
>> -- 
>> gitgitgadget
diff mbox series

Patch

diff --git a/Documentation/git-show-ref.txt b/Documentation/git-show-ref.txt
index ba757470059..9627b34b37f 100644
--- a/Documentation/git-show-ref.txt
+++ b/Documentation/git-show-ref.txt
@@ -10,7 +10,7 @@  SYNOPSIS
 [verse]
 'git show-ref' [--head] [-d | --dereference]
 	     [-s | --hash[=<n>]] [--abbrev[=<n>]] [--tags]
-	     [--heads] [--] [<pattern>...]
+	     [--heads] [--symbolic-name] [--] [<pattern>...]
 'git show-ref' --verify [-q | --quiet] [-d | --dereference]
 	     [-s | --hash[=<n>]] [--abbrev[=<n>]]
 	     [--] [<ref>...]
@@ -58,6 +58,11 @@  OPTIONS
 	Dereference tags into object IDs as well. They will be shown with `^{}`
 	appended.
 
+--symbolic-name::
+
+	Print out the value the reference points to without dereferencing. This
+	is useful to know the reference that a symbolic ref is pointing to.
+
 -s::
 --hash[=<n>]::
 
@@ -146,6 +151,20 @@  $ git show-ref --heads --hash
 ...
 -----------------------------------------------------------------------------
 
+When using `--symbolic-name`, the output is in the format:
+
+-----------
+<oid> SP <ref> SP <symbolic-name>
+-----------
+
+For example,
+
+-----------------------------------------------------------------------------
+$ git show-ref --symbolic-name
+b75428bae1d090f60bdd4b67185f814bc8f0819d refs/heads/SYMBOLIC_REF ref:refs/heads/main
+...
+-----------------------------------------------------------------------------
+
 EXAMPLES
 --------
 
diff --git a/builtin/show-ref.c b/builtin/show-ref.c
index 1c15421e600..1d681505eac 100644
--- a/builtin/show-ref.c
+++ b/builtin/show-ref.c
@@ -12,7 +12,7 @@ 
 static const char * const show_ref_usage[] = {
 	N_("git show-ref [--head] [-d | --dereference]\n"
 	   "             [-s | --hash[=<n>]] [--abbrev[=<n>]] [--tags]\n"
-	   "             [--heads] [--] [<pattern>...]"),
+	   "             [--heads] [--symbolic-name] [--] [<pattern>...]"),
 	N_("git show-ref --verify [-q | --quiet] [-d | --dereference]\n"
 	   "             [-s | --hash[=<n>]] [--abbrev[=<n>]]\n"
 	   "             [--] [<ref>...]"),
@@ -26,10 +26,13 @@  struct show_one_options {
 	int hash_only;
 	int abbrev;
 	int deref_tags;
+	int symbolic_name;
 };
 
 static void show_one(const struct show_one_options *opts,
-		     const char *refname, const struct object_id *oid)
+		     const char *refname,
+		     const char *referent,
+		     const struct object_id *oid, const int is_symref)
 {
 	const char *hex;
 	struct object_id peeled;
@@ -44,7 +47,9 @@  static void show_one(const struct show_one_options *opts,
 	hex = repo_find_unique_abbrev(the_repository, oid, opts->abbrev);
 	if (opts->hash_only)
 		printf("%s\n", hex);
-	else
+	else if (opts->symbolic_name & is_symref) {
+		printf("%s %s ref:%s\n", hex, refname, referent);
+	} else
 		printf("%s %s\n", hex, refname);
 
 	if (!opts->deref_tags)
@@ -63,8 +68,11 @@  struct show_ref_data {
 	int show_head;
 };
 
-static int show_ref(const char *refname, const struct object_id *oid,
-		    int flag UNUSED, void *cbdata)
+static int show_ref_referent(struct repository *repo UNUSED,
+			     const char *refname,
+			     const char *referent,
+			     const struct object_id *oid,
+			     int flag, void *cbdata)
 {
 	struct show_ref_data *data = cbdata;
 
@@ -91,11 +99,17 @@  static int show_ref(const char *refname, const struct object_id *oid,
 match:
 	data->found_match++;
 
-	show_one(data->show_one_opts, refname, oid);
+	show_one(data->show_one_opts, refname, referent, oid, flag & REF_ISSYMREF);
 
 	return 0;
 }
 
+static int show_ref(const char *refname, const struct object_id *oid,
+		    int flag, void *cbdata)
+{
+	return show_ref_referent(NULL, refname, NULL, oid, flag, cbdata);
+}
+
 static int add_existing(const char *refname,
 			const struct object_id *oid UNUSED,
 			int flag UNUSED, void *cbdata)
@@ -171,10 +185,11 @@  static int cmd_show_ref__verify(const struct show_one_options *show_one_opts,
 
 	while (*refs) {
 		struct object_id oid;
+		int flags = 0;
 
 		if ((starts_with(*refs, "refs/") || refname_is_safe(*refs)) &&
-		    !read_ref(*refs, &oid)) {
-			show_one(show_one_opts, *refs, &oid);
+		    !read_ref_full(*refs, 0, &oid, &flags)) {
+			show_one(show_one_opts, *refs, NULL, &oid, flags & REF_ISSYMREF);
 		}
 		else if (!show_one_opts->quiet)
 			die("'%s' - not a valid ref", *refs);
@@ -208,11 +223,11 @@  static int cmd_show_ref__patterns(const struct patterns_options *opts,
 		head_ref(show_ref, &show_ref_data);
 	if (opts->heads_only || opts->tags_only) {
 		if (opts->heads_only)
-			for_each_fullref_in("refs/heads/", show_ref, &show_ref_data);
+			for_each_ref_all("refs/heads/", show_ref_referent, &show_ref_data);
 		if (opts->tags_only)
-			for_each_fullref_in("refs/tags/", show_ref, &show_ref_data);
+			for_each_ref_all("refs/tags/", show_ref_referent, &show_ref_data);
 	} else {
-		for_each_ref(show_ref, &show_ref_data);
+		for_each_ref_all("", show_ref_referent, &show_ref_data);
 	}
 	if (!show_ref_data.found_match)
 		return 1;
@@ -289,6 +304,7 @@  int cmd_show_ref(int argc, const char **argv, const char *prefix)
 		OPT_BOOL(0, "tags", &patterns_opts.tags_only, N_("only show tags (can be combined with heads)")),
 		OPT_BOOL(0, "heads", &patterns_opts.heads_only, N_("only show heads (can be combined with tags)")),
 		OPT_BOOL(0, "exists", &exists, N_("check for reference existence without resolving")),
+		OPT_BOOL(0, "symbolic-name", &show_one_opts.symbolic_name, N_("print out symbolic reference values")),
 		OPT_BOOL(0, "verify", &verify, N_("stricter reference checking, "
 			    "requires exact ref path")),
 		OPT_HIDDEN_BOOL('h', NULL, &patterns_opts.show_head,
diff --git a/refs.c b/refs.c
index 77ae38ea214..9488ad9594d 100644
--- a/refs.c
+++ b/refs.c
@@ -1734,6 +1734,12 @@  int for_each_replace_ref(struct repository *r, each_repo_ref_fn fn, void *cb_dat
 				    DO_FOR_EACH_INCLUDE_BROKEN, cb_data);
 }
 
+int for_each_ref_all(const char *prefix, each_repo_ref_fn fn, void *cb_data)
+{
+	return do_for_each_repo_ref(the_repository, prefix, fn, 0,
+				    0, cb_data);
+}
+
 int for_each_namespaced_ref(const char **exclude_patterns,
 			    each_ref_fn fn, void *cb_data)
 {
diff --git a/refs.h b/refs.h
index 23e5aaba2e9..54b459375be 100644
--- a/refs.h
+++ b/refs.h
@@ -337,7 +337,6 @@  int refs_for_each_branch_ref(struct ref_store *refs,
 			     each_ref_fn fn, void *cb_data);
 int refs_for_each_remote_ref(struct ref_store *refs,
 			     each_ref_fn fn, void *cb_data);
-
 /* just iterates the head ref. */
 int head_ref(each_ref_fn fn, void *cb_data);
 
@@ -381,6 +380,7 @@  int for_each_tag_ref(each_ref_fn fn, void *cb_data);
 int for_each_branch_ref(each_ref_fn fn, void *cb_data);
 int for_each_remote_ref(each_ref_fn fn, void *cb_data);
 int for_each_replace_ref(struct repository *r, each_repo_ref_fn fn, void *cb_data);
+int for_each_ref_all(const char *prefix, each_repo_ref_fn fn, void *cb_data);
 
 /* iterates all refs that match the specified glob pattern. */
 int for_each_glob_ref(each_ref_fn fn, const char *pattern, void *cb_data);
diff --git a/t/t1403-show-ref.sh b/t/t1403-show-ref.sh
index 33fb7a38fff..0aebe709dca 100755
--- a/t/t1403-show-ref.sh
+++ b/t/t1403-show-ref.sh
@@ -286,4 +286,24 @@  test_expect_success '--exists with existing special ref' '
 	git show-ref --exists FETCH_HEAD
 '
 
+test_expect_success '--symbolic-name with a non symbolic ref' '
+	commit_oid=$(git rev-parse refs/heads/$GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME) &&
+	cat >expect <<-EOF &&
+	$commit_oid refs/heads/$GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME
+	EOF
+	git show-ref --symbolic-name refs/heads/$GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success '--symbolic-name with symbolic ref' '
+	test_when_finished "git symbolic-ref -d refs/heads/SYMBOLIC_REF_A" &&
+	commit_oid=$(git rev-parse refs/heads/$GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME) &&
+	cat >expect <<-EOF &&
+	$commit_oid refs/heads/SYMBOLIC_REF_A ref:refs/heads/$GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME
+	EOF
+	git symbolic-ref refs/heads/SYMBOLIC_REF_A refs/heads/$GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME &&
+	git show-ref --symbolic-name SYMBOLIC_REF_A >actual &&
+	test_cmp expect actual
+'
+
 test_done