0

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?

Max Heiber
  • 14,346
  • 12
  • 59
  • 97

3 Answers3

3

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.

Community
  • 1
  • 1
torek
  • 448,244
  • 59
  • 642
  • 775
2

I'm not sure whether this will work on your remote repository, but it should at least work on your local repo.

To see what has been done on the dev branch:

git reflog show dev

This should show you the changes to that branch during the last 30 days.

Gerhard Poul
  • 124
  • 1
  • 5
  • I tested out rewriting local history with `git pull -r` and seeing what the reflog says. In this case, I guest the best tip-off I have that a commit got "lost" is that the most recent SHA is the same as an earlier SHA. That's good, but not the sort of thing I would notice right away. Pasting a minimal reflog that shows this, sorry for fmt: `b21e39b HEAD@{1}: rebase finished: returning to refs/heads/master// b21e39b HEAD@{2}: pull -r origin dev: checkout b21e39b921adaeb18712b5b00c62d58d20812ce1 // b94d674 HEAD@{3}: commit: second commit // b21e39b HEAD@{4}: commit: first commit` – Max Heiber Aug 06 '16 at 16:08
  • Yes, you'll not notice this right away. You might want to take the opportunity to [disable force-pushes](http://stackoverflow.com/questions/1754491/is-there-a-way-to-configure-git-repository-to-reject-git-push-force) to branches that are important to you. – Gerhard Poul Aug 06 '16 at 16:18
1

Considering the local reflog might be lost, in case the local repo was deleted right after the force push, I tried to find the history of the branch that was updated by force in the remote repo.

Make a new clone of the remote repo and run git gc so that all the reachable commits and related objects are packed up while the unreachable are left as loose objects, which could be seen in .git/objects. However I just cannot find any git command to list these unreachable objects. git rev-list seems unable to list unreachable objects. So I tried a dumm method to list them all, joining the first 2 byte folder name and the left 38 byte file name into a complete sha1 one by one. And then use git cat-file -t <object> one by one to find all the commit objects. And then run git show <commit-object> to see them all. I think among them you could find the commit the branch was pointing to before the force update.

When the previous tip commit is found, the lost history can be taken back.

ElpieKay
  • 27,194
  • 6
  • 32
  • 53
  • Won't `git gc` wipe out the unreachable commits? What does "packed up" mean? – Max Heiber Aug 07 '16 at 22:10
  • 1
    @mheiber In this case they won't be wiped out unless we add the option like `--prune=now`, because they are not expired yet. In order to save space and be more efficient, Git removes the loose objects and compose them into `.pack` files, with `.idx` files for mapping. – ElpieKay Aug 08 '16 at 00:19