git lfs管理下のファイルに対するコンフリクト解消漏れを防ぐ

コンフリクト解消をしそこねて>>>>>>みたいなマーカーがそのまま上がってしまってる場合がある。以下に紹介されているようにhookを書いてがマーカーが含まれていればcommitをキャンセルすることで回避できる。

[Git]コンフリクトをよりスマートに解消したい!

ただ、git lfs管理下のファイルがあった場合には上記だけだとうまくいかない。 git lfsのファイルはgit上ではポインタファイルと呼ばれるメタデータとして存在している。実体は.git/lfs/objects以下に保持されていて、ポインタファイルではそのhash値やファイルサイズなどを記録している。 詳しくはこちら

git-lfs/spec.md at master · git-lfs/git-lfs · GitHub

実体がworkspace内に存在しないファイルについては、git checkoutをした際にgit lfsのサーバからdownloadされて.git/lfs/objects以下に置かれる。

git lfs管理下のファイルがコンフリクトした際は以下のような差分が出ている。(適当なimageファイルでコンフリクトさせた)

-oid sha256:71fbb9a95fe26f41d33d50766a01b68204e73970bac5922b2a6f31617d96106f
-size 21890
+oid sha256:c1a8f9c529bbb8b049397593c869be1a4f9d1518948297f6386be315984c7196
+size 148

これが表しているのは、.git/lfs/objects以下にある実体ファイルがsize 148のファイルになっているということ。そこで実体を見てみるとこうなっている。

<<<<<<< HEAD
oid sha256:71fbb9a95fe26f41d33d50766a01b68204e73970bac5922b2a6f31617d96106f
size 21890
=======
oid sha256:c1a8f9c529bbb8b049397593c869be1a4f9d1518948297f6386be315984c7196
size 148
>>>>>>> test

ポインタファイルのフォーマットがコンフリクトマーカーによって崩れたため、ポインタファイルとして認識されなくなり、実体ファイルとして.git以下に置かれたと考えられる。

ちなみに実体ファイルのpathはこんなルールで決まっている

  • .git/lfs/objects/<sha256の上2桁>/<sha256の3,4桁目>/<sha256そのもの>
    • 今回の例なら
    • git/lfs/objects/71/fb/71fbb9a95fe26f41d33d50766a01b68204e73970bac5922b2a6f31617d96106f

そのため、これをhookから検出するためには実体ファイルを取得してそこに含まれる文字列を見ればOK。そしてgit lfsにはそのためのコマンドとしてgit lfs sumdgeが用意されている(git checkout時の処理では内部でこれを使っている)

ポインタファイルから実体を取ってくるためには以下

$ cat <ポインタファイル> |  git lfs sumdge --skip

これにgrepなどでコンフリクトマーカーを検出するようにすればhookで検知することが可能