📌 定義

残念なことに表記が結構ぶれていて、いくつもの言い方があって混乱しやすくなっているのが現状です.本書では次のようにします.

🔹 snapshot (スナップショット)

バージョンごとのファイルそのもの.

🔹 worktree (作業ツリー)

ローカルにあるファイルを編集する場所です.リモートには存在しません.作業ツリーがないリポジトリのことを bare リポジトリといいます.

🔹 index (インデックス)

次のコミットのスナップショットです.ステージングエリアとも言われます.作業ツリーからインデックスにスナップショットを記録することを staging(ステージング) といいます.そのため、staged とも言われます.他には cached とも言います.Gitのコマンドでは、stage(d), cached を指定することがあるので覚えておく必要があります.

🔹 database (データベース)

Gitオブジェクト(後述)のデータベースです.ローカルリポジトリとも言われる部分ですが、ローカルリポジトリはコミット履歴や設定ファイルなどさまざまな情報が含まれているものです.

📌 ファイルの状態

Git から見た作業ツリーのファイルは次の3種類に分けられます.

  • バージョン管理されていないファイル (Untracked)
  • バージョン管理されていて、更新されていないファイル (Unmodified)
  • バージョン管理されていて、更新されたファイル (Modified)

バージョン管理されていないファイルを管理対象とするには、まずステージングしてコミットします.

Untracked -> Staged -> Unmodified

バージョン管理されているファイル(Unmodified)を編集して更新すると(Modified)になって、ステージングできます.

Unmodified -> Modified

ステージングすると更新されたファイルのスナップショットがインデックスに登録されます.

Modified -> Staged

これらの関係は次の図をご覧ください.

re-git-basic-230212163409

▲図: ファイルの状態 (「Pro Git 2nd edition 日本語版」p.25)

📌 Gitリポジトリ

git init コマンドでGitリポジトリを作成できます.

PS > git init
Initialized empty Git repository in F:/re-git/.git/

Gitリポジトリの中は次のようになっています.

PS > lat
.
└── .git
    ├── hooks
    │   ├── applypatch-msg.sample
    │   ├── commit-msg.sample
    │   ├── fsmonitor-watchman.sample
    │   ├── post-update.sample
    │   ├── pre-applypatch.sample
    │   ├── pre-commit.sample
    │   ├── pre-merge-commit.sample
    │   ├── pre-push.sample
    │   ├── pre-rebase.sample
    │   ├── pre-receive.sample
    │   ├── prepare-commit-msg.sample
    │   ├── push-to-checkout.sample
    │   └── update.sample
    ├── info
    │   └── exclude
    ├── objects
    │   ├── info
    │   └── pack
    ├── refs
    │   ├── heads
    │   └── tags
    ├── config
    ├── description
    └── HEAD

🔹 hooks/

フックスクリプトを格納するディレクトリです.ここでは詳しく解説しません.

🔹 info/exclude

バージョン対象外にしたいファイルを指定できます.これは .gitignore でも同様のことができますが、ローカル環境でのみ適用したい場合に使います.

PS > cat .git\info\exclude
# git ls-files --others --exclude-from=.git/info/exclude
# Lines that start with '#' are comments.
# For a project mostly in C, the following would be a good set of
# exclude patterns (uncomment them if you want to use them):
# *.[oa]
# *~

🔹 objects/

Gitオブジェクトを格納するディレクトリです.Gitオブジェクトについては後述します.

🔹 refs/

参照情報を格納するディレクトリです.参照とは、コミットを指すポインタのことです.実体はコミットのハッシュ値が記録されたファイルです. refs/heads/ディレクトリには各ブランチの最新のコミット情報が格納されています.

🔹 config

ローカルの設定ファイルです.

PS > cat .git\config
[core]
	repositoryformatversion = 0
	filemode = false
	bare = false
	logallrefupdates = true
	symlinks = false
	ignorecase = true

filemode

filemode はパーミッションの設定です.true だと環境によってパーミッションが変化する場合があります.デフォルトは false です.

bare

bare はベアリポジトリかどうかです.false なら作業ツリーを認識します.

symlinks はシンボリックリンクを有効にするかどうかです. デフォルトは false です.

repositoryformatversion

repositoryformatversion はリポジトリのフォーマットバージョンです.extensions.*キーを指定する場合は 1 を、そうでないなら 0 を指定します.

ignorecase

ignorecase はファイル名の大文字小文字を区別しないかどうかです.

logallrefupdates

logallrefupdatesはreflogを取るかどうかです.ベアリポジトリでは false、そうでないなら true になります.

🔹 description

Gitリポジトリの説明が格納されています.

PS > git cat .git\description
Unnamed repository; edit this file 'description' to name the repository.

🔹 HEAD

現在のHEAD情報です.HEADは通常ブランチを指します.

PS > cat .git\HEAD
ref: refs/heads/main

Gitリポジトリ初期化時では refs/heads/main が存在しないので、この HEAD は無効になっています.また、ブランチではなくコミットを直接指すこともあります.この場合、 detached HEAD となります.

📌 ファイルの状態を確認

git statusで確認できます.確認できるのは作業ツリーとインデックスでの状態です.

PS > git status
On branch main

No commits yet

nothing to commit (create/copy files and use "git add" to track)

📌 ファイルの追加

バージョン管理したいファイルを追加してみましょう.まずファイル(README.md)を作成します.

PS > echo 'My Project' > README.md
PS > git status
On branch main

No commits yet

Untracked files:
  (use "git add <file>..." to include in what will be committed)
	README.md

nothing added to commit but untracked files present (use "git add" to track)

ファイル状態を確認するとREADME.mdファイルはUntrackedであることがわかります.バージョン管理するには、まずステージングします.これは git add を使います.

PS > git add README.md
warning: in the working copy of 'README.md', CRLF will be replaced by LF the next time Git touches it
PS > git status
On branch main

No commits yet

Changes to be committed:
  (use "git rm --cached <file>..." to unstage)
	new file:   README.md

出力にもある通り、ここで git rm --cached を使うとインデックスから取り除くことができます.

PS > git rm --cached README.md

このとき、作業ツリーにあるファイルは削除されません.もし、作業ツリーにあるファイルも無かったことにしたい場合、 -f を指定します.

PS > git rm -f README.md

ファイルを追加した状態でGitリポジトリの中身はどうなっているか確認します.

PS > lat
.
├── .git
│   ├── info
│   │   └── exclude
│   ├── objects
│   │   ├── 56
│   │   │   └── 266d360f3da9f922766101055bd78ffa3724bf
│   │   ├── info
│   │   └── pack
│   ├── refs
│   │   ├── heads
│   │   └── tags
│   ├── config
│   ├── description
│   ├── HEAD
│   └── index
└── README.md

objectsディレクトリにファイルが追加され、indexファイルが作成されています.これらが一体何なのか理解するためにGitオブジェクトについて知る必要があります.

📌 Gitオブジェクト

GitオブジェクトはGitがバージョン管理するために作成するオブジェクトのことです.これらは .git\objectsディレクトリに作成されます.本書ではこのディレクトリのことをデータベースと呼ぶことにします.

Gitオブジェクトは以下の4種類あります.

  • blobオブジェクト
  • treeオブジェクト
  • commitオブジェクト
  • tagオブジェクト

Gitオブジェクトはバイナリ形式で、名前がハッシュ値(SHA-1)になっています.データベースにはハッシュ値の先頭2文字のディレクトリの中に、その文字分除外したファイル名で格納されています.ですから、 56/266d360f3da9f922766101055bd78ffa3724bf というのは 56266d360f3da9f922766101055bd78ffa3724bf というハッシュ値になります.

Gitオブジェクトはその種類とその情報が圧縮されて保存されています.これらを中身を見るためのコマンド(git cat-file)が用意されています.

-t を指定すると種類、 -p を指定すると情報、 -s を指定すると圧縮前のサイズがわかります.

PS > git cat-file -t 56266d360f3da9f922766101055bd78ffa3724bf
blob

PS > git cat-file -p 56266d360f3da9f922766101055bd78ffa3724bf
My Project

PS > git cat-file -s 56266d360f3da9f922766101055bd78ffa3724bf
11

README.mdファイルを追加したことで、blobオブジェクトが生成されたことがわかります.

