将git仓库转换为浅层仓库?

95

如何将已经克隆的Git代码库转换为浅层存储库?

该Git代码库是通过我的控制范围之外的脚本下载的,因此我无法进行浅层克隆。

这样做的原因是为了节省磁盘空间。(是的,我的磁盘空间非常有限,因此即使一个浅层存储库节省的空间不多,也是必须的。)

我已经尝试过

git repack -a -d -f -depth=1

但实际上,这使得存储库变得更大了。


https://dev59.com/OHM_5IYBdhLWcg3wZSI6#1400849 可能会有所帮助。在重新打包之后,运行 git gc 命令会发生什么? - VonC
huitseeker:谢谢你提出来。我了解有限制,并且我没问题。我只需要访问最新的提交,或者最好是几个提交就可以了。 - Robert
VonC:我现在正在进行一次gc --aggressive。我应该会从中获得一些好处,但如果可能的话,我也想丢弃我不需要的对象。 - Robert
我刚看到http://progit.org/2010/03/17/replace.html,其提出了一种替代的、可能更简单的流程,涉及到`git commit-tree`。 - Tyler
1
git repack 中的 --depth 参数与 shallowing 无关,它是 deltification 算法中的深度:--depth=1 表示我们想要一个 deltification 的深度为1,这比默认值50要小,因此压缩较少。 - Seb35
我制作了一个 git-shallow-maker,用于将所有本地分支复制到一个新的本地仓库。这将只复制所需的提交,因此新仓库是浅层的。 - milahu
6个回答

87

首先,您可能需要删除标签(因为它们会阻止已标记提交的垃圾回收),例如:

git tag -d $(git tag -l)

然后,这对我起作用了:

git pull --depth 1
git gc --prune=all

这仍然会留下 reflog,就像标签引用其他可能占用空间的提交一样。请注意,除非必须,否则不要删除 reflog:它包含用于从错误中恢复的本地更改历史记录。

下面的注释中有关于如何删除 reflog 的其他命令,以及一个类似问题的链接,其中有更长的答案。

如果您仍然使用了大量空间,请确保已删除标签,这应该是在删除 reflog 之前首先尝试的。


15
我已经运行了以上命令。存储库现在确实是浅的(git log只显示一个提交,git branch只显示一个分支)。但是.git文件夹仍占用2.5 GB的空间。使用--depth 1克隆的同一存储库大约占用1 GB的空间。你有什么建议来减少磁盘使用量吗? - Dzmitry
1
@Dzmitry,你说得对。看看我发布的答案,我认为它可以节省空间。 - VasiliNovikov
2
@Dzmitry,我已经更新了答案中的命令,添加了--prune=all来进行垃圾回收。这会立即删除多余的对象。 - fuzzyTew
15
根据我的实验结果,git pull --depth=1 命令会保留非 HEAD 标签,这些标签不会被 git gc --prune=all 命令删除。我需要使用 git tag -d $(git tag -l) 命令来垃圾回收这些引用。 - v1bri
8
我发现上述的命令还不够,我还需要执行以下命令: git reflog expire --expire=all --all。这个命令被推荐在 https://dev59.com/5loT5IYBdhLWcg3wxx3g。另外,上面提到的 git tag 命令也是必需的。 - Wayne Piekarski
显示剩余8条评论

17

您可以按照以下方法将git仓库转换为浅仓库:

git show-ref -s HEAD > .git/shallow
git reflog expire --expire=0
git prune
git prune-packed

在进行这个具有破坏性的操作之前,请务必备份。同时请注意,不支持从浅层仓库进行克隆或提取!要真正地删除所有历史记录,您还需要在修剪之前删除对先前提交的所有引用。


6
实际上,这似乎没有任何作用。 - hendry
对于子模块,您可能需要解析.git文件到git目录(git rev-parse --git-dir)。此外,您可以使用git describe --always HEAD~5代替show-ref -s HEAD来保留最新的提交。 同时,还有git fetch --unshallow来取消浅克隆。 - blueyed
2
为了删除所有引用,请在reflog命令中添加--all:git reflog expire --expire=now --all - Jiyong Park
4
请注意,git prune 已经执行了 git prune-packed。同时请注意,如果您希望存储所有分支,它们必须在 .git/shallow 文件中列出其 tip。以下命令适用于我,但我不知道它是否始终有效:find .git/refs -type f | xargs cat | sort -u > .git/shallow - fuzzyTew
在运行 git prune 命令之前,您可能需要从 .git/packed-refs 中删除不必要的引用。 - ryenus
显示剩余3条评论

13

从特定日期开始转换为浅层复制:

git pull --shallow-since=YYYY-mm-dd
git gc --prune=all

同样适用:

git fetch --shallow-since=YYYY-mm-dd
git gc --prune=all

1
谢谢!对我来说,git fetch --depth 1; git gc --aggressive --prune=all 也起作用了。这样做似乎相当于使用 git clone --depth 1 进行浅克隆。 - Night Train
我收到了 你目前不在任何分支上。 请指定您要反基于哪个分支。 的信息。 - rubo77

11

创建本地 repo 的浅克隆:

git clone --depth 1 file:///full/path/to/original/dir destination

请注意,第一个“address”应该是file://,这很重要。此外,git将假定您的原始本地文件file://地址为“remote”(“origin”),因此您需要更新新存储库并指定正确的git remote


这对我很有帮助。在我们的CI设置中,我想克隆整个存储库以便从另一个分支应用补丁,然后尽可能缩小目录大小,因为它将被TAR并存储。 - gablin

9

将@fuzzyTew的回答和该回答下的评论结合起来:

git pull --depth 1
git tag -d $(git tag -l)
git reflog expire --expire=all --all
git gc --prune=all

想要在整个磁盘上运行此命令以节省空间吗?- 那么请运行此fd 命令:

fd -HIFt d '.git' -x bash -c 'pushd "$0" && ( git pull --depth 1; git tag -d $(git tag -l); git reflog expire --expire=all --all; git gc --prune=all ) && popd' {//}

或者只使用常规的find命令:

find -type d -name '.git' -exec bash -c 'pushd "${0%/*}" && ( git pull --depth 1; git tag -d $(git tag -l); git reflog expire --expire=all --all; git gc --prune=all ) && popd' {} \;

这个在我的 git 2.28.0 中仍然不能让我节省空间。 - Nir Friedman
嘿!它可以工作了!只是要提醒一下,如果您正在活跃地使用存储库,则“reflog expire”是危险的。但由于我只想浏览最新的代码,所以它非常完美! - Afriza N. Arief
最好使用 `git pull --depth=1 --no-tags'。 - ipcjs
--prune=all应该改为--prune=now,请参考:https://www.spinics.net/lists/git/msg354409.html - Paul

3
请注意,浅仓库(例如使用git clone --depth 1将现有仓库转换为浅仓库的方式)在执行git repack时可能会失败。
查看 提交 5dcfbf5提交 2588f6e提交 328a435(2018年10月24日)由 Johannes Schindelin (dscho)完成。
(由Junio C Hamano -- gitster --提交 ea100b6中合并,2018年11月6日)

repack -ad: prune the list of shallow commits

git repack can drop unreachable commits without further warning, making the corresponding entries in .git/shallow invalid, which causes serious problems when deepening the branches.

One scenario where unreachable commits are dropped by git repack is when a git fetch --prune (or even a git fetch when a ref was force-pushed in the meantime) can make a commit unreachable that was reachable before.

Therefore it is not safe to assume that a git repack -adlf will keep unreachable commits alone (under the assumption that they had not been packed in the first place, which is an assumption at least some of Git's code seems to make).

This is particularly important to keep in mind when looking at the .git/shallow file: if any commits listed in that file become unreachable, it is not a problem, but if they go missing, it is a problem.
One symptom of this problem is that a deepening fetch may now fail with:

fatal: error in object: unshallow <commit-hash>

To avoid this problem, let's prune the shallow list in git repack when the -d option is passed, unless -A is passed, too (which would force the now-unreachable objects to be turned into loose objects instead of being deleted).
Additionally, we also need to take --keep-reachable and --unpack-unreachable=<date> into account.

Note: an alternative solution discussed during the review of this patch was to teach git fetch to simply ignore entries in .git/shallow if the corresponding commits do not exist locally.
A quick test, however, revealed that the .git/shallow file is written during a shallow clone, in which case the commits do not exist, either, but the "shallow" line does need to be sent.
Therefore, this approach would be a lot more finicky than the approach presented by the this patch.


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