See the image. I had a master
branch. At commit A
, I branched dev
branch from it. At point B
I synced dev
with master
, creating M1
. At point C
our team branched release
branch from it. In future, I will need to merge dev
back to release
.
Unfortunately, I accidentally merged commit D
from master
to my dev
branch, creating M2
. Now I cannot merge dev
to release
since it contains commits C..D
, which belong to master
and should not go to release
.
My development is not over, and I'm not going to merge dev
to release
right now. However, I want to stay synced and merge release
to my dev
branch. I expect such merges to happen several more times in future, before I finish development and merge dev
to release
.
Thus, at some point I would need to revert M2
from dev
. I want to do it as early as possible, since commits from release
may conflict with changes in M2
. Remember that some of these changes do not exist in release
.
Since I want to revert M2
as early as possible, I would like to do it before I merge from release
to dev
. That's where the problem actually begins. Thank you for reading to this point :)
I can revert M2
in dev
, this is not a problem. However, after that, when I merge release
to dev
, git computes merge base as C
. While I want merge base to be B
, to pretend like merge M2
just didn't happen at all. Because of this incorrect base, changes B..C
are actually automatically discarded by the merge. Git thinks, I manually removed them, since this is what he sees in revert commit of M2
!
Let me clarify this. Imagine someone created file foo.txt
in C
. This file will be added to dev
with M2
. And it will be removed from dev
once I revert M2
. When I merge release
to dev
, git sees foo.txt
in release
, it sees foo.txt
in base commit C
, but it does not see foo.txt
in dev
. Thus git thinks I removed foo.txt
. But I didn't do it.
There would be no problem if I specified B
as the merge base. Is there a way to do it in git?
Since I found no way to override merge base, I did a little hack. I post it here because I had another related question.
See second image. I started new local branch tmp
from E
, commit before M2
. I cherry-picked to this branch all changes from dev
except for M2
. I merged release
to tmp
and commited result M3
with only one parent from tmp
(note dashed line on the image).
I reverted M2
in dev
, commit G
. I created "fake" merge from release
to dev
. This commit contained no changes, but had two parents: from both dev
and release
. I then cherry-picked M3
to dev
and squashed it with my empty fake merge. I thus created M4
, with correct changes and with correct parents.
The question is: did I actually need this "fake" merge? Maybe there is a way to cherry-pick M3
to dev
and make it to have two parents?
I'll summarize questions here:
Thank you if you were able to read through this!
As I realized after the discussion, my solution (or any other solution to the stated problem) has a crucial defect. As I've said, at some point I will merge dev
to release
. As I have not said, at some point after that we will merge release
to master
. And at this point we will face exactly the same problem. The base for this merge will be resolved to D
, not to C
. And this will lead to the similar problem, but at a much greater scale.
Thus, the best solution to this problem is to continue development in tmp
, and make it the new dev
, effectively excluding bad merge M2
from the history.
I'm pretty sure the short answer for both your bottom line questions is no:
git reset
, though it's a workaround. You git reset <wanted base>
. All the changes between your current commit and the new base are uncommitted in the work area, so you can make a new commit that you want (though you lose some tree information you may have wanted).In any case you can stich your dev
branch around M1 to get rid of it:
git checkout dev
git rebase -i <commit prior to M1 or even to A>
The -i
will allow to remove the merge commit.
EDIT
Your workaround's final commit in dev
may seem like what you want, but you have to remember in git, if there's a line below connecting to you then those commits are a part of you as well. What you did was elaborately rebased to delete M2
, so commits C..D would not be taken into account in any parent.
If you could tell git to just merge dev
torelease
based at B, then the following I think is a good estimate of what would happen:
There is no getting around C..D if M2 exists in the hierarchy. But, you can skip it by reverting or rebasing, or doing that crazy branch bypass you did.