Git:打标签分支并从标签创建新分支

3

我有一些自动化系统来标记我的分支。我这样做是为了避免大量分支的出现。

但有时候,之前标记的一些分支需要我进行额外的更改。

有没有合适的方法来处理这些标记?

据我所知,我可以像这样从这些标记创建新分支:

git checkout -b <new_branch_name> <tag>

看起来很好,但是...

1) 我正在调用与原始分支名称相同的标签。

2) 我想创建一个与原来命名相同的分支。

这是不可能的,因为它会发生错误:

warning: refname '<branch_name>' is ambiguous.
fatal: A branch named '<branch_name>' already exists.

我想补充的另一件事是,当从它创建一个分支时,我希望删除这个标签。

是否可以使用一些简单的命令来创建分支并找出这个问题?

或者也许有人有类似的流程,并且可以建议更好的解决方案?

2个回答

4
分支或标签名称在某种程度上只是一个短的、易于理解的名称,用于表示你已经看到的那些 Git 巨大且丑陋的哈希 ID 之一。
实际上,像分支名 X 和标签名 X 这样的名称之间几乎没有什么区别。事实上,主要的区别在于:
- 分支 X 的完整名称为 refs/heads/X; - 标签 X 的完整名称为 refs/tags/X; - 通常情况下,标签名不应该更改其所指向的哈希 ID,但是分支名通常会随着时间的推移以明确定义的方式发生更改:它总是指向添加到分支中的最新提交。
实际上,这正是 Git 知道哪些提交属于该分支的方法:分支名 X(实际上是 refs/heads/X)标识了作为该分支末端的特定提交。然后,该提交再引用其父提交,该父提交可能曾经是该分支的末端。父提交引用其自己的父提交,以此类推;这就是该分支的历史记录。
当 Git 向您报告分支名称时,它会去掉前缀 refs/heads/。当它向您报告标签名称时,它会去掉前缀 refs/tags/。(有一些例外情况,但这是通常规则。)
通常情况下,具有简短形式 X 的两个事物是不明智的...并不是因为 Git 会感到困惑,而是因为您会感到困惑。Git 有非常精确(尽管相当复杂)的规则,总是选择一个......但不一定是您所想要的那个。这就是为什么您会收到警告消息的原因。
您可以选择忽略该警告,或更努力地避免它。要更加努力地避免它,请使用 Git 的所谓 “plumbing” 命令。这些命令旨在从脚本中使用,并具有非常可预测和可重复的行为,但它们往往需要大量的输入(这没关系,这是计算机在输入)。创建、删除或更新引用(引用是“分支、标签或其他名称”的花哨长单词)的 plumbing 命令是 git update-ref,并且它要求您每次都拼写出 refs/heads/X 或 refs/tags/X。因此,您可以编写一个脚本,检查 refs/heads/whatever 和/或 refs/tags/whatever 是否存在,并创建或删除 “另一个”。
要查找某个引用是否存在,并获取其值,请使用plumbing命令git rev-parse并拼写出引用的完整名称。如果该名称不存在,则命令将失败(并返回非零状态,通常会打印一条消息,但您可以抑制此消息),如果该名称存在,则会打印出哈希ID。
注意:当您删除一个分支名称时,也会删除其reflog。分支(或任何引用)的reflog包含该引用在过去30到90天左右的所有先前值(数字有点复杂,如果需要可以进行调整)。由于标签通常不移动,它们的reflog(如果有)往往相当无聊:“这个标签始终具有值0f39ca43...”然而,正如我们上面所提到的那样,分支确实会移动,它们的reflog充满了“昨天这个分支指向哪里”等信息。

1
所以,git update-ref refs/heads/v3.2 v3.2; git tag -d v3.2 - jthill
@torek,感谢您的解释。让我总结一下。有几件事情我想澄清:1)您建议为分支和标签使用不同的名称吗?(例如,将feature-123用于分支,将feature-123_tag用于标签);2)您建议使用类似于git rev-parse /refs/tags/feature-123_tag这样的命令来检查标签是否已存在。这正确吗? - smart
@smart:这取决于你想做什么。例如,一个名为git-convert-branch-to-tag的脚本可能会检查其参数是否命名了一个现有的分支和零个现有的标签,然后创建一个标签并删除该分支;一个名为git-convert-tag-to-branch的脚本将检查对应项并执行相应操作。或者,一个名为git-switcheroo的脚本将接受一个名称,并确定它当前是(分支,非标签)还是(非分支,标签),无论现在是哪种情况,都会切换它。将这些命名的脚本放在您的$PATH中,可以让您运行git <whatever>来运行它们。最后, - torek
只需使用refs/tags/feature-123_tag,而不是/refs/tags/feature-123_tag。如果您使用未经限定的名称(例如git rev-parse feature),Git将使用链接文档中的六步过程来确定要使用哪个名称... 除了 git checkout 首先尝试将其用作分支名称(请注意,在六个步骤的过程中,Git首先尝试使用标签名称,因此git checkout与其他Git命令不同意哪个优先级)。 - torek
哦,我看到我在答案中没有链接到gitrevisions。六步流程在这里显示(https://www.kernel.org/pub/software/scm/git/docs/gitrevisions.html),位于页面顶部附近。 - torek

3
您可以执行以下一系列命令来完成此操作:
$ git checkout -b _tmp_<tag name> <tag name>
$ git tag -d <tag name>
$ git checkout -b <tag name>
$ git branch -d _tmp_<tag name>

这将会做以下几件事(按顺序):
  • 创建一个名为_tmp_tag_branch的分支,指向原标签
  • 删除对原标签的引用
  • 创建另一个分支,指向_tmp_tag_branch和原标签
  • 删除_tmp_tag_branch
编辑 为了让生活更轻松,你可以将以下内容添加到你的~/.gitconfig中:
[alias]
    tag2branch = ! sh -c 'git checkout -b _tmp_$1 $1 && git tag -d $1 && git checkout -b $1 && git branch -d _tmp_$1' -

之后,您只需要执行$ git tag2branch <标签名>即可。

注意:此操作仅适用于您的系统和拥有~/.gitconfig文件的用户。


有没有更简单的方法? - smart
也许将它们添加为别名会起作用吗?如果是,我应该编辑答案。 - zeekhuge
你说的别名是什么意思? - smart
修改了答案,请查看。 - zeekhuge

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接