Gitのブランチで効率的に開発・運用・保守・管理する方法

分散バージョン管理システム「Git」のブランチ機能を利用して、効率的に開発・運用・保守・管理する方法を「A successful Git branching model」(日本語訳)をベースにまとめてみました。

はじめに

最初に、Gitに関するリソースとして、本では「入門Git」と「実用Git」、Web上では「Pro Git」が読みやすく、わかりやすいため、Gitについて知りたい人は一読をおすすめします。

特に、他のバージョン管理システムに関する前提知識がある場合には、Gitの概念や使い方も比較的スムーズに理解できるかと思います。実際に、バージョン管理システムをSubversionからGitへと移行してからしばらくが経ちますが、通常の操作に関しては、それほど不自由することなくGitを利用できています。

しかし、Gitを利用していくにつれて色々と疑問も出てきます。局所的なワークフローについては、様々なリソースによって理解することができます。では、効率的に開発・運用・保守・管理を行うために、大局的・継続的なワークフローをどのように採ればよいのか、特にGitの柔軟性を活かすにはブランチをどのように使えばよいのか、という疑問です。

A successful Git branching model

Gitでの開発・運用の疑問に答えてくれる一つのモデルがあります。それが「A successful Git branching model」です。Gitでの開発・運用方法がブランチグラフと実際のシナリオ、コマンドとともに詳しく解説されています。

要約すると、タグ付け専用のブランチ(master)と開発用のブランチ(develop)を用意し、通常の開発やリリースの準備はdevelopブランチをベースに、リリースやホットフィックスはmasterブランチをベースに行うというものです。

大規模な運用でなければ、このモデルでほとんどのケースに対応できるかと思います。そして、このモデルでの運用をサポートしてくれる「git-flow」というGitプラグインもあります。

新たな疑問

実際の運用に「A successful Git branching model」を当てはめて考えた場合、細かい部分で次のような新たな疑問も湧いてきました。

  • ホットフィックス(緊急性の高いバグの修正)以外のバグフィックスはどのように行うか?
  • 過去のリリースに対するサポートをどのように行うか?
  • 環境(サーバーやサイト)ごとに異なるリソースの開発・運用・保守・管理をどのように行うか?

これらの疑問を解決するために、「A successful Git branching model」周りのリソースや他のGitに関するリソースを参考にして、「A successful Git branching model」のブランチグラフと開発・運用シナリオを掘り下げてまとめてみました。(以降、「A successful Git branching model」を理解していることが前提の内容となります。)

  • Gitのブランチによる開発・運用モデル (「A successful Git branching model」ベース)

(上記は、「A successful Git branching model」のブランチグラフをベースに、いくつかのブランチ群とシナリオを追加、一部の説明とレイアウトを調整したものです。破線の矢印を含む薄くなっている部分が追加部分になります。詳細は以降の章で解説します。)

「--no-ff」(no fast-forward)フラグをデフォルトにする方法

その前に、「--no-ff」(no fast-forward)フラグをデフォルトにするためのいくつかのアイデアを紹介します。

というのは、「A successful Git branching model」ではブランチ間のマージに「--no-ff」フラグを指定することが推奨されています。これはファストフォワード(Fast-forward)マージを防ぎ、意図的にマージコミットを作成するためです。Fast-forwardマージではHEADの移動のみが行われるため、ブランチをマージした履歴が残りません。意図的にマージコミットを作成することで、ブランチをマージした履歴を残し、後々になっても容易に履歴の追跡ができるようにしておきます。

マージオプション(mergeoptions)を指定する方法

Gitの設定で、「branch.ブランチ名.mergeoptions」に「"--no-ff"」を設定します。

git config branch.ブランチ名.mergeoptions "--no-ff"

指定したブランチへのマージでは、「"--no-ff"」がデフォルトのオプションとなり、Fast-forwardマージが行われなくります。ただし、この設定はブランチごとに行う必要があります

また、プル(pull)によってマージが行われる状況においてもFast-forwardマージが行われなくなるため、複数人で開発を行う場合などには注意が必要です(この記事のコメント欄を参照)。

エイリアス(alias)を作成する方法

Gitの設定で、「nffmerge」等のコマンド名で「'merge --no-ff」のエイリアスを追加します。

git config --add alias.nffmerge 'merge --no-ff'

「git merge --no-ff」の代わりに、Fast-forwardマージを行わない「git nffmerge」コマンドを使えるようになります。ただし、既存のコマンド(「merge」等)をエイリアスで上書きすることはできません

参考
A successful Git branching model」のコメント欄
git-config(1)
git-merge(1)

バグフィックス用のブランチ(fix)

  • バグフィックス(fix)用のブランチ - Gitのブランチによる開発・運用モデル (「A successful Git branching model」ベース)

ホットフィックス以外のバグフィックスは、必要に応じて、developブランチからバグフィックス用のブランチ(fix)を作成します。

git checkout -b fix/2 develop

バグの修正後はdevelopブランチへマージし、バグフィックス用のブランチを削除します。

git checkout develop
git merge --no-ff fix/2
git branch -d fix/2

ただし、リリース用のブランチ(release)が存在する場合は、リリース用のブランチ(release)からバグフィックス用のブランチ(fix)を作成します。

git checkout -b fix/3 release/2.0.0

バグの修正後はリリース用のブランチ(release)へマージします。

git checkout release/2.0.0
git merge --no-ff fix/3
git branch -d fix/3

ホットフィックスに関しても、リリース用のブランチ(release)が存在する場合、ホットフィックス用のブランチ(hotfix)はバグの修正後にリリース用のブランチ(release)にのみマージします。

参考
Support for 'feature' or 'fix' branches off release branch -gitflow-users | Google グループ
7.6.2.2. バグ修正のワークフロー - 実用Git

サポート用のブランチ(support)

  • サポート(support)用のブランチ - Gitのブランチによる開発・運用モデル (「A successful Git branching model」ベース)

過去のリリースに対するホットフィックス

既にいくつかのリリースを行った後に、過去のリリースに対してホットフィックスを行わなければいけない状況になったとします。その場合は、該当するタグからサポート用のブランチ(support)を作成します。さらに、サポート用のブランチ(support)からホットフィックス用のブランチ(hotfix)を作成し、そこでバグを修正します。

git checkout -b support/1.x 1.1.0
git checkout -b hotfix/1.1.1

バグの修正後はサポート用のブランチ(support)へマージし、タグを付けます

# チェリーピック(cherry-pick)を使用する場合は、この前にcherry-pickを行う(後述)
git checkout support/1.x
git merge --no-ff hotfix/1.1.1
git tag -a 1.1.1
git branch -d hotfix/1.1.1

混乱を避けるために、原則として、サポート用のブランチ(support)や、そこから派生したホットフィックス用のブランチ(hotfix)を他のブランチへマージすることは避けます

過去のリリースが複数ある場合は、該当するタグから、それぞれ新たにサポート用のブランチ(support)とホットフィックス用のブランチ(hotfix)を作成します。バグの修正後は、派生元のサポート用のブランチへそれぞれマージし、タグを付けます。

サポート用のブランチ(support)は、サポートを終了するまでの間、過去のリリースにおけるmasterブランチとして扱います。

最新のリリースに対するホットフィックス

最新のリリースに対してもホットフィックスを行う必要がありますが、この場合は通常通り、最新のタグからホットフィックス用のブランチ(hotfix)を作成します。

git checkout -b hotfix/2.0.1 2.0.0
# チェリーピック(cherry-pick)を使用する場合は、この後にcherry-pickを行う(後述)

バグの修正後は、developブランチとmasterブランチへマージし、タグを付けます(リリース用のブランチがある場合は、リリース用のブランチへマージするのみ)。

git checkout develop
git merge --no-ff hotfix/2.0.1
git checkout master
git merge --no-ff hotfix/2.0.1
git tag -a 2.0.1
git branch -d hotfix/2.0.1

複数のリリースに対するホットフィックス

複数のリリースでホットフィックスを行う場合には、cherry-pickを使用すると修正が多少容易になります。

git checkout -b support/1.x 1.1.0
git checkout -b hotfix/1.1.1
# バグを修正
git commit -a -m "Fix bug #5"

# cherry-pickで他のリリースに対してもホットフィックスを適用
git checkout -b hotfix/2.0.1 2.0.0
git cherry-pick hotfix/1.1.1
# Git 1.7.2以上であれば、複数のコミットもcherry-pickできる
# git cherry-pick hotfix/1.1.1~2..hotfix/1.1.1

