# 公開 GitHub リポジトリを自分の Private リポジトリへ完全ミラーする方法

> Canonical: https://vxi.me/ja/posts/github-public-repo-private-mirror-backup/
> Locale: ja-jp
> Published: 2026-10-05
> Updated: 2026-10-05
> Markdown: https://vxi.me/ja/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 添付、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 のログイン状態を確認します。

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

未ログインなら、

```bash
gh auth login
```

を使います。

PAT、パスワード、Cookie、秘密鍵をリポジトリ URL やスクリプトへ直接書かない方が安全です。GitHub CLI の credential 設定か 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. **ローカル mirror ディレクトリがすでにあれば停止する**

どちらも「とりあえず上書き」はしません。

なお、Private / Public は GitHub リポジトリ側の設定です。`git push` 自体が visibility を変更するわけではありません。

## なぜ普通の clone + push ではないのか

通常の clone は日常開発向けで、作業ツリーと現在の branch を中心に扱います。

ミラーの目的は、上流の Git refs をできるだけそのまま別の Git リポジトリへ保存することです。

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

で作るディレクトリは bare repository で、普通の作業ツリーはありません。バックアップ専用タスクなら、その方が構造が明確です。

## 2 回目以降の増分同期

初回ミラー後は毎回 clone し直す必要はありません。

```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 を使っているなら、refs の同期だけでは足りません。

まず確認します。

```bash
git lfs version
```

その後、mirror 内で LFS オブジェクトを取得し、backup 側へ送ります。

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

更新時は例えば次の順番です。

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

`--all` は大規模リポジトリでは時間がかかり、GitHub LFS のストレージ / 転送量も消費する点に注意します。

## 本当に同期できたか確認する

まず対象が Private であることを確認します。

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

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

次に上流と backup の refs / object 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 と object ID は一致しています。

LFS がある場合は、Private 側から実際に clone / checkout して、大きなファイルをダウンロードできることまで確認した方が安心です。

## push --mirror で最も危険な点

覚えておくべきことは一つです。

> Mirror は「追加バックアップ」ではなく「状態を一致させる操作」です。

対象にしかない refs は削除される可能性があります。同名 ref が別 object を指していれば強制更新される可能性もあります。

そのため私は次を固定ルールにしています。

- 一つの上流につき一つの専用 Private mirror
- mirror 側で独自 branch を開発しない
- 普通の共同開発リポジトリとして使わない
- 初回 push 前に対象が空であることを確認する
- 自動化する前に一度手動で通す

## 自動化するなら安全条件を増やす

複数の公開リポジトリを長期保存するなら、cron や 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 先の backup repository
- 対象が想定外なら即終了

`push --mirror` では、スクリプトを短くすることより、**誤ったリポジトリへ push しないこと**の方が重要です。

## CLI Agent に依頼する場合の境界

AI エージェントに作業させるなら、「バックアップして」だけでは足りません。

例えば次のように停止条件まで書きます。

```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 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` はシンプルで検証しやすい方法です。

## 公式資料

- [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)
