diff mbox series

[4/4] tag: "git tag" refuses to use HEAD as a tagname

Message ID 20241202070714.3028549-5-gitster@pobox.com (mailing list archive)
State New
Headers show
Series forbid HEAD as a tagname | expand

Commit Message

Junio C Hamano Dec. 2, 2024, 7:07 a.m. UTC
Even though the plumbing level allows you to create refs/tags/HEAD
and refs/heads/HEAD, doing so makes it confusing within the context
of the UI Git Porcelain commands provides.  Just like we prevent a
branch from getting called "HEAD" at the Porcelain layer (i.e. "git
branch" command), teach "git tag" to refuse to create a tag "HEAD".

Helped-by: Jeff King <peff@peff.net>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
---
 refs.c         | 2 +-
 t/t7004-tag.sh | 6 ++++++
 2 files changed, 7 insertions(+), 1 deletion(-)

Comments

Patrick Steinhardt Dec. 2, 2024, 10:54 a.m. UTC | #1
On Mon, Dec 02, 2024 at 04:07:14PM +0900, Junio C Hamano wrote:
> diff --git a/refs.c b/refs.c
> index a24bfe3845..01ef2a3093 100644
> --- a/refs.c
> +++ b/refs.c
> @@ -735,7 +735,7 @@ int check_branch_ref(struct strbuf *sb, const char *name)
>  
>  int check_tag_ref(struct strbuf *sb, const char *name)
>  {
> -	if (name[0] == '-')
> +	if (name[0] == '-' || !strcmp(name, "HEAD"))
>  		return -1;
>  
>  	strbuf_reset(sb);

I was thinking a bit about whether we can spin this a bit wider and
disallow creation of any refname that looks like a root ref. But I don't
think that would make sense, as root refs are defined as all-uppercase
refs living in the root, and disallowing tags that look like this would
go way too far.

So I then wondered what a reasonable alternative would be, and the only
rule I could come up with was to disallow root refs with "HEAD" in it.
But even that doesn't feel reasonable to me.

All to say: singling out "HEAD" feels like a sensible step, and I don't
think we should handle root refs more generally here.

The other patches look good to me, as well. Thanks!

Patrick
shejialuo Dec. 2, 2024, 1:01 p.m. UTC | #2
On Mon, Dec 02, 2024 at 04:07:14PM +0900, Junio C Hamano wrote:

[snip]

> diff --git a/refs.c b/refs.c
> index a24bfe3845..01ef2a3093 100644
> --- a/refs.c
> +++ b/refs.c
> @@ -735,7 +735,7 @@ int check_branch_ref(struct strbuf *sb, const char *name)
>  
>  int check_tag_ref(struct strbuf *sb, const char *name)
>  {
> -	if (name[0] == '-')
> +	if (name[0] == '-' || !strcmp(name, "HEAD"))

I am wondering whether we should also update "check_refname_format"
function to report "refs/heads/HEAD" and "refs/tags/HEAD" is bad ref
name.

>  		return -1;
>  
>  	strbuf_reset(sb);

Thanks,
Jialuo
Rubén Justo Dec. 2, 2024, 8:42 p.m. UTC | #3
On Mon, Dec 02, 2024 at 04:07:14PM +0900, Junio C Hamano wrote:
> Even though the plumbing level allows you to create refs/tags/HEAD
> and refs/heads/HEAD, doing so makes it confusing within the context
> of the UI Git Porcelain commands provides.  Just like we prevent a
> branch from getting called "HEAD" at the Porcelain layer (i.e. "git
> branch" command), teach "git tag" to refuse to create a tag "HEAD".

This sounds like a good step in the right direction for me.

From the subject in this patch, I was worried that we were also
preventing deletion.  However, I have confirmed that we still allow
the intuitive deletion of a tag named 'HEAD' with "git tag -d HEAD";
for example, in repositories where such a tag already exists.

Perhaps tangential, but a silly change like this hasn't broken any
tests:

diff --git a/builtin/tag.c b/builtin/tag.c
index 670e564178..b65f56e5b4 100644
--- a/builtin/tag.c
+++ b/builtin/tag.c
@@ -88,6 +88,8 @@ static int for_each_tag_name(const char **argv, each_tag_name_fn fn,
 
        for (p = argv; *p; p++) {
                strbuf_reset(&ref);
+               if (!strcmp(*p, "HEAD"))
+                     die("Hi!");
                strbuf_addf(&ref, "refs/tags/%s", *p);
                if (refs_read_ref(get_main_ref_store(the_repository), ref.buf, &oid)) {
                        error(_("tag '%s' not found."), *p);

Therefore, if the previous seems reasonable, perhaps we should add a
test like:

--- a/t/t7004-tag.sh
+++ b/t/t7004-tag.sh
@@ -97,6 +97,11 @@ test_expect_success 'HEAD is forbidden as a tagname' '
 	test_must_fail git tag -a -m "useless" HEAD
 '
 
+test_expect_success 'allow deleting a tag named HEAD' '
+	git update-ref refs/tags/HEAD HEAD &&
+	git tag -d HEAD
+'
+
 test_expect_success 'creating a tag with --create-reflog should create reflog' '
 	git log -1 \
 		--format="format:tag: tagging %h (%s, %cd)%n" \

> 
> Helped-by: Jeff King <peff@peff.net>
> Signed-off-by: Junio C Hamano <gitster@pobox.com>
> ---
>  refs.c         | 2 +-
>  t/t7004-tag.sh | 6 ++++++
>  2 files changed, 7 insertions(+), 1 deletion(-)
> 
> diff --git a/refs.c b/refs.c
> index a24bfe3845..01ef2a3093 100644
> --- a/refs.c
> +++ b/refs.c
> @@ -735,7 +735,7 @@ int check_branch_ref(struct strbuf *sb, const char *name)
>  
>  int check_tag_ref(struct strbuf *sb, const char *name)
>  {
> -	if (name[0] == '-')
> +	if (name[0] == '-' || !strcmp(name, "HEAD"))
>  		return -1;
>  
>  	strbuf_reset(sb);
> diff --git a/t/t7004-tag.sh b/t/t7004-tag.sh
> index b1316e62f4..2082ce63f7 100755
> --- a/t/t7004-tag.sh
> +++ b/t/t7004-tag.sh
> @@ -91,6 +91,12 @@ test_expect_success 'creating a tag using default HEAD should succeed' '
>  	test_must_fail git reflog exists refs/tags/mytag
>  '
>  
> +test_expect_success 'HEAD is forbidden as a tagname' '
> +	test_when_finished "git tag -d HEAD || :" &&

I'm not considering this as a test for it :-)

> +	test_must_fail git tag HEAD &&
> +	test_must_fail git tag -a -m "useless" HEAD
> +'
> +
>  test_expect_success 'creating a tag with --create-reflog should create reflog' '
>  	git log -1 \
>  		--format="format:tag: tagging %h (%s, %cd)%n" \
> -- 
> 2.47.1-514-g9b43e7ecc4
>
Jeff King Dec. 2, 2024, 9:03 p.m. UTC | #4
On Mon, Dec 02, 2024 at 04:07:14PM +0900, Junio C Hamano wrote:

> Even though the plumbing level allows you to create refs/tags/HEAD
> and refs/heads/HEAD, doing so makes it confusing within the context
> of the UI Git Porcelain commands provides.  Just like we prevent a
> branch from getting called "HEAD" at the Porcelain layer (i.e. "git
> branch" command), teach "git tag" to refuse to create a tag "HEAD".

This looks good and mostly as expected. I do think Rubén's suggestion to
add an explicit deletion test might be worth having to future-proof
things.

> @@ -91,6 +91,12 @@ test_expect_success 'creating a tag using default HEAD should succeed' '
>  	test_must_fail git reflog exists refs/tags/mytag
>  '
>  
> +test_expect_success 'HEAD is forbidden as a tagname' '
> +	test_when_finished "git tag -d HEAD || :" &&
> +	test_must_fail git tag HEAD &&
> +	test_must_fail git tag -a -m "useless" HEAD
> +'

The test_when_finished surprised me a little, just because we would not
expect anything to have been created. I don't think we usually bother
with cleaning up failure modes, as it is a losing battle (if the test
did not succeed you are only guessing at what mess may have been left
behind). But I don't think it's hurting anything, beyond a few wasted
cycles to run what should be a noop.

-Peff
diff mbox series

Patch

diff --git a/refs.c b/refs.c
index a24bfe3845..01ef2a3093 100644
--- a/refs.c
+++ b/refs.c
@@ -735,7 +735,7 @@  int check_branch_ref(struct strbuf *sb, const char *name)
 
 int check_tag_ref(struct strbuf *sb, const char *name)
 {
-	if (name[0] == '-')
+	if (name[0] == '-' || !strcmp(name, "HEAD"))
 		return -1;
 
 	strbuf_reset(sb);
diff --git a/t/t7004-tag.sh b/t/t7004-tag.sh
index b1316e62f4..2082ce63f7 100755
--- a/t/t7004-tag.sh
+++ b/t/t7004-tag.sh
@@ -91,6 +91,12 @@  test_expect_success 'creating a tag using default HEAD should succeed' '
 	test_must_fail git reflog exists refs/tags/mytag
 '
 
+test_expect_success 'HEAD is forbidden as a tagname' '
+	test_when_finished "git tag -d HEAD || :" &&
+	test_must_fail git tag HEAD &&
+	test_must_fail git tag -a -m "useless" HEAD
+'
+
 test_expect_success 'creating a tag with --create-reflog should create reflog' '
 	git log -1 \
 		--format="format:tag: tagging %h (%s, %cd)%n" \