... I understood the rebased commits should get lost
They're not lost, they're (deliberately) "abandoned" (my term).
It's true that rebase
copies (the contents of) the old commits. In fact, except for special optimizations and such, it's basically identical to doing git cherry-pick
(and the interactive rebase script uses git cherry-pick
for each "pick" operation, and amend-style commits for "squash" and "fixup" operations).
When and whether commits in a repository are visible, however, is decided by something else entirely. Normally git log
starts with the name of the branch you're on, as recorded in HEAD
(there's a file in your .git
directory called HEAD
, which contains the string ref: refs/heads/master
, and that's how git knows that you're "on branch master").1
Given a branch name, git turns that into a (single) commit by "reading the reference":
$ git rev-parse master # note: you can also rev-parse HEAD directly
676699a0e0cdfd97521f3524c763222f1c30a094
The log
command can then read the commit object by its SHA-1. That commit object has some parent SHA-1s, and git log
reads those too, and so on, until it reaches a commit that has no parents (a "root" commit).
So, given a root commit A
, and second and third commits B
and C
—plus a label, master
, that points to C
:
A <-- B <-- C <-- HEAD=master
(the arrows here show who points to whom, it goes the other way than in your drawing!), git can find (reach) commits A
through C
, starting at C
and working backwards.
The rebase copies B
and folds in C
, giving B'
as you expected:
A <-- B <-- C
^
\
B'
What makes B'
show up with git log
is that the label, master
, is "peeled off" of commit C
and "pasted onto" commit B'
. More precisely, the file for branch master
(.git/refs/heads/master
)2 gets rewritten with the new SHA-1 for B'
:
A <-- B <-- C [no label, "abandoned"]
^
\
B' <-- HEAD=master
As the answers that beat me to it noted, the "abandoned" commits (along with any other abandoned objects in the repository) are eventually removed for real by the "garbage collector", git gc
.
The claim that there's "no" label is a little overblown, though. There's at least one label, hidden away in the "reflog", that keeps commit C
from being garbage-collected. And, if you create a branch or tag label that refers to C
, either before or after the rebase
moves the master
label, that label will also keep C
in the repository, accessible by "ordinary" name, and you'll see it with git log --all
(which looks at all branch and tag names, rather than just the one in HEAD
).
1The HEAD
file can instead contain a raw SHA-1. In this case you have what git calls a "detached HEAD": you're at a commit by its SHA-1, rather than its branch-name.
2Branch and tag names (really, any reference at all) can be "packed", in which case the separate file goes away. This saves space, and you're not supposed to depend on the existence of the separate file. However, once a branch becomes "active"—being updated a lot—the separate file will re-appear since it's faster and easier to update that one file, than to update the packed-refs file.