diff mbox series

[1/2] show-ref --verify: accept pseudorefs

Message ID 4dedc5704c3af6ab4ec8cc7503dc826480775b8e.1707324277.git.gitgitgadget@gmail.com (mailing list archive)
State Accepted
Commit 1dbe40156307763255d96653cc853ef4ff841920
Headers show
Series show-ref --verify: accept psuedorefs | expand

Commit Message

Phillip Wood Feb. 7, 2024, 4:44 p.m. UTC
From: Phillip Wood <phillip.wood@dunelm.org.uk>

"git show-ref --verify" is useful for scripts that want to look up a
fully qualified refname without falling back to the DWIM rules used by
"git rev-parse" rules when the ref does not exist. Currently it will
only accept "HEAD" or a refname beginning with "refs/". Running

    git show-ref --verify CHERRY_PICK_HEAD

will always result in

    fatal: 'CHERRY_PICK_HEAD' - not a valid ref

even when CHERRY_PICK_HEAD exists. By calling refname_is_safe() instead
of comparing the refname to "HEAD" we can accept all one-level refs that
contain only uppercase ascii letters and underscores.

Signed-off-by: Phillip Wood <phillip.wood@dunelm.org.uk>
---
 builtin/show-ref.c  | 2 +-
 t/t1403-show-ref.sh | 8 ++++++++
 2 files changed, 9 insertions(+), 1 deletion(-)

Comments

Junio C Hamano Feb. 7, 2024, 5:12 p.m. UTC | #1
"Phillip Wood via GitGitGadget" <gitgitgadget@gmail.com> writes:

> ... when CHERRY_PICK_HEAD exists. By calling refname_is_safe() instead
> of comparing the refname to "HEAD" we can accept all one-level refs that
> contain only uppercase ascii letters and underscores.

Geez.  We have at least three implementations to determine if a ref
is a valid name?

> diff --git a/builtin/show-ref.c b/builtin/show-ref.c
> index 79955c2856e..1c15421e600 100644
> --- a/builtin/show-ref.c
> +++ b/builtin/show-ref.c
> @@ -172,7 +172,7 @@ static int cmd_show_ref__verify(const struct show_one_options *show_one_opts,
>  	while (*refs) {
>  		struct object_id oid;
>  
> -		if ((starts_with(*refs, "refs/") || !strcmp(*refs, "HEAD")) &&
> +		if ((starts_with(*refs, "refs/") || refname_is_safe(*refs)) &&

I think the helper you picked is the most sensible one, modulo a few
nits.

 - We would want to teach refname_is_safe() to honor is_pseudoref()
   from Karthik's series to make rules more consistent.

 - The refname_is_safe() helper is not just about the stuff at the
   root level.  While starts_with("refs/") is overly lenient, it
   rejects nonsense like "refs/../trash".  We would want to lose
   "starts_with() ||" part of the condition from here.

These are minor non-blocking nits that we should keep in mind only
for longer term maintenance, #leftoverbits after the dust settles.

Will queue.

Thanks.
Patrick Steinhardt Feb. 8, 2024, 8:24 a.m. UTC | #2
On Wed, Feb 07, 2024 at 09:12:29AM -0800, Junio C Hamano wrote:
> "Phillip Wood via GitGitGadget" <gitgitgadget@gmail.com> writes:
> 
> > ... when CHERRY_PICK_HEAD exists. By calling refname_is_safe() instead
> > of comparing the refname to "HEAD" we can accept all one-level refs that
> > contain only uppercase ascii letters and underscores.
> 
> Geez.  We have at least three implementations to determine if a ref
> is a valid name?

`check_refname_format()` and `refname_is_safe()` are often used in
tandem. `check_refname_format()` performs the first set of checks to
verify whether the pathname components are valid, whereas
`refname_is_safe()` checks for refs which are unsafe e.g. because they
try to escape out of "refs/".

I think that we really ought to merge `refname_is_safe()` into
`check_refname_format()`. It would require us to introduce a new flag
`REFNAME_ALLOW_BAD_NAME` to the latter function so that it would accept
refs with a bad name that are otherwise safe. But I think we shouldn't
ever allow unsafe names, so merging these two functions would overall
reduce the potential for security-relevant issues.

Patrick
Phillip Wood Feb. 8, 2024, 2:34 p.m. UTC | #3
Hi Junio

On 07/02/2024 17:12, Junio C Hamano wrote:
> "Phillip Wood via GitGitGadget" <gitgitgadget@gmail.com> writes:
> I think the helper you picked is the most sensible one, modulo a few
> nits.
> 
>   - We would want to teach refname_is_safe() to honor is_pseudoref()
>     from Karthik's series to make rules more consistent.

Yes, I held off sending this series waiting for a while but then got 
impatient. We may want to split out a helper from is_pseudoref() that 
just checks the name without trying to read the ref for callers like 
this which are going to read the ref anyway.

>   - The refname_is_safe() helper is not just about the stuff at the
>     root level.  While starts_with("refs/") is overly lenient, it
>     rejects nonsense like "refs/../trash".  We would want to lose
>     "starts_with() ||" part of the condition from here.

I left the "starts_with()" in as we check the refname when we look it up 
with read_ref() so it seemed like wasted effort to do it here as well.

> These are minor non-blocking nits that we should keep in mind only
> for longer term maintenance, #leftoverbits after the dust settles.
 >
> Will queue.

Thanks

Phillip
diff mbox series

Patch

diff --git a/builtin/show-ref.c b/builtin/show-ref.c
index 79955c2856e..1c15421e600 100644
--- a/builtin/show-ref.c
+++ b/builtin/show-ref.c
@@ -172,7 +172,7 @@  static int cmd_show_ref__verify(const struct show_one_options *show_one_opts,
 	while (*refs) {
 		struct object_id oid;
 
-		if ((starts_with(*refs, "refs/") || !strcmp(*refs, "HEAD")) &&
+		if ((starts_with(*refs, "refs/") || refname_is_safe(*refs)) &&
 		    !read_ref(*refs, &oid)) {
 			show_one(show_one_opts, *refs, &oid);
 		}
diff --git a/t/t1403-show-ref.sh b/t/t1403-show-ref.sh
index d0a8f7b121c..33fb7a38fff 100755
--- a/t/t1403-show-ref.sh
+++ b/t/t1403-show-ref.sh
@@ -174,6 +174,14 @@  test_expect_success 'show-ref --verify HEAD' '
 	test_must_be_empty actual
 '
 
+test_expect_success 'show-ref --verify pseudorefs' '
+	git update-ref CHERRY_PICK_HEAD HEAD $ZERO_OID &&
+	test_when_finished "git update-ref -d CHERRY_PICK_HEAD" &&
+	git show-ref -s --verify HEAD >actual &&
+	git show-ref -s --verify CHERRY_PICK_HEAD >expect &&
+	test_cmp actual expect
+'
+
 test_expect_success 'show-ref --verify with dangling ref' '
 	sha1_file() {
 		echo "$*" | sed "s#..#.git/objects/&/#"