•13 min read

フォークしてプライベートに保ち、Upstreamと同期する

フォークしてプライベートに保ち、Upstreamと同期する

オープンソースプロジェクトで、必要な機能のほとんどは備わっているけれど、すべてではない、というものに出会ったことはありませんか?私はいつもこれに遭遇します。自分の機能を追加して非公開にし、しかも正気を保ちながらアップストリームのバグ修正を取り込みたい、と。

GitHubの標準的なフォークワークフローは、公開された貢献にはうまく機能します。しかし、誰かのオープンソースの上に独自のものを構築している場合はどうでしょうか?その場合は、異なるアプローチが必要です。

いくつかの異なる戦略を試した結果、私がたどり着いた設定がこれです。派手ではありません。しかし、機能します。

Audio Briefing
0:00 / 0:00
目標

最新のアップストリームの変更の上に、カスタムコミットをきれいに適用したプライベートフォークを維持する。マージノイズなし。手動での競合の悪夢なし。


セットアップ

まず、元のプロジェクトをクローンします。次に、リモートを入れ替えて、プライベートリポジトリがoriginになり、元のリポジトリがupstreamになるようにします。

git clone https://github.com/original-author/some-project
cd some-project
git remote set-url origin https://github.com/you/your-private-repo
git remote add upstream https://github.com/original-author/some-project
git push origin main

なぜこのようにするのか?理由は2つあります。まず、originはgitのデフォルトのリモートです。何も考えずにgit pushと入力すると、プライベートリポジトリに送られます。これは安全です。次に、元のリポジトリをupstreamとして保持することで、更新がどこから来るのかが明確になります。

これを行わずに、フォークのリモートに直接プッシュする人を見たことがあります。それもうまくいきます。しかし、私はどのリモートがどれであるかを明示的にするのが好きです。そうすることで、夜11時に間違った場所に何かをプッシュしてしまうのを防げます。


Advertisement

同期ループ

ここに実際のワークフローがあります。元のプロジェクトは動き続けています。カスタム作業を壊すことなく、それらの変更を取り込む必要があります。

git fetch upstream
git checkout main
git rebase upstream/main
git push origin main --force-with-lease

コマンドは4つだけです。それぞれの重要性について説明しましょう。

フェッチ、プルではない

git fetch upstreamは、元のプロジェクトから最新のコミットをダウンロードします。それらを自分のブランチにマージすることはありません。これは意図的なものです。適用する前に、何が来るのかを確認したいのです。

以前は習慣でgit pull upstream mainを実行していました。悪い考えです。プルはフェッチと暗黙的なマージを行います。自分の変更と相手の変更がどのように結合されるかを制御できなくなります。

リベース

git rebase upstream/mainは、自分のコミットを巻き戻し、アップストリームの変更を適用し、その上に自分のコミットを再生します。カスタム作業は上に残り、アップストリームの変更は下に残ります。

なぜマージではなくリベースなのか

マージコミットは、共同作業において目的を果たします。しかし、単独のフォーク同期の場合、それらはノイズです。git mergeを実行するたびに、「ブランチxをブランチyにマージしました」というコミットが作成されます。これを何十回も行うと、履歴が空のマージバブルの混乱になります。リベースは履歴を線形に保ち、読みやすくします。

安全ネット付きの強制プッシュ

リベースは履歴を書き換えます。コミットハッシュが変わります。通常のプッシュを試みると、Gitは警告を発します。そこで--force-with-leaseの出番です。

git push origin main --force-with-lease

これは単純な--forceよりも安全です。前回のフェッチ以降に他の誰もリモートブランチにプッシュしていないことを確認します。もしプッシュしていれば、gitはプッシュを拒否します。誤って誰かの作業を上書きすることはありません。

強制ではなくリース

単純な--forceは盲目的にプッシュします。--force-with-leaseはあなたの安全ベルトです。常に使用してください。


マージの代替案

リベースよりもマージを主張する人もいます。彼らは、リベースは履歴を書き換えるので悪いと言います。その主張は理解できます。複数の共同作業者がいる共有ブランチでは、私も同意します。マージコミットは、いつ何が起こったかの記録を作成します。

しかし、プライベートフォークの場合はどうでしょうか?作業しているのはあなただけです。クリーンな線形履歴は、すべての同期のタイムスタンプ付き記録よりも価値があります。6か月後に振り返って、アップストリームのリリースの上にきれいに積み重ねられたカスタムコミットを見たいと思うでしょう。マージバブルの絡み合ったウェブではありません。

