.gitattributes で Git LFS の適用をディレクトリごとに変更する

こんにちは、kenzauros です。

Git LFS で巨大ファイルとして管理したいファイルgit lfs track で管理対象として指定すればいいのですが、今回は同じリポジトリ内でフォルダによって LFS の適用を切り替えたくなったので、その方法をメモしておきます。

Git LFS の適用

バイナリーでなくとも、たとえば数 MB を超えるようなデータベースダンプファイルなどは差分を表示するだけでも時間がかかる上、特に差分管理が必要でない場合があります。

こんなときは git lfs track *.sql と叩いて、拡張子が *.sql のファイルを Git LFS で追跡させるようにします。

これを行うとルートディレクトリの .gitattributes ファイルに下記のような設定が追加されます。

*.sql filter=lfs diff=lfs merge=lfs -text

.gitattributes のリファレンスはあまりいいものがないのですが、見づらいながらも公式ページでオプションが確認できます。

これを見ながら *.sql の設定を解釈すると filter, diff, merge オプションがそれぞれ lfs に設定され、 text オプションの設定が解除されていることがわかります。

これで *.sql ファイルは Git LFS が扱ってくれるようになり、 SourceTree などにも diff が表示されなくなります。

特定のディレクトリだけ設定を変える

さて、すべての *.sql ファイルが巨大なダンプファイルなら問題ないのですが、小さな SQL を書いたファイルの場合、変更を差分で見たいことがあります。

たとえば下記のようなディレクトリ構造を考えます。

ルートの .gitattributes に前項の設定がされており、 LARGE/LARGE.sql が巨大なファイル、 small/small.sql が比較的小さな SQL ファイルだと仮定します。

/
    .gitattributes
    LARGE/
        LARGE.sql
    small/
        small.sql

この例だとルートの .gitattributes のせいで small.sql まで Git LFS で扱われてしまいます。

これを解消するには下層の small/ ディレクトリにも .gitattributes ファイルを配置します。

/
    .gitattributes
    LARGE/
        LARGE.sql
    small/
        .gitattributes <- 追加
        small.sql

Git は .gitignore.gitattributes も下層ディレクトリに配置することができ、下層のファイルにはその下層ディレクトリに存在する設定が上書きされて適用されます。

ここにルートディレクトリで指定された filter=lfs diff=lfs merge=lfs -text を打ち消す設定を追記します。

*.sql !filter !diff !merge text

filter, diff, merge オプションを未指定の状態に, text オプションを設定状態に設定しています。 (リファレンスも不親切で明記はされていないのですが、 ! で未指定の状態に戻すことができます。)

これでこのディレクトリ以下は *.sql を通常のソースコードのように差分管理できるようになっているはずです。

おまけ: .gitattributes の属性

参考までに .gitattributes の属性について、公式ページの内容を要約しておきます。

意訳がずれているかもしれませんので、正確な情報は公式ページを参照してください。

text: 改行コードの正規化

text 属性は改行コードの正規化を有効にするかどうか+ファイルの中身を判断するかどうかを設定します。

  • text: 強制的にテキストファイルだと認識され、中身にかかわらず改行コードの変換が行われます。
  • -text: 改行コードの正規化を全く行いません。バイナリーファイルで -text が指定されるのはこのためです。
  • text=auto: 中身がテキストだと判断されるとチェックインの際に改行コードが LF に変換されます。ただし、CRLF のファイルは変換されません。

よくルートディレクトリの .gitattributes* text=auto と指定されているのはこのためですね。

未指定の状態では core.autocrlf の設定に依存して変換が行われます。

Git における改行コード関連は core.autocrlfcore.eol を含めてけっこうややこしいので私自身完全には把握できていません。

eol: 改行コードの指定

eol 属性は text と組み合わせて、ファイルの改行コードを指定します。

  • eol=crlf: チェックインの際に正規化され、チェックアウトの際に CRLF に変換されると記載されていますが、チェックインのときにどう正規化されるのか明記がありません。
  • eol=lf: チェックインの際に LF に正規化され、チェックアウトの際には CRLF に変換されることを防ぎます。つまり常に LF のファイルとして扱います。

リファレンスでもサンプルとして上がっている下記の設定が参考になります。

*               text=auto
*.txt		text
*.vcproj	text eol=crlf
*.sh		text eol=lf
*.jpg		-text

*.txt, *.vcproj, *.sh はテキストファイルとして改行コードを正規化するが、 *.vcproj は CRLF 、 *.sh は LF であることが作業ディレクトリ内で保証されるようになります。

この作業ディレクトリ内で保証というのが割とツボで、つまりリモートリポジトリに上がっているファイルがどういう状態かは保証されません。

filter: checkout/add 時のフィルター指定

Git における filter は checkout するときや add するときにファイルに対してなんらかの操作をかけるものです。

Git LFS はこの filter 機能を使って、 checkout/add のタイミングでポインターファイルと実際のコンテンツファイルとを相互に置換しています。

その他 filter を使うといろいろできるようですが、あまり活用されているところを見たことがありません。どなたかご存知でしたら教えてください。

diff: 差分生成の指定

diff 属性はどのように差分を生成するかを指定します。

  • 未指定: テキストと判断できるファイルかつ core.bigFileThreshold より小さい場合に、テキストとして扱い、差分を生成します。
  • diff: テキストファイルとして扱い、差分を生成します。
  • -diff: バイナリファイルとして扱い、 Binary files differ を生成します。
  • diff=ドライバー名: 指定したドライバーを使って差分を生成します。

csshtml, php, tex などいくつかデフォルトのパターンが定義されているようですが、使用されている例を見たことがありません。

merge: マージ方法の指定

merge 属性はマージをどのように行うかを指定します。

  • 未指定 もしくは merge: 標準のマージドライバー (3-way merge driver) を使ってマージします。
  • -merge: 現在のブランチのバージョンを仮の結果として、競合があることを通知します。バイナリーファイルなど自動でマージできない場合に使用します。
  • merge=ドライバー名: 指定したドライバーを使ってマージを行います。 text を指定すると merge と同様に、 binary を指定すると -merge を指定した場合と同様になります。

ちなみに 3-way merge driver というのはコンフリクト部分が <<<<<<<, =======, >>>>>>> で表示される、いつものアレです。

kenzauros