Git初心者のメモ その4：ブランチを使ってみる 

Gitに関する記事

* 前の記事：[Git初心者のメモ その3：Gitのコマンド覚え書き](http://qiita.com/y_ito/items/91cd96659a56ec9f9dbf)
* 次の記事：[Git初心者のメモ その5：共有リポジトリ](https://qiita.com/y_ito/items/d1ed245b99729ad929c2)

<br>

---
ブランチなるものがどんなもんか試してみる。


#ブランチ関連のコマンド

- `git branch [-r|-a]`
ブランチの一覧を表示する。*（アスタリスク）が付いているのが現在のブランチ。
オプションなし：ローカルブランチ一覧
-r：リモートブランチ一覧
-a：ローカル＆リモートブランチ一覧

- `git branch ＜ブランチ＞`
ブランチを作成する。

- `git checkout ＜ブランチ＞`
ブランチを切り替える。

- `git checkout -b ＜ブランチ＞`
ブランチ作成してそのブランチに切り替える（＝branch＋checkout）。

- `git checkout -b ＜ブランチ＞ ＜リビジョン＞`
指定したリビジョンからブランチを作成してそのブランチに切り替える。
単純にリビジョンを指定してブランチを作る（ブランチの切り替えはしない）方法はないものかと調べたが、`git branch` でそういうことをする方法は見つけられず、`git checkout` で上記のようにするしかないようだ。

- `git branch -m ＜新しいブランチ名＞`
現在のブランチの名前を変更する。

- `git branch -d ＜ブランチ＞`
ブランチを削除する。

- `git branch -rd ＜リモート追跡ブランチ＞`
リモート追跡ブランチ（origin/hoge みたいな名前のやつ）を削除する。

- `git checkout -b ＜ブランチ＞ ＜リビジョン＞`
特定のリビジョンからブランチを作成してそのブランチに切り替える。

- `git merge [--no-ff] ＜ブランチ＞`
指定したブランチを現在のブランチに統合する。指定したブランチのコミットはそのまま残り、統合先のブランチに新しいコミットが作成される。
統合先のブランチで実行する。例えば、bugfixブランチをmasterブランチに統合する場合は、masterブランチで `git merge bugfix` を実行する。
オプション --no-ff については後で説明する。

- `git rebase ＜ブランチ＞`
指定したブランチに現在のブランチを統合する。
統合元のブランチで実行する。例えば、bugfixブランチをmasterブランチに統合する場合は、bugfixブランチで `git rebase master` を実行する。

- `git diff ＜ブランチ1＞ ＜ブランチ2＞`
ブランチ間の差分を表示する。

- `git log --graph --date-order --all --pretty=format:'%h %Cred%d %Cgreen%ad %Cblue%cn %Creset%s' --date=short`
コミットグラフ（ブランチの状況）をわかりやすく表示する。
オプションが長いので以下のようにエイリアスを設定して `git graph` で使えるようにしておくとよい。

 - `git config --global alias.graph "log --graph --date-order --all --pretty=format:'%h %Cred%d %Cgreen%ad %C(blue bold)%cn %Creset%s' --date=short"`


#マージ

`git merge` で他のブランチの変更内容を取り込むこと（マージ）ができる。
マージには fast-forwardマージと non fast-forwardマージ（3-way マージとも呼ばれる）の2種類がある。
`git merge` コマンドが自動で判定してどちらかのマージを行うが、オプションで指定することもできる。

## fast-forwardマージ

fast-forwardマージは新たなコミットを作らずに**ブランチが指すコミットを変更するだけ**のコミットである。
例で説明する。masterブランチのコミットBからtopicブランチを作成し、topicブランチで何回かコミットした。この段階で topicブランチを作ってからmasterブランチには1回もコミットしておらず、topicブランチはmasterブランチの履歴をすべて持っている。

![before_ff_merge.png](https://qiita-image-store.s3.amazonaws.com/0/110278/934e6b36-1106-27e7-e0db-e931dedea373.png "fast-forwadマージ前")

上記の状態からtopicブランチをmasterブランチにマージするため masterブランチで `git merge topic` を実行すると、masterブランチのHEADがDに移動される。これでマージ完了。これが fast-foward マージである。
先に述べたようにtopicブランチはmasterブランチの履歴をすべて持っているのでバージョン管理対象のファイルの更新は発生せず、また新たなコミットも作られない。masterブランチが指しているコミットが変更されるだけである。

![after_ff_merge.png](https://qiita-image-store.s3.amazonaws.com/0/110278/4725bfa9-344d-fcee-a2a8-cac7c9332c8e.png "fast-forwardマージ後")

## non fast-forwardマージ

no fast-forwarcマージ（3-wayマージ）は**新たなコミット「マージコミット」を作成する**マージである。
例で説明する。fast-forwardマージの説明の例と同じくmasterブランチとtopicブランチがある。先の例と異なるのはmasterブランチの方でも何回かコミットしていることである。つまりtopicブランチがmasterブランチのすべての履歴を持っているわけではない。

![before_nff_merge.png](https://qiita-image-store.s3.amazonaws.com/0/110278/31c477d6-8185-f034-d337-fb257446d2bf.png "non fast-forwadマージ前")


この状態でmasterブランチにおいて `git merge topic` を実行すると、masterブランチとtopicブランチを統合したマージコミットが作成され、それをmasterブランチが指すようになる。

![after_nff_merge.png](https://qiita-image-store.s3.amazonaws.com/0/110278/4b7d4a83-43a7-5118-898f-4e1d852babaa.png "non fast-forwardマージ後")


### --no-ff オプション
fast-forwardマージが実行されるような状態でもマージしたという履歴を意図的に残してマージしたい場合には `git merge` の --no-ff オプションを使用する。`git merge --no-ff ＜ブランチ名＞` で必ず non fast-forwardマージが実行される。


#ブランチを使ってみる

##masterブランチ
`git init` 直後に `git branch` としても何も表示されない。

```shell-session
y_ito@note MINGW64 /c/work/git_test
$ git init
Initialized empty Git repository in C:/work/git_test/.git/

y_ito@note MINGW64 /c/work/git_test (master)
$ git branch

y_ito@note MINGW64 /c/work/git_test (master)
$
```

というかブランチは単にコミットのどこかを指すポインタみたいなものらしいので、指し示すべきコミットがなければブランチも在りようがない。
コミットするとデフォルトのブランチである master が現れる。

```shell-session
y_ito@note MINGW64 /c/work/git_test (master)
$ git commit --allow-empty -m 'first commit'
[master (root-commit) 0b7383d] first commit

y_ito@note MINGW64 /c/work/git_test (master)
$ git log --oneline
0b7383d (HEAD -> master) first commit

y_ito@note MINGW64 /c/work/git_test (master)
$ git branch
* master

```

##ブランチの作成
ファイルを1個コミットする。

```shell-session
y_ito@note MINGW64 /c/work/git_test (master)
$ cat uchinaaguchi.txt
うちなーぐち,標準語
はいさい,やあ

y_ito@note MINGW64 /c/work/git_test (master)
$ git add .

y_ito@note MINGW64 /c/work/git_test (master)
$ git status
On branch master
Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

        new file:   uchinaaguchi.txt

y_ito@note MINGW64 /c/work/git_test (master)
$ git commit -m 'ファイルを新規作成した。'
[master 93ed5c0] ファイルを新規作成した。
 1 file changed, 2 insertions(+)
 create mode 100644 uchinaaguchi.txt

```

コミットログとブランチはこうなっている。masterブランチだけがある。

```shell-session
y_ito@note MINGW64 /c/work/git_test (master)
$ git log --name-status
commit 93ed5c0031f205b1e10ad58ed74175574c652be0 (HEAD -> master)
Author: y_ito <intointo@nifty.com>
Date:   Tue May 15 21:34:24 2018 +0900

    ファイルを新規作成した。

A       uchinaaguchi.txt

commit 0b7383d2c2ecd652118d06aef56de1054c60ab19
Author: y_ito <intointo@nifty.com>
Date:   Tue May 15 21:20:23 2018 +0900

    first commit

y_ito@note MINGW64 /c/work/git_test (master)
$ git branch
* master

```

master以外のブランチを作る。

```shell-session
y_ito@note MINGW64 /c/work/git_test (master)
$ git branch branch-01

y_ito@note MINGW64 /c/work/git_test (master)
$ git branch branch-02

y_ito@note MINGW64 /c/work/git_test (master)
$ git branch
  branch-01
  branch-02
* master

```

##ブランチの切り替え
ブランチを切り替え、それぞれのブランチでファイルを編集する。

```shell-session
y_ito@note MINGW64 /c/work/git_test (master)
$ git checkout branch-01
Switched to branch 'branch-01'

y_ito@note MINGW64 /c/work/git_test (branch-01)
$ git branch
* branch-01
  branch-02
  master

<<<ここでファイルの編集>>>

y_ito@note MINGW64 /c/work/git_test (branch-01)
$ git diff
diff --git a/uchinaaguchi.txt b/uchinaaguchi.txt
index 125268c..f0a3e1b 100644
--- a/uchinaaguchi.txt
+++ b/uchinaaguchi.txt
@@ -1,2 +1,3 @@
 うちなーぐち,標準語
 はいさい,やあ
+ちゅーうがなびら,こんにちは

y_ito@note MINGW64 /c/work/git_test (branch-01)
$ git commit -a -m '「ちゅーうがなびら」を追加した。'
[branch-01 ba401ce] 「ちゅーうがなびら」を追加した。
 1 file changed, 1 insertion(+)

y_ito@note MINGW64 /c/work/git_test (branch-01)
$ git log --oneline
ba401ce (HEAD -> branch-01) 「ちゅーうがなびら」を追加した。
93ed5c0 (master, branch-02) ファイルを新規作成した。
0b7383d first commit

y_ito@note MINGW64 /c/work/git_test (branch-01)
$ git checkout branch-02
Switched to branch 'branch-02'

y_ito@note MINGW64 /c/work/git_test (branch-02)
$ git branch
  branch-01
* branch-02
  master

<<<ここでファイルの編集>>>

y_ito@note MINGW64 /c/work/git_test (branch-02)
$ git diff
diff --git a/uchinaaguchi.txt b/uchinaaguchi.txt
index 125268c..04fdd9c 100644
--- a/uchinaaguchi.txt
+++ b/uchinaaguchi.txt
@@ -1,2 +1,3 @@
 うちなーぐち,標準語
 はいさい,やあ
+めんそーれ,いらっしゃいませ

y_ito@note MINGW64 /c/work/git_test (branch-02)
$ git commit -a -m '「めんそーれ」を追加した。'
[branch-02 b4eeaaf] 「めんそーれ」を追加した。
 1 file changed, 1 insertion(+)

y_ito@note MINGW64 /c/work/git_test (branch-02)
$ git log --oneline
b4eeaaf (HEAD -> branch-02) 「めんそーれ」を追加した。
93ed5c0 (master) ファイルを新規作成した。
0b7383d first commit

```

それそれのブランチでファイルの内容が異なっている。

```shell-session
y_ito@note MINGW64 /c/work/git_test (branch-02)
$ git checkout master; cat uchinaaguchi.txt
Switched to branch 'master'
うちなーぐち,標準語
はいさい,やあ

y_ito@note MINGW64 /c/work/git_test (master)
$ git checkout branch-01; cat uchinaaguchi.txt
Switched to branch 'branch-01'
うちなーぐち,標準語
はいさい,やあ
ちゅーうがなびら,こんにちは

y_ito@note MINGW64 /c/work/git_test (branch-01)
$ git checkout branch-02; cat uchinaaguchi.txt
Switched to branch 'branch-02'
うちなーぐち,標準語
はいさい,やあ
めんそーれ,いらっしゃいませ

```

##マージする
mater で `git merge` コマンドを使って branch-01 を master にマージする。
fast-forward マージが行われる。

```shell-session
y_ito@note MINGW64 /c/work/git_test (branch-02)
$ git checkout master
Switched to branch 'master'

y_ito@note MINGW64 /c/work/git_test (master)
$ git merge branch-01
Updating 93ed5c0..ba401ce
Fast-forward
 uchinaaguchi.txt | 1 +
 1 file changed, 1 insertion(+)

y_ito@note MINGW64 /c/work/git_test (master)
$ git log --oneline
ba401ce (HEAD -> master, branch-01) 「ちゅーうがなびら」を追加した。
93ed5c0 ファイルを新規作成した。
0b7383d first commit

```

##コンフリクトを解決してマージする
###git mergeのみでマージする
さらに branch-02 をマージしようとすると、コンフリクトする。

```shell-session
y_ito@note MINGW64 /c/work/git_test (master)
$ git merge branch-02
Auto-merging uchinaaguchi.txt
CONFLICT (content): Merge conflict in uchinaaguchi.txt
Automatic merge failed; fix conflicts and then commit the result.

```

TortoiseGit ではファイルのアイコンにエクスクラメーションマークが付く。
![TortoiseGitでコンフリクトしているファイルの表示](https://qiita-image-store.s3.amazonaws.com/0/110278/4faa8769-28c6-acfe-aaa8-1a7f9a9111db.png)

ファイルの中身はこうなっている。

```text:uchinaaguchi.txt
うちなーぐち,標準語
はいさい,やあ
<<<<<<< HEAD
ちゅーうがなびら,こんにちは
=======
めんそーれ,いらっしゃいませ
>>>>>>> branch-02
```

各ブランチで編集した内容を両方残したいので、下記のように編集して保存する。

```text:uchinaaguchi.txt
うちなーぐち,標準語
はいさい,やあ
ちゅーうがなびら,こんにちは
めんそーれ,いらっしゃいませ
```

編集したファイルをaddしてcommitする。このマージはnon fast-forward マージとなる。

```shell-session
y_ito@note MINGW64 /c/work/git_test (master|MERGING)
$ git diff
diff --cc uchinaaguchi.txt
index f0a3e1b,04fdd9c..0000000
--- a/uchinaaguchi.txt
+++ b/uchinaaguchi.txt
@@@ -1,3 -1,3 +1,4 @@@
  うちなーぐち,標準語
  はいさい,やあ
 +ちゅーうがなびら,こんにちは
+ めんそーれ,いらっしゃいませ

y_ito@note MINGW64 /c/work/git_test (master|MERGING)
$ git add .

y_ito@note MINGW64 /c/work/git_test (master|MERGING)
$ git commit -m 'branch-02をマージした。'
[master 33a4d0c] branch-02をマージした。

y_ito@note MINGW64 /c/work/git_test (master)
$ git log --oneline
33a4d0c (HEAD -> master) branch-02をマージした。
b4eeaaf (branch-02) 「めんそーれ」を追加した。
ba401ce (branch-01) 「ちゅーうがなびら」を追加した。
93ed5c0 ファイルを新規作成した。
0b7383d first commit

```

`git log` にオプションを付けて表示される履歴グラフは下記のようになる。2つのブランチで別々に編集され、それがマージされたことがグラフィカルに分かる。

```shell-session
y_ito@note MINGW64 /c/work/git_test (master)
$ git log --graph --oneline --decorate=full
*   33a4d0c (HEAD -> refs/heads/master) branch-02をマージした。
|\
| * b4eeaaf (refs/heads/branch-02) 「めんそーれ」を追加した。
* | ba401ce (refs/heads/branch-01) 「ちゅーうがなびら」を追加した。
|/
* 93ed5c0 ファイルを新規作成した。
* 0b7383d first commit

```

###git rebaseしてgit mergeする

master と branch-02 をマージするもう1つの方法に、rebaseしてからmergeするやり方がある。
まず、master を branch-02 とマージする前に戻す。

```shell-session
y_ito@note MINGW64 /c/work/git_test (master)
$ git reset --hard HEAD~
HEAD is now at ba401ce 「ちゅーうがなびら」を追加した。

y_ito@note MINGW64 /c/work/git_test (master)
$ git log --graph --oneline --decorate=full
* ba401ce (HEAD -> refs/heads/master, refs/heads/branch-01) 「ちゅーうがなびら」を追加した。
* 93ed5c0 ファイルを新規作成した。
* 0b7383d first commit

```

そして branch-02 を rebase する。

```shell-session
y_ito@note MINGW64 /c/work/git_test (master)
$ git checkout branch-02
Switched to branch 'branch-02'

y_ito@note MINGW64 /c/work/git_test (branch-02)
$ git rebase master
First, rewinding head to replay your work on top of it...
Applying: 「めんそーれ」を追加した。
error: Failed to merge in the changes.
Using index info to reconstruct a base tree...
M       uchinaaguchi.txt
Falling back to patching base and 3-way merge...
Auto-merging uchinaaguchi.txt
CONFLICT (content): Merge conflict in uchinaaguchi.txt
Patch failed at 0001 「めんそーれ」を追加した。
The copy of the patch that failed is found in: .git/rebase-apply/patch

When you have resolved this problem, run "git rebase --continue".
If you prefer to skip this patch, run "git rebase --skip" instead.
To check out the original branch and stop rebasing, run "git rebase --abort".


```

すると、ファイルはこうなっている。

```text:uchinaaguchi.txt
うちなーぐち,標準語
はいさい,やあ
<<<<<<< HEAD
ちゅーうがなびら,こんにちは
=======
めんそーれ,いらっしゃいませ
>>>>>>> 「めんそーれ」を追加した。
```

これを先の merge だけする場合と同じように編集して保存する。

```text:uchinaaguchi.txt
うちなーぐち,標準語
はいさい,やあ
ちゅーうがなびら,こんにちは
めんそーれ,いらっしゃいませ
```

そして add して rebase --continue すると、branch-02 はrebaseされる。
＃rebase って、base を re なのね。なんとなく判ったような気がする。

```shell-session
y_ito@note MINGW64 /c/work/git_test (branch-02|REBASE 1/1)
$ git diff
diff --cc uchinaaguchi.txt
index f0a3e1b,04fdd9c..0000000
--- a/uchinaaguchi.txt
+++ b/uchinaaguchi.txt
@@@ -1,3 -1,3 +1,4 @@@
  うちなーぐち,標準語
  はいさい,やあ
 +ちゅーうがなびら,こんにちは
+ めんそーれ,いらっしゃいませ

y_ito@note MINGW64 /c/work/git_test (branch-02|REBASE 1/1)
$ git status
rebase in progress; onto ba401ce
You are currently rebasing branch 'branch-02' on 'ba401ce'.
  (fix conflicts and then run "git rebase --continue")
  (use "git rebase --skip" to skip this patch)
  (use "git rebase --abort" to check out the original branch)

Unmerged paths:
  (use "git reset HEAD <file>..." to unstage)
  (use "git add <file>..." to mark resolution)

        both modified:   uchinaaguchi.txt

no changes added to commit (use "git add" and/or "git commit -a")

y_ito@note MINGW64 /c/work/git_test (branch-02|REBASE 1/1)
$ git add uchinaaguchi.txt

y_ito@note MINGW64 /c/work/git_test (branch-02|REBASE 1/1)
$ git status
rebase in progress; onto ba401ce
You are currently rebasing branch 'branch-02' on 'ba401ce'.
  (all conflicts fixed: run "git rebase --continue")

Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

        modified:   uchinaaguchi.txt


y_ito@note MINGW64 /c/work/git_test (branch-02|REBASE 1/1)
$ git rebase --continue
Applying: 「めんそーれ」を追加した。

y_ito@note MINGW64 /c/work/git_test (branch-02)
$ git status
On branch branch-02
nothing to commit, working tree clean

y_ito@note MINGW64 /c/work/git_test (branch-02)
$ git log --oneline
c0e3b9e (HEAD -> branch-02) 「めんそーれ」を追加した。
ba401ce (master, branch-01) 「ちゅーうがなびら」を追加した。
93ed5c0 ファイルを新規作成した。
0b7383d first commit

```

あとは mater で branch-02 をマージする。このマージは fast-forwar マージとなる。

```shell-session
y_ito@note MINGW64 /c/work/git_test (branch-02)
$ git checkout master
Switched to branch 'master'

y_ito@note MINGW64 /c/work/git_test (master)
$ git merge branch-02
Updating ba401ce..c0e3b9e
Fast-forward
 uchinaaguchi.txt | 1 +
 1 file changed, 1 insertion(+)

```

先の rebase しないマージの場合とファイルの内容は同じだが、コミットグラフが異なる。
コミットの履歴が1本になっている。

```shell-session
y_ito@note MINGW64 /c/work/git_test (master)
$ git log --graph --oneline --decorate=full
* c0e3b9e (HEAD -> refs/heads/master, refs/heads/branch-02) 「めんそーれ」を追加した。
* ba401ce (refs/heads/branch-01) 「ちゅーうがなびら」を追加した。
* 93ed5c0 ファイルを新規作成した。
* 0b7383d first commit

```

###rebaseする／しないの判断

rebaseせずにマージした場合とrebaseしてマージした場合で以下の表のような違いがある。

|  | マージコミットが | マージグラフは |
|:--|:-:|:-:|
| **rebaseしなかった場合** | 作成される | 分岐あり |
| **rebaseした場合** | 作成されない  | 分岐なしで1本線|

「マージした」という履歴を残すことを重視する場合はrebaseしない、「履歴をきれいに保つ」ということを重視する場合はrebaseするという感じ。
GitHubなどを使って複数人で開発している場合は、「履歴をきれいに保つ」というのが重要視されていてpushする前にrebaseしましょうというのがルールになっているようだ。ただし、既にpushしたブランチをrebaseしてまたpushするのは禁止らしい。


##感想

例えば次期リリースの開発中に前のリリースの緊急対応が必要なバグが見つかった場合、ブランチを切れば時期リリース開発に影響なくバグフィックスができる。Gitの書籍や入門サイトのよくあげられている例だが、確かにそういう場面では役に立ちそう。
一人での開発でも、GitHubとかGitLabとか使ってなくても、Gitを使う意味は十分あるように思った。


#参考文献・参考ページ

##参考文献
WEB+DB PRESS Vol.90  特集1：もう困らない！Git実践活用

##参考ページ
[チュートリアル1 ブランチを使ってみよう | サルでもわかるGit入門 〜バージョン管理を使いこなそう〜 | どこでもプロジェクト管理バックログ](http://www.backlog.jp/git-guide/stepup/stepup2_1.html)
[初心者でもわかる！リベースの使い方を解説します | Git編：一歩踏み出すフロントエンド入門](https://liginc.co.jp/web/tool/79390)
[git logにブランチ名を表示する git log --oneline --graph --decorate=fullのススメ - Qiita](http://qiita.com/masarufuruya/items/7480854d4bc2f0443fc1)
[Gitで特定のcommit idを指定してbranchを切ってcheckoutする方法 - Qiita](http://qiita.com/knt45/items/075e8a48fa6af6fc3b80)
[git graphを使ってターミナルで Git のツリー構造を表示する | ワードプレスのホームページ制作｜新宿区のフリーランスWeb制作　ピクセルデザイン - Webサイトのディレクションからデザイン・コーディングまで](http://wp.pxdesign.jp/2014/08/23/git-hierarchy-structure/)
[[Git] 使い分けできていますか？マージ（merge）&リベース（rebase）再入門 - The Powerful Code](http://powerful-code.com/blog/2012/11/merge-or-rebase/)
[シロク流、Gitの使い方 - Qiita](http://qiita.com/shuntaro_tamura/items/f9d337ecec328a1e0852)
[怖くないGit](https://www.slideshare.net/kotas/git-15276118)
[【git】分かりやすく！mergeは「合流」、rebaseは「付け替え」! | NullNote meta : Description](http://nullnote.com/web/git/merge_rebase/)
[Git 過去の特定のコミット位置からブランチを切りたい - かもメモ](https://chaika.hatenablog.com/entry/2017/07/20/093000)
[gitのローカルのブランチ名を変更したい - Qiita](https://qiita.com/suin/items/96c110b218d919168d64)