合并后分支的Git历史记录 [英] Git history for branch after merge

查看:140
本文介绍了合并后分支的Git历史记录的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述



我已经合并了分支 A 到分支 B 成功。现在,当我转到一个文件时,在分支 B 中,这是合并的一部分,我可以看到分支 A 但我没有看到分支 B 的任何历史记录。我的分支 B 去过哪个档案?



我合并的方式是通过 git merge< branch> ,所以在这种情况下, code> B 并使用 git merge A






例如,在分支 A 中,我有以下提交:
a aa aaa 对应不同的文件。
$ b 在分支 B 中,我有以下提交:
b bb bbb 对应不同的档案。



现在,当我将分支 A 合并到分支 B ,我在分支 B git log 中看到的所有内容都是 a aa aaa 历史。我没有看到我的 b 历史。



实质上,我希望我的合并是线性的,合并 A B 然后我希望历史记录具有所有分支 B 历史,并且在历史的顶部,它将是刚发生的合并,类似于SVN的合并。

我目前的git日志历史非常混乱。 历史不是 ,Git只是不显示。



在Git中,历史提交的集合。没有文件历史记录!



运行像 git log dir / sub / file.ext 这样的命令时,或者为此, git log dir / sub git log。,同时在 dir / sub ,Git会通过从实际历史记录中提取一些子历史记录(即提交集合)来合成(临时)文件历史记录。这个合成过程故意放弃一些提交。例如,它会删除所有不影响您询问的任何文件的所有提交。但是默认情况下,它通过 git log 调用历史简化
$ b

Longer



每次提交都有一个唯一的哈希ID。例如,您可以在 git log 输出中看到这些内容。哈希ID实际上只是提交内容的加密校验和。

每个提交都存储(文件快照的哈希ID)-Git将其称为。合并提交也是如此:合并提交与任何其他提交一样,都有一棵树。

每次提交还会存储您的姓名(作者和提交者)和电子邮件地址和时间戳,以便Git可以向您显示这些信息。它存储一条日志消息 - 无论你给它什么 - 这样Git也可以显示它。



Git存储在提交中的最后一件事 - 第二件事,真的,紧接着 - 是父提交的列表,通过它们的唯一哈希ID。



线性历史很简单



在处理普通的非合并提交时,查看历史记录非常简单。我们只需从 master 之类的某个分支名称标识的 latest 提交开始,并向后工作。分支名称包含最后一次提交的哈希ID - 分支的 tip - 并且我们说分支名称​​指向提交的

  ...<  -  1234567 ...<  -  master 

如果提交 1234567 master 的提示, git log 可以显示您提交 1234567 ...并提交 1234567 里面有提交的哈希ID 1234567



如果我们换出单个字母的实际散列ID,为了使事情更容易,我们得到如下所示的结果:

  A< B <-C <-D <-E <-F <-G < - 主

提交 G 指向提交 F ,它指向 E ,依此类推,直到我们到达第一个提交,提交 A 。这个提交没有指向任何地方 - 它*不能*,它是第一次提交;它不能有父母 - 所以这是历史在开始时结束(开始?)的地方。 Git调用 A` a root commit:一个没有父对象的提交。

很容易显示线性历史记录,从时间的尽头开始到结束。 Git每次挑选一个提交并显示它。这就是:

  git log master 

确实是这样的:它由 master 标识的一个提交开始,并显示它,然后显示一个提交的一个父代,然后显示以前的那个,等等。



当你让Git向你展示一个提交时,你可以 - 事实上,你几乎总是 - 让Git把它显示为一个 patch ,而不是作为快照。例如, git log --patch 这样做。为了将提交显示为补丁,Git首先查看提交的的树,然后查看提交的树,并比较两者。由于两者都是快照,无论父母的快照发生什么变化,都必须是子女提交实际执行的人。



非线性历史记录更难



现在我们知道Git向后工作了,我们来看看更复杂的历史记录,包括包含实际合并提交的历史记录。 (让我们不要被 git merge 并不总是合并的事实所左右)



合并提交只是一个至少有两个父母的提交。在大多数情况下,你不会看到有三个或更多的父母提交 - Git把这些章鱼合并为,他们不会做任何你不能用普通合并做的事情,所以章鱼合并主要是为了炫耀你的Git-fu。 : - )



我们通常通过执行 git checkout somebranch来进行合并; git merge otherbranch ,我们可以像这样绘制结果链:

  ...- -E  -  F  -  G ------ M < - 主
\ /
H - I - J < - 特征

现在,假设您运行 git log master (note:no - patch 选项)。 Git当然应该显示你首先提交 M 。但是Git下一步会提交哪个提交? J G ?如果显示其中之一,那么应该在之后显示哪一个?



Git对此问题有一个通用的答案:当它向您显示合并提交时,它可以添加 父母的提交到提交尚未显示队列。当它向你显示一个普通的非合并提交时,它将(单个)父代添加到同一个队列中。然后,它可以遍历队列,显示你一次提交一个,并将其父母添加到队列中。



当历史记录为线性时,队列有一个提交它在一次:一个提交被删除并显示,现在队列中有一个父母,你会看到父母。



当历史记录合并,队列从一次提交开始,Git将提交从队列中弹出并显示出来,并将父项放入队列中。然后,Git挑选其中一位父母,并向您显示 G J ,并将 F I 进入队列。队列中仍然有两个提交。



最后,Git会尝试将 F 放置在当 F 已经在队列中时排队。 Git避免了两次添加,所以最终队列深度再次减少到一次,在这种情况下显示 F E D 等等。 (这里的细节有些复杂:队列特别是 优先级队列 ,其优先级由额外的 git log 排序参数确定,因此可能会发生这种情况。)



你可以查看 git log --graph



的连接如果添加 - graph 添加到你的 git log 命令中,Git将绘制一个粗略的ASCII艺术图,并且将子提交连接回父项。尽管 git log 一次显示一次提交(因为它必须),这对告诉您所查看的提交历史记录并非线性非常有帮助, 。



显示合并提交



上面提到的 -p - 修补程序 git log 会显示已更改通过将父级的快照/树与孩子的快照/树进行比较来进行提交。但对于合并提交,有两个(甚至更多)父项:没有办法向您显示 父对子的比较,因为至少有 父母。



默认情况下,放弃什么 git log 完全。它根本不显示补丁。其他命令可以做更复杂的事情,并且您可以说服 git log 也可以这样做,但是我们只需要注意缺省值是 git log 在这里放弃。



历史简化(这是一个可点击的链接到 git log 文档)



运行 git log file.ext 时,Git会故意跳过任何非合并提交,其中diff(通过比较父对象给孩子)不会触及 file.ext 。这很自然:如果你有一个连锁:

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

,并且您已更改(或首次创建) file.ext 当你提交 A E 时,你只想看到这两个提交。 Git可以通过找出 D -vs - E 的补丁并查看 file.ext 改变了(所以它应该显示 E ),然后转到 D C -vs - D 比较显示没有更改为 file.ext ,所以Git 不会显示 D ,但它会把 C 在优先队列中,继续访问 C 。这也没有改变文件,所以Git最终转向 B ,它没有改变,并且Git移动到 A 。为了比较, A 中的所有文件都是新的 - 这是任何root提交的规则;所有文件都被添加 - 因此Git也显示了 A



我们刚刚看到,默认情况下 git log 不喜欢为合并计算补丁。太难了!所以 git log 通常不会在这里显示合并。但是,它会尽量简化提交图的任何部分。正如文档所说的那样,默认模式:如果最终结果是相同的,那么


会修剪一些分支。



如果提交是合并,并且该文件与其中一个父文件的文件相同,请仅跟随该父文件。 ...

所以在像 M 在我们的图中,Git会做一个快速检查: file.ext M codeG ?如果是,请将 G 添加到队列中。如果不是,那么它与 J M 相同吗?如果是这样,请在队列中添加 J 。否则 - 即 file.ext M 中不同于 G J - 添加 G J 到队列中。



历史简化还有其他模式,您可以使用各种标志进行选择。这个答案已经太长了,所以我会把它们留给文档(参见上面的链接)。



结论



<由于Git执行的历史简化,你不能从 git log - path 显示你的过程中引用太多推断。如果您想查看所有内容,请考虑运行 git log - 完整历史记录-m -p - 路径 代替。 -m 选项将每个合并分割为 git diff 目的(这适用于 -p 选项)和 - 完整历史记录强制Git始终关注所有父母。


I am a bit confused on how git stores history after merging.

I have merged branch A to branch B successfully. 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. Where has my history for that file for branch B gone to?

The way I merged was through git merge <branch> so in this case, I was in branch B and used git merge A.


For example, in branch A I had the following commits: a, aa, aaa corresponding to different files.

In branch B, I had the following commits: b, bb, bbb corresponding to different files.

Now when I merged branch A into branch B, all I see in branch B git log are a, aa, aaa history. I don't see my b history.

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.

My current git log history is very confusing.

解决方案

TL;DR

The history isn't gone, Git just isn't showing it.

In Git, the history is the set of commits. 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 . while in dir/sub, Git will synthesize a (temporary) file history, by extracting some sub-history from the real history—the set of commits. 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.

Longer

Every commit has a unique hash ID. You see these in git log output, for instance. The hash ID is actually just a cryptographic checksum of the commit's content.

Each commit stores (the hash ID of) a snapshot of files—Git calls this a tree. 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. It stores a log message—whatever you give it—so that Git can show that as well.

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.

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. 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:

... <--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.

If we swap out real hash IDs for single letters, to make things easier, we get something like this:

A <-B <-C <-D <-E <-F <-G   <--master

Commit G points back to commitF, which points back toE, and so on until we reach the very first commit, commitA. 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 callsA` a root commit: a commit with no parent.

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. 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.

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. For instance, git log --patch does this. 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. 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. (Let's not get sidetracked by the fact that git merge does not always 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. :-)

We normally get a merge by doing git checkout somebranch; git merge otherbranch, and we can draw the resulting commit chain like this:

...--E--F--G------M   <-- master
         \       /
          H--I--J   <-- feature

Now, suppose you run git log master (note: no --patch option). Git should of course show you commit M first. But which commit will Git show next? J, or 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". 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. Then Git picks one of the two parents and shows you G or J, and puts F or I into the queue. The queue still has two commits in it. Git pops one off and shows that commit and puts another one on.

Eventually Git tries to put F on the queue when F is already on the queue. 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. (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.)

You can view connections with 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. 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).

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. 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.

What git log does, by default, is to give up entirely. 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.

History Simplification (this is a clickable link to git log documentation)

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. 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. 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. 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. That, too, has no change to the file, so Git eventually moves on to B, which has no change, and Git moves to A. For comparison purposes, all files in A are always new—that's the rule for any root commit; all files are added—so Git shows you A as well.

We just saw, though, that by default git log doesn't like to compute patches for a merge. It's too hard! So git log generally won't show you the merge here. 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? If so, add G to the queue. If not, is it the same in M as in J? If so, add J to the queue. Otherwise—i.e., file.ext is different in M than in both G and J—add both G and J to the queue.

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. If you want to see everything, consider running git log --full-history -m -p -- path instead. 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.

这篇关于合并后分支的Git历史记录的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