プログラミング
Gitコミットのサイズはどれくらいか?
How big is a Git commit? (ratfactor.com)
要約
Gitコミットのサイズは、格納されるデータの圧縮率とGitの内部的なオブジェクト管理方式に依存します。小さな変更でも数キロバイトのオーバーヘッドが発生する一方、大きなファイルや多数のファイルを含むコミットでは、圧縮により元のファイルサイズの10%から50%程度に収まることが実験で示されています。Gitは最終的にパックファイル化することで、オブジェクト間の冗長性を排除し、さらに効率的なストレージを実現します。
全文翻訳
Gitに戻りましょう。短い答えは「場合による」です。Gitは「オブジェクト」をzlib圧縮データとして格納します。そのため、コミットの正確なサイズは、ファイルの圧縮可能性(基本的にファイル内の繰り返し量に依存する関数)によって決まります。https://git-scm.com/docs/gitformat-loose もう一つの要因は、Gitが最終的にオブジェクトファイルをさらに効率的なパックファイルに変換し、オブジェクト間の冗長性を排除することで追加のスペースを節約することです。https://git-scm.com/book/en/v2/Git-Internals-Packfiles 遊びで、いくつかの小さな実験を行いました(すべてパックファイルが作成されていない「ルーズオブジェクト」の状態です)。結果は以下の通りです。
64,828 バイト - git init
47,640 バイト - 250個の小さなファイルをコミット
17,145 バイト - 50個のファイルがあるディレクトリ内のファイルに3バイトの変更をコミット
8,709 バイト - 単一ファイルリポジトリに変更を3バイトコミット
297,873 バイト - 583,840 バイトのバイナリファイルをコミット
1,622,188 バイト - 16,415,223 バイトの巨大なソースファイルをコミット
このように、非常に小さなコミットではキロバイト単位のオーバーヘッドが見られます。その正確な量は、変更に関連するファイル/ディレクトリツリーのサイズによって異なります。ファイル数が少ない大きなコミットの場合、.gitに格納されるデータは、圧縮されていないコミット済みファイルの50%から10%の範囲になります。つまり、非常に効率的です。
以下は、上記の数値につながった実験の実行ログです。
初期化、5つのディレクトリ、250個のファイル
まず、新しく空のGitディレクトリ(.git)はどれくらいの大きさでしょうか?
$ git init
Initialized empty Git repository in /home/dave/tmp/foo/.git/
$ du -sb .git
64828 .git
約64 KBです。(duオプションは、sは要約、bはバイト単位での表示です。)
次に、ネストされたシェルループで、ディレクトリ内に多数の小さなファイルを作成します。
$ for d in {1..5};do mkdir $d; for f in {1..50};do echo foo > $d/$f; done; done
これで、文字列「foo」を含む5つのディレクトリに250個のファイルができました。
$ ls 1 2 3 4 5
$ cat 2/45
foo
これらをコミットします。
$ git add .
$ git commit -m 'initial commit'
ここで.gitのサイズを見てみましょう。
$du -sb .git
112468 .git
コミットにより、初期の空のリポジトリに約47 KBが追加されました。補足:これを考えられる限り最もギークな方法で計算しました。標準のUnixコマンドであるdc(任意精度逆ポーランド記法デスクトップ計算機)を使用します('p'は結果を表示するためのprintコマンドです!)。
$ dc -e '112468 64828 - p'
47640
次に、1つのファイルの1つの小さな変更を行い、それをコミットします。
$ echo bar > 1/1
$ git commit -am 'changed 1/1'
$ du -sb .git
129613
$ dc -e '129613 112468 - p'
17145
つまり、リポジトリへの約3バイトの変更を格納するために17 KBかかったということです。この重さはどこから来ているのでしょうか?もう一つ小さなコミットを行います。変更前は以下のようになっています。
$ du -b .git
4096 .git/objects/info
4096 .git/objects/pack
4115 .git/objects/25
4155 .git/objects/7b
4225 .git/objects/af
4115 .git/objects/57
4271 .git/objects/07
4177 .git/objects/61
4255 .git/objects/bb
4299 .git/objects/a8
45900 .git/objects
4336 .git/info
4096 .git/refs/tags
4137 .git/refs/heads
12329 .git/refs
4096 .git/branches
4411 .git/logs/refs/heads
8507 .git/logs/refs
12918 .git/logs
27538 .git/hooks
129613 .git
次に、ファイル1/1を再度変更して文字列「baz」を含ませ、コミットします。変更後は以下のようになります。
4096 .git/objects/info
4096 .git/objects/pack
4115 .git/objects/25
4253 .git/objects/81 <-- 新規 4 KB
4155 .git/objects/7b
4225 .git/objects/af
4115 .git/objects/57
4271 .git/objects/07
4177 .git/objects/61
4302 .git/objects/fc <-- 新規 4 KB
4255 .git/objects/bb
4115 .git/objects/76 <-- 新規 4 KB
4299 .git/objects/a8
4177 .git/objects/92 <-- 新規 4 KB
62747 .git/objects
4336 .git/info
4096 .git/refs/tags
4137 .git/refs/heads
12329 .git/refs
4096 .git/branches
4561 .git/logs/refs/heads <-- 0.1 KB追加
8657 .git/logs/refs
13218 .git/logs
27538 .git/hooks
146759 .git
単一ファイルコミットで4つの新しいオブジェクトができました。Julia "b0rk" Evans氏による新しいGitデータモデルのドキュメント(jvns.ca)の素晴らしい仕事のおかげで、これらの新しいオブジェクトが何であるかを特定できます。これらのファイルはすべてzlib圧縮データですが、git showはコミットだけでなく、ハッシュ識別子によって任意のオブジェクトの値を示すことができることに気づきました!そこで、これらのオブジェクトを表示するために次のようにしました。
$ ls .git/objects/81
b1a9d27494b40679d612b96bb6acaac83afa9c
$ git show 81b1a9d27494b40679d612b96bb6acaac83afa9c
結果は以下の通りです。
81… は実際のコミットです
fc… は変更されたディレクトリ1.(そのディレクトリ内のすべてのファイル、1から50までをリストしています)のツリーファイルです。
76… はファイル1/1の実際のコンテンツです(現在は文字列「baz」になっています)。
92… はリポジトリのルートディレクトリの別のツリーです。
これは理にかなっています。コミットが1つ、ツリーが2つ、ファイルが1つです。
小さなリポジトリ
単一の小さなファイルしかないリポジトリはどうでしょうか。コミットにはどれくらいのスペースが必要でしょうか?
$ git init
$ echo foo > foo
$ git add foo
$ git commit -m 'initial'
$ du -sb .git
94208
$ echo bar > foo
$ git -am 'change1'
$ du -sb .git
102917
$ dc -e '102917 94208 - p'
8709
つまり、コミットあたり8 KBで、これはファイル数が多いツリーを持つリポジトリのサイズの半分程度です。これらの両方のサイズのかなりの部分はオーバーヘッドであり、より大きなコミットでは大幅な節約が見られると予想されます。
「大きな」ファイル?
「大きな」(580 KB)バイナリファイルを追加してみましょう。
$ cp /usr/bin/librewolf .
$ du -b librewolf
583840
$ git add .
$ git commit -m 'added librewolf'
$ du -sb .git
400790
$ dc -e '400790 102917 - p'
297873
バイナリはかなり圧縮されるため、コミットサイズは追加されたファイル(580 KBのファイルに対して300 KB)よりも小さくなります。さらに良いことに、巨大な(16.2 MB)テキストソースファイルです。
$ cp /usr/src/linux...mask.h .
$ du -b nbio_7_2_0_sh_mask.h
16415223
$ git add .
$ git commit -m 'huge source'
$ du -sb .git
2022978
$ dc -e '2022978 400790 - p'
1622188
つまり、16.4 MBのソースファイルをコミットするために1.6 MBかかったということです!テキストがうまく圧縮されることは予想していましたが、これは本当に印象的です。