理解 git 图
Understanding the git graph
我使用 git log --graph --all
命令来可视化我的 commit/branch 历史。然而,虽然我理解图表显示的大部分内容,但我很难解释分支可视化的某些部分。
我难以解释的分支是:
a) 提交 309a287
b) 提交 3f7475
c) e9415f
中)
的解释
我也有点疑惑为什么我只同时有一个master分支和一个additional分支,为什么好像是三个分支(三个垂直平行线)。
|
| * 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
|
(我发现你的问题有点不集中,因为我不确定 git log --graph
输出的哪些特定方面令人困惑,所以这会很长,恐怕:我'我会尽量涵盖所有重要内容。)
杂散结束 parenthesis 只是前一行的环绕。通常 git log
运行 通过寻呼机输出,智能寻呼机可以通过在文本上给你一个 left-and-right 可滚动的“window”来解决这个问题,这样close parenthesis 只是从终端 window(或您使用的任何终端仿真器)的右侧消失。
说完这些,让我们具体看一下 309a287
和 3f7475
,但从这个开始:
I am also slightly puzzled by why there seems to be three branches (three vertical parallel lines) as I only ever had a master branch and one additional branch at the same time.
你有,apparently,两个名字:master
和 blackforest
。但是您还有两个不同的 存储库:
Merge branch 'master' of [snipped]
截断的部分将是URL;此特定消息 Merge branch '<name>' of <url>
(可能以 into <name>
结尾)是 git pull
在将合并提交消息传递给 git merge
.[=408= 时生成的消息]1 所以这个 git log --graph
输出意味着你 运行 git pull
在从其他 Git 存储库获得的提交中调用了 git merge
。
我们实际上可以看到那个提交:它是合并提交 e9415f4...
的第二个 parent。因此,如果我们只采用这四行:
| * commit e9415f49f953ca4fe53bd9631e4afea94c3ba4ba (HEAD -> master, ori...
| |\ Merge: 9a1ff1f 3f74752
| | * commit 3f7475269c1134aef39a06f13ae11d74c9496542
| | |\ Merge: 309a287 3d41b51
我们可以看到。这是您自己的神秘提交之一,在本例中为 3f74752...
。它的提交消息以 Merge pull request #1 from
开头。 GitHub 和其他类似的托管站点生成带有此类消息的提交。
所以,您——或其他人——一定已经对某些 web-hosting-site 进行了 this 提交,例如 GitHub。您或他们必须在托管在那里的存储库中进行此提交。这是一个 第二 Git 存储库 并且 它有自己的 b运行ch 名称,所以取决于有多少 b运行ch 名称,可能有数千或数百万个 b运行ches.
他们的 b运行仅在您允许时影响您的存储库。你让你的 Git 调用他们的 Git 并从他们那里获得任何新的提交。您的 Git 将这些提交添加到您的存储库数据库中,该数据库会相当努力地保留它所见过的每个提交。如果您随后 git merge
他们的 提交之一,您将获得直接访问权限,从您自己的 b运行ch 名称——无论 b运行ch 您现在重新开始——提交。
1记住,git pull
表示运行git fetch
,然后运行秒Git 命令。 第二个 Git 命令默认为 git merge
。 git merge
命令有时需要一个合并消息——每当它进行真正的合并时——而 git pull
提供一个,当第二个命令是 git merge
时。 (如果您将 git pull
改为 运行 git rebase
,则不需要合并消息,因此 git pull
不提供。)
Git 并不是关于 b运行ches
了解正在发生的事情的方法是认识到 Git 最后真正关心 提交 。它不太关心 b运行ches。它根本不关心文件,大多数时候——文件只是提交的一些麻烦事。当然,如果不是提交中的文件,我们根本不会使用 Git,但这只是 us 关心文件:那不是 Git.
一旦我们看到 commits 对 Git 很重要,我们就可以看到 b运行ch names进入图片。然后就有可能——尽管仍然有点飞跃——从 b运行ch names 到我们实际上用 b 表示什么的基本问题运行ch(见What exactly do we mean by "branch"?):
A b运行ch name in Git 是指向一个特定提交的指针。 即, 它按名称标识一个提交哈希 ID。其他 Git 名称,例如标签名称,可以完成同样的工作,但是 b运行ch 名称还有一个特殊的 属性:它们 自动移动 , 所以他们总是在 b运行ch.
中命名 last 提交
换句话说,根据定义,存储在 b运行ch 名称 下的哈希 ID 是 b运行ch 中的最后一次提交。然后我们只需要再了解一件关键的事情:每个提交都存储了一组先前提交的哈希 ID。
提交本身就是图表的组成部分。给定一些提交——由一些哈希 ID 标识——Git 可以进入该提交并从该提交中提取其直接前身的哈希 ID。或者,对于合并提交,我们有两个而不是一个直接的前身。2 但是,无论哪种方式,我们最终都会得到这样的结果:
... <-F <-G <-H
其中 H
是某个链中 last 提交的哈希 ID。或者也许一条链在合并提交处结束:
...--I--J
\
M
/
...--K--L
或者之后有一些额外的提交:
...--I--J
\
M--N
/
...--K--L
但在l 情况下,一条链 ends 在 b运行ch tip commit,它这样做是因为 b 运行ch name 指向该提交:
...--G--H <-- master
\
I--J <-- feature
All 到 tip 的提交都在 b运行ch 上,所以在这种情况下,到 H
的提交都在 master
和 feature
,以及 I--J
单独在 feature
上。
2一个合并提交实际上可以有两个以上的parent,但我们真的不需要在这里担心这个。
git log
必须将事物线性化
假设我们有这样一个图表:
...--I--J
\
M--N <-- master
/
...--K--L
其中 N
是最新的提交,其 parent M
是具有两个 parents J
和 L
的合并.通过横向绘制此图,最新的提交向右,我们可以表明在提交的上行或下行(分别为 I-J
和 K-L
)上完成的任何工作都没有严格按照尊重排序到 other 行,但仅在其自己的行内。
请注意,Git 通过从提示提交开始查找提交,如 b运行ch 名称所找到的那样——这里是 master
,它导致提交 N
——然后逆向工作。当它向后工作时,git log
需要从每个提交移动到它的 parent 或 parents。从 N
到 M
这很容易,因为只有一个 parent。但是,从 M
回来,git log
确实 应该 同时访问两个提交 J
和 L
... 但它不能.
具体来说,git log
必须垂直打印。 提示 提交N
将首先出现,因此位于我们终端window 的顶部。在此之下将是提交 M
。如果 git log
可以完成我要绘制的内容,它可能会像这样显示合并的左侧和右侧:
N <information>
|
M <information>
/ \
J L <information about J> <information about L>
| |
I K <information about I> <information about K>
: :
但是git log
做不到,所以近似。它选择 J
和 L
中有一个较晚的 提交者日期 的提交并将其放在第一位:
N <information>
|
M <information>
/ \
| L <information about L>
| |
J | <information about J>
: :
这与实际输出非常接近,但略有不同:
N <information about N>
|
M <information about M>
|\
| L <information about L>
| |
J | <information about J>
: :
这就是您在自己的 git log
输出中最常看到的内容。
"First-parent" 意义重大
最后一件看起来特别奇怪的事情是:
| * commit 309a287f9c39d219d719efbcc872a176f7644b19
| |\ Merge: dafe938 806e855
| |/ Author:
|/| Date: Sun May 17 17:42:46 2020 +0100
| |
| | Merge branch 'blackforest'
| |
* | commit 806e8558cd7b24658a998b2ee5d19500e608b77d
为什么 git log
做这种时髦的 向右伸出,然后又向左摆动 的事情?也就是为什么画这个:
| *
| |\
| |/
|/|
: :
时间:
| *
|/|
: :
可能会怎样? [编辑,2022 年 5 月: 现代 Git 现在将执行此操作。我不确定 Git 的哪个版本在这里变得更聪明。]
这里的答案是,在Git中,合并提交的first-parent-ness很重要。在我自己的横图中:
...--I--J
\
M--N <-- master
/
...--K--L
我试图让 J <-M
连接看起来没有什么比 L <-M
连接更重要。这是因为在某种意义上,没有更重要的东西:J
和L
都是M
的parent ,并且当我们进行合并时,如果我们不使用 -X ours
或 -X theirs
,那么在解决冲突时提交也没有什么比这更重要的了。
但是任何合并提交的 first parent 都是特殊的,原因与 first and only parent 任何正常,non-merge 提交是特殊的:它是我们所做的提交的直接血统,向后,一次提交。
考虑一下我们如何进行正常的日常 non-merge 提交。我们开始于:
git checkout master
这让我们得到这样的结果:
...--G--H <-- master (HEAD)
也就是说,特殊名称 HEAD
现在 附加到 b运行ch 名称 master
。 当前的 b运行ch 现在是 master
。 当前提交是master
的提示提交,即提交H
:它是提交H
的内容,我们可以在 work-tree 中处理。
如果我们确实做了一些工作,git add
和 git commit
,我们会得到一个新的提交,我们将其称为 I
。新提交的 H
作为其 parent:
...--G--H [master used to point to H]
\
I
git commit
的最后一幕是将 I
的实际哈希 ID 写入 name master
:
...--G--H [master used to point to H]
\
I <-- master (HEAD)
之后我们可以再次将它们画成一条直线:
...--G--H--I <-- master (HEAD)
当我们重复这个过程时,b运行ch 会增长。又是那个模棱两可的词,b运行ch:这次它的意思是一系列以特定指定的提交结束的提交 和 名字master
,两者同时,depending on what we want it to mean at that moment.
...--G--H--I--J <-- master (HEAD)
如果我们现在有一些其他的b运行ch:
...--G--H--I--J <-- master (HEAD)
\
K----L <-- feature
和运行 git merge feature
,我们得到一个新的合并提交M
。合并提交扩展 master
因为 HEAD
附加到 master
:
...--G--H--I--J--M <-- master (HEAD)
\ /
K----L <-- feature
即使名字 feature
不存在,我们也可以这样做,只要我们可以 fin 以某种方式提交 L
。也就是说,如果我们像这样提交 K
和 L
,那么 会完全删除名称 feature
,同时确保 Git 不会'清除提交,我们将有:
...--G--H--I--J <-- master (HEAD)
\
K----L [unnamed]
然后我们可以 运行 git 合并 <em>hash-of-L</em>
我们会得到相同的结果结果如前:
...--G--H--I--J--M <-- master (HEAD)
\ /
K----L
提交 L
现在又变成了 find-able 的名字:它是 M
的 第二个 parent。
更常见的是,我们可能会将 feature
合并到 master
中,生成 M
,然后 删除名称 feature
,让我们处于同样的状态。
把这些放在一起
Git 不关心(太多)名称。 Git 只关心提交。 Git 使用 名称来 找到 提示提交,然后向后工作;如果可以找到任何给定的提交,则该提交将保留。
但是 在所有情况下,提交的 first-parent-ness 都很重要 。对于普通 (non-merge) 提交,第一个 parent 是唯一的 parent。对于合并提交,第一个 parent 是 是 提示的提交,在我们进行合并时。
如果我们使用 git log --first-parent
——有或没有 --graph
——git log
将在到达合并提交时 忽略除第一个 parent。也就是说,给定以合并提交 M
结束的图片段,没有 --first-parent
的 git log
将显示:
- 提交 M;然后
- 按某种顺序提交 J 或 L;然后
- 按某种顺序提交 I 或 J 或 K 或 L,但跳过之前显示的任何提交;
依此类推,直到它显示了可以通过从 M
开始并向后工作找到的所有提交。它一次显示每个提交,并且它使用的 order 是您可以控制的,在 git log
output[=426] 中使用各种 sort 提交=]选项:
git log --author-date-order
使用 author-date 时间戳而不是 committer-date 时间戳(每个提交都有两个 date-and-time 时间戳)。或者:
git log --topo-order
使用了 git log --graph
要求的顺序,因此 git log --graph
启用了该顺序约束。
添加 --first-parent
告诉 git log
当它从提交 M
退回时,它应该在提交 J
时只 ],不提交 L
。提交 L
是 second parent,因此 git log
应该将其从要访问的提交列表中删除。结果将是 git log --first-parent master
将显示 M
,然后是 J
,然后是 I
,然后是 H
,然后是 G
,依此类推。
这个特定 well-controlled 顺序的原因是 git log
使用 priority queue 遍历图表 一次提交一个 。您给 git log
一些开始提交:
git log master
或:
git log --all
例如,git log
计算出这些提交的哈希 ID。如果您给一个 b运行ch 命名为 master
,则命名为一个提交:该 b运行ch 的 tip 提交。现在队列中有一个条目。
然后,只要队列不为空,git log
执行一个循环:
- 从队列中取出前面(最高优先级)的条目。
- 显示该提交。 (有些选项可能不显示在这里,但我们没有使用任何这些选项。)
- 如果我们还没有显示它们并且它们还没有在队列中,请将此提交的 parent(s) 放入队列中。使用
--first-parent
选项,仅将 first parent 放入队列。
- 重复。
所以我们的 git log master
和 --first-parent
在任何时候都不会在队列中有超过一个提交:它从一个开始,将其删除到零,显示提交,然后放入那个 parent。没有 --first-parent
,它从一个提交开始——M
——在队列中,删除它(队列为空),显示 M
,然后插入 两个提交到队列中:J
和 L
。现在优先级很重要。
默认优先级是后面-committer-date提交有更高的优先级。如果我们提交 L
晚于提交 J
,我们将显示的下一个提交是 L
。不管 L
是第一个还是第二个 parent.
都是如此
git log --graph
代码将确保 行 从 M
连接到 L
——它的第二个 parent——首先去 down-and-right [edit: or down-and-left now]。这就是我们在这里看到的:
| * commit 309a287f9c39d219d719efbcc872a176f7644b19
| |\ Merge: dafe938 806e855
| |/ Author:
|/| Date: Sun May 17 17:42:46 2020 +0100
| |
| | Merge branch 'blackforest'
| |
* | commit 806e8558cd7b24658a998b2ee5d19500e608b77d
从 309a287...
到 806e855...
的连接器从向下和向右开始,然后加倍返回以加入 left-side 行。 309a287...
的秒parent是806e855...
。 (第一个parent是dafe938...
。)
延伸到 806e855...
的行来自提交 3d41b51...
,这是一个普通的 one-parent 提交。 那个提交是从提交c501f9fc...
中找到的,这也是一个普通的one-parent提交,并且是——或者至少是是, 上次你的 Git 检查——b运行ch blackforest
在 t 中的 tip commite Git 存储库位于 origin
(在 GitHub 或其他地方)。
(您的图表也因 git stash
的两次提交而略显混乱。这些提交不在任何 b运行ch 上,但是可以通过名称 refs/stash
访问。请注意,其中一个在技术上是合并提交——但是 git merge
没有进行此提交;git stash
进行了;如果您将其视为这是一个普通的合并,大多数 Git 命令从中没有很好的意义,因为他们会假设 git merge
成功了。只有 git stash
自己知道以后如何把这个分开.)
我使用 git log --graph --all
命令来可视化我的 commit/branch 历史。然而,虽然我理解图表显示的大部分内容,但我很难解释分支可视化的某些部分。
我难以解释的分支是:
a) 提交 309a287
b) 提交 3f7475
c) e9415f
中)
的解释
我也有点疑惑为什么我只同时有一个master分支和一个additional分支,为什么好像是三个分支(三个垂直平行线)。
|
| * 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
|
(我发现你的问题有点不集中,因为我不确定 git log --graph
输出的哪些特定方面令人困惑,所以这会很长,恐怕:我'我会尽量涵盖所有重要内容。)
git log
运行 通过寻呼机输出,智能寻呼机可以通过在文本上给你一个 left-and-right 可滚动的“window”来解决这个问题,这样close parenthesis 只是从终端 window(或您使用的任何终端仿真器)的右侧消失。
说完这些,让我们具体看一下 309a287
和 3f7475
,但从这个开始:
I am also slightly puzzled by why there seems to be three branches (three vertical parallel lines) as I only ever had a master branch and one additional branch at the same time.
你有,apparently,两个名字:master
和 blackforest
。但是您还有两个不同的 存储库:
Merge branch 'master' of [snipped]
截断的部分将是URL;此特定消息 Merge branch '<name>' of <url>
(可能以 into <name>
结尾)是 git pull
在将合并提交消息传递给 git merge
.[=408= 时生成的消息]1 所以这个 git log --graph
输出意味着你 运行 git pull
在从其他 Git 存储库获得的提交中调用了 git merge
。
我们实际上可以看到那个提交:它是合并提交 e9415f4...
的第二个 parent。因此,如果我们只采用这四行:
| * commit e9415f49f953ca4fe53bd9631e4afea94c3ba4ba (HEAD -> master, ori...
| |\ Merge: 9a1ff1f 3f74752
| | * commit 3f7475269c1134aef39a06f13ae11d74c9496542
| | |\ Merge: 309a287 3d41b51
我们可以看到。这是您自己的神秘提交之一,在本例中为 3f74752...
。它的提交消息以 Merge pull request #1 from
开头。 GitHub 和其他类似的托管站点生成带有此类消息的提交。
所以,您——或其他人——一定已经对某些 web-hosting-site 进行了 this 提交,例如 GitHub。您或他们必须在托管在那里的存储库中进行此提交。这是一个 第二 Git 存储库 并且 它有自己的 b运行ch 名称,所以取决于有多少 b运行ch 名称,可能有数千或数百万个 b运行ches.
他们的 b运行仅在您允许时影响您的存储库。你让你的 Git 调用他们的 Git 并从他们那里获得任何新的提交。您的 Git 将这些提交添加到您的存储库数据库中,该数据库会相当努力地保留它所见过的每个提交。如果您随后 git merge
他们的 提交之一,您将获得直接访问权限,从您自己的 b运行ch 名称——无论 b运行ch 您现在重新开始——提交。
1记住,git pull
表示运行git fetch
,然后运行秒Git 命令。 第二个 Git 命令默认为 git merge
。 git merge
命令有时需要一个合并消息——每当它进行真正的合并时——而 git pull
提供一个,当第二个命令是 git merge
时。 (如果您将 git pull
改为 运行 git rebase
,则不需要合并消息,因此 git pull
不提供。)
Git 并不是关于 b运行ches
了解正在发生的事情的方法是认识到 Git 最后真正关心 提交 。它不太关心 b运行ches。它根本不关心文件,大多数时候——文件只是提交的一些麻烦事。当然,如果不是提交中的文件,我们根本不会使用 Git,但这只是 us 关心文件:那不是 Git.
一旦我们看到 commits 对 Git 很重要,我们就可以看到 b运行ch names进入图片。然后就有可能——尽管仍然有点飞跃——从 b运行ch names 到我们实际上用 b 表示什么的基本问题运行ch(见What exactly do we mean by "branch"?):
A b运行ch name in Git 是指向一个特定提交的指针。 即, 它按名称标识一个提交哈希 ID。其他 Git 名称,例如标签名称,可以完成同样的工作,但是 b运行ch 名称还有一个特殊的 属性:它们 自动移动 , 所以他们总是在 b运行ch.
中命名 last 提交换句话说,根据定义,存储在 b运行ch 名称 下的哈希 ID 是 b运行ch 中的最后一次提交。然后我们只需要再了解一件关键的事情:每个提交都存储了一组先前提交的哈希 ID。
提交本身就是图表的组成部分。给定一些提交——由一些哈希 ID 标识——Git 可以进入该提交并从该提交中提取其直接前身的哈希 ID。或者,对于合并提交,我们有两个而不是一个直接的前身。2 但是,无论哪种方式,我们最终都会得到这样的结果:
... <-F <-G <-H
其中 H
是某个链中 last 提交的哈希 ID。或者也许一条链在合并提交处结束:
...--I--J
\
M
/
...--K--L
或者之后有一些额外的提交:
...--I--J
\
M--N
/
...--K--L
但在l 情况下,一条链 ends 在 b运行ch tip commit,它这样做是因为 b 运行ch name 指向该提交:
...--G--H <-- master
\
I--J <-- feature
All 到 tip 的提交都在 b运行ch 上,所以在这种情况下,到 H
的提交都在 master
和 feature
,以及 I--J
单独在 feature
上。
2一个合并提交实际上可以有两个以上的parent,但我们真的不需要在这里担心这个。
git log
必须将事物线性化
假设我们有这样一个图表:
...--I--J
\
M--N <-- master
/
...--K--L
其中 N
是最新的提交,其 parent M
是具有两个 parents J
和 L
的合并.通过横向绘制此图,最新的提交向右,我们可以表明在提交的上行或下行(分别为 I-J
和 K-L
)上完成的任何工作都没有严格按照尊重排序到 other 行,但仅在其自己的行内。
请注意,Git 通过从提示提交开始查找提交,如 b运行ch 名称所找到的那样——这里是 master
,它导致提交 N
——然后逆向工作。当它向后工作时,git log
需要从每个提交移动到它的 parent 或 parents。从 N
到 M
这很容易,因为只有一个 parent。但是,从 M
回来,git log
确实 应该 同时访问两个提交 J
和 L
... 但它不能.
具体来说,git log
必须垂直打印。 提示 提交N
将首先出现,因此位于我们终端window 的顶部。在此之下将是提交 M
。如果 git log
可以完成我要绘制的内容,它可能会像这样显示合并的左侧和右侧:
N <information>
|
M <information>
/ \
J L <information about J> <information about L>
| |
I K <information about I> <information about K>
: :
但是git log
做不到,所以近似。它选择 J
和 L
中有一个较晚的 提交者日期 的提交并将其放在第一位:
N <information>
|
M <information>
/ \
| L <information about L>
| |
J | <information about J>
: :
这与实际输出非常接近,但略有不同:
N <information about N>
|
M <information about M>
|\
| L <information about L>
| |
J | <information about J>
: :
这就是您在自己的 git log
输出中最常看到的内容。
"First-parent" 意义重大
最后一件看起来特别奇怪的事情是:
| * commit 309a287f9c39d219d719efbcc872a176f7644b19 | |\ Merge: dafe938 806e855 | |/ Author: |/| Date: Sun May 17 17:42:46 2020 +0100 | | | | Merge branch 'blackforest' | | * | commit 806e8558cd7b24658a998b2ee5d19500e608b77d
为什么 git log
做这种时髦的 向右伸出,然后又向左摆动 的事情?也就是为什么画这个:
| *
| |\
| |/
|/|
: :
时间:
| *
|/|
: :
可能会怎样? [编辑,2022 年 5 月: 现代 Git 现在将执行此操作。我不确定 Git 的哪个版本在这里变得更聪明。]
这里的答案是,在Git中,合并提交的first-parent-ness很重要。在我自己的横图中:
...--I--J
\
M--N <-- master
/
...--K--L
我试图让 J <-M
连接看起来没有什么比 L <-M
连接更重要。这是因为在某种意义上,没有更重要的东西:J
和L
都是M
的parent ,并且当我们进行合并时,如果我们不使用 -X ours
或 -X theirs
,那么在解决冲突时提交也没有什么比这更重要的了。
但是任何合并提交的 first parent 都是特殊的,原因与 first and only parent 任何正常,non-merge 提交是特殊的:它是我们所做的提交的直接血统,向后,一次提交。
考虑一下我们如何进行正常的日常 non-merge 提交。我们开始于:
git checkout master
这让我们得到这样的结果:
...--G--H <-- master (HEAD)
也就是说,特殊名称 HEAD
现在 附加到 b运行ch 名称 master
。 当前的 b运行ch 现在是 master
。 当前提交是master
的提示提交,即提交H
:它是提交H
的内容,我们可以在 work-tree 中处理。
如果我们确实做了一些工作,git add
和 git commit
,我们会得到一个新的提交,我们将其称为 I
。新提交的 H
作为其 parent:
...--G--H [master used to point to H]
\
I
git commit
的最后一幕是将 I
的实际哈希 ID 写入 name master
:
...--G--H [master used to point to H]
\
I <-- master (HEAD)
之后我们可以再次将它们画成一条直线:
...--G--H--I <-- master (HEAD)
当我们重复这个过程时,b运行ch 会增长。又是那个模棱两可的词,b运行ch:这次它的意思是一系列以特定指定的提交结束的提交 和 名字master
,两者同时,depending on what we want it to mean at that moment.
...--G--H--I--J <-- master (HEAD)
如果我们现在有一些其他的b运行ch:
...--G--H--I--J <-- master (HEAD)
\
K----L <-- feature
和运行 git merge feature
,我们得到一个新的合并提交M
。合并提交扩展 master
因为 HEAD
附加到 master
:
...--G--H--I--J--M <-- master (HEAD)
\ /
K----L <-- feature
即使名字 feature
不存在,我们也可以这样做,只要我们可以 fin 以某种方式提交 L
。也就是说,如果我们像这样提交 K
和 L
,那么 会完全删除名称 feature
,同时确保 Git 不会'清除提交,我们将有:
...--G--H--I--J <-- master (HEAD)
\
K----L [unnamed]
然后我们可以 运行 git 合并 <em>hash-of-L</em>
我们会得到相同的结果结果如前:
...--G--H--I--J--M <-- master (HEAD)
\ /
K----L
提交 L
现在又变成了 find-able 的名字:它是 M
的 第二个 parent。
更常见的是,我们可能会将 feature
合并到 master
中,生成 M
,然后 删除名称 feature
,让我们处于同样的状态。
把这些放在一起
Git 不关心(太多)名称。 Git 只关心提交。 Git 使用 名称来 找到 提示提交,然后向后工作;如果可以找到任何给定的提交,则该提交将保留。
但是 在所有情况下,提交的 first-parent-ness 都很重要 。对于普通 (non-merge) 提交,第一个 parent 是唯一的 parent。对于合并提交,第一个 parent 是 是 提示的提交,在我们进行合并时。
如果我们使用 git log --first-parent
——有或没有 --graph
——git log
将在到达合并提交时 忽略除第一个 parent。也就是说,给定以合并提交 M
结束的图片段,没有 --first-parent
的 git log
将显示:
- 提交 M;然后
- 按某种顺序提交 J 或 L;然后
- 按某种顺序提交 I 或 J 或 K 或 L,但跳过之前显示的任何提交;
依此类推,直到它显示了可以通过从 M
开始并向后工作找到的所有提交。它一次显示每个提交,并且它使用的 order 是您可以控制的,在 git log
output[=426] 中使用各种 sort 提交=]选项:
git log --author-date-order
使用 author-date 时间戳而不是 committer-date 时间戳(每个提交都有两个 date-and-time 时间戳)。或者:
git log --topo-order
使用了 git log --graph
要求的顺序,因此 git log --graph
启用了该顺序约束。
添加 --first-parent
告诉 git log
当它从提交 M
退回时,它应该在提交 J
时只 ],不提交 L
。提交 L
是 second parent,因此 git log
应该将其从要访问的提交列表中删除。结果将是 git log --first-parent master
将显示 M
,然后是 J
,然后是 I
,然后是 H
,然后是 G
,依此类推。
这个特定 well-controlled 顺序的原因是 git log
使用 priority queue 遍历图表 一次提交一个 。您给 git log
一些开始提交:
git log master
或:
git log --all
例如,git log
计算出这些提交的哈希 ID。如果您给一个 b运行ch 命名为 master
,则命名为一个提交:该 b运行ch 的 tip 提交。现在队列中有一个条目。
然后,只要队列不为空,git log
执行一个循环:
- 从队列中取出前面(最高优先级)的条目。
- 显示该提交。 (有些选项可能不显示在这里,但我们没有使用任何这些选项。)
- 如果我们还没有显示它们并且它们还没有在队列中,请将此提交的 parent(s) 放入队列中。使用
--first-parent
选项,仅将 first parent 放入队列。 - 重复。
所以我们的 git log master
和 --first-parent
在任何时候都不会在队列中有超过一个提交:它从一个开始,将其删除到零,显示提交,然后放入那个 parent。没有 --first-parent
,它从一个提交开始——M
——在队列中,删除它(队列为空),显示 M
,然后插入 两个提交到队列中:J
和 L
。现在优先级很重要。
默认优先级是后面-committer-date提交有更高的优先级。如果我们提交 L
晚于提交 J
,我们将显示的下一个提交是 L
。不管 L
是第一个还是第二个 parent.
git log --graph
代码将确保 行 从 M
连接到 L
——它的第二个 parent——首先去 down-and-right [edit: or down-and-left now]。这就是我们在这里看到的:
| * commit 309a287f9c39d219d719efbcc872a176f7644b19 | |\ Merge: dafe938 806e855 | |/ Author: |/| Date: Sun May 17 17:42:46 2020 +0100 | | | | Merge branch 'blackforest' | | * | commit 806e8558cd7b24658a998b2ee5d19500e608b77d
从 309a287...
到 806e855...
的连接器从向下和向右开始,然后加倍返回以加入 left-side 行。 309a287...
的秒parent是806e855...
。 (第一个parent是dafe938...
。)
延伸到 806e855...
的行来自提交 3d41b51...
,这是一个普通的 one-parent 提交。 那个提交是从提交c501f9fc...
中找到的,这也是一个普通的one-parent提交,并且是——或者至少是是, 上次你的 Git 检查——b运行ch blackforest
在 t 中的 tip commite Git 存储库位于 origin
(在 GitHub 或其他地方)。
(您的图表也因 git stash
的两次提交而略显混乱。这些提交不在任何 b运行ch 上,但是可以通过名称 refs/stash
访问。请注意,其中一个在技术上是合并提交——但是 git merge
没有进行此提交;git stash
进行了;如果您将其视为这是一个普通的合并,大多数 Git 命令从中没有很好的意义,因为他们会假设 git merge
成功了。只有 git stash
自己知道以后如何把这个分开.)