Eclipse Collections にコントリビュートしたときにやった Git(GitHub) の操作

縁あって [Eclipse Collections](https://github.com/eclipse/eclipse-collections) にコントリビュートすることができた。

Git は個人的な趣味プログラミングでしか使っておらず、 Pull Request を考慮した Git 操作というものについては全く知識も経験もなかった。

今回 Eclipse Collections に Pull Request を出すにあたって、様々な初めての Git 操作を経験することができ大変勉強になったので、忘れないようにメモする。


# Eclipse Collections にコントリビュートするときのルール
適当に Pull Request を送れば良いわけではなく、いくつかルールがある。
具体的な話は [eclipse-collections/CONTRIBUTING.md at master · eclipse/eclipse-collections](https://github.com/eclipse/eclipse-collections/blob/master/CONTRIBUTING.md) に書いてある（英語）。

ざっと、以下のような感じ。
（2016年7月現在のものなので、今後変更される可能性があるかもしれません。実際にコントリビュートする際は、本家の説明を必ず読んでください）

1. Eclipse コミュニティのアカウント作成
    - [Eclipse Foundation Contributor License Agreement](https://www.eclipse.org/legal/CLA.php) に同意する。
1. ソースの修正
    - 修正は、 GitHub 上で[Eclipse Collections のリポジトリ](https://github.com/eclipse/eclipse-collections) を fork して行う。
    - コーディングスタイルのチェックは、 CheckStyle と FindBugs で行うので、 Pull Request を送る前に実行して問題が無いか確認する。
        - Travis CI と連携しているので、それを利用するのも OK。
        - というか、 Windows で動かそうとすると改行コードの設定に問題があるのか CheckStyle でエラーになるので、途中からは Travis CI でチェックするようにしていた。
        - 単体テストは Windows 上でも動くので、 IntelliJ 上で動かしていた。
1. コミット
    - コミットメッセージは命令形にする（`Fixed bug` より `Fix bug`）。
    - GitHub の issue と関連するコミットの場合は、 issue へのメンションを入れる（`#18` とか）
    - Eclipse コミュニティに登録したものと同じメールアドレスで署名（`--signoff`）する。
1. リリースドラフトの修正
    - 自身が修正した内容を [RELEASE_NOTE_DRAFT.md](https://github.com/eclipse/eclipse-collections/blob/master/RELEASE_NOTE_DRAFT.md) に追記する。
1. Pull Request のコミット
    - コミットは１つにまとめ、本家 `master` の最新の上に `rebase` してから送る。

# Git のコミットログの矢印が持つ意味
![commit-log.jpg](https://qiita-image-store.s3.amazonaws.com/0/28302/65e2b745-daf6-8d46-93bc-ce795704162f.jpeg)

よく Git のコミットログを説明するために上のような図が描かれることがある。

最初、この図を見た時「なぜ矢印の向きがコミットの順序（時間軸）と逆向きなのだろう？」と疑問に思っていた。
矢印といったら時間順序を表しているイメージがあり、それに逆行していることに違和感があった。

しかし、 Git のコミットが持つ特徴を知ることで、その意味が理解できるようになった。

Git のコミットには更新者やファイルのスナップショットの情報と共に、**「１つ前のコミット」**の情報が保持されている。
「１つ前のコミット」は、そのコミットを識別する属性の１つであり、これが異なるコミットは Git 上では**別のコミット**として認識される。

矢印は、この「１つ前のコミット」を表している。
矢印で「１つ前のコミット」を表現することで、変更内容が同じでも「１つ前のコミット」が異なればそれらのコミットは別物であることが意識しやすくなる。
また、もし `rebase` で「１つ前のコミット」を変更した場合も「コミットが別物に変化した」ということを意識しやすくなる。

**参考**

[こわくない Git | SlideShare](http://www.slideshare.net/kotas/git-15276118)

# 今思えば（振り返り）
以下に記載している操作は実際に試行錯誤しながら実施した Git 操作なので、今思えば他の方法があったのではないかなぁと思っている。

例えば、 Pull Request は `master` から出すのではなく、 `topic` ブランチから出しておけばもう少し楽だったかもしれない。
`master` は常に本家の状態を追従するようにしておけば、 [reset --hard などの作業](#reset_hard) も不要だったのかなぁと思っている。


# Git 操作
以下、実際にやった Git 操作。

## 本家リポジトリを fork する
![eclipse_collections_pull_request_1.jpg](https://qiita-image-store.s3.amazonaws.com/0/28302/750da390-492b-7f84-a90c-3eb039a80ead.jpeg)

![fork](https://qiita-image-store.s3.amazonaws.com/0/28302/151b4bf8-4809-9931-c626-999050d20d1c.jpeg)

- これは簡単。
- GitHub 上で「Fork」ボタンを押して自分のアカウントを fork 先に指定すればいい。

## fork したリモートリポジトリをローカルに clone する

![eclipse_collections_pull_request_2.jpg](https://qiita-image-store.s3.amazonaws.com/0/28302/3842601a-4406-7ae2-1a7c-884513f739ce.jpeg)

```bash:コマンド
$ git clone <url>
```

- これも簡単な話。

## ローカルで開発する
![eclipse_collections_pull_request_3.jpg](https://qiita-image-store.s3.amazonaws.com/0/28302/0aaa51b3-8375-8a7c-175d-2e9a9fe7b832.jpeg)

```bash:コマンド
# 作業用のブランチを作成し、そちらへ移動
$ git checkout -b topic

# 署名付きでコミット
$ git commit --signoff -m "<コミットメッセージ>"
```

- ローカルに `clone` したリポジトリで開発作業を行う。
- `master` から作業用のブランチ（トピックブランチ）を作成、そちらでコミットを行う。
- コミットには署名を付けなければならないルールなので、 `--signoff` オプションを付けてコミットする。
    - `git config --list` で表示される設定の中の `user.email` で設定されているメールアドレスが署名に使用される。
    - このメールアドレスは、 Eclipse コミュニティに登録しておいたメールアドレスと同じものにしておくこと。
- ローカルで開発している間も、本家の master ブランチにはどんどんコミットが追加されていっている。

## <span id="rebase_i">コミットを１つにまとめる</span>
![eclipse_collections_pull_request_4.jpg](https://qiita-image-store.s3.amazonaws.com/0/28302/e9a2f8d3-c910-da90-dd1a-c0b878091d31.jpeg)

```bash:コマンド
# 過去４件のログを確認
$ git log --oneline --decorate --graph -4
* 12ebd5e (HEAD, topic) c
* 1ba2503 b
* 50a8717 a
* 280720d (master) 1

# rebase -i でコミットをまとめる
$ git rebase -i HEAD~3

# rebase 後のログを確認
$ git log --oneline --decorate --graph -2
* 8a1a17c (HEAD, topic) a + b + c
* 280720d (master) 1
```

- Pull Request 時にはコミットを１つにまとめるルールなので、ローカルで行ったコミットを１つにまとめる。
- `git rebase -i` を使用すると、複数のコミットを１つにまとめることができる。
- `HEAD~3` の部分は、 `HEAD` （現在の作業ブランチの最新）から 3 つ前のコミットを対象にする、という意味になる。

### rebase -i 中に立ち上がるエディタ
```text:修正前
pick 50a8717 a
pick 1ba2503 b
pick 12ebd5e c

（以下略）
```

- `rebase -i` を実行すると、指定した範囲のコミットが古いもの順に表示されたエディタが開く。
- 先頭の `pick` の部分を変更することで、そのコミットをどう変更するかを指定できる。
- `s` または `squash` と記述すると、そのコミットは直上のコミットに統合される。
- 今回はコミット a に全てのコミットを統合したいので、以下のように書き換える。

```text:修正後
pick 50a8717 a
s 1ba2503 b
s 12ebd5e c

（以下略）
```

- 保存してからエディタを終了させると、次にコミットコメントを修正するエディタが開く。

```text:修正前（コミットコメント）
# This is a combination of 3 commits.
# The first commit's message is:
a

# This is the 2nd commit message:

b

# This is the 3rd commit message:

c
（以下略）
```

- 統合対象のコミットのコミットメッセージが表示されるので、統合後のコミットメッセージに整形する。
- `#` で始まる行はコメントとなり無視される。
- また、空行も無視される。

```text:修正後（コミットコメント）
# This is a combination of 3 commits.
a + b + c

（以下略）
```

- 修正が完了したら、保存してからエディタを終了させる。

## 本家 master の最新を取り込む
![eclipse_collections_pull_request_5.jpg](https://qiita-image-store.s3.amazonaws.com/0/28302/c55cf0e9-778a-6afc-66d7-10fc15d97670.jpeg)

```bash:コマンド
# 本家のリポジトリをリモートに追加する
$ git remote add eclipse git@github.com:eclipse/eclipse-collections.git

# リモートが追加されたことを確認する
$ git remote -v
eclipse git@github.com:eclipse/eclipse-collections.git (fetch)
eclipse git@github.com:eclipse/eclipse-collections.git (push)
origin  git@github.com:opengl-8080/eclipse-collections.git (fetch)
origin  git@github.com:opengl-8080/eclipse-collections.git (push)

# リモート追跡ブランチに最新の情報を取り込む
$ git fetch eclipse master

# 情報が取り込まれたことを確認する
$ git branch -av
  master                    280720d [ahead 2] 1
* topic                     8a1a17c a + b + c
  remotes/eclipse/master    1f699b5 4
（略）

# ローカルの master に eclipse/master を merge して一致させる
$ git checkout master

$ git merge eclipse/master

# マージ後のログを確認
$ git log --oneline --decorate --graph --branches -5
* 1f699b5 (HEAD, eclipse/master, master) 4
* b958a4b 3
* 707e739 2
| * 8a1a17c (topic) a + b + c
|/
* 280720d 1
```

- 本家の `master` を取得するため、 `eclipse` という名前でリモートを追加する。
- `fetch` コマンドでリモートの情報をローカルの[リモート追跡ブランチ](http://qiita.com/uasi/items/69368c17c79e99aaddbf) (`eclipse/master`)に落としてくる。
- `git checkout` で一旦 `master` ブランチに移動してから、 `merge` コマンドで `eclipse/master` ブランチの内容をマージする。
- これで、ローカルの `master` ブランチは本家の `master` と同じ状態になる。

## <span id="rebase_merge">rebase でマージする</span>
![eclipse_collections_pull_request_6.jpg](https://qiita-image-store.s3.amazonaws.com/0/28302/cf164733-d756-4880-6ad9-656cf7d58f03.jpeg)

```bash:コマンド
# rebase 前のログを確認
$ git log --oneline --decorate --graph --branches -5
* 1f699b5 (HEAD, eclipse/master, master) 4
* b958a4b 3
* 707e739 2
| * 8a1a17c (topic) a + b + c
|/
* 280720d 1

# rebase で topic の変更を master の先頭に持ってくる
$ git checkout topic

$ git rebase master

# rebase 後のログを確認
$ git log --oneline --decorate --graph --branches -5
* 62cce6a (HEAD, topic) a + b + c
* 1f699b5 (eclipse/master, master) 4
* b958a4b 3
* 707e739 2
* 280720d 1

# merge コマンドで master に topic をマージする
$ git checkout master

$ git merge topic

# マージ後のログを確認
$ git log --oneline --decorate --graph --branches -5
* 62cce6a (HEAD, topic, master) a + b + c
* 1f699b5 (eclipse/master) 4
* b958a4b 3
* 707e739 2
* 280720d 1
```

- 最新化した `master` ブランチにローカルでの変更をマージする。
- Eclipse Collections では、 `rebase` によるマージがルールとなっている。
- `rebase` とは、あるコミットのベース（１つ前のコミット）を別のコミットに切り替える処理のことで、今回は `topic` のコミットを `master` の最新の後ろに切り替える処理を行う。

### rebase 時にコンフリクトした場合
- `rebase` 時にコンフリクトする可能性は十分ある。
- `rebase` 時にコンフリクトが発生すると、 git は `rebase` 処理を中断してコンフリクトの解消を促してくる。

```bash:コンフリクトした際のメッセージ
$ git rebase master
First, rewinding head to replay your work on top of it...
（略）
CONFLICT (content): Merge conflict in text.md
（略）

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".
```

- コンフリクトしたファイルを修正したら、そのファイルを `git add` でステージングに上げてから `git rebase --continue` を実行する。
- これで、 git は中断していた `rebase` 処理を再開する。

### rebase しないで merge した場合
![eclipse_collections_pull_request_6.5.jpg](https://qiita-image-store.s3.amazonaws.com/0/28302/d8fdc202-c74b-eebc-e895-44534a22766b.jpeg)

- `rebase` せずに `master` に `topic` ブランチをマージした場合、コミットは上図のようになる。
- マージしたことを表すコミット（マージコミット）が作成され、ログ上はブランチが分岐したという情報が残ることになる。
    - ただし [Fast-Forward Merge](http://www.backlog.jp/git-guide/stepup/stepup1_4.html) が可能な場合はマージコミットは作成されない。
- `rebase` した場合は Fast-Forward Merge になるので、マージコミットは作成されなくなる。

#### rebase して Fast-Forward Merge した場合のメリット・デメリット
- メリット
    - 履歴が一直線になるため、コミットログが見やすくなる。
- デメリット
    - ブランチを切ったときの情報は失われる。

#### merge で Non Fast-Forward Merge した場合のメリット・デメリット
- メリット
    - コマンドが単純。
    - ブランチを切ったときの情報は残る。
- デメリット
    - たくさんブランチを切っている場合は、履歴が複雑になる可能性がある。

## リモートに push してから Pull Request を送る
![eclipse_collections_pull_request_7.jpg](https://qiita-image-store.s3.amazonaws.com/0/28302/483f8653-4234-428a-f9ca-60cba9f12753.jpeg)

```bash:コマンド
git push origin master
```

![create_pull_request.jpg](https://qiita-image-store.s3.amazonaws.com/0/28302/31808539-0188-a4d5-55e9-1d01829465f6.jpeg)

- ローカルでのマージが完了したら、それを GitHub の自分のリポジトリに `push` する。
- `push` が完了したら、画面上で「New pull request」ボタンを押して Pull Request を作成する。
- あとは、中の人にレビューして頂くのを座して待つ。

## Pull Request の結果さらなる修正が必要になった
![eclipse_collections_pull_request_8.jpg](https://qiita-image-store.s3.amazonaws.com/0/28302/bded8725-d3b3-1c35-e48d-bbd031a4e954.jpeg)

- 残念ながら？初回の Pull Request は一発 OK を貰えなかった。
- 指摘を受けた問題をローカルで修正するため、`topic` ブランチで修正作業を再開させた。

## ローカルで新規に作成したコミットを Pull Request 用のコミットに統合する
![eclipse_collections_pull_request_9.jpg](https://qiita-image-store.s3.amazonaws.com/0/28302/551d71b5-1be7-3b51-28a1-15ad0a9a0920.jpeg)

```bash:コマンド
# rebase 前のログ確認
$ git log --oneline --decorate --graph --branches -7
* d446040 (HEAD, topic) e
* 991821f d
* 62cce6a (master) a + b + c
* 1f699b5 (eclipse/master) 4
* b958a4b 3
* 707e739 2
* 280720d 1

# rebase -i でコミットを統合
$ git rebase -i HEAD~3

# rebase 後のログ確認
$ git log --oneline --decorate --graph --branches -6
* cb077a7 (HEAD, topic) a + b + c + d + e
| * 62cce6a (master) a + b + c
|/
* 1f699b5 (eclipse/master) 4
* b958a4b 3
* 707e739 2
* 280720d 1
```

- Pull Request のコミットは１つにまとめるので、ローカルで追加したコミット `d` と `e` を、 `a + b + c` のコミットに統合する。
- コミットの統合は、[前述](#rebase_i) した `rebase -i` を使用する。

## <span id="reset_hard">再び本家 master の最新を取り込む</span>
![eclipse_collections_pull_request_10.jpg](https://qiita-image-store.s3.amazonaws.com/0/28302/20a4dd73-984b-dafc-cb9f-98471697e443.jpeg)

```bash:コマンド
# 本家 master の情報を取得する
$ git fetch eclipse master

# 本家の最新が取得できたことを確認
$ git branch -av
  master                    62cce6a [ahead 6] a + b + c
* topic                     cb077a7 a + b + c + d + e
  remotes/eclipse/master    cacd681 6

# ローカルの master を eclipse/master で上書きする
$ git checkout master

$ git reset --hard eclipse/master

# 上書き後のログを確認
$ git log --oneline --decorate --graph --branches -7
* cacd681 (HEAD, eclipse/master, master) 6
* 5082497 5
| * cb077a7 (topic) a + b + c + d + e
|/
* 1f699b5 4
* b958a4b 3
* 707e739 2
* 280720d 1
```

- Pull Request を修正している間も、本家の `master` には次々とコミットが追加されている。
- これらを再びローカルに取り込んでマージしなければならない。
- 本家からの情報取得は `fetch` コマンドで実行する。
- ローカルの `master` を本家 `master` と同じ状態にするには、 `reset --hard` を使用する。
    - ※このときローカルの `master` の情報は消えてしまうので、消えたら困るコミットがある場合は他の方法を検討する必要がある。

## 再び rebase でマージする
![eclipse_collections_pull_request_11.jpg](https://qiita-image-store.s3.amazonaws.com/0/28302/da773faa-ec21-2f26-0187-cef8455640c0.jpeg)

```bash:コマンド
# rebase 前のログ確認
$ git log --oneline --decorate --graph --branches -7
* cacd681 (HEAD, eclipse/master, master) 6
* 5082497 5
| * cb077a7 (topic) a + b + c + d + e
|/
* 1f699b5 4
* b958a4b 3
* 707e739 2
* 280720d 1

# rebase で topic を master の先頭に持ってくる
$ git checkout topic

$ git rebase master

# merge で master の先頭に topic の内容をマージ
$ git checkout master

$ git merge topic

# マージ後のログを確認
$ git log --oneline --decorate --graph --branches -7
* d6243de (HEAD, topic, master) a + b + c + d + e
* cacd681 (eclipse/master) 6
* 5082497 5
* 1f699b5 4
* b958a4b 3
* 707e739 2
* 280720d 1
```

- ここでの作業内容は[rebase でマージ](#rebase_merge)したときのものと同じになる。

## 修正内容をリモートに push する
![eclipse_collections_pull_request_12.jpg](https://qiita-image-store.s3.amazonaws.com/0/28302/c10d62ad-77a7-26ca-24d5-27a1842ff1db.jpeg)

```bash:コマンド
$ git checkout master

$ git push -f origin master
```

- 修正＋本家最新のマージが完了したので、修正したコミットをリモートに `push` する。
- しかし、そのまま `push` しようとしてもエラーになってしまう。
    - ローカルの `master` は本家の最新をもとに作りなおしているため、リモートの `master` とは全然違う履歴になってしまっているため。
- リモートに存在する前回の Pull Request のコミットはもう不要なので、今回 `push` し直す情報で上書きしても問題ない。
- なので、 `push` コマンドに `-f` オプションをつけることでリモートの情報を `push` した情報で強制的に上書きしている。
- GitHub 上の Pull Request の表示も、 `-f` 付きで `push` することで新しいコミットの内容で更新されるようになっている。
- もし Pull Request のスレッド上でコードにコメントを付けていたとしても、そのコメントが失われることはない。

# おわりに
[伊藤博志さん](https://github.com/itohro)
今回 Eclipse Collections にコントリビュートするという素晴らしい機会をくださり、ありがとうございました。
GS 社の方々との英語でのやりとりをサポート頂いたり、開発方法についての様々なご質問にも丁寧にご回答頂き、とても助かりました。

[Craig P. Motlin さん](https://github.com/motlin)、 [Donald Raab さん](https://github.com/donraab)
私の拙い英語にも親切に対応頂き、ありがとうございました。
私の書いた英語がネイティブの方に通じていると感じた時は、とても感動しました。

# 参考
- [【git】分かりやすく！mergeは「合流」、rebaseは「付け替え」! | NullNote](http://nullnote.com/web/git/merge_rebase/)
- [ブランチの統合【ブランチ】 | サルでもわかるGit入門 〜バージョン管理を使いこなそう〜 | どこでもプロジェクト管理バックログ](http://www.backlog.jp/git-guide/stepup/stepup1_4.html)
- [Git で「追跡ブランチ」って言うのやめましょう - Qiita](http://qiita.com/uasi/items/69368c17c79e99aaddbf)
- [こわくない Git | SlideShare](http://www.slideshare.net/kotas/git-15276118)