-
Notifications
You must be signed in to change notification settings - Fork 6.8k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
chore: add script to pull commits to cherrypick (#16637)
* chore: add script to pull commits to cherrypick * lint
- Loading branch information
1 parent
2994365
commit 917871b
Showing
8 changed files
with
384 additions
and
38 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
import {GitHub} from './github'; | ||
import {outputResults} from './output-results'; | ||
import { | ||
requestLatestCherryPickedCommitSha, | ||
requestPatchBranch, | ||
verifyLatestCherryPickedCommit | ||
} from './prompt'; | ||
|
||
|
||
/** | ||
* Task to run the script that attempts to produce cherry-pick commands for the patch branch. | ||
*/ | ||
class CherryPickPatchTask { | ||
github = new GitHub(); | ||
|
||
async run() { | ||
const patchBranchSuggestion = await this.github.getPatchBranchSuggestion(); | ||
const branch = await requestPatchBranch(patchBranchSuggestion); | ||
const sha = await this.getLatestCherryPickedCommitSha(branch); | ||
|
||
const commit = await this.github.getCommit(sha); | ||
const pullRequests = await this.github.getPatchPullRequestsSince(commit.commit.author.date); | ||
|
||
outputResults(pullRequests); | ||
} | ||
|
||
/** Returns the commit SHA of the last cherry-picked commit on master. */ | ||
async getLatestCherryPickedCommitSha(branch: any): Promise<string> { | ||
const commits = await this.github.listCommits(branch); | ||
|
||
/** Gets the SHA from the string: "(cherry picked from commit 4c6eeb9aba73d3)" */ | ||
const regexp = new RegExp('cherry picked from commit (.*[^)])'); | ||
const latestShas = commits | ||
.map(d => { | ||
const result = d.commit.message.match(regexp); | ||
return result ? result[1] : null; | ||
}) | ||
.filter(d => !!d); | ||
|
||
const latestSha = latestShas[0]; | ||
if (!latestSha) { | ||
return await requestLatestCherryPickedCommitSha(); | ||
} else { | ||
const commit = await this.github.getCommit(latestSha); | ||
return await verifyLatestCherryPickedCommit(commit); | ||
} | ||
} | ||
} | ||
|
||
/** Entry-point for the script. */ | ||
if (require.main === module) { | ||
new CherryPickPatchTask().run(); | ||
} |
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,88 @@ | ||
import * as OctokitApi from '@octokit/rest'; | ||
|
||
// TODO: Consider using local git information for the data to avoid worrying about rate limits. */ | ||
/** Class to act as an interface to the GitHub API. */ | ||
export class GitHub { | ||
// TODO: Use an authentication token to increase rate limits. | ||
/** Octokit API instance that can be used to make Github API calls. */ | ||
private _api = new OctokitApi(); | ||
|
||
/** Owner of the repository to query. */ | ||
private _owner = 'angular'; | ||
|
||
/** Name of the repository to query. */ | ||
private _name = 'components'; | ||
|
||
/** | ||
* Retrieves merged patch-eligible pull requests that have been merged since the date. | ||
* Results are sorted by merge date. | ||
*/ | ||
async getPatchPullRequestsSince(dateSince: string): Promise<OctokitApi.PullsGetResponse[]> { | ||
const query = 'base:master is:pr -label:"target: minor" -label:"target: major" is:merged' + | ||
` merged:>${dateSince}`; | ||
const result = await this._search(query); | ||
|
||
// Load information for each pull request. Waits for each pull request response until loading | ||
// the next pull request to avoid GitHub's abuse detection (too many calls in a short amount | ||
// of time). | ||
const pullRequests: OctokitApi.PullsGetResponse[] = []; | ||
for (let i = 0; i < result.items.length; i++) { | ||
pullRequests.push(await this.loadPullRequest(result.items[i].number)); | ||
} | ||
|
||
// Sort by merge date. | ||
pullRequests.sort((a, b) => (a.merged_at < b.merged_at) ? -1 : 1); | ||
return pullRequests; | ||
} | ||
|
||
/** Loads the information for the provided pull request number. */ | ||
async loadPullRequest(prNumber: number): Promise<OctokitApi.PullsGetResponse> { | ||
const response = await this._api.pulls.get({ | ||
owner: this._owner, | ||
repo: this._name, | ||
pull_number: prNumber, | ||
}); | ||
return response.data; | ||
} | ||
|
||
/** Gets the commit information for the given SHA. */ | ||
async getCommit(sha: string): Promise<OctokitApi.ReposGetCommitResponse> { | ||
const response = await this._api.repos.getCommit({ | ||
owner: this._owner, | ||
repo: this._name, | ||
ref: sha, | ||
}); | ||
|
||
return response.data; | ||
} | ||
|
||
/** Retrieves the list of latest commits from the branch. */ | ||
async listCommits(branch: string): Promise<OctokitApi.ReposListCommitsResponse> { | ||
const response = await this._api.repos.listCommits({ | ||
owner: this._owner, | ||
repo: this._name, | ||
sha: branch, | ||
}); | ||
|
||
return response.data; | ||
} | ||
|
||
// TODO: Handle pagination in case there are more than 100 results. | ||
/** Gets a suggestion for the latest patch branch. */ | ||
async getPatchBranchSuggestion(): Promise<string> { | ||
const response = await this._api.repos.listBranches({owner: this._owner, repo: this._name}); | ||
|
||
// Matches branch names that have two digits separated by period and ends with an x | ||
const patchBranches = | ||
response.data.map(branch => branch.name).filter(name => !!/^\d+\.\d+\.x$/g.exec(name)); | ||
return patchBranches.pop() || ''; | ||
} | ||
|
||
// TODO: Handle pagination in case there are more than 100 results. | ||
/** Searches the repository using the provided query. */ | ||
private async _search(query: string): Promise<{items: any[]}> { | ||
const scopedQuery = `repo:${this._owner}/${this._name} ${query}`; | ||
const result = await this._api.search.issuesAndPullRequests({per_page: 100, q: scopedQuery}); | ||
return result.data; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
import {PullsGetResponse} from '@octokit/rest'; | ||
import {cyan} from 'chalk'; | ||
|
||
/** Outputs the information of the pull requests to be cherry-picked and the commands to run. */ | ||
export function outputResults(pullRequests: PullsGetResponse[]) { | ||
if (!pullRequests.length) { | ||
console.log('No pull requests need to be cherry-picked'); | ||
return; | ||
} | ||
|
||
console.log(); | ||
console.log(cyan('------------------------')); | ||
console.log(cyan(' Results ')); | ||
console.log(cyan('------------------------')); | ||
console.log(); | ||
|
||
pullRequests.forEach(p => { | ||
const data = [p.number, p.merged_at, p.merge_commit_sha, p.html_url, p.title]; | ||
console.log(data.join('\t')); | ||
}); | ||
|
||
console.log(); | ||
console.log(cyan('------------------------')); | ||
console.log(cyan(' Cherry Pick Commands')); | ||
console.log(cyan('------------------------')); | ||
|
||
pullRequests.forEach((pr, index) => { | ||
if (index % 5 === 0) { | ||
console.log(); | ||
} | ||
|
||
console.log(`git cherry-pick -x ${pr.merge_commit_sha};`); | ||
}); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
import {prompt} from 'inquirer'; | ||
import * as OctokitApi from '@octokit/rest'; | ||
|
||
/** Requests the user to provide the name of the patch branch. */ | ||
export async function requestPatchBranch(suggestion: string): Promise<string> { | ||
const result = await prompt<{branch: string}>([{ | ||
type: 'input', | ||
name: 'branch', | ||
message: `What is the name of the current patch branch?`, | ||
default: suggestion || null, | ||
}]); | ||
|
||
return result.branch; | ||
} | ||
|
||
/** Confirms the latest cherry-picked commit on master; requests one if not confirmed. */ | ||
export async function verifyLatestCherryPickedCommit(commit: OctokitApi.ReposGetCommitResponse) { | ||
console.log(`\nThe last cherry-picked commit on master is "${commit.commit.message}"`); | ||
|
||
const result = await prompt<{confirm: boolean}>([{ | ||
type: 'confirm', | ||
name: 'confirm', | ||
message: `Is this correct?`, | ||
default: true, | ||
}]); | ||
|
||
if (!result.confirm) { | ||
return await requestLatestCherryPickedCommitSha(); | ||
} else { | ||
return commit.sha; | ||
} | ||
} | ||
|
||
/** Requests the SHA of the latest cherry picked commit on master. */ | ||
export async function requestLatestCherryPickedCommitSha(): Promise<string> { | ||
const result = await prompt<{sha: string}>([{ | ||
type: 'input', | ||
name: 'sha', | ||
message: `What is the SHA of the latest cherry-picked commit on master?`, | ||
}]); | ||
|
||
return result.sha; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
{ | ||
"compilerOptions": { | ||
"lib": ["es2016"], | ||
"types": ["node"], | ||
"strictNullChecks": true | ||
} | ||
} |
Oops, something went wrong.