使用JavaScript控制前端项目git提交流程
使用JavaScript控制前端项目git提交流程
1. 背景故事
作为一名前端开发工程师,在日常工作中,有大量的bug要改,有许多页面要做。而每一次bug的解决或者新功能的添加都需要经历拉取新代码--修改代码--提交代码
的无趣过程。然而,这还不是重点,在此过程中,不论是因为电脑卡了,还是自己不小心,都会造成代码提交的失败,很多时候git会给开发一点点震撼。这不,前两天我们组的一个女同事就因为不当的操作导致写了一个月的代码全部丢失,坐在那里哭了一个下午。当然,这里面有她自己不良习惯的原因,但是作为程序员,我觉得这个过程完全是可以更加自动化和智能的。
2. git指令
现在假设开发的主分支为REMOTE_MAIN
,那么现在如果我要修改一个名为pop_error的bug需要执行多少git指令呢?见下面:
git add .
git stash
git checkout REMOTE_MAIN
git pull origin REMOTE_MAIN
git checkout -b fix/bug-pop_error
git stash pop
git add .
git commit -m "fix: 修改了pop_error问题"
git push origin HEAD
如果一天改5个bug,那么就需要45条上述的命令,如果此bug在3个分支上都有,那么就需要输入135条;这还是在假设一次性就能成功的前提下。总之,这种方式我不喜欢,非常low.
3. 改造思路
- 如果我能够在js文件中执行git命令就可以了,虽然shell不熟,但是我熟悉js呀
- 执行完git命令之后要能够返回执行是否成功,以便判断下一步要不要继续
- 此js文件在执行之前可以将git提交的相关信息以命令参数的方式注入,这样更加灵活
- 如果执行失败了,我希望回到我执行此文件之前的状态
- 我希望在控制台输出提交过程的各个状态
4. 逐项突破
- npm上有一个名为
simple-git
的库,这个库可以让以js的方式执行git命令; simple-git
库中的方法执行返回的是Promise对象,从此对象上可以获取到git命令执行完毕的结果(是否成功或者失败);- npm上有一个名为
yargs
的库,这个库可以将命令参数转成js中的对象; - 对于每一个git命令,几乎总是能够找到其反命令,如果执行某一条命令失败,则只需要倒序执行此命令之前成功执行命令的反命令,就可以回到初始状态;
- 使用
chalk
第三方库美化控制台的输出。
5. simple-git库
这个库的使用非常简单,可以看成是三步走:引入-配置-使用
- 引入
我是用的版本是
3.19.1
的,引入方法为:const simpleGit = require('simple-git');
- 配置 使用一个对象作为配置对象对其进行初始化即可
const options = {
baseDir: process.cwd(),
binary: 'git',
maxConcurrentProcesses: 6,
};
const git = simpleGit(options);
- 使用 使用配置生成的git对象上的方法即可完成git命令的执行:
try {
await git.add('.');
} catch (e) {
throw new Error('add fails');
}
6. yargs库
使用yargs库,可以将执行js文件的时候传入的命令参数转化成js中的一个对象,例如:
node cmt.js -t REMOTE_MAIN -k fix -n pop_error -m 修改pop问题
这条shell命令中,传入了4个命令参数,分别是:
- t: target, 表示要将代码合并到的主分支的名称
- k: kind, 表示提交的类型,如fix, ci, chore, feat等
- n: name, 表示被合并的分支的名称
- m: message, 表示commit的时候写的注释
如果直接使用process.argv.slice(2)
处理命令参数,则得到一个数组,使用起来非常的不方便,这个时候,只需要使用yargs这个库,就可以方便的获取命令参数的值了。
yargs库的使用也可以看成是三步走:引入-配置-获取信息
- 引入:
使用的版本号为
17.7.2
,引入的方式为:const yargs = require('yargs');
- 配置
使用option方法定义对命令参数的解析模式,这里可以看成使用了
解释器设计模式
const argv = yargs
.option('target', {
alias: 't',
description: 'target branch name',
type: 'string',
// demandOption: true,
})
.option('kind', {
alias: 'k',
description: 'commit type',
type: 'string',
// demandOption: true,
})
.option('name', {
alias: 'n',
description: 'your branch name',
type: 'string',
// demandOption: true,
})
.option('message', {
alias: 'm',
description: 'your commit message',
type: 'string',
// demandOption: true,
})
.argv;
- 使用 yargs按照预先指定的方式解析命令行参数。
const defaultTarget = `REMOTE_MAIN`;
const defaultName = 'rand-'.concat(Math.random().toString(16).slice(-4));
const defaultKind = `fix`;
const defaultMessage = `新的修改`;
const target = argv.target || defaultTarget;
const kind = argv.kind || defaultKind;
const name = argv.name || defaultName;
const message = argv.message || defaultMessage;
const source = `${kind}/${name}`;
console.log(`${chalk.grey(`\n将创建${chalk.blue(source)}分支,并提交到目标分支${chalk.blue(target)}上,描述为:\n\t${chalk.blue(message)}\n输入${chalk.red('y')}确认,其他键取消提交...\n`)}`);
7. git命令翻译
获取命令行参数之后,就可以使用simple-git将之前手写的git命令翻译成js函数了,这个过程中重点在于错误的捕获和处理,这个是定制化的需求,各位可以按照自己的喜好自行设置。
async function gitOperation() {
try {
console.log(chalk.grey(`进入git提交流程...`));
console.log(chalk.blue(`\t1. 将工作区修改内容加入暂存区`));
try {
await git.add('.');
} catch (e) {
throw new Error('step1');
}
console.log(chalk.blue(`\t2. stash暂存区中的内容`));
try {
await git.stash();
} catch (e) {
throw new Error('step2');
}
console.log(chalk.blue(`\t3. 切换到主分支${chalk.blue(target)}`));
try {
await git.checkout(target);
} catch (e) {
throw new Error('step3');
}
console.log(chalk.blue(`\t4. 同步主分支代码`));
try {
await git.pull('origin', target);
} catch (e) {
console.log(chalk.red(e));
throw new Error('step4');
}
console.log(chalk.blue(`\t5. 创建分支${source}`));
try {
await git.checkoutLocalBranch(source);
} catch (e) {
throw new Error('step5');
}
console.log(chalk.blue(`\t6. 将stash的内容释放至${source}分支`));
try {
await git.stash(['pop']);
} catch (e) {
throw new Error('step6');
}
console.log(chalk.blue(`\t7. 将工作区修改内容加入暂存区`));
try {
await git.add('.');
} catch (e) {
throw new Error('step7');
}
console.log(chalk.blue(`\t8. 将暂存区修改内容提交至本地仓库`));
try {
await git.commit(message);
} catch (e) {
throw new Error('step8');
}
console.log(chalk.blue(`\t9. 将本地仓库修改内容提交至远程仓库`));
try {
await git.push(['origin', 'HEAD']);
} catch (e) {
throw new Error('step9');
}
} catch (err) {
console.log(chalk.red(err));
try {
await git.stash(['pop']);
} catch (e) {
console.log(chalk.red(e));
}
}
}
gitOperation();
8. 工程化
下面是将上述内容添加到前端工程项目中的过程:
- 安装依赖
- 在根目录下面创建mst.js文件
- 在package.json中增加执行命令
- 将修改提交到仓库
# 安装依赖
yarn add <chalk@2.3.1> <simple-git@3.19.1> <yargs@17.7.2>
# 在根目录下面创建mst.js文件
touch cmt.js
# 在package.json中增加执行命令
# "cmt": "node cmt.js"
# 将修改提交到仓库
git add cmt.js && git commit -m "chore: 修改git工作流" && git push origin HEAD
9. cmt.js完整内容
// yarn cmt -t REMOTE_MAIN -k fix -n pop_error -m 修改pop问题
const yargs = require('yargs');
const chalk = require('chalk');
const simpleGit = require('simple-git');
const options = {
baseDir: process.cwd(),
binary: 'git',
maxConcurrentProcesses: 6,
};
const git = simpleGit(options);
const argv = yargs
.option('target', {
alias: 't',
description: 'target branch name',
type: 'string',
// demandOption: true,
})
.option('kind', {
alias: 'k',
description: 'commit type',
type: 'string',
// demandOption: true,
})
.option('name', {
alias: 'n',
description: 'your branch name',
type: 'string',
// demandOption: true,
})
.option('message', {
alias: 'm',
description: 'your commit message',
type: 'string',
// demandOption: true,
})
.argv;
const defaultTarget = `REMOTE_MAIN`;
const defaultName = 'rand-'.concat(Math.random().toString(16).slice(-4));
const defaultKind = `fix`;
const defaultMessage = `新的修改`;
const target = argv.target || defaultTarget;
const kind = argv.kind || defaultKind;
const name = argv.name || defaultName;
const message = argv.message || defaultMessage;
const source = `${kind}/${name}`;
console.log(`${chalk.grey(`\n将创建${chalk.blue(source)}分支,并提交到目标分支${chalk.blue(target)}上,描述为:\n\t${chalk.blue(message)}\n输入${chalk.red('y')}确认,其他键取消提交...\n`)}`);
async function gitOperation() {
try {
console.log(chalk.grey(`进入git提交流程...`));
console.log(chalk.blue(`\t1. 将工作区修改内容加入暂存区`));
try {
await git.add('.');
} catch (e) {
throw new Error('step1');
}
console.log(chalk.blue(`\t2. stash暂存区中的内容`));
try {
await git.stash();
} catch (e) {
throw new Error('step2');
}
console.log(chalk.blue(`\t3. 切换到主分支${chalk.blue(target)}`));
try {
await git.checkout(target);
} catch (e) {
throw new Error('step3');
}
console.log(chalk.blue(`\t4. 同步主分支代码`));
try {
await git.pull('origin', target);
} catch (e) {
console.log(chalk.red(e));
throw new Error('step4');
}
console.log(chalk.blue(`\t5. 创建分支${source}`));
try {
await git.checkoutLocalBranch(source);
} catch (e) {
throw new Error('step5');
}
console.log(chalk.blue(`\t6. 将stash的内容释放至${source}分支`));
try {
await git.stash(['pop']);
} catch (e) {
throw new Error('step6');
}
console.log(chalk.blue(`\t7. 将工作区修改内容加入暂存区`));
try {
await git.add('.');
} catch (e) {
throw new Error('step7');
}
console.log(chalk.blue(`\t8. 将暂存区修改内容提交至本地仓库`));
try {
await git.commit(message);
} catch (e) {
throw new Error('step8');
}
console.log(chalk.blue(`\t9. 将本地仓库修改内容提交至远程仓库`));
try {
await git.push(['origin', 'HEAD']);
} catch (e) {
throw new Error('step9');
}
} catch (err) {
console.log(chalk.red(err));
try {
await git.stash(['pop']);
} catch (e) {
console.log(chalk.red(e));
}
}
}
gitOperation();
10. package.json完整内容
{
"name": "test-simple-git",
"version": "1.0.0",
"main": "index.js",
"license": "MIT",
"dependencies": {
"chalk": "2.3.1",
"shelljs": "0.8.5",
"simple-git": "3.19.1",
"yargs": "17.7.2"
},
"scripts": {
"cmt": "node cmt.js"
}
}
11. 提交后测试
执行yarn cmt
, 看看gitlab上是否有对应的分支
转载自:https://juejin.cn/post/7281266407492042806