概要
Gitにはワークツリー・ステージ・リポジトリの3つの環境があります。
ワークツリーは作業をしているディレクトリを指し、ステージはワークツリーの作業内容を置く場所でした。
ステージにファイルを置くことを『ステージング』とも呼びます。
ステージングには『git add』コマンドを使います。
ステージングすることで、まずリポジトリにスナップショットが作成されます。
そのスナップショットと、ファイル名を紐付けた『インデックス』がステージに置かれます。
後にインデックスをもとに、ツリーファイル・コミットファイルが作成されリポジトリに登録されます。
ここではステージングした際に、裏側でどういうことが起きているかを説明します。
環境
モデル | MacBook Air (Retina, 13-inch, 2018) |
プロセッサ | 1.6 GHz デュアルコアIntel Core i5 |
グラフィックス | Intel UHD Graphics 617 1536 MB |
メモリ | 8 GB 2133 MHz LPDDR3 |
MacOS | Ventura 13.3.1 |
前提条件
- Gitの学習用リポジトリ『learning-git』が作成済みである
手順
index.htmlファイルの作成
カレントディレクトリを学習用のリポジトリに変更します。
そこに、動作を確認するための、かんたんなhtmlファイルを作成しましょう。
~ % cd learning-git
learning-git % echo '<h1>Hello!Git!!</h1>' >> index.html
learning-git % ls
index.html
learning-git % cat index.html
<h1>Hello!Git!!</h1>
index.htmlファイルをステージング
git add コマンドを実行します。
ドットが抜けるとエラーになるので忘れないようにして下さい。
learning-git % git add .
「ドット(.)」はaddする範囲を示すもので、今いるディレクトリにあるファイル全てを指します。
ファイル名やディレクトリ名で指定することも可能です。
これによりワークツリーをまるごと記録したスナップショットがリポジトリに作成されます。
スナップショットとは圧縮ファイルのようなものだと思ってください。
スナップショットはファイル名の情報を持ちません。
そのため、そのスナップショットとファイル名を紐付けたインデックスファイルが別途作成されます。
そのインデックスファイルが、ステージに置かれるとイメージして下さい。
git status の確認
Gitではワークツリー・ステージ・リポジトリをデータが行き来します。
各エリアで今ファイルがどういう状態なのか確認するコマンドが git status です。
具体的には以下を表示してくれるコマンドになります。
- ワークツリーとステージを比較した結果
- ステージとリポジトリを比較した結果
git add することで index.html がステージに追加されたはずです。
git status で現在の状態を確認してみましょう。
learning-git % git status
On branch master
No commits yet
Changes to be committed:
(use "git rm --cached <file>..." to unstage)
new file: index.html
上半分は “master ブランチにまだコミットはありません” と書かれています。
下半分には “Changes to be committed” で “コミットする変更がある” と記載されています。
さらに “new file: index.html” とあります。
ステージはリポジトリの手前で、コミットする準備をするエリアです。
つまり “コミットする変更がある” というのは、ステージにファイルが追加されている事を意味します。
ちなみに”(use “git rm –cached …” to unstage)”とあるのは、ステージングの取消方法です。
記載の通り git rm –cached でステージに追加した内容を取り消す事ができます。
learning-git % git rm --cached index.html
rm 'index.html'
learning-git % ls
index.html ← ファイルは残っている!
learning-git % git status
On branch master
No commits yet
Untracked files:
(use "git add <file>..." to include in what will be committed)
index.html
nothing added to commit but untracked files present (use "git add" to track)
“Untracked files”つまり”追跡できてないファイル”として、index.htmlが表示されています。
そして “git addしてコミットされるファイルに含めてね”という記載もあります。
以上の内容から、ステージングが取り消されているのがわかりますね。
突っ込んだお話
ここからは少し突っ込んだお話です。
ステージングのやり方だけを知れれば良い方は、飛ばしてOKです。
とはいえ、Git の仕組みを理解する上で重要な内容です。
やや長いですが、気になる方は読んでみてください。
“ステージにファイルが追加された” と言っても、どこにどのように保管されているのでしょうか?
スナップショットやインデックスはどこにあるのでしょうか?
git add (ステージング)した後、何が起きているのかについて詳しく見ていきます。
スナップショットはどこにあるのか
ステージングするとリポジトリにスナップショットが作成されます。
そのスナップショットとファイル名が結び付けられたものが『インデックス』でした。
前述した通り .git の下にある objects がリポジトリの本体です。
この中にスナップショットが作成されます。
find1 コマンドで中身を確認してみましょう。
% find .git/objects -type f
.git/objects/7e/94ba462a6a97c456073da969fb11dc6e27aea3
Objects ディレクトリの配下に “7e” というサブディレクトリが出来ています。
その下に”94〜”という なが〜い 名前のファイルがありますね。
これが記録されたスナップショットの実態です。
“7e” も含めて、この長い文字列はファイルの内容をSHA-1を用いてハッシュ化2したものです。
これをGitでは『オブジェクトID』と呼び、そのファイルを『オブジェクト』と言います。
オブジェクトIDの付与されたファイル(オブジェクト)の中身を確認すると、ステージングされた内容が確認できます。
スナップショットの中身を確認する
git cat-file コマンドを使うとオブジェクトの中身や、オブジェクトのタイプが確認できます。
-p オプションはオブジェクトの中身を確認するオプションになりますのでセットで覚えましょう。
コマンドの後に先程確認した、長い文字列のファイル名(オブジェクトID)を記載します。
尚、ファイル名(オブジェクトID)は4文字以上を指定すればOKです。
learning-git % git cat-file -p 7e94
<h1>Hello!Git!!</h1>
index.html の内容が表示されたことが確認できました。
オブジェクトのタイプ
git cat-file -t のように -t オプションをつけると、指定したオブジェクトのオブジェクトタイプが見れます。
learning-git % git cat-file -t 7e94
blob
オブジェクトには以下のような種類があります。
- blob
- commit
- tag
- tree
blob とはファイルの変更内容をリポジトリに保存する際に使用されるオブジェクトです。
このハッシュ値で名付けられたファイルは blob オブジェクトと呼ばれ、ファイル名はオブジェクトIDと呼ばれます。
indexはどこにあるのか
.git の中を確認してみると index というディレクトリが増えていることが確認できます。
git add したことによって新たに追加されています。
learning-git % ls .git
HEAD config description hooks index info objects refs
indexファイルの中身
index は、スナップショットとファイル名が紐付けられたものでした。
本当にindexファイルの中で、その紐づけが行われているのか確認しましょう。
ただし index ファイルはバイナリファイルと言って、人が読めるものではありません。
試しにファイルの内容を確認できる cat コマンドで見てみましょう。
learning-git % cat .git/index
DIRCd�
��d�
��"���~��F*j��V=�i��n'��
index.htmlN4���rp�'������"3�l%
バイナリファイルを8進数16進数で表示するコマンドで表示するとこんな感じ。
learning-git % od -x .git/index
0000000 4944 4352 0000 0200 0000 0100 f464 1e89
0000020 b70c cf12 f464 1e89 b70c cf12 0001 0800
0000040 1a00 22b8 0000 a481 0000 f501 0000 1400
0000060 0000 1500 947e 46ba 6a2a c497 0756 a93d
0000100 fb69 dc11 276e a3ae 0a00 6e69 6564 2e78
0000120 7468 6c6d 0000 0000 0000 0000 344e 85ae
0000140 728f fa70 0027 919b a6f8 eea4 3322 6cfa
0000160
learning-git %
文字列を抽出してくれる strings コマンドを実行すると、一応 index.html というファイル名が出てきました。
learning-git % strings .git/index
DIRC
index.html
ですが、これだとちゃんと紐付けられているのかよくわかりません。
そのために以下のコマンドが用意されています。
git ls-files -s(または --stage )
先程のスナップショットのオブジェクトと、index.html が関連付けられていることが確認できました。
learning-git % git ls-files -s
100644 7e94ba462a6a97c456073da969fb11dc6e27aea3 0 index.html
先頭の 100644 はファイルモードとパーミッションです。
100の部分がファイルモードで、100の場合は通常ファイル、シンボリックリンクのファイルだと120のように変化します。
その後の 644 はそのファイルに対するアクセス権限(パーミッション)を示しています。
オブジェクトIDの後ろにある『0』の数字は ステージ番号 と呼ばれており、通常は0になります。
では、0以外になるのはどういったときなのか?
それは自分と別の人がおなじ場所を編集した場合に生じる『コンフリクト』が起きたときです。
少しむずかしいので作業の流れの例に乗せて簡単に説明します。
まず、Gitでは自分の変更内容をリモートリポジトリに反映させることを『マージ』と言います。
まず僕がリモートリポジトリからリポジトリを clone(取得)して index.html を更新したとます。
この際、新垣結衣さんも同じように index.html の同じ部分を更新して先にマージしていたとします。
すると、後から僕がマージしようとしたときに、コンフリクトが発生します。
コンフリクトとは衝突という意味です。
これは僕が取得したリポジトリと、現在の最新のリモートリポジトリの状態が一致してない為に発生します。
この場合、何がどう衝突しているのか確認して解消することになるのですが、この時点では全体で3つのバージョンが登場しています。
- 新垣結衣さんと僕が clone したバージョン(共通の祖先)
- 新垣結衣さんが更新したバージョン(現在のブランチ)
- 僕が更新したバージョン(マージしようとしたブランチ)
Gitはこれらのバージョンをインデックスに保持するので、それらに割り振られる番号がステージ番号です。
ブランチについては後述しますが、それらを見分ける数字なんだなと理解していただけると良いと思います。
blob オブジェクトの性質を理解する
ここで改めて blob オブジェクトについてお話します。
リポジトリに保存された blob オブジェクトは更新されません。
つまり index.html を更新したり削除しても、blobオブジェクトが書き換えられたり、消えたりする事がないのです。
実際に確認してみましょう、まず現在の .git/objects の状態を確認します。
learning-git % find .git/objects -type f
.git/objects/7e/94ba462a6a97c456073da969fb11dc6e27aea3
そこから、index.htmlにタグを追記して更新します。
learning-git % echo '<p>Goodbye!Git!!</p>' >> index.html
learning-git % cat index.html
<h1>Hello!Git!!</h1>
<p>Goodbye!Git!!</p>
git add して objects ディレクトリを確認します。
learning-git % git add .
learning-git % find .git/objects -type f
.git/objects/7e/94ba462a6a97c456073da969fb11dc6e27aea3
.git/objects/ea/128c597d01806e7a6203631cc0897ef3147ed3
“ea” からはじまるオブジェクトが1つ増えました。
オブジェクトの中身とタイプを見てみましょう。
learning-git % git cat-file -p ea12
<h1>Hello!Git!!</h1>
<p>Goodbye!Git!!</p>
learning-git % git cat-file -t ea12
blob
先程追記した index.html の blobオブジェクト であることが確認できました。
このように同じファイルを編集しても、先程の blob オブジェクトが更新されるのではなく、前のスナップショットは残ったまま、新しく ea からはじまるオブジェクトが生成されています。
Gitではファイルの更新を行うと毎回新しくblobオブジェクトが生成されます。
過去の blobオブジェクト の内容が更新されることはありません。
この性質を理解しておいて下さい。
では、今度はインデックスファイルを確認します。
learning-git % git ls-files -s
100644 ea128c597d01806e7a6203631cc0897ef3147ed3 0 index.html
“7e” からはじまるオブジェクトIDがなくなり “ea” からはじまるオブジェクトIDが置かれています。
インデックスが配置される ステージ は、この後リポジトリにコミットすべきファイルの内容が置かれる場所なので、このように内容が都度更新されます。
おしまい
長くなりましたが git add を実行したあと、indexがステージに追加されるというのがどういうことか、少しイメージがつきましたでしょうか。
Gitの裏側を詳しく知りたい方は、本家の解説をお読みください。