简体   繁体   English

合并后分支的 Git 历史记录

[英]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我有以下提交: aaaaaa对应于不同的文件。

In branch B , I had the following commits: b , bb , bbb corresponding to different files.在分支B ,我有以下提交: bbbbbb对应于不同的文件。

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中看到的只是aaaaaa历史记录。 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 日志历史非常混乱。

TL;DR TL; 博士

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/subgit 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 的东西,它下降的远不止这些。

Longer更长

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。

Linear history is easy线性历史很容易

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 .如果提交1234567master的提示,则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.由于两者都是快照,无论从父快照更改为子快照,都必须是让子提交的人实际做了什么。

Non-linear history is harder非线性历史更难

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 选择两个父对象之一并显示GJ ,然后将FI放入队列中。 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 避免将其添加两次,因此最终队列深度再次减少为一次提交,在本例中显示FED等。 (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排序参数决定,因此有不同的方式可以发生这种情况。)

You can view connections with 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向您显示一个提交(因为它必须)。

Showing merge commits显示合并提交

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--patchgit 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在这里放弃。

History Simplification (this is a clickable link to 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.并且您在提交AE时更改(或首次创建) 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.extMG是否相同? If so, add G to the queue.如果是,则将G添加到队列中。 If not, is it the same in M as in J ?如果不是,在MJ是否相同? 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比在两者GJ -add既GJ到队列中。

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).这个答案已经太长了,所以我会把它们留给文档(见上面的链接)。

Conclusion结论

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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM