6

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 [email protected] 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?

2
  • Is it a merge commit that you are missing? Commented Jul 20 at 13:04
  • @eftshift0 It's the commit with message "Removed aaa". Commented Jul 20 at 13:26

1 Answer 1

3

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.

Sign up to request clarification or add additional context in comments.

9 Comments

After rebase has stopped due to a conflict and the user has resolved the conflict such that the commit is logically empty, the situation is indistinguishable for git rebase --continue from many other situations that may have occurred where no changes were left behind. It is undecidable whether an empty commit should be made or not. Therefore, the burden to make the empty commit is placed on the user in this situation.
I don't quite see how can I do it in a script. I don't know which commit will be skipped ahead of time. If I knew the exact rule of when a commit get skipped, I could maybe detect this situation and make the commit myself. However, if I don't nail it perfectly, I'll only make the situation worse because I will then have both missing commits and duplicated commits.
The git commit --allow-empty is safe to systematically add to your script after each conflict resolution: git rebase --continue will notice that the commit was committed and will resume after it. It will not duplicate any commits on you.
Also, you could implement error checking in your script, counting commits on the branch before and after rebase, and error out immediate if the count is not equal, giving you a chance to fix things instead of getting corrupted results.
@j6t Is it a quote from somewhere? Is it in anyway official stand on the topic or just something you wrote? To me it sound like an excuse for adding exceptions to how it all works just for the sake of making it less reliable.
@joanis "git rebase --continue will notice that the commit was committed and will resume after it. It will not duplicate any commits on you." - Maybe that's the reason it skips empty commits? I just assumes that it's empty because it was already committed? That doesn't strike me as a good way to implement that but it would explain the observed behavior.
I think that's about right, you've got how it works and I get why you want it to just do the empty commit for you, but it's debatable. git rebase --continue can't be used without having addressed whatever caused the rebase to stop. Whatever it was, the automated rebase processing you kicked off couldn't handle it. So, it stopped and asked you to handle it before --continueing. Whatever it picks is going to be the wrong thing in some circumstances, I'd say it's simplest, clearest and safest to just say if rebase stops you do all the fixing yourself, always, before continuing.
|

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.