Search code examples
gitversion-controlpushrebasegit-reflog

How can I see how a Git branch changed over time (including history rewrites)?


Is there a way to see, for a given repo and branch, how that branch changed over time, including history rewrites? For example:

April 1st: Commit A -> B -> C -> D

April 2nd, Max Heiber—git push -f: Commit A -> B -> C'

April 3rd, Somebody else—git merge feature Commit A -> B -> C' -> D'

Here's why I'm asking:

We were merging features into our dev branch, but the changes would later disappear from dev. We found out that the cause was that one of our developers was doing git push -f and had this in his .gitconfig:

[push] default = matching

This had the effect of force-pushing all his branches, including stale versions of dev.

It took a while to figure out that this was happening. What we really wanted, while troubleshooting the issue was to see how and why our history was changing. Is it possible to get this kind of a view of a branch?


Solution

  • Git doesn't really save this information. If you have reflogs, it does save something, for the reflog expiration time. These times are 30 days and 90 days by default, for unreachable and reachable commits respectively.1 Hence Gerhard Poul's answer will work on your local dev, and because reflogs are normally enabled even for remote-tracking branches, you can also use git reflog show origin/dev to look at what your Git has recorded during its git fetch / git pull operations.

    Expiration is normally run from git gc, so if git gc has not run for a while, you can have quite a few extra days of information.

    If reflogs are enabled on your server—by default they are not enabled—you can log in to your server and run git reflog show dev there.

    In all cases, you may want to add --date=<format> (e.g., --date=iso) to get the {@n} replaced with @{date}:

    $ git reflog --date=iso master
    11ae6ca master@{2016-06-17 13:32:00 -0700}: reset: moving to HEAD^
    3d9eb53 master@{2016-06-17 13:31:44 -0700}: commit: Revert "fdmillion: repair example"
    11ae6ca master@{2016-04-22 05:27:07 -0700}: commit (amend): add run-checks script
    becf391 master@{2016-04-22 05:24:48 -0700}: commit: add run-checks script
    

    This will get you the time stamps for each reference change, which will be useful for correlating with "who did what when".


    1This is technically nonsense. :-) Commits—well, all Git objects, really—are either reachable or unreachable, but reflog entries make them reachable, so this particular bit of shorthand can be puzzling. The actual definition is reachable from the current value of the corresponding reference. That is, when git reflog expire is expiring a reference, it looks at this:

    • this is a reflog entry for refs/heads/foo
    • what commit does branch foo name? (call this H for head)
    • what commit does this reflog entry name? (call this E for entry)
    • is E an ancestor of H? (see git merge-base --is-ancestor)
    • if yes, use gc.reflogExpire or gc.<pattern>.reflogExpire
    • if no, use gc.reflogExpireUnreachable or gc.<pattern>.reflogExpireUnreachable

    The two non-pattern names default to 90.days.ago and 30.days.ago respectively (the pattern values are not set by default). There's a special case for refs/stash which is set to never.