Git表示分支已合并,但显然不存在更改 [英] Git says branch is merged, but changes are apparently not present

查看:294
本文介绍了Git表示分支已合并,但显然不存在更改的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我已经把自己弄成了对我来说没有道理的情况.我会尽力描述它.

I've worked myself into a situation that is not making sense to me. I'll try to describe it as best I can.

我有一个开发分支,并且已通过git checkout develpment && git merge master将master合并到其中.我在这里没有任何合并冲突.

I have a development branch and I've merged master into it via git checkout develpment && git merge master. I didn't get any merge conflicts here.

我感兴趣的是一个特定的提交,假设它是abcd123.当我运行git branch --contains abcd123时,它报告developmentmaster都包含abcd123.当我执行git log时,它会在developmentmaster上的提交列表中显示abcd123.

There is a specific commit that I'm interested in, let's say it's abcd123. When I run git branch --contains abcd123, it reports that both development and master contain abcd123. When I do git log, it shows abcd123 in the list of commits, both on development and on master.

git show abcd123显示包含对两个文件的更改.但是我似乎找不到这些变化.当我查看文件时,无论在development还是master上都没有看到这些更改.当我检查git log -- path/to/file1时,没有看到abcd123,与git log -- path/to/file2相同.

git show abcd123 shows that contains changes to two files. But I can't seem to find these changes. When I look at the files, I don't see those changes, neither on development nor on master. When I inspect git log -- path/to/file1, I don't see abcd123, same for git log -- path/to/file2.

这是怎么回事?提交如何显示,但更改显然不存在?

What's going on here? How can the commit be present, but the changes are apparently not there?

abcd123可能最初是在合并到master的另一个分支(不是development)中引入的.我不知道这是否可以有所作为.

It is possible that abcd123 was originally introduced in another branch (other than development) that was merged into master. I don't know if that could make a difference.

顺便说一句,当我尝试git checkout master && git merge development时(如上所示将master合并到development之后),我遇到很多合并冲突,包括file1和file2.因此,这似乎表明master实际上并未合并到development中-如果已经执行了git merge mastergit merge development应该不会成功?为了引起更多的混乱,git branch --merged developmentmaster已合并到development中.我想这与git merge master ....

By the way, when I try git checkout master && git merge development (after merging master into development as shown above) I get a lot of merge conflicts, including file1 and file2. So that seems to show that master was not actually merged into development -- shouldn't git merge development succeed if git merge master was already executed? To cause more confusion, git branch --merged development says that master has been merged into development. I guess that is consistent with git merge master ....

在这一点上,任何建议都是值得赞赏的.

Any advice at this point is much appreciated.

在这一点上,问题似乎是由于合并失败或以某种方式弄乱了.如果仍然有人在阅读,我认为torek的回答将朝着最富有成果的方向发展.

At this point it appears that the problem is due to a merge that failed or was messed up in some way. If anyone is still reading, I think the direction that torek's answer is going seems most fruitful.

推荐答案

这个答案很长,因为这里有很多事情要做.但是,TL; DR摘要可能是您想要的--full-history.

This answer is long, because there is a lot going on here. The TL;DR summary, though, is probably that you want --full-history.

这里需要解决多个单独的问题:

There are multiple separate issues here that need to be untangled:

  • 显示变化"或您在git log -pgit show中看到的短语通常使人们在解释Git存储的内容时走错了路.
  • git log命令有时对您不利(尤其是在合并周围),纯粹是为了不让您被无用的信息淹没.
  • git merge的操作可能有些棘手.从原理上讲这很简单,但是大多数人不会马上得到它.
  • The phrase "show changes", or what you see in git log -p or git show, often leads people down the wrong path in interpreting what Git stores.
  • The git log command sometimes lies to you (especially around merges), purely in the interest of not overwhelming you with useless information.
  • What git merge does can be a bit tricky. It's straightforward in principle, but most people don't get it right away.

首先让我们看一下Git最常见的普通提交.在Git中,提交是快照(按文件名的文件内容).进行一次提交,然后进行一些更改,并git add更改一个或两个文件,然后进行第二次提交,第二次提交与第一次提交具有所有相同的文件,除了被git add覆盖的内容.

Let's look first at Git's most common, ordinary commits. A commit, in Git, is a snapshot (of file-contents by file names). You make one commit, then you change a few things and git add a changed file or two and make a second commit, and the second commit has all the same files as the first commit, except for the ones overwritten by the git add.

