最初に
なんとなくでも使用できるGitですが実はとても奥深く複雑な構造をしています。
そんなGitを使い始めた時ほぼ全員が思う「HEAD」とは何者なのか説明したいと思います。
また合わせて「Branchとは」「detached HEADとは」についても話します。
先に結論ファーストで話しますと
HEADとは
今自分が作業している場所を示すポインタ
Branchとは
開発の本流から分岐し、本流の開発を邪魔することなく作業を続ける機能
detached HEADとは
HEADがBranchを指していない状態のこと
そして僕自身以前までブランチとはなにか聞かれた場合、下図のようなイメージを持っていました。
そしてこれは誤った認識です。
この記事はMake IT アドベントカレンダー9日目の記事として寄稿しています!
明日は @wh1tecat が投稿する予定です。
楽しみですね(笑)
Branchとは
まずGitのブランチというシステムについてお話します。
ブランチと言われてなにを想像しますか?
ブランチを説明するために図を使って説明したいと思います。
下図のGitでは左上が最初のコミットを示し、コミットを重ねるごとに下に向かって伸びています。左上のmasterブランチから始まり、途中でdevelopブランチ、またdevelopからfeat/aブランチに派生してる様子を示しています。
このGitでは3つのブランチを管理しています。
そして今あなたはmasterブランチで作業しています。
ではもしこのツリーを見た時『「master」「develop」「feat/a」のブランチはそれぞれどこですか?』と質問された場合、僕自身以前まで下図のように考えていました。そしてそれは間違った認識でした。
青枠で囲った中全体がmasterブランチであると認識していました。
また後述しますHEADも青い枠を指しているとなんとなく考えていました。
しかし、実際のブランチとは下図のようになります
この状況の結論を言うと
Gitにおけるブランチとは、単にコミットを指す軽量なポインタに過ぎません
そしてそれぞれ3つのブランチはある特定のコミットを示すポインタです
僕自身以前まではブランチとは派生元から最新の履歴までの枝全体、もしくはファイル群のようなものを指し示していると考えていましたがそれは誤っていました。
ではそれ以外のコミットはどこのブランチに所属しているのか?
そもそもコミットとはなんなのか
Gitはどの様にしてファイルの履歴を管理しているのか
結局HEADとは?
こうした疑問が出てきたところで次はGitの仕組みについて少し触れたいと思います。
Gitのファイル管理の仕組み
Gitをコミットするために必要なコマンドはおおよそ以下のものです。
1. /* ファイルを編集 */
2. git add -A
3. git commit -m "add my-files"
この時Git内部の流れは下のようになります
- ステージされたファイルのハッシュを計算
- ツリーオブジェクトが作成される
- ツリーオブジェクトにステージしたコードのスナップショットへのポインタが格納される
- git commit が実行されるとコミットオブジェクトを作成
- コミットオブジェクトにツリーオブジェクトへのポインタ、親コミットへのポインタ、作成者のメタデータなどが格納される
- ブランチのポインタを最新のコミットオブジェクトに進める
コミットが繰り返されるごとブランチは次のブランチに自動で移動します
合わせてHEADも移動します。
ステージングされたファイルのポインタがツリーオブジェクトにblobとして格納されます。
ポインタの先はステージされたファイルのスナップショットが格納されています。
コミットオブジェクトが作られる度ツリーオブジェクトのポインタが格納されます
コミットオブジェクトにはツリーオブジェクトへのポインタの他、親コミットのポインタ、コミッターのメタ情報、コミットメッセージなども合わせて格納されます。
そしてそれらのコミットオブジェクトが数珠つなぎとなることでGitのツリーは形成されています
このようにしてGitはコミットを作成し管理しています。
そしてGitにおけるブランチとは単にこれらのコミットを示す軽量なポインタでしかありません。
もし仮に新しいブランチが作られた場合は単に新しいポインタが作られるだけです。
ブランチとはコミットを示すポインタのことでファイルの履歴群や枝の様に見えるもの全体を指すわけではありません。
Gitはそれぞれのコミットの親となるコミットのハッシュを持つことでファイルの履歴を管理しています。
この親の値をたどることでファイルの変更履歴を確認しています。
一つのコミットが複数の親コミットを保つ場合もあります。マージした場合などが該当します。
また一つのコミットを複数のブランチが参照する時もあります。ブランチはポインタであるためブランチが被っていても特に害はありません。
HEADとは
Gitがどうやって今作業しているブランチを把握しているかご存知でしょうか。その時に使われるのがHEADです。
HEADの中身はこちらもポインタであり、Gitの中で特殊で唯一の存在です。
HEADがブランチを指すことで自分が現在いるブランチがどれか確認できる仕組みになっています
つまりコミットの場所を記憶しているのがブランチ、ブランチの場所を記憶しているのがHEADです
いわゆるポインタのポインタってやつです
もし、新しいコミットが発生した場合、自動的にブランチのポインタも進みます。HEADがブランチを指していいる場合HEADの値も合わせて追尾する仕組みになっています
GitはHEADが示しているブランチから自分が作業しているブランチを確認しています
因みに直近で自分が移動したHEADの位置を確認したい場合は下記のコマンドを実行します。
git reflog
またブランチのツリー図が見たい場合はSourceTreeやGitUPなどのGUIツールやtigなどを使用して確認するのが良いでしょう。
detached HEADとは
最後にdetached HEADとはなにか説明します。
detached HEADが起こることは稀ですが、rebase等を行った際に起こることがあります。
detached HEADとはなにか結論からいうと
HEADがブランチ以外のコミットのポインタを示している状態のことです。
先程HEADはブランチを示しているといいましたが、HEADはブランチ以外のなんでもないただのコミットを示すこともできます。
そうした状態をdetached HEADと呼びます。
例えば特定のコミット時のファイルの状態が確認したい時など敢えてHEADをブランチ以外に移動させたりなどを行います。
detached HEADになった場合の対処ですが、任意のブランチにHEADをチェックアウトさせれば、ブランチを参照しているのでdetached HEADではなくなります。
detached HEADの簡単な起こし方を紹介します
- git logでコミットを確認しブランチが示していないなんでもないコミットのハッシュを控えます
- 控えたハッシュ宛にチェックアウトします
- git branchで確認
git log
git checkout c215891f5b9b997ec4020c4539b773ef94c2449c
git branch
* (HEAD detached at c215891)
develop
feat/a
master
戻し方
- git branchで戻るブランチ名を確認
- 確認したブランチ宛にチェックアウト
git branch
* (HEAD detached at c215891)
develop
feat/a
master
git checkout develop
git branch
* develop
feat/a
master
detached HEADがいい状態か悪い状態か状況によりますのでなんとも言えませんが、意図せず発生してしまった場合はいい状態とは言えないでしょう。詳しい人に状況を説明して対処してもらうのがいいと思います。
終わりに
この記事の動機ですが、元々は数ヶ月前にファストフォワードとノンファストフォワードを理解するために調べ、ブランチについて知ったのがきっかけでした。しかしまとめる気力がなくまた今度まとめようと思うばかりでしたが、今回いい機会だと思いまとめたのがこの記事の動機になります。
GitのHEADについて分かっている風に説明しましたが、僕自身も分からないことが多く日々勉強の毎日です。
いつかGitマスターになれることを夢見て生きていきたいです。
精進したいお気持ち
参考