Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Rebasing when existing commit hashes have changed

Tags:

git

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:

  • The old commit hash (pre-rebase/merge-conflicts) still remains on the developer branch
  • The new commit hash (post-rebase) now exists on the feature branch.

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?

like image 213
helion3 Avatar asked Mar 02 '15 19:03

helion3


1 Answers

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.

like image 104
Schwern Avatar answered Nov 19 '22 11:11

Schwern