Git LFS

Git LFS

Git LFS とは何か

Git は分散バージョン管理システムです。つまり、リポジトリの全履歴が複製作成プロセス中にクライアントに転送されます。大きなファイルを含むプロジェクト、特に定期的に変更される大きなファイルの場合、すべてのファイルのすべてのバージョンをクライアントがダウンロードする必要があるため、この初期の複製には膨大な時間がかかります。Git LFS (Large File Storage) は、Atlassian、GitHub、およびその他のオープンソース提供者によって開発された Git 拡張モジュールで、関連するバージョンを遅れてダウンロードすることによって、大容量ファイルがご使用のリポジトリに及ぼす影響を軽減します。具体的には、複製やフェッチ中ではなく、チェックアウト処理中に大きなファイルがダウンロードされます。

Git LFS は、リポジトリ内の大きなファイルを小さなポインタファイルに置き換えることでこれを行います。通常の使用時には Git LFS によって自動的に処理されるため、これらのポインタファイルは決して表示されません。

  1. リポジトリにファイルを追加すると、Git LFS はそのコンテンツをポインタで置き換え、ファイルの内容をローカルの Git LFS キャッシュに保存します。 

    git lfs - git add
  2. 新しいコミットをサーバーにプッシュすると、新しくプッシュされたコミットによって参照される Git LFS ファイルは、ローカルの Git LFS キャッシュから、ユーザーの Git リポジトリに関連付けられたリモートの Git LFS ストアに転送されます。 

    git lfs - git push
  3. Git LFS ポインタを含むコミットをチェックアウトすると、ポインタはローカルの Git LFS キャッシュからのファイルに置き換えられるか、リモートの Git LFS ストアからダウンロードされます。 git lfs - git checkout

Git LFS はシームレスです。作業コピーには実際のファイルコンテンツのみ表示されます。つまり、既存の Git ワークフローを変更せずに Git LFS を使用することができます。ユーザーは単に git checkout、編集、git add、および git commit を通常どおり行うだけです。git clonegit pull 操作は大幅に高速になります。これは、これまでに存在したファイルのすべてのバージョンではなく、実際にチェックアウトするコミットによって参照される大きなファイルのバージョンのみをダウンロードするからです。

To use Git LFS, you will need a Git LFS aware host such as Bitbucket Cloud or Bitbucket Server. Repository users will need to have the Git LFS command-line client installed, or a Git LFS aware GUI client such as Sourcetree. Fun fact: Steve Streeting, the Atlassian developer who invented Sourcetree, is also a major contributor to the Git LFS project, so Sourcetree and Git LFS work together rather well.

Git LFS とは何か

Git LFS のインストール

  1. Git LFS をインストールするには、3つの簡単な方法があります。

    a. お気に入りのパッケージマネージャーを使用してインストールします。git-lfs パッケージは、Homebrew、MacPorts、dnf、および packagecloud で利用できます。または

    b. プロジェクトの Web サイトから Git LFS をダウンロードしてインストールします。

    c. Install Sourcetree, a free Git GUI client that comes bundled with Git LFS.

  2. Once git-lfs is on your path, run git lfs install to initialize Git LFS (you can skip this step if you installed Sourcetree):

    $ git lfs install
    Git LFS initialized.
    

    git lfs install は一度実行するだけで済みます。ユーザーのシステム用に初期化された後は、Git LFS コンテンツを含むリポジトリを複製すると、Git LFS が自動的にブートストラップします。

新しい Git LFS リポジトリの作成

新しい Git LFS 対応リポジトリを作成するには、リポジトリの作成後に git lfs install を実行する必要があります。

# initialize Git
$ mkdir Atlasteroids
$ cd Atlasteroids
$ git init
Initialized empty Git repository in /Users/tpettersen/Atlasteroids/.git/
# initialize Git LFS
$ git lfs install
Updated pre-push hook.
Git LFS initialized.

これにより、ローカルのリポジトリに特別な pre-push Git フック がインストールされます。この特別なフックは、git push の実行時に Git LFS ファイルをサーバーに転送します。

Git LFS is automatically enabled for all Bitbucket Cloud repositories. For Bitbucket Server, you'll need to enable Git LFS in your repository settings:

Bitbucket Server Git LFS

Git LFS がローカルのリポジトリ用に初期化されると、git lfs track を使用して追跡するファイルを指定できるようになります。

既存の Git LFS リポジトリを複製します。

Git LFS がインストールされたら、git clone を使用して通常どおり Git LFS リポジトリを複製できます。複製プロセスの最後に、Git は既定のブランチ (通常はマスター) をチェックアウトします。チェックアウトプロセスを完了するために必要な Git LFS ファイルは自動的にダウンロードされます。例:

$ git clone git@bitbucket.org:tpettersen/Atlasteroids.git
Cloning into 'Atlasteroids'...
remote: Counting objects: 156, done.
remote: Compressing objects: 100% (154/154), done.
remote: Total 156 (delta 87), reused 0 (delta 0)
Receiving objects: 100% (156/156), 54.04 KiB | 31.00KiB/s, done.
Resolving deltas: 100% (87/87), done.
Checking connectivity... done.
Downloading Assets/Sprites/projectiles-spritesheet.png (21.14 KB)
Downloading Assets/Sprites/productlogos_cmyk-spritesheet.png (301.96KB)
Downloading Assets/Sprites/shuttle2.png (1.62 KB)
Downloading Assets/Sprites/space1.png(1.11 MB)
Checking out files: 100% (81/81), done.

Git LFS によって追跡されるこのリポジトリには、4つのPNG があります。git clone を実行すると、Git LFS ファイルは、ポインタファイルがリポジトリからチェックアウトされるときに1つずつダウンロードされます。

複製の高速化

多数の LFS ファイルがあるリポジトリを複製している場合、明示的な git lfs clone コマンドを使用すると、パフォーマンスがはるかに向上します。

$ git lfs clone git@bitbucket.org:tpettersen/Atlasteroids.git
Cloning into 'Atlasteroids'...
remote: Counting objects: 156, done.
remote: Compressing objects: 100% (154/154), done.
remote: Total 156 (delta 87), reused 0 (delta 0)
Receiving objects: 100% (156/156), 54.04 KiB | 0 bytes/s, done.
Resolving deltas: 100% (87/87), done.
Checking connectivity... done.
Git LFS: (4 of 4 files) 1.14 MB / 1.15 MB

git lfs clone コマンドは、一度に1つずつ Git LFS ファイルをダウンロードするのではなく、チェックアウトが完了するまで待ってから、必要な Git LFS ファイルをバッチとしてダウンロードします。これにより、並列化ダウンロードが利用され、生成されるHTTP 要求とプロセスの数が大幅に削減されます (Windowsのパフォーマンスを向上させる上で特に重要です)。

プルとチェックアウト

複製と同じように、通常の git pull を使用して Git LFS リポジトリからプルできます。プルが完了すると、必要な Git LFS ファイルは自動チェックアウトプロセスの一部としてダウンロードされます。

$ git pull
Updating 4784e9d..7039f0a
Downloading Assets/Sprites/powerup.png (21.14 KB)
Fast-forward
Assets/Sprites/powerup.png | 3 +
Assets/Sprites/powerup.png.meta| 4133 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 4136 insertions(+)
create mode 100644 Assets/Sprites/projectiles-spritesheet.png
create mode 100644 Assets/Sprites/projectiles-spritesheet.png.meta

Git LFS コンテンツを取得するための明示的なコマンドは必要ありません。ただし、予期せぬ理由でチェックアウトに失敗した場合は、git lfs pull を使用して、現在のコミットに関して欠落している Git LFS コンテンツをダウンロードできます。

$ git lfs pull
Git LFS: (4 of 4 files) 1.14 MB / 1.15 MB

プルの高速化

git lfs clone と同様に、git lfs pull はあなたの Git LFS ファイルをバッチとしてダウンロードします。最後にプルしてから多数のファイルが変更されたことがわかっている場合は、チェックアウト時に自動 Git LFS のダウンロードを無効にしてから、Git LFS コンテンツを明示的に git lfs pull で一括ダウンロードできます。これは git pull を起動するときに -c オプションを指定して Git の設定をオーバーライドすることで実行できます。

$ git -c filter.lfs.smudge=-c filter.lfs.required=falsepull && git lfs pull

入力する項目が多いので単純な Git エイリアスを作成し、一括して Git と Git LFS プルを実行したいと考える場合もあるでしょう。

$ git config --global alias.plfs"\!git -c filter.lfs.smudge=-c filter.lfs.required=falsepull && git lfs pull"
$ git plfs

これは、大量の Git LFS ファイルをダウンロードする必要があるとき (特にWindows上)、パフォーマンスを大幅に向上させます。

Git LFS によるファイル追跡

新しいタイプの大きなファイルをリポジトリに追加するときは、git lfs track コマンドを使用してパターンを指定することで、Git LFS にそれを追跡するように指示する必要があります。

$ git lfs track "*.ogg"
Tracking *.ogg

"*.ogg" の引用符は重要であることに注意してください。引用符を省略すると、シェルによってワイルドカードが拡張され、現在のディレクトリの各 .ogg ファイルについて個別にエントリが作成されます。

# probably not what you want
$ git lfs track *.ogg
Tracking explode.ogg
Tracking music.ogg
Tracking phaser.ogg

Git LFS でサポートされているパターンは .gitignore でサポートされているパターンと同じです。以下に例を示します。

# track all .oggfiles in any directory
$ git lfs track "*.ogg"
# track files named music.oggin any directory
$ git lfs track "music.ogg"
# track all files in the Assets directory and all subdirectories
$ git lfs track "Assets/"
# track all files in the Assets directory but *not* subdirectories
$ git lfs track "Assets/*"
# track all ogg files in Assets/Audio
$ git lfs track "Assets/Audio/*.ogg"
# track all ogg files in any directory named Music
$ git lfs track "**/Music/*.ogg"
# track png files containing "xxhdpi" in their name, in any directory
$ git lfs track "*xxhdpi*.png

これらのパターンは、git lfs track コマンドを実行したディレクトリへの相対パスです。物事を単純にするために、リポジトリのルートから git lfs track を実行するのが最善です。Git LFS は 否定パターン (.gitignore など) をサポートしていないことに注意してください。

git lfs track を実行すると、コマンドを実行したディレクトリに .gitattributes という名前の新しいファイルが作成されます。.gitattributes は特定の動作を特定のファイルパターンにバインドするための Git の仕組みです。Git LFS は .gitattributes ファイルを自動的に作成または更新して追跡したファイルパターンを Git LFS フィルターにバインドします。ただし、.gitattributes ファイルへの変更をご使用のリポジトリに自分でコミットする必要があります。

$ git lfs track "*.ogg"
Tracking *.ogg
$ git add .gitattributes
$ git diff --cached
diff --git a/.gitattributes b/.gitattributes
new file mode 100644
index 0000000..b6dd0bb
--- /dev/null
+++ b/.gitattributes
@@ -0,0 +1 @@
+*.ogg filter=lfs diff=lfs merge=lfs -text
$ git commit -m "Track ogg files with Git LFS"

保守を容易にするために、すべての Git LFS パターンを単一の .gitattributes ファイルに保存するのが最も簡単です。これを実行するには、ご使用のリポジトリのルートから常に git lfs track を実行してください。一方、Git LFS (およびパターンが定義されている .gitattributes ファイル) によって現在追跡されているすべてのパターンのリストを表示することができます。これを実行するには、引数なしで git lfs track を呼び出します。

$ git lfs track
Listing tracked paths
*.stl (.gitattributes)
*.png (Assets/Sprites/.gitattributes)
*.ogg (Assets/Audio/.gitattributes)

.gitattributes ファイルから適切な行を削除するだけで、Git LFS による特定のパターンの追跡を止めることができます。または、git lfs untrack コマンドを実行します。

$ git lfs untrack "*.ogg"
Untracking *.ogg
$ git diff
diff --git a/.gitattributes b/.gitattributes
index b6dd0bb..e69de29 100644
--- a/.gitattributes
+++ b/.gitattributes
@@ -1 +0,0 @@
-*.ogg filter=lfs diff=lfs merge=lfs -text

git lfs untrack を実行した後、再度その変更を自分で .gitattributes にコミットする必要があります。

コミットとプッシュ

Git LFS コンテンツを含むリポジトリへのコミットとプッシュは通常どおりです。Git LFS により追跡されているファイルに変更をコミットした場合、Git LFS コンテンツがサーバーに転送されるときに、git push からの追加出力がいくつか表示されます。

$ git push
Git LFS: (3 of 3 files) 4.68 MB / 4.68 MB
Counting objects: 8, done.
Delta compression using up to 8 threads.
Compressing objects: 100% (8/8), done.
Writing objects: 100% (8/8), 1.16 KiB | 0 bytes/s, done.
Total 8 (delta 1), reused 0 (delta 0)
To git@bitbucket.org:tpettersen/atlasteroids.git
7039f0a..b3684d3 master -> master

If transferring the LFS files fails for some reason, the push will be aborted and you can safely try again. Like Git, Git LFS storage is content addressable: content is stored against a key which is a SHA-256 hash of the content itself. This means it is always safe to re-attempt transferring Git LFS files to the server; you can't accidentally overwrite a Git LFS file's contents with the wrong version.

ホスト間での Git LFS リポジトリの移動

あるホスティングプロバイダーから別のホスティングプロバイダーに Git LFS リポジトリを移行するには、git lfs fetchgit lfs push を組み合わせて使用し、--all オプションを指定します。

たとえば、すべての Git および Git LFS リポジトリを github という名前のリモートから bitbucket という名前のリモートに移動するには😉:

# create a bare clone of the GitHub repository
$ git clone --bare git@github.com:kannonboy/atlasteroids.git
$ cd atlasteroids
# set up named remotes for Bitbucket and GitHub
$ git remote add bitbucket git@bitbucket.org:tpettersen/atlasteroids.git
$ git remote add github git@github.com:kannonboy/atlasteroids.git
# fetch all Git LFS content from GitHub
$ git lfs fetch --all github
# push all Git and Git LFS content to Bitbucket
$ git push --mirror bitbucket
$ git lfs push --all bitbucket

追加の Git LFS 履歴の取得

Git LFS は通常、実際にローカルでチェックアウトしたコミットに必要なファイルのみをダウンロードします。ただし、git lfs fetch --recent を使用すると、最近変更された他のブランチのために追加のコンテンツをダウンロードするように Git LFS に強制できます。

$ git lfs fetch --recent
Fetching master
Git LFS: (0 of 0 files, 14 skipped) 0 B / 0 B, 2.83 MB skipped Fetching recent branches within 7 days
Fetching origin/power-ups
Git LFS: (8 of 8 files, 4 skipped) 408.42 KB / 408.42 KB, 2.81 MB skipped
Fetching origin/more-music
Git LFS: (1 of 1 files, 14 skipped) 1.68 MB / 1.68 MB, 2.83 MB skipped

これは、ランチに出かけている間、またはチームメートからの作業のレビューを計画しており、限られたインターネット接続のために後でコンテンツをダウンロードできない場合、新しい Git LFS コンテンツを一括してダウンロードするのに便利です。たとえば、飛行機に飛び乗る前に git lfs fetch --recent を実行する必要が生じるかもしれません!

Git LFS は、コミットを含むブランチやタグが作成されてから 7 日未満のものを最近とみなします。lfs.fetchrecentrefsdays プロパティを設定すると、最近とみなされる基準の日数を設定できます。

# download Git LFS content for branches or tags updated in the last 10 days
$ git config lfs.fetchrecentrefsdays 10

既定では、git lfs fetch --recent は最近のブランチやタグの先端にあるコミットの Git LFS コンテンツのみをダウンロードします。

git lfs - git lfs fetch --recent

ただし、lfs.fetchrecentcommitsdays プロパティを設定することによって、最近のブランチやタグの以前のコミットのコンテンツをダウンロードするように Git LFS を設定することができます。

# download the latest 3 days of Git LFS content for each recent branch or tag
$ git config lfs.fetchrecentcommitsdays 3

この設定は慎重に使用してください。ブランチを高速に移動すると、膨大な量のデータがダウンロードされる可能性があります。しかし、ブランチ上の割り込み変更を確認して、複数のブランチからコミットを選別する必要がある場合や、履歴の書き換えが必要な場合には役立ちます。

git lfs - git lfs fetch --recent commits

ホスト間での Git LFS リポジトリの移動」で説明したように、ご使用のリポジトリのすべての Git LFS コンテンツは git lfs fetch --all を使用して取得することもできます。

$ git lfs fetch --all
Scanning for all objects ever referenced...
✔ 23 objects found
Fetching objects...
Git LFS: (9 of 9 files, 14 skipped) 2.06 MB / 2.08 MB, 2.83 MB skipped

ローカルの Git LFS ファイルの削除

git lfs prune コマンドを使用して、ローカルの Git LFS キャッシュからファイルを削除できます。

$ git lfs prune
✔ 4 local objects, 33 retained
Pruning 4 files, (2.1 MB)
✔ Deleted 4 files

これにより、古いとみなされるローカルの Git LFS ファイルは削除されます。古いファイルは、以下のコミットにより参照されていないファイルです。

  • 現在チェックアウト中のコミット
  • (origin、または lfs.pruneremotetocheck が設定されているものへ) まだプッシュされていないコミット
  • 最近のコミット

既定では、最近のコミットは過去 10 日間に作成されたコミットです。これは、以下を追加して計算されます。

  • lfs.fetchrecentrefsdays プロパティの値 (これについては「追加の Git LFS 履歴の取得」で説明。既定値は 7)。
  • lfs.pruneoffsetdays プロパティの値 (既定値は 3)
git lfs prune

You can configure the prune offset to retain Git LFS content for a longer period:

# don't prune commits younger than four weeks (7 + 21)
$ git config lfs.pruneoffsetdays21

Unlike Git's built-in garbage collection, Git LFS content is not pruned automatically, so running git lfs prune on a regular basis is a good idea to keep your local repository size down.

You can test out what effect a prune operation will have with git lfs prune --dry-run:

$ git lfs prune --dry-run
✔ 4 local objects, 33 retained
4 files would be pruned (2.1 MB)

And exactly which Git LFS objects will be pruned with git lfs prune --verbose --dry-run:

$ git lfs prune --dry-run --verbose
✔ 4 local objects, 33 retained
4 files would be pruned (2.1 MB)
* 4a3a36141cdcbe2a17f7bcf1a161d3394cf435ac386d1bff70bd4dad6cd96c48 (2.0 MB)
* 67ad640e562b99219111ed8941cb56a275ef8d43e67a3dac0027b4acd5de4a3e (6.3 KB)
* 6f506528dbf04a97e84d90cc45840f4a8100389f570b67ac206ba802c5cb798f (1.7 MB)
* a1d7f7cdd6dba7307b2bac2bcfa0973244688361a48d2cebe3f3bc30babcf1ab (615.7 KB)

The long hexadecimal strings output by --verbose mode are SHA-256 hashes (also known as Object IDs, or OIDs) of the Git LFS objects to be pruned. You can use the techniques described in Finding paths or commits that reference a Git LFS object to find our more about the objects that will be pruned.

As an additional safety check, you can use the --verify-remote option to check whether the remote Git LFS store has a copy of your Git LFS objects before they are pruned:

$ git lfs prune --verify-remote
✔ 16 local objects, 2 retained, 12 verified with remote
Pruning 14 files, (1.7 MB)
✔ Deleted 14 files

This makes the pruning process significantly slower, but gives you peace of mind knowing that any pruned objects are recoverable from the server. You can enable the --verify-remote option permanently for your system by configuring the lfs.pruneverifyremotealways property globally:

$ git config --global lfs.pruneverifyremotealways true

Or you can enable remote verification for just the context repository by omitting the --global option from the command above.

サーバーからのリモート Git LFS ファイルの削除

The Git LFS command-line client doesn't support pruning files from the server, so how you delete them depends on your hosting provider.

In Bitbucket Cloud, you can view and delete Git LFS files via Repository Settings > Git LFS:

Bitbucket Cloud - delete lfs from server

Note that each Git LFS file is indexed by its SHA-256 OID; the paths that reference each file are not visible via the UI. This is because there could be many different paths at many different commits that may refer to a given object, so looking them up would be a very slow process.

特定の Git LFS ファイルに実際に何が含まれているかを調べるには、3つのオプションがあります。

  • look at the file preview image and file type in the left hand column of the Bitbucket Git LFS UI
  • download the file using the link in the right hand column of the Bitbucket Git LFS UI -search for commits referencing the Git LFS object's SHA-256 OID, as discussed in the next section

Git LFS オブジェクトを参照しているパスまたはコミットの検索

If you have a Git LFS SHA-256 OID, you can determine which commits reference it with git log --all -p -S <OID>:

$ git log --all -p -S 3b6124b8b01d601fa20b47f5be14e1be3ea7759838c1aac8f36df4859164e4cc
commit 22a98faa153d08804a63a74a729d8846e6525cb0
Author: Tim Pettersen <tpettersen@atlassian.com>
Date: Wed Jul 27 11:03:27 2016 +1000
Projectiles and exploding asteroids
diff --git a/Assets/Sprites/projectiles-spritesheet.png
new file mode 100755
index 0000000..49d7baf
--- /dev/null
+++ b/Assets/Sprites/projectiles-spritesheet.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:3b6124b8b01d601fa20b47f5be14e1be3ea7759838c1aac8f36df4859164e4cc
+size 21647

This git log incantation generates a patch (-p) from commits on any branch (--all) that add or remove a line (-S) containing the specified string (a Git LFS SHA-256 OID).

The patch shows you the commit and the path to the LFS object, as well as who added it, and when it was committed. You can simply checkout the commit, and Git LFS will download the file if needed and place it in your working copy.

If you suspect that a particular Git LFS object is in your current HEAD, or on a particular branch, you can use git grep to find the file path that references it:

# find a particular object by OID in HEAD
$ git grep 3b6124b8b01d601fa20b47f5be14e1be3ea7759838c1aac8f36df4859164e4cc HEAD
HEAD:Assets/Sprites/projectiles-spritesheet.png:oid sha256:3b6124b8b01d601fa20b47f5be14e1be3ea7759838c1aac8f36df4859164e4cc
# find a particular object by OID on the "power-ups" branch
$ git grep e88868213a5dc8533fc9031f558f2c0dc34d6936f380ff4ed12c2685040098d4 power-ups
power-ups:Assets/Sprites/shield2.png:oidsha256:e88868213a5dc8533fc9031f558f2c0dc34d6936f380ff4ed12c2685040098d4

You can replace HEAD or power-ups with any ref, commit, or tree that contains the Git LFS object.

Git LFS ファイルを含める/除外する

In some situations you may want to only download a subset of the available Git LFS content for a particular commit. For example, when configuring a CI build to run unit tests, you may only need your source code, so may want to exclude heavyweight files that aren't necessary to build your code.

You can exclude a pattern or subdirectory using git lfs fetch -X (or --exclude):

$ git lfs fetch -X "Assets/**"

Alternatively, you may want to only include a particular pattern or subdirectory. For example, an audio engineer could fetch just ogg and wav files with git lfs fetch -I (or --include):

$ git lfs fetch -I "*.ogg,*.wav"

If you combine includes and excludes, only files that match an include pattern and do not match an exclude pattern will be fetched. For example, you can fetch everything in your Assets directory except gifs with:

$ git lfs fetch -I "Assets/**" -X "*.gif"

Excludes and includes support the same patterns as git lfs track and .gitignore. You can make these patterns permanent for a particular repository by setting the lfs.fetchinclude and lfs.fetchexclude config properties:

$ git config lfs.fetchinclude "Assets/**"
$ git config lfs.fetchexclude "*.gif"

These settings can also be applied to every repository on your system by appending the --global option.

Git LFS ファイルのロック

Unfortunately, there is typically no way of resolving binary merge conflicts. The traditional way to avoid merge conflicts in version control systems is to lock files. Git LFS does not yet support file locking. However there is a detailed proposal to implement file locking and some implementation work started, so we'll hopefully see it in a Git LFS release sometime soon.

Until then, the best way to avoid merge conflicts is to communicate with team members before making changes to a binary file that they are likely to be modifying at the same time as you.

Git LFS の仕組み

If you're interested in learning more about clean and smudge filters, pre-push hooks, and the other interesting computer science behind Git LFS, check out this presentation from Atlassian on Git LFS at LinuxCon 2016: