使用 simple-git 在 Node.js 中高效调用 Git 命令

我因为在写 cli 工具的时候,大量的使用各种 npm 包,近期把我认为好玩的一些包分享出来。纯属自己的一些见解,有什么建议可以私底下沟通。

今天要我想重点介绍的就是simple-gitsimple-git是一个用于在 Node.js 中简化 Git 命令操作的轻量级库。它提供了一种简单的接口来执行常见的 Git 操作,而无需直接使用命令行。

为什么要用他?

  1. 优化提交效率:因为我在写PLUS团队CLI工具的时候有一个场景,就是代码的提交发布,这个动作在开发中非常频繁,正常的提交我们都需要有很多步骤如 git add, git commit,git push,其实这些步骤是可以简化和可视化的,直接一个xxx push 就能拿完成上面三个动作
  2. 标准化commit信息:通过对simple-git的方法的二次封装,可以轻松实现提交信息的格式化,确保每次提交都能提供清晰、有条理的信息,这对于维护代码库的整洁性和可读性大有裨益
  3. 直接跟 CI/CD打通:很多团队在触发公司流水线的时候还在通过手动或者提交代码,但是这两个动作都有一些问题,手动提交效率低,直接提交代码就是太随意不是所有提交都需要去触发流水线,可以通过对 git 一些动作去选择性触发流水线

综上所述:simple-git 不仅通过其简洁的接口降低了使用 Git 的复杂性,还通过优化提交低了使用 Git 的复杂性,还通过优化提交流程和促进提交信息标准化。

安装方式

我比较喜欢pnpm相对于npm yarn 更加便捷和快速

shell 复制代码
pnpm install simple-git
npm install simple-git

基础的方法封装

  • 获取分支:获取当前分支gitPath是当前用户的操作目录
js 复制代码
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 复制代码
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 复制代码
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 复制代码
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 复制代码
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 复制代码
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 复制代码
// 删除远程分支用
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 复制代码
//这是注册的命令 
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 复制代码
// 选择提交功能
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 大模型对代码的提交进行代码审核

相关推荐
日记成书1 分钟前
【HTML 基础教程】HTML 表格
前端·html
木木黄木木5 分钟前
HTML5贪吃蛇游戏开发经验分享
前端·html·html5
无名之逆11 分钟前
hyperlane:Rust HTTP 服务器开发的不二之选
服务器·开发语言·前端·后端·安全·http·rust
李鸿耀15 分钟前
前端包管理工具演进史:从 npm 到 pnpm 的技术革新
前端·面试
麓殇⊙17 分钟前
前端基础知识汇总
前端
MariaH20 分钟前
邂逅jQuery库
前端
Jenlybein25 分钟前
学完 Vue3 记不牢?快来看这篇精炼Vue3笔记复习一下 [ Route 篇 ]
前端·vue.js
页面魔术29 分钟前
[译]专访尤雨溪: 2025年有什么计划?
前端·vue.js·vite
zhangxiao30 分钟前
Vite项目打包生成dist.zip方法
前端
Enti7c31 分钟前
定时器的定义
javascript