zmb*_*mbq 27 git version-control merge
我有两个分支--A和B. B是从A创建的.
两个分支机构的工作同时进行.分支A上的工作很糟糕(导致非工作版本),而分支B上的工作很好.在此期间,分支B有时合并到分支A(但不是相反).
现在我想让分支A与分支B相同.我不能使用git revert,因为我需要恢复过多的提交 - 我只想恢复在分支A上完成的提交但不是因为合并分支B的结果.
我找到的解决方案是将分支B克隆到另一个文件夹,从分支A的工作文件夹中删除所有文件,从temp分支B文件夹中复制文件并添加所有未跟踪的文件.
是否有一个git命令做同样的事情?我错过了一些git revert开关?
tor*_*rek 42
有很多方法可以做到这一点,哪一个你应该使用取决于结果,你想要什么,尤其是你和任何人合作,你(如果这是一个共享的存储库)期望在将来看到.
这样做的三种主要方式:
A,制作新的A2,并使用它.使用git reset或等同于重新指向A别处.
从长远来看,方法1和2实际上是相同的.例如,假设您从放弃开始A.每个人都会在A2一段时间内发展.然后,一旦每个人都在使用A2,您将A完全删除该名称.现在A已经过去了,你甚至可以重命名A2为A(其他人使用它必须做同样的重命名,当然).此时,如果有的话,在使用方法1的情况和使用方法2的情况之间看起来有什么不同?(有一个地方你可能仍然可以看到差异,这取决于你的"长跑"有多长,以及当你过期的旧日志.)
-s theirs,但它不存在,你必须假装它.附注:其中一个分支"从创建"基本上是无关紧要的:Git并不跟踪这样的事情,并在总体上是不可能后来发现它,所以它可能不应该要么关系到你.(如果它真的对你很重要,你可以对你如何操纵分支施加一些限制,或者用标记标记它的"起始点",这样你就可以在以后机械地恢复这些信息.但这通常是一个迹象,你首先做错了什么 - 或者至少,期待一些git没有提供的git.参见下面的脚注1.)
分支 - 或者更确切地说,分支名称 -in git只是指向提交图中某些特定提交的指针.其他引用(如标记名称)也是如此.与标记相比,使分支特殊的是,当您在分支上并进行新的提交时,git会自动更新分支名称,以便它现在指向新的提交.
例如,假设我们有一个类似于此的提交图,其中o节点(以及标记的节点*)表示提交,A并且B是分支名称:
o <- * <- o <- o <- o <- o <-- A
\
o <- o <- o <-- B
Run Code Online (Sandbox Code Playgroud)
A并且B每一个指向分支的最尖端提交,或者更确切地说,分支数据结构通过从一些提交开始并且通过所有可到达提交返回而形成,每个提交指向一些父提交.
如果你使用git checkout B这样你在分支上B,并进行新的提交,新的提交将使用前一个提示B作为其(单个)父级设置,并且git更改存储在分支名称下的ID,B以便B指向新提示最多提交:
o <- * <- o <- o <- o <- o <-- A
\
o <- o <- o <- o <-- B
Run Code Online (Sandbox Code Playgroud)
标记的提交*是两个分支提示的合并基础.此提交以及所有早期提交都在两个分支上.1 合并基础很重要,合并(duh :-)),也适用于类似git cherry和其他发布管理类型的操作.
你提到该分支B偶尔合并回A:
git checkout A; git merge B
Run Code Online (Sandbox Code Playgroud)
这会产生一个新的合并提交,它是一个具有两个2个父项的提交.第一个父级是当前分支的前一个提示,即前一个提示A,第二个父级是命名提交,即分支的最尖端提交B.重新绘制上面的内容(但添加更多-s以使os更好),我们从:
o--*--o--o--o--o <-- A
\
o---o--o---o <-- B
Run Code Online (Sandbox Code Playgroud)
最后得到:
o--*--o--o--o--o--o <-- A
\ /
o---o--o---* <-- B
Run Code Online (Sandbox Code Playgroud)
我们移动*到新的合并基础A和B(这实际上是尖B).据推测,我们然后再添加一些提交,B并且可能会合并几次:
...---o--...--o--o <-- A
/ /
...-o--...--*--o--o <-- B
Run Code Online (Sandbox Code Playgroud)
git merge <thing>要进行合并,请检查一些分支(A在这些情况下),然后运行git merge并给它至少一个参数,通常是另一个分支名称B.merge命令首先将名称转换为提交ID.分支名称将变为分支上最末端提交的ID.
接下来,git merge找到合并基础.这些是我们一直在标记的提交*.合并基础的技术定义是在提交图形最低的共同祖先(在某些情况下,可能不止一个),但我们只是以"提交标记去*"在这里为简便起见.
最后,对于普通合并,git运行两个git diff命令.3 第一个git diff比较提交*与HEAD提交,即当前分支的提示.第二个差异比较提交*反对论据提交,即另一个分支的末端(你能说出具体的承诺,它不一定是一个分支的末端,但在我们的例子中,我们正在合并B成A这样我们就得到这两个branch-tip提交).
当git发现某些文件被修改时,与合并基础版本相比,在两个分支中,git尝试以半智能(但不是非常智能)的方式组合这些更改:如果两个更改都将相同的文本添加到同一区域,git保留了一个添加的副本.如果两个更改都删除了同一区域中的相同文本,git只删除该文本一次.如果两个更改都修改了同一区域中的文本,则会产生冲突,除非修改完全匹配(然后您将获得一个修改副本).如果一方进行一次更改而另一方做出不同的更改,但更改似乎不重叠,则git会进行两项更改.这是三向合并的本质.
最后,假设一切顺利,git会生成一个新的提交,其中包含两个(或者在脚注2中已经注明过的)父母.与这个新提交相关联的工作树是一个git在进行三向合并时提出的.
虽然git merge的默认recursive策略有-X选择ours和theirs他们不这样做,我们想在这里.这些只是说,在冲突的情况下,git应该通过选择"我们的更改"(-X ours)或"他们的更改"(-X theirs)来自动解决该冲突.
该merge命令完全具有另一种策略-s ours:这个说明不是将合并基础与两个提交区分开来,而是使用我们的源代码树.换句话说,如果我们在分支上A并且运行git merge -s ours B,git将进行新的合并提交,其中第二个父级是分支的尖端B,但是源树与分支的前一个尖端中的版本匹配A.也就是说,新提交的代码将与其父代码完全匹配.
正如其他答案所述,有很多方法可以强制git有效实现-s theirs.我认为最简单的解释是这个序列:
git checkout A
git merge --no-commit -s ours B
git rm -rf . # make sure you are at the top level!
git checkout B -- .
git commit
Run Code Online (Sandbox Code Playgroud)
第一步是A像往常一样确保我们在分支机构.第二个是启动合并,但是避免提交结果(--no-commit).为了使git更容易合并 - 这不是必需的,它只是让事情变得更快更安静 - 我们使用-s oursgit可以完全跳过差异步骤,我们避免所有合并冲突投诉.
在这一点上,我们得到了诀窍.首先,我们删除整个合并结果,因为它实际上是没有价值的:我们不希望工作树来自提示提交A,而是来自提示的提示B.然后我们从提示中检出每个文件B,使其准备好提交.
最后,我们提交了新的合并,它的第一个父级是旧的尖端,A第二个父级是尖端B,但是有来自提交的树B.
如果提交之前的图形是:
...---o--...--o--o <-- A
/ /
...-o--...--*--o--o <-- B
Run Code Online (Sandbox Code Playgroud)
那么新的图表现在是:
...---o--...--o--o--o <-- A
/ / /
...-o--...--o--o--* <-- B
Run Code Online (Sandbox Code Playgroud)
新的合并库是B常见的提示,从提交图的角度来看,这个合并看起来与任何其他合并完全一样.什么是不寻常的是,源代码树在尖端的新的合并A完全源代码树相匹配的尖端B.
1在这种特殊情况下,由于两个分支保持独立(从未被合并),它也可能是两个分支之一被创建的点(或者甚至可能在两个分支都被创建的地方),尽管你无法证明这一点(因为过去有人可能已经使用过git reset各种其他技巧来移动分支标签).然而,一旦我们开始合并,合并基础显然不再是起点,并且合理的起点变得更难找到.
2从技术上讲,合并提交是指具有两个或更多父项的任何提交.Git调用与两个以上父母"octopus merges"的合并.根据我的经验,除了git本身的git存储库之外,它们并不常见,最后,它们实现了与多个普通双父合并相同的功能.
3差异通常在某种程度上在内部完成,而不是运行实际的命令.这允许进行大量的快捷优化.这也意味着如果您编写自定义合并驱动程序,则不会运行该自定义合并驱动程序,除非git发现该文件在两个差异中都被修改.如果它仅在两个差异中的一个中进行了修改,则默认合并只需要修改一个.
Pia*_*nov 15
签出您的目标分支:
git checkout A;
Run Code Online (Sandbox Code Playgroud)
要删除分支A中的所有内容并使其与B相同,请执行以下操作:
git reset B --hard;
Run Code Online (Sandbox Code Playgroud)
如果您需要放弃所有本地更改(有点恢复,但不会像git那样使git混乱git revert):
git reset HEAD --hard;
Run Code Online (Sandbox Code Playgroud)
完成后,请不要忘记更新远程分支(使用--force或-f标记以防您需要覆盖历史记录)。
git push origin A -f;
Run Code Online (Sandbox Code Playgroud)
这篇文章中的答案对我来说太复杂了。
这就是我所做的:
假设您位于要更改的分支上
第 1 步 - 使分支与主分支相同
git reset origin/master --hard
第 2 步 - 拉取您想要相同的分支
git pull origin branch-i-want-to-be-identical-to
步骤 3(可选)- 强制推送到远程以覆盖远程历史记录
git push --force
这是最后的手段(它很脏,而且不是 git 的做事方式) ......