I have a tree:
/--b1---b2 <-- topic b
/
a1---a2 <-- topic a
where 'b' depends on 'a'. Then I realize I need to do some changes related to topic 'a' in order to continue with 'b', but i'd like to do them on 'b' as a normal development course of 'b':
/--b1---b2---a3---a4---b3---b4---a5---b5 <-- topic b
/
a1---a2 <-- topic a
Then, when the thing I want to accomplish on 'b' is done, I'd like my tree to look like this:
/--b1---b2--------m---b3'---b4'---b5' <-- topic b
/ /
a1---a2---a3'---a4'---a5' <-- topic a
as if I actually did all the changes on 'a', then merged them on 'b' and then continued on 'b'.
I know I can do this manually doing:
1- rebase/cherry-pick 'a' commits from branch 'b' to 'a'
2- create a temporal branch 'b-tmp' on 'b'.
3- reset branch 'b' to 'b2'.
4- merge 'a' onto 'b'.
5- rebase/cherry-pick 'b' commits from 'b-tmp' onto 'b'.
6- delete branch 'b-tmp'.
I can create some script to do this, I'd just like to know if there are better ways/ideas to do this, other than this 6 steps.
Let me start out with saying that I'd rather integrate the topic branches often (Integrate Early, Build Often ... rings a bell?). So in fact, when there are changes that should go onto a eventually, I'd go back, do the changes on a, and rebase b on top of a.
If a needs to be stable for some other purpose, I'd make a a-for-b branch to contain the work temporarily, and rebase b on top of that.
If you really must work with 'topic-a' changes inside branch 'b', Here's what i'd do.
Note: screencasts included
This is a simple working tree with the layout from the question:
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
See 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
Again, see screencast
We can now do a before/after tree comparison:
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
I have deliberately chosen to make non-conflicting changes for the demo. Of course in real life you'll get merge conflicts. Use git mergetool
and git rebase --continue
.
If your changes are hygienic and really belong on their respective topic branches, chances are that conflicts should be few and easily resolved. Otherwise, it is time to revise your branching scheme (see Martin Fowler e.a.)
In response to
You also say that "when there are changes that should go onto 'a' eventually, I'd go back, do the changes on 'a', and rebase 'b' on top of 'a'". I'm not sure I understand this also. Could you explain?
I mean that I would try to make the a3,a4,a5 revisions on the a branch anyway, and rebase b onto that, so
<-- this is the
Merge Early/Soon/Frequently
mantra
Here is the 'Getting to the starting' point, but adapted to my idealized workflow. Note that the end-result of this alternative result is already exactly what you end up after all the juggling shown under 'Reorganizing commits onto their topic branches' above!
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
Note that it is in practice not hard to go back to the a branch to commit your a3/a4/a5 commits:
git add -i
or similar in git guis or e.g. vim fugitive
)git checkout b
)2 as long as they don't conflict with other changes from a..b; in that case you'd git stash
first and git stash apply
when on the a branch
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With