Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Move commits from one branch to another and then merge them back in

Tags:

git

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.

like image 431
chila Avatar asked Sep 25 '11 14:09

chila


1 Answers

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


Getting to the starting point

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


Reorganizing commits onto their topic branches

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


Inspecting results

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

Making it permanent:

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

Notes

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.)

Discussion

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

  • the person doing the merge is the same person doing the commits
  • you do the commit and merge in succession (meaning you don't mess things up due to short-term-memory loss) <-- this is theMerge Early/Soon/Frequently mantra
  • you avoid unnecessary merge conflicts
  • you don't have to 'go back in time' later and rewrite the original commits: a3, a4, a5 would just be merged, not copied (see Git Cherry-pick vs Merge Workflow)

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:

  • in case you only realized you touched things that belong on that branch, you can just switch to the a branch with pending local changes2
  • you then selectively (!!) stage the parts that needed to go onto the a branch (git add -i or similar in git guis or e.g. vim fugitive)
  • commit on a
  • switch back to the b branch (git checkout b)
  • commit the remaining bits
  • rebase b onto the latest revision in a

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

like image 178
sehe Avatar answered Oct 16 '22 15:10

sehe