使用 Git 和 Hg 的部分克隆

是否有可能在 Git 和 Mercurial 中只克隆一个分支(或从给定的提交) ?我的意思是,我想克隆一个中央回购,但因为它是巨大的,我想只得到它的一部分,仍然能够贡献我的变化。有可能吗?比如,我只想从130标签开始还是什么的?

如果是这样,怎么做?

28801 次浏览

在这片变幻莫测的土地上,你谈论的是三种不同类型的部分克隆:

  • 浅克隆: 我想要历史从修订点 X 向前 使用 < a href = “ https://bitbucket. org/facebook/Remotefilelog”rel = “ noReferrer”> Remotefilelog 扩展名
  • 通过 filepath 部分克隆: 我希望所有目录/路径中的修订历史都包含在 实验性的 < a href = “ https://bitbucket. org/Google/narrowhg”rel = “ noReferrer”> narrowhg 扩展 中,或者我希望只有目录/路径中的文件在我的工作目录中包含在 实验性稀疏延伸实验性稀疏延伸中(从4.3版本开始发布,参见 hg help sparse)。
  • 分支的部分克隆: 我想要分支 Y: 使用克隆 -R上的所有修订历史

如果你知道人们想要怎样通过文件路径(同一个回购中的多个项目)来分解事物,你可以使用子存储库(有点像 svn 外部)来预先将回购分解成单独的可克隆部分

另外,关于“如此巨大,我只想得到它的一部分”: 你真的只需要做一次。只要在你吃午饭的时候克隆它,然后你就可以永远拥有它了。随后,您可以 pull和得到有效的增量前进。如果你想要它的另一个克隆,只要克隆你的第一个克隆。在哪里获得克隆并不重要(本地克隆不会占用额外的磁盘空间,因为它们是隐藏在外壳下的硬链接)。

在 Git 的世界里,你谈论的是三种不同类型的部分克隆:

  • 浅显的克隆: 我希望历史从修订点 X 向前发展。

    为此使用 git clone --depth <n> <url> ,但是请记住,浅克隆在与其他存储库交互时受到一定限制。您可以生成补丁并通过电子邮件发送它们。

  • 通过 filepath 部分克隆: 我希望在某个目录 /path中保存所有的修订历史记录。

    在 Git 中不可能 。使用现代 Git,虽然你可以使用 很少结账,也就是说,你有完整的历史记录,但是你只检出所有文件的子集。

  • 只克隆选定的分支: 我只想克隆一个分支(或分支的选定子集)。

    有可能

    在 git 1.7.10之前并不简单: 你需要做克隆人手动做的事情,比如 git init [<directory>],然后 git remote add origin <url>,编辑 .git/config,用请求的分支(可能是‘ master’)替换 remote.origin.fetch中的 *,然后是 git fetch

    从 git 1.7.10 开始,git clone提供了 --single-branch选项,它看起来就是为了这个目的而添加的,而且看起来非常简单。

    但是请注意,由于分支通常共享它们的大部分历史,因此仅克隆一部分分支的收益可能比您想象的要小。

您还可以只对选定的分支子集进行浅克隆。

如果您知道人们希望如何通过文件路径(同一存储库中的多个项目)分解内容,那么可以使用子模块(有点像 svn: foreign als)将回购预先分解为单独的可克隆部分。

在 mercurial 中,您应该能够使用以下命令:

hg convert --banchmap FILE SOURCEDEST REVMAP

你可能还需要:

--config convert.hg.startrev=REV

源可以是 git、 mercurial 或各种其他系统。

我还没试过,不过皈依者很有钱。

这个方法创建一个没有子存储库的未版本归档文件:

hg clone -U ssh://machine//directory/path/to/repo/project projecttemp


cd projecttemp


hg archive -r tip ../project-no-subrepos

没有子库的未版本化源代码位于 project-no-subrepos 目录中

关于 Git,Linus Torvalds 在2007年的一次演讲中从概念的角度回答了这个问题,这次演讲被记录下来,并且可以在线获得,这可能具有历史意义。

问题是是否可以只签出 Git 存储库中的一些文件。

技术对话: Linus Torvalds on git t = 43:10

