最近着手处理一个混合有 Binary 的复杂 Repository,由于 Git 本身使用 text-diff 进行差分管理,而二进制文件无法很好的通过这种方式管理,因此需要使用 Git-LFS(Git extension for versioning large files) 分开处理,在支持 Git-LFS 的服务器中,只要本地启用 Git-LFS(git lfs install
) 并对需要的文件开启 LFS 追踪即可。
但这对于一个混合有 Binary 的复杂 Repository 并不是那么容易:如果对文件逐一添加追踪会消耗大量时间并且会导致 .gitattributes
文件变得特别复杂,不利于管理。那么显而易见,这里的最佳的方案是使用文件扩展名进行批量的管理,并针对不具备扩展名的文件,或者例外文件进行额外的管理。
对于实现这个目标,最大的问题在于如何快速可靠的确定文件类型。为了达到这个目的需要两个工具:
file
可以查看文件的类型,通过 file -i
还可以获取到它的 mime type 以及 charset。该命令在 Linux 或者 Windows 的 MSYS2 均存在,它通过检查文件的 magic num 确定文件类型,因此可以视为比较精确的文件识别方案。 git check-attr -a
可以查看当前文件是否被 git-lfs
追踪。git-lfs
工作时通过获取 .gitattributes
中的 filter 信息了解当前文件是否需要应用 filter: lfs
使用上述工具可以生成几个不同的列表:
- 应当使用 git-lfs 管理的文件类型
- 应当使用 git 管理的文件类型
- git-lfs 的例外
file
获取到的 charset 如果是 binary 则属于应该受到 git-lfs 管理的二进制文件,对比 git check-attr -a
得到的结果,它们应当一致。
file -i
输出内容样例:mg.txt: text/plain; charset=us-ascii
,使用 regex 分离信息。
我们可以用如下 Python 函数获取 mime_type 和 charset:
def get_mime_type(file_path): """call linux file to get mime type information """ cmd = f"file -i \"{file_path}\"" cli = shlex.split(cmd) result = subprocess.run( cli, capture_output=True, text=True, check=True ) p = re.compile(r"(?P<filename>.*):\s(?P<mime>.*/.*);\scharset=(?P<charset>.*)") m = p.search(output_str) mime_type = "" charset = "" if m is not None: filename = m.group("filename") mime_type = m.group("mime") charset = m.group("charset") return mime_type, charset
类似的也可以获取 git check-attr -a 的信息:
def test_lfs(root_dir, file_path): """call git to test lfs """ full_path = os.path.abspath(file_path) cmd = f"git check-attr -a \"{full_path}\"" cli = shlex.split(cmd) result = subprocess.run( cli, cwd=root_dir, capture_output=True, text=True, check=True ) output_str = str(result.stdout) if len(output_str)>0 and "filter: lfs" in output_str: return True else: return False
由于上述操作在运行 file
命令以及git
命令时没有 data dependence,因此完全可以使用 ThreadPoolExecutor
等简单的多线程工具将这个运行过程多线程化,即可加快整个流程。
最后只需要判断 charset 中是否存在 binary 字样即可,分别创建 text_extensions
、non_text_extensions
、exclude_extensions
等 set,直接将相应的扩展名加入 set,通过 set 的特性保证唯一性。
值得注意的是在生成 .gitattributes
时,需要关注执行顺序,在 git 中,每一个文件最多对应该文件中的一条规则,最后一个匹配的规则决定了文件的属性。因此最佳的方案是先加载所有的扩展名列表,随后是不包含扩展名的文件列表,最后是所有的例外文件扩展名或者文件名。
使用 git-lfs 的文件使用规则 {ext} filter=lfs diff=lfs merge=lfs -text
若需要为个别文件设置例外则使用 {ext} !filter !merge !diff !text
不符合上述任意一条规则的文件将不会被 lfs 管理。