使两个分支相同
Posted
技术标签:
【中文标题】使两个分支相同【英文标题】:Making two branches identical 【发布时间】:2016-07-19 03:30:45 【问题描述】:我有两个分支 - A 和 B。B 是从 A 创建的。
两个分支的工作同时继续进行。分支 A 上的工作很糟糕(导致版本不工作),而分支 B 上的工作很好。在此期间分支 B 有时会合并到分支 A(但不是相反)。
现在我想让分支 A 与分支 B 相同。我不能使用 git revert 因为我需要恢复太多提交 - 我只想恢复在分支 A 上完成的提交,但不是因为合并分支B。
我找到的解决方案是将分支B克隆到另一个文件夹中,从分支A的工作文件夹中删除所有文件,从临时分支B文件夹中复制文件并添加所有未跟踪的文件。
有没有做同样事情的 git 命令?我错过了一些 git revert 开关?
【问题讨论】:
【参考方案1】:检查你的目标分支:
git checkout A;
删除分支 A 中的所有内容并使其与 B 相同:
git reset B --hard;
如果您需要放弃所有本地更改(有点还原,但不会像 git revert
那样搞乱 git):
git reset HEAD --hard;
完成后,不要忘记更新您的远程分支(使用--force
或-f
标志以防您需要覆盖历史记录)。
git push origin A -f;
【讨论】:
你需要 git pull 然后 git push 来完成它。 好主意,但是对于它的价值,这不适用于受保护的分支(不能使用--force
推送到受保护的分支)【参考方案2】:
git merge A
如果头部在分支 b 中,是使 2 个分支相同的最佳方法
【讨论】:
【参考方案3】:这篇文章中的答案方式对我来说太复杂了。
这就是我所做的:
假设您在要更改的分支上
第 1 步 - 使分支与 master 相同
git reset origin/master --hard
第 2 步 - 拉出您想要与之相同的分支
git pull origin branch-i-want-to-be-identical-to
第 3 步(可选)- 强制将其推送到远程以覆盖远程历史记录
git push --force
【讨论】:
当您有一个受保护的分支时,此解决方案不起作用,因为这些分支不允许使用--force
覆盖历史记录。
@RobinBastiaan 然后你就被复杂的解决方案困住了。查看此问题的其他答案。【参考方案4】:
这是我用来为我做这件事的好方法。 这个想法是获取 A 和 B 的差异,提交差异,恢复它以创建相反的提交,而不是樱桃选择最后恢复提交到 A
这里是命令
git checkout B
git checkout -b B_tmp
git merge --squash A
git commit -a -m 'A-B diff'
git revert B_tmp
git checkout A
git cherry-pick B_tmp
git branch -d B_tmp
在这里,A 与 B 相同,并且您在 A 历史记录中只有一个提交重置为与 B 分支相同。
检查 bash 脚本,你必须更改你的 repo 目录和分支名称,这些还不是可变的,但可以轻松管理https://github.com/serg94/git_bash/blob/master/reset_dev
【讨论】:
【参考方案5】:这是一个不得已的想法(它很脏,不是 git 做事的方式)...
-
转到分支 B (git checkout B)。
复制该分支的所有内容(ctrl + c)
转到分支 A (git checkout A)
从分支 A 中删除所有内容(用鼠标选择所有内容并删除)
将分支 B 的所有内容复制到所有分支 A 内容所在的文件夹中。 (ctrl + v)
暂存所有新更改 (git add .)
提交分阶段的更改(git commit -m "Branch A is now the same as B")
【讨论】:
尽管您在提供有关此选项如何工作的更多详细信息方面做得很好,但提出此问题的人显然要求提供“git-way”解决方案,因为他们已经尝试过您提出的解决方案. 优秀而简单的解决方案。 也许这适用于一个或两个文件。但是,一旦分支变得陈旧,使用大型代码库将不可行。【参考方案6】:有很多方法可以做到这一点,你应该使用哪一种取决于你想要什么结果,特别是你和任何与你合作的人(如果这是一个共享存储库)期望在未来看到什么。
执行此操作的三种主要方法:
不要打扰。放弃分支A
,创建一个新的A2
,然后使用它。
使用git reset
或等同于在别处重新指向A
。
从长远来看,方法 1 和 2 实际上是相同的。例如,假设您从放弃A
开始。每个人都在A2
上发展了一段时间。然后,一旦每个人都在使用A2
,您就完全删除了名称A
。现在A
消失了,您甚至可以将A2
重命名为A
(当然,其他所有使用它的人都必须进行相同的重命名)。此时,您使用方法 1 的情况和使用方法 2 的情况看起来有什么不同(如果有的话)? (有一个地方你可能仍然能够看到差异,这取决于你的“长期运行”有多长以及旧的 reflog 何时到期。)
使用特殊策略进行合并(请参阅下面的“替代合并策略”)。这里你想要的是-s theirs
,但它是doesn't exist,你必须伪造它。
旁注:从哪里“创建”分支基本上是无关紧要的:git 不会跟踪这些事情,而且通常以后不可能发现它,所以它可能对你来说也不重要。 (如果它对你来说真的很重要,你可以对你如何操作分支施加一些限制,或者用标签标记它的“起点”,这样你以后可以机械地恢复这些信息。但这通常是一个迹象,你一开始就做错了——或者至少,你期望 git 没有提供 git 的东西。另见下面的脚注 1。)
分支定义(另见What exactly do we mean by branch?)
一个分支——或者更准确地说,一个分支name——在 git 中只是一个指向提交图中某个特定提交的指针。其他引用(如标签名称)也是如此。例如,与标签相比,分支的特别之处在于,当您 on 一个分支并进行 new 提交时,git 会自动更新分支名称,以便它现在指向新的提交。
例如,假设我们有一个看起来像这样的提交图,其中o
节点(以及标记为*
的节点)代表提交,A
和B
是分支名称:
o <- * <- o <- o <- o <- o <-- A
\
o <- o <- o <-- B
A
和B
分别指向一个分支的最尖端提交,或者更准确地说,是从某个提交开始并返回所有可到达的提交而形成的分支数据结构 , 每个提交都指向一些父提交。
如果您使用git checkout B
以便在分支B
上进行新提交,则新提交将设置为使用B
的前一个提示作为其(单个)父级,并且git 更改存储在分支名称 B
下的 ID,以便 B
指向新的最尖端提交:
o <- * <- o <- o <- o <- o <-- A
\
o <- o <- o <- o <-- B
标记为*
的提交是两个分支提示的合并基础。这个提交,以及所有早期的提交,都在 both 分支上。1 合并基础对于合并很重要(duh :-)),但对于 @ 之类的东西也很重要987654350@ 和其他发布管理类型的操作。
您提到分支B
偶尔会合并回A
:
git checkout A; git merge B
这会产生一个新的合并提交,这是一个包含两个2 父级的提交。 first parent是当前分支的前一个tip,即A
的前一个tip,第二个parent是named commit,即分支B
的最tip-most commit .将上面的内容重新绘制得更紧凑(但添加更多-
s 以使o
s 更好地排列),我们开始:
o--*--o--o--o--o <-- A
\
o---o--o---o <-- B
最终得到:
o--*--o--o--o--o--o <-- A
\ /
o---o--o---* <-- B
我们将*
移动到A
和B
的新合并基础(实际上是B
的尖端)。大概我们随后会向B
添加更多提交,并可能再合并几次:
...---o--...--o--o <-- A
/ /
...-o--...--*--o--o <-- B
git 默认对git merge <thing>
做了什么
要进行合并,请检查一些分支(在这些情况下为 A
),然后运行 git merge
并为其提供至少一个参数,通常是另一个分支名称,例如 B
。合并命令首先将名称转换为提交 ID。分支名称变成分支上最尖端提交的 ID。
接下来,git merge
找到 合并基数。这些是我们一直用*
标记的提交。合并基础的技术定义是提交图中的最低公共祖先(在某些情况下可能不止一个),但为了简单起见,我们将在此处使用“标记为 *
的提交”。
最后,对于普通合并,git 运行两个git diff
命令。3 第一个git diff
将提交*
与HEAD
提交进行比较,即当前分支的尖端.第二个差异将提交*
与参数提交进行比较,即另一个分支的尖端(您可以命名一个特定的提交,它不必是分支的尖端,但在我们的例子中,我们正在合并B
进入A
,所以我们得到了这两个分支提示提交)。
当 git 在 both 分支中发现一些文件被修改时,与合并基础版本相比,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
。也就是说,新提交的代码将与其父级的代码完全匹配。
正如this other answer 中所述,有多种方法可以强制 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
第一步是确保我们像往常一样在分支A
。第二个是启动合并,但避免提交结果(--no-commit
)。为了使 git 的合并更容易——这不是必需的,它只是让事情变得更快更安静——我们使用 -s ours
以便 git 可以完全跳过差异步骤,并且我们避免了所有合并冲突的投诉。
在这一点上,我们得到了诀窍。首先我们删除整个合并结果,因为它实际上毫无价值:我们不想要来自A
的提示提交的工作树,而是来自B
的提示的工作树。然后我们从B
的提示检查每个文件,使其准备好提交。
最后,我们提交新的合并,它的第一个父级有 A
的旧提示,第二个父级有 B
的提示,但有来自提交 @ 的 tree 987654403@.
如果提交之前的图表是:
...---o--...--o--o <-- A
/ /
...-o--...--*--o--o <-- B
那么现在的新图是:
...---o--...--o--o--o <-- A
/ / /
...-o--...--o--o--* <-- B
新的合并基础和往常一样是B
的尖端,从提交图表 的角度来看,这个合并看起来与任何其他合并完全一样。不寻常的是,A
顶端的新合并的 源树 与 B
顶端的源树完全匹配。
1在这种特殊情况下,由于两个分支保持独立(从未合并),它也可能是 创建两个分支之一的点(或者甚至可能是两者都被创建的地方),尽管此时您无法证明这一点(因为过去有人可能使用过git reset
或其他各种技巧来移动分支标签)。但是,一旦我们开始合并,合并基础显然不再是起点,并且更难找到合理的起点。
2从技术上讲,合并提交是具有两个或更多父级的任何提交。 Git 将与两个以上父级的合并称为“章鱼合并”。根据我的经验,它们并不常见,除了在 git 本身的 git 存储库中,它们最终实现了与多个普通双亲合并相同的事情。
3差异通常在某种程度上在内部完成,而不是运行实际命令。这允许许多捷径优化。这也意味着如果您编写自定义合并驱动程序,除非 git 发现文件在两个差异中都被修改,否则该自定义合并驱动程序不会运行。如果它只在两个差异之一中进行了修改,则默认合并仅采用修改后的一个。
【讨论】:
我不敢相信你没有一百张赞成票。你对-s theirs
的实现是我见过的最好的。【参考方案7】:
你需要 git reset
分支 A 到分支 B。
见docs。
【讨论】:
我不想重置,因为这会重写历史以上是关于使两个分支相同的主要内容,如果未能解决你的问题,请参考以下文章
如果知道warp中所有线程的条件相同,如何避免在CUDA程序中执行条件的两个分支?