Reputation: 750
Let's say I have a master
branch, and a feature-a
branch. feature-a
is up for review but everyone is busy. I need to start feature-b
but feature-b
is dependent on code done in feature-a
.
So up until now I've been branching feature-b
from feature-a
, and then merging down any changes that might happen to feature-a
.
Now let's say someone gets freed up, and passes feature-a
, therefore feature-a
gets merged into master
.
Now it's time to sync feature-b
with master
. Whether I rebase or merge master
down to feature-b
, often I end up with conflicts that, when resolved, result in no changes to commit, which means I can't commit.
I've done some googling and literally the only viable solution seems to be:
This seems bonkers to me :thinking_face:
Does anyone know of a better way of resolving the conflicts/managing my branches in the above situation?
Upvotes: 1
Views: 1117
Reputation: 489708
Draw the commits you have. Really, literally, draw the graph of commits, on paper or a whiteboard or whatever (or let git log --decorate --oneline --graph
draw it for you, but you'll get more out of this if you do it by hand). You will end up with something that looks like this:
...--o--o--* <-- master
\
A--B--C--D <-- feature-a
\
E--F--G <-- feature-b
before the someone-else gets freed up and merges feature-a
. (The letters stand in for raw hash IDs.) Then, someone else gets freed up and merges feature-a
. So, draw that:
...--o--o--*------------M <-- master
\ /
A--B--C--D <-- feature-a
\
E--F--G <-- feature-b
If they did the merge some other way, or if other commits got merged first, draw that. Draw the actual graph! It's important, because it is how Git works.
Now consider what you'd like to get as the final merge result. Would you prefer:
...--o--o--*------------M---------M2 <-- master
\ / /
A--B--C--D /
\ /
E--F--G
or:
...--o--o--*------------M---------M2 <-- master
\ / \ /
A--B--C--D E'-F'-G' <--- feature-b
\
E--F--G [abandoned, do not use]
or something else entirely?
It sounds like you're going for this second option, which is most easily obtained with git rebase
: rebase copies commits. We need to copy E
to new commit E'
, which is like E
but comes after M
(uses M
as its predecessor and when compared to M
, makes the same changes as E
did compared to D
). Then we need to copy F
to F'
and G
to G'
.
If you just run git rebase master
, Git sometimes1 chooses too many commits to copy. In particular, it might—not in the example I drew here, but in other cases—decide that it should copy A
, B
, C
, and D
first.
When Git decides to do that, you'll get the kind of merge conflicts you're seeing, because everything that each of these commits did, is already done. When you resolve these merge conflicts, you're left with nothing to commit.
The git rebase
command has an answer for that: in mid-rebase, after resolving these conflicts, you run git rebase --skip
. But it's often easier to do your rebase in such a way that Git "pre-skips" the four commits that you don't want copied. To do this, you can use git rebase --onto
:
git rebase --onto master feature-a
for instance, assuming your name feature-a
still selects commit D
. (Remember that branch names move over time, while commits themselves, as identified by their hash IDs, never change.)
The --onto
tells rebase where to put the copies: after M
. The feature-a
part tells Git what not to copy: the commit identified by feature-a
, and any earlier commits reachable by starting at that commit and working backwards through the graph. (The commits to be copied are always those that end wherever your HEAD
is now, and the branch name to be moved at the end of the process, so as to abandon the old commits in favor of the new-and-improved chain, is the branch name you're on right now.)
Drawing the graph is the key to many of Git's operations.
1This depends on both the graph and the results of running git patch-id
on each commit. In the graph drawn here, in which feature-a
was literally merged as-is, Git would know not to copy A-B-C-D
as those commits, with whatever their raw hash IDs are, are reachable from the name master
, working back along the second parent of merge commit M
. So this is not the correct graph drawing.
Upvotes: 1