值得将它们作为Git的 commit图的一部分进行绘制.请注意,每个提交都有自己唯一的哈希ID(这些难以记住的字符串之一,例如93aefcbadf00dcafedad),以及 parent 的ID(或以前的ID) ) 犯罪.父提交哈希使Git以反向方式将这些东西串在一起:

It's worth drawing these as parts of Git's commit graph. Note that each commit has its own unique hash ID (one of those impossible-to-remember strings like 93aefc or badf00d or cafedad), plus the ID of a parent (or previous) commit. The parent commit hash lets Git string these things together, in a backwards fashion:

... <-E <-F <-G ...

,每个大写字母代表一个哈希ID,箭头表示每个提交都指向"其父对象的想法.通常我们不需要画内部箭头(最后它们不是很有趣),所以我将它们绘制为:

where each uppercase letter stands in for a hash ID, and the arrows cover the idea that each commit "points back" to its parent. Normally we don't need to draw in the internal arrows (they're not very interesting in the end) so I draw these as:

...--E--F--G   <-- master

名称 master仍然应使用箭头,因为该箭头指向的提交将随着时间而改变.

The name master, however, still deserves an arrow, because the commit to which this arrow points will change over time.

如果我们选择一个类似G的提交,并使用git log -pgit show在没有的情况下查看它,则我们将完整地看到每个文件,与存储在提交中的文件完全相同.实际上,这就是我们使用git checkout进行检出时发生的情况:我们将所有文件全部提取到工作树中,以便我们可以查看和处理它们.但是,当我们使用git log -pgit show进行查看时,Git不会向我们显示所有内容.它只会告诉我们发生了什么变化.

If we pick a commit like G and view it without using git log -p or git show, we will see every file in full, exactly as it is stored in the commit. In fact, that's what happens when we use git checkout to check it out: we extract all the files in full, into the work-tree, so that we can see and work on them. But when we view it with git log -p or git show, Git doesn't show us everything; it only shows us what changed.

为此,Git提取提交其父提交,然后在该对上运行一个大的git diff.父级F和子级G之间的区别是,更改了,所以这就是git log -pgit show向您显示的内容.

To do this, Git extracts both the commit and its parent commit, and then runs a big git diff on the pair. Whatever is different between the parent F and the child G, that's what changed, so that's what git log -p or git show shows you.

对于普通的单亲提交,这一切都很好,但是对于 merge 提交却不起作用.合并提交只是具有两个(或更多,但我们不会担心)父提交的任何提交.您可以通过成功完成git merge来获得这些,我们可能会这样绘制.我们从两个分支开始(从某个起点分叉):

This is all well and good for ordinary, single-parent commits, but it doesn't work for merge commits. A merge commit is simply any commit with two (or more, but we won't worry about this case) parent commits. You get these by doing a successful git merge, and we might draw that like this. We start with the two branches (which fork off from some starting-point):

       H--I   <-- development (HEAD)
      /
...--E--F--G   <-- master

然后运行git merge master. 1 Git现在尝试合并两个分支.如果成功,它将进行一个新的提交,该提交具有两个父级:

and then we run git merge master.1 Git now tries to combine the two branches. If it succeeds, it makes one new commit that has two parents:

       H--I--J   <-- development (HEAD)
      /     /
...--E--F--G   <-- master

名称development现在指向新的合并提交J.括号中的(HEAD)在这里表示这是我们的当前分支.这告诉我们哪个 name 被移动:我们进行一个新的提交(包括任何新的合并提交),而development是更改为指向新提交的分支名称.

The name development now points to the new merge commit J. The parenthesized (HEAD) here denotes that this is our current branch. That tells us which name gets moved: we make a new commit—including any new merge commit—and development is the branch-name that changes to point to the new commit.

如果我们不用担心如何确定合并提交的内容(各种提交的文件),那么这一切就非常简单了.合并提交与其他任何提交一样:具有完整的快照,一堆包含内容的文件.我们检查合并提交,然后像往常一样将这些内容放入我们的工作树中.

If we don't worry about how the contents (the various committed files) of the merge commit are determined, this is all pretty straightforward. The merge commit is like any other commit: it has a complete snapshot, a bunch of files with contents. We check out the merge commit, and those contents get in our work-tree as usual.

但是当我们进入 view 时,合并提交...好吧,Git通常会将提交与父对象进行比较.合并有两个父级,每个分支一个. Git应该与哪一个进行比较以显示更改?

But when we go to view the merge commit ... well, Git normally diffs a commit against its parent. The merge has two parents, one for each branch. Which one should Git diff against, to show you changes?

此处,git loggit show采用不同的方法.当您使用git log查看提交时,默认情况下它完全不显示 .它不会选择I -vs- J,也不会选择G -vs- J!对于git log -p,它什么也没显示.

Here, git log and git show take different approaches. When you view the commit with git log, it shows nothing at all by default. It won't choose I-vs-J, and it won't choose G-vs-J either! It just shows nothing at all, for git log -p.

1 在某些Git工作流程中,不建议将 from master合并到任何其他分支中.但是它可以工作,并且既然您已经做到了,那就让我们运行它.

1In some Git workflows, merging from master into any other branch is discouraged. It can work, though, and since you did, let's run with it.

git show命令的功能有所不同,并且效果更好.它运行两个 git diff,一个运行I -vs- J,另一个运行G -vs- J.然后,它尝试组合这两个差异,仅向您显示两者中的变化.也就是说,在JI不同但没有特别有趣的方式的情况下,Git抑制了这种差异.在JG不同但没有特别有趣的方式的地方,Git也抑制了这种差异.这可能是最有用的模式,因此它是git show所显示的内容.仍然还很不完善,但是您在这里无法做的所有事情都是完美的.

The git show command does something different and better. It runs two git diffs, one for I-vs-J and one for G-vs-J. It then tries to combine the two diffs, showing you only what changed in both. That is, where J is different from I but not in a particularly interesting way, Git suppresses the difference. Where J is different from G but not in a particularly interesting way, Git suppresses this difference as well. This is probably the most useful mode, so it's what git show shows. It's still quite imperfect, but nothing you can do here is perfect for all purposes.

通过将--cc添加到git log -p选项,可以使git log执行相同的操作.或者,您可以使用-m更改 git log git show显示合并提交的方式(请注意,对于-m,一个破折号,对于--cc,两个破折号>,顺便说一句.

You can make git log do this same thing by adding --cc to the git log -p options. Or, you can change how either git log or git show shows a merge commit by using -m (note one dash for -m, two for --cc, by the way).

-m选项告诉Git,出于查看目的,它应该分割合并.现在,Git将IJ进行比较,以向您展示通过合并G带来的一切.然后,Git将G与拆分后的J附加版本进行比较,以向您展示通过合并I带来的一切.产生的差异通常很大,但(或因为)它会向您显示所有内容.

The -m option tells Git that for viewing purposes, it should split the merge. Now Git compares I to J, to show you everything you brought in through merging G. Then Git compares G to the split-off extra version of J, to show you everything you brought in through merging I. The resulting diff is usually very large but (or because) it shows you everything.

还有更多方法来查找某个文件发生了什么,但是我们需要稍等片刻,然后才能找到您的文件:

There are more ways to try to find what happened to some file, but we need to hold off a moment before getting to your:

git log -- path/to/file1

命令.就像我们看到git log skipping 合并一样,它在这里可能会跳过更多的事情(但是有一些方法可以阻止Git这样做).

command. Just as we saw git log skipping merges, it may skip even more things here (but there are ways to stop Git from doing that).

让我们再次查看该合并前的图形:

Let's look at that pre-merge graph again:

       H--I   <-- development (HEAD)
      /
...--E--F--G   <-- master

在这里,分支development上有两个提交不在分支master上,而master上有两个提交不在development上.提交E(以及所有 all 早期提交)在两个分支上.但是,Commit E是特殊的:这两个分支上都是最近的 2 提交.提交E是Git所说的合并基础.

Here, there are two commits on branch development that are not on branch master, and two commits on master that are not on development. Commit E (along with all earlier commits) is on both branches. Commit E is special, though: it's the most recent2 commit that's on both branches. Commit E is what Git calls the merge base.

要执行合并,Git有效地运行两个git diff命令:

To perform a merge, Git effectively runs two git diff commands:

git diff E I
git diff E G

第一个对一组文件产生了一组更改,这些更改是我们在分支development上所做的事情".实际上,如果将HI视为补丁,则它们之和.第二个对不同文件产生了一组可能不同的更改,我们在master上所做的",并且像以前一样有效地是FG的总和作为补丁.

The first produces a set of changes to various files, which are "what we did on branch development". It is, in effect, the sum of H and I if they are treated as patches. The second produces a—probably different—set of changes to various files, "what we did on master", and as before it's effectively the sum of F and G as patches.

Git然后尝试合并这两个差异.无论它们之间是什么完全独立的,它都需要进行这两组更改,并将它们应用于提交E的内容,并将其用作结果.无论两个变更集在 same 文件中的哪一行接触同一行,Git都会尝试查看它是否只能获取该变更的一个副本.如果两者都修复了文件README的第33行上的单词的拼写,那么Git可以仅获取拼写修复程序的一个副本.但是,只要这两个变更集在同一个文件的同一行上发生了什么变化,但进行了不同更改,Git就会声明合并冲突",将其隐喻的手举起来,并使修复造成的混乱.

Git then tries to combine these two diffs. Whatever is completely independent between them, it takes both sets of changes, applies them to the contents of commit E, and uses that as the result. Wherever the two change-sets touch the same line in the same file, Git tries to see if it can just take one copy of that change. If both fixed the spelling of a word on line 33 of file README, Git can just take one copy of the spelling fix. But wherever the two change-sets touch the same line of the same file, but make a different change, Git declares a "merge conflict", throws its metaphorical hands in the air, and makes you fix up the resulting mess.

如果您(或进行合并的任何人)愿意,即使Git认为一切顺利,他们也可以阻止Git提交合并结果:git merge --no-commit master使Git在合并所有内容后停止.此时,您可以在编辑器中打开工作树文件,进行 change 编写,写回它们,git add更改后的文件,然后git commit合并以将某些内容放入合并后的文件中.不是来自三个输入(基本输入和两个分支提示)的 any .

If you (or whoever does the merge) wants to, they can stop Git from committing the merge result even if Git thinks it all went swimmingly: git merge --no-commit master makes Git stop after combining everything. At this point, you can open work-tree files in your editor, change them, write them back, git add the changed file, and git commit the merge to put something in the merge that did not come from any of the three inputs (base and two branch-tips).

无论如何,理解所有这些的关键是合并基础提交的概念.如果您坐下来绘制提交图,则合并基础通常非常明显,除非该图失去控制(实际上发生的次数很多).您还可以让Git为您找到合并基础,或者在某些情况下合并基础(复数形式):

In any case, the key to understanding all of this is the concept of the merge base commit. If you sit down and draw the commit graph, the merge base is usually pretty obvious unless the graph gets way out of hand (which happens a lot, actually). You can also have Git find the merge base for you—or merge bases, plural, in some cases:

git merge-base --all master development

这将打印出哈希ID.在我们的假设情况下,这将是提交E的哈希ID.一旦有了它,就可以手动运行git diff,以查看每个文件发生了什么.或者,您可以运行单个文件git diff:

This prints out a hash ID. In our hypothetical case here, that would be the hash ID of commit E. Once you have that, you can run git diff manually, to see what happened to every file. Or you can run an individual-file git diff:

git diff E development -- path/to/file1
git diff E master -- path/to/file1

请注意,如果将名称masterdevelopment替换为 当前之前的提交的哈希ID,则执行git merge甚至在合并后 都可以工作.这将告诉您Git认为path/to/file1应该结合什么.反过来,这会告诉您Git是否没有看到更改,或者是谁进行了合并覆盖 Git,还是不正确地处理了冲突的合并.

Note that if you replace the names master and development with the hash IDs of the commits that were current before you did a git merge, this works even after the merge. That will tell you what Git thought it should combine for path/to/file1. That, in turn, will tell you whether Git did not see the change, or whether whoever made the merge overrode Git, or handled a conflicting merge incorrectly.

一旦合并,随后的合并将找到不同的合并基础:

Once you have a merge, a subsequent merge will find a different merge base:

       H--I--J----K   <-- development
      /     /
...--E--F--G--L--M   <-- master

我们现在查看两个分支技巧,并沿着历史记录(向左方向)向后追溯,沿着J之类的合并的两个分支,找到我们可以从都到达的第一个提交分支提示.从K开始,我们回到J,然后回到IG.从M开始,我们回到L,然后回到G.我们发现G在两个分支上,因此commit G是新的合并基础. Git将运行:

We look now at both branch tips and work our way backwards through history (in the leftward direction), following both forks of a merge like J, to find the first commit we can get to from both branch tips. Starting at K, we go back to J, then to both I and G. Starting at M, we go back to L, then to G. We find G to be on both branches, so commit G is the new merge base. Git will run:

git diff G K
git diff G M

以获得两个更改集以应用于基于合并的提交G.

to get the two change-sets to apply to merge-base commit G.

2 这里的最新"是指提交 graph 的顺序,而不是时间戳,尽管这可能是两个分支上具有最新时间戳的提交

2"Most recent" here refers to commit graph order, rather than time stamps, although it's probably also the commit with the newest time stamp that is on both branches.

我们已经看到git log -p只是跳过合并提交.您根本看不到任何差异,好像合并完全是魔术.但是当您运行时:

We already saw that git log -p just skips right over merge commits. You don't see any diff at all, as if the merge were totally magic. But when you run:

git log -- path/to/file1

发生其他事情,甚至更加阴险.在(长)中对此进行了描述,尽管相当不透明. git log文档在标题为简化历史记录.

something else, even more insidious, happens. This is described, albeit rather opaquely, in the (long) git log documentation under the section titled History Simplification.

在上面的示例中,假设git logK向后走.它找到J,这是一个合并.然后,它会检查IG,并在排除您正在查看的一个文件之外的所有文件之后,将它们与J 进行比较.也就是说,它只是在三个提交中比较path/to/file1.

In our example above, suppose git log is walking from K backwards. It finds J, which is a merge. It then inspects both I and G, comparing each to J after excluding all but the one file you are looking at. That is, it's just comparing path/to/file1 in the three commits.

如果合并的一侧没有显示path/to/file1的任何更改,则意味着合并结果J与输入(IG)没有什么不同. . Git将此称为"TREESAME".如果合并J在被精简到一个文件之后匹配了IG同样被精简的文件,则J是对IG(或也许两者)的树.在这种情况下,Git会选择TREESAME父级或其中的任何一个,并在该路径下仅 .假设它选择的是I(沿上排),而不是G(沿下排).

If one side of the merge doesn't show any change to path/to/file1, that means the merge result J was no different from the input (I or G). Git calls this "TREESAME". If the merge J, after being stripped down to this one file, matches I or G similarly stripped-down, then J is TREESAME to I or G (or perhaps both). In this case, Git picks the, or any one of the, TREESAME parent(s) and looks only at that path. Let's say it picks I (along the top row) rather than G (along the bottom).

在我们的情况下,这意味着如果某人在合并过程中丢球,而丢失了原本应该从F进入J的更改,则git log不会显示该更改. log命令先查看K,然后查看J,然后查看但丢弃G,仅查看I,然后H,然后再E,然后再执行任何更早的提交.它根本不会查看commit F!因此,我们看不到从Fpath/to/file1的更改.

What this means in our case is that if someone dropped the ball during a merge, losing a change that was supposed to come in to J from F, git log never shows it. The log command looks at K, then J, then looks at but drops G, and looks only at I, then H, then E, and then any earlier commits. It never looks at commit F at all! So we don't see the change to path/to/file1 from F.

这里的逻辑很简单;我将直接引用git log文档,但要强调一些重点:

The logic here is simple; I'll quote the git log documentation directly but add some emphasis:

[默认模式]将历史简化为最简单的历史,解释树的最终状态.最简单,因为如果最终结果相同,它会修剪一些侧枝 ...

由于删除了F中的更改,因此Git宣布它们无关紧要.您不需要看他们!我们将完全忽略合并的那一面!

Since the changes in F were dropped, Git declares them to be irrelevant. You don't need to see them! We'll just ignore that side of the merge entirely!

您可以使用--full-history彻底击败它.这告诉Git not 修剪合并的任何一侧:它应该查看两个历史记录.这将为您找到提交F.添加-m -p还应该找到所做更改的 位置,因为它将找到与文件相关的 all 个提交:

You can defeat this completely with --full-history. That tells Git not to prune either side of a merge: it should look down both histories. This will find commit F for you. Adding -m -p should also find where the changes were dropped, since it will find all commits that touch the file:

git log -m -p --full-history -- path/to/file1

如果更改在那里(在我们的示例中为commit F)不再存在,则丢失的方式只有两种:

If the changes were there (in commit F in our example) and are no longer, there are only two ways they were lost:

  • 在普通提交中将它们还原(通过git revert或手动).即使没有-m,您也会将其视为触及历史中的path/to/file1的提交; -p将显示差异.

  • They were reverted (either with git revert, or manually) in an ordinary commit. You would see this as a commit that touches path/to/file1 in the history even without -m; -p will show you the diff.

或者,它们在合并期间被删除而丢失.即使没有-m,您也将看到合并,但不确定是否有人将合并丢到了这里.但是-m -p将显示两个父差异,包括应该具有(但没有)的差异.

Or, they were lost by being dropped during a merge. You would see the merge even without -m, but not know for sure that whoever did the merge dropped the ball here; but -m -p will show both parent diffs, including the one that should have (but did not) take the change.

这篇关于Git表示分支已合并,但显然不存在更改的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

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