Retrieve specific commit from a remote Git repository

401,235

Solution 1

Starting with Git version 2.5+ (Q2 2015), fetching a single commit (without cloning the full repo) is actually possible.

See commit 68ee628 by Fredrik Medley (moroten), 21 May 2015.
(Merged by Junio C Hamano -- gitster -- in commit a9d3493, 01 Jun 2015)

You now have a new config (on the server side)

uploadpack.allowReachableSHA1InWant

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.

If you combine that server-side configuration with a shallow clone (git fetch --depth=1), you can ask for a single commit (see t/t5516-fetch-push.sh:

git fetch --depth=1 ../testrepo/.git <full-length SHA1>

You can use the git cat-file command to see that the commit has been fetched:

git cat-file commit <full-length SHA1>

"git upload-pack" that serves "git fetch" can be told to serve commits that are not at the tip of any ref, as long as they are reachable from a ref, with uploadpack.allowReachableSHA1InWant configuration variable.

As noted by matt in the comments:

Note that SHA must be the full unabbreviated SHA, otherwise Git will claim it couldn't find the commit


The full documentation is:

upload-pack: optionally allow fetching reachable sha1

With uploadpack.allowReachableSHA1InWant configuration option set on the server side, "git fetch" can make a request with a "want" line that names an object that has not been advertised (likely to have been obtained out of band or from a submodule pointer).
Only objects reachable from the branch tips, i.e. the union of advertised branches and branches hidden by transfer.hideRefs, will be processed.
Note that there is an associated cost of having to walk back the history to check the reachability.

This feature can be used when obtaining the content of a certain commit, for which the sha1 is known, without the need of cloning the whole repository, especially if a shallow fetch is used.

Useful cases are e.g.

  • repositories containing large files in the history,
  • fetching only the needed data for a submodule checkout,
  • when sharing a sha1 without telling which exact branch it belongs to and in Gerrit, if you think in terms of commits instead of change numbers.
    (The Gerrit case has already been solved through allowTipSHA1InWant as every Gerrit change has a ref.)

Git 2.6 (Q3 2015) will improve that model.
See commit 2bc31d1, commit cc118a6 (28 Jul 2015) by Jeff King (peff).
(Merged by Junio C Hamano -- gitster -- in commit 824a0be, 19 Aug 2015)

refs: support negative transfer.hideRefs

If you hide a hierarchy of refs using the transfer.hideRefs config, there is no way to later override that config to "unhide" it.
This patch implements a "negative" hide which causes matches to immediately be marked as unhidden, even if another match would hide it.
We take care to apply the matches in reverse-order from how they are fed to us by the config machinery, as that lets our usual "last one wins" config precedence work (and entries in .git/config, for example, will override /etc/gitconfig).

So you can now do:

git config --system transfer.hideRefs refs/secret
git config transfer.hideRefs '!refs/secret/not-so-secret'

to hide refs/secret in all repos, except for one public bit in one specific repo.


Git 2.7 (Nov/Dec 2015) will improve again:

See commit 948bfa2, commit 00b293e (05 Nov 2015), commit 78a766a, commit 92cab49, commit 92cab49, commit 92cab49 (03 Nov 2015), commit 00b293e, commit 00b293e (05 Nov 2015), and commit 92cab49, commit 92cab49, commit 92cab49, commit 92cab49 (03 Nov 2015) by Lukas Fleischer (lfos).
Helped-by: Eric Sunshine (sunshineco).
(Merged by Jeff King -- peff -- in commit dbba85e, 20 Nov 2015)

config.txt: document the semantics of hideRefs with namespaces

Right now, there is no clear definition of how transfer.hideRefs should behave when a namespace is set.
Explain that hideRefs prefixes match stripped names in that case. This is how hideRefs patterns are currently handled in receive-pack.

hideRefs: add support for matching full refs

In addition to matching stripped refs, one can now add hideRefs patterns that the full (unstripped) ref is matched against.
To distinguish between stripped and full matches, those new patterns must be prefixed with a circumflex (^).

Hence the new documentation:

transfer.hideRefs:

If a namespace is in use, the namespace prefix is stripped from each reference before it is matched against transfer.hiderefs patterns.
For example, if refs/heads/master is specified in transfer.hideRefs and the current namespace is foo, then refs/namespaces/foo/refs/heads/master is omitted from the advertisements but refs/heads/master and refs/namespaces/bar/refs/heads/master are still advertised as so-called "have" lines.
In order to match refs before stripping, add a ^ in front of the ref name. If you combine ! and ^, ! must be specified first.


R.. mentions in the comments the config uploadpack.allowAnySHA1InWant, which allows upload-pack to accept a fetch request that asks for any object at all. (Defaults to false).

See commit f8edeaa (Nov. 2016, Git v2.11.1) by David "novalis" Turner (novalis):

upload-pack: optionally allow fetching any sha1

It seems a little silly to do a reachabilty check in the case where we trust the user to access absolutely everything in the repository.

Also, it's racy in a distributed system -- perhaps one server advertises a ref, but another has since had a force-push to that ref, and perhaps the two HTTP requests end up directed to these different servers.


With Git 2.34 (Q4 2021), "git upload-pack"(man) which runs on the other side of git fetch(man) forgot to take the ref namespaces into account when handling want-ref requests.

See commit 53a66ec, commit 3955140, commit bac01c6 (13 Aug 2021) by Kim Altintop (kim).
(Merged by Junio C Hamano -- gitster -- in commit 1ab13eb, 10 Sep 2021)

docs: clarify the interaction of transfer.hideRefs and namespaces

Signed-off-by: Kim Altintop
Reviewed-by: Jonathan Tan

Expand the section about namespaces in the documentation of transfer.hideRefs to point out the subtle differences between upload-pack and receive-pack.

3955140 ("upload-pack.c: treat want-ref relative to namespace", 2021-07-30, Git v2.34.0 -- merge listed in batch #5) taught upload-pack to reject want-refs for hidden refs, which is now mentioned.
It is clarified that at no point the name of a hidden ref is revealed, but the object id it points to may.

git config now includes in its man page:

reference before it is matched against transfer.hiderefs patterns. In order to match refs before stripping, add a ^ in front of the ref name. If you combine ! and ^, ! must be specified first.

git config now includes in its man page:

is omitted from the advertisements. If uploadpack.allowRefInWant is set, upload-pack will treat want-ref refs/heads/master in a protocol v2 fetch command as if refs/namespaces/foo/refs/heads/master did not exist. receive-pack, on the other hand, will still advertise the object id the ref is pointing to without mentioning its name (a so-called ".have" line).

Solution 2

You only clone once, so if you already have a clone of the remote repository, pulling from it won't download everything again. Just indicate what branch you want to pull, or fetch the changes and checkout the commit you want.

Fetching from a new repository is very cheap in bandwidth, as it will only download the changes you don't have. Think in terms of Git making the right thing, with minimum load.

Git stores everything in .git folder. A commit can't be fetched and stored in isolation, it needs all its ancestors. They are interrelated.


To reduce download size you can however ask git to fetch only objects related to a specific branch or commit:

git fetch origin refs/heads/branch:refs/remotes/origin/branch

This will download only commits contained in remote branch branch (and only the ones that you miss), and store it in origin/branch. You can then merge or checkout.

You can also specify only a SHA1 commit:

git fetch origin 96de5297df870:refs/remotes/origin/foo-commit

This will download only the commit of the specified SHA-1 96de5297df870 (and its ancestors that you miss), and store it as (non-existing) remote branch origin/foo-commit.

Solution 3

You can simply fetch a single commit of a remote repo with

git fetch <repo> <commit>

where,

  • <repo> can be a remote repo name (e.g. origin) or even a remote repo URL (e.g. https://git.foo.com/myrepo.git)
  • <commit> can be the SHA1 commit

for example

git fetch https://git.foo.com/myrepo.git 0a071603d87e0b89738599c160583a19a6d95545

after you fetched the commit (and the missing ancestors) you can simply checkout it with

git checkout FETCH_HEAD

Note that this will bring you in the "detached head" state.

Solution 4

I did a pull on my git repo:

git pull --rebase <repo> <branch>

Allowing git to pull in all the code for the branch and then I went to do a reset over to the commit that interested me.

git reset --hard <commit-hash>

Hope this helps.

Solution 5

You can simply fetch the remote repo with:

git fetch <repo>

where,

  • <repo> can be a remote repo name (e.g. origin) or even a remote repo URL (e.g. https://git.foo.com/myrepo.git)

for example:

git fetch https://git.foo.com/myrepo.git 

after you fetched the repos you may merge the commits that you want (since the question is about retrieve one commit, instead merge you may use cherry-pick to pick just one commit):

git merge <commit>
  • <commit> can be the SHA1 commit

for example:

git cherry-pick 0a071603d87e0b89738599c160583a19a6d95545

or

git merge 0a071603d87e0b89738599c160583a19a6d95545

if is the latest commit that you want to merge, you also may use FETCH_HEAD variable :

git cherry-pick (or merge) FETCH_HEAD
Share:
401,235

Related videos on Youtube

Varun Chitre
Author by

Varun Chitre

Updated on July 08, 2022

Comments

  • Varun Chitre
    Varun Chitre almost 2 years

    Is there any way to retrieve only one specific commit from a remote Git repo without cloning it on my PC? The structure of remote repo is absolutely same as that of mine and hence there won't be any conflicts but I have no idea how to do this and I don't want to clone that huge repository.

    I am new to git, is there any way?

    • CharlesB
      CharlesB over 11 years
      Is your existing repo already a clone of the remote one, or is it completely different?
    • Varun Chitre
      Varun Chitre over 11 years
      Well, the repo is Linux kernel source, and its pretty much same
    • CharlesB
      CharlesB over 11 years
      so is it a clone or no?
    • Varun Chitre
      Varun Chitre over 11 years
      Not exactly. Consider this, Let the remote repo be at the head D and mine is at head A and is behind by B,C,D commits. I wish to merge commit B from one repo and C from another and D from else one as the B,C,D commits in these repos are different with their own specialities
    • VonC
      VonC about 9 years
      With Git 2.5+ (Q2 2015), you will be able to fetch a single commit if you need to! (And if the Git repo hosting server authorizes it) See my answer below.
    • Ciro Santilli OurBigBook.com
      Ciro Santilli OurBigBook.com almost 9 years
    • CharlesB
      CharlesB almost 8 years
      @VarunChitre can you accept the other answer from VonC?
    • Ciro Santilli OurBigBook.com
      Ciro Santilli OurBigBook.com almost 6 years
  • Varun Chitre
    Varun Chitre over 11 years
    I wish to avoid this because I am working with Linux kernel sources, and I like to visit different repos and merge the commits I like from them which improve certain components I like and so I cannot clone every remote repo :( they are humongous in size
  • CharlesB
    CharlesB over 11 years
    Looks like you're making a confusion on what clone means. When you fetch changes from a remote repo you don't clone it, you just get the commits in your history. Then you choose which commit you want to check out, or merge it in your history
  • Varun Chitre
    Varun Chitre over 11 years
    It still downloads lots of data(430mb) with git fetch. The required commit is just of few kbs. There is no special command to do this really? And what if I want to remove the 'git fetched' repo? where is it stored?
  • CharlesB
    CharlesB over 11 years
    Actually you can do partial fetch, but still it will download the intermediate commits between the ones you have. Commits are interrelated together, to build the history you need their succession.
  • Jack O'Connor
    Jack O'Connor almost 10 years
    When I try to fetch a specific rev like you do there, git fails with error code 1 and no output. Was this something that used to work in past versions? (I'm v2.0.2.)
  • Jack O'Connor
    Jack O'Connor almost 10 years
    Edit: It does work if I already have the that commit locally, like if I've already done a full fetch, though in that case I'm not sure what the use is.
  • Flow
    Flow almost 10 years
    Indeed, this doesn't seem to work for me any more with git 2.0.2 either. :(
  • ocroquette
    ocroquette over 9 years
    That doesn't work with Git 2.1.1 if the commit is not already in the repository.
  • michaeltintiuc
    michaeltintiuc over 9 years
    None of the answers worked, this one though, saved my life! Thanks a bunch!
  • Nick-ACNB
    Nick-ACNB over 9 years
    The reset --hard worked for me after cloning! Thanks.
  • lzl124631x
    lzl124631x about 9 years
    git checkout FETCH_HEAD helps.
  • 杨苏杭
    杨苏杭 almost 9 years
    Can you give a more complete example on how to create a repo clone with just this single commit? I tried but failed.. Thanks!
  • VonC
    VonC almost 9 years
    @LarsBilke do you have the right version for the Git repo server? If the server has not the uploadpack.allowReachableSHA1InWant config activated, any single-commit clone would fail.
  • 杨苏杭
    杨苏杭 almost 9 years
    I want to push to GitHub. Maybe they don't allow this.
  • VonC
    VonC almost 9 years
    @LarsBilke we are talking about clone or pull here, not push. And I am pretty sure GitHub doesn't have Git 2.5 on the server side yet.
  • Ycon
    Ycon almost 9 years
    This didn't work for me- using git clone (not fetch) just downloaded the master into a folder
  • Theodore Murdock
    Theodore Murdock over 8 years
    This is rather out-of-date now. We have both the ability to perform a shallow clone, as well as to fetch a single commit. Shallow clones are now allowed to push and fetch normally, without having to know the full history of the project, so it's no longer correct to say that a commit cannot exist alone without its ancestors. What you say about fetching after the initial clone is very true, but we also have even cheaper options.
  • Taacoo
    Taacoo almost 8 years
    If you store it in /foo-commit(a non-existing branch) will it create this branch? And can I easly merge with this branch and if something goes wrong undo the merge?
  • HRJ
    HRJ almost 8 years
    The last command (using SHA1 commit) doesn't work for me. The command silently does "something" for a while, and then exits with no message or apparent side-effect.
  • jww
    jww about 7 years
    This requires a Git account setup on a machine. It does not work under a test account. Do you have something that works under a test account?
  • Sérgio
    Sérgio about 7 years
    what you mean ? you can't do git fetch ?
  • kingmakerking
    kingmakerking over 6 years
    This method won't work with shallow fetch (e.g. --depth=1)!
  • gertvdijk
    gertvdijk over 6 years
    @HRJ Yes, I encountered this too, on Ubuntu 16.04 with Git 2.7.4-0ubuntu1.3. However, when using 2.16.2-0ppa1~ubuntu16.04.1 from the git-core PPA, this works as it should. Sounds like a bug that got fixed. Couldn't find a reference to that with a quick search. If someone can get me a pointer on that, I would love to get this fix backported.
  • BoltzmannBrain
    BoltzmannBrain about 6 years
    To avoid the detached head state, checkout a new branch: git checkout -b <branch name> FETCH_HEAD
  • R.. GitHub STOP HELPING ICE
    R.. GitHub STOP HELPING ICE about 6 years
    Now even better, there's uploadpack.allowAnySHA1InWant without the reachability-computation penalty (and DoS vector).
  • VonC
    VonC about 6 years
    @R.. Thank you. I have included your comment in the answer for more visibility.
  • R.. GitHub STOP HELPING ICE
    R.. GitHub STOP HELPING ICE about 6 years
    Thanks! I find it funny that they describe it as "trust the user to access" rather than "trust the repo authors not to push random crap they don't intend to make public".
  • ramwin
    ramwin almost 6 years
    Thank you. This is exactly the answer that I'm seeking.
  • Ry Biesemeyer
    Ry Biesemeyer over 5 years
    -1: "destructive" commands like git reset --hard, when shared in generalised solutions, can lead people into traps where they lose data (or, in this case: in a state where getting their data back is nontrivial).
  • Alexander Mills
    Alexander Mills about 5 years
    Ummm so the command would be git config set uploadpack.allowReachableSHA1InWant?
  • kubanczyk
    kubanczyk about 5 years
    The git fetch origin only works with unabbreviated hash. Tested on Ubuntu 18.04 with gerrit remote.
  • sorin
    sorin over 4 years
    Clearly NOT with older git versions like 1.8.x
  • Khaled AbuShqear
    Khaled AbuShqear over 3 years
  • matt
    matt about 3 years
    Note that SHA must be the full unabbreviated SHA, otherwise Git will claim it couldn't find the commit.
  • VonC
    VonC about 3 years
    @matt Good point, thank you. I have edited the answer accordingly, and included your comment in it, for more visibility.