その手の平は尻もつかめるさ

ギジュツ的な事をメーンで書く予定です

最近のgitを使った開発フローについて

最近のgitを使ったWebアプリケーションのプロジェクトの開発フロー (主にブランチ運用) について記すものです.
なお前提としてGitHub Enterpriseを利用しています.

git-flow

大上段に構えたもののあまり特殊なことはしていなくて,基本的にgit-flowをそのまま踏襲しています.
git-flowについてはしっかりした解説記事がインターネット上に数多く存在しますからそれらを参考にしていただければと思いますが,ざっくり説明すると

  • masterブランチ,developブランチ,releaseブランチ,featureブランチ及びhotfixブランチがある
  • masterブランチは常にリリース可能な状態になっている (すなわち現在本番で稼働しているアプリケーションのコードと等しい)
  • developブランチは開発中の状態で,ステージング環境等に上がっている
  • releaseブランチはリリース準備の為のブランチで,developから派生しmasterにマージされる
  • featureブランチはdevelopブランチから切られるブランチで,新機能やバグ修正などが実装される
  • hotfixブランチはmasterブランチから切られるブランチで,緊急で修正しなければならないバグ修正などが実装される
  • 開発中はfeatureブランチがdevelopブランチにマージされる
  • 通常のリリースではdevelopブランチをmasterブランチにマージして,そのmasterブランチをデプロイする
  • hotfixリリースではhotfixブランチをmasterブランチにマージして,そのmasterブランチをデプロイする
    • hotfixブランチの内容は何らかの方法でdevelopブランチに反映する

という感じです (間違ってたら教えてください).

実際にそれぞれのパターンで或るブランチから或るブランチにマージする際はpull requestを発行し,それを経由してマージするという感じでやっています.
この時featureブランチをdevelopブランチにマージする場合や,hotfixブランチをmasterブランチにマージする場合はコードレビューを行い,適切なフィードバックや修正が行われてからマージされるという流れになっています.

なぜgit-flowのようになったかというと,developブランチが複数走るという状況が日常化してきたためです (後述).複数のコンポーネントが絡んでくるとリリース時期を調整したりなんなりをフレキシブルにやる必要があってですね……

実際のブランチ構成

  • master (1本)
  • develop (複数本, 後述)
  • release (複数本,後述)
  • backport (複数本,後述)
  • feature (複数本)
  • fix (複数本)
  • hotfix (複数本)

といった感じで存在しています.

featureとfixについては上記のgit-flowの説明的には「featureブランチ」にあたるものですが,それぞれ「新機能の実装」と「バグの修正」とで役割に応じてブランチを分けることで見通しを良くしています.
release, backportブランチについては後述.

ブランチの命名規則

ブランチの命名規則としては

  • develop/x.x.x (x.x.xはバージョン番号)
  • feature/<description>
  • fix/<description>
  • hotfix/<description>

という風に定めて運用しています.
かつてはfeature, fix及びhotfixブランチの<description>部分は詳細に記述すべきだとして,詳しく説明を書いていたのですが最近ではそこまで詳細じゃなくて良いかな〜という気持ちになっています.そもそもマージする時はpull requestを作るのでそのpull requestのタイトルでその内容が把握できるし,branch名長いとタイプする時とかめんどいやん? みたいな感じになったためです.
あとJIRAにGitHubプラグインが入っているとfeature/XXX-123のような風にJIRAのチケット番号をブランチ名に含めた上でpull requestを発行すると自動でJIRA側にトラッキングされて便利というのもあり,JIRAのチケット番号をブランチ名に含めることも多くなってきました.

リリース時

リリース時はdevelopブランチからrelease/x.x.x (x.x.xにはバージョン名が入る) というブランチを切り,そのreleaseブランチをmasterに向けたpull requestを出すというスタイルでやっています.
いちいちreleaseブランチを切る理由としてはreleaseブランチが切られた時点のスナップショットがmasterに取り込まれる事をはっきりさせたかったのと,リリース処理に係るコミット (例えばメタ情報が書かれているファイル中のバージョンを書き換えるなど) をdevelopに含めたくなかったという理由からです.
リリース後はmasterブランチから新たなdevelopブランチを作成し,そちらを今後の開発用ブランチにするという感じにしています.

hotfixリリース

hotfixリリースを行なう際,git-flowではmasterブランチから直接hotfixブランチを作成し,そのブランチ上で修正してmasterにマージしてリリースという流れが示されています.基本的にはこの方法でhotfixリリースを行なうことが多いです.
しかし一方で一旦ステージングで確認してからmasterに反映してhotfixリリースを行いたい場合もままあります.そうした時は

  1. developブランチからfixブランチを作成
  2. fixブランチ上で修正してdevelopブランチに向けてpull request作成 & マージ
  3. ステージング環境で確認
  4. masterからhotfixブランチ作成
  5. hotfixブランチ上で,fixブランチのcommitをcherry-pick
  6. あとは通常のhotfix flowに従う

という流れで対応しています.これで対応できない場合がまれにあるので (cherry-pick時にconflictしたり……) そういった場合は気合で対応しています.

なお,hotfix後にdevelopブランチへその内容を反映させる際は,masterブランチからbackport/x.x.xというブランチを作成し、それを対象developブランチへと向けたpull requestを作成し,マージするということでhotfix内容の反映・同期を行っています.

developブランチが複数存在していて並行に開発している場合

さて,プロジェクトがある程度の規模になってくるとdevelopブランチが複数走る状況になることがあると思います.実際に我々のプロジェクトについてもdevelopブランチが複数本存在する状況がままあるので,そのような時にどのように運用しているのかという事について以下に記します *1

複数のdevelopブランチが存在している場合はそれぞれのブランチでは独立してコミットを積んでゆき,可能であればそれぞれのdevelopブランチと対応するステージング環境を用意して開発を進めていきます.
それぞれのdevelopブランチ間での同期については,いずれかのdevelopブランチがリリースされた (つまりmasterにマージされた) タイミングで行っています.以下のような感じ (develop/1.1.0develop/2.0.0がある場合の例).

  1. develop/1.1.0がmasterに取り込まれる
  2. masterからbackport/1.1.0というブランチを作成する
  3. backport/1.1.0develop/2.0.0に向けたpull requestを作成
  4. Conflictしても泣かない,頑張って直す
  5. そのpull requestをマージして同期完了

とは言え,masterにマージする前にdevelopブランチ間で同期を取りたいこともあるとは思うので,その際はcherry-pickなりmergeなりをマニュアルでやって頑張りましょうという感じです.

hotfix時は上述のhotfixリリースのフローに従いつつ,複数のdevelopブランチにbackportするという流れを採っています.

その他雑感

マージされたブランチはどんどん消しています.リモートにゴミブランチがたまると良くない!

Ref:
moznion.hatenadiary.com

結び

以上のようなフローで最近は開発していますという話でした.
これらがオーバーキルっぽい場合はGitHub flowなども良いと思います.

追記

なぜdevelopブランチが複数あるのかという疑問が散見されたので,我々のケースについて書いておきます.
ざっくり言ってしまえばリリースサイクルの違いです.例えば大きめの新機能を実装しつつ既存コードの保守もするという時,その時点で稼働しているコード (master) から派生したdevelop/A (便宜上Aとします) の上で新機能の実装を入れてしまうと,保守のコードと新機能コードが混ざってしまい,保守のためのデプロイのタイミングで出て欲しくない新機能のコードまで露出してしまうことになるので,それを防ぐ (分離させる) という目的でdevelopを複数に分ける (例えばdevelop/Bで新機能の開発は行なう) というような形にしています.
保守の為のdevelopブランチでも,新機能の為のdevelopでも,どちらも独立したステージング環境では見ておきたいので……

*1:git-flowの解説ではdevelopブランチが複数存在している場合についてあまり言及されてない気がする……

git-reviewer 書いた

code review の reviewer 選出をする時,pull request の内容をざっと眺めてから「この部分だから XX さんかな」とか「あそこのコードは YY さんが詳しいだろう」とか,そういう感じで選ぶことが多くて,つまりは勘と経験で選びがちになってしまう.これについては常々いくばくかの危うさを感じていた.
そもそも,「reviewer として誰が最適か」という知識はプロジェクトに長く関わっている人でなければ知りにくいものであり,いわば属人的な知識のひとつだと思っている.プロジェクトからそういった長老的な人がいなくなってしまったら,最適な code review を実施できなくなってしまう可能性がある.


従って,やはり技術で解決ということになる.
Facebook が作っている mention-bot という GitHubbot として動作するやつがあって,これは pull request が送られてくるとその pull request について blame を実行して code reviewer の候補を出してくれるという気の利いた処理を自動で行ってくれる.

mention-bot は便利で,我々も使っているのだけれど若干の不満もある (おおよそ良いのだけれど……)

  • WIP の pull request だと,作業途中時点での reviewer が選出されてしまう.mention-bot は pull request が送られた時点での reviewer を選出してしまうので,WIP pull request との相性が悪い.
  • GitHub じゃないと動かない

後者は身も蓋もない話だけれど,前者については若干問題があるなーと思っていた.
任意の時点での reviewer 選出をもっと気軽にできれば良いのに,と.


というわけで,git-reviewer というスクリプトを書いた.手元で git のサブコマンドを実行することで reviewer 候補の選出が出来る.

使い方は至って簡単で,

$ git reviewer <into branch> <from branch>

としてやると,その brnach 間の diff について最適と思われる reviewer を選出してくれる (into branch を省略すると,current branch が into branch として扱われる).


仕組みとしては極めてシンプルで,

1. branch 間の diff を取る
2. diff が出た各ファイルについて,削除された行をかき集める (すなわち,diff の先頭に - が付いている行)
3. 削除された行 (つまり変更を入れられた行) のもともとの author を git blame により特定する
4. その author をかき集める

という処理を行い,頻出する author が reviewer 候補として選出される.
もしも diff に削除行が1つもない場合は,変更があったファイルの全行についてその author を集計し,その数が多い人を reviewer 候補として扱うようにしている.


このコマンドを手元で実行することで,手軽に reviewer 候補を知ることが出来て便利になった.めでたしめでたし.
何か「こうした方が良いのではないか」「おかしいのではないか」などがあったら教えて下さい.


[追記]
実行してみればお分かり頂けると思うのだけれど,git-reviewer の出力はとてもシンプルなものになっている.以下のような感じ.

moznion: 123
nozniom: 42
foobar: 2

これらは影響行数の降順として出力されるので,上に表示されればされるほど reviewer 力が高いという事になる.

もしも除外したい committer がいるならば,パイプで grep -v とかで除外すれば良いのかな〜とか思っていたのだけれど,確かに reviewer 側のオプションで食わせられるようにしても良いかもしれない.これが UNIX 哲学だ!! と頭ごなしに殴りつけても良いことはないのです.参考になりました.


以下はコミュニケーションの様子

git で管理しているリポジトリの各ブランチの中身をそれぞれ個別のディレクトリにエクスポートする

人生にはこういう事が度々あります.深く考えないようにすることにします.


その結果できたのがこれです.

リポジトリのディレクトリ以下でこのコマンドを叩くとイナフという感じです.


Enjoy!!

git grepの代わりにgit agを使う

git grepが便利なので,同じ感覚でag (The Silver Searcher)を使ってみたいという話です.何事も速いほうが良い.

前提

ぶっちゃけagは,デフォの状態で.git/以下の内容や.gitignoreに書かれてるファイルやディレクトリなんかを検索の対象から排除するのであんま旨味は無い.

方法

以下のエイリアスを張る *1.もちろんconfigファイルを直で編集しても良いです.

$ git config alias.ag '!git ls-files | xargs ag'

git ls-filesを使って,gitで管理されているファイルの一覧を持ってきて,xargsを使ってagに渡してやるという感じ.

実際僕はこれで十分なんですが,表示と挙動をgit grep(1)っぽくしたい場合は以下のようになるでしょう.

$ git config alias.ag '!git ls-files | xargs ag --pager="less -F -R" --nogroup --color-match=0\;31 --color-path=0\;0 --no-numbers'

--pager="less -F -R"はお使いのpagerに応じて変更するか,あるいはお好みに応じてpagerオプションを取り去ると良いでしょう.

結論

$ git ls-files | xargs $COMMAND

が基本的に便利!!!!!

*1:場合によって--globalオプションなどを付けても良いでしょう

git stashで作業内容をスタックに積んでる時に、それをコンソールに表示しておく方法

git stash便利ですよね!
「ちょっと別のブランチをcheck out したいな」みたいな時に大変重宝しています。
ただ、いろいろ便利なgit stash ですが、git stash pop をし忘れると色々と悲劇が起こります。
例えば……

・「あれ、前にここの部分は修正したはずなのに、なんで元に戻ってるんだろう」
疑問に思いながらもソースコードを修正してcommit → 残念! git stash pop のし忘れでした!
作業していたこの1時間は徒労!! イエーイ!!!

・「数件前のcommit で修正したってコミットログに書いてるけど、修正されてないよ」
残念! git stash pop をし忘れた為にそのコミットには反映されてませんでした!
で、git stash pop してみる → おや! 直前にcommit した内容と衝突してconflict してるぞ!! 面倒くせえ!

・「スタックに作業内容がたくさん積まれてるなぁ……どれだったっけ。まあいいや。とりあえずgit stash pop、っと」
残念! そのpop した作業内容は現在行なっている作業とは異なる内容でした!
謎のマージがガンガン行われているぞ!!

などなど……

「stash した内容は、その用事が終わったらすぐにpop しないと駄目だよ! 忘れずpop しようね!」

というルールを守れるのが一番好ましい訳ですが、stash した事自体忘れてしまう事が多々あります。にんげんだもの。*1
なので、stash した事を忘れない為にも、コンソール画面に「スタックに作業内容が積まれてるぞ! 早くpop しろ!」
警告を出して、悲劇を未然に防ぐ方法を以下に記したいと思います。


よういするもの

  • git-completion


てじゅん

  1. git-completion をインストールする(もう既に入ってる人は省略)
  2. .bashrc を書き換える
  3. /etc/bash_completion.d/git を書き換える


やりかた

1. git-completion をインストールする
Linux の場合はapt-get なりyum なりでインストールしましょう

例)
$ sudo apt-get install git-completion

Cygwin の場合はsetup.exe を使うか、apt-cyg(cyg-apt) を使ってインストールしましょう
[setup.exe を使う場合]

パッケージを選択する画面で、こんな感じでInstall するように指示すればオッケーです。

[apt-cyg(cyg-apt) を使う場合]
apt-cyg かcyg-apt が入っている前提で。以下のようにコマンドを打てばインストールされると思います。

例)
$ apt-cyg install git-completion

2. .bashrc を書き換える
環境変数PS1 に"\$(__git_ps1)"という文字列を組み込むと、
git のワークツリーがあるディレクトリに移動した時、ブランチ名がコンソールに表示されるようになります。
更にオプションを指定してあげると、様々な情報を提供してくれるようになります。*2

今回はstash に何かが入っている時に知らせてくれるようにしたいので、
"GIT_PS1_SHOWSTASHSTATE" という変数を有効にしてあげます。
なので、以下の様に.bashrc を書き換えます。

例)
export GIT_PS1_SHOWSTASHSTATE=1
export PS1="$PS1\$(__git_ps1)"
すると、stash されてスタックに何かが積まれている時にはブランチ名の後に'$'が付加されて教えてくれるようになります。

3. /etc/bash_completion.d/git を書き換える
これまでの手順で、stash されている事を教えてくれるようにはなりましたが、'$'マークだけだと味気ないというか、
最悪の場合、見落とす可能性があるので、表示する文字列を変えます。
(念のため、/etc/bash_completion.d/git のバックアップを取っておいたほうが良いかもしれません)

/etc/bash_completion.d/git に
if [ -n "${GIT_PS1_SHOWSTASHSTATE-}" ]; then
    git rev-parse --verify refs/stash >/dev/null 2>&1 && s="$"
という部分があるので、この末尾部分(s="$")を書き換えます。

例)
if [ -n "${GIT_PS1_SHOWSTASHSTATE-}" ]; then
    git rev-parse --verify refs/stash >/dev/null 2>&1 && s=":POPしろ"
こう書き換えると、スタックに積まれている時にはブランチ名の横に":POPしろ"と表示されるようになります。
これなら見落とす心配も少なくなりますね。

以上で作業は終了です。


本当はこうしたかった

":POPしろ"を赤い文字で表示して、警告感を煽ろうと試みたんですが、
s="\[\e[1;31m\]:POPしろ\[\e[00m\]"
と書いてやっても色が変わらず、エスケープシーケンスが文字列のまま表示されちゃうんですよね。
まあ、エスケープシーケンスがそのまま表示されれば、
それはそれで目を引くので警告としての役割はバッチリ果たしますがw

「こうすりゃできるよー」と教えてくれる方、歓迎です。

*1:「忘れてしまうから、stash した後はトイレに立たないこと」という自分ルールが出来るまでに至りました

*2:詳しくは→[http://d.hatena.ne.jp/gnarl/20110901/1314846241]