Git ブランチ作成からマージされるまでの流れ

Issueが割り振られてから、マージされるまでの流れをご紹介します。
プロジェクトによって細かい部分は異なるかもしれませんが、概ねは大丈夫かなと思います。

## 概要

- 実務におけるGitの作業フローをまとめ
- ブランチ戦略の話ではない
- SourceTree等のGUIツールは使用しません。Git CLIのみ
- リモートリポジトリをクローンして、ローカルリポジトリは作成済みの想定
- デフォルトブランチが main ではなく develop など別のブランチになっている方は読み替えてください

## 事前準備

### ローカルの main ブランチを削除

ローカルの `main` ブランチに切り替えて、 `git pull` して `git switch` するのが手間に感じたので、あえてローカル `main` ブランチを削除する運用をしています。

```shell-session
$ git switch -d origin/main
$ git branch -D main
```

慣れると快適だったのでおすすめしてです。
チームで運用ルールが厳格に決まっている場合はそちらのるーるを優先してください。

ローカルの main ブランチを使用しない運用については下記の記事で詳しく書いてます。

https://qiita.com/ucan-lab/items/0bbf07daa58959c1010a

## 流れ

1. [新しくブランチを作成する](#新しくブランチを作成する)
1. [作業してコミットをする](#作業してコミットをする)
1. [作業ブランチに最新のmainブランチを取り込む](#作業ブランチに最新のmainブランチを取り込む)
1. [作業ブランチを整理する](#作業ブランチを整理する)
1. [リモートブランチへプッシュする](#リモートブランチへプッシュする)
1. [プルリクエストを作成する](#プルリクエストを作成する)
1. [レビューを受ける](#レビューを受ける)
1. [レビュー内容を反映したコミットを作る](#レビュー内容を反映したコミットを作る)
1. [変更点の再レビューを受ける](#変更点の再レビューを受ける)
1. [再度作業ブランチを整理する](#再度作業ブランチを整理する)
1. [GitHub上でマージする](#GitHub上でマージする)
1. [ローカルのブランチを削除する](#ローカルのブランチを削除する)

書き出してみましたが、多いので補足していきます。

### 新しくブランチを作成する

ブランチを作成する前に必ず、最新のリモートリポジトリの情報を取り込んでください。
これをしないと古いコミットからブランチを切り出してしまうことがあります。

```shell-session
$ git fetch --prune
```

:::note info
`--prune` オプションはリモートリポジトリで消されたブランチをローカルリポジトリに反映するオプションです。
毎回打つのは面倒なので次のコマンドで設定しておくと良いです。

```shell-session
$ git config --global fetch.prune true
```

```shell-session
$ git fetch
```
:::

派生元の `origin/main` リモート追跡ブランチから新しく `topic-123` ブランチを作成しています。
また、引数で派生元のブランチを指定しない場合は現在のブランチ(コミット)から新しいブランチが作成されます。

```shell-session
$ git switch -c topic-123 origin/main
```

:::note warn

理由なくマージ済みのブランチを再利用したり、そこからブランチを生やさないでください。
最新に追いつくためのマージやコンフリクト解消するコストが発生します。

:::

ブランチ名の命名ルールはプロジェクトのブランチ戦略によるのでプロジェクトに合わせてください。
導入しているタスク管理ツールによってはタスク番号をブランチに含めると自動で紐付くものもあるのでツールに合わせて命名してください。

### 作業してコミットをする

作業した変更内容をコミットします。

```shell-session
$ git add .
$ git commit
```

vimエディタで開くのでコミットメッセージを入力します。

:::note info
git commit の便利なオプション

```shell-session
$ git commit -m "A 機能を実装しました"
```

`-m` オプションを付けるとvimエディタを立ち上げずに1行コミットメッセージを入力できます。

```shell-session
$ git commit --amend
```

`--amend` 直前のコミットを変更できます。
git add してインデックス(ステージング)上がっている変更が直前のコミットに反映されます。
また、コミットメッセージの変更もできます。

```shell-session
$ git commit --amend --no-edit
```

コミットメッセージを変更する必要ない場合は `--no-edit` オプションを付けます。

:::

### 補足: main ブランチの更新について

チーム開発していくと `main` ブランチがどんどん更新されていき、作業ブランチが古くなってしまいます。

```
[リベース前]
      E---F---G topic-123
     /
A---B---C---D origin/main

[リベース後]
              E'---F'---G' topic-123
             /
A---B---C---D origin/main
```

ブランチが古くなると大量にコンフリクトが発生することもあるので更新があればこまめに取り込むのが良いです。

:::note info

プルリクエストを出す前にもコンフリクトしていないか確認をするために必ず最新を取り込んでください。
コンフリクトが発生したままプルリクエストを出すとレビュワーの負担と不必要なコミュニケーションコストが増えます。

:::

```shell-session
$ git stash -u
```

作業途中のファイルがある場合は、スタッシュして一時保存するかコミットしてください。
`-u` オプションを付けると未追跡ファイル(untracked file)を含めてスタッシュしてくれます。

### 作業ブランチに最新のmainブランチを取り込む

```shell-session
$ git fetch
```

最新のリモートブランチの情報を取得します。

```shell-session
$ git rebase origin/main
```

途中コンフリクトが発生したらエディタを開いてコンフリクトを解決してください。

```shell-session
$ git status

# 対象のファイルを開いてコンフリクトを解消する

$ git add .
$ git rebase --continue
```

`git add` した後に `git commit` するのではなく、 `git rebase --continue` してリベースを継続します。
途中でやり直ししたい場合は `git rebase --abort` するとリベースする前の状態に戻ります。

:::note info
`--continue` オプションはコンフリクト解消後の再開コマンドです。
コンフリクトがなくなるまで繰り返します。

`--abort` オプションはリベース前の状態に戻します。途中でリベースをやめたい場合に使います。安全に巻き戻すためのコマンドです。
:::

```shell-session
$ git stash pop
```

スタッシュに一時保存した変更を取り出します。
`git rebase` のわかりやすそうなサイトをご紹介します。
https://www.atlassian.com/ja/git/tutorials/rewriting-history/git-rebase

:::note info
git rebase についてはハンズオン形式で紹介した記事です。

https://qiita.com/ucan-lab/items/0a128bb14528e4e52fd2
:::

### 作業ブランチを整理する

コミットメッセージを間違えたり、同じメッセージのコミットが複数あったり、wipなど試行錯誤した作業コミットは残さないようにします。

1つのコミットは1つ1つ意味のあるまとまりにします。
Issueを細かく区切って1プルリクエスト1コミットになるのが理想です。

mainブランチのコミットが綺麗になっているとあとから保守する人が助かります。

```shell-session
$ git rebase -i origin/main
```

### リモートブランチへプッシュする

```shell-session
$ git push -u origin HEAD
```

`-u` オプションを付けると次回以降は `git push` だけで良くなります。

:::note info
push.default オプション

```shell-session
$ git config --global push.default current
```

このオプションを設定している場合は `git push` だけでOKです。

```shell-session
$ git push
```

https://qiita.com/ucan-lab/items/c65297c062b477be01e5

:::

### プルリクエストを作成する

GitHubを開いてプルリクエストを作成します。

### レビューを受ける

先輩からの手厚いレビューを受けます

![Ev7kuJUUUAI9yBz (1).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/52879/219459a4-48e1-6441-0f3f-66331b5e7742.png)

### レビュー内容を反映したコミットを作る

```shell-session
$ git add .
$ git commit -m "レビュー内容の反映"
$ git push
```

レビューが通るまで繰り返します💪

### 変更点の再レビューを受ける

Approveされたら、作業ブランチを整理してマージ作業を行います。

![EoHxDS-UcAEDyjd.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/52879/0360a8d5-f338-5353-8876-93506a23b7ee.jpeg)

### 再度作業ブランチを整理する

レビューが完了したら、
レビューを通すために試行錯誤したコミットを見直したり、レビュー中にmainブランチが進んでいる場合があるので最新を取り込みます。
(レビュー依頼したらrebase禁止で運用しているプロジェクトもあります。その場合はスキップしてください)

```shell-session
$ git rebase origin/main
$ git rebase -i origin/main
$ git push --force-with-lease --force-if-includes
```

- `git rebase` したのでコミットハッシュ値が変わり、強制プッシュが必要
- `git push --force-with-lease --force-if-includes` ブランチを強制プッシュします
  - 個人で使用している作業ブランチのみ強制プッシュ

:::note info
`--force` or `--force-with-lease --force-if-includes` の違い

リモートリポジトリのブランチにプッシュする場合、リモートリポジトリの最終コミットとローカルリポジトリの最終コミットが親子関係になっていないとエラーとなり通常はpushできません。

`--force` オプションを付けるとその制限を回避できます。
こうするとGitはリモート履歴を完全に無視して、無条件にブランチを上書きします。

しかし、他の人がそのブランチを変更していた場合はその変更が失われてしまいます。

`--force-with-lease` オプションは
リモートリポジトリの特定のブランチが最後に`fetch`または`pull`した時から更新されていないことを確認することで安全な強制プッシュを実現します。
もし誰かがリモートブランチへ新しいコミットをプッシュしていた場合は、プッシュが拒否されます。

`--force-if-includes` オプションは
リモートリポジトリにあるコミットがローカルのプッシュしようとしているブランチの履歴に含まれている場合のみ、強制プッシュを許可します。このオプションにより直前に`fetch`していると`push`が成功してしまう問題が解消します。

https://zenn.dev/mary_pp/articles/eaac544eaf600a

```
# エイリアス登録
$ git config --global alias.pushf 'push --force-with-lease --force-if-includes'
# 強制プッシュ実行
$ git pushf
```
:::

:::note info
リベース後に強制プッシュが必要になり、その場合は他の人がリベース前のブランチをローカルで持っていた場合はそれと衝突して履歴の不整合が発生します。

レビュワーが再度rebaseされた内容を取り込みたい場合は次のコマンドで同期すると良いです。

```shell-session
$ git fetch
$ git reset --hard origin/topic-123
```
:::

### GitHub上でマージする

GitHub上で作業者自身(レビューイ)が `Create a merge commit` を選択し `Merge pull request` を押してマージします。
(レビュワーがマージするとコミット整理中にマージされてしまう場合がある)

![ScreenShot 2022-10-03 17.04.03.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/52879/30769e60-d39a-49e6-073b-0b60b8e6a022.png)

その際にリモートブランチも削除するオプションを指定してください。

:::note info
マージの種類についての補足

- Create a merge commit
  - マージコミットが残ります。
  - 作業ブランチのコミットはそのまま残ります。
  - プルリクエスト単位でリバートしやすい。
- Squash and merge
  - マージコミットが残ります。
  - 作業ブランチのコミットが1つにまとめられます。
  - まとめる前のコミットには戻せない。
  - プルリクエスト単位でリバートしやすい。
- Rebase and merge
  - マージコミットは作成されず、履歴が一本化されます。
  - 作業ブランチのコミットはそのまま残ります。
  - プルリクエスト単位でリバートしたい場合に困る。

コミット自体はリベースで整理されていることと、何かあったときにプルリクエスト単位でリバートしたいので、個人的には `Create a merge commit` が推しです。

誤って別のマージを選択してしまうのを防止するため、使用しないマージボタンを無効化できます。
https://qiita.com/ucan-lab/items/d594404d0d2c64a85a38#settings--options--merge-button
:::

### ローカルのブランチを削除する

```shell-session
$ git fetch -p
```

`-p` を付けると削除されたリモートブランチも反映されます。

```shell-session
$ git branch -D topic-123
```

ローカルのブランチを削除する。

マージ済みブランチを再度利用することはないので削除します。
古いブランチが溜まると単純に見辛かったり、git fetchが遅くなる場合があります。
また、古いブランチを残して再利用すると最新に追いつくためのマージやコンフリクト解消がコストになります。

---

## 補足: 最新の main ブランチに切り替える

```shell-session
$ git fetch
$ git switch -d origin/main
```

ローカルの main ブランチは削除しているので、 `-d` のdetached HEAD状態で切り替えます。

## 補足: git fetch -p オプションを常に有効化する

```shell-session
$ git config --global fetch.prune true
```

この設定をしておくと以降は `-p` オプションを付ける必要はありません。

```shell-session
$ git fetch
```

## 補足: --update-refs オプション

Git 2.38.0 以降は `--update-refs` オプションを使用できます。

https://github.blog/2022-10-03-highlights-from-git-2-38/

:::note info

```shell-session
$ git rebase --update-refs -i origin/main
```

とすると標準モードで取り込み、対話モードでコミットの整理が行えます。

```shell-session
$ git rebase origin/main
$ git rebase -i origin/main
```

一つのコマンドでできるようになったのでとても便利です。

:::

毎回オプションを指定するのは面倒なのでグローバル設定しておくと良いです。

```shell-session
$ git config --global rebase.updateRefs true
```

以降は対話モードのリベースのみで標準モードの取り込みも合わせて行ってくれます。

```shell-session
$ git rebase -i origin/main
```

## 使わないコマンド

### 使用しないコマンド: git checkout

Git 2.23.0から `git checkout` は `git switch` と `git resotre` に機能が分割されました。

https://kakakakakku.hatenablog.com/entry/2020/04/08/151627

### 使用しないコマンド: git pull

`git fetch` と `git rebase` で事足りるので、`git pull` は使用しません。

http://dqn.sakusakutto.jp/2012/11/git_pull.html

### 使用しないコマンド: git merge

https://zenn.dev/yumiyoshi/scraps/37723d020d4541

`git rebase`を使うか、`git merge`を使うかはプロジェクトの運用ルールによると思います。
コミットログが綺麗になるので個人的には `git rebase` がオススメです。

## 関連記事

https://qiita.com/ucan-lab/items/f824580cbf995495de00

https://qiita.com/ucan-lab/items/aadbedcacbc2ac86a2b3

https://qiita.com/ucan-lab/items/e02f2d3a35f266631f24

https://qiita.com/ucan-lab/items/d594404d0d2c64a85a38