ククログ

株式会社クリアコード > ククログ > フリーソフトウェア・OSS開発プロジェクトでよく見られるブランチ運用

フリーソフトウェア・OSS開発プロジェクトでよく見られるブランチ運用

結城です。

GitやGitHubといった便利なツール、Git FlowやGitHub Flowなどのブランチ運用ルールが普及したことで、今では「初めて入社した会社でGitを教わり、GitHub Flowで開発を覚えました」という方も多いのではないでしょうか。

それらのブランチ運用ルールに比べて簡素なブランチ運用として、トランクベース開発(Trunk Based Development)と呼ばれるスタイルがあります。 そのブランチ運用のあまりに簡素な様子に、GitFlowやGitHub Flowなどに親しんできた方は驚かれる場合もあるようです。

当社ではGitLab.comのclear-codeのグループGitHubの同名グループ、あるいは個々のプロジェクトごとのグループなどで、フリーソフトウェア開発プロジェクトのリポジトリを公開しています。 また、メンバー個々人も個人で開発しているソフトウェアを同様に公開していることがあります。

この記事では、それらのリポジトリで採用していることが多い1トランクベース開発において、具体的にどのようなブランチ運用を行っているかを図を交えてご紹介します。 (特別に何か優れた運用を行っているから紹介するというわけではなく、社内向けの説明資料としての性質が強い記事です。)

基本的なブランチ運用の方針

トランクベース開発は、OSやテキストエディター、Webブラウザーなどのように「使用者が自分の管理下のコンピューターにインストールして使う」種類のソフトウェアや、ライブラリのように「使用者が自分で開発するソフトウェアに組み込んで使う」種類のソフトウェアに適した運用スタイルです。 「最も先端の開発が行われている不安定な開発版」と「機能追加はないがセキュリティ脆弱性の修正が提供されている安定版」が併存しているタイプのソフトウェアは、トランクベース開発で運用されている可能性があります。 Gitの登場以前からあるブランチ運用なので、歴史の長いフリーソフトウェア開発プロジェクトで採用されていることがあるほか、最近開始されたプロジェクトでも、明示的にブランチ運用ルールを定めずに運用していると、暗黙のうちにトランクベース開発に該当する運用になっている場合もあります。

最も単純なトランクベース開発は、以下のように説明できます。

(トランクベース開発を解説する図。デフォルトブランチで主な開発が進行し、そこから適宜リリースが行われる)

  • あらゆる新機能の開発・修正は原則として デフォルトブランチ で行う
    • GitLab.comやGitHubで作成したリポジトリではデフォルトブランチ名はmainとなるが、歴史的な経緯からmastertrunkなどのブランチ名を使うプロジェクトもある(以下、具体例ではデフォルトブランチ名をmainと仮定する)
    • 規模の大きな変更2や、リスクが高い変更は、トピックブランチを作成してそちらで作業し、完了後にデフォルトブランチにマージする
      • デフォルトブランチへの直接のコミットを禁止して、すべての変更で必ずトピックブランチの作成を求めるプロジェクトもある
  • リリースはデフォルトブランチから適宜行う(その際はtagを作成する)
    • GitHub Flowなどで見られる「デフォルトブランチは常にリリース可能な状態にしておく」という原則は特に設けない3
  • 特定のバージョンを長期間保守する必要が生じた場合、そのバージョンの時点のデフォルトブランチから 安定版ブランチ を作成する
    • 安定版ブランチは以下の運用を原則とする
      • デフォルトブランチに追加された新機能は(よほどの例外を除いて)安定版ブランチには反映しない
      • 独自の新機能の開発も行わない
      • デフォルトブランチに行われた修正のうち、機能に影響を与えない修正(セキュリティ上の脆弱性の修正、起動できないレベルの致命的な不具合の修正など)は安定版ブランチにも反映する
      • 安定版ブランチ独自に、機能に影響を与えない修正(セキュリティ上の脆弱性の修正、起動できないレベルの致命的な不具合の修正など)を行う場合はある
    • 安定版ブランチからは、マイナーアップデートのリリースを適宜行う(その際はtagを作成する)
    • 特定のバージョンを保守せず、デフォルトブランチからのリリースのみを行う運用を取る場合もある4。その場合、安定版ブランチは作られることはない。

