Help us understand the problem. What is going on with this article?

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

縁あって Eclipse Collections にコントリビュートすることができた。

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

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

Eclipse Collections にコントリビュートするときのルール

適当に Pull Request を送れば良いわけではなく、いくつかルールがある。
具体的な話は eclipse-collections/CONTRIBUTING.md at master · eclipse/eclipse-collections に書いてある(英語)。

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

  1. Eclipse コミュニティのアカウント作成
  2. ソースの修正
    • 修正は、 GitHub 上でEclipse Collections のリポジトリ を fork して行う。
    • コーディングスタイルのチェックは、 CheckStyle と FindBugs で行うので、 Pull Request を送る前に実行して問題が無いか確認する。
      • Travis CI と連携しているので、それを利用するのも OK。
      • というか、 Windows で動かそうとすると改行コードの設定に問題があるのか CheckStyle でエラーになるので、途中からは Travis CI でチェックするようにしていた。
      • 単体テストは Windows 上でも動くので、 IntelliJ 上で動かしていた。
  3. コミット
    • コミットメッセージは命令形にする(Fixed bug より Fix bug)。
    • GitHub の issue と関連するコミットの場合は、 issue へのメンションを入れる(#18 とか)
    • Eclipse コミュニティに登録したものと同じメールアドレスで署名(--signoff)する。
  4. リリースドラフトの修正
  5. Pull Request のコミット
    • コミットは1つにまとめ、本家 master の最新の上に rebase してから送る。

Git のコミットログの矢印が持つ意味

commit-log.jpg

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

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

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

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

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

参考

こわくない Git | SlideShare

今思えば(振り返り)

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

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

Git 操作

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

本家リポジトリを fork する

eclipse_collections_pull_request_1.jpg

fork

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

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

eclipse_collections_pull_request_2.jpg

コマンド
$ git clone <url>
  • これも簡単な話。

ローカルで開発する

eclipse_collections_pull_request_3.jpg

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

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

コミットを1つにまとめる

eclipse_collections_pull_request_4.jpg

コマンド
# 過去4件のログを確認
$ 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 時にはコミットを1つにまとめるルールなので、ローカルで行ったコミットを1つにまとめる。
  • git rebase -i を使用すると、複数のコミットを1つにまとめることができる。
  • HEAD~3 の部分は、 HEAD (現在の作業ブランチの最新)から 3 つ前のコミットを対象にする、という意味になる。

rebase -i 中に立ち上がるエディタ

修正前
pick 50a8717 a
pick 1ba2503 b
pick 12ebd5e c

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

(以下略)
  • 保存してからエディタを終了させると、次にコミットコメントを修正するエディタが開く。
修正前(コミットコメント)
# 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
(以下略)
  • 統合対象のコミットのコミットメッセージが表示されるので、統合後のコミットメッセージに整形する。
  • # で始まる行はコメントとなり無視される。
  • また、空行も無視される。
修正後(コミットコメント)
# This is a combination of 3 commits.
a + b + c

(以下略)
  • 修正が完了したら、保存してからエディタを終了させる。

本家 master の最新を取り込む

eclipse_collections_pull_request_5.jpg

コマンド
# 本家のリポジトリをリモートに追加する
$ 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 コマンドでリモートの情報をローカルのリモート追跡ブランチ (eclipse/master)に落としてくる。
  • git checkout で一旦 master ブランチに移動してから、 merge コマンドで eclipse/master ブランチの内容をマージする。
  • これで、ローカルの master ブランチは本家の master と同じ状態になる。

rebase でマージする

eclipse_collections_pull_request_6.jpg

コマンド
# 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 とは、あるコミットのベース(1つ前のコミット)を別のコミットに切り替える処理のことで、今回は topic のコミットを master の最新の後ろに切り替える処理を行う。

rebase 時にコンフリクトした場合

  • rebase 時にコンフリクトする可能性は十分ある。
  • rebase 時にコンフリクトが発生すると、 git は rebase 処理を中断してコンフリクトの解消を促してくる。
コンフリクトした際のメッセージ
$ 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

  • rebase せずに mastertopic ブランチをマージした場合、コミットは上図のようになる。
  • マージしたことを表すコミット(マージコミット)が作成され、ログ上はブランチが分岐したという情報が残ることになる。
    • ただし Fast-Forward Merge が可能な場合はマージコミットは作成されない。
  • rebase した場合は Fast-Forward Merge になるので、マージコミットは作成されなくなる。

rebase して Fast-Forward Merge した場合のメリット・デメリット

  • メリット
    • 履歴が一直線になるため、コミットログが見やすくなる。
  • デメリット
    • ブランチを切ったときの情報は失われる。

merge で Non Fast-Forward Merge した場合のメリット・デメリット

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

リモートに push してから Pull Request を送る

eclipse_collections_pull_request_7.jpg

コマンド
git push origin master

create_pull_request.jpg

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

Pull Request の結果さらなる修正が必要になった

eclipse_collections_pull_request_8.jpg

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

ローカルで新規に作成したコミットを Pull Request 用のコミットに統合する

eclipse_collections_pull_request_9.jpg

コマンド
# 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 のコミットは1つにまとめるので、ローカルで追加したコミット de を、 a + b + c のコミットに統合する。
  • コミットの統合は、前述 した rebase -i を使用する。

再び本家 master の最新を取り込む

eclipse_collections_pull_request_10.jpg

コマンド
# 本家 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

コマンド
# 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

修正内容をリモートに push する

eclipse_collections_pull_request_12.jpg

コマンド
$ 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 のスレッド上でコードにコメントを付けていたとしても、そのコメントが失われることはない。

おわりに

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

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

参考

opengl-8080
ただのSE。Java好き。
tis
創業40年超のSIerです。
https://www.tis.co.jp/
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした