验证标记是否在 master 分支中完成

Verify if a tag was done in the master branch

在我工作的这个项目中,我们根据标签进行部署。虽然标记必须针对 master 分支完成(在您在那里合并发布之后),但有时有人会错误地针对 dev 或发布分支进行标记,这是不正确的。这会导致几个问题。

在我们的部署脚本中,有一个步骤我们从 git 克隆了一个特定的标签,使用的过程类似于这个问题中描述的过程:Download a specific tag with Git

$ git clone
$ git checkout tags/<tag_name>

我如何修改这个脚本来检查这个标记是否真的是针对 master 分支完成的?如果分支不是主分支,我想停止部署并抛出错误。

谢谢。

简短的回答是你不能。至少不是事后。

您可以做的是创建一个带注释的标签。这会在标签上放置一条消息。 此消息然后可以包含当前签出的分支的分支名称。然后您可以检查此消息是否包含 master.

另一种解决方案是使用 pre-push 提交挂钩来检查是否可以从 master 分支访问任何要推送的新标签。

这两种解决方案都需要在客户端进行设置,因此可能会被愚蠢或恶意的用户回避或禁用。

如果您可以控制 Git 存储库管理器,则可以考虑针对相同的可达性标准进行 pre-receive 挂钩测试。

我的建议是找出使您的 release-tagging 程序自动化的正确方法。

祝你好运!

如果是轻量级标签:

git branch --contains $(git rev-parse your-tag) | grep '^master$'

如果是带注释的标签:

git branch --contains $(git rev-list -n 1 your-annotated-tag) | grep '^master$'

如果标记指向的提交在 master 中,则结果不应为空(您甚至可以测试 grep 的退出代码,returns如果在 master 中则为 0,否则为 1)

正如一些人指出的那样,您的问题只有重新表述才能真正得到解答。这是因为 Git 标记或分支名称仅标识 一个特定的提交。 分支名称的预期效果是标识 提示 分支的提交,它会随着时间的推移而变化,因此它标识的特定提交也会随着时间而变化。标签名称的预期效果是永远标识一个特定的提交,而不更改。因此,如果有人标记 master,将有一些时间解析名称 master 产生提交哈希 H,并解析标记名称 [=81] =]also 生成提交哈希 H:

if test $(git rev-parse master) = $(git rev-parse $tag^{commit}); then
    echo "master and $tag both identify the same commit"
else
    echo "master and $tag identify two different commits"
fi

此特定测试一直有效,直到有人将分支名称master提前,之后它就不再有用了。如果我们按照我通常喜欢在 Whosebug 上绘制 Git 提交图的方式来绘制它,我们可以将其视为:

          tag
           |
           v
...--o--o--H   <-- master
          /
 ...--o--o   <-- develop

目前名称tagmaster都标识提交H,这是一个合并提交。但是,一旦有人在 master 上创建 new 提交,图表就会变成:

          tag
           |
           v
...--o--o--H--I   <-- master
          /
 ...--o--o   <-- develop

现在 master 识别新提交 I,所以执行 rev-parse tag^{commit} 会找到 H 而执行 rev-parse master 会找到 I并且它们不相等,测试将失败。

(我在这里将提交 I 绘制为普通提交,但它可能是带有第二个 parent 的合并提交。如果是这样,请想象第二个 backwards-pointing 行/箭头来自 I,指向其他一些较早的提交。)

有两种形式,回答的问题略有不同。由于分支名称 do 随着时间的推移而移动,我们可以使用 git branch --contains 找到 all 使标记的提交可访问的分支名称,并查看如果其中之一是 master。这将为上述情况给出正确/是的答案。不幸的是,它还会告诉您标签 error 包含在 master 中——这是真的!——对于 这个 图:

          tag
           |
           v
...--o--o--H   <-- master
          /
 ...--o--G   <-- develop
         ^
         |
       error

这是因为标签 error 标识了提交 G,并且提交 G 可从 提交 H (通过跟随 H 的第二个 parent)。事实上,沿着底行的任何标记,指向包含在 develop 分支中的任何提交,标识包含在 master 分支中的提交,因为 当前在 develop 上的每个提交] 也在 master.

(顺便说一句,使用 git rev-parse your-tag 和使用 git rev-list -n 1 your-annotated-tag 之间的区别由使用 git rev-parse $tag^{commit} 涵盖。这里的问题是带注释的标签有一个实际的存储库 object,名称指向的 "annotated tag" 类型;单独使用 git rev-parse your-annotated-tag 会找到 标签 object 而不是它的提交。 ^{commit} 后缀语法在 the gitrevisions documentation 中有描述。)

一种方法来判断任何给定标记指向的提交是否在master的历史记录中,该历史记录仅发生在first-parent链。它不是最漂亮的:git branch --containsgit merge-base --is-ancestor 是查找可达性的常用构建块,但它们都遵循 all parents。要仅跟随 first parents,我们需要使用 git rev-list --first-parent 枚举仅跟随 first [=] 时可从名称 master 访问的所有提交120=]秒。然后我们简单地检查标记的修订是否在该列表中。这是一个糟糕的方法,它清楚地表明我们在做什么:

tagged_commit=$(git rev-parse $tag^{commit}) ||
    fatal "tag $tag does not exist or does not point to a commit object"
found=false
for hash in $(git rev-list --first-parent master); do
    test $hash == $tagged_commit && found=true
done

为了加快速度,最好通过搜索 $tagged_commitgrep 来传输 git rev-list 输出(丢弃 grep 的输出,因为我们只关心状态):

if git rev-list --first-parent master | grep $tagged_commit >/dev/null; then
    echo "$tag points to a commit reachable via first-parent from master"
else
    echo "$tag does not point to a commit reachable via first-parent from master"
fi

例如。 (这里的一个缺陷是 git rev-list 将 运行 一直通过每个可到达的提交;在大型存储库中,这可能需要 。)

git 分支 -a --contains | grep 大师 |剪切-d/-f3

示例: git branch -a --contains v.1.2.3 | grep 大师 |剪切-d/-f3