Search code examples
gitgit-fetchgit-reflog

git fetching 'lost' commits


Short version: Is it possible to use 'git fetch' to get commits from a remote repo that are not visible in the git log (part of/under the HEAD commit)

Longer version: I have a repo (remote copy) that looks like this:

A - B - C - D(HEAD)
         \
          \-E

The remote version got to this state by making commit E, resetting to commit C, then making commit D.

Locally, I have an old clone of that repo that looks like this:

A - B - C(HEAD)

When I fetch from the remote repo, I only get commit D, but I would like to be able to reset --hard to commit E in my local repo.


Solution

  • Some background first so that it's clearer what I mean below: The fetch command works in two parts, one on your end (you run git fetch) and one on the remote (the remote gets the incoming request over http://, git://, ssh://, or some other protocol and fires up something, usually the internal git upload-pack program). The upload-pack step, which runs on the remote, makes a series of offers of the form "I have an object" (typically a commit or annotated tag) "with ID id of type type named name".

    You can see what the remote will offer by running git ls-remote instead of git fetch. This still runs upload-pack but instead of retrieving what is offered, it just shows (lists) the offers.

    Whatever you see here in the offer list is what you can get. If it does not show up here, you cannot get it—or at least, not through git fetch. Some other methods, including git archive, may allow you to access commits by their raw SHA-1s, if you know them.

    Making it fetch-able

    Given your description, it sounds like the remote has reflogs turned on (is a regular, non-bare repository). Commit E used to be on some branch (and at HEAD) but has been reset away so that it is only accessible through the two reflogs for HEAD and the branch.

    Normally1 upload-pack offers HEAD plus all refs in refs. Reflogs are not in refs so upload-pack does not offer them. This means that if there is no current ref pointing to your commit E it will not be offered. To get it to be offered, log in to the remote and create a ref pointing to commit E, and then it will.


    1This is somewhat configurable; see git-namespaces and the three hideRefs configuration items in git-config.