总而言之,他说 Git 的一个设计决策使它有别于其他的源代码管理系统(他引用了 BitKeeper 和 SVN) ,那就是 Git 管理的是内容,而不是文件。其含义是,例如,在两个修订版本中的一个文件子集的 diff 是通过首先获取整个 diff,然后将其修剪为所请求的文件来计算的。另一个是你必须查看整个历史; 以全有或全无的方式。出于这个原因,他建议在多个存储库之间分离松散相关的组件,并提到当时正在努力实现一个用户界面来管理存储库,该存储库的结构是一个包含较小存储库的超级项目。

据我所知,这个基本的设计决策至今仍然适用。这个超级项目可能变成了现在的 子模组

所选择的答案提供了一个很好的概述,但缺乏一个完整的例子。

尽量减少您的下载和签出的足迹 (a)(a), (b) :

git clone --no-checkout --depth 1 --single-branch --branch (name) (repo) (folder)
cd (folder)
git config core.sparseCheckout true
echo "target/path/1" >>.git/info/sparse-checkout
echo "target/path/2" >>.git/info/sparse-checkout
git checkout

定期优化您的本地存储库足迹 (c) (可选,小心使用) :

git clean --dry-run # consider and tweak results then switch to --force
git gc
git repack -Ad
git prune

参见: 如何使用 git 处理大型存储库

如果像在 Brent Bradburn回答中那样,在 Git 的部分克隆中重新打包,请确保:

git clone --filter=blob:none --no-checkout https://github.com/me/myRepo
cd myRepo
git sparse-checkout init
# Add the expected pattern, to include just a subfolder without top files:
git sparse-checkout set /mySubFolder/


# populate working-tree with only the right files:
git read-tree -mu HEAD

关于局部克隆中的局部优化,如:

git clean --dry-run # consider and tweak results then switch to --force
git gc
git repack -Ad
git prune

使用 Git 2.32(Q22021) ,其中“ git repack -A -d(< a href = “ https://git-scm.com/docs/git-repack # Document/git-repack.txt--A”rel = “ nofollow noReferrer”> man )在部分克隆中不必要地松开承诺者包中的对象在2.32之前: 固定。

提交643157(2021年4月21日) by 拉斐尔 · 席尔瓦(raffs)
(由 朱尼奥 · C · 哈马诺 gitster于2021年5月10日在 犯下罪行合并)

repack : 避免在部分克隆中放松承诺对象

报道: SZEDER Gábor
帮助者: Jeff King
帮助者: Jonathan Tan
签名: Rafael Silva

当在部分克隆中运行 git repack -A -d(< a href = “ https://git-scm.com/docs/git-repack # Document/git-repack.txt--A”rel = “ nofollow noReferrer”> man )时,将调用 pack-objects两次: 一次用于重新打包所有允诺程序对象,一次用于重新打包所有非允诺程序对象。
后一个 pack-objects调用是与 --exclude-promisor-objects--unpack-unreachable一起进行的,它放松了在此调用期间未使用的所有对象。
不幸的是,这包括允诺者对象。

因为 git repack(< a href = “ https://git-scm.com/docs/git-repack”rel = “ nofollow norefrer”> man )-d参数随后也会删除包中的所有松散对象,所以这些刚刚松散的承诺对象将立即被删除。
然而,这种额外的磁盘搅拌在第一位是不必要的。
例如,在一个新克隆的过滤所有 blob 对象(例如 --filter=blob:none)的部分回购中,repack最终解压缩所有树并提交到文件系统中,因为在这个特殊情况下,每个对象都是一个允诺对象。
根据回购文件的大小,这将大大增加磁盘使用量: 在我的 linux.git 副本中,对象目录的磁盘使用量达到了26GB 的峰值。

为了避免这种额外的磁盘波动,请将承诺程序包文件的名称作为 --keep-pack参数传递给 pack-objects的第二次调用。
这通知 pack-objects允诺者对象已经在一个安全的包文件中,因此不需要松开。

对于测试,我们需要验证是否有任何对象被松开。
然而,“证据”(松散的对象)在过程中被删除,这阻止了我们检查对象目录。
相反,让我们教 pack-objects计算松散对象并通过 trace2发出,从而允许在流程结束后检查调试事件。
此新事件用于添加的回归测试。

最后,添加一个新的 perf 测试来评估这些更改对性能的影响(在 Git Git上测试) :

Test          HEAD^                 HEAD
----------------------------------------------------------
5600.3: gc    134.38(41.93+90.95)   7.80(6.72+1.35) -94.2%

对于 linux.git 这样更大的仓库,改进甚至更大:

Test          HEAD^                     HEAD
-------------------------------------------------------------------
5600.3: gc    6833.00(918.07+3162.74)   268.79(227.02+39.18) -96.1%

这些改进特别大,因为新克隆的部分存储库中的每个对象都是允诺者对象。


正如 Git 2.33(Q32021)中提到的,git-repack(< a href = “ https://git-scm.com/docs/git-repack”rel = “ nofollow norefrer”> man )文档清楚地指出,它的 是的操作允诺程序包文件(在一个单独的分区中) ,并指定“ -a”。

这里的声明可能已经过时了,因为它们是2017年第一篇文档的特色(并且重新包装支持是在2018年添加的)

犯罪(2021年6月2日) by 陶克乐(TaoK)
(由 朱尼奥 · C · 哈马诺 gitster于2021年7月8日在 提交4009809合并)

签名: Tao Klerks
校对: Taylor Blau
编剧: Jonathan Tan

参见 technical/partial-clone

另外,仍然使用 Git 2.33(Q32021) ,“ git read-tree(< a href = “ https://git-scm.com/docs/git-read-tree”rel = “ nofollow noReferrer”> man )有一个代码路径,可以从允诺者远程一个接一个地提取 blobs,这个代码路径已经更正为 批量取货

提交 d3da223提交 b2896d2(2021年7月23日) by 谭(jhowtan)
(由 朱尼奥 · C · 哈马诺 gitster于2021年8月2日在 提交8230107合并)

cache-tree : 部分克隆读取树中的预取

签名: Jonathan Tan

git read-tree(< a href = “ https://git-scm.com/docs/git-read-tree”rel = “ nofollow noReferrer”> man )检查给定树引用的 blobs 是否存在,但不对它们进行大容量预取。
添加批量预取。

这里缺少预取是在 $DAYJOB的一次合并中发现的,涉及到一些特定的提交,但是我找不到一个最小的合并,它不会在 unpack-trees.ccheck_updates()中触发预取(在所有这些情况下,cache-tree.c中缺少预取并不重要,因为所有相关的 blobs 到那时已经被预取了)。
这就是为什么我在这里使用 read-tree 来练习这个代码路径。


Git 2.39(Q42022)避免调用“ cache_tree_update()”,因为这样做是多余的。

提交652bd02提交 dc5d40f犯下第四十七集提交68fcd48犯下94fcf0e(2022年11月10日) by 维多利亚染料(vdye)
(由 泰勒 · 布劳 ttaylorr做个92fce4合并,2022年11月18日)

read-tree : 使用‘ skip_cache_tree_update’选项

签字人: 维多利亚 · 戴
由 Taylor Blau 签名

当使用一个没有前缀的树运行“ read-tree”时,在解压缩树之后调用“ prime_cache_tree()”。
在这种情况下,通过启用“ skip_cache_tree_update”解包选项,跳过对“ unpack_trees()”中的“ cache_tree_update()”的冗余调用。

删除冗余的缓存树更新提供了对‘ git read-tree(< a href = “ https://git-scm.com/docs/git-read-tree”rel = “ nofollow noReferrer”> man ) <tree-ish>的实质性性能改进,如在‘ p0006-read-tree-checkout 中添加的测试所示。嘘:

Test                          before            after ---------------------------------------------------------------------- read-tree `br_ballast_plus_1`   3.94(1.80+1.57)   3.00(1.14+1.28) -23.9%

请注意,‘ t1022-read-tree-partial-clone.sh’中的‘ read-tree’被更新为读取两棵树,而不是一棵树。
该测试首先在 D3da223(“ cache-tree: 部分克隆读取树中的预取”,2021-07-23,Git v2.33.0-rc0—— 合并)中引入,以执行“ cache_tree_update()”代码路径,如在“ git merge(< a href = “ https://git-scm.com/docs/git-merge”rel = “ nofollow norefrer”> man )中所使用的。
由于这个补丁删除了对单树“ git read-tree”中的“ cache_tree_update()”的调用,因此将测试更改为使用双树变体,以便按预期调用“ cache_tree_update()”。