章 ▾ 第2版

5.2 分散Git - プロジェクトへの貢献

プロジェクトへの貢献

プロジェクトに貢献する方法を説明する上での主な困難は、その方法が多数存在することです。Gitは非常に柔軟であるため、人々はさまざまな方法で協力でき、どのように貢献すべきかを記述するのは困難です — 各プロジェクトは少しずつ異なります。関連する変数には、アクティブな貢献者の数、選択されたワークフロー、コミットアクセス、そして外部からの貢献方法などが含まれます。

最初の変数は、アクティブな貢献者の数です。このプロジェクトに積極的にコードを貢献しているユーザーが何人いて、どれくらいの頻度で貢献しているか?多くの場合、1日に数回のコミットを行う開発者が2〜3人、あるいは多少休止状態のプロジェクトではそれ以下ということもあります。より大きな企業やプロジェクトでは、開発者の数が数千人に上り、毎日数百または数千のコミットが発生することもあります。これは、開発者が増えるにつれて、コードがクリーンに適用されるか、簡単にマージできることを確認する上での問題が増えるため重要です。あなたが作業している間、または変更が承認または適用されるのを待っている間にマージされた作業によって、提出した変更が古くなったり、ひどく壊れたりする可能性があります。コードを常に最新の状態に保ち、コミットを有効に保つにはどうすればよいでしょうか?

次の変数は、プロジェクトで使用されているワークフローです。各開発者がメインコードラインへの書き込みアクセス権を平等に持つ、集中型ですか?プロジェクトには、すべてのパッチをチェックするメンテナまたは統合マネージャーがいますか?すべてのパッチはピアレビューされ、承認されていますか?あなたはそのプロセスに関与していますか?中尉システムが導入されており、まず彼らに作業を提出する必要がありますか?

次の変数は、あなたのコミットアクセスです。プロジェクトへの書き込みアクセス権がある場合とない場合では、プロジェクトに貢献するために必要なワークフローは大きく異なります。書き込みアクセス権がない場合、プロジェクトは貢献された作業をどのように受け入れることを好みますか?そもそもポリシーがありますか?一度にどれくらいの作業を貢献しますか?どれくらいの頻度で貢献しますか?

これらすべての質問は、あなたがプロジェクトに効果的に貢献する方法、そしてどのようなワークフローが推奨されるか、または利用可能であるかに影響を与えます。これらの各側面を、単純なものから複雑なものへと移行する一連のユースケースで説明します。これらの例から、実践で必要な特定のワークフローを構築できるようになるでしょう。

コミットのガイドライン

特定のユースケースを見る前に、コミットメッセージについて簡単に説明します。コミットを作成するための良いガイドラインを持ち、それに従うことで、Gitでの作業や他者との共同作業がはるかに簡単になります。Gitプロジェクトは、パッチを提出するためのコミットを作成するためのいくつかの良いヒントをまとめたドキュメントを提供しています。それはGitのソースコードのDocumentation/SubmittingPatchesファイルで読むことができます。

まず、あなたの提出物には空白のエラーが含まれていてはなりません。Gitにはこれをチェックする簡単な方法があります。コミットする前にgit diff --checkを実行すると、可能性のある空白のエラーを特定し、それらをリストアップしてくれます。

Output of `git diff --check`
図56. git diff --check の出力

コミットする前にそのコマンドを実行すれば、他の開発者を悩ませる可能性のある空白の問題をコミットしようとしているかどうかを知ることができます。

次に、各コミットを論理的に独立した変更セットにするように努めてください。可能であれば、変更を消化しやすいものにしてください — 週末を通して5つの異なる問題についてコーディングし、月曜日にそれらすべてを1つの巨大なコミットとして提出しないでください。週末中にコミットしなくても、月曜日にステージングエリアを使用して、作業を少なくとも1つの問題につき1つのコミットに分割し、各コミットに役立つメッセージを付けてください。一部の変更が同じファイルを変更する場合は、git add --patchを使用してファイルを部分的にステージングしてみてください(詳細はインタラクティブステージングで詳しく説明されています)。ブランチの先端にあるプロジェクトのスナップショットは、すべての変更がどこかの時点で追加されていれば、1つのコミットであっても5つのコミットであっても同じなので、他の開発者があなたの変更をレビューする際に楽になるように努めてください。

このアプローチは、後で必要になった場合に、変更セットの1つを取り出したり元に戻したりするのを容易にもします。履歴の書き換えでは、履歴を書き換えたりファイルをインタラクティブにステージングしたりするための便利なGitトリックが多数説明されています — これらのツールを使用して、作業を他の人に送る前に、クリーンで理解しやすい履歴を作成するのに役立ててください。

最後に、コミットメッセージについて心に留めておいてください。質の高いコミットメッセージを作成する習慣を身につけることは、Gitの使用と共同作業をはるかに容易にします。一般的なルールとして、メッセージは50文字程度の簡潔な変更セットの説明から始まり、空白行を挟んで、より詳細な説明が続くべきです。Gitプロジェクトは、より詳細な説明に、変更の動機と、その実装が以前の振る舞いとどのように異なるかを記述することを要求しています — これは従うべき良いガイドラインです。コミットメッセージは命令形で書いてください:「バグを修正」のように「Fix bug」とし、「Fixed bug」や「Fixes bug」とはしないでください。これは、Tim Popeが元々書いたものを軽く修正した、あなたが従うことができるテンプレートです。

Capitalized, short (50 chars or less) summary

More detailed explanatory text, if necessary.  Wrap it to about 72
characters or so.  In some contexts, the first line is treated as the
subject of an email and the rest of the text as the body.  The blank
line separating the summary from the body is critical (unless you omit
the body entirely); tools like rebase will confuse you if you run the
two together.

Write your commit message in the imperative: "Fix bug" and not "Fixed bug"
or "Fixes bug."  This convention matches up with commit messages generated
by commands like git merge and git revert.

Further paragraphs come after blank lines.

- Bullet points are okay, too

- Typically a hyphen or asterisk is used for the bullet, followed by a
  single space, with blank lines in between, but conventions vary here

- Use a hanging indent

もしあなたのすべてのコミットメッセージがこのモデルに従っていれば、あなた自身と共同作業を行う開発者にとって、物事ははるかに簡単になるでしょう。Gitプロジェクトは整形されたコミットメッセージを持っています — そこにgit log --no-mergesを実行して、うまく整形されたプロジェクトのコミット履歴がどのようなものか見てみてください。

言うは易く行うは難し

簡潔さのために、この書籍の多くの例では、このようにきれいに整形されたコミットメッセージはありません。代わりに、git commit-mオプションを単に使用しています。

要するに、言うは易く行うは難し、です。

小規模なプライベートチーム

おそらく遭遇する最もシンプルなセットアップは、他の開発者が1人か2人いるプライベートプロジェクトです。この文脈での「プライベート」とは、クローズドソース、つまり外部に公開されていないことを意味します。あなたと他の開発者全員がリポジトリへのプッシュアクセスを持っています。

この環境では、Subversionや他の集中型システムを使用していたときと同様のワークフローに従うことができます。オフラインでのコミットや、ブランチとマージがはるかにシンプルになるなどの利点は依然として得られますが、ワークフローは非常に似ている可能性があります。主な違いは、コミット時にサーバー上ではなくクライアント側でマージが行われることです。2人の開発者が共有リポジトリで共同作業を開始したときにどうなるか見てみましょう。最初の開発者であるJohnは、リポジトリをクローンし、変更を加え、ローカルでコミットします。これらの例では、プロトコルメッセージは…​に置き換えられ、多少短縮されています。

# John's Machine
$ git clone john@githost:simplegit.git
Cloning into 'simplegit'...
...
$ cd simplegit/
$ vim lib/simplegit.rb
$ git commit -am 'Remove invalid default value'
[master 738ee87] Remove invalid default value
 1 files changed, 1 insertions(+), 1 deletions(-)

2人目の開発者であるJessicaも同じことをします — リポジトリをクローンし、変更をコミットします

# Jessica's Machine
$ git clone jessica@githost:simplegit.git
Cloning into 'simplegit'...
...
$ cd simplegit/
$ vim TODO
$ git commit -am 'Add reset task'
[master fbff5bc] Add reset task
 1 files changed, 1 insertions(+), 0 deletions(-)

さて、Jessicaは自分の作業をサーバーにプッシュします。これは問題なく機能します。

# Jessica's Machine
$ git push origin master
...
To jessica@githost:simplegit.git
   1edee6b..fbff5bc  master -> master

上記の出力の最後の行は、プッシュ操作からの有用な返信メッセージを示しています。基本フォーマットは<oldref>..<newref> fromref → torefで、oldrefは古い参照を意味し、newrefは新しい参照を意味し、fromrefはプッシュされるローカル参照の名前、torefは更新されるリモート参照の名前です。以下の議論でも同様の出力が表示されるため、その意味の基本的な理解はリポジトリの様々な状態を理解するのに役立ちます。詳細については、git-pushのドキュメントを参照してください。

この例を続行すると、その直後、Johnはいくつかの変更を加え、それらをローカルリポジトリにコミットし、同じサーバーにプッシュしようとします。

# John's Machine
$ git push origin master
To john@githost:simplegit.git
 ! [rejected]        master -> master (non-fast forward)
error: failed to push some refs to 'john@githost:simplegit.git'

この場合、Johnのプッシュは、Jessicaが以前に彼女の変更をプッシュしたために失敗します。これは、Subversionに慣れている場合に特に理解することが重要です。なぜなら、2人の開発者が同じファイルを編集していないことに気づくからです。Subversionは異なるファイルが編集された場合、サーバー上でそのようなマージを自動的に行いますが、Gitでは、まずローカルでコミットをマージする必要があります。言い換えれば、JohnはまずJessicaのアップストリームの変更をフェッチし、それらを自分のローカルリポジトリにマージしてからでないとプッシュが許可されません。

最初のステップとして、JohnはJessicaの作業をフェッチします(これはJessicaのアップストリームの作業をフェッチするだけで、まだJohnの作業にマージするわけではありません)

$ git fetch origin
...
From john@githost:simplegit
 + 049d078...fbff5bc master     -> origin/master

この時点で、Johnのローカルリポジトリは次のようになります

John’s divergent history
図57. Johnの分岐した履歴

これでJohnは、フェッチしたJessicaの作業を自分のローカル作業にマージできます

$ git merge origin/master
Merge made by the 'recursive' strategy.
 TODO |    1 +
 1 files changed, 1 insertions(+), 0 deletions(-)

そのローカルマージがスムーズに進む限り、Johnの更新された履歴は次のようになります

John’s repository after merging `origin/master`
図58. origin/masterをマージした後のJohnのリポジトリ

この時点で、JohnはJessicaの作業が自分の作業に影響を与えていないことを確認するために、この新しいコードをテストしたいと思うかもしれません。すべて問題なければ、最終的に新しいマージされた作業をサーバーにプッシュできます。

$ git push origin master
...
To john@githost:simplegit.git
   fbff5bc..72bbc59  master -> master

最終的に、Johnのコミット履歴は次のようになります

John’s history after pushing to the `origin` server
図59. originサーバーにプッシュした後のJohnの履歴

その間に、Jessicaはissue54という新しいトピックブランチを作成し、そのブランチに3つのコミットを行いました。彼女はまだJohnの変更をフェッチしていないため、彼女のコミット履歴は次のようになります

Jessica’s topic branch
図60. Jessicaのトピックブランチ

突然、JessicaはJohnがサーバーに新しい作業をプッシュしたことを知り、それを見てみたいと思っています。そこで、彼女はまだ持っていないサーバーからのすべての新しいコンテンツを次のようにしてフェッチできます。

# Jessica's Machine
$ git fetch origin
...
From jessica@githost:simplegit
   fbff5bc..72bbc59  master     -> origin/master

これにより、Johnがその間にプッシュした作業がプルダウンされます。Jessicaの履歴は次のようになります

Jessica’s history after fetching John’s changes
図61. Johnの変更をフェッチした後のJessicaの履歴

Jessicaは自分のトピックブランチが準備できたと考えていますが、プッシュできるようにJohnがフェッチした作業のどの部分を自分の作業にマージする必要があるかを知りたいと思っています。彼女はgit logを実行して調べます。

$ git log --no-merges issue54..origin/master
commit 738ee872852dfaa9d6634e0dea7a324040193016
Author: John Smith <jsmith@example.com>
Date:   Fri May 29 16:01:27 2009 -0700

   Remove invalid default value

issue54..origin/masterという構文は、後者のブランチ(この場合はorigin/master)にあり、最初のブランチ(この場合はissue54)にはないコミットのみを表示するようGitに要求するログフィルターです。この構文については、コミット範囲で詳しく説明します。

上記の出力から、Johnが作成したコミットが1つあり、Jessicaがまだ自分のローカル作業にマージしていないことがわかります。彼女がorigin/masterをマージした場合、それが彼女のローカル作業を変更する唯一のコミットになります。

これで、Jessicaは自分のトピック作業をmasterブランチにマージし、Johnの作業(origin/master)を自分のmasterブランチにマージし、そして再びサーバーにプッシュすることができます。

まず(issue54トピックブランチでのすべての作業をコミットした後)、Jessicaはこれらすべての作業を統合する準備として、自分のmasterブランチに切り替えます。

$ git checkout master
Switched to branch 'master'
Your branch is behind 'origin/master' by 2 commits, and can be fast-forwarded.

Jessicaはorigin/masterまたはissue54のいずれかを先にマージできます — どちらもアップストリームなので、順序は関係ありません。彼女がどちらの順序を選択しても、最終的なスナップショットは同一になります。履歴だけが異なります。彼女はissue54ブランチを最初にマージすることを選択します。

$ git merge issue54
Updating fbff5bc..4af4298
Fast forward
 README           |    1 +
 lib/simplegit.rb |    6 +++++-
 2 files changed, 6 insertions(+), 1 deletions(-)

問題は発生しませんでした。ご覧のように、単純なfast-forwardマージでした。Jessicaは、origin/masterブランチにあるJohnの以前にフェッチされた作業をマージすることで、ローカルマージプロセスを完了します。

$ git merge origin/master
Auto-merging lib/simplegit.rb
Merge made by the 'recursive' strategy.
 lib/simplegit.rb |    2 +-
 1 files changed, 1 insertions(+), 1 deletions(-)

すべてがクリーンにマージされ、Jessicaの履歴は次のようになります

Jessica’s history after merging John’s changes
図62. Johnの変更をマージした後のJessicaの履歴

これで、origin/masterはJessicaのmasterブランチから到達可能になり、彼女は正常にプッシュできるはずです(その間にJohnがさらに多くの変更をプッシュしていないと仮定して)。

$ git push origin master
...
To jessica@githost:simplegit.git
   72bbc59..8059c15  master -> master

各開発者は数回コミットし、お互いの作業を正常にマージしました。

Jessica’s history after pushing all changes back to the server
図63. すべての変更をサーバーにプッシュした後のJessicaの履歴

これは最もシンプルなワークフローの1つです。あなたはしばらく作業し(通常はトピックブランチで)、それが統合準備ができたときにその作業をmasterブランチにマージします。その作業を共有したいときは、origin/masterからmasterをフェッチしてマージし(変更があった場合)、最後にサーバーのmasterブランチにプッシュします。一般的なシーケンスは次のようになります。

General sequence of events for a simple multiple-developer Git workflow
図64. シンプルな複数開発者Gitワークフローの一般的なイベントシーケンス

管理されたプライベートチーム

次のシナリオでは、大規模なプライベートグループにおける貢献者の役割を見ていきます。ここでは、小規模なグループが機能について共同作業を行い、その後、チームベースの貢献が別の当事者によって統合される環境で作業する方法を学びます。

JohnとJessicaが1つの機能(「featureA」と呼びましょう)に共同で取り組んでおり、一方、Jessicaと3人目の開発者Josieが2つ目の機能(例えば「featureB」)に取り組んでいるとします。この場合、会社は統合マネージャー型のワークフローを使用しており、個々のグループの作業は特定のエンジニアによってのみ統合され、メインリポジトリのmasterブランチはそれらのエンジニアによってのみ更新できます。このシナリオでは、すべての作業はチームベースのブランチで行われ、後でインテグレーターによってまとめられます。

この環境で、2人の異なる開発者と並行して共同作業を行いながら、Jessicaが2つの機能に取り組むワークフローを追ってみましょう。彼女はすでにリポジトリをクローンしていると仮定し、まずfeatureAに取り組むことにします。彼女はその機能のために新しいブランチを作成し、そこでいくつかの作業を行います。

# Jessica's Machine
$ git checkout -b featureA
Switched to a new branch 'featureA'
$ vim lib/simplegit.rb
$ git commit -am 'Add limit to log function'
[featureA 3300904] Add limit to log function
 1 files changed, 1 insertions(+), 1 deletions(-)

この時点で、彼女はJohnと作業を共有する必要があるため、自分のfeatureAブランチのコミットをサーバーにプッシュします。Jessicaはmasterブランチへのプッシュアクセス権を持っていません — 統合担当者のみが持っています — したがって、Johnと共同作業するために別のブランチにプッシュする必要があります。

$ git push -u origin featureA
...
To jessica@githost:simplegit.git
 * [new branch]      featureA -> featureA

JessicaはJohnに、featureAという名前のブランチに作業をプッシュしたので、今すぐ見ることができるとメールで伝えます。Johnからのフィードバックを待つ間、JessicaはJosieとfeatureBの作業を開始することにしました。まず、彼女はサーバーのmasterブランチをベースとして新しい機能ブランチを開始します。

# Jessica's Machine
$ git fetch origin
$ git checkout -b featureB origin/master
Switched to a new branch 'featureB'

さて、JessicaはfeatureBブランチでいくつかのコミットを行います。

$ vim lib/simplegit.rb
$ git commit -am 'Make ls-tree function recursive'
[featureB e5b0fdc] Make ls-tree function recursive
 1 files changed, 1 insertions(+), 1 deletions(-)
$ vim lib/simplegit.rb
$ git commit -am 'Add ls-files'
[featureB 8512791] Add ls-files
 1 files changed, 5 insertions(+), 0 deletions(-)

Jessicaのリポジトリは次のようになります

Jessica’s initial commit history
図65. Jessicaの最初のコミット履歴

彼女は作業をプッシュする準備ができていますが、Josieから、いくつかの初期の「featureB」作業を含むブランチがすでにfeatureBeeブランチとしてサーバーにプッシュされたというメールを受け取ります。Jessicaは、自分の作業をサーバーにプッシュする前に、それらの変更を自分のものとマージする必要があります。Jessicaはまずgit fetchでJosieの変更をフェッチします。

$ git fetch origin
...
From jessica@githost:simplegit
 * [new branch]      featureBee -> origin/featureBee

Jessicaがまだチェックアウト済みのfeatureBブランチにいると仮定すると、彼女はgit mergeを使ってJosieの作業をそのブランチにマージできるようになります。

$ git merge origin/featureBee
Auto-merging lib/simplegit.rb
Merge made by the 'recursive' strategy.
 lib/simplegit.rb |    4 ++++
 1 files changed, 4 insertions(+), 0 deletions(-)

この時点で、Jessicaはマージされた「featureB」の作業すべてをサーバーにプッシュしたいと考えていますが、単に自分のfeatureBブランチをプッシュするのではなく、JosieがすでにアップストリームのfeatureBeeブランチを開始しているため、Jessicaはそのブランチにプッシュしたいと考えています。彼女は次のようにしてそれを行います。

$ git push -u origin featureB:featureBee
...
To jessica@githost:simplegit.git
   fba9af8..cd685d1  featureB -> featureBee

これはrefspecと呼ばれます。Refspecで、Gitのrefspecとその様々な使い方について詳しく説明しています。また、-uフラグにも注目してください。これは--set-upstreamの短縮形で、後のプッシュとプルを容易にするようにブランチを設定します。

突然、JessicaはJohnからメールを受け取ります。Johnは共同作業しているfeatureAブランチにいくつかの変更をプッシュしたと伝え、Jessicaにそれらを確認するよう依頼します。再び、Jessicaはシンプルなgit fetchを実行して、Johnの最新の作業を含む(もちろん)サーバーからのすべての新しいコンテンツをフェッチします。

$ git fetch origin
...
From jessica@githost:simplegit
   3300904..aad881d  featureA   -> origin/featureA

Jessicaは、新しくフェッチしたfeatureAブランチの内容を自分のローカルコピーの同じブランチと比較することで、Johnの新しい作業のログを表示できます。

$ git log featureA..origin/featureA
commit aad881d154acdaeb2b6b18ea0e827ed8a6d671e6
Author: John Smith <jsmith@example.com>
Date:   Fri May 29 19:57:33 2009 -0700

    Increase log output to 30 from 25

もしJessicaが気に入れば、彼女はJohnの新しい作業を自分のローカルfeatureAブランチに次のようにマージできます。

$ git checkout featureA
Switched to branch 'featureA'
$ git merge origin/featureA
Updating 3300904..aad881d
Fast forward
 lib/simplegit.rb |   10 +++++++++-
1 files changed, 9 insertions(+), 1 deletions(-)

最後に、Jessicaはマージされたコンテンツすべてにいくつかの小さな変更を加えたいと思うかもしれません。そこで、彼女は自由にそれらの変更を行い、ローカルのfeatureAブランチにコミットし、最終結果をサーバーにプッシュすることができます。

$ git commit -am 'Add small tweak to merged content'
[featureA 774b3ed] Add small tweak to merged content
 1 files changed, 1 insertions(+), 1 deletions(-)
$ git push
...
To jessica@githost:simplegit.git
   3300904..774b3ed  featureA -> featureA

Jessicaのコミット履歴は次のようになります

Jessica’s history after committing on a feature branch
図66. 機能ブランチでコミットした後のJessicaの履歴

ある時点で、Jessica、Josie、Johnは、サーバー上のfeatureAfeatureBeeブランチがメインラインへの統合準備ができたことを統合担当者に伝えます。統合担当者がこれらのブランチをメインラインにマージした後、フェッチによって新しいマージコミットがダウンロードされ、履歴は次のようになります。

Jessica’s history after merging both her topic branches
図67. 両方のトピックブランチをマージした後のJessicaの履歴

多くのグループがGitに移行するのは、複数のチームが並行して作業し、プロセスの後半で異なる作業ラインをマージできるこの能力のためです。チーム内の小規模なサブグループが、チーム全体を巻き込んだり妨げたりすることなくリモートブランチを介して共同作業できる能力は、Gitの大きな利点です。ここで見たワークフローのシーケンスは次のようになります。

Basic sequence of this managed-team workflow
図68. この管理されたチームワークフローの基本的なシーケンス

フォークされた公開プロジェクト

公開プロジェクトへの貢献は少し異なります。プロジェクトのブランチを直接更新する権限がないため、他の方法で作業をメンテナに渡す必要があります。この最初の例では、簡単なフォークをサポートするGitホスト上でのフォークを介した貢献について説明します。多くのホスティングサイトがこれをサポートしており(GitHub、BitBucket、repo.or.czなどを含む)、多くのプロジェクトメンテナはこの貢献スタイルを期待しています。次のセクションでは、電子メールを介して貢献されたパッチを受け入れることを好むプロジェクトについて説明します。

まず、メインリポジトリをクローンし、貢献する予定のパッチまたはパッチシリーズ用のトピックブランチを作成し、そこで作業を行うことになります。シーケンスは基本的に次のようになります。

$ git clone <url>
$ cd project
$ git checkout -b featureA
  ... work ...
$ git commit
  ... work ...
$ git commit

rebase -iを使用して作業を単一のコミットにまとめるか、コミット内の作業を再配置して、メンテナがパッチをレビューしやすくしたいと思うかもしれません — インタラクティブなリベースの詳細については、履歴の書き換えを参照してください。

ブランチでの作業が完了し、メンテナに貢献する準備ができたら、元のプロジェクトページにアクセスし、「Fork」ボタンをクリックして、プロジェクトの書き込み可能な独自のフォークを作成します。次に、このリポジトリURLをローカルリポジトリの新しいリモートとして追加する必要があります。この例では、それをmyforkと呼びましょう。

$ git remote add myfork <url>

次に、このリポジトリに新しい作業をプッシュする必要があります。作業中のトピックブランチをフォークしたリポジトリにプッシュする方が、その作業をmasterブランチにマージしてプッシュするよりも簡単です。その理由は、あなたの作業が受け入れられなかったり、チェリーピックされたりした場合に、masterブランチを巻き戻す必要がないためです(Gitのcherry-pick操作はリベースとチェリーピックのワークフローでさらに詳しく説明されています)。メンテナがあなたの作業をmergerebase、またはcherry-pickした場合でも、いずれにせよ彼らのリポジトリからプルすることでそれを取り戻すことができます。

いずれにしても、次のようにして作業をプッシュできます。

$ git push -u myfork featureA

リポジトリのフォークに作業をプッシュしたら、元のプロジェクトのメンテナに、マージしてもらいたい作業があることを通知する必要があります。これはしばしばプルリクエストと呼ばれ、通常、このようなリクエストはウェブサイトを介して生成します — GitHubには独自の「プルリクエスト」メカニズムがあり、それについてはGitHubで詳しく説明します — あるいは、git request-pullコマンドを実行し、その後の出力を手動でプロジェクトメンテナにメールで送信することもできます。

git request-pullコマンドは、トピックブランチをプルしたいベースブランチと、プルしてほしいGitリポジトリのURLを受け取り、プルを要求しているすべての変更の要約を生成します。たとえば、JessicaがJohnにプルリクエストを送信したい場合で、彼女がちょうどプッシュしたトピックブランチに2つのコミットを行っている場合、彼女はこれを実行できます。

$ git request-pull origin/master myfork
The following changes since commit 1edee6b1d61823a2de3b09c160d7080b8d1b3a40:
Jessica Smith (1):
        Create new function

are available in the git repository at:

  https://githost/simplegit.git featureA

Jessica Smith (2):
      Add limit to log function
      Increase log output to 30 from 25

 lib/simplegit.rb |   10 +++++++++-
 1 files changed, 9 insertions(+), 1 deletions(-)

この出力はメンテナに送信できます — それは作業がどこからブランチされたか、コミットを要約し、新しい作業がどこからプルされるべきかを特定します。

あなたがメンテナではないプロジェクトでは、masterのようなブランチを常にorigin/masterを追跡させ、作業をトピックブランチで行う方が一般的に簡単です。トピックブランチは、拒否された場合に簡単に破棄できます。作業テーマをトピックブランチに分離することで、メインリポジトリの先端がその間に移動し、あなたのコミットがクリーンに適用されなくなった場合でも、作業をリベースしやすくなります。たとえば、プロジェクトに2つ目の作業トピックを提出したい場合、先ほどプッシュしたトピックブランチでの作業を続けるのではなく、メインリポジトリのmasterブランチからやり直してください。

$ git checkout -b featureB origin/master
  ... work ...
$ git commit
$ git push myfork featureB
$ git request-pull origin/master myfork
  ... email generated request pull to maintainer ...
$ git fetch origin

これで、各トピックはサイロ — パッチキューに似たもの — に含まれ、トピックが互いに干渉したり依存したりすることなく、次のように書き換え、リベースし、修正できます。

Initial commit history with `featureB` work
図69. featureB作業を含む初期コミット履歴

プロジェクトのメンテナが他の多数のパッチを取り込み、あなたの最初のブランチを試したが、もはやクリーンにマージできないとしましょう。この場合、そのブランチをorigin/masterの上にリベースし、メンテナのために競合を解決し、その後変更を再提出することができます。

$ git checkout featureA
$ git rebase origin/master
$ git push -f myfork featureA

これにより、あなたの履歴は今やfeatureA作業後のコミット履歴のようになります。

Commit history after `featureA` work
図70. featureA作業後のコミット履歴

ブランチをリベースしたため、-fをプッシュコマンドに指定して、サーバー上のfeatureAブランチを、その子孫ではないコミットで置き換える必要があります。別の方法としては、この新しい作業をサーバー上の異なるブランチ(おそらくfeatureAv2と呼ばれるもの)にプッシュすることが挙げられます。

もう1つの可能性のあるシナリオを見てみましょう。メンテナがあなたの2番目のブランチの作業を見て、そのコンセプトは気に入ったものの、実装の詳細を変更してほしいと考えています。この機会に、作業をプロジェクトの現在のmasterブランチに基づいたものに移動させましょう。現在のorigin/masterブランチから新しいブランチを開始し、そこでfeatureBの変更をスカッシュし、競合を解決し、実装の変更を行い、それを新しいブランチとしてプッシュします。

$ git checkout -b featureBv2 origin/master
$ git merge --squash featureB
  ... change implementation ...
$ git commit
$ git push myfork featureBv2

--squashオプションは、マージされるブランチ上のすべての作業を1つの変更セットにまとめ、実際のマージが発生したかのようなリポジトリの状態を生成しますが、実際にはマージコミットを作成しません。これにより、将来のコミットは親を1つだけ持ち、別のブランチからのすべての変更を導入し、新しいコミットを記録する前に追加の変更を行うことができます。また、--no-commitオプションは、デフォルトのマージプロセスの場合にマージコミットを遅延させるのに役立ちます。

この時点で、あなたは要求された変更を行ったこと、そしてそれらの変更がfeatureBv2ブランチで見つかることをメンテナに通知できます。

Commit history after `featureBv2` work
図71. featureBv2作業後のコミット履歴

メール経由での公開プロジェクトへの貢献

多くのプロジェクトでは、パッチを受け入れるための確立された手順があります — それぞれのプロジェクトで特定のルールが異なるため、確認する必要があります。開発者メーリングリストを介してパッチを受け入れる古い大規模なプロジェクトがいくつかあるため、その例を今から見ていきましょう。

ワークフローは以前のユースケースと似ています — 作業する各パッチシリーズのためにトピックブランチを作成します。違いは、プロジェクトへの提出方法です。プロジェクトをフォークして独自の書き込み可能なバージョンにプッシュする代わりに、各コミットシリーズのメール版を生成し、それを開発者メーリングリストにメールで送信します。

$ git checkout -b topicA
  ... work ...
$ git commit
  ... work ...
$ git commit

これで、メーリングリストに送信したい2つのコミットができました。git format-patchを使用して、リストにメールできるmbox形式のファイルを生成します — これは、各コミットを、コミットメッセージの最初の行を件名とし、残りのメッセージとコミットが導入するパッチを本文とするメールメッセージに変換します。これの良い点は、format-patchで生成されたメールからパッチを適用すると、すべてのコミット情報が適切に保持されることです。

$ git format-patch -M origin/master
0001-add-limit-to-log-function.patch
0002-increase-log-output-to-30-from-25.patch

format-patchコマンドは、作成するパッチファイルの名前を出力します。-Mスイッチは、Gitにリネームを検出するよう指示します。ファイルは最終的に次のようになります。

$ cat 0001-add-limit-to-log-function.patch
From 330090432754092d704da8e76ca5c05c198e71a8 Mon Sep 17 00:00:00 2001
From: Jessica Smith <jessica@example.com>
Date: Sun, 6 Apr 2008 10:17:23 -0700
Subject: [PATCH 1/2] Add limit to log function

Limit log functionality to the first 20

---
 lib/simplegit.rb |    2 +-
 1 files changed, 1 insertions(+), 1 deletions(-)

diff --git a/lib/simplegit.rb b/lib/simplegit.rb
index 76f47bc..f9815f1 100644
--- a/lib/simplegit.rb
+++ b/lib/simplegit.rb
@@ -14,7 +14,7 @@ class SimpleGit
   end

   def log(treeish = 'master')
-    command("git log #{treeish}")
+    command("git log -n 20 #{treeish}")
   end

   def ls_tree(treeish = 'master')
--
2.1.0

これらのパッチファイルを編集して、コミットメッセージには表示したくない、メーリングリスト向けの追加情報を加えることもできます。---行とパッチの開始(diff --git行)の間にテキストを追加すると、開発者はそれを読むことができますが、その内容はパッチ適用プロセスでは無視されます。

これをメーリングリストにメールするには、ファイルをメールプログラムに貼り付けるか、コマンドラインプログラムで送信することができます。テキストを貼り付けると、特に改行やその他の空白を適切に保持しない「賢い」クライアントでは、書式設定の問題が発生することがよくあります。幸いにも、GitはIMAPを介して適切にフォーマットされたパッチを送信するのに役立つツールを提供しており、これはあなたにとってより簡単かもしれません。ここでは、私たちが最もよく知っているメールエージェントであるGmailを介してパッチを送信する方法をデモンストレーションします。Gitソースコード内の前述のDocumentation/SubmittingPatchesファイルの最後に、いくつかのメールプログラムに関する詳細な手順を読むことができます。

まず、~/.gitconfigファイルにimapセクションを設定する必要があります。各値をgit configコマンドで個別に設定することも、手動で追加することもできますが、最終的には設定ファイルは次のようになるはずです。

[imap]
  folder = "[Gmail]/Drafts"
  host = imaps://imap.gmail.com
  user = user@gmail.com
  pass = YX]8g76G_2^sFbd
  port = 993
  sslverify = false

IMAPサーバーがSSLを使用しない場合、最後の2行はおそらく不要であり、ホスト値はimaps://の代わりにimap://になります。それが設定されたら、git imap-sendを使用して、指定されたIMAPサーバーのドラフトフォルダにパッチシリーズを配置できます。

$ cat *.patch |git imap-send
Resolving imap.gmail.com... ok
Connecting to [74.125.142.109]:993... ok
Logging in...
sending 2 messages
100% (2/2) done

この時点で、ドラフトフォルダに行き、宛先フィールドをパッチを送信するメーリングリストに変更し、必要であればメンテナまたはそのセクションの担当者をCCに入れ、送信することができます。

SMTPサーバー経由でパッチを送信することもできます。以前と同様に、各値をgit configコマンドで個別に設定することも、~/.gitconfigファイルのsendemailセクションに手動で追加することもできます。

[sendemail]
  smtpencryption = tls
  smtpserver = smtp.gmail.com
  smtpuser = user@gmail.com
  smtpserverport = 587

これが完了したら、git send-emailを使用してパッチを送信できます。

$ git send-email *.patch
0001-add-limit-to-log-function.patch
0002-increase-log-output-to-30-from-25.patch
Who should the emails appear to be from? [Jessica Smith <jessica@example.com>]
Emails will be sent from: Jessica Smith <jessica@example.com>
Who should the emails be sent to? jessica@example.com
Message-ID to be used as In-Reply-To for the first email? y

すると、Gitは送信する各パッチについて、次のような大量のログ情報を出力します。

(mbox) Adding cc: Jessica Smith <jessica@example.com> from
  \line 'From: Jessica Smith <jessica@example.com>'
OK. Log says:
Sendmail: /usr/sbin/sendmail -i jessica@example.com
From: Jessica Smith <jessica@example.com>
To: jessica@example.com
Subject: [PATCH 1/2] Add limit to log function
Date: Sat, 30 May 2009 13:29:15 -0700
Message-Id: <1243715356-61726-1-git-send-email-jessica@example.com>
X-Mailer: git-send-email 1.6.2.rc1.20.g8c5b.dirty
In-Reply-To: <y>
References: <y>

Result: OK
ヒント

システムとメールの設定、より多くのヒントとトリック、およびメール経由でテストパッチを送信するためのサンドボックスについては、git-send-email.ioにアクセスしてください。

まとめ

このセクションでは、複数のワークフローについて取り上げ、クローズドソースプロジェクトの小規模チームの一員として作業することと、大規模な公開プロジェクトに貢献することの違いについて説明しました。コミットする前に空白のエラーをチェックする方法を知り、素晴らしいコミットメッセージを書けるようになりました。パッチをフォーマットし、開発者メーリングリストにメールで送信する方法を学びました。異なるワークフローの文脈でのマージの扱いも説明されました。これで、どんなプロジェクトでも共同作業する準備が整いました。

次に、コインの裏側、つまりGitプロジェクトの管理方法を見ていきます。あなたは寛大な独裁者や統合マネージャーになる方法を学ぶでしょう。

scroll-to-top