taisablog

taisa's engineer blog

Git

Gitの参照 – HEADとheadsとtagsとremotes

投稿日:November 30, 2016 更新日:

Gitの参照についてまとめました。また別記事にてGitの内側について記載しています。

Gitの内側についておさらい

  • 「git init」すると「.git」ディレクトリができるがその中についてがテーマ
  • 普段は使わない配管コマンドと呼ばれる下位レベルのコマンドがある
  • 4つのオブジェクトがある
    • blobオブジェクト(ファイルに相当)
    • treeオブジェクト(ディレクトリに相当)
    • commitオブジェクト(commit情報を保持)
    • tagオブジェクト(Tagを保持)

Gitの参照って?

ここで取り上げるGitの参照は「HEAD」ファイルと「.git/refs」配下のことを示します。「git init」した直後「HEAD」ファイルは「refs/heads/master」を参照し(初期化直後はなにも存在しない)、「.git/refs」配下には、headsとtagsディレクトリがあります。ブランチにcommitするとheads配下にブランチ名のファイルができ、HEADファイルはheads配下のブランチ名を参照します。

# masterにcommit後
# HEADファイル
ref: refs/heads/master
# refs配下の構造
└── refs
    ├── heads
    │   └── master
    └── tags

Gitの参照の種類

Gitの参照の種類は次の通りです。

  • heads:作業中ブランチのcommitを参照
  • tags:commitをわかりやすくする為の名前付け用
  • remotes:リモートブランチのcommitを参照
  • HEAD:作業中のブランチに対するシンボリック参照。HEADは基本的にheadsを参照しているので、他の参照と区別する為シンボリック参照と呼ばれます

動作確認の為の事前準備

次のような3つのcommitがあるリポジトリを用意しremoteへpushしておきます。

% git log --pretty=oneline master
1f36229bc26825222b191dcd63929392d9d8e2fd third commit
543ee947f5a865e1a998f793120667c3bf89fc80 second commit
23391397b4316d334e9293c4f7d1d601c1f24c4c first commit
% git remote add origin git@github.com:taisa007/internal-git.git

次からはこのリポジトリを利用します。クローンする場合はこちらを利用してください。

構成

構成は次の通りです。「.git/refs」ディレクトリ配下にそれぞれheads, remotes, tagsというディレクトリが作られています。

% tree .git
└── refs
    ├── heads
    │   └── master
    ├── remotes
    │   └── origin
    │       └── master
    └── tags

Gitの参照の解説

「refs」配下にあるファイルはそれぞれ最後のcommitをファイルに記録しています。例えば現在masterは「third commit」にいますが「git log –pretty=oneline master」とすると次のようなcommit履歴が確認できます。

% git log --pretty=oneline master
1f36229bc26825222b191dcd63929392d9d8e2fd third commit
543ee947f5a865e1a998f793120667c3bf89fc80 second commit
23391397b4316d334e9293c4f7d1d601c1f24c4c first commit
# このコマンドでも上記と同じ結果となる
% git log 1f36229bc26825222b191dcd63929392d9d8e2fd
1f36229bc26825222b191dcd63929392d9d8e2fd third commit
543ee947f5a865e1a998f793120667c3bf89fc80 second commit
23391397b4316d334e9293c4f7d1d601c1f24c4c first commit
# masterファイルの中を確認
% cat .git/refs/heads/master
17fb14ac7c9493751cff838ba0c22897582406a5
# 現在の位置のハッシュ値が書かれている

これはハッシュを指定した次のコマンド「git log –pretty=oneline 17fb14ac7c9493751cff838ba0c22897582406a5」と同じ結果です。「.git/refs/heads/master」ファイルにcommit情報が記述されている為同じ結果となります。
上記のことから「.git/refs/heads/master」にcommitのハッシュ値を指定することでmasterのheadが変更可能です。ただし直接操作は非推奨(通常操作することはありませんが)なので参照を変更する時は次のコマンドを使います。

# 参照を変更
% git update-ref refs/heads/master 543ee947f5a865e1a998f793120667c3bf89fc80
# headが変わった
% git log --pretty=oneline master
543ee947f5a865e1a998f793120667c3bf89fc80 second commit
23391397b4316d334e9293c4f7d1d601c1f24c4c first commit
% cat .git/refs/heads/master
543ee947f5a865e1a998f793120667c3bf89fc80

