参考:
概述
git 概述
Git 是一个分布式版本控制软件,最初由 「林纳斯·托瓦兹」 创作,于 2005 年发布。最初目的是为更好地管理 Linux 内核开发。Git 在本地磁盘上就保存着所有有关当前项目的历史更新,处理速度快。Git 中的绝大多数操作都只需要访问本地文件和资源,不用实时联网。
「Git LFS」(Large File Storage - 大文件存储)是可以把⾳乐、图⽚、视频等指定的任意文件存在 Git仓库之外,而在 Git 仓库中用一个占用空间 1KB 不到的文本指针来代替的小⼯具。通过把大文件存储在 Git 仓库之外,可以减小 Git 仓库本身的体积,使克隆 Git 仓库的速度加快,也使得 Git 不会因为仓库中充满大⽂件而损失性能。
使用 Git LFS ,在默认情况下,只有当前签出的 commit 下的 LFS 对象的当前版本会被下载。此外,也可以做配置,只取由 Git LFS 管理的某些特定文件的实际内容,而对于其他由 Git LFS 管理的⽂件则只保留文件指针,从而节省带宽,加快克隆仓库的速度;也可以配置⼀次获取大文件的最近版本,从而能方便地检查大文件的近期变动。
.git 目录
虽然 .git 这个隐藏目录并不算在代码体积之后,但是拉代码的时候,是需要拉下来的,因为里面包含之前的提交记录等信息。这就会导致下载速度变的很慢。
├── HEAD
├── branches
├── index
├── logs
│ ├── HEAD
│ └── refs
│ └── heads
│ └── master
├── objects
│ ├── 88
│ │ └── 23efd7fa394844ef4af3c649823fa4aedefec5
│ ├── 91
│ │ └── 0fc16f5cc5a91e6712c33aed4aad2cfffccb73
│ ├── 9f
│ │ └── 4d96d5b00d98959ea9960f069585ce42b1349a
│ ├── info
│ └── pack
└── refs
├── heads
│ └── master
└── tags
- description:用于GitWeb程序
- config:配置特定于该仓库的设置
- hooks:放置客户端或服务端的hook脚本
- HEAD:指明当前处于哪个分支
- objects:Git对象存储目录
- refs:Git引用存储目录
- branches:放置分支引用的目录
.git 目录变大原因
- 当使用 git add 和 git commit 命令的过程中,Git 都会生成一个 Git 对象,称为 blob对象,存放在 objects 目录下,然后更新 index 索引,再然后创建 tree 对象,最后创建出了 commit 对象。这些 commit 对象指向了顶层 tree 对象以及先前的 commit 对象。
- 而上述创建出来的对象,都以文件的方式保存在 .git/objects 目录下。所以,当在使用的过程中,提交了一个体积特别大的文件,就会被 Git 追踪记录在 .git/objects 文件夹下面。
- 即使后面删除了这个体积特别大的文件,但其实 Git 只会记录删除的这个操作,并不会把文件从 .git 文件夹下面真正的删除,即 .git 文件夹完全不会变小。
解决方案
方案1:重建仓库
重建仓库的这种做法,算是一种比较一劳永逸且相对而言比较简单的方式。但是,这种做法一般情况下,都是不可行,除非是自己的本地项目。
方案2:删除大文件
直接找到 .git 目录下的大文件,将其删除掉,之后推送到远程代码库里面。这样做的前提是,删除所有其他分支,保留 master 或者 main 分支,并清空 git 服务器的当前项目所有分支,重新推送。这里需要注意的是,操作有风险,后果请自负。
- 使用在 git 项目的根目录鼠标右击使用 Git Bash Here 调出 Git 的命令窗口
找出 git项目 中较大的五个提交记录文件
git verify-pack -v .git/objects/pack/pack-*.idx | sort -k 3 -g | tail -5 # 命令说明: # verify-pack 显示已打包的内容(找大文件) # sort -k 3 -g 以第三列排序
执行结果(当前以查询最大的五次做介绍):
dbad6eb20d31a5aefe132b74b2137cd10105c574 blob 16684712 7287784 86721282 6c858bc93421b2db41dafc2bfd4eb82c77c50266 blob 17504576 8257957 47764046 416088453a2514ada98ba639af3ff298510b4246 blob 22216104 10854126 75719527 f0d8d3b476526af42b4e06f390a1b4925580e99b blob 22435760 10589160 16678516 553ba826b92c9d42acb1e586774ac697661588c9 blob 29814552 12998052 60871184
第一行的字母其实相当于文件的 id,用以下命令可以找出 id 对应的文件名
git rev-list --objects --all | grep dbad6eb20d31a5aefe132b74b2137cd10105c574 # 命令说明: # rev-list 列出Git仓库中的所有提交记录 # --objects 列出该提交涉及的所有文件ID # --all 所有分支的提交(位于/refs下的所有引用)
执行结果
dbad6eb20d31a5aefe132b74b2137cd10105c574 Pods/UMengUShare/UShareSDK/SocialLibraries/WeChat/WechatSDK/libWeChatSDK.a
删除大文件记录
git filter-branch --force --prune-empty --index-filter 'git rm -rf --cached --ignore-unmatch Pods/UMengUShare/UShareSDK/SocialLibraries/WeChat/WechatSDK/libWeChatSDK.a' --tag-name-filter cat -- --all # 命令说明 # filter-branch 重写Git仓库中的提交 # --index-filter 指定后面命令进行删除 # --all 所有分支的提交(位于/refs下的所有引用)
上面代码执行完毕后有可能会报以下错误
WARNING: git-filter-branch has a glut of gotchas generating mangled history rewrites. Hit Ctrl-C before proceeding to abort, then use an alternative filtering tool such as 'git filter-repo' (https://github.com/newren/git-filter-repo/) instead. See the filter-branch manual page for more details; to squelch this warning, set FILTER_BRANCH_SQUELCH_WARNING=1. Proceeding with filter-branch... Cannot rewrite branches: You have unstaged changes.
如果出现以上错误可执行以下命令 (不报错则跳过此步)
git stash
然后重新执行移除命令
执行结果
WARNING: git-filter-branch has a glut of gotchas generating mangled history rewrites. Hit Ctrl-C before proceeding to abort, then use an alternative filtering tool such as 'git filter-repo' (https://github.com/newren/git-filter-repo/) instead. See the filter-branch manual page for more details; to squelch this warning, set FILTER_BRANCH_SQUELCH_WARNING=1. Proceeding with filter-branch... Rewrite 967ff01a8b2bc7d7f6c90630ede38a2135ba5813 (1/41) (0 seconds passed, remaiRewrite 9674de7f9f8ed029785421f2246bb9d41786cc49 (2/41) (0 seconds passed, remaiRewrite e04ab331167f24b7d26d69e169386e690081af74 (3/41) (0 seconds passed, remaiRewrite 997e93fdaff75162eda8e5c6ee6d3265ce61fe96 (4/41) (0 seconds passed, Rewrite 429d321e2ee4c54d8d96b35a8e35d021bd8930f1 (35/41) (3 seconds passed, remaining 0 predicted) rm 'Pods/UMengUShare/UShareSDK/SocialLibraries/WeChat/WechatSDK/libWeChatSDK.a' Ref 'refs/heads/master' was rewritten Ref 'refs/remotes/origin/master' was rewritten WARNING: Ref 'refs/remotes/origin/master' is unchanged Ref 'refs/stash' was rewritten
彻底清除
rm -rf .git/refs/original/ # 删除本地仓库引用 git reflog expire --expire=now --all # 设置所有reflog条目现在过期 git gc --prune=now # 回收空间,移除无效或异常的文件
强制推送远程
git push --force --all # 让远程仓库变小 git remote prune origin
方案3:使用 git repo-clean 工具清理(亲测,推荐)
git repo-clean是用 Golang 开发的具备 Git 仓库大文件扫描,清理,并重写 commit 提交记录功能的 Git 拓展工具。
源码及下载安装详见:https://gitee.com/oschina/git-repo-clean
推荐使用二进制包安装,简单方便
使用:
有两种使用方式,一种是命令行,一种是交互式。
目前选项有如下:
-v, --verbose 显示处理的详细过程
-V, --version 显示 git-repo-clean 版本号
-h, --help 显示使用信息
-p, --path 指定Git仓库的路径, 默认是当前目录,即'.'
-s, --scan 扫描Git仓库数据,默认是扫描所有分支中的数据
-f, --file 直接指定仓库中的文件或目录,与'--scan'不兼容
-b, --branch 设置需要删除文件的分支, 默认是从所有分支中删除文件
-l, --limit 设置扫描文件阈值, 比如: '--limit=10m'
-n, --number 设置显示扫描结果的数量
-t, --type 设置扫描文件后缀名,即文件类型
-i, --interactive 开启交互式操作
-d, --delete 执行文件删除和历史重写过程
-L, --lfs 将大文件转换为Git LFS指针文件
命令行式用法:
git repo-clean --verbose --scan --limit=1G --type=tar.gz --number=5
加上 --delete 选项,则会批量删除扫描出的文件,并重写相关提交历史(包括HEAD)
git repo-clean --verbose --scan --limit=1G --type=tar.gz --number=5 --delete
注意:
- 若直接在git项目的根目录进入 bash 命令窗口使用 repo-clean 命令时,显示无 repo-clean 命令,则可以通过左下角【开始】输入 cmd 进入命令窗口,然后使用命令切换至 git 项目的根目录,再使用 repo-clean 命令
- 目前扫描操作和删除操作都是默认在所有分支上进行,而
--branch
选项只是指定删除时的分支,不能指定扫描时的分支。因此如果使用了这个选项指定了某个分支,可能从扫描结果中选择了另一个分支中的文件,因此不会有文件真正被删除。
方案4:使用 migrate 命令优化 .git 目录
迁移已有的 git 仓库,使用 git lfs 来进行管理。重写历史后的提交需执行 git commit --force,请确认在本地的操作合适无误后再进行提交。如有迁移至 git lfs 前的仓库有多份拷贝,其他拷贝可能需要执行 git reset --hard origin/master 来重置其本地的分支,注意执行 git reset --hard 命令将会丢失本地的改动
git lfs migrate :用来将当前已经被 GIT 储存库(.git)保存的文件以 LFS 文件的形式保存
# 重写master分⽀
# 将历史提交(指的是.git目录)中的*.zip都⽤lfs进⾏管理
$ git lfs migrate import --include-ref=master --include="*.zip"
# 重写所有分⽀及标签
# 将历史提交(指的是.git目录)中的*.rar,*.zip都⽤lfs进⾏管理
$ git lfs migrate import --everything --include="*.rar,*.zip"
# 切换后需要把切换之后的本地分支提交到远程仓库了,需要手动push更新远程仓库中的各个分支
$ git lfs push --force
# 切换成功后,GIT仓库的大小可能并没有变化
# 主要原因可能是之前的提交还在,因此需要做一些清理工作
# 如果不是历史记录非常重要的仓库,建议不要像上述这么做,而是重新建立一个新的仓库
$ git reflog expire --expire-unreachable=now --all
$ git gc --prune=now
拓展
使用 lfs 管理大文件
比较好的避免上述问题的出现,就是及时使用 lfs
来追踪、记录和管理大文件。这样大文件既不会污染我们的 .git
目录,也可以让我们更方便的使用。
# 1.开启lfs功能
$ git lfs install
# 2.追踪所有后缀名为“.psd”的文件
$ git lfs track "*.iso"
# 3.追踪单个文件
git lfs track "logo.png"
# 4.提交存储信息文件
$ git add .gitattributes
# 5.提交并推送到GitHub仓库
$ git add .
$ git commit -m "Add some files"
$ git push origin master
同时,还有一个方法,就是灵活使用 .gitignore
文件,及时排除仓库不需要的特殊目录或者文件,从而不会让不应该存在的文件,出现在代码仓库里面
.DS_Store
node_modules
/dist
*.zip
*.tar.gz