Gitは概念だ
そんなことを弊社の
神 が言ってました。
じゃあ概念理解しようってのがこの記事の目的なんですが、なんか世間に出回ってるGitの説明ってむずかしくないですか? 僕の頭なんてメロンパンみたいなのしか入ってないのでもっと簡単に言ってもらわないと困ります(逆切れ)
ということで、私がGitで一番大事だとおもうリファレンス(参照)について書いてみようと思います。
Gitはリファレンスがわかっていることを前提にした動作をするのが多々あるので、理解しておきましょう。
リビジョンとかブランチとかそういった簡単な単語は知ってる前提で進んでいくのでご了承ください。
概要
リファレンス(ref)とはリビジョンを指し示すものでラベルだとかポインタという言い方のほうがしっくりくるかもしれませんね(ここではリファレンスと言い続けますが)。
リファレンスは一つのリビジョンに紐づきます。とったりつけたりは自由自在です。
そして、Gitはこのリファレンスから遡れるリビジョンしか見せてくれません。
$ git log --all --oneline --graph --decorate=full * f762a9f (tag: refs/tags/tag5) 5 | * 1b364d7 (refs/heads/c) 4 |/ | * e2377a8 (refs/heads/b) 3 |/ | * 7e229a6 (refs/heads/a) 2 |/ * cfe5cc5 (HEAD -> refs/heads/master) 1
言い換えるとリファレンスから遡れないリビジョンは存在しても見えません。
Gitはこれを利用して(リファレンスを差し替えることで)リビジョンの履歴改変を実現しているんですね(以下イメージ図)。
ディスク領域をとって邪魔なので辿れないリビジョンを削除することを
ガベージコレクション
と呼びます
なんらかの操作をして、すでにあったリビジョンが消えたように見えても ガベージコレクションが行われる前であればローカルに残っているので救出できるという訳です。
で、救出するためのコマンドが git reflog
と呼ばれるコマンドで、
リファレンスのログ
を閲覧するコマンドです。
$ git reflog cfe5cc5 HEAD@{0}: checkout: moving from f762a9fa3b708ef7ca14a38d17be5df19dbb5811 to master f762a9f HEAD@{1}: checkout: moving from master to tag5 cfe5cc5 HEAD@{2}: reset: moving to HEAD^ f762a9f HEAD@{3}: commit: 5 cfe5cc5 HEAD@{4}: checkout: moving from c to master 1b364d7 HEAD@{5}: commit: 4 cfe5cc5 HEAD@{6}: checkout: moving from master to c cfe5cc5 HEAD@{7}: checkout: moving from b to master e2377a8 HEAD@{8}: commit: 3 cfe5cc5 HEAD@{9}: checkout: moving from master to b cfe5cc5 HEAD@{10}: checkout: moving from a to master 7e229a6 HEAD@{11}: commit: 2 cfe5cc5 HEAD@{12}: checkout: moving from master to a cfe5cc5 HEAD@{13}: commit (initial): 1
じゃあリファレンスって具体的に何なのかといえばそれは タグとブランチ です。
何が違うのか見ていきましょう。
タグ
タグは特定リビジョンにつける永続的なリファレンスです。
特定のリビジョンに対してリリースバージョン等、目印として利用するのが主目的となります。
ブランチ
バージョン管理システムにはブランチという欠かせない概念があり、もちろんGitにもブランチはあります。
Gitは前述したとおりブランチをリファレンスによって表現します。
ブランチというと分岐したすべてのリビジョンと捉えがちですが、 Gitにおけるブランチはリファレンスがつけられた一点(リビジョン)だけです。
そして、一つのリビジョンがもつリファレンスの数に制限はないため、 一つのリビジョンに複数のブランチが同居するという事態はよく発生します。
ブランチの種類は リモートブランチ
, リモート追跡ブランチ
, ローカルブランチ
に分けられます。 いずれもリファレンスであることに変わりはありません。
ローカルブランチ
は手元にクローンしたリポジトリが持つブランチです。説明不要ですね。
リモートブランチ
は、例えばGithub等のリモートに存在するブランチで手元のコミットグラフには表示されません。
リモート追跡ブランチ
私たちが見ているのは リモートブランチ
(リファレンス)の
写し ということになります。これがリモート追跡ブランチです。
ただし常にリモートサーバに接続しているわけじゃないので時間の経過とともにどんどんずれていきます。
同期するためのコマンドが fetch
や pull
です。
ちなみにリモート追跡ブランチはローカルブランチと同居することが正常な状態といえます。
例えば、ローカルブランチがリモート追跡ブランチよりも進んでいる場合 1
のように表示され、
遅れている場合は 2
と表示されます。(数字部分は差分なので可変)
ちなみにリモート追跡ブランチの名称は私たちが自由に設定することはできず
リモート名/ブランチ名
という書式になります。
で、リファレンスとしてのブランチとタグの違いは一言でいうと永続化を目的とするかどうかという点にあります。
私が言う永続化は以下の2つの意味を指しています。
リファレンスの追加削除
タグもブランチも追加削除可能ですし運用ルールによりますが、 基本的にタグは一度つけたらそのままで頻繁に取り外しされるようなものではありません。
逆にブランチは一部の永続ブランチを除いて、目的ごとにブランチを作成、目的を達したら削除するのが一般的です。
一部の永続ブランチというのも特に決まっているわけではなく、運用ルールによりますが、一番基本的なものはGitのデフォルトブランチであるmasterでしょう。
リファレンスの移動
Gitでは常に何らかのブランチに属していますが、その状態からコミットするとブランチの位置は最新のリビジョンに移ります。
内部的にはローカルブランチはheads/ディレクトリに入っています。先端を意味するので新しいリビジョンができたら移動するということですね。
対する タグ
はコミットで動きません。
外さない限りは同じリビジョンを見続けます。リベースしても動きません。
--force
PUSH/PULL/FETCH(リモートリポジトリへの変更送受信)はリモートリポジトリ、かつ、ブランチ単位で行います。
(--all
オプションですべて)
そしてリビジョンは親(前)リビジョンへのポインタを持っています。
リモートリポジトリがローカルからリビジョンを受け取るとき、そのリビジョンがリモート側のリビジョンのブランチとしてポインタをたどれる必要があります。
rebase
を行うとリファレンスも移動されるため、別のリビジョンとして判断されPUSHの際にエラーが発生します。
PUSHの際に --force
を設定することで受け取ったリビジョンを新たなブランチとして判断してくれます。
fast-forward
単にリファレンスを進めることで変更差分を取り込むマージ方法をfast-forwardといいます。
この方法だとマージコミットが発生しないためコミットグラフがきれいになりますが、
逆にマージしたという事実をコミットとして残したい場合は --no-ff
(non fast-forward) オプションをマージの際に指定する必要があります。
fast-forwardでマージできる条件は決まっています。
コミットグラフ的に言えば、マージ対象コミットから過去に遡った直線状に現コミットがいる場合です。
これがわかりにくい場合はリビジョンを集合として見れば現コミットの祖先リビジョン集合がマージ対象の祖先リビジョン集合にすべて含まれていると考えてみましょう。
上記の例だとmasterブランチはtopicブランチにもhotfixブランチにもfast-forward可能です。 要素を取り込んで同じ集合になるイメージですね。
逆にこの場合hotfixブランチからtopicブランチに、またはtopicブランチからhotfixブランチにfast-forwardマージはできません。
とりあえずこんなところで。
間違ってるところがあったら指摘ください。