使用 simple-git 在 Node.js 中高效调用 Git 命令
我因为在写 cli 工具的时候,大量的使用各种 npm 包,近期把我认为好玩的一些包分享出来。纯属自己的一些见解,有什么建议可以私底下沟通。
今天要我想重点介绍的就是simple-git,simple-git是一个用于在 Node.js 中简化 Git 命令操作的轻量级库。它提供了一种简单的接口来执行常见的 Git 操作,而无需直接使用命令行。
为什么要用他?
- 优化提交效率:因为我在写PLUS团队CLI工具的时候有一个场景,就是代码的提交发布,这个动作在开发中非常频繁,正常的提交我们都需要有很多步骤如
git add,git commit,和git push,其实这些步骤是可以简化和可视化的,直接一个xxx push就能拿完成上面三个动作 - 标准化
commit信息:通过对simple-git的方法的二次封装,可以轻松实现提交信息的格式化,确保每次提交都能提供清晰、有条理的信息,这对于维护代码库的整洁性和可读性大有裨益 - 直接跟 CI/CD打通:很多团队在触发公司流水线的时候还在通过手动或者提交代码,但是这两个动作都有一些问题,手动提交效率低,直接提交代码就是太随意不是所有提交都需要去触发流水线,可以通过对 git 一些动作去选择性触发流水线
综上所述:simple-git 不仅通过其简洁的接口降低了使用 Git 的复杂性,还通过优化提交低了使用 Git 的复杂性,还通过优化提交流程和促进提交信息标准化。
安装方式
我比较喜欢pnpm相对于npm yarn 更加便捷和快速
shell
体验AI代码助手
代码解读
复制代码
pnpm install simple-git npm install simple-git
基础的方法封装
- 获取分支:获取当前分支
gitPath是当前用户的操作目录
js
体验AI代码助手
代码解读
复制代码
const simpleGit = require('simple-git'); const getBranchName = async (gitPath) => { return await simpleGit(gitPath) .branch() .then((branch) => { return branch.current || null; }) .catch((err) => { console.log(err); return null; }); };
- 拉取分支代码:拉取分支代码,并且输出一些信息,gitPath 是当前用户的操作目录,
branchName是拉取那个分支名,其中我对拉取的信息做了一个表格输出cli-table,后续有人感兴趣我可以讲下这个包的使用
js
体验AI代码助手
代码解读
复制代码
const Table = require('cli-table');//也是一个很好玩的包,支持各种表格信息输出 //表格的边框样式 const chars = { top: '═', 'top-mid': '╤', 'top-left': '╔', 'top-right': '╗', bottom: '═', 'bottom-mid': '╧', 'bottom-left': '╚', 'bottom-right': '╝', left: '║', 'left-mid': '╟', mid: '─', 'mid-mid': '┼', right: '║', 'right-mid': '╢', middle: '│' }; const pullBranch = async (gitPath, branchName) => { return await simpleGit(gitPath) .exec(() => { //对 log 做了一层封装可以用 console.log 代替 log.info(`《${branchName}》分支 开始拉取...`); }) .pull('origin', branchName) .then((update) => { if (update) { log.info(`《${branchName}》分支 拉取信息`); var headArr = Object.keys(update.summary); var valArr = Object.values(update.summary); var table = new Table({ head: headArr, colWidths: [30, 30, 30], chars: chars }); table.push(valArr); console.log(chalk.greenBright(table.toString())); log.success(`《${branchName}》分支 拉取成功`); console.log('\n'); } return true; }) .catch((e) => { console.log(e); log.error(`《拉取线上分支${branchName}》异常`); return false; }); };
- 提交信息并且 pus分支,这是把两个动作合并成一个动作,commit 和 push
js
体验AI代码助手
代码解读
复制代码
const commitPushBranch = async (gitPath, pushBranch, commitMessage) => { return await simpleGit(gitPath) .commit(commitMessage) .push('origin', pushBranch) .then((status) => { log.success(`${pushBranch}代码提交成功`); return true; }) .catch((e) => { log.error(e.message); return false; }); };
- 切换分支
js
体验AI代码助手
代码解读
复制代码
const checkBranch = async (gitPath, branch) => { return await simpleGit(gitPath) .checkout(branch) .then((st) => { log.info(st); return true; }) .catch((e) => { log.error(e); return false; }); };
- 合并分支,注意异常我会切到用户当前的分支
js
体验AI代码助手
代码解读
复制代码
const mergeBranch = async (gitPath, currentBranch, targetBranch) => { return await simpleGit(gitPath) .mergeFromTo(currentBranch, targetBranch) .then((status) => { log.success(`《${targetBranch}》合并《${currentBranch}》分支成功`); return true; }) .catch((e) => { log.error(e); simpleGit(gitPath).checkout(currentBranch); return false; }); };
- push 分支
js
体验AI代码助手
代码解读
复制代码
const pushBranch = async (gitPath, branch, currentBranch, successMsg) => { await simpleGit(gitPath) .push('origin', branch) .then((status) => { log.success(successMsg); }) .catch((e) => { log.error(e.message); simpleGit(gitPath).checkout(currentBranch); }); };
- 删除远程分支
js
体验AI代码助手
代码解读
复制代码
// 删除远程分支用 const deleteOriginBranch = async (gitPath, branchName) => { await simpleGit(gitPath) .push('origin', [branchName, '--delete']) .then((status) => { log.success(`已删除远程分支${branchName}`); }) .catch((e) => { log.error(`删除远程分支${branchName}失败`); log.error(e.message); }); };
跟 CLI 结合或者说跟业务结合
我还是按照前面提到的三个功能
- 优化提交效率:我在写 cli 的时候会有一个提交命令,大体意思就是你在终端运行
xxx deploy,具体注册 cli 的命令我这就不细讲,只讲git提交相关部分
js
体验AI代码助手
代码解读
复制代码
//这是注册的命令 program .command('deploy') .description('把代码按照部署规则提交到git仓库,触发自动部署的功能') .alias('d') .action(function () { const packageName = '@xxx-cli/deploy'; const cmd = arguments[arguments.length - 1]; const cmdName = cmd._name; exec( { cmdName, packageName }, cmd ); }); //开启部署动作 async runDeploy() { //第一步:让你选择你要把你的代码部署到那个环境 const { deployEnv } = await prompts({ type: 'select', name: 'deployEnv', message: 'Pick 请选择部署的环境:(正常开发顺序是 beta -> pretest -> production )', choices: [ { title: 'beta', value: 'beta', description: '部署预发环境(打预发包,相当于我们在预发环境开发,上线前有大量的bug还需要修改)' }, { title: 'pretest', value: 'pretest', description: '部署预上线环境(打线上包,此功能主要是打包线上包发布到预发)' }, { title: 'production', value: 'production', description: '直接部署上线(把pretest的最后版本合并到master触发上线审批流程)' } ] }); //选择不同的环境进行不同代码的提交流程 switch (deployEnv) { case 'beta': this.deployBeta(); break; case 'pretest': this.deployPretest(); break; case 'production': this.deployProduction(); break; default: log.error(`未识别的环境,请联系开发人员`); break; } } //此处只讲部署到预发,通过这一个方法,把代码提交,信息填写,和触发流水线整合在一起 async deployBeta() { // 预发的环境的特有分支 这次用来标准化,只有发布到这个分支才会触发流水线 const branch = 'xxx-beta'; // 获取所在分支 const currentBranch = await getBranchName(this.userRoot); // 其中不准许对 master 进行操作 主要用来规范化 if (currentBranch && currentBranch !== 'master' && currentBranch !== branch) { // 提示是否拉取本分支的线上代码 const { needPullCurrentBranch } = await prompts({ type: 'toggle', name: 'needPullCurrentBranch', message: `是否拉取《${currentBranch}》分支的线上代码?`, initial: true, active: 'yes', inactive: 'no' }); // 拉取线上的开发中的分支代码 默认是不拉取的 if (needPullCurrentBranch) { const needContinue = await pullBranch(this.userRoot, currentBranch); // 判断拉取失败了 就退出程序不往下走了 !needContinue && process.exit(); } // 本地提交的status await getStatus(this.userRoot); // 选择提交功能 const { commitType } = await prompts({ type: 'select', name: 'commitType', message: '请选择提交类型', //此处做了终端复选框 让用户直接选择信息并且填写 choices: [ { value: 'feat', title: 'feat', description: '特性(feat):新功能' }, { value: 'fix', title: 'fix', description: '修复(fix):修复' }, { value: 'refactor', title: 'refactor', description: '重构(refactor): 重构(既不是增加feature,也不是修复bug)' }, { value: 'perf', title: 'perf', description: '性能(perf):性能优化' }, { value: 'docs', title: 'docs', description: '文档(docs):文档变更' }, { value: 'style', title: 'style', description: '格式(style):代码格式(不影响代码运行的变动)' }, { value: 'chore', title: 'chore', description: '日常(chore):构建过程或辅助工具的变动' }, { value: 'revert', title: 'revert', description: '回滚(revert):代码回退' }, { value: 'test', title: 'test', description: '测试(test):增加测试' } ] }); // 用户取消选择 !commitType && process.exit(); // 输出commit信息 约束提交信息 const { commitMessage } = await prompts({ type: 'text', name: 'commitMessage', message: `请输入本次提交的信息`, validate: (v) => { const minLength = 3; if (v.length > minLength) { return true; } else { return `请输入描述文字的长度大于${minLength}`; } } }); // 用户取消选择 !commitMessage && process.exit(); // 最后核实是否提交 const { needCommit } = await prompts({ type: 'toggle', name: 'needCommit', message: `请确认是否提交?`, initial: true, active: 'yes', inactive: 'no' }); // 判断是否取消 if (!needCommit) { log.info('取消提交'); process.exit(); } // 提交开发代码 const hasPush = await commitPushBranch( this.userRoot, currentBranch, `${commitType}:${commitMessage}(xxx-beta)` ); // 判断是否取消 if (!hasPush) { log.info('push分支失败,退出提交流程'); process.exit(); } // 拉取线上部署分支代码 const pullDone = await pullBranch(this.userRoot, branch); // 切换到merge分支 pullDone && (await checkBranch(this.userRoot, branch)); // 从部署分支合并开发分支的代码 const isMerge = await mergeBranch(this.userRoot, currentBranch, branch); //如果合并失败立刻切回开发分支 !isMerge && (await checkBranch(this.userRoot, currentBranch)); // 提交merge分支代码 isMerge && (await pushBranch( this.userRoot, branch, currentBranch, `《${branch}》代码提交成功,即将触发自动发布预发流程,最终结果以斑布发布流程为准` )); // 切会原有开发分支 await checkBranch(this.userRoot, currentBranch); } else { currentBranch == 'master' ? log.error(`master分支受保护,请不要在master分支执行部署命令`) : log.error( `无法获取到你的开发分支,请关联git和确定git相关的分支,或者你所在的分支在《${branch}》受保护的分支上` ); process.exit(); } }
- 标准化
commit信息,在上述代码中里面有一个动作是让你选择你要提交的内容是什么,还有对内容进行了一些限制,这块其实还可以做更加严格的封装, 其中commitType是提交信息前部分内容,commitMessage是提交信息的主体内容,我还在最后加了上我有 cli 工具带上的特有标识符xxx-beta,这样就可以识别那些信息是有 cli 提交上来,这样可以在流水线上对不规范的非 cli 提交的内容进行过滤不给打包
js
体验AI代码助手
代码解读
复制代码
// 选择提交功能 const { commitType } = await prompts({ type: 'select', name: 'commitType', message: '请选择提交类型', //此处做了终端复选框 让用户直接选择信息并且填写 choices: [ { value: 'feat', title: 'feat', description: '特性(feat):新功能' }, { value: 'fix', title: 'fix', description: '修复(fix):修复' }, { value: 'refactor', title: 'refactor', description: '重构(refactor): 重构(既不是增加feature,也不是修复bug)' }, { value: 'perf', title: 'perf', description: '性能(perf):性能优化' }, { value: 'docs', title: 'docs', description: '文档(docs):文档变更' }, { value: 'style', title: 'style', description: '格式(style):代码格式(不影响代码运行的变动)' }, { value: 'chore', title: 'chore', description: '日常(chore):构建过程或辅助工具的变动' }, { value: 'revert', title: 'revert', description: '回滚(revert):代码回退' }, { value: 'test', title: 'test', description: '测试(test):增加测试' } ] }); // 用户取消选择 !commitType && process.exit(); // 输出commit信息 约束提交信息 const { commitMessage } = await prompts({ type: 'text', name: 'commitMessage', message: `请输入本次提交的信息`, validate: (v) => { const minLength = 3; if (v.length > minLength) { return true; } else { return `请输入描述文字的长度大于${minLength}`; } } });
- 直接跟 CI/CD打通:前面的代码都已经有,就是只有当通过 cli 工具进行提交并且带有特殊标识信息的提交我们才会走 CI/CD的操作,避免一些非规范性的提交
总结:
近期在一个项目里面,我觉得其实可以基于 simple-git 在 cli 代码提交的时候跟 gpt 进行结合,再合并分支的时候通过大模型进行审核和优化建议,如果再牛逼一点其实可以直接去操作代码进行修改,主要灵感来自同事聊天,去年跟同事合作的过程中发现他在用 node+ gtp 大模型对代码的提交进行代码审核
原文:https://juejin.cn/post/7486670839922819124
更多推荐



所有评论(0)