元々 Git というバージョン管理システムは、その性質として大きなファイルやバイナリファイルを扱うのが苦手だった。 そんな欠点を補うために GitHub が開発したのが今回扱う Git LFS (Large File Storage) という拡張機能 (仕様) になる。
これは、大きなファイルやバイナリファイルの実体を Git リポジトリではなく HTTPS サーバで保持することで実現している。 Git リポジトリでは、ファイルを本体の代わりにメタ情報を含むテキストファイルの形で管理することになる。 これらの仕様 (プロトコル) は公開されているため GitHub 以外の Git ホスティング事業者でも Git LFS を実装できる。
例えば現在では 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 ホスティング事業者で使える