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
コマンドを実行してマージを続行しましょう。
ここまでの操作を実際に行うと、次の動画のようになります。