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

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

最近の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ブランチが複数存在している場合についてあまり言及されてない気がする……

builderscon tokyo 2016で話してきました

去年の話を今するのはどうかという感じですが *1,表題の通りbuilderscon tokyo 2016で話してきました.

builderscon.io

ビールサーバーを作ったという話です.スライドは以下です.

speakerdeck.com

発表ではデモも行ったのですが,サーバから水が一瞬吹き出てしまったり,ソレノイドバルブの機構が上手く動かなかったりと,やはり本番発表のデモには魔物が住んでいるというな〜感じでした.概ね上手く行ったとは思います! *2

今更ですが他の発表の感想など.

builderscon.io

ちょっと遅れて行ったのですが,mattnさんの変態的なモチベーションの話が聞けて楽しかった.Vimで動画を再生するデモで聴衆が大きくどよめいていたのが印象的.Windowsパッチでは日頃お世話になっております.

builderscon.io

変態的な話かと思ったら果たして変態的な話だったのだけれど,現実的な,未来のウェブ・ブラウザの話という感じで興味深かった.asm.jsでどこまで出来るのかがまだ分かっていないけれどいずれ触ってみたい.しかしEmscriptenって普通に実用レベルになってたんですね……

builderscon.io

ウェブアプリケーションのソフトウェアアーキテクチャについて,昔ながらのアプローチからFluxのような最新 (ナウい) 方法までをわかりやすく説明してて良かった.小さなウェブアプリの画面とかだと自分もvue.jsを使うことが多いのだけれど,そのvueがサンプルとして示されていて個人的に分かりやすかった.vuexはいずれ使ってみたい.

builderscon.io

格好良いの一言.キーボードを自作するのにあたってハードウェアからソフトウェアまでのフルスタックな技術の実戦を紹介していて極めて格好良かった.ジェダイは自らが使うライトセーバーをイチから自作するという話がスター・ウォーズの中にあるのだけれど,ソフトウェアエンジニアにとってキーボードはまさにライトセーバーにあたるそれで,これはすなわちジェダイの話だった.

感想ここまで.


さて結びになりますが,ビールサーバーの作成に係りましてご支援下さった皆様に感謝します (敬称略).
@, @, @, @, @, @

ところでビールサーバーが生活の中にあると非常にはかどりまして,実際もう4回ほど自作のビールサーバーが稼働しております.皆さんもやりましょう!

*1:本当に色々なことがあったのです

*2:しかしまだコードを公開していない……

Vimでコードをペーストする時にauto indentなんだかスマートな言語syntax解釈なんだかよくわからねえがとにかくインデントが崩れたり,突然ある行以降全部コメントになったりしてああああああああって時

タイトルの通りです.

:set paste

と打てば良い.ずっと知らなかったので今までの自分に苛立った一方感動のあまりデプロイが大成功するなどしました.

なお戻す時は

:set nopaste

とすれば良い.

ところで今までどうやってこうした問題を解決していたかというと,一切の設定をしていない素のVimが環境内に一個あって,それにはrawvimという名前すなわちコマンド名が与えられていてそれを使っていたのだけれど,これを機にrawvimはいなくなりましたとさ.めでたしめでたし.

[追記]

コメントでid:cho45さんとid:tyruさんからメッチャ便利情報もらいました.知らなかった,便利便利!

req_mirror書いた

req_mirrorというサーバアプリケーションを書いた.受け取ったHTTP requestをJSONにserializeして,responseに詰めて返してくれるというやつ.つまりHTTP requestをおうむ返しにしてくれる雰囲気です.鏡的な挙動なのでreq_mirrorという名前にした次第.

github.com

Exampleのところにも書いているのだけれど,要はこういう動き方をします (localhost:22222でreq_mirror serverが立ち上がっている前提).

$ curl -s -XPOST -F "foo=bar" -F "buz=qux" 127.0.0.1:22222/hoge/fuga | jq .
{
  "Method": "POST",
  "URL": {
    "Scheme": "",
    "Opaque": "",
    "User": null,
    "Host": "",
    "Path": "/hoge/fuga",
    "RawPath": "",
    "ForceQuery": false,
    "RawQuery": "",
    "Fragment": ""
  },
  "Proto": "HTTP/1.1",
  "Header": {
    "Accept": [
      "*/*"
    ],
    "Content-Length": [
      "236"
    ],
    "Content-Type": [
      "multipart/form-data; boundary=------------------------74755673fc9beca6"
    ],
    "Expect": [
      "100-continue"
    ],
    "User-Agent": [
      "curl/7.43.0"
    ]
  },
  "Body": "--------------------------74755673fc9beca6\r\nContent-Disposition: form-data; name=\"foo\"\r\n\r\nbar\r\n--------------------------74755673fc9beca6\r\nContent-Disposition: form-data; name=\"buz\"\r\n\r\nqux\r\n--------------------------74755673fc9beca6--\r\n",
  "TransferEncoding": null,
  "Host": "127.0.0.1:22222"
}

はじめて使うHTTP Clientの挙動を確かめながらコード書く場合とかに地味に便利.あとは自作のHTTP Clientのテストをする時とかに威力を発揮します (発揮している).

まあコード自体はめちゃめちゃシンプルというか特になにもやっていないんですが,あると便利だったので外に置いた次第.なおhttp.Requestをそのままjson.Marshalに投げ込んだら処理系に怒られたという心温まるエピソードが開発中にはありました.
以上です.

PHPでテストに使うための空きポートを取ってくる

kazuhoさんのこの記事と同じことがPHPでも出来る.

d.hatena.ne.jp

<?php
$sock = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
socket_bind($sock, '127.0.0.1', 0); // localhostのport 0をbindする
socket_getsockname($sock, $address, $port); // ここの$portに取得した空きポートが入ってくる
socket_close($sock); // closeしないともちろんalready bindが出るので閉じておく

以上です.元記事にもあるように競合する可能性がありますが,テスト用途だったらなんとかなるでしょう.

最近の社内Wikiの書き出し

最近,社内Wiki書く時にその記事の頭に「この記事で分かること」というセクションを持ってくるようにしている.こんな感じ.

f:id:moznion:20161118190135p:plain

wikiのページを開いた時に真っ先に「どういう情報が得られるか」が書いてあると,取捨選択を早い段階から行えるので調べ物のスピードが上がって良いような気がしている.
あと所属している組織ではConfluenceを使っていて,Confluenceの検索機能に引っかかるような (引っかけやすいような) ワードを「この記事で分かること」に含めるように心がけている.
地味に便利になりつつある気がしている.

[追記]
そう言えばConfluenceの検索結果は「タイトル」と「サマリ」が出るんだけど,冒頭にこういう情報を書いておくとそれがサマリ部分に表示されるから,検索結果一覧の段階から情報の取捨選択できて便利というのもあった.

golangで書いたツールをCircleCI上でビルドしてその成果物をGitHub Releasesにリリースする

表題の通り.いくらかポイントがあったのでメモとして記す.

基本的にこの記事の内容を真似した.

medium.com

あらかじめ,CircleCIの側の設定でGITHUB_TOKENという環境変数を登録しておく.なおGitHubのPersonal access tokenにはrepoのpermissionを付与しておく.

とは言えこれでは動かない.理由はghr (ghrについてはこちら: 高速に自作パッケージをGithubにリリースするghrというツールをつくった | SOTA) がcontextに依存しているからで,contextはgo 1.7以降でないと利用できない.しかしながらCircleCIのgolang環境は1.6系が使える内の最新なので *1 このままではghrをgo getすることが出来ない.
というわけでCircleCI上でgo 1.7を使うようにしましょう,ということでそういうconfigをcircle.ymlに書く.基本的にこのgistの真似.
しかしこのgistにはタイポがあってそのままでは動かない.ので以下のようにする.

machine:
  environment:
    GODIST: "go1.7.3.linux-amd64.tar.gz"
  post:
    - mkdir -p downloads
    - test -e downloads/$GODIST || curl -o downloads/$GODIST https://storage.googleapis.com/golang/$GODIST
    - sudo rm -rf /usr/local/go
    - sudo tar -C /usr/local -xzf downloads/$GODIST

こうしておくとgo 1.7.3がビルド時に利用されるようになる.

そして以下のようなdeploymentセクションを書く.オリジナルの記事ではmasterにpushする度にGitHub Releasesにアップロードされてしまうので,git tagがpushされた時にだけリリース処理が走るようにする (deployment.release.tagの部分でそうしている).

deployment:
  release:
    tag: /[0-9]+\.[0-9]+\.[0-9]+/
    commands:
    - go get github.com/tcnksm/ghr
    - make clean
    - make VERSION=`git describe --tags | perl -anlE 'm/\A([^\-]+)-?/; print $1'`
    - rm bin/.gitkeep
    - ghr -t $GITHUB_TOKEN -u $CIRCLE_PROJECT_USERNAME -r $CIRCLE_PROJECT_REPONAME --replace `git describe --tags | perl -anlE 'm/\A([^\-]+)-?/; print $1'` bin/

その他の細々した差としては,ghrのオプション部分で--replace `git describe --tags`として渡されている部分を--replace `git describe --tags | perl -anlE 'm/\A([^\-]+)-?/; print $1'`というperlワンライナーを噛ませた形に書き換えている.git describe --tagsを実行すると{タグ名}-{ハッシュ}という結果が出てくる場合があるので,タグ名以降の内容を落とすためにそうしている.
あとは.gitkeepがアップロード元のディレクトリに含まれているとghrのアップロード時に失敗する (そんなものアップロードするな,みたいなエラーが出る) のであらかじめ消しておくという感じ.
なおビルド自体はmakeコマンドを叩くだけでglide installからクロスコンパイルまでやるという感じにしている (Makefileはこんな感じ
https://github.com/moznion/linenotcat/blob/4349058d9557d49e1f2fc9a23ebeb79e2279f81d/Makefile).

最終的なcircle.ymlとしては以下のような感じ.

machine:
  environment:
    GODIST: "go1.7.3.linux-amd64.tar.gz"
  post:
    - mkdir -p downloads
    - test -e downloads/$GODIST || curl -o downloads/$GODIST https://storage.googleapis.com/golang/$GODIST
    - sudo rm -rf /usr/local/go
    - sudo tar -C /usr/local -xzf downloads/$GODIST
deployment:
  release:
    tag: /[0-9]+\.[0-9]+\.[0-9]+/
    commands:
    - go get github.com/tcnksm/ghr
    - make clean
    - make VERSION=`git describe --tags | perl -anlE 'm/\A([^\-]+)-?/; print $1'`
    - rm bin/.gitkeep
    - ghr -t $GITHUB_TOKEN -u $CIRCLE_PROJECT_USERNAME -r $CIRCLE_PROJECT_REPONAME --replace `git describe --tags | perl -anlE 'm/\A([^\-]+)-?/; print $1'` bin/

これでtagをpushしたら自動的にGitHub Releasesにビルド成果物がリリースされるようになった.めでたしめでたし.それはそうとghr便利ですね.

[追記]

確かに!!!! ghrのバイナリダウンロードしてくれば良いですね.