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. 跨平臺兼容性處理;

博客寫得比較簡單,更像是工作筆記。有一段時間沒有寫博客了,因爲**陷入了一個怪圈,覺得小的東西沒必要寫,大的東西又還沒有成果不好寫。**好在同事建議說:“就當做工作筆記好了,不用太認真”。想想也是,起碼一直在記錄,也能促進自己沉澱。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章