git之深入解析如何解决.git目录过大的问题(代码片段)

Serendipity·y Serendipity·y     2022-12-02     431

关键词:

一、前言

  • Git 是一个分布式版本控制软件,最初目的是为更好地管理 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 这个隐藏目录特别大:
➜  du -d 1 -h
680M    ./.git
500K    ./misc
 68K    ./docker
...
1.1G    .
  • 虽然 .git 这个隐藏目录并不算在代码体积之后,但是拉取代码的时候,是需要拉下来的,因为里面包含之前的提交记录等信息,这就会会非常费时费力,因为下载速度变的很慢。

三、问题分析

  • 当使用 git add 和 git commit 命令的过程中,Git 不知不觉就会创建出来 blob 文件对象,然后更新 index 索引,再然后创建 tree 对象,最后创建出 commit 对象,这些 commit 对象指向顶层 tree 对象以及先前的 commit 对象。
  • 而上述创建出来的对象,都以文件的方式保存在 .git/objects 目录下。所以,当在使用的过程中,提交了一个体积特别大的文件,就会被 Git 追踪记录在 .git/objects 文件夹下面。
  • 此时,如果再次删除这个体积特别大的文件,其实 Git 只会记录我们删除的这个操作,但并不会把文件从 .git 文件夹下面真正的删除,即 .git 文件夹完全不会变小。

四、解决方法

① 重建仓库

  • 重建仓库的这种做法,算是一种比较一劳永逸且相对而言比较简单的方式。既然现在的仓库已经让我们无法忍受,与其这样,还不是删除重建来的爽快。但是,这种做法一般情况下,都是不可行,除非是自己的本地项目。

② 删除大文件

  • 直接找到 .git 目录下的大文件,将其删除掉,之后推送到远程代码库里面。这样做的前提是,删除所有其他分支,保留 master 或者 main 分支。
# 查找大文件
$ git verify-pack -v .git/objects/pack/*.idx
12235d...dewaaaa34 tree   135 137 144088922
a453ab...34se212qz blob   3695898 695871 144734158
......

# 筛除前五个且保留第一列
$ git verify-pack \\
    -v .git/objects/pack/*.idx | \\
    sort -k 3 -n | tail -5 | awk 'print$1'
12q626a...23a3
2z32ax1...azfd
......

# 查找出最大的5个文件和对应Commit信息
$ git rev-list --objects --all | \\
    grep "$(git verify-pack -v .git/objects/pack/*.idx | \\
    sort -k 3 -n | tail -5 | awk 'print$1')"
91266a...sdfa3    data/xxx.pkl
232ax1...acafd    data/yyy.pkl
......

# rev-list:    列出Git仓库中的所有提交记录
# --objects:   列出该提交涉及的所有文件ID
# --all:       所有分支的提交(位于/refs下的所有引用)
# verify-pack: 显示已打包的内容(找大文件)
# 将其删除掉
$ git filter-branch \\
    --force --prune-empty --index-filter \\
    "git rm -rf --cached --ignore-unmatch YOU-FILE-NAME" \\
    --tag-name-filter cat -- --all

# filter-branch:  重写Git仓库中的提交
# --index-filter: 指定后面命令进行删除
# --all:          所有分支的提交(位于/refs下的所有引用)
# 强制推送
$ git push --force --all

# 彻底清除
$ rm -rf .git/refs/original/
$ git reflog expire --expire=now --all
$ git gc --prune=now

③ 使用工具清理

  • Github 上有一个叫做 git-filter-branch 的工具,就是帮助我们来清理大文件对象的,其使用 Scala 语言进行编写的,且操作起来也十分方便。只需要简单几步,就可以完成我们的需要,最新版需要确保本地的 java 为 Jdk8+。
# 下载封装好的jar包
$ wget https://repo1.maven.org/maven2/com/madgag/bfg/1.13.0/bfg-1.13.0.jar

# 克隆的时候需要--mirror参数
$ git clone --mirror git://example.com/big-repo.git

# 运行BFG来清理存储库
$ java -jar bfg.jar --strip-blobs-bigger-than 100M big-repo.git

# 去除脏数据
$ cd big-repo.git
$ git reflog expire --expire=now --all
$ git gc --prune=now --aggressive

# 推送上去
# 此推将更新远程服务器上的所有refs分支
$ git push
# 下载封装好的jar包
$ wget https://repo1.maven.org/maven2/com/madgag/bfg/1.13.0/bfg-1.13.0.jar

# 克隆的时候需要--mirror参数
$ git clone --mirror git://example.com/big-repo.git

# 运行BFG来清理存储库
$ java -jar bfg.jar --strip-blobs-bigger-than 100M big-repo.git

# 去除脏数据
$ cd big-repo.git
$ git reflog expire --expire=now --all
$ git gc --prune=now --aggressive

# 推送上去
# 此推将更新远程服务器上的所有refs分支
$ git push

④ 使用 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 来追踪、记录和管理大文件,这样大文件既不会污染 .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

git之深入解析如何使用git调试项目源码中的问题(代码片段)

...暂存区和轻量级地分支及合并的威力。如果想进一步对Git深入学习,可以学习一些Git更加强大的功能,这些功能可能并不会在日常操作中使用,但在某些时候可能还是会起到一定的关键性作用。如果还不清楚Git的基础... 查看详情

git之深入解析如何在应用中嵌入git(代码片段)

...式工作流程等相关的技术和能力,请参考:Git之深入解析Git的安装流程与初次运行Git前的环境 查看详情

git之深入解析rerere重用记录的解决方案(代码片段)

...暂存区和轻量级地分支及合并的威力。如果想进一步对Git深入学习,可以学习一些Git更加强大的功能,这些功能可能并不会在日常操作中使用,但在某些时候可能还是会起到一定的关键性作用。如果还不清楚Git的基础... 查看详情

git之深入解析如何借助git的配置方法和钩子机制来自定义git需求(代码片段)

...式工作流程等相关的技术和能力,请参考:Git之深入解析Git的安装流程与初次运行Git前的环境 查看详情

git之深入解析如何重写提交历史(代码片段)

...暂存区和轻量级地分支及合并的威力。如果想进一步对Git深入学习,可以学习一些Git更加强大的功能,这些功能可能并不会在日常操作中使用,但在某些时候可能还是会起到一定的关键性作用。如果还不清楚Gi 查看详情

git之深入解析如何选择修订的版本(代码片段)

...暂存区和轻量级地分支及合并的威力。如果想进一步对Git深入学习,可以学习一些Git更加强大的功能,这些功能可能并不会在日常操作中使用,但在某些时候可能还是会起到一定的关键性作用。如果还不清楚Git的基础... 查看详情

git之深入解析如何替换数据库中的git对象(代码片段)

...暂存区和轻量级地分支及合并的威力。如果想进一步对Git深入学习,可以学习一些Git更加强大的功能,这些功能可能并不会在日常操作中使用,但在某些时候可能还是会起到一定的关键性作用。如果还不清楚Git的基础... 查看详情

git之深入解析如何使用git的分布式工作流程与如何管理多人开发贡献的项目(代码片段)

...以及托管服务器的相关技术,请参考博客:Git之深入解析Git的安装流程与初次 查看详情

git之深入解析如何运行自己的git仓库托管服务器(代码片段)

一、协议了解了Git的基础使用流程和Git的分支管理之后,我们应该已经有办法使用Git来完成日常的工作。然而,为了使用Git协作功能,还需要有远程的Git仓库。尽管在技术上可以从个人仓库进行推送(push)和... 查看详情

git之深入解析如何交互式暂存(代码片段)

...暂存区和轻量级地分支及合并的威力。如果想进一步对Git深入学习,可以学习一些Git更加强大的功能,这些功能可能并不会在日常操作中使用,但在某些时候可能还是会起到一定的关键性作用。如果还不清楚Git的基础... 查看详情

git之深入解析如何通过gpg签署和验证工作(代码片段)

...暂存区和轻量级地分支及合并的威力。如果想进一步对Git深入学习,可以学习一些Git更加强大的功能,这些功能可能并不会在日常操作中使用,但在某些时候可能还是会起到一定的关键性作用。如果还不清楚Git的基础... 查看详情

github之深入解析如何将项目迁移到git(代码片段)

一、前言如果我们现在有一个正在使用其他VCS的代码库,但是已经决定开始使用Git,必须通过某种方式将项目迁移至Git,那该怎么办呢?Git有一些通用系统的导入器,也可以开发自己定制的导入器,这里将... 查看详情

git内部原理之深入解析环境变量(代码片段)

一、前言Git总是在一个bashshell中运行,并借助一些shell环境变量来决定它的运行方式。有时候,知道它们是什么以及它们如何让Git按照想要的方式去运行会很有用。二、全局行为像通常的程序一样,Git的常规行为依赖... 查看详情

github之深入解析如何在托管在不同系统的项目上使用git客户端(代码片段)

一、前言通常,在开发工作时,不能立刻就把接触到的每一个项目都切换到Git,有时候使我们被困在使用其他VCS的项目中,却希望使用Git。在某些时候,可能我们想要将已有项目转换到Git,那么该怎么处理... 查看详情

git内部原理之深入解析git对象(代码片段)

一、Git的核心部分Git是一个内容寻址文件系统,听起来很酷,但这是什么意思呢?这意味着,Git的核心部分是一个简单的键值对数据库(key-valuedatastore),可以向Git仓库中插入任意类型的内容,它... 查看详情

git之深入解析高级合并(代码片段)

...暂存区和轻量级地分支及合并的威力。如果想进一步对Git深入学习,可以学习一些Git更加强大的功能,这些功能可能并不会在日常操作中使用,但在某些时候可能还是会起到一定的关键性作用。如果还不清楚Git的基础... 查看详情

git之深入解析凭证存储(代码片段)

...暂存区和轻量级地分支及合并的威力。如果想进一步对Git深入学习,可以学习一些Git更加强大的功能,这些功能可能并不会在日常操作中使用,但在某些时候可能还是会起到一定的关键性作用。如果还不清楚Git的基础... 查看详情

git内部原理之深入解析引用规范(代码片段)

一、引用规范在Git使用的过程中,会使用一些诸如远程分支到本地引用的简单映射方式,这种映射可以更复杂。假设现在在本地创建了一个小的Git仓库,现在想要添加一个远程仓库:$gitremoteaddoriginhttps://github.com/sc... 查看详情