Gitの参照周りについて整理する
Gitの参照周りの概念や用語があまり理解できていなかったので整理してみようと思った。
まとめ
Commit Hash
- コミットの識別子
- 以下データのSHA1ハッシュを計算したもの
- 一番上のGit TreeオブジェクトのSHA1ハッシュ値
- 親コミットのハッシュ値
- Author
- Committer
- タイムスタンプ
- タイムスタンプを含むので同じデータに対するコミットでも時刻によりハッシュ値は違うものになる
- Git公式の用語はなさそう[1]
- GitHubではCommit ID[2]と呼ばれる
- 例
dc948eb27d5150c72b09750e856c015024666bd1
-
git rev-parse
コマンドでrefをcommit hashとして解決してくれる
$ git rev-parse main
dc948eb27d5150c72b09750e856c015024666bd1
参考
Reference
- Commit Hashへのエイリアス
-
ref
と呼ばれる - 実態は
.git/refs
に保存されている
.git/refs/
-- heads/ ローカルブランチ refs/heads/mainなど
-- remotes/origin/ リモートブランチ refs/remotes/origin/mainなど
-- tags/ タグ refs/tags/v0.0.1など
Short name
単にmainやHEADと指定するとGitは ${GIT_DIR}/<refname>
> refs/<refname>
> refs/tags/refname
> refs/heads/<refname>
> refs/remotes/<refname>
という順番で同名のファイルがないかを探して見つかるとそのrefとして解決してくれる。
-
git rev-parse --symbolic-full-name
でどういう参照として解決されるかが確認できる
# ローカルブランチ
$ git rev-parse --symbolic-full-name main
refs/heads/main
# リモートブランチ
$ git rev-parse --symbolic-full-name origin/main
refs/remotes/origin/main
# タグ
$ git tag
v0.0.1
$ git rev-parse --symbolic-full-name v0.0.1
refs/tags/v0.0.1
Packed ref
-
.git/refs
ディレクトリの内容を圧縮したもの - レポジトリが大きいと
git gc
の際に圧縮されることがある - 能動的に実行するには
git pack-refs --all
を実行する(unpackする方法はgitでは提供されていない)
Special ref
- 主にGitが内部で利用している特殊な参照
-
.git
以下にファイルとして保存されるが、内容はそれぞれの種類ごとに異なる - 普段の作業でユーザが利用するのは
HEAD
くらい - 例
参照 概要 HEAD 現在のブランチ。これが指しているコミットに新たなコミットが追加される。通常はSymbolic refだが、detached状態の場合はCommit Hashになる。 FETCH_HEAD 直近でフェッチされたブランチ。 git pull
=git fetch
+git merge FETCH_HEAD
ORIG_HEAD git merge や git rebase など大きな変更がある場合に直前のHEADの位置を保存するために作られる。 git reset --hard ORIG_HEAD
で元に戻すことができる。
Symbolic ref
- 他のrefを指すref
- HEADは通常symbolic ref
- 例
$ cat .git/HEAD
ref: refs/heads/main
Refspec
- リモートサーバー上のブランチとリモート追跡ブランチ(remote-tracking branch)をマッピングを表記するフォーマット
[+]<src>:<dst>
という形式をしている。<src>
はリモートサーバー上のブランチ、 <dst>
はリモート追跡ブランチを指す。 fetchしたときにデフォルトではfast-forwardマージができないとfetchが失敗するが、 +
をつけると強制的にリモート追跡ブランチを更新する。
Gitのブランチのローカルとリモート
あるブランチには3種類ある
(1) ローカルブランチ ローカルマシン上の refs/heads/
(2) リモート追跡ブランチ ローカルマシン上の refs/remotes/<origin name>/
(3) リモートサーバ上のブランチ サーバマシン上の refs/heads/
Refspecは(2)と(3)のマッピングを定義するもの。(1)と(2)のマッピングはconfigの branch.<branch-name>.merge
で定義される。
git clone
や git remote add origin <url>
した際に
fetch = +refs/heads/*:refs/remotes/origin/*
という設定が .git/config
に書き込まれるが、これはfetchしたときにすべてのリモートサーバ上のブランチをリモート追跡ブランチとしてfetchするという意味になる。
参考
- git - What are the differences between local branch, local tracking branch, remote branch and remote tracking branch? - Stack Overflow
- Gitの内側 - Refspec
- Git - リモートブランチ
Reflog
CHANGELOG 2025/2/16
HEADの変更ログだけを意味していると記載してましたが、HEADだけではないと@nosuke23さんよりコメントで指摘をいただきましたので修正しました。
- Reference logs
- refの変更ログ
- ログのエントリはある時点でrefが参照していたコミットを指す
-
git reflog
コマンドでログの確認やログの操作ができる - ログの実態は
.git/logs/
以下に保存されている
git reflog
コマンド
サブコマンドやrefを引数として指定できるが、何も指定しない場合ではshowサブコマンド、HEADをrefとして指定した場合と同じ扱いになる。つまり、下記の(1)、(2)のコマンドは等価になる。
git reflog # (1)
git reflog show HEAD # (2)
git reflog show [<ref>]
コマンドは指定されたrefに対するreflogを閲覧するコマンド。
他のサブコマンドとして reflogを持つref一覧を出力するgit reflog list
, 古いログを削除するgit reflog expire
などがある。
mainブランチで2つコミット、topicブランチを作成して切り替えた場合、git reflog
は以下のような出力になる。
$ git reflog
d43dd39 (HEAD -> topic, main) HEAD@{0}: checkout: moving from main to topic
d43dd39 (HEAD -> topic, main) HEAD@{1}: commit: B
48669b2 HEAD@{2}: commit (initial): A
ここで、各行のShort SHA-1はHEAD変更後の参照先のコミットのハッシュになっている。
git reflog <branch>
で、ブランチの変更ログも確認できる。
$ git reflog main
d43dd39 (HEAD -> topic, main) main@{0}: commit: B
48669b2 main@{1}: commit (initial): A
$ git reflog topic
d43dd39 (HEAD -> topic, main) topic@{0}: branch: Created from HEAD
以前の状態に戻ることができる
誤ったgit操作をしてしまった場合に、Reflogを使って、誤った操作の前の状態に戻ることができる。
例えば、git reset --hard
してしまっても、reflogにはgit reset
前のHEADの参照先コミットが残っているので、もう一度git reset
を使って元に戻すことができる。
$ git reset --hard HEAD^ # 誤ってgit resetしてしまった
$ git reflog
f68d5ca HEAD@{0}: reset: moving to HEAD^ # <-- git reset後のHEAD
23a0caa HEAD@{1}: commit: E # <-- git reset直前のHEAD
f68d5ca HEAD@{2}: commit: D
607bed5 HEAD@{3}: commit: C
# 直前のHEADの状態に戻ることができる
$ git reset --hard HEAD@{1}
# or
$ git reset --hard 23a0caa
参考
Revision
CHANGELOG 2025/2/16
revisionとgitrevisionsを混同していましたが、revisionはcommitを一意に示すものであると @nosuke23 さんよりコメントで指摘をいただきましたので修正しました。
- 一意のコミットを指す(=コミットと同義)
参考
gitrevisions
- いろいろなコマンドが引数としてrevision parameterを受け取るが、コマンドや表記によってそれが指すものが変わる
- 単一のコミット
- 指定されたコミットから到達可能な複数のコミット (例:
git log
) - ツリー(tree)やブロブ(blob) (例:
git show
,git push
)
-
git rev-parse
コマンドでどのようなCommit Hashとして解決されるかが確認できる
例
-
Short SHA-1
- 他に同じプレフィックスをもつSHA1が存在しなければ、SHA1ハッシュの一部でもフルSHA1ハッシュとして解決される
-
git rev-parse
コマンドでフルSHA1が取得できる
$ git rev-parse ae13719 ae137196659150446b94f8dacc5549d50ccf8414
-
@
-
@
が単独で利用されると、HEADのエイリアスを意味する
$ git rev-parse @ a1ace7478533b9fa20938f0f231f43275b7c1ddd $ git rev-parse HEAD a1ace7478533b9fa20938f0f231f43275b7c1ddd
-
-
<refname>@{<n>}
-
refname
のn
番前を表す
$ git log --oneline --format='%h %s' a1ace74 C # <-- HEAD 7045d92 B # <-- HEAD@{1} 60e5dac A # <-- HEAD@{2} $ git rev-parse HEAD@{2} 60e5dacad70de9cc1577580dfe370dc103c0f1af
-
-
<rev>@~<n>
,<rev>@^<n>
- あるコミットからの相対的なコミットを参照するref
-
~{n}
では第一の親コミットをn番目前までたどる - mergeによって親コミットが複数あるときに、第一の親コミット以外を選択したい場合は
^{n}
で第nの親を選択する - 例
HEAD~1 # HEADが指すコミットから1つ前のコミット(2つ親がある場合は第一の親コミット) HEAD^2 # HEADが指すコミットの第二の親コミットを指す main^2~2 # HEADが指すコミットから1つ前のさらに2つ前のコミット(第二の親ルート) Gに対して - Dは第一の親コミット - Fは第二の親コミット HEAD^2~2 HEAD~1 HEAD~3 HEAD^1 ▼ ▼ --A--B--C--D--G <--HEAD, main \ / E----F <-- HEAD^2
-
<rev>:<path>
-
<rev>
がtree-ishオブジェクトを指すとき、そのオブジェクト内のpathにあるブロブかツリー -
git show HEAD:README.md
はHEADが指すコミットのREADME.mdファイルの内容を表示する -
特定のコミットであるファイルがどういう状態だったかを確認するのに利用できる
-
rev-parseから得られるSHA-1 Hashがブロブを指している
$ git rev-parse HEAD:README.md f80e74b29035d60c1a82c6b3aadf65edb35ca3b1 $ git cat-file -t f80e74b29035d60c1a82c6b3aadf65edb35ca3b1 blob
-
-
Commit Ranges
-
git log
やgit rev-list
はリビジョンを受け取るとそこからたどり着けるコミットの一覧を返す -
<rev>
- revからたどり着けるコミットのリスト
-
^<rev>
- revからたどり着けるコミットを除外する
- Double dot
<rev1>..<rev2>
- rev1からはたどり着けず、rev2からのみたどり着けるコミットのリスト
-
^<rev1> <rev2>
と同じ
- Triple dot
<rev1>...<rev2>
- rev1もしくはrev2のどちらかしかからたどり着けないコミットのリスト
-
参考
おわりに
Gitの多くのコマンドでコミットを参照することがあるが、Commit SHA-1 Hash値以外にも参照の仕方を知っていると役立つと思う。
全体の参考
- Git Refs: What You Need to Know | Atlassian Git Tutorial
- Git - revisions Documentation (git-scm.com)
- Git - Revision Selection (git-scm.com)
Discussion
reflogは文字通りただのrefのログ。
git reflog <branch>
とすればブランチの参照履歴が表示される。revisionはcommitを一意に示すもので、セットやレンジで扱えるってだけ。gitの情報は公式のリファレンス以外参考にしないほうがいい。用語集などは目的的な説明に終始しているので、触りとしてはいいが実用的じゃない返信が遅くなってしまいすみません。ご指摘ありがとうございます。
reflog, revisionあたりは改めて公式ドキュメントを見直して修正したいと思います。
たしかに、用語集より逆引き的な方が役立つとは思いました。勉強になります。。