From efac7b61ba48e52cc7e4ed24abf15ae33b4838b7 Mon Sep 17 00:00:00 2001 From: Filipe Silva Date: Mon, 11 Mar 2019 15:32:19 +0000 Subject: [PATCH] ci: rebase PRs on target branch --- .appveyor.yml | 7 ++++ .circleci/config.yml | 25 +++++------- tools/rebase-pr.js | 92 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 108 insertions(+), 16 deletions(-) create mode 100644 tools/rebase-pr.js diff --git a/.appveyor.yml b/.appveyor.yml index 19fa94a43d..39413e3df3 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -9,6 +9,13 @@ skip_branch_with_pr: true install: - ps: Install-Product node $env:nodejs_version + - ps: | + if (Test-Path env:APPVEYOR_PULL_REQUEST_NUMBER) { + # user is required for rebase. + git config user.name "angular-ci" + git config user.email "angular-ci" + # Rebase on target branch. + node tools\rebase-pr.js angular/angular-cli $env:APPVEYOR_PULL_REQUEST_NUMBER } # --network-timeout is a workaround for https://github.com/yarnpkg/yarn/issues/6221 - yarn --frozen-lockfile --network-timeout=500000 - yarn webdriver-update diff --git a/.circleci/config.yml b/.circleci/config.yml index 6add7f5afc..b4cc7281db 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -25,26 +25,19 @@ anchor_1: &defaults docker: - image: *docker_image -# After checkout, rebase on top of master. -# Similar to travis behavior, but not quite the same. -# See https://discuss.circleci.com/t/1662 +# After checkout, rebase on top of target branch. anchor_2: &post_checkout run: - name: Post checkout step + name: Rebase PR on target branch command: > if [[ -n "${CIRCLE_PR_NUMBER}" ]]; then - # Fetch the head and merge commits for this PR. - git fetch origin +refs/pull/$CIRCLE_PR_NUMBER/head:pr/$CIRCLE_PR_NUMBER/head - git fetch origin +refs/pull/$CIRCLE_PR_NUMBER/merge:pr/$CIRCLE_PR_NUMBER/merge - # Checkout the merged PR for testing as CircleCI will just use the PR head otherwise. - git checkout -qf pr/$CIRCLE_PR_NUMBER/merge - # Reset the merge commit into its PR head. - git reset pr/$CIRCLE_PR_NUMBER/head - # Commit the merge changes into the head of the PR. - # This way we keep the last commit message. - git config user.name "angular-ci" - git config user.email "angular-ci" - git commit . --amend --no-edit + # User is required for rebase. + git config user.name "angular-ci" + git config user.email "angular-ci" + # Rebase PR on top of target branch. + node tools/rebase-pr.js angular/angular-cli ${CIRCLE_PR_NUMBER} + else + echo "This build is not over a PR, nothing to do." fi anchor_3: &restore_cache restore_cache: diff --git a/tools/rebase-pr.js b/tools/rebase-pr.js new file mode 100644 index 0000000000..e74b6156d9 --- /dev/null +++ b/tools/rebase-pr.js @@ -0,0 +1,92 @@ +/** + * @license + * Copyright Google Inc. All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + + // tslint:disable:no-console +// ** IMPORTANT ** +// This script cannot use external dependencies because it needs to run before they are installed. + +const util = require('util'); +const https = require('https'); +const child_process = require('child_process'); +const exec = util.promisify(child_process.exec); + +function determineTargetBranch(repository, prNumber) { + const pullsUrl = `https://api.github.com/repos/${repository}/pulls/${prNumber}`; + // GitHub requires a user agent: https://developer.github.com/v3/#user-agent-required + const options = { headers: { 'User-Agent': repository } }; + + return new Promise((resolve, reject) => { + https.get(pullsUrl, options, (res) => { + const { statusCode } = res; + const contentType = res.headers['content-type']; + + let error; + if (statusCode !== 200) { + error = new Error(`Request Failed.\nStatus Code: ${statusCode}.\nResponse: ${res}.\n' +`); + } else if (!/^application\/json/.test(contentType)) { + error = new Error('Invalid content-type.\n' + + `Expected application/json but received ${contentType}`); + } + if (error) { + reject(error); + res.resume(); + return; + } + + res.setEncoding('utf8'); + let rawData = ''; + res.on('data', (chunk) => { rawData += chunk; }); + res.on('end', () => { + try { + const parsedData = JSON.parse(rawData); + resolve(parsedData['base']['ref']); + } catch (e) { + reject(e); + } + }); + }).on('error', (e) => { + reject(e); + }); + }); +} + +if (process.argv.length != 4) { + console.error(`This script requires the GitHub repository and PR number as arguments.`); + console.error(`Example: node scripts/rebase-pr.js angular/angular 123`); + process.exitCode = 1; + return; +} + +const repository = process.argv[2]; +const prNumber = process.argv[3]; +let targetBranch; + + +return Promise.resolve() + .then(() => { + console.log(`Determining target branch for PR ${prNumber} on ${repository}.`); + return determineTargetBranch(repository, prNumber); + }) + .then(target => { + targetBranch = target; + console.log(`Target branch is ${targetBranch}.`); + }) + .then(() => { + console.log(`Fetching ${targetBranch} from origin.`); + return exec(`git fetch origin ${targetBranch}`); + }) + .then(target => { + console.log(`Rebasing current branch on ${targetBranch}.`); + return exec(`git rebase origin/${targetBranch}`); + }) + .then(() => console.log('Rebase successfull.')) + .catch(err => { + console.log('Failed to rebase on top or target branch.\n'); + console.error(err); + process.exitCode = 1; + }); \ No newline at end of file