把仓库固定指向一个具体提交的带名字把手。

git tag 把一次发布固到提交

发布标签为某个源码版本提供一个容易交流的名字。它与分支不同,创建新提交时不会自动前进。但 Git 并没有从物理上禁止标签变化:拥有相应权限的人仍能替换或删除标签引用。可靠的发布约定来自几件事共同作用:记录对象标识、坚持发布名称不可变,以及限制谁能够更新发布引用。不能仅凭“用了标签”就断言任何人拿到的代码一定相同。 两种标签 轻量标签是直接指向某个对象的引用,通常指向提交。附注标签则指向单独的标签对象,其中保存目标、打标签者身份、时间、说明,以及可选的签名。打标签的人并不一定是提交作者;有了这些元数据,也不意味着附注已经经过密码学签名。发布记录需要说明谁作出发布决定,而不是把提交作者与发布者混为一谈。 对于附注标签,git rev-parse refs/tags/v2.4.1 得到标签对象标识,git rev-parse 'refs/tags/v2.4.1^{commit}' 则解引用到目标提交。当说明或签名身份也是发布证据的一部分时,应同时记录二者。只比较提交会漏掉标签留言或签名者变化;只比较可读名称,则连源码是否一致也无法确定。对象标识与名称各自承担不同的核对职责。 为什么给发布签名 git tag -v 使用配置的签名后端及本地信任材料验证签名标签。密码学验证成功之后,还需要确认该密钥确实属于项目发布策略认可的人。一个陌生人的有效签名,并不能证明维护者批准了这次发布。密钥的来源、授权范围和撤销状态,都应由实际发布流程明确管理,而不是靠命令里出现“成功”两个字代替。 源码标签有签名,也不能直接证明下载的二进制就是从该源码构建的。制品校验和、构建来源记录及构建过程,需要补上这条独立关系。另外,不要把验签命令接到 head 后面,再依赖管道最后一项的退出码:只截取几行输出可能掩盖验签失败。发布检查应保留并判断验签命令自身的结果,显示多少行只是界面选择。 移动要格外小心 修正尚未分享的本地标签,与替换消费者已经获取的标签,是两种不同操作。标签一旦分发,应优先使用新版本名称,并记录旧版本被撤回的原因。修补安全缺陷也不要求重写旧标签;保留原引用反而有助于识别受影响部署。服务端的引用保护应阻止未经授权的发布标签更新与删除,不能只靠客户端的习惯维持这个约定。 如果特殊情况下确实批准替换,应公布准确的新旧对象标识,并只协调那个明确的引用。把所有标签一起强制获取,会覆盖其他无关的本地引用,不应成为常规修复建议。消费者需要知道自己持有的是哪一个对象,不能让两个不同提交在没有说明的情况下继续共享同一个发布名称。 坏掉的样子 latest 这类浮动名称可能在两次构建之间解析到不同提交。即便界面保留方便阅读的名字,也应为每个制品和每次部署记录真正解析到的提交标识。同名不保证两次构建一致,而固定提交也不会自动固定外部依赖或编译器版本。源码定位与可复现构建有关,却不能互相替代。 另一种故障是发布标签只存在于某个本地克隆。普通分支推送不一定传输它;--follow-tags 处理的是从推送提交可达、符合条件的附注标签,不是本地全部标签。应使用 git ls-remote --tags 核对准确的远端引用及对象标识,不能对版本号作模糊子串匹配,否则可能把另一个版本当成目标。远端可见只能证明分发,不能证明构建正确或签名可信。 快速验证 用 Python 3 及 Git 运行下面的实验。它创建独立临时仓库,使用示例身份,为演示明确关闭签名,并且不配置远端。所有提交和标签都位于这个临时仓库,不会给当前项目打标签;退出后由临时目录管理器清理。命令通过参数列表调用,不需要拼接或解释来自外部的 Shell 文本。 import os import subprocess import tempfile env = {key: value for key, value in os.environ.items() if not key.startswith("GIT_")} env.update(GIT_CONFIG_NOSYSTEM="1", GIT_CONFIG_GLOBAL=os.devnull) with tempfile.TemporaryDirectory(prefix="git-tag-demo-") as directory: def git(*args, check=True): return subprocess.run( ["git", "-c", "user.name=Tag Example", "-c", "user.email=tags@example.invalid", "-c", "commit.gpgsign=false", "-c", "tag.gpgsign=false", "-c", "core.hooksPath=/dev/null", *args], cwd=directory, env=env, text=True, capture_output=True, check=check, ) git("init", "-q") git("commit", "--allow-empty", "-qm", "first") first = git("rev-parse", "HEAD").stdout.strip() git("tag", "-a", "demo-v1", "-m", "example release") git("tag", "demo-bookmark") print("annotated:", git("cat-file", "-t", "demo-v1").stdout.strip()) print("lightweight:", git("cat-file", "-t", "demo-bookmark").stdout.strip()) git("commit", "--allow-empty", "-qm", "second") target = git("rev-parse", "demo-v1^{commit}").stdout.strip() print("tag-still-first:", target == first) print("head-moved:", git("rev-parse", "HEAD").stdout.strip() != first) verification = git("tag", "-v", "demo-v1", check=False) print("unsigned-rejected:", verification.returncode != 0) 开头应分别输出对象类型 tag 和 commit,随后三个判断都为真。新提交让分支前进,但标签仍指向第一个提交;未签名的附注会验签失败。这个实验有意不把“标签存在”冒充“可信签名”或“远端已经发布”:后两项仍需要项目实际认可的密钥策略,以及对真实远端引用的准确检查。 ...

六月 11, 2027 · 1 分钟 · XB Field Notes