git,

给你的Git目录剪剪枝 - reflog + repack + prune

DolorHunter DolorHunter Follow May 10, 2020 · 15 mins read

给你的Git目录剪剪枝 - reflog + repack + prune
Share this

好景不长, 距离上次玩坏 Git 只过了大概半年多, 今天我又把他给玩坏了.

翻车现场

起因是这周 lib-hfut 更新的文件比较多, 添加文件的时候一不留神混进去了一个 103MB 的参考书籍. 因为 GitHub 限制单个文件最大为 100MB, push 到 GitHub 的时候就出了问题.

按照以往的经验, 我习惯先 reset 一下然后把头指针撤回到上个 commit, 然后把有问题的文件拿掉再重新 add, commit, push 就可以了. 这样的问题我已经处理过很多次了, 这并不是什么难事.

但是这一次并没有那么幸运, 不知道我触发了什么, 导致后续的 commit 一直无法进行.

当时的情况大概长这样:

$ git commit
error: inflate: data stream error (incorrect header check)
error: unable to unpack a94406345ac44982b00cf57b4b9660a35436637f header
fatal: a94406345ac44982b00cf57b4b9660a35436637f is not a valid object

其实我不太清楚是怎么回事, 根据在 StackOverflow 上看到的其他人的描述反推, 我猜有可能是因为之前 commit 的时候, 因为发现有个文件正在占用, 于是就把正在执行 commit 的 Git 强制退出, 从而导致这个 commit 只完成了一半.(但是之前强制退出从未发生过这个状况, 因此我也不太确定是不是这个原因)

简单 Google 了一下, 进入了以上的链接. 排名第一的答案蛮简单的, 只让我把损坏的 object 删掉就行了. 我想说快 70 个赞同的答案, 应该没什么问题, 然后就跟着他的提示直接开始 rm -f.

提示: rm 的时候一定要先备份!!!

把目录删除后, 重新 commit. 没想到 Git 直接跳出错误信息.

fatal: unable to read a5b7cd1d601a1b29c1d6c23ed166ce7bd0454fe1
error: failed to run repack

commit 显示的内容正是刚刚被我删掉的那个. 一时间真是头皮发麻, 心想怎么那么多人都能好好解决, 就我出状况. 真是气气气气气气气.. 又没有备份一下, 处理难度直线上升.

喔 气气气气气气气

可能是因为一般人都不会去 rm .git/object/ 里面的东西, 所以当我搜索 Failed to run repack 的时候并没有找到比较直接的解决方法. 先是在 GitHub 上看了一个解决办法发现并没有什么用, 兜兜转转, 最后又回到了 StackOverflow, 这次是一个完全文不对题的搜索结果.

这时候有些丧气, 已经在计划说如果还是失败了, 大不了把这个分支删除了, 新建一个分支重头再 commit, 顺便还能把 .git 变小一些.

创建无父节点的分支命令

$ git checkout --orphan

救命稻草

索性这个回答写的很详细, 甚至还包括了出现问题的原因和其他的解决方法. 写的挺好的就把内容贴上来. 原文 How can I clean my .git folder? Cleaned up my project directory, but .git is still massive

  • If you added the files and then removed them, the blobs still exist but are dangling. git fsck will list unreachable blobs, and git prune will delete them.
  • If you added the files, committed them, and then rolled back with git reset –hard HEAD^, they’re stuck a little deeper. git fsck will not list any dangling commits or blobs, because your branch’s reflog is holding onto them. Here’s one way to ensure that only objects which are in your history proper will remain:
$ git reflog expire --expire=now --all
$ git repack -ad  # Remove dangling objects from packfiles
$ git prune       # Remove dangling loose objects
  • Another way is also to clone the repository, as that will only carry the objects which are reachable. However, if the dangling objects got packed (and if you performed many operations, git may well have packed automatically), then a local clone will carry the entire packfile:
git clone foo bar                 # bad
git clone --no-hardlinks foo bar  # also bad

You must specify a protocol to force git to compute a new pack:

$ git clone file://foo bar  # good

回答首先回答了处理一般情况的解法: if blobs exist but are dangling, git fsck to list unreachable blobs and git prune will delete them

$ git fsck

这个命令是用来驗證數據庫中對象的連接性和有效性。算是一个颇为常见的检错手段, 应该作为常见命令记录下来. 详见 Git - fsck

$ git prune

git prune 和 git gc 其实是一个命令, Git 手册不建议用户使用这个命令(虽然在StackOverflow上也看到了很多使用这个命令的人). 它可以刪除任何引用都無法訪問和无法打包的條目。详见 Git - prune

这个方法我在蛮多的回答也有看到. 但是他说并不能解决一些深度阻塞, 例如使用了 git reset –hard HEAD^ 回退之类的问题. 很不幸, 我面临的就是深度阻塞. 但是他说可以使用后面的那三个命令可以解决所有的问题.

$ git reflog expire --expire=now --all
$ git repack -ad  # Remove dangling objects from packfiles
$ git prune       # Remove dangling loose objects

git reflog 具有非常多的子命令, 虽然我见过他很多次但是还是不晓得怎么用. expire 是到期的意思, 整个命令会修剪所有在指定时间之前的 reflog 条目(Prune entries older than the specified time). --expire=all 意思是修剪所有條目,無論其年齡如何. 详见 Git - reflog

git repack 此命令用於將當前不在“包”中的所有對象(object)組合到一個包中。 它也可以用於將現有包重新組織為一個更有效的包。-ad 情况下現有包中的所有無法訪問的對像都將附加到packfile的末尾,而不是被刪除。详见 Git - repack

git prune 可以刪除任何引用都無法訪問和无法打包的 object 條目. 之前讲过了就不再重复.

新知总结

总结一下三个命令:

  1. reflog 修剪 reflog 条目
  2. repack 重新打包 object
  3. prune 修剪无效的 object

這是確保僅保留您歷史上適當的對象的一種方法. 这个方法比较保守, 而且不太可能对已有的记录造成破坏, 也能用来清理过大的 .git 目录, 因此有必要学起来. 至于他提到的第二个方案, 就先当拿来储备知识好了.

虽然这次翻车花了不少的时间处理, 但是趁机学了几个用于管理的 Git 命令, 也算是因祸得福吧.

Join Newsletter
Get the latest news right in your inbox. We never spam!
DolorHunter
Written by DolorHunter
Developer & Independenet Blogger