git 使两个分支相同

qcbq4gxm  于 2022-12-10  发布在  Git
关注(0)|答案(7)|浏览(515)

我有两个分支- A和B。B是从A创建的。
两个分支上的工作并行进行。分支A上的工作很糟糕(导致一个不工作的版本),而分支B上的工作很好。在这段时间里,分支B有时会合并到分支A中(但不是相反)。
现在我想让分支A和分支B完全相同。我不能使用git revert,因为我需要恢复太多的提交--我只想恢复在分支A上完成的提交,而不是合并分支B的结果。
我找到的解决方案是将分支B克隆到另一个文件夹中,删除分支A的工作文件夹中的所有文件,从临时分支B文件夹中复制文件,并添加所有未跟踪的文件。
有没有一个git命令可以做同样的事情?我错过了一些git revert开关?

oxiaedzo

oxiaedzo1#

有很多方法可以做到这一点,您应该使用哪一种方法取决于您想要什么结果,特别是您和与您协作的任何人(如果这是一个共享存储库)希望在未来看到什么。
主要有三种方法:
1.不用麻烦了,放弃分支A,创建一个新的A2,然后使用它。
1.使用git reset或同等工具将A重新指向其他位置。
方法1和方法2从长远来看实际上是一样的。例如,假设您从放弃A开始。每个人都在A2上开发了一段时间。然后,一旦每个人都在使用A2,您就完全删除了A这个名称。现在A已经没有了,您甚至可以将A2重命名为A(当然,使用它的其他所有人都将不得不做同样的重命名)。此时,如果有什么,在使用方法1和方法2的情况下看起来有什么不同?(有一个地方你 * 可能 * 仍然能够看到差异,这取决于你的“长期运行”有多长,以及你什么时候过期旧的reflog。)
1.使用一个特殊的策略进行合并(见下面的“可选合并策略”)。这里你想要的是-s theirs,但是它是doesn't exist,你必须伪造它。
旁注:分支“创建自”的位置基本上是无关紧要的:git不会跟踪这些东西,一般来说,以后也不可能发现它,所以它可能对你来说也不重要。(如果这对您确实很重要,您可以对如何操作分支施加一些限制,或者用标记来标记它的“起点,”以便以后可以机械地恢复这些信息,但这通常表明您一开始就做错了什么-或者至少,期待git提供一些git没有提供的东西。另请参见下面的脚注1。)

分支定义(另请参阅What exactly do we mean by branch?

分支--或者更准确地说,分支 name--在git中只是一个指向提交图中某个特定提交的指针。其他引用也是如此,比如标记名。分支与标记相比的特殊之处在于,当你 * 在 * 一个分支上并进行了一次 * 新 * 提交时,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

标记为*的提交是两个分支提示的 merge base。这个提交和所有之前的提交都在 * 两个 * 分支上。1 merge base对于合并(duh:-))很重要,但对于git cherry和其他发布管理类型的操作也很重要。
您提到分支B偶尔会合并回A

git checkout A; git merge B
  • 第一个 * 父提交是当前分支的前一个尖端,即A的前一个尖端,而第二个父提交是命名提交,即,分支B的最顶端提交。将上面的代码重新绘制得更紧凑一点(但是添加更多的-以使o排列得更好),我们从以下内容开始:
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 <thing>做什么

要进行合并,你需要 checkout 某个分支(这里是A),然后运行git merge,并至少给予它一个参数,通常是另一个分支名称,比如B。merge命令首先将名称转换为提交ID。
接下来,git merge找到 merge base,这些是我们沿着用*标记的提交。merge base的技术定义是提交图中的最低公共祖先(在某些情况下可能不止一个),但为了简单起见,我们在这里只使用“标记为*的提交“。

最后,对于普通的合并,git运行两个git diff命令。3第一个git diff命令比较commit *和当前分支的HEAD commit,第二个diff命令比较commit *和参数commit,另一个分支的顶端(你可以指定一个特定的提交,它不一定是分支的顶端,但在我们的例子中,我们将B合并到A中,这样我们就得到了这两个分支顶端提交)。
当git在 both 分支中发现某个文件被修改时,与合并基础版本相比,git会尝试以半智能(但不是真正智能)的方式合并这些修改:如果两个修改都在同一个区域添加了相同的文本,git会保留一份添加的内容。如果两个修改都删除了同一个区域的相同文本,git只删除一次。如果两个修改都修改了同一个区域的文本,你会得到一个冲突,除非修改完全匹配(这样你会得到一份修改的副本)。如果一方做了一个修改,另一方做了一个不同的修改,但是改变看起来并不重叠,那么git会接受两个改变。这就是 * 三路合并 * 的本质。
最后,假设一切顺利,git会创建一个新的提交,这个提交有两个(或者更多)父提交。与这个新提交相关的 work-tree 是git在进行三向合并时创建的。

备选合并策略

虽然git merge的默认策略recursive-X选项ourstheirs,但它们并没有实现我们想要的功能。这些选项只是简单地说,如果出现 conflict,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就可以完全跳过diff步骤,避免所有合并冲突的抱怨。
现在我们来看看这个技巧的要点。首先我们删除整个合并结果,因为它实际上是没有价值的:我们不需要来自A的tip commit的工作树,而是来自B的tip commit的工作树,然后我们 checkout 来自B的tip commit的每个文件,使其准备好提交。
最后,我们提交新的merge,它的第一个父项是A的旧tip,第二个父项是B的tip,但它的 tree 来自提交B
如果提交之前的图形为:

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

那么新的图现在是:

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

新的merge-base和往常一样是B的顶端,从commit graph 的Angular 来看,这个合并看起来和其他合并完全一样。不寻常的是,A顶端的新合并的 source treeB顶端的源树完全匹配。
1在本例中,由于这两个分支保持独立(从未合并),它也 * 可能 * 是创建两个分支之一的点(或者甚至可能两者都是在哪里创建的),尽管此时无法证明(因为过去可能有人使用过git reset或其他各种技巧来移动分支标签)。合并基础显然不再是起始点,并且合理的起始点变得更加难以定位。
2从技术上讲,合并提交是指任何有两个或更多父提交的提交。Git将有两个以上父提交的合并称为“octopus merges”。根据我的经验,除了在git仓库中之外,它们并不常见,最终,它们实现了与多个普通的两父合并相同的效果。
3在某种程度上,diff通常是在内部完成的,而不是运行实际的命令。这允许了很多快捷优化。这也意味着如果你编写了一个自定义的合并驱动程序,除非git发现文件在两个diff中都被修改了,否则这个自定义的合并驱动程序不会运行。如果它只在两个diff中的一个中被修改了,默认的合并只会使用修改后的那个。

wb1gzix0

wb1gzix02#

checkout 目标分支:

git checkout A;

要删除分支A中的所有内容并使其与分支B相同:

git reset B --hard;

如果你需要丢弃所有本地修改(有点像revert,但不会像git revert那样把git搞得一团糟):

git reset HEAD --hard;

完成后,不要忘记更新您的远程分支(使用--force-f标志,以防需要覆盖历史记录)。

git push origin A -f;
ewm0tg9j

ewm0tg9j3#

这篇文章的答案对我来说太复杂了。
我是这样做的:

假设您位于要更改的分支上

步骤1 -使分支与主分支相同
git reset origin/master --hard
第2步-拉出您希望与之相同的分支
git pull origin branch-i-want-to-be-identical-to
步骤3(可选)-强制将其推送到远程以覆盖远程历史记录
git push --force

zc0qhyus

zc0qhyus4#

这里有一个最后的想法**(这是肮脏的,不是一个git的做事方式)**...
1.转到分支B(git checkout B)。
1.复制该分支的所有内容(ctrl + c)
1.转到分支A(git checkout A)
1.删除分支A中的所有内容(用鼠标全选并删除)
1.将分支B的所有内容复制到分支A的所有内容所在的文件夹中。(ctrl + v)
1.暂存所有新更改(git add .)
1.提交暂存的更改(git commit -m“分支A现在与分支B相同”)

i7uaboj4

i7uaboj45#

这里有一个很好的方法,我用它来做这件事.我的想法是得到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

rqqzpn5f

rqqzpn5f6#

您需要git reset分支A到分支B。请参阅docs

czq61nw1

czq61nw17#

git merge A

如果头部在分支b中,则使2个分支相同的最佳方法是

相关问题