Git のトランクベース開発とトピックブランチ開発の選び方

# 2021.5.24 追記 (2021.10.1 URL 更新)

本稿を再構成したものがテクノロジーコラムとして公開されましたので併せてご覧ください。

-   [「アジャイルなら Git」の神話 : 富士通](https://www.fujitsu.com/jp/services/agile/featurestories/about-agile-08.html)

# 背景

ここ数年、職場でも Git を使うことがわりと当たり前になってきました。私が個人的に Git を使い始めた頃 (10年前みたいです)、こんな複雑なバージョン管理は職場で普及しないだろうと思っていました。何より Subversion では積極的に使う気になれなかったブランチを誰もが作ってマージしたりするとは想像できませんでした。

そんなある日、会社の先輩に言われたことがあります。

「**Git のブランチは分断を助長し常時結合を阻害するから必ずしも良いとは思えないんですよね**。」

最初に聞いたとき、なんとなく分かるも、釈然としないところもありました。 **ブランチを手軽に作って機能の開発に集中できることこそ Git の強み** であり、ほかのバージョン管理システム (VCS) と差別化される特徴だと思っていたからです。

「**ブランチが機能するのは OSS だから**。 そして **OSS の開発は余り速くない。** プルリクエストはリーダー (メンテナー) に承認をもらうための機能とも言える。一方、業務として、特に**アジャイルでソフトウェア開発する速さにブランチは必ずしも合ってない**。」

ならば分散型 VCS である Git より必然的に常時結合しやすくなる集中型 VCS の Subversion が何故使われないのでしょう。そして Git で常時結合を志向するにはどうしたらいいのか考えてみました。

# ブランチについて

ブランチの功罪について改めて整理してみます。

## 利点

前述したとおり、機能の開発に集中できることがブランチの利点でしょう。

-   メインブランチからの影響を避けられる

    トピックブランチで開発を進めている間にメインブランチが進んだとしても、別のブランチですから影響を受けることなく開発を継続することができます。

-   メインブランチへの影響を避けられる

    レビューやテストが不十分なままコミットしても、それがメインブランチでなければメインブランチへの影響を避けることができます。

-   開発途中のコードをローカル以外に残せる

    機能の開発がすぐに終わりそうにないとき、ブランチを分けていなければ完成するまでローカルディレクトリに変更を残し続けなければなりません。

    しかし、ブランチを分けてコミット (プッシュ) しておけば、もし翌日、ローカルの環境が壊れたり、急用 (病気とか含めて) で続けられないときにも引き継げるので安心です。

-   コミットを使ってレビューできる

    GitHub や Bitbucket のプルリクエスト、GitLab のマージリクエストを使ってコードの変更差分を確認しながらレビューをすることができます。差分の中にレビューのコメントを残すことも簡単です。

## 弊害

ブランチが長大化することにより様々な弊害を生みます。

-   メインブランチとの乖離によるコンフリクト

    複数人で開発していれば、ブランチで分かれて開発している間にメインブランチも変化します。その期間が長くなるほど、コンフリクトが発生しやすくなります。コンフリクトしたらそれをマージするときに解消しなければなりません。

-   常時結合の阻害

    トピックブランチ上で CI ツール等を使って自動テストをしていたとしても、そのときのメインブランチの変更が反映されていないわけですから、Continuous Integration、つまり継続的に統合できている状態とは言えません。

-   レビュー機会の減少

    それぞれの開発者はそれぞれのトピックブランチを使って開発しているわけですから、ほかの人が開発を進めているトピックブランチを見る機会は、余程視野の広い開発者でなければなかなかないでしょう。

    すると、レビューがマージの直前だけになってしまいそうです。

[^alt-git-push-f]: 強制プッシュを避けるために新しいブランチを作ってリベースしてから通常のプッシュをする方法もありますが、GitHub のプルリクエスト上でレビューをしていた場合、それを失うことになるので、これも良い方法とは言えません。

# トランクベースでの開発

ここで、ブランチを日常的に使っていなかった (であろう) Subversion (または CVS) を使っていたときはどうしていたか思い出してみます。

## Subversion の頃

Subversion を使うことは、通常、トランクベースで開発することを意味していました。コミットがすなわちチームで共有しているリポジトリへの反映を意味していたので、自分がコミットする前にリポジトリ (ブランチ) の最新のコミットを作業ディレクトリに反映するため `svn update` していました。

ここでリモートのリポジトリと自分の作業ディレクトリの両方で変更があれば自動的に **作業ディレクトリに** マージされます。変更箇所に競合があれば、作業ディレクトリ上のファイルを編集して解消した後、自分の変更をコミットする流れです (競合の解消に失敗すると自分の変更を失いかねないのが怖いところ)。

これは言い方を変えれば、変更箇所の競合を解消しない限り、自分の変更をコミットできないと言うことを意味します。つまり、 **ツールが常時結合をするように制約していた** のです。

## Git でのトランクベース

さて、Git でトピックブランチを作らなかったらトランクベースの開発となるでしょうか。

Git では、リモートリポジトリ (ブランチ) の変更をローカルに反映するのに `git pull` を使います。

通常は、ローカルリポジトリでリモートに未反映のコミットがなければ、リモートの変更がローカルリポジトリにそのまま反映されます (fast-forward マージ)。
![fast-forward-merge](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/127827/25560c99-1a1d-eccb-35d6-605bc42c4491.png)


一方、ローカルリポジトリでリモートに未反映のコミットがあれば、それぞれのコミットはそのまま残され、更にそれぞれのコミットをマージしたコミットが発生します。マージがうまくできれば、それをリモートにプッシュできます。
![non-fast-forward-merge](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/127827/39d67edc-5ef0-87a7-4f75-c160d99cd6f3.png)

つまり、細かくコミットの分岐や統合が発生することはあるものの、設定やオプションの使い方に気を付ければ、 **トランクベースで開発を進めることはできそうです**。

設定やオプションの使い方には注意が必要です。例えば `git config merge.ff false` などと設定した状態のまま `svn update` の感覚で `git pull` すると (おそらく意図しない) マージコミットが残ってしまいます。 [^merge-ff]

[^merge-ff]: 一応、`git config pull.ff only` もあわせて設定しておけば意図しないマージリクエストは防げますが。

# Git でブランチを多用するようになった訳

Git が普及したのは GitHub の影響が大きいでしょう (自分もそうだったから…)。

GitHub が OSS のフォークを簡単なものにし、プルリクエストなどを使ったソーシャルコーディングへと発展しました。従来、メーリングリスト等で行っていたであろうパッチの説明やレビューをプルリクエストは容易なものにしました。

プルリクエストでは、メンテナーに何をプル (マージ) してもらうか明らかにするためにブランチを明確にすることが不可欠です。実際はフォークしたリポジトリのブランチがトピックブランチになります。またリポジトリをフォークせず、一つのリポジトリを複数の開発者で共有する場合もブランチを明確にする必要があるのは同様です。 [^merge-request]

[^merge-request]: GitLab が「プルリクエスト」ではなく「マージリクエスト」と呼んでいるのは、GitLab ではフォークよりも一つのリポジトリを共有することが多いためだと思います。


このソーシャルコーディングの開発スタイルは、頻繁にコードを変更する業務上のソフトウェア開発であっても便利に使うことができました。その結果、トランクベースの開発スタイルが減っていったように思います。

# トランクベースとトピックブランチベースのどちらを選ぶか

トランクベースもトピックブランチベースもやはり一長一短です。

今後、ソフトウェア開発ではどちらをベースにするのが良いか整理してみます。

## トランクベースで開発

### 向いているプロジェクト

-   常時レビュー (ペアプログラミング) している。
-   プッシュしなくてもテストを走らせることができる。
-   コミット毎に常に全体が統合されている状態にしたい。
-   チケットに関連する **コミット** が分かれば良い。
-   メインブランチへの反映にメンテナー (リーダー、オーナー) によるレビュー (承認) を要しない。

### コツ

-   コミットメッセージにチケットへのリファレンスを付ける (チケット番号を書く)。
    -   1つのチケットに複数のコミットがあっても良い。
-   頻繁に `git pull` する。
-   以下の何れの設定もしない。
    -   `git config pull.ff only`
    -   `git config pull.ff false`
    -   `git config merge.ff only`
    -   `git config merge.ff false`
-   作りかけの (不完全な) 物を一時的に残したいときは一時保存用にトピックブランチを作ってコミットするのも可。
    -   一時ブランチは後で捨てる前提。

## トピックブランチを作って開発

### 向いているプロジェクト

-   レビューは主にマージする直前に実施する。
-   プルリクエスト (マージリクエスト) でレビュー (の記録を残) したい。
-   ローカルでは (リモートにプッシュしないと) テストを走らせることができない。
-   作りかけの不完全な状態でも失わないようにコミットとプッシュを許容したい。
-   チケットと **ブランチ** を関連付けたい。
-   メインブランチへの反映はメンテナー (リーダー、オーナー) がレビュー、承認したものだけにしたい。

### コツ

-   ブランチ名やマージコミット (通常は一つ) にチケットへのリファレンスを付ける。
    -   ブランチ内の個々のコミットには必ずしもリファレンスを付けなくて良い。
-   不用意に `git pull` や `git merge` をしない。
-   以下の設定をする。
    -   `git config pull.ff only`
-   トピックブランチ内でも不完全な物はなるべくコミットしない。
    -   不完全な物を一時的に残したいときは一時保存用にさらなるブランチを作ってコミットするのも可。

## トランクベースとトピックブランチのハイブリッド

例えば常にペアプログラミングをすることに内外の理解を得ることが難しいなどの事情がある場合は、ペアを組める場合のみトランクベース、組めない場合はトピックブランチと組み合わせても良いかも知れません。

いずれにせよ、チームの規律の中で主体的に選択できることが重要だと思います。

# むすび

これを書くにあたり、[入門Git - 秀和システム](https://www.shuwasystem.co.jp/book/9784798023809.html) を読み返しました。少し古い本で、コマンドのデフォルトの動作など今とは変わっているところもありますが、ツールの基礎からその思想をふりかえるのは改めて良い学びになりました。

GitHub などのコードホスティングもどんどん使い易くなっていますが、機能の使い方に目を奪われて、自分達のやりたかったことと合っているのか考えることが減ったかも知れません。

また暫くしたらここに書いた考えも変わるかも知れませんが、迷ったときは基本に立ち戻って考え直すのが良いと思いました。