Why Does `git rebase` Merge Conflict?
- David Reed
I've got a really ugly branch (
daves-feature) going. I started my branch from
main, but later realized it was better suited to be based on
parent. My commit history looks like this:
ac1f97e (HEAD -> daves-feature) Hush, Pyright 1a48f15 Cleanup c18fe09 Working parallelization with passing tests b99aef1 Merge 8c048ba (parent) Add multi-status field 30084f1 WIP c7c4f26 Try a different strategy 77bfbf7 WIP
In addition to lots of WIP commits, I've got a merge of my new base branch,
parent, there in the middle of my commit history. (In retrospect, this was a mistake - I should have rebased then). I want to rebase this branch to squash it into a single, clean commit that I can merge into
parent, the new base branch. Essentially, I'd like my Pull Request to depict the outcome of GitHub's "Squash and Merge" operation.
But when I do
git rebase -i parent, I get
error: could not apply 77bfbf7... WIP Resolve all conflicts manually, mark them as resolved with "git add/rm <conflicted_files>", then run "git rebase --continue". You can instead skip this commit: run "git rebase --skip". To abort and get back to the state before "git rebase", run "git rebase --abort". Could not apply 77bfbf7... WIP
I can't rebase because although my branch currently does not have merge conflicts, at commit
77bfbf7, it did. I resolved those merge conflicts at commit
b99aef1, when I merged the base branch into my branch. But the rebase applies my commits in order on top of the latest commit on
parent, which means my merge conflict resolution is in the wrong place.
Rewriting this history so that the rebase goes off cleanly is... complicated to say the least, and there's no guarantee it's even possible in every case (for example, what if the merge resolution depends on changes introduced in
Luckily, there is a way to achieve the high-level objective without having to dance through the commit history:
$ git switch parent $ git switch -c daves-feature-new $ git merge --squash daves-feature $ git branch -D daves-feature $ git branch -m daves-feature
I check out the base branch and create a new branch from it,
daves-feature-new. Then I squash-merge my old branch into that new branch, delete the old branch, and rename the new branch to
daves-feature. That sets me up with exactly the situation I wanted, a clean, one-commit history with
8c048ba as the base:
$ git diff --oneline dddcf75 (HEAD -> celery-new) Add Celery 8c048ba (parent) Add multi-status field
Shiny. Then I can force-push (subject to all the usual caveats!) with
$ git branch --set-upstream-to=origin/daves-feature $ git push -f
And my PR is nice and clean.