DEV Community

Benedict Steven for ClickPesa

Posted on

Automating pull request creation from one branch to another using javascript, bitbucket pipelines and bitbucket api

Bitbucket pipelines can be used to automate several operations by simply running a series of commands as if you were running them from your terminal.

What you will learn

  1. What is bitbucket pipelines, how and where it can be used
  2. What are the Advantages of automating pull requests in your project?
  3. Prerequisites for the automation to work
  4. Automate pull request with bitbucket pipelines
  5. Automate pull request with bitbucket pipelines and javascript

What is bitbucket pipelines, how and where it can be used

Bitbucket Pipelines is an integrated CI/CD service built into Bitbucket. It allows you to automatically build, test, and even deploy your code based on a configuration file in your repository. Essentially, we create containers in the cloud for you. Inside these containers, you can run commands (like you might on a local machine) but with all the advantages of a fresh system, customised and configured for your needs (Learn how to get started).

These commands include installing packages, making api requests using curl, storing variables, running javascript tasks and many more. Now that we have the advantages of storing variables and installing packages, making api requests and running javascript tasks we can create pull requests from one branch to another in your bitbucket repository.

Bitbucket pipelines can be used to automate several operations by simply running a series of commands as if you were running them from your terminal. These operations include but are not limited to creating pull requests, merging pull requests, deploying code to the remote server, unit tests, e2e tests, bumping versions in package.json files, read and write files and a lot more. At clickpesa we use bitbucket pipelines to do all those operations.

What are the Advantages of automating pull requests in your project?

  • Improve development process ( contributors don’t have to manually create PR to another branch )
  • Reduces human error such as creating PR to the wrong branch
  • Useful for the team with big number of members/developers
  • Helps in formatting PR descriptions. This helps Reviewers to understand what changes in the PR before merging to another branch.

Prerequisites

Authentication your workspace to be authorised to do operations on your behalf.

i. Create an OAuth Consumer in your workspace. Go to Your bitbucket workspace → settings → OAuth Consumers → Add consumer

bitbucket workspace

creating OAuth consumer

ii. Fill in required information, especially the name and give your new consumer permissions for different operations but in our case we’ll give permission to read and write pull requests. Make sure to check the This is a private consumer checkbox and a callback url (you can add any url).

OAuth consumer

iii. After saving the consumer, click the consumer to view the key and secret

Consumer keys

Open another tab and visit the repository where the pull request will be automated. If you do not have bitbucket pipelines in your repository you should skip this part for now and view step v. An admin access is needed for this action. After visiting the repository, go to Repository settings → Repository variables (here we should be able to add variables).

iv. Create a variable with any name say BB_AUTH_STRING and the values should be added as key:value for instance VzW7ubArG8T3huEDXs:eRNqGCycMVPvfzsGhEyd7xP33tYLd2jZ, this variable should be secured. Now we can use this value as a variable in the pipeline to authenticate users.

repository variables

v. ( if you haven’t set up pipelines in your project ) In the repository on the side bar visit pipelines → Select starter pipeline → Commit the pipeline to your repository. Congratulations, you have created your first bitbucket pipelines, after this process go back to step c.

creating first pipelines

commiting first pipelines

Steps

NB: .yml files has set of rules in its scripting such as indentation rules

Creating pull request using only pipelines

i. We already have the pipelines created, delete all the items and let’s start afresh, let's run hello world script using pipeline, the bitbucket pipelines contains a bunch of pipelines per user’s choice, for now let's stick with branches, the pipeline will only work if changes are pushed to the branch

pipelines:
  branches:
    master:
      - step:
          name: first pipelines
          script:
            - echo " - ✨ Hello World!!"
Enter fullscreen mode Exit fullscreen mode

Output

pipelines runner

ii. Enough of these simple things, let's dive to the main topic. First we will need to create a new branch say develop which we will automate the pull request from develop to master branch.

  • In the pipeline change the branch name from master to develop. Then install curl and jq, curl is used to make api requests in the terminal and jq is used to format responses.
pipelines:
  branches:
    develop:
      - step:
          name: automate pull request
          script:
            - apt-get update
            - apt-get -y install curl jq
Enter fullscreen mode Exit fullscreen mode

iii. Using the authentication variable we saved earlier, create a curl request to fetch authorization token. Store it in a bitbucket variable, say BB_TOKEN. Use jq to extract a token from the response.

pipelines:
  branches:
    develop:
      - step:
          name: automate pull request
          script:
            - apt-get update
            - apt-get -y install curl jq
            - >
              export BB_TOKEN=$(curl -s -S -f -X POST -u "${BB_AUTH_STRING}" \
                https://bitbucket.org/site/oauth2/access_token \
                -d grant_type=client_credentials -d scopes="repository" | jq --raw-output '.access_token')
            - echo $BB_TOKEN
Enter fullscreen mode Exit fullscreen mode
  • Let’s create our first pull request with pipelines.
pipelines:
  branches:
    develop:
      - step:
          name: automate pull request
          script:
            - apt-get update
            - apt-get -y install curl jq
            - >
              export BB_TOKEN=$(curl -s -S -f -X POST -u "${BB_AUTH_STRING}" \
                https://bitbucket.org/site/oauth2/access_token \
                -d grant_type=client_credentials -d scopes="repository" | jq --raw-output '.access_token')
            - echo $BB_TOKEN
            - >
              curl https://api.bitbucket.org/2.0/repositories/${BITBUCKET_REPO_OWNER}/${BITBUCKET_REPO_SLUG}/pullrequests \
                -s -S -X POST \
                -H 'Content-Type: application/json' \
                -H "Authorization: Bearer ${BB_TOKEN}" \
                -d '{
                    "title": "Automated Pull request",
                    "description": "",
                    "source": {
                      "branch": {
                        "name": "develop"
                      }
                    },
                    "destination": {
                      "branch": {
                        "name": "master"
                      }
                    },
                    "close_source_branch": false,
                    "reviewers": '[]'
                  }’
Enter fullscreen mode Exit fullscreen mode
  • Congratulations, you have created your first PR with pipelines

iv. We have created the simplest request for pull request, on the complex part we need to add default reviewers and descriptions (from the commits) dynamically.

  • First fetch default reviewers of the project by running another curl request just before creating pull request. Format the response with jq to only contain an array of uuid of reviewers, a contributor cannot be a reviewer.
pipelines:
  branches:
    develop:
      - step:
          name: automate pull request
          script:
            - apt-get update
            - apt-get -y install curl jq
            - >
              export BB_TOKEN=$(curl -s -S -f -X POST -u "${BB_AUTH_STRING}" \
                https://bitbucket.org/site/oauth2/access_token \
                -d grant_type=client_credentials -d scopes="repository" | jq --raw-output '.access_token')
            - echo $BB_TOKEN
            - >
              export DEFAULT_REVIEWERS=$(curl https://api.bitbucket.org/2.0/repositories/${BITBUCKET_REPO_OWNER}/${BITBUCKET_REPO_SLUG}/default-reviewers \
              -s -S -f -X GET \
              -H "Authorization: Bearer ${BB_TOKEN}" | jq '.values' | jq 'map({uuid})' )
            - echo $DEFAULT_REVIEWERS
Enter fullscreen mode Exit fullscreen mode
  • Pass the created array of default reviewers in the pull request payload.
pipelines:
  branches:
    develop:
      - step:
          name: automate pull request
          script:
            - apt-get update
            - apt-get -y install curl jq
            - >
              export BB_TOKEN=$(curl -s -S -f -X POST -u "${BB_AUTH_STRING}" \
                https://bitbucket.org/site/oauth2/access_token \
                -d grant_type=client_credentials -d scopes="repository" | jq --raw-output '.access_token')
            - echo $BB_TOKEN
            - >
              export DEFAULT_REVIEWERS=$(curl https://api.bitbucket.org/2.0/repositories/${BITBUCKET_REPO_OWNER}/${BITBUCKET_REPO_SLUG}/default-reviewers \
              -s -S -f -X GET \
              -H "Authorization: Bearer ${BB_TOKEN}" | jq '.values' | jq 'map({uuid})' )
            - echo $DEFAULT_REVIEWERS
            - >
              curl https://api.bitbucket.org/2.0/repositories/${BITBUCKET_REPO_OWNER}/${BITBUCKET_REPO_SLUG}/pullrequests \
                -s -S -X POST \
                -H 'Content-Type: application/json' \
                -H "Authorization: Bearer ${BB_TOKEN}" \
                -d '{
                   "title": "Automated Pull request",
                   "description": "",
                   "source": {
                     "branch": {
                       "name": "develop"
                      }
                    },
                   "destination": {
                     "branch": {
                       "name": "master"
                     }
                   },
                   "close_source_branch": false,
                   "reviewers": '"${DEFAULT_REVIEWERS}"'
                }’
Enter fullscreen mode Exit fullscreen mode

v. Adding commits in the pull request descriptions, first let's fetch commits, commits can be fetched by comparing changes between two branches, in our case we compare branch develop and master and the differences are the commits that we will send as pull request descriptions. In this part we can have commits from other branches that will include commits messages starting with merged in {branch name} which we we don’t need to show it to the reviewers, commits can be filtered to omit unwanted commits, create an empty string variable and loop the returned commits , in each loop append the commit if it fits in the empty string.

pipelines:
  branches:
    develop:
      - step:
          name: automate pull request
          script:
            - apt-get update
            - apt-get -y install curl jq
            - >
              export BB_TOKEN=$(curl -s -S -f -X POST -u "${BB_AUTH_STRING}" \
                https://bitbucket.org/site/oauth2/access_token \
                -d grant_type=client_credentials -d scopes="repository" | jq --raw-output '.access_token')
            - >
              export COMMITS=$(curl -H "Authorization: Bearer ${BB_TOKEN}" -d "include=develop" -d "exclude=master" "https://api.bitbucket.org/2.0/repositories/${BITBUCKET_REPO_OWNER}/${BITBUCKET_REPO_SLUG}/commits")
            - echo $COMMITS
            - export COMMIT_MESSAGES=""
            - export value=""
            - >
              for i in $(jq '.values | keys | .[]' <<< "$COMMITS"); do
                value=$(jq -r ".values[$i]" <<< ${COMMITS})
                message=$(jq -r '.message' <<< "$value")
            - if [[ "$message" != *"Merge"* ]]; then
              COMMIT_MESSAGES="$COMMIT_MESSAGES> "$message"\n\n"
            - fi
              done
            - echo $COMMIT_MESSAGES
Enter fullscreen mode Exit fullscreen mode
  • Add the obtained string in the description part of the pull request payload and commit, the title of pull request can be altered dynamically as well.
pipelines:
  branches:
    develop:
      - step:
          name: automate pull request
          script:
            - apt-get update
            - apt-get -y install curl jq
            - >
              export BB_TOKEN=$(curl -s -S -f -X POST -u "${BB_AUTH_STRING}" \
                https://bitbucket.org/site/oauth2/access_token \
                -d grant_type=client_credentials -d scopes="repository" | jq --raw-output '.access_token')
            - >
              export DEFAULT_REVIEWERS=$(curl https://api.bitbucket.org/2.0/repositories/${BITBUCKET_REPO_OWNER}/${BITBUCKET_REPO_SLUG}/default-reviewers \
                -s -S -f -X GET \
                -H "Authorization: Bearer ${BB_TOKEN}" | jq '.values' | jq 'map({uuid})' )
            - echo $DEFAULT_REVIEWERS
            - >
              export COMMITS=$(curl -H "Authorization: Bearer ${BB_TOKEN}" -d "include=develop" -d "exclude=master" "https://api.bitbucket.org/2.0/repositories/${BITBUCKET_REPO_OWNER}/${BITBUCKET_REPO_SLUG}/commits")
            - echo $COMMITS
            - export COMMIT_MESSAGES=""
            - export value=""
            - >
              for i in $(jq '.values | keys | .[]' <<< "$COMMITS"); do
                value=$(jq -r ".values[$i]" <<< ${COMMITS})
                message=$(jq -r '.message' <<< "$value")
            - if [[ "$message" != *"Merge"* ]]; then
              COMMIT_MESSAGES="$COMMIT_MESSAGES> "$message"\n\n"
            - fi
              done
            - echo $COMMIT_MESSAGES
            - >
              curl https://api.bitbucket.org/2.0/repositories/${BITBUCKET_REPO_OWNER}/${BITBUCKET_REPO_SLUG}/pullrequests \
                -s -S -X POST \
                -H 'Content-Type: application/json' \
                -H "Authorization: Bearer ${BB_TOKEN}" \
                -d '{
                  "title": "Automated Pull request",
                  "description":"'"${COMMIT_MESSAGES}"'",
                  "source": {
                    "branch": {
                      "name": "develop"
                    }
                  },
                  "destination": {
                    "branch": {
                      "name": "master"
                    }
                  },
                  "close_source_branch": false,
                  "reviewers": '"${DEFAULT_REVIEWERS}"'
                }’
Enter fullscreen mode Exit fullscreen mode
  • By playing around with this at a certain point pull request creation will return error 400 - Bad request and will give no further error descriptions. Don't worry there is a solution for this.

Alternative solution (Using javascript)

  1. Pull requests can be created using javascript using fetch api or axios.
  const headers = {
    "Content-Type": "application/json",
    Authorization: `Bearer ${authorization token}`,
  };

  const postData = {
    title: `title`,
    description: `description`,
    source: {
      branch: {
        name: `develop`,
      },
    },
    destination: {
      branch: {
        name: "master",
      },
    },
    close_source_branch: false,
    reviewers: [],
  };

const url = `https://api.bitbucket.org/2.0/repositories/${repo_owner}/${repo_name}/pullrequests`
const { data } = axios.post(url, postData, { headers });
console.log(data)
Enter fullscreen mode Exit fullscreen mode

ii. But this function can’t be called directly in the pipeline, this is where Gulp Js comes in. For this to happen we need to install several packages, (nodejs is required for this operation)

npm install gulp axios –save-dev
Enter fullscreen mode Exit fullscreen mode
  • Create a file called gulpfile.js.
  • With gulp we can create a task in javascript and call that task in the pipeline which will call the function defined by that task in javascript.
  • Create a task createpr in the gulpfile. Commit your changes and push. Check the bitbucket pipelines and pull requests will be created
const gulp = require("gulp");
const axios = require("axios").default;

gulp.task("createpr", async () => {
 const headers = {
    "Content-Type": "application/json",
    // make sure to pass the token
    Authorization: `Bearer ${authorization_token}`,
  };

  const postData = {
    title: title,
    description: “”,
    source: {
      branch: {
        name: develop,
      },
    },
    destination: {
      branch: {
        name: "master",
      },
    },
    close_source_branch: false,
    reviewers: [],
  };

  const url = `https://api.bitbucket.org/2.0/repositories/${repo_owner}/${repo_name}/pullrequests`
  const { data } = axios.post(url, postData, { headers });
  console.log(data)
}
Enter fullscreen mode Exit fullscreen mode
# nodejs image is required
image: node:14.16.1

pipelines:
  branches:
    develop:
      - step:
          name: automate pull request
          script:
            - apt-get update
            - apt-get -y install curl jq
            # install gulp globally
            - npm i -g gulp
            # this will list all defined tasks in the gulpfile we’ve created
            - gulp –tasks
            - gulp createpr # this will call the function we defined to create pull request.
Enter fullscreen mode Exit fullscreen mode

iii. Since we need variables from the pipeline such as authorization token, branch name, repo name, commits etc, The task will receive variables from the pipeline using process.argv[variable number] the variable number is the position of the variable defined in the pipeline for instance our task is createpr, we’ll call it as gulp createpr --t $BB_TOKEN --b "develop" --o $BITBUCKET_REPO_OWNER --s $BITBUCKET_REPO_SLUG --d "$COMMIT_MESSAGES" where BB_TOKEN is the authorization token, BITBUCKET_REPO_OWNER and BITBUCKET_REPO_SLUG are already available in the pipeline and doesn’t need to be defined. By counting from gulp BB_TOKEN is the fourth item hence we can use it as process.argv[4] and for the source branch (develop) can be called as process.argv[6] same goes to other variables.

# nodejs image is required
image: node:14.16.1
pipelines:
  branches:
    develop:
      - step:
          name: automate pull request
          script:
            - apt-get update
            - apt-get -y install curl jq
            - >
              export BB_TOKEN=$(curl -s -S -f -X POST -u "${BB_AUTH_STRING}" \
                https://bitbucket.org/site/oauth2/access_token \
                -d grant_type=client_credentials -d scopes="repository" | jq --raw-output '.access_token')
            - >
               export DEFAULT_REVIEWERS=$(curl https://api.bitbucket.org/2.0/repositories/${BITBUCKET_REPO_OWNER}/${BITBUCKET_REPO_SLUG}/default-reviewers \
              -s -S -f -X GET \
              -H "Authorization: Bearer ${BB_TOKEN}" | jq '.values' | jq 'map({uuid})' )
            - echo $DEFAULT_REVIEWERS
            - >
              export COMMITS=$(curl -H "Authorization: Bearer ${BB_TOKEN}" -d "include=develop" -d "exclude=master" "https://api.bitbucket.org/2.0/repositories/${BITBUCKET_REPO_OWNER}/${BITBUCKET_REPO_SLUG}/commits")
            - echo $COMMITS
            - export COMMIT_MESSAGES=""
            - export value=""
            - >
              for i in $(jq '.values | keys | .[]' <<< "$COMMITS"); do
                value=$(jq -r ".values[$i]" <<< ${COMMITS})
                message=$(jq -r '.message' <<< "$value")
            - if [[ "$message" != *"Merge"* ]]; then
              COMMIT_MESSAGES="$COMMIT_MESSAGES> "$message"\n\n"
            - fi
              done
            - echo $COMMIT_MESSAGES
            - npm i -g gulp
            - gulp –tasks
            - gulp createpr --t $BB_TOKEN --b "develop" --o $BITBUCKET_REPO_OWNER --s $BITBUCKET_REPO_SLUG --d "$COMMIT_MESSAGES" –r $DEFAULT_REVIEWERS

Enter fullscreen mode Exit fullscreen mode
const gulp = require("gulp");
const axios = require("axios").default;

gulp.task("createpr", async () => {
  const headers = {
    "Content-Type": "application/json",
    Authorization: `Bearer ${process.argv[4]}`,
  };
  const postData = {
    title: `${process.argv[6]}`,
    description: `${process.argv[12]}`,
    source: {
      branch: {
        name: `${process.argv[6]}`,
      },
    },
    destination: {
      branch: {
        name: "master",
      },
    },
    close_source_branch: true,
    reviewers: process.argv[14] ,
  };
  const url = `https://api.bitbucket.org/2.0/repositories/${process.argv[8]}/${process.argv[10]}/pullrequests`;

  const { data } = axios.post(url, postData, { headers });
  console.log(data)
}
Enter fullscreen mode Exit fullscreen mode

iv. Commit your changes and push. Check the bitbucket pipelines and pull requests will be created

Discussion (0)