Watch at Ease看见世界,读懂生活。

科技 · 实用指南

Git LFS 克隆后只剩指针文件?重克隆前先核对属性与对象

克隆后的工作树里出现指针文本,只是症状,不是根因。先保留本地改动、识别准确 LFS 对象,再把属性和过滤器配置与下载、认证、本地对象及 checkout 故障分开判断,然后才考虑重克隆或重置。

Git LFS 小型指针通过属性与对象身份核对后解析为受保护大文件的原创概念图
原创概念图:改动工作树前,先核对指针、属性与准确对象身份。 Watch at Ease

Git LFS 在 Git 中保存小型指针,把大对象放在另一套存储中。工作树里出现指针,可能是检出文件时尚未安装 Git LFS、主动跳过了 smudge、属性规则没有匹配该路径、对象没有抓取成功,或 checkout 为了保护已修改文件而没有替换内容。它本身不能证明仓库损坏。

最安全的诊断顺序,是沿着对象经过指针、属性、过滤器、本地缓存、远端抓取和工作树 checkout 的路径逐层核对。每一层产生的证据和修法都不同。盲目重克隆既可能重复同一配置错误,也可能浪费带宽并丢掉有用的本地现场。

本指南以只读核对为主:不改写历史、不运行 hard reset 或 clean、不绕过凭据,也不删除现有克隆。最终交付是一份简短证据记录和最小安全下一动作。

1. 保留工作树,并确认看到的确实是指针

先运行 `git status --short` 并保存输出。测试前,把任何经过本地编辑、看起来像指针的文件和未跟踪工作复制到仓库外。诊断期间不要运行 `git reset --hard`、`git clean`、历史迁移,也不要删除克隆。

只读打开异常的小文本文件。正常的 Git LFS v1 指针包含规范行、`oid sha256:` 行和声明的 `size`。记录相对路径、OID 与大小。如果文件只是看起来像指针,却无法按格式解析,应归入单独的内容损坏分支。

若当前版本支持,可运行 `git lfs pointer --check --file="PATH"`,也可以与文档中的指针结构核对。不要把仓库凭据、签名下载网址或私有远端细节放进公开证据。

2. 核对这条路径是否真的受 LFS 属性管理

在受影响的提交中运行 `git check-attr -a -- "PATH"`。对已由 LFS 跟踪的路径,检查 Git 实际读取到的 filter、diff、merge 与 text 属性。仅凭工作树文件名,不能证明当前属性规则已匹配。

检查 Git 中对应版本的 `.gitattributes`,不要只看尚未提交的本地副本。属性模式依赖路径;上游 FAQ 还指出,跨平台差异可能暴露错误模式,或让在跟踪规则生效前提交的历史文件表现不同。

如果该提交中的路径没有 LFS filter 标记,就先停止。只安装 Git LFS 或抓取对象,并不能单独修复属性不匹配。必须另行制定变更方案,确认应修改哪些文件和提交,再考虑 renormalization 或 migration。

3. 记录本地 Git LFS 过滤与选择状态

记录 `git lfs version` 和已脱敏的 `git lfs env`。确认当前用户和仓库已安装 Git LFS,并记录创建工作树时是否使用了 `GIT_LFS_SKIP_SMUDGE=1`、`git lfs install --skip-smudge` 或等价过滤设置。

不要先假定所有 LFS 对象都应下载,应先检查 include 与 exclude 规则。pull 手册说明了路径过滤,而 partial clone 或 sparse checkout 也可能有意限制进入工作树的路径。

同时记录 sparse-checkout 状态和所有 partial-clone filter。已有 Git LFS issue 展示了 partial checkout 与 include 过滤相关的真实“只剩指针”现象,但只有在属性、过滤器、抓取和 checkout 等常规边界都核对后,才应考虑该特定回归分支。

4. 把抓取对象与检出对象分开判断

`git lfs pull` 组合了 fetch 与 checkout;单独的 `git lfs checkout` 不会下载缺失内容,只会使用本地 LFS 存储中已有的对象替换符合条件的指针。这解释了为何反复 checkout 可能毫无变化。

在大范围下载前,先针对受影响引用与路径做限定抓取。例如核对仓库布局后运行 `git lfs fetch REMOTE REF --include="PATH"`,并记录准确的远端、引用与第一条脱敏错误。

