2016年1月2日土曜日

Git 最速マスター

gitを使って開発する場合のチュートリアル。
下の流れを追っていけば最低限必要な使い方はマスターできるだろう。

目次
 【ローカルリポジトリの基本操作】
 【リモートリポジトリの基本操作】
 【Pull Request】
 【リポジトリのメンテナンス】
 【実開発例】


【ローカルリポジトリの基本操作】
◆ ワークツリーの作成
適当な作業用ディレクトリを作る。
$ mkdir /var/tmp/git-tutorial
$ cd /var/tmp/git-tutorial

ワークツリーを作成する。
$ git init

適当なファイルを作成し、ワーキングツリー内の状態を確認する。
$ touch README.md

$ git status
Untracked files:
README.md



◆ インデックス(ステージ)への登録
ワーキングツリーから、インデックス(コミットするためのの一時領域)へファイルを登録する。
$ git add README.md

$ git status
Changes to be committed:
        new file:   README.md



◆ コミットによるローカルリポジトリへの反映
インデックスからローカルリポジトリへ反映する。
(1行だけコミットメッセージを残す場合)
$ git commit -m "First commit"

(詳細なコミットメッセージを残す場合)
$ git commit
1行目:要約(※1行)
2行目:空行
3行目:詳細

addとcommitの手順を一度に実施することもできる。
$ git commit -am "コメント"

コミット時のlogを確認することができる。
$ git log
コミットをユニークに指す値として利用できるハッシュ値が表示される。
commit 66a3c9fab08b3a5b57fee2f5302b389997a20b8e

コミット時の差分をlogとして確認することもできる。
$ git log -p

特定のファイルやディレクトリを対象としたログを確認することも可能である。
$ git log README.md



◆ 変更差分の確認方法
(ワークツリー、ステージ間の変更差分)
$ git diff
差分なし

MarkDown記法で適当に編集。
$ vi README.md
hoge

$ git diff
--- a/README.md
+++ b/README.md
@@ -0,0 +1 @@
+# hoge

$ git add README.md

$ git diff
差分はなくなる。


(ワークツリー、最新コミット間の変更差分)
オプションのHEADとは現在の作業ブランチの最新のコミットを参照するポインタである。
git commit前に確認するとよい。

$ git diff HEAD
diff --git a/README.md b/README.md
index e69de29..6ededf2 100644
--- a/README.md
+++ b/README.md
@@ -0,0 +1 @@
+# hoge

$ git commit -m "add hoge"

$ git diff HEAD
差分はなくなる。



◆ ブランチの操作
masterはディフォルトで作成されるブランチである。
*が現在のブランチである。
$ git branch
* master



◆ ブランチの作成と切り替え
トピックブランチを作成する。トピックブランチとは1つのトピック(特性)に集中し、他の作業は行わないトピックでる。

ある特性Aを実装するfeatureAという名前でブランチを切ることとする。

ブランチを作り、作成したブランチへ切り替える。
$ git branch featureA
$ git checkout featureA

この2行は以下の1行と同じである。
$ git checkout -b featureA

$ git branch
* featureA
  master

ファイルを修正し、このブランチでadd, commit操作をしてみる。
$ vi README.md
piyo

ステージ領域へファイルと登録する。
$ git add .

リポジトリのヒストリへ記録する。
$ git commit -m "Add featureA"

masterへの影響を確認する。
$ git checkout master

影響は受けていない。
$ cat README.md
hoge



◆ ブランチのマージ
featureAブランチをmasterブランチへ統合(マージ)する。
チケット単位でfeatureを作成し、本線はマージ役に徹するのが基本である。
$ git branch
  featureA
* master

ブランチからマージしたことを記録に残すため"--no-ff"オプションをつける。
ファストフォワードすると、masterに変更がなかった時に、masterでのコミットに見えるためである。
$ git merge --no-ff featureA

--no-ff: no fast forward
http://www.backlog.jp/git-guide/stepup/stepup1_4.html


ブランチを視覚的に確認できる。
トピックブランチでのコミット内容がマージされたことがわかる。
$ git log --graph
*   commit c1348f5ea1f59102562b80cb66ae51c3e6b72dd1
|\  Merge: 7d60233 c3ff424
| | Author: alpha-netzilla
| |
| |     Merge branch 'featureA'
| |
| * commit c3ff424d7d6590c39f75a8ed13393ef2c8f53031
|/  Author: alpha-netzilla
|
|       Add featureA
|
* commit 7d602339f91fc81fa7a1a9c0909dff21b983ae6c
| Author: alpha-netzilla
|
|     add hoge
|
* commit 24ce9c8369026c64e10ac24620f6fadcbb5a212d
  Author: alpha-netzilla



