Git ワークフローの比較: 知っておくべきこと
現在、Git は最も普及しているバージョン管理システムです。Git ワークフローは、Git を使用して一貫性のある生産的な方法で作業を行うためのレシピや推奨事項のようなものです。Git ワークフローによって、開発者や DevOps チームは一貫性を持って Git を効果的に活用できます。Git では、ユーザーは変更を柔軟に管理できます。Git は柔軟性を重視しているため、Git を操作するための標準化されたプロセスはありません。Git で管理するプロジェクトにチームで取り組む場合は、変更をどのように適用するのかについてチームで完全に合意することが重要です。チームの認識を統一するには、合意した Git ワークフローを作成または選択する必要があります。公開されている Git ワークフローの中に、チームに適したものがあるかもしれません。ここでは、このような Git ワークフロー オプションをいくつかご紹介します。
利用可能なワークフローがいくつもあるために、Git を開発現場に組み込むに当たって何から着手すべきかの判断が難しくなることがあります。このページでは、ソフトウェアチーム向けの一般的な Git ワークフローを概観し、その着手点を明らかにします。
読み進めると分かることですが、ここで示すワークフローは厳密な規則ではなく、ガイドラインとして作成したものです。ここでは利用可能なワークフロー例を示しますので、各種ワークフローの様々な要素を組み合わせてお客様のニーズに合わせて利用してください。
優れた Git ワークフローとは
チームのためのワークフローを評価する場合、チームの文化を考慮することがとても大切です。必要なのは、チームの効率性を高めるワークフローです。生産性を制限する重荷となるものではありません。Git ワークフローを評価する際に検討すべき点をいくつかご紹介します。
- このワークフローはチームの拡大に対応できるか。
- このワークフローは間違いやエラーを簡単に元に戻せるか。
- このワークフローは、チームにとって新たな認識を必要とする、不要な負担とならないか。
集中化ワークフロー
集中化ワークフローは、SVN からの移行に適した Git ワークフローです。Subversion の場合と同様に、集中化ワークフローではプロジェクトにおけるすべての変更の単一の入力箇所として中央リポジトリを使用します。デフォルトの開発用ブランチは trunk
ではなく main
と呼ばれ、すべての変更がこのブランチにコミットされます。集中化ワークフローでは main
以外のブランチは不要です。
分散型バージョン管理システムへの移行は困難なタスクとなる場合がありますが、Git を有効活用するに当たってワークフローを変更する必要はありません。Subversion を利用していたときと全く同じ方法でプロジェクトを開発できます。
しかも、Git を利用して開発ワークフローを強化することにより、SVN に勝る点がいくつか生まれます。第一は、すべての開発者がそれぞれプロジェクト全体のローカルコピーを保有する点です。この独立した開発環境により、各々の開発者は他の開発者がプロジェクトに加えた変更とは無関係に作業を進めることが可能です。
第二は、Git の強力なブランチおよびマージ モデルを利用できる点です。SVN とは異なり、Git におけるブランチは、コードの統合やリポジトリ間での変更の共有に対し、フェイルセーフ メカニズムが機能するように設計されています。集中化ワークフローは、ホストされているリモートのサーバーサイド リポジトリを使ってプッシュおよびプルを行うという点で、他のワークフローと似ています。他のワークフローと比較すると、集中化ワークフローには定義済みのプル リクエストやフォーク パターンがありません。一般に集中化ワークフローは、SVN から Git に移行するチームや小規模なチームに適しています。
関連資料
高度な Git ログ
ソリューションを見る
Bitbucket Cloud での Git の使用方法についてのチュートリアルです。
仕組み
開発作業は中央リポジトリをクローンすることから始まります。それによって作成された中央リポジトリの作業コピー上で SVN の場合と同様にファイルを編集し、変更のコミットを行いますが、新たなコミットはローカルに保存され、中央リポジトリとは完全に独立しています。これにより、開発者は区切りがつくまで中央リポジトリとの同期を遅延させることができます。
中央リポジトリに対して変更を公開する場合は、ローカルの main
ブランチを中央リポジトリに「プッシュ」します。これは、中央リポジトリの main
ブランチに存在しないすべてのローカル コミットを追加するという点を除けば、svn commit
と同等の機能です。
中央リポジトリの作成
最初に、誰かがサーバー上に中央リポジトリを作成しなければなりません。新規プロジェクトの場合は誰がそれを実行しても構いません。既存プロジェクトの場合は、既存の Git または SVN リポジトリをインポートする必要があります。
中央リポジトリは必ずベアリポジトリでなければならず (作業ディレクトリがあってはなりません)、そのようなベアリポジトリは次のようにして作成します:
ssh user@host git init --bare /path/to/repo.git
ここで、user
には有効な SSH ユーザー名を、host
にはサーバーのドメイン名か IP アドレスを、/path/to/repo.git
にはリポジトリの保存場所のパスを間違いなく入力します。なお、そのリポジトリがベアリポジトリであることを表すために、習慣的に .git
拡張子を付加することに留意してください。
ホスト型中央リポジトリ
中央リポジトリはよく、Bitbucket Cloud のようなサードパーティの Git ホスティング・サービスを通して作成されます。上述したベア・リポジトリの作成プロセスは、ホスティング・サービスが処理します。その後、ホスティング・サービスは、ローカル・リポジトリからアクセスできる中央リポジトリのアドレスを提供します。
中央リポジトリのクローン作成
次に、各々の開発者が中央リポジトリ全体の作業コピー(ローカル・リポジトリ)を作成します。これには、git clone コマンドを使用します。
git clone ssh://user@host/path/to/repo.git
リポジトリをクローンすると、Git は開発者が以降その「親」リポジトリとの通信を行うものと想定してそれをポイント バックする origin
という名称のショートカットを自動作成します。
変更とコミットの実行
リポジトリがローカルにクローンされると、編集、ステージ、コミットなど通常の Git でのコミット操作を使って変更を加えることができます。ステージになじみの薄い方のために説明を付け加えますが、ステージとは作業ディレクトリ内の変更の一部を取り出してそれをコミットする準備をすることです。このステージという機能により、多数のローカルな変更があったとしても焦点の明確なコミットを行うことができます。
git status # View the state of the repo
git add <some-file> # Stage a file
git commit # Commit a file</some-file>
既に説明したように、これらのコマンドはローカルなコミットを行うものであり、従って John は中央リポジトリで起こっていることを気にすることなくコミットを何度でも繰り返すことができます。これは、作業を単純で小規模な部分に分割する必要のある大規模フィーチャーの場合に特に有用な機能です。
新しいコミットを中央リポジトリにプッシュ
ローカルリポジトリに新しい変更をコミットしたら、それらの変更をプッシュして、同じプロジェクトに携わる他の開発者と共有する必要があります。
git push origin main
このコマンドは、新たにコミットされた変更を中央リポジトリにプッシュします。中央リポジトリに変更をプッシュすると、そのプッシュによる更新と競合するコードが含まれている、それまでにプッシュされた他の開発者による更新を実行できるようになります。Git は、この競合を示すメッセージを表示します。この場合、まず git pull
を実行する必要があります。この競合については、次のセクションでさらに詳しく説明します。
競合の管理
中央リポジトリはプロジェクトを公式に代表するものであり、そのコミット履歴は大切に扱わなければならず、また安易に改変してはなりません。開発者のローカルなコミット履歴が中央リポジトリと分岐状態にある場合は、中央リポジトリに誤書き込みを起こす可能性があるため、変更のプッシュは Git によって拒否されます。
この場合開発者は、フィーチャーを公開する前に最新の中央リポジトリをフェッチしてその先端にローカルな変更をリベースする必要があります。この操作は、「私の変更作業は皆が変更を完了したものをベースとして行いたい」と言うに等しいものです。その結果、履歴は従来の SVN ワークフローの場合と同様に完全に直線的になります。
ローカルな変更が中央リポジトリと直接競合する場合、Git はリベースを中止して手作業で競合を解決するように促します。Git の優れている点は、コミットの作成に使用する git status
と git add
の 2 つのコマンドをマージ時の競合解決の際にも使用できることにあります。このことにより、新たに加わった開発者でも容易にマージを管理できます。さらに、トラブルが発生した場合はリベースを完全に中止して再試行 (またはサポートの依頼) を簡単に行うことができます。
例
典型的な小規模チームがこのワークフローを利用してコラボレーションする方法の一般的な例をご紹介します。ここでは、2 人の開発者、John と Mary が別々のフィーチャーで作業を行い、中央リポジトリを経由してその成果を共有する場合を考えます。
John がフィーチャー開発作業を開始します
John は、このローカルリポジトリ上で、編集、ステージ、コミットなど通常の Git でのコミット操作を駆使してフィーチャー開発を行います。
既に説明したように、これらのコマンドはローカルなコミットを行うものであり、従って John は中央リポジトリで起こっていることを気にすることなくコミットを何度でも繰り返すことができます。
Mary もフィーチャー開発作業を開始します
一方、Mary も自分のローカルリポジトリにおいて、同じく編集、ステージ、コミットなどの操作を駆使してフィーチャー開発を始めます。John と同様に、Mary も中央リポジトリで起こっていることを気にする必要はありませんが、すべてのローカルリポジトリは非公開であるため John がローカルリポジトリで作業している内容に関しても全く気にする必要はありません。
John がフィーチャーを公開します
John がフィーチャー開発を完了すると、他のチーム・メンバーがアクセスできるようにローカルなコミットを中央リポジトリに公開します。これは、次のように git push コマンドを使用して行います。
git push origin main
既に説明したように、origin
は John が中央リポジトリをクローンしたときに Git が生成したリモート接続の名称です。main
引数は、origin
の main
ブランチをローカルの main
ブランチに一致させるように指示しています。今の場合、中央リポジトリでは John がクローンしてから何も更新されていないため、プッシュ操作を行っても競合は発生せずに正常に完了します。
Mary がフィーチャーの公開を試みます
John がローカルな変更を首尾よく中央リポジトリに公開した後で Mary がフィーチャーのプッシュを試みるとどうなるでしょうか。全く同様にプッシュコマンドを使用することはできます:
git push origin main
しかしながら、Mary のローカルリポジトリが中央リポジトリと分岐状態にあるため、プッシュリクエストは拒否され、長文のエラーメッセージが表示されます:
error: failed to push some refs to '/path/to/repo.git'
hint: Updates were rejected because the tip of your current branch is behind
hint: its remote counterpart. Merge the remote changes (e.g. 'git pull')
hint: before pushing again.
hint: See the 'Note about fast-forwards' in 'git push --help' for details.
この機能により、Mary が中央リポジトリに対して誤書き込みを起こすことが防止できます。Mary は、John による変更をローカルリポジトリにプルしてそれにローカルな変更を統合し、再度プッシュを試みなければなりません。
Mary が John のコミットの先端にリベースします
Mary は git pull コマンドを使用して中央リポジトリをプルします。このコマンドは svn update
コマンドに幾分似ていて、上流の中央リポジトリ全体を Mary のローカル・リポジトリにプルしてから、それをローカルなコミットとマージしようとします。
git pull --rebase origin main
--rebase
オプションは、main
ブランチを中央リポジトリの main と同期させてから、その先端に Mary のすべてのコミットを移動することを指示するもので、これを図に示すと次のようになります。
このフラグを指定しない場合もプルは可能ですが、その場合は中央リポジトリとの同期を行うたびに余計な「マージコミット」が発生します。このワークフローにおいては、三方向マージを行うのではなく常にリベースを行うことが推奨されます。
Mary がマージの競合を解決します
リベースでは、同期された main
ブランチにローカルなコミットをひとつずつ移動します。これによって、巨大なマージ コミットを作成してすべての競合を一度に解決するのではなく、コミット別にマージ時の競合を把握することが可能になります。即ち、ひとつのコミットは焦点が明確なものとなり、プロジェクト履歴もよく整理されたものとなります。さらに、バグが入り込んだ場所の発見や、必要に応じてプロジェクトへの影響を最小限に抑えつつ変更を展開できます。
Mary と John が作業しているフィーチャーが無関係なものであった場合は、リベースの際に競合が起こる可能性は小さいものと考えられます。それでも競合が起こった場合、リベース動作は処理中のコミットで停止され、次のようなメッセージと関連情報を表示します
CONFLICT (content): Merge conflict in <some-file>
Git の優れている点は、誰でも自分のリポジトリで発生したマージ時の競合を解決できることにあります。この例では、Mary は直ちに git status コマンドを実行して問題の所在を確認できます。競合の発生したファイルは、マージされていないパスのセクションに表示されます。
# Unmerged paths:
# (use "git reset HEAD <some-file>..." to unstage)
# (use "git add/rm <some-file>..." as appropriate to mark resolution)
#
# both modified: <some-file>
そこで、該当のファイルに必要な編集を加えます。編集が完了したら、通常の方法でコミットしてから残りに git rebase コマンドを実行します。
git add <some-file>
git rebase --continue
これがすべてです。Git の処理は次のコミットに移り、その後競合を発生するコミットがあれば同一の処理を繰り返します。
この時点で何がどうなっているのかわからなくなっても、焦らないでください。次のコマンドを実行すれば、最初の状態に戻ることができます。
git rebase --abort
Mary がフィーチャーの公開に成功しました
中央リポジトリとの同期が完了すると、Mary は変更を公開することができるようになります:
git push origin main
次のステップ
ここまで説明してきたように、少数の Git コマンドのみを使用して Subversion を利用した従来型の開発環境を踏襲することが可能です。これは、SVN からの移行過程にあるチームにとっては非常に有用ですが、これではGit の分散的特質を活用できません。
集中化ワークフローは、小規模なチームに適しています。上述の競合解決プロセスは、チームの規模が大きくなるとボトルネックになる場合があります。チームは集中化ワークフローに慣れ親しんでいるがコラボレーション作業の効率化が必要な場合は、フィーチャーブランチワークフローの採用を検討するべきです。このワークフローでは、各々のフィーチャーに独立した専用のブランチを割り当てることにより、変更を中央リポジトリに統合する前にそれに関する詳細な検討が可能となります。
その他の一般的なワークフロー
集中化ワークフローは基本的に他の Git ワークフローの構成要素です。広く使われている Git ワークフローには、個々の開発者がプッシュおよびプルする、ある種の集中化リポジトリが含まれています。その他の一般的な Git ワークフローについて、以下に簡単に説明します。これらの拡張されたワークフローは、フィーチャー開発、ホットフィックス、最終リリースのブランチ管理において、より専門化したパターンを提供します。
フィーチャー ブランチング
フィーチャー ブランチングは、集中化ワークフローを論理的に拡大したものです。フィーチャー ブランチ ワークフローの本旨は、すべてのフィーチャー開発を main
ブランチではなく専用のブランチ上で行うことにあります。このカプセル化によって、メインのコードベースに影響を与えることなく複数の開発者が別々のフィーチャー開発作業を行うことが可能になります。また、この手法によって main
ブランチに不良コードが入らなくなり、継続的なインテグレーションが頻繁に行なわれる環境においては大きなメリットとなります。
Gitflow ワークフロー
Gitflow ワークフローが最初に高く評価されたのは、nvie の Vincent Driessen 氏による 2010 年のブログ投稿でした。Gitflow ワークフローは、プロジェクトのリリースに関連した利用を想定して設計された厳密なブランチ モデルを定義します。このワークフローでは、フィーチャー ブランチ ワークフローに使用されているもの以外の概念や命令は不要です。その代わり、異なるブランチに対して個別にロールを割り当て、それらが相互に作用する手順や条件を定義します。
フォーク型ワークフロー
フォーク型ワークフローは、このチュートリアルで説明した他のワークフローとは根本的に異なるものです。「中央」コードベースとして機能する単一のサーバーサイド リポジトリを使うのではなく、各々の開発者にサーバーサイド リポジトリを割り当てます。つまり、各々の開発者は、1 つの Git リポジトリではなく、非公開のローカル リポジトリと公開されたサーバーサイド リポジトリという 2 つの Git リポジトリを保有することになります。
ガイドライン
すべてに適した Git ワークフローは存在しません。前に述べたとおり、チームの生産性が高まる Git ワークフローを作ることが大切です。ワークフローは、チームの文化だけでなくビジネス文化も補う必要があります。ブランチやタグのような Git の機能は、ビジネスのリリーススケジュールをサポートします。チームでタスク追跡プロジェクト管理ソフトウェアを使用しているなら、進行中のタスクに対応するブランチを活用できます。さらに、ワークフローを決める際に考慮すべきガイドラインを一部ご紹介しましょう。
ブランチの使用は短時間
ブランチがプロダクションブランチから分岐して時間が経てば経つほど、マージの競合やデプロイメントの問題が生じるリスクも高くなります。ブランチを短時間の使用に抑えることで、マージやデプロイメントが簡潔になります。
打ち消しを最小限に抑え、簡素化する
マージを打ち消す必要のないワークフローを作ることが重要です。たとえば、main
ブランチへのマージを許可する前にブランチをテストするワークフローを作成します。それでもアクシデントは発生するものですが、他のチーム メンバーの作業を妨げることなく、簡単に打ち消しを実行できるワークフローを作ることは有益です。
リリーススケジュールに合わせる
ワークフローは、ビジネスのソフトウェア開発リリース サイクルを補うものでなければなりません。1 日に何度もリリースする場合は、main
ブランチを安定した状態に保つ必要があります。一方、リリース スケジュールの頻度がそれほど高くなければ、Git タグを使用してブランチをバージョンにタグ付けすることをお勧めします。
要約
このドキュメントでは、Git ワークフローについて説明し、実用的な例を用いて集中化ワークフローを掘り下げました。集中化ワークフローについて詳しく説明しながら、その他の特別なワークフローについても説明しました。このドキュメントの重要なポイントは、以下のとおりです。
- すべてに適した Git ワークフローはない
- ワークフローはシンプルで、チームの生産性を高めるものである必要がある
- ビジネス要件が Git ワークフローの形成に役立つ必要がある
次の Git ワークフローについて読む前に、フィーチャーブランチワークフローの包括的な概要を確認してください。
この記事を共有する
次のトピック
おすすめコンテンツ
次のリソースをブックマークして、DevOps チームのタイプに関する詳細や、アトラシアンの DevOps についての継続的な更新をご覧ください。