2026年8月31日 2 分钟阅读

git3 实战:不搭 Git 服务器,如何把 S3 变成原生 Git 远端

tinyash 0 条评论

很多团队把 Git 远端等同于 GitHub、GitLab 或一台运行 SSH 的裸仓库服务器。但如果需求只是让受控的 CI、内部工具或个人项目共享版本历史,未必需要先搭建一套代码托管平台。git3 提供了另一种思路:通过 Git remote helper,把 s3:// 地址变成 Git 可以直接 clone、fetch 和 push 的远端。

这里的重点不是把整个 .git 目录同步到对象存储。Git 仍然负责生成和校验对象、packfile 以及引用;git3 负责把这些数据组织成对象存储上的仓库协议,并用条件写入发布引用变化。于是,S3 的耐久存储和访问控制可以成为 Git 的后端,而不是一个容易产生竞态的文件同步目录。

项目仓库创建于 2026 年 8 月 29 日,当前主语言是 Go,许可证为 Apache-2.0;最新 release 为 v0.0.5。它很新,适合做受控环境中的架构试用,不宜直接宣传成成熟代码托管平台的替代品。

先把 Git remote helper 装好

git3 的 README 要求 Git 2.38 或更高版本,并提供 Linux、macOS 的 amd64 与 arm64 构建。官方安装脚本如下:

curl -fsSL https://github.com/robertpitt/git3/releases/latest/download/install.sh | sh

如果要从源码构建,README 给出的关键步骤是生成三个名称相同内容的入口,其中 git-remote-s3 是 Git 识别 s3:// 远端时需要找到的 helper:

go build -o git3 ./cmd/git3
ln -s git3 git-s3
ln -s git3 git-remote-s3

这几个文件需要位于 PATH 中。当前项目没有 Windows release binary;如果团队依赖 Windows 开发机,应先确认自行构建和运行环境是否可接受,而不是把 Linux 安装命令直接复制过去。

创建 bucket 和配置 AWS 凭据仍然是前置工作。git3 不会替你创建 bucket,也不应把密钥写进 remote URL。它可以沿用 AWS credential chain,例如通过 profile 选择账号:

AWS_PROFILE=development git clone s3://my-bucket/repos/example

也可以把已有仓库指向一个 bucket 中的 prefix。首次推送会在这个 prefix 下建立 git3 仓库,但 bucket 本身必须已经存在:

git remote add origin s3://my-bucket/repos/example
git push -u origin HEAD:refs/heads/main

S3 上到底保存了什么

git3 的协议设计把仓库数据分成两类。pack 和事务记录属于不可变数据;一个较小、可变的 HEAD 文档则指向当前可见的仓库状态。一次 push 先准备并上传不可变对象,最后通过对 HEAD 的条件写入发布新状态。这个最后的发布点很重要:并发客户端不会简单地“最后一个写入者覆盖前一个目录”,而是要在 compare-and-swap 语义下检查自己看到的旧状态。

这也解释了为什么普通的 aws s3 sync .git s3://... 不是等价方案。同步目录缺少 Git 引用发布的原子边界,可能把尚未完整上传的 pack 暴露给读取者,也无法自然表达 force-with-lease 需要的旧值检查。git3 还将 fetch 设计为基于已验证 cursor 获取增量;拉下来的 pack 会进入本地 .git/objects/pack,即使以后移除 git3,普通的 git logcheckoutfsckrepack 仍可在本地仓库上工作。

README 明确支持完整 clone、普通 push、--force--force-with-lease,并保留 SHA-1、SHA-256 仓库、签名 commit/tag、atomic push 和 submodule 等 Git 语义。这里应注意“支持协议”与“提供托管平台能力”是两回事:它没有 Web UI、PR/review、服务端 hook、分支保护、内建 LFS,也不支持 shallow 或 partial clone。

因此,git3 的适用边界很清楚:它是 Git 的存储与同步层,不是一个 forge。需要代码审查、团队权限界面和 issue 管理时,仍应使用 GitHub、GitLab 或 Gitea。

用最小 IAM 权限开始

git3 的 IAM 文档把读取、写入和 GC 权限分开。只读 clone/fetch 可以只授予目标 prefix 下的 s3:GetObject,并不需要给每个开发者 bucket 列表权限:

{
  "Effect": "Allow",
  "Action": ["s3:GetObject"],
  "Resource": "arn:aws:s3:::BUCKET/PREFIX/.git/git3/*"
}

写入者需要增加 s3:PutObject 和 multipart 上传中止权限:

{
  "Effect": "Allow",
  "Action": [
    "s3:GetObject",
    "s3:PutObject",
    "s3:AbortMultipartUpload"
  ],
  "Resource": "arn:aws:s3:::BUCKET/PREFIX/.git/git3/*"
}

只有执行垃圾回收的维护身份才应额外拥有 s3:DeleteObject,以及限定在目标 prefix 的 s3:ListBucket。如果 bucket 使用 SSE-KMS,还要按照实际 key policy 配置加密、解密和生成数据密钥权限。最稳妥的做法是把读者、推送者、维护者和 CI 角色分开,而不是为了省事给所有人一个管理员凭据。

对 S3 兼容服务,官方配置支持 GIT3_ENDPOINT;需要 path-style addressing 时设置 GIT3_PATH_STYLE=true

export GIT3_ENDPOINT=https://s3.example.internal
export GIT3_PATH_STYLE=true
git clone s3://team-bucket/repos/example

兼容服务必须满足强 read-after-write、单 key 条件写入与删除、Range 请求、multipart upload 以及相容的 S3 错误语义。只有“看起来像 S3”的 API 并不足以承载这个协议,接入 MinIO 或其他服务前应先在测试 bucket 中验证并发 push、断点恢复和对象读取行为。

维护、垃圾回收与恢复

先用诊断命令检查远端状态,再安排压缩和回收:

git s3 doctor origin
git s3 fsck origin --full
git s3 maintenance origin
git s3 gc origin                 # 只预览
git s3 gc origin --execute --older-than 30d

上面的最后一条命令在实际粘贴时应去掉行首多余空格。git3 的配置文档给出的默认维护参考阈值是 32 个 transaction 或 128 MiB WAL;维护应从完整、非 shallow clone 执行。GC 不是“直接扫描后删除”:operations 文档描述了 plan、barrier、重新验证候选对象和条件删除的步骤。计划中断后可以用 --resume 继续,或用 --abort 清理对应 barrier。

如果需要回滚,先启用 S3 bucket versioning。灾难恢复时不能只恢复 HEAD:应先恢复所选 HEAD 引用的全部不可变对象,最后再恢复 HEAD,然后执行完整 fsck。否则读取者可能先看到一个指向缺失 pack 的发布状态。这个顺序是对象存储后端最容易被忽略、却最关键的运维边界。

用 HEAD 事件触发 CI,但别把事件当快照

git3 的 S3 events 文档给出了一条很实用的集成链:

git push -> S3 .git/git3/HEAD -> Lambda -> CodeBuild -> git clone s3://BUCKET/PREFIX

S3 通知规则应监听仓库的 HEAD 对象,使用 s3:ObjectCreated:Put,并把 suffix 设为 HEAD。不要监听每个 pack,因为 pack 还没有完成发布时就可能触发构建;HEAD 才是 git3 已经提交新状态的发布点。

不过,S3 事件是至少一次投递且不保证严格顺序。Lambda 或 CodeBuild 启动后应 clone 当前远端状态,而不是假定自己拿到的是触发事件对应的精确 commit。连续 push 还可能造成并发构建;若需要缓冲、去重或死信处理,应在 S3 和启动器之间加入 SQS 或 EventBridge。事件触发适合做“有新状态就唤醒 CI”,不适合直接充当精确的构建队列。

并发推送前先做一个小型演练

在把远端交给 CI 之前,最好不要只验证“能不能 clone”。至少准备两个工作目录,从同一提交创建两个不同分支或提交,然后同时执行 push,观察冲突是否被拒绝;再测试 --force-with-lease,确认旧状态发生变化后,过期的 lease 不会悄悄覆盖新提交。随后在测试 bucket 中删除一个明显的临时对象,运行 git s3 fsck origin --full,确认损坏能被发现,而不是等到下一次构建才暴露。

还要记录对象存储的生命周期规则。Git 历史里的 pack 不是普通日志文件,不能仅凭对象年龄删除;未被当前 HEAD 直接显式引用的对象,也可能仍被事务记录或其他引用间接需要。GC 应由 git3 的 plan 和 barrier 流程执行,S3 生命周期策略最多处理明确隔离的临时上传对象。对重要仓库启用 versioning 后,保留一份恢复演练记录:包括 bucket、prefix、选定 HEAD 版本、恢复顺序、fsck 输出和回滚责任人。

成本和延迟也值得在试用阶段量化。小提交可能带来多次对象请求,跨区域 CI 还会增加传输延迟;如果开发者频繁交互式 fetch,传统 Git 服务器可能更快。相反,备份、批处理构建和内部网络中的共享仓库,往往更能接受对象存储的访问模式。不要只比较“是否省掉一台服务器”,还要把请求费、跨区域流量、维护时间和故障恢复时间放进同一张评估表。

最后的判断

git3 值得尝试的地方,是它没有把 Git 简化成文件上传,而是把不可变对象、事务记录和 HEAD 条件发布组合成一个面向对象存储的远端协议。个人项目、内部备份、受控 CI 输入、私有 S3 兼容存储,都是合理的试用场景。

但它目前仍是 v0.x 的新项目。没有 shallow clone、LFS、服务端 hook、分支保护和 PR 界面,意味着它不能替代完整 forge;S3 费用、延迟、IAM 配置和 GC 运维也会成为新的责任边界。建议先创建隔离 bucket,用只读和单独写入身份跑通 clone、并发 push、fsck、maintenance 与恢复演练,再决定是否接入真实 CI。把它当作一个可审计的 Git 存储层,而不是“免费 GitHub”,才是更准确的预期。

相关链接

发表评论

你的邮箱地址不会被公开,带 * 的为必填项。