◆  歴史の行き来
歴史の操作を覚えてるために意図的にこういったシナリオを考える。
1. featureAブランチを分岐する前に戻る。

2. その状態からfeatureBという別ブランチも並行して作成する。

3. その次に、featureAブランチを統合ブランチに取り込んだ状態まで歴史を進める。
既にfeatureAは統合ブランチへ取り込んであるため、戻るだけでよい。

4. その後にfeatureBブランチを取り込む。


それでは、featureAブランチを分岐する前に戻るってみる。
$ git reset --hard 7d602339f91fc81fa7a1a9c0909dff21b983ae6c

ファイルも戻っている。
$ cat README.md
hoge


この状態でfeatureBブランチを作る。
$ git checkout -b featureB

$ vi README.md
bar

$ git add README.md

$ git commit -m "featureB"


歴史を進めるために、featureAをマージした状態へ行く。
git logではなく、reflogを使う。
logオプションはその状態からの過去ログしか確認できないためである。
reflogオプションは現在のリポジトリで行われた作業ログを確認できる。

$ git reflog
32e6ba8 HEAD@{0}: commit: featureB
7d60233 HEAD@{1}: checkout: moving from master to featureB
7d60233 HEAD@{2}: reset: moving to 7d602339f91fc81fa7a1a9c0909dff21b983ae6c
c1348f5 HEAD@{3}: merge featureA: Merge made by the 'recursive' strategy.
7d60233 HEAD@{4}: checkout: moving from featureA to master
c3ff424 HEAD@{5}: commit: Add featureA
7d60233 HEAD@{6}: checkout: moving from master to featureA
7d60233 HEAD@{7}: commit: add hoge
24ce9c8 HEAD@{8}: commit (initial): First commit

$ git checkout master

現在のHEADを指定箇所へリセットする。
$ git reset --hard c1348f5

$ git merge --no-ff featureB
Auto-merging README.md
CONFLICT (content): Merge conflict in README.md
Automatic merge failed; fix conflicts and then commit the result.

featureAとfeatureBでの変更が衝突している。

$ vi README.md
<<<<<<< HEAD
piyo
=======
bar
>>>>>>> featureB

どちらを優先するか決めて編集する。
$ vi README.md
bar

$ git add README.md

$ git commit -m "Fix confilict"



◆ 修正
(直前のコミットメッセージの直前)
 直前のコミットメッセージを修正する。
先ほど"Fix confict"と記録を残したが、featureBのブランチをマージしたことが意図的に分かるように修正するとする。

$ git commit --amend
Fix conflict → Merge branch 'featureB'

$ git log --graph


(コミット歴史の改変)
過去のコミットの歴史を見る。
$ git rebase -i HEAD~~
pick c3ff424 Add featureA
pick 32e6ba8 featureB

# Commands:
#  p, pick = use commit
#  r, reword = use commit, but edit the commit message
#  e, edit = use commit, but stop for amending
#  s, squash = use commit, but meld into previous commit
#  f, fixup = like "squash", but discard this commit's log message
#  x, exec = run command (the rest of the line) using shell

pick部分を例えばfixupへ変更することで複数のコミットをまとめたり等できる。
http://www.backlog.jp/git-guide/stepup/stepup7_5.html





【リモートリポジトリの基本操作】
◆ リモートリポジトリへの送信
githubに事前にリモートリポジトリを作成しておく。
ここでは以下として説明する。
https://github.com/alpha-netzilla/XXX.git

ここへ手元のローカルリポジトリを登録する。
今いる場所は先ほどまで作業していたディレクトリである。
$ pwd
/var/tmp/git-tutorial

リモートリポジトリをoriginという名前の識別子で指すよう指定する。
$ git remote add origin https://github.com/alpha-netzilla/XXX.git

ローカルリポジトリの現在のブランチを、
リモートリポジトリであるoriginのmasterブランチへ送信する。
$ git push -u origin master


(featureAブランチをへmasterブランチへ送信)
ローカルリポジトリのfeatureAブランチを、
リモートリポジトリであるoriginの同名ブランチへ送信する。
$ git checkout featureA

$ git push -u origin featureA

-aオプションをつけると、ローカルだけではなく、リモートリポジトリも含んだブランチ情報を確認できる。
$ git branch -a
* featureA
  featureB
  master
  remotes/origin/featureA
  remotes/origin/master



◆ リモートリポジトリの取得
リモートリポジトリの複製を行う。
先ほどとは異なるディレクトリへ移動しておくこと。

$ git clone https://github.com/alpha-netzilla/XXX.git

$ cd git-tutorial

$ git branch -a
* master
  remotes/origin/HEAD -> origin/master
  remotes/origin/featureA
  remotes/origin/master


ここで、リモートリポジトリ(origin)のfeatureAブランチを元に、ローカルリポジトリの同名のブランチを作成、チェックアウトしてみる。
$ git checkout -b featureA origin/featureA

$ git branch -a
* featureA
  master
  remotes/origin/HEAD -> origin/master
  remotes/origin/featureA
  remotes/origin/master





【Pull Request】
手順1からの流れに沿っていけばよい。

(1. fork)
オリジナルのコード(XXXとする)をforkする。
githubであればforkボタンをクリックすればよい。
自分のユーザ名/XXX というリポジトリが作成される。


(2. clone)
$ git clone https://github.com/alpha-netzilla/XXX.git
$ cd XXX


(3. branch)
cloneしたリポジトリのブランチを確認する。
$ git branch -a
* test
  remotes/origin/HEAD -> origin/test
  remotes/origin/test

コードの修正はトピックブランチで行う。
$ git checkout -b work

$ git branch -a
  test
* work
  remotes/origin/HEAD -> origin/test
  remotes/origin/test


(4. コード修正)
適当にコードを修正する。
$ git diff
$ git add .
$ git commit


(5. リモートブランチの作成)
リモートリポジトリへローカルで用意したworkに相当する同名ブランチを作成する。
$ git push origin work

$ git branch -a
  test
* work
  remotes/origin/HEAD -> origin/test
  remotes/origin/test
  remotes/origin/work

(6. Pull Request)
github上でworkブランチへ切り替え、Create Pull Requestをクリックすればよい。





【リポジトリのメンテナンス】
fork、cloneしてきたリポジトリを、オリジナルのリポジトリと同期を取り、最新の状態にするための手順を残しておく。

オリジナルをforkし、clone。
$ git clone https://github.com/alpha-netzilla/XXX.git
$ cd XXX


オリジナルのリポジトリに名前をつける。
originという名前でオリジナルのリポジトリを参照するようにする。
$ git remote add origin https://github.com/XXX/XXX.git

最新のコードを取得する。
リモートブランチを追跡するローカルリポジトリ側のトラッキングブランチへ反映される。
$ git fetch origin

ローカルリポジトリの現在のブランチへマージする。
$ git merge origin/master

fetch + merge操作はまとめてpullでできる。
$ git pull origin master

リモートに存在しないブランチをローカルブランチからも削除する場合、"-p, --prune"オプションを使うといいだろう(fetch時にも使える)。
$ git -p pull origin master &&amp



【実開発例】
チュートリアルでは作業ブランチ名を適当に決めていたが、実際はブランチモデルに従い開発を進めていくべきである。
"A successful Git branching model"の図を参照しながら以下読み進めてもらいたい。

このブランチモデルの導入を簡単にするgit-flowというコマンドもあるがここではgitのみで対応する。

(開発開始)
githubなどからリモートにあるマスタファイルを取ってくる。
$ git clone https://github.com/alpha-netzilla/XXX.git
$ cd XXX/

originという名前をつけておく。
[master]$ git remote add origin https://github.com/alpha-netzilla/XXX.git
どこにいるか分かりやすく表現するため、[ ]で現在のブランチを示す。


(新機能開発)
開発ブランチを切りそこへ移動する。
[master]$ git checkout -b develop

フィーチャー用ブランチを切りそこへ移動する。
[develop]$ git checkout -b featureA
[featureA]$ 開発!
[featureA]$ git add .
[featureA]$ git commit -m "add featureA"


(開発環境へのマージ)
[featureA]$ git checkout develop
[develop]$ git merge featureA


(リモートの開発環境へのマージ)
[develop]$ git push origin develop

masterへも反映する。
[remote] github上でdevelopブランチへ切り替え、Create Pull Requestをクリック


(hotfix、緊急のパッチ適用)
[develop] git checkout -b hotfix origin/master

既にhotfixブランチを切ってある場合は、そこへ移動し、リポジトリのメンテナンス時にやったようにfetchしmergeすればよい。
[develop]$ git checkout hotfix
[hotfix]$ git fetch origin
[hotfix]$ git merge origin/master

[hotfix]$ 修正!
[hotfix]$ git add .
[hotfix]$ git commit -m "fix"

[hotfix]$ git checkout develop
[develop] git merge hotfix

[develop]$ git checkout master
[master]$ git merge hotfix

リモートの開発環境へのマージする。
[develop]$ git push origin develop

masterへ反映する。
[remote] github上でdevelopブランチへ切り替え、Create Pull Requestをクリック