「second commit」に変わりました。また次のコマンドはそこからブランチを切ることができます。

% git update-ref refs/heads/test 543ee947f5a865e1a998f793120667c3bf89fc80
# testブランチができた
% git branch
master
test

「.git/refs」ディレクトリの構成を見ていきましたので、次は「HEAD」ファイルと「.git/refs」を構成する各参照について解説します。

HEAD

HEADファイルは現在作業中のブランチを参照しています。「git branch (ブランチ名)」を実行したときは、HEADからブランチが切られます。HEADファイルは 「.git」直下に配置されます。

.git
├── COMMIT_EDITMSG
├── FETCH_HEAD
├── HEAD ← ココ
├── ORIG_HEAD
├── config
├── description
├── hooks
│   ├── applypatch-msg.sample
│   ├── commit-msg.sample
│   ├── post-update.sample
│   ├── pre-applypatch.sample
│   ├── pre-commit.sample
│   ├── pre-push.sample
│   ├── pre-rebase.sample
│   ├── prepare-commit-msg.sample
│   └── update.sample
・
・
・
## HEADファイルの中を確認
% cat .git/HEAD
ref: refs/heads/master ← refs/heads配下を参照している

「git checkout test」を実行した内容は次の通りです。

% git checkout test
cat .git/HEAD
ref: refs/heads/master

このHEADファイルを確認したり変更するには次のコマンドを使います。

# 確認
% git symbolic-ref HEAD
refs/heads/test
# 変更
% git symbolic-ref HEAD refs/heads/master
% git symbolic-ref HEAD
refs/heads/master

tags

tagはGitのオブジェクトでもあります。tagオブジェクトはcommitオブジェクトに似ていますが、treeではなくcommitを指し示します。また、tagには軽量 (lightweight) 版と注釈付き (annotated) 版の2つのタイプがあります。

軽量 (lightweight) 版

  • tagをつけられる
  • commitオブジェクトにtagがつく
  • tagオブジェクトではない
# tagを付ける
% git update-ref refs/tags/v1.0 543ee947f5a865e1a998f793120667c3bf89fc80
# 確認
% git tag -n
v1.0            second commit

注釈付き (annotated)版

  • tagとコメントがつけられる
  • tagオブジェクトになる
# tagとコメント付き
% git tag -a v1.1 23391397b4316d334e9293c4f7d1d601c1f24c4c -m 'test tag'
% git tag -n
v1.0            second commit
v1.1            test tag ← コメントが付く
% cat .git/refs/tags/v1.0
543ee947f5a865e1a998f793120667c3bf89fc80 ← second commitのハッシュ
% cat .git/refs/tags/v1.1
0d69685716366ce02ea4452c8e47c64e779a40d8 ← 新しいハッシュ
% git cat-file -p d4f4075dfccb64a4c1b1966b6c7aa853f526c33a
object 23391397b4316d334e9293c4f7d1d601c1f24c4c ← first commitのハッシュ
type commit
tag v1.1
tagger sato.masaki <sato.masaki@aainc.co.jp> 1435421112 +0900
test tag

軽量版は「second commit」のハッシュ値になのに対し、注釈付きは別のハッシュの値となっています。これはcommitに対する直接的な参照ではなく、tagオブジェクトがつくられた為です。また、refsの中は下記の通りです。

└── refs
    ├── heads
    │   ├── master
    │   └── test
    ├── remotes
    │   └── origin
    │       └── master
    └── tags
        ├── v1.0 ← タグ追加
        └── v1.1 ← タグ追加

remotes

remoteもほぼ上記内容と同様の仕組みとなっています。では試しにtestブランチをremoteにプッシュしてみます。するとさっきまでなかった「.git/refs/remotes/origin/test」が追加されます。またその中身はtestブランチのHEADのハッシュです。これはつまりremoteを確認することで最後にoriginと通信したときの情報を取得できるということです。

└── refs
    ├── heads
    │   ├── master
    │   └── test
    ├── remotes
    │   └── origin
    │       ├── master
    │       └── test ← 追加
    └── tags
        ├── v1.0
        └── v1.1
