理解git图

pic*_*olo 0 git git-log git-branch

我使用该git log --graph --all命令来可视化我的提交/分支历史记录。然而,虽然我理解图表显示的大部分内容,但我很难解释分支可视化的某些部分。

我难以解释的分支是:

a) 提交 309a287

b) 提交 3f7475

c))在 e9415f 中的解释

我也有点困惑为什么似乎有三个分支(三个垂直的平行线),因为我同时只有一个主分支和一个额外的分支。

|
| * commit 88531a7dc85d030016296a51c5c433b72c7186c5 (refs/stash)
|/| Merge: c501f9f 631678b
| | Author: 
| | Date:   Wed May 20 17:01:59 2020 +0100
| |
| |     On blackforest: ddd
| |
| * commit 631678b558576418c21496712db524eb86a755ec
|/  Author: 
|   Date:   Wed May 20 17:01:59 2020 +0100
|
|       index on blackforest: c501f9f induced typo
|
* commit c501f9fc6b817541d8cb6a466c77b8d84231afcb (origin/blackforest)
| Author: 
| Date:   Sun May 17 18:53:59 2020 +0100
|
|     induced typo
|
| *   commit e9415f49f953ca4fe53bd9631e4afea94c3ba4ba (HEAD -> master, origin/master
)
| |\  Merge: 9a1ff1f 3f74752
| | | Author: 
| | | Date:   Sun May 17 18:51:40 2020 +0100
| | |
| | |     Merge branch 'master' of
| | |
| | *   commit 3f7475269c1134aef39a06f13ae11d74c9496542
| | |\  Merge: 309a287 3d41b51
| |_|/  Author:
|/| |   Date:   Sun May 17 18:46:19 2020 +0100
| | |
| | |       Merge pull request #1 from 
| | |
| | |       enhanced emoticon
| | |
* | | commit 3d41b51c8157abe38d251e065ad33b3d265d332c
| | | Author: 
| | | Date:   Sun May 17 18:42:00 2020 +0100
| | |
| | |     enhanced emoticon
| | |
| * | commit 9a1ff1fa645e8390ab26f751dbf1b716e15b0df6
| |/  Author: 
| |   Date:   Sun May 17 18:50:09 2020 +0100
| |
| |       capitalised a
| |
| *   commit 309a287f9c39d219d719efbcc872a176f7644b19
| |\  Merge: dafe938 806e855
| |/  Author: 
|/|   Date:   Sun May 17 17:42:46 2020 +0100
| |
| |       Merge branch 'blackforest'
| |
* | commit 806e8558cd7b24658a998b2ee5d19500e608b77d
| | Author: 
| | Date:   Sun May 17 17:06:10 2020 +0100
| |
| |     new common
| |
* | commit a5e386a55389a6435a050be9981ea97011449783
| | Author: 
| | Date:   Sun May 17 17:32:26 2020 +0100
| |
| |     Edited firstfile to reflect new branch name
| |
| * commit dafe938bcfbdfe6eaf62d5f446637e6f5d594015
|/  Author: 
|   Date:   Sun May 17 17:06:10 2020 +0100
|
Run Code Online (Sandbox Code Playgroud)

tor*_*rek 5

(我发现你的问题有点没有重点,因为我不确定git log --graph这里输出的哪些特定方面令人困惑,所以这会很长,恐怕:我会尽量涵盖所有重要的内容。)

knittl 在评论中指出,杂散的括号只是前一行的环绕。通常git log通过寻呼机运行它的输出,智能寻呼机可以通过在文本上为您提供一个左右滚动的“窗口”来解决这个问题,这样右括号就会从终端窗口的右侧消失(或您使用的任何终端模拟器)。

顺便说一下,让我们专门看看309a287and 3f7475,但从这个开始:

我也有点困惑为什么似乎有三个分支(三个垂直的平行线),因为我同时只有一个主分支和一个额外的分支。

显然,您有两个名字:masterblackforest。但是您也有两个不同的存储库:

Merge branch 'master' of [snipped]

剪下的部分将是一个 URL;此特定消息Merge branch '<name>' of <url>(可能以 结尾into <name>)是将git pull合并提交消息传递到 时生成的消息git merge1 所以这个git log --graph输出意味着你运行了git pullwhichgit merge在从其他 Git 存储库获得的提交上调用。

我们实际上可以看到提交:它是合并提交的第二个父级e9415f4...。因此,如果我们仅采用以下四行:

