为什么同一个标签有两个不同的git提交?

时间:2016-09-28 17:46:28

标签: git

我正在尝试确定与特定标记关联的提交SHA。当我执行show-ref时,我得到以下输出

$ git show-ref my_tag
6a390ca7bca7b52b2009069138873fdbc7922c1d refs/tags/my_tag

当我执行rev-list时,我得到了这个输出

$ git rev-list -n 1 my_tag
b6dcf8fa20296d146e9501ab9d25784879adeac8

提交SHA是不同的,但我不明白为什么。由b6dcf8生成的rev-list看起来是正确的。如果我尝试使用git checkout 6a390c检出第一次提交,然后查看日志,我实际上并不在6a390c;显示b6dcf8

任何人都可以解释为什么可能会断开连接?当我尝试结帐b6dcf8时,为什么要重定向到6a390ca

更新

我还注意到,当我执行git show my_tag时,我得到的输出看起来像这样

tag my_tag
Tagger: Me <me@me.com>
Date:   Mon Apr 4 14:43:46 2016 -0400

Tagging Release my_tag

tag my_tag_Build_1
Tagger: Me <me@me.com>
Date:   Thu Mar 31 10:46:18 2016 -0400

Tagging my_tag_Build_1

commit b6dcf8fa20296d146e9501ab9d25784879adeac8
Author: Me <me@me.com>
Date:   Wed Mar 30 18:12:10 2016 -0400

Remove secret_key_base values from secrets.yml

它正在拾取两个标记my_tagmy_tag_Build_1。但是,如果我运行git tag,则代码列表只有

my_tag

如果我运行git show my_tag_Build_1,我会

fatal: ambiguous argument 'my_tag_Build_1': unknown revision or path not in the working tree.
Use '--' to separate paths from revisions, like this:

似乎git很困惑。也许my_tag_Build_1标签在某个时刻存在,但它似乎不再存在。

3 个答案:

答案 0 :(得分:3)

执行“git show my_tag”并看到两个标签,因为my_tag标签是使用my_tag_Build_1标签作为参数创建的,如下面的命令所示:

git tag -m "Tagging Release my_tag" -a my_tag my_tag_Build_1

另一方面,我不能解释为什么my_tag_Build_1没有显示在“git tag”命令中。这真的很奇怪。

答案 1 :(得分:3)

即使Marcelo Ávila de Oliveira's answer正确,我还会再添加一个答案,因为我想绘制图形位。 : - )

通常我喜欢这样绘制提交图,至少对于StackOverflow:

...--A--B--C     <-- foobranch
         \
          D--E   <-- barbranch

这里有两个 tip (最右边)提交两个分支CE,每个分支都有一个指向它们的分支名称。也就是说,refs/heads/foobranch包含提交C的ID,refs/heads/barbranch包含提交E的ID。

轻量级标签

轻量级标签的工作方式与分支名称完全相同。如果我们添加标记bartag以指向提交E,我们会得到:

...--A--B--C     <-- foobranch
         \
          D--E   <-- barbranch, tag: bartag

其中refs/heads/bartag.git中的实际文件,除非它已成为&#34;打包&#34;而现在存储在文件.git/packed-refs中)也存储了ID提交E。轻量级标记和分支之间有三个区别:

  1. 轻量级代码的全名以refs/tags/而不是refs/heads/开头。
  2. 轻量级标记永远不应该更改为指向另一个提交(这只是由Git半强制执行,而在1.8.something之前的版本中则很差,但分支名称通常会更改它们指向的提交,与标记相比,哪个不是。
  3. Git强制执行一个规则,即分支名称只应指向提交对象。通常标记名称​​也仅指向提交对象,但可以创建指向树或blob的对象。还有一个对象类型 - 带注释的标记对象 - 但这使得标记不同!坚持这个想法,让我们完成这部分。
  4. 轻量级代码,只是一个全名拼写为refs/tags/...的引用。此外部引用存在于某处 - 通常作为单独的文件,如.git/refs/tags/bartag - 它指向存储库中的Git对象(.git/objects/...,可能打包到包文件中)。当它指向一个提交时,这是正常情况,这会让我们进入提交DAG:标签定位提交,它可以为我们提供工作树,并且还允许我们通过跟随&来探索早期(祖先)提交#34;父&#34; ID,从提交E返回D

    带注释的标签

    带注释的标签使用几乎相同的图片,除了现在,而不是将直接指向的轻量级标签bartag,现在Git存储带注释的标签对象进入存储库。此带注释的标记对象具有自己的数据(日期,标记,消息,可选的数字签名以及您喜欢的任何其他内容),并且还存储一个哈希ID。哈希ID是标记的目标(或object,正如Git所说的那样)。

    我不喜欢在这里画这些,所以我只是做点什么:

    ...--A--B--C     <-- foobranch
             \
              D--E   <-- barbranch
                 ^
                 :
                 t   <-- tag: annotag
    

    在这里,Git在存储库中存储了一个新的带注释的标记对象t,现在我们将外部引用refs/tags/annotag指向t。同时,标记对象t指向提交E

    这意味着标记annotag涉及两个哈希ID:带注释的标记对象的ID,以及提交E的ID。同样,引用指向带注释的标记对象,对象指向下一个 - 在这种情况下,提交E

    与轻量级标记一样,带注释的标记对象可以指向除提交之外的其他对象类型。轻量级标记不能指向带注释的标记对象,但这只是因为,当引用指向带注释的对象时,我们不再调用它&#34;轻量级&#34;标签,我们现在称之为&#34;注释&#34;标签。但是,带注释的标记对象可以指向另一个带注释的标记对象。让我们这样做,让zomgtag指向对象t

    ...--A--B--C     <-- foobranch
             \
              D--E   <-- barbranch
                 ^
                 :
                 t   <-- tag: annotag
                 ^
                 :
                 z   <-- tag: zomgtag
    

    如果您删除其中一个......

    现在让我们尝试删除标记annotag。关于Git的一个有趣的事情是删除引用 实际上删除了底层对象。通常会留下底层对象,直到存储库中的垃圾太多,此时Git会为您运行git gc --auto。 GC(垃圾收集器)查找未引用的对象并实际删除它们。因此,这个GC是一种死神,或者可能是Grim Collector,它将死对象回收到可用的磁盘空间。

    对于分支名称引用也是如此,例如:删除分支名称只是放弃分支提示,而不是实际删除它。此外,如果有一些其他方式来达到该提交,那么即使Grim Collector过来,提交本身也不会消失。如果还有一些联系,GC会将对象留在原位。对于普通(未删除)分支,当您进行rebase(将提交链复制到新链)时,原始提交链提示ID存储在分支的 reflog 中,这将保留整个链可以到达,直到reflog条目到期。 (这意味着您可以默认返回并恢复已重新提交的提交至少30天,因为30天和90天是默认的reflog到期时间。)

    但是这些相同的规则适用于带注释的标签对象!因此,如果我们在离开annotag时删除zomgtag,则现在是:

    ...--A--B--C     <-- foobranch
             \
              D--E   <-- barbranch
                 ^
                 :
                 t
                 ^
                 :
                 z   <-- tag: zomgtag
    

    标记对象t不再有名称,但可通过z 到达<{em>},我们通过{{1}到达所以refs/tags/zomgtag永远存在于存储库中。 (好吧,除非t 被删除,否则zomgtag会被取消引用。)

    现在t中有两个 Git对象:从外部引用开始,我们找到带注释的标记对象zomgtag。从中我们找到带注释的标记对象z,从t我们找到提交t

    Git在the gitrevisions documentation中描述了一种特殊的语法,用于&#34;剥离&#34;标签:E。描述说:

      

    后缀为zomgtag^{}后跟空括号对意味着该对象可以是一个标记,并递归取消引用该标记,直到找到非标记对象。

    如果我们制作更多带注释的标签,我们可以让^指向一个标签对象,该对象指向指向另一个标签对象的第二个标签对象,最终,在跟随许多标签之后,指向{{ 1}}指向refs/tags/wacky,指向z。符号t表示&#34;找到非标签对象&#34; (在这种情况下,提交,尽管一如既往,端点也可以是树或blob)。

答案 2 :(得分:2)

您的标记可能是一个打包标记,这意味着已将一堆引用放入一个文件中以提高传输效率。来自gitrepository-layout文档:

packed-refs
    records the same information as refs/heads/, refs/tags/, and friends record in
    a more efficient way. See git-pack-refs(1). This file is ignored if
    $GIT_COMMON_DIR is set and "$GIT_COMMON_DIR/packed-refs" will be used instead.

来自git-pack-refs

  

传统上,分支和标签的提示(统称为refs)在$ GIT_DIR / refs目录下的(子)目录中每个ref存储一个文件。虽然许多分支提示往往会经常更新,但大多数标签和一些分支提示永远不会更新。当存储库有数百或数千个标记时,这种one-file-per-ref格式既浪费存储又会损害性能。

看看.git/packed-refs,您会看到类似的内容。

6a390ca7bca7b52b2009069138873fdbc7922c1d refs/tags/my_tag
^b6dcf8fa20296d146e9501ab9d25784879adeac8

标签基本上是指向提交的对象。您可以使用-d解决此问题,以取消引用该标记。你应该看到这样的东西:

git show-ref -d my_tag
6a390ca7bca7b52b2009069138873fdbc7922c1d refs/tags/my_tag
b6dcf8fa20296d146e9501ab9d25784879adeac8 refs/tags/my_tag^{}