% cat .git/refs/remotes/origin/test
543ee947f5a865e1a998f793120667c3bf89fc80
% cat .git/refs/heads/test
543ee947f5a865e1a998f793120667c3bf89fc80
# 同じハッシュとなる

まとめ

  • 参照にはheadとremoteとtagがある
  • HEADファイルは参照を参照し現在のブランチのシンボリックリンクを参照している
  • tagには軽量版と注釈付き版があり注釈付きの方がtagオブジェクトとなる
  • remoteは最後にサーバと通信したときのハッシュを保持している

Gitの参照と参照系の下位コマンドが確認できました。普段何気なく使っているコマンドの内側で何が行なわれてるかが分かっておもしろいですね。

参考

-Git
-

執筆者:


  1. […] Git の参照 – HEAD と heads と tags と remotes | たいさブログ […]

関連記事

Git 2.9でdiffがちょっとかしこくなった

ちょっと前にはなるがGit 2.9がリリースされてdiffがちょっとかしこくなった。 リリースノート Git 2.9 Release Notes 記事 Git 2.9 has been releasedGit2.9のキレイなdiffを出すためのconfig diffをいままでよりいい感じにする git diffに「–compaction-heuristic」オプションを追加するかgit configに「diff.compactionHeuristic true」を設定することで、これまでdiffがコンフリクト起こして正しく出せてなかったケースをカバーするようになった。今回のリリースではオプションつけるか設定変更する必要があるけど後々デフォルトになる予定とのこと。ちなみにほんとかな?と記事のソースで新旧バージョンで比較してみたところしっかり解消されてました。はいすいません。オプションつける場合 git diff –compaction-heuristic コンフィグに設定する場合 git config –global diff.compactionHeuristic true ハイライトもこれまでよりいい感じにできる diff-highlightでは同行の文字変更を見やすくするというものだけどこれ自体は以前からあった。ではどこが変わったかというと、これまでごく一部でハイライトできなかったけどそれ解消したよってことだと思う。でこっちも設定変える必要あるよとのこと。設定がなければ追加 git config –global pager.log ‘diff-highlight | less’ git config –global pager.show ‘diff-highlight | less’ git config –global pager.diff ‘diff-highlight | less’ 今回のリリースで追加された新しい設定 git config interactive.diffFilter diff-highlight ということで今回のリリースによって.gitconfigに設定が追加された。 [diff] compactionHeuristic = true [interactive] diffFilter = diff-highlight

Gitのソースコードをデバッグする

これまでGitの内側の仕組みなどをチェックしてきて、最近ようやくソースコードをデバッグしてみても処理が追えそうというところまできたので記事にまとめておきます。ただ色々試しながらやった結果なので、もっとよいやり方はあるかもしれません。 emacsとgdbをインストール 今回OSはCentOSを使っていきます。 sudo yum install emacs sudo yum install gdb gitのソースコードをクローンする 今回はGitHubにあるソースコードを利用します。 git clone https://github.com/git/git.git cd git # バージョンを指定してチェックアウトしておきます。 git chekcout -b v2.6.0-rc1 ソースコードをコンパイルする まず必要なライブラリをインストールしてからコンパイルします。 yum install curl-devel expat-devel gettext-devel openssl-devel zlib-devel perl-ExtUtils-MakeMaker make emacsを起動してデバッグをする 環境が整ったので、ここからgdbを起動しgitコマンドのデバッグをしていきます。gdbはそのまま起動しても使えますが、emacsからの方がデバッグしやすいのでemacsからgdbを起動します。 # emacsを起動する emacs -nw # gdbを起動する M-x gdb gdb –annotate=3 git # main関数で止まるようにブレークポイントを指定します (gdb) b main # 実行 (gdb) r デバッガが起動しました。画面上側がコマンドラインで、下側がソースになっています。 # ステップ実行(next) (gdb) n # 変数の内容をチェック (gdb) print argc $1 = 1 # ステップイン(step) (gdb) s # 最後まで実行(continue) (gdb) c gitコマンドを実行した場合、676行目までいき usage と The most commonly used git commands が表示されて終了します。 /* The user didn’t specify a command; give them help */ commit_pager_choice(); …