Reputation: 387
Not sure if this is a duplicate, but spent almost a day trying to figure out a neat solution and could not find an answer yet.
My git repo status is something like this now:
C---D---E feature-branch
/
A---B---C---D---X---F master
|
Reverts C and D
I had been working on feature-branch
and the commits C
& D
got merged to master accidentally(complicated story). Others working on the repo reverted back those commits since it breaks the build.
Now, after the commit E
, I want to rebase feature-branch with current master. But the problem is that since the commits C
& D
are already present in the master, those commits are not getting included while rebasing.
From git man page:
If the upstream branch already contains a change you have made (e.g., because you mailed a patch which was applied upstream), then that commit will be skipped. For example, running git rebase master on the following history (in which A' and A introduce the same set of changes, but have different committer information):
A---B---C topic / D---E---A'---F master
will result in:
B'---C' topic / D---E---A'---F master
How can I include the commits C
& D
(probably with a new SHA) now since the changes are reverted back by the commit X
?
Upvotes: 3
Views: 726
Reputation: 45659
tl;dr : You can do what you want with a command like
git rebase --onto master feature-branch~3 feature-branch
(where feautre-branch~3
works in the example, but in general would be any expression that resolves to commit B
- such as the commit hash for B
, etc.)
To see why that works, read on...
First, we should clarify your diagram a bit. You said
I had been working on the feature-branch and the commits C & D got merged to master accidentally(complicated story). Others working on the repo reverted back those commits since it breaks the build.
(emphasis added). Now merge could do one of two things[1], but duplicating commits C
and D
isn't one of them. The default behavior would come out looking like this:
A -- B -- C -- D -- !CD -- F <-(master)
\
E <-(feature-branch)
Since master
apparently was at B
at the time of the accidental merge, the default behavior would be a "fast-forward" of master
onto the existing D
commit. It's then as though feature-branch
hand't been created until after D
.
(I also made some notation changes. !CD
indicates a merge that reverted C
and D
(without the need for an annotation). And I find this way of drawing the lines to be more readable. But the important point is how C
and D
are represented...)
The other possibility, if you had used the --no-ff
option (or if master
had diverged from feature-branch
before the merge) would look more like
C - D -- E <--(feature-branch)
/ \
A -- B ----- M -- !CD -- F <--(master)
Again merge
would not duplicate C
or D
; instead it would create a "merge commit" M
that incorporates changes from C
and D
into master
's history (and makes C
and D
"reachable" from master
).
In both cases, the "merge base" between feature-branch
and master
, after the merge, is D
. So you have to solve two problems: the "duplicate patch id" problem described by the docs; and the merge-base being at a point that excludes your commits - because rebase
won't even consider copying commits that are already reachable from the upstream.[2]
Often it's useful to solve just the "merge base" problem. At the end, we'll see why you don't have to for your purposes, but for completeness here's how you would:
First find an expression that resolves to commit B
(such as the commit has for B
, or feature-branch~3
in the above examples). Use it in a command like
git rebase -f feature-branch~3 feature-branch
This would copy commits C
, D
, and E
so that you have
E
/
A -- B -- C -- D -- !CD -- F <-(master)
\
C' -- D' -- E' <-(feature-branch)
(where E
technically still exists but is unreachable so might eventually get destroyed by gc
). Of course this assumes the "fast-forward" case; if you had a merge commit, it would look different, but the upshot would be the same.
And that upshot is, you now could merge feature-branch
into master
without a problem. But if you want to rebase
feature-branch
to master
(in preparation for a fast-forward), then you still have to solve the original problem you pointed out: C'
and D'
will be skipped as duplicates of C
and D
.
What you want is to thwart the patch ID duplicate check; and while there doesn't seem to be an actual option for that, you can do it by keeping git from knowing where to look for the duplicates. Instead of giving master
as the upstream, give commit B
as the upstream; and then rebase --onto master
.
git rebase --onto master feature-branch~3 feature-branch
Not only does git
have no reason to consider C
and D
when you give this command, but also because you're no longer using master
as the upstream, the master
-to-feature-branch
merge base no longer matters. You're explicitly saying that B
is your upstream, so this solves both problems at once - which is why, as I noted above, you end up not having to worry about the rebase -f
command after all.
[1] Actually, there's at least a third thing it could do. If you had specified the --squash
option, then it would not do a real merge, but instead would create a single new commit CD
on master
. This is essentially the same as the merge commit that is normally created, except it doesn't include D
as a parent.
C - D -- E <--(feature-branch)
/
A -- B ----- CD -- !CD -- F <--(master)
This is useful for some specific situations, but in general I don't recommend it as git "loses track" of the fact that C
and D
are already "accounted for" in master
. In your situation, that would happen to work out nicely - which is why I know you didn't do it this way and didn't include this possibility in the main text of the answer. But in most cases it makes future interactions between the branches more difficult.
[2] These problems seem similar, but are distinct. In one case C
is already itself reachable from the upstream to which you're rebasing; in the other case, it's not, but another commit that introduces the same changes is.
Upvotes: 2
Reputation: 12465
In the feature-branch execute,
git rebase master
which will rebase on master and result as you have expected by ignoring C D. ( A---B---C---D---X---F--E
in feature-branch)
Then do git reset --soft <sha of F>
to temporarily get rid of commit E.
Next git stash
the reset commit changes (i.e E)
Then in the same feature-branch
execute the following,
git cherry-pick <sha of C>
git cherry-pick <sha of D>
Finally git stash pop
and do git commit
so you will get back your E.
This will do what you expected.
I tested in my machine and below is the result obtained by git log --online
in feature-branch
ae4b6bb E commited
eee0a6a D commited
3141961 C commited
a44aa27 F Commited
265da4c Commited X
9e2729d D commited
84a3a9b C commited
8a543ca B commited
4e4a8ca A commited
Upvotes: 1
Reputation: 11
You can use git cherry-pick for that. In your particular example, the command would then be:
in master# git cherry-pick topic~3..topic
Upvotes: 1