gitrebasesquash

Git rebase conflicts optimisation


Lets say I have created a branch F1 from main. This F1 branch has 20 commits in it. I now want to rebase this branch onto main to pick up changes from main

git checkout F1
git rebase -i main

However, I know that almost all the commits on F1 modify a file that has also been modified on main. So in almost all of the 20 commits I am going to have to resolve conflicts.

It seems like a waste of time fixing a conflict in commit 1/20, just for it to then also conflict in 2/20 and 3/20 and so on.

Should I instead rebase F1 from its first commit (1/20) and squash all of the 20 commits into 1? and THEN do the rebase onto main?

i.e something like :

git checkout F1
git rebase -i HEAD~19 (add -squash to the last 19 commits)
git push -f

then

git checkout F1
git rebase -i main

Solution

  • We have some excellent comments on the question, which I will credit and summarize here into an aggregate answer.

    Should I instead ... squash all of the 20 commits into 1 ... and THEN do the rebase onto main?

    As NoDataFound mentioned in a comment:

    I would say it depends on the importance of these commits in the history!

    If you are willing to squash...

    As Axnyff mentioned:

    I usually do a squash then a rebase but you shouldn't do it if you have meaningful commits.

    If you were planning on eventually squashing your 20 commits into fewer "good" commits anyway, then yes, absolutely do that squash first and then the rebase. If you probably weren't going to squash them, but it doesn't bother you that much if you do, then again, you might as well squash first. Note that if you're using a Pull Request tool that tracks the history of a branch in the PR, then consider creating the PR before rebasing, and then force push your branch after the rebase so that the 20 commits can still be found in the history of the PR. This is one way to preserve history in the PR tool, even though you won't have the history in the repo itself. I do this sometimes when I suspect someday in the future I may wish to see more granularity of the individual commits, perhaps for determining the reasoning behind why a certain change was made that may be harder to determine in a larger squashed commit. Even without the individual commits in the repo, it's easy to go back and look at the PR to see the original list of commits which you could investigate or even checkout locally again.

    If you don't want to squash...

    If your commits are important enough to keep, even though a linear history is nice, sometimes the benefit of having a linear history is outweighed by the pain to achieve it. This may be one of those cases where you decide to accept a merge commit. As Tim Biegeleisen points out:

    In fact, the easy way out of this would be to git merge instead. A merge would yield a single event with all conflicts in one go.

    That is by far the simplest solution, and even in a strict rebase workflow, perhaps an occasional merge commit from bringing in main wouldn't cause too much angst.

    If you don't want to squash and can't have a merge commit...

    If you want to keep your full history and also not have a merge commit, then you'll have to deal with the conflicts. There are some options which may help make the process more efficient. From LeGEC's comment:

    git rerere can help: git config --global rerere.enabled true. See git help rerere and the git book for more detailed explanations

    Another option is to try using the -X option for resolving conflicts. This will attempt to resolve the conflicts by favoring either "ours" or "theirs". Note: when rebasing, the meaning of "ours" and "theirs" are flipped compared to when merging. If you are merging main into F1 then you will have F1 checked out, and as expected "ours" is F1 and "theirs" is main. However, when rebasing F1 onto main, then during the rebase main is "ours" and F1 is "theirs". For example, if you wish to resolve the conflicts by keeping the change on main for all the conflicts in the 20 commits on F1, you would use:

    git rebase main F1 -Xours
    

    Note when rebasing you'll also see the terms "incoming" and "current" which are similar to "theirs and "ours". More info here.