【问题标题】:Move commits from one branch to another and then merge them back in将提交从一个分支移动到另一个分支,然后将它们合并回
【发布时间】:2011-11-24 15:35:16
【问题描述】:

我有一棵树:

       /--b1---b2 <-- topic b
      /    
a1---a2 <-- topic a

其中“b”取决于“a”。然后我意识到我需要做一些与主题'a'相关的更改才能继续'b',但我想在'b'上做这些作为'b'的正常发展课程:

       /--b1---b2---a3---a4---b3---b4---a5---b5 <-- topic b
      /    
a1---a2 <-- topic a

然后,当我想在“b”上完成的事情完成后,我希望我的树看起来像这样:

       /--b1---b2--------m---b3'---b4'---b5' <-- topic b
      /                 /
a1---a2---a3'---a4'---a5' <-- topic a

就好像我实际上在“a”上做了所有更改,然后在“b”上合并它们,然后在“b”上继续。

我知道我可以手动执行此操作:

1- rebase/cherry-pick 'a' 从分支 'b' 提交到 'a'
2- 在“b”上创建一个时间分支“b-tmp”。
3- 将分支“b”重置为“b2”。
4- 将“a”合并到“b”。
5- rebase/cherry-pick 'b' 从 'b-tmp' 提交到 'b'。
6-删除分支'b-tmp'。

我可以创建一些脚本来执行此操作,我只是想知道是否有更好的方法/想法来执行此操作,除了这 6 个步骤。

【问题讨论】:

  • 我认为这是最好的
  • @knitti:我想我不同意。樱桃采摘很少是持久的动作。让我进一步阅读这个问题,我可能会发表一个想法
  • 我记录了我会做什么;这可能是你所描述的(但我不会使用樱桃挑选)。

标签: git


【解决方案1】:

首先让我说我宁愿经常集成主题分支(早期集成,经常构建......敲响了警钟?)。所以事实上,当最终应该进行更改时,我会返回,在 a 上进行更改,然后在 之上重新设置 b一个

如果 a 需要稳定以用于其他目的,我会创建一个 a-for-b 分支来临时包含工作,然后重新设置 b 最重要的是。

如果您真的必须处理分支“b”内的“topic-a”更改,我会这样做。

注意:包括截屏


到达起点

这是一个带有问题布局的简单工作树:

mkdir /tmp/q
cd /tmp/q
git init .
touch f
git add f
git commit -am initial
git checkout -b a; for a in a{1,2}; do echo $a>$a; git add $a; git commit -am $a; git tag $a; done
git checkout -b b; for a in b{1,2} a{3,4} b{3,4} a5 b5; do echo $a>$a; git add $a; git commit -am $a; git tag $a; done
git show-branch 

screencast here


将提交重新组织到他们的主题分支上

git checkout -b a_new a     # for safety, work on a clone of branch a
git reset --hard b          # start by moving it to the end of the b branch
git status                  # (just show where we are)
git log --oneline

git rebase -i a             # rebase it on top of the original branch a

    # not shown: delete every line with non-branch-a commits (b1-b5)
    # see screencast

git log --oneline           # (just show where we are again)

git checkout -b b_new b     # for safety, work on a clone of branch b
git log --oneline           # (just show where we are again: the end of branch b)
git rebase -i a_new         # this time, rebase it on top of the new branch a_new
git log --oneline           # (check results)
git show-branch b_new a_new

再说一遍,see screencast


检查结果

我们现在可以进行之前/之后的树比较:

sehe@natty:/tmp/q$ git show-branch a b
! [a] a2
 ! [b] b5
--
 + [b] b5
 + [b^] a5
 + [b~2] b4
 + [b~3] b3
 + [b~4] a4
 + [b~5] a3
 + [b~6] b2
 + [b~7] b1
++ [a] a2

sehe@natty:/tmp/q$ git show-branch a_new  b_new 
! [a_new] a5
 * [b_new] b5
--
 * [b_new] b5
 * [b_new^] b4
 * [b_new~2] b3
 * [b_new~3] b2
 * [b_new~4] b1
+* [a_new] a5

使其永久化:

for name in a b; 
do 
     git branch -m $name ${name}_old && 
     git branch -m ${name}_new $name
done
git branch -D a_old b_old # when sure

注意事项

我特意选择对演示进行不冲突的更改。当然,在现实生活中,您会遇到合并冲突。使用git mergetoolgit rebase --continue

如果您的更改是卫生的并且确实属于它们各自的主题分支,那么冲突应该很少并且很容易解决。 否则,是时候修改您的分支方案了(参见 Martin Fowler 等)

讨论

回应

您还说“当最终应该对 'a' 进行更改时,我会返回,在 'a' 上进行更改,然后在 'a' 之上重新设置 'b'”。我不确定我是否也理解这一点。你能解释一下吗?

我的意思是,无论如何我都会尝试在 a 分支上进行 a3、a4、a5 修订,然后将 b 重新设置为基础,所以

  • 进行合并的人与进行提交的人是同一个人
  • 你连续提交和合并(这意味着你不会因为短期记忆丢失而搞砸)&lt;-- this is theMerge Early/Soon/Frequently mantra支持>
  • 您可以避免不必要的合并冲突
  • 您不必稍后“回到过去”并重写原始提交:a3、a4、a5 只会被合并,而不是复制(请参阅Git Cherry-pick vs Merge Workflow

这是“开始”的起点,但适合我理想化的工作流程。请注意,这种替代结果的最终结果已经完全在上面“将提交重新组织到他们的主题分支”下显示的所有杂耍之后结束!

mkdir /tmp/q
cd /tmp/q
git init .
touch f
git add f
git commit -am initial
git checkout -b a;                        # no change
echo a1>a1; git add a1; git commit -am a1
echo a2>a2; git add a2; git commit -am a2

git checkout -b b;                        # start off the a branch
echo b1>b1; git add b1; git commit -am b1
echo b2>b2; git add b2; git commit -am b2

git checkout a                            # go back to the a branch for a3 and a4
echo a3>a3; git add a3; git commit -am a3
echo a4>a4; git add a4; git commit -am a4

git checkout b
git rebase a                              # here is the magic: rebase early
echo b3>b3; git add b3; git commit -am b3
echo b4>b4; git add b4; git commit -am b4

git checkout a                            # go back to the a branch for a5
echo a5>a5; git add a5; git commit -am a5

git checkout b
git rebase a                              # again: rebase early
echo b5>b5; git add b5; git commit -am b5

git show-branch

注意实际上返回到 a 分支来提交你的 a3/a4/a5 提交并不难:

  • 如果您只意识到您触摸了属于该分支的东西,您可以切换a 分支等待本地更改 2
  • 然后您有选择地 (!!) 将需要进入 a 分支的部分暂存(git add -i 或 git guis 中的类似内容或例如 vim fugitive
  • 提交 a
  • 切换回 b 分支 (git checkout b)
  • 提交剩余的位
  • b 重新定位到 a 中的最新版本

2 只要它们不与 a..b 的其他更改发生冲突;在这种情况下,您首先 git stashgit stash applya 分支上时

【讨论】:

  • 在我说什么之前,感谢您的详细回答,我正在分析它。 Q:你说我应该早点整合,是什么意思?您还说“当最终应该对'a'进行更改时,我会返回,在'a'上进行更改,并在'a'之上重新设置'b'”。我不确定我是否也理解这一点。你能解释一下吗?
  • @chila:我在底部提到(“讨论”)。请注意,您可以将脚本复制并粘贴到您的 shell 中并查看最终结果(请注意,生成的 show-branch 显示的最终结果与重组后的最终结果完全相同)
  • 哦,这里有一些支持 Martin Fowler 观点的链接回复:主题分支martinfowler.com/bliki/FeatureBranch.htmlmartinfowler.com/bliki/SemanticConflict.html
  • 通过讨论示例,我已经意识到 rebase 有多么强大(我以前从未使用过它),但我意识到我可以做你在讨论中所做的事情,而无需来回移动/到“一个”。我所要做的就是在完成对“b”的提交后,转到“a”并从“b”中挑选属于“a”的提交。然后,我只是移动到'b'并做一个'rebase a'。由于 rebase 不会应用已经应用的提交,因此只有 'b' 提交会重新应用到 'b' 分支上。
  • 这种方法的唯一问题是'rebase'重写了一个分支的历史,所以如果我已经从'b'推送了一些提交,我会错误地推送rebase提交。这是正确的吗?
猜你喜欢
  • 2011-05-06
  • 1970-01-01
  • 2016-03-23
  • 2018-08-12
  • 2015-08-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-12-29
相关资源
最近更新 更多