複数リポジトリが関係してくると、ブランチがなんだかとても分かりにくくなります。
マニュアルや解説を見ても何か分かったような、分からないような感じがすることが多いです。
それは、主に2つの誤解と、リモートがらみのブランチの意味や機能や名前の混乱のせいです。
(と言うか、私が、最初わからなくて、後で「あっ!そういう事か!」と分かった事ですが。。。いまだに誤解していなければいいけど。。。)
誤解の1つ目は、プッシュやプルを実行すると、何か「魔法の同期」が行われて、リポジトリ全体が同じ状態になるのだ、という誤解です。
実際にプルとプッシュとフェッチの行うことは、リモートとローカルの、指定した(基本的には)1つのブランチを「マージ」する、と言うだけの事です。
ただこの「指定した」の指定が、知らぬ間に行われているもんで、なにか「魔法」の様に感じるだけなのです。
極論を言うと、プッシュもプルもフェッチも、みんなどれもただのマージです。
普通の(ローカルの)マージとの違いは相手がリモートだって言うだけの事です。
リポジトリが「同期される」と言うことはありません。指定したブランチがマージされるだけで、それ以外のブランチは、違うままなんです。
誤解の2つ目は、同じ名前のブランチは、リポジトリ間で「共有」されている、という誤解です。別のリポジトリのブランチは、たとえ同じ名前であったとしても、全く別のブランチです。たとえばどのリポジトリも、masterと言うブランチを持っていますが、本来は、全く無関係の、単にたまたま名前が同じだけの、別のブランチです。だから、あなたがローカルで作ったブランチはプッシュしなければ誰も知らない、あなただけのブランチです。いつの間にかリモートにも入っていたなんて事はありません。
Gitの練習で最初に使うことになるmasterブランチは、プッシュやプルでリモートに送ったり、逆に取り込んだりが簡単に出来るので、リポジトリ間で同じ名前のブランチに何か特別の関係があるように見えますが、実はこれは、単に「ブランチ間のマージ」をしているだけなのです。ただ、デフォルトで特別な設定がされているので、いかにも連携しているように見えるのです。そのため、最初みんな(少なくとも私は)「同じ名前のブランチは、リポジトリ間で共有されている」と誤解してしまうのです。
「リモートがらみのブランチの意味や機能や名前の混乱」とは何かと言うと、これがなかなか言葉では説明しづらい上に、名前がどうも統一されていない、と言うか、色々調べたんですが、ちゃんと名前がついていないような感じなんです。
では、順に説明していきましょう。次のような共通リポジトリがあったとします。
XSystem.git リポジトリ
F <-- develop
/
@ -- A -- D <-- *master
\
B -- C -- E <-- test
これをIchiroの下にクローンすると、
Ichiro/XSystem リポジトリ remotes/origin -> XSystem.git
F <-- origin/develop
/
@ -- A -- D <-- *master origin/master
\
B -- C -- E <-- origin/test
この様に、XSystem.gitがoriginと言う名前でリモートリポジトリとして登録されているリポジトリが出来ます。
XSystem.gitからすべてのコミットがコピーされ、masterブランチはクローン元と同じ状態になります。masterは特別扱いなんですね。
また、リモートリポジトリの全部のブランチが、そのままでは無くて、origin/master, origin/test, origin/test と、orign/... と言う名前で出来ています。このブランチは、リモートリポジトリのブランチのローカルコピーみたいなもので、originと情報交換(フェッチ等)するとその時点の最新の状態に更新されます。
このブランチの名前がどうもはっきりしないんです。ネット上の情報では、リモートブランチと言ったり、追跡ブランチと言ったり、リモート追跡ブランチと言ったり。Git Guiでは、トラッキング・ブランチと言ってますね。
Git Guiを良く使うので、われわれはこれをトラッキング・ブランチと呼ぶことにしましょう。
このトラッキング・ブランチは、チェックアウトして更新したり、コミットしたりしてはいけない特別なブランチです。
リモートリポジトリのブランチの内容を取得するのだけが目的のブランチだからです。
チェックアウトやコミットはやろうと思えば出来ますが、Git Gui では、「本とにいいの?やばいよ!」的な確認のメッセージが表示されます。
しかし誤解しないでほしいのですが、これはブランチが特殊なだけであって、コミットは、全く普通のコミットです。ローカルブランチのコミットや、あなたがローカルで行ったコミットとなんら変わりはありません。
またコミットは一度記録されたら、内容が変更されることはありません。
リベースなどで変更されているように見えることがありますが、それは新しいコミットが出来ているだけです。
では、この状態で、Ichiro以外の誰かが、XSystem.gitのmasterに新しいコミットを一つプッシュしたとしましょう。すると、こんな感じになります。
XSystem.git リポジトリ
F <-- develop
/
@ -- A -- D ― G <-- *master
\
B -- C -- E <-- test
Ichiro/XSystem リポジトリ
F <-- origin/develop
/
@ -- A -- D <-- *master origin/master
\
B -- C -- E <-- origin/test
コミットGが増えました。当然ですが、Ichiroのリポジトリには変化がありません。これを一郎がフェッチしたら。。
Ichiro/XSystem リポジトリ
F <-- origin/develop
/
@ -- A -- D <-- *master
\ \
\ G <-- origin/master
\
B -- C -- E <-- origin/test
あっ、origin/masterが一つ進んだけど、masterは元のままだ!。。これをorigin/masterに合わせるには、masterをチェックアウトした状態でプルするか、origin/master をマージします。今回はマージしてみます。すると、
Ichiro/XSystem リポジトリ
F <-- origin/develop
/
@ -- A -- D -- G <-- *master origin/master
\
B -- C -- E <-- origin/test
originと同じ状態になりました。masterをチェックアウトした状態(既にその状態です)で、ためしにプッシュしてみましょう。
Everything up-to-dateと表示されました。最新の状態でしたので何もしませんでした、って事です。先ほどのフェッチの時にプルすれば、一気にこの状態になります。でも前にも言いましたが、プルするときは気をつけないと、期待しないマージコミットが出来てしまうので注意しましょう。
次にIchiryoは、新しい機能をorigin/develop で作成する様指示されました。
とはいっても、origin/developは更新してはいけないブランチなので、その場合は、
origin/developからローカルなブランチを作成して作業します。Git Gui のメニューから、ブランチ/作成… を選び、「ブランチを作成」ダイアログで、ブランチ名のところは、「トラッキング・ブランチ名を合わせる」をチェックし、初期リビジョンのところで、トラッキングブランチをチェックし、origin/develop を選択し「作成」を押します。するとこうなります。
Ichiro/XSystem リポジトリ
F <-- *develop origin/develop
/
@ -- A -- D -- G <-- master origin/master
\
B -- C -- E <-- origin/test
ローカルな普通のブランチ develop が出来て、そのdevelopがチェックアウトされました。
このdevelopブランチがどういう状態かと言うと、origin/developと連携し、結果、リモートリポジトリorigin中のdevelopとも連携しています。
masterと同じ状態になりました。このブランチ上でプッシュやプルを行うと対応するリモートのdevelopとやり取りするようになるのです。
連携していない普通のブランチ上でプルすると、「連携していないから駄目」とおこられます。連携していない普通のブランチをGit Guiでプッシュすると、サーバー上に同じ名前のブランチが新たに作られ、ローカルにはorigin/ブランチ名 が作られます。しかし、これだけではトラッキング(リモートと連携)はしていません。(オプションを設定すると出来るみたいですが、未確認です。)
また、連携していない普通のブランチ上でGit Bashでオプションなしで git push とすると、masterブランチがプッシュされるみたいです(未確認)。
では、トラッキングしていると言うのはどう言うことかと言うと、まず設定としては、.git\configに、下記のような設定が登録されている事です。
[remote "origin"]
fetch = +refs/heads/*:refs/remotes/origin/*
url = 共用リポジトリへのパス
[branch "master"]
remote = origin
merge = refs/heads/master
[branch "develop"]
remote = origin
merge = refs/heads/develop
[remote .. の行は、urlで指定された共用リポジトリを"origin"として登録してあり、originをフェッチしたときは、fetchの指定の通り、すべてのブランチをトラッキング・ブランチとしてローカルに、origin/.. の形式で作る、と言う設定です(詳しくはどこかで説明、、しないかも。)。[barnch .. の行は、このブランチが、origin中の指定ブランチと連携するよ、と言う設定です。
上記のうち、[remote "origin"]と、[branch "master"]は、クローンしたときに自動的に作成されます。[branch "develop"] は、origin/develop からブランチを作成したときに作成されました。
では連携って言うのはどんな動作なのかと言うと、そのブランチをチェックアウトした状態で、Git Bash で、git pull と入力すると、対応するブランチがマージされます。連携していない場合git pullは「連携して無いよ」と言うエラーになります。(pushは良くわからない。。もう少し調査しよう)
ちょっと話が変わりますが、Git GuiやTortowiseGit でpushやpullを実行したとき、どんなオプションで実行しているのかが分かりにくいですね。
あまり考えなくても、うまくいくようにカバーしてくれてはいるようですが。
Git Bashで、直接コマンドを打ったほうが実感しやすいです。
で、話を戻します。今までに出てきたローカルのブランチは下記の4種類、と言うか4パターンになりました。
- ローカルにだけあるブランチ : 連携していない
- ローカルにありリモートにプッシュしたブランチ : 連携していない
- トラッキング・ブランチから作成したブランチ : 連携している
- トラッキング・ブランチ : リモートリポジトリ上のブランチのコピー(更新不可)
でも、これらにどうも適切な名前が付けられて無いみたいなんですよね。
特にトラッキングブランチについては、前も言いましたけど、リモートブランチなんて書いてあるサイトもありました。
名前がついていないか統一されていない概念っていうのは、説明がややこしいし、読む人が混乱するんですよね。
と言うわけで、統一した名前が無いので、何とか概念を理解してください。
こういうところでGitって難しい、と思われちゃうんじゃあないかなあ。
では、ここから先ほどの例にIchiroが2つコミットしたとして、その状態をoriginと比べてみましょう。
XSystem.git リポジトリ
F <-- develop
/
@ -- A -- D ― G <-- *master
\
B -- C -- E <-- test
Ichiro/XSystem リポジトリ
H -- I <-- *develop
/
F <-- origin/develop
/
@ -- A -- D -- G <-- master origin/master
\
B -- C -- E <-- origin/test
だいぶややこしい図になってきましたね。ゆっくり眺めて理解してくださいね。
これで、プッシュすると、こうなります。
XSystem.git リポジトリ
F ― H -- I <-- develop
/
@ -- A -- D ― G <-- *master
\
B -- C -- E <-- test
Ichiro/XSystem リポジトリ
F -- H -- I <-- *develop origin/develop
/
@ -- A -- D -- G <-- master origin/master
\
B -- C -- E <-- origin/test
いかがですか、ここまで理解できましたでしょうか?ところでプッシュですが、前もお話したとおり、これもマージの一種です。
指定したローカルブランチを指定したリモートリポジトリ上のブランチにマージして、最後にフェッチ(この順序は未確認)しているんです。
しかし、プッシュの時は、ファストフォワードでのマージしか(デフォルトでは)許可されていません。
もしファストフォワード出来ないときは、「ファストフォワードできねえよ!」と拒否されます。
逆に言うと、ファストフォワードできる状態にまでローカルで調整してからプッシュしないといけない、という事です。でもそりゃそうですよね。共有リポジトリに送るのは、普通はきれいになった段階ですから、この時点でマージやコンフリクトが起きて、またテストしなきゃっていうのは、へんですよね。