# ホットフィックス用のブランチをマージ
git checkout support/1.x
git merge --no-ff hotfix/1.1.1
git tag -a 1.1.1
git branch -d hotfix/1.1.1

git checkout develop
git merge --no-ff hotfix/2.0.1
git checkout master
git merge --no-ff hotfix/2.0.1
git tag -a 2.0.1
git branch -d hotfix/2.0.1

「A successful Git branching model」では触れられていませんが、最新の「git-flow」プラグインには、このサポート用のブランチ(support)を扱うためのコマンド(「git flow support」)もあるようです。

参考
What is a "Support Branch"? -gitflow-users | Google グループ
Support branches - zaach's gist: 287178 — Gist

環境別・機能別の開発・運用・保守・管理

模索中の点として、環境別・機能別の開発・運用・保守・管理をどうするかということがあります。例えば、開発用・公開用などの用途により微妙に異なる内容を管理したい場合や、特別な機能を持たせたバージョンなどの部分的に異なる内容を管理したい場合です。

異なる内容が些細な場合には専用のブランチを作成し、リベースを行いながら専用のブランチを維持していく方法が考えられます。異なる内容が大きい場合には、サポート用ブランチと同様の方法で専用のブランチを作成し、マージを行いながら専用のブランチを維持していく方法が考えられます。

しかし、どちらの方法も専用のブランチは継続的に維持していく必要があります。「A successful Git branching model」の「維持するブランチを最小・最短に抑えるモデル」を考えると、派生元・マージ先ブランチの決め方を含め、リポジトリとブランチの管理が煩雑になりそうです。

そこで、実際には状況に応じた方法を柔軟に選択する必要があるかと思いますが、とりあえずは以下のような方針で運用を行ってみようかと考えています。

  1. 設定等で切り替えが可能な内容は、フィーチャーブランチ(feature)を利用して、開発用のブランチ(develop)へ盛り込む
  2. 設定等での切り替えや部分的な書き換えで対応できる場合は、シェルスクリプト(バッチファイル)やGitのフック(hook)機能、デプロイツール等を利用して切り替え・書き換えを行う
  3. 切り替えや書き換えが難しい内容は、ベースとなるリポジトリをクローン(clone)、またはフォークする
  4. クローンしたリポジトリのmasterブランチはプル(pull)専用とし、変更する内容ごとに専用のブランチを作成する
  5. masterブランチは定期的にプル(pull)し、専用のブランチへマージ(あるいは専用のブランチをリベース)する
  6. 原則として、masterブランチへのマージ、クローン元へのプッシュ(push)は行わない
参考
rebase で本番用の設定と開発用の設定を簡単に切り替える - 予定は未定Blog版
7.6.3. リリースブランチ - 実用Git

TIPS

TIPSとして、「A successful Git branching model」におけるmasterブランチとdevelopブランチの役割を入れ替える方法があります。つまり、masterブランチを開発用のブランチ(develop)として使います公開用のブランチは専用のブランチ(production等)を作り、そこでタグ付けを行います。

個人規模での開発や、公開よりも継続的な開発に重点を置く場合には、この方法のほうがスムーズな運用ができるかもしれません。

参考
zaach's gist: 287237 — Gist

まとめ

GitはSubversion等とは異なるブランチ、タグ、チェリーピックの概念、分散リポジトリやリベース、多様なマージ方法を持っており、標準で便利な機能が多く用意されています。そのため、多種多様な開発・運用・保守・管理方法に柔軟に対応できますが、それゆえにどの機能をどう使うかは利用者次第です。「A successful Git branching model」のようなシンプルなモデルは、開発・運用・保守・管理方法のベースとして一つの指針となるはずです。

最後に、基本的な運用方法を含めたGitの使い方を理解したい人には「入門Git」、Gitの詳しい概念と仕組みを理解したい人には「実用Git」がおすすめです。

「実用Git」は「入門Git」と合わせて読むと理解しやすいかと思います。「実用Git」は、基本的な運用方法についてはあまり書かれていませんが、一つ一つの概念、仕組みが丁寧に解説されています。その他のリソースでは曖昧にしか理解できない部分でも、「実用Git」を読むことですっきりします。Subversionを利用してきた人には、特に「16章 GitとSubversionリポジトリの併用」がかなり役立つ内容になっています。

入門Git
入門Git - Amazon
入門Git - 楽天ブックス
実用Git
実用Git - Amazon
実用Git - 楽天ブックス

コメント (3)

こんにちは。
git config branch.ブランチ名.mergeoptions “–no-ff”
で常にファストフォワードではないマージを行うことについて一つ。

git pull はデフォルトでは「git fetch してから git merge」 と同じなので、
変更を取ってきたいだけの git pull でもマージコミットが作られます。
(–no-ffのmergeにしてなくても、他人と一緒に作業しているブランチが
 すぐマージコミットまみれになってしまうのを見たことがあるかもしれません)

もしこれが嫌という場合、git pull を git pull –rebase と打てば、
git pull の効果が「git fetch してから git rebase (変更を一つずつ再適用する)」になります。
git pull と打つだけで常に git pull –rebase とするようにするには、

$ git config branch.ブランチ名.rebase true

これで、該当ブランチで git pull をするとそれが git pull –rebase になります。
また、全てのブランチで rebase するようにするには

$ git config branch.autosetuprebase always

です。

意図的にマージコミットを増やしたい場合のための –no-ff、
マージコミットを増やしたくない時のための git pull –rebase という感じです。

git pull –rebase は『入門Git』でも『実用Git』でもほとんど触れられていないので、
本だけだと案外知らない方もいるかもしれません。
海外では「git pullではなくどんな時もgit pull –rebaseしろ」という議論もあるようです。
http://yehudakatz.com/2010/05/13/common-git-workflows/

本題外のことをいきなり長々とすみませんでした。
自分が元記事を翻訳してからずっと気になってた点だったもので…。

こんにちは。
O-Showさんの翻訳をきっかけにこのモデルを知り、日本語訳はモデルの理解に大変参考にさせていただきました。そして、補足ありがとうございます

複数人が関わる場合などには branch.ブランチ名.mergeoptions “–no-ff” で設定してしまうと困るケースは多いかもしれませんね。
もっとも毎度ブランチごとに設定するよりは、エイリアスを使うかその都度指定するのが確実な気はします。

元記事のコメント欄を確認してみたところ、同様のつっこみもあるようです。

Bobby Eickhoff 6 months ago
“I would like to politely disagree with you on one statement: “Unfortunately, I have not found a way to make –no-ff the default behaviour of git merge yet, but it really should be.” I think you make an excellent case that there are times when –no-ff makes the most sense. However, there are times when it would make no sense. Consider that if –no-ff were the default for git merge, then every invocation of git pull could introduce a (completely unnecessary) commit which would only clutter up the commit graph. (Note also that the amount of such clutter will grow linearly with the number of developers and the frequency with which they pull.)””

git pull –rebase は、本では本当に軽く存在が触れられている程度ですが、設定次第で全ブランチでrebaseできるのならかなり便利そうですね。
これは是非使っていきたいので、紹介してくださったリンク先と合わせて確認してみます。

Gitを使いこなすにはまだまだほど遠いですが、今後ともよろしくお願いします。

元記事のコメントまでは追っていませんでした、
情報ありがとうございます!

仰るとおりエイリアスかその都度指定するのがいいという気がします。
後はうまく出来るかわかりませんが、コミットフックとかを使って
–no-ffしてない時は警告を出すみたいなことぐらいでしょうか
(merge後にやっぱり–no-ffが必要だったと思ったら、その場ですぐresetしてやり直し)。

git pull –rebaseは、このモデルに関わらず必要となると思いますので、
是非もっとみんなに知ってほしいです。

自分もGitを使いこなすにはまだまだですので、勉強して行きたいと思います!

コメントフォーム

トラックバック (1)

[…] ・Gitのブランチで効率的に開発・運用・保守・管理する方法 – (DxD)∞ Gitポケットリファレンス posted with ヨメレバ 岡本 隆史,武田 健太郎,相良 幸範 技術評論社 2012-07-10 Amazon Kindle […]

この記事のトラックバックURI
http://dxd8.com/archives/218/trackback/
この記事のURI
http://dxd8.com/archives/218/