6.ブランチとプルリクエスト
学習目標
- 作業ごとにブランチを分け、
mainを安定させたまま変更できる - プッシュで変更を共有し、プルで取り込める
- プルリクエストで変更のレビューを受け、承認後にマージできる
- マージ後に
mainを最新にして、次の作業を始められる - コンフリクトの原因を説明し、解消できる
本章では、バージョン管理 で共有したプロジェクトを、main から分けたブランチで並行して開発します。完成した変更はプルリクエストとマージで main に取り込み、変更が衝突したときはコンフリクトを解消します。
1. ブランチ
Section titled “1. ブランチ”クローンした直後は、全員のローカルに同じ main があります。この main に直接コミットすると、2 つの問題が起きます。1 つは、未完成のコードが main に入り、ほかのメンバーの環境でアプリケーションが動かなくなることです。もう 1 つは、複数の作業のコミットが main に入り交じり、どの作業のものか分からなくなることです。
この 2 つを避けるために、作業を main から切り離して進める仕組みを ブランチ と呼びます。
1-1. ブランチの役割
Section titled “1-1. ブランチの役割”ブランチは、main から分かれた独立した作業の履歴です。1 つの機能や修正など、作業のまとまりごとに専用のブランチを作ります。たとえば「質問の投稿」と「検索」は、それぞれ別のブランチで進めます。
main の最新のコミットから、2 本のブランチが分かれています。質問の投稿のコミットは feature/question-post、検索のコミットは feature/search と、それぞれのブランチにだけ記録され、main やもう一方のブランチには現れません。
そのため、作業が未完成でも、main は正しく動く状態のままです。チーム開発では main を常にこの状態に保ち、完成した変更だけを取り込みます。
| ブランチ | 説明 |
|---|---|
main | チーム全員で共有する、常に正しく動くブランチ |
| 作業用ブランチ | 1 つの機能や修正のために main から分けるブランチ |
1-2. ブランチの作成と作業
Section titled “1-2. ブランチの作成と作業”作業用ブランチには、作業の内容を表す名前を付けます。機能の追加には feature/、不具合の修正には fix/ を先頭に付けるのが一般的です。
git switch -c ブランチ名git switch -c は、現在のブランチから新しいブランチを作り、その新しいブランチへ切り替えます。
git switch -c feature/question-post切り替えたあとのコミットは、このブランチにだけ記録されます。
作業は、次の手順で進めます。
-
最新の
mainからブランチを作るTerminal window git switch maingit pullgit switch -c feature/question-postmainに切り替え、git pullで最新の状態を取り込んでから、作業用ブランチを作ります。リモートの変更をローカルに取り込むこの操作を プル と呼びます。古いmainから作ると、あとでコンフリクトが起きやすくなります。 -
作業してコミットする
機能を実装し、作業の区切りごとにコミットします。コミットの手順は バージョン管理 と同じで、1 つのブランチで何度でもコミットできます。
-
ブランチをプッシュする
Terminal window git push -u origin feature/question-post作業用ブランチを初めてプッシュするときは、
-u origin ブランチ名で、ローカルのブランチを送信先のリモートブランチに対応づけます。2 回目以降はgit pushだけで実行できます。
2. プルリクエストとマージ
Section titled “2. プルリクエストとマージ”作業用ブランチで機能が完成したら、その変更を main に取り込みます。取り込みは、プルリクエストとマージの 2 段階で行います。
2-1. プルリクエスト
Section titled “2-1. プルリクエスト”完成した変更を確認せずに取り込むと、誤りに気づかないまま main に入ります。変更を main に取り込んでよいかをチームに依頼する仕組みを プルリクエスト(pull request、PR)と呼びます。
ブランチをプッシュすると、GitHub のリポジトリページに、プルリクエストを作成するボタンが表示されます。取り込み先の main を指定し、変更の内容と目的を書いて作成します。ほかのメンバーは、その差分を読んで問題がないかを確認します。この確認を レビュー と呼びます。
2-2. マージと main の最新化
Section titled “2-2. マージと main の最新化”ブランチの変更を main に取り込む操作を マージ と呼びます。
プルリクエストがレビューで承認されると、GitHub の画面からマージでき、変更が main に反映されます。マージが済んだ作業用ブランチは、マージ後に表示される削除ボタン(Delete branch)で、リモートから削除します。
マージ後、リモートの main は更新されますが、各メンバーのローカルの main は古いままです。次の作業を始める前に、ローカルの main を最新にします。
git switch maingit pullプルすると、ほかのメンバーがマージした変更が、ローカルの main にも反映されます。マージ済みの作業用ブランチがローカルに残っていれば、git branch -d feature/question-post で削除します。
3. コンフリクトの解消
Section titled “3. コンフリクトの解消”3-1. コンフリクトの原因
Section titled “3-1. コンフリクトの原因”複数人が並行して作業すると、同じファイルの同じ行を、別々のブランチで変更することがあります。この 2 つの変更を 1 つにまとめるとき、Git はどちらを採用すべきか判断できません。この状態を コンフリクト(競合)と呼びます。
別々の箇所への変更なら、Git が自動で 1 つにまとめます。同じ行を両方のブランチで変更したときだけ、手作業で解消します。たとえば、検索のブランチが先にマージされ、main が更新されたとします。
質問の投稿のブランチでも、質問の一覧を表示するコードの同じ行を変更していると、main を取り込むときにその行でコンフリクトが起きます。
3-2. 解消の手順
Section titled “3-2. 解消の手順”プルリクエストを作成する前に、最新の main を自分のブランチに取り込んでおくと、コンフリクトをローカルで解消できます。main の変更を自分のブランチに取り込むには、git merge を使います。
-
最新の
mainを取り込むTerminal window git switch maingit pullgit switch feature/question-postgit merge main作業用ブランチに切り替え、
git merge mainで最新のmainを取り込みます。コンフリクトがあると、Git がコンフリクトしたファイルを表示して、マージを中断します。 -
コンフリクトの箇所を確認する
コンフリクトしたファイル(ここでは
QuestionController.java)には、Git が次の コンフリクトマーカー(conflict marker)を書き込みます。<<<<<<< HEADmodel.addAttribute("questions", questionService.findAll());=======model.addAttribute("questions", questionService.search(keyword));>>>>>>> main<<<<<<< HEADから=======までが自分のブランチの変更、=======から>>>>>>> mainまでが取り込むmainの変更です。 -
正しい内容に直す
どちらを採用するか、または両方を組み合わせるかを決め、マーカー(
<<<<<<<・=======・>>>>>>>)を消して、1 つの内容にまとめます。 -
解消をコミットする
Terminal window git add QuestionController.javagit commit修正したファイルをステージに加えてコミットすると、解消が履歴に記録されます。マージのコミットにはメッセージが用意されているため、エディターが開いたら、そのまま保存して閉じます。
-
解消をプッシュする
Terminal window git push解消した内容がリモートのブランチに反映され、コンフリクトのない状態でプルリクエストをマージできます。
3-3. コンフリクトの予防
Section titled “3-3. コンフリクトの予防”コンフリクトは解消に手間がかかります。完全には避けられませんが、次の習慣で起こりにくくできます。
- 作業が長引くほど、
mainとの差分が大きくなります。途中でmainを取り込み、差分を小さく保ちます。 - 1 つのプルリクエストの変更範囲を狭くすると、ほかの変更とコンフリクトしにくくなります。
- 同じファイルを複数人で同時に変更しないよう、担当をチームで分けます。