git lfs 上传必须严格遵循两步协议:先调用 /objects/batch 获取上传地址与认证头,再按返回信息流式上传;oid 必须为小写 sha256、size 精确匹配,且上传 url 有时效性。

Git LFS 协议不是 HTTP 文件上传那么简单
直接用 HttpClient POST 一个大文件到 Git LFS endpoint 几乎必然失败——LFS 不是普通表单上传,它走的是两步协议:先向 LFS server 发起 POST /objects/batch 拿到上传地址和验证信息,再按返回的 actions.upload.href 和 header 执行真实上传。跳过 batch 步骤,服务器会直接返回 404 Not Found 或 412 Precondition Failed。
常见错误现象:
• 上传后 Git LFS 检出时提示 smudge filter lfs failed
• git lfs pull 报错 Object does not exist on the server,但文件明明已 push
• 用 curl -X PUT 硬传成功,但 Git 客户端后续无法校验(SHA256 不匹配)
- 必须严格使用 Git LFS v2 的
batchAPI,不能伪造或省略oid、size、authentication字段 - 上传前需本地计算文件 SHA256(不是 MD5),且必须与
git lfs pointer中的oid完全一致 - 上传 URL 带有时效性(通常 5–10 分钟),超时后需重新 batch 获取
C# 调用 LFS batch API 的关键参数构造
Git LFS server 的 /objects/batch 接口要求 JSON body 包含 operation("upload" 或 "download")、objects 数组,每个 object 必须有 oid(小写十六进制 SHA256)和 size(字节数)。漏掉 accept header 或用错 content-type,会返回 406 Not Acceptable。
实操建议:
- Header 必须包含:
Accept: application/vnd.git-lfs+json、Content-Type: application/vnd.git-lfs+json -
oid必须是小写,长度 64 位(SHA256),可用System.Security.Cryptography.SHA256.HashData计算后转ToLower() - 若服务启用了认证(如 GitHub/GitLab),需在 header 加
Authorization: Basic xxx或Bearer xxx,不能只靠 cookie - 示例 body:
{"operation":"upload","objects":[{"oid":"a1b2c3...","size":10485760}]}
上传大文件时避免内存爆炸和超时
用 HttpClient.PutAsync 直接传 byte[] 会把整个文件加载进内存,1GB 文件就崩。而且默认 timeout 是 100 秒,LFS 上传常超时。
正确做法是流式上传 + 显式 timeout:
- 用
FileStream包裹文件,传给HttpContent构造函数,避免File.ReadAllBytes - 设置
HttpClient.Timeout = TimeSpan.FromMinutes(30)(根据网络调整) - 上传 URL 的 header 必须包含 batch 返回的全部
header字段(比如X-Auth-Token、Content-Type),缺一不可 - 别忽略响应状态码:非
200 OK或202 Accepted都要中止,不能靠 try-catch 吞掉
下载 LFS 对象本质是 GET 重定向,但要注意指针解析
Git LFS 下载不走 batch,而是读取工作区的 LFS pointer 文件(内容形如 version https://git-lfs.github.com/spec/v1\noid sha256:a1b2c3...\nsize 10485760),提取 oid 后拼出下载 URL:{lfs_base_url}/objects/{oid[0:2]}/{oid[2:4]}/{oid}。但这个 URL 通常是 302 重定向到 CDN 或对象存储直链。
容易踩的坑:
- 没处理重定向:
HttpClient默认跟随,但某些私有 LFS server 禁用 redirect,需手动HttpClientHandler.AllowAutoRedirect = false并解析Locationheader - 指针文件路径不对:C# 里不能硬写
.git/lfs/objects/...,应调用git lfs pointer --file <path></path>或解析工作区对应文件的实际内容 - 没校验下载完整性:下载后必须重新计算 SHA256 并比对 pointer 中的
oid,否则可能拿到损坏或中间人篡改的数据
复杂点在于,不同 Git 服务商(GitHub、GitLab、Azure DevOps)的 LFS endpoint 路径、认证方式、重定向策略都不一样,没法写一套代码通吃;最易被忽略的是 pointer 文件的换行符——Windows 是 \r\n,Linux 是 \n,解析时要用 Split(new[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries),不然 oid 取出来带空格就校验失败。










