从概念上讲,'git revert'与三路合并有何关系?
我试图了解git revert
如何从
https://stackoverflow.com/a/37151159
假设当前分支为B
,命令git revert C
创建提交D
,因此B
是C
和D
相对于
Suppose the current branch is B
, does command git revert C
create a commit D
, so that B
is the result of three-way merge of C
and D
with respect to C~
?
git revert
的作用是使用非常规选择的合并基础进行三向合并.除了选择的合并基础不同之外,其他操作与git cherry-pick
相同.
What git revert
does is to do a three-way merge with an unusually-chosen merge base. This is the same as what git cherry-pick
does, except that the chosen merge base is different.
在所有情况下,我们都可以绘制出提交图:
In all cases, we can draw out the commit graph:
...--o--o--C1--C2--...--o <-- somebranch
\
o--o--L <-- our-branch (HEAD)
或:
...--o--C1--C2--o--...--L <-- our-branch (HEAD)
或类似(绘制任何您的图是什么样的.)
or similar (draw whatever your graph looks like).
您告诉Git:樱桃摘C2
或还原C2
.您的当前提交是L
,可通过HEAD
找到. Git现在继续进行三向合并操作.与此不同的是,下面的 merge base (称为 B )是C1
或C2
,而另一个提交是 R 下面的em>也是C1
或C2
-两者都不是合并基础.对于git cherry-pick
, B = C1
和 R = C2
.对于git revert
, B = C2
, R = C1
.
You tell Git: cherry-pick C2
or revert C2
. Your current commit is L
, found via HEAD
. Git now proceeds to do a three-way merge operation. What's unusual about this is that the merge base, called B below, is either C1
or C2
, and the other commit, called R below, is also either C1
or C2
—whichever wasn't the merge base. For git cherry-pick
, B = C1
and R = C2
. For git revert
, B = C2
and R = C1
.
所有Git合并都是以相同的方式实现的. 1 我们从三个提交开始 2 :
All Git merges are implemented the same way.1 We start2 with three commits:
- 有一个合并基础提交 B .
- 存在左侧或本地或
--ours
提交 L .git mergetool
代码将其称为本地",但大多数Git命令仅将其称为HEAD
或--ours
. - 有一个右侧或远程或
--theirs
提交 R .git mergetool
代码将其称为远程",而git merge
本身使用MERGE_HEAD
.
- There is a merge base commit B.
- There is a left-hand-side or local or
--ours
commit L. Thegit mergetool
code calls it "local", but most Git commands just call itHEAD
or--ours
. - There is a right-hand-side or remote or
--theirs
commit R. Thegit mergetool
code calls it "remote", whilegit merge
itself usesMERGE_HEAD
.
从图中可以明显看出许多实际合并的合并基础:
The merge base for many real merges is obvious from the graph:
o--...--L <-- our-branch (HEAD)
/
...--o--B
\
o--...--R <-- their-branch
对于樱桃选择或还原,强制将 B 和 R 提交至某些特定的提交.例如,如果您运行git revert <hash>
,则 B 是您标识的提交,而 R 是其父级:
For a cherry-pick or a revert, commits B and R are forced to some particular commit. For instance, if you run git revert <hash>
, B is the commit you identified and R is its parent:
...--o--R--B--o--...--L <-- our-branch (HEAD)
现在,有了三个提交 B , L 和 R (或更确切地说,是它们的哈希ID),Git将实际上,运行两个 git diff
操作:
Now, with the three commits B, L, and R—or rather, their hash IDs—in hand, Git will, in effect, run two git diff
operations:
-
git diff --find-renames B L
-
git diff --find-renames B R
第一个差异查找在底部和左侧之间不同的文件(包括跨越该间隙的任何重命名文件).第二个差异查找的是底部和右侧之间不同的文件,同样包括任何重命名的文件.
The first diff finds files that are different between the base and the left hand side (including any renamed files across that gap). The second diff finds files that are different between the base and the right hand side, again including any renamed files.
任何在 端均未更改的文件在所有三个提交中均相同.合并结果是所有三个提交共享的文件的(单个)版本.
Any files that are not changed on either side are the same in all three commits. The merge result is the (single) version of that file that all three commits share.
任何仅在一个一侧进行过更改的文件,Git都会从该侧获取文件的版本.
Any files that were changed on only one side, Git takes the version of the file from that side.
任何在双方上都更改过但内容相同的文件中,Git可以使用 L 或 R 复制.根据定义,这两个副本是相同的,因此Git选择一个(实际上总是 L ,因为它更方便-Git直接在索引中完成所有这些工作,并且这避免了移动 L 排在第零位!).
Any files that were changed on both sides, but to the same contents, Git can take either the L or the R copy. These two copies are by definition identical, so Git picks one (actually always L since it's more convenient—Git is doing all this work directly in the index, and this lets it avoid moving the L file out of slot zero in the first place!).
最后,对于在两侧进行了任何更改的文件,Git都会尝试-可能成功或失败-组合两个变更集.合并的更改将应用于来自基本提交 B 的文件副本.如果合并成功,那就是合并的结果.否则,Git会尽最大努力在工作树中进行合并,并因合并冲突而停止. 3 添加-X ours
或-X theirs
会告诉Git:而不是因冲突而停止,通过从差异中选择我们的或他们的大块来解决此冲突.请注意,这是 only 情况,实际上必须填充文件的三个索引位置,然后调用低端位置级别的合并代码(如果设置了一个,则为.gitattributes
中的合并驱动程序).
Last, for any files changed on both sides, Git attempts—and maybe succeeds, or may fails—to combine the two sets of changes. The combined changes get applied to the copy of the file that came from the base commit B. If the combining is successful, that's the result of the merge. Otherwise Git leaves its best effort at merging in the work-tree, and stops with a merge conflict.3 Adding -X ours
or -X theirs
tells Git: instead of stopping with a conflict, resolve this conflict by choosing the ours or theirs hunk from the diff. Note that this is the only case that actually has to populate the three index slots for the file, and then invoke the low-level merge code (or your merge driver from .gitattributes
, if you set one).
成功的结果将自动由git merge
提交为合并提交,或者由git cherry-pick
或git revert
作为普通提交,除非您告诉Git不要提交结果.失败(由于冲突)合并将停止,在索引和工作树中留下一团糟,您必须清除它们.
A successful result is automatically committed as a merge commit by git merge
, or as an ordinary commit by git cherry-pick
or git revert
, unless you tell Git not to commit the result. A failed (due to conflicts) merge stops, leaving a mess in the index and work-tree, which you must clean up.
1 Git的所谓的章鱼合并仍然可以像这样工作,但是它是迭代的,反复将多个分支提示合并到索引中,而没有提交结果.这使它有点特殊,因为索引中的ours
状态仅是 ,而不是实际的提交.其他Git命令通常检查索引和HEAD
提交是否匹配,除了git cherry-pick -n
和git revert -n
只是使用索引就像是一样,与章鱼合并相同.在上面的主要答案文本中,您可以将索引的内容视为ours
提交:在内部,Git只是将所有处于阶段0的条目都移到了阶段2来实现此目的.
1Git's so-called octopus merge still works like this, but is iterative, repeatedly merging multiple branch tips into the index without committing the result. This makes it a little bit special since the ours
state is only in the index, rather than an actual commit. The other Git commands generally check that the index and HEAD
commit match, except that git cherry-pick -n
and git revert -n
simply use the index as if it were a commit, the same way that octopus merge does. In the main answer text above, you can can think of the index's content as the ours
commit: internally, Git just shifts all the stage-zero entries to stage-2 to make this happen.
2 对于git merge -s recursive
或git merge
调用的递归合并,Git首先查找您的合并基础.这可能会出现多个提交.如果发生这种情况,Git将使用git merge
的内部版本合并合并基础.这是递归合并"的递归部分:合并合并基础以便提出单个提交.那么,该单个提交就是最外面的git merge
的合并基础.
2For a recursive merge invoked by git merge -s recursive
or git merge
, Git first finds the merge base for you. This may turn up more than one commit. If that does happen, Git merges the merge bases, using (an internal version of) git merge
. This is the recursive part of the "recursive merge": merging the merge bases so as to come up with a single commit. That single commit is then the merge base for the outermost git merge
.
如果使用git merge -s resolve
,并且Git找到多个合并基础,则Git选择一种更简单的方法:它选择(看起来像是)随机的一个合并基础(它不是真正的随机的,只需要选择其中一个即可)可以很容易地从其合并基础查找算法中得出-但是它没有受到仔细控制;没有内在的理由偏爱任何一种候选合并基础.
If you use git merge -s resolve
, and Git finds more than one merge base, Git chooses a simpler approach: it picks one of the merge bases at (what seems like) random (it's not really random—it just takes whichever one comes out most easily from its merge-base-finding algorithm—but it's not carefully controlled; there's no inherent reason to prefer any one candidate merge base to any other).
3 对于在合并两个合并库时发生的递归(内部)合并期间的合并冲突,Git只会提交冲突的文本.结果不是很漂亮.
3For a merge conflict during the recursive (inner) merge that happens when merging two merge bases, Git simply commits the conflicted text. The result is not very pretty.