关于我是怎么在git提交时既偷懒又规范(基于nodejs)

前言

这篇文章主要是对于公司近期对于git提交规范进行了限制之后产出,其主要目的是为了实现本公司的提交规范的实现,以及简单方便化个人操作,本文的代码是由git-cz基础上进行的修改。

注:本文会写上主要代码各位可以自行下载git-cz源码修改后创建自己的独特的git提交管理工具,也会附上一些api接口<b>(如果有想要我来帮忙修改的也可以私聊我哦)</b>

实现效果

image

这就是进行修改后的提交流程逻辑,如果在正常对于jira号和提交信息对应的情况下我们可以不用输入任何信息就可以完成提交,并且生成信息也是完全可以按照我们制定的规范来进行提交,所以这样做可以对于我们的提交信息进行管理并且还可以把我们的提交信息和jira号关联对应上,如下图:


image

这样其实就简单的实现我们这样做的最基本需求,就是规范提交信息让我们的jira和git提交对应起来,并且后期可以通过git log来生成我们需求的日志文件。但是,我既然都写到这了怎么不再把gitlab上的操作再省略一些呢:

image

是的,我们一次性完成了全部操作直接把gitlab上的mr请求都干掉了直接提交这样岂不美滋滋,<b>再也不用打开jira网页查看jira号和gitlab发送mr请求了!</b>

具体实现

其实整体下来说有两种方式可以实现这种效果:

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

puppeteer

什么是puppeteer

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

Puppeteer 默认情况下无头运行,但可以配置为运行完整(无头)的Chrome或Chromium。

image

好吧其实这是官方的介绍,简单来说就是给我们提供了一个可以操控的浏览器,而我们的目的就是通过这个浏览器的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的时候很慢很慢,由于有这么大一个浏览器下载经常出现下载失败和下载很慢的问题
  • 使用的时候获取效率较慢,由于是采用模拟浏览器操作所以有页面加载时间需要进行等待

这个方案代码写起来简单轻松但是问题还是挺多的,但是如果需求不高这样操作也不失是一个办法,因为逻辑上来说这样可以代替你的所有操作,并且不用担心接口权限等问题

接口请求

这个字眼看着就是简单粗暴的做法了,看起来很简单其实并没有,最关键的问题在于:<b>找接口!</b>

是的,对于一个不熟悉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的源码了,我就简单说一下我们应该怎么修改让我们的功能添加上

image

首先这个基本上就是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" %*
image

结尾

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


image
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,179评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,229评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,032评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,533评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,531评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,539评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,916评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,574评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,813评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,568评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,654评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,354评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,937评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,918评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,152评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,852评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,378评论 2 342