はじめに
前回は、GitLab上のリモートリポジトリとの連携やプロジェクトの作成・管理、ローカル環境でのクローン、ローカルリポジトリでの変更内容をリモートリポジトリに反映してみました。これで、複数の開発者で同じ変更履歴を共有することができるため、円滑にチーム開発を進められます。
Gitを使った開発では、コードの変更履歴を管理するだけでなく、複数の開発者が同時に作業を進めるための仕組みが必要です。その中心となるのが「ブランチ」という概念です。
今回は、Gitのブランチとは何かという基本から、チーム開発でよく使われるブランチ戦略、そしてGitLabでのマージリクエストを通じた具体的な開発フロー、さらにはコンフリクト(競合)の解消方法まで、実践的に解説していきます。
ブランチ説明と利用ケース
ブランチとは、第1回でも軽く触れましたが、現在の作業から派生して新しい開発ラインを作成する機能です。新しい機能開発やバグ修正を行う際に、メインの開発ラインに影響を与えずに作業を進めることができます。作業が完了したら、上位のブランチに統合(マージ)します。
ブランチを作成することで、複数の開発者がそれぞれ異なる機能やバグ修正に同時に取り組んだり、メインの安定した状態を壊すことなく、新しい機能を追加したり、大きな変更を加えたりすることができます。
ブランチ戦略について
Gitのブランチを効果的に使うためには、チーム全体で共通のルールが必要です。これが「ブランチ戦略」と呼ばれるものです。ここで大切なことは、必ずしも絶対的なブランチ戦略はなく、プロジェクトの性質に応じて適切なブランチ戦略を選択することです。
ここでは、代表的な4つの戦略の概要を見ていきましょう。
Git Flow
Git Flowは、最も歴史が長く複雑なブランチ戦略です。
main、developブランチを中心としてそれぞれの修正用ブランチとリリース用ブランチを運用します。
特に大規模なプロジェクトや、頻繁なリリースよりも安定性と厳密なバージョン管理が求められるプロジェクトに適しています。
Webアプリのような継続的デリバリーを想定したプロジェクトには適していません。
バージョン管理と安定したリリースが可能ですが、5種類 (main, hotfix, release, develop, feature) のブランチを運用するため、ブランチ管理が複雑で運用コストが高い点が特徴です。
- main: 本番環境にデプロイされている、常に安定したコードを保持するブランチです。ここに直接コミットすることは通常ありません。
- develop: 次期リリースに向けた開発の中心となるブランチです。featureブランチでの作業が完了すると、ここにマージされます。
- feature: 新機能開発のためのブランチです。developから分岐し、開発が完了するとdevelopにマージされます。
- release: リリース準備のためのブランチです。developから分岐し、最終的なバグ修正やリリースバージョン情報の更新が行われます。完了後、mainとdevelopの両方にマージされます。
- hotfix: 本番環境で発見された緊急のバグ修正のためのブランチです。mainから分岐し、修正が完了するとmainとdevelopの両方にマージされます。
Image Source:https://nvie.com/posts/a-successful-git-branching-model/
GitHub Flow
GitHub Flowは、非常にシンプルで軽量なブランチ戦略です。継続的デリバリーを重視し、頻繁なデプロイが行われるWebサービスやSaaS開発に広く採用されています。
main, featureの2種類のブランチで運用します。基本的に main ブランチのみをメインとし、このブランチは「常にデプロイ可能」な状態を保ちます。すべての開発はmainから派生した新しいfeatureブランチで行われ、プルリクエスト(GitLabでのマージリクエスト)を通じてmainに統合されます。
シンプルで理解しやすく、運用が容易で、CI/CDとも相性が良いですが、複数のリリースを管理する場合には適していない点が特徴です。
GitLab Flow
GitLab Flowは、Git Flowの厳密さとGitHub Flowのシンプルさを組み合わせたブランチ戦略です。GitHub Flowをベースにしつつ、環境ごとのデプロイや長期安定版の管理を考慮しており、特にGitLabのCI/CDやDevOps機能との統合を前提としています。
main, featureの2種類のブランチ運用を基本としつつ環境用のブランチを適宜導入して運用します。main ブランチを常にデプロイ可能にするのはGitHub Flowと同じですが、必要に応じて pre-production や production といった環境ブランチを導入し、段階的なデプロイを管理できます。これにより、ステージング環境でのテストを経て本番にデプロイするといったワークフローをブランチレベルで表現できます。
比較的シンプルな構造のまま複数のリリースを管理できますが、Git Flowほどの厳密なリリース管理には向いていない点が特徴です。
トランクベース開発
トランクベース開発は、チーム全員が1つのメインブランチ(trunkやmain)を中心に開発を進め、各自の変更を小さな単位で頻繁にメインブランチへマージするGitのブランチ戦略です。
この手法では、長期間存在する開発用ブランチや大規模なマージ作業を避け、「短命なブランチ」または直接のコミットを用い、1日1回〜数日に1回のペースでメインブランチに変更を統合します。これにより、コンフリクト(競合)の発生を抑止しやすく、常にリリース可能な安定したmainブランチ状態を保つことができます。
常に安定したメインブランチを維持でき、マージコンフリクトやリリース遅延のリスクが低いですが、長期ブランチや大規模リリースの運用には向いていない点が特徴です。
Trunk-Based Development For Smaller Teams:
Scaled Trunk-Based Development:
Image Source:https://trunkbaseddevelopment.com/
一般に大規模かつ安定性を重視する場合は、運用するブランチを増やして対応する戦略が推奨され、小規模や頻繁なデプロイが行われる場合は、運用するブランチを少なくシンプルに対応する戦略が推奨されます。
チーム開発での課題
チーム開発では、複数の開発者が同じファイルを同時に変更することが頻繁にあります。その際に発生するのが「コンフリクト(競合)」です。Gitは賢いツールですが、同じファイルの同じ行を異なる方法で変更した場合など、どちらの変更を採用すべきか自動で判断できない場合にコンフリクトが発生します。
コンフリクトの例
図は、同じ起点から分岐したブランチAとブランチBで、同じファイル(index.html)をそれぞれ変更していることを示しています。この2つのブランチをmainブランチ(青い線)にマージしようとすると、Gitはどの変更を最終的に採用すべきか判断できず、コンフリクトが発生します。
チーム開発の実例
それでは、具体的なシナリオでGitHub Flowを使ったチーム開発を見ていきましょう。コンフリクトの実例と解決も行っていきます。
第4回のブログの手順を実施後、すでにgit cloneでローカルにリポジトリを取得していることを前提とします。
複数メンバーによる競合発生
GitHub Flowでは、mainブランチは常に安定しており、デプロイ可能な状態を保ちます。そのため、機能開発やバグ修正は必ずmainから新しいブランチを切って行います。
コンフリクトを起こすための2つのブランチを作成します。
ブランチA (feature/add-comment)での作業
mainブランチから新しいブランチfeature/add-commentを作成し、README.mdファイルにコメントを追加します。ブランチを作成する際は、git checkoutコマンドを実行します。-bオプションをつけることで、作成したブランチにそのまま移動します。
$ git checkout -b feature/add-comment
Switched to a new branch 'feature/add-comment'
README.mdを開き、以下の内容を追記します。
# プロジェクト概要
このプロジェクトはGitLabの学習用です。
// Aさんが追加したコメント
コミットとプッシュを行います。
$ git add README.md
$ git commit -m "feat: Add a comment in README"
$ git push -u origin feature/add-comment
ブランチB (feature/update-description)での作業
次に、mainブランチに戻り、別の新しいブランチfeature/update-descriptionを作成します。こちらも同じREADME.mdファイルの同じ箇所に別の変更を加えます。
mainブランチに戻ります。
$ git checkout main
Switched to branch 'main'
Your branch is up to date with 'origin/main'.
ブランチの作成と切り替えを行います。
$ git checkout -b feature/update-description
Switched to a new branch 'feature/update-description'
README.mdを開き、以下の内容に追記します。
# プロジェクト概要
このプロジェクトはGitLabの学習用です。
// Bさんが追加したコメント
コミットとプッシュを行います。
$ git add README.md
$ git commit -m "feat: Update project description"
$ git push -u origin feature/update-description
これで、mainブランチを起点に、同じファイルの同じ行を異なる内容で変更した2つのブランチができました。
マージリクエスト(プルリクエスト)作成
GitLabのWeb UI上で、feature/add-commentブランチの変更をmainに統合するためのマージリクエスト (MR) を作成します。
GitLabでMRを作成します。メニューの「マージリクエスト」から「新しいマージリクエスト」をクリックします。
ソースブランチとターゲットブランチを選択し、「ブランチを比較して続行する」をクリックして、feature/add-commentからmainへのMRを作成します。
「作成 merge request」をクリックしてMRを作成します。
GitLabでは、マージが完了したfeatureブランチを自動的に削除するオプションがあります。基本的にマージ後は不要になるブランチなので、積極的に削除してブランチリストをきれいに保ちましょう。ローカルブランチも git branch -d <ブランチ名>
で削除できます。
mainにマージします。
この時点ではまだコンフリクトは起きていません。問題なくマージできるので、「マージ」をクリックしてマージを完了させます。
マージできたことを確認します。
次に、feature/update-descriptionブランチの変更をmainに統合するためのMRを作成します。
ソースブランチとターゲットブランチを選択し、「ブランチを比較して続行する」をクリックして、feature/update-descriptionからmainへのMRを作成します。
「作成 merge request」をクリックしてMRを作成します。
コンフリクトの発生を確認します。
MR作成後、GitLabの画面に「マージがブロックされました」というメッセージが表示されます。これは、feature/update-descriptionの変更と、すでにmainにマージされたfeature/add-commentの変更が同じ箇所を編集しているため、GitLabが自動でマージできないことを示しています。
コンフリクト解消
コンフリクトが発生したら、開発者が手動で解決する必要があります。
ローカルブランチに最新の変更を取り込みます。feature/update-descriptionブランチにmainブランチの最新の変更を取り込みます。
自分の作業ブランチにいることを確認します。
$ git checkout feature/update-description
Already on 'feature/update-description'
Your branch is up to date with 'origin/feature/update-description'.
mainブランチの最新を取り込みます。
git pullを実行すると、コンフリクトが発生したことがコマンドラインに表示されます。
$ git pull origin main
Auto-merging README.md
CONFLICT (content): Merge conflict in README.md
Automatic merge failed; fix conflicts and then commit the result.
手動でファイルを修正します。
README.mdをエディタで開き、Gitが挿入した競合マーカーを確認します。
# プロジェクト概要
このプロジェクトはGitLabの学習用です。
<<<<<<< HEAD
// Bさんが追加したコメント
=======
// Aさんが追加したコメント
>>>>>>> main
<<<<<<< HEADから=======までが現在のブランチ(feature/update-description)での変更、=======から>>>>>>> mainまでがmainブランチでの変更です。今回は両方のコメントを残すことにしましょう。マーカーを削除し、以下のように修正します。
# プロジェクト概要
このプロジェクトはGitLabの学習用です。
// Bさんが追加したコメント
// Aさんが追加したコメント
コンフリクト解消のコミットとプッシュを行います。
修正が完了したら、変更をコミットし、リモートにプッシュします。
$ git add README.md
$ git commit -m "Fix: Resolve conflict in README.md"
$ git push origin feature/update-description
マージ
ローカルでコンフリクトが解決され、リモートにプッシュされると、GitLabのMR画面の「マージがブロックされました」というメッセージが消えます。レビューが完了したら、安心して「マージ」をクリックできます。
このように、コンフリクトは複数のブランチが同じ箇所を同時に変更した際に発生しますが、git pull
で最新の変更を取り込み、競合マーカーを参考に手動で修正することで、簡単に解決できます。
今回はシンプルなテキストにおけるコメントの重複でしたが、チーム開発の現場においてはソースコード内での処理など複雑なコンフリクトが発生する場合があります。そのような場合、コンフリクトは発生してから解決するよりも、事前に避けることの方が重要です。コンフリクトを未然に防ぐために大切なことは、作業を始める前や、ブランチをプッシュする前に、必ず最新の変更をメインブランチから取り込むことです。
たとえば、git pull --rebase
というコマンドを使うと、リモートの最新コミットを基に自分のコミットを再適用し、履歴をきれいに保ちながらコンフリクトを最小限に抑えられます。こうした習慣を身につけることで、チーム開発はよりスムーズになり、余計なトラブルを減らすことができます。
コンフリクト解消の基本プロセスを習得することで、チーム開発でのトラブルを恐れず、安心して開発を進められるようになりますが、解決にかかる時間を減らすためにも、日頃からこまめに最新の変更を取り込むことを心がけましょう。
まとめ
今回の記事では、Gitを使ったチーム開発の要である「ブランチ」について深く掘り下げました。GitHub Flowを前提としたチーム開発の流れを実践し、さらにはコンフリクト(競合)の解消方法まで解説しました。
次回は、GitLabの主な利用機能とGitHubとの違いを解説していきます。
参考文献
https://nvie.com/posts/a-successful-git-branching-model/
https://docs.github.com/ja/get-started/using-github/github-flow
https://about.gitlab.com/ja-jp/topics/version-control/what-is-gitlab-flow/
https://trunkbaseddevelopment.com/