git 仓库修复不完全指南

git 仓库损坏的事件在本地电脑上鲜有发生,毕竟除非正在操作 git 的时候突然断电/蓝屏/硬盘升天,一般不会损坏到 git 仓库里存储的对象。

但在 T 厂 Xodespaces 却不是这样的:实例默认规格是 8C16G,然而厂里不少长须鲸级前端项目需要 24~32G 内存才可以稳定运行,于是 VSCode 可能在开发中某一刻默默被 OOM Killer 鲨凋了。T 厂的研发只是感觉到 Workbench 掉线,刷新页面却发现 git 怎么操作都会报错,绝望地在 on-call 系统上开工单:

怎么会是呢?

如果你运行 git 命令但是输出像下面这样,恭喜你中奖了,git 仓库某些文件已经损坏了。

fatal: not a git repository (or any of the parent directories): .git
error: object file .git/objects/xx/xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx is empty
fatal: loose object  (stored in .git/objects/xx/xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx) is corrupt
fatal: unable to read tree xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
fatal: failed to read object xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx: Invalid argument
fatal: index file corrupt

发生损坏的时候每个案例可能在执行不同的命令,损坏了不同类型的 git 对象,所以网上搜索到的命令不一定能修复你遇到的问题,甚至有些还会让你丢失数据或者进一步损坏仓库。

我修过不少损坏的仓库,每个案例坏掉的地方都不太一样,但大部分情况都是可以找回损失的。由于工作上是别人的仓库我需要尽可能无损修复,有时候会难以实现。但是自己的仓库自己清楚哪些 (e.g. 索引index, stash) 可以不要了,或者所有修改都已经 push 到远端,那么重新 git clone 可能是最快的方法,后面的内容就不需要看了(

1. 备份

要是仓库里还有未完成的工作也先不要慌,首先把目录里的 .git 文件夹1备份一下。

为了防止 reset/checkout 之类的操作影响到没有提交的代码,还可以备份整个目录,或者用 git worktree 来创建一个专门用来恢复文件的目录:

# git 2.15+
git worktree add $path $branch
# git 2.15+
git worktree add $path $branch

备份很重要,如果修复过程中误操作可能会导致没有上传的代码丢失。

2. 找到问题

现在再学习 git 内部的实现已经来不及了,但是让我们极速并且极简地了解一下 git 仓库内部的相关知识。

一般来说,.git 仓库会有下面这些文件夹2,其中的任何文件都可能因为「故障发生时正在写入」损坏:

.git
├── COMMIT_EDITMSG #使用第三方工具(IDE,VIM)填写 commit message 的文件
├── config # git 配置,这里也包含 remote 的 push/pull url 和本地-远端分支的追踪信息
├── FETCH_HEAD # 最后一次 fetch 到的 commit 指针
├── HEAD # 当前 git 指向的 commit/ref
├── hooks # 进行特定 git 操作之前/之后运行的脚本
├── index # git add 的暂存区
├── info # 仓库额外信息,例如本地 exclude 的文件,gitattributes,sparse checkout pattern 等
├── logs # git 指针切换日志(切换分支、rebase、merge 等)
   ├── HEAD
   └── refs
├── objects # 实际存储的文件历史和目录结构
   ├── 00
   ├── ...
   ├── ff
   ├── info
   └── pack
├── ORIG_HEAD
└── refs # 存储 ref 文件
    ├── heads # 每个分支的最新 commit
    ├── remotes # 远端分支的最新 commit
    ├── stash # git stash 产生的临时 commit
    └── tags # tag 指针
.git
├── COMMIT_EDITMSG #使用第三方工具(IDE,VIM)填写 commit message 的文件
├── config # git 配置,这里也包含 remote 的 push/pull url 和本地-远端分支的追踪信息
├── FETCH_HEAD # 最后一次 fetch 到的 commit 指针
├── HEAD # 当前 git 指向的 commit/ref
├── hooks # 进行特定 git 操作之前/之后运行的脚本
├── index # git add 的暂存区
├── info # 仓库额外信息,例如本地 exclude 的文件,gitattributes,sparse checkout pattern 等
├── logs # git 指针切换日志(切换分支、rebase、merge 等)
   ├── HEAD
   └── refs
├── objects # 实际存储的文件历史和目录结构
   ├── 00
   ├── ...
   ├── ff
   ├── info
   └── pack
├── ORIG_HEAD
└── refs # 存储 ref 文件
    ├── heads # 每个分支的最新 commit
    ├── remotes # 远端分支的最新 commit
    ├── stash # git stash 产生的临时 commit
    └── tags # tag 指针

3. 动手修复

有一些文件大概率无法恢复,比如 logs 下面的 ref 变更记录;还有一些不会影响继续使用,比如 COMMIT_EDITMSG,这里就不再细数了。需要关注的是两类文件:objectref,其中 object 有三种:

  • blob: 存储单个文件,只有文件内容
  • tree: 存储目录结构,列表里包含本目录下的子 blob/tree 和它们的 hash,权限,文件名
  • commit: 存储提交信息,记录 commit 对应的 tree,上一个 commit,提交人等
  • tag: 存储带附注的标签信息,记录对应的 commit SHA、标记人和日期、注释等信息

每个「对象名」都由其内容的 hash 确定3,某个 hash 为 fd36f9494e2c202c823cdc0f25a6d2e48d352a2b 的对象可能在 .git/objects/fd/36f9494e2c202c823cdc0f25a6d2e48d352a2b 下,或者在某个 pack 中没有解压出来。

ref 也有几种:

  • tag: 标签,指向 commit(轻量标签)/tag object(带附注的标签) 的指针
  • branch: 指向对应的本地和远程分支的最新 commit(称作 "tip")
  • HEAD: 当前 git 的工作指针,一般是指向某个分支(也就是另一个 ref)的指针 e.g. ref: refs/heads/main 或者某个 commit (detatched head)

ref 本质上只有一类:一个指针,要么指向一个 object(ref 文件内容为 hash)要么指向另一个 ref(文件内容为 ref: path/to/ref),所有的 ref 文件不管放在哪个路径都是这样的格式。

和 object 类似,ref 也有可能被打包 .git/packed-refs 里来节约空间,如果 .git/refs 中找不到某个 ref,它可能在压缩包里。

git objects
git objects

git 的 object 和 ref 之间的「引用」关系如上图,下文提到「指向」或者「引用」时就是指这个文件里面包含目标的 hash(或者对于 HEAD 来说可能是分支对应的 ref 文件路径)

3.1 尝试用工具自动修复

个人推荐一个叫 git-repair 的自动修复工具。

使用 apt 直接安装 Ubuntu | Debian

Hackage 安装 | 下载 Haskell 源码 编译

  • 会删掉所有损坏的 object 文件,然后从 remote 获取缺失的 object
  • 如果本地仍有没有上传到远端的 object 无法完全自动恢复,可以使用 --force 参数尝试把分支重置到损坏之前(会导致 commit 丢失)
  • 只能修复 object 损坏的错误,无法修复 logs, stash, tags 等文件的损坏
  • 但包含几十万到上百万个 object 的大型仓库可能会逐渐变慢最后卡住4

如果你最近 push 过的话有很大概率可以通过这个工具完全修复损坏的文件,运行后再 git fsck 检查如果没有提示错误,那么仓库此时已经可以正常使用了。

就算不能完全修复我也会先使用工具处理一遍,毕竟能修多少算多少,而且 git-repair 会删掉损坏的文件并且解压所有压缩的对象到对应的目录,省掉手动修复的一部分步骤

3.2 无法使用 git-repair 需要手动进行的操作

如果仓库无法使用 git-repair,则需要手动进行一些额外的步骤:

  1. 删除损坏的 object

最常见的情况损坏的文件都是没有正常完成写入的 object 造成的5,这些文件里面没有内容,找到这些损坏的文件然后删掉:

find .git/objects/ -type f -size 0 -delete -print
find .git/objects/ -type f -size 0 -delete -print
  1. 解压对象包

前面提到 git 可能会压缩一些文件到 pack 中来节省磁盘空间,但如果 git 在压缩的时候出错(比如写入时发生比特反转)就会产生损坏的 pack 文件,另外压缩的格式也不方便我们手动把好的副本存到 git 的数据库

我们需要把 pack 文件里面尽可能多的对象先解压出来。使用 git unpack-objects -r 可以解压一个 pack 文件中的每个对象到单独的文件里,这些没有被压缩的文件叫稀疏对象loose object

但它只会解压没有在当前 git 仓库里出现的对象,所以在 unpack 之前还需要先把仓库里现有的 pack 文件移到别的地方

# `$path` 替换成自选目录,只要不在 .git/objects 下面就行
mv .git/objects/pack/* $path
for i in $path/*.pack; do
    git unpack-objects -r < $i
done
rm $path/*
# `$path` 替换成自选目录,只要不在 .git/objects 下面就行
mv .git/objects/pack/* $path
for i in $path/*.pack; do
    git unpack-objects -r < $i
done
rm $path/*

3.3 分析报错信息

在开始手动修复之前,我们需要知道到底有哪些东西损坏了。git 仓库的所有数据都是以文件的形式存储在 .git 目录的,运行 git fsck --full 可以检查仓库完整性,找到有问题的 git 对象。

如果 git fsck 的报错信息里有这样的内容的话,说明可能有 object 文件损坏了

  • dangling <blob/tree/commit/tag> <hash>

dangling commit 是没有任何分支指向它的 commit,同理 dangling blob/tree 是没有被任何分支引用的文件/文件夹,dangling tag 是没有指向任何现有分支上的 commit 的 tag

有小概率可能是指向该 blob/tree/commit 的 object/ref 损坏,但大多数时候可能是 git add/git stash/git rebase/git commit --amend... 产生的悬空对象,不需要太担心。后面或许会从这些悬空对象中恢复数据

  • missing <blob/tree/commit/tag> <hash>

某个对象引用了这个对象,但是这个 hash 的对象并不存在。在前面我们已经删掉所有损坏的文件让他们变成丢失状态,这些对象就是要修复的文件,我们得从某个地方(远端、当前仓库工作区里的文件)恢复回去,§3.4 手动修复损坏的 object 介绍了每种对象可能的修复方式

  • broken link <from_type> <hash> <to_type> <hash>

一般是跟上面的 missing <type> <hash> 一起出现的,如果一个 blob 损坏,引用这个 blob 的 tree 就会产生一条这样的报错

  • error: failed to read object <hash> at offset <offset> from .git/objects/pack/pack-<hash>.pack

pack 文件里面的某个 object 损坏,按照 3.2 的方法解压损坏的 pack 文件

  • fatal: object <hash> is corrupted

object 文件损坏,有按照 3.2 的方法删掉损坏的对象

此外还有一些别的损坏也会导致 git 无法使用,直接对着报错消息在文章里面搜索,请

3.4 手动修复损坏的 object

然后根据损坏的对象类型和是否存在备份,有不同的策略:

3.4.1 远端上存在副本

git-repair 可以自动修复这种损坏,如果不能使用的话也可以手动操作。

重新 git clone 一遍,然后把新仓库的目录里 .git/objects/pack/ 里的文件复制到旧仓库里(比如一个叫 git_packs 的文件夹),然后在旧仓库里运行

for i in $git_packs/*.pack; do
    git unpack-objects -r < $i
done
for i in $git_packs/*.pack; do
    git unpack-objects -r < $i
done

这是在从 pack 文件里面批量恢复对象,剩下的不在 pack 里的对象需要逐个手动修复。

再次进行 fsck 扫描或者运行之前报错的命令,对于每个报错的对象名 $hash 都进行下面的操作

# 检测新 clone 的仓库是否有副本,这个命令会输出 object 的类型
# 输出的 $type 可能是 blob, tree, commit, tag 之一
git cat-file -t $hash
 
# 如果输出的不是 `fatal:... bad file`,说明远端有备份
# 把它恢复到文件里面,$hash[2:] 是 hash 去掉头两位
# 比如 git cat-file tree xxyyyyyyyyyyyyyy > yyyyyyyyyyyyyy
git cat-file $type $hash > $hash[2:]
# 存回旧仓库的对象数据库
cp $hash[2:] $old_repo/.git/objects/$hash[0:2]/$hash[2:]
 
# 另一种恢复方法,不需要手动创建文件夹复制到对应路径
git cat-file -p $hash > tempfile
# 在文件复制到旧仓库,然后运行
git hash-object -w tempfile
# 检测新 clone 的仓库是否有副本,这个命令会输出 object 的类型
# 输出的 $type 可能是 blob, tree, commit, tag 之一
git cat-file -t $hash
 
# 如果输出的不是 `fatal:... bad file`,说明远端有备份
# 把它恢复到文件里面,$hash[2:] 是 hash 去掉头两位
# 比如 git cat-file tree xxyyyyyyyyyyyyyy > yyyyyyyyyyyyyy
git cat-file $type $hash > $hash[2:]
# 存回旧仓库的对象数据库
cp $hash[2:] $old_repo/.git/objects/$hash[0:2]/$hash[2:]
 
# 另一种恢复方法,不需要手动创建文件夹复制到对应路径
git cat-file -p $hash > tempfile
# 在文件复制到旧仓库,然后运行
git hash-object -w tempfile

这样这个对象就恢复完成,重复直到所有可以从远端恢复的 blob 都写入到损坏的仓库。

如果 git 仍然不能用,那么你麻烦大了

3.4.2 没有远端备份的 blob 损坏

非常不幸,你的 blob 没有远端备份。

blob 只包含文件内容,但是我们不知道它是哪个文件。如果 tree 没有损坏的话,我们可以从 fsck 里面从引用它的信息里面找到一些信息

broken link from tree 4bf4869299b294be9dee4ecdcb45d2c204ce623b
            to   blob d193ccbc48a30e8961e9a2515a708e228d5ea16d

在这里,tree c218a6e8cd2299f14bd3c2322e85f71ef23a41d7 引用了损坏的 blob d193ccbc48a30e8961e9a2515a708e228d5ea16d。然后我们获取 tree 的内容:

# git cat-file -p $tree_hash
$ git cat-file -p c218a6e8cd2299f14bd3c2322e85f71ef23a41d7
100644 blob a6d435f7c26d1e8f6e8b592a1263e9572019f935    .gitignore
100644 blob c87e0421d2214fac22bccfb3a0106a0f469c99a0    README.md
100644 blob d193ccbc48a30e8961e9a2515a708e228d5ea16d    index.js
...
# git cat-file -p $tree_hash
$ git cat-file -p c218a6e8cd2299f14bd3c2322e85f71ef23a41d7
100644 blob a6d435f7c26d1e8f6e8b592a1263e9572019f935    .gitignore
100644 blob c87e0421d2214fac22bccfb3a0106a0f469c99a0    README.md
100644 blob d193ccbc48a30e8961e9a2515a708e228d5ea16d    index.js
...

我们可以看到损坏的文件叫 index.js,并且根据目录下的其他文件的名字找到这个 tree 对应的是项目根目录,于是我们使用 git hash-object 检查工作区里面已经 checkout 出来的文件是否可以用来修复损坏的对象

# git hash-object $path
git hash-object -w index.js
d193ccbc48a30e8961e9a2515a708e228d5ea16d
# git hash-object $path
git hash-object -w index.js
d193ccbc48a30e8961e9a2515a708e228d5ea16d

如果输出的 hash 跟损坏的 blob hash 一致,git 应该已经把这个文件编码成 blob 对象存储到数据库里了,这个 blob 文件就算修复完成。

有少数情况,工作区在损坏 git 仓库之后又修改了那个文件,如果 hash 不一致,那你的手牌就快打完了 😨

一些流行的 IDE 提供文件保存历史的功能,比如 VSCode 在文件列表下方提供了时间线Timeline,Jetbrains 的 Intellij 系列 IDE 提供了本地历史Local History,这些是在 git 之外额外存储的版本历史6,如果你记得在损坏之前改过/提交过哪些文件的话,打开那些文件,切回之前 commit 的版本,然后用 hash-object 碰碰运气吧

3.4.3 没有远端备份的 tree 损坏

比 blob 损坏更麻烦的是 tree 损坏了而且没有 push 到远端,能不能修只能看运气,只有很少一部分情况可以恢复出来。我没修过这样的案例,如果有也会建议 §4 有损恢复

这里只能提供一些无损恢复的思路:每个 tree 文件记录的是对应的目录下的 tree/blob 的 hash 以及它们的文件名,所以要恢复 tree 我们只能从引用它的其他对象获得这个 tree 的相关信息。

检查 fsck 的报错里面有没有某个 commit/tree 指向损坏的 tree,如果有那或许还能尝试救一下

# 损坏的是某个子目录的 tree
broken link from tree c8563b4d16e02d7e3a0521ee35a99c9f610408af
            to   tree df084ac4214f1a981481b40080428950865a6b31
 
# 损坏的是对应 commit 的根目录对应的 tree
broken link from commit a37c9abfaed7e7929f37d5eb46c46b5c8827c3d4
            to   tree b1bf5829f9f0beec5bccd6fcc2c194f5b9667256
# 损坏的是某个子目录的 tree
broken link from tree c8563b4d16e02d7e3a0521ee35a99c9f610408af
            to   tree df084ac4214f1a981481b40080428950865a6b31
 
# 损坏的是对应 commit 的根目录对应的 tree
broken link from commit a37c9abfaed7e7929f37d5eb46c46b5c8827c3d4
            to   tree b1bf5829f9f0beec5bccd6fcc2c194f5b9667256

通过 git cat-file -p $from_hash 找到这个损坏的 tree 应该对应哪个目录,回忆一下目录可能长什么样,然后尽可能把文件恢复到「损坏的 tree 里面记录的状态」。但是你不知道损坏的 tree 里面应该记录的是每个文件的哪个状态,这就是 tree 为什么几乎无法无损修复。

由于 tree 损坏了,这个目录下面改动的文件全都会变成 dangling blob/tree,可以灵活使用 git fsck --lost-found 把所有悬挂dangling对象恢复到 .git/lost-found/other 目录下,然后在里面找「或许是那个 tree 记录的对象」,记住这个对象的 hash 和文件名

收集到所有(或许是)损坏的 tree 的目录下的对象就可以开始手动构造 tree 了

# 为了方便先找到前一个 commit 的相同目录对应的 tree 对象减少工作量
# 列出来本分支历史 commit
git rev-list --all
# 对第二个也就是上上次 commit 的 hash 运行,一般之前的 commit 状态是好的
git ls-tree $hash
# 如果损坏的 tree 不是根目录,把 $hash 替换成路径重复运行 ls-tree
# 直到找到对应目录的上一个 commit tree 状态
# 为了方便先找到前一个 commit 的相同目录对应的 tree 对象减少工作量
# 列出来本分支历史 commit
git rev-list --all
# 对第二个也就是上上次 commit 的 hash 运行,一般之前的 commit 状态是好的
git ls-tree $hash
# 如果损坏的 tree 不是根目录,把 $hash 替换成路径重复运行 ls-tree
# 直到找到对应目录的上一个 commit tree 状态

使用 ls-tree 列出来上个 commit 对应的 tree 状态之后我们会得到一个这样的输出,每一行对应一个该目录下面的对象

040000 tree 116279e76d259680c358ab3d3e2feea10d8d7adc    .github
100644 blob 2e32d157bfccbb8f08c0356eebf4cbd614869c6d    .gitignore
100644 blob c87e0421d2214fac22bccfb3a0106a0f469c99a0    README.md
...

创建一个新文件把内容复制进去,把其中当前 commit 修改过的所有文件的 hash (前面记下来的 hash)替换进去,得到或许是那个状态的 tree 内容。注意 hash 和文件名中间是 '\t' 一个 tab 字符不是四个空格,需要禁用掉编辑器的「用空格缩进」和「文件末尾自动添加换行」功能并把复制下来的空格改回 tab

然后用 git mktree 创建 tree 对象

cat $tree_file | git mktree
cat $tree_file | git mktree

如果 hash 是损坏的 tree 的 hash 的话就修复完成了,否则只能重新排列组合那个损坏的 tree 的目录结构重试

剩下的情况只能 §4 有损恢复

3.4.4 没有远端备份的 commit 损坏

损坏的 commit 可以直接删掉,除非它有 GPG 签名而且作者不是你。之后把引用这个 commit 的 ref 修复一下 git 就可以继续正常使用。

如果 commit 对应的 tree 没有损坏的话,可以直接重新用对应的 tree 生成 commit。在 fsck 的报错中找到有哪些 dangling tree 的 hash,每个 dangling tree 都有可能是损坏的 commit 对应的根目录。

  • 在 worktree 或者复制的项目的文件夹里面运行 git checkout $tree_hash . 把 tree 的内容读出来,检查下里面的内容是不是你要的 commit 的文件列表,如果是就重新 commit 一下

  • 或者更简单地,用 git diff $tree_hash $previous_commit_hash 判断修改的文件内容是不是损坏的 commit 的

3.5 修复其他类型的损坏

3.5.1 损坏的 ref 文件

ref 文件比 object 文件简单很多,只需要重新填入一个有效的 commit hash。

  • HEAD 损坏

HEAD 损坏会导致 git 无法识别到 git 仓库,

fatal: not a git repository (or any of the parent directories): .git

运行 git reflog 或者从 .git/logs/HEAD 文件从操作历史找到最后一次的值,写回 .git/HEAD

或者手动给 .git/HEAD 写入当前指向的分支 ref: refs/heads/$branch

  • branch 损坏

.git/logs/refs/heads/$branch 文件里找到最后一次操作的 commit,用 git branch --force $branch $commit 或者手动把 commit hash 写回 .git/refs/heads/$branch,重置分支指针到那个 commit 上。

  • tag 损坏

.git/refs/tags 找到损坏的 tag 删掉然后重新 git tag

  • stash 损坏

stash 分为两部分,一个是 .git/refs/stash 指向最新一次 stash commit 的指针,一个是 .git/logs/refs/stash 下面 stash ref 的变动记录,也就是历史创建的 stash。

同上找到 logs 里 stash 最后一次操作的 stash commit 然后写入回 .git/refs/stash 即可。

3.5.2 修复损坏的 index

index 是 git add暂存区staging area,发生损坏会报错 fatal: index file corrupt

可以直接删掉,然后 git reset 或者 git read-tree $tree_hash 重建索引。

rm -f .git/index
git reset
rm -f .git/index
git reset

4. 有损恢复

如果自动修复之后仍然不能使用,并且你不想太麻烦/有无法修复的对象,可以接受丢失一些不太重要的数据让 git 能够继续使用的话

先用 git-repair --force 试试

删掉那些损坏对象,然后把所有引用损坏对象的地方删掉。

# 删掉空文件
find .git/objects/ -type f -size 0 -delete -print
find .git/refs/ -type f -size 0 -delete -print
find .git/logs/ -type f -size 0 -delete -print
 
# 清理掉 reflog 里指向损坏对象的记录
# 修复 invalid reflog entry <hash>
git reflog expire --stale-fix --all
# 清空 dangling objects
git gc --prune
# 清空 stash,如果是 stash 损坏的话
git stash clear
# 删掉空文件
find .git/objects/ -type f -size 0 -delete -print
find .git/refs/ -type f -size 0 -delete -print
find .git/logs/ -type f -size 0 -delete -print
 
# 清理掉 reflog 里指向损坏对象的记录
# 修复 invalid reflog entry <hash>
git reflog expire --stale-fix --all
# 清空 dangling objects
git gc --prune
# 清空 stash,如果是 stash 损坏的话
git stash clear

删完之后 ref 可能会指向无效的对象然后 fsck 报错:

error: HEAD: invalid sha1 pointer c6492f7ad72197e2fb247dcb7d9215035acdca7f
error: refs/heads/main does not point to a valid object!

例如上面的输出,我们不打算恢复 c6492f7ad72197e2fb247dcb7d9215035acdca7f 这个 main 分支指向的 commit 了,按照 §3.5.1 修复分支的方式处理,但操作变成重置到 logs 里面倒数第二个 commit

Finally

某位 git contributor 老板提到可以开启 core.fsync=all7 降低写坏的概率。我给 Xodespaces 的环境启用了但是不确定有没有效果,因为同时我们也给厂里面那些大型仓库增加了默认分配的内存减少了 OOM 的情况。可以图个安心打开试试

一开始本来想写贵厂 Xodespaces git 烂掉的千奇百怪的姿势的,不知道为什么就变成教程了。

希望不会帮到你。

Take care of yourself and be well.


  1. 在 IDE 的文件管理器里面通常是隐藏的,可以用命令行操作

  2. 不完全列表,.git 目录下可能还有一些基本不会用到的和废弃的文件/文件夹,参见 Git Repository Layout

  3. 实际上是 <type> <size>\0<content> 比如 SHA1("blob 5\0hello") = "b6fc4c620b67d95f953a5c1c1230aaab5db5a1b0"

  4. 我猜可能与需要 unpack 的文件太多了有关,看不懂 Haskell 所以分析不来

  5. git 打开文件的时候使用了 O_TRUNC,并且默认没有 fsync

  6. 在 VSCode 保存到了 <user-data-dir>/User/History 下,Intellij 则是保存到了 <data>/JetBrains/<product><version>/LocalHistory,都不受 git 仓库损坏影响

  7. git 2.36 以下为 core.fsyncObjectFiles=true,但不完全等价

Loading New Comments...