github運用: 別々のブランチに cherry-pick / merge した あと merge してみる

# 動機
git戦略でよく見かける、main, develop, hotfixというブランチがあります。
その運用について特に異論はないのですが、hotfixでの作業をdevelopブランチへ反映する方法を変えてみた場合、versionアップリリース時 main に develop の変更を反映する際どのような挙動になるのか気になったため、簡単に作業してみようと思います。
主に、cherry-pickでコミットハッシュが別となる場合の挙動を調べるモチベーションです。

<前提>
* mainから3つほど作業が進んでいるdevelopブランチがある
    * → ver1.0としてmainがリリースされ、developで時期リリースに向けた開発を進めている想定
* リリース後、早急に対応したいバグが発見されたので、main ブランチ から hotfixブランチを切ってバグ修正作業をする
* 作業後、hotfixの作業をmain, developに反映する ← このdevelop反映で merge or cherry-pickを選択する


<テスト内容>
1. hotfixをdevelopにもmergeする
1. developから、hotfixの変更をcherry-pickする


# 作業開始
## ① main, develop双方にmergeする場合
1.まずはブランチを用意、初期コミットをします。
今回はindex.htmlを作成するのみです。コードは以下だけ。
```html:index.html
<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  <h2>first commit</h2>
</body>
</html>
```

ブラウザでは画像のように見えています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2704095/b73ec32f-12b1-1ce1-6000-e0d363b7be8c.png)

2.developブランチを作成、作業をしていきます
コードはこんな感じ
```diff_html:index.html
<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  <h2>first commit</h2>
+ <h3>second commit</h3>
+ <h4>third commit</h4>
</body>
</html>
```

履歴は画像の通り
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2704095/c7e295c6-7860-ba3f-43de-7fe50ca7d701.png)

3.hotfixを切る
mainで早急に対応したいことがあり、hotfixで作業します。
```diff_html:index.html
<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  <h2>first commit</h2>
+ <p>hotfix commit</p>
</body>
</html>
```

さて、これで準備ができました。
現在の状態としては、
main: first commit
dev : first commit -> **second commit -> third commit**
hot : first commit -> **hotfix commit**
この太字部分で差異が発生しています。
よって、hotfix commit を main および developに反映してみます。

まずはmainにマージします。特に問題ないです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2704095/ddc8a8a0-f7fd-d4cb-0bfa-dc55fa4df017.png)

developにマージしようとしますが、やはりコンフリクト発生します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2704095/9f1be485-95d7-cba0-fab7-1048b01581df.png)


エディタで直します。
```html:index.html
<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  <h2>first commit</h2>
  <!-- 両方の変更を取り込みました -->
  <p>hotfix commit</p>
  <h3>second commit</h3>
  <h4>third commit</h4>
</body>
</html>
```

コミットしてリモートのdevelopにpushします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2704095/44c89554-9cd4-83b5-bd98-6d68bb7b6554.png)

これで、マージが完了しました。マージの場合、hotfix commitのコミットハッシュはdevelop / mainで同じことがわかります。
↓main(再掲)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2704095/f3be4a77-873a-6cb0-8947-654094971ada.png)

では、developブランチからmainに対してプルリクエストを作成してみます。
少しわかりにくいですが、**コミットハッシュが同じhotfix commitについて差分は出てきません。**
githubがその辺をよしなに解決してくれています。**コンフリクトも起きません。**

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2704095/41d9fb16-e6ed-1dd9-9e21-a1dce0d35949.png)

マージを完了すると、以下のような履歴になりました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2704095/4306c9d2-6cf6-928d-403c-b18e5afb4a79.png)

## cherry-pickをする
さて、次はhotfix -> develop への反映を、developからcherry-pickするという流れで行ってみましょう。先ほどのhotfix作成、mainへマージまでの流れは同じです。

mainにはhotfixをマージしており、developでは別の変更が進んでいます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2704095/4ebd1a0b-fffe-136b-465a-47f5642f00bd.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2704095/12633e00-a22b-29b7-66f2-e14d20a49b5c.png)

では、hotfix commit の コミットハッシュをcherry-pickしてみましょう。

```sh
(develop)> git cherry-pick ff8085b5e09982eba78b2747f0e2dc8f4c7627e9
```

先ほどと同様、コンフリクトが発生しました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2704095/5f6cdb97-f02d-2934-cfbe-134474af8de9.png)

両方の変更を保持したいので、今回も組み合わせを受け入れます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2704095/2ef94634-b0ff-78dc-fd0b-2e3aaa034363.png)

すると、以下のような履歴になります。
hotfix commit というコミットはなさそうです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2704095/9e166281-09d3-c4f2-4283-10f0523306a7.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2704095/e416b061-5369-052d-8f1c-7666db2de356.png)

マージした時は以下のようになっていました。
cherry-pick時と比べてみると、mergeコミットは多いですが、hotfix commitはhotfix commitのまま履歴に残っていました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2704095/44c89554-9cd4-83b5-bd98-6d68bb7b6554.png)

中身はこんな感じです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2704095/986e49a5-c7de-d602-6498-2d0787f3c1c9.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2704095/3bb94d75-9813-0aa0-b85c-c3b895efd32c.png)

どうやら、cherry-pickをすると新しいコミットとして履歴に追加され、mergeをするとそのままのコミット + merge コミット という履歴になりそうです。

さて、ここでdevelopからmainに向けたプルリクエストを作成してみましょう。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2704095/a1887d0c-1a70-c67c-1745-e5c51c7a0583.png)

main, develop双方にマージした場合、特にコンフリクトは起きませんでしたが、**今回はコンフリクトが起きてしまっています。**
エディタで確認してみましょう。
```sh
(main)> git merge develop
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2704095/d15cd136-bf44-7e71-0e28-b9c21c45ab41.png)

やはりコンフリクトが起きています。
とりあえず変更を取り込み、mainにpushしてしまいます。
最終的な履歴は以下のようになりました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2704095/d53873aa-3f8d-d4f2-0120-0b45cd65f6c6.png)

mergeした方(再掲)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2704095/82a9fe02-2132-930b-bd39-aae85ee4f8fa.png)

正直あまり履歴に大きな差はなさそうです。変更が簡単だったこともあると思いますが。

ただ、cherry-pickを利用すると同じ変更内容なのに違うコミットハッシュで履歴に残ります。
その結果、mainとdevelopではほぼ確実にコンフリクトが発生することになり、次回リリース時にその解決をすることになりそうです。
特に問題なければいいのですが、リリース時にコンフリクト解決などデリケートな作業は避けたい気持ちがあり、個人的には特に理由がない限りmain, develop双方にmergeで良いのではと思いました。
同じコンフリクト解消作業を2度行っている気もしますし。
※間違った認識であればコメントください！自信はありません。。

また、今回はmainにdevelopを反映する際マージをしましたが、rebaseする場合でもまた挙動が変わってきそうです。（developをmainにリベースする感じでしょうか？）

その場合、hotfixの上にdevelopの変更が乗っかるのかなと思いますが、今度試してみようと思います。（が、force pushをすることにはなるので、develop全体のコミットハッシュが変わるの怖いかもですね。。）

# 最後に

というわけで、雑ですがgit branchの管理について実験してみました。
安全なgit操作を身につけ、自身を持って作業できるようになりたいものです。
何かの参考になれば幸いです。