🔹 indexファイル

indexは次のコミットのスナップショットであると前述しました.その実体がこのファイルになります.indexファイルはバイナリで、バージョン管理しているすべてのスナップショットを記録しています.

PS > hexyl .git\index

これは次のように出力されます.

re-git-basic-230212183008

このファイルに記録されている情報を確認するコマンドがあります.それは git ls-files --stage です.

PS > git ls-files --stage
100644 56266d360f3da9f922766101055bd78ffa3724bf 0       README.md

これは次のような形式になっています.

<mode> <object> <stage> <file>

🔹 mode

1006440b1000000110100100 のことで、3つのパーツ(10000-000-110100100)に分けられます.

1000の部分は、ファイルの種類です.次の種類があります.

  • 1000: regular file
  • 1010: symbolic link
  • 1110: gitlink

次の000は使用されていません.最後の 110100100 はUNIXパーミッションです.通常ファイルでは 07550644 のみです.シンボリックリンクとgitリンクは 0 です.

🔹 object

Gitオブジェクトのハッシュ値です.

🔹 stage

ステージ番号はマージ衝突時に使われます.

  • Slot 0: “normal”, unconflicted , all-is-well entry.
  • Slot 1: “base”, the common ancestor version.
  • Slot 2: “ours”, the target (HEAD) version.
  • Slot 3: “theirs”, the being-merged-in version.

🔹 file

ファイル名です.

📌 コミット

インデックスに更新されたスナップショットが記録されていればコミットできます.コミットしてみましょう.

PS > git commit

上記のコマンドを実行するとコミットメッセージ(.git\COMMIT_EDITMSGファイル)がエディタで開かれます.メッセージを書いて保存し、閉じるとコミットが実行されます.もし、コマンドと一緒にメッセージを書きたい場合、 -m を指定します.

PS > git commit -m "first commit"
[main (root-commit) b4c9631] first commit
 1 file changed, 1 insertion(+)
 create mode 100644 README.md

この時点のGitリポジトリがどうなっているか確認してみます.

PS > lat
.
├── .git
│   ├── info
│   │   └── exclude
│   ├── logs
│   │   ├── refs
│   │   │   └── heads
│   │   │       └── main
│   │   └── HEAD
│   ├── objects
│   │   ├── 56
│   │   │   └── 266d360f3da9f922766101055bd78ffa3724bf
│   │   ├── b4
│   │   │   └── c96315ae08b029192fa57b46b035d0317018cc
│   │   ├── c8
│   │   │   └── a887332c98b8d43304c2d8c4ba0389f2665d60
│   │   ├── info
│   │   └── pack
│   ├── refs
│   │   ├── heads
│   │   │   └── main
│   │   └── tags
│   ├── COMMIT_EDITMSG
│   ├── config
│   ├── description
│   ├── HEAD
│   └── index
└── README.md

いくつか追加されていますね.更新されたファイルをわかりやすく見てみましょう.

PS > laft

これは次のような出力になります.

re-git-basic-230212191329

タイムスタンプを見ると以下のファイルがコミット時に生成または更新されたことがわかります.

.git\logs\refs\heads\main
.git\logs\HEAD
.git\refs\heads\main
.git\objects\b4\c96315ae08b029192fa57b46b035d0317018cc
.git\COMMIT_EDITMSG
.git\index
.git\objects\c8\a887332c98b8d43304c2d8c4ba0389f2665d60

それぞれ見ていきましょう.

🔹 logs/

コミットの変更が記録されます.それぞれのファイルは次のようになっています.

PS > cat .git\logs\refs\heads\main
0000000000000000000000000000000000000000 b4c96315ae08b029192fa57b46b035d0317018cc mebiusbox <mebiusbox@gmail.com> 1676196569 +0900	commit (initial): first commit

PS > cat .git\logs\HEAD
0000000000000000000000000000000000000000 b4c96315ae08b029192fa57b46b035d0317018cc mebiusbox <mebiusbox@gmail.com> 1676196569 +0900	commit (initial): first commit

これは 0000000000000000000000000000000000000000 から b4c96315ae08b029192fa57b46b035d0317018cc のコミットに変わったことを表しています.この履歴は git reflog で確認できます.

