version-control: node下的版本管理模块(svn & git)

最近在开发一个构建工具,需要操作版本管理系统,实现代码提交(如:svn add, svn commit…),GitHub上有类似的库,但存在两个问题:

  1. 长久没人维护,使用人数较少;
  2. 只支持git或svn;

基于我需要兼容git和svn,于是自己简单实现一个版本管理模块:version-control,下面将简单记录一下设计中的一些考虑和遇到的一些问题。

由于开发时间有限和本人使用场景较为简单,若你想使用version-control,请先自行评估是否能满足你的需求,当然作为开源项目,欢迎提issue,发PR。

目录

  1. 【思路】node下的版本管理模块实现;
  2. 【思路】 如何优雅兼容svn和git操作;
  3. 【问题】中文显示的问题;
  4. 【问题】svn本地版本和线上服务器版本不一致问题;
  5. 【问题】svn 如何add所有文件;
  6. 【问题】svn log没有显示刚刚commit的信息;
  7. 【TODO】

一、node下的版本管理模块实现思路

回想我们在没有GUI版本管理工具的时候是怎么进行版本操作的:敲命令行,于是自然想到实现思路:基于node执行指定命令行来实现版本操作

这里要注意的是,Windows下在命令行中不一定有git或者svn的执行环境,需要安装额外的安装包,由于Mac OS暂时没有这个问题,所以我还没有做处理。

// node执行命令
const child_process = require('child_process');

module.exports = (command, cwd) => new Promise((resolve, reject) => {
  child_process.exec(command, { cwd }, (err, stdout) => {
    if (err) reject(err);
    else resolve(stdout);
  });
});

二、如何优雅兼容svn和git操作

凡是说到兼容,我就会想到抽象,站在用户的角度,要完成的任务是版本管理,并不太在意具体用哪些命令。于是我从svn和git操作中跳脱出来,只关心用户需要,针对用户需要,梳理出以下几个接口:

  1. 查看当前文件修改状态:VersionControl.status
  2. 更新代码:VersionControl.update
  3. 提交代码:VersionControl.commit
  4. 版本回退:VersionControl.revert

确定了抽象接口之后,我采用动态引入的方式兼容svn和git,并在架构上进行作用域隔离,单独维护svn操作逻辑和git操作逻辑:

VersionControl在实例化的时候判断用户需要svn还是git,根据用户需要引入底层实现,最后在对外接口中调用具体的底层实现来实现svn和git兼容。

/**
 * VersionControl
 * @TODO 实现Git状态管理
 */
class VersionControl {
  constructor(versionControlSystemType) {
    if (versionControlSystemType && !['svn', 'git'].contains(versionControlSystemType)) throw new Error('Only git or svn can be pass in.');
    this.type = versionControlSystemType || 'svn';
    // vsc = versionControlSystem
    /* eslint global-require: "off" */
    this.vcs = this.type === 'svn' ? require('./svn') : require('./git');
  }

  status(codePath) {
    return this.vcs.status(codePath);
  }

  update(codePath) {
    return this.vcs.update(codePath);
  }

  commit(codePath, commitMsg) {
    return this.vcs.commit(codePath, commitMsg);
  }

  log(codePath) {
    return this.vcs.log(codePath);
  }

  revert(codePath, commitMsg, version) {
    return this.vcs.revert(codePath, commitMsg, version);
  }
}

module.exports = VersionControl;

三、中文显示的问题

在执行svn commit -m '中文'时,在某些node环境下(例如electron编译后的安装包中)会报错:

svn: Can't convert string from 'UTF-8' to native encoding

经过构建前后差异分析定位,我发现,electron构建前后执行时,node的环境不一样(具体环境参数可以通过process.env获取,更多详情请参考:node.js文档 - process.env)。具体原因在于:编码格式不一样

解决方案:将node执行环境中的语言改成UTF8:process.env.LANG = 'zh_CN.UTF-8';

四、svn本地版本和线上服务器版本不一致问题

在执行svn操作是,报错:svn: E155036: Please see the 'svn upgrade' command; svn: E155036: The working copy at 'xxx',意思是本地svn和服务器svn版本不一致,需要更新,此时执行svn upgrade即可,但需要注意的是,svn upgrade只能在svn根目录中执行,不然会报另外一个错误,于是我们需要在上面的报错信息中解析出svn root,也就是上面说的working copy。

部分代码如下,有很大的优化空间,例如这个错误处理逻辑,应该抽离出来当成通用的错误处理逻辑(因为所有的svn操作都有可能会报这个错误)。

static async status(codePath) {
  const command = 'svn status';
  let statusData = [];
  await exec(command, codePath)
    .then((data) => {
      statusData = parseStatus(data);
    })
    .catch(async (error) => {
      const { message } = error;
      // 处理svn upgrade提示:在svn根目录(Working Copy Root Path)执行svn upgrade
      // @NOTE 错误提示
      // svn: E155036: Please see the 'svn upgrade' command
      // svn: E155036: The working copy at 'xxx'
      if (message.indexOf('svn upgrade') !== -1) {
        const SVN_ROOT_REG = /The working copy at '(.*)'/;
        const svnRoot = SVN_ROOT_REG.exec(message)['1'];
        await this.upgrade(svnRoot);
        return this.status(codePath);
      }
      return statusData;
    });
  return statusData;
}

五、svn 如何add所有文件

我在svn文档中没有找到类似git add --all的命令,于是在stackoverflow中找到了解决方法:

svn add --force * --auto-props --parents --depth infinity -q

刚开始在本地测试的时候测试通过,后台构建工具发布后,同事反馈commit的时候耗时很长(一分钟),于是我开始了问题定位之旅:

  1. 理论分析svn commit慢的因素(①传输内容多 ②svn仓库大 ③svn add all耗时长);
  2. 打点分析;
  3. 得出结论,svn add all耗时长,占总commit耗时的90%以上;

从上面命令的参数可以看到,执行这个命令会去深度遍历指定路径下的所有目录,导致耗时过长

这其实是一个全量操作和增量操作的问题,全量操作方便,但耗费性能,增量操作繁琐,但性能优越。于是在全量操作遇到问题后,我只好自己手动实现svn add all:

  1. svn status:分析当前的文件修改;
  2. svn add xxx:对每一个修改进行svn add;(如果是资源删除,则是svn delete);
static async addAll(codePath) {
  const statusData = await this.status(codePath);
  for (let i = 0; i < statusData.length; i += 1) {
    const { rawType, path } = statusData[i];
    /* eslint default-case: "off" */
    /* eslint no-await-in-loop: "off" */
    switch (rawType) {
      case '?':
        // svn add xxx
        await this.add(codePath, path);
        break;
      case '!':
        // svn delete xxx
        await this.delete(codePath, path);
        break;
    }
  }

六、svn log没有显示刚刚commit的信息;

在使用svn commit之后,执行svn log并没有看到刚刚提交的记录,查阅资料发现,需要在commit后执行svn update才会看到记录,这属于svn的特性吧,简单记录一下。

TODO

上面只是提到一些实现思路和解决的一些问题,细节欢迎大家阅读version-control源码。此外大家会发现里面有许多不完善的地方,但目前已经能满足我的需求,后续随着需求的变更,我也继续更新,也欢迎大家issue,pr走起。下面是一些要做的事情:

  1. 支持git;
  2. 发布npm包;
  3. 跨平台兼容性处理;

博客写得比较简单,更像是工作笔记。有一段时间没有写博客了,因为**陷入了一个怪圈,觉得小的东西没必要写,大的东西又还没有成果不好写。**好在同事建议说:“就当做工作笔记好了,不用太认真”。想想也是,起码一直在记录,也能促进自己沉淀。

你可能感兴趣的:(node)