Gitのキホン – ステージにファイルを追加する(git add)

目次

概要

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
MacOSVentura 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です。

指定するファイル名は、サブディレクトリの2文字からはじまるので注意して下さい。

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つのバージョンが登場しています。

  1. 新垣結衣さんと僕が clone したバージョン(共通の祖先)
  2. 新垣結衣さんが更新したバージョン(現在のブランチ)
  3. 僕が更新したバージョン(マージしようとしたブランチ)

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の裏側を詳しく知りたい方は、本家の解説をお読みください。

あわせて読みたい
  1. findコマンドはファイルを検索するコマンドです。 -f はファイルだけを検索対象とするオプションです。 ↩︎
  2. ハッシュ値:データを不可逆変換した値。データの内容が少しでも変わればハッシュ値も変わる為、データの改ざんがない事を確認する際などに用いられる。例えば、ダウンロードしたファイルを実行する前に、提供元が公開するハッシュ値と、ダウンロードしたファイルのハッシュ値を比較することで改ざんされていないかの確認をすることが出来る。逆にハッシュ値からデータの内容を求める事難しい性質を利用している。 ↩︎
よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

この記事を書いた人

sibaのアバター siba プログラミング学習に出遅れた社会人

プログラミング学習に出遅れた社会人。

目次