最近在使用jenkins对某个服务进行编译的时候发现,docker镜像文件非常大,使用du命令查看,发现.git目录中objects目录占用空间非常大,竟然达到100多MB。

直接进入编译目录删除这个大文件之后进行编译,这个文件居然又下载下了了,直觉发现这个必须在git上删除。

.git介绍

当你创建一个仓库的时候,使用 git init 指令, git 将会创建一个神奇的目录:.git。这个目录下包含了所有 git 正常工作所需要的信息。说白一点,如果你想从你的项目中删除 git 但是又要保留项目文件,只需要删除 .git 文件夹就可以了。

git目录结构:
在这里插入图片描述

  • 这个文件包含你仓库的设置信息。例如这里会放你远程仓库的 URL,你的 email 地址,你的用户名等等信息。 每次你在控制台使用“git config…”指令时,修改的就是这里。

  • 这是一个有意思的特性。Git 提供了一系列的脚本,你可以在 git 每一个有实质意义的阶段让它们自动运行。这些脚本就是 hooks,可以在 commit/rebase/pull…. 的前后运行。脚本的名字表示它什么时候被运行。例如一个有用的预推送 hook 可能会测试关于保持远程仓库一致性的式样原则。

  • 你可以把你不想让 git 处理的文件放到 .gitignore 文件里。那么,exclude 文件也有同样的作用,不同的地方是它不会被共享,比如当你不想跟踪你的自定义的 IDE 相关的配置文件时,即使通常情况下 .gitignore 就足够了(如果你用到了这个请在评论中告诉我)。

commit 的真相

每一次你创建一个文件并跟踪它会发现,git 会对其进行压缩然后以 git 自己的数据结构形式来存储。这个压缩的对象会有一个唯一的名字,即一个哈希值,这个值存放在 object 目录下。

在探索 object 目录前,我们先要问自己 commit 到底是何方神圣。commit 大致可以视为你工作目录的快照,但是它又不仅仅只是一种快照。

实际上,当你提交的时候,为创建你工作目录的快照 git 只做了两件事:

如果这个文件没有改变,git 仅仅只把压缩文件的名字(就是哈希值)放入快照。
如果文件发生了变化,git 会压缩它,然后把压缩后的文件存入 object 目录。最后再把压缩文件的名字(哈希值)放入快照。

一旦快照创建好,其本身也会被压缩并且以一个哈希值命名。那么所有的压缩对象都放在哪里呢?答案是object 目录。

Git 维护着一个微型的文件系统,其中的文件也被称作数据对象。所有的数据对象均存储于项目下面的 .git/objects中。

commit 包含四个部分:

  • 工作目录快照的哈希
  • 提交的说明信息
  • 提交者的信息
  • 父提交的哈希值

只要我有一次将一个大文件误提交了,那么即使我后面把它删除了,但是,实际上在.git中,这个文件还是存在的,虽然我们可能再也不需要他了,但是他还在那里默默的存在着。。。

Git与大部分版本控制系统的差别是很大的,比如Subversion、CVS、Perforce、Mercurial 等等,使用的是“增量文件系统” (Delta Storage systems), 就是说它们存储每次提交(commit)之间的差异。Git正好与之相反,它会把你的每次提交的文件的全部内容(snapshot)都会记录下来。这会是在使用Git时的一个很重要的理念。

就是说,如果我又一次把一个大文件务提交到git仓库中了,那么,下次提交时,即使你只改动了某个文件的一行内容,Git 也会生成一个全新的对象来存储新的文件内容。

解决问题

  1. 查看哪些历史提交过文件占用空间较大

git rev-list --objects --all | grep “$(git verify-pack -v .git/objects/pack/*.idx | sort -k 3 -n | tail -5 | awk ‘{print$1}’)”

  • rev-list命令用来列出Git仓库中的提交,我们用它来列出所有提交中涉及的文件名及其ID。 该命令可以指定只显示某个引用(或分支)的上下游的提交。

  • –objects:列出该提交涉及的所有文件ID。

  • –all:所有分支的提交,相当于指定了位于/refs下的所有引用。

  • verify-pack命令用于显示已打包的内容

  1. 重写commit,删除大文件
    使用以下命令,删除历史提交过的大文件:

git filter-branch --force --index-filter ‘git rm -rf --cached --ignore-unmatch big-file.jar’ --prune-empty --tag-name-filter cat – --all

上面脚本中的big-file.jar请换成你第一步查出的大文件名,或者这里直接写一个目录。

  • filter-branch命令可以用来重写Git仓库中的提交
  • –index-filter参数用来指定一条Bash命令,然后Git会检出(checkout)所有的提交, 执行该命令,然后重新提交。
  • –all参数表示我们需要重写所有分支(或引用)。

如果显示 xxxxx unchanged, 说明repo里没有找到该文件, 请检查路径和文件名是否正确,重复上面的脚本,把所有你想删除的文件都删掉。

  1. 推送修改后的repo
    以强制覆盖的方式推送你的repo, 命令如下:

git push origin master --force

  1. 清理和回收空间
    虽然上面我们已经删除了文件, 但是我们的repo里面仍然保留了这些objects, 等待垃圾回收(GC), 所以我们要用命令彻底清除它, 并收回空间,命令如下:

rm -rf .git/refs/original/
git reflog expire --expire=now --all
git gc --prune=now

参考资料
记一次删除Git记录中的大文件的过程
Git 内部原理

Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