GitとGitHubを用いたワークフロー
現代のチーム開発では、通常Gitが用いられます。Gitの概念は複雑ですが、チーム開発で起こる様々な状況に適切に対処するためには、ある程度の理解が必要となります。この節では、Gitの思想を解説したうえで、GitHubを用いてチーム開発を行う手法を示します。
コミットが記録される仕組み
Gitの節では、Gitのコミットに一意のIDが割り当てられることを説明しました。実は、コミットIDは、次の情報から計算可能です。つまり、次の情報が完全に一致しているのであれば、どのような環境でコミットを行なっても同じコミットIDが割り当てられます。逆に、次の情報のうち一つでも異なるものがあれば、全く違う コミットIDが割り当てられます。
- すべてのファイルやディレクトリの名前
- コミットの作成者の名前やメールアドレス
- コミットが作成された日時
- コミットメッセージ
- 親コミット (ひとつ前のコミット) のID
これらの情報の中に、リポジトリは含まれていません。コミットは、リポジトリとは独立して存在するものなのです。
また、注目したいのは、コミットの情報の中に親コミットのIDが含まれているところです。つまり、歴史の流れの方向と、コミットグラフの参照の方向は逆向きになります。この性質により、一度作成されたコミットはその後の変更に影響を受けません。
ブランチとHEAD
ブランチは、ソースコードへの変更の枝分かれを扱うための仕組みです。ブランチはリポジトリの中に存在し、コミットを指し示します。
各リポジトリには、HEADと呼ばれる、現在実際にディレクトリに表れている状態を表す特殊なポインタがあります。作業中のブランチがある場合、HEADはそのブランチを指し示します。git init コマンドによりリポジトリを新しく作成した場合、HEADは自動的に master ブランチを指すように設定されます。
HEADが master ブランチを指している状態で、コミットを行った際に起こる変化を表したのが次の図です。直前まで master ブランチが指していたコミット 2ce3d099 を親とする新しいコミット cee8a14f が作成され、ブランチ master が指し示す先は新しく作成されたコミットに変更されます。
別のブランチで作業する
新しいブランチを作成する場合には、git checkout -b コマンドを実行します。次の図は、HEADがコミット 2ce3d099 を指している状態で、git checkout -b feature を実行した例です。直前までHEADが指していたコミットを指し示すブランチ feature が作成され、HEADが指し示す先も新しいブランチに変更されます。
この状態で新しいコミット cee8a14f を作成すると、feature ブランチが指し示す先のみが新しいコミットに変更されます。
ここで git checkout master を実行すると、HEADの指し示す先のみが master ブランチに変更され、ディレクトリ内のファイルはコミット 2ce3d099 のものに戻ります。
このまま、さらにコミット bfaaf878 を追加します。これにより、コミットグラフの枝分かれが生じます。これが、複数人で同時に開発が行われている状態です。
ここまでの操作を実際に行った様子が次の動画で確認できます。
枝分かれしたブランチをマージする
git merge コマンドを用いると、現在のブランチに他のブランチの変更を取り込むことができます。次の例では、HEADが master ブランチにある状態で、git merge feature を実行することで feature ブランチを master ブランチにマージしています。
このマージを実行すると、bfaaf878 と cee8a14f の2つの親を持つマージコミット d021150b が生成され、2つのブランチ両方で行われた変更を含むコミットとなります。マージコミットのコミットメッセージは自分で指定することもできますが、Git側で用意してくれる標準のメッセージ (この例では Merge branch 'feature') をそのまま用いても良いでしょう。
コンフリクト
git merge コマンドが実行されると、Gitはまずコミットグラフ上の共通の祖先を探します。例えば、コミットグラフが次のような状態であるとき、Gitは master ブランチと feature ブランチの共通の祖先であるコミット 2ce3d099 を起点とした変更を取得します。
<li>吾輩は猫である</li>
<li>坊っちゃん</li>
<li>吾輩は猫である</li>
<li>坊っちゃん</li>
<li>三四郎</li>
<li>吾輩は猫である</li>
<li>坊っちゃん</li>
<li>こころ</li>
この例の場合、共通の祖先に対して master は <li>三四郎</li> が、feature は <li>こころ</li> が同じ場所に追加されています。この状態で git merge feature を実行すると、Gitはコンフリクトを報告し、マージを中断します。コンフリクトが発生したファイルには、Gitにより自動的に <<<<<<< や =======、>>>>>>> といったコンフリクトマーカーが挿入されます。
<li>吾輩は猫である</li>
<li>坊っちゃん</li>
<<<<<<< HEAD
<li>三四郎</li>
=======
<li>こころ</li>
>>>>>>> feature
コンフリクトを解決するには、ファイルを編集してコンフリクトマーカーを削除する必要があります。全てのコンフリクトに対応できたら、コンフリクトしたファイルをステージし、git merge --continue コマンドを実行してマージを続行しましょう。
ここまでの操作を実際に行うと、次の動画のようになります。