{"thread":{"id":"62589","subject":"[PATCH v2 0/4] forbid HEAD as a tagname","startedAt":"2024-12-03T02:32:43Z","lastAt":"2024-12-03T02:32:50Z","messageCount":5,"participants":["Junio C Hamano"],"isPatch":true,"patchVersion":2,"patchTotal":4},"messages":[{"id":"508496","messageId":"20241203023240.3852850-1-gitster@pobox.com","threadId":"62589","inReplyTo":null,"subject":"[PATCH v2 0/4] forbid HEAD as a tagname","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2024-12-03T02:32:36Z","receivedAt":"2024-12-03T02:32:43Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"As discussed earlier, if you accidentally did \"git tag HEAD\",\nthe command happily creates a tag with HEAD as its name, but then it\nwould be confusing as use of it becomes ambiguous with the most often\nused ref, HEAD, the currently checked-out commit.\n\nLet's forbid creation of such a tag using the \"git tag\" Porcelain\ncommand, while still allowing creation of such a ref using the\nplumbing command \"git update-ref\".  \"git tag -d HEAD\" can still be\nused to remove such a tag to recover from an earlier mistake.  This\nfollows the design pattern used to forbid use of \"HEAD\" as a branch\nname, where \"git branch\" refuses to create such a branch, but \"git\nupdate-ref\" is usable to do so.\n\nThe intent is that the repository layout and format allows use of\nsuch names and we keep our low-level command usable for those who\nwant to write a different UI with different usability constrants\n(e.g. they may not even have a concept of \"currently checked-out\nbranch\" hence the word HEAD may not even be special).\n\nThe first two clean-up patches are unchanged.\n\nThe third step has an updated log message to mention an unrelated\nclean-up of the test.\n\nThe final step has an extra test to make sure that \"update-ref\" is\nstill usable to create such a tag, and \"git tag -d\" can remove it.\n\nJunio C Hamano (4):\n  refs: move ref name helpers around\n  refs: drop strbuf_ prefix from helpers\n  t5604: do not expect that HEAD can be a valid tagname\n  tag: \"git tag\" refuses to use HEAD as a tagname\n\n branch.c                   |  2 +-\n builtin/branch.c           | 10 ++++----\n builtin/check-ref-format.c |  2 +-\n builtin/checkout.c         |  2 +-\n builtin/merge.c            |  2 +-\n builtin/tag.c              | 13 +----------\n builtin/worktree.c         |  8 +++----\n gitweb/gitweb.perl         |  2 +-\n object-name.c              | 36 -----------------------------\n refs.c                     | 47 ++++++++++++++++++++++++++++++++++++++\n refs.h                     | 29 +++++++++++++++++++++++\n strbuf.h                   | 22 ------------------\n t/t5604-clone-reference.sh |  6 ++---\n t/t7004-tag.sh             | 12 ++++++++++\n 14 files changed, 106 insertions(+), 87 deletions(-)\n\n1:  717418e6c0 = 1:  717418e6c0 refs: move ref name helpers around\n2:  e7b29c0967 = 2:  e7b29c0967 refs: drop strbuf_ prefix from helpers\n3:  e143e68d9c ! 3:  a0efd9b681 t5604: do not expect that HEAD is a valid tagname\n    @@ Metadata\n     Author: Junio C Hamano <gitster@pobox.com>\n     \n      ## Commit message ##\n    -    t5604: do not expect that HEAD is a valid tagname\n    +    t5604: do not expect that HEAD can be a valid tagname\n     \n         09116a1c (refs: loosen over-strict \"format\" check, 2011-11-16)\n         introduced a test piece (originally in t5700) that expects to be\n    @@ Commit message\n         Before forbidding \"git tag\" from creating \"refs/tags/HEAD\", update\n         these tests to use 'foo', not 'HEAD', as the name of the test tag.\n     \n    +    Note that the test piece that uses the tag learned the value of the\n    +    tag in unnecessarily inefficient and convoluted way with for-each-ref.\n    +    Just use \"rev-parse\" instead.\n    +\n         Signed-off-by: Junio C Hamano <gitster@pobox.com>\n     \n      ## t/t5604-clone-reference.sh ##\n4:  6595acfbf3 ! 4:  2c6438eccf tag: \"git tag\" refuses to use HEAD as a tagname\n    @@ Commit message\n         branch from getting called \"HEAD\" at the Porcelain layer (i.e. \"git\n         branch\" command), teach \"git tag\" to refuse to create a tag \"HEAD\".\n     \n    +    With a few new tests, we make sure that\n    +\n    +     - \"git tag HEAD\" and \"git tag -a HEAD\" are rejected\n    +\n    +     - \"git update-ref refs/tags/HEAD\" is still allowed (this is a\n    +       deliberate design decision to allow others to create their own UI\n    +       on top of Git infrastructure that may be different from our UI).\n    +\n    +     - \"git tag -d HEAD\" can remove refs/tags/HEAD to recover from an\n    +       mistake.\n    +\n         Helped-by: Jeff King <peff@peff.net>\n    +    Helped-by: Rubén Justo <rjusto@gmail.com>\n         Signed-off-by: Junio C Hamano <gitster@pobox.com>\n     \n      ## refs.c ##\n    @@ t/t7004-tag.sh: test_expect_success 'creating a tag using default HEAD should su\n      '\n      \n     +test_expect_success 'HEAD is forbidden as a tagname' '\n    -+\ttest_when_finished \"git tag -d HEAD || :\" &&\n    ++\ttest_when_finished \"git update-ref --no-deref -d refs/tags/HEAD || :\" &&\n     +\ttest_must_fail git tag HEAD &&\n     +\ttest_must_fail git tag -a -m \"useless\" HEAD\n     +'\n    ++\n    ++test_expect_success '\"git tag\" can remove a tag named HEAD' '\n    ++\ttest_when_finished \"git update-ref --no-deref -d refs/tags/HEAD || :\" &&\n    ++\tgit update-ref refs/tags/HEAD HEAD &&\n    ++\tgit tag -d HEAD\n    ++'\n     +\n      test_expect_success 'creating a tag with --create-reflog should create reflog' '\n      \tgit log -1 \\\n"},{"id":"508497","messageId":"20241203023240.3852850-2-gitster@pobox.com","threadId":"62589","inReplyTo":"20241203023240.3852850-1-gitster@pobox.com","subject":"[PATCH v2 1/4] refs: move ref name helpers around","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2024-12-03T02:32:37Z","receivedAt":"2024-12-03T02:32:45Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"strbuf_branchname(), strbuf_check_{branch,tag}_ref() are helper\nfunctions to deal with branch and tag names, and the fact that they\nhappen to use strbuf to hold the name of a branch or a tag is not\nessential.  These functions fit better in the refs API than strbuf\nAPI, the latter of which is about string manipulations.\n\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\n builtin/tag.c | 11 -----------\n object-name.c | 36 ------------------------------------\n refs.c        | 47 +++++++++++++++++++++++++++++++++++++++++++++++\n refs.h        | 29 +++++++++++++++++++++++++++++\n strbuf.h      | 22 ----------------------\n 5 files changed, 76 insertions(+), 69 deletions(-)\n\ndiff --git a/builtin/tag.c b/builtin/tag.c\nindex 93d10d5915..8279dccbe0 100644\n--- a/builtin/tag.c\n+++ b/builtin/tag.c\n@@ -447,17 +447,6 @@ static int parse_msg_arg(const struct option *opt, const char *arg, int unset)\n \treturn 0;\n }\n \n-static int strbuf_check_tag_ref(struct strbuf *sb, const char *name)\n-{\n-\tif (name[0] == '-')\n-\t\treturn -1;\n-\n-\tstrbuf_reset(sb);\n-\tstrbuf_addf(sb, \"refs/tags/%s\", name);\n-\n-\treturn check_refname_format(sb->buf, 0);\n-}\n-\n int cmd_tag(int argc,\n \t    const char **argv,\n \t    const char *prefix,\ndiff --git a/object-name.c b/object-name.c\nindex c892fbe80a..9f2ae164e4 100644\n--- a/object-name.c\n+++ b/object-name.c\n@@ -1734,42 +1734,6 @@ int repo_interpret_branch_name(struct repository *r,\n \treturn -1;\n }\n \n-void strbuf_branchname(struct strbuf *sb, const char *name, unsigned allowed)\n-{\n-\tint len = strlen(name);\n-\tstruct interpret_branch_name_options options = {\n-\t\t.allowed = allowed\n-\t};\n-\tint used = repo_interpret_branch_name(the_repository, name, len, sb,\n-\t\t\t\t\t      &options);\n-\n-\tif (used < 0)\n-\t\tused = 0;\n-\tstrbuf_add(sb, name + used, len - used);\n-}\n-\n-int strbuf_check_branch_ref(struct strbuf *sb, const char *name)\n-{\n-\tif (startup_info->have_repository)\n-\t\tstrbuf_branchname(sb, name, INTERPRET_BRANCH_LOCAL);\n-\telse\n-\t\tstrbuf_addstr(sb, name);\n-\n-\t/*\n-\t * This splice must be done even if we end up rejecting the\n-\t * name; builtin/branch.c::copy_or_rename_branch() still wants\n-\t * to see what the name expanded to so that \"branch -m\" can be\n-\t * used as a tool to correct earlier mistakes.\n-\t */\n-\tstrbuf_splice(sb, 0, 0, \"refs/heads/\", 11);\n-\n-\tif (*name == '-' ||\n-\t    !strcmp(sb->buf, \"refs/heads/HEAD\"))\n-\t\treturn -1;\n-\n-\treturn check_refname_format(sb->buf, 0);\n-}\n-\n void object_context_release(struct object_context *ctx)\n {\n \tfree(ctx->path);\ndiff --git a/refs.c b/refs.c\nindex 5f729ed412..59a9223d4c 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -697,6 +697,53 @@ static char *substitute_branch_name(struct repository *r,\n \treturn NULL;\n }\n \n+void strbuf_branchname(struct strbuf *sb, const char *name, unsigned allowed)\n+{\n+\tint len = strlen(name);\n+\tstruct interpret_branch_name_options options = {\n+\t\t.allowed = allowed\n+\t};\n+\tint used = repo_interpret_branch_name(the_repository, name, len, sb,\n+\t\t\t\t\t      &options);\n+\n+\tif (used < 0)\n+\t\tused = 0;\n+\tstrbuf_add(sb, name + used, len - used);\n+}\n+\n+int strbuf_check_branch_ref(struct strbuf *sb, const char *name)\n+{\n+\tif (startup_info->have_repository)\n+\t\tstrbuf_branchname(sb, name, INTERPRET_BRANCH_LOCAL);\n+\telse\n+\t\tstrbuf_addstr(sb, name);\n+\n+\t/*\n+\t * This splice must be done even if we end up rejecting the\n+\t * name; builtin/branch.c::copy_or_rename_branch() still wants\n+\t * to see what the name expanded to so that \"branch -m\" can be\n+\t * used as a tool to correct earlier mistakes.\n+\t */\n+\tstrbuf_splice(sb, 0, 0, \"refs/heads/\", 11);\n+\n+\tif (*name == '-' ||\n+\t    !strcmp(sb->buf, \"refs/heads/HEAD\"))\n+\t\treturn -1;\n+\n+\treturn check_refname_format(sb->buf, 0);\n+}\n+\n+int strbuf_check_tag_ref(struct strbuf *sb, const char *name)\n+{\n+\tif (name[0] == '-')\n+\t\treturn -1;\n+\n+\tstrbuf_reset(sb);\n+\tstrbuf_addf(sb, \"refs/tags/%s\", name);\n+\n+\treturn check_refname_format(sb->buf, 0);\n+}\n+\n int repo_dwim_ref(struct repository *r, const char *str, int len,\n \t\t  struct object_id *oid, char **ref, int nonfatal_dangling_mark)\n {\ndiff --git a/refs.h b/refs.h\nindex 108dfc93b3..f19b0ad92f 100644\n--- a/refs.h\n+++ b/refs.h\n@@ -180,6 +180,35 @@ int repo_dwim_log(struct repository *r, const char *str, int len, struct object_\n  */\n char *repo_default_branch_name(struct repository *r, int quiet);\n \n+/*\n+ * Copy \"name\" to \"sb\", expanding any special @-marks as handled by\n+ * repo_interpret_branch_name(). The result is a non-qualified branch name\n+ * (so \"foo\" or \"origin/master\" instead of \"refs/heads/foo\" or\n+ * \"refs/remotes/origin/master\").\n+ *\n+ * Note that the resulting name may not be a syntactically valid refname.\n+ *\n+ * If \"allowed\" is non-zero, restrict the set of allowed expansions. See\n+ * repo_interpret_branch_name() for details.\n+ */\n+void strbuf_branchname(struct strbuf *sb, const char *name,\n+\t\t       unsigned allowed);\n+\n+/*\n+ * Like strbuf_branchname() above, but confirm that the result is\n+ * syntactically valid to be used as a local branch name in refs/heads/.\n+ *\n+ * The return value is \"0\" if the result is valid, and \"-1\" otherwise.\n+ */\n+int strbuf_check_branch_ref(struct strbuf *sb, const char *name);\n+\n+/*\n+ * Similar for a tag name in refs/tags/.\n+ *\n+ * The return value is \"0\" if the result is valid, and \"-1\" otherwise.\n+ */\n+int strbuf_check_tag_ref(struct strbuf *sb, const char *name);\n+\n /*\n  * A ref_transaction represents a collection of reference updates that\n  * should succeed or fail together.\ndiff --git a/strbuf.h b/strbuf.h\nindex 003f880ff7..4dc05b4ba7 100644\n--- a/strbuf.h\n+++ b/strbuf.h\n@@ -637,28 +637,6 @@ static inline void strbuf_complete_line(struct strbuf *sb)\n \tstrbuf_complete(sb, '\\n');\n }\n \n-/*\n- * Copy \"name\" to \"sb\", expanding any special @-marks as handled by\n- * repo_interpret_branch_name(). The result is a non-qualified branch name\n- * (so \"foo\" or \"origin/master\" instead of \"refs/heads/foo\" or\n- * \"refs/remotes/origin/master\").\n- *\n- * Note that the resulting name may not be a syntactically valid refname.\n- *\n- * If \"allowed\" is non-zero, restrict the set of allowed expansions. See\n- * repo_interpret_branch_name() for details.\n- */\n-void strbuf_branchname(struct strbuf *sb, const char *name,\n-\t\t       unsigned allowed);\n-\n-/*\n- * Like strbuf_branchname() above, but confirm that the result is\n- * syntactically valid to be used as a local branch name in refs/heads/.\n- *\n- * The return value is \"0\" if the result is valid, and \"-1\" otherwise.\n- */\n-int strbuf_check_branch_ref(struct strbuf *sb, const char *name);\n-\n typedef int (*char_predicate)(char ch);\n \n void strbuf_addstr_urlencode(struct strbuf *sb, const char *name,\n-- \n2.47.1-515-g5132b7d2ef\n\n"},{"id":"508498","messageId":"20241203023240.3852850-3-gitster@pobox.com","threadId":"62589","inReplyTo":"20241203023240.3852850-1-gitster@pobox.com","subject":"[PATCH v2 2/4] refs: drop strbuf_ prefix from helpers","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2024-12-03T02:32:38Z","receivedAt":"2024-12-03T02:32:47Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"The helper functions (strbuf_branchname, strbuf_check_branch_ref,\nand strbuf_check_tag_ref) are about handling branch and tag names,\nand it is a non-essential fact that these functions use strbuf to\nhold these names.  Rename them to make it clarify that these are\nmore about \"ref\".\n\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\n branch.c                   |  2 +-\n builtin/branch.c           | 10 +++++-----\n builtin/check-ref-format.c |  2 +-\n builtin/checkout.c         |  2 +-\n builtin/merge.c            |  2 +-\n builtin/tag.c              |  2 +-\n builtin/worktree.c         |  8 ++++----\n gitweb/gitweb.perl         |  2 +-\n refs.c                     |  8 ++++----\n refs.h                     |  8 ++++----\n 10 files changed, 23 insertions(+), 23 deletions(-)\n\ndiff --git a/branch.c b/branch.c\nindex 08fa4094d2..58b61831af 100644\n--- a/branch.c\n+++ b/branch.c\n@@ -372,7 +372,7 @@ int read_branch_desc(struct strbuf *buf, const char *branch_name)\n  */\n int validate_branchname(const char *name, struct strbuf *ref)\n {\n-\tif (strbuf_check_branch_ref(ref, name)) {\n+\tif (check_branch_ref(ref, name)) {\n \t\tint code = die_message(_(\"'%s' is not a valid branch name\"), name);\n \t\tadvise_if_enabled(ADVICE_REF_SYNTAX,\n \t\t\t\t  _(\"See `man git check-ref-format`\"));\ndiff --git a/builtin/branch.c b/builtin/branch.c\nindex fd1611ebf5..17acf598d2 100644\n--- a/builtin/branch.c\n+++ b/builtin/branch.c\n@@ -257,7 +257,7 @@ static int delete_branches(int argc, const char **argv, int force, int kinds,\n \t\tchar *target = NULL;\n \t\tint flags = 0;\n \n-\t\tstrbuf_branchname(&bname, argv[i], allowed_interpret);\n+\t\tcopy_branchname(&bname, argv[i], allowed_interpret);\n \t\tfree(name);\n \t\tname = mkpathdup(fmt, bname.buf);\n \n@@ -579,7 +579,7 @@ static void copy_or_rename_branch(const char *oldname, const char *newname, int\n \tint recovery = 0, oldref_usage = 0;\n \tstruct worktree **worktrees = get_worktrees();\n \n-\tif (strbuf_check_branch_ref(&oldref, oldname)) {\n+\tif (check_branch_ref(&oldref, oldname)) {\n \t\t/*\n \t\t * Bad name --- this could be an attempt to rename a\n \t\t * ref that we used to allow to be created by accident.\n@@ -894,7 +894,7 @@ int cmd_branch(int argc,\n \t\t\t\tdie(_(\"cannot give description to detached HEAD\"));\n \t\t\tbranch_name = head;\n \t\t} else if (argc == 1) {\n-\t\t\tstrbuf_branchname(&buf, argv[0], INTERPRET_BRANCH_LOCAL);\n+\t\t\tcopy_branchname(&buf, argv[0], INTERPRET_BRANCH_LOCAL);\n \t\t\tbranch_name = buf.buf;\n \t\t} else {\n \t\t\tdie(_(\"cannot edit description of more than one branch\"));\n@@ -933,7 +933,7 @@ int cmd_branch(int argc,\n \t\tif (!argc)\n \t\t\tbranch = branch_get(NULL);\n \t\telse if (argc == 1) {\n-\t\t\tstrbuf_branchname(&buf, argv[0], INTERPRET_BRANCH_LOCAL);\n+\t\t\tcopy_branchname(&buf, argv[0], INTERPRET_BRANCH_LOCAL);\n \t\t\tbranch = branch_get(buf.buf);\n \t\t} else\n \t\t\tdie(_(\"too many arguments to set new upstream\"));\n@@ -963,7 +963,7 @@ int cmd_branch(int argc,\n \t\tif (!argc)\n \t\t\tbranch = branch_get(NULL);\n \t\telse if (argc == 1) {\n-\t\t\tstrbuf_branchname(&buf, argv[0], INTERPRET_BRANCH_LOCAL);\n+\t\t\tcopy_branchname(&buf, argv[0], INTERPRET_BRANCH_LOCAL);\n \t\t\tbranch = branch_get(buf.buf);\n \t\t} else\n \t\t\tdie(_(\"too many arguments to unset upstream\"));\ndiff --git a/builtin/check-ref-format.c b/builtin/check-ref-format.c\nindex e86d8ef980..cef1ffe3ce 100644\n--- a/builtin/check-ref-format.c\n+++ b/builtin/check-ref-format.c\n@@ -42,7 +42,7 @@ static int check_ref_format_branch(const char *arg)\n \tint nongit;\n \n \tsetup_git_directory_gently(&nongit);\n-\tif (strbuf_check_branch_ref(&sb, arg) ||\n+\tif (check_branch_ref(&sb, arg) ||\n \t    !skip_prefix(sb.buf, \"refs/heads/\", &name))\n \t\tdie(\"'%s' is not a valid branch name\", arg);\n \tprintf(\"%s\\n\", name);\ndiff --git a/builtin/checkout.c b/builtin/checkout.c\nindex c449558e66..5e5afa0f26 100644\n--- a/builtin/checkout.c\n+++ b/builtin/checkout.c\n@@ -742,7 +742,7 @@ static void setup_branch_path(struct branch_info *branch)\n \t\t\t   &branch->oid, &branch->refname, 0))\n \t\trepo_get_oid_committish(the_repository, branch->name, &branch->oid);\n \n-\tstrbuf_branchname(&buf, branch->name, INTERPRET_BRANCH_LOCAL);\n+\tcopy_branchname(&buf, branch->name, INTERPRET_BRANCH_LOCAL);\n \tif (strcmp(buf.buf, branch->name)) {\n \t\tfree(branch->name);\n \t\tbranch->name = xstrdup(buf.buf);\ndiff --git a/builtin/merge.c b/builtin/merge.c\nindex 84d0f3604b..d0c31d7714 100644\n--- a/builtin/merge.c\n+++ b/builtin/merge.c\n@@ -498,7 +498,7 @@ static void merge_name(const char *remote, struct strbuf *msg)\n \tchar *found_ref = NULL;\n \tint len, early;\n \n-\tstrbuf_branchname(&bname, remote, 0);\n+\tcopy_branchname(&bname, remote, 0);\n \tremote = bname.buf;\n \n \toidclr(&branch_head, the_repository->hash_algo);\ndiff --git a/builtin/tag.c b/builtin/tag.c\nindex 8279dccbe0..670e564178 100644\n--- a/builtin/tag.c\n+++ b/builtin/tag.c\n@@ -639,7 +639,7 @@ int cmd_tag(int argc,\n \tif (repo_get_oid(the_repository, object_ref, &object))\n \t\tdie(_(\"Failed to resolve '%s' as a valid ref.\"), object_ref);\n \n-\tif (strbuf_check_tag_ref(&ref, tag))\n+\tif (check_tag_ref(&ref, tag))\n \t\tdie(_(\"'%s' is not a valid tag name.\"), tag);\n \n \tif (refs_read_ref(get_main_ref_store(the_repository), ref.buf, &prev))\ndiff --git a/builtin/worktree.c b/builtin/worktree.c\nindex fc31d072a6..c68f601358 100644\n--- a/builtin/worktree.c\n+++ b/builtin/worktree.c\n@@ -432,7 +432,7 @@ static int add_worktree(const char *path, const char *refname,\n \tworktrees = NULL;\n \n \t/* is 'refname' a branch or commit? */\n-\tif (!opts->detach && !strbuf_check_branch_ref(&symref, refname) &&\n+\tif (!opts->detach && !check_branch_ref(&symref, refname) &&\n \t    refs_ref_exists(get_main_ref_store(the_repository), symref.buf)) {\n \t\tis_branch = 1;\n \t\tif (!opts->force)\n@@ -604,7 +604,7 @@ static void print_preparing_worktree_line(int detach,\n \t\tfprintf_ln(stderr, _(\"Preparing worktree (new branch '%s')\"), new_branch);\n \t} else {\n \t\tstruct strbuf s = STRBUF_INIT;\n-\t\tif (!detach && !strbuf_check_branch_ref(&s, branch) &&\n+\t\tif (!detach && !check_branch_ref(&s, branch) &&\n \t\t    refs_ref_exists(get_main_ref_store(the_repository), s.buf))\n \t\t\tfprintf_ln(stderr, _(\"Preparing worktree (checking out '%s')\"),\n \t\t\t\t  branch);\n@@ -745,7 +745,7 @@ static char *dwim_branch(const char *path, char **new_branch)\n \tchar *branchname = xstrndup(s, n);\n \tstruct strbuf ref = STRBUF_INIT;\n \n-\tbranch_exists = !strbuf_check_branch_ref(&ref, branchname) &&\n+\tbranch_exists = !check_branch_ref(&ref, branchname) &&\n \t\t\trefs_ref_exists(get_main_ref_store(the_repository),\n \t\t\t\t\tref.buf);\n \tstrbuf_release(&ref);\n@@ -838,7 +838,7 @@ static int add(int ac, const char **av, const char *prefix)\n \t\tnew_branch = new_branch_force;\n \n \t\tif (!opts.force &&\n-\t\t    !strbuf_check_branch_ref(&symref, new_branch) &&\n+\t\t    !check_branch_ref(&symref, new_branch) &&\n \t\t    refs_ref_exists(get_main_ref_store(the_repository), symref.buf))\n \t\t\tdie_if_checked_out(symref.buf, 0);\n \t\tstrbuf_release(&symref);\ndiff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl\nindex b09a8d0523..8cdb0d9b9f 100755\n--- a/gitweb/gitweb.perl\n+++ b/gitweb/gitweb.perl\n@@ -2094,7 +2094,7 @@ sub format_log_line_html {\n         (\n             # The output of \"git describe\", e.g. v2.10.0-297-gf6727b0\n             # or hadoop-20160921-113441-20-g094fb7d\n-            (?<!-) # see strbuf_check_tag_ref(). Tags can't start with -\n+            (?<!-) # see check_tag_ref(). Tags can't start with -\n             [A-Za-z0-9.-]+\n             (?!\\.) # refs can't end with \".\", see check_refname_format()\n             -g$regex\ndiff --git a/refs.c b/refs.c\nindex 59a9223d4c..a24bfe3845 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -697,7 +697,7 @@ static char *substitute_branch_name(struct repository *r,\n \treturn NULL;\n }\n \n-void strbuf_branchname(struct strbuf *sb, const char *name, unsigned allowed)\n+void copy_branchname(struct strbuf *sb, const char *name, unsigned allowed)\n {\n \tint len = strlen(name);\n \tstruct interpret_branch_name_options options = {\n@@ -711,10 +711,10 @@ void strbuf_branchname(struct strbuf *sb, const char *name, unsigned allowed)\n \tstrbuf_add(sb, name + used, len - used);\n }\n \n-int strbuf_check_branch_ref(struct strbuf *sb, const char *name)\n+int check_branch_ref(struct strbuf *sb, const char *name)\n {\n \tif (startup_info->have_repository)\n-\t\tstrbuf_branchname(sb, name, INTERPRET_BRANCH_LOCAL);\n+\t\tcopy_branchname(sb, name, INTERPRET_BRANCH_LOCAL);\n \telse\n \t\tstrbuf_addstr(sb, name);\n \n@@ -733,7 +733,7 @@ int strbuf_check_branch_ref(struct strbuf *sb, const char *name)\n \treturn check_refname_format(sb->buf, 0);\n }\n \n-int strbuf_check_tag_ref(struct strbuf *sb, const char *name)\n+int check_tag_ref(struct strbuf *sb, const char *name)\n {\n \tif (name[0] == '-')\n \t\treturn -1;\ndiff --git a/refs.h b/refs.h\nindex f19b0ad92f..c5280477f0 100644\n--- a/refs.h\n+++ b/refs.h\n@@ -191,23 +191,23 @@ char *repo_default_branch_name(struct repository *r, int quiet);\n  * If \"allowed\" is non-zero, restrict the set of allowed expansions. See\n  * repo_interpret_branch_name() for details.\n  */\n-void strbuf_branchname(struct strbuf *sb, const char *name,\n+void copy_branchname(struct strbuf *sb, const char *name,\n \t\t       unsigned allowed);\n \n /*\n- * Like strbuf_branchname() above, but confirm that the result is\n+ * Like copy_branchname() above, but confirm that the result is\n  * syntactically valid to be used as a local branch name in refs/heads/.\n  *\n  * The return value is \"0\" if the result is valid, and \"-1\" otherwise.\n  */\n-int strbuf_check_branch_ref(struct strbuf *sb, const char *name);\n+int check_branch_ref(struct strbuf *sb, const char *name);\n \n /*\n  * Similar for a tag name in refs/tags/.\n  *\n  * The return value is \"0\" if the result is valid, and \"-1\" otherwise.\n  */\n-int strbuf_check_tag_ref(struct strbuf *sb, const char *name);\n+int check_tag_ref(struct strbuf *sb, const char *name);\n \n /*\n  * A ref_transaction represents a collection of reference updates that\n-- \n2.47.1-515-g5132b7d2ef\n\n"},{"id":"508499","messageId":"20241203023240.3852850-4-gitster@pobox.com","threadId":"62589","inReplyTo":"20241203023240.3852850-1-gitster@pobox.com","subject":"[PATCH v2 3/4] t5604: do not expect that HEAD can be a valid tagname","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2024-12-03T02:32:39Z","receivedAt":"2024-12-03T02:32:48Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"09116a1c (refs: loosen over-strict \"format\" check, 2011-11-16)\nintroduced a test piece (originally in t5700) that expects to be\nable to create a tag named \"HEAD\" and then a local clone using the\nrepository as its own reference works correctly.  Later, another\ntest piece started using this tag starting at acede2eb (t5700:\ndocument a failure of alternates to affect fetch, 2012-02-11).\n\nBut the breakage 09116a1c fixed was not specific to the tagname\nHEAD.  It would have failed exactly the same way if the tag used\nwere foo instead of HEAD.\n\nBefore forbidding \"git tag\" from creating \"refs/tags/HEAD\", update\nthese tests to use 'foo', not 'HEAD', as the name of the test tag.\n\nNote that the test piece that uses the tag learned the value of the\ntag in unnecessarily inefficient and convoluted way with for-each-ref.\nJust use \"rev-parse\" instead.\n\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\n t/t5604-clone-reference.sh | 6 +++---\n 1 file changed, 3 insertions(+), 3 deletions(-)\n\ndiff --git a/t/t5604-clone-reference.sh b/t/t5604-clone-reference.sh\nindex 9b32db8478..5f5c650ff8 100755\n--- a/t/t5604-clone-reference.sh\n+++ b/t/t5604-clone-reference.sh\n@@ -131,7 +131,7 @@ test_expect_success 'cloning with multiple references drops duplicates' '\n \n test_expect_success 'clone with reference from a tagged repository' '\n \t(\n-\t\tcd A && git tag -a -m tagged HEAD\n+\t\tcd A && git tag -a -m tagged foo\n \t) &&\n \tgit clone --reference=A A I\n '\n@@ -156,10 +156,10 @@ test_expect_success 'fetch with incomplete alternates' '\n \t\tgit remote add J \"file://$base_dir/J\" &&\n \t\tGIT_TRACE_PACKET=$U.K git fetch J\n \t) &&\n-\tmain_object=$(cd A && git for-each-ref --format=\"%(objectname)\" refs/heads/main) &&\n+\tmain_object=$(git -C A rev-parse --verify refs/heads/main) &&\n \ttest -s \"$U.K\" &&\n \t! grep \" want $main_object\" \"$U.K\" &&\n-\ttag_object=$(cd A && git for-each-ref --format=\"%(objectname)\" refs/tags/HEAD) &&\n+\ttag_object=$(git -C A rev-parse --verify refs/tags/foo) &&\n \t! grep \" want $tag_object\" \"$U.K\"\n '\n \n-- \n2.47.1-515-g5132b7d2ef\n\n"},{"id":"508500","messageId":"20241203023240.3852850-5-gitster@pobox.com","threadId":"62589","inReplyTo":"20241203023240.3852850-1-gitster@pobox.com","subject":"[PATCH v2 4/4] tag: \"git tag\" refuses to use HEAD as a tagname","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2024-12-03T02:32:40Z","receivedAt":"2024-12-03T02:32:50Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Even though the plumbing level allows you to create refs/tags/HEAD\nand refs/heads/HEAD, doing so makes it confusing within the context\nof the UI Git Porcelain commands provides.  Just like we prevent a\nbranch from getting called \"HEAD\" at the Porcelain layer (i.e. \"git\nbranch\" command), teach \"git tag\" to refuse to create a tag \"HEAD\".\n\nWith a few new tests, we make sure that\n\n - \"git tag HEAD\" and \"git tag -a HEAD\" are rejected\n\n - \"git update-ref refs/tags/HEAD\" is still allowed (this is a\n   deliberate design decision to allow others to create their own UI\n   on top of Git infrastructure that may be different from our UI).\n\n - \"git tag -d HEAD\" can remove refs/tags/HEAD to recover from an\n   mistake.\n\nHelped-by: Jeff King <peff@peff.net>\nHelped-by: Rubén Justo <rjusto@gmail.com>\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\n refs.c         |  2 +-\n t/t7004-tag.sh | 12 ++++++++++++\n 2 files changed, 13 insertions(+), 1 deletion(-)\n\ndiff --git a/refs.c b/refs.c\nindex a24bfe3845..01ef2a3093 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -735,7 +735,7 @@ int check_branch_ref(struct strbuf *sb, const char *name)\n \n int check_tag_ref(struct strbuf *sb, const char *name)\n {\n-\tif (name[0] == '-')\n+\tif (name[0] == '-' || !strcmp(name, \"HEAD\"))\n \t\treturn -1;\n \n \tstrbuf_reset(sb);\ndiff --git a/t/t7004-tag.sh b/t/t7004-tag.sh\nindex b1316e62f4..34d34b0dfb 100755\n--- a/t/t7004-tag.sh\n+++ b/t/t7004-tag.sh\n@@ -91,6 +91,18 @@ test_expect_success 'creating a tag using default HEAD should succeed' '\n \ttest_must_fail git reflog exists refs/tags/mytag\n '\n \n+test_expect_success 'HEAD is forbidden as a tagname' '\n+\ttest_when_finished \"git update-ref --no-deref -d refs/tags/HEAD || :\" &&\n+\ttest_must_fail git tag HEAD &&\n+\ttest_must_fail git tag -a -m \"useless\" HEAD\n+'\n+\n+test_expect_success '\"git tag\" can remove a tag named HEAD' '\n+\ttest_when_finished \"git update-ref --no-deref -d refs/tags/HEAD || :\" &&\n+\tgit update-ref refs/tags/HEAD HEAD &&\n+\tgit tag -d HEAD\n+'\n+\n test_expect_success 'creating a tag with --create-reflog should create reflog' '\n \tgit log -1 \\\n \t\t--format=\"format:tag: tagging %h (%s, %cd)%n\" \\\n-- \n2.47.1-515-g5132b7d2ef\n\n"}]}