Search code examples
gitgit-rebasegit-rewrite-history

Interactive rebasing git


I'm doing git rebase -i --root and conflicts are happening sometimes. I want to understand how it works and why conflicts happen.

So as I understand when the interactive rebase is in progress it applies commits from the top of the commit list to the bottom, so I name it something like source onto target where source is current commit to be applied and target represents already applied commits. Is it correct understanding? If so, then if there is a conflict with the message next to a file 'deleted by them' does it mean that the file has been deleted in the source and edited in the target? So, probably it has been edited in the last applied commit, but deleted in the current commit to be applied.

One more question, is it possible to see which commits have been already applied? I mean commits in the target.

I'm sorry if my understanding of the interactive rebase is wrong and all my details above don't make any sense, the feature just seems to be confusing to me, so I'm trying to get the basic idea.


Solution

  • ... as I understand when the interactive rebase is in progress it applies commits from the top of the commit list to the bottom

    Yes: it starts by doing a detached HEAD checkout of the initial (--onto) commit.1 This serves as the point where the new commit chain will be built.

    Then, for each pick command, it runs git cherry-pick. This is where merge conflicts can occur, because cherry-pick uses Git's merge engine.2 The merge base of this merge is the parent of the current (detached HEAD) commit. The --ours side of the merge is the current commit. The --theirs side of the merge is the commit being cherry-picked.

    (When the last pick or other command is done and all has gone well, the interactive rebase moves the branch name to point to the detached HEAD, and attaches HEAD to that branch name.)

    ... if there is a conflict with the message next to a file 'deleted by them' does it mean that the file has been deleted in the source and edited in the target? So, probably it has been edited in the last applied commit, but deleted in the current commit to be applied.

    Yes. One way to think of this during a more typical rebase is that --theirs (or index slot 3) is your commit. The --ours commit is also usually your new copy of one of your old commits, except for the first commit being copied where --ours is their commit from the --onto target.

    One more question, is it possible to see which commits have been already applied? I mean commits in the target.

    One relatively stable and quick way is to run git rebase --edit-todo, which shows you the remaining to-do list. That's not quite the same as the "done" list, but if you remember the original to-do list, you can recover it mentally by subtracting.

    Other options include using the current (detached) HEAD with git log:

    git log [--oneline] onto-target..HEAD
    

    which shows the commits that have been copied so far, or in some cases, peeking directly into the .git/rebase-* directory in which everything is stored (but this last location has moved around over time).


    1When using --root, interactive rebase uses a special case instead of cherry-pick for the first commit (which must be a "pick" instruction). It must, because git cherry-pick cannot be used without a base commit. So it just grabs the tree from the first commit-to-be-picked and makes a new root commit from that tree. This step must always succeed: there's no chance of a conflict here. Finishing this step means removing that first commit from the list of commits to be picked, after which the rebase continues normally.

    2Cherry-pick directly invokes the merge strategy, e.g., git merge-recursive, rather than running git merge. There are a bunch of technical reasons for this, but the main two are that this way, cherry-pick itself chooses the merge base, and this way, cherry-pick itself makes the final commit once the merge is complete, as a regular non-merge commit.