この記事は、フロムスクラッチ Advent Calendar 2016 の18日目の記事です。

フロムスクラッチ Advent Calendar 2016
http://qiita.com/advent-calendar/2016/fromscratch

gitというバージョン管理システムは、
ソフトウェア開発者にとって身近なツールの一つではあるのですが。

チーム開発でgitによるバージョン管理を運用していると、
いろいろと混乱することがあると思います。

このエントリでは、
gitを使い始めた人に対する説明として、gitにどのような特徴があるのか。
また、チーム開発でgitを運用するためにgithubをどのように使うかを記載したいと思います。

  • gitの特徴
  • gitでの開発の流れ
  • githubでの開発の流れ

gitの特徴

Wikipediaなどを見るとgitは「分散型バージョン管理システム」と説明されています。

git | Wikipedia
https://ja.wikipedia.org/wiki/Git

個人的な意見ですが、
分散型はともかく「バージョン管理」という言葉をgitに対して使うことが、
gitの理解を妨げている大きな要因だと思っています。

gitは、プログラムのソースコードを第一版、第二版というように、
バージョン管理(=版管理)するのではなく、
gitが管理しているのはパッチ(=修正内容)と考えた方が理解しやすいと思います。

gitでのソースコード管理(特にチームでの開発)では、
以下の①ではなく②の流れのように。
パッチ(=修正内容)を作る作業と、それを版管理に取り込む作業を、
分けて考えることが一般的だと思います。

①バージョン管理の流れ

第一版  
 ↓ 第一版に修正を加える  
第二版  
 ↓ 第二版に修正を加える  
第三版  
 ↓ 第三版に修正を加える  
第四版

②パッチ管理の流れ

修正内容Aを作る  
修正内容Bを作る  
修正内容Cを作る  

第一版  
 ↓ 第一版に修正内容Aを加える  
第二版  
 ↓ 第二版に修正内容Bを加える  
第三版  
 ↓ 第三版に修正内容Cを加える  
第四版

②の流れでソースコード管理をすることで、
修正内容Aと修正内容Bを作る作業を並行して行ったり、
問題のある修正内容は版管理に取り込まないなどの運用を行うことが出来ます。

これがgitの大きな特徴の一つです。
と同時に、①で示すようなバージョン管理のイメージでgitを使うと、
自分が何を行っているのか訳がわからなくなる原因でもあります。

gitでの開発の流れ

それでは、
前項の「②パッチ管理の流れ」をgitを使って行ってみます。

第一版を作る

以下のようにして、gitのリポジトリを作成します。

$ mkdir gitsample && cd $_
$ git init

次に、適当に初期状態を作ってコミットしておきます。

$ touch .keep
$ git add .keep
$ git commit .keep -m "initial commit"

ここまでで、第一版が用意できた状態になります。

修正内容A、B、Cを作る

次に、修正内容Aをつくりますが、
修正内容Aは「spike/A」というbranchを作成し、そこで作成します。

以下のようにして、branch「spike/A」を作成し、作業場所を移動します。

$ git branch spike/A
$ git checkout spike/A
Switched to branch 'spike/A'

「git branch」で現在の作業branchを確認します。

$ git branch
  master
* spike/A

ここで適当に修正内容Aを作ります。

$ touch A
$ git add A
$ git commit -a -m "commit A"

「git log」で、現在の「spike/A」branchに修正内容Aが含まれていることを確認します。

$ git log
commit 00d05bfc7f7358003f7052f4d40014197b775fc6
Author: ※省略※
Date:   Sat Dec 17 23:29:11 2016 +0900

    commit A

commit a998f39f8004bcc496c215e021b9ec9c8f2f7aa5
Author: ※省略※
Date:   Sat Dec 17 23:16:47 2016 +0900

    initial commit

以下のようにして、作業場所を「master」branchに戻します。
「git log」で修正内容Aが含まれていないことが確認出来ます。

$ git checkout master
Switched to branch 'master'
$ git log
commit a998f39f8004bcc496c215e021b9ec9c8f2f7aa5
Author: ※省略※
Date:   Sat Dec 17 23:16:47 2016 +0900

    initial commit

同様にして、修正内容BとCも作成します。

以下、修正内容Bの作成です。

$ git branch spike/B
$ git checkout spike/B
Switched to branch 'spike/B'
$ touch B
$ git add B
$ git commit -a -m "commit B"
[spike/B 2d86b39] commit B
 1 file changed, 0 insertions(+), 0 deletions(-)
 create mode 100644 B
$ git log
commit 2d86b399846d5d9c99d9cf50064f58000e94eb7f
Author: ※省略※
Date:   Sat Dec 17 23:37:41 2016 +0900

    commit B

commit a998f39f8004bcc496c215e021b9ec9c8f2f7aa5
Author: ※省略※
Date:   Sat Dec 17 23:16:47 2016 +0900

    initial commit
$ git checkout master
Switched to branch 'master'

次に、修正内容Cの作成です。

$ git branch spike/C
$ git checkout spike/C
Switched to branch 'spike/C'
$ touch C
$ git add C
$ git commit -a -m "commit C"
[spike/C 1043f51] commit C
 1 file changed, 0 insertions(+), 0 deletions(-)
 create mode 100644 C
$ git log
commit 1043f5162e8405b3b044526a4736ec2c15c312df
Author: ※省略※
Date:   Sat Dec 17 23:40:11 2016 +0900

    commit C

commit a998f39f8004bcc496c215e021b9ec9c8f2f7aa5
Author: ※省略※
Date:   Sat Dec 17 23:16:47 2016 +0900

    initial commit
$ git checkout master

修正内容を版管理に加える

修正内容A、B、Cが作成出来たので、
これらの修正を版管理に加えていきます。
ここでは「master」branchに修正内容を加えていくことにします。

まず、はじめに、現在のbranchが「master」になっていることを確認します。

$ git branch
* master
  spike/A
  spike/B
  spike/C

次に、全てのbranchをログを確認しておきます。
「sipke/A」「spike/B」「spike/C」にmasterに取り込まれていない修正内容があることがわかります。

$ git log --branches --decorate=full
commit 1043f5162e8405b3b044526a4736ec2c15c312df (refs/heads/spike/C)
Author: ※省略※
Date:   Sat Dec 17 23:40:11 2016 +0900

    commit C

commit 2d86b399846d5d9c99d9cf50064f58000e94eb7f (refs/heads/spike/B)
Author: ※省略※
Date:   Sat Dec 17 23:37:41 2016 +0900

    commit B

commit 00d05bfc7f7358003f7052f4d40014197b775fc6 (refs/heads/spike/A)
Author: ※省略※
Date:   Sat Dec 17 23:29:11 2016 +0900

    commit A

commit a998f39f8004bcc496c215e021b9ec9c8f2f7aa5 (HEAD, refs/heads/master)
Author: ※省略※
Date:   Sat Dec 17 23:16:47 2016 +0900

    initial commit

「git merge」で修正内容Aを取り込みます。

$ git merge spike/A
Updating a998f39..00d05bf
Fast-forward
 A | 0
 1 file changed, 0 insertions(+), 0 deletions(-)
 create mode 100644 A

取り込まれたことを「git log」で確認します。

$ git log
commit 00d05bfc7f7358003f7052f4d40014197b775fc6
Author: ※省略※
Date:   Sat Dec 17 23:29:11 2016 +0900

    commit A

commit a998f39f8004bcc496c215e021b9ec9c8f2f7aa5
Author: ※省略※
Date:   Sat Dec 17 23:16:47 2016 +0900

    initial commit

同様にして修正内容B、Cも取り込みます。
git mergeする際に、コメント編集が求められますが、
ここではデフォルトのまま、エディタを終了します。

$ git merge spike/B
Merge made by the 'recursive' strategy.
 B | 0
 1 file changed, 0 insertions(+), 0 deletions(-)
 create mode 100644 B
$ git merge spike/C
Merge made by the 'recursive' strategy.
 C | 0
 1 file changed, 0 insertions(+), 0 deletions(-)
 create mode 100644 C

取り込まれたことを「git log」で確認します。

commit a7eed6831768511cd187a98dd64c63889f040974
Merge: 71fb921 1043f51
Author: ※省略※
Date:   Sat Dec 17 23:55:03 2016 +0900

    Merge branch 'spike/C'

commit 71fb9216128eb9ddc6b30f5c9c403d9026afc2f7
Merge: 00d05bf 2d86b39
Author: ※省略※
Date:   Sat Dec 17 23:54:53 2016 +0900

    Merge branch 'spike/B'

commit 1043f5162e8405b3b044526a4736ec2c15c312df
Author: ※省略※
Date:   Sat Dec 17 23:40:11 2016 +0900

    commit C

commit 2d86b399846d5d9c99d9cf50064f58000e94eb7f
Author: ※省略※
Date:   Sat Dec 17 23:37:41 2016 +0900

    commit B

commit 00d05bfc7f7358003f7052f4d40014197b775fc6
Author: ※省略※
Date:   Sat Dec 17 23:29:11 2016 +0900

    commit A

commit a998f39f8004bcc496c215e021b9ec9c8f2f7aa5
Author: ※省略※
Date:   Sat Dec 17 23:16:47 2016 +0900

    initial commit

以上で、 「②パッチ管理の流れ」と同等の作業をgitで行う流れになります。

githubでの開発の流れ

次に同じ流れをgithubを使って行ってみます。

第一版を作る

gitの場合と同様、gitのリポジトリを作成し、初期状態をコミットします。

$ mkdir gitsample && cd $_
$ git init
$ touch .keep
$ git add .keep
$ git commit .keep -m "initial commit"

以下のように、githubのリポジトリにpushします。

$ git remote add origin git@github.com:※githubのリポジトリ名※.git
$ git push -u origin master

修正内容A、B、Cを作る

gitの場合と同様、修正内容Aを作成します。

$ git branch spike/A
$ git checkout spike/A
$ touch A
$ git add A
$ git commit -a -m "commit A"

修正内容Aをgithubのリポジトリにpushします。

$ git push origin spike/A
Counting objects: 3, done.
Compressing objects: 100% (2/2), done.
Writing objects: 100% (2/2), 237 bytes | 0 bytes/s, done.
Total 2 (delta 0), reused 0 (delta 0)
To git@github.com:※githubのリポジトリ名※.git
 * [new branch]      spike/A -> spike/A
$ git checkout master
Switched to branch 'master'

同様に修正内容Bを作成、pushします。

$ git branch spike/B
$ git checkout spike/B
$ touch B
$ git add B
$ git commit -a -m "commit B"
$ git push origin spike/B
$ git checkout master

修正内容Cも同様に。

$ git branch spike/C
$ git checkout spike/C
$ touch C
$ git add C
$ git commit -a -m "commit C"
$ git push origin spike/C
$ git checkout master

ここまで作業した状態を、githubのWebUIのNetworkで見ると以下のようになっています。

github-1

修正内容を版管理に加える

ここからはgithubのWebUIで作業を行っていくことにします。

gitのコマンドラインでは「git merge」で修正内容を版管理に加えていきましたが。
githubでは「pull request」と「merge」の2アクションで行うことになります。

修正内容の作成から「pull request」までは開発者。
「merge」の実施はプロダクトの管理者というように分担することで、
チェック済みの修正のみを版管理に加えるなどの品質管理をおこないやすくなります。
これがチーム開発、git単体でなくgithubを使う大きなメリットです。

該当リポジトリのWebUIで、
対象ブランチ「spike/A」の「Compare& pull requst」ボタンから、pull requestを作成します。

github-2

gitub-3

作成されたpull requestをWebUIから「merge」します。

「spike/B」「spike/C」も同様にmergeした状態を、
githubのWebUIのNetworkで見ると以下のようになります。

github-4

以上で、 「②パッチ管理の流れ」と同等の作業をgithubで行う流れになります。

githubをチーム開発で利用する場合に、

  • gitflowやgithub flowの導入
  • Protected Branchの活用
  • CIツールとpullrequestの連携
  • CDツールによる自動デプロイ

などなど、いろいろなテクニックはありますが。

このエントリで示したような、
gitにおけるmergeの基本的な流れと、
githubの「pull request」という仕組みが、それら管理出来るようにしていること。

これらの基本に立ち返って考えて見ると、
それらのテクニックもより整理された形で導入を考えられるのではないかなと思い。
このエントリでは、これらの基本を整理してみました。