gitgit-rebase

Git rebase removes an empty conflict resolution despite options `--empty=keep` and `--keep-empty`


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?


Solution

  • Solution

    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.

    Keeping "identical" commits

    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.

    Musings

    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.