Git 再入門 リモートリポジトリを使った作業
この文書のゴールは以下になります:
- 別の場所にある git リポジトリ(リモートリポジトリ)と連携する方法について再学習します。
- はじめに git remote, git fetch, git merge を使ってリモートリポジトリの内容を手元のリポジトリ(ローカルリポジトリ)に取り込む方法について学習します。
- 次に git fetch, git merge を使った一連の作業を自動的に行なってくれる git pull について学習します。
- 次に git push を使ってローカルリポジトリの内容をリモートリポジトリに書き出す方法について 学習します。
- 最後に, トラッキングブランチ (tracking branch) について理解し、 git pull, git push の引数を省略した際のデフォルトの挙動のルールについて学びます。
目次
対象とする人
- 入門本でリモート, push, pull について読んだけど何が「プッシュ」されてなにが「プル」されるのか、よく分からなかった人。
- 2.5 Git の基本 - リモートでの作業 や 3.5 Git のブランチ機能 - リモートブランチ を読んで何となく分かったけれど、理解を再確認したい人。
git pull
とgit push
を使って 1 人で github を使ってるけど、実はgit pull
,push
が何をしているのかよく理解していない人。git pull/push
とfetch/merge
の関係を理解していない人git pull/push
で 「fast-forward ナンタラカンタラ」とかエラーが出てもそれが何なの分かっていない人。 それの解決方法 (例えば git fetch && git merge origin/master)が何故それでうまくいくのかきちんと説明できない人git push/pull
の引数が省略された場合のデフォルトの動作を厳密に説明出来ない人 「リモートトラッキングブランチ」と「トラキングブランチ」がそれぞれ何であるか説明できない人- というか少し前の自分のことです...
下準備
アリスとボブが 2 人で自分たちの部門の紹介ページを作ろうとしてるとしています。アリスは
git リポジトリーを作ってその上で部門の紹介ページの作成を開始しました。
まずはアリスになったつもりでリポジトリーを作成して、紹介ページを作ってコミットしましょう
(/tmp/my/workingdir
は好きなディレクトリに置き換えて下さい)。
$ cd /tmp/my/workingdir
$ mkdir alice
$ cd alice
$ git init
Initialized empty Git repository in /tmp/my/workingdir/alice/.git/
これでリポジトリができました。まず自分の紹介ページを作ってコミットしましょう。
$ echo "I’m Alice." >> alice.txt
$ git add alice.txt
$ git commit -m "Added alice.txt"
[master (root-commit) 8f6e1d0] Added alice.txt
1 file changed, 1 insertion(+)
create mode 100644 alice.txt
次にメンバー一覧ページも作ってコミットします。
$ echo "Alice <alice.txt>" >> members.txt
$ git add members.txt
$ git commit -m "Added Alice to members.txt"
[master 9e73b53] Added Alice to members.txt
1 file changed, 1 insertion(+)
create mode 100644 members.txt
アリスが紹介ページを作り始めたのを知らずに、ボブも紹介ページを手元で作りはじめてしまいました。今度はボブになったつもりでリポジトリの作成とコミットを行います。
$ cd /tmp/my/workingdir
$ mkdir bob
$ cd bob
$ git init
Initialized empty Git repository in /tmp/my/workingdir/alice/.git/
$ echo "I’m Bob." > bob.txt
$ git add bob.txt
$ git commit -m "Added bob.txt"
[master (root-commit) 527df0c] Added bob.txt
1 file changed, 1 insertion(+)
create mode 100644 bob.txt
$ echo "Bob <bob.txt>" > members.txt
$ git add members.txt
$ git commit -m "Added Bob to members.txt"
[master f3bc482] Added Bob to members.txt
1 file changed, 1 insertion(+)
create mode 100644 members.txt
アリスとボブがそれぞれ手元で紹介ページをある程度作ったところで、アリスとボブはお互いが別々の場所で紹介ページの作成をしていたことに気が付きます。アリスとボブはそれぞれが手元で作ったコミットを相手と共有しなければなりません。こうしたケースに対処するために
git では「リモート」という仕組みと、git fetch
, pull
, push
といったコマンドが用意されています。
リモート (Remote)
git では別の場所にあるリポジトリ(以下リモートリポジトリ)を手元のリポジトリ(以下ローカルリポジトリ)と連携させることができます。 リモートリポジトリとローカルリポジトリの連携により、リモートリポジトリにあるコミットをローカルリポジトリに持ってきたり、 逆にローカルリポジトリで行ったコミットをリモートリポジトリに書き出すことができます。
リモートリポジトリの登録と読み込み
リモートリポジトリ
と連携するために、まずローカルリポジトリにリモートリポジトリを登録する必要があります。
リモートリポジトリの登録には git remote add
を利用します。
git remote add <name> <URL>
現在ローカルリポジトリに登録されているリモートリポジトリの一覧は
git remote
で確認することができます。 各 remote
の、例えば URL などの、詳細な情報 は git remote show <remote>
で確認できます。
リモートリポジトリにあるコミットを読み込んでみましょう。リモートリポジトリにあるコミットは
git fetch <remote>
でローカルリポジトリにロードすることができます。
git fetch
でコミットをロードすると、リモートリポジトリ上のブランチに対応する「リモートトラキングブランチ」という特殊なブランチが自動的に作成されます(ブランチはコミットに対するポインタであることに注意して下さい)。
リモートトラッキングブランチの名前は <remote>/<branch>
という名前になります。現在のリポジトリにあるリモートトラッキングブランチの一覧は
git branch -r
で確認できます。
アリスとボブのケースに戻りましょう。ボブはアリスが紹介ページを自分の作っているものにマージすることにしました。 ボブはアリスのリポジトリー上のコミットを取り込むために、まずアリスのリポジトリを自分のリポジトリに「リモートリポジトリ」として登録します。
$ cd /tmp/my/workingdir/bob
$ git remote add alice /tmp/my/workingdir/alice
$ git remote
alice
$ git remote show alice
* remote alice
Fetch URL: /tmp/my/workingdir/alice
Push URL: /tmp/my/workingdir/alice
HEAD branch: master
Remote branch:
master new (next fetch will store in remotes/alice)
Local ref configured for 'git push':
master pushes to master (local out of date)
次にアリスのリポジトリをフェッチします。
$ git fetch alice
warning: no common commits
remote: Counting objects: 6, done.
remote: Compressing objects: 100% (3/3), done.
remote: Total 6 (delta 0), reused 0 (delta 0)
Unpacking objects: 100% (6/6), done.
From /tmp/my/workingdir/alice
* [new branch] master -> alice/master
$ git branch -r
alice/master
bob のリポジトリの状態を図を書くと次のようになります。 リポジトリ alice
からコミットがコピーされており、master ブランチの代わりに alice/master
という「リモートトラッキングブランチ」が作成されていることに注意して下さい。
リモートトラキングブランチを扱う
これで git remote add
と git fetch
で別の場所にあるリポジトリを登録し、そこからコミットを読み込むことができました。
その際にリモートリポジトリ上のブランチに対応する <remote>/<branch>
という名前の「リモートトラキングブランチ」が自動的に作られることが分かりました。
「リモートトラッキングブランチ」(e.g. origin/master
) は通常のブランチ
(e.g. master
) や HEAD
, HEAD^
, コミットのハッシュ ID
(198bc17aade
),
タグと同様に手元のリポジトリに保存されているコミットに対するポインタです。
そのため前章で学んだような git checkout
, git merge
, git branch
のようなコマンドはリモートトラッキングブランチに対しても変わらず利用できます。
git log alice/master
でボブはリモートリポジトリ(アリスのリポジトリ)上の master
ブランチのログを見ることができます。
git checkout alice/master
とするとボブはアリスのリポジトリの master
ブランチの最新の状態を再現することができます (ただし
You are in 'detached HEAD' state.
と言われてこのままでは編集作業ができないことに注意)。
リモートトラッキングブランチとマージする
次にリモートトラッキングブランチ(リモートリポジトリ上のブランチ)をローカルのブランチにマージしてみましょう。 これはローカルでブランチをマージした場合と全く同じようにできます。
ボブはアリスの master ブランチを自分の master ブランチにマージすることにしました。 マージしようとしている 2 つのコミットに共通する先祖が存在しない点が、ローカルのみで作業していた場合と少し違いますが、 これはファイルが何もない「初期状態」が共通の親として暗黙的存在すると考えれば特に問題はないかと思います。
$ git checkout master
$ git merge alice/master
Auto-merging members.txt
CONFLICT (add/add): Merge conflict in members.txt
Automatic merge failed; fix conflicts and then commit the result.
members.txt はアリスとボブ両方のリポジトリで編集されていたのでコンフリクトが発生します。 適当にコンフリクトを解消します。このあたりの作業もローカルのブランチをマージする場合となにも変わりません。
$ emacs members.txt # 適当なエディタで衝突を解消
$ git add members.txt
$ git commit
[master 5de7b36] Merge remote-tracking branch 'alice/master'
これで無事、ボブはアリスが行った変更を自分のリポジトリに取り込むことができました。ボブのリポジトリの状態は図で書くと以下のようになります
git pull
今度はアリスの立場になってボブのリポジトリ上の master
ブランチをローカルリポジトリの master ブランチにマージしましょう。
ボブがアリスのコミットを読み込んだ時と同じようにまずはリモートリポジトリを
git remote add
で登録します。 続いて、git fetch と git merge
を実行すれば前述したようにボブのリポジトリ上の master ブランチをローカルリポジトリの
master ブランチにマージすることが出来ます。 この fetch → merge
という一連の処理は非常に頻繁に行われるので、これを一度に行う git pull
というコマンドが用意されています。
$ git pull <remote> <src>
このコマンドを実行すると git fetch <remote>
→
git merge <remote>/<src>
を実行した場合と同じようにリモートリポジトリ
<remote> 上の <src> ブランチが
現在のブランチにマージされます(厳密には git fetch <remote> <src> →
git merge FETCHED_HEAD)。
$ cd /tmp/my/workingdir/alice
$ git remote add bob /tmp/my/workingdir/bob
$ git pull bob master
remote: Counting objects: 11, done.
remote: Compressing objects: 100% (5/5), done.
remote: Total 9 (delta 0), reused 0 (delta 0)
Unpacking objects: 100% (9/9), done.
From /tmp/my/workingdir/bob
* branch master -> FETCH_HEAD
* [new branch] master -> bob/master
Updating 9e73b53..5de7b36
Fast-forward
bob.txt | 1 +
members.txt | 1 +
2 files changed, 2 insertions(+)
create mode 100644 bob.txt
また後述するように git pull
は引数を省略することができるので、日常の業務で利用するには git fetch
と merge
をばらばらに実行するよりも便利です。
ここまでのまとめ
- 別の場所にある git リポジトリ(リモートリポジトリ)と連携するための仕組みとして git には「リモート」というものが用意されています。
- リモートリポジトリと連携するには、まずローカルリポジトリに登録するリモートリポジトリを登録します。これには
git remote add <name> <url>
を利用します。 - リモートリポジトリ上のコミットを
git fetch
でロードすることができます。 この際、リモートリポジトリ上のブランチに対応する「リモートトラッキングブランチ」が自動的に作成されます。 リモートトラッキングブランチの名前は<remote>/<branch>
(e.g.alice/master
) となります。 - git fetch
で取り込んだ後は、リモートトラッキングブランチという特殊なブランチがある以外は単一のリポジトリで作業している場合全く同じように作業できる。
リモートリポジトリ上で行われたコミットを手元のブランチに取り込むには
git merge
を行えば良い。 git fetch
→git merge
の一連の流れはgit pull
コマンドで行うことができる。
git push
ここまで紹介した方法で、異なる場所にあるリポジトリを連携させることができるようになりました。 しかし今まで紹介した、お互いのリポジトリーからコミットを読み込んでマージするという方法は
- アリスのコンピュータの電源が落ちているなどの理由でアリスのリポジトリにアクセス出来ないと共同作業が出来ない。
- チャーリーが作業に参加するとアリスは、ボブとチャーリのリポジトリそれぞれからコミットを git pull しなければなりません。 更に参加者が増えると読み込むリポジトリが参加人数に比例して増えてしまい破綻してしまいます。
のような問題があり実際に日常の業務で使うのには現実的でありません。
この問題を解決する git
の運用方法は複数存在しますが、1 つの単純な解決方法は共有のリポジトリを 1 つ用意して作業に参加する人が各自のローカルレポジドリに共有リポジトリからコミットを読み込み、ローカルレポジドリに作成したコミットを共有リポジトリに書き込むことです。
これを実現するには今まで述べてきたリモートリポジトリからコミットを読み込む方法に加えて、リモートリポジトリに対して手元のコミットを書き込む方法も必要になります。
このコミットをリモートリポジトリに書き込むための手段が git push
です。
共有用のリポジトリを作る
まず共有用のリポジトリを作成しましょう。各々のユーザがコミットの書き込み
(git push
) を行うリポジトリは 「裸」のリポジトリ(bare
repository)
である必要があります。 bare repository は作業ディレクトリのない(.git
ディレクトリのみ存在する) git リポジトリです。 bare リポジトリは git
init に --bare
オプションをつけて実行することで作成できます。
あるいは、 git clone で既存のリポジトリを複製する際に --bare
オプションを付けることで 既存のリポジトリを bare
リポジトリとして複製することも出来ます。 また bare
リポジトリのディレクトリ名には .git
を末尾につけるのが慣習です。
$ cd /tmp/my/workingdir
$ mkdir shared.git
$ cd shared.git
$ git init --bare
Initialized empty Git repository in /private/tmp/my/workingdir/shared.git/
ls
で shared.git
の中身を確認してみると、通常のリポジトリの .git
ディレクトリの中身が保存されていることが分かります。
$ ls
HEAD branches config description hooks info objects refs
git push でコミットを書き込む
次にアリスの立場に手元のコミットを共有リポジトリに書き出してみましょう。まずは共有リポジトリをリモートとして登録します。
$ cd /tmp/my/workingdir/alice
$ git remote add shared /tmp/my/workingdir/shared.git
次に shared に対してコミットを書き込みます。コミットの書き込みには git push を利用します。
$ git push <remote> <src>:<dst>
このコマンドを実行するとローカルリポジトリの <src> が指すコミットとその先祖のコミットを <remote> に対して書き込み、 同時に <remote> の <dst> が <src> と同じコミットを指すように変更します。
- <dst> ブランチが存在しない場合は <dst> ブランチが <remote> に作成されます。
- <dst> ブランチがすでに存在する場合には <dst> を <src> まで fast-forward します。fast-forward できない場合 (<dst> が <src> の祖先でない場合)には git push は 失敗 します。
アリスのリポジトリから共有リポジトリに対して、master ブランチを書き込んでみましょう
$ git push shared master:master
ounting objects: 15, done.
Delta compression using up to 8 threads.
Compressing objects: 100% (8/8), done.
Writing objects: 100% (15/15), 1.32 KiB | 0 bytes/s, done.
Total 15 (delta 0), reused 0 (delta 0)
To /tmp/my/workingdir/shared.git
* [new branch] master -> master
これでアリスの master ブランチ(の指すコミットとその全ての先祖コミット)が shared に対して書き込まれ、shared に master ブランチが作成されます。図にすると以下な状態になります。
続いて新しいコミットを作ってそれも shared に push してみましょう。
$ echo "Email: alice@example.com" >> alice.txt
$ git commit -a -m "Added an email addr to alice.txt"
[master c671712] Added an email addr to alice.txt
1 file changed, 1 insertion(+)
$ git push shared master:master
Counting objects: 5, done.
Delta compression using up to 8 threads.
Compressing objects: 100% (2/2), done.
Writing objects: 100% (3/3), 357 bytes | 0 bytes/s, done.
Total 3 (delta 0), reused 0 (delta 0)
To /tmp/my/workingdir/shared.git
77c39e3..c671712 master -> master
push (fast-forward) に失敗した場合の対処法
次にボブの立場になって共有リポジトリに新しいコミットを push
してみましょう。 まず共有リポジトリを git remote add
で登録します
$ cd /tmp/my/workingdir/bo
$ git remote add shared /tmp/my/workingdir/shared
続いて新しいコミットを作成します。
$ echo "Email: bob@example.com" >> bob.txt
$ git commit -a -m "Added an email addr to bob.txt"
[master 99af642] Added an email addr to bob.txt
1 file changed, 1 insertion(+)
これを git push
してみましょう。そうするとエラーが発生してしまいます。
$ git push shared master:master
To /tmp/my/workingdir/shared.git
! [rejected] master -> master (fetch first)
error: failed to push some refs to '/tmp/my/workingdir/shared.git'
hint: Updates were rejected because the remote contains work that you do
hint: not have locally. This is usually caused by another repository pushing
hint: to the same ref. You may want to first integrate the remote changes
hint: (e.g., 'git pull ...') before pushing again.
hint: See the 'Note about fast-forwards' in 'git push --help' for details.
前述したように git push <remote> <src>:<dst>
は <remote>
上の
<dst>
(今回は shared
上の master
) を <src>
(今回は bob
の
master
) が指しているコミットまで fast-forward します。 fast-forward
出来ない場合は失敗してしまいます。
fast-forward を可能にして push
を行うためには、ローカルの master
ブランチを shared
の master
とマージしてあげる必要があります。
前述したように git pull
でマージしましょう (あるいは git fetch
と
git merge
でもよい)。
$ git pull shared master
Merge made by the 'recursive' strategy.
alice.txt | 1 +
1 file changed, 1 insertion(+)
bob のリポジトリは図のような状態になります。
これで shared/master
が bob の master
の先祖になったので、 shared
の master
を bob の master
に fast-forward できるようになりました。
もう一度プッシュを行うと今度は成功するはずです。
$ git push shared master:master
Counting objects: 8, done.
Delta compression using up to 8 threads.
Compressing objects: 100% (4/4), done.
Writing objects: 100% (5/5), 684 bytes | 0 bytes/s, done.
Total 5 (delta 0), reused 0 (delta 0)
To /tmp/my/workingdir/shared
c671712..14245e3 master -> master
リモートリポジトリを使って作業を行うのに最低限必要な知識は以上です。 あとはトラッキングブランチ、アップストリーム、そして git pull/push の引数を省略した場合に何が起こるかについて理解していれば、 リモートレポジトリを使う上で困ることはあまり無いかと思います。 それらについては
を参考にして下さい。