5

I would like to fetch a single commit (with history, of course) of a remote repository into a local one. Some Stack Overflow responses suggest that the following should work, but it does not:

md foo
cd foo
git init
git fetch https://github.com/ld4apps/lda-serverlib.git f1e32e18a90e10c150221af55c69aeafaa42c57a

This produces the following error:

error: no such remote ref f1e32e18a90e10c150221af55c69aeafaa42c57a

Surprisingly (to me) the following does (sort of) work:

git fetch https://github.com/ld4apps/lda-serverlib.git :f1e32e18a90e10c150221af55c69aeafaa42c57a

This creates a new local branch called f1e32e18a90e10c150221af55c69aeafaa42c57a (which I did not want), but at least the new branch has the contents I was trying to fetch. I might have expected that the following command would do the same thing, but it gets the same error as the first example:

git fetch https://github.com/ld4apps/lda-serverlib.git f1e32e18a90e10c150221af55c69aeafaa42c57a:f1e32e18a90e10c150221af55c69aeafaa42c57a

I tried this on Windows and Linux with a couple of different git versions and saw the same behavior.

Can anyone explain what is going on here? What am I not understanding about Git, fetch, and refspecs? What I really want is a single git fetch command to which I can provide either the id of a remote branch or tag or a commit id and it will fetch the corresponding commit.

Ry-
  • 218,210
  • 55
  • 464
  • 476
Martin Nally
  • 105
  • 1
  • 6
  • What does “a single commit with history” mean? The other commits are the history… – Ry- Sep 12 '14 at 19:18
  • I just mean the usual git behavior, nothing special. I want to fetch a particular commit, and I understand that git will also fetch the commits that constitute the history for the commit I specify. – Martin Nally Sep 12 '14 at 19:34
  • Well, it won’t if you use `git fetch --depth=1` (and that does happen to work, since f1e32e18a90e10c150221af55c69aeafaa42c57a is the current tip for your example, but that’s not the question, is it?). – Ry- Sep 12 '14 at 19:36
  • I wasn't aware of the depth option - thanks for the info. As you say, although interesting, that doesn't seem pertinent to my question – Martin Nally Sep 12 '14 at 19:40
  • Isn't this Q a duplicate of http://stackoverflow.com/questions/14872486/pull-a-specific-commit-from-a-remote-git-repository ? – vorburger Jul 16 '15 at 13:05
  • Should the github tag be removed? It makes the page title confusing. – tuxayo Feb 16 '18 at 15:10

1 Answers1

7

Update: since Git 2.5 (mid-2015 or so), servers now may expose raw hashes. This is a server-side configuration item. You must have sufficient control on the server to set it, and you should consider potential security issues before you do set it. See this answer to Retrieve specific commit from a remote Git repository for details. (Original answer, which applies to pre-2.5 or if the configuration knob is not set, below.)


The git fetch command delivers references (names, not raw commit-IDs) to the remote, more or less. (More specifically, use git ls-remote remotename to see what the remote is willing to give you in terms of names. This produces a list of SHA-1s on the left with names on the right, and the only thing your fetch can ask for is the names-on-the-right. At which point you'll get the ID-on-the-left if the name on the remote still points to that ID, so it depends on how actively that remote gets updated.)

It is possible, in various ways, to deliver raw commit-IDs to a remote and ask that remote what is visible starting from that point, and sometimes working backwards through history as well, but not via git fetch. (You can use git archive but the remote can decide whether or not to allow you to access via raw commit-IDs; or with remotes that have web server access, including to specific commits, you can often just view the top-level contents of a commit, and use that to "drill down", as they say, to the various pieces. But that is a very slow way to do it.)

If you'd like to use git fetch to get some particular commit, probably the easiest way to do that is to have someone with access to the remote attach a name—most likely a tag—to that commit ID. Then you can have your git fetch bring over that refspec, and put it under any other refspec you like. For instance, suppose you can ssh directly to whatever hosts origin:

$ ssh our.origin.host 'cd /repos/repo.git; git tag temporary f1e32e1'
[enter password, etc; observe tag created]
$ git fetch origin refs/tags/temporary:refs/heads/newbranch
[observe fetch happen; now you have local branch 'newbranch']
$ ssh our.origin.host 'cd /repos/repo.git; git tag -d temporary'

Note that the name need not be a branch, it need only be a reference you can pull over with git fetch and see with git ls-remote. You then use a name that will match that on the left-hand-side of your refspec when fetching. The name created in your repo is controlled by the right-hand-side of the refspec (refs/heads/newbranch in the example above).

This is also the answer to your last paragraph question: you can only name things that have names on the remote (this is partly intended to avoid "leaking" unnamed commits that remain in a repository before garbage-collection, so it's considered a feature rather than a bug). These names go on the LHS of the refspec. Your own names go on the right.

Your name on the right is assumed to be a branch or tag name (based on what the name on the left matches, though you can explicitly spell out refs/heads/ or refs/tags/ to override it), so even though f1e32e1... is a valid SHA-1, it's treated as a branch name here—the missing name on the left translates to HEAD, as missing names almost always do—and git fetch creates a branch whose name is disturbingly SHA-1-ish. (Incidentally I once created a branch name that looked like an SHA-1, and later confused myself. I forget exactly what the name was, something like de-bead without the hyphen. I renamed it to the hyphenated version just to make it clear I didn't mean a raw commit ID! :-) )

Community
  • 1
  • 1
torek
  • 448,244
  • 59
  • 642
  • 775
  • Thanks, Torek, that is helpful and seems to explain most of what I saw. I don't see that it explains why/how git fetch https://github.com/ld4apps/lda-serverlib.git :f1e32e18a90e10c150221af55c69aeafaa42c57a worked. What is going on there? – Martin Nally Sep 12 '14 at 19:45
  • Added a last paragraph with "intent". Will see if I can clarify the branch-creating... – torek Sep 12 '14 at 19:46
  • Perhaps I'm being dull here. I understand that the RHS controls my own names, so I'm not surprised that 'git fetch repo :f1e32e18a90e10c150221af55c69aeafaa42c57a' created a local branch named f1e32e18a90e10c150221af55c69aeafaa42c57a. What does surprise me is that it also fetched the commit of the same id on the remote, even though that commit was not addressable when I put it on the LHS. – Martin Nally Sep 12 '14 at 19:54
  • Ah, it was the empty LHS that confused you. See re-edited last paragraph. – torek Sep 12 '14 at 19:55
  • Note: [this appears to be possible now, if the server is configured to allow it](http://stackoverflow.com/a/30701724/135889). – Ruud Aug 22 '16 at 20:25