I have a script that does a git rebase
with the flags --empty=keep
and --keep-empty
. It's important that no commit is dropped because the script later relies on relative refs (like HEAD~3
) and on the order of the commits in general.
Unfortunately, I found a scenario in which a commit is still lost in the rebase.
If during a rebase you have a conflict of a file being modified in the target branch and deleted in the rebased branch, and you solve it by keeping the file, the commit that removes it is dropped from the rebase despite the option to keep empty commits. Here's a script that reproduces that scenario:
git init
git config --local user.name foo
git config --local user.email foo@bar.baz
echo foo >aaa
git add aaa
git commit -m 'Added aaa'
git branch -m target_branch
echo bar >aaa
git add aaa
git commit -m 'Changed aaa'
git switch -c rebased_branch HEAD~1
git rm aaa
git commit -m 'Removed aaa'
git log --oneline --graph --all --decorate
# * 25bd816 (HEAD -> rebased_branch) Removed aaa
# | * 7c888f8 (target_branch) Changed aaa
# |/
# * d22ceb0 Added aaa
git rebase target_branch --empty=keep --keep-empty
git add aaa
git rebase --continue
git log --oneline --graph --all --decorate
# * 7c888f8 (HEAD -> rebased_branch, target_branch) Changed aaa
# * d22ceb0 Added aaa
The first git log
shows the expected commit tree.
Then the rebase happens and the commit "Removed aaa" vanishes.
Is this intended or a bug? Is there some way to keep this commit?
I was able to keep the empty commit by adding an explicit commit:
git rebase target_branch --empty=keep --keep-empty
git add aaa
git commit --allow-empty --no-edit
git rebase --continue
git log --oneline --graph --all --decorate
* 866c7222d (HEAD -> rebased_branch) Removed aaa
* dbd67680a (target_branch) Changed aaa
* 17a1f8763 Added aaa
Note that this solution can be used systematically: it will commit the result of conflict resolution whether that's empty or not, and git rebase --continue
will see that it's been dealt with and continue with the next commit.
Note 2: I incorporated Piotr Siupa's suggestion to use --no-edit
on that commit command, instead of manually editing the message. That will be more convenient in a script, and will automatically reuse the message from the commit that had the conflict in the first place.
If you're counting on relative refs being stable, you might also want to specify --reapply-cherry-picks
, otherwise a commit with the same change set (i.e., a commit that looks like it's been cherry picked before) will still be skipped.
I wonder if it's a bug that git rebase --continue
forgets your --empty=keep
setting?
Or is becoming empty through conflict resolution different from becoming empty because of previously applied commits? Your case is not explicitly discussed in the documentation.
Unfortunately git rebase --continue --empty=keep --keep-empty
yields an immediate CLI error so that's not a solution.
Based on the comments below, it's probably not a bug: there are many ways you can deal with conflicts, and that can take the form of zero, one, or sometimes more than one commit, and when it looks like you've dealt with things, rebase just moves to the next commit in the todo list. -- Thanks to Piotr Siupa and jthill for feedback on this.