Combining the function correctly into the master
Problem:
I always face the same problem when it comes to multiple dependent feature branches.
master ---A---B---C
\
feature1 D---E---F
\
feature2 G---H
I have something like this. Assuming both of these branches have been covered, I'll merge feature1
into master
. Then check and pull master
, then check and reinstall feature2
on master
. While doing this, I always see repeated conflicts over and over again, especially with multiple branches.
Good recovery:
I saw online that in the example above, I first have to do something like this to make sure I am configured correctly:
Rebase feature1
upmaster
master ---A---B---C
\ \
feature1 \ D'--E'--F'
\
feature2 D---E---F---G---H
Rebase feature2
on new feature1
commits
master ---A---B---C
\
feature1 D'--E'--F'
\
feature2 G'--H'
I would do it with git rebase --onto feature1 feature1@{1} feature2
.
Confusion:
As I understand it, it is better to reinstall this, because when you rebind, your branch will actually contain completely new commits (i.e. F
and F'
above), which can cause unnecessary conflicts.
With all this in mind, what would be the correct approach to:
- Combine
feature1
intomaster
- Combine
feature2
into master`
I want to try and explore the recommended approach where I can be sure that every time I have multiple dependent branches I won't accept painful conflicts.
source to share
Please note that this answer has two parts. The first is about the mechanics of rebase, and the second is about rebase vs merge.
Complex restructuring mechanics
I would do it using
git rebase --onto feature1 feature1@{1} feature2
Yes: it works by splitting the argument to rebase: instead of just feature1
now it --onto feature1 feature1@{1}
.
Usually we run git rebase name
, for example git rebase feature1
. We must first git checkout feature2
. Adding feature2
to the end of the command (as above) just does the git checkout
step for us. So in the case where I call "normal" here, there is only one argument git rebase
, usually a different branch name, eg feature1
.
But what it git rebase
does is copy some commits, and for that it needs to know two things:
- what to copy (list of commits) and
- where to put new copies.
This one argument feature1
makes the hard climb for both of them. It's great when it works , but for a reboot feature2
to an already reinstalled feature1
it doesn't always work.
The reason it doesn't work (when it doesn't work) has to do with the graphs you drew. You drew them in a strange way, with the branch names on the left. This kind of figure is misleading: it means that every commit is on the same branch, which is simply not true.
Here's the first drawing again:
master ---A---B---C
\
feature1 D---E---F
\
feature2 G---H
Q: Which branch has an A
on commit ? (Trick question!)
A: He is on all branches.
These diagrams should be drawn with commits on the left and labels on the right, with labels pointing to one particular commit - since these things actually work in Git. Here's the same diagram redrawn:
--A--B--C <-- master
\
D--E--F <-- feature1
\
G--H <-- feature2
Starting at the right and next (internal and always backward) parent links from commit to commit, we can see that C
, B
and A
are on master; F
, E
, D
And A
are on feature1
; and H
, G
, F
, E
, D
and A
they are located on feature2
, for example.
Now we can draw a second diagram after the first git rebase
, for example:
D'-E'-F' <-- feature1
/
--A--B--C <-- master
\
D--E--F [reflog: feature1@{1}]
\
G--H <-- feature2
This is where the split argument goes git rebase
.
Typically Git finds a set of commits that it git rebase
copies using:
git rev-list <argument>..<current-branch>
which will be here feature1..feature2
. This means that all transactions are reachable from feature2
, except for all commits reachable from feature1
. Now, before we moved feature1
, this was the correct set of commits: it was H
and G
. But we moved feature1
and now this is the wrong set of commits because it includes F
back through D
too.
Once we say feature1@{1}..feature2
, we get the correct set of commits again. But now we have lost space for copying, and therefore we need --onto
: what to place the copies.
When the second rebase is over, we should do the final result like this:
G'-H' <-- feature2
/
D'-E'-F' <-- feature1
/
--A--B--C <-- master
\
D--E--F [reflog: feature1@{1}]
\
G--H [reflog: feature2@{1}]
and since the locks are usually invisible, we can remove the bottom half of the diagram entirely.
Rebase vs merge
As I understand it is better to reinstall this, because ...
A very slippery term is best.
There are two problems with rebase:
-
It copies the commits and then leaves the originals in favor of the new copies. Does anyone else have the originals? If so, it was difficult for you too: they too have to abandon the originals in favor of new copies.
-
It copies commits. The new copies are at least slightly different from the originals (otherwise they would actually be originals). What exactly is different in copies? It is important? Did you break something in the process, that is, submit errors?
The use git merge
avoids these problems. Instead, he inserts his problems:
-
The story is confused. If it is necessary to find out what happened, then the student of history may have to look down at the "legs" of the fusion. Some argue that this is more positive than negative, because this is a real story, not a later, refined, artificial story.
(I lean toward a cleansed story, but there are merit to both sides of this argument.)
-
The merge itself can introduce errors.
If you have a really good set of tests, it helps you deal with "inject errors" problems.
If it helps enough, it leaves only one problem with reinstallation (if you are smart enough or have a good tool to carry out your complex redistributions like this one): does anyone else have the originals? Balance your answer to this question versus your answer to the question of whether a convoluted story is bad enough to make a rebase merge decision.
If you don't have good tests, ok ... :-)
source to share