危なくないgitこと、うちのチームのgit戦略草案(ver. 1)
この記事は更新された版があります
2012/11/16: いただいたフィードバックをもとに、version 2を書きました。
まえがき
gitでトラブった!という話を何度か聞いたことがあります。なんでトラブッてるんだろう…と話を聞いたところ、同一のリモートブランチに対して複数人・複数環境から操作が行われているようです。極端な例を挙げると、masterブランチしか存在しておらず、コミットログをキレイにするためと称してgit pull –rebaseを常用しているような環境です。
gitは、以下のように使えば安全です。
- mergeに相当する操作をしない
- rebaseに相当する操作をしない
…悪い冗談。極端な話ですね。しかし、「merge/rebaseの回数を減らせば、トラブルが起こる確率を減らすことができる」というのは事実です。
そこで、GitHub(Enterprise)の利用を前提に、こういった運用ルールだといいんじゃね、という私案を公開します。ツッコミよろしくです!
なお、A successful Git branching model(日本語訳)の用語を前提としています。… modelは長いので、以下その支援ツールのgit-flowで代替します。developブランチとfeatureブランチしか説明に用いません。開発ブランチとトピックブランチと読み替えてもらってかまいません。
想定する前提条件
比較的大規模なチーム開発において、大量に画像を使うようなWebサイトを制作します。本番デプロイは週3回程度。GitHubもしくはGitHub Enterpriseを開発に用います。
チームメンバーには、非英語圏のオフショア開発拠点が含まれ、スキル・経験ともさまざまです。チーム全体が顔を合わせた状態で、同じ時間に働くことが期待できません。
ねらい
mergeとrebaseを減らしつつ、それらを行う際には意識的に行うようにします。結果、merge/rebaseミスによる作業者が意図しない変更を防ぎ、コードレビューのタイミングを設け、コミットログも管理上扱いやすく保ちます。
なお、ルール化を行うことによって、100点の使い方をできる人に、70点の使い方を強制し、それを許容します。
mergeを減らす
developブランチでのmergeを減らすためには、developブランチに変更を行える人を制限し、一般の作業者が変更できないようにします。
git-flowを用いて、特定の環境でだけfeatureブランチを編集し、developへのマージはある特定の人だけが行う、といった運用は、上記の観点からトラブル防止に有効です。
developブランチへのfeatureブランチの取り込みは、すべてpull requestを用いて行います。developブランチに対する直接作業は禁止します。
pull requestのやり方は後述します。
pull requestに取り込みにおいて、相互レビューをするとよいです。自分がpull requestを受ける立場になれば、レビュワーにやさしいpull requestというものの重要性が実感としてわかります。
同一ブランチに対する共同作業をどうするか
featureブランチについて、複数人で作業したい場合があります。たとえば、sotarok先生のgit-dailyは、そのような状況も想定したワークフロー・ツールです。
feature/sugoiというfeatureブランチを開発するにあたって、サーバサイドロジック側エンジニアが2人、HTML/CSSコーダーが2人、JavaScript側エンジニアが2人、画像製作者が2人参加すると想定します。
画像製作者が制作する画像については、git管理外とします。詳しくは後述。
それ以外の作業者については、feature/sugoiブランチ以下に個人ごとのサブブランチを立てます(ex. feature/sugoi/suenaga)。サブブランチはfeatureブランチを基に作成します。
あとは、個々人が自分の管理下のブランチを育てていきます。個人ごとのサブブランチは、リモートにpushしません。リモートにpushする場合には、featureブランチにmergeを行い、featureブランチをpushします。ここでもpull requestを用いてよいのですが、レビューが必要な場合などにとどめます。
featureブランチのサブブランチは個々人の管理なので、rebaseしようが何しようが問題なし。本体のコミットログが英語onlyであっても、日本語のコミットログを残してもOK。自分がやりやすいように作業します。
画像ファイルなど、巨大かつ大量なバイナリファイルの管理はどうするか。
gitには向いていません。Alienbrainなどの専用ソフトを使うか、Subversionの利用にとどめましょう。
gitで巨大かつ大量なバイナリファイルを置くと、cloneが重くなり、.gitディレクトリが肥大します。
まず、バイナリファイルはmergeができません。よって、画像のバージョン管理の要件としては、任意のバージョンの画像が取得できれば十分となります。
次に、不自然だからです。例えば、Photoshopで作成したjpeg画像がプロジェクトに必要だったとします。Photoshopのpsdファイルはプログラムで言うソースコードにあたり、そこから生成されたjpeg画像はプログラムで言う実行バイナリにあたります。jpeg画像をgitリポジトリに入れることは、実行バイナリをgitリポジトリ管理していることに相当すると考えます。確かに、デプロイの都合や、サードパーティー製ライブラリでソースコードがない場合など、実行バイナリのみをgit管理下にすることはありえます。しかし、一般的に実行バイナリをgit管理下に置くのは不自然だと考えます。だからといって、psdファイルをgit管理下に置くとさらに容量を圧迫しちゃいます。
最後に、画像作成者の学習コストが減ります。例えばSubversionを用いた場合、TortoiseSVNなどの枯れたツールが採用できます。gitの概念を教育する必要もありません。Subversionを用いる場合には、ブランチなどは作らず、一本道のコミットを想定します。ブランチ的なことをやりたい場合には、パス・ファイル名を分けて作業します。例えば、特定の期間だけ、ある画像に「NEW」というレイヤーが入った画像を使いたいとします。そのような場合には、image.pngとimage_new.pngのように2ファイルで管理します。これで、参照元のgitのどのブランチからも、svn HEADを見ればよい状態を担保します。
画像作成者がgitを使いこなせるのであっても、画像をgit管理下に置くのはメリットが少ないと考えます。せめて、リポジトリを分けたいところです。
git-svnを用いてsubmoduleとしてSubversionを使う、というアイデアはありますが、調査できていません。
なお、git自身でバイナリファイルを扱うことそのものは問題ありません。大量に画像を使わない&それらが頻繁に更新されないのであれば、git管理下に置くのがよいでしょう。
pull requestのやり方をどうするか
githubには、pull requestという機能があります。github上にpushされた変更点を、特定のリポジトリの特定のブランチに取り込むように依頼する機能です。
pull requestは、基となるリポジトリの位置によって2種類に分類されます。
- github上でforkを行い、forkされたリポジトリからpull requestする方法
- forkを行う代わりにブランチを作成し、同一リポジトリ内別ブランチからpull requestする方法
1.については、fork元のリポジトリにpush権限がない場合に行います。fork元のリポジトリにpush権限を持つ場合は、1.でも2.でもかまいません。github上のforkは重いので、2.で十分。本稿では、今後2.を前提とします。
pull requestは、「レビュー可能なような」コミット列からなるブランチをもって行います。普段から「意図が伝わるコミットのしかた」のように、コミットをわかりやすくしている方はそのままpull requestしましょう。「継続的コミット」のように、コミットが増えるような作業パターンを採用している人は、git rebase -i developなどを用いてコミットを「レビュー可能」にしましょう。
例えば、以下の2コミットがpull requestされたとします。
commit 2 +my $arg = 'test'; -use Data::Dumper; -print Dumper($kikisan_peropero); -$kikisan_peropero->func('test'); +$kikisan_peropero->func($arg); commit 1 +use Data::Dumper; +print Dumper($kikisan_peropero); +$kikisan_peropero->func('test');
意図が掴みづらいですね。さまざまな試行錯誤をしている過程で、printfデバッグなどを行って不必要なコードが混ざったり、ソースコードのフォーマットが変わったりすることがあります。上記の例は、まだ単純。このようなコミットが重なると、レビューを妨げます。試行錯誤の過程をpull requestに入れるのはやめましょう。ローカルのみに存在するブランチでは、このような履歴になってもよいです。しかし、それを人に見せるときには、「レビュー可能に」整形したほうがよいです。
レビュー可能に整形する過程において、期せずして振り返りレビューを自ら行うことにもなります。デバッグ用コードが残っていないか。コミットし忘れているファイルはないか。
なお、一つのpull request(≒featureブランチ)は小さいほうが望ましいです。大きな機能を作る際でも、ある程度分割をして取り込んだほうが負担が少ないです。
pull requestをする前に、以下のようなコマンドで対象となるコミット群がレビュー可能であるかどうか確認しましょう。
git log -p (派生元ブランチ)…(派生先ブランチ)
他にも、レビュー可能性を上げるためにやったほうがよいことがあります。
コミットメッセージについて、コミットの内容を端的に表し、他のコミットメッセージとフォーマットを揃えましょう。「コミットメッセージの書き方」、という記事があるので、参考にしてください。
各種lintでソースコードの見た目を整えておくことも重要です。また、githubサイト上でdiffを見たりする際の利便性のためにも、タブキャラクターは使わないほうがよいです。
pull requestの後処理
取り込んだブランチは、まめに削除します。 git push origin :feature/sugoi
他の人が消したブランチがローカルではまだ表示される場合は git branch -rd origin/feature/sugoi や git remote prune origin すればよいです。
リモートで消えているブランチがgit branch -a/-rで表示されなくなります。
しないこと
公開された歴史の破壊
自らの手元にしかないブランチのコミットは、黒歴史として葬ることができます。 しかし、いったん外に出した歴史は、破壊すべきでないです。
具体的には、git push -fをしないように。必然的に、pushしたブランチに関して、git rebaseしてはいけません。
git rebaseは、1からコミット群を繋ぎ直す作業です。よって、リモートブランチにすでにpush済みであれば、git push -fが必要となります。いったん出したpull requestは、そのブランチに新たなコミットを重ねてpush、再度pull requestを行います。もしくは、pull requestを取り下げ、新たなpull requestを作成します。その際、ブランチは新しくする必要があります。
過度なコミットの綺麗さの追求
バージョン管理ツールは、人間のミスをリカバーすることも目的の一つです。過度なコミットの綺麗さの追求はしません。最終成果物の品質を上げることに注力しましょう。