3度の飯と最新技術

もう巨大なデータをgitignoreしなくていい! ~git-mediaの使い方~

はじめに

gitはコミットごとにレポジトリ内のファイル全てをスナップショットとして保存するというリッチな 設計になっている。
それがgitの便利さの所以なのだが画像データや音声データのようなバイナリデータを持とうとすると 少しの変更でもそのたびにコピーが生じてファイルサイズ分の容量が増えることになり、あっという間にレポジトリが 肥大化してしまう。
特に学習結果をファイルに保持してテスト等に使いまわすようなプログラムを管理しようとすると アルゴリズムのパラメータを少し変えるたびに100kB近い容量が増えていき、実にイケてない。

普通なら.gitignoreに*.xmlと書いてデータ自体は手動管理したり、シンボリックリンクにして別ディレクトリに置いてそれだけrsyncで同期するようにしたりするんだが 過去の実験時の状態に戻れなかったり、毎回rsyncするのは不便だった。 なんか無いかなーと思ってstack overflowを彷徨ってたらgit-mediaっていうのを見つけたので導入してみた。

このプラグインを使うとgitのコミット/チェックアウトの時にトリガ機能を使ってデータファイルを自動でハッシュに変換して 指定した場所に転送してくれる。
(注:ハッシュ=SHA1を用いて生成した、データに対して一意な40字の文字列)

ファイルのバージョン自体はハッシュで管理されるので昔のものをチェックアウトすれば自動的に古いデータに逆変換されるという仕様である。
便利そうに聞こえるがコマンドや構造の概念が少し複雑で、日本語の解説記事もなかったので入りづらかった。   コードを読んで理解してから使ってみたら実際にはほとんど裏の複雑さを意識する必要はなく、とても使い心地がよかったのでまとめておく。

概念

git-mediaを理解するときに大事なのがワーキングツリーとレポジトリの違い。
それぞれ簡単に言ってしまえばワーキングツリーは実際に作業しているディレクトリやファイルのそのもののことで、 レポジトリは.git以下にある、git管理下に収まったファイルのことである。
普通のgitではcommitやチェックアウトを使ってこれらの二つの同期をとっていく(インデックスは単純化のため無視)

commit image

git-mediaを導入するとデータファイルにおいてこの二つに差分が生じる。 ワーキングツリーにおけるデータファイルはレポジトリの中では40字の一意なハッシュに置換される。

commit image

これにより、実際に使うファイルとしてはちゃんとしたデータを持ちつつ、 レポジトリ内ではハッシュで変更を管理できるようになる。 また、ハッシュからデータへの逆置換はそのコミットをチェックアウトするまで必要ないので git clone時に無駄なファイルをダウンロードする必要も無くなる。

インストール

git-media に書いてあるとおりでおk。
今だとgit-media-0.1.2が入ることに注意

実際の使い方

基本的にはgit-mediaのREADMEを読めば全部書いてある。
ただ、単語が省略されてたりしてわかりにくいので軽く補足を入れながら再解説をする。

はじめにインストールしたらすること

  • フィルタの設定
    git config —global filter.media.clean ‘git-media filter-clean’
    git config —global filter.media.smudge ‘git-media filter-smudge’

これがワーキングツリーとレポジトリの間に差分を生むおまじない。コミット時とチェックアウト時にファイルの中身に処理をかけれる。
詳しくはgitの属性 を参照。個人的には目から鱗だった。他にも使えそう。

  • ファイルの転送先の設定 vim ~/.gitconfig
    以下を追記 (SCPで管理する場合) [git-media]
    transport = scp
    scpuser = [ユーザーID]
    scphost = [ホスト]
    scpport = [ポート]
    scppath = [絶対パス]

これでそのPC内で使うgit-mediaのファイルが全てscppath以下に格納される。
もしレポジトリごとにディレクトリを分けたり、そもそもscpだけでなくS3や、 実験的にlocalディレクトリを使いたいとかであれば各レポジトリの.git/configに同じように書く。

各レポジトリごとにすること

  • どのファイルをフィルタに通すかの指定
    touch .gitattributes
    echo ‘*.xml filter=media -crlf’ >> .gitattributes
    echo ‘*.avi filter=media -crlf’ >> .gitattributes

コマンドの使い方

media sync/media status/media clearという3つのコマンドが用意されているが gitによくある、何に対してどういう方向でxxxされるのかわかりづらい問題があるので 簡単に説明する。
関係するのは

  1. ローカル:ワーキングツリーの中にあるローカルファイル
  2. キャッシュ:.git/mediaの中にあるキャッシュ (≠リポジトリ管理下のファイル)
  3. リモート:configで設定したtransport先にあるリモートファイル

の3つ。レポジトリは基本的に関係しない。 出力文中にexpandという言葉が使われているが、これはローカルにあるハッシュファイル(逆置換にミスったもの。普通はデータになってる)をキャッシュに存在するデータファイルで置き換えることを意味する。

git media sync

内部ではexpand_referenceという処理とupload_local_cacheという処理が走っている。
前者はリモート->キャッシュ->ローカルという順でダウンロードおよびコピーを行い、後者はキャッシュ->リモートでアップロードを行う(もちろんコピー済みのものはコピーしない)

git media status

リモート<–>キャッシュ<–>ローカルの差分を表示するコマンド。
具体的には、media-syncでexpand_referenceとupload_local_cacheの対象となるファイルの一覧が出力される。
ローカルにあるにも関わらずハッシュのままのファイルがto_expand。
既に正しくデータになっているファイルがexpanded。
レポジトリに存在するのにローカル(=ワーキングツリー)に無いファイルがdeletedである。
また、upload_local_chaceの対象となるファイルのために、リモートと キャッシュの差分を調べ、すでにリモートにあるをpushd media、まだ無いファイルを unpushed mediaとして表示する。

git media clear

リモートに送信済みのキャッシュのファイルを削除する。

まとめ

syncにリモート->キャッシュ->ローカルという処理とキャッシュ->リモートという処理が混ざっているせいでわかりにくいが、 使うのは、smudgeフィルタがキャッシュに無いデータをローカルにexpandしようとして失敗し、 ハッシュファイルが現れてしまった時(media missing っていう警告が出る)とpushの時だと思っていいと思う。前者は普通にsmudgeフィルタの処理に組み込めばいいじゃんと思うんだが、コード中にはTODOと書かれたまま放置されてる。 なんでだろう?謎

長く運用したわけじゃないからそんなに自信はないが、ハッシュ管理のおかげでコンフリクトはありえ無いので 基本的にsyncしまくってれば問題なさそう。なのでstatusは基本的に使ってない。 またディスク容量さえ気にならなければキャッシュが膨れても何も影響しないのでclearも基本的に使ってない。

所感

YAPC::Asia Tokyo 2013でGitの話を聞いてからgitの内部の仕組みに興味を持っていたんだが、 git-mediaのアプローチはとても参考になった。rubyでgitコマンドが拡張できるとことか、 smudge/cleanフィルターの存在とかも初めて知った。

webサービスの開発をしてると、やっぱりgitにも使いにくいところがちらほらあって、今は運用や汚い方法でカバーしてるんだけど、 git本体の拡張で解決できればクールだなーと思った。 とりあえずrubyからgitが操作できるようになるGritRuggedが色々できそうだったのでそっちも見て見ようと思う。

git