114 lines
		
	
	
		
			3.7 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
		
		
			
		
	
	
			114 lines
		
	
	
		
			3.7 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
|  | /** | ||
|  |  * @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
 | ||
|  |  */ | ||
|  | 
 | ||
|  | /** | ||
|  |  * **Usage:** | ||
|  |  * ```
 | ||
|  |  * node rebase-pr <github-repository> <pull-request-number> | ||
|  |  * ```
 | ||
|  |  * **Example:** | ||
|  |  * ```
 | ||
|  |  * node rebase-pr angular/angular 123 | ||
|  |  * ```
 | ||
|  |  * | ||
|  |  * Rebases the current branch on top of the GitHub PR target branch. | ||
|  |  * | ||
|  |  * **Context:** | ||
|  |  * Since a GitHub PR is not necessarily up to date with its target branch, it is useful to rebase | ||
|  |  * prior to testing it on CI to ensure more up to date test results. | ||
|  |  * | ||
|  |  * **Implementation details:** | ||
|  |  * This script obtains the base for a GitHub PR via the | ||
|  |  * [GitHub PR API](https://developer.github.com/v3/pulls/#get-a-single-pull-request), then
 | ||
|  |  * fetches that branch, and rebases the current branch on top of it. | ||
|  |  * | ||
|  |  * **NOTE:** | ||
|  |  * This script cannot use external dependencies or be compiled because it needs to run before the | ||
|  |  * environment is setup. | ||
|  |  * Use only features supported by the NodeJS versions used in the environment. | ||
|  |  */ | ||
|  | 
 | ||
|  | // This script uses `console` to print messages to the user.
 | ||
|  | // tslint:disable:no-console
 | ||
|  | 
 | ||
|  | // Imports
 | ||
|  | const util = require('util'); | ||
|  | const https = require('https'); | ||
|  | const child_process = require('child_process'); | ||
|  | const exec = util.promisify(child_process.exec); | ||
|  | 
 | ||
|  | // CLI validation
 | ||
|  | if (process.argv.length != 4) { | ||
|  |   console.error(`This script requires the GitHub repository and PR number as arguments.`); | ||
|  |   console.error(`Example: node tools/rebase-pr.js angular/angular 123`); | ||
|  |   process.exitCode = 1; | ||
|  |   return; | ||
|  | } | ||
|  | 
 | ||
|  | // Run
 | ||
|  | _main(...process.argv.slice(2)).catch(err => { | ||
|  |   console.log('Failed to rebase on top of target branch.\n'); | ||
|  |   console.error(err); | ||
|  |   process.exitCode = 1; | ||
|  | }); | ||
|  | 
 | ||
|  | // Helpers
 | ||
|  | async function _main(repository, prNumber) { | ||
|  |   console.log(`Determining target branch for PR ${prNumber} on ${repository}.`); | ||
|  |   const targetBranch = await determineTargetBranch(repository, prNumber); | ||
|  |   console.log(`Target branch is ${targetBranch}.`); | ||
|  |   await exec(`git fetch origin ${targetBranch}`); | ||
|  |   console.log(`Rebasing current branch on ${targetBranch}.`); | ||
|  |   await exec(`git rebase origin/${targetBranch}`); | ||
|  |   console.log('Rebase successful.'); | ||
|  | } | ||
|  | 
 | ||
|  | 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 rawData = ''; | ||
|  | 
 | ||
|  |               res.on('data', (chunk) => { rawData += chunk; }); | ||
|  |               res.on('end', () => { | ||
|  | 
 | ||
|  |                 let error; | ||
|  |                 if (statusCode !== 200) { | ||
|  |                   error = new Error( | ||
|  |                       `Request Failed.\nStatus Code: ${statusCode}.\nResponse: ${rawData}`); | ||
|  |                 } else if (!/^application\/json/.test(contentType)) { | ||
|  |                   error = new Error( | ||
|  |                       'Invalid content-type.\n' + | ||
|  |                       `Expected application/json but received ${contentType}`); | ||
|  |                 } | ||
|  | 
 | ||
|  |                 if (error) { | ||
|  |                   reject(error); | ||
|  |                   return; | ||
|  |                 } | ||
|  | 
 | ||
|  |                 try { | ||
|  |                   const parsedData = JSON.parse(rawData); | ||
|  |                   resolve(parsedData['base']['ref']); | ||
|  |                 } catch (e) { | ||
|  |                   reject(e); | ||
|  |                 } | ||
|  |               }); | ||
|  |             }) | ||
|  |         .on('error', (e) => { reject(e); }); | ||
|  |   }); | ||
|  | } |