Move initial commits off master to another branch in Git

I'm trying to migrate to the GitFlow workflow, and I want rewrite the history of the repository so that all of it complies with the new repository.

At the moment it looks like this:

Master: A - B - C - D - E - F - - - - - - - - - L
                             \                 /
Release:                      \           J - K
                               \         /     \
Development:                    G - H - I       M

I would like it to look like this:

Master:  A - - - - - - - - - - - - - - - - - - - L
          \                                     /
Release:   \                               J - K
            \                             /     \
Development: B - C - D - E - F - G - H - I       M

I've tried searching for an answer here, and I found an answer, but it only seems to work if you are creating the new branch, rather than using an already existing branch.

Many thanks in advance.


In Git, as pointed out by larsmans in his comment, a branch is simply a pointer/reference to a particular commit. Labelling codelines with branch names as you did on the left-hand side of your graph can be confusing.

For instance, it may have been true that, in the past, commits G, H, and I were part of the ancestry of the development branch only. However, in the current state of your repo, they belong to the ancestries of all three branches (master, development, and release).

To think that those three commits (G, H, and I) are, in some way, still more related to the development branch than to master or release no longer makes any sense, simply because your repo doesn't remember where branch references pointed to in the past (although that information is stored locally in something called the reflog). Your Git repo only knows where the branch references are pointing to at the present.

Therefore, when you draw a graph to describe what state your repo is in, if a branch points to a commit, it makes more sense to label the commit itself with the corresponding branch name. I did so on all my graphs below.

Original state

A - B - C - D - E - F - - - - - - - - - L [master]
                     \                 /
                      G - H - I - J - K [release]
                                       \
                                        M [development]

Desired state

A - - - - - - - - - - - - - - - - - - - L' [master]
 \                                     /
  B - C - D - E - F - G - H - I - J - K [release]
                                       \
                                        M [development]

To end up in this state, you should take the following three steps.

Procedure

1 - Check out your master branch

git checkout master

After that, HEAD points to master:

A - B - C - D - E - F - - - - - - - - - L [HEAD -> master]
                     \                 /
                      G - H - I - J - K [release]
                                       \
                                        M [development]

2 - Do a hard reset of master to commit A

git reset --hard <commit_ID_of_A>

Because L is no longer reachable by any reference in your repo, it "vanishes" from the history graph and, you're simply left with

A [HEAD -> master]
 \
  B - C - D - E - F - G - H - I - J - K [release]
                                       \
                                        M [development]

3 - Do a true merge of release into master

At this stage, if you were to simply run

git merge release

because the tip of master is an ancestor of the tip of release, a fast-forward merge would take place, and you would simply end up with

A - B - C - D - E - F - G - H - I - J - K [HEAD -> master,release]
                                         \
                                          M [development]

which isn't what you want. Therefore, the --no-ff option is required to enforce a true merge, here:

git merge --no-ff release

After this last command, your repo should be in the desired state:

A - - - - - - - - - - - - - - - - - - - L' [HEAD -> master]
 \                                     /
  B - C - D - E - F - G - H - I - J - K [release]
                                       \
                                        M [development]

Note that I nicknamed the new commit L' instead of L because those two commits have different parents: the parents of L are F and K, whereas the parents of the new commit, L', are A and K.


This should do what you want, (though I'm not sure about your release branch)

git checkout master
git reset --hard commit_A # the commit id for A

git merge --no-ff release