Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

List squashed commits

After I do the interactive rebase: git rebase -i HEAD~20 I get a new commit, e.g. ea1234ea

I know that the history is in the reflog, but how can I get a list of commits squashed in this commit including their identifiers (sha)?

git show ea1234ea would show a commit message which lists the messages of the squashed, but without the identifiers.

like image 392
takeshin Avatar asked Oct 22 '22 00:10

takeshin


1 Answers

Start with git reflog. The output will look something like this (but with more rebase -i entries):

aa4e140 HEAD@{0}: rebase -i (finish): returning to refs/heads/branch
aa4e140 HEAD@{1}: rebase -i (squash): c1-c3, squashed
3a422a7 HEAD@{2}: rebase -i (squash): # This is a combination of 2 commits.
f7cac12 HEAD@{3}: rebase -i (start): checkout HEAD~3
283263c HEAD@{4}: commit: blah yadda etc, but not a rebase

That last non-rebase line has the SHA1 of the commit that was the HEAD before you did the rebase -i. You might want to stick a temporary branch or tag label on it at this point, although it's not technically required. Here I'll put a lightweight tag named temp on it:

git tag temp 283263c

Now you can simply run git log temp, or (to limit it to just the ones you rebased):

git log temp --not HEAD

or:

git log temp ^HEAD

(these are two ways to spell the same thing)1, or:

git log -n 20 temp

(this uses the fact that there are 20 commits in the rebased HEAD~20..HEAD, which is only true if the original history was linear, and of course depends on that ~20 part).

When you are done with the temporary label, delete it:

git tag -d temp

The way this works is that the rebase -i does not actually remove any commits, it only adds new commits. (This is in fact true of almost every git command. The exception is the garbage-collection process, which deletes unreferenced objects.)

The complete set of rebase "commit-and-branch" arguments (i.e., ignoring important stuff like -i and -p flags) is: start-point, destination, and branch-name. The documentation cleverly disguises :-) the first two of these as "upstream" and "onto" (seriously, "onto" is not a bad name and I will stick with it below, but "upstream" is misleading in some cases). All the commits after start-point, up to the tip of branch, are copied (or omitted or squashed or whatever) by, essentially, cherry-picking them, one at a time, as a new commit added on to a branch grown at the destination. If the original commit tree looks (in part) like this:

old -- start-point -- c2 -- c3 -- c4   <-- branch
    \
      onto -- c6 -- c7                 <-- another-branch

then the rebase starts by copying (or "replaying the changes from") c2 (the first commit after start-point) to an identical change (but with different commit info), c2', placing it so that onto is its parent:

old -- start-point -- c2 -- c3 -- c4   <-- branch
    \
      onto -- c6 -- c7                 <-- another-branch
        \
          c2'

Then it copies c3 to a new (c3') version, with c3''s parent being c2', and so on. When it is all done it peels the label (branch) off of c4, and pastes it on pointing to the last new commit (c4') instead:

old -- start-point -- c2 -- c3 -- c4   <-- [no label]
    \
      onto -- c6 -- c7                 <-- another-branch
        \
          c2' -- c3' -- c4'            <-- branch

Note that the old commits (start-point, c2, c3, and c4) are all still there, they just no longer have a branch label. Note also that in this particular case (using the --onto argument as shown, with this particular commit-tree), the commit named start-point itself becomes "invisible" (in the same sense as c2 through c4) as it no longer has any branch or tag label pointing to it—unless, of course, you set one before or after you do the rebase. (Normally there are no such "skipped" commits. Of course in an interactive rebase, you can make it skip some, but then it's clear enough that you meant to do that.)

The unlabeled ("invisible" or "hidden") commits stick around as long as they remain in the reflog (90 days, unless you change the default setting). To make them stick around even longer, set a label—such as a branch or tag name—to point to them. That's what I do with the temp tag above. Now they're visible again, and easy to see in any commit-tree-viewer, such as gitk or git log.

(If you ask an interactive rebase to "squash" several commits, it simply folds the changes together into one single commit. Since each new commit is a copy—a "replay", as it were—it's easy to just keep stacking more changes together, committing them all as one squashed-commit. If you omit a commit, it just skips over it, copying only the remaining ones. If you re-order commits, it just copies them in the new order.)


1There are lots more ways to spell it. In fact, git log HEAD..temp does the trick, even though it looks wrong, somehow. :-) These versions all assume that HEAD is still a name for the branch you did the rebase on. If you were on branch squiggle you could use git log temp ^squiggle, once HEAD has moved elsewhere, for instance.

like image 104
torek Avatar answered Nov 02 '22 10:11

torek