Why does git-rebase give me merge conflicts when all I'm doing is squashing commits?
Solution 1:
If you don't mind creating a new branch, this is how I dealt with the problem:
Being on main:
# create a new branch
git checkout -b new_clean_branch
# apply all changes
git merge original_messy_branch
# forget the commits but have the changes staged for commit
git reset --soft main
git commit -m "Squashed changes from original_messy_branch"
Solution 2:
All right, I'm confident enough to throw out an answer. Maybe will have to edit it, but I believe I know what your problem is.
Your toy repo test case has a merge in it - worse, it has a merge with conflicts. And you're rebasing across the merge. Without -p
(which doesn't totally work with -i
), the merges are ignored. This means that whatever you did in your conflict resolution isn't there when the rebase tries to cherry-pick the next commit, so its patch may not apply. (I believe this is shown as a merge conflict because git cherry-pick
can apply the patch by doing a three-way merge between the original commit, the current commit, and the common ancestor.)
Unfortunately, as we noted in the comments, -i
and -p
(preserve merges) don't get along very well. I know that editing/rewording work, and that reordering doesn't. However, I believe that it works fine with squashes. This is not documented, but it worked for the test cases I describe below. If your case is way, way more complex, you may have a lot of trouble doing what you want, though it'll still be possible. (Moral of the story: clean up with rebase -i
before merging.)
So, let's suppose we have a very simple case, where we want to squash together A, B, and C:
- o - A - B - C - X - D - E - F (master)
\ /
Z -----------
Now, like I said, if there were no conflicts in X, git rebase -i -p
works as you'd expect.
If there are conflicts, things get a little trickier. It'll do fine squashing, but then when it tries to recreate the merge, the conflicts will happen again. You'll have to resolve them again, add them to the index, then use git rebase --continue
to move on. (Of course, you can resolve them again by checking out the version from the original merge commit.)
If you happen to have rerere
enabled in your repo (rerere.enabled
set to true), this will be way easier - git will be able to reuse the recorded resolution from when you originally had the conflicts, and all you have to do is inspect it to make sure it worked right, add the files to the index, and continue. (You can even go one step farther, turning on rerere.autoupdate
, and it'll add them for you, so the merge won't even fail). I'm guessing, however, that you didn't ever enable rerere, so you're going to have to do the conflict resolution yourself.*
* Or, you could try the rerere-train.sh
script from git-contrib, which attempts to "Prime [the] rerere database from existing merge commits" - basically, it checks out all the merge commits, tries to merge them, and if the merge fails, it grabs the results and shows them to git-rerere
. This could be time-consuming, and I've never actually used it, but it might be very helpful.
Solution 3:
I was looking for a similar requirement , i.e. discarding intermeiate commits of my development branch , I've found this procedure worked for me.
on my working branch
git reset –hard mybranch-start-commit
git checkout mybranch-end-commit . // files only of the latest commit
git add -a
git commit -m”New Message intermediate commits discarded”
viola we have connected the latest commit to the start commit of the branch! No merge conflict issues! In my learning practice I have come to this conclusion at this stage , Is there a better approach for the purpose .
Solution 4:
If you want to create exactly one commit out of a long branch of commits, some of which are merge commits, the easiest way is to reset your branch to the point before the first commit while keeping all your changes, then recommitting them:
git reset $(git merge-base origin/master @)
git add .
git commit
Replace origin/master
with the name of the branch from which you branched off.
The add .
is necessary because files that were newly added appear as untracked after the reset.