合并后丢失的Git提交 [英] Git commit lost after merge
问题描述
我们有3个分支(A,B,C),如下所示:
We have 3 branches (A, B, C) as below:
---\--A1--\------Am------Merge1---An---Merge2---
\ \ / /
\ \--C1---C2---/ /
\ /
\--B1--------------Bn---------/
问题出现在Merge2.分支C上的某些提交(不是全部,而是,例如C2)在Merge2之后丢失了,分支A上的分支A出现在Merge1和Merge2之间.
The problem appears at Merge2. Some commit on branch C ( not all but some, let's say C2) is lost on branch A after Merge2, which is present between Merge1 and Merge2.
执行Merge2时,只有一个文件冲突,与丢失的提交(C2)不相关.并且我们解决了冲突,并成功完成了合并.
When doing Merge2, there is only one file conflict, which not relates to the lost commit (C2). And we resolve the confilict and finish the merge successfully.
似乎分支A上的Merge2反转了C2,没有任何日志.
发生了什么事?造成这种情况的原因可能是什么?
What happened? What might be the cause to this situation?
推荐答案
您的意思不是说本身丢失了,而是所做的更改(到某个文件)中的提交已恢复(未创建).
What you mean is not that the commit itself is lost, but rather that the changes made (to some file) in that commit have been reverted (un-made).
在这里值得注意的是,提交实际上并不是对文件进行更改".相反,每个提交都存储完整的完整文件集.当您要求Git向您显示一些提交时(例如,其ID为c2c2c2...
):
It's worth noting here that commits don't really "make changes to" files. Instead, each commit stores a complete set of files, whole and intact. When you ask Git to show you some commit (let's say its ID is c2c2c2...
):
$ git show c2c2c2
Git所做的是:
- 提取提交
c2c2c2...
- 提取
c2c2c2...
的父提交
- 生成父母到孩子的差异列表
- extract commit
c2c2c2...
- extract the parent commit of
c2c2c2...
- produce a diff listing from parent to child
这是Git设法向您显示更改的方式:它将您刚刚提交之前所拥有的内容"与截至该提交时您所拥有的内容"进行了比较. (Git可以非常快速,优化地执行此操作,因为每个文件都被简化为唯一的哈希指纹",并且Git可以首先仅比较指纹(哈希).如果哈希相同,则文件相同.如果哈希值不同,实际上只需要麻烦提取实际的文件数据.)
This is how Git manages to show you what changed: it compares "what you had just before that commit" to "what you had as of that commit". (Git can do this pretty quickly, optimized-ly, because every file is reduced to a unique hash "fingerprint", and Git can first just compare the fingerprints (hashes). If the hashes are the same, the files are the same. It only really has to bother extracting the actual file data if the hashes differ.)
此过程(保存整个文件,而不是累积更改)被称为存储快照". (其他版本控制系统往往会累积更改,这称为存储增量".之所以这样做,是因为将 changes 保存到文件显然比保存文件占用的空间少得多.还是比旧的基于delta的版本控制系统更节省磁盘空间.)
This process—saving whole files, instead of accumulating changes—is called "storing snapshots". (Other version control systems tend to accumulate changes, which is called "storing deltas". They do this because saving the changes to files obviously takes far less space than saving the files. Git sneaks around the issue in a clever way and winds up using less disk space than older delta-based version control systems anyway.)
合并提交是特殊的,以一种特殊且显而易见的方式.查看您自己的图并考虑Merge1
.即将发生什么提交?
A merge commit is special, in one particular and obvious way. Look at your own diagram and consider Merge1
. What commit comes right before it?
这里的答案是Am
和C2
都在Merge1
之前.也就是说,提交Merge1
具有两个父提交.
The answer here is that both Am
and C2
come "right before" Merge1
. That is, commit Merge1
has two parent commits.
如果您要求Git证明您提交了Merge1
,应该与哪个父级进行比较?
If you ask Git to show you commit Merge1
, which parent should it compare-with?
在这里事情变得特别奇怪. git log -p
和git show
seem 这两个命令非常相似.实际上,它们非常相似.一个明显的区别是git log -p
显示了多个提交,而git show
仅显示了您告诉它显示的一个提交.您可以运行git log -n 1 -p <commit>
仅显示一次提交,现在它似乎就像这些完全一样.
Here's where things get particularly odd. The two commands git log -p
and git show
seem very similar. In fact, they are very similar. The one obvious difference is that git log -p
shows more than one commit, while git show
shows just the one commit you tell it to show. You can run git log -n 1 -p <commit>
to show just the one commit, and now it seems like these are exactly the same.
他们不是!
当您在合并提交上使用git show
时,Git尝试通过同时与两个父母进行比较来解决与哪个提交进行比较"的问题.产生的差异称为组合差异.
When you use git show
on a merge commit, Git tries to solve the "what commit to compare against" problem by comparing, simultaneously, against both parents. The resulting diff is called a combined diff.
但是,当您在合并提交上使用git log -p
时,Git只是举起它的隐喻之手,说我不能向两个父母展示补丁",然后放弃并继续下一个提交. 换句话说,git log -p
甚至不用费心为合并尝试差异.
When you use git log -p
on a merge commit, though, Git just throws up its metaphorical hands, says "I can't show patches against two parents", and gives up and goes on to the next commit. In other words, git log -p
doesn't even bother trying diffs for the merge.
现在,在这种情况下,您可能会想看看是否可以在两个合并中(特别是在Merge2
上)使用git show
从提交c2c2c2...
中弄清文件c2c2c2...
对文件造成了什么影响已还原.但是默认情况下,git show
会产生组合差异,组合差异有意忽略了许多差异输出.特别是,组合的差异仅列出文件由所有父母修改的.
Now, in this case you might be tempted to see if you can figure out what happened to your file from commit c2c2c2...
using git show
on the two merges—in particular, on Merge2
, where the changes got reverted. But git show
produces, by default, a combined diff, and a combined diff deliberately omits a lot of diff output. In particular, a combined diff lists only files which were modified from all parents.
比方说,您从C2
所做的更改被还原的文件是文件f2
.而且,从图中可以看出,Merge2
的两个父级分别是An
(具有f2
您想要的方式)和Bn
(没有).
Let's say the file where your changes from C2
were reverted is file f2
. And, from the graph, the two parents of Merge2
are An
(which has f2
the way you want it) and Bn
(which doesn't).
这里实际发生的是,在创建Merge2
的合并期间,您以某种方式告诉Git使用提交Bn
中的f2
版本.也就是说,Merge2
中的文件f2
与Bn
中的文件f2
完全相同,并且与提交An
中的f2
不同.
What actually happened here is that, during the merge that created Merge2
, you somehow told Git to use the version of f2
from commit Bn
. That is, file f2
in Merge2
is exactly the same as file f2
in Bn
, and different from f2
in commit An
.
如果使用git show
查看Merge2
,则组合的差异将跳过 f2
,因为它与Bn
.
If you use git show
to view Merge2
, the combined diff will skip f2
, because it is the same as the f2
in Bn
.
git log -p
同样如此,甚至更糟:它完全跳过合并,因为很难显示差异.
The same is true, only even worse, with git log -p
: it skips the merge entirely, because it's just too hard to show diffs.
即使没有-p
,当您要求文件已更改"时,git log
也会执行相同的操作-完全跳过合并.这就是为什么您无法在日志输出中看到它的原因.
Even without -p
, when you ask for "files changed", git log
winds up doing the same thing—skipping the merge entirely. That's why you can't see it in the log output.
(顺便说一句,git log master -- f2
从未显示commit C2
本身的原因是,在git log
的选项中添加文件名会启用历史记录简化".在我看来,这是错误的行为, Git结束时简化了太多历史记录,因此它从不显示提交C2
.在-- f2
还原C2
到显示的提交集之前添加--full-history
.但是缺少了,因为git log
跳过了它.)
(As an aside, the reason git log master -- f2
never shows commit C2
itself is that adding a file name to the options to git log
turns on "history simplification". In what I consider to be somewhat buggy behavior, Git winds up simplifying away too much history, so that it never shows commit C2
. Adding --full-history
before the -- f2
restores C2
to the set of commits shown. The merge is still missing, though, because git log
skips it.)
有一个解决方案. git show
和git log
都带有一个附加标志-m
,该标志拆分"合并.也就是说,这些操作不会将Merge2
视为合并提交,而是会将合并分为两个虚拟提交".一个将是"Merge2
vs An
",您将看到这两个提交之间的所有差异,另一个将是"Merge2
vs Bn
",并且您将看到那些两次提交.这将显示文件f2
被重新设置为Bn
中的方式,从而丢失了An
中出现的C2
中的版本,而不是Bn
中出现的版本.
There is a solution. Both git show
and git log
take an additional flag, -m
, which "splits" merges. That is, instead of treating Merge2
as a merge commit, these will break the merge into two "virtual commits". One will be "Merge2
vs An
", and you will see all the differences between those two commits, and the other will be "Merge2
vs Bn
", and you will see all the differences between those two commits. This will show that file f2
got re-set to the way it is in Bn
, losing the version from C2
that appears in An
but not in Bn
.
(包括--full-history
和-m
以确保同时显示提交C2
.)
(Include --full-history
as well as -m
to ensure that commit C2
shows up as well.)
这部分完全不是 清楚.您说过存在合并冲突,这意味着git merge
停下来并得到了人工的帮助.在此协助期间的某个时候,人类可能会使用Bn
中的文件f2
版本(或者至少是在C2
中没有进行更改的f2
版本)来更新索引.
This part is not clear, at all. You said there was a merge conflict, though, which means git merge
stopped and got manual assistance, from a human. At some point during this assistance, the human probably updated the index with the version of file f2
from Bn
(or at least, a version of f2
that did not have the change made back in C2
).
这可能会在合并过程中发生,并且因为有点阴险,因为Git显示了合并了这些压缩(合并)的diff的情况,或者在git log -p
的情况下,默认情况下完全没有.这是需要提防的事情,特别是如果合并需要手动解决冲突时.在大多数情况下,捕获此类合并错误的方法是使用自动化测试. (当然,并不是所有的东西都可以测试.)
This can happen during merges, and it's a bit insidious precisely because Git shows merges with these compressed (combined) diffs, or in the case of git log -p
, not at all, by default. It's something to watch out for, especially if a merge required manual conflict resolution. In most cases, the way to catch this sort of merge error is with automated tests. (Of course, not everything can be tested.)
这篇关于合并后丢失的Git提交的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!