😺

Git コマンドの裏側で何が起こっているのか?〜init, add, commit 編〜

に公開

はじめに

普段何気なく使っている Git コマンドの裏で一体何が起こっているのか?

以下の 3 つのコマンドを対象に、簡単にまとめる。

  • git init
  • git add
  • git commit

参考にさせていただいた他の方の記事は、参考に記載した。

👍 git init

git init すると、.git/ フォルダ[1]が作成される。

.git/ フォルダ内には何が入っているのだろうか。

❯ ls -a .git
./
../
HEAD
config
description
hooks/
info/
objects/
refs

中身を見ると、以下のようになっている。

ref: refs/heads/master

HEAD が参照しているものが記録されている。

config

中身を見ると、以下のようになっている。

[core]
	repositoryformatversion = 0
	filemode = true
	bare = false
	logallrefupdates = true
	ignorecase = true
	precomposeunicode = true

description

中身を見ると、以下のようになっている。

Unnamed repository; edit this file 'description' to name the repository.

hooks/

❯ ls .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*

何やらサンプルがたくさんある。

1 つ例にとって見てみる。

❯ cat .git/hooks/commit-msg.sample*
#!/bin/sh
#
# An example hook script to check the commit log message.
# Called by "git commit" with one argument, the name of the file
# that has the commit message.  The hook should exit with non-zero
# status after issuing an appropriate message if it wants to stop the
# commit.  The hook is allowed to edit the commit message file.
#
# To enable this hook, rename this file to "commit-msg".

# Uncomment the below to add a Signed-off-by line to the message.
# Doing this in a hook is a bad idea in general, but the prepare-commit-msg
# hook is more suited to it.
#
# SOB=$(git var GIT_AUTHOR_IDENT | sed -n 's/^\(.*>\).*$/Signed-off-by: \1/p')
# grep -qs "^$SOB" "$1" || echo "$SOB" >> "$1"

# This example catches duplicate Signed-off-by lines.

test "" = "$(grep '^Signed-off-by: ' "$1" |
	 sort | uniq -c | sed -e '/^[ 	]*1[ 	]/d')" || {
	echo >&2 Duplicate Signed-off-by lines.
	exit 1

An example hook script to check the commit log message.

コミットメッセージを確認するための hook スクリプト[2]が書かれている。

info/

❯ ls .git/info
./
../
exclude
❯ 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/

./
../
info/
pack/

refs/

./
../
heads/
tags/

👍 git add

❯ echo "hello" >> hello.txt
❯ git add hello.txt

新たに、以下のファイル群が作られた。

.git
 (略)
├── index
 (略)
├── objects
│   ├── ce
│   │   └── 013625030ba8dba906f756967f9e9ca394464a
│   ├── info
│   └── pack
 (略)

git cat-file -t コマンド[3]で見てみると、
ce/013625030ba8dba906f756967f9e9ca394464a は blob オブジェクト。

❯ git cat-file -t ce01
blob

git cat-file -p コマンド[4]で見てみると、
先ほど add した hello.txt の中身が格納されている。

❯ git cat-file -p ce01
hello

👍 git commit

❯ git commit -m "add hello.txt"
[master (root-commit) b364b36] add hello.txt
 1 file changed, 1 insertion(+)
 create mode 100644 hello.txt

ls -l をしてみる。

❯ ls -l .git
total 40
drwxr-xr-x  12 Macユーザ名  staff  384  1 28 14:20 ./
drwxr-xr-x   4 Macユーザ名  staff  128  1 28 14:08 ../
-rw-r--r--   1 Macユーザ名  staff   14  1 28 14:20 COMMIT_EDITMSG
-rw-r--r--   1 Macユーザ名  staff   23  1 28 13:23 HEAD
-rw-r--r--   1 Macユーザ名  staff  137  1 28 13:23 config
-rw-r--r--   1 Macユーザ名  staff   73  1 28 13:23 description
drwxr-xr-x  15 Macユーザ名  staff  480  1 28 13:23 hooks/
-rw-r--r--   1 Macユーザ名  staff  137  1 28 14:20 index
drwxr-xr-x   3 Macユーザ名  staff   96  1 28 13:23 info/
drwxr-xr-x   4 Macユーザ名  staff  128  1 28 14:20 logs/
drwxr-xr-x   7 Macユーザ名  staff  224  1 28 14:20 objects/
drwxr-xr-x   4 Macユーザ名  staff  128  1 28 13:23 refs/

新たに、以下のファイル群に変更があったと分かる。

.git
├── COMMIT_EDITMSG
 (略)
├── index
 (略)
├── objects
│   ├── aa
│   │   └── a96ced2d9a1c8e72c56b253a0e2fe78393feb7
│   ├── b3
│   │   └── 64b367a98fc9261779eb3af098a520c31e124d
 (略)

aa/a96ced2d9a1c8e72c56b253a0e2fe78393feb7 は tree オブジェクト。

❯ git cat-file -t aaa9
tree

b3/64b367a98fc9261779eb3af098a520c31e124d は commit オブジェクト。

❯ git cat-file -t b364
commit

blob オブジェクト、tree オブジェクト、commit オブジェクトとは、それぞれどのようなオブジェクトなのだろうか?

blob オブジェクト

ファイルの中身のみが書かれている。

❯ git cat-file -t ce01
blob
❯ git cat-file -p ce01
hello

tree オブジェクト

blob オブジェクトを参照するための情報を持っている。

❯ git cat-file -t aaa9
tree
❯ git cat-file -p aaa9
100644 blob ce013625030ba8dba906f756967f9e9ca394464a	hello.txt

以下が書かれている。

  • 100644 ... モード(このファイルが持つ権限)。100644 は、通常のファイルであることを意味する。他にも、100755 (実行可能)、120000(シンボリックリンク)などがある(参考 10.2 Gitの内側 - Gitオブジェクト
  • blob ... blob オブジェクト
  • ce013625030ba8dba906f756967f9e9ca394464a ... blob オブジェクトのハッシュ
  • hello.txt ... ファイル名

commit オブジェクト

commit した tree オブジェクトや、作者、コミットメッセージの情報を持っている。

❯ git cat-file -t b364
commit
❯ git cat-file -p b364
tree aaa96ced2d9a1c8e72c56b253a0e2fe78393feb7
author GitHubユーザ名 <GitHubメールアドレス> 1706419205 +0900
committer GitHubユーザ名 <GitHubメールアドレス> 1706419205 +0900

add hello.txt

以下が書かれている。

  • tree ... commit した tree オブジェクトのハッシュ
  • author ... commit したコードの最初の作者の情報
  • committer ... commit した人の情報
  • コミットメッセージ

参考

脚注
  1. そのディレクトリの git 関連の情報は全て .git/ フォルダに入っている。.git フォルダを吹き飛ばせば、git 管理がなかったことにできる。 ↩︎

  2. hook スクリプトとは、イベントを引き金に動作するスクリプトのこと。 ↩︎

  3. ファイルの種類を出力するコマンド。 ↩︎

  4. ファイルの中身を出力するコマンド。blob オブジェクトは zlib 圧縮されたものだが、このコマンドを叩けば圧縮前のものを出力できる。 ↩︎

Discussion