使用Git和Mercurial进行部分克隆

75

在Git和Mercurial中,是否可以克隆一个分支(或从给定的提交)?我的意思是,我想克隆一个中央存储库,但由于它非常庞大,我只想获取其中的一部分,并仍然能够向其做出贡献。这可能吗?比如,我只需要从标签130开始之后的内容?

如果可以,如何操作?


1
请参阅 Git 2.17 部分克隆(或“窄克隆”)https://dev59.com/unA75IYBdhLWcg3wy8br#48852630。 - VonC
7个回答

75

在Git中,有三种不同类型的部分克隆:

  • 浅层克隆:我想从某个修订点X开始获取历史记录。

    使用git clone --depth <n> <url>来进行浅层克隆,但请记住,浅层克隆在与其他仓库交互方面有一定的限制。你只能生成补丁并通过电子邮件发送。

  • 按文件路径部分克隆:我想要某个目录 /path 中的所有修订历史。

    在Git中不可能。不过,在现代的Git中,你可以使用稀疏检出,即你拥有整个历史记录,但只检出(在工作区域内)所有文件的子集。

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

    是可行的

    在git 1.7.10之前不太简单:你需要手动执行克隆所做的操作,即git init [<directory>],然后git remote add origin <url>,编辑.git/configremote.origin.fetch中的*替换为请求的分支(可能是'master'),然后git fetch

    从Git 1.7.10开始git clone提供了--single-branch选项,似乎是专门为此目的添加的,并且非常简单。

    但请注意,因为分支通常共享大部分历史记录,所以仅克隆分支子集的获益可能比你想象的要小。

您还可以进行浅层克隆,只选择部分分支。

如果您知道人们将如何通过文件路径(在同一存储库中的多个项目)对其进行拆分,您可以使用子模块(类似于svn:externals)将存储库预先拆分为可分别克隆的部分。


那么,如果我克隆分支“XX”,它会获取来自“master”的所有父提交吗?还是只有我在该分支上做的单个提交? - pablo
1
如果你只克隆(获取)“XX”分支,你将获得它所有的提交,包括那些与“master”分支共有的提交。在Git中,提交并不“属于”某个分支。 - Jakub Narębski
好的,那么它并不是一个部分克隆,因为您可以获取所有父级,从而获取整个存储库(好的,主分支上的最大部分)。 - pablo
1
在1.8.0版本(或稍早版本)中,制作单分支克隆现在变得更加容易了。 - Jakub Narębski
1
你可以在那个列表中添加"partial clone" (或者 "narrow clone"),使用 Git 2.17 (2018年第二季度):https://dev59.com/unA75IYBdhLWcg3wy8br#48852630 - VonC
显示剩余3条评论

53
在Mercurial领域,你需要了解三种不同的部分克隆类型:
- 浅层克隆: 我只需要从修订点X之后的历史记录 使用remotefilelog扩展 - 基于文件路径的部分克隆: 我需要目录/path下的所有修订历史记录,可以使用实验性的narrowhg扩展 或者我只想把目录/path中的文件放在我的工作目录中,这时可以使用实验性的sparse扩展(从4.3版本开始默认集成,详见hg help sparse) - 基于分支的部分克隆: 我需要分支Y上的所有修订历史记录:使用clone -r 如果你知道人们希望按文件路径拆分(在同一个repo中有多个项目(真没谁了)),那么可以使用子仓库(类似于svn externals)将仓库预先拆分为可单独克隆的部分。
此外,对于“太大了我只想获取其中一部分”的情况:你只需要这样做一次。只需利用午餐时间进行克隆,然后你就永远拥有它了。之后,你可以使用pull有效地获取增量版本。如果你需要另一个克隆副本,只需克隆第一个克隆副本即可。从哪里获得副本并不重要(本地副本不占用额外的磁盘空间,因为它们在内部是硬链接)。

1
同时,标签与分支不同于某些版本控制系统,因此这属于第一点。 - jk.
Mercurial有修剪历史和浅克隆插件。我不知道它们的好坏。 - panzi
8
这两个方案都是被拒绝的建议,没有被实施。 - Ry4an Brase
5
使用'remotefilelog'现在可以实现浅克隆:https://bitbucket.org/facebook/remotefilelog 部分文件路径克隆也是可行的(但仍处于试验阶段),请参见http://comments.gmane.org/gmane.comp.version-control.mercurial.devel/69389 - Mathiasdm
1
2017年初:按文件路径的部分克隆(也称为窄克隆)仍未进入主线Mercurial,但可以使用Google的扩展程序实现-https://bitbucket.org/Google/narrowhg。同样,稀疏检出(也称为窄检出)不在主线Mercurial中,但可以使用Facebook的`sparse.py` Mercurial扩展程序实现-https://bitbucket.org/facebook/hg-experimental。 - Anon
显示剩余2条评论

11

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

最小化您的下载和检出占用空间 (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处理大型仓库


7

该方法创建一个没有子存储库的非版本化存档:

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

cd projecttemp

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

不带子仓库的非版本化源代码位于project-no-subrepos目录中


4

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

问题是是否可以仅检出Git存储库中的某些文件。

Tech Talk: Linus Torvalds on git t=43:10

总之,他说Git的设计决策之一使其与其他源管理系统(他引用了BitKeeper和SVN)不同,即Git管理内容而不是文件。这意味着例如,在两个版本的子文件集上进行差异比较是通过首先获取整个差异,然后仅将其修剪为所请求的文件来计算的。另一个是您必须以全有或全无的方式检出整个历史记录。因此,他建议将松散相关的组件分成多个存储库,并提到正在进行的努力实现用于管理结构化为超级项目持有较小存储库的存储库的用户界面。

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

2
我知道这篇文章... 我最初是提交给 Slashdot 的 :P - pablo

1

如果像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 (Q2 2021),其中 "git repack -A -d"(man) 在部分克隆中不必要地放松了 2.32 之前承诺打包中的对象:已修复。

请查看 提交记录 a643157(2021年4月21日),作者为 Rafael Silva (raffs)
(由Junio C Hamano -- gitster --提交记录 a0f521b中合并,日期为2021年5月10日)

重新打包:避免在部分克隆中松动承诺对象

报告者:SZEDER Gábor
协助者:Jeff King
协助者:Jonathan Tan
签名作者:Rafael Silva

When git repack -A -d(man) is run in a partial clone, pack-objects is invoked twice: once to repack all promisor objects, and once to repack all non-promisor objects.
The latter pack-objects invocation is with --exclude-promisor-objects and --unpack-unreachable, which loosens all objects unused during this invocation.
Unfortunately, this includes promisor objects.

Because the -d argument to git repack(man) subsequently deletes all loose objects also in packs, these just-loosened promisor objects will be immediately deleted.
However, this extra disk churn is unnecessary in the first place.
For example, in a newly-cloned partial repo that filters all blob objects (e.g. --filter=blob:none), repack ends up unpacking all trees and commits into the filesystem because every object, in this particular case, is a promisor object.
Depending on the repo size, this increases the disk usage considerably: In my copy of the linux.git, the object directory peaked 26GB of more disk usage.

In order to avoid this extra disk churn, pass the names of the promisor packfiles as --keep-pack arguments to the second invocation of pack-objects.
This informs pack-objects that the promisor objects are already in a safe packfile and, therefore, do not need to be loosened.

For testing, we need to validate whether any object was loosened.
However, the "evidence" (loosened objects) is deleted during the process which prevents us from inspecting the object directory.
Instead, let's teach pack-objects to count loosened objects and emit via trace2 thus allowing inspecting the debug events after the process is finished.
This new event is used on the added regression test.

Lastly, add a new perf test to evaluate the performance impact made by this changes (tested on git.git):

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

For a bigger repository, such as linux.git, the improvement is even bigger:

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

These improvements are particular big because every object in the newly-cloned partial repository is a promisor object.


如Git 2.33(2021年第三季度)所述,git-repack(man)文档清楚地说明了它在指定"-a"时确实可以操作承诺pack文件(在独立分区中)。

可以推测,这里的陈述已经过时,因为它们来自2017年的第一个文档(而重打包支持是在2018年添加的)

请查看提交 ace6d8e(2021年6月2日)由Tao Klerks (TaoK)提交。
(由Junio C Hamano -- gitster --提交4009809中合并,2021年7月8日)

签署者:Tao Klerks
审核者:Taylor Blau
确认者:Jonathan Tan

请查看technical/partial-clone手册页面

另外,自 Git 2.33(2021年第3季度)开始, "git read-tree"(手册)存在一种代码路径,其中单个blob会从承诺者远程一个接一个地获取,这已被更正为批量获取

请查看提交d3da223提交b2896d2(2021年7月23日),由Jonathan Tan(jhowtan创建。
(由Junio C Hamano -- gitster --于2021年8月2日合并至提交8230107

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

由Jonathan Tan签署

"git read-tree"(man) 检查给定树引用的 blob 是否存在,但不会批量预取它们。
添加批量预取功能。
在一个涉及某些特定提交的合并过程中,$DAYJOB 注意到这里缺少预取,但我找不到一个最小的合并示例,它不会触发 unpack-trees.c 中的 check_updates() 中的预取(在所有这些情况下,cache-tree.c 中的预取不重要,因为所有相关的 blob 在那时都已经被预取了)。
这就是为什么我在这里使用 read-tree 来执行此代码路径的原因。"

Git 2.39(2022年第四季度)避免在冗余时调用'cache_tree_update()'。

请查看提交 652bd02, 提交 dc5d40f, 提交 0e47bca, 提交 68fcd48, 提交 94fcf0e (2022年11月10日) 由Victoria Dye (vdye)提交。
(由Taylor Blau -- ttaylorr --合并于提交 a92fce4, 2022年11月18日)

read-tree:使用“skip_cache_tree_update”选项

署名:Victoria Dye
署名:Taylor Blau

When running 'read-tree' with a single tree and no prefix, 'prime_cache_tree()' is called after the tree is unpacked.
In that situation, skip a redundant call to 'cache_tree_update()' in 'unpack_trees()' by enabling the 'skip_cache_tree_update' unpack option.

Removing the redundant cache tree update provides a substantial performance improvement to 'git read-tree'(man) <tree-ish>, as shown by a test added to 'p0006-read-tree-checkout.sh':

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

Note that the 'read-tree' in 't1022-read-tree-partial-clone.sh' is updated to read two trees, rather than one.
The test was first introduced in d3da223 ("cache-tree: prefetch in partial clone read-tree", 2021-07-23, Git v2.33.0-rc0 -- merge) to exercise the 'cache_tree_update()' code path, as used in 'git merge'(man).
Since this patch drops the call to 'cache_tree_update()' in single-tree 'git read-tree', change the test to use the two-tree variant so that 'cache_tree_update()' is called as intended.


-1

在Mercurial中,你应该能够使用以下方法之一:

hg convert --banchmap FILE SOURCEDEST REVMAP

您可能还需要:

--config convert.hg.startrev=REV

源代码可以使用git、mercurial或其他各种系统。

我没有尝试过,但convert非常丰富。


4
转换扩展会更改哈希值,因此这不是现有仓库的部分克隆,而是一个全新的仓库。这意味着它将是一个独立的仓库,无法从原始仓库拉取或推送。 - Priit

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