gitmerge-conflict-resolution

Automatically resolve conflicts during rebase when files are deleted


I did a massive search and replace in a large codebase of multiple strings, and created many new commits. Between the time when I started, and when I tried to complete the PR, some of the files had been deleted on the target branch (let's call that target branch main). Normally I might do something like this:

git fetch
git rebase origin/main my-feature-branch
# I now have conflicts on all deleted files
git rm <list-of-deleted-files-here>
git rebase --continue

But in this case, I have to repeat that process for many commits, and it's time consuming. As much as I like rebasing my feature branch, I could just throw in the towel and merge instead:

git fetch
git switch my-feature-branch
git merge origin/main
# I now have conflicts on all deleted files
git rm <list-of-deleted-files-here>
git merge --continue

With the merge I only have to do that extra git rm step one time instead of N times for each commit if I choose rebase. This is an acceptable solution, however, I am stubborn and I really want to avoid adding merge commits to my feature branch, whenever possible. (And I'm convinced it's possible to automate the rebase.) Essentially I am looking for something like git rebase origin/main -X ours, except which will also work when one side is deleted. (Note the automatic conflict resolution of -X ours/theirs only works when both sides change the file; it doesn't work when one side deletes the file.)

Side Note: I feel like an option similar to -X ours --include-deleted might be nice.


Solution

  • Here's what I ended up doing, which achieves my goal of automating the rebase in O(1) time. (Meaning a set of operations I need to do just once, instead of repeating over many commits.) Note I'm using bash for my commands below.

    1. Merge in the target branch. Note I won't keep this merge, but I'm using this to get the list of files that were deleted:
    git fetch
    git switch my-feature-branch
    git merge origin/main
    

    At this point I have a bunch of files as conflicts.

    1. Use git status to see the list of files that were deleted. (Or if the only conflicts are deleted files, git diff is slightly easier to parse.) Save this list to a file:
    git diff --name-only --diff-filter=U > deleted-files.txt
    
    1. Abort the merge. (git merge --abort)
    2. Check out a new branch from the parent commit of my branch:
    git switch -c rewrite-deleted-files $(git merge-base @ origin/main)
    
    1. Rewrite each of the deleted files with some random text:
    cat deleted-files.txt | while read file; do echo deleted > $file; done
    
    1. Commit the change to those re-written files:
    git commit -am "wip: rewrite deleted files"
    

    Now I have a branch with the same merge-base as my feature branch, with exactly one commit that replaces the entire contents of the deleted files with the word "deleted".

    1. Here's the cool part. Now rebase my feature branch onto the deleted files commit, using the -X ours strategy to only keep the change from the temporary commit:
    git rebase origin/main my-feature-branch --onto rewrite-deleted-files -X ours
    # It's super satisfying as the rebase goes through every commit without stopping
    
    1. Now rebase all of my good commits onto main without the temporary commit:
    git rebase rewrite-deleted-files my-feature-branch --onto origin/main
    # Once again, this is super satisfying...
    
    1. Delete the temporary file with the list of files in it:
    rm deleted-files.txt
    

    Note: I based this answer on another similar answer I wrote last year, which was for automating rewriting of changes to specific files.