如何把公開 GitHub 儲存庫完整鏡像備份到自己的 Private 儲存庫
如果你想長期儲存一個公開 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
這裡有兩個安全點值得保留:
- 目標儲存庫已存在時直接停止。 不要預設覆蓋。
- 本地鏡像目錄已存在時也停止。 先確認它是不是對應同一個上游。
另外,儲存庫的 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 是一條簡單、透明、可驗證的路徑。