GitのFast Forwardの動作を確認してみる

# はじめに
[前回の記事](https://qiita.com/neruneruo/items/0daf2b30c59582007c4e)からの続き。
[理想を追い求めたCI/CDパイプライン](https://jawsdays2020.jaws-ug.jp/session/%E9%87%91%E8%9E%8D%E3%82%B5%E3%83%BC%E3%83%93%E3%82%B9%E5%90%91%E3%81%91%E3%81%AB%E7%90%86%E6%83%B3%E3%81%AEci-cd%E3%82%92%E8%BF%BD%E3%81%84%E6%B1%82%E3%82%81%E3%81%9F%E3%81%8A%E8%A9%B1/)では環境とブランチが1対1に紐付き、その間では1つのコンテナベースを渡り歩かせるのが原則となっているため、Commitとコンテナベースを一意に紐付ける識別子が必要になる。一方で、GitLabではデフォルトのマージはmergeCommitとなり、CommitIDが都度作られてしまうことから、コンテナべベースを一意に識別するためには、外部から識別子を渡してあげなければいけない。外部から渡すということは人力の作業が増え、自動化原則から外れてしまう。対応するには、mergeCommitをしないFast Forwardなマージを考える必要がある。

# Fast Forwardなマージとは
[GitLab Docs](https://gitlab-docs.creationline.com/ee/user/project/merge_requests/fast_forward_merge.html)参照。
なぜか日本語ドキュメントでもこのパートは訳されていないけど……。

さらに、Fast ForwardとNon Fast Forwardの違いをしっかり説明してくれているのが以下のブログ
[「こわくない Git」というスライドを発表しました](http://kotas.hatenablog.jp/entry/2012/11/22/000046)
ちょうわかりやすい。Gitに苦手意識のあるすべての人が読んでおくと良いと思った。

Fast Forwardでは、たしかにスライド中にある「マージをしたという記録が残らない」「マージが取り消しづらい」がデメリットになってしまうので、これについては、「ちゃんとタグで記録残していきましょう」なんだろうな……。

# 実際に動かしてみる
さて、実際に以下のようなGitLab Flowを運用していると仮定して実験してみる。
- masterブランチ
- productionブランチ
- 開発時 feature/issueXX ブランチ

## 単一featureブランチからの単純なFast Forward
### 初期状態
feature/issue01 では b.txt の追加と a.txt の修正をした状態から、productionへの反映をやってみよう。
Commitグラフは以下のような状態がスタートだ。

![キャプチャ1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/111314/a8f41f33-c82f-1be4-1eca-4824ba6ec424.png)

### masterへのマージ
ブランチからマージリクエストを送り、Fast Forwardなマージを行う。

![キャプチャ2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/111314/9b788726-8de1-a4bd-bcf1-b23a13a5c44b.png)

たしかに、マージの記録が非常に分かりにくいが、ブランチはfeature/issue01と同じCommitIDに移動している。

### productionへのマージ
ブランチからのマージではないので操作方法に一瞬悩むが、新規のマージリクエストからであれば、masterブランチからのマージリクエストを作成できるので、そこから行う。

![キャプチャ3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/111314/d8333db7-e87c-e309-a455-18eadef2ad1a.png)

これで、production も master と同じところまで進んだ状態になる。

さて、ここまでは基本的なFast Forwardの動作なので特に考えることはないはずだ。

## 複数featureブランチからのFast Forward
では、複数人で開発を行っていて、feature ブランチが並走している場合はどうなるだろう？
一度、これまでの開発の feature は削除して新たに feature/issue02、feature/issue03 を作る。

### 初期状態
以下の開発を行ったと仮定する。
- feature/issue02: c.txtの追加
- feature/issue03: b.txtの修正

マージ前のCommitグラフは以下のようになる。

![キャプチャ4.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/111314/2928b6b1-331a-1b9a-9b76-c839556dba50.png)

### masterへのマージ
まずは、feature/issue02 をマージする。

![キャプチャ5.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/111314/288f4908-39cb-b89d-ee0e-5ea5dfb50cc5.png)

Commitグラフは上記のようになり、c.txt は追加されているが、b.txt の修正は取り込まれていない状態だ。

![キャプチャ6.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/111314/838540da-0af7-f1c2-5eb9-0570fd84c34d.png)

ここから更に、feature/issue03 をマージする。すると…

![キャプチャ7.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/111314/415e78f1-6727-f8cf-9acb-ee871824a93c.png)

この場合はファイルが競合していなくても、どうやら Fast Forward なマージはできないようだ。
Rebase をしてみよう。

![キャプチャ8.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/111314/c5bd4a0d-7632-3bdf-3d68-6fc599f3a972.png)

なんかそれっぽくマージされたように見える。
リポジトリの中身は問題ないだろうか。

![キャプチャ9.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/111314/7b82036b-ec03-13c2-6b05-84a48df66b2a.png)

ちゃんと feature/issue02 と feature/issue03 が取り込まれている！

### productionへのマージ
ここまでできていれば一安心で、production へのマージは単一featureブランチと同じように実施可能だ。

![キャプチャ10.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/111314/1aa8b2f4-67a9-7caf-1d0c-ab81735f4040.png)

## 先に進んでいるmasterブランチの過去のCommitIDからproductionブランチにマージする
feature/issue04 ブランチの開発については master ブランチへのマージまで完了していてあとは production 環境にデプロイするだけなのだけど、もろもろの事情でちょっと待って、と言われてるところに次の feature/issue05 ブランチの開発が来ていて stage 環境にはデプロイしなきゃいけないからとりあえず master ブランチは進めないと、といった状況になった場合の方法論(今回は stage 環境ないけど…)。
さらに考えると、この stage 環境での開発を先に production 環境にデプロイしないとといけないケースも想定できるが、そこまでいくともはや Fast Forward は諦めて別の方法を考えた方が良いと思うので、一旦除外する。

### 初期状態
以下の開発を行ったと仮定する。
- feature/issue04: d.txtの追加、c.txtの追加 → masterブランチへのマージ
- feature/issue05: 上記の後、e.txtの追加、d.txtの修正 → masterブランチへのマージ

Commitツリーは以下の状態。

![キャプチャ11.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/111314/1bc1d0fa-3f82-7493-41e8-6eff6dc5067d.png)

feature/issue04ブランチは以下の状態。

![キャプチャ12.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/111314/643d7359-a46a-b3f3-2912-ce4889b7dd59.png)

masterブランチおよびfeature/issue05ブランチは以下の状態。

![キャプチャ13.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/111314/d6594374-5d51-7fc4-4a45-52c3a5255c48.png)

### productionへのマージ
さて、上記の master ブランチからマージをかけてしまうと、当然 master ブランチの最新の状態になってしまう。今回のケースであれば、まだ feature/issue04 が残っているから、そこからマージしてしまうというのも一つの手段だ。ただし、今回は分かりやすくするために残しているが、通常は feature ブランチは、master ブランチへのマージが完了した時点で消してしまうため、この手段が使えない。

こういう時は、タグを使おう。
と言うか、冒頭に書いたように、Fast Forwardなマージコミットの場合、マージした際にタグをつけておかないとワケが分からなくなるため、本来はそのタイミングで付与しておくべきだろう。

![キャプチャ14.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/111314/41794023-b87f-0596-5338-a9c496c8d86c.png)

タグを作ったら、そこからブランチを作ることができる。

![キャプチャ15.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/111314/799941fd-aec7-be6d-9402-f1bbeabbbf0e.png)

ブランチができれば、後は普通にブランチから production ブランチにマージすれば良い。

![キャプチャ16.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/111314/4c3b2beb-a68d-a4b5-e153-c8e61c1fe90b.png)

production ブランチの内容も、しっかり feature/issue04 のみが取り込まれ、feature/issue05 は入っていない状態になっている。

![キャプチャ17.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/111314/87345da2-e424-f5d5-9c76-cce35741fa49.png)

# production 環境での不具合発生時にどう対応するか
## master ブランチが進んでいない場合
仮に、次の開発が進んでいたとしても、まだ feature ブランチでの開発を行っているだけで、master ブランチへのマージが行われていない場合は、[複数featureブランチからのFast Forward](https://qiita.com/neruneruo/items/456c08275913258fa9e8#%E8%A4%87%E6%95%B0feature%E3%83%96%E3%83%A9%E3%83%B3%E3%83%81%E3%81%8B%E3%82%89%E3%81%AEfast-forward)と同じように行うだけである。

1. hotfix ブランチの作成
2. master ブランチへのFast Forwardなマージ
3. production ブランチへのFast Forwardなマージ
4. その後、feature ブランチからのマージでは rebase が発生するので、充分注意してマージする

## master ブランチが進んでいる場合
### 初期状態
厄介なのは、既に master ブランチに次の開発がマージされてしまっている以下のケースである。

![キャプチャ18.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/111314/c3f58662-b43d-c997-ecbf-ebf7b65cb098.png)

このケースでは、既に以下の開発が master ブランチに取り込まれている状態で、過去の開発のファイル a.txt に不具合が見つかったと仮定する。
- feature/issue06: f.txtの追加

この時は、production 環境へのデプロイが必要だが、まだ feature/issue06 を取り込んではいけない。
仕方がないので、一旦 production を真として進めるしかないだろう。

### hotfix ブランチの作成
production ブランチを元に、hotfix ブランチを作成して、a.txt を修正する。
当然のことながら、feature/issue06 はまだ取り込まれていない。

![キャプチャ19.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/111314/54da4da7-030b-c769-7193-e167d9419e54.png)

![キャプチャ20.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/111314/a2a3508f-d472-9170-d43d-7760fe8e6f1e.png)

この状態でパイプラインを起動させる必要があるため、普段 master ブランチで開発環境にデプロイをしているのであれば、設定変更をするか、パイプラインをクローンして開発環境でテストを行う。

### hotfix ブランチを production ブランチに Fast Forward でマージ
テストが終わったら、hotfix から production にマージをする。
production は既にパイプラインがあるはずなので、ここは通常の操作で良いだろう。

![キャプチャ21.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/111314/e3ab5273-007b-8898-4d04-5c51298e97c0.png)

![キャプチャ22.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/111314/531ca5e0-c225-afed-0fa9-4e5c10005163.png)

これで、production 環境は無事修正された。
後始末で、masterへのマージを忘れないようにしよう。

### hotfix ブランチを master ブランチに rebase でマージ
ブランチが途中で完全に分岐してしまっているため、ここは rebase になってしまうのは致し方ない。
ただし、ここで気を付けなければいけないのは、必ずproduction ブランチではなくhotfixブランチをマージすること。
rebase したタイミングでproductionブランチにcommitが発生するため、パイプラインが起動してしまう上に、開発中の feature/issue06 が取り込まれてしまう。

hotfix ブランチを rebase すると、ブランチツリーは以下の状態になる。

![キャプチャ23.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/111314/ba9d6bf8-7735-b9ae-a868-3c60408c5370.png)

![キャプチャ24.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/111314/cf645fba-cbe7-b7b9-0d9d-3e9d8b3086ae.png)

この状態で、master ブランチを Fast Forward でマージすることで、master は feature/issue06 と hotfix/bug01 を取り込んだ状態にできる。

![キャプチャ25.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/111314/210d4341-4798-ad54-1006-1f6aa5cfce1c.png)

### production ブランチを master ブランチに合流させる
最後に、feature/issue06 の開発完了時に、production ブランチに反映させなければいけないが、ここが曲者である。ブランチが途中で分岐してしまっているため、どうしても rebase が発生してしまう。しかし、rebase でいきなり production ブランチを更新してデプロイのパイプラインが実行されるのはリスクが高い。

ここは、ちゃんと実績のある資材を動かすために、多少強引であるが、production ブランチを master ブランチに付け替えてあげよう。

```shell-session
$ git reset --hard [masterブランチのCommitIDのハッシュ]
```

なにげにこれはかなり危険な操作なので、ちゃんとGitを理解している人とレビューしながら操作した方が良い。

これを行うことで、

![キャプチャ26.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/111314/11009522-8fac-871c-a12a-47147ff9c1f2.png)

といった具合で master ブランチに production ブランチが合流したことになる。