Git tag 也标记所有以前的提交

Git tag tags all the previous commits as well

目前我正在尝试让我的库在 github 上可用。有两个版本:0.10.2。我知道 pip 可以通过它在 git 上的标签找到一个包版本,所以我想给它打标签。

当我这样做时:

git add .
git commit -m "msg1"
git tag -a 0.1 -m "lib v0.1"
git push origin master --tag 0.1

第一次提交被标记为 0.1

但是当我更改代码中的某些内容时,我所做的几乎相同:

git add .
git commit -m "msg2"
git tag -a 0.2 -m "lib v0.2"
git push origin master --tag 0.2

最后一次提交标记为 0.2,但第一次提交同时标记为 0.2 和 0.1。难道我做错了什么?还是它应该是这样的?

@编辑。这是它在我的 git:

上的样子

第一次提交:

第二次提交:

Releases 选项卡:

这只是基于 github 在其 UI 中显示标签的方式造成的误解。当 git 在提交上显示标签时,这并不一定意味着该提交被标记为该版本。要查看 github 中的实际标签,您可以单击 "Releases" 选项卡。

TL;DR

一切都很好

GitHub 试图向 GitHub 用户隐藏很多 Git 的复杂性。我认为这是一个错误。虽然 Git 是出了名的用户不友好(参见 "10 things I hate about Git" or xkcd #1597),但确实需要 一些 的复杂性。1

在 Git 标签的情况下,要了解标签的一点是,任何一个标签都只是一些特定哈希 ID 的人类可读名称。哈希 ID 是那些丑陋的大数字,像 GitHub 变灰和缩写的东西,几乎无法阅读。在您的屏幕截图中,此处显示的两个是 a89f1cc31a6a2d.

这些哈希 ID 是 Git 对象的 真实 名称,即 Git 本身用于 查找 对象的内容。这些散列 ID 本身是通过将加密散列 应用于 内容而生成的,因此散列 ID 是键值数据库的短(大概)键:给定键,它是保证对那些特定内容是唯一的,2 Git 可以查找该值并获取全部内容。由于两个哈希值不同,因此两个对象也必然不同。如果两个对象相同,则两个哈希值将相同。

对于带注释的标签对象,其实有必要再深入一点:带注释的标签的内容包括目标对象的散列。这最容易从命令行完成——同样,GitHub 隐藏了细节,并不是说命令行是那么清楚:

git rev-parse a89f1cc^{}

例如会查找对象 a89f1cc,检查它是否是带注释的标签对象,如果是,则跟随它到标签本身命名的任何 other 对象。如果 a89f1cc 是其他类型的对象,则后缀 ^{} 无效。我们也可以这样写:

git rev-parse a89f1cc^{commit}

将找到 a89f1cc 解析到的 commit 对象,或者产生错误,在这种特殊情况下,这就是我们想要的:这要么给我们 a89f1cc 本身的完整哈希 ID,如果它已经是一个提交,或者生成 commit 的哈希 ID,a89f1cc 解析到它,如果它解析提交,否则出错。

对于使用该系统的人来说更实用,给定两个标签名称,您可以只使用 names:

git rev-parse v0.1^{commit}

和:

git rev-parse v0.2^{commit}

找到标签指向的提交。 (某些 shell 可能需要在帽子 ^ and/or 大括号 {...} 字符周围进行某种引用;这取决于您是否使用 bash、tcsh、PowerShell , 或者其他什么。您可能需要稍微调整这些命令行命令以使 shell 满意。)

将两个标签哈希中的每一个解析为提交将告诉您哪个提交或提交两个不同的标签名称。可以为单个提交提供多个标签,但您没有这样做,因此您将获得两个不同的提交哈希值。这些提交是 pip install 将签出、构建和安装的提交。


1Albert Einstein is often paraphrased as saying Everything should be made as simple as possible, but not simpler, although Wikiquote says he actually wrote It can scarcely be denied that the supreme goal of all theory is to make the irreducible basic elements as simple and as few as possible without having to surrender the adequate representation of a single datum of experience.

2根据您的 Git 版本,这有点夸张。有关详细信息,请参阅 Hash collision in git


如果您是 Git 用户,还有更多信息需要了解:标签与分支,以及提交图

如果您只是要安装,以上可能就是您关心的全部内容。不过,作为自己使用Git的人来说,还有一个值得学习的地方。

我们在上面看到,标签名称通过 git rev-parse(大多数 Git 命令在适当的时候在内部执行)解析为哈希 ID。但是,对于 分支名称 ,例如 masterdevelop 大部分 也是如此。我们可以 运行:

$ git rev-parse v2.16.0
e1c2c6b098dfb717a4a6ff7f3894d57343210a41

并得到这样的哈希ID,但我们也可以运行:

$ git rev-parse master
ccdcbd54c4475c2238b310f7113ab3075b5abc9c

并获取哈希 ID。此哈希 ID 是名称 master 指向的(一个,单个!)提交。 Git 的所有 引用都是如此: 分支名称、标签名称、远程跟踪名称(如 origin/master 等)都是引用,在Git中,每个引用都转换为一 (1) 个哈希 ID。

分支名称的特别之处在于它们会随着时间的推移而变化。他们现在存储一个哈希 ID,然后在你 运行 git commit 之后,一些分支名称存储一个 新的,不同的 哈希 ID。这实际上是树枝生长的方式:你告诉 Git 你想 某个树枝上,通过 运行ning,例如:

git checkout master

之后 git statuson branch master在分支上的意思是git commit更改您的分支名称:Git将使new 提交,它将获得一些新的、随机的哈希 ID,然后 Git 将存储 new 提交的哈希 ID进入名字 master.

其底层机制是特殊名称 HEAD(在所有大写字母中,尽管在 Windows 和 Mac 进行大小写折叠的系统上,您通常可以使用全小写). Git 将单词 HEAD 附加到分支名称 以便记住您要求 Git 在哪个分支上。

如果分支名称只有一个大写字母,我们可以这样画出来:

A <-B <-C   <-- master (HEAD)

这意味着 HEAD 附加到 master,名称 master 指向 提交 C——它包含提交 C 的哈希 ID。提交 C 本身包含早期提交 B 的哈希 ID,提交 B 包含提交 A.

的哈希 ID

在我们的例子中,我们的存储库很小:它只有这三个提交。提交 A 没有更早的提交——Git 称它为 root 提交——所以它没有指向任何地方。这显示了 Git 是如何工作的:它使用 name master 来查找 current commit C,然后使用提交 C 的内容找到 Cparent 提交 BB 的内容让 Git 找到 B 的父级 A,而 A 没有父级所以现在动作停止: Git 显示您提交 C,然后 B,然后 A,然后停止。

Git,换句话说,向后

当您进行新提交时 D,Git 通过记录所有代码的快照、记录先前提交 C 的实际哈希 ID 来创建此提交,然后写出提交并获取其实际哈希 ID(无论是什么)。 Git 然后使用 HEAD 知道将新的哈希 ID 写入 master,给出:

A <-B <-C <-D   <-- master (HEAD)

现在 master 名称(指向)提交 D.

同样,这是树枝正常移动和生长的方式。 分支名master标签名v0.1的区别在于分支名是应该移动——应该随着时间的推移而改变,命名该分支的last提交——但是标签名称不会 移动。一旦您分配了一个标记名称以指向某个特定的提交,它就会停留在那里:它一直指向该提交。

从提交到提交的链接形成了一个 ,正是这个提交图真正将 Git 粘合在一起。 GitHub 试图对您隐藏提交图。因为 Git 主要是关于图表的——仅仅 files 只是为了顺路而来——这是一种可怕的伤害。了解提交图对于使用 Git.

至关重要