gitversion-control

git rebase -i doesn't show the sha of the commit I asked to rebase


I did git rebase --interactive b222222, but it didn't show the commit with the sha b222222. Why is that?

A shortened version of my git log reads like this:

commit c333333
commit b222222
commit a111111

I tried to do git rebase -i b222222 and I saw this:

pick c333333

# Rebase b222222..c333333 onto b222222 (1 command)

b222222 is the one I want to edit. Why do I only see c333333 listed, not b222222?

Note on potential duplicate[1]


[1]: I'm sure there's a question out there like this one, I just couldn't find it. These are just a few questions I found that didn't help me:

The most useful hint I found was in a low-point unaccepted answer about changing an old commit message, and it wasn't as useful as it could be.

Since it was so hard to find, even if there is a duplicate question and answer out there, this post might still be useful. There have been a few conversations in stackoveflow meta about deliberately posting duplicates in order to help guide people to useful answers. Feel free to give a link and mark it as a duplicate, of course.


Solution

  • tl;dr: When rebasing, you specify what you don't want to rebase. In other words, you specify the new "base" (commit or branch), which represents everything you don't want to change.

    This means the title has a false premise:

    git rebase -i doesn't show the sha of the commit I asked to rebase

    The commit you listed is not what you "asked to rebase", but is instead what you asked your new base to become, which is what you do not wish to rebase. (Or perhaps a slightly better word choice would be "replay" or "reapply".)

    At first this seems counter intuitive, but when you rebase onto branches it makes more sense. For example, suppose you have my-feature currently checked out and you want to update your branch with the latest version of main. You could use the following commands:

    git fetch
    git rebase origin/main
    

    Note this is equivalent to the slightly less efficient, yet likely more common, set of commands:

    git switch main
    git pull
    git switch my-feature
    git rebase main
    

    Also, note that:

    git switch my-feature
    git rebase main
    

    is the same thing as the one-liner:

    git rebase main my-feature
    

    The second argument, my-feature, is only necessary if you don't have it checked out already.

    For completeness, this is actually short for the fuller syntax of rebase:

    git rebase main my-feature --onto main
    

    In words, you can read this as:

    Take all of the commits reachable by my-feature, that aren't reachable by main, and replay them one by one, in order, onto main.

    Another way to say that is:

    Take all the commits on my-feature, minus all the commits on main, and replay them one by one, in order, onto main.

    So now substituting your example:

    git rebase --interactive b222222
    

    is, in the expanded syntax equivalent to:

    git rebase -i b222222 c333333 --onto b222222
    

    In words that says,

    Take all commits reachable by c333333 (which is all three commits), minus all commits reachable by b222222 (the bottom two commits), and note this leaves just the top 1 commit c333333, and take that one commit and replay it onto b222222, and do so in interactive mode so that I can view what the rebase will do, and possibly edit it.

    This is the reason you must specify the parent of the first commit you wish to rebase- it's because you're specifying all of the commits to exclude. IMHO this is one of reasons rebase is initially confusing and oftentimes a difficult concept to learn. I hope by the time you read this sentence it will be less so. 😉