公開 GitHub リポジトリを自分の Private リポジトリへ完全ミラーする方法
公開 GitHub リポジトリを長期保存したいなら、自分のアカウント配下に専用の Private ミラーを作る方法があります。
ここでいう「ミラー」は通常の git clone ではありません。git clone --mirror と git push --mirror を使い、Git refs をできるだけそのまま別リポジトリへ同期します。
これはソースコードレベルのバックアップとして便利ですが、GitHub プロジェクト全体の完全バックアップではありません。Issues、Pull Requests、Release 添付、Repository Settings、Secrets などの GitHub プラットフォーム側データは別です。Git LFS を使っている場合は LFS オブジェクトも追加で同期する必要があります。
注意:
git push --mirrorは「追加」ではなく「同期」です。対象リポジトリにだけ存在する refs を削除したり、同名 refs を強制更新したりできます。バックアップ専用の空 Private リポジトリを使うのが安全です。
どんな用途に向いているか
この方法は次のようなケースに向いています。
- 依存している OSS の完全な Git 履歴を手元に残したい
- 上流が削除、改名、非公開化されてもコードを保持したい
- 上流の branch / tag を定期同期したい
- Fork として開発参加するのではなく、復旧用コピーを持ちたい
コードを変更して上流へ PR を送りたいなら Fork の方が自然です。単に読むだけなら通常の clone で十分です。
mirror で保存されるもの / されないもの
git clone --mirror は bare mirror repository を作成し、遠隔 refs を追跡する設定も含めます。
主に保存できるのは次です。
- Git commit 履歴
- branch refs
- tag refs
- その他の遠隔 Git refs
- Git object
一方、次は自動では入りません。
- Issues
- Pull Requests
- Discussions
- Release 添付
- Actions Secrets / Variables
- Webhook
- Collaborators / 権限
- Branch protection / Rulesets
- Repository visibility
Git LFS の大きなファイルも、Git 側には pointer が保存されるため実体を別途転送します。
事前準備
ローカルには次を用意します。
- Git
- GitHub CLI (
gh) - 必要なら Git LFS
GitHub CLI のログイン状態を確認します。
gh auth status
gh auth setup-git
未ログインなら、
gh auth login
を使います。
PAT、パスワード、Cookie、秘密鍵をリポジトリ URL やスクリプトへ直接書かない方が安全です。GitHub CLI の credential 設定か 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
ここでは二つの停止条件を重要視しています。
- 同名の対象リポジトリがすでにあれば停止する
- ローカル mirror ディレクトリがすでにあれば停止する
どちらも「とりあえず上書き」はしません。
なお、Private / Public は GitHub リポジトリ側の設定です。git push 自体が visibility を変更するわけではありません。
なぜ普通の clone + push ではないのか
通常の clone は日常開発向けで、作業ツリーと現在の branch を中心に扱います。
ミラーの目的は、上流の Git refs をできるだけそのまま別の Git リポジトリへ保存することです。
git clone --mirror "$UPSTREAM_URL" "$MIRROR_DIR"
で作るディレクトリは bare repository で、普通の作業ツリーはありません。バックアップ専用タスクなら、その方が構造が明確です。
2 回目以降の増分同期
初回ミラー後は毎回 clone し直す必要はありません。
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 を使っているなら、refs の同期だけでは足りません。
まず確認します。
git lfs version
その後、mirror 内で LFS オブジェクトを取得し、backup 側へ送ります。
git -C REPOSITORY.git lfs fetch --all origin
git -C REPOSITORY.git lfs push --all backup
更新時は例えば次の順番です。
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
--all は大規模リポジトリでは時間がかかり、GitHub LFS のストレージ / 転送量も消費する点に注意します。
本当に同期できたか確認する
まず対象が Private であることを確認します。
ACCOUNT="$(gh api user --jq .login)"
gh repo view "$ACCOUNT/REPOSITORY" --json nameWithOwner,visibility,defaultBranchRef
次に上流と backup の refs / object 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 と object ID は一致しています。
LFS がある場合は、Private 側から実際に clone / checkout して、大きなファイルをダウンロードできることまで確認した方が安心です。
push —mirror で最も危険な点
覚えておくべきことは一つです。
Mirror は「追加バックアップ」ではなく「状態を一致させる操作」です。
対象にしかない refs は削除される可能性があります。同名 ref が別 object を指していれば強制更新される可能性もあります。
そのため私は次を固定ルールにしています。
- 一つの上流につき一つの専用 Private mirror
- mirror 側で独自 branch を開発しない
- 普通の共同開発リポジトリとして使わない
- 初回 push 前に対象が空であることを確認する
- 自動化する前に一度手動で通す
自動化するなら安全条件を増やす
複数の公開リポジトリを長期保存するなら、cron や 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 先の backup repository
- 対象が想定外なら即終了
push --mirror では、スクリプトを短くすることより、誤ったリポジトリへ push しないことの方が重要です。
CLI Agent に依頼する場合の境界
AI エージェントに作業させるなら、「バックアップして」だけでは足りません。
例えば次のように停止条件まで書きます。
指定した公開 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 object も同期する。
9. visibility=PRIVATE を確認し、両側の git ls-remote --refs を比較する。
10. 次回更新用の fetch --prune + push --mirror コマンドを提示する。
安全条件:
- Token、パスワード、Cookie、秘密鍵を表示・要求しない。
- 公開元リポジトリを変更しない。
- 既存リポジトリや不明なローカルディレクトリを削除しない。
- 対象リポジトリに既存内容がある場合は停止する。
- 関係ない本番デプロイを実行しない。
エージェントに重要なのは、作業内容だけでなく「どの状態なら止まるか」を伝えることです。
この方法の境界
これは Git repository mirror backup であり、GitHub 全体の disaster recovery ではありません。
完全な GitHub プロジェクト保全が必要なら、さらに次を考える必要があります。
- Issues / Pull Requests
- Releases / 添付
- Wiki
- Actions 設定と variables
- Repository permissions / rules
- Packages / Container images
- Discussions
ただ、目的が「公開リポジトリが将来消えても、ソース履歴、branch、tag を自分で保持したい」なら、clone --mirror + push --mirror はシンプルで検証しやすい方法です。