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
.