diff options
author | Junio C Hamano <gitster@pobox.com> | 2024-12-16 02:54:26 +0100 |
---|---|---|
committer | Junio C Hamano <gitster@pobox.com> | 2024-12-16 02:54:26 +0100 |
commit | ab738b2f1f35fbc3658ac107e1ad55b21c1e0fbc (patch) | |
tree | 8eb63219f3a49d742357586e6b924254a5adb834 | |
parent | Merge branch 'jk/describe-perf' (diff) | |
parent | tag: "git tag" refuses to use HEAD as a tagname (diff) | |
download | git-ab738b2f1f35fbc3658ac107e1ad55b21c1e0fbc.tar.xz git-ab738b2f1f35fbc3658ac107e1ad55b21c1e0fbc.zip |
Merge branch 'jc/forbid-head-as-tagname'
"git tag" has been taught to refuse to create refs/tags/HEAD
as such a tag will be confusing in the context of UI provided by
the Git Porcelain commands.
* jc/forbid-head-as-tagname:
tag: "git tag" refuses to use HEAD as a tagname
t5604: do not expect that HEAD can be a valid tagname
refs: drop strbuf_ prefix from helpers
refs: move ref name helpers around
-rw-r--r-- | branch.c | 2 | ||||
-rw-r--r-- | builtin/branch.c | 10 | ||||
-rw-r--r-- | builtin/check-ref-format.c | 2 | ||||
-rw-r--r-- | builtin/checkout.c | 2 | ||||
-rw-r--r-- | builtin/merge.c | 2 | ||||
-rw-r--r-- | builtin/tag.c | 13 | ||||
-rw-r--r-- | builtin/worktree.c | 8 | ||||
-rwxr-xr-x | gitweb/gitweb.perl | 2 | ||||
-rw-r--r-- | object-name.c | 36 | ||||
-rw-r--r-- | refs.c | 47 | ||||
-rw-r--r-- | refs.h | 29 | ||||
-rw-r--r-- | strbuf.h | 22 | ||||
-rwxr-xr-x | t/t5604-clone-reference.sh | 6 | ||||
-rwxr-xr-x | t/t7004-tag.sh | 12 |
14 files changed, 106 insertions, 87 deletions
@@ -372,7 +372,7 @@ int read_branch_desc(struct strbuf *buf, const char *branch_name) */ int validate_branchname(const char *name, struct strbuf *ref) { - if (strbuf_check_branch_ref(ref, name)) { + if (check_branch_ref(ref, name)) { int code = die_message(_("'%s' is not a valid branch name"), name); advise_if_enabled(ADVICE_REF_SYNTAX, _("See `man git check-ref-format`")); diff --git a/builtin/branch.c b/builtin/branch.c index 05ba4cd7a6..200909c23e 100644 --- a/builtin/branch.c +++ b/builtin/branch.c @@ -257,7 +257,7 @@ static int delete_branches(int argc, const char **argv, int force, int kinds, char *target = NULL; int flags = 0; - strbuf_branchname(&bname, argv[i], allowed_interpret); + copy_branchname(&bname, argv[i], allowed_interpret); free(name); name = mkpathdup(fmt, bname.buf); @@ -579,7 +579,7 @@ static void copy_or_rename_branch(const char *oldname, const char *newname, int int recovery = 0, oldref_usage = 0; struct worktree **worktrees = get_worktrees(); - if (strbuf_check_branch_ref(&oldref, oldname)) { + if (check_branch_ref(&oldref, oldname)) { /* * Bad name --- this could be an attempt to rename a * ref that we used to allow to be created by accident. @@ -896,7 +896,7 @@ int cmd_branch(int argc, die(_("cannot give description to detached HEAD")); branch_name = head; } else if (argc == 1) { - strbuf_branchname(&buf, argv[0], INTERPRET_BRANCH_LOCAL); + copy_branchname(&buf, argv[0], INTERPRET_BRANCH_LOCAL); branch_name = buf.buf; } else { die(_("cannot edit description of more than one branch")); @@ -939,7 +939,7 @@ int cmd_branch(int argc, if (!argc) branch = branch_get(NULL); else if (argc == 1) { - strbuf_branchname(&buf, argv[0], INTERPRET_BRANCH_LOCAL); + copy_branchname(&buf, argv[0], INTERPRET_BRANCH_LOCAL); branch = branch_get(buf.buf); } else die(_("too many arguments to set new upstream")); @@ -969,7 +969,7 @@ int cmd_branch(int argc, if (!argc) branch = branch_get(NULL); else if (argc == 1) { - strbuf_branchname(&buf, argv[0], INTERPRET_BRANCH_LOCAL); + copy_branchname(&buf, argv[0], INTERPRET_BRANCH_LOCAL); branch = branch_get(buf.buf); } else die(_("too many arguments to unset upstream")); diff --git a/builtin/check-ref-format.c b/builtin/check-ref-format.c index e86d8ef980..cef1ffe3ce 100644 --- a/builtin/check-ref-format.c +++ b/builtin/check-ref-format.c @@ -42,7 +42,7 @@ static int check_ref_format_branch(const char *arg) int nongit; setup_git_directory_gently(&nongit); - if (strbuf_check_branch_ref(&sb, arg) || + if (check_branch_ref(&sb, arg) || !skip_prefix(sb.buf, "refs/heads/", &name)) die("'%s' is not a valid branch name", arg); printf("%s\n", name); diff --git a/builtin/checkout.c b/builtin/checkout.c index c449558e66..5e5afa0f26 100644 --- a/builtin/checkout.c +++ b/builtin/checkout.c @@ -742,7 +742,7 @@ static void setup_branch_path(struct branch_info *branch) &branch->oid, &branch->refname, 0)) repo_get_oid_committish(the_repository, branch->name, &branch->oid); - strbuf_branchname(&buf, branch->name, INTERPRET_BRANCH_LOCAL); + copy_branchname(&buf, branch->name, INTERPRET_BRANCH_LOCAL); if (strcmp(buf.buf, branch->name)) { free(branch->name); branch->name = xstrdup(buf.buf); diff --git a/builtin/merge.c b/builtin/merge.c index 51038eaca8..e32c99087f 100644 --- a/builtin/merge.c +++ b/builtin/merge.c @@ -498,7 +498,7 @@ static void merge_name(const char *remote, struct strbuf *msg) char *found_ref = NULL; int len, early; - strbuf_branchname(&bname, remote, 0); + copy_branchname(&bname, remote, 0); remote = bname.buf; oidclr(&branch_head, the_repository->hash_algo); diff --git a/builtin/tag.c b/builtin/tag.c index 5e1f904d35..affa14d659 100644 --- a/builtin/tag.c +++ b/builtin/tag.c @@ -447,17 +447,6 @@ static int parse_msg_arg(const struct option *opt, const char *arg, int unset) return 0; } -static int strbuf_check_tag_ref(struct strbuf *sb, const char *name) -{ - if (name[0] == '-') - return -1; - - strbuf_reset(sb); - strbuf_addf(sb, "refs/tags/%s", name); - - return check_refname_format(sb->buf, 0); -} - int cmd_tag(int argc, const char **argv, const char *prefix, @@ -650,7 +639,7 @@ int cmd_tag(int argc, if (repo_get_oid(the_repository, object_ref, &object)) die(_("Failed to resolve '%s' as a valid ref."), object_ref); - if (strbuf_check_tag_ref(&ref, tag)) + if (check_tag_ref(&ref, tag)) die(_("'%s' is not a valid tag name."), tag); if (refs_read_ref(get_main_ref_store(the_repository), ref.buf, &prev)) diff --git a/builtin/worktree.c b/builtin/worktree.c index 5dfc446242..0186d60ab1 100644 --- a/builtin/worktree.c +++ b/builtin/worktree.c @@ -438,7 +438,7 @@ static int add_worktree(const char *path, const char *refname, worktrees = NULL; /* is 'refname' a branch or commit? */ - if (!opts->detach && !strbuf_check_branch_ref(&symref, refname) && + if (!opts->detach && !check_branch_ref(&symref, refname) && refs_ref_exists(get_main_ref_store(the_repository), symref.buf)) { is_branch = 1; if (!opts->force) @@ -605,7 +605,7 @@ static void print_preparing_worktree_line(int detach, fprintf_ln(stderr, _("Preparing worktree (new branch '%s')"), new_branch); } else { struct strbuf s = STRBUF_INIT; - if (!detach && !strbuf_check_branch_ref(&s, branch) && + if (!detach && !check_branch_ref(&s, branch) && refs_ref_exists(get_main_ref_store(the_repository), s.buf)) fprintf_ln(stderr, _("Preparing worktree (checking out '%s')"), branch); @@ -746,7 +746,7 @@ static char *dwim_branch(const char *path, char **new_branch) char *branchname = xstrndup(s, n); struct strbuf ref = STRBUF_INIT; - branch_exists = !strbuf_check_branch_ref(&ref, branchname) && + branch_exists = !check_branch_ref(&ref, branchname) && refs_ref_exists(get_main_ref_store(the_repository), ref.buf); strbuf_release(&ref); @@ -843,7 +843,7 @@ static int add(int ac, const char **av, const char *prefix, new_branch = new_branch_force; if (!opts.force && - !strbuf_check_branch_ref(&symref, new_branch) && + !check_branch_ref(&symref, new_branch) && refs_ref_exists(get_main_ref_store(the_repository), symref.buf)) die_if_checked_out(symref.buf, 0); strbuf_release(&symref); diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index c4e0008d59..b50a1444b2 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -2094,7 +2094,7 @@ sub format_log_line_html { ( # The output of "git describe", e.g. v2.10.0-297-gf6727b0 # or hadoop-20160921-113441-20-g094fb7d - (?<!-) # see strbuf_check_tag_ref(). Tags can't start with - + (?<!-) # see check_tag_ref(). Tags can't start with - [A-Za-z0-9.-]+ (?!\.) # refs can't end with ".", see check_refname_format() -g$regex diff --git a/object-name.c b/object-name.c index c892fbe80a..9f2ae164e4 100644 --- a/object-name.c +++ b/object-name.c @@ -1734,42 +1734,6 @@ int repo_interpret_branch_name(struct repository *r, return -1; } -void strbuf_branchname(struct strbuf *sb, const char *name, unsigned allowed) -{ - int len = strlen(name); - struct interpret_branch_name_options options = { - .allowed = allowed - }; - int used = repo_interpret_branch_name(the_repository, name, len, sb, - &options); - - if (used < 0) - used = 0; - strbuf_add(sb, name + used, len - used); -} - -int strbuf_check_branch_ref(struct strbuf *sb, const char *name) -{ - if (startup_info->have_repository) - strbuf_branchname(sb, name, INTERPRET_BRANCH_LOCAL); - else - strbuf_addstr(sb, name); - - /* - * This splice must be done even if we end up rejecting the - * name; builtin/branch.c::copy_or_rename_branch() still wants - * to see what the name expanded to so that "branch -m" can be - * used as a tool to correct earlier mistakes. - */ - strbuf_splice(sb, 0, 0, "refs/heads/", 11); - - if (*name == '-' || - !strcmp(sb->buf, "refs/heads/HEAD")) - return -1; - - return check_refname_format(sb->buf, 0); -} - void object_context_release(struct object_context *ctx) { free(ctx->path); @@ -698,6 +698,53 @@ static char *substitute_branch_name(struct repository *r, return NULL; } +void copy_branchname(struct strbuf *sb, const char *name, unsigned allowed) +{ + int len = strlen(name); + struct interpret_branch_name_options options = { + .allowed = allowed + }; + int used = repo_interpret_branch_name(the_repository, name, len, sb, + &options); + + if (used < 0) + used = 0; + strbuf_add(sb, name + used, len - used); +} + +int check_branch_ref(struct strbuf *sb, const char *name) +{ + if (startup_info->have_repository) + copy_branchname(sb, name, INTERPRET_BRANCH_LOCAL); + else + strbuf_addstr(sb, name); + + /* + * This splice must be done even if we end up rejecting the + * name; builtin/branch.c::copy_or_rename_branch() still wants + * to see what the name expanded to so that "branch -m" can be + * used as a tool to correct earlier mistakes. + */ + strbuf_splice(sb, 0, 0, "refs/heads/", 11); + + if (*name == '-' || + !strcmp(sb->buf, "refs/heads/HEAD")) + return -1; + + return check_refname_format(sb->buf, 0); +} + +int check_tag_ref(struct strbuf *sb, const char *name) +{ + if (name[0] == '-' || !strcmp(name, "HEAD")) + return -1; + + strbuf_reset(sb); + strbuf_addf(sb, "refs/tags/%s", name); + + return check_refname_format(sb->buf, 0); +} + int repo_dwim_ref(struct repository *r, const char *str, int len, struct object_id *oid, char **ref, int nonfatal_dangling_mark) { @@ -184,6 +184,35 @@ int repo_dwim_log(struct repository *r, const char *str, int len, struct object_ char *repo_default_branch_name(struct repository *r, int quiet); /* + * Copy "name" to "sb", expanding any special @-marks as handled by + * repo_interpret_branch_name(). The result is a non-qualified branch name + * (so "foo" or "origin/master" instead of "refs/heads/foo" or + * "refs/remotes/origin/master"). + * + * Note that the resulting name may not be a syntactically valid refname. + * + * If "allowed" is non-zero, restrict the set of allowed expansions. See + * repo_interpret_branch_name() for details. + */ +void copy_branchname(struct strbuf *sb, const char *name, + unsigned allowed); + +/* + * Like copy_branchname() above, but confirm that the result is + * syntactically valid to be used as a local branch name in refs/heads/. + * + * The return value is "0" if the result is valid, and "-1" otherwise. + */ +int check_branch_ref(struct strbuf *sb, const char *name); + +/* + * Similar for a tag name in refs/tags/. + * + * The return value is "0" if the result is valid, and "-1" otherwise. + */ +int check_tag_ref(struct strbuf *sb, const char *name); + +/* * A ref_transaction represents a collection of reference updates that * should succeed or fail together. * @@ -637,28 +637,6 @@ static inline void strbuf_complete_line(struct strbuf *sb) strbuf_complete(sb, '\n'); } -/* - * Copy "name" to "sb", expanding any special @-marks as handled by - * repo_interpret_branch_name(). The result is a non-qualified branch name - * (so "foo" or "origin/master" instead of "refs/heads/foo" or - * "refs/remotes/origin/master"). - * - * Note that the resulting name may not be a syntactically valid refname. - * - * If "allowed" is non-zero, restrict the set of allowed expansions. See - * repo_interpret_branch_name() for details. - */ -void strbuf_branchname(struct strbuf *sb, const char *name, - unsigned allowed); - -/* - * Like strbuf_branchname() above, but confirm that the result is - * syntactically valid to be used as a local branch name in refs/heads/. - * - * The return value is "0" if the result is valid, and "-1" otherwise. - */ -int strbuf_check_branch_ref(struct strbuf *sb, const char *name); - typedef int (*char_predicate)(char ch); void strbuf_addstr_urlencode(struct strbuf *sb, const char *name, diff --git a/t/t5604-clone-reference.sh b/t/t5604-clone-reference.sh index fa5ca4f522..470bfb610c 100755 --- a/t/t5604-clone-reference.sh +++ b/t/t5604-clone-reference.sh @@ -130,7 +130,7 @@ test_expect_success 'cloning with multiple references drops duplicates' ' test_expect_success 'clone with reference from a tagged repository' ' ( - cd A && git tag -a -m tagged HEAD + cd A && git tag -a -m tagged foo ) && git clone --reference=A A I ' @@ -155,10 +155,10 @@ test_expect_success 'fetch with incomplete alternates' ' git remote add J "file://$base_dir/J" && GIT_TRACE_PACKET=$U.K git fetch J ) && - main_object=$(cd A && git for-each-ref --format="%(objectname)" refs/heads/main) && + main_object=$(git -C A rev-parse --verify refs/heads/main) && test -s "$U.K" && ! grep " want $main_object" "$U.K" && - tag_object=$(cd A && git for-each-ref --format="%(objectname)" refs/tags/HEAD) && + tag_object=$(git -C A rev-parse --verify refs/tags/foo) && ! grep " want $tag_object" "$U.K" ' diff --git a/t/t7004-tag.sh b/t/t7004-tag.sh index b1316e62f4..34d34b0dfb 100755 --- a/t/t7004-tag.sh +++ b/t/t7004-tag.sh @@ -91,6 +91,18 @@ 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 update-ref --no-deref -d refs/tags/HEAD || :" && + test_must_fail git tag HEAD && + test_must_fail git tag -a -m "useless" HEAD +' + +test_expect_success '"git tag" can remove a tag named HEAD' ' + test_when_finished "git update-ref --no-deref -d refs/tags/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" \ |