{"thread":{"id":"55539","subject":"[PATCH 0/3] fast-export, fast-import: let tags specify a different refname","startedAt":"2021-04-22T01:07:39Z","lastAt":"2021-04-22T01:07:47Z","messageCount":4,"participants":["Luke Shumaker"],"isPatch":true,"patchVersion":1,"patchTotal":3},"messages":[{"id":"422653","messageId":"20210422010659.2498280-1-lukeshu@lukeshu.com","threadId":"55539","inReplyTo":null,"subject":"[PATCH 0/3] fast-export, fast-import: let tags specify a different refname","fromName":"Luke Shumaker","fromEmail":"lukeshu@lukeshu.com","sentAt":"2021-04-22T01:06:56Z","receivedAt":"2021-04-22T01:07:39Z","isPatch":true,"sender":{"key":"lukeshu@lukeshu.com","avatar":"https://gravatar.com/avatar/b040e950069ff2e81f026755b364d668aab9d278527aaa2415d0a8845f640bc5?d=mp&s=160"},"body":"From: Luke Shumaker <lukeshu@datawire.io>\n\nAfter receiving positive feedback on the RFC, here's the polished-up\nv1.\n\nThis patchset adds an optional \"refname\" sub-command to fast-import's \"tag\"\ntop-level-command; the stream\n\n    tag foo\n    refname refs/tags/bar\n    ...\n\nwill create a tag at \"refs/tags/bar\" that says \"tag foo\" internally.\nIf there is no \"refname\" line, it will continue with the existing\nbehavior of using \"refs/tags/<tagname>\" as the refname.\n\nThis makes it now-possible for fast-export/fast-import to represent\ntags for which the internal tagname and the refname disagree.  It also\nadds support for tags with refnames that do not begin with\n\"refs/tags/\".  I discuss the motivation for supporting this in the\ncommit message of the 3rd patch.\n\nChanges from the RFC:\n\n - I've flipped it around; the existing `tag` line now specifices the\n   tagname, and the added `refname` line now specifies the refname\n   (rather than having the `tag` line specify the suffix of the\n   refname, and the added `name` line specify the tagname).  This\n   allows for tags whose refnames to not begin with \"refs/tags/\".\n - I've added prose documentation, rather than just updating the BNF.\n   (The wording is based on what Junio suggested on the RFC.)\n - I added tests.\n - After responding to feedback, I ended up with just enough \"moving\n   and renaming things\" noise that in the single commit that for v1 I\n   split that out in to a econd commit.\n - While implementing the tests, I discovered a pre-existing bug with\n   nested tags, so I added a separate third commit to fix that.\n\nThis passes all of the GitHub Actions CI checks, and passes all but\none of the Travis-CI checks; the failing Travis-CI check seems to be\nan unrelated 404 from `apt-get`.\nhttps://github.com/LukeShu/git/runs/2405478827\n\n[1]: https://lore.kernel.org/git/Pine.LNX.4.58.0506221433540.2353@ppc970.osdl.org/\n\nLuke Shumaker (3):\n  fast-export, fast-import: make stylistic changes\n  fast-export: fix bug with nested tags\n  fast-export, fast-import: let tags specify a different refname\n\n Documentation/git-fast-import.txt | 22 +++++++++------\n builtin/fast-export.c             | 47 ++++++++++++++++++++++++++-----\n builtin/fast-import.c             | 47 +++++++++++++++++++------------\n t/t9350-fast-export.sh            | 20 ++++++++++---\n 4 files changed, 98 insertions(+), 38 deletions(-)\n\n-- \n2.31.1\n\nHappy hacking,\n~ Luke Shumaker\n"},{"id":"422654","messageId":"20210422010659.2498280-3-lukeshu@lukeshu.com","threadId":"55539","inReplyTo":"20210422010659.2498280-1-lukeshu@lukeshu.com","subject":"[PATCH 2/3] fast-export: fix bug with nested tags","fromName":"Luke Shumaker","fromEmail":"lukeshu@lukeshu.com","sentAt":"2021-04-22T01:06:58Z","receivedAt":"2021-04-22T01:07:42Z","isPatch":true,"sender":{"key":"lukeshu@lukeshu.com","avatar":"https://gravatar.com/avatar/b040e950069ff2e81f026755b364d668aab9d278527aaa2415d0a8845f640bc5?d=mp&s=160"},"body":"From: Luke Shumaker <lukeshu@datawire.io>\n\nThe t9350-fast-export.sh 'handling nested tags' test takes an annotated\ntag named 'muss' and creates a second annotated tag named 'nested' that\npoints to 'muss'.  As the test observes, fast-export indeed does spit out\na stream that creates a tag named 'nested' that points to another tag.\n\nHowever, the test doesn't do a very thorough job of inspecting the\nresulting tag.  It doesn't notice that the output 'nested' isn't quite the\nsame as the input 'nested'.\n\nThe 'nested' tags are different because the 'muss' tags that they point to\nare different; fast-export accidentally creates the 'muss' tag object as\nsaying \"tag nested\" instead of \"tag muss\".\n\nThis is because of a quirk in how the fast-export walk sets the refname\nfor objects that aren't directly pointed to by an exported ref.  So, fix\nthe bug by getting the tagname from the tag object itself, rather than\nfrom the refname.\n\nSigned-off-by: Luke Shumaker <lukeshu@datawire.io>\n---\n builtin/fast-export.c  | 28 +++++++++++++++++++++++++---\n t/t9350-fast-export.sh |  9 ++++++++-\n 2 files changed, 33 insertions(+), 4 deletions(-)\n\ndiff --git a/builtin/fast-export.c b/builtin/fast-export.c\nindex e697f87172..2bf83fe52e 100644\n--- a/builtin/fast-export.c\n+++ b/builtin/fast-export.c\n@@ -550,6 +550,21 @@ static const char *anonymize_refname(const char *refname)\n \treturn anon.buf;\n }\n \n+static const char *anonymize_tagname(size_t tagname_len, const char *tagname)\n+{\n+\t/*\n+\t * Use anonymize_refname internally, so that the anonymization\n+\t * is consistent between a tag's refname and its internal\n+\t * tagname (if they were consistent to begin with, anyway).\n+\t */\n+\tstatic struct strbuf as_refname = STRBUF_INIT;\n+\n+\tstrbuf_reset(&as_refname);\n+\tstrbuf_addf(&as_refname, \"refs/tags/%.*s\", (int)tagname_len, tagname);\n+\n+\treturn anonymize_refname(as_refname.buf) + strlen(\"refs/tags/\");\n+}\n+\n /*\n  * We do not even bother to cache commit messages, as they are unlikely\n  * to be repeated verbatim, and it is not that interesting when they are.\n@@ -775,6 +790,7 @@ static void handle_tag(const char *refname, struct tag *tag)\n \tconst char *message;\n \tsize_t message_size = 0;\n \tconst char *tagname;\n+\tsize_t tagname_len;\n \tconst char *tagger, *tagger_end;\n \tstruct object *tagged;\n \tint tagged_mark;\n@@ -804,6 +820,12 @@ static void handle_tag(const char *refname, struct tag *tag)\n \t\tmessage_size = strlen(message);\n \t}\n \n+\ttagname = memmem(buf, message ? message - buf : size, \"\\ntag \", 5);\n+\tif (!tagname)\n+\t\tdie(\"malformed tag %s\", oid_to_hex(&tag->object.oid));\n+\ttagname += 5;\n+\ttagname_len = (size_t)(strchrnul(tagname, '\\n') - tagname);\n+\n \ttagger = memmem(buf, message ? message - buf : size, \"\\ntagger \", 8);\n \tif (!tagger) {\n \t\tif (fake_missing_tagger)\n@@ -821,6 +843,8 @@ static void handle_tag(const char *refname, struct tag *tag)\n \n \tif (anonymize) {\n \t\trefname = anonymize_refname(refname);\n+\t\ttagname = anonymize_tagname(tagname_len, tagname);\n+\t\ttagname_len = strlen(tagname);\n \t\tif (message) {\n \t\t\tstatic struct hashmap tags;\n \t\t\tmessage = anonymize_str(&tags, anonymize_tag,\n@@ -890,9 +914,7 @@ static void handle_tag(const char *refname, struct tag *tag)\n \t\tprintf(\"reset %s\\nfrom %s\\n\\n\",\n \t\t       refname, oid_to_hex(&null_oid));\n \t}\n-\ttagname = refname;\n-\tskip_prefix(tagname, \"refs/tags/\", &tagname);\n-\tprintf(\"tag %s\\n\", tagname);\n+\tprintf(\"tag %.*s\\n\", (int)tagname_len, tagname);\n \tif (mark_tags) {\n \t\tmark_next_object(&tag->object);\n \t\tprintf(\"mark :%\"PRIu32\"\\n\", last_idnum);\ndiff --git a/t/t9350-fast-export.sh b/t/t9350-fast-export.sh\nindex 409b48e244..0bcc1bd54e 100755\n--- a/t/t9350-fast-export.sh\n+++ b/t/t9350-fast-export.sh\n@@ -572,10 +572,17 @@ test_expect_success 'handling tags of blobs' '\n \n test_expect_success 'handling nested tags' '\n \tgit tag -a -m \"This is a nested tag\" nested muss &&\n+\tNESTED=$(git rev-parse --verify nested) &&\n \tgit fast-export --mark-tags nested >output &&\n \tgrep \"^from $ZERO_OID$\" output &&\n \tgrep \"^tag nested$\" output >tag_lines &&\n-\ttest_line_count = 2 tag_lines\n+\ttest_line_count = 1 tag_lines &&\n+\trm -rf new &&\n+\tmkdir new &&\n+\tgit --git-dir=new/.git init &&\n+\t(cd new &&\n+\t git fast-import &&\n+\t test $NESTED = $(git rev-parse --verify refs/tags/nested)) <output\n '\n \n test_expect_success 'directory becomes symlink'        '\n-- \n2.31.1\n\n"},{"id":"422655","messageId":"20210422010659.2498280-2-lukeshu@lukeshu.com","threadId":"55539","inReplyTo":"20210422010659.2498280-1-lukeshu@lukeshu.com","subject":"[PATCH 1/3] fast-export, fast-import: make stylistic changes","fromName":"Luke Shumaker","fromEmail":"lukeshu@lukeshu.com","sentAt":"2021-04-22T01:06:57Z","receivedAt":"2021-04-22T01:07:44Z","isPatch":true,"sender":{"key":"lukeshu@lukeshu.com","avatar":"https://gravatar.com/avatar/b040e950069ff2e81f026755b364d668aab9d278527aaa2415d0a8845f640bc5?d=mp&s=160"},"body":"From: Luke Shumaker <lukeshu@datawire.io>\n\nIn order to keep the diffs in the next two commits small easy-to-read,\ngo ahead and make some essentially stylistic changes to fast-export and\nfast-import.  Rename some variables and struct members, move some code\narround, introduce new temporary variables for clarity.  That sort of\nthing.\n\nSigned-off-by: Luke Shumaker <lukeshu@datawire.io>\n---\n builtin/fast-export.c | 19 ++++++++++++-------\n builtin/fast-import.c | 41 +++++++++++++++++++++++------------------\n 2 files changed, 35 insertions(+), 25 deletions(-)\n\ndiff --git a/builtin/fast-export.c b/builtin/fast-export.c\nindex 85a76e0ef8..e697f87172 100644\n--- a/builtin/fast-export.c\n+++ b/builtin/fast-export.c\n@@ -767,13 +767,15 @@ static void handle_tail(struct object_array *commits, struct rev_info *revs,\n \t}\n }\n \n-static void handle_tag(const char *name, struct tag *tag)\n+static void handle_tag(const char *refname, struct tag *tag)\n {\n \tunsigned long size;\n \tenum object_type type;\n \tchar *buf;\n-\tconst char *tagger, *tagger_end, *message;\n+\tconst char *message;\n \tsize_t message_size = 0;\n+\tconst char *tagname;\n+\tconst char *tagger, *tagger_end;\n \tstruct object *tagged;\n \tint tagged_mark;\n \tstruct commit *p;\n@@ -795,11 +797,13 @@ static void handle_tag(const char *name, struct tag *tag)\n \tbuf = read_object_file(&tag->object.oid, &type, &size);\n \tif (!buf)\n \t\tdie(\"could not read tag %s\", oid_to_hex(&tag->object.oid));\n+\n \tmessage = memmem(buf, size, \"\\n\\n\", 2);\n \tif (message) {\n \t\tmessage += 2;\n \t\tmessage_size = strlen(message);\n \t}\n+\n \ttagger = memmem(buf, message ? message - buf : size, \"\\ntagger \", 8);\n \tif (!tagger) {\n \t\tif (fake_missing_tagger)\n@@ -816,7 +820,7 @@ static void handle_tag(const char *name, struct tag *tag)\n \t}\n \n \tif (anonymize) {\n-\t\tname = anonymize_refname(name);\n+\t\trefname = anonymize_refname(refname);\n \t\tif (message) {\n \t\t\tstatic struct hashmap tags;\n \t\t\tmessage = anonymize_str(&tags, anonymize_tag,\n@@ -870,7 +874,7 @@ static void handle_tag(const char *name, struct tag *tag)\n \t\t\t\tp = rewrite_commit((struct commit *)tagged);\n \t\t\t\tif (!p) {\n \t\t\t\t\tprintf(\"reset %s\\nfrom %s\\n\\n\",\n-\t\t\t\t\t       name, oid_to_hex(&null_oid));\n+\t\t\t\t\t       refname, oid_to_hex(&null_oid));\n \t\t\t\t\tfree(buf);\n \t\t\t\t\treturn;\n \t\t\t\t}\n@@ -884,10 +888,11 @@ static void handle_tag(const char *name, struct tag *tag)\n \n \tif (tagged->type == OBJ_TAG) {\n \t\tprintf(\"reset %s\\nfrom %s\\n\\n\",\n-\t\t       name, oid_to_hex(&null_oid));\n+\t\t       refname, oid_to_hex(&null_oid));\n \t}\n-\tskip_prefix(name, \"refs/tags/\", &name);\n-\tprintf(\"tag %s\\n\", name);\n+\ttagname = refname;\n+\tskip_prefix(tagname, \"refs/tags/\", &tagname);\n+\tprintf(\"tag %s\\n\", tagname);\n \tif (mark_tags) {\n \t\tmark_next_object(&tag->object);\n \t\tprintf(\"mark :%\"PRIu32\"\\n\", last_idnum);\ndiff --git a/builtin/fast-import.c b/builtin/fast-import.c\nindex 3afa81cf9a..43287f929f 100644\n--- a/builtin/fast-import.c\n+++ b/builtin/fast-import.c\n@@ -127,7 +127,7 @@ struct branch {\n \n struct tag {\n \tstruct tag *next_tag;\n-\tconst char *name;\n+\tconst char *refname;\n \tunsigned int pack_id;\n \tstruct object_id oid;\n };\n@@ -375,7 +375,7 @@ static void write_crash_report(const char *err)\n \t\tfor (tg = first_tag; tg; tg = tg->next_tag) {\n \t\t\tfputs(oid_to_hex(&tg->oid), rpt);\n \t\t\tfputc(' ', rpt);\n-\t\t\tfputs(tg->name, rpt);\n+\t\t\tfputs(tg->refname, rpt);\n \t\t\tfputc('\\n', rpt);\n \t\t}\n \t}\n@@ -1654,7 +1654,6 @@ static void dump_tags(void)\n {\n \tstatic const char *msg = \"fast-import\";\n \tstruct tag *t;\n-\tstruct strbuf ref_name = STRBUF_INIT;\n \tstruct strbuf err = STRBUF_INIT;\n \tstruct ref_transaction *transaction;\n \n@@ -1664,10 +1663,7 @@ static void dump_tags(void)\n \t\tgoto cleanup;\n \t}\n \tfor (t = first_tag; t; t = t->next_tag) {\n-\t\tstrbuf_reset(&ref_name);\n-\t\tstrbuf_addf(&ref_name, \"refs/tags/%s\", t->name);\n-\n-\t\tif (ref_transaction_update(transaction, ref_name.buf,\n+\t\tif (ref_transaction_update(transaction, t->refname,\n \t\t\t\t\t   &t->oid, NULL, 0, msg, &err)) {\n \t\t\tfailure |= error(\"%s\", err.buf);\n \t\t\tgoto cleanup;\n@@ -1678,7 +1674,6 @@ static void dump_tags(void)\n \n  cleanup:\n \tref_transaction_free(transaction);\n-\tstrbuf_release(&ref_name);\n \tstrbuf_release(&err);\n }\n \n@@ -2783,6 +2778,8 @@ static void parse_new_commit(const char *arg)\n static void parse_new_tag(const char *arg)\n {\n \tstatic struct strbuf msg = STRBUF_INIT;\n+\tstatic struct strbuf refname = STRBUF_INIT;\n+\tchar *tagname;\n \tconst char *from;\n \tchar *tagger;\n \tstruct branch *s;\n@@ -2792,17 +2789,15 @@ static void parse_new_tag(const char *arg)\n \tenum object_type type;\n \tconst char *v;\n \n-\tt = mem_pool_alloc(&fi_mem_pool, sizeof(struct tag));\n-\tmemset(t, 0, sizeof(struct tag));\n-\tt->name = mem_pool_strdup(&fi_mem_pool, arg);\n-\tif (last_tag)\n-\t\tlast_tag->next_tag = t;\n-\telse\n-\t\tfirst_tag = t;\n-\tlast_tag = t;\n+\ttagname = xstrdup(arg);\n+\n \tread_next_command();\n \tparse_mark();\n \n+\tstrbuf_reset(&refname);\n+\tstrbuf_addstr(&refname, \"refs/tags/\");\n+\tstrbuf_addstr(&refname, tagname);\n+\n \t/* from ... */\n \tif (!skip_prefix(command_buf.buf, \"from \", &from))\n \t\tdie(\"Expected from command, got %s\", command_buf.buf);\n@@ -2850,13 +2845,23 @@ static void parse_new_tag(const char *arg)\n \t\t    \"object %s\\n\"\n \t\t    \"type %s\\n\"\n \t\t    \"tag %s\\n\",\n-\t\t    oid_to_hex(&oid), type_name(type), t->name);\n+\t\t    oid_to_hex(&oid), type_name(type), tagname);\n \tif (tagger)\n \t\tstrbuf_addf(&new_data,\n \t\t\t    \"tagger %s\\n\", tagger);\n \tstrbuf_addch(&new_data, '\\n');\n \tstrbuf_addbuf(&new_data, &msg);\n \tfree(tagger);\n+\tfree(tagname);\n+\n+\tt = mem_pool_alloc(&fi_mem_pool, sizeof(struct tag));\n+\tmemset(t, 0, sizeof(struct tag));\n+\tt->refname = mem_pool_strdup(&fi_mem_pool, refname.buf);\n+\tif (last_tag)\n+\t\tlast_tag->next_tag = t;\n+\telse\n+\t\tfirst_tag = t;\n+\tlast_tag = t;\n \n \tif (store_object(OBJ_TAG, &new_data, NULL, &t->oid, next_mark))\n \t\tt->pack_id = MAX_PACK_ID;\n@@ -2895,7 +2900,7 @@ static void parse_reset_branch(const char *arg)\n \t\t */\n \t\tstruct tag *t, *prev = NULL;\n \t\tfor (t = first_tag; t; t = t->next_tag) {\n-\t\t\tif (!strcmp(t->name, tag_name))\n+\t\t\tif (!strcmp(t->refname, b->name))\n \t\t\t\tbreak;\n \t\t\tprev = t;\n \t\t}\n-- \n2.31.1\n\n"},{"id":"422656","messageId":"20210422010659.2498280-4-lukeshu@lukeshu.com","threadId":"55539","inReplyTo":"20210422010659.2498280-1-lukeshu@lukeshu.com","subject":"[PATCH 3/3] fast-export, fast-import: let tags specify a different refname","fromName":"Luke Shumaker","fromEmail":"lukeshu@lukeshu.com","sentAt":"2021-04-22T01:06:59Z","receivedAt":"2021-04-22T01:07:47Z","isPatch":true,"sender":{"key":"lukeshu@lukeshu.com","avatar":"https://gravatar.com/avatar/b040e950069ff2e81f026755b364d668aab9d278527aaa2415d0a8845f640bc5?d=mp&s=160"},"body":"From: Luke Shumaker <lukeshu@datawire.io>\n\nA tag object contains the tag-name in the object, and is also pointed\nto by a ref named 'refs/tags/{tag-name}'.  It's possible to end up\nwith a tag for which the internal name and the refname disagree.\n\nThis \"shouldn't\" happen, but sometimes it can:\n\n - In the \"the coolest merge ever\"[1], if Linus had wanted to import\n   existing tags from Paul's gitk repo, he'd likely have wanted to give\n   them a `gitk/` or `gitk-` prefix; you don't want gitk v0.0.1 to\n   appear to be git v0.0.1, so when importing it, you'd rename the tag\n   to `gitk/v0.0.1`.  (Less hypothetically, in-the-wild, my employer's\n   repo has _several_ such merges/imports, where the tags from each repo\n   were given a prefix.)\n - Sometimes tags are \"archived\" to something like\n   \"refs/archived-tags/foo\".\n\nThat'd work fine if they're lightweight tags, but if they're annotated\ntags, then after the rename the internal name in the tag object\n(`v0.0.1`) is now different than the refname (`gitk/v0.0.1`).  Which\nis still mostly fine, since not too many tools care if the internal\nname and the refname disagree.\n\nBut, fast-export/fast-import are tools that do care: it's currently\nimpossible to represent these tags in a fast-import stream.\n\nThis patch adds an optional \"refname\" sub-command to fast-import's \"tag\"\ntop-level-command, the stream\n\n    tag foo\n    refname refs/tags/bar\n    ...\n\nwill create a tag at \"refs/tags/bar\" that says \"tag foo\" internally.\n\n[1]: https://lore.kernel.org/git/Pine.LNX.4.58.0506221433540.2353@ppc970.osdl.org/\n\nSigned-off-by: Luke Shumaker <lukeshu@datawire.io>\n---\n Documentation/git-fast-import.txt | 22 +++++++++++++---------\n builtin/fast-export.c             |  6 ++++++\n builtin/fast-import.c             | 10 ++++++++--\n t/t9350-fast-export.sh            | 11 ++++++++---\n 4 files changed, 35 insertions(+), 14 deletions(-)\n\ndiff --git a/Documentation/git-fast-import.txt b/Documentation/git-fast-import.txt\nindex 39cfa05b28..8c1756bdad 100644\n--- a/Documentation/git-fast-import.txt\n+++ b/Documentation/git-fast-import.txt\n@@ -822,24 +822,28 @@ Creates an annotated tag referring to a specific commit.  To create\n lightweight (non-annotated) tags see the `reset` command below.\n \n ....\n-\t'tag' SP <name> LF\n+\t'tag' SP <tagname> LF\n \tmark?\n+\t('refname' SP <refname> LF)?\n \t'from' SP <commit-ish> LF\n \toriginal-oid?\n \t'tagger' (SP <name>)? SP LT <email> GT SP <when> LF\n \tdata\n ....\n \n-where `<name>` is the name of the tag to create.\n+where `<tagname>` is the name of the tag that is stored in the tag\n+object itself.  The value of `<tagname>` must be a valid for inclusion\n+as part of a refname in Git and therefore may contain forward slashes.\n+As `LF` is not valid in a Git refname, no quoting or escaping syntax\n+is supported here.\n \n-Tag names are automatically prefixed with `refs/tags/` when stored\n-in Git, so importing the CVS branch symbol `RELENG-1_0-FINAL` would\n-use just `RELENG-1_0-FINAL` for `<name>`, and fast-import will write the\n-corresponding ref as `refs/tags/RELENG-1_0-FINAL`.\n+The `refname` command controls where in the ref hierarchy to put the\n+reference that points to the resulting tag object.  If `refname` is\n+not given, then `refs/tags/<tagname>` is used.\n \n-The value of `<name>` must be a valid refname in Git and therefore\n-may contain forward slashes.  As `LF` is not valid in a Git refname,\n-no quoting or escaping syntax is supported here.\n+So, for example, importing the CVS branch symbol `RELENG-1_0-FINAL`\n+would use just `RELENG-1_0-FINAL` for `<tagname>`, and fast-import\n+will write the corresponding ref as `refs/tags/RELENG-1_0-FINAL`.\n \n The `from` command is the same as in the `commit` command; see\n above for details.\ndiff --git a/builtin/fast-export.c b/builtin/fast-export.c\nindex 2bf83fe52e..de04aaf241 100644\n--- a/builtin/fast-export.c\n+++ b/builtin/fast-export.c\n@@ -784,6 +784,7 @@ static void handle_tail(struct object_array *commits, struct rev_info *revs,\n \n static void handle_tag(const char *refname, struct tag *tag)\n {\n+\tstatic struct strbuf autorefname = STRBUF_INIT;\n \tunsigned long size;\n \tenum object_type type;\n \tchar *buf;\n@@ -910,6 +911,9 @@ static void handle_tag(const char *refname, struct tag *tag)\n \t\t}\n \t}\n \n+\tstrbuf_reset(&autorefname);\n+\tstrbuf_addf(&autorefname, \"refs/tags/%.*s\", (int)tagname_len, tagname);\n+\n \tif (tagged->type == OBJ_TAG) {\n \t\tprintf(\"reset %s\\nfrom %s\\n\\n\",\n \t\t       refname, oid_to_hex(&null_oid));\n@@ -919,6 +923,8 @@ static void handle_tag(const char *refname, struct tag *tag)\n \t\tmark_next_object(&tag->object);\n \t\tprintf(\"mark :%\"PRIu32\"\\n\", last_idnum);\n \t}\n+\tif (strcmp(refname, autorefname.buf))\n+\t\tprintf(\"refname %s\\n\", refname);\n \tif (tagged_mark)\n \t\tprintf(\"from :%d\\n\", tagged_mark);\n \telse\ndiff --git a/builtin/fast-import.c b/builtin/fast-import.c\nindex 43287f929f..49e5a0c159 100644\n--- a/builtin/fast-import.c\n+++ b/builtin/fast-import.c\n@@ -2794,9 +2794,15 @@ static void parse_new_tag(const char *arg)\n \tread_next_command();\n \tparse_mark();\n \n+\t/* refname ... */\n \tstrbuf_reset(&refname);\n-\tstrbuf_addstr(&refname, \"refs/tags/\");\n-\tstrbuf_addstr(&refname, tagname);\n+\tif (skip_prefix(command_buf.buf, \"refname \", &v)) {\n+\t\tstrbuf_addstr(&refname, v);\n+\t\tread_next_command();\n+\t} else {\n+\t\tstrbuf_addstr(&refname, \"refs/tags/\");\n+\t\tstrbuf_addstr(&refname, tagname);\n+\t}\n \n \t/* from ... */\n \tif (!skip_prefix(command_buf.buf, \"from \", &from))\ndiff --git a/t/t9350-fast-export.sh b/t/t9350-fast-export.sh\nindex 0bcc1bd54e..089c0c0a24 100755\n--- a/t/t9350-fast-export.sh\n+++ b/t/t9350-fast-export.sh\n@@ -34,6 +34,9 @@ test_expect_success 'setup' '\n \tgit commit -m sitzt file2 &&\n \ttest_tick &&\n \tgit tag -a -m valentin muss &&\n+\tgit tag -a -m joseph pure &&\n+\tgit update-ref refs/english-tags/rein refs/tags/pure &&\n+\tgit update-ref -d refs/tags/pure &&\n \tgit merge -s ours main\n \n '\n@@ -42,6 +45,7 @@ test_expect_success 'fast-export | fast-import' '\n \n \tMAIN=$(git rev-parse --verify main) &&\n \tREIN=$(git rev-parse --verify rein) &&\n+\tPURE=$(git rev-parse --verify refs/english-tags/rein) &&\n \tWER=$(git rev-parse --verify wer) &&\n \tMUSS=$(git rev-parse --verify muss) &&\n \tmkdir new &&\n@@ -51,6 +55,7 @@ test_expect_success 'fast-export | fast-import' '\n \t git fast-import &&\n \t test $MAIN = $(git rev-parse --verify refs/heads/main) &&\n \t test $REIN = $(git rev-parse --verify refs/tags/rein) &&\n+\t test $PURE = $(git rev-parse --verify refs/english-tags/rein) &&\n \t test $WER = $(git rev-parse --verify refs/heads/wer) &&\n \t test $MUSS = $(git rev-parse --verify refs/tags/muss)) <actual\n \n@@ -359,7 +364,7 @@ test_expect_success 'fast-export | fast-import when main is tagged' '\n \n \tgit tag -m msg last &&\n \tgit fast-export -C -C --signed-tags=strip --all > output &&\n-\ttest $(grep -c \"^tag \" output) = 3\n+\ttest $(grep -c \"^tag \" output) = 4\n \n '\n \n@@ -374,11 +379,11 @@ test_expect_success 'cope with tagger-less tags' '\n \tTAG=$(git hash-object -t tag -w tag-content) &&\n \tgit update-ref refs/tags/sonnenschein $TAG &&\n \tgit fast-export -C -C --signed-tags=strip --all > output &&\n-\ttest $(grep -c \"^tag \" output) = 4 &&\n+\ttest $(grep -c \"^tag \" output) = 5 &&\n \t! grep \"Unspecified Tagger\" output &&\n \tgit fast-export -C -C --signed-tags=strip --all \\\n \t\t--fake-missing-tagger > output &&\n-\ttest $(grep -c \"^tag \" output) = 4 &&\n+\ttest $(grep -c \"^tag \" output) = 5 &&\n \tgrep \"Unspecified Tagger\" output\n \n '\n-- \n2.31.1\n\n"}]}