CUBE SUGAR CONTAINER

技術系のこと書きます。

macOS で Git LFS (Large File Storage) を使ってみる

元々 Git というバージョン管理システムは、その性質として大きなファイルやバイナリファイルを扱うのが苦手だった。 そんな欠点を補うために GitHub が開発したのが今回扱う Git LFS (Large File Storage) という拡張機能 (仕様) になる。

git-lfs.github.com

これは、大きなファイルやバイナリファイルの実体を Git リポジトリではなく HTTPS サーバで保持することで実現している。 Git リポジトリでは、ファイルを本体の代わりにメタ情報を含むテキストファイルの形で管理することになる。 これらの仕様 (プロトコル) は公開されているため GitHub 以外の Git ホスティング事業者でも Git LFS を実装できる。

github.com

例えば現在では Bitbucket Cloud でも次のように Git LFS に対応している。

Git Large File Storage in Bitbucket - Atlassian Documentation

今回は、そんな Git LFS を GitHub 上のリポジトリを使って試してみることにする。

使った環境は次の通り。

$ sw_vers 
ProductName:    Mac OS X
ProductVersion: 10.12.6
BuildVersion:   16G1036

利用する上での注意点

使い方の説明に入る前に注意点を一つ。 Git LFS では大きなファイルをアップロードできることから、保存先のストレージと転送量も大量に消費することになる。 そのため、一般的には利用する上で無条件にタダというわけにはいかない。

例えば GitHub であればストレージと転送量という二つのクオータ (割当量) がある。 具体的には、無料で保存できるストレージの容量は 1GB まで。 そして、転送量についても無料で利用できるのは月間で 1GB まで、という制限がある。 もし、それを超えて使いたいときは別途ストレージおよび帯域幅のクオータを購入する必要がある。 詳しくは次のドキュメントに記載されている。

About storage and bandwidth usage - User Documentation

ちなみに転送量のクオータについてはアップロードのトラフィックはカウントされない。 クオータに換算されるのは、リポジトリをクローンするときなどに生じるダウンロードのトラフィックだけ。 つまり、アップロードして消してアップロードして消して、というような操作であればカウントされない。

インストールとセットアップ

前置きが長くなったけど、ここからやっと Git LFS を使っていく。 まずは Git LFS クライアントをインストールする。 というのも、前述した通り Git LFS はあくまで Git の拡張機能という位置づけになっている。 そのため、利用するにはまず拡張機能を含むソフトウェアを入れる必要があるというわけ。

macOS であれば Homebrew を使って git-lfs をインストールする。

$ brew install git-lfs

インストールすると git コマンドで lfs サブコマンドが使えるようになる。

$ git lfs version
git-lfs/2.3.4 (GitHub; darwin amd64; go 1.9.1)

次に git lfs install コマンドを実行して Git LFS クライアントの初期設定をする。

$ git lfs install
Git LFS initialized.

具体的には、このコマンドを実行すると Git クライアントの設定ファイルである ~/.gitconfig に Git LFS 用の設定が入る。

$ grep -A 4 lfs ~/.gitconfig
[filter "lfs"]
    smudge = git-lfs smudge -- %f
    process = git-lfs filter-process
    required = true
    clean = git-lfs clean -- %f

サンプル用のリポジトリを準備する

ここからは実際に GitHub にサンプル用のリポジトリを用意して試していく。 ここからの手順を自分で試すときは、アカウントやリポジトリ名を自分で作ったものに適宜読み替えてほしい。

まずはサンプル用に作ったリポジトリをクローンしてくる。

$ git clone git@github.com:momijiame/lfs-example.git
$ cd lfs-example

現状では、まっさらな Git リポジトリになっている。

$ git status
On branch master

Initial commit

nothing to commit (create/copy files and use "git add" to track)

ひとまず、これで準備が整った。

そのまま大きなファイルを扱おうとするとどうなるか?

ここに、ひとまず大きなファイルとして 101MB のブランクファイルを作って追加してみることにしよう。

$ dd if=/dev/zero of=blankfile bs=1m count=101
101+0 records in
101+0 records out
105906176 bytes transferred in 0.060923 secs (1738358305 bytes/sec)
$ du -m blankfile 
101    blankfile

まずは、何も考えず作ったファイルをそのまま Git リポジトリに追加してみる。

$ git add blankfile
$ git commit -m "Add blankfile"
[master 7cfa851] Add blankfile
 1 file changed, 0 insertions(+), 0 deletions(-)
 create mode 100644 blankfile

コミットした内容を GitHub のリモートリポジトリにプッシュしようとすると、次のようなエラーになる。 GitHub では 50MB を超えるファイルがあると警告になるし 100MB を超えるとそもそもプッシュできない。

$ git push origin master
Counting objects: 3, done.
Delta compression using up to 4 threads.
Compressing objects: 100% (2/2), done.
Writing objects: 100% (3/3), 100.78 KiB | 146.00 KiB/s, done.
Total 3 (delta 0), reused 0 (delta 0)
remote: error: GH001: Large files detected. You may want to try Git Large File Storage - https://git-lfs.github.com.
remote: error: Trace: 7d4213addf9cf92b5299d989f6f34b1d
remote: error: See http://git.io/iEPt8g for more information.
remote: error: File blankfile is 101.00 MB; this exceeds GitHub's file size limit of 100.00 MB
To github.com:momijiame/lfs-example.git
 ! [remote rejected] master -> master (pre-receive hook declined)
error: failed to push some refs to 'git@github.com:momijiame/lfs-example.git'

エラーメッセージにも大きなファイルを扱うときは Git LFS を使うべし、とある。

このままだと先に進めないので、一旦コミット内容を取り消しておこう。

$ git reset --soft HEAD^
$ git reset blankfile

大きなファイルを Git LFS で扱う

次は先ほどのファイルを Git LFS で扱ってみる。 それには、まず git lfs track コマンドを使って Git LFS で管理するファイルに追加する。

$ git lfs track blankfile
Tracking "blankfile"

これで先ほど作った 101MB のファイルが Git LFS の管理対象になった。

$ git lfs track
Listing tracked patterns
    blankfile (.gitattributes)

この際 .gitattributes が作成される。

$ cat .gitattributes 
blankfile filter=lfs diff=lfs merge=lfs -text

作成されたファイルと一緒にステージングエリアに追加する。

$ git status
On branch master
Your branch is up-to-date with 'origin/master'.
Untracked files:
  (use "git add <file>..." to include in what will be committed)

    .gitattributes
    blankfile

nothing added to commit but untracked files present (use "git add" to track)
$ git add -A
$ git status
On branch master
Your branch is up-to-date with 'origin/master'.
Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

    new file:   .gitattributes
    new file:   blankfile

あとは一般的な Git の使い方と同じようにコミットする。

$ git commit -m "Add blankfile"
[master 35e7ee7] Add blankfile
 2 files changed, 4 insertions(+)
 create mode 100644 .gitattributes
 create mode 100644 blankfile

コミット内容をリモートにプッシュすると Git LFS で管理されているファイルは別口でアップロードされる。

$ git push origin master
Git LFS: (1 of 1 files) 101.00 MB / 101.00 MB                                  
Counting objects: 4, done.
Delta compression using up to 4 threads.
Compressing objects: 100% (3/3), done.
Writing objects: 100% (4/4), 449 bytes | 449.00 KiB/s, done.
Total 4 (delta 0), reused 0 (delta 0)
To github.com:momijiame/lfs-example.git
   ca8478b..35e7ee7  master -> master

Git LFS でファイルを管理するのに必要な作業は、これだけ。

次はリモートリポジトリをクローンするときの挙動の説明に入りたいんだけど、その前に大きなファイルは消しておく。 なぜかというと最初に説明した通り GitHub にはストレージ容量と転送量にクオータがあるから。 大きいファイルを使ってクローン操作を実行するとダウンロード方向でカウントされる転送量を大きく消費してしまう。

$ git rm blankfile
rm 'blankfile'
$ git commit -m "Delete blankfile"
[master 0401cca] Delete blankfile
 1 file changed, 3 deletions(-)
 delete mode 100644 blankfile
$ git push origin master
Counting objects: 2, done.
Delta compression using up to 4 threads.
Compressing objects: 100% (1/1), done.
Writing objects: 100% (2/2), 249 bytes | 249.00 KiB/s, done.
Total 2 (delta 0), reused 0 (delta 0)
To github.com:momijiame/lfs-example.git
   35e7ee7..0401cca  master -> master

代わりに小さなテキストファイルを Git LFS で管理される形でリモートリポジトリにプッシュしておこう。 別に小さなファイルだからといって Git LFS で管理してはいけないということはない。

$ echo "Hello, World" > greeting.txt
$ git lfs track greeting.txt 
Tracking "greeting.txt"
$ git add -A
$ git commit -m "Add greeting.txt"
[master 283f21e] Add greeting.txt
 2 files changed, 4 insertions(+)
 create mode 100644 greeting.txt
$ git push origin master
Git LFS: (1 of 1 files) 13 B / 13 B                                            
Counting objects: 4, done.
Delta compression using up to 4 threads.
Compressing objects: 100% (4/4), done.
Writing objects: 100% (4/4), 466 bytes | 466.00 KiB/s, done.
Total 4 (delta 0), reused 0 (delta 0)
To github.com:momijiame/lfs-example.git
   0401cca..283f21e  master -> master

別のディレクトリに移動して、先ほどファイルをアップロードしたリポジトリをクローンしてみよう。 すると自動的に Git LFS で管理されているファイルについてもダウンロードされてくる。

$ git clone git@github.com:momijiame/lfs-example.git
Cloning into 'lfs-example'...
remote: Counting objects: 15, done.
remote: Compressing objects: 100% (11/11), done.
remote: Total 15 (delta 0), reused 13 (delta 0), pack-reused 0
Receiving objects: 100% (15/15), 101.34 KiB | 97.00 KiB/s, done.
Downloading greeting.txt (13 B)
$ cat lfs-example/greeting.txt 
Hello, World

もしクローンの時点ではファイルをダウンロードしたくないという場合は GIT_LFS_SKIP_SMUDGE という環境変数を有効にする。

$ GIT_LFS_SKIP_SMUDGE=1 git clone git@github.com:momijiame/lfs-example.git
Cloning into 'lfs-example'...
remote: Counting objects: 15, done.
remote: Compressing objects: 100% (11/11), done.
remote: Total 15 (delta 0), reused 13 (delta 0), pack-reused 0
Receiving objects: 100% (15/15), 101.34 KiB | 180.00 KiB/s, done.

すると、クローンした時点では Git LFS で管理されているファイルがメタ情報を含むテキストの状態になる。

$ cat lfs-example/greeting.txt 
version https://git-lfs.github.com/spec/v1
oid sha256:8663bab6d124806b9727f89bb4ab9db4cbcc3862f6bbf22024dfa7212aa4ab7d
size 13

こうすれば HTTPS サーバからファイルのダウンロードが発生しないので転送量の節約になる。

改めてファイルを HTTPS サーバからダウンロードしたいときは git lfs pull コマンドを使う。

$ cd lfs-example
$ git lfs pull
Git LFS: (1 of 1 files) 13 B / 13 B

もしファイルを個別にダウンロードしたいときは -I オプションでファイル名や名前のパターンを指定する。

$ git lfs pull -I greeting.txt
Git LFS: (1 of 1 files) 13 B / 13 B

まとめ

  • Git は大きなファイルやバイナリファイルを扱うのが苦手
  • その欠点を補うために開発されたのが Git LFS という拡張機能
  • Git LFS を使うとファイルを Git リポジトリではなく HTTPS サーバに保存する
  • Git LFS は GitHub や Bitbucket といった Git ホスティング事業者で使える