i'm trying to understand more about a git issue our team recently had. We have several layers of branches:
-> Master
|-> Feature Branch
|-> Individual developer branches
Small groups of developers work off a feature branch and we have several different feature branches going at once.
When one is merged with Master, feature branch owners will rebase their feature branches. Every so often they have merge conflicts with any work that's been merged to the feature branch from individual developers.
Fixing the merge conflict often changes the commit hash during rebase.
When an individual developer then does a rebase of the feature branch into their development branch, there are technically two commit hashes for the item that was corrected:
When this developer attempts to compare against the feature branch, the old commit is counted as a new work and "dirties" the pull request.
I can easily "workaround" this by having them create a new branch from the latest feature branch, and cherry-pick their work. This discards the old commit hashes, but this isn't efficient.
Is there any way to change our practice or rebase differently so that we can exclude "changed" commit hashes in developer branches?
TL;DR version: Have everybody use git pull --rebase
.
If I understand you correctly, you have this situation.
A - B - C [m]
\
D - E - F [f]
\
G - H - I [d]
m for master, f for feature branch and d for dev branches. You want to be able to rebase f onto m and also rebase d onto the new f.
G1 - H1 - I1 [d]
/
D1 - E1 - F1 [f]
/
A - B - C [m]
\
D - E - F
\
G - H - I
The answer is to have everybody git pull --rebase
which is equivalent to a git fetch
plus a git rebase
. Here's how it would work. This is what the repositories look like after there's been work pushed to master (commit C).
origin
A - B - C [m]
\
D - E - F [f]
\
G - H - I [d]
feature branch maintainer
A - B [m][o/m]
\
D - E - F [f][o/f]
\
G - H - I [o/d]
dev branch maintainer
A - B [m][o/m]
\
D - E - F [o/f]
\
G - H - I [d][o/d]
Feature branch maintainer decides its time to update, so they git pull --rebase origin master
. This does a git fetch origin
plus a git rebase origin/master
. Resulting in this.
feature branch maintainer
D1 - E1 - F1 [f]
/
A - B [m]- C [o/m]
\
D - E - F [o/f]
\
G - H - I [o/d]
Then they git push origin
(I believe they will need to force) and this is the result.
origin
D1 - E1 - F1 [f]
/
A - B - C [m]
\
D - E
\
G - H - I [d]
feature branch maintainer
D1 - E1 - F1 [f][o/f]
/
A - B [m]- C [o/m]
\
D - E
\
G - H - I [o/d]
dev branch maintainer
A - B [m][o/m]
\
D - E - F [o/f]
\
G - H - I [d][o/d]
Now the dev branch maintainer wants to update, blissfully unaware that anything has happened to the feature branch. They git pull --rebase
which is equivalent to git fetch origin
and git rebase origin/feature
. After the fetch it looks like this.
dev branch maintainer
D1 - E1 - F1 [o/f]
/
A - B [m]- C[o/m]
\
D - E
\
G - H - I [d][o/d]
And then the git rebase origin/feature
.
dev branch maintainer
G1 - H1 - I1 [d]
/
D1 - E1 - F1 [o/f]
/
A - B [m]- C[o/m]
\
D - E
\
G - H - I [o/d]
And they can push that, probably with a --force
.
You'll note that the dev branch is on F1, not E1 as it originally was. If you want to maintain that you'll have to rebase specifically onto E1.
If Git can't figure out that D and E are not really part of the dev branch, you might wind up with this.
dev branch maintainer
D2 - E2 - G1 - H1 - I1 [d]
/
D1 - E1 - F1 [o/f]
/
A - B [m]- C[o/m]
\
D - E
\
G - H - I [o/d]
Git should be able to figure this out using "patch ids" which are like commit ids, but they only check the content and none of the rest. If it can't, try updating Git and see if that helps. If it still can't do it, then you are in what the git-rebase docs calls "the hard case". You'll have to spell out to Git which commits you want to rebase with --onto
. Fortunately, the reflog contains the previous location of the feature branch as f@{1} and you can use that for reference.
git rebase --onto f f@{1}
That says to rebase the current branch (d) onto f, but only rebase from d to f@{1} (which is where f used to be).
I have git pull --rebase
aliased to git repull
. I do this almost exclusively, it's fairly safe and generally the right thing. You can configure it as the default with git config --global pull.rebase true
.
For further information, see Recovering From An Upstream Rebase in the git-rebase
docs.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With