[英]Git history for branch after merge
I am a bit confused on how git stores history after merging.我对合并后 git 如何存储历史感到有些困惑。
I have merged branch A
to branch B
successfully.我已成功将分支
A
合并到分支B
Now, when I go to a file, in branch B
, that was part of the merge I see all the history for that file for branch A
but I don't see any history for branch B
.现在,当我转到分支
B
的文件时,这是合并的一部分,我看到分支A
该文件的所有历史记录,但我没有看到分支B
任何历史记录。 Where has my history for that file for branch B
gone to?我的分支
B
那个文件的历史记录去了哪里?
The way I merged was through git merge <branch>
so in this case, I was in branch B
and used git merge A
.我合并的方式是通过
git merge <branch>
所以在这种情况下,我在分支B
并使用了git merge A
。
For example, in branch A
I had the following commits: a
, aa
, aaa
corresponding to different files.例如,在分支
A
我有以下提交: a
、 aa
、 aaa
对应于不同的文件。
In branch B
, I had the following commits: b
, bb
, bbb
corresponding to different files.在分支
B
,我有以下提交: b
、 bb
、 bbb
对应于不同的文件。
Now when I merged branch A
into branch B
, all I see in branch B git log
are a
, aa
, aaa
history.现在,当我将分支
A
合并到分支B
,我在分支B git log
中看到的只是a
、 aa
、 aaa
历史记录。 I don't see my b
history.我没有看到我的
b
历史。
In essence, I want my merge to be linear, when I merge A
to B
then I want the history to have all of branch B
history and on top of the history it will be the merge that just occurred similar to how SVN does it.从本质上讲,我希望我的合并是线性的,当我将
A
合并到B
我希望历史拥有分支B
所有历史,并且在历史之上,它将是刚刚发生的合并,类似于 SVN 的做法。
My current git log history is very confusing.我当前的 git 日志历史非常混乱。
The history isn't gone , Git just isn't showing it.历史并没有消失,只是 Git 没有展示它。
In Git, the history is the set of commits.在 Git 中,历史是一组提交。 There is no file history!
没有文件历史记录!
When you run a command like git log dir/sub/file.ext
, or for that matter, git log dir/sub
or git log .
当您运行
git log dir/sub/file.ext
类的命令时,或者就此而言, git log dir/sub
或git log .
while in dir/sub
, Git will synthesize a (temporary) file history, by extracting some sub-history from the real history—the set of commits.而在
dir/sub
,Git 将通过从真实历史记录(提交集)中提取一些子历史记录来合成(临时)文件历史记录。 This synthetic process deliberately drops some commits.这个合成过程故意丢弃一些提交。 For instance, it drops all commits that don't affect any of the files you have asked about.
例如,它会删除所有不影响您询问的任何文件的提交。 But by default, it drops a lot more than that, via something that
git log
calls History Simplification .但默认情况下,通过
git log
调用History Simplification 的东西,它下降的远不止这些。
Every commit has a unique hash ID.每个提交都有一个唯一的哈希 ID。 You see these in
git log
output, for instance.例如,您可以在
git log
输出中看到这些。 The hash ID is actually just a cryptographic checksum of the commit's content.哈希 ID 实际上只是提交内容的加密校验和。
Each commit stores (the hash ID of) a snapshot of files—Git calls this a tree .每个提交都存储(的哈希 ID)文件快照——Git 称其为树。 This is true of merge commits as well: a merge commit, like any other commit, has a tree.
合并提交也是如此:合并提交,就像任何其他提交一样,有一个树。
Each commit also stores your name (author and committer) and email address and time-stamp, so that Git can show these to you.每个提交还会存储您的姓名(作者和提交者)、电子邮件地址和时间戳,以便 Git 可以向您显示这些内容。 It stores a log message—whatever you give it—so that Git can show that as well.
它存储一条日志消息——不管你给它什么——这样 Git 也可以显示出来。
The last thing that Git stores in a commit—the second thing, really, right after the tree
—is a list of parent commits, by their unique hash IDs. Git 存储在提交中的最后一件事——第二件事,实际上,就在
tree
——是父提交的列表,通过它们唯一的哈希 ID。
When dealing with ordinary, non-merge commits, it's pretty straightforward to look at the history.在处理普通的非合并提交时,查看历史非常简单。 We simply start with the latest commit, as identified by some branch name like
master
, and work backwards.我们只是从最新的提交开始,由一些分支名称(如
master
标识,然后向后工作。 The branch name contains the hash ID of the last commit—the tip of the branch—and we say that the branch name points to that commit:分支名称包含最后一次提交的哈希 ID——分支的尖端——我们说分支名称指向该提交:
... <--1234567... <--master
If commit 1234567
is the tip of master
, git log
can show you commit 1234567
... and commit 1234567
has inside it the hash ID of the commit that comes right before 1234567
.如果提交
1234567
是master
的提示,则git log
可以显示您提交1234567
... 并且提交1234567
在其中包含在1234567
之前的提交的哈希 ID。
If we swap out real hash IDs for single letters, to make things easier, we get something like this:如果我们将真实的哈希 ID 换成单个字母,为了让事情变得更容易,我们会得到这样的结果:
A <-B <-C <-D <-E <-F <-G <--master
Commit G
points back to commit F
, which points back to E
, and so on until we reach the very first commit, commit A
.提交
G
指向回提交F
,后者又指向E
,依此类推,直到我们到达第一个提交,即提交A
。 This commit does not point anywhere—it can't , it was the first commit;这个提交没有指向任何地方——它不能,这是第一次提交; it cannot have a parent—so this is where the history ends (starts?), at the beginning of time.
它不能有父级——所以这是历史结束(开始?)的地方,在时间的开始。 Git calls
A
a root commit: a commit with no parent. Git 将
A
称为根提交:没有父提交的提交。
It's easy to show linear history, starting at the end of time and ending at the start.很容易显示线性历史,从时间的尽头开始,在开始时结束。 Git just picks out each commit one at a time and shows it.
Git 一次只挑选一个提交并显示它。 That's what:
就是这样:
git log master
does: it starts with the one commit identified by master
, and shows it, and then shows the one commit's one parent, and then shows the one before that, and so on.确实:它从
master
标识的一个提交开始,并显示它,然后显示一个提交的一个父提交,然后显示之前的一个,依此类推。
When you have Git show you a commit, you can—in fact, you almost always—have Git show it as a patch , rather than as a snapshot.当您让 Git 向您展示提交时,您可以——事实上,您几乎总是——让 Git 将其显示为补丁,而不是快照。 For instance,
git log --patch
does this.例如,
git log --patch
这样做的。 To show a commit as a patch, Git just looks at the commit's parent 's tree first, then at the commit's tree, and compares the two.要将提交显示为补丁,Git 只需先查看提交的父树,然后查看提交的树,然后比较两者。 Since both are snapshots, whatever changed from the parent's snapshot to the child's, must be whatever the person who made the child commit actually did.
由于两者都是快照,无论从父快照更改为子快照,都必须是让子提交的人实际做了什么。
Now that we know that Git works backwards, let's take a look at more complex history, including history that includes an actual merge commit.现在我们知道 Git 是反向工作的,让我们来看看更复杂的历史,包括包含实际合并提交的历史。 (Let's not get sidetracked by the fact that
git merge
does not always merge!) (让我们不要因为
git merge
并不总是合并的事实而git merge
轨道!)
A merge commit is simply a commit with at least two parents.合并提交只是具有至少两个父项的提交。 In most cases you won't see commits with three or more parents—Git calls these octopus merges , and they don't do anything you cannot do with ordinary merges, so octopus merges are mainly for showing off your Git-fu.
在大多数情况下,您不会看到三个或更多父级的提交——Git 将这些称为octopus merges ,并且它们不会做任何普通合并无法做的事情,因此章鱼合并主要是为了炫耀您的 Git-fu。 :-)
:-)
We normally get a merge by doing git checkout somebranch; git merge otherbranch
我们通常通过
git checkout somebranch; git merge otherbranch
进行合并git checkout somebranch; git merge otherbranch
git checkout somebranch; git merge otherbranch
, and we can draw the resulting commit chain like this: git checkout somebranch; git merge otherbranch
,我们可以像这样绘制结果提交链:
...--E--F--G------M <-- master
\ /
H--I--J <-- feature
Now, suppose you run git log master
(note: no --patch
option).现在,假设您运行
git log master
(注意:没有--patch
选项)。 Git should of course show you commit M
first. Git当然应该让你先提交
M
。 But which commit will Git show next?但是接下来 Git 会显示哪个提交呢?
J
, or G
? J
,还是G
? If it shows one of those, which one should it show after that?如果它显示其中之一,那之后应该显示哪一个?
Git has a general answer to this problem: when it shows you a merge commit, it can add both parents of the commit to a queue of "commits yet to be shown". Git 对这个问题有一个通用的答案:当它向您显示合并提交时,它可以将提交的两个父项添加到“尚未显示的提交”队列中。 When it shows you an ordinary non-merge commit, it adds the (single) parent to the same queue.
当它向您显示一个普通的非合并提交时,它会将(单个)父项添加到同一个队列中。 It can then loop through the queue, showing you commits one at a time, adding their parents to the queue.
然后它可以循环遍历队列,一次向您显示提交一个,将它们的父项添加到队列中。
When the history is linear, the queue has one commit in it at a time: the one commit gets removed and shown, and the queue now has the one parent in it and you see the parent.当历史是线性的时,队列一次只有一个提交:一个提交被删除并显示,队列现在有一个父级,你会看到父级。
When the history has a merge, the queue starts with one commit, Git pops the commit off the queue and shows it, and puts both parents in the queue.当历史有合并时,队列从一个提交开始,Git 将提交从队列中弹出并显示出来,并将两个父项放入队列中。 Then Git picks one of the two parents and shows you
G
or J
, and puts F
or I
into the queue.然后 Git 选择两个父对象之一并显示
G
或J
,然后将F
或I
放入队列中。 The queue still has two commits in it.队列中仍然有两个提交。 Git pops one off and shows that commit and puts another one on.
Git 弹出一个并显示该提交并放置另一个。
Eventually Git tries to put F
on the queue when F
is already on the queue.最终,当
F
已经在队列中时,Git 会尝试将F
放入队列中。 Git avoids adding it twice, so eventually the queue depth reduces to one commit again, in this case showing F
, E
, D
, and so on. Git 避免将其添加两次,因此最终队列深度再次减少为一次提交,在本例中显示
F
、 E
、 D
等。 (The details here are a bit complicated: the queue is specifically a priority queue with the priority being determined by additional git log
sorting parameters, so there are different ways that this can happen.) (这里的细节有点复杂:队列具体是一个优先级队列,优先级由附加的
git log
排序参数决定,因此有不同的方式可以发生这种情况。)
git log --graph
git log --graph
查看连接If you add --graph
to your git log
command, Git will draw a somewhat crude ASCII-art graph with lines connecting child commits back to their parents.如果您将
--graph
添加到您的git log
命令,Git 将绘制一个有点粗糙的 ASCII 艺术图,其中包含将子提交连接回其父提交的线条。 This is very helpful in telling you that the commit history you are viewing is not linear after all, even though git log
is showing you one commit at a time (because it must).这非常有助于告诉您您正在查看的提交历史毕竟不是线性的,即使
git log
向您显示一个提交(因为它必须)。
I mentioned above that with -p
or --patch
, git log
will show what changed in a commit by comparing the parent's snapshot/tree against the child's snapshot/tree.我在上面提到过,使用
-p
或--patch
, git log
将通过将父快照/树与子快照/树进行比较来显示提交中的更改。 But for a merge commit, there are two (or even more) parents: there's no way to show you the comparison of the parent vs the child, because there are at least two parents.但对于合并提交,有两个(或更多)家长:有没有办法给你看父VS儿童的比较,因为至少有两名家长。
What git log
does, by default, is to give up entirely.默认情况下,
git log
所做的是完全放弃。 It simply doesn't show a patch.它根本不显示补丁。 Other commands do something more complicated, and you can convince
git log
to do that too, but let's just note that the default is for git log
to give up here.其他命令做一些更复杂的事情,你也可以说服
git log
这样做,但让我们注意默认是git log
在这里放弃。
git log
documentation) git log
文档的可点击链接) When you run git log file.ext
, Git will deliberately skip any non-merge commit where the diff (as obtained by comparing parent to child) does not touch file.ext
.当您运行
git log file.ext
,Git 会故意跳过任何非合并提交,其中差异(通过将父子与子项进行比较获得)未触及file.ext
。 That's natural enough: if you have a chain like:这很自然:如果你有一个像这样的链:
A--B--C--D--E <-- master
and you changed (or first created) file.ext
when you made commits A
and E
, you'd like to see just those two commits.并且您在提交
A
和E
时更改(或首次创建) file.ext
,您只想看到这两个提交。 Git can do this by figuring out a patch for D
-vs- E
and seeing that file.ext
changed (so it should show E
), then moving on to D
. Git 可以通过为
D
-vs- E
找出补丁并查看file.ext
更改(因此它应该显示E
)来完成此操作,然后转到D
。 The C
-vs- D
comparison shows no change to file.ext
, so Git won't show D
, but it will put C
in the priority queue and go on to visit C
. C
-vs- D
比较显示file.ext
没有变化,所以 Git不会显示D
,但它会将C
放在优先级队列中并继续访问C
。 That, too, has no change to the file, so Git eventually moves on to B
, which has no change, and Git moves to A
.这也对文件没有任何更改,因此 Git 最终移动到
B
,它没有变化,而 Git 移动到A
。 For comparison purposes, all files in A
are always new—that's the rule for any root commit;出于比较的目的,
A
中的所有文件总是新的——这是任何根提交的规则; all files are added—so Git shows you A
as well.所有文件都被添加了——所以 Git 也会向你显示
A
We just saw, though, that by default git log
doesn't like to compute patches for a merge.不过,我们刚刚看到,默认情况下
git log
不喜欢为合并计算补丁。 It's too hard!太难了! So
git log
generally won't show you the merge here.所以
git log
通常不会在这里显示合并。 It does, however, try to simplify away any part of the commit graph.然而,它确实试图简化提交图的任何部分。 As the documentation puts it, the default mode:
正如文档所说,默认模式:
prunes some side branches if the end result is the same ...
如果最终结果相同,则修剪一些侧枝......
If the commit was a merge, and [the file is the same as in] one parent, follow only that parent.
如果提交是合并,并且 [文件与] 一个父项相同,则仅关注该父项。 ... Otherwise, follow all parents.
...否则,跟随所有的父母。
So at a merge commit like M
in our graph, Git will do a fast check: is file.ext
the same in M
as in G
?所以在我们的图中像
M
这样的合并提交时,Git 会做一个快速检查: file.ext
在M
和G
是否相同? If so, add G
to the queue.如果是,则将
G
添加到队列中。 If not, is it the same in M
as in J
?如果不是,在
M
和J
是否相同? If so, add J
to the queue.如果是,则将
J
添加到队列中。 Otherwise—ie, file.ext
is different in M
than in both G
and J
—add both G
and J
to the queue.否则-即
file.ext
是在不同的M
比在两者G
和J
-add既G
和J
到队列中。
There are other modes for History Simplification, which you can select with various flags.历史简化还有其他模式,您可以使用各种标志进行选择。 This answer is already too long so I will leave them to the documentation (see the above link).
这个答案已经太长了,所以我会把它们留给文档(见上面的链接)。
You cannot draw too many inferences from what git log -- path
shows you, because of the history simplification that Git performs.由于 Git 执行的历史简化,您不能从
git log -- path
显示的内容中得出太多推论。 If you want to see everything , consider running git log --full-history -m -p -- path
instead.如果你想看到的一切,考虑运行
git log --full-history -m -p -- path
来代替。 The -m
option splits each merge for git diff
purposes (this goes with the -p
option), and the --full-history
forces Git to follow all parents at all times. -m
选项为git diff
目的拆分每个合并(这与-p
选项一起使用),并且--full-history
强制 Git --full-history
跟随所有父级。
It should all still be there.它应该仍然存在。 Try viewing it with
git log --graph
.尝试使用
git log --graph
查看它。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.