最近在開發一個構建工具,需要操作版本管理系統,實現代碼提交(如:svn add, svn commit…),GitHub上有類似的庫,但存在兩個問題:
- 長久沒人維護,使用人數較少;
- 只支持git或svn;
基於我需要兼容git和svn,於是自己簡單實現一個版本管理模塊:version-control,下面將簡單記錄一下設計中的一些考慮和遇到的一些問題。
由於開發時間有限和本人使用場景較爲簡單,若你想使用version-control,請先自行評估是否能滿足你的需求,當然作爲開源項目,歡迎提issue,發PR。
目錄
- 【思路】node下的版本管理模塊實現;
- 【思路】 如何優雅兼容svn和git操作;
- 【問題】中文顯示的問題;
- 【問題】svn本地版本和線上服務器版本不一致問題;
- 【問題】svn 如何add所有文件;
- 【問題】svn log沒有顯示剛剛commit的信息;
- 【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操作中跳脫出來,只關心用戶需要,針對用戶需要,梳理出以下幾個接口:
- 查看當前文件修改狀態:VersionControl.status
- 更新代碼:VersionControl.update
- 提交代碼:VersionControl.commit
- 版本回退: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的時候耗時很長(一分鐘),於是我開始了問題定位之旅:
- 理論分析svn commit慢的因素(①傳輸內容多 ②svn倉庫大 ③svn add all耗時長);
- 打點分析;
- 得出結論,svn add all耗時長,佔總commit耗時的90%以上;
從上面命令的參數可以看到,執行這個命令會去深度遍歷指定路徑下的所有目錄,導致耗時過長。
這其實是一個全量操作和增量操作的問題,全量操作方便,但耗費性能,增量操作繁瑣,但性能優越。於是在全量操作遇到問題後,我只好自己手動實現svn add all:
- svn status:分析當前的文件修改;
- 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走起。下面是一些要做的事情:
- 支持git;
- 發佈npm包;
- 跨平臺兼容性處理;
博客寫得比較簡單,更像是工作筆記。有一段時間沒有寫博客了,因爲**陷入了一個怪圈,覺得小的東西沒必要寫,大的東西又還沒有成果不好寫。**好在同事建議說:“就當做工作筆記好了,不用太認真”。想想也是,起碼一直在記錄,也能促進自己沉澱。