14

We have a sequence of events like this:

  1. Create branch A, and add a few commits to it
  2. Time passes, hundreds of commits added to master
  3. Master is merged into A
  4. Time passes, maybe another 50 commits added to master

Is it possible to turn the merge in step 3 into a rebase? If nothing else, it would make the impending merge simpler as there would be less history to look at.

We haven't had rerere enabled.

Steve Bennett
  • 114,604
  • 39
  • 168
  • 219

1 Answers1

21

Consider the following Git graph:

A...C...D...F
^       ^   ^ master
 \       \
  G...H...I...J
              ^ feature

The ... means "lots of commits".

Suppose we want to essentially rebase, i.e. take the commits in feature's history but not in master's history and reform them as a linear sequence of commits in master. This confounded by the fact that we merged master into feature at commit I. If we try to rebase, Git screws up because it tries to apply e.g. C on top of master and finds conflicts.


The best method is from this answer, copied below for convenience:

git checkout feature
git branch -m feature-old
git checkout main
git checkout -b feature
git merge --squash feature-old
git commit

We can solve this problem by grabbing the actual changes from feature and packing them into a new commit. Here's a way to do this using only very basic Git commands:

(1) git checkout feature
(2) git merge master
(3) git reset A
(4) git add -A  # This stages all working copy changes
(5) git commit -m "Every change between A and J"

In step (2), the feature branch has all changes in both master and J. After step (3), HEAD points at A, but our working copy has all the changes from master and J, and steps (4) and (5) stage and commit those changes.

At this point our graph looks like this

A...C...D...F
^           ^ master
 \
  J'
  ^ feature

Note that J' contains everything in A...F. Now we do

git rebase master

Git happily applies the changes in J' as a new commit J'', but the only changes to add are those in G...J because the other changes are already in master. So now we have

  A...F<--J''
master^   ^feature

with all of the changes in our feature branch squashed into commit J''. At this point, you can reset to F and re-apply changes in J'' in a more granular fashion (even using git add --patch) if you want.


Another way to do essentially the same thing is with read-tree as explained in this other answer

git checkout master
git read-tree -u -m feature

Yet another way to do the same thing, stolen from this answer, is

git diff master > feature.patch
git checkout master
patch -p1 < feature.patch
DanielSank
  • 3,303
  • 3
  • 24
  • 42
  • 1
    Is there an easy way to find commit A ? Because that's what's blocking me right now – Arthur Rainbow Jun 15 '19 at 13:11
  • you are a rockstar! – Raymond Ruiz-Veve Sep 26 '19 at 15:41
  • The original solution didn't work for me, but the `read-tree` method did. – ryantuck Jun 04 '20 at 19:40
  • SIR YOU DID MAGIC! Solved so many of my issues. ^^. I would add to your solution, if you have a push or pull issues. Think of deleting remote and pushing branch back up. in my case 1 binary file from A on remote, was in conflict with J'' local when i tried to push. The one in J was actually a better matlab simulink file i did separately and saved under same name, hence no fast forward possible but actually not needed too. They told me to do a choose theirs or mine. it was mine obviously. but it gave me A merge link between A remote and J'' for my feature branch. – Weltgeist Feb 24 '21 at 20:35
  • Using the first method, you would essentially be squashing the `feature` branch onto `master`, right? You would lose all the individual commits. – somethingRandom Nov 10 '22 at 07:59
  • @somethingRandom yes, and you could reform things into individual commits using e.g. `git add --patch`. – DanielSank Nov 30 '22 at 07:04