いきなりですが、git branch
コマンドを使わずにブランチを作ってみます。 ブランチは、とある一時点のコミットのSHA-1ハッシュを参照するポインターに過ぎません。 配管コマンドと呼ばれる、低次なサブコマンドを用いることで、簡単にブランチを操作することができます。 以下のような3つのログがある時、
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
$ git log --graph * commit 1234567890abcdef1234567890abcdef12345678 (HEAD -> master) | Author: ********@i-studio.co.jp | Date: Sun May 6 06:16:55 2018 +0900 | | third | * commit 1234567890abcdef1234567890abcdef87654321 | Author: ********@i-studio.co.jp | Date: Sun May 6 06:16:16 2018 +0900 | | second | * commit 1234567890abcdef1234567890abcdef01010101 Author: ********@i-studio.co.jp Date: Sun May 6 06:15:38 2018 +0900 first |
2番目のコミット1234567890abcdef1234567890abcdef87654321
を参照するブランチfeature
を作成するには下記のコマンドを実行することで可能です。
1 |
$ echo "1234567890abcdef1234567890abcdef87654321" > .git/refs/heads/feature |
つまり、ブランチを作成する、というのはrefsファイル(参照ファイル)を作成することと内部的には等価なのです。 ただ、refsファイルを直接操作するのは非推奨なので、git update-ref
コマンドを用いて以下のようにします。
1 |
$ git update-ref refs/heads/feature 1234567890abcdef1234567890abcdef87654321 |
これでfeature
が作成されました。
1 2 3 4 5 6 7 8 9 10 11 12 |
$ git log --graph feature * commit 1234567890abcdef1234567890abcdef87654321 (feature) | Author: ********@i-studio.co.jp | Date: Sun May 6 06:16:16 2018 +0900 | | second | * commit 1234567890abcdef1234567890abcdef01010101 Author: ********@i-studio.co.jp Date: Sun May 6 06:15:38 2018 +0900 first |
HEAD
git branch
コマンドに、起点となるコミットを渡さなかった場合、HEAD
がデフォルトの起点として選択され、HEAD
と同位置に新しいブランチが作成されます。 先ほどの例では、下記のコマンドを実行すると、master
と同位置に作成されます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
$ git branch test $ git log --graph * commit 1234567890abcdef1234567890abcdef12345678 (HEAD -> master, test) | Author: ********@i-studio.co.jp | Date: Sun May 6 06:16:55 2018 +0900 | | third | * commit 1234567890abcdef1234567890abcdef87654321(feature) | Author: ********@i-studio.co.jp | Date: Sun May 6 06:16:16 2018 +0900 | | second | * commit 1234567890abcdef1234567890abcdef01010101 Author: ********@i-studio.co.jp Date: Sun May 6 06:15:38 2018 +0900 first |
HEAD
は基本的に現在チェックアウトしているブランチに対するシンボリック参照という、特殊な存在で、直接的にSHA-1ハッシュを格納しているわけではありません。 実際にHEAD
ファイルを確認してみるとrefs/heads/master
を参照しています。
1 2 3 4 5 |
$ cat .git/HEAD ref: refs/heads/master $ cat .git/refs/heads/master 1234567890abcdef1234567890abcdef12345678 |
つまりrefs/heads/master
が1234567890abcdef1234567890abcdef12345678
を参照しているため、結果的にmaster
と同位置になったという訳です。 git checkout
コマンドを実行するとHEAD
のシンボリック参照が変化します。
1 2 3 4 5 |
$ git checkout feature Switched to branch 'feature' $ cat .git/HEAD ref: refs/heads/feature |
これをサブコマンドで実行するにはgit symbolic-ref
コマンドを使用します。 先ほど作ったtest
ブランチにHEAD
の参照先を移動させてみます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
$ git symbolic-ref HEAD refs/heads/test $ git log --graph * commit 1234567890abcdef1234567890abcdef12345678 (HEAD -> test, master) | Author: ********@i-studio.co.jp | Date: Sun May 6 06:16:55 2018 +0900 | | third | * commit 1234567890abcdef1234567890abcdef87654321 (feature) | Author: ********@i-studio.co.jp | Date: Sun May 6 06:16:16 2018 +0900 | | second | * commit 1234567890abcdef1234567890abcdef01010101 Author: ********@i-studio.co.jp Date: Sun May 6 06:15:38 2018 +0900 first $ cat .git/HEAD ref: refs/heads/test |
参照先をtest
に移動することができました。
HEAD
は必ずしもブランチに対するシンボリック参照とは限りません。 いわゆるdetached HEAD
の状態です。 その状態を再現してみます。 先ほどの例の1番目のコミットにHEAD
を移動してみます。
1 2 |
$ git symbolic-ref HEAD 1234567890abcdef1234567890abcdef01010101 fatal: Refusing to point HEAD outside of refs/ |
git symbolic-ref
コマンドではrefs
ファイル以外を参照先に選ぶことはできないので、直接書き換えます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
$ echo "1234567890abcdef1234567890abcdef01010101" > .git/HEAD $ git symbolic-ref HEAD fatal: ref HEAD is not a symbolic ref $ git log --graph * commit 1234567890abcdef1234567890abcdef01010101(HEAD) Author: ********@i-studio.co.jp Date: Sun May 6 06:15:38 2018 +0900 first $ cat .git/HEAD 1234567890abcdef1234567890abcdef01010101 |
HEAD
がシンボリック参照ではなくなったのでgit symbolic-ref
コマンドで確認できなくなりました。
ログを確認するとHEAD
が1234567890abcdef1234567890abcdef01010101のコミットを指していることがわかります。
最後に、コミットが実行されるとrefsファイルが、どのように変化するのか見てみます。 まず、master
をチェックアウトします。
1 2 3 4 5 6 7 8 |
$ git checkout master Switched to branch 'master' $ git symbolic-ref HEAD refs/heads/master $ cat .git/refs/heads/master 1234567890abcdef1234567890abcdef12345678 |
refs/heads/master
は1234567890abcdef1234567890abcdef12345678
を参照している状態です。 コミットを実行します。
1 2 3 4 5 6 7 8 9 10 11 |
$ echo "fourth" > fourth.txt $ git add fourth.txt $ git commit -m "fourth" [master 68d7d12] fourth 1 file changed, 1 insertion(+) create mode 100644 fourth.txt $ cat .git/refs/heads/master 1234567890abcdef1234567890abcdef99999999 |
コミットが実行されるとrefs/heads/master
が、今新たに追加した4番目のコミットのSHA-1ハッシュ1234567890abcdef1234567890abcdef99999999
を参照していることがわかります。 Gitは、コミットやリセットが実行され、参照すべきSHA-1ハッシュが変化するとrefs
ファイルを書き換えます。 結果的に、HEAD
が常に最後のコミットの位置を知ることができるようになるのです。
Gitの内部構造はとても面白いので、公式のGitの内側は、一度読んでみると、新たな発見があるかもしれません。