肉とビールとパンケーキ by @sotarok

少し大人になった「肉とご飯と甘いもの」

第59回PHP勉強会のLTでGITLABの紹介してきました

資料あげておきます。LTだしほとんどデモだったし、資料自体にあまり意味ありません。。



喋ったこと

クロコスでの、Pull Request ベースの開発フローとか、具体的な話はまた別の機会にさせてもらおうかなって思います。
前回ブログ書いたときは、こうしたほうがいいな、こうしようかな、と色々思ってた段階だったんだけど、最近だと、それを実践してから数ヶ月たった状態なので、またそのへんをネタにしてブログにでも書きますかーとおもってます。


その他

  • 会場は弊社クロコスでした。
    • ちょっと迷いやすいけど&ちょっと狭いけど、でも良い場所でしょ?
    • お越しいただいた皆様、ありがとうございました
  • 開催に関して: おしゃれな照明とおしゃれな音楽がかけられるといいな
  • @synboo さんがつくっているというセキココ、楽しい イベント座席表サービス - セキココ
    • ロゴかわいい。椅子すわるやつ。
    • 勉強会を通じた人との出会いとか、あとからあの人だれだったんだっけというのにすごく良いと思う
    • PHP勉強会のやつはこちら 第59回 PHP 勉強会@東京 #phpstudyの座席表 - セキココ
    • 座席の設定をもっと柔軟にできたらいいかも
    • 課題としては、
      • 事前に席がわからないから事前に登録できない → 開催中に登録する or 開催おわってから登録することになる
      • 開催中に登録の難しい点: 開催中に、このURLを周知すること、とか。モバイル対応とか。
      • 開催終わってからやる場合の難しい点: このURLの周知、席わからなくなる、とか。
  • クロコスのエンジニアブログもよろしく Crocos Engineering Blog
  • LT中に議論が飛び交うとても楽しい勉強会だった。双方向性大事。こういう活発な勉強会もっとやりたい。
    • そのために色々ネタが出てくるのはとても良い
    • みんな簡単なものでもいいのでどんどん発表しよう
    • 今回は gusagi さんが燃料投下大賞だった。
  • 幹事 id:gusagi さんおつかれさまでした。ありがとうございました。

「Pull Request」 はオープンソースに限らず使える優れた開発フローだ

チーム開発において、「チケット/Issue」「TDD」「コードレビュー」など、ソースコードの変更に対する効果的な開発フローについてよく考えるのだけど、なんにしてもこのあたりは非常に課題が多く、各社各コミュニティで色々なやり方が模索されているポイントだと思う。

で、まぁご多分に漏れず僕もよく考えるわけだけど、現状その過程で Pull Request こそが非常に効果的なのではないか、と思うので、ちょっとまとめてみようかと思う。
もちろん、言うまでもないようなことだよ、という人もいるかもしれないけど、そういう人がたくさんいると、非常に喜ばしいことだね。

Pull Request とは

GitHub でこう呼ばれているので、こう呼ぶことにするが、ここでは、複数のリポジトリ/ブランチ間でのオープンな patch のやりとりのことだと考える。
あと、自分が使っているのが Git なので、ここでは Git について扱うけど、Git 以外の分散VCSでも当てはまる議論だと思う。

前提条件
  • 複数人で1つのソフトウェアを開発する
  • 分散型VCS (Git。もちろん Mercurial などでも良いと思う) で、各自がリポジトリを保持している。
  • チーム開発なので全員が同じものを開発していて、リリースマネジメントの過程では、どこかに「マスター」的なものが存在する
    • つまり、ここからリリースしますよ、という大元。
    • Linux などでは Linus が持っているリポジトリ、なのかもしれないけど、大抵の場合はGitサーバ (e.g. GitHub) などのようなものが存在しているよね?
  • 各自は自分のリポジトリで、開発ブランチまたは開発ブランチから派生したブランチで開発を行う
Pull Request

フローを手順化すると、概ねこういうことになる:

  • あるリポジトリ A の develop ブランチから fork したリポジトリ A' の develop ブランチがあり、そこに変更を加えたコミットが存在する。
  • リポジトリ A の管理者 (あるいは、その変更箇所に対する責任者) に、「A' の develop ブランチに変更おいてあるので pull してくれ」と要求する
    • それが Issue (チケット) 化される
  • A の責任者がそれを確認し、A' -> A に merge を行う。


ちなみに、GitHub では、fork して変更して push し、元のリポジトリのページを開き、Pull Request というボタンを押して、どのブランチをどのブランチに Pull Requset をするか、という確認をし、確定すると完了。Request された人は、diff を確認したり、その変更されたブランチをチェックアウトしてテストを行ったりして、問題がなければ merge 、というフローとなる(fast-forward 可能であればウェブ上からもボタン1つで merge できる)。

ソースコードに対する責任とカオス化

複数人での開発で必ず伴うのは「人が書いたコードでは足りない機能を実現する」ことだ。

ここに登場するのは、ある機能を提供するサービス *1 と、それを書いた Alice さんがいるとする。

このとき、ソースコードに対する責任は、Alice さんにある (とする)。これは、よくあることだと思う。*2

そして、そのサービスを利用した機能を実装していた Bob さんが、このサービスで提供されている機能だけでは足りないことに気づいたとする。そうしたときに、いくつかのやり方があると思うけど、それをちょっと書きだしてみようかと思う。

変更・対応を依頼する

ひとつは、Alice さんに「こういう機能が足りないんだけど、追加してほしい」と依頼することだ。これは、縦割りになった会社や、そもそも会社が違ったり、クローズドなライブラリを利用している場合にはよくあることだと思う。

ただ、 オーバーヘッドが大きいという問題がある。Alice さんがいつ対応してくれるか、という問題 *3。変更の依頼・対応は、コミュニケーションコストが大きいし、「今すぐ欲しいのに」にはどうしても対応できない。さらに、コミュニケーションミスで、変更されたのに依頼された機能が提供されない場合もある。そしたら差し戻しだし、さらにコストはかさむ。

しかし、ある程度これは、業務形態とか色々な要因 (外部サービスを利用する場合など) によって、たとえオーバーヘッドが大きくても、こうせざるを得ない場合も多いね。まぁそれは仕方がないけど..

変更し、確認をとりコミットする


よくコミュニケーションのとれたチームではこれが出来ると思う。
Bob さんも、ある機能を追加したい場所を探し当て、そこに変更を加える。diff を見せて、「こういう変更をしたいんだけど」と Alice さんに確認をとる。Alice さんは、「大丈夫だ問題ない」と答え、Bob さんはそれをコミットする。

変更せずに解決する方法を探す


当該箇所には変更せずに自分の希望する機能が提供されるようにする。
これは、Aliceさんの責任範囲には影響しない自分の責任でコードを追加するというイメージ。

しかし、こういうことを繰り返すと、謎のオプションが増えたり、似た様なメソッドが増えたりして、後述のカオス化が完成する。

勝手に変更する



最も悪いのはこのパターン。勝手に変更してしまう。あとから Aliceさんが「おいちょっと何この変更」となることは目に見えている。
テストでこけてくれればまだ良いが、気づかずにバグが混入している場合もある。

カオス化

一番ツライのは、「変更せずに解決する方法を探す」と「勝手に変更する」だ。
Alice さんが作ったらしき機能に変更を加えずに自分の欲しい機能を追加していったり、あるいはAliceさんの意図とは別の意図がコードにどんどん追加されたりすると、そういったものが積み重なって、例えば「似た様な機能を提供するメソッドが別名でたくさんある、しかもいたるところで両方が使われているしどっちも消せない手が出せない」みたいなことがおこってしまいにはちょっと違うことをやる似た様な機能について更に同じようなことが繰り返される。カオスである。

こうなると誰も手を付けられないので「あーそこは樹海だからさわらないで」みたいなものが完成する。おめでとう。

Pull Request がもたらす「小さなパッチ」とコードレビューとリファクタの機会

そもそも、Git の利点として、自分の元にリポジトリを持てる、気軽にブランチを切り気軽にマージできる、というのがあるけど、Pull Request こそ、これを最大限生かした理想的な開発フローなのではないか。

上述のやりとりを Pull Request に置き換えると、次のようになる。

  • Alice さんが責任をもつ箇所について、Bob さんが変更し、patch をつくる (小さなパッチ)
  • Pull Request を送る
  • Alice さんがそれを確認する (レビューとリファクタ)
  • 変更を取り込む

上述でいうところの「変更し、確認をとりコミットする」によく似ているけど、

  • Git の分散性、ブランチ機能を生かした効率的なフローがシステムよって提供され、
  • オープンにやりとりされる、

という点が異なる。

小さなパッチを繰り返すこと

ソースコードへの変更は、大きければ大きいほど影響範囲やバグの混入する確率も大きくなるので、パッチは小さければ小さい方が良い。
できれば単一機能についてその都度パッチがあるのが良い。

だから、小さな変更でも、自分の責任下で済まないソースコードの変更は、すべて Pull Request になっているべきだ。小さな diff であればあるほど、小さなコストで確認ができる。それを繰り返すことが重要だ。

コードレビューの機会を設けること

コードレビュー会のようなものが開かれるチームもあると思うけど、色々な変更が含まれた大きな diff や、複数人が責任をもったソースコードについて、いっぺんにたくさんのコードをレビューするのは非常に大変だし、コストが大きい。その点、Pull Request であれば、小さな patch ごとにレビューが入るため、コストが小さく、議論が1点に集中できるため質も高い。

また、防げるべきことが防げる、という利点もある。
Bob さんは、その変更の影響範囲はわかっていたつもりで、それに対応したつもりだけど、想定できていなかった影響範囲について、Aliceさんのチェックが入る。これは非常に大きなことだ*4。「勝手に変更された」場合、あとからこの変更を知り「ちょっとなんでこういう変更になってるの?」となってしまうし、できるだけそれは避けたい。

コードに対するチェックの目は多ければ多いほうが良いが、とはいえ毎回全員の変更を誰かがあるいは全員がチェックし合うのは非常に大変。自分が書いたことのある単一機能に対して他の人が変更加えたいときに、どう変更されるかをチェックできる、という仕組みが Pull Request によって、コストの低い形で提供される。

リファクタされる

Bob さんが欲しい機能についての変更がレビューされるということは、Aliceさんが、それがその機能を提供するための変更にとって良い変更なのか、がチェックされることになる。
あるいは、「このメソッドはこういう意図で使われるからこの変更はココじゃないほうがいいなあ、こっちのメソッドにオプションを作ろう」のような提案ができるかもしれない。

小さなdiffでリファクタされることは、カオスになりきった大きなコードをあとからリファクタすることよりもコストが非常に小さく済む。

オープンであること

Alice さんと Bob さんが、こういうやり取りをしてこの変更が加えられたのだ、ということが他の人に分かる形で残る、というも非常に良い。

上記の「変更し、確認をとりコミットする」では、Alice さんと Bob さんの間では共通認識の確認がとれるが、そこに Charlie さんがまた変更したいと思った場合に、その時のやり取りがわかるかわからないかで、考えられることが違うと思う。その機能を提供するコードが、どういう意図で書かれているのか両人の意図がすぐにわかるはずだ。

まとめ

  • ちいさなパッチを常々レビューしよう
  • そのために、仕事でも Pull Request を使おう。
    • 「GitHub を仕事で使う」はモチロンOKだと思います。
      • お金のあるところは GitHub Enterprise が手っ取り早い。まじ高いです。
      • そうでないなら Organization で有料プランを使うのがよさげ
    • GitHub はちょっと... という人は Gitlab オススメ。Merge Request という機能があります。 *5


ちなみに、Redmine で Pull Request 的なことを何度かやってみたけど、チケット化して当人同士で管理するのはできるけど、どのブランチからどのブランチへどの diff を? のような記録を明示的に残すのが難しいなーと感じた。


繰り返しになるが、patch のやり取りやコードレビューを、システムによって効率的に低コストで行うことができるようになったことが、Git の、GitHub の革命であり、これを開発フローに取り込むというのは非常に有効な手段だと思う。


(追記) :

すいません、すいません、本当に忘却してました。
メールでのやりとりで、request-pull / am でパッチのやりとりをする機能が Git にはありますね。


オススメ:
入門Git (濱野 純(Junio C Hamano)) - Amazon

*1:とここでは呼ぶけど、MVCでいうところのモデル的な場所のこととか、リポジトリクラスとか、まぁだいたいそういうイメージ。

*2:ちなみに、ちょっと横道にそれるけど、自分が書いたコードに対する責任をちゃんと持つようにしましょう

*3:Aliceさんがすぐ対応してくれる場合もある。だけど、その場合Aliceさんにとっては差し込みの仕事だし、これはAliceさんにとってのコストが増大する

*4:テストが書いてある、というのも重要だけど、それは別の話になるので今回は割愛

*5:そういや横道にそれるけど、何度も Twitter などで言っているけど、GitHub クローンなどと称されるプロダクトはちょいちょいでてきてるのだけど、Pull Request こそが GitHub が起こした革命だと思うので、これが無いものは GitHub クローンなどではなく、ただの Git リポジトリビューワーですね。

git add -p のときの e(手動編集)

自分ではあまり add -p の e ってつかったことなかったんだけど友達がよくわからんってことだったので調べてみた。

以下のような diff を add -p して、 e してみます。

$ git add -p
diff --git a/fuga b/fuga
index 1a39df0..5bc676d 100644
--- a/fuga
+++ b/fuga
@@ -1,5 +1,5 @@
 //
 hoge
-fuga
 piyo
 hogera
+fugera
Stage this hunk [y,n,q,a,d,/,s,e,?]? e


で、エディタが起動するわけです。以下のような感じで書いてあります。

# Manual hunk edit mode -- see bottom for a quick guide
@@ -1,5 +1,5 @@
 //
 hoge
-fuga
 piyo
 hogera
+fugera
# ---
# To remove '-' lines, make them ' ' lines (context).
# To remove '+' lines, delete them.
# Lines starting with # will be removed.
#
# If the patch applies cleanly, the edited hunk will immediately be
# marked for staging. If it does not apply cleanly, you will be given
# an opportunity to edit again. If all lines of the hunk are removed,
# then the edit is aborted and the hunk is left unchanged.

ここで出てくる謎のメッセージ(いや、謎でもないんだけど)

# To remove '-' lines, make them ' ' lines (context).
# To remove '+' lines, delete them.
# Lines starting with # will be removed.

これの意味がわかりづらいですね。先に結論を言うと、

  • - の行を add したくなかったら - を スペース (' ') にかえてください
  • + の行を add したくなかったらその行を削除してください
  • # から始まる行は、add されません

ということなんですが、'delete' が hunk からの削除 = diff からの削除、つまり、add する patch に含めない、つまり、add しない、ということです。

読み替えると、

  • 削除行 (-) を add しないなら、- をスペースにかえてください
  • 追加行 (+) を add しないなら、その行を削除してください
  • add したくない行は先頭に # って書いてください

ですかね。

実際にやってみる

エディタ上で、次のようにしてみます。 (- を ' ' に変更)

# Manual hunk edit mode -- see bottom for a quick guide
@@ -1,5 +1,5 @@
 //
 hoge
 fuga
 piyo
 hogera
+fugera

で、エディタを閉じると、次のようになります。

$ git status
# On branch master
# Changes to be committed:
#
#       modified:   fuga
#
# Changed but not updated:
#
#       modified:   fuga

$ git diff
diff --git a/fuga b/fuga
index 387b231..5bc676d 100644
--- a/fuga
+++ b/fuga
@@ -1,6 +1,5 @@
 //
 hoge
-fuga
 piyo
 hogera
 fugera

$ git diff --cached
diff --git a/fuga b/fuga
index 1a39df0..387b231 100644
--- a/fuga
+++ b/fuga
@@ -3,3 +3,4 @@ hoge
 fuga
 piyo
 hogera
+fugera

hunk から削除した「fugaの行の削除」という diff が add されてないですね。

つまりそういうことでした

まぁわかってしまえば簡単ですが最初はよくわからないかも。
+ の行の削除とかも、気になったら試してみてね。


多分自分では add -p の「e」は使わないと思うけどw

#pyfes で git-daily について話してきました

git-daily について #pyfes で話してきました。


Webアプリケーション開発におけるGitのブランチ運用戦略についての話です。
パッケージものの開発などと大きく異なるのはやはりリリース頻度や、リリース環境です。デプロイサーバが登場するとかもそういうことですね *1

ちなみにこのへんの、概要的な話は、GREEのころエンジニアブログにも書きましたので、そちらを参照ください。

発表資料

SpeakerDeck におきました (はてなに埋め込めるようにならないかなー)

demo の内容

demoはコマンドの切り替えが速すぎて流れが読めない的な感じになってしまってすみません。良い機会なので、図と一緒にまとめようかと思います。

前提とストーリー

以下の話はgitflowのブランチモデルを採用したgit-dailyでのリリース作業のフローですね。


前提:

  • 開発者A (a_dev) さんと開発者B (b_dev)、deployサーバ(deploy)、Gitリポジトリサーバ(server)がいます
  • Gitサーバは、GitHub でも gitosis でも代わりません。bareリポジトリを保持していてアカウント管理されているリモートのサーバだと思ってください
  • deployサーバで、リリースマネージャ的な人が、リリースブランチ切ったりmasterにマージする人だったりします。

ストーリー:

  • Aさんがリポジトリを作成し、serverにそれをcloneします
  • Aさん、Bさんはdevelopブランチで開発を進めます
  • developブランチの開発がある程度まとまってリリースしたいという状況になります
  • deployサーバで、リリースマネージャ的な人がリリース作業を開始します。
  • リリース前に、修正が必要なことがわかりました。Bさんが手元で修正してリリースブランチにpushします。
  • 修正が完了し、リリースブランチをproductionにデプロイしました。その後、deployサーバでreleaseを閉じる作業をします。
  • 各開発者は、syncすることでremoteで閉じられたリリースブランチを、ローカルから掃除します。


※コマンドのログ中にある右に出てるのはRPROMPTです。ディレクトリ名とかブランチ名が出てます。

リポジトリの作成とserverへのclone

まず基本的なところですが、Aさんがリポジトリを作成してserverにcloneします。serverにclone するところですが、serverやら、Bさんの開発環境などは、すべて別のサーバにある前提です。
今回は、demoシミュレーションについては、同じサーバ内でやりますが、ディレクトリがそれぞれの開発環境/サーバに対応すると思ってください。また、RPROMPTにディレクトリ名出しておくので、cd とか書いてないのにディレクトリ移動してる場合とかはそっちを見て判断してください(とかいう)


デモに用いるディレクトリ。最初は空です。

% ls -la                                             [~/tmp/gitdaily-demo]
合計 8
drwxr-xr-x  2 sotarok sotarok 4096 2011-10-15 16:10 .
drwxr-xr-x 50 sotarok sotarok 4096 2011-10-15 13:59 ..

Aさんがリポジトリを作成

% mkdir a_dev                                        [~/tmp/gitdaily-demo]
% cd a_dev                                           [~/tmp/gitdaily-demo]
% git init                                       [tmp/gitdaily-demo/a_dev]
Initialized empty Git repository in /home/sotarok/tmp/gitdaily-demo/a_dev/.git/
% touch README                           [tmp/gitdaily-demo/a_dev @master]
% git add README                         [tmp/gitdaily-demo/a_dev @master]
% git commit -m "initialize"             [tmp/gitdaily-demo/a_dev @master]
[master (root-commit) d7a313d] initialize
 0 files changed, 0 insertions(+), 0 deletions(-)
 create mode 100644 README

これでリポジトリが作成されました。今回は、gitosisなどを用いないため、server というディレクトリ名でmirrorを作成します。--mirrorは、bareリポジトリとしてcloneするという意味です。

% git clone --mirror a_dev ./server                  [~/tmp/gitdaily-demo]
Cloning into bare repository ./server...
done.
% ls server                                          [~/tmp/gitdaily-demo]
HEAD      config       hooks  objects      refs
branches  description  info   packed-refs

Aさんがこれをoriginとしてremoteに追加します。これで、なんとなくGitHub <-> 開発者、とか、社内のGitサーバ <-> 開発者、の関係性がイメージ出来る状態になったと思います。

% cd a_dev                                           [~/tmp/gitdaily-demo]
% git remote add origin ../server        [tmp/gitdaily-demo/a_dev @master]
% git remote                             [tmp/gitdaily-demo/a_dev @master]
origin
% git fetch origin                       [tmp/gitdaily-demo/a_dev @master]
From ../server
 * [new branch]      master     -> origin/master
% git branch -a                          [tmp/gitdaily-demo/a_dev @master]
* master
  remotes/origin/master
developブランチの作成とBさんの開発への参加

develop ブランチは master ブランチからの派生で作ります。また、それをoriginにもpushします。

% git branch -a                          [tmp/gitdaily-demo/a_dev @master]
* master
  remotes/origin/master
% git checkout -b develop                [tmp/gitdaily-demo/a_dev @master]
Switched to a new branch 'develop'
% git push -u origin develop            [tmp/gitdaily-demo/a_dev @develop]
Total 0 (delta 0), reused 0 (delta 0)
To ../server
 * [new branch]      develop -> develop
Branch develop set up to track remote branch develop from origin.

続いて、Aさんのローカルで、git daily initしておきます。masterとかどうする?とか聞かれますが、ここで他のブランチ名を指定することも可能です。何も入力しなければ、デフォルトです。

% git daily init                        [tmp/gitdaily-demo/a_dev @develop]
Your remote is [origin]
Name master branch [master]:
Name develop branch [develop]:

git-daily completed to initialize.

続いて、Bさんの環境も整えます。Bさんは、最初から server から clone してきます。また、git daily init もしておきます。

% git clone server ./b_dev                           [~/tmp/gitdaily-demo]
Cloning into ./b_dev...
done.
% cd b_dev                                           [~/tmp/gitdaily-demo]
% git log                                [tmp/gitdaily-demo/b_dev @master]
commit d7a313d863aa1938d4aff0caf43460e30562ffb7
Author: Sotaro KARASAWA <sotarok@crocos.co.jp>
Date:   Sat Oct 15 16:11:16 2011 +0900

    initialize

% git daily init                         [tmp/gitdaily-demo/b_dev @master]
Your remote is [origin]
Name master branch [master]:
Name develop branch [develop]:

git-daily completed to initialize.

% git branch -a                         [tmp/gitdaily-demo/b_dev @develop]
* develop
  master
  remotes/origin/HEAD -> origin/master
  remotes/origin/develop
  remotes/origin/master

また、deployサーバという位置づけで、 deployというディレクトリにも clone を作って git daily init しておきます。(結果省略)

% git clone server ./deploy                          [~/tmp/gitdaily-demo]
% cd deploy                                          [~/tmp/gitdaily-demo]
% git daily init                        [tmp/gitdaily-demo/deploy @master]
% git branch -a                        [tmp/gitdaily-demo/deploy @develop]
* develop
  master
  remotes/origin/HEAD -> origin/master
  remotes/origin/develop
  remotes/origin/master


ここまでの状況を図にすると以下のようなかんじです。(a_dev意外のリポジトリ内部の構成は省略)

リリースしたい状況、deployサーバでrelease作業の開始

developブランチで開発がすすめられてしばらくたったとしましょう。リリースしても良いかな、と思える状況になったとします。

以下はAさんの手元のdevelopブランチです。開発中、originへのpush/pullは適宜行われているものとします。この例では、--decorateしてみてmasterから、開発が2コミット分進んでいることがわかります。

% git log --oneline --graph --all --decorate
* efbe37b (HEAD, origin/develop, develop) modify comment
* 107be63 added sample
* d7a313d (origin/master, master) initialize

さて、deployサーバで、リリースマネージャ的な人が、release openします。現在は、まだ一番最初にcloneしただけの状態です。

% cd deploy                                          [~/tmp/gitdaily-demo]
% git log --oneline --graph --all --decorate         [~/tmp/gitdaily-demo/deploy @develop]
* d7a313d (HEAD, origin/master, origin/develop, origin/HEAD, master, develop)

git daily release open すると、日付でreleaseブランチが作成され、自動的にremoteにpushされます。あ、ちなみに最新のdevelopがremoteから取得された後openされます。

% git daily release open               [tmp/gitdaily-demo/deploy @develop]
[INFO] first, fetch remotes
Confirm: create branch release/20111015-1635 from develop ? [yN] : y
[INFO] merge develop branch from remote
[INFO] create release branch: release/20111015-1635
[INFO] push to remote: origin
To /home/sotarok/tmp/gitdaily-demo/server
 * [new branch]      release/20111015-1635 -> release/20111015-1635
release opened

% git branch -a          [tmp/gitdaily-demo/deploy @release/20111015-1635]
  develop
  master
* release/20111015-1635
  remotes/origin/HEAD -> origin/master
  remotes/origin/develop
  remotes/origin/master
  remotes/origin/release/20111015-1635

% git log --oneline      [tmp/gitdaily-demo/deploy @release/20111015-1635]
efbe37b modify comment
107be63 added sample
d7a313d initialize

この状態で、デプロイサーバから、stagingサーバなどへdeployし、確認作業などをするわけですね。以下では、たとえば capistrano (いや実際設定とか作ってないけど)

% cap staging deploy     [tmp/gitdaily-demo/deploy @release/20111015-1635]

ここまでを図にすると以下のような感じです。

releaseブランチ上で修正作業

さて、staging環境などでテストをしていたら、このままではリリースできない、修正したいなー、という状況が発生しました。
この修正はBさんが担当することにします。

Bさんは、最初、release ブランチをローカルにもっていません。remoteにしかない状態ですね。

% git branch -a                         [tmp/gitdaily-demo/b_dev @develop]
* develop
  master
  remotes/origin/HEAD -> origin/master
  remotes/origin/develop
  remotes/origin/master
  remotes/origin/release/20111015-1635

ここで、git daily release sync します。すると、remoteにあるreleaseブランチがローカルのブランチとしてチェックアウトされます。

% git daily release sync                [tmp/gitdaily-demo/b_dev @develop]
[INFO] first, fetch remotes
[INFO] cleanup remote
[INFO] checkout and tracking release/20111015-1635
Switched to a new branch 'release/20111015-1635'
Branch release/20111015-1635 set up to track remote branch release/20111015
-1635 from origin.
start to tracking release branch

% git branch -a           [tmp/gitdaily-demo/b_dev @release/20111015-1635]
  develop
  master
* release/20111015-1635
  remotes/origin/HEAD -> origin/master
  remotes/origin/develop
  remotes/origin/master
  remotes/origin/release/20111015-1635

修正作業をし、コミットしたらもう一度 sync することで、push されます。ログを確認すると、ローカルのほうが1つだけコミットが進んでいる状態ですね(releaseブランチに、1つ修正コミット)

% git status              [tmp/gitdaily-demo/b_dev @release/20111015-1635]
# On branch release/20111015-1635
# Your branch is ahead of 'origin/release/20111015-1635' by 1 commit.
#
nothing to commit (working directory clean)

% git log --oneline --decorate
aba974f (HEAD, release/20111015-1635) fixed: not PHP but Python
efbe37b (origin/release/20111015-1635, origin/develop) modify comment
107be63 added sample
d7a313d (origin/master, origin/HEAD, master, develop) initialize

% git daily release sync  [tmp/gitdaily-demo/b_dev @release/20111015-1635]
[INFO] first, fetch remotes
[INFO] cleanup remote
[INFO] git pull
[INFO] run git pull origin release/20111015-1635
From /home/sotarok/tmp/gitdaily-demo/server
 * branch            release/20111015-1635 -> FETCH_HEAD
Already up-to-date.
pull completed
[INFO] git push
[INFO] run git push origin release/20111015-1635
To /home/sotarok/tmp/gitdaily-demo/server
   efbe37b..aba974f  release/20111015-1635 -> release/20111015-1635
push completed

これで release ブランチの修正が完了したので、deployサーバで再度 sync してその修正を取得し、再度stagingなどにdeploy、確認作業などをします。

% git daily release sync [tmp/gitdaily-demo/deploy @release/20111015-1635]
[INFO] first, fetch remotes
[INFO] cleanup remote
[INFO] git pull
[INFO] run git pull origin release/20111015-1635
From /home/sotarok/tmp/gitdaily-demo/server
 * branch            release/20111015-1635 -> FETCH_HEAD
Updating efbe37b..aba974f
Fast-forward
 sample.php |    2 +-
 1 files changed, 1 insertions(+), 1 deletions(-)
pull completed
[INFO] git push
[INFO] run git push origin release/20111015-1635
Everything up-to-date
push completed

% cap staging deploy     [tmp/gitdaily-demo/deploy @release/20111015-1635]

ここまでを図にすると以下のような感じです。

確認が完了し、リリース作業を終了します

statingで確認が完了し、productionにデプロイします。

% cap production deploy  [tmp/gitdaily-demo/deploy @release/20111015-1635]

deployサーバで、release close します。まずログ確認。

% git log --oneline --decorate --graph
* aba974f (HEAD, origin/release/20111015-1635, release/20111015-1635) fixed
* efbe37b (origin/develop, develop) modify comment
* 107be63 added sample
* d7a313d (origin/master, origin/HEAD, master) initialize

さて、closeです。

% git daily release close
[INFO] first, fetch remotes
[INFO] diff check
[INFO] checkout master and merge release/20111015-1635 to master
[INFO] run git pull origin master
From /home/sotarok/tmp/gitdaily-demo/server
 * branch            master     -> FETCH_HEAD
Already up-to-date.
pull completed
Merge made by recursive.
 sample.php |    7 +++++++
 1 files changed, 7 insertions(+), 0 deletions(-)
 create mode 100644 sample.php
[INFO] push master to origin
To /home/sotarok/tmp/gitdaily-demo/server
   d7a313d..f62dba1  master -> master
[INFO] first, fetch remotes
[INFO] diff check
[INFO] checkout develop and merge release/20111015-1635 to develop
[INFO] run git pull origin develop
From /home/sotarok/tmp/gitdaily-demo/server
 * branch            develop    -> FETCH_HEAD
Already up-to-date.
pull completed
Merge made by recursive.
 sample.php |    2 +-
 1 files changed, 1 insertions(+), 1 deletions(-)
[INFO] push develop to origin
To /home/sotarok/tmp/gitdaily-demo/server
   efbe37b..b5a59b7  develop -> develop
[INFO] delete branch: release/20111015-1635
Deleted branch release/20111015-1635 (was aba974f).
To /home/sotarok/tmp/gitdaily-demo/server
 - [deleted]         release/20111015-1635
release closed

たくさん出力されたのでよくわからないことになってるかもしれませんが、ブランチ、ログは以下のようになっています。

% git branch -a                        [tmp/gitdaily-demo/deploy @develop]
* develop
  master
  remotes/origin/HEAD -> origin/master
  remotes/origin/develop
  remotes/origin/master

% git log --oneline --decorate --graph --all
*   b5a59b7 (HEAD, origin/develop, develop) Merge branch 'release/20111015-
|\
| | *   f62dba1 (origin/master, origin/HEAD, master) Merge branch 'release/
| | |\
| | |/
| |/|
| * | aba974f fixed: not PHP but Python
|/ /
* | efbe37b modify comment
* | 107be63 added sample
|/
* d7a313d initialize

ブランチに --no-ff で develop と master に merge され、releaseブランチが掃除されていることがわかると思います。

これでリリース作業が完了しました。

各自の開発環境の掃除

deployサーバ上でリリースが完了しました。が、ここはGitの特徴ですが、remoteでリリースブランチが掃除されてもローカルには残ってるわけです。これを掃除しようとおもいます。

ここでも release sync が登場します。
Bさんは、先程修正作業のためにローカルにreleaseブランチを取得しましたので、以下の状態になっています。

% git branch -a           [tmp/gitdaily-demo/b_dev @release/20111015-1635]
  develop
  master
* release/20111015-1635
  remotes/origin/HEAD -> origin/master
  remotes/origin/develop
  remotes/origin/master
  remotes/origin/release/20111015-1635

↑はまだfetch前の状態なので、remoteで消えたはずのreleaseブランチが、まだあります。
syncすればそのへんは適当に取得してやってくれます。

% git daily release sync  [tmp/gitdaily-demo/b_dev @release/20111015-1635]
[INFO] first, fetch remotes
[INFO] cleanup remote
[INFO] release closed! so cleanup local release branch
[INFO] checkout develop
[INFO] run git pull origin develop
From /home/sotarok/tmp/gitdaily-demo/server
 * branch            develop    -> FETCH_HEAD
Updating d7a313d..b5a59b7
Fast-forward
 sample.php |    7 +++++++
 1 files changed, 7 insertions(+), 0 deletions(-)
 create mode 100644 sample.php
pull completed
[INFO] delete release/20111015-1635
Deleted branch release/20111015-1635 (was aba974f).
Closed old release branch
sync to release close

ブランチとログを確認すると:

% git branch -a                         [tmp/gitdaily-demo/b_dev @develop]
* develop
  master
  remotes/origin/HEAD -> origin/master
  remotes/origin/develop
  remotes/origin/master


% git log --oneline --decorate --graph  [tmp/gitdaily-demo/b_dev @develop]
*   b5a59b7 (HEAD, origin/develop, develop) Merge branch 'release/20111015...
|\
| * aba974f fixed: not PHP but Python
|/
* efbe37b modify comment
* 107be63 added sample
* d7a313d (master) initialize

これで、release ブランチがお掃除されて、developに戻りました。 *2

作業の流れのまとめ

これで一連の作業が完了しました。
簡単に普段の作業のまとめをします:

  • develop ブランチで開発作業をする
  • release 作業をはじめるとdevelopブランチからブランチが切られる (release open)
  • release ブランチで最終確認中、修正が必要ならreleaseブランチで修正作業をする (release sync)
  • 本番リリースできたら、releaseブランチをmergeします。 (release close)
  • リリース後、各開発者は sync してお掃除とかする (release sync)

まとめと余計な話

  • Python かいわいでの発表はじめてで緊張しました
  • ちなみに、git-daily本体はgitflowを使って開発しています。
  • スライド中でも話しましたが、Git運用のプラクティス、どんどん共有していきたいですね!


おまけ: zshの連携は、vsc_info使ってます。

autoload -Uz vcs_info
zstyle ':vcs_info:*' enable git
zstyle ':vcs_info:*' actionformats ' %F{3}@%F{4}%b%F{3} !!%F{1}%a%f'
zstyle ':vcs_info:*' formats       ' %F{3}@%F{4}%b%f'
RPROMPT="%F{5}[%F{2}%3~\${vcs_info_msg_0_}%F{5}]%f"

*1: id:Voluntas さんと話したら、デプロイサーバとかないしとかいわれて、ああ、そうか、と思いました

*2:ちなみに、localのmasterブランチがoriginのmasterに追いついてないですが、これはmasterでpullしてないからですね。まぁ各開発者はローカルでmasterブランチを更新することがないので、気持ち悪いかもしれませんが、masterはほったらかしでもほとんど問題ないとおもいます。

Git関連ツール開発中に手元で remote 関連の動作テスト

いやまあこんなことは、どうでもいいといえばいいのですが、意外と知らない人もいるかも意知れないと思ってメモ。

git-daily の開発とかでよく remote とのやりとりの動作確認とかが必要になるんだけど、そのテスト方法について。いちいち gitosis やら github やらの実際のサーバを用意しなくてもテストはできるんですよ、という話。

適当なリポジトリを用意

$ cd /path/to/tmp
$ mkdir hoge
$ cd hoge
$ git init
$ touch hoge.txt
$ git add hoge.txt
$ git commit -m "first commit"

remote 用のリポジトリを用意

続き。bare リポジトリ作成

$ mkdir ../fuga
$ cd ../fuga
$ git init --bare

remote 追加

hoge に戻って、相対パスで remote の追加&first push。

$ cd ../hoge
$ git remote add origin ../fuga
$ git push -u origin master
Counting objects: 3, done.
Writing objects: 100% (3/3), 231 bytes, done.
Total 3 (delta 0), reused 0 (delta 0)
Unpacking objects: 100% (3/3), done.
To ../fuga
 * [new branch]      master -> master
Branch master set up to track remote branch master from origin.

あとは適当にリモート操作

pushしたり

$ touch fuga.txt
$ git add fuga.txt
$ git commit -m "add fuga"
[master 263204f] add fuga
 0 files changed, 0 insertions(+), 0 deletions(-)
 create mode 100644 fuga.txt
$ git push
Counting objects: 4, done.
Delta compression using up to 2 threads.
Compressing objects: 100% (2/2), done.
Unpacking objects: 100% (3/3), done.
Writing objects: 100% (3/3), 280 bytes, done.
Total 3 (delta 0), reused 0 (delta 0)
To ../fuga
   d3a54c5..263204f  master -> master

clone もできます

$ cd ..
$ ls
fuga  hoge
$ git clone fuga piyo
Cloning into piyo...
done.
$ ls
fuga  hoge  piyo
$ cd piyo
$ git log
commit 263204f294a67959a5978fbe5a862e2c05526e91
Author: Sotaro KARASAWA <sotarok@crocos.co.jp>
Date:   Wed Jul 13 22:22:47 2011 +0900

    add fuga

commit d3a54c5d76851286052d216db0925637460e6f03
Author: Sotaro KARASAWA <sotarok@crocos.co.jp>
Date:   Wed Jul 13 22:16:07 2011 +0900

    first commit

結論

Git は簡単。

Git で過去にさかのぼってタグ付けする (git tag)

もうだいぶ歴史を進めて開発進めてたんだけど、そういやあのプロトタイプが動いたときタグうっときゃよかったなーなどと思ったんだけど、意外と情報がなかったからメモ。
git-flow 使って、develop で開発進めてたりして、リモート/ローカルで push/pull も頻繁にしてるリポジトリ。現存するのは、develop, master のみ、だいぶ昔に feature/hoge からマージした段階に戻って master にマージしてタグ付けしたい、みたいな要望(割とあるよね?あるよね?

$ git log --all --graph

とか見ながら、コミットオブジェクトのハッシュ確認。例えば、「38fef39」が対象のコミットだとする。

そのハッシュをチェックアウト。

$ git checkout 38fef39
Note: checking out '38fef39'.

You are in 'detached HEAD' state. You can look around, make experimental
changes and commit them, and you can discard any commits you make in this
state without impacting any branches by performing another checkout.

If you want to create a new branch to retain commits you create, you may
do so (now or later) by using -b with the checkout command again. Example:

  git checkout -b new_branch_name

HEAD is now at 38fef39... hogehoge

この時点では、どのブランチにも属していない状態になる。なので、一旦ブランチ切ってそこに入る。releaseブランチを切ってしまおう。

$ git branch -a
* (no branch)
  develop
  master
  remotes/origin/develop
  remotes/origin/master

$ git checkout -b release/0.0.1

masterをチェックアウトして、merge。no fast forward で。

$ git checkout master
$ git merge --no-ff release/0.0.1

で、タグを打つ。

$ git tag -a 0.0.1

あとはブランチ削除

$ git branch -d release/0.0.1

完了!

Gitorious設置失敗 -> (追記)成功

ううむ。なんかなかうまくいかん。


CentOS 5.3なので、とりあえず、

の通りに進めてみたんだけど、rake db:create でつまった。

なんか

$ rake db:create RAILS_ENV=production
(in /var/www/gitorious)
rake aborted!
Unknown database 'gitorious_production'

(See full trace by running task with --trace)

となる。Unknown ってことは、作った方がいいのか?と思って

mysql> create database gitorious_production; 
Query OK, 1 row affected (0.01 sec)

してみたら、

$ rake db:create RAILS_ENV=production
(in /var/www/gitorious)
gitorious_production already exists

となった。
ううむ、これは違うのか。

でも、

$ rake db:create 

すると、developmentの方は入るっぽい。

なにがいけないんだろこれ?
よくわかりません。。

追記

db:create無視して、mysqlコンソールから直接CREATE DATABASEして rake db:migrate のほうに進んだら普通にいけた!ありがとうございます!>CLAさん

で、今はgitにcloneやpushができないっていう別の問題にはまりちゅうだったりしますが。つかれたよママン

追記

git 操作ができないのは authorized_keys のパーミッションの問題だった。
これはすぐ気づけたからすぐ直せた。

あと動いてないのが多数あって、

  • http経由でのgit
  • wiki ページ開くと 500 Internal Server Error
    • ログにものこってたけど追うのは勘弁だからやめた
  • メール送信
    • 自宅はddnsなのもあってメールサーバはたててないんだよねー。てことで、actionmailerでgmail使うようにあとでする予定。できればいいなー
  • indexerがエラーはいてる
    • cronでなんかエラーでてる。

そんなもん。
まあ、gitリポジトリの共有+ウェブからの操作的な需要は満たせてるし、wikiはTracのものがあるのでそれで良いかな。

あと反省点としては、Rubyの環境とか相当いじったから、tig.rbやwig.rbが動かなくなる予感。こういう環境がらっといじるものは仮想化とかすべきだよなあ。。まあいいか。

その他のはまりどころ

gemのhoeだとかrmagickだとか、バージョンが微妙に違うだけでrakeがとまっちゃうからバージョンを指定しながらやらなきゃいけなかったり。めんどくせ。