Skip to content
Playground

6.ブランチとプルリクエスト

学習目標

  • 作業ごとにブランチを分け、main を安定させたまま変更できる
  • プッシュで変更を共有し、プルで取り込める
  • プルリクエストで変更のレビューを受け、承認後にマージできる
  • マージ後に main を最新にして、次の作業を始められる
  • コンフリクトの原因を説明し、解消できる

本章では、バージョン管理 で共有したプロジェクトを、main から分けたブランチで並行して開発します。完成した変更はプルリクエストとマージで main に取り込み、変更が衝突したときはコンフリクトを解消します。

クローンした直後は、全員のローカルに同じ main があります。この main に直接コミットすると、2 つの問題が起きます。1 つは、未完成のコードが main に入り、ほかのメンバーの環境でアプリケーションが動かなくなることです。もう 1 つは、複数の作業のコミットが main に入り交じり、どの作業のものか分からなくなることです。

この 2 つを避けるために、作業を main から切り離して進める仕組みを ブランチ と呼びます。

ブランチは、main から分かれた独立した作業の履歴です。1 つの機能や修正など、作業のまとまりごとに専用のブランチを作ります。たとえば「質問の投稿」と「検索」は、それぞれ別のブランチで進めます。

main から 2 つの作業用ブランチが分かれる図

main の最新のコミットから、2 本のブランチが分かれています。質問の投稿のコミットは feature/question-post、検索のコミットは feature/search と、それぞれのブランチにだけ記録され、main やもう一方のブランチには現れません。

そのため、作業が未完成でも、main は正しく動く状態のままです。チーム開発では main を常にこの状態に保ち、完成した変更だけを取り込みます。

ブランチ説明
mainチーム全員で共有する、常に正しく動くブランチ
作業用ブランチ1 つの機能や修正のために main から分けるブランチ

作業用ブランチには、作業の内容を表す名前を付けます。機能の追加には feature/、不具合の修正には fix/ を先頭に付けるのが一般的です。

git switch -c ブランチ名

git switch -c は、現在のブランチから新しいブランチを作り、その新しいブランチへ切り替えます。

Terminal window
git switch -c feature/question-post

切り替えたあとのコミットは、このブランチにだけ記録されます。

作業は、次の手順で進めます。

  1. 最新の main からブランチを作る

    Terminal window
    git switch main
    git pull
    git switch -c feature/question-post

    main に切り替え、git pull で最新の状態を取り込んでから、作業用ブランチを作ります。リモートの変更をローカルに取り込むこの操作を プル と呼びます。古い main から作ると、あとでコンフリクトが起きやすくなります。

  2. 作業してコミットする

    機能を実装し、作業の区切りごとにコミットします。コミットの手順は バージョン管理 と同じで、1 つのブランチで何度でもコミットできます。

  3. ブランチをプッシュする

    Terminal window
    git push -u origin feature/question-post

    作業用ブランチを初めてプッシュするときは、-u origin ブランチ名 で、ローカルのブランチを送信先のリモートブランチに対応づけます。2 回目以降は git push だけで実行できます。

作業用ブランチで機能が完成したら、その変更を main に取り込みます。取り込みは、プルリクエストとマージの 2 段階で行います。

完成した変更を確認せずに取り込むと、誤りに気づかないまま main に入ります。変更を main に取り込んでよいかをチームに依頼する仕組みを プルリクエスト(pull request、PR)と呼びます。

ブランチをプッシュすると、GitHub のリポジトリページに、プルリクエストを作成するボタンが表示されます。取り込み先の main を指定し、変更の内容と目的を書いて作成します。ほかのメンバーは、その差分を読んで問題がないかを確認します。この確認を レビュー と呼びます。

ブランチの変更を main に取り込む操作を マージ と呼びます。

作業用ブランチを main にマージする図

プルリクエストがレビューで承認されると、GitHub の画面からマージでき、変更が main に反映されます。マージが済んだ作業用ブランチは、マージ後に表示される削除ボタン(Delete branch)で、リモートから削除します。

マージ後、リモートの main は更新されますが、各メンバーのローカルの main は古いままです。次の作業を始める前に、ローカルの main を最新にします。

Terminal window
git switch main
git pull

プルすると、ほかのメンバーがマージした変更が、ローカルの main にも反映されます。マージ済みの作業用ブランチがローカルに残っていれば、git branch -d feature/question-post で削除します。

複数人が並行して作業すると、同じファイルの同じ行を、別々のブランチで変更することがあります。この 2 つの変更を 1 つにまとめるとき、Git はどちらを採用すべきか判断できません。この状態を コンフリクト(競合)と呼びます。

別々の箇所への変更なら、Git が自動で 1 つにまとめます。同じ行を両方のブランチで変更したときだけ、手作業で解消します。たとえば、検索のブランチが先にマージされ、main が更新されたとします。

検索のマージで main が進み、作業中のブランチでコンフリクトが起きる図

質問の投稿のブランチでも、質問の一覧を表示するコードの同じ行を変更していると、main を取り込むときにその行でコンフリクトが起きます。

プルリクエストを作成する前に、最新の main を自分のブランチに取り込んでおくと、コンフリクトをローカルで解消できます。main の変更を自分のブランチに取り込むには、git merge を使います。

  1. 最新の main を取り込む

    Terminal window
    git switch main
    git pull
    git switch feature/question-post
    git merge main

    作業用ブランチに切り替え、git merge main で最新の main を取り込みます。コンフリクトがあると、Git がコンフリクトしたファイルを表示して、マージを中断します。

  2. コンフリクトの箇所を確認する

    コンフリクトしたファイル(ここでは QuestionController.java)には、Git が次の コンフリクトマーカー(conflict marker)を書き込みます。

    <<<<<<< HEAD
    model.addAttribute("questions", questionService.findAll());
    =======
    model.addAttribute("questions", questionService.search(keyword));
    >>>>>>> main

    <<<<<<< HEAD から ======= までが自分のブランチの変更、======= から >>>>>>> main までが取り込む main の変更です。

  3. 正しい内容に直す

    どちらを採用するか、または両方を組み合わせるかを決め、マーカー(<<<<<<<=======>>>>>>>)を消して、1 つの内容にまとめます。

  4. 解消をコミットする

    Terminal window
    git add QuestionController.java
    git commit

    修正したファイルをステージに加えてコミットすると、解消が履歴に記録されます。マージのコミットにはメッセージが用意されているため、エディターが開いたら、そのまま保存して閉じます。

  5. 解消をプッシュする

    Terminal window
    git push

    解消した内容がリモートのブランチに反映され、コンフリクトのない状態でプルリクエストをマージできます。

コンフリクトは解消に手間がかかります。完全には避けられませんが、次の習慣で起こりにくくできます。

  • 作業が長引くほど、main との差分が大きくなります。途中で main を取り込み、差分を小さく保ちます。
  • 1 つのプルリクエストの変更範囲を狭くすると、ほかの変更とコンフリクトしにくくなります。
  • 同じファイルを複数人で同時に変更しないよう、担当をチームで分けます。