乐趣区

关于开发工具:关于我是怎么在git提交时既偷懒又规范基于nodejs

前言

这篇文章次要是对于公司近期对于 git 提交标准进行了限度之后产出,其次要目标是为了实现本公司的提交标准的实现,以及简略不便化集体操作,本文的代码是由 git-cz 根底上进行的批改。

注:本文会写上次要代码各位能够自行下载 git-cz 源码批改后创立本人的独特的 git 提交管理工具,也会附上一些 api 接口(如果有想要我来帮忙批改的也能够私聊我哦)

实现成果

这就是进行批改后的提交流程逻辑,如果在失常对于 jira 号和提交信息对应的状况下咱们能够不必输出任何信息就能够实现提交,并且生成信息也是齐全能够依照咱们制订的标准来进行提交,所以这样做能够对于咱们的提交信息进行治理并且还能够把咱们的提交信息和 jira 号关联对应上,如下图:

这样其实就简略的实现咱们这样做的最根本需要,就是标准提交信息让咱们的 jira 和 git 提交对应起来,并且前期能够通过 git log 来生成咱们需要的日志文件。然而,我既然都写到这了怎么不再把 gitlab 上的操作再省略一些呢:

是的,咱们一次性实现了全副操作间接把 gitlab 上的 mr 申请都干掉了间接提交这样岂不美滋滋,再也不必关上 jira 网页查看 jira 号和 gitlab 发送 mr 申请了!

具体实现

其实整体下来说有两种形式能够实现这种成果:

  • 利用 puppeteer 自动化无头浏览器来实现
  • 利用 jira 和 gitlab 的接口来实现

puppeteer

什么是 puppeteer

Puppeteer 是一个 Node 库,它提供了高级 API 来通过 DevTools 协定管制 Chrome 或 Chromium。

Puppeteer 默认状况下无头运行,但能够配置为运行残缺(无头)的 Chrome 或 Chromium。

好吧其实这是官网的介绍,简略来说就是给咱们提供了一个能够操控的浏览器,而咱们的目标就是通过这个浏览器的 dom 来获取咱们的信息

var PCR = require('puppeteer');

const browser = await PCR.launch({
      // executablePath: this.pcr.executablePath,
      headless: true,
      // 设置超时工夫
      timeout: 120000,
      // 如果是拜访 https 页面 此属性会疏忽 https 谬误
      ignoreHTTPSErrors: true,
      // 关上开发者工具, 当此值为 true 时, headless 总为 false
      devtools: true,
      defaultViewport: {
        width: 1900,
        height: 900,
        hasTouch: true,
        isMobile: true,
        deviceScaleFactor: 3
      },
      // 敞开 headless 模式, 不会关上浏览器
      // headless: enableChromeDebug !== 'Y',
      args: ['--no-sandbox']
    });
    let json = {};

    try {json = fs.readFileSync(_path.default.resolve(__dirname + '/../../cz-cli-git.json'), 'utf8');
      json = JSON.parse(json);
    } catch (e) {console.log(e);
    }

    if (!json.username || !json.password) {
      const getMessage = await _inquirer.default.prompt([{
        type: 'input',
        name: 'username',
        message: '请输入您的账号'
      }, {
        type: 'input',
        name: 'password',
        message: '请输入您的明码'
      }]);
      json = {
        username: getMessage.username,
        password: getMessage.password
      };
      fs.writeFileSync(_path.default.resolve(__dirname + '/../../cz-cli-git.json'), JSON.stringify(json));
    }

    let loading = ora();
    loading.start(` 正在获取分支信息 `);
    const page = await browser.newPage();
    await page.goto('公司的 jira 地址 /users/sign_in');
    await page.waitFor(1000);
    const elUsername = await page.$('#user_login');
    const elPassword = await page.$('#user_password');
    const elSubmit = await page.$('.move-submit-down');
    await elUsername.type(json.username);
    await elPassword.type(json.password);
    await elSubmit.click();
    await page.waitFor(1000);
    await page.goto(gitUrl + '/merge_requests/new');
    await page.waitFor(1000);
    const sourceBranch = await page.$('.js-source-branch');
    const targetBranch = await page.$('.js-target-branch');
    await sourceBranch.click();
    await page.waitFor(1000);
    const AllBranch = await page.$$eval('.js-source-branch-dropdown a', el => el.map(x => {
      return {
        name: x.innerText,
        value: x.getAttribute('data-ref')
      };
    }));
    loading.succeed('分支信息获取胜利');
    console.log(AllBranch);

看着这么长一串的 await 其实就是获取 dom 节点和实现 dom 操作而已,这样咱们就能获取到 AllBranch 了(当然咱们公司的 jira 版本号是 7.3.8)

尽管这样就能够获取到咱们的 jira 信息了,然而有以下几个问题

  • 整个 npm 库较重,应为利用了无头浏览器,所以 npm 会给你下载一个 Chromium 自动化测试浏览器(大概 134MB)
  • 装置 npm 的时候很慢很慢,因为有这么大一个浏览器下载经常出现下载失败和下载很慢的问题
  • 应用的时候获取效率较慢,因为是采纳模仿浏览器操作所以有页面加载工夫须要进行期待

这个计划代码写起来简略轻松然而问题还是挺多的,然而如果需要不高这样操作也不失是一个方法,因为逻辑上来说这样能够代替你的所有操作,并且不必放心接口权限等问题

接口申请

这个字眼看着就是简略粗犷的做法了,看起来很简略其实并没有,最要害的问题在于:找接口!

是的,对于一个不相熟 jira 和 gitlab 接口的人来说(本菜鸡),官网提供的一大片接口几乎辣眼睛,jira 上还没有官网提供的都是去页面上本人试出来的。那么这样的作法的长处当然也很显著了:

  • npm 包较轻,无浏览器装置
  • 相应速度块,能够疾速获取接口信息展现
  • 异样捕捉容易,能够较为轻松的实现异常情况的解决

如果可能应用接口申请的状况下我感觉还是尽量应用接口来进步应用品质和异样解决,当然如果有接口无奈实现的工作当我没说。

要害说一个对于 gitlab 的拜访令牌的阐明的,因为 gitlab 的平安验证比较复杂所以应用了简略的拜访令牌来实现,具体如果生成令牌能够点击这个生成令牌查看,并且所有接口都须要在头部增加

jira 就不展现代码了毕竟只有一个接口,这里就把 gitlab 代码展现进去吧(jira 版本:7.3.8,gitlab 如同没太大版本之分)

获取未实现 jira 列表:url: /rest/issueNav/1/issueTable 
       type: post
    params: {
        layoutKey: 'split-view', // 固定填写
          jql: 'assignee = currentUser() AND resolution = Unresolved order by updated DESC', // 固定填写
          os_username: json.username, // 用户账号
          os_password: json.password // 用户明码
     }
const reg = /http(.*?)\s/;
  const git = await execSync('git remote -v').toString().trim(); // 姓名
  const gitUrl = git.match(reg)[0].replace(/\s/g , '').replace('https:// 域名地址 ','http://ip 地址 ').replace('.git','');

  let response
  try {
    response = await axios.get( 'https:// 域名地址 /api/v4/projects' , {
      params : {search : gitUrl.split('/')[gitUrl.split('/').length - 1]
      } ,
      headers : {'Authorization' : `Bearer ${json.Authorization}` ,
        'Content-Type' : 'application/x-www-form-urlencoded'
      }
    } );
  } catch (e) {throw Error('获取我的项目信息失败')
  }

  response = response.data.filter(( item) => item.web_url === gitUrl );

  if (response.length > 1) {throw Error('项目管理有问题,请分割 jira 我的项目管理员');
  }

  const projectId = response[0].id;

  let forksRes;

  try {forksRes = await axios.get( `https:// 域名地址 /api/v4/projects/${projectId}/repository/branches` , {
      headers : {'Authorization' : `Bearer ${json.Authorization}` ,
        'Content-Type' : 'application/x-www-form-urlencoded'
      }
    } );
  } catch (e) {throw Error('申请分支失败请重试')
  }

  const branchList = forksRes.data.map(item => {
    return {
      name: item.name,
      value: item.name
    }
  })

  let menberRes;

  try {menberRes = await axios.get( `https:// 域名地址 /api/v4/projects/${projectId}/members/all` , {
      headers : {'Authorization' : `Bearer ${json.Authorization}` ,
        'Content-Type' : 'application/x-www-form-urlencoded'
      }
    } );
  } catch (e) {throw Error('获取检查用户失败请重试')
  }

  const menberList = menberRes.data.map(item => {
    return {
      name: item.name,
      value: item.id
    }
  })

  loading.succeed(` 填写信息获取胜利 `);
  console.warn('以后分支为:' + await execSync( 'git name-rev --name-only HEAD').toString().trim() + '(为确保操作准确性,只容许在以后分支发动 mr 申请)');

  const getInputB = await inquirer
    .prompt([
      {
        type: 'input',
        name: 'branchIn',
        message: '输出合并入的主分支如果想手动抉择能够间接回车进入下一步',
      }
    ])

  const proptList = [{
    type: 'list',
    name: 'branch',
    message: '请抉择合入的主分支',
    choices: branchList
  },
    {
      type: 'input',
      name: 'title',
      message: '请输出合并题目',
      default: '默认合并申请信息'
    },
    {
      type: 'input',
      name: 'desc',
      message: '请输出合并形容'
    },
    {
      type: 'list',
      name: 'users',
      message: '抉择合并用户',
      choices: menberList
    }]

  getInputB.branchIn && proptList.splice(0, 1)

  const getBranch = await inquirer
    .prompt(proptList)

  try {let mergeRes = await axios.post( `https:// 域名地址 /api/v4/projects/${projectId}/merge_requests` , qs.stringify({source_branch: await execSync( 'git name-rev --name-only HEAD').toString().trim(),
      target_branch: getInputB.branchIn ? getInputB.branchIn : getBranch.branch,
      title: getBranch.title,
      assignee_id: getBranch.users,
      description: getBranch.desc,
      remove_source_branch: true
    }), {
      headers : {'Authorization' : `Bearer ${json.Authorization}` ,
        'Content-Type' : 'application/x-www-form-urlencoded'
      }
    } );

    if (mergeRes.status === 201) {console.log('创立胜利');
      console.log('拜访地址:' + mergeRes.data.web_url);
    } else {console.log('请查看分支抉择和题目不能为空');
    }
  } catch (e) {throw Error('提交失败, 请查看分支抉择和题目不能为空, 或者线上曾经存在一样合并申请了无需再提交');
  }

其余问题

账号治理

如果看了下面的代码其实有个货色就是账号治理没有看到,这个中央我应用的是第一次填写后将账号密码保留至文件种,前面再拜访就通过文件获取

let json = {};
  try {json = fs.readFileSync(path.resolve(__dirname + '/cz-cli.json'), 'utf8')
    json = JSON.parse(json);
  } catch (e) {// console.log(e);
  }
  if (!json.username || !json.password) {
    const getMessage = await inquirer
        .prompt([
          {
            type: 'input',
            name: 'username',
            message: '请输入您的账号'
          },
          {
            type: 'input',
            name: 'password',
            message: '请输入您的明码'
          }
        ])
    json = {
      username: getMessage.username,
      password: getMessage.password
    }
    fs.writeFileSync(path.resolve(__dirname + '/cz-cli.json'), JSON.stringify(json))
  }

这个代码其实很简略就不再做形容了。

git cz

其实说实话这个才是最要害的货色,然而我感觉既然都看到这了大佬可能都不缺这点实力来读读 git-cz 的源码了,我就简略说一下咱们应该怎么批改让咱们的性能增加上

首先这个基本上就是 git cz 执行的根底逻辑了,真正在代码中的重复办法调用,办法传递较深所以理论看起来代码没有那么简单明了,然而也能够说的是在代码中的获取发问信息是通过 require 第三方包而后加载实现的(同步获取!),是的他是属于同步获取,所以咱们的接口申请无奈期待,那么就要咱们进行一点点的魔改了
第一处

#!src/commitizen/adapter.js
// 142 行本来代码
function getPrompter (adapterPath) {
  // Resolve the adapter path
  let resolvedAdapterPath = resolveAdapterPath(adapterPath);

  // Load the adapter
  let adapter = require(resolvedAdapterPath);

  /* istanbul ignore next */
  if (adapter && adapter.prompter && isFunction(adapter.prompter)) {return adapter.prompter;} else if (adapter && adapter.default && adapter.default.prompter && isFunction(adapter.default.prompter)) {return adapter.default.prompter;} else {throw new Error(`Could not find prompter method in the provided adapter module: ${adapterPath}`);
  }
}

// 批改为
function getPrompter (adapterPath) {
  // Resolve the adapter path
  return new Promise(async (resolve) => {let resolvedAdapterPath = resolveAdapterPath(adapterPath);

    // Load the adapter
    let adapter = await require(resolvedAdapterPath);

    /* istanbul ignore next */
    if (adapter && adapter.prompter && isFunction(adapter.prompter)) {resolve(adapter.prompter);
    } else if (adapter && adapter.default && adapter.default.prompter && isFunction(adapter.default.prompter)) {resolve(adapter.default.prompter);
    } else {throw new Error(`Could not find prompter method in the provided adapter module: ${adapterPath}`);
    }
  })
}

咱们将本来的间接执行代码给改成了异步执行返回 Primose 回调了,而后只须要进行上面一步就能够把获取改成异步了

#!src/commitizen/adapter.js
// 将第 43 行的代码
let prompter = getPrompter(adapterConfig.path);
// 批改为(并且将办法改为 async 就能够了)let prompter = await getPrompter(adapterConfig.path);

这样咱们就能够进行 jira 的异步获取了,那么对于解决实现后咱们的操作呢?

在 /src/strategies/git-cz.js 文件中的第 57 行的 commit 办法执行中有一个 done 的回调办法,咱们能够把后续操作增加到这个办法内执行就能够了

commit(inquirer, process.cwd(), prompter, {
      args: parsedGitCzArgs,
      disableAppendPaths: true,
      emitData: true,
      quiet: false,
      retryLastCommit,
      hookMode
    }, function (error) { // 就是这个办法
      if (error) {throw error;}
    });

最初一步

其实到这了基本上看了整篇文章就能够实现你想自主批改的 git cz 了,而后咱们增加一个 git mr 指令来让咱们在没有执行提交的时候也能够进行 gitlab 的 merge request 申请,在更目录下的 bin 文件夹中增加文件而后再 package.jso 中配置以下即可(其实这就是减少指令就是创立脚手架的,如果不相熟脚手架能够查看我的历史文章中有一篇对于脚手架的实战)

// package.json
"bin": {
    "git-cz": "./bin/git-cz",
    "git-mr": "./bin/git-mr",
    "commitizen": "./bin/commitizen"
  },
//git-mr
#!/usr/bin/env node
require('./git-mr.js');
//git-mr.js
process.on('uncaughtException', function (err) {console.error(err.message || err);
  process.exit(1);
})
require('../dist/cli/git-cz.js').MrApi();
//git-mr.cmd
@node "%~dpn0" %*

结尾

这就是本文全部内容了,如果有任何问题或者想让我帮忙进行开发欢送进行评论的私聊我,上面贴上自己微信二维码。

退出移动版