# 如何把公开 GitHub 仓库完整镜像备份到自己的 Private 仓库

> Canonical: https://vxi.me/posts/github-public-repo-private-mirror-backup/
> Locale: zh-cn
> Published: 2026-10-05
> Updated: 2026-10-05
> Markdown: https://vxi.me/posts/github-public-repo-private-mirror-backup.md

如果你想长期保存一个公开 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 已经登录：

```bash
gh auth status
gh auth setup-git
```

如果尚未登录：

```bash
gh auth login
```

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

## 第一次创建 Private 镜像仓库

假设你要备份：

```text
https://github.com/OWNER/REPOSITORY
```

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

```bash
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 状态一致。

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

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

## 后续怎么增量更新

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

进入包含 `REPOSITORY.git` 的父目录，执行：

```bash
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` 再把当前镜像状态同步到目标端。

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

```bash
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 不够。

先确认：

```bash
git lfs version
```

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

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

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

对于后续更新，可以使用：

```bash
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：

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

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

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

```bash
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：

```bash
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 或编码助手代执行，可以给它类似这样的任务：

```text
请把我提供的公开 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` 是一条简单、透明、可验证的路径。

## 官方参考

- [GitHub Docs：Duplicating a repository](https://docs.github.com/en/repositories/creating-and-managing-repositories/duplicating-a-repository)
- [GitHub CLI：gh repo create](https://cli.github.com/manual/gh_repo_create)
- [Git：git push](https://git-scm.com/docs/git-push)
- [Git LFS：git lfs fetch](https://github.com/git-lfs/git-lfs/blob/main/docs/man/git-lfs-fetch.adoc)
- [Git LFS：git lfs push](https://github.com/git-lfs/git-lfs/blob/main/docs/man/git-lfs-push.adoc)
