Git 的 Merge 與 Fast-Forward

*（日本語はまだ、中国語ですみません :sweat_drops: ）*

台湾人の vc7 です。

在一般使用 Git 的開發日常中， branch 的新增和 merge 是很常見的事情。進行 merge 的時候，git 預設會以 fast-forward 的模式進行。

那什麼是 fast-forward merge 呢？和 no fast-forward 的 merge 有什麼差別？

# Fast-Forward Merge 是什麼

當你目前的位置 (`HEAD`) 是某個要被 merge branch 上的 commit 的 root commit ，在沒有任何新的 commit 的情形下，要 merge 回來的時候，就會觸發 fast-forward 。

由於在原本的分支上沒有新的變更紀錄需要被 merged ，預設就會觸發 fast-forward merge ，就會直接把這個 commit `HEAD` 移動到要被 merge 的 commit 的位置，也不會新增一個 merge commit 。

# 前置作業

首先我們要先準備一系列含有 branch 的 commit ，長得如下圖

![image](https://qiita-image-store.s3.amazonaws.com/0/41979/1a630ad1-4be4-905b-ae91-9f7a41b3a59a.png)

說明：
- HEAD - 當前所在位置，指標位置指向 master
- 藍色標籤 - branch 指標所指的 commit 位置
- 灰色底的文字 - commit messages

## 建立資料夾並初始化 git repo

打開終端機之後，移動到你想要的資料夾

``` bash
mkdir git-ff-demo
cd git-ff-demo
git init
```

## 做幾個 commit 、建立 branch 後，再回到 master 上

初始化 git repository 之後，預設就會在 master 上。為了降低複雜度，這個範例在 commit 時會用到另外一個  flag - `—-allow-empty` ，可以讓你不用變更資料夾的情形下就可以執行 commit 。

``` bash
git commit —-allow-empty -m “Initial Commit”
git commit —-allow-empty -m “a”
```

建立兩個 commit 之後，現在來建立如圖中的 `b-01` 分支

``` bash
git checkout -b b-01
```

接著在 `b-01` 上再繼續多 commit 兩個變更

``` bash
git commit —-allow-empty -m “b”
git commit —-allow-empty -m “c”
```

最後 checkout 回到 `master` ，準備把 `b-01` merge 到 `master` 上：

``` bash
git checkout master
```

這樣子就完成事前準備了，如果在 [SourceTree](https://www.sourcetreeapp.com/) 裡面看就會長這個樣子：

![image](https://qiita-image-store.s3.amazonaws.com/0/41979/0a8109a6-da01-7d18-f3c8-2b83a437529d.png)


# 進行 Merge

## 預設的 merge

這時候執行

```
git merge b-01
```

這時候就會看到終端機有跳出回饋的訊息：

```
Updating 464aead..0169033
Fast-forward
```
提示我們他只是幫我們把目前 `HEAD` 所指到的 `master` 的指標位置換到 `0169033` 

在 SourceTree 裡面看，就變成這樣了：

![image](https://qiita-image-store.s3.amazonaws.com/0/41979/76c52b14-97f2-6260-81ee-1ea65b7799f7.png)

> 我的 branch 怎麼不見了（慌張）

因為 fast-forward 的關係，`b-01` 分支的特徵也就變得無法辨識了

## Merge 時不要 fast-forward 

在 merge 指令有個 `—-no-ff` flag，可以告訴 git 這次的 merge 無論如何都不要進行 fast-forward 。

開始前，可以在另外一個資料夾，在進行一次前置作業所做的事情，確認一下你的 git repo 是不是目前這個狀態：

![image](https://qiita-image-store.s3.amazonaws.com/0/41979/3276d8a0-6c41-4b5c-a341-2a7761797a7d.png)


### 執行指令

在 merge 的指令中，加上 `—-no-ff` 即可：

``` bash
git merge —-no-ff b-01
```
（接著可能會跳出 vim 的編輯畫面，輸入 `:wq` 儲存並關閉就可以完成 merge commit）

這時候終端機就會跳出以下訊息，

```
Already up-to-date!
Merge made by the 'recursive' strategy.
```

來到 SourceTree 看，也就可以看到 `b-01` branch 的分支特徵也保留下來：

![image](https://qiita-image-store.s3.amazonaws.com/0/41979/4ab211b2-0f92-ad4d-a436-62819ee16d04.png)

除此之外，加上 --no-ff 之後就有加一次 merge 的 commit 記錄這一次的變更

# 加上 `--no-ff` 的優缺點

## 優點

在開發一個功能時，通常都會開一支新的分支，使用 `--no-ff` 可以讓成員在日後可以很清楚辨識不同的功能所包含的 commit 歷程有哪些

除此之外，GitHub merge pull request 的策略，也是使用 `--no-ff` ，也是讓開發者可以方便辨別。

## 缺點

假使這個 task 或是 bug-fix 只有一個  commit ，很容易造成一堆小耳朵的存在，如下圖：

![image](https://qiita-image-store.s3.amazonaws.com/0/41979/44809ec1-5255-2209-ac8a-ee0a12325c7e.png)


### 對策

- 思考 task 規劃時的範圍是不是切太細？
- 或是在 git 流程中，啟用 hot-fix 之類的 branch 來負責 bug fix 的 commits ，零碎的 hot-fix 可以直接在這個 branch 上解決，比較龐大的 bug fix 再另外建立 branch 來負責

# 結語

我第一次執行 merge 的時候就有發現我的 branch 被 fast-forward merge 掉了，第一次看到真的有嚇到；到後來才知道原來這個叫做 fast-forward 。

幫團隊做 code review 後需要 merge 的時候，也都是使用這個方式保留 branch 的特徵（當時公司內還沒有採用 PR 的機制，所以都是手動來的）。

這篇的優缺點是我自己的想法和心得，如果有任何的想法和討論都可以在下面留言唷！

如果覺得這篇不錯，可以分享、或是點最最最上面的 Stock 按鈕儲存起來，謝謝您。

# 參考資料

- [Git Merge Documentation](http://git-scm.com/docs/git-merge)
- [Fast-Forward Git Merge](http://ariya.ofilabs.com/2013/09/fast-forward-git-merge.html)

*本篇環境： Mac OS X 10.11, git 2.6.2, SourceTree 2.0.5.2*