"いにっと"、"あど"、"こみっと"、"ちぇっくあうと"、"まーじ"・・魔法の言葉を理解してみたい
この投稿は インタープリズムはAdvent Calendarを愛しています。世界中のだれよりも。 Advent Calendar 2017の4日目 の記事です。
こんにちは、imamotoです。
突然ですが、皆さんGitの操作はGUI派ですか?それともコマンドライン派ですか? 私はコマンドライン派です!
Subversionがメインだった頃はGUIのアプリケーションで操作をしていたのですが、 Gitに触れるようになってからはめっきりコマンドライン派になりました。
業務中もまるで魔法の呪文のようにgit add .
git commit
git push origin branch_name
とタイピングしています。
おそらく人と会話しながら余裕で打てるくらいには手に馴染んでいる愛すべきコマンド達です。
しかしふと振り返ってみると、私はこのコマンド達の本当の姿をきちんと知る努力をしてきてはいませんでした。
コミット間の差分をどのような形で保持し、どのようにコミットの履歴をたどり、どのように2つのブランチがマージされるのか。。
これらを知ることで、私はGitともっと仲良くなれるのではないかと考えました。
ということで、これからGitと仲良くなる旅に出ようと思います!
どうやってGitと仲良くなるのか?
Gitでは、ローカルのGitリポジトリのルートディレクトリにある .git
フォルダの中で、
リポジトリに関するすべての情報を管理しています。
今回はその .git
フォルダの中身を覗いてみることで、Gitと仲良くなっていきます。
各種コマンド実行時に .git
の変更点を確認する
今回の記事のスコープについて
記事の分量が多くなりすぎるので、
今回取り扱うGit操作は リポジトリ初期化→変更をステージング→変更をコミット に限定したいと思います。
git init
でGitリポジトリを初期化
まずは適当なフォルダを作り、そのフォルダ内に移動してから git init
コマンドで初期化を行いましょう。
[projects] mkdir advent-calendar-git 18:54:41 [projects] cd advent-calendar-git 18:54:56 # git initで初期化 [advent-calendar-git] git init 18:55:01 Initialized empty Git repository in /Users/imamoto/projects/advent-calendar-git/.git/ # .gitフォルダが生成されている [advent-calendar-git] ls -la 18:55:03 ☁ master ☀ total 0 drwxr-xr-x 3 imamoto staff 102 12 5 18:55 . drwxr-xr-x 27 imamoto staff 918 12 5 18:54 .. drwxr-xr-x 9 imamoto staff 306 12 5 18:55 .git
advent-calendar-gitフォルダ内に .git
フォルダが生成されました。
.git
の内部のファイル構成をtreeコマンドで確認してみます。
[advent-calendar-git] tree .git -aRF 19:05:22 ☁ master ☀ .git ├── 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* │ ├── pre-receive.sample │ ├── prepare-commit-msg.sample* │ └── update.sample* ├── info/ │ └── exclude ├── objects/ │ ├── info/ │ └── pack/ └── refs/ ├── heads/ └── tags/ 8 directories, 14 files
主なファイルの初期値を確認
.git/HEAD
.git/HEAD
ファイルは、通常は現在チェックアウトしているブランチへの参照を保持しています。
(ブランチへの参照ではなく、コミットそのものを参照することもあります)
[advent-calendar-git] cat .git/HEAD 19:07:40 ☁ master ☀ ref: refs/heads/master
.git/config
.git/config
ファイルには、このGitリポジトリに関する設定情報が記載されています。
[advent-calendar-git] cat .git/config 19:09:20 ☁ master ☀ [core] repositoryformatversion = 0 filemode = true bare = false logallrefupdates = true ignorecase = true precomposeunicode = true
リポジトリに関する設定を追加すると、このconfigファイルに追記されます。
# ユーザ名とEmailアドレスの設定を追加 [advent-calendar-git] git config user.name "Imamoto-kun" 19:11:46 ☁ master ☀ [advent-calendar-git] git config user.email "imamoto@sample.com" # .git/configを確認 [advent-calendar-git] cat .git/config 19:13:07 ☁ master ☀ [core] repositoryformatversion = 0 filemode = true bare = false logallrefupdates = true ignorecase = true precomposeunicode = true [user] name = Imamoto-kun email = imamoto@sample.com
.git/info/exclude
.git/info/exclude
ファイルは、お馴染みの .gitignore
と同じような役割を果たします。
バージョン管理外としたいファイルがチームメンバーで共有している .gitignore
ファイルに追記しづらい場合、ローカル環境でのみバージョン管理対象外にすることができます。
# 初期値は管理対象外ファイルなし [advent-calendar-git] cat .git/info/exclude 19:14:43 ☁ master ☀ # 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] # *~ # 新たなファイル imamoto.txt を追加 [advent-calendar-git] touch imamoto.txt 19:18:15 ☁ master ☀ [advent-calendar-git] git status 19:19:07 ☁ master ☂ ✭ On branch master Initial commit Untracked files: (use "git add <file>..." to include in what will be committed) imamoto.txt nothing added to commit but untracked files present (use "git add" to track) # .git/info/exclude に imamoto.txtを追記 [advent-calendar-git] echo "imamoto.txt" >> .git/info/exclude [advent-calendar-git] cat .git/info/exclude 19:21:03 ☁ master ☀ # 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] # *~ imamoto.txt # imamoto.txtが管理対象外になっている [advent-calendar-git] git status 19:21:12 ☁ master ☀ On branch master Initial commit nothing to commit (create/copy files and use "git add" to track)
git add
でコミットしたいファイルをステージング
hoge.txt
の作成
ファイルの中身が1行の hoge.txt
を作成し、 git add
コマンドでステージングします。
ステージングとは、次回のコミットに含めたい変更内容を一時的に保存しておく処理のことです。
# hoge.txtを作成してステージング [advent-calendar-git] echo "hoge" > hoge.txt 19:37:50 ☁ master ☀ [advent-calendar-git] cat hoge.txt 19:38:06 ☁ master ☂ ✭ hoge [advent-calendar-git] git add hoge.txt 19:38:14 ☁ master ☂ ✭ # 更新日時順にフルパス表示 [advent-calendar-git] tree .git -DFafi | sort -r 19:39:20 ☁ master ☂ ✚ [Dec 5 19:38] .git/objects/22/62de0c121f22df8e78f5a37d6e114fd322c0b0 [Dec 5 19:38] .git/objects/22/ [Dec 5 19:38] .git/objects/ [Dec 5 19:38] .git/index [Dec 5 19:21] .git/info/exclude // 以降続く
ステージングすることで、以下の2つのファイルが新たに作成されました。
- git/objects/22/62de0c121f22df8e78f5a37d6e114fd322c0b0
- git/index
1つめのファイルはGitオブジェクトと呼ばれる種類のファイルです。
それぞれのファイルの内容を確認してみましょう。
Gitオブジェクト
.git/objects
配下に格納されるバイナリファイルをGitオブジェクトといいます。
GitオブジェクトはGitリポジトリ内のファイル変更に関する情報を保持しています。
今回のステージングで追加された .git/objects/22/62de0c121f22df8e78f5a37d6e114fd322c0b0
もGitオブジェクトです。
Gitオブジェクトのファイルが格納されている2桁の16進数のフォルダ名 + ファイル名をオブジェクトIDといいます。
(.git/objects/22/62de0c121f22df8e78f5a37d6e114fd322c0b0
のオブジェクトIDは 2262de0c121f22df8e78f5a37d6e114fd322c0b0
です)
このオブジェクトの内容を確認してみます。
Gitオブジェクトの情報を確認
Gitオブジェクトはそれぞれ、オブジェクトタイプとファイル変更に関する情報を持っており、
git cat-file
コマンドでその内容を確認することができます。
# オブジェクトタイプを確認 [advent-calendar-git] git cat-file -t 2262de0c121f22df8e78f5a37d6e114fd322c0b0 blob # ファイル変更に関する情報を確認 [advent-calendar-git] git cat-file -p 2262de0c121f22df8e78f5a37d6e114fd322c0b0 hoge
-t
オプションで取得したオブジェクトタイプは blob となっています。
blobは変更されるファイルそのものを表すGitオブジェクトで、-p
オプションで確認した内容は hoge.txt
の内容そのものです。
ポイントは、blobのGitオブジェクトは hoge.txt
のファイル名やアクセス権限の情報を持たないことです。
indexファイルを確認
blobのGitオブジェクトが保持していなかった hoge.txt
ファイルに関する情報は、 .git/index
ファイルに保持しています。
このファイルはステージングされたファイル情報の格納場所となっています。
.git/index
もバイナリファイルで、その内容は git ls-files --stage
コマンドで確認できます。
[advent-calendar-git] git ls-files --stage 20:45:25 ☁ master ☂ ✚ 100644 2262de0c121f22df8e78f5a37d6e114fd322c0b0 0 hoge.txt
hoge.txt
のファイル名・アクセス権限の情報や、blobのGitオブジェクトのオブジェクトIDが記載されています。
hoge.txt
の更新、fuga.txt
の作成
hoge.txt
に更に1行追加・ fuga.txt
を新規に作成して、再び git add
コマンドでステージングを行います。
# hoge.txtを更新 [advent-calendar-git] echo "hogehoge" >> hoge.txt 21:28:33 ☁ master ☂ ✚ # fuga.txtを作成 [advent-calendar-git] echo "fuga" > fuga.txt 21:28:57 ☁ master ☂ ⚡ # hoge.txt, fuga.txtをステージング [advent-calendar-git] git add hoge.txt fuga.txt 21:29:06 ☁ master ☂ ⚡ ✭ # 更新日時順にフルパス表示 [advent-calendar-git] tree .git -DFafi | sort -r 21:29:14 ☁ master ☂ ✚ [Dec 5 21:29] .git/objects/91/28c3eb56a3547e2cca631042366bf0f37abe67 [Dec 5 21:29] .git/objects/91/ [Dec 5 21:29] .git/objects/19/04c092b649dc54f3c8fc931acb0ca5bb952c3b [Dec 5 21:29] .git/objects/19/ [Dec 5 21:29] .git/objects/ [Dec 5 21:29] .git/index [Dec 5 19:38] .git/objects/22/62de0c121f22df8e78f5a37d6e114fd322c0b0 // 以降続く
今回は以下の2つのGitオブジェクトファイルが追加されました。
- git/objects/91/28c3eb56a3547e2cca631042366bf0f37abe67
- git/objects/19/04c092b649dc54f3c8fc931acb0ca5bb952c3b
また、.git/index
が更新されています。
それぞれの変更内容を確認していきます。
Gitオブジェクトの情報を確認
まずは 9128c3eb56a3547e2cca631042366bf0f37abe67
のGitオブジェクトを確認します。
[advent-calendar-git] git cat-file -t 9128c3eb56a3547e2cca631042366bf0f37abe67 blob [advent-calendar-git] git cat-file -p 9128c3eb56a3547e2cca631042366bf0f37abe67 fuga
9128c3eb56a3547e2cca631042366bf0f37abe67
は fuga.txt
の内容を保持したblobのGitオブジェクトでした。
続いて 1904c092b649dc54f3c8fc931acb0ca5bb952c3b
のGitオブジェクトを確認します。
[advent-calendar-git] git cat-file -t 1904c092b649dc54f3c8fc931acb0ca5bb952c3b blob [advent-calendar-git] git cat-file -p 1904c092b649dc54f3c8fc931acb0ca5bb952c3b hoge hogehoge
1904c092b649dc54f3c8fc931acb0ca5bb952c3b
は hoge.txt
の内容を保持したblobのGitオブジェクトでした。
ポイントは以下の2つです。
- 一度目のステージング時に生成された
2262de0c121f22df8e78f5a37d6e114fd322c0b0
とは違うGitオブジェクトが生成された。 - 一度目のステージングとの差分ではなく、
hoge.txt
全体が保存された。
blobのGitオブジェクトはステージングを行う度に新たに作成され、ファイル全体を保存します。
indexファイルを確認
git/index
ファイルは以下のようになっています。
[advent-calendar-git] git ls-files --stage 21:46:26 ☁ master ☂ ✚ 100644 9128c3eb56a3547e2cca631042366bf0f37abe67 0 fuga.txt 100644 1904c092b649dc54f3c8fc931acb0ca5bb952c3b 0 hoge.txt
fuga.txt
の情報が新たに追加されたのに加え、hoge.txt
の行のblobのGitオブジェクトIDが最新のものに更新されました。
git commit
でステージングしたファイルをコミットする
続いて、先ほどステージングしたファイルをコミットしましょう。
[advent-calendar-git] git commit -m "commit 1" 21:57:55 ☁ master ☂ ✚ [master (root-commit) 561d2be] commit 1 2 files changed, 3 insertions(+) create mode 100644 fuga.txt create mode 100644 hoge.txt # 更新日時順にフルパス表示 [advent-calendar-git] tree .git -DFafi | sort -r 21:58:07 ☁ master ☀ [Dec 5 21:58] .git/refs/heads/master [Dec 5 21:58] .git/refs/heads/ [Dec 5 21:58] .git/objects/9d/59adb6ab05b5c697d0adc18241f09ce241ee29 [Dec 5 21:58] .git/objects/9d/ [Dec 5 21:58] .git/objects/56/1d2bea3175f9d70230d57b72029374b523f23b [Dec 5 21:58] .git/objects/56/ [Dec 5 21:58] .git/objects/ [Dec 5 21:58] .git/logs/refs/heads/master [Dec 5 21:58] .git/logs/refs/heads/ [Dec 5 21:58] .git/logs/refs/ [Dec 5 21:58] .git/logs/HEAD [Dec 5 21:58] .git/logs/ [Dec 5 21:58] .git/index [Dec 5 21:58] .git/COMMIT_EDITMSG [Dec 5 21:29] .git/objects/91/28c3eb56a3547e2cca631042366bf0f37abe67 // 以降続く
今回は新たに以下のファイルが追加されました。
- git/refs/heads/master
- git/logs/refs/heads/master
- git/logs/HEAD
- git/COMMIT_EDITMSG
- git/objects/9d/59adb6ab05b5c697d0adc18241f09ce241ee29
- git/objects/56/1d2bea3175f9d70230d57b72029374b523f23b
また、今回も .git/index
が更新されています。
Gitオブジェクトの情報を確認
treeタイプのGitオブジェクト
まずは 9d59adb6ab05b5c697d0adc18241f09ce241ee29
のGitオブジェクトを確認します。
# オブジェクトタイプを確認 [advent-calendar-git] git cat-file -t 9d59adb6ab05b5c697d0adc18241f09ce241ee29 tree # ファイル変更に関する情報を確認 [advent-calendar-git] git cat-file -p 9d59adb6ab05b5c697d0adc18241f09ce241ee29 100644 blob 9128c3eb56a3547e2cca631042366bf0f37abe67 fuga.txt 100644 blob 1904c092b649dc54f3c8fc931acb0ca5bb952c3b hoge.txt
-t
オプションで取得したオブジェクトタイプは tree となっています。
treeタイプのGitオブジェクトには、直前にステージングされていた情報が格納されています。
そのため、 .git/index
と同様にファイル名やアクセス権限、blobのオブジェクトIDを保持しています。
複数のblobオブジェクトがまとまっており、1度のコミットで1つ生成されますが、
コミット自体の情報は保持していません。
commitタイプのGitオブジェクト
続いて、561d2bea3175f9d70230d57b72029374b523f23b
のGitオブジェクトを確認します。
# オブジェクトタイプを確認 [advent-calendar-git] git cat-file -t 561d2bea3175f9d70230d57b72029374b523f23b commit # ファイル変更に関する情報を確認 [advent-calendar-git] git cat-file -p 561d2bea3175f9d70230d57b72029374b523f23b tree 9d59adb6ab05b5c697d0adc18241f09ce241ee29 author Imamoto-kun <imamoto@sample.com> 1512478687 +0900 committer Imamoto-kun <imamoto@sample.com> 1512478687 +0900 commit 1
-t
オプションで取得したオブジェクトタイプは commit となっています。
commitタイプのGitオブジェクトには、コミットメッセージやコミット日時、コミットした人等、コミットに関する情報が格納されています。
また、その変更内容として、treeタイプのGitオブジェクトへの参照を持っています。
Gitオブジェクト同士の依存関係
ここまでで登場した、blob、tree、commitの3つのGitオブジェクトの関係性は以下のようになります。
.git/logs/HEADファイルを確認
カレントブランチの内容やブランチ切り替えが行われたログが格納されています。
commitタイプのGitオブジェクトのID(コミットID)が記載されています。
以下は、今回のコミットによって、
0000000000000000000000000000000000000000
から 561d2bea3175f9d70230d57b72029374b523f23b
の状態へ変更されたことを示しています。
[advent-calendar-git] cat .git/logs/HEAD 22:26:25 ☁ master ☀ 0000000000000000000000000000000000000000 561d2bea3175f9d70230d57b72029374b523f23b Imamoto-kun <imamoto@sample.com> 1512478687 +0900 commit (initial): commit 1
.git/refs/heads/masterファイルを確認
.git/refs/heads/branch_name
のファイルには、そのブランチでの最新のコミットIDを保持しています。
[advent-calendar-git] cat .git/refs/heads/master 22:45:57 ☁ master ☀ 561d2bea3175f9d70230d57b72029374b523f23b
.git/logs/refs/heads/masterファイルを確認
.git/logs/refs/heads/branch_name
のファイルには、そのブランチの内容が切り替わった際のログが格納されています。
現在はブランチがmasterしかないため、.git/logs/HEAD
と同一の内容です。
[advent-calendar-git] cat .git/logs/refs/heads/master 22:46:31 ☁ master ☀ 0000000000000000000000000000000000000000 561d2bea3175f9d70230d57b72029374b523f23b Imamoto-kun <imamoto@sample.com> 1512478687 +0900 commit (initial): commit 1
.git/COMMIT_EDITMSGファイルを確認
.git/COMMIT_EDITMSG
ファイルは、コミットメッセージ編集時の一時ファイルです。
[advent-calendar-git] cat .git/COMMIT_EDITMSG 22:51:55 ☁ master ☀ commit 1
2度目のコミットをしてみる
最後に、2度目のコミットをしてみます。
[advent-calendar-git] echo "hogehogehoge" >> hoge.txt 22:57:23 ☁ master ☀ [advent-calendar-git] git add hoge.txt 22:57:39 ☁ master ☂ ⚡ [advent-calendar-git] git commit -m "commit 2" 22:57:45 ☁ master ☂ ✚ [master 1e80717] commit 2 1 file changed, 1 insertion(+) # 更新日時順にフルパス表示 [advent-calendar-git] tree .git -DFafi | sort -r 22:57:53 ☁ master ☀ [Dec 5 22:57] .git/refs/heads/master [Dec 5 22:57] .git/refs/heads/ [Dec 5 22:57] .git/objects/c6/d755d5c7f0e8c2a3af634eeb40e9f3296ddd56 [Dec 5 22:57] .git/objects/c6/ [Dec 5 22:57] .git/objects/9d/1d67e349dc0f3309c6e5f7e94c01151b21e2ca [Dec 5 22:57] .git/objects/9d/ [Dec 5 22:57] .git/objects/1e/80717acb767ae8917ed44de4fcb9207f2dda02 [Dec 5 22:57] .git/objects/1e/ [Dec 5 22:57] .git/objects/ [Dec 5 22:57] .git/logs/refs/heads/master [Dec 5 22:57] .git/logs/HEAD [Dec 5 22:57] .git/index [Dec 5 22:57] .git/COMMIT_EDITMSG [Dec 5 21:58] .git/objects/9d/59adb6ab05b5c697d0adc18241f09ce241ee29 // 以降続く
先ほどとほとんど変わらない操作だったので、更新されたファイルの顔ぶれもほとんど変わりません。
今回は、特に注目すべき commitタイプのGitオブジェクト のみを確認します。
commitタイプのGitオブジェクトの確認
# オブジェクトタイプを確認 [advent-calendar-git] git cat-file -t 1e80717acb767ae8917ed44de4fcb9207f2dda02 commit # ファイル変更に関する情報を確認 [advent-calendar-git] git cat-file -p 1e80717acb767ae8917ed44de4fcb9207f2dda02 tree c6d755d5c7f0e8c2a3af634eeb40e9f3296ddd56 parent 561d2bea3175f9d70230d57b72029374b523f23b author Imamoto-kun <imamoto@sample.com> 1512482272 +0900 committer Imamoto-kun <imamoto@sample.com> 1512482272 +0900 commit 2
特筆すべき点は、親コミット(parent)のコミットIDを保持している点です。
このコミットIDは、先ほどの1度目のコミット時に生成されたIDです。
このように、親コミットのIDを保持することでコミットの履歴をたどることができます。
Gitオブジェクト同士の依存関係(親子関係を含む)
コミットの親子関係を含んだGitオブジェクト同士の依存関係は以下のようになります。
まとめ
今回は リポジトリ初期化〜コミット までをスコープにして、.git
フォルダの中身を見ていきました。
本当はブランチの切り替えやmerge
, rebase
, cherry-pick
等も取り扱いたかったのですが、
長くなってしまったので次の機会にしたいと思います。
ただ、ここまで色々書いてきましたが、言いたかったことは「Gitのファイル構成、めっちゃシンプル!!」ということです。
こういったファイル構成が頭に入っていれば、Gitで複雑なことをしたい時にも操作のイメージが付きやすくなると思います。
皆さんもお暇な時には是非 .git
フォルダを覗いて、Gitと仲良くなってみてください。それではまた!!