preface

This article is mainly for the company’s recent restrictions on the Git submission specification output, its main purpose is to achieve the implementation of the company’s submission specification, as well as simple and convenient personal operation, the code in this paper is modified on the basis of Git-Cz.

You can download the git-cz source code to create your own unique git submission management tool, and also attach some API interface (if you want me to help modify it, you can also talk to me).

Implementation effect

This is the logic of the submission process after modification. If the JIRA number and submission information are normally corresponding, we can complete the submission without input any information, and the generated information can be submitted in accordance with our specifications. Therefore, we can manage our submission information and associate our submission information with the JIRA number, as shown below:In fact, this is a simple implementation of the most basic requirements for us to do so, is to standardize the commit information to make our JIRA and Git commit corresponding, and later through the Git log to generate our required log files. However, since I have written here, why not omit some of the operations on GitLab:

Yes, we did it all in one go and killed all the Mr Requests on GitLab and submitted them directly, so we don’t have to open the JIRA page to check the JIRA number and send Mr Requests to GitLab!

The specific implementation

In general, there are two ways to achieve this effect:

  • This is done using the Puppeteer automated headless browser
  • This is done using jIRA and GitLab interfaces

puppeteer

What is a puppeteer

Puppeteer is a Node library that provides a high-level API for controlling Chrome or Chromium via the DevTools protocol.

Puppeteer runs headless by default, but can be configured to run full (headless) Chrome or Chromium.

Well, this is the official introduction, but basically it gives us a browser that we can manipulate, and our goal is to access our information through the DOM of this browser

var PCR = require('puppeteer');

const browser = await PCR.launch({
      // executablePath: this.pcr.executablePath,
      headless: true.// Set the timeout period
      timeout: 120000.// This property ignores HTTPS errors if you are accessing HTTPS pages
      ignoreHTTPSErrors: true.// Open developer tools. When this value is true, headless is always false
      devtools: true.defaultViewport: {
        width: 1900.height: 900.hasTouch: true.isMobile: true.deviceScaleFactor: 3
      },
      // Turn off the headless mode, the browser will not open
      // 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: 'Please enter your account number'
      }, {
        type: 'input'.name: 'password'.message: 'Please enter your password'
      }]);
      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('Getting branch information');
    const page = await browser.newPage();
    await page.goto('Company jIRA address /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('Branch information obtained successfully');
    console.log(AllBranch);
Copy the code

To look at such a long list of await is to get dom nodes and implement DOM operations so that we can get AllBranch (of course our jIRA version is 7.3.8).

Although we can get our JIRA information in this way, there are the following problems

  • The entire NPM library is heavy because it uses a headless browser, so NPM will download a Chromium automated test browser for you (about 134MB).
  • NPM is very slow to install, as there are often download failures and slow downloads due to such a large browser
  • When using, the efficiency of obtaining is slow, because it is used to simulate the browser operation, so the page load time needs to wait

This solution code is easy to write but there are still a lot of problems, but if the demand is not high it is also a solution, because logically speaking it can replace all your operations, and do not worry about interface permissions and other issues

Interface request

This word looks like a simple and crude approach, it looks very simple in fact there is no, the most critical problem lies in: find interface!

Yes, for a person who is not familiar with JIRA and GitLab interface (this dish chicken), a large number of interfaces provided by the official is simply eye-watering, jIRA has not provided the official are to go to the page to try out. The advantages of this approach are obvious:

  • The NPM package is light and does not have a browser to install
  • Corresponding speed block, you can quickly obtain the interface information display
  • Exception capture is easy, can be relatively easy to achieve the processing of exceptions

If interface requests can be used, I think it is better to use the interface to improve the quality of use and exception handling. Of course, if there are tasks that the interface cannot complete, forget me.

The key is to explain the access token of GitLab. Since the security verification of GitLab is complicated, simple access token is used to realize it. Specifically, if the token is generated, you can click the generated token to view it, and all interfaces need to be added in the header

Jira will not show the code because there is only one interface, so I will show the GitLab code here (JIRA version: 7.3.8, gitLab seems not too big)

Get the list of unfinished JIRas:url: /rest/issueNav/1/issueTable 
   	type: post
    params: {
    	layoutKey: 'split-view'.// Always fill in
      	jql: 'assignee = currentUser() AND resolution = Unresolved order by updated DESC'.// Always fill in
      	os_username: json.username, // User account
      	os_password: json.password // User password
     }
Copy the code
const reg = /http(.*?) \s/;
  const git = await execSync( 'git remote -v' ).toString().trim(); / / name
  const gitUrl = git.match( reg )[ 0 ].replace( /\s/g , ' ' ).replace('https:// domain name address '.'http://ip address').replace('.git'.' ');

  let response
  try {
    response = await axios.get( 'https:// domain name address/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('Failed to get project information')
  }

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

  if ( response.length > 1 ) {
    throw Error('Project management problem, please contact JIRA Project Administrator');
  }

  const projectId = response[0].id;

  let forksRes;

  try {
    forksRes = await axios.get( 'https:// domain address/API /v4/projects/${projectId}/repository/branches` , {
      headers : {
        'Authorization' : `Bearer ${json.Authorization}` ,
        'Content-Type' : 'application/x-www-form-urlencoded'}}); }catch ( e ) {
    throw Error('Branch request failed please try again')}const branchList = forksRes.data.map(item= > {
    return {
      name: item.name,
      value: item.name
    }
  })

  let menberRes;

  try {
    menberRes = await axios.get( 'https:// domain address/API /v4/projects/${projectId}/members/all` , {
      headers : {
        'Authorization' : `Bearer ${json.Authorization}` ,
        'Content-Type' : 'application/x-www-form-urlencoded'}}); }catch ( e ) {
    throw Error('Failed to obtain check user please try again')}const menberList = menberRes.data.map(item= > {
    return {
      name: item.name,
      value: item.id
    }
  })

  loading.succeed('Fill in the information obtained successfully');
  console.warn('Current branch is:' + await execSync( 'git name-rev --name-only HEAD' ).toString().trim() + '(To ensure accuracy, only Mr Requests are allowed on the current branch)');

  const getInputB = await inquirer
    .prompt([
      {
        type: 'input'.name: 'branchIn'.message: 'Enter the main branch that you merged into and if you want to manually select it you can just press Enter and go to next',}])const proptList = [{
    type: 'list'.name: 'branch'.message: 'Please select the main branch to merge into'.choices: branchList
  },
    {
      type: 'input'.name: 'title'.message: 'Please enter merge title'.default: 'Default merge request information'
    },
    {
      type: 'input'.name: 'desc'.message: 'Please enter merge description'
    },
    {
      type: 'list'.name: 'users'.message: 'Select Merge user'.choices: menberList
    }]

  getInputB.branchIn && proptList.splice(0.1)

  const getBranch = await inquirer
    .prompt(proptList)

  try {

    let mergeRes = await axios.post( 'https:// domain address/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('Created successfully');
      console.log('Access Address:' + mergeRes.data.web_url);
    } else {
      console.log('Please check that branch selection and title cannot be empty'); }}catch ( e ) {
    throw Error('Submit failed, please check that branch selection and title cannot be empty, or merge request already exists online no need to submit again');
  }
Copy the code

Other problems

Account management

If you look at the code above, there is one thing you don’t see in the account management. In this area, I use the account password saved to the file after the first filling, and then access it through the file

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: 'Please enter your account number'
          },
          {
            type: 'input'.name: 'password'.message: 'Please enter your password'
          }
        ])
    json = {
      username: getMessage.username,
      password: getMessage.password
    }
    fs.writeFileSync(path.resolve(__dirname + '/cz-cli.json'), JSON.stringify(json))
  }
Copy the code

This code is actually pretty simple and I’m not going to describe it.

git cz

In fact, this is the most important thing, but I think that since you have seen this, you may not lack the strength to read the source code of Git-cz, I will briefly say how we should change our functionality to add

Git cZ requires a third party package to load git cZ files. Git cZ requires a third party package to load git cZ files. Git cZ requires a third party package to load git files. Yes, it belongs to synchronous fetch, so our interface request cannot wait, so we need to make a little bit of magic change to the first place

#! SRC/commitizen/adapter. Js / / 142 lines of code originally 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}`); }})}Copy the code

We changed the direct execution code to asynchronous execution returning the Primose callback, and then we just need to do the next step to change the fetch to asynchronous

#! src/commitizen/adapter.js// Put the code at line 43
let prompter = getPrompter(adapterConfig.path);
// Change the method to async.
let prompter = await getPrompter(adapterConfig.path);
Copy the code

Now that we can do the asynchronous fetch of jIRA, what about after the processing is done?

In the/SRC /strategies/git-cz.js file, there is a done callback in the commit method execution at line 57. We can add subsequent actions to this method and execute it

commit(inquirer, process.cwd(), prompter, { args: parsedGitCzArgs, disableAppendPaths: true, emitData: true, quiet: False, retryLastCommit, hookMode}, function (error) {throw error; }});Copy the code

The last step

Git CZ: Merge Request git CZ: Merge Request Git CZ: Merge Request Git CZ: Merge Request Git CZ: Merge Request Git CZ: Merge Request Git CZ Add the file to the bin folder in the package.jso folder and configure the following command (actually this is the add command is to create scaffolding, if you are not familiar with scaffolding, check out my history article about scaffolding in action)

// 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" %*Copy the code

At the end

This is all the content of this article, if you have any questions or want me to help with the development of private chat, welcome to comment on me, the following affixed my wechat TWO-DIMENSIONAL code.