フォークでチームと作業している場合は、マージを使用してください。チームとコミュニケーションを取りましょう。1つの戦略を選び、それを守りましょう。チームの半分がリベースし、もう半分がマージするという最悪の事態はありません。


競合の処理

リベースは時々競合を引き起こします。楽しいことではありませんが、管理可能です。

git rebase upstream/main
# git says: conflict in some-file.ts
# Fix the file manually
git add some-file.ts
git rebase --continue

重要な洞察:競合は彼らのコードではなく、あなたのコードで解決してください。アップストリームの変更は事実です。あなたのカスタム変更は、アップストリームのメンテナーが変更した内容に適応する必要があります。

競合が大きすぎる場合は、いくつかの選択肢があります。

  • 中止してマージする代わりに: git rebase --abortはすべてを元に戻します。時間がない場合はgit merge upstream/mainを試してください。
  • コミットを分割する: 多くのファイルを変更する大きなコミットは、より頻繁に競合します。コミットは小さく、焦点を絞ったものに保ちましょう。
  • 連絡を取る: アップストリームが依存するAPIを変更した場合、メンテナーは移行に関するアドバイスを持っているかもしれません。
競合に対する考え方

アップストリームはあなたのフォークについて知りません。彼らが内部をリファクタリングすると、あなたのカスタムコードが壊れるでしょう。それは普通のことです。大規模なアップストリームリリース後には、競合解決のための時間を予算に組み込みましょう。


Advertisement

このワークフローを使用しない場合

このアプローチは、すべての状況に適しているわけではありません。以下は、私が別の方法を選ぶ場合です。

  • 貢献する予定がある場合: GitHubで公開フォークを作成し、PRを送信するだけです。プライベートリポジトリの面倒な作業は必要ありません。
  • アップストリームが常に変更される場合: 毎日リリースがある場合、常にリベースすることになります。代わりに依存関係をベンダー化することを検討してください。
  • 変更が非常に大きい場合: プロジェクトの半分を書き換えている場合、フォークを維持することは摩擦を増やします。自分で何かを構築する方が良いかもしれません。
  • ライセンスが許可しない場合: これが大きな問題です。
ライセンスの互換性

フォークを非公開にする前に、アップストリームのライセンスを読んでください。MITとApache 2.0はプライベートフォークを許可します。GPLとAGPLは、ソフトウェアを配布する場合、変更を公開することを義務付けています。これはオプションではありません。ライセンスに違反すると、法的な問題に巻き込まれる可能性があります。


クイックリファレンス

以下に、ワークフロー全体を1つのブロックにまとめました。ブックマークしてください。

# Initial setup
git clone https://github.com/original-author/some-project
cd some-project
git remote set-url origin https://github.com/you/your-private-repo
git remote add upstream https://github.com/original-author/some-project
git push origin main

# Each sync cycle
git fetch upstream
git checkout main
git rebase upstream/main
git push origin main --force-with-lease

最後に

私はこのワークフローを約2年間、いくつかのプロジェクトで使ってきました。完璧ではありません。競合はまだ発生します。大規模なリベースは面倒なこともあります。しかし、他の選択肢よりはましです。

他の選択肢はもっとひどいです。アップストリームと同期せず、フォークが腐ってしまうか、盲目的にマージして履歴が誰も読めないほど絡み合ってしまうか、あるいはプライバシーを諦めてすべてをオープンソースにするかです。

自分のワークフローは自分で決める

自分の状況に合ったアプローチを選びましょう。私にとって、定期的なリベース同期を伴うプライベートフォークは、最新の状態を保ちながらコントロールを維持するのに最適な方法でした。あなたの場合は異なるかもしれません。それで構いません。

こちらもどうぞ

Share this article:

Stay Updated

Get the latest posts delivered straight to your inbox.

Free Developer Utilities

Free In-Browser Developer Tools

Clean AI CLI logs, build cron expressions, decode JWTs, and calculate chmod permissions offline.

Explore Tools
Advertisement
GitHub ActionsでCI/CDをセットアップする方法
developer-tools

GitHub ActionsでCI/CDをセットアップする方法

GitHub Actionsワークフローをセットアップし、テスト実行、dependencyのキャッシュ、Vercelへのデプロイ、secretsの処理、CIを遅くしたり不安定にしたりする一般的な間違いの回避など、典型的なWebプロジェクトにおけるCI/CDの実践的なウォークスルーを紹介します。

Read more