「トランク(trunk)」はカバンのことではなく、ここでは木の枝(branch)に対するのことです。 Gitが一般化する以前の時代には、Gitのデフォルトブランチに相当する物を木の幹に例えてそう呼んでいた例が少なくありませんでした5。 「トランクベース開発」という名前は、そのような名前付けに由来します。

また、ここではリリースをデフォルトブランチから行うと説明しましたが、「先にデフォルトブランチから安定版ブランチを作成して、リリースは安定版ブランチから行う」という運用もあります。

(トランクベース開発において、リリースに向けた安定版ブランチを作成してから安定化作業を行う場合を解説する図。安定版ブランチが作成されてから安定化作業が進行する場合がある)

ただ、安定版ブランチを先に作る運用は、開発に関わる人が多い(開発リソースが潤沢である)場合にのみ可能です。 個人での小規模な開発のように、充分な開発リソースを確保できないプロジェクトでは、複数のブランチを並行して維持し続けるのは難しい場合が多いです。

そのようにリソースが限られるケースでは、「リリースが近いので、リリースまでの間は新機能の追加は停止する」と決めて(場合によっては明示的に宣言して)、「デフォルトブランチで安定化の作業を行い、リリースを行ったら新機能の追加を解禁する」という運用が取られることがあります。 これをフィーチャーフリーズと呼びます。

フィーチャーフリーズは、ブランチ作成やブランチ感のマージのコストが大きいバージョン管理システムが主流だった頃に度々見られた運用です。 Gitのようにブランチ作成やブランチ間のマージのコストが小さいバージョン管理システムが一般化してからは、フィーチャーフリーズでデフォルトブランチでの開発の手を止める代わりに、安定版ブランチを気軽に作成してから安定化の作業を進める運用をしやすくなっています。

開発の成果は常にデフォルトブランチから安定版ブランチへと流れる、という原則

いずれの場合も大事なのは、開発の流れは常にデフォルトブランチから安定版ブランチへ向けて一方通行で行われ、安定版ブランチからデフォルトブランチへの流れは生じないという原則です。

(トランクベース開発において、デフォルトブランチでの変更のうち安定版ブランチに影響する物だけが安定版ブランチにバックポートされる他、安定版ブランチに固有の修正も行われる様子の図。機能追加や安定版ブランチに影響しない修正は、安定版ブランチへはバックポートされない)

すべての不具合は、原則として、まずデフォルトブランチでの修正を試みます。その上で、安定版ブランチも影響を受けるようであれば、修正内容をデフォルトブランチから安定版ブランチへバックポートします6。 不具合の対象がデフォルトブランチで既に廃止されていて安定版ブランチにしか存在しない機能だった場合には、安定版ブランチでのみ修正を行います。

安定版の EOL(End of Life:サポート期限) とは、この「安定版ブランチに対していつまで修正をバックポートし続けるか」「安定版ブランチ固有の修正をいつまで行い続けるか」の期限を指します。 繰り返しになりますが、「安定版ブランチで先に修正して、後からそれをデフォルトブランチにも反映する」ということは、「デフォルトブランチから安定版ブランチへ」の一方通行の流れに反するため、原則として行いません。

ここで「では、安定版ブランチに追加した新機能はいつデフォルトブランチにマージされるのか?」と疑問を抱く方もいるかもしれません。 その答えは、安定版ブランチには新機能は追加しないという運用ルールを徹底する前提なので、安定版ブランチからデフォルトブランチへのマージは永久に発生しない(サポート期限が終了した安定版ブランチは、そのまま放置あるいはアーカイブ扱いとする)というのが、トランクベース開発での原則となります。

そのようにルールを徹底するのは、開発プロジェクトに以下の動機があるからです。

  • 限られた人的・時間的リソースはデフォルトブランチでの開発に集中させたい
  • 安定版のニーズがあるため安定版のブランチを維持はするが、その維持にかけるリソースは可能な限り減らしたい

複数のブランチを維持するのには、本質的には、全く別のソフトウェアをもう1つ別に開発・維持するのと同等のコストがかかります。 多くのフリーソフトウェア開発プロジェクトは開発それ自体でお金を稼げる状況にはなく、「自分達にとって必要だ」という開発チーム自身のニーズに基づいて、開発チームがコストを自己負担して開発・維持されているため、コスト削減の圧力が強く働きます。 トランクベース開発とは、そのような状況を前提としたプロジェクト運用方針自体を指していて、ブランチ運用はあくまでその方針の実践の一端だと言えます。

このような前提があるため、安定版ブランチでの修正対象は、以下のようにユーザーへの影響が深刻な問題のみに限定されます。

  • 大規模な設計変更なしに解消できるセキュリティ脆弱性の修正7
  • ソフトウェアが動作しなくなるような致命的な不具合の修正

こういった基準に該当しない修正や新機能追加の要望は、原則として、プロジェクトの運用方針に基づいて却下される事になります。 自分の使い方において、安定版でどうしても急いで対応する必要がある問題に遭遇した場合には、自分でそのバージョンをフォークして修正するか、当社のようにフリーソフトウェア・OSS一般を対象とした技術サポートを行っているサポートベンダーに対応を依頼する必要があります。

まとめ

以上、Gitのブランチ運用スタイルの1つとしてのトランクベース開発を紹介しました。

トランクベース開発という運用スタイルは、Subversionなどの集中型バージョン管理システムが主流だった時代に確立されました。 集中型バージョン管理システムでは、登場するリポジトリが「全員が参照する中央リポジトリ」1つだけで、仮に競合が発生した場合も、各自が手元で修正してからコミットすれば話は済みます。 よって、新たに開発に参加する人に対しても、本記事のような説明だけでおおむね必要な情報は伝わっていました。

他方、GitやMercurialのような分散型バージョン管理システムでは、ここまでの説明に基づいて実際に作業し始めてみると、つまずくことが度々あります。 具体的には、中央リポジトリと、開発に関わる個々人の作業用リモートリポジトリ、そしてローカルリポジトリという3つのリポジトリ(およびそれぞれのブランチ)の関係を把握した上で、適切な順番で変更を適用していかないと、せっかく作業を行っても成果をマージできない場面が生じます。

次の記事では、Gitでトランクベース開発のブランチ運用を採用しているソフトウェア開発プロジェクトに関わる際の、つまずきやすい場面とその解決方法を紹介していきます。

  1. 具体例としては、GroongaFluentdmilter managerなどが該当します。

  2. 複数のコミットをまとめて反映しないと、個々のコミットだけではデフォルトブランチが動作しなくなってしまうような変更。

  3. 「リリース可能な状態である」ことを「shippableである」と表現する場合もあります。デフォルトブランチが常にshippableである方が開発プロジェクトとしては健全で、そのような状態が維持されていることが望ましいのは間違いありません。ただ、原始的な「トランクベース開発」のモデルとしては、「デフォルトブランチが常にshippableであること」は必須の条件ではありません。(もちろん、そのようにルールを設けて運用すると「トランクベース開発」とは呼べなくなる、ということもありません。)

  4. 開発リソースが限られていて、安定版ブランチを維持する余力が無い場合など。個人開発ではそのような運用となっている場合が多い印象が筆者はあります。

  5. バージョン管理システムの用語としてtrunkという語を明示的に使ったのは、2004年リリースのSubversionが最初です。ただ、CVSなどのそれ以前のバージョン管理システムを使ったプロジェクトにおいても、運用上ではtrunkという呼び方が使われていたようです。

  6. 具体的には、デフォルトブランチに行われた変更を安定版ブランチにチェリーピックします。デフォルトブランチと安定版ブランチの間が離れていてチェリーピックできない場合は、デフォルトブランチでの修正に相当する変更を安定版ブランチに直接コミットすることになります。

  7. 例えば、「全体が1つのプロセスで動作していると、サイドチャネル攻撃によってクリティカルな情報の漏洩が発生しうる」と考えた場合、シングルプロセス設計のアプリケーションには脆弱性があり、マルチプロセス設計に改めることがその解決(修正)方法だと言えます。しかしながら、シングルプロセス設計のアプリケーションをマルチプロセス設計に改めるのは容易ではなく、大規模な変更が必要となります。このような変更は、安定版ブランチで取り扱うべき変更の範囲には含みません。