从远程 Git 仓库检索特定的提交记录

240

有没有办法从远程 Git 存储库中检索特定的提交,而不必在我的计算机上克隆它?远程存储库的结构与我的完全相同,因此不会有任何冲突,但我不知道该如何做,并且我不想克隆那个庞大的存储库。

我是 Git 新手,有什么方法吗?


1
你现有的代码库已经是远程代码库的克隆版本,还是完全不同的? - CharlesB
1
不完全是这样。考虑一下,假设远程仓库在D处,而我的仓库在A处,并且落后于B、C、D三个提交。我希望从一个仓库合并B提交,从另一个仓库合并C提交,再从另一个仓库合并D提交,因为这些仓库中的B、C、D提交都有各自的特点。 - Varun Chitre
从Git 2.5+(2015年第二季度)开始,如果需要,您将能够获取单个提交! (如果Git存储库托管服务器授权)请参见[下面的我的答案](https://dev59.com/MmUp5IYBdhLWcg3w_rvT#30701724)。 - VonC
http://serverfault.com/questions/117255/git-pull-specific-revision-from-remote-repository - Ciro Santilli OurBigBook.com
1
@VarunChitre,你能接受VonC的其他答案吗? - CharlesB
显示剩余3条评论
11个回答

141

从Git 2.5+(2015年第二季度)开始,实际上可以获取单个提交(而无需克隆完整仓库)。

参见 提交 68ee628Fredrik Medley (moroten 于2015年5月21日提交。
(由 Junio C Hamano -- gitster -- 提交a9d3493中合并,2015年6月1日)

现在你在服务器端有一个新的配置。

uploadpack.allowReachableSHA1InWant

允许 upload-pack 接受一个获取请求,该请求要求可从任何参考端点到达的对象。但是请注意,计算对象可达性代价高昂。
默认为false
如果将这个服务器端配置与浅克隆 (git fetch --depth=1) 结合使用,则可以请求单个提交(参见 t/t5516-fetch-push.sh:
git fetch --depth=1 ../testrepo/.git <full-length SHA1>

你可以使用git cat-file命令查看已获取的提交内容:
git cat-file commit <full-length SHA1>

"git fetch"所使用的"git upload-pack"可以通过uploadpack.allowReachableSHA1InWant配置变量来服务于那些不在任何引用顶端但仍可从引用到达的提交。正如matt评论中所指出的:请注意,SHA必须是完整的非缩写SHA,否则Git会声称找不到该提交。
完整的文档是:

upload-pack:可选择允许获取可达到的sha1

当服务器端设置了uploadpack.allowReachableSHA1InWant配置选项时,"git fetch"可以发出一个“want”行的请求,该行命名了一个未被广告(可能已经通过子模块指针或者其他方式获得)的对象。
只有从分支末端可到达的对象,即广告分支和由transfer.hideRefs隐藏的分支的并集,将被处理。
请注意,必须回溯历史记录以检查可达性,这是相关成本。
当需要获取某个已知sha1的提交内容时,特别是在使用浅层抓取时,可以使用此功能,而不必克隆整个存储库。
有用的用例包括:
  • 包含大文件历史记录的存储库,
  • 仅获取子模块检出所需的数据,
  • 在Gerrit中共享sha1而不告诉它属于哪个确切的分支,并且如果你按提交而不是更改编号思考,那么在Gerrit中也是如此。
    (Gerrit案例已通过allowTipSHA1InWant解决,因为每个Gerrit更改都有一个引用。)

Git 2.6 (Q3 2015)将改进该模型。
请参考提交记录2bc31d1, 提交记录cc118a6(由Jeff King (peff)于2015年7月28日提交)。
(由Junio C Hamano -- gitster --提交记录824a0be中合并,2015年8月19日)

refs:支持负数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 (2015年11月/12月) 将再次提升:

请查看提交 948bfa2提交 00b293e(2015年11月5日),提交 78a766a提交 92cab49提交 92cab49提交 92cab49(2015年11月3日),提交 00b293e提交 00b293e(2015年11月5日)和提交 92cab49提交 92cab49提交 92cab49提交 92cab49,由Lukas Fleischer (lfos)完成。
协助者:Eric Sunshine (sunshineco)
(由Jeff King -- peff --提交 dbba85e中合并,2015年11月20日)

config.txt:记录带有命名空间的hideRefs的语义

目前,对于设置了命名空间时transfer.hideRefs应如何行为没有明确的定义。
解释在这种情况下hideRefs前缀匹配被剥离名称的方式。这是当前在receive-pack中处理hideRefs模式的方法。

hideRefs: 添加支持完全匹配引用的功能

除了匹配剥离的引用之外,现在可以添加hideRefs模式来匹配完整(未剥离)的引用。
为了区分剥离和完全匹配,这些新模式必须以插入符号(^)作为前缀。

因此有了新文档

transfer.hideRefs:

如果一个命名空间正在使用中,则在与transfer.hiderefs模式匹配之前,从每个引用中剥离命名空间前缀。
例如,如果在transfer.hideRefs中指定了refs/heads/master, 并且当前命名空间为foo,则refs/namespaces/foo/refs/heads/master 将被省略不在广告中,但refs/heads/masterrefs/namespaces/bar/refs/heads/master仍然作为所谓的“have”行进行广告。
为了在剥离之前匹配引用,请在引用名称前面添加^。如果结合使用!^,则必须先指定!

R..提到在评论中配置uploadpack.allowAnySHA1InWant,它允许upload-pack接受一个请求fetch,该请求可以获取任何对象。(默认值为false)。

参见提交记录f8edeaa(2016年11月,Git v2.11.1),作者是David "novalis" Turner (novalis)

upload-pack:可选地允许获取任何sha1

在我们信任用户可以访问存储库中的所有内容的情况下进行可达性检查似乎有点愚蠢。此外,在分布式系统中,这是一种竞争条件——也许一个服务器广告了一个 ref,但另一个服务器已经对该 ref 进行了强制推送,而且也许这两个 HTTP 请求最终被定向到这些不同的服务器。
在 Git 2.34(2021年第四季度)中,运行在git fetch(man)另一侧的 "git upload-pack"(man) 在处理 want-ref 请求时忘记考虑 ref 命名空间。

查看 提交 53a66ec, 提交 3955140, 提交 bac01c6 (2021年8月13日) 由 Kim Altintop (kim) 提交。
(由 Junio C Hamano -- gitster --提交 1ab13eb 中合并,2021年9月10日)

文档: 澄清transfer.hideRefs和命名空间的交互

签名:Kim Altintop
审核:Jonathan Tan

请扩展transfer.hideRefs文档中关于命名空间的部分,以指出upload-packreceive-pack之间微妙的区别。 3955140(“upload-pack.c:将want-ref相对于命名空间处理”,2021年7月30日,Git v2.34.0 -- merge列在batch #5中)教会了upload-pack拒绝隐藏引用的want-ref,现在已经提到。
澄清了一个隐藏引用的名称从未被透露过,但它所指向的对象ID可能会被透露。

git config现在在其手册页面中包含:

在与transfer.hiderefs模式匹配之前引用。为了在剥离之前匹配引用,请在引用名称前面添加^。如果结合使用!^,则必须先指定!

git config现在在其手册页面中包含:

广告中省略了一些内容。如果设置了uploadpack.allowRefInWant,则在协议v2的fetch命令中,upload-pack会将want-ref refs/heads/master视为不存在refs/namespaces/foo/refs/heads/master。另一方面,receive-pack仍会发布引用所指向的对象id,但不会提及其名称(所谓的“.have”行)。

在Git 2.39(2022年第四季度)中, "git receive-pack"(man) 用于检查 git push(man) 发送的数据的连通性时,以前会使用所有本地引用作为边界,但现在它仅使用向推送者广告的引用。
在具有 .hideRefs 配置的存储库中,这减少了执行检查所需的资源。

请看提交 bcec678, 提交 5ff36c9, 提交 8c1bc2a, 提交 1e9f273, 提交 05b9425, 提交 9b67eb6, 提交 5eeb9aa (2022年11月17日),作者为Patrick Steinhardt (pks-t)
Junio C Hamano -- gitster --提交 f8828f9中合并(2022年11月23日)。

修订版:添加新参数以排除隐藏的引用

签名作者:Patrick Steinhardt
签名作者:Taylor Blau

用户可以通过transfer.hideRefs可选地隐藏git-upload-pack(1), git-receive-pack(1)和其他远程用户的引用,但目前还没有一种简单的方法来获取所有可见或隐藏的引用列表。
然而,为了提高连接性检查的性能,我们需要这样做。

添加一个新选项--exclude-hidden=,它会从下一个伪引用(如--all--branches)中排除任何隐藏的引用。

rev-list-options现在包含以下内容在其手册页面中:

--exclude-hidden=[receive|uploadpack]

不要包括由git-receive-packgit-upload-pack隐藏的引用,通过查看适当的receive.hideRefsuploadpack.hideRefs配置以及transfer.hideRefs(参见git config)。此选项影响下一个伪引用选项--all--glob,并在处理它们后清除。

和:

rev-parse: 添加 --exclude-hidden= 选项

签署者: Patrick Steinhardt
签署者: Taylor Blau

添加一个新的 --exclude-hidden= 选项,与我们刚刚添加到 git-rev-list(1) 中的选项类似。
如果给定一个章节名称 uploadpackreceive 作为参数,则会导致我们排除所有受相应的 $section.hideRefs 配置影响而被隐藏的引用。

git rev-parse现在在其man页面中包含:

--exclude-hidden=[receive|uploadpack]

不包含被git-receive-pack或者git-upload-pack隐藏的引用,这是通过参考相应的receive.hideRefs或者uploadpack.hideRefs配置以及transfer.hideRefs来实现的(请参阅git config)。此选项会影响下一个伪引用选项--all--glob,并在处理它们后被清除。


5
您能否提供一个更完整的例子,说明如何只克隆某个提交的存储库?我尝试了但失败了。谢谢! - Lars Bilke
1
我想要推送到GitHub。也许他们不允许这样做。 - Lars Bilke
2
@LarsBilke 我们正在讨论克隆或拉取,而不是推送。而且我很确定GitHub服务器端还没有Git 2.5版本。 - VonC
2
现在更好的是,有了uploadpack.allowAnySHA1InWant,就没有可达性计算惩罚(和DoS向量)了。 - R.. GitHub STOP HELPING ICE
1
@matt 说得好,谢谢。我已经相应地编辑了答案,并将您的评论包含在其中,以增加可见度。 - VonC
显示剩余5条评论

110

你只需要克隆一次,所以如果你已经有了远程仓库的克隆版本,从中pull不会重新下载所有内容。只需指定要pull的分支,或者获取更改并检出所需的提交。

从一个新的仓库fetch非常便宜,因为它只会下载你没有的更改。在Git中考虑做正确的事情,以最小的负载。

Git将所有内容存储在.git文件夹中。无法孤立地获取和存储提交,需要获取它的所有祖先。它们是互相关联的。


然而,为了减少下载量,你可以让 Git 只获取与特定分支或提交相关的对象:

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

这将仅下载远程分支 branch 包含的提交 (以及您错过的提交) 并将其存储在 origin/branch 中。然后您可以合并或检出。

您也可以指定一个 SHA1 提交 id -- 但请注意,您必须使用 完整的40个字符未缩写的 标识符:

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

这将仅下载指定SHA-1 96de5297df87096de5297df87096de5297df8709的提交(以及您错过的其祖先),并将其存储为(不存在的)远程分支 origin/foo-commit


3
看起来你对“克隆”一词的含义有些混淆。当你从远程仓库获取更改时,并不是在进行克隆,而只是获取了提交记录。然后你可以选择要检出哪个提交记录,或将其合并到你的提交历史中。 - CharlesB
2
它仍然通过git fetch下载了大量数据(430mb)。所需的提交只有几个kbs。真的没有特殊的命令可以做到这一点吗? 如果我想删除“git fetched”存储库怎么办?它存储在哪里? - Varun Chitre
9
现在这段内容已经有些过时了。我们现在可以进行“浅层克隆”,也可以“获取单个提交”。浅层克隆现在可以正常地进行推送和获取,而不必知道项目的完整历史记录,因此不能再说一个提交没有祖先就不能存在了。您提到的关于在初始克隆之后进行获取的内容是非常正确的,但我们还有更便宜的选项。 - Theodore Murdock
7
使用SHA1提交的最后一个命令对我不起作用。该命令在默默地执行了一段时间后,没有任何消息或明显的副作用就退出了。 - HRJ
2
git fetch origin 只能使用 未缩写的 哈希值。在 Ubuntu 18.04 上测试过,适用于 gerrit 远程仓库。 - kubanczyk
显示剩余5条评论

78

你可以使用以下命令从远程库中获取单个提交记录:

git fetch <repo> <commit-id>

其中,

  • <repo>可以是远程仓库名称(例如origin),甚至可以是远程仓库URL(例如https://git.foo.com/myrepo.git
  • <commit-id>是提交的ID

例如:

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

在获取提交及其缺失的祖先后,您可以使用以下命令轻松切换到该提交:

git checkout FETCH_HEAD

请注意,这将使你进入“分离头”状态。


12
当我尝试像你那样fetch特定的版本时,git会失败并显示错误代码1和无输出。这在以前的版本中曾经有效吗?(我的版本是v2.0.2.) - Jack O'Connor
2
编辑:如果我已经在本地有那个提交,就可以工作,就像我已经完成了完整的“fetch”一样,尽管在这种情况下我不确定用途是什么。 - Jack O'Connor
3
实际上,对我来说,使用git 2.0.2也无法正常工作了。 :( - Flow
2
git checkout FETCH_HEAD 有所帮助。 - lzl124631x
2
这种方法无法与浅层获取(例如 --depth=1)一起使用! - kingmakerking
显示剩余4条评论

66

我在我的git仓库上执行了一次pull操作:

git pull --rebase <repo> <branch>

允许git拉取所有分支代码,然后我去重置到我感兴趣的提交记录。

git reset --hard <commit-hash>

希望这有所帮助。


1
没有一个答案有效,但是这个确实救了我的命!非常感谢! - michaeltintiuc
1
克隆后,reset --hard 对我很有用!谢谢。 - Nick-ACNB
5
通用解决方案中分享类似git reset --hard的“破坏性”命令可能会使人们陷入困境,导致数据丢失(或在此情况下:处于恢复数据不易的状态)。为避免这种情况,请小心使用这些命令。 - Ry Biesemeyer

21

您可以使用以下命令轻松获取远程存储库:

git fetch <repo>

其中,

  • <repo>可以是远程仓库名称(例如origin),甚至可以是远程仓库的URL(例如https://git.foo.com/myrepo.git)。

例如:

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

在获取完仓库后,您可以合并所需的提交(由于问题是检索一个提交,因此您可以使用 cherry-pick 仅选择一个提交):

git merge <commit>
  • <commit>可以是SHA1提交记录

例如:

git cherry-pick 0a071603d87e0b89738599c160583a19a6d95545
或者
git merge 0a071603d87e0b89738599c160583a19a6d95545

如果您想要合并的是最新的提交,您也可以使用FETCH_HEAD变量:

git cherry-pick (or merge) FETCH_HEAD

这需要在计算机上设置一个Git账户。它不能在测试账户下工作。您有适用于测试账户的解决方案吗? - jww
你的意思是什么?你不能执行git fetch吗? - Sérgio
那么命令应该是 git config set uploadpack.allowReachableSHA1InWant,对吧? - Alexander Mills

11

这样做效果最佳:

git fetch origin specific_commit
git checkout -b temp FETCH_HEAD

名称为“temp”,可以随意更改...但此分支可能会成为孤立分支


明显不适用于旧版本的git,如1.8.x。 - sorin

1

最后我找到了使用git cherry-pick克隆特定提交的方法。假设您在本地没有任何存储库,并且要从远程拉取特定提交,

1)在本地创建空存储库并使用git init

2)git remote add origin "存储库的url"

3)git fetch origin [这不会将文件移动到本地工作区,除非您合并]

4)git cherry-pick "输入您需要的长提交哈希"

完成。这样,您只会在本地拥有来自该特定提交的文件。

输入长提交哈希:

您可以使用 -> git log --pretty=oneline 来获取此信息


1

0
# make sure you fetch from the origin first
$ git fetch origin

# view code at COMMIT_ID (abc123)
$ git checkout abc123

# bring only COMMIT_ID (abc123)
# to your branch
# assuming your branch is master
$ git checkout master
$ git cherry-pick abc123

# bring all changes up to 
# COMMIT_ID (abc123) to your branch
# assuming your branch is master
$ git checkout master
$ git merge abc123

参考 - https://unfuddle.com/stack/tips-tricks/git-pull-specific-commit/


0
如果所请求的提交在远程仓库的拉取请求中,您可以通过其ID获取它:
# Add the remote repo path, let's call it 'upstream':
git remote add upstream https://github.com/repo/project.git

# checkout the pull ID, for example ID '60':
git fetch upstream pull/60/head && git checkout FETCH_HEAD

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接