PS > git reflog
b4c9631 HEAD@{0}: commit (initial): first commit

🔹 refs/heads/main

これはmainブランチの最新のコミットを指す参照です.内容は次のようになっています.

PS > cat .git\refs\heads\main
b4c96315ae08b029192fa57b46b035d0317018cc

つまり、 b4c96315ae08b029192fa57b46b035d0317018cc はコミットを表すオブジェクトであることが想像できると思います.

🔹 commitオブジェクト

このcommitオブジェクトを見てましょう.

PS > git cat-file -t b4c96315ae08b029192fa57b46b035d0317018cc
commit

PS > git cat-file -p b4c96315ae08b029192fa57b46b035d0317018cc
tree c8a887332c98b8d43304c2d8c4ba0389f2665d60
author mebiusbox <mebiusbox@gmail.com> 1676196569 +0900
committer mebiusbox <mebiusbox@gmail.com> 1676196569 +0900

first commit

PS > git cat-file -s b4c96315ae08b029192fa57b46b035d0317018cc
175

commitオブジェクトの中は、 tree, author, committer があります.この tree オブジェクトを見てみましょう.

📌 treeオブジェクト

先ほどのcommitオブジェクトが指していたtreeオブジェクトを見てみましょう.

PS > git cat-file -t c8a887332c98b8d43304c2d8c4ba0389f2665d60
tree

PS > git cat-file -p c8a887332c98b8d43304c2d8c4ba0389f2665d60
100644 blob 56266d360f3da9f922766101055bd78ffa3724bf	README.md

PS > git cat-file -s c8a887332c98b8d43304c2d8c4ba0389f2665d60
37

treeオブジェクトの中身はインデックスに書かれたものに似ていますね.treeオブジェクトはディレクトリを表すので、ツリー構造になっています.今回はルートディレクトリにあるファイルだけですが、サブディレクトリがあれば、treeオブジェクトの中で別のtreeオブジェクトを参照します.

📌 Gitオブジェクトと参照

これまで見てきたように、HEAD はブランチを指し、ブランチはcommitオブジェクトを参照し、commitオブジェクトはtreeオブジェクトを参照し、treeオブジェクトはblobオブジェクトやTreeオブジェクトを参照します.

HEAD -> Branch -> commit -> tree -> blob, tree

今回は最初のコミットでしたが、通常はcommitオブジェクトに親のコミット情報(parent)が含まれています.

これらの関係から HEAD やブランチ、タグは唯一のコミットを特定できるので、Gitコマンドでコミットを指定する部分に使うことができます.Gitコマンドのパラメータで commit-ish と指定されている部分は、コミットが特定できるものなら指定できることになります.そして、参照しているコミットを調べるコマンド(git rev-parse)があります.

PS > git rev-parse main
b4c96315ae08b029192fa57b46b035d0317018cc

PS > git rev-parse HEAD
b4c96315ae08b029192fa57b46b035d0317018cc

📌 初回コミットの取り消し

最初のコミットを取り消すにはどうすればよいでしょうか.それには、 git update-refコマンドを使います.これは参照を安全に書き換えることができるコマンドです.-d オプションを指定して、HEADを無効な値 (0000000000000000000000000000000000000000 = 0)にすることで、初回のコミットを取り消すことができます.コミットのログで、40文字分の0が並んだ値は、無効な値だったんですね.

PS > git update-ref -d HEAD

.git/logs/HEADはHEADを操作するとその記録が残ります.もう一度見てみましょう.

PS > cat .git/logs/HEAD
0000000000000000000000000000000000000000 b4c96315ae08b029192fa57b46b035d0317018cc mebiusbox <mebiusbox@gmail.com> 1676196569 +0900	commit (initial): first commit
b4c96315ae08b029192fa57b46b035d0317018cc 0000000000000000000000000000000000000000 mebiusbox <mebiusbox@gmail.com> 1676294876 +0900

無効な値に戻っていることが確認できます.しかし、この状態では reflog では参照できません.

PS > git reflog

何も表示されません.なぜでしょうか.ここからは不正確な情報なので注意です.まず、git reflog は以下のコマンドのエイリアスです.

