如果你想長期儲存一個公開 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 是一條簡單、透明、可驗證的路徑。

官方參考