11

Doing push --force is always kinda risky and here is an example of how it could produce some problems like loosing revisions remotely.

Suppose, there is some person Bob that has updated remote master branch from B to C. And there is another person Mike that doesn't fetch this update yet and HEAD of his master is still B. Then Mike do push --force and suddenly roll back remote master to B again:

mike@laptop $> git push --force origin
Counting objects: 19, done.
Delta compression using up to 8 threads.
Compressing objects: 100% (12/12), done.
Writing objects: 100% (12/12), 2.27 KiB, done.
Total 12 (delta 8), reused 0 (delta 0)
remote: => Syncing... [OK]
To git@gitserver.com:path/to/project.git
   C..B  master -> master (forced update)

In other words, before Mike did that, remote master was like A---B---C and he changed it to A---B.

As you can see, C revision here is remote only - it exists on git-server and on the Bob's laptop, because he pushed it to the remote master. It means that there is no such local ref C on the Mike's laptop.

Question 1: how can Mike set remote master back to C?

Mike has tried to solve that problem with push --force again like the answer for similar question offers, but it doesn't work because there is no suck local ref:

mike@laptop $> git push --force origin C:master
error: src refspec C does not match any.
error: failed to push some refs to 'git@gitserver.com:path/to/project.git'

Question 2: how can Mike fetch C revision from the git server? And if he can't do that - why git designed like that? Is it about safety in some rare cases? What problems it exactly prevents?

Usually fetch retrieves branches and tags only, but C doesn't belong to any branch (which means that there is no any remote branch that C is parent commit or HEAD of).

PS: let's assume that Mike has no ssh access to git server, it means that there is no way to call git from the server side.

PPS: Mike doesn't want Bob to know about that accident, so answer "Make Bob to push this commit again" is not what this question is about.

Community
  • 1
  • 1
  • Why does `git fetch` not satisfy your use case, and what do you mean that the commit "doesn't belong to any branch"? –  Sep 18 '13 at 17:36
  • 4
    If we can assume the git server is Github, Mike could use the API to [create a new branch at the orphaned commit](http://developer.github.com/v3/git/refs/#create-a-reference), then fetch the no longer orphaned commit and repair. – Charlie Sep 19 '13 at 02:05
  • Note that while push-to repos are usually bare, and thus have the reflog disabled, nothing prevents you from enabling it by setting `core.logAllRefUpdates` to `true` in them, and then all "drastic" branch movements will be logged and available via the reflog (in the server repo only, I suppose -- one can't access the reflog remotely, AIUI). – kostix Sep 19 '13 at 10:04

4 Answers4

16

Anyone who has commit C locally can use git branch some-name C to give C a name, then git push origin some-name:master to (try to) append C back to master on origin.

Anyone who doesn't have C can't recover C at all (even if it's in origin's object store).

Mike cannot fix the problem except using a repo that contains commit C (possibly the shared one, possibly Bob's, possibly some other repo that was cloned when C was on master).

You can't push commits you don't have locally. The local half of git-push has to be able to walk history to figure out what objects to upload, and it can't do that if it doesn't have that history available.

You can't fetch commits that aren't reachable from refs, because literally every git security layer out there does security on a ref-by-ref basis, not an object-by-object basis, and because there's no real reason to reference loose objects remotely. Git's remoting layer is entirely built around copying refs around.

But you can fetch arbitrary commits' contents using git-archive, if the server has that feature turned on. Most don't, and even if yours does, it's not enough information to reconstruct the commit object.

(c) irc://freenode/#git, ojacobson

16

If you are using GitHub, you can follow the steps in this post to create a branch on commit C after it has been orphaned.

Community
  • 1
  • 1
westse
  • 615
  • 5
  • 8
4

These git commands solves the issue. Only works if you have the full hash.

# Fetch the remote commit and create a reference for it
git fetch origin C:refs/remotes/origin/foo-commit

# Checkout the remote branch we just created
git checkout foo-commit

# Force push foo-commit branch to master
git push -f origin master

A nice extra is that it does not touch the local master branch.

Darwin
  • 4,686
  • 2
  • 30
  • 22
3

Without access to the server, this is ... very hard, at best. (I say "hard at best" as it might be possible to write a program that uploads no pack, then sends a rev update that mentions C by number. I don't know if this would work at all; it might.)

In general, though, the server will have acquired revision C from some client (most push servers are --bare which means no work was actually performed on the server). That client has the missing commit and it's very easy to recover the commit there (find in reflog, add a new branch or tag, push).

If you have ssh access to the server, log in and use git fsck to find the dropped commit (or if you have the abbreviated SHA-1 just use that), then use git branch or git tag to create a reference to it.

Edit, per question edit: it's actually possible that Mike does have commit C. He can try, e.g., git rev-parse C to see if he has it. But the git push --force origin C:master failure already tells us that he does not have it. Why can't he get it? Because when he connects to the server, he says (in effect) "here are my branch heads, what are yours?" and the server says (in effect) "oh, mine are these, would you like some of these?". That's all that's in the protocol—the client can't go ask for specific commits by ID, at least, not through these interfaces. Some web server access methods do allow you to send over raw SHA-1 values and ask for the corresponding object(s); you'd have to find out if the server is running any such service.

As for "Mike doesn't want Bob to know about that accident" ... well, sometimes you just have to confess. :-)

torek
  • 448,244
  • 59
  • 642
  • 775
  • 1
    Your intuition about that program that might work is correct! I wrote [a script](https://github.com/cryslith/git-recover-commit) which does exactly what you described (upload no pack, update ref) and it works fine :) – Lily Chung Nov 07 '19 at 07:05