git log -g --abbrev-commit --pretty=oneline

-g (--walk-reflogs) オプションに注目です.ドキュメントには次のように書かれています.

Instead of walking the commit ancestry chain, walk reflog entries from the most recent one to older ones.

要約すると、コミット履歴を辿るのではなく、reflogを辿ると書いてあります.まあ、その通りです.このコマンドで表示されるのは現在のブランチのreflogです.現在のブランチ(main)のreflogは .git/logs/refs/heads/main です.しかし、コミットがない状態だとこのファイルは存在しません.では、初回コミットして、git update-refコマンドを使って取り消した後に、さらにその処理を取り消したい場合はどうするのでしょうか.再コミットすればいいという野暮な発言は却下です..git/logsディレクトリを見てみると .git/logs/HEAD は残っています.これを使うことは出来ないでしょうか.

もちろん、できます.この場合、 git log --reflog を使います.この --reflog オプションは何でしょうか.ドキュメントには次のように書かれています.

Pretend as if all objects mentioned by reflogs are listed on the command line as <commit>.

これも要約すると、reflogに記録されているすべてのGitオブジェクトをコミットとして表示するそうです.試してみましょう.

PS > git log --reflog
commit b4c96315ae08b029192fa57b46b035d0317018cc
Author: mebiusbox <mebiusbox@gmail.com>
Date:   Sun Feb 12 19:09:29 2023 +0900

    first commit

表示されました. --oneline を指定すれば1行で表示されます.

PS > git log --reflog --oneline
b4c9631 first commit

データベースには、キャッシュとしてまだ残っています.これを使って復元しましょう.

PS > git reset --hard b4c9631
HEAD is now at b4c9631 first commit

ログを見ると次のようになっています.

PS > git log
commit b4c96315ae08b029192fa57b46b035d0317018cc (HEAD -> main)
Author: mebiusbox <mebiusbox@gmail.com>
Date:   Sun Feb 12 19:09:29 2023 +0900

    first commit

問題なさそうです.ここで、 reflogを見てみましょう.

PS > git reflog
b4c9631 (HEAD -> main) HEAD@{0}: reset: moving to b4c9631
b4c9631 (HEAD -> main) HEAD@{2}: commit (initial): first commit

resetしたことがきちんと記録されています.コミットも元に戻っているので、 .git/logs/refs/heads/main も存在します.見てみましょう.

PS > cat .git\logs\refs\heads\main
0000000000000000000000000000000000000000 b4c96315ae08b029192fa57b46b035d0317018cc mebiusbox <mebiusbox@gmail.com> 1676295921 +0900	reset: moving to b4c9631

reflogで表示されているHEAD@{2}の情報はここにないですね. .git/logs/HEADを見てみましょう.

PS > cat .git\logs\HEAD
0000000000000000000000000000000000000000 b4c96315ae08b029192fa57b46b035d0317018cc mebiusbox <mebiusbox@gmail.com> 1676196569 +0900	commit (initial): first commit
b4c96315ae08b029192fa57b46b035d0317018cc 0000000000000000000000000000000000000000 mebiusbox <mebiusbox@gmail.com> 1676294876 +0900
0000000000000000000000000000000000000000 b4c96315ae08b029192fa57b46b035d0317018cc mebiusbox <mebiusbox@gmail.com> 1676295921 +0900	reset: moving to b4c9631

こちらを参照しているようです.このあたりの挙動はどうなっているのかはちょっとわかりません.

📌 ガベージコレクト

せっかく復元したのですが、また初回コミットを取り消しましょう.

PS > git update-ref -d HEAD

コミットはなくなりましたが、作業ツリーとインデックスには残ったままです.

PS > git status
On branch main

No commits yet

Changes to be committed:
  (use "git rm --cached <file>..." to unstage)
	new file:   README.md

すべて無かったことにしましょう. git resetコマンドを使います.

PS > git reset --hard

作業ツリーやインデックスからは削除されました.確認してみましょう.

PS > lat
.
└── .git
    ├── info
    │   └── exclude
    ├── logs
    │   ├── refs
    │   │   └── heads
    │   └── HEAD
    ├── objects
    │   ├── 56
    │   │   └── 266d360f3da9f922766101055bd78ffa3724bf
    │   ├── b4
    │   │   └── c96315ae08b029192fa57b46b035d0317018cc
    │   ├── c8
    │   │   └── a887332c98b8d43304c2d8c4ba0389f2665d60
    │   ├── info
    │   └── pack
    ├── refs
    │   ├── heads
    │   └── tags
    ├── COMMIT_EDITMSG
    ├── config
    ├── description
    ├── HEAD
    └── index

データベースには、Gitオブジェクトが残っています.もし、これがこのまま使われなければ不要ですよね.不要なGitオブジェクトを削除する git pruneコマンドがあります.試しに、 -n を指定(dry-run)して確認します.

PS > git prune -n

何も表示されません.git pruneコマンドが削除するオブジェクトは到達できないオブジェクトです.作業ツリーやインデックスになくとも、前のところで説明したとおり、 .git/logs/HEAD から参照しているので、到達できます.では、どうするかというとreflogを削除します.通常は git reflogコマンドを使って削除します.しかし、今回は初回コミットなので、 .git\logs\HEAD を直接削除します.

PS > rm .git\logs\HEAD

それでは、もう一度 git pruneコマンドを実行してみましょう.

PS > git prune -n
56266d360f3da9f922766101055bd78ffa3724bf blob
b4c96315ae08b029192fa57b46b035d0317018cc commit
c8a887332c98b8d43304c2d8c4ba0389f2665d60 tree

表示されました.実際に削除してみましょう. -v を指定すれば削除されたファイルが出力されます.

PS > git prune -v
56266d360f3da9f922766101055bd78ffa3724bf blob
b4c96315ae08b029192fa57b46b035d0317018cc commit
c8a887332c98b8d43304c2d8c4ba0389f2665d60 tree

確認してみましょう.

PS > lat
.
└── .git
    ├── info
    │   └── exclude
    ├── logs
    │   └── refs
    │       └── heads
    ├── objects
    │   ├── info
    │   └── pack
    ├── refs
    │   ├── heads
    │   └── tags
    ├── COMMIT_EDITMSG
    ├── config
    ├── description
    ├── HEAD
    └── index

削除されていますね.

追跡されていないファイルに関しては git prune で削除できることがわかりました.それでは追跡できるファイルはどうでしょうか.Gitはスナップショットを保存しているので、ファイルが増えれば増えるほど、バージョンが重なるたびにデータベースが肥大化していくことになります.それに対して、Gitはガベージコレクトで容量節約する機能がしっかりあります.そのコマンドは git gc です.先ほど見た git pruneコマンドはガベージコレクトの処理の1つとして実行されるようです.

ここまでの手順をやってきた人は、ほとんど空っぽなGitリポジトリの状態になっていると思います.ガベージコレクトを試すために、再び初回コミットして、そのコミットを取り消してリセットした状態にしてください.

PS > echo 'My Project' > README.md
PS > git add README.md
PS > git commit -m "first commit"
PS > git update-ref -d HEAD
PS > git reset --hard
PS > lat
.
└── .git
    ├── info
    │   └── exclude
    ├── logs
    │   ├── refs
    │   │   └── heads
    │   └── HEAD
    ├── objects
    │   ├── 56
    │   │   └── 266d360f3da9f922766101055bd78ffa3724bf
    │   ├── 81
    │   │   └── 1993fddcde91487288bcccd3016971cab6c7a4
    │   ├── c8
    │   │   └── a887332c98b8d43304c2d8c4ba0389f2665d60
    │   ├── info
    │   └── pack
    ├── refs
    │   ├── heads
    │   └── tags
    ├── COMMIT_EDITMSG
    ├── config
    ├── description
    ├── HEAD
    └── index

reflogは残した状態です.データベースにあるGitオブジェクトは追跡可能なので、 git pruneコマンドでは消せません.この状態で、 git gcコマンドを実行しましょう.

PS > git gc
Enumerating objects: 4, done.
Counting objects: 100% (4/4), done.
Writing objects: 100% (4/4), done.
Total 4 (delta 0), reused 0 (delta 0), pack-reused 0

4つのオブジェクトが処理されたようです.確認してみましょう.

PS > lat
.
└── .git
    ├── info
    │   ├── exclude
    │   └── refs
    ├── logs
    │   ├── refs
    │   │   └── heads
    │   └── HEAD
    ├── objects
    │   ├── info
    │   │   └── packs
    │   └── pack
    │       ├── pack-c3e54044977a6ee8b9652fd225f51978c072b92c.idx
    │       └── pack-c3e54044977a6ee8b9652fd225f51978c072b92c.pack
    ├── refs
    │   ├── heads
    │   └── tags
    ├── COMMIT_EDITMSG
    ├── config
    ├── description
    ├── HEAD
    ├── index
    └── packed-refs

いくつかファイルが追加されています.

PS > laf | %{ $_.Name }
.git\info\refs
.git\objects\info\packs
.git\objects\pack\pack-c3e54044977a6ee8b9652fd225f51978c072b92c.idx
.git\objects\pack\pack-c3e54044977a6ee8b9652fd225f51978c072b92c.pack
.git\logs\HEAD
.git\packed-refs
...

🔹 (info/refs)

ここにはブランチの一覧が格納されています.今回の例では特に関係がありません.サイズも 0 です.

🔹 objects/info/packs

ファイルの中身は次のようになっています.

PS > cat .git\objects\info\packs
P pack-c3e54044977a6ee8b9652fd225f51978c072b92c.pack

pack-c3e54044977a6ee8b9652fd225f51978c072b92c.packを参照しているようです.

🔹 objects/pack

2つのファイルが追加されています.

  • pack-c3e54044977a6ee8b9652fd225f51978c072b92c.idx
  • pack-c3e54044977a6ee8b9652fd225f51978c072b92c.pack

これらは packfile と呼ばれるものです.これはGitオブジェクトを1つのバイナリファイル(.pack)にまとめたものと、そのインデックスファイル(.idx)です.blobオブジェクトはファイルのスナップショットです.最初はファイルの完全なデータが含まれています.このようなオブジェクトのことを loose objectといいます.Gitはガベージコレクトでloose objectをバイナリファイルにまとめます.その際、似たような名前とサイズのファイルを探して、ファイルのあるバージョンから次のバージョンまでの差分を格納します.これで容量を節約します.packfileは git verify-packコマンドで中身を確認できます. -v を指定し詳細を表示してみましょう. git verify-packコマンドにはpackfileのインデックスファイルを指定します.

PS > git verify-pack -v .\.git\objects\pack\pack-c3e54044977a6ee8b9652fd225f51978c072b92c.idx
d566fe024bafa6e4e7be6bd429ee1a96010f3fba commit 175 118 12
4b825dc642cb6eb9a060e54bf8d69288fbee4904 tree   0 9 130
c8a887332c98b8d43304c2d8c4ba0389f2665d60 tree   37 47 139
56266d360f3da9f922766101055bd78ffa3724bf blob   11 20 186
non delta: 4 objects
.\.git\objects\pack\pack-c3e54044977a6ee8b9652fd225f51978c072b92c.pack: ok

目録や差分を格納したかどうか、対応するバイナリファイルの情報が表示されています.このようにガベージコレクトを実行することで容量の節約ができるわけです.ガベージコレクトは定期的に実行されます.また、リポジトリの更新頻度が高いとガベージコレクトを促すプロンプトが出力された気がします.

🔹 logs/HEAD

packfileの内容によって情報が書き換わっています.

0000000000000000000000000000000000000000 d566fe024bafa6e4e7be6bd429ee1a96010f3fba mebiusbox <mebiusbox@gmail.com> 1676302779 +0900	commit (initial): first commit
d566fe024bafa6e4e7be6bd429ee1a96010f3fba 0000000000000000000000000000000000000000 mebiusbox <mebiusbox@gmail.com> 1676302795 +0900	

🔹 packed-refs

.git/refsディレクトリにある情報が packed-refs ファイルにまとめられます.

📌 .git/ORIG_HEAD

コミットを行うと、その前の有効なHEADがORIG_HEADに記録されます.これを使えば、1つ前のHEADに戻すことができます.

PS > git reset --hard ORIG_HEAD