如果你想长期保存一个公开 GitHub 仓库,又不想依赖上游仓库一直存在,最直接的办法之一就是:把它完整镜像到自己账号下的 Private 仓库。

这里说的“镜像”不是普通 git clone,而是用 git clone --mirror + git push --mirror 同步 Git refs。它适合做源码级备份,但不等于备份整个 GitHub 项目:Issues、Pull Requests、Release 附件、仓库设置、Secrets 等平台数据不会一起复制;如果仓库用了 Git LFS,也需要额外同步 LFS 对象。

注意: git push --mirror 会让目标仓库的 refs 与本地镜像保持一致,可能强制更新或删除目标端多余 refs。目标仓库最好专门用于镜像备份,不要和日常开发仓库混用。

什么时候适合用镜像备份

这种方式比较适合下面几种场景:

  • 依赖某个公开开源仓库,希望自己保留一份完整 Git 历史。
  • 担心上游仓库删除、改名或转为不可访问。
  • 想定期同步上游分支和标签,但不需要通过 Fork 参与协作。
  • 希望备份仓库保持 Private,只作为自己的恢复副本。

如果你只是想改代码并向上游提交 PR,Fork 更合适;如果只是临时读代码,普通 clone 就够了。

镜像会备份什么,不会备份什么

git clone --mirror 会创建 bare mirror,并配置成跟踪远端 refs。配合 git push --mirror,可以把本地镜像中的 refs 同步到另一个 Git 仓库。

它主要覆盖:

  • Git 提交历史
  • 分支 refs
  • 标签 refs
  • 其他远端可见 Git refs
  • Git 对象

它不会自动复制 GitHub 平台层的数据,例如:

  • Issues
  • Pull Requests
  • Discussions
  • Release 附件
  • Actions Secrets / Variables
  • Webhook
  • Collaborators / 权限
  • Branch protection / Rulesets
  • 仓库可见性设置

另外,Git LFS 文件在 Git 历史中主要保存的是指针,因此 LFS 对象要单独迁移。

前置条件

本机先准备:

  • Git
  • GitHub CLI(gh)
  • 如果源仓库使用 Git LFS,再安装 Git LFS

确认 GitHub CLI 已经登录:

gh auth status
gh auth setup-git

如果尚未登录:

gh auth login

不要把 PAT、密码或其他凭据直接写进仓库 URL、脚本或文档里。优先使用 GitHub CLI 的凭据配置,或者 SSH。

第一次创建 Private 镜像仓库

假设你要备份:

https://github.com/OWNER/REPOSITORY

可以在准备存放本地镜像的目录里执行:

set -euo pipefail

UPSTREAM_URL="https://github.com/OWNER/REPOSITORY.git"
REPO_NAME="REPOSITORY"
ACCOUNT="$(gh api user --jq .login)"
BACKUP_REPO="$ACCOUNT/$REPO_NAME"
BACKUP_URL="https://github.com/$BACKUP_REPO.git"
MIRROR_DIR="$REPO_NAME.git"

if gh repo view "$BACKUP_REPO" >/dev/null 2>&1; then
  echo "Target repository already exists: $BACKUP_REPO"
  exit 1
fi

if [ -e "$MIRROR_DIR" ]; then
  echo "Local path already exists: $MIRROR_DIR"
  exit 1
fi

gh repo create "$BACKUP_REPO" --private

git clone --mirror "$UPSTREAM_URL" "$MIRROR_DIR"

git -C "$MIRROR_DIR" remote add backup "$BACKUP_URL"

git -C "$MIRROR_DIR" push --mirror backup

这里有两个安全点值得保留:

  1. 目标仓库已存在时直接停止。 不要默认覆盖。
  2. 本地镜像目录已存在时也停止。 先确认它是不是对应同一个上游。

另外,仓库的 Private 属性由 GitHub 仓库设置决定,Git push 本身不会改变仓库可见性。

为什么不是普通 clone + push

普通工作区 clone 更偏向日常开发,主要围绕当前分支、远端跟踪分支和工作区文件。

镜像备份的目标不同:我们希望保留远端 refs,并让另一个仓库尽量和上游 Git 状态一致。

git clone --mirror "$UPSTREAM_URL" "$MIRROR_DIR"

创建的目录本身就是 bare repository,没有普通工作区。对于专门做同步和备份的任务,这反而更干净。

后续怎么增量更新

第一次镜像完成后,不需要每次重新 clone。

进入包含 REPOSITORY.git 的父目录,执行:

git -C REPOSITORY.git fetch --prune origin
git -C REPOSITORY.git push --mirror backup

其中:

  • origin:公开上游仓库
  • backup:你自己的 Private 镜像仓库

fetch --prune origin 会拉取上游最新 refs,并清理已经从上游消失的远端 refs;随后 push --mirror backup 再把当前镜像状态同步到目标端。

如果你把镜像统一放在固定目录,也可以直接使用绝对路径:

git -C "$HOME/git-mirrors/REPOSITORY.git" fetch --prune origin
git -C "$HOME/git-mirrors/REPOSITORY.git" push --mirror backup

Git LFS 仓库要额外处理

如果项目用了 Git LFS,只同步 Git refs 不够。

先确认:

git lfs version

在镜像目录中同步 LFS 对象:

git -C REPOSITORY.git lfs fetch --all origin
git -C REPOSITORY.git lfs push --all backup

大仓库执行 --all 可能需要比较长时间,也会消耗目标仓库的 LFS 存储和流量配额。

对于后续更新,可以使用:

git -C REPOSITORY.git fetch --prune origin
git -C REPOSITORY.git lfs fetch --all origin
git -C REPOSITORY.git lfs push --all backup
git -C REPOSITORY.git push --mirror backup

这样做的好处是,在更新目标 refs 前,先尽量确保对应 LFS 对象已经同步过去。

怎么确认备份真的成功

第一步先确认目标仓库确实是 Private:

ACCOUNT="$(gh api user --jq .login)"

gh repo view "$ACCOUNT/REPOSITORY"   --json nameWithOwner,visibility,defaultBranchRef

然后比较上游和备份仓库当前公开的 refs 与对象 ID:

UPSTREAM_URL="https://github.com/OWNER/REPOSITORY.git"
ACCOUNT="$(gh api user --jq .login)"
BACKUP_URL="https://github.com/$ACCOUNT/REPOSITORY.git"

git ls-remote --refs "$UPSTREAM_URL"   | awk '{print $1, $2}'   | sort > /tmp/upstream-refs.txt

git ls-remote --refs "$BACKUP_URL"   | awk '{print $1, $2}'   | sort > /tmp/backup-refs.txt

diff -u /tmp/upstream-refs.txt /tmp/backup-refs.txt

如果 diff 没有输出,并且退出码是 0,说明两边当前列出的 refs 和对象 ID 一致。

对于 LFS 项目,还应该从 Private 仓库实际 clone 或 checkout 一次,确认大文件可以正常下载,而不是只看 Git refs。

push --mirror 最大的坑

最需要记住的一点就是:

Mirror 是“对齐”,不是“追加备份”。

如果目标仓库中存在上游没有的 refs,git push --mirror 可能会把它们删除;如果同名 refs 指向不同对象,也可能被强制更新。

因此最稳妥的原则是:

  • 一个上游仓库对应一个专用 Private 镜像仓库。
  • 不要在镜像仓库里额外开发自己的分支。
  • 不要把镜像仓库当成普通协作仓库。
  • 第一次同步前检查目标是否为空。
  • 自动化前先手工跑通一次完整流程。

如果要自动化,我会怎么做

长期保存多个公开仓库时,可以把更新动作放进计划任务或 CI:

git -C "$MIRROR_DIR" fetch --prune origin

if git -C "$MIRROR_DIR" lfs env >/dev/null 2>&1; then
  git -C "$MIRROR_DIR" lfs fetch --all origin
  git -C "$MIRROR_DIR" lfs push --all backup
fi

git -C "$MIRROR_DIR" push --mirror backup

不过自动化之前,最好再加三层保护:

  • 明确固定允许操作的上游 URL。
  • 明确固定允许推送的备份仓库。
  • 目标仓库不是预期仓库时直接退出。

因为一旦把 push --mirror 放进无人值守任务,最重要的就不是“少写几行脚本”,而是避免它推错目标。

让 CLI AI 帮你执行时,提示词要写清楚边界

如果你习惯让 CLI Agent 或编码助手代执行,可以给它类似这样的任务:

请把我提供的公开 GitHub 仓库完整镜像到当前 GitHub 登录账号下的同名 Private 仓库。

执行要求:
1. 先检查 git、gh 和 gh auth status。
2. 从公开仓库 URL 解析仓库名。
3. 创建目标前检查同名仓库是否已经存在;若已存在,停止并汇报,不覆盖。
4. 检查本地 mirror 目录是否已存在;若目录用途不明确,停止,不删除。
5. 创建空 Private 仓库,不创建 Fork。
6. 使用 git clone --mirror 克隆上游。
7. 添加 backup remote,并执行 git push --mirror backup。
8. 如果仓库使用 Git LFS,同步 LFS 对象。
9. 最后确认目标仓库 visibility=PRIVATE,并比较两边 git ls-remote --refs。
10. 给出以后增量更新使用的 fetch --prune + push --mirror 命令。

安全边界:
- 不打印或索取 Token、密码、Cookie、私钥。
- 不修改公开源仓库。
- 不删除任何已有仓库或未知本地目录。
- 目标仓库已有内容时必须停止。
- 不执行生产部署或其他无关操作。

对 AI Agent 来说,最重要的不是告诉它“把仓库备份一下”,而是明确哪些情况必须停下来。

这套方案的边界

最后再总结一次:这是一套 Git 仓库镜像备份,不是 GitHub 全量灾备。

如果你需要真正完整地保存一个 GitHub 项目,还要另外考虑:

  • Issues / Pull Requests 导出
  • Releases 和附件
  • Wiki
  • Actions 配置与变量
  • 仓库权限和规则
  • Packages / Container images
  • Discussions

但如果你的主要目标是“即使公开仓库以后消失,我仍然保留完整源码历史、分支和标签”,那么 clone --mirror + push --mirror 是一条简单、透明、可验证的路径。

官方参考