| *   commit e9415f49f953ca4fe53bd9631e4afea94c3ba4ba (HEAD -> master, ori...
| |\  Merge: 9a1ff1f 3f74752
| | *   commit 3f7475269c1134aef39a06f13ae11d74c9496542
| | |\  Merge: 309a287 3d41b51
Run Code Online (Sandbox Code Playgroud)

我们可以看到。在这种情况下,这是您自己的神秘提交之一3f74752...。它的提交信息以Merge pull request #1 from. GitHub 和其他类似的托管站点会生成带有此类消息的提交。

所以,你——或其他人——必须在一些网络托管站点(如 GitHub)上进行了提交。您或他们必须在托管在那里的存储库中进行此提交。那是第二个 Git 存储库它有自己的分支名称,因此根据它有多少个分支名称,可能会有数千或数百万个分支。

他们的分支只会在您允许时影响您的存储库。你让你的 Git 调用他们的 Git 并从他们那里获得任何新的提交。您的 Git 将这些提交添加到您的存储库数据库中,该数据库非常努力地保留它所见过的每个提交。如果随后git merge的一个自己的提交,您将获得直接进入,从自己的分行名称,无论分支你现在到是对这个承诺。


请记住,git pull意味着run git fetch,然后运行第二个 Git 命令。 第二个 Git 命令默认为git merge. 该git merge命令有时需要合并消息——每当它进行真正的合并时——并git pull提供一个,当第二个命令是git merge. (如果您告诉git pull改为运行git rebase,则不需要合并消息,因此git pull不提供。)


Git 与分支无关

了解正在发生的事情的方法是意识到 Git 最终真正关心的是commits。它不是那么关心分支。大多数情况下,它根本不关心文件——文件只是提交所具有的一些令人讨厌的东西。当然,如果不是因为提交中的文件,我们根本不会使用 Git,但只有我们关心文件:那不是 Git。

一旦我们看到对 Git 来说重要的是提交,我们就可以看到分支名称如何进入图片。然后有可能——尽管仍有一点飞跃——从分支名称转到我们所说的分支实际意味着什么的基本问题(请参阅我们所说的“分支”究竟是什么意思?):

一个分支名在Git是一个指针,指向一个具体的承诺。 也就是说,它通过名称标识一个提交哈希 ID。其他 Git 名称,例如标记名称,可以完成相同的工作,但分支名称还有一个特殊属性:它们自动移动,因此它们始终命名分支中的最后一次提交。

换句话说,根据定义,存储在分支名称下的哈希 ID 是分支的最后一次提交。然后我们只需要意识到一个更关键的事情:每个提交都存储一些先前提交的哈希 ID。

提交本身就是图表的形式。给定某个提交(由某个哈希 ID 标识),Git 可以访问该提交并从该提交中提取其直接前任的哈希 ID。或者,对于合并提交,我们有两个,而不是一个直接的前任。2 不管怎样,我们最终都会得到这样的结果:

... <-F <-G <-H
Run Code Online (Sandbox Code Playgroud)

哪里H是某个链中最后一次提交的哈希 ID 。或者一个链可能在合并提交时结束:

...--I--J
         \
          M
         /
...--K--L
Run Code Online (Sandbox Code Playgroud)

或者之后有一些额外的提交:

...--I--J
         \
          M--N
         /
...--K--L
Run Code Online (Sandbox Code Playgroud)

但在所有情况下,链都在分支提示 commit处结束,这样做是因为分支名称指向该提交:

...--G--H   <-- master
         \
          I--J   <-- feature
Run Code Online (Sandbox Code Playgroud)

所有的提交了通过尖端是在树枝上,所以在这种情况下,提交了经过H上都masterfeatureI--Jfeature孤独。


2一个合并提交实际上可以有两个以上的父级,但我们真的不需要在这里担心这个。


git log 必须将事物线性化

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

...--I--J
         \
          M--N   <-- master
         /
...--K--L
Run Code Online (Sandbox Code Playgroud)

N最新的提交在哪里,它的父级M是一个合并,它有两个父级JL. 通过横向绘制此图,将最新的提交向右,我们可以表明在提交的上行或下行(I-JK-L分别)上完成的任何工作都不是相对于另一行严格排序的,而是仅在其自己的范围内排。

请注意,Git 通过从分支名称找到的提示提交开始来查找提交 - 在这里,这是master导致提交N- 然后向后工作。由于它向后工作,git log需要从每个提交移动到其父级或父级。从NM这很容易,因为只有一位家长。从M回,不过,git log访问这两个提交J,并L同时...但它不能。

特别是,git log, 必须垂直打印出来。该尖端承诺N会先出来,因此成为我们的终端窗口的顶部。下面是 commit M。如果git log可以做我将要绘制的内容,它可能会像这样显示合并的左右“侧面”:

     N    <information>
     |
     M    <information>
    / \
   J   L   <information about J> <information about L>
   |   |
   I   K   <information about I> <information about K>
   :   :
Run Code Online (Sandbox Code Playgroud)

git log不能这样做,所以它近似。它选择任何一个提交JL有一个较晚的提交者日期,并首先将其输出:

     N    <information>
     |
     M    <information>
    / \
   |   L    <information about L>
   |   |
   J   |    <information about J>
   :   :
Run Code Online (Sandbox Code Playgroud)

这与实际输出非常接近,但略有不同:

N  <information about N>
|
M  <information about M>
|\
| L  <information about L>
| |
J |  <information about J>
: :
Run Code Online (Sandbox Code Playgroud)

这就是您在自己的git log输出中最常看到的内容。

“第一父母”很重要

最后一件看起来特别奇怪的事情是:

| *   commit 309a287f9c39d219d719efbcc872a176f7644b19
| |\  Merge: dafe938 806e855
| |/  Author: 
|/|   Date:   Sun May 17 17:42:46 2020 +0100
| |
| |       Merge branch 'blackforest'
| |
* | commit 806e8558cd7b24658a998b2ee5d19500e608b77d
Run Code Online (Sandbox Code Playgroud)

为什么git log这个时髦的突出到右边,然后摆动回左边的东西?也就是说,为什么要画这个:

| *
| |\
| |/
|/|
: :
Run Code Online (Sandbox Code Playgroud)

什么时候:

| *
|/|
: :
Run Code Online (Sandbox Code Playgroud)

可能会吗?

这里的答案是,在 Git 中,合并提交的第一父级是重要的。在我自己的水平图中:

...--I--J
         \
          M--N   <-- master
         /
...--K--L
Run Code Online (Sandbox Code Playgroud)

我试图让连接看起来没有什么J <-M比连接更重要的了L <-M。那是因为从某种意义上说,这里没有什么更重要的了:JL都是 的父级M,当我们进行合并时,如果我们不使用-X oursor -X theirs,那么在解决冲突时任何一个提交都没有什么更重要的了,任何一个。

但是任何合并提交的第一个父项都是特殊的,原因任何正常的非合并提交的第一个也是唯一的父项是特殊的相同:它是我们所做的提交的直接沿袭,向后,一次提交一次.

考虑我们如何进行正常的日常非合并提交。我们从:

git checkout master
Run Code Online (Sandbox Code Playgroud)

这让我们得到了这样的东西:

...--G--H   <-- master (HEAD)
Run Code Online (Sandbox Code Playgroud)

也就是说,特殊名称HEAD现在附加到分支名称master。在当前分支是现在master。在最近提交的尖端提交 master,即承诺H:它是承诺H的内容,我们已经在我们的工作树,我们可以工作。

如果我们确实做了一些工作,并且git addgit commit,我们会得到一个新的提交,我们称之为I. 新提交已提交H作为其父项:

...--G--H   [master used to point to H]
         \
          I
Run Code Online (Sandbox Code Playgroud)

最后的行为git commit是将I的实际哈希 ID(无论是什么)写入name master

...--G--H   [master used to point to H]
         \
          I   <-- master (HEAD)
Run Code Online (Sandbox Code Playgroud)

之后我们可以再次将它们绘制为一条直线:

...--G--H--I   <-- master (HEAD)
Run Code Online (Sandbox Code Playgroud)

当我们重复这个过程时,分支就会增长。还是那个含糊不清的词,branch:这次它意味着一系列以特定指定的提交 名称master结束的提交,两者都在同一时间,这取决于我们当时想要它的含义

...--G--H--I--J   <-- master (HEAD)
Run Code Online (Sandbox Code Playgroud)

如果我们现在有其他一些分支:

...--G--H--I--J   <-- master (HEAD)
         \
          K----L   <-- feature
Run Code Online (Sandbox Code Playgroud)

并运行git merge feature,我们得到一个新的合并提交M。合并提交扩展,master因为HEAD附加到master

...--G--H--I--J--M   <-- master (HEAD)
         \      /
          K----L   <-- feature
Run Code Online (Sandbox Code Playgroud)

即使名称feature不存在,我们也可以这样做,只要我们能以某种方式找到提交L。也就是说,如果我们做了提交KL这个样子,然后删除名字feature完全,同时确保Git不会清理出的提交,我们将有:

...--G--H--I--J   <-- master (HEAD)
         \
          K----L   [unnamed]
Run Code Online (Sandbox Code Playgroud)

然后我们可以运行,我们会得到和以前一样的结果:git merge hash-of-L

...--G--H--I--J--M   <-- master (HEAD)
         \      /
          K----L
Run Code Online (Sandbox Code Playgroud)

提交L现在已经成为名称查找,能够再次:这是第二次的家长M

更常见的是,我们可能会合并featuremaster、产生M,然后删除 namefeature,让我们处于同样的状态。

把这一切放在一起

Git 并不关心(很)名字。 Git 只关心提交。Git使用名称来查找提示提交,然后向后工作;如果可以找到任何给定的提交,则提交保持不变。

在所有情况下,提交的首父性很重要。对于普通(非合并)提交,第一个父级是唯一的父级。对于合并的提交,第一父提交即尖端,在我们提出合并的时间。

如果我们使用git log --first-parent—with or without --graphgit logwill,每当它到达合并提交时,忽略除第一个 parent 之外的所有内容。也就是说,给定以合并提交结束的图形片段Mgit logwithout--first-parent将显示:

  • 提交 M; 然后
  • 按某种顺序提交 J 或 L;然后
  • 以某种顺序提交 I 或 J 或 K 或 L,但跳过之前显示的提交;

依此类推,直到它显示了所有可以通过开始M和向后工作找到的提交。它一次显示每个提交,它使用的顺序是您可以控制的,git log输出选项中对提交进行各种排序

git log --author-date-order
Run Code Online (Sandbox Code Playgroud)

使用作者日期时间戳而不是提交者日期时间戳(每个提交都有两个日期和时间戳)。或者:

git log --topo-order
Run Code Online (Sandbox Code Playgroud)

使用git log --graph需要的排序,因此git log --graph打开该排序约束。

添加--first-parent告诉git log当它退出提交时M,它应该查看提交J,而不是提交L。CommitL第二个父级,因此git log应该将其从要访问的提交列表中删除。结果将git log --first-parent master显示M,然后J,然后I,然后H,然后G,依此类推。

这种特殊的控制良好的顺序的原因是使用优先级队列一次提交一个git log图。你给出了一些起始提交:git log

git log master
Run Code Online (Sandbox Code Playgroud)

或者:

git log --all
Run Code Online (Sandbox Code Playgroud)

例如,并git log计算出这些提交的哈希 ID。如果你给了一个分支名称,比如master,那就是一个提交:那个分支的提示提交。队列中现在有一个条目。

然后,只要队列不为空,就git log执行一个循环:

  1. 从队列中取出前面(最高优先级)条目。
  2. 显示那个提交。(有些选项可能不在这里显示,但我们没有使用任何这些选项。)
  3. 如果我们还没有显示它们并且它们还没有在队列中,则将此提交的父项放入队列中。使用该--first-parent选项,仅将第一个父项放入队列。
  4. 重复。

因此,我们的git log masterwith--first-parent在任何时候队列中都不会有多个提交:它从 1 开始,将其删除到零,显示提交,然后放入一个父项。没有--first-parent,它从一个提交开始M——在队列中,删除它(队列为空),显示M,并将两个提交插入队列:JL。现在优先级很重要。

默认优先级是后提交者日期提交具有更高的优先级。如果我们提交的提交L晚于提交J,我们将显示的下一个提交是L. 无论L是第一父母还是第二父母都是如此。

git log --graph代码将确保从到的连接线ML第二个父级)从向下和向右开始。这就是我们在这里看到的:

| *   commit 309a287f9c39d219d719efbcc872a176f7644b19
| |\  Merge: dafe938 806e855
| |/  Author: 
|/|   Date:   Sun May 17 17:42:46 2020 +0100
| |
| |       Merge branch 'blackforest'
| |
* | commit 806e8558cd7b24658a998b2ee5d19500e608b77d
Run Code Online (Sandbox Code Playgroud)

309a287...到的连接器从806e855...向下和向右开始,然后折回以加入左侧线。的第二个父级309a287...806e855...。(第一个父母是dafe938...。)

向下延伸到的行806e855...来自 commit 3d41b51...,这是一个普通的单父提交。 提交是从 commit 中找到的c501f9fc...,这也是一个普通的单父提交,并且是——或者至少,你的 Git 最后一次检查blackforest——Git 存储库中分支的提示提交origin(在 GitHub 或任何地方)。

(您的图表也与 的两次提交略微混乱git stash。这些提交不在任何分支上,但可以通过名称 访问refs/stash。请注意,其中一个在技术上是合并提交 - 但git merge没有进行此提交;已git stash完成它;如果你把它当作一个普通的合并,大多数 Git 命令都不会从中得到很好的意义,因为他们会假设git merge它已经完成。只有git stash它自己知道以后如何将它分开。)