Git フォークの完全な同期
GitHub などにある Git リポジトリをフォークしたとき、 アップストリームでフォーク後に発生したブランチ・タグの追加や削除も含むすべての更新を、 フォーク先に同期する方法に悩んだことはないでしょうか。
このトピックでウェブを検索して見つかるのは、 GitHub の Syncing a fork の記事のようにコマンドラインで単一ブランチをフェッチ・マージ・プッシュして同期させる方法がほとんどです。 しかしながら、アップストリームの開発が活発で多くのブランチ・タグが作られたり消されたりする場合、 手作業ですべての変更を同期することは現実的ではありません。
最近 GitHub にある Redmine のように多数のブランチ・タグがあるプロジェクトをセルフホストな GitLab にフォークして独自機能をメンテナンスしたり CI でコンテナ化したりすることが増え、 この問題に悩むことが多くなりましたので、汎用的に使えるスクリプトを書いてみました。
スクリプト
次のシェルスクリプトは Linux や macOS、Windows の WSL や Git Bash などで動きます。 一部で Git 2.10 以降の機能を使っています (後述)。
#!/bin/sh
set -e
REMOTE_SRC=git@github.com:redmine/redmine
REMOTE_DST=git@gitlab.com:yaegashi/redmine
PREFIX=fork/
GIT_DIR=
PUSH_OPTIONS="--prune --push-option=ci.skip"
# Initialize bare repository in temporary $GIT_DIR
if test -z "$GIT_DIR"; then
GIT_DIR=$(mktemp -d)
trap "rm -rf $GIT_DIR" EXIT
fi
export GIT_DIR
git init --bare
# Fetch all branches/tags from src repository
git fetch $REMOTE_SRC +refs/heads/*:refs/src/heads/* --prune --no-tags
git fetch $REMOTE_SRC +refs/tags/*:refs/src/tags/* --prune --no-tags
# Fetch $PREFIX-ed branches/tags from dst to local src
git fetch $REMOTE_DST +refs/heads/${PREFIX}*:refs/src/heads/${PREFIX}* --no-tags
git fetch $REMOTE_DST +refs/tags/${PREFIX}*:refs/src/tags/${PREFIX}* --no-tags
# Push all branches/tags from local src to dst repository
git push $REMOTE_DST +refs/src/heads/*:refs/heads/* $PUSH_OPTIONS
git push $REMOTE_DST +refs/src/tags/*:refs/tags/* $PUSH_OPTIONS
最初にある次の変数によりスクリプトの動作を設定します。
変数 | 説明 |
---|---|
REMOTE_SRC | 同期元リポジトリのURL |
REMOTE_DST | 同期先リポジトリのURL |
PREFIX | 同期先で保持するブランチ・タグ名のプリフィクス |
GIT_DIR | ローカルで使う Git リポジトリの場所 空の場合はテンポラリフォルダを作成して使います |
PUSH_OPTIONS | git push に指定するオプション (後述) |
このスクリプトは同期先で作られたブランチ・タグのうち $PREFIX
が名前の先頭についたものは消さずに残します。
これにより、同期先でカスタマイズやプルリクエストのためのブランチを作成するときは
fork/patch-1
のような名前を使用するといった取り決めの運用ができます。
$GIT_DIR
は固定の場所に設定しておけば、繰り返し実行したときに無駄なダウンロードを避けることができます。
スクリプトの説明と注意事項
このスクリプトは残すべきブランチ・タグをすべてローカルにフェッチしてから、
最後のプッシュで --prune
をつけて一括で反映しています。
この方法は最も効率的で、無駄なプッシュのイベントを発生させません。
ただし、めったにないことだとは思いますが、フェッチの段階でなにかバグや不具合が発生すると、
同期先にしかない $PREFIX
つきブランチ・タグを消してしまう可能性があります。
心配な場合は $PUSH_OPTIONS
から --prune
を外したほうがよいかもしれません。
そうすると同期先のブランチ・タグが削除されることはなくなりますが、
同期元の削除は反映されず、増える一方となります。
--push-option=ci.skip
は Git 2.10 で追加されたプッシュオプションの指定です。
Git 2.10 より前の場合は動きませんので
$PUSH_OPTIONS
から --push-option=ci.skip
を削除してください。
GitLab にはプッシュオプションに ci.skip
を指定すると CI の実行をスキップする機能がありますので、
同期元が他人のリポジトリでブランチに .gitlab-ci.yml
が含まれている場合でも、安心して同期ができます。
もちろん安全なリポジトリを同期しており CI をやりたい場合は
--push-option=ci.skip
を削除してもかまいません。
現時点では避けることが難しい問題として、フェッチからプッシュまでの動作がアトミックでないため、
同時に作業中のユーザーによる $PREFIX
つきブランチ・タグへの更新が失われることがあります。
昼間にこのスクリプトを頻繁に実行することは避けたほうがよいでしょう。
GitLab CI による定期実行
同期先が GitLab のリポジトリの場合 GitLab CI で前述のようなスクリプトを定期実行すると便利です。 そのための GitLab CI ブランチのサンプルを https://gitlab.com/yaegashi/gitlab-fork-sync に作りました。 以下、セットアップの手順を簡単に説明します。
まず同期先リポジトリを用意します。 これは GitLab の新規プロジェクト作成で同期元からの Import を選んでもよいですし、 空のリポジトリから始めてもかまいません。
次に gitlab-fork-sync の
fork/sync
ブランチを同期先のリポジトリにコピーします。
git clone https://gitlab.com/yaegashi/gitlab-fork-sync
cd gitlab-fork-sync
git remote add dst git@gitlab.com:yaegashi/redmine.git
git push dst fork/sync
次にリポジトリのアクセスに使う SSH 鍵を作ります。
ssh-keygen -N '' -f ssh-key
このコマンドで ssh-key
(秘密鍵) と ssh-key.pub
(公開鍵) の 2つのテキストファイルができます。
同期先のリポジトリの deploy key に ssh-key.pub
の中身を設定します。
その際にリポジトリ書き込み権限を与える必要があります (write access allowed)。
続いて同期先リポジトリで CI/CD schedule を作成します。
-
Interval Pattern にはアクセスの少ない時間帯を設定する
-
Target Branch を
fork/sync
に設定する -
Variables 設定
変数 説明 REMOTE_SRC
同期元リポジトリのURL REMOTE_DST
同期先リポジトリのURL
空の場合はプロジェクトリポジトリにSSHアクセスSSH_PRIVATE_KEY
リポジトリアクセスに使うSSH秘密鍵 ssh-key
の中身を設定するPREFIX
保存するブランチ・タグ名前のプリフィクス
空の場合はfork/
Scheduled pipeline ができたら、マニュアル実行して動作確認してみてください。
https://gitlab.com/yaegashi/redmine/pipelines より Redmine を使った実行例を見ることが出来ます。
なお GitLab CI で使用しているコンテナ registry.gitlab.com/yaegashi/gitlab-fork-sync
は fork/docker
ブランチ で作成しています。
Alpine Linux で Git を使えるようにしただけのものです。
GitLab の有償版機能
実は GitLab には組み込みで リモートリポジトリをミラーする機能 や リモートリポジトリ用の GitLab CI といった機能があります。ただしこれらは有償版でしか使えない機能です。
gitlab-fork-sync を使うよりもセットアップが簡単かつ高機能なので、 利用可能であればこちらを利用するのがよいと思います。 なお https://gitlab.com では 2019/09/22 までこの機能は無料で使えるとのことです。