若 fetch 明确返回认证、授权、对象缺失或配额错误,就停在该边界。普通 Git 克隆成功,并不能证明独立的 LFS 对象端点也接受了请求,或仍保存着该 OID。

5. checkout 前先核对本地存储中的准确 OID

抓取成功后,确认本地 LFS 存储中存在与记录的 SHA-256 OID 对应的对象,而且字节长度等于指针声明的 size。应使用 Git LFS 命令和仓库诊断,不要根据同名文件猜测。

适合当前仓库时,运行 `git lfs fsck --objects` 并保留输出。本地对象大小或摘要不符,属于完整性问题;本地完全没有该对象,则仍是抓取或远端可用性问题。

不要因为文件名相同,就把任意二进制文件覆盖到指针上。指针 OID 才是内容身份。正确 checkout 必须解析到准确对象,而且不改动无关文件。

6. 让 checkout 保护改动,再解释结果

确认准确对象已在本地后,运行路径限定的 `git lfs checkout -- "PATH"`。checkout 手册说明它不会覆盖已修改文件。因此,如果指针样式的文件与索引不同,没有变化可能是主动保护,而非 Git LFS 安装失败。

对照 `git status --short`、`git diff -- "PATH"` 与索引中记录的指针。恢复干净占位符前,先备份所有本地修改,并判断差异究竟是用户工作、换行损坏,还是异常 checkout 结果。

checkout 成功后,确认工作树文件已不再被解析为指针,大小与声明值一致;在常规 Git LFS 布局允许时,再核对其摘要与 OID。

证据 | 失败边界 | 最小安全动作 路径没有 LFS filter 属性 | 属性 | 另行修正并审核跟踪规则,不要盲目抓取 未安装 LFS,或有意跳过 smudge | 过滤器安装 | 安装或配置 LFS,再做限定 pull fetch 返回认证或对象缺失错误 | 远端访问或对象可用性 | 修复访问或恢复准确 OID,不必重克隆 对象已在本地,但 checkout 留下已修改指针 | 工作树保护 | 备份改动、比较索引,再做路径限定 checkout 其他检查通过后,只有 partial/sparse 路径失败 | 检出选择或回归 | 升级为 issue 前,用准确版本和最小过滤条件复现

7. 验证恢复,并整理可交给支持人员的证据

验收条件必须具体:指针能解析,OID 与声明大小已记录;fetch 后本地存在同一 OID;工作树路径包含解析后的内容;无关改动保持不变。最后再用预期应用成功打开文件,作为实用层面的补充检查。

提交支持请求时,请包括已脱敏的 Git 与 Git LFS 版本、操作系统、指针 OID 前缀与大小、`check-attr` 结果、过滤器和 include/exclude 状态、远端名称、fetch 结果、本地对象结果、sparse 或 partial checkout 状态,以及路径限定 checkout 结果。观察与结论必须分开写。

只有在属性、配置、远端访问、对象存在和修改保护都已排除后,最小复现仍失败,才应上报可能的版本特定回归。不要把所有只剩指针的 checkout 都归为同一个已报告 issue。

常见问题

Git 克隆成功,为什么 Git LFS 文件仍没有下载? Git 仓库与 LFS 对象服务是两个边界。即使未安装 LFS、跳过 smudge、路径被排除、凭据失败或远端对象缺失,Git 仍可能成功克隆指针文件。

`git lfs checkout` 会下载缺失对象吗? 不会。checkout 只使用本地 LFS 存储中已有的对象。缺失对象必须先由 fetch 或 pull 取得。

应该运行 `git reset --hard` 来替换指针吗? 诊断阶段不要这样做。hard reset 可能丢掉无关工作,也不能证明所需 LFS 对象可用。先记录状态、属性、OID 与抓取证据。

看到指针文本是否表示文件损坏? 不一定。指针本来就是正常仓库数据。问题在于工作树没有填入与之匹配的大对象,而多种不同边界都可能造成该状态。

Git LFS 从远端抓取对象到本地缓存,再受保护地检出到工作文件夹的原创概念图
原创概念图:fetch 负责取得对象;checkout 是保护已修改文件的独立步骤。 Watch at Ease