gitmergerebase

Git fixup after a merge with upsteam


Scenario:

  1. work... work... work... git commit -m "Step1"
  2. work... work... work... git commit -m "Step2"
  3. work... work... work... git commit -m "Step3"
  4. git pull # merge a lot of changes from upstream, resolve conflicts
  5. work... work... work... git commit -m "Step4"
  6. oh! found a small issue in the Step2. git commit --fixup <Step2>

Now I have a separate fixup commit. How could I squash back that one fixup without destroying the merge history?

So I need something around the following:

git reset --hard <Step3>
git cherry-pick <fixup! Step2>
git rebase --autosquash <Step1>
git merge <same upstream commit-id> # resolve the same conflicts again!
git cherry-pick <Step4>

In other words: go to a commit just before the merge. Cherry-pick all fixups. Autosquash. Redo merge. cherry-pick all commits after the merge.

Is there an easier way to do all of this automatically?

Update. My recipe which sounds like that it should be reliable enough.

  1. Interactive rebase to move the <fixup! Step2> right after the merge commit.
  2. reset to <Step 3>
  3. cherry-pick <fixup! Step2>
  4. rebase --autosquash
  5. merge with --no-commit the same upstream commit
  6. load the contents of the rebased fixup commit git checkout <fixup! Step2> .
  7. commit the merge
  8. cherry-pick <Step4>

It works, linear approach, no conflicts to re-resolve, but quite cumbersome if done manually. Will try to use/adapt the @jthill script suggested in the comment...

I wonder if this is so uncommon scenario that it was not implemented out of box... For me it occurs quite often.


Solution

  • Recipe 1

    Ok... you will have to redo the merge but you can avoid having to redo solving the conflicts with a little trick: prepare a commit that has the content of the merge commit applying the fixup.... so, something like:

    git checkout -b template the-merge-commit
    git cherry-pick the-fixup-commit # apply the change of the fixup
    

    Now, run the rebase you want to run (with --rebase-merges) and when you get the conflicts on the merge commit, simply do:

    git restore --worktree --stage --source template -- .
    # ready to move on
    git rebase --continue
    

    That should do.

    Recipe 2

    Another way would be running the rebase (with --rebase-merges) and then, when you get the conflict you can pull the content of the original commit and you can apply the changes from the fixup by hand

    git rebase --rebase-merges blah blah
    # when you hit the conflict on the rebase
    git restore --worktree --source REBASE_HEAD -- . # bring everything from the original merge commit
    git show the-fixup-change | git apply - # apply changes of the fixup by hand
    git add . # add everything to the index
    git rebase --continue