Reputation: 6089
I'm in a little trouble with git.
Here is what i did.
Can I know how I can force merge all the changes of master to my branch?
Thanks in advance.
Upvotes: 2
Views: 409
Reputation: 522161
Let's say that your feature branch feature
and master
started off like this:
master: A -> B -> C
feature: A -> D
After merging master
into feature, this is how things looked:
master: A -> B -> C
feature: A -> D -> M # M is a merge commit
Next your reverted the merge in the feature
branch by doing a git revert
. This means that you told Git to add a new commit to undo the result of the merge. Now this is the state of the two branches:
master: A -> B -> C
feature: A -> D -> M -> R # R is a revert commit
When you try to pull master
into feature
, Git is telling you that you are already up to date, because you are! The revert commit functionally undid whatever happened during the merge, but now you have two new commits in your feature
branch.
To get back to the state you were in before you made the erroneous merge in your feature
branch, you can nuke the two merge and revert commits in feature
. Follow these steps exactly:
git checkout feature # switch to your feature branch
git reset --hard HEAD~2 # nuke the 'M' and 'R' commits
After this, you can try doing a merge with master
again and all should be fine. Of course, make sure that you do the merge properly.
Note that this option involves rewriting the history of the feature
branch, via nuking commits, and should probably not be used if the branch is shared and you have already pushed the branch with the merge commit. See the answer by @torek for other options if you fall into this category.
Upvotes: 2
Reputation: 489223
You have at least three options. See Tim Biegeleisen's answer for one of them: it's the simplest, and is the one to use if you have no additional commits and have not pushed the merge (or merge-and-revert).
Let me re-draw his master
-and-feature
graph this way, though:
A - B - C <-- master
\ (pre-merge)
D <-- feature
and:
A - B - C <-- master
\ \ (post-merge)
D --- M <-- feature
The thing that makes a merge commit be a merge commit is that it has two parents: the "main line" parent (D
, in this case) and the commit that was merged (C
).
When you ask git to perform a merge, git starts by finding a common ancestor, in this case commit A
. It then does two diffs: A
vs D
(what happened on the main line, feature
) and A
vs C
(what happened on the branch being merged-in, master
). It then combines these two sets-of-diffs, so that it can make a new commit that contains one copy of every change made in both lines.1 If all goes well, it commits the result automatically; if not, it stops with a merge conflict, makes you fix things up, and has you commit the final result. Either way, the final commit M
lists both commits C
and D
as its (two) parent commits, rather than just one parent. That, in git, is the definition of a merge: a commit with two2 parents.
For illustration purposes, let's add a new, ordinary commit on feature
at this point:
A - B - C <-- master
\ \
D --- M - E <-- feature
What happens now if, while on branch feature
, you ask git to merge master
again?
Git must first find the most recent common ancestor of (the tips of) feature
and master
(aka the "merge base"). The ancestors of commit E
, the tip of feature
, are—in order of distance—E
itself (zero steps back); M
(one step back); C
and D
(both two steps back: both are parents of M
); and A
and B
(both three steps back). (There's another way to get to A
that's four steps back but we take the shorter route.) The ancestors of commit C
, the tip of master
, start with C
itself (zero steps back). So this is the merge base, and to merge master
, git should diff C
(the merge base) against C
... but clearly there's no change there, so there is nothing to merge and you get the "up to date" message.
What about a revert, though? Well, as far as git is concerned, a revert is just an ordinary commit. What makes it a reversion is that the changes it applies "undo" the changes from a previous commit. That is, to make a revert, git essentially does a git diff
(as for either side of a merge), but then wherever the diff says "add a line", git deletes that line instead, or where it says "delete a line", git puts back what got deleted. (This is a little bit trickier when you revert a merge since git has to avoid undoing something that, when you did the merge, had been done on both sides; but git does it correctly.)
Back to the problem at hand, namely, re-doing the merge: you can use the suggested method, which is to (in effect) remove the undesired revert and the undesired merge. Using git reset --hard
will do that, putting you back into the original pre-merge state:
A - B - C <-- master
\
D <-- feature
(The extra commits M
and R
are still stored in the repository, but are hidden from view and will eventually be garbage-collected.) Now you can re-do the merge, presumably getting it right this time.
One drawback to the simplest method is that when you make a new (corrected) merge, it will have a new and different underlying SHA-1 ID:
A - B - C <-- master
\ \
D --- M2 <-- feature
In itself, this is necessary and even desirable. But, if you have pushed (or otherwise published) merge commit M
somewhere (with or without pushing the revert R
as well), someone else may have copies of that original bad merge. Pushing the new feature
will require using --force
to "rewrite history". Anyone who picked up the bad merge M
may be depending on it, and rewriting history will make things difficult for them.
(You can of course choose to say "tough luck" to them and make them deal with your reset and re-merge. That's the easy way, and sometimes even the best way. But in case it isn't, I will press on.)
The other reason you might not want to do this happens if you've made some good commits since the bad one:
A - B - C <-- master
\ \
D --- M - E - R - F <-- feature
Let's say that commit M
is the bad merge, R
is its reversion, but E
and F
are both good and desirable. If you use git reset --hard
to wipe out M
, you also wipe out E
and F
. So now what?
Git being git, there are many ways to handle this. You must choose:
Let's address the last one first. You can save these commits as patches, using git format-patch
. That lets you wipe them out (with git reset --hard
) and then just re-apply them (with git am
). Or, you can leave them in the repository, so that you can use git cherry-pick
to copy them, or leave them in the original git commit chain, so that you can get a fast-forward-able history.
Let's look at the fast-forward-able item next. This basically means that every published commit (every commit you've ever pushed, or made available to someone else via git pull
, or whatever) must remain unchanged: you must only add on to such history. That's really all there is to it (of course it means leaving your mistakes out there for others to view).
Finally, let's look at the middle item: do you really want to re-do the merge, or just take what's there and fix it? There's actually a third option, which seems a bit stupid at first: you can completely re-do the merge and then (using that one) fix the broken one. The advantage to this method is that it lets you produce, pretty easily, a fast-forward-able history.
There are actually several ways to do this, but I'll just show one in particular. You want git to see the same setup it saw "pre-merge" even though there's a merge. I'll draw the same graph again, but stretch it out a bit:
A - B - C <-- master
\ \
D \
\ \
----- M - E - R - F <-- feature
Now let's do something git does easily, namely, check out commit D
. We can find its ID (78cefa3
or whatever), or count back commits on feature
: F
is 0 back, R
is 1 back, E
is 2 back, M
is 3 back, D
... well, it's slightly tricky: both D
and C
are 4 back, but D
is on the "main line", so feature~4
will name commit D
.
Either way we check it out ... but we make a new branch label for it, let's call this feature-alt
:
$ git checkout -b feature-alt 78cefa3 # by ID
Now we have this graph:
A - B - C <-- master
\ \
D ....\................ <-- feature-alt
\ \
----- M - E - R - F <-- feature
(I put in the .
s to show that feature-alt
points directly to commit D
). Now, even though there's a feature
branch, let's simply ignore it for a while, and draw the graph one more time:
A - B - C <-- master
\
D <-- feature-alt
This should look very familiar: it's the graph we had when we made the bad merge. So now, while we're on feature-alt
, we simply run:
$ git merge master
and this time do the merge correctly (use git merge --no-commit
if you like, to prevent git from committing an automatic merge result, so you can fix it up first).
Once the merge finishes and is committed, we'll have this:
A - B - C___ <-- master
\ \ \
D ----\-- M2 <-- HEAD=feature-alt
\ \
----- M - E - R - F <-- feature
I added the HEAD=
here to show that we're still on feature-alt
, with new merge commit M2
. Note that M
has parents D
and C
(in that order), and so does M2
, but the contents of M2
are (presumably) different (the merge being correct this time).
At this point, you can simply git cherry-pick
commits E
and F
, then delete branch feature
and rename feature-alt
to feature
, and push the result with --force
. That gives you a clean history with a correct merge and your two good commits. It's not fast-forward-able, but it preserves the changes you made in E
and F
.
Or, if M2
is the correct merge result, and you want a fast-forward-able history, what you need now is to apply the changes in M2
to the tip of existing branch feature
. Since R
reverted M
, you can now do this:
$ git checkout feature
$ git cherry-pick -m 1 feature-alt
This is like any other cherry-pick except that you must specify the main-line of the merge (just like with git revert
). This says, in essence, "compare D
(the first parent) to M2
and apply those changes to the tip of the current branch feature
": that is, put the correct merge in, instead of the wrong one.
If all goes well, you now have this:
A - B - C___ <-- master
\ \ \
D ----\-- M2 <-- feature-alt
\ \
----- M - E - R - F - M3 <-- HEAD=feature
where M3
is a copy of M2
(but is a regular non-merge commit).
If all has gone well, you can now simply delete the feature-alt
branch entirely. The bad merge is preserved in history, as is its reversion, and the corrected version of the merge appears as if by magic as an ordinary commit.
If all of this is too complicated and you don't want to repeat the merge, you can simply revert your revert of the merge, bringing back the bad merge:
A - B - C <-- master
\ \
D --- M - E - R - F <-- feature
becomes:
A - B - C <-- master
\ \
D --- M - E - R - F - R2 <-- feature
where R2
reverses R
. Now you can make whatever fixes are needed and git commit --amend
to adjust R2
(or even just git commit
without --amend
to just add another fix on top, and then maybe use git rebase -i
to squash it in later before pushing). What this does is make R2
be exactly the same as what you'd have gotten with M3
, the cherry-picked copy of M2
, the corrected merge. You avoid re-doing the merge, but of course, you have to figure out what to do to fix the bad merge.
In any case, the final result of all this can be pushed without -f
as it is simply new commits added atop all previous ones.
1For instance, if A
-to-D
adds a line of text to README.txt
and A
-to-C
adds a different line of text to main.c
, the new commit M
will have each line added to each file. However, if A
-to-C
adds the same line to README.txt
, in the same place, git will know to just add one copy of that new line, not one from each set of changes. The new README.txt
will have just the one line added, without repeating it.
2Really two or more, but multi-armed "octopus" merges basically work the same way.
Upvotes: 2