minimize

事業拡大のため、新しい仲間を募集しています。
→詳しくはこちら

複数の開発者でリポジトリを使う

さて、ここからはいよいよ分散型バージョン管理の神髄です。
A さん一人で作業していましたが手が回らないので、B さんがプロジェクトに追加されました。

まず最初に B さんは A さんのリポジトリの完全なコピーを受け取ります。
今回は説明を簡単にするために、A さんと B さんは同一のマシン上の異なるディレクトリ上で作業することにします。

$ git clone /path/prj1 /other_path/bob_work
Cloning into 'bob_work'...
done.

これで、/other_path/bob_work が B さんの作業ディレクトリとなり
/other_path/bob_work/.git には B さん専用のリポジトリが構築されます。
もちろん、/other_path/bob_work/file1 には A さんが持っているのと全く同じファイルがあります。

この時点で、/path/prj1 と /other_path/bob_work は完全に同等です。
clone コマンドは、元のリポジトリの完全なコピーを作成します。
例えばこの時点で A さんのリポジトリが死んでしまっても、B さんは何の支障も無く
作業を続けることができます。

唯一、B さんのリポジトリは A さんのリポジトリに無い情報を持っています。
それは「このリポジトリの元(original)は A さんのリポジトリである」という情報です。

$ git config -l
remote.origin.url=/path/prj1

このように、環境変数に設定されています。
これは後々便利なことがあります。

繰り返しになりますが、仮に A さんのリポジトリが死んでしまったらこの情報は
有効ではなくなってしまいますが、それでも B さんの作業に何ら支障はありません。
ただし、紛らわしさを防ぐためにもそのときにはこの環境変数の値を消しておいた方がいいでしょう。

pull

では、B さんでファイルを作ったり変更したりしてみます。

/other_path/bob_work (master)
$ echo "bob" > file_created_bob
$ echo "bob" >> file1
$ git add .
$ git commit -m "bob first commit"
[master 3746707] bob first commit
 2 files changed, 2 insertions(+), 0 deletions(-)
 create mode 100644 file_created_bob

コミットが完了しました。さて、これで今回の B さんの作業は終わりです。
では A さんがこの作業内容を取り込むことにします。
Aさんの作業ディレクトリ上で、git pull コマンドを使います。

/path/prj1 (master)
$ git pull /other_path/bob_work
remote: Counting objects: 6, done.
remote: Compressing objects: 100% (3/3), done.
remote: Total 4 (delta 1), reused 0 (delta 0)
Unpacking objects: 100% (4/4), done.
From /other_path/bob_work
 * branch            HEAD       -> FETCH_HEAD
Updating 22626a1..3746707
Fast-forward
 file1            |    1 +
 file_created_bob |    1 +
 2 files changed, 2 insertions(+), 0 deletions(-)
 create mode 100644 file_created_bob

さて、何やら色々処理が走りましたね。
git pull は、以下のことを行っています。

  1. リモートブランチ(今回ならば /other_path/bob_work)から変更内容を取得

  2. その変更内容を現在のブランチにマージ

git merge と似ています。
違うのは、マージする対象が リモートブランチであるという点です。
言い方がリモートブランチなのでわかりにくいかもしれませんが、要はリモートリポジトリのことを指しています。

自分のローカルリポジトリ以外は全て、リモートリポジトリという扱いになります。
今回の例では同一マシンの別ディレクトリですが、もちろん別マシンであることもありますし
ファイルではない HTTP などのプロトコルを使うこともできます。
ここら辺は SVN と同じですね。

git merge は、自分のリポジトリ内にある別のブランチから現在のブランチに変更をマージします。

git pull は、他人のリポジトリ内にあるブランチから現在のブランチに変更をマージします。
今回は B さんの master ブランチからマージしましたが、もし B さんの exp ブランチからマージしたとしても同じです。
いずれにせよ、A さんのブランチは B さんのいかなるブランチとも異なるものであることに変わりはないのですから。
( A.master ≠ B.exp であり、A.master ≠ B.master)

fetch

上のメッセージに FETCH_HEAD という見慣れない表現があります。
これは何でしょう。

B さんがファイルをコミットした段階(まだ A さんが pull していない状態)で
A さん側で以下のコマンドを実行してみましょう。

/path/prj1 (master)
$ git fetch ../bob_work
remote: Counting objects: 5, done.
remote: Compressing objects: 100% (3/3), done.
remote: Total 3 (delta 1), reused 0 (delta 0)
Unpacking objects: 100% (3/3), done.
From ../bob_work
 * branch            HEAD       -> FETCH_HEAD

git fetch は、リモートブランチから変更内容を受け取るコマンドです。
ただし受け取った内容はそのままでは何も見えません。
git diff とやっても、(Aさんがローカルで何も変更していなければ)何も表示されません。
おさらいになりますが、git diff は「前回コミットした内容」と「ローカルで変更した内容」を比較します。
どちらも自分のリポジトリ内で閉じている内容ですから、リモートブランチの内容とは関係がありません。

ここで、先ほどの FETCH_HEAD を使います。

/path/prj1 (master)
$ git diff HEAD..FETCH_HEAD
diff --git a/file1 b/file1
index 9cdaf07..fab69dc 100644
--- a/file1
+++ b/file1
@@ -1,4 +1,4 @@
-hello world master
+hello world master bob
 hello world2
 hello world3
 hello world exp exp

今度は、差分が表示されました。
上のコマンドは「Aさんが前回コミットした内容」と「Bさんがコミットした内容」を比較しています。
git fetch コマンドで取得したリモートブランチの変更内容を示すキーワードが FETCH_HEAD なのです。

一方、HEAD は自分のリポジトリ内に保持された「前回コミットした内容」ということになります。
言い換えれば、FETCH_HEAD はリモートブランチ上での「前回コミットした内容」ということになります。

もし A さんがローカル内でファイルを変更していた場合は、以下のようになります。

git diff

「Aさんが前回コミットした内容」と「Aさんがローカルで変更した内容」の差分

git diff HEAD..FETCH_HEAD

「Aさんが前回コミットした内容」と「Bさんが前回コミットした内容」の差分

FETCH_HEAD には、B さんが変更中のファイルは含まれません。
(ファイルがステージに上がっているかいないかに関わらず)
つまり、B さんがローカルで変更した内容はそれをコミットするまでは
他人の目に触れることはありません。

また、fetch コマンドで取得した内容は A さんのローカルリポジトリ上に保持されます。
ですから、例えば fetch した後に B さんが2回目のコミットをして
その後 A さんが FETCH_HEAD を使って diff を取ったとしても
そのとき比較する内容はあくまで「fetch コマンドによって取得した時点での内容」となります。

B さんが2回目にコミットした内容と比較したければ、再度 fetch コマンドを実行する必要があります。
リモートブランチの内容は一旦 fetch しなければわかりません。
大きなプロジェクトの場合、fetch の処理は時間が掛かることがあります。

少し戻って、pull は単純に言えば fetch + merge です。

もし、リモートリポジトリの内容をマージしようとしたらどうなるでしょうか。

$ git merge /other_path/bob_work
fatal: '/other_path/bob_work' does not point to a commit

このようにエラーになります。
リモートリポジトリの内容はそのままではマージできません。
しかし、一度 fetch していればその内容をマージすることは可能です。

$ git merge FETCH_HEAD

先ほども言ったように、ここでマージする内容は B さんの最新コミット内容ではなく
「前回 fetch したときのコミット内容」となります。

最後に。
BさんがAさんの変更内容を取り込みたい場合は以下のようにします。

$ git pull

取り込み先のURLを指定していません。
先ほど、「Bさんは自分のリポジトリの元(original)がAさんだとわかっている」と書きました。
この場合、取り込み先を省略することができます。

リモートリポジトリの登録

先ほどの例で、AさんはBさんのリポジトリから今後頻繁に fetch, pull をすることになるかもしれません。
その度に

$ git fetch /other_path/bob_work

などとやるのは面倒です。
Git は、よく使うリモートリポジトリを登録してエイリアス(別名)を付けることができます。

$ git remote add bob /other_path/bob_work

/other_path/bob_work というリモートリポジトリに bob というエイリアスを付けました。
これで、Aさんは以下のようにしてエイリアスを利用できます。

$ git fetch bob
From ../bob_work
   ebe3162..5af370d  master     -> bob/master

エイリアスを利用して取得したフェッチ結果は、FETCH_HEAD ではなく別の場所に格納されます。
上のメッセージを見るとわかりますね。bob/master です。
自分の最終コミットとフェッチ結果を比較するコマンドは以下になります。

$ git diff HEAD..bob/master

これを利用すると、複数のリモートリポジトリから同時にフェッチ結果を取得して
比較したりマージしたりすることが可能になります。