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

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

#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はほったらかしでもほとんど問題ないとおもいます。