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.
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.
git fetch
git switch my-feature-branch
git merge origin/main
At this point I have a bunch of files as conflicts.
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
git merge --abort
)git switch -c rewrite-deleted-files $(git merge-base @ origin/main)
cat deleted-files.txt | while read file; do echo deleted > $file; done
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".
-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
main
without the temporary commit:git rebase rewrite-deleted-files my-feature-branch --onto origin/main
# Once again, this is super satisfying...
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.