【问题标题】:git name-rev tag syntax meaning?git name-rev tag 语法含义?
【发布时间】:2020-12-17 20:25:43
【问题描述】:

我在我的主分支上运行git name-rev 'git rev-list master'。从输出列表中,我看到了以下标签/版本格式:

<SHA-1 Hash> tag/version-0.8.7
<SHA-1 Hash> tag/version-0.8.7~1
<SHA-1 Hash> tag/version-0.8.7~2
<SHA-1 Hash> tag/version-0.8.7~2^0
<SHA-1 Hash> tag/version-0.7.6~10^2~2

我对 ~^ 感到困惑,即使是文档(下面的链接):

~[],例如HEAD~,主人~3 修订参数的后缀 ~ 表示该提交对象的第一个父对象。修订参数的后缀 ~ 表示作为指定提交对象的第 代祖先的提交对象,仅在第一个父代之后。 IE。 ~3 相当于 ^^^ 相当于 ^1^1^1。

那么tag/version-0.8.7~2 是否意味着在 tag/version-0.8.7 之前提交了两次更改? “第代祖先”是什么意思?

^[],例如头^,v1.5.1^0 修订参数的后缀 ^ 表示该提交对象的第一个父对象。 ^ 表示第 个父级(即 ^ 等价于 ^1)。作为一个特殊规则,^0 表示提交本身,并且在 是引用提交对象的标记对象的对象名称时使用。

tag/version-0.8.7~2^0 本身是否意味着两个已提交的更改(由于 ^0)

希望我能得到澄清。 还有没有办法在制作标签时不显示这些~^? 谢谢

https://git-scm.com/docs/git-rev-parse#Documentation/git-rev-parse.txt-emltrevgtltngtemegemHEADv1510em

https://git-scm.com/docs/git-rev-parse#Documentation/git-rev-parse.txt-emltrevgtltngtemegemHEADmaster3em

【问题讨论】:

    标签: git


    【解决方案1】:

    警告:这个答案很长,顶部没有 TL;DR。

    我不完全确定你在这里问什么,尤其是在这部分:

    还有没有办法在制作标签时不显示这些~和^?

    当您制作标签时,您自己选择名称。您不能在您选择的名称中嵌入 ~^ 字符(参见 the check-ref-format documentation 中的规则 4),因此它们永远不会成为任何标签的一部分。

    git name-rev 命令添加 ~^ 字符 借用它们时存在的有效名称,但仅在必要时。要了解这里发生了什么,您需要了解一点 graph theory,以及 Git 的提交形成 有向无环图DAG

    ...tag/version-0.8.7~2 是否意味着 tag/version-0.8.7 之前的两个已提交更改?

    有点。不过,放弃“两个已提交的更改”这个短语是个好主意。 Git 的提交不是更改。提交代表所有文件的完整快照

    Git 存储库中的每个提交都是真实的、实际的东西:可以说是实实在在的东西,您可以取出并检查。把它们想象成袋子里的弹珠,或者建筑物里的砖块,或者你喜欢的任何其他类比。如果您使用“建筑物中的砖块”进行类比,那么建筑物(整个系列)确实不是砖块,仅仅拥有砖块不会让您获得建筑物 - 但在同时,每一块砖都是真材实料。它不是一个抽象的概念(尽管这个概念本身确实存在;你可以想象一块“完美的砖”,如果你愿意的话,没有缺陷或不完美,但你永远不会找到没有任何缺陷的实际缺陷:见Plato's Theory of Forms)。如果你从建筑物中取出一块真正的砖块,它是坚固的东西,你可以用它敲打某人的头部。 (噢!别说了!?)考虑到这一点,让我们注意,任何给定的存储库主要由该存储库中的提交组成,与Puni Distillery in Glorenza, Italy 的方式大致相同由那些特定的砖块组成,按照它们的特定排列。 (我从未尝试过 Puni 威士忌,但这看起来是一个有趣的地方!)你不能随意堆放任何旧砖块(或提交);你需要这些,在这个特定的安排中。

    回到 Git 存储库中的提交,让我们考虑是什么使提交成为提交。存储库中的每个提交都是唯一的,每个提交都有一个唯一的编号,让 Git 可以找到 那个 提交。所以我们知道每个提交都有编号。这些提交编号是 hash IDs,例如看起来像 3cf59784d42c4152a0b3de7bb7a75d0071e5f878 的大而丑陋的字符串。你可能不知道——并不是所有的 Git 教程都清楚地表明这一点——每个提交都包含一个每个文件的完整快照

    为了使这项工作顺利进行,Git 以一种特殊的、只读的、压缩的、仅 Git、永久冻结和 去重 格式存储文件。因此,如果您连续进行一堆提交,并且其中大多数提交大多重复使用与以前提交相同的文件,则大多数提交几乎不占用空间,因为这些文件实际上是重新-用过的。只有对该特定提交唯一的文件才必须存储新副本。 (请注意,如果您存储 5 GiB DVD 映像,这种技术就会崩溃。每个稍微不同的 DVD 映像都需要另外 5 GiB。如果您正在存储文本文件,并且您有数以万计的这些文件并更改第一,您只更改了一个 small 文件(最多可能只有几个 kiB 或 MiB),并且在存储一个新的 5 kiB 甚至 5 MiB 文件时重用所有其他文件很容易,与存储一个新的 5 GiB 文件相比。)

    除了文件(通常是提交的主要数据)之外,每个提交还存储一些元数据,或关于提交本身的信息。这包括做出提交的人的姓名。它包括一个日期和时间戳,显示他们提交的时间。它包括他们的提交信息。但它还包括 Git 想要的 Git 的一个关键项:Git 在提交中存储 提交编号——哈希 ID——一组 previous 提交用于到目前为止建立存储库。大多数提交只需要 一个 先前提交的哈希 ID,这就是我们首先要了解的内容。

    评论:提交是带有元数据的快照

    从上面要带走的是:

    • 每个提交都使用哈希 ID 进行编号。从字面上看,这就是 Git finds 提交的方式。

    • 每个提交都包含两个部分:它是 Git 在您(或任何人)进行提交时所知道的所有文件的快照,以及一些元数据关于提交,包括提交人,以及提交在历史记录中的位置信息。

    历史信息是每个提交的一部分,它只是一个链接——一个指针,如果你愿意的话——指向一组先前提交。

    这些提交和链接构成了一个有向图

    在数学上,只是顶点(有时称为节点)和边的集合:从一个顶点到另一个顶点的连接。如果边有方向箭头,数学家和计算机科学家倾向于称它们为 arcs 而不是边。我们将首先使用箭头绘制它们。

    在 Git 中,最简单的图形是直线。我们有一系列提交,从我们在某个存储库中所做的第一个提交开始,到最近的提交结束。每个都有一个唯一且看起来随机的哈希 ID,但为了保持理智,我们将使用连续的大写字母绘制这些提交,如下所示:

    A <-B <-C
    

    提交C 是我们最近的提交。它有一个箭头(边/弧)从它中出来指向提交B。 Git 通过存储提交 B inside commit C 的实际哈希 ID 来存储这条边;这就是为什么我们的箭头来自C

    提交B 就像C:它有一个箭头出来,指向之前的提交A

    提交A 有点特别,因为这是我们有史以来的第一次提交。作为第一个,它不能向后指向任何更早的提交——所以它只是没有。事实证明,这就是 Git 知道停止倒退的方式。

    为了让 Git 使用这三个提交,我们必须告诉 Git last 提交的真实哈希 ID,C。我们可以将这个哈希 ID 写在纸上、白板或其他东西上。但这似乎很愚蠢。我们有一台计算机:为什么不让计算机将哈希ID存储在一个文件中?

    分行名称

    这是分支名称的来源。这也是标签名称的来源,但标签名称有一个或两个分支名称没有的皱纹,所以让我们从分支名称开始。

    分支名称,在 Git 中,仅包含我们想说的“在分支上”的 lastlatest 提交的实际哈希 ID。在某种程度上,这有点像提交:提交保存 previous 提交的哈希 ID,分支名称保存提交的哈希 ID。因此,这两件事都被称为指向一个提交。我们现在可以画出来了,像这样:

    A <-B <-C   <--master
    

    name master 将保存最后一次提交的真实哈希 ID C。所以Git可以使用这个名字来查找C。然后,找到C,Git 可以找到与提交C 相关的快照和元数据。它可以获取所有文件;它可以告诉我们谁做出了提交;它可以使用commit C 后退一跳,commit B,因为C 指向B

    如果我们想向这个集合添加一个 new 提交,我们首先让 Git 在某处提取提交 C。我们需要一个工作区,因为C 中的文件被冻结了,我们甚至无法用大多数计算机程序读取它们;只有 Git 可以读取自己的格式。所以我们让 Git 将文件复制到工作区。然后,我们处理文件的可用副本——在 Git 中的副本——并最终告诉 Git 进行新的提交,即对更新的文件进行快照。 (这比这个简单的描述所暗示的要多得多,但这是我们如何进行新提交的核心。)

    当然,新的提交需要元数据和快照。元数据将使用您的姓名和电子邮件地址作为作者,并使用“现在”作为您提交的时间。 Git 也会从您那里收集提交消息。然后 Git 会将所有这些写成一个新的提交。新提交将获得一个新的、唯一的、外观随机的哈希 ID,但我们将在此处调用新提交 D。新的提交D 将指向提交C,因为那是你用来制作 D 的那个,然后Git 会将D 的实际哈希ID 写入名称@ 987654365@,因为那是你在制作D时使用的分支名称

    A <-B <-C <-D   <--master
    

    我们现在回到了和以前一样的设置,除了现在,还有一个提交。 名称 master 找到DD 找到 CC 找到 BB 找到 A

    请注意,我们将这些向后指向的箭头(从提交到提交)称为 链接。提交C 是提交D。关于这一点还有一件事要知道:一旦制作完成,这些都无法改变。不只是每次提交中的 文件 一直被冻结:每个 提交的 所有 部分一直被冻结。

    使用多个分支名称

    在存储库中只有一个master 分支,我们无法真正获得分支-y。为了使分支看起来像分支,我们需要有多个分支名称。一旦我们这样做了,我们需要稍微更新一下我们的绘图,以便我们可以知道我们实际使用的哪个名称。

    旁注:在这一点上,我将变得懒惰并开始绘制从提交到其父级的连接作为线条,因为很难绘制指向左上或左上的箭头向下。请记住,连接总是向后的。 Git 向后工作,因为它必须这样做:子提交知道它的父级是谁,但父级永远不知道它未来的子级可能是谁。孩子出生后,它可以成为父母,但它也被冻结了,所以它无法记住它未来的孩子。

    我们将从四个提交开始:

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

    我们会将特殊名称 HEAD 附加到其中一个分支名称中。然后,要创建一个新分支,我们将从现有的四个提交中选择一个,并创建一个指向该提交的 new 名称。让我们选择D,因为这是最容易找到的提交,因为它是我们已经在使用的提交,因为master 现在选择提交D

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

    我们通过运行git branch develop 得到这个。由于我们没有选择特定的提交,git branch 使新名称指向与我们现在使用的提交相同的提交。

    如果我们现在运行 git checkout develop(或者,从 Git 2.23 开始,git switch develop),Git 从使用名称 master 转移到使用名称 develop。这两个名字都选择了提交D,所以没有其他事情发生现在:我们输出的文件是提交D的文件,我们没有更改提交,所以 Git 不会更改任何签出的文件。但是现在HEAD附上了develop这个名字:

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

    如果我们现在进行新的提交,并将新的提交称为 E,我们可以这样绘制:

    A--B--C--D   <-- master
              \
               E   <-- develop (HEAD)
    

    我们现在发布的文件以及我们所在的分支是来自 / 提交 E 的文件。 name develop 选择了这个提交。 ED 作为其父级,因此提交 A-B-C-D两个分支上。但是分支master结束在提交D

    如果我们现在运行 git checkout master,Git 用来自提交 D 的文件替换我们的工作文件,并将 HEAD 移动到附加到 master 而不是 develop,例如这个:

    A--B--C--D   <-- master (HEAD)
              \
               E   <-- develop
    

    请注意,我们在E更改 并提交的文件仍然存在,在E,但现在 Git 已将文件从 D 取出到我们的工作区,什么我们看到是来自D 的文件,而不是来自E 的文件。1

    我们现在可以进行更多更改(大概,与我们为 E 所做的更改不同)并为 Git 标记更新的文件并运行 git commit 并获得一个新的提交,这我们将在分支master 上调用F,如下所示:

               F   <-- master (HEAD)
              /
    A--B--C--D
              \
               E   <-- develop
    

    我以这种特殊的方式绘制了这张特殊的图表,以强调提交A-B-C-D (仍然)在两个分支上的想法,而提交EF 仅在develop 上和master 分别。但我们可以把它画成:

    A--B--C--D--F   <-- master (HEAD)
              \
               E   <-- develop
    

    或:

               F   <-- master (HEAD)
              /
    A--B--C--D--E   <-- develop
    

    如果我们愿意。 这些图代表同一张图。

    图本身只需要通过正确的弧集(子到父箭头)连接正确的顶点集(提交)。我们可以像 Git 那样垂直绘制图形,将更新的提交放在顶部。我们可以垂直绘制它,将新的提交放在底部(有些人有时会这样做)。如果您以前没有尝试过,最好运行 git log --all --decorate --oneline --graph 并查看 Git 如何尝试绘制图形。 查看图表对理解~^ 表示法有很大帮助。


    1在某些特殊情况下,如果我们实际上没有提交某些东西,Git 会为我们携带未提交的文件。这并不总是有效!有时 Git 会说不:由于未提交的工作,我无法更改提交。这里的规则很复杂。如果您想了解这些规则的来源,请参阅 Checkout another branch when there are uncommitted changes on the current branch — 但请注意,您最好的选择通常是避免切换未提交工作的分支!


    一旦我们有了分支,我们就可以合并

    假设我们有一个这样的提交图:

              I--J   <-- br1
             /
    ...--G--H
             \
              K--L   <-- br2
    

    分支br1 上的最新 提交是提交J,分支br2 上的最新提交是提交L。 (我们还没有选择其中一个来检查。)每个分支都有一些对每个分支都是唯一的提交,然后,回到过去——实际上是从每个分支的末尾返回的两个提交——两个分支有一个共同的历史。从JIH 的历史,以及从LKH 的历史,在提交H 时相遇。

    使用这个共同的开始(结束?)点,Git 可以使用提交H 中的快照来确定某人在br1 上做了什么工作,从而到达提交J 中的快照。同样,Git 可以找出其他人在br2 上做了什么工作,以在提交L 中获得快照。然后我们可以让 Git组合这两组工作。通过将组合工作应用于提交H中的快照,我们将保留导致J的所有更改,并添加所有导致J的更改L。或者,我们将保留导致L 的所有更改,并添加所有导致J 的更改。无论哪种方式,我们都会得到相同的最终结果。2 所以我们将运行:

    git checkout br1 && git merge br2
    

    或:

    git checkout br2 && git merge br1
    

    并选择一侧作为“我们的”,另一侧作为“他们的”,并让 Git 进行合并。如果一切顺利——如果这里的脚注中没有提到任何冲突——Git 将自己进行一个新的 merge commit;我们将此提交称为M:

              I--J
             /    \
    ...--G--H      M
             \    /
              K--L
    

    commit M 的特别之处在于,它不仅指向J,而且不仅指向L,还指向J L .这就是 Git 知道历史的方式,提交 M 一个合并。除了这两个反向链接之外,提交M 只是一个普通的快照,就像任何其他提交一样。

    当然,Git 现在必须更新两个分支名称中的一个(并且一个),以便它指向新的合并提交 M。当然,要更新的名称是我们选择附加HEAD 的名称。因此,如果我们运行git checkout br1,然后运行git merge br2,我们会得到:

              I--J
             /    \
    ...--G--H      M   <-- br1 (HEAD)
             \    /
              K--L   <-- br2
    

    2好吧,我们将得到相同的最终结果除非在合并的一侧完成某些操作—H-to-J与某人发生冲突另一方,H-to-L。在这种情况下,必须有人想办法解决冲突。如果他们使用-X ours-X theirs 之类的标志来更喜欢一侧或另一侧,那么当他们进行合并时,他们站在哪一侧就开始变得重要了。 p>


    合并有第一和第二父母

    请记住,M 中的 快照 将是相同的,无论我们在此处更新了哪个 分支名称。但是 Git 也会记住我们用来进行合并的分支名称,方法是确保两个反向链接以正确的顺序存储。

    假设我们开始时位于br1,并且br1 现在指向M,那么提交M第一个父项 将是提交J

              I--J
             /    \₁
    ...--G--H      M   <-- br1 (HEAD)
             \    /²
              K--L   <-- br2
    

    如果我们切换合并 - 如果我们 git checkout br2 然后运行 ​​git merge br1 - 两个父链接的顺序将相反。当然,得到更新的 name 也会发生变化:

              I--J   <-- br1
             /    \₂
    ...--G--H      M   <-- br2 (HEAD)
             \    /¹
              K--L
    

    请注意,如果您向后跟随 first 父链接,您将在提交时结束该名称 在合并之前找到的提交。在第一个示例中,更新后的名称是br1,沿着第一个父链接后退一步可以让您提交J,这是我们在运行git merge 时得到的提交。在第二个示例中,更新后的名称是 br2,沿着第一个父链接退一步让您提交 L:同样,我们在运行 git merge 时没有提交。

    Git 支持三个或多个父级合并

    虽然我们不会在这里详细介绍,但 Git 实际上可以有一个具有三个甚至更多父级的合并提交。 Git 将这些东西称为章鱼合并。它们不会做任何你通过重复的双父合并不能做的事情,事实上,你可以用 Git 的 two-父合并做一些事情不会与三个或更多父合并。所以它们不是绝对必要的。除了 first 父元素之外,Git 的大多数部分也不区分任何东西:git log 有一个 --first-parent 标志,这意味着 忽略每个合并的另一边,并且如果你在章鱼合并中使用它,它会忽略除多路合并的相同分支名称“边”之外的所有内容。

    不过,这确实会影响^-suffix 符号。仅仅说“第一父母”和“第二父母”是不够的,因为可能有“第三父母”、“第四父母”等等。在 Linux Git 存储库中,有一个与 66 个父级的疯狂合并。 Linus Torvalds once called it a Cthulhu Merge.

    标签

    在 Git 中,标签名称与分支名称非常相似:标签名称包含一个特定提交的哈希 ID。但是这里有一个机制问题,Git 并没有隐藏机制以使一切按你希望的方式工作,而是公开了机制,让你——程序员——处理它。

    具体来说,标签有两种形式:有一个轻量级标签,它通常直接指向一个提交,3还有一个带注释的标签,根据定义,它指向一个带注释的标签对象。这个带注释的标签对象有它自己的哈希 ID,就像一个提交有一个哈希 ID。这允许 Git 在这个带注释的标签对象内部存储一些额外的信息 in 标签。然后带注释的标记对象指向提交(尽管再次参见脚注 3)。

    这一切最终的意义是,对于 annotated 标签,如果我们想要标签应该表示的 commit,我们必须告诉 Git:不要只告诉我标签。告诉我标签的标签是什么!一般语法是<em>tag-name</em>^{},即标签名称,后跟帽子或插入符号,后跟空括号。这意味着跟随标签到达目的地(又名剥离标签,如脚注3)。如果没有这个后缀,如果我们有一个像 v1.2 这样的标签,它是一个带注释的标签,我们运行:

    git rev-parse v1.2
    

    我们得到了注解的标签对象的哈希ID,而不是v1.2标签的标签。下面是 Git 本身的 Git 存储库中的一个示例:

    $ git rev-parse v1.2.0
    041ed88c7369c3e45077502b74664d8101f99ab3
    $ git cat-file -p v1.2.0
    object bd9ca0baff88107e26915cdaaf9821dc70a187e3
    type commit
    tag v1.2.0
    tagger ... [snipped]
    $ git rev-parse v1.2.0^{}
    bd9ca0baff88107e26915cdaaf9821dc70a187e3
    $  git cat-file -p v1.2.0^{}
    tree 933b7a642528ce47ef2f538a007c0e48cc448e1f
    parent 4bbdfab766782dc06ac496730e3a578bd35d67c5
    author Junio C Hamano ... [snipped]
    

    因为标签可以指向任何Git的内部对象,如果你想要求标签指向一个commit,你可以告诉 Git 它必须这样做:

    $ git rev-parse v1.2.0^{commit}
    bd9ca0baff88107e26915cdaaf9821dc70a187e3
    

    请注意我们是如何在这里获得提交的哈希 ID,而不是带注释的标签的。我们可以告诉 Git 我们想要一个 blob 的哈希 ID,但我们会得到一个错误:

    $ git rev-parse v1.2.0^{blob}
    error: v1.2.0^{blob}: expected blob type, but the object dereferences to tree type
    ... [snipped]
    

    这里有一个奇怪的地方:我们收到了关于找到 tree 类型而不是 commit 类型的投诉。这是一个完全独立的讨论,关于 Git 所谓的“commit-ish”或“tree-ish”,与最初的问题并没有真正的关系,所以我们暂时将其放在一边。

    无论如何,<em>anything</em>^0 实际上只是稍长的<em>anything</em>^{commit} 的同义词。就 Git 而言,您可以互换使用它们。任何一个后缀都会强制 Git 将一个带注释的标签对象解析为一个提交对象哈希 ID,如果 Git 不能 由于某种原因这样做,Git 会产生一个错误,抱怨“预期的提交类型” ,类似于我们上面的 blob 错误。


    3标签,轻量级或带注释的,在技术上允许指向 Git 的任何内部对象。有四种类型的对象:blobtreescommits注释标签。根据定义,指向带注释的标记对象的标记名称是带注释的标记;根据定义,任何指向其他三种对象类型的任何标记名称都是轻量级标记。然后,带注释的标记对象指向任何类型的对象,甚至可能指向其他带注释的标记对象。然而,最终,所有带注释的标记对象链都需要指向其他三种对象之一。这允许 Git 追踪带注释标记的最终目标对象,并且该对象,无论其类型是什么,都是标记 name 指向的带注释标记的“目标类型”。 (哇!)据我所知,这个复杂的标签到标签到标签到标签到提交(或其他)序列没有任何用处,但它很容易创建,而且 Git允许它。跟随一个带注释的标签到它的最终对象的过程,不管是什么,都称为剥离标签,最终的目标对象类型是我们关心的。这几乎总是一个提交。


    考虑到所有这些,我们现在可以解释后缀符号

    如果需要,Git 中的任何 name 都会被解析为哈希 ID。如果您使用git rev-parse 命令执行此操作,您可以提供准确的拼写。其他一些命令会做一些猜测,还有一些允许您输入准确的拼写,将它们传递给 to git rev-parse 或内置的等效命令。在这些情况下:

    • masterdevelop这样的分支名称,是全名refs/heads/masterrefs/heads/develop的缩写,总是指向一个提交哈希ID,并且总是可以解析为一个提交哈希ID。

    • v1.2 这样的标签名称是全名refs/tags/v1.2 的缩写,它指向某种对象。如果它是一个轻量级标记,它直接指向你标记的任何类型的对象,并且名称将解析为该哈希 ID。但如果它是一个带注释的标记,它通常会解析为底层带注释的标记对象的哈希ID。 (有些命令会自动为您剥离标签;有些则不会。)

    • 所以,如果您确实使用了标签名称,并且您希望绝对确定 Git 在末尾找到 commit它,您可以在名称中添加^{commit}^0。如果您使用的是 branch 名称,则无需执行此操作,因为分支名称不能指向任何内容提交。

    在其他情况下也不需要这样做。引入这种需求的主要是带注释的标签。这就是 ^0 后缀的全部含义:强制 Git 剥离标签,然后进行提交(如果不是提交则出错)。

    您询问的另外两种后缀形式是~<em>number</em>^<em>number</em>。这两种形式与来自提交的弧(向后箭头)一起工作。如果某个名称找到了某个提交,则该提交存在于 提交图表中: 那些图如下:

              I--J
             /    \₁
    ...--G--H      M   <-- br1 (HEAD)
             \    /²
              K--L   <-- br2
    

    这里,名称br1 选择提交M。如果我们想要提交J,我们可以后退一步,沿着M 的第一个父级,使用br1^1 提交J。如果我们想要提交L,我们可以后退一步,沿着Msecond 父级,使用br1^2。 (或者我们可以只使用名称br2,但它没有任何这些花哨的后缀。?)

    带有数字的帽子后缀专门用于处理合并提交。由于M 一个合并提交,我们可以使用数字来选择要遵循的父级。如果省略数字,则帽子后缀默认为第一个父级,因此 br1^br1^1 的含义相同,这意味着 沿着第一个父级后退一步提交J

    如果你想多退一步,你可以添加更多的帽子后缀字符:br1^^ 退一步两次。首先我们登陆J,然后我们登陆I。所以这会找到提交I。如果我们想提交K,我们可以使用br1^2^^2 退回到L,然后第二个^ 退回到L

    假设我们想后退四步,到达G。然后br1^^^^ 会成功:我们从M 开始,使用第一个^(以及隐含的1)到达J,使用另一个^ 到达I,使用第三个^ 到达H,并使用第四个^ 到达G。不过,这需要输入一组仔细计算的帽子字符。能写br1~4就更好了。 ~4 后缀表示后退四次,就像^^^^

    使用~ 后缀,我们不能在任何时候输入任何父编号。回溯操作仅回溯到第一父母。只要我们没有任何合并提交,或者想要沿着第一父行返回,这很好!我们只需要使用带有数字的^ 后缀,当我们点击合并提交时并且想要沿着第一父行以外的东西返回。

    tag/version-0.8.7~2
    

    这将使用tag/version-0.8.7 来查找一些对象。由于末尾有一个~2 后缀,这个对象需要是一个commit 对象;如果它是一个带注释的标签对象,Git 将“剥离”标签并确保那里实际上有一个提交。然后,从那时起,它将返回两个第一父链接。

    tag/version-0.8.7~2^0
    

    这做同样的事情,但在返回两个第一父链接后,需要(通过^0 后缀)找到的哈希 ID 是提交的哈希 ID。由于提交只允许指向之前的提交,所以这里的^0 后缀是不必要的。它没有有害,但它没有增加~2 尚未强制执行的任何内容。

    tag/version-0.7.6~10^2~2
    

    这从tag/version-0.7.6 标识的任何内容开始。然后它必须是一个提交,因为~10 后缀,所以它经历了与我们刚刚看到的相同的过程。然后~10 后缀后退十个第一父链接。现在有一个^2 后缀,所以我们需要在这一点上进行合并提交,并且我们后退一个第二父链接。 (对于来自提交的反向链接,这与往常一样,是另一个提交。)然后我们后退两个第一父链接,这样就完成了所有后缀处理,我们有了最终的提交哈希 ID。

    【讨论】:

    • 您好 Torek,非常感谢您花时间和精力提供如此彻底和深入的解释。绝对喜欢你画的图表。它使理想更加清晰。从长远来看,这真的对我有帮助!特别感谢您让我了解 git 中涉及的图论。一旦covid消亡,我肯定很喜欢参观意大利的Puni Distillery ?(看起来像现实生活中的Minecraft建筑)
    猜你喜欢
    • 1970-01-01
    • 2020-11-23
    • 2021-01-31
    • 2019-09-20
    • 2011-10-01
    • 2015-10-01
    • 2020-07-17
    • 2020-08-16
    • 1970-01-01
    相关资源
    最近更新 更多