1

Given the following situation:

  • A local git branch has been force-pushed to the server, or the server's previous version of that branch was deleted or otherwise lost.
  • There was one commit present on the server that had not been fetched locally.
  • The SHA1 of that "lost" commit is known, and it has not yet been GC'd on the server, but no server-side refs currently point to it or any descendants of it.

(How this situation was reached is irrelevant.)

Is there anything that can be done purely client-side to "recover" this commit? eg. remotely create a remote branch that points at it, or fetch it as a local unreachable object that can then have a local branch pointed at it?

Do the answers change if it's possible to set some server-side config options in advance of this event happening? (I know Git normally tries to not let you fetch unreachable commits, so part of the question is whether there's a way to disable that.)

(FWIW, I've already recovered from this by directly creating a local branch on the server, which then allows it to be fetched locally as a remote branch and recovered as desired. But I'm curious how this situation could be solved if server access hadn't been available. Since GC might destroy unreachable commits at any time, this is time-sensitive and "wait until you can ask server admins to recover it for you" is not a valid solution.)

On a peripherally related note, why is the "safe" option --force-with-lease longer to type than the unsafe option --force (or even -f)?


Some old answers on this site and elsewhere suggest that it's possible to use one of these:

git fetch origin SHA1
git fetch origin SHA1:refs/heads/temp
git fetch origin SHA1:refs/remotes/origin/temp

However at least with Git 2.5.3 none of these appear to work with an unreachable SHA1 -- they just silently do nothing.

Miral
  • 12,637
  • 4
  • 53
  • 93
  • 1
    I think this can be achieved by creating a branch with the commit hash you have: `git branch recover-branch `. – joker Sep 01 '16 at 10:02
  • If this commit is still in the client side, make a ref point to it. If the local repo is deleted, make another clone which will still have that commit, so just make a ref point to it. If the commit sha1 is unknown, after the clone run `git fsck` to list all the dangling objects, in which the commit is expected to be found. – ElpieKay Sep 01 '16 at 13:29
  • Both of these assume that the commit exists locally, which is not the case, as stated in the question. – Miral Sep 01 '16 at 23:02
  • Checked with git version 2.30.2.windows.1, `git fetch origin SHA1:refs/heads/temp` works perfectly! Note it needs a **full** commit id. – cateyes Aug 06 '21 at 12:21

2 Answers2

1

[Q1] Is there anything that can be done purely client-side to "recover" this commit [by ID]?

No, unless ... well, this gets a bit complicated, so let's just jump to Q2.

[Q2] Do the answers change if it's possible to set some server-side config options in advance of this event happening?

Yes, but this may not help.

The configurable item is uploadpack.allowReachableSHA1InWant which is described this way in the git config documentation:

        Allow upload-pack to accept a fetch request that asks for an object that is reachable from any ref tip. However, note that calculating object reachability is computationally expensive. Defaults to false.

The bold-italic here is mine, and is important. While this configuration knob allows you to request specific objects by hash ID, the server must still find that they are reachable, i.e., are on some reference.

If such an object is reachable at all, and you have a full clone, you will already have the object. This particular option is thus useful only in some special cases:

  • if the reference that makes the server's object reachable is hidden (using uploadpack.hideRefs), or
  • if the client has only a partial clone (such as a shallow clone)

(it's possible there are other cases I have not considered here; those are the two obvious ones).

[Q3] On a peripherally related note, why is the "safe" option --force-with-lease longer to type than the unsafe option --force (or even -f)?

This requires a bit of speculation, unless we find whoever actually wrote the code, but it seems likely that the answer is that --force-with-lease came (much) later than --force, appearing first in Git version 1.8.5 in late 2013.1 Even today the lease options are still described as "experimental" (all except the --force-with-lease=<refname>:<expect> form).


1One might think that since this was three years ago now, surely nobody is running any versions of Git so ancient, but some are still using Git 1.7.

Community
  • 1
  • 1
torek
  • 448,244
  • 59
  • 642
  • 775
  • [Q2] I read about `allowReachableSHA1InWant`, however the scenario here is that the remote no longer has any refs pointing at the commit (other than the reflog, perhaps, which I suspect does not count), so this does not seem like a solution. – Miral Sep 01 '16 at 23:01
  • [Q3] I expected that. I suppose a better way of phrasing the question is why a single-character or otherwise shorter form of `--fetch-with-lease` has not been added since it was proven to be safer. – Miral Sep 01 '16 at 23:01
  • @Miral: right, that's why I bold-italic-ized the phrase: it doesn't help for this case, only for other cases. – torek Sep 01 '16 at 23:15
0

However at least with Git 2.5.3 none of these appear to work with an unreachable SHA1

This is usually the case since by default Git is configured with uploadpack.allowReachableSHA1InWant to false.

A recent commit for Git 2.12 (Q1 2017) add more on that topic:

See commit 235ec24 (14 Nov 2016) by Matt McCutchen (mattmccutchen).
(Merged by Junio C Hamano -- gitster -- in commit 5f52e70, 10 Jan 2017)

This commit introduces a new SECURITY section which clearly states:

The fetch and push protocols are not designed to prevent one side from stealing data from the other repository that was not intended to be shared.
If you have private data that you need to protect from a malicious peer, your best option is to store it in another repository.
This applies to both clients and servers.

Typically:

The victim sends "have" lines advertising the IDs of objects it has that are not explicitly intended to be shared but can be used to optimize the transfer if the peer also has them.

Or:

the attacker chooses an object ID X to steal. The victim sends an object Y that the attacker already has, and the attacker falsely claims to have X and not Y, so the victim sends Y as a delta against X. The delta reveals regions of X that are similar to Y to the attacker.

In that last case, it should still be possible to get back your lost commit...

VonC
  • 1,262,500
  • 529
  • 4,410
  • 5,250