最佳实践:Node.js集成Git方案梳理

1,128 阅读5分钟

1. 背景

在开发一款js静态分析工具时,我遇到了一些git相关的需求:

  • 通过git比较出工程中两个分支的改动文件
  • 跨分支读取

针对这个两个问题,我们先看看用 git 命令如何操作:

1.1 分支比较

通过如下的 git 命令即可进行分支比较:

git diff [分支1] [分支2] --name-status

打印结果可能如下:

M       a.js
D       b.vue
A       c.vue
R092    .whistle.js     .whistle111.js
...

这里每一行就是一个改动文件,空格前是改动类型,空格后是改动的具体文件。

M: 修改 D: 删除 A: 新增 R: 重命名,这里的 092 代表是改动前后两个文件的相似度。[了解更多]

1.2 跨分支读取

这里先说一下跨分支读取的意思:假如我现在处于a分支中开发,我想要实现在不切换分支的情况下读取b分支某个文件的内容和某个文件夹下面的文件列表。

通过调研,发现可以通过 git cat-file 和 git ls-tree 来实现:

  • 读取文件内容
#读取文件
git cat-file -p [分支]:[文件相对路径]
  • 列出目录下的文件及文件夹列表
git ls-tree [分支]:[目录相对路径]

所以,现在的问题是如何在nodejs中集成这功能,且支持打包到 Cli工具和 Vscode 拓展。

2. 实现方案

2.1 exec 调用 Git 命令

首先想到最简单的方案就是通过Node.js 的 child_process 模块中的 exec调用Git命令:

import { execSync } from "child_process";

function getChangeFileList(baseBranch: string, targetBranch: string) {
  return execSync(`git diff ${baseBranch} ${targetBranch} --name-status`, {
    cwd: projectDir,
  }).toString().trim();
}

这种方法使用简单,无需依赖外部的包,且能够被打包到node cli工具和vscode插件中。

目前有许多js库都基于这个原理,例如 simple-git,可以进一步简化使用的成本。

但经过一段时间的使用,发现这种方式存在两个问题:

  1. 依赖本机安装的Git,且需要正确配置环境变量(一般开发都会安装git,不是很严重的问题)。
  2. 通过创建子进程调用外部命令,而创建子进程是有性能开销的,当调用太多次后有性能问题,具体有多差可以看后面的对比。

由于有了这些问题,所以调研实践了市面的其他几种方案。

2.2 execFile 调用 Git 可执行文件

上面我们介绍了通过 child_process模块中的 exec(execSync是exec的同步形式)方法,它是通过调用shell来执行命令的,而在这个模块中还有一个execFile 方法,可以通过git程序的路径来执行:

const res = execFileSync("/usr/bin/git", ["cat-file", "-p", `master:packages.json`]);

下面是nodejs文档的描述

child_process.exec() 和 child_process.execFile() 之间区别的重要性可能因平台而异。 在 Unix 类型的操作系统(Unix、Linux、macOS)上,child_process.execFile() 可以更高效,因为它默认不衍生 shell。 但是,在 Windows 上,.bat 和 .cmd 文件在没有终端的情况下无法自行执行,因此无法使用 child_process.execFile() 启动。

可以看到这种方法性能有所提升,但是必须指定git的位置。

2.3 dugite

github.com/desktop/dug…

github.com/desktop/dug…

dugite 是一个execFile原理实现git绑定的js库,但于上面不同的是,它在安装后会调用postinstall钩子去下载一个精简版版的git,所以不需要手动指定git路径。

image.png

postinstall 钩子

image.png

node_modules

中的可执行文件

再从dugite的github可以看成,dugite是github-desktop的一部分,而github-desktop是基于electron开发的,那么可以推测出这个库对于开发跨端的桌面程序有很好的支持。

image.png

2.3 NodeGit

官网:www.nodegit.org/

NodeGit 是一个基于 C++ 实现的Node.js 原生模块,实现了对 libgit2 的封装。

const repo = await NodeGit.Repository.open(gitBasePath);

经过使用发现这个库优点如下:

  1. 功能强大,API复杂
  2. 原生模块性能好
  3. API基于Promise来实现。

但是使用了这个库也发现了一些问题:

  1. 原生模块导致安装复杂,比如在我的mac m1 电脑安装会失败,需要指定安装^0.28.0-alpha.21 这个版本。
  2. 在一些低版本Linux 上面安装会失败,需要手动升级GCC版本。
  3. vscode插件不支持原生模块。

2.4 isomorphic-git

isomorphic-git.org/

isomorphic-git 是一个纯 JavaScript 的库,提供了跨浏览器和 Node.js 环境使用的 Git 功能。它不依赖于外部的 Git 客户端或二进制文件,而是通过 JavaScript 实现了 Git 的核心功能。

使用 isomorphic-git 实现跨分支读取:

import * as git from "isomorphic-git";
import fs from 'fs';

const oid = await git1.resolveRef({
    fs: fs,
    dir: projectDir,
    ref: 'master',
});

let cache = {};
const res = await git1.readBlob({
    fs: fs,
    dir: projectDir,
    oid,
    cache,
    filepath: 'packages.json'
 });
 console.log(res.blob);

这个库支持通过 cache 参数缓存结果,提高批量读取文件的速度。

3. 总结

下面我会从性能、浏览器支持、vscode插件机制几个角度进行对比:

exec + gitexecFile + gitdugiteNodeGitisomorphic-git
读取 620个不同文件11888 ms9357 ms4002 ms92ms开启cache: 810 ms 不开cache: 12828 ms
star--4435.5k7.1k
浏览器支持不支持不支持不支持不支持支持
vscode插件支持支持不支持,需要 npm install下载依赖不支持支持
node cli 工具支持支持支持支持,但可能需要升级系统的库支持
API支持需要手写git命令需要手写git命令需要手写git命令API封装度高,使用复杂API封装度高,使用较为简单

根据上述表格,可以得到如下结论:

  1. isomorphic-git综合能力最强,可以兼容大部分场景。
  2. 需要浏览器支持使用isomorphic-git
  3. 需要性能使用 nodegit
  4. electron 集成可以考虑使用 dugite,github 官方背书
  5. 不依赖外部git的方案:dugite、nodegit、isomorphic-git

但接入哪一个,还需要在项目中对比。