メンテナンス版、2026-09-01確認。 2019年の短いコマンドメモは末尾に原文のまま保存しています。ここでは、1つの論理リポジトリに対する代替Push URLと、実体が異なる2つのホストを区別し、2宛先へのPushを単一の原子的トランザクションではなくレプリケーションとして扱います。
Table of Contents
2026年メンテナンスガイド
Gitは、1つのリモートに設定されたすべてのpushurlへPushできます。そのため元の記事の方法は動作しますが、「同時」という表現は誤解を招きます。Gitは宛先ごとに接続するので、一方が成功した後でも、もう一方が失敗し得ます。独立した2つのホスティングサービスへ送るなら、別々の名前付きリモートにする方が通常は明確で安全です。
例ではmainブランチを使います。実際に公開するブランチへ置き換え、リポジトリ固有のpush.defaultやupstream設定に結果を左右されないよう、ブランチ名を明示してください。
適切なモデルを選ぶ
| 状況 | 推奨モデル | 理由 |
|---|---|---|
| 管理者が同一の論理リポジトリを公開すると保証している2つの認証済みエンドポイント | 1つのリモートに複数のpushurlを設定 | 1つのリモート名で全書き込みエンドポイントを使い、1つのFetch URLを維持できます。 |
| 独立した2つのリポジトリ、プロバイダー、認証情報、またはポリシー | primaryとmirrorのような別々のリモート | 宛先ごとに確認、Push、再試行、検証ができます。 |
この区別は公式の`git remote`ドキュメントに基づきます。1つのリモートのPush URLとFetch URLは同じ場所を指すべきであり、ある場所から取得して別の場所へ公開するなら別々のリモートを使うよう記載されています。`git push`ドキュメントも、Pushが定義済みのすべてのPush URLへ作用することを明記しています。
方法A:1つのリモートに複数のPush URL
originがすでに存在するとします。Fetch URLを設定してから、プライマリエンドポイントも含む、意図したPush先をすべて追加します。
git remote set-url origin ssh://git@read.example/team/project.git
git remote set-url --add --push origin
ssh://git@write-a.example/team/project.git
git remote set-url --add --push origin
ssh://git@write-b.example/team/project.git
pushurlが1つでも存在すると、GitはFetch URLへフォールバックせず、設定されたPush URL一覧を使います。2番目のエンドポイントだけを追加すると、以後のPushからプライマリが抜けてしまいます。
FetchとPushの設定を分けて確認します。
git remote get-url --all origin
git remote get-url --all --push origin
Push URLを1つ削除するときは、最後の引数が正規表現である点に注意してください。先頭と末尾を固定すれば、意図せず広く一致することを防げます。
git remote set-url --delete --push origin
'^ssh://git@write-b[.]example/team/project[.]git$'
カスタムPush URLを意図的にすべて消すと、Gitは再びそのリモートのFetch URLをPushにも使います。
git config --unset-all remote.origin.pushurl
URLを編集するだけの目的でgit remote rm originを使わないでください。リモート全体と、それに関連する追跡設定まで削除されます。
方法B:別々のホストに別々のリモート
独立したリポジトリには、宛先ごとに意味のある名前を付けます。
git remote add primary ssh://git@code-a.example/team/project.git
git remote add mirror ssh://git@code-b.example/team/project.git
git remote get-url --all --push primary
git remote get-url --all --push mirror
その名前のリモートがすでに存在する場合は、確認してからgit remote set-url <name> <url>で変更し、重複して追加しないでください。別名にすることで、認証情報、ブランチ規則、エラーログ、再試行の違いをoriginの背後に隠さず扱えます。
ドライラン、Push、検証
独立した宛先ごとに、対象ブランチをドライランします。
git push --dry-run --porcelain primary main
git push --dry-run --porcelain mirror main
--dry-runはref更新の実送信以外を行い、--porcelainは機械処理しやすい安定したステータス行を出力します。ドライランは有用ですが、成功を予約するものでも、後のPushがサーバー側の全規則を通過する証明でもありません。
順序を明確にしてPushします。&&を使うと、プライマリが失敗した場合はミラーを実行しません。
git push primary main &&
git push mirror main
2つ目が失敗しても、最初のPushはロールバックされません。各宛先の状態を記録し、遅れている宛先だけを再試行します。
Push後、ローカルのコミットIDと、各サーバーが公開するブランチIDを比較します。
git rev-parse main
git ls-remote --exit-code --branches primary refs/heads/main
git ls-remote --exit-code --branches mirror refs/heads/main
2つのls-remote出力の第1フィールドが、どちらもgit rev-parse mainの値と一致する必要があります。自動化では値を厳密に比較し、refが存在しない、または異なる場合にジョブを失敗させてください。
原子性と部分的な失敗
git push --atomicは、1台の受信サーバーに対して、1回のPushに含まれる全refを更新するか、すべて更新しないかを要求します。サーバーがatomic pushをサポートしなければ失敗します。2つの接続やホストを1つのトランザクションにまとめることはできません。宛先を複数のPush URLで表しても、別々のリモートで表しても同じです。
Git 2.43.0と使い捨てのローカルリポジトリで、実際の失敗パターンを再現しました。
git push --dry-runは設定した2つのローカルPush URLを確認しましたが、どちらのベアリポジトリも更新しませんでした。- 実際のPushでは、両方が同じコミットへ更新されました。
- 1つ目を有効なURLのまま残し、2つ目を存在しないリポジトリへ変更すると、1つ目は更新済み、2つ目は失敗となり、
git pushはステータス128で終了しました。
具体的なエラーと終了ステータスはトランスポートにより異なることがありますが、部分的成功の危険は変わりません。堅牢なレプリケーションジョブは不一致を検出して解消する必要があり、--atomicを付けてもホスト間ロールバックは実現しません。
ブランチ、タグ、Mirrorモード
対象refを明示します。
git push <remote> mainはmainだけを公開します。git push <remote> --allはローカルの全ブランチを公開しますが、タグは含みません。git push <remote> --tagsは全タグと、コマンドで明示したrefspecを公開します。git push <remote> --mirrorはrefs/配下の全refをミラーし、変更されたrefを強制更新し、ローカルにないリモートrefを削除します。
--mirrorは破壊的な同期であり、「バックアップを作る」の同義語ではありません。全refの範囲と削除ポリシーを確認済みの、意図的に管理するミラーにだけ使ってください。通常の公開では名前を指定したブランチと、配布する意思のあるタグだけをPushします。
認証情報とサーバー側ポリシー
パスワードやアクセストークンをリモートURLへ埋め込まないでください。URLは.git/config、コマンド出力、スクリーンショット、ログに現れます。エージェントで管理するSSH鍵、またはOSの安全な資格情報ストアを使うGit credential helperからHTTPS認証情報を渡してください。独立したホストごとに認証し、Pushに必要なリポジトリ権限だけを与えます。
アーカイブのコマンドはgit://を使っています。Gitの`git daemon`ドキュメントによれば、このプロトコルには認証がなく、読み取り用のupload-packだけがデフォルトで有効で、匿名のreceive-packはデフォルトで無効です。歴史的なURLを、現代の認証付き書き込みのひな型にしないでください。
各サーバーは、保護ブランチ規則、権限、non-fast-forwardポリシー、フック、署名要件などのホスティング制御を独立して評価します。一般的なGitサーバーでは`receive.denyNonFastForwards`でnon-fast-forward Pushを拒否でき、`pre-receive`と`update`フックでも提案されたref更新を拒否できます。クライアント側のforceオプションは、そのサーバー判断を上書きしません。両ホストのポリシーをそろえない限り、一方が受理した更新をもう一方が拒否する可能性があります。
実用的な自動化チェックリスト
- 独立したホストには別々のリモートを使い、ブランチまたはrefspecを明示します。
- URLやログへ秘密情報を入れず、各宛先の認証を個別に試します。
- リモートごとにドライランしますが、保証とは見なしません。
- 意図した順序でPushし、宛先ごとの結果を保存します。
- 公開されたリモートブランチIDを、意図したローカルコミットと比較します。
- 失敗または遅延した宛先だけを再試行し、不一致が残れば通知します。
これにより「2回Pushする」を観測可能なレプリケーション処理にでき、存在しない分散トランザクション保証を装うこともありません。
公式一次資料
- Git:`git remote`のURLとPush URLの動作
- Git:`git push`、ドライラン、ref選択、Mirrorモード、atomic push
- Git:`git ls-remote`と`--exit-code`
- Git:credential helper
- Git:non-fast-forward拒否を含むreceive設定
- Git:受信側フック
- Git:`git daemon`のサービスと匿名Pushの警告
---
2019年の原文アーカイブ(逐語保存)
以下は、2019-04-24に公開・最終更新されたWordPressエクスポートの表示可能な本文全文です。リポジトリの整形に合わせ、見えない行末空白だけを正規化しました。認証のないgit://の例と障害処理がない点も来歴として保存しており、現在の推奨手順ではありません。
git remote set-url --add --push origin git://original/repo.git
git remote set-url --add --push origin git://another/repo.git
Remove one url from origin
git remote set-url --delete --push origin git://another/repo.git
Remove origin totally
git remote rm origin
git remote set-url origin git://another/repo.git
