使两个分支相同

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 节点(以及标记为* 的节点)代表提交,AB 是分支名称:

o <- * <- o <- o <- o <- o   <-- A
      \
       o <- o <- o           <-- B

AB 分别指向一个分支的最尖端提交,或者更准确地说,是从某个提交开始并返回所有可到达的提交而形成的分支数据结构 , 每个提交都指向一些父提交。

如果您使用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 以使os 更好地排列),我们开始:

o--*--o--o--o--o      <-- A
    \
     o---o--o---o     <-- B

最终得到:

o--*--o--o--o--o--o   <-- A
    \            /
     o---o--o---*     <-- B

我们将* 移动到AB 的新合并基础(实际上是B 的尖端)。大概我们随后会向B 添加更多提交,并可能再合并几次:

...---o--...--o--o    <-- A
     /       /
...-o--...--*--o--o   <-- B

git 默认对git merge &lt;thing&gt; 做了什么

要进行合并,请检查一些分支(在这些情况下为 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 选项ourstheirs,但它们并没有在这里做我们想要的。这些只是说,在发生冲突的情况下,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。

【讨论】:

我不想重置,因为这会重写历史

以上是关于使两个分支相同的主要内容,如果未能解决你的问题,请参考以下文章

s-s-rS 报告 - 如何使参数灵活

如果知道warp中所有线程的条件相同,如何避免在CUDA程序中执行条件的两个分支?

如何使两个小部件高度相同?

javascript 使用jQuery使两个div的高度相同

计算最小操作以使两个树结构相同

HTML/CSS:使两个浮动 div 具有相同的高度