Is it possible to rewrite a branch's history without losing merge info along the way?
We have a master branch into which we’ve merged about 10 feature branches, one at a time.
So the recent history looks like this:
merged feat/10 (HEAD of master) merged feat/9 merged feat/8 merged feat/7 merged feat/6 merged feat/5 ...
Now we found out that
feat/7 was bad and we want to take it out of master. Reverting that merge commit isn’t enough because we don’t want that broken commit to exist in our history at all. We can’t really use interactive rebase because that will flatten out the history to make it look as if it was all done on a single branch, and we want to preserve all that good merge history.
Is there a way to zap out a particular merge commit from a branch’s history?
I’ll note that the real history is much more complex than what you see in the example above, so manually re-doing all the merges since feat/7 wouldn’t be a good option.
To clarify for those who vote to close this as a dup: this isn’t the FAQ about how to take out a commit with rebase, which of course has been answered many times. The question here is about taking out a commit without flattening the merge history.
4 Solutions collect form web for “Is it possible to rewrite a branch's history without losing merge info along the way?”
If your history currently looks like that and you didnt delete the branches yet you can simply
git reset --hard HEAD~4 this will reset your code back to state before you merged in 7 then you can simply
git merge the good ones back in. This is the simplest way I can think of off my head.
You can use -p switch on rebase to preserve merges but using this switch with -i might have consequances. Check man git-rebase page and see the BUGS part to see current bugs.
EDIT2 : I don’t take any responsibility if you don’t take proper precautions before using this command. Don’t use it before reading the manpage too.
You can use
git filter-branch --parent-filter to rewrite the
feat/8 commit so that its parent points to the
feat/6 commit. Leaving the parents of all other commits (9-10) as they are, which should preserve merge commits in history as they were.
Only problem with this is what will happen to conflicts that result in the removed code changed … there is no real way of knowing, and it might be the culprit.
This isn’t quite what you want to do (“zap” the merge commit, so to speak), but in practical terms it’s way easier than convincing your collaborators to
git reset --hard after a
filter-branch. Just revert the merge.
git revert -m 1 <commit_for_feat7>
I don’t particularly like polluting my master branches with reverts, but there is nothing inherently wrong with it. If you’re not going to be patching
feat7 for a while, or just want its change sets out of history, this solution is much less trouble than history-revision.
You can’t just zap stuff out of git because the current hash depends on the entire history.
One option is to create a new branch that starts from feat/6 and then has the merges starting from feat/8 so that the branch head can point to a different hash without the changes from feat/7.
Another option, if I’m not mistaken, is
git replace (I think it used to be called grafts). It can let you “replace” the pointer at feat/8 from feat/7 to feat/6. I’m not entirely sure how exactly it achieves this, but it looks like it isn’t a true replacement because feat/8 still does have a pointer somewhere to feat/7 because the hash doesn’t change, but
git replace somehow adds an alternate history in which feat/8 points to feat/6.
From the man page:
git replace [-f] <object> <replacement> git replace -d <object>… git replace -l [<pattern>]
Adds a replace reference in .git/refs/replace/
The name of the replace reference is the SHA1 of the object that is
replaced. The content of the replace reference is the SHA1 of the
Unless -f is given, the replace reference must not yet exist in
Replacement references will be used by default by all git commands
except those doing reachability traversal (prune, pack transfer and
It is possible to disable use of replacement references for any
command using the –no-replace-objects option just after git.
EDIT: on second thought
git replace might be a bad idea since changes from feat/7 would exist in the merge of feat/8. You should probably just go with the first option: start a new branch off of the merged feat/6 and remerge starting from feat/8