Git LFSが1.0になってGitHubで使えるようになったので試してみた

先日GitHub Universeを[ダラダラと聞いてたら](https://daikikohara.github.io/blog/2015/10/02/github-universe-2015/)、Git LFSの1.0がリリースされてGitHub上で使えるようになったらしいので試してみました。

* 追記
  * 2016/8/20: まだベータでしたが[Bitbucket](https://confluence.atlassian.com/bitbucket/git-large-file-storage-in-bitbucket-829078514.html)でも使えるようになってました。試したら詳しく追記します。
  * 2017/5/20: `git lfs init`が`git lfs install`に[renameされていた](https://github.com/git-lfs/git-lfs/pull/838/)のでその旨追記しました。

## 概要

Git LFSはGitHubが中心となって開発しているラージファイル(画像・音声・映像等)を扱うための拡張機能です。gitレポジトリにはテキストファイルのポインタを保存しておいて別の場所にラージファイルの実態を保存しておくことができます。これによって不要なファイルのpullやfetchをすることなくgitレポジトリの最新化ができるようになったりします。
先日のGitHub UniverseでLFS1.0のリリースとGitHub上でのLFS対応が発表されたので試してみました。

## 今回試した環境

* 前提条件 - Git v1.8.2以上が必要
* 試した環境 - Fedora22(x64)

## インストール

* [公式サイト](https://git-lfs.github.com/)からインストーラを入手。
  * 公式サイトにはインストーラを使う方法の他にrpmやdebによるインストールも記載されていましたが、自分の環境ではできませんでした。
* インストーラを実行

```bash:インストール
tar xfz git-lfs-linux-amd64-1.0.0.tar.gz
cd git-lfs-1.0.0
sudo bash install.sh
```
独自のインストーラは使いたくなかったんですが、中身を見たら/usr/local/binにgit-lfsのバイナリを一つコピーしてgit lfs initするだけだったので気にせずインストーラを使いました。

ただ、少しだけ罠がありました。
git lfs initすると~/.gitconfgに以下のエントリを追加するんですが、sudo付けて実行するとrootユーザの.gitconfigに追加するので、インストール後に手動でgit lfs initを実行する必要があります。
(sudo付けないと/usr/local/binに書き込めない)

```bash:~/.gitconfigに追記される内容
[filter "lfs"]
        required = true
        clean = git-lfs clean %f
        smudge = git-lfs smudge %f
```

また、git lfs initするとカレントディレクトリに.git/hooks/pre-pushを作りますが、gitレポジトリ以外で打った場合は削除してしまってOKです。gitレポジトリではgit lfs initを毎回手動で打つ必要はなく、あとで出てくるgit lfs track等のコマンド実行時にpre-pushは自動で追加されます。

### 追記(2017/5/20)
久しぶりに使ったらfedoraでも`dnf install git-lfs`で入りました。また`git lfs init`コマンドは`git lfs install`に[renameされてました。](https://github.com/git-lfs/git-lfs/pull/838/)

## 使ってみる

### pushする側

* まず、ネットで拾ってきたパブリックドメインの画像ファイルを以下のように配置します。
images/1以下はpng、images/2、images/3以下はjpgがいくつか入ってます。

```bash:フォルダの中身
$ ls -lh images/1/ images/2/ images/3/
images/1/:
total 34M
-rw-rw-r-- 1 dk dk 1.8M Oct  3 07:17 abstract-20650.png
(以下略)

images/2/:
total 34M
-rw-rw-r-- 1 dk dk 2.3M Oct  2 21:21 garda-202068.jpg
(以下略)

images/3/:
total 35M
-rw-rw-r-- 1 dk dk 110K Oct  2 21:25 sun-49143_1280.jpg
(以下略)
```

* 現在lfsの対象になっているファイルはtrackサブコマンドで確認します。

```bash:trackでlfsの対象を確認
$ git lfs track
Listing tracked paths
```

* jpgをtrackするようにします。

```bash:jpgをtrackする
$ git lfs track "*.jpg"
Tracking *.jpg
```

* track対象になったか確認します。

```bash:再度trackを実行
$ git lfs track
Listing tracked paths
    *.jpg (.gitattributes)
```

* ここでカレントディレクトリに以下の内容の.gitattributesが出来上がっていました。

```bash:
$ cat .gitattributes
*.jpg filter=lfs diff=lfs merge=lfs -text
```

* また、track後に.git内にlfsフォルダが配置されました。

```bash:track前
$ ls .git/
branches  config  description  FETCH_HEAD  HEAD  hooks  index  info  logs  objects  refs
```

```bash:track後
$ ls .git
branches  COMMIT_EDITMSG  config  description  HEAD  hooks  index  info  lfs  logs  objects  refs
```

* さらにpre-pushフックも自動で追加されました。

```bash:pre-push
$ cat .git/hooks/pre-push
#!/bin/sh
command -v git-lfs >/dev/null 2>&1 || { echo >&2 "\nThis repository is configured for Git LFS but 'git-lfs' was not found on your path. If you no longer wish to use Git LFS, remove this hook by deleting .git/hooks/pre-push.\n"; exit 2; }
git lfs pre-push "$@"
```

* add、commit、pushは通常通りに行います。
commitまでするとls-filesサブコマンドで対象のファイルを確認できます。

```bash:ls-filesで対象のファイルを確認
$ git lfs ls-files
72efd47286 * images/2/garda-202068.jpg
f9a9d2f678 * images/2/golden-sunset-173594.jpg
(省略)
ecbaed3ab4 * images/3/sun-49143.jpg
699ef8586d * images/3/sun-49143_1280.jpg
(省略)
```

途中省略してますが、track対象をjpgのみにしたので、pngの画像ファイルは含まれていないことが分かります。

* ここでpushしたらエラーになってしまいました。

```bash:pushでエラー
$ git push origin master
(21 of 21 files) 33.09 MB / 33.09 MB
Post https://api.github.com/lfs/daikikohara/lfs-test/objects/5e17ac7d1353e297e821eb4b49a54eb9db53b95b19ddccf6b324fdb4aea30453/verify: EOF
error: failed to push some refs to 'git@github.com:daikikohara/lfs-test.git'
```

* が、再実行したら問題なく終わりました。

```bash:push再実行したら無事完了
$ git push origin master
(1 of 21 files, 20 skipped) 1.10 MB / 33.09 MB, 31.99 MB skipped
Counting objects: 25, done.
Delta compression using up to 4 threads.
Compressing objects: 100% (23/23), done.
Writing objects: 100% (25/25), 3.45 KiB | 0 bytes/s, done.
Total 25 (delta 0), reused 0 (delta 0)
To git@github.com:daikikohara/lfs-test.git
 * [new branch]      master -> master
```

* 念の為再度キレイな状態にしてやり直したら、今度は一発で大丈夫でした。ネットワークが不安定だったとかAPIがまだ不安定とかかもしれません。

```bash:キレイにして再実行したら一発でOKだった
$ git push origin master
(19 of 16 files) 40.43 MB / 34.85 MB
Counting objects: 20, done.
Delta compression using up to 4 threads.
Compressing objects: 100% (19/19), done.
Writing objects: 100% (20/20), 2.71 KiB | 0 bytes/s, done.
Total 20 (delta 0), reused 0 (delta 0)
To git@github.com:daikikohara/lfs-test.git
   09eb0ff..ece1774  master -> master
```

### pullする側

* pullは普通に行なえます。

```bash:pullする
$ git pull origin master
remote: Counting objects: 25, done.
remote: Compressing objects: 100% (23/23), done.
remote: Total 25 (delta 0), reused 25 (delta 0), pack-reused 0
Unpacking objects: 100% (25/25), done.
From github.com:daikikohara/lfs-test
 * branch            master     -> FETCH_HEAD
 * [new branch]      master     -> origin/master
```

* この状態だとファイルの中身はテキストファイルのポインタになっているためpullは一瞬で終わります。

```bash:中身がテキストなのでファイルサイズが小さい
$ ls -lh images/2
total 84K
-rw-rw-r--. 1 dk dk 132 Oct  3 14:42 garda-202068.jpg
(以下略)
```

* ファイルの中身を見てみるとテキストなのが分かります。

```bash:jpgの中身を見てみる
$ cat images/2/garda-202068.jpg
version https://git-lfs.github.com/spec/v1
oid sha256:72efd472864c7faccdbbf832a4b63e7c2faff150eab3ad5ffc8c1df091084575
size 2402319
```

* ファイルの実態をpullするにはgit lfs pullを使います。
lfs pullするとファイルの中身が画像ファイルとして見れるようになります。

```bash:ファイルの実態のpull
$ git lfs pull origin master
(21 of 21 files) 32.97 MB / 33.09 MB
```

* ちなみに、includeやexcludeをすることもできるので、git pullは普通にしてラージファイルは必要なファイルのみlfs pullすればローカルは軽いままにしておけるし、pullやfetchで長時間待つこともないですね。
また、git pullすればgitレポジトリ自体は最新にできるので、チーム内等でlfs使ってない人がいても問題ないです。

```bash:includeとexclude
$ git lfs pull origin master --include=images --exclude=images/3
```

* なお、GitHubのUI上ではファイルはテキストファイルのポインタじゃなくて普通に画像ファイルとして見れます。


以上。
簡単ですが使い方でした。
機能はもっと色々あるようなので詳細は公式サイトを見てもらえればと思います。