-
Notifications
You must be signed in to change notification settings - Fork 1
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Automatically count required staging checks #68
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -173,17 +173,12 @@ class StatusChecks | |
// For example, failed() should count only genuine statuses, whereas | ||
// final() should count all statuses. | ||
|
||
// expectedStatusCount: | ||
// for staged commits: the bot-configured number of required checks (Config.stagingChecks()), | ||
// for PR commits: GitHub configured number of required checks (requested from GitHub) | ||
// context: either "PR" or "Staging"; | ||
// contextsRequiredByGitHubConfig: the GitHub protected branch checks in a format of a list of string contexts | ||
constructor(expectedStatusCount, context, contextsRequiredByGitHubConfig) { | ||
assert(expectedStatusCount !== undefined); | ||
assert(expectedStatusCount !== null); | ||
constructor(context, contextsRequiredByGitHubConfig) { | ||
assert(context); | ||
assert(contextsRequiredByGitHubConfig); | ||
this.expectedStatusCount = expectedStatusCount; | ||
this._contextsRequiredByGitHubConfig = contextsRequiredByGitHubConfig; | ||
this.context = context; | ||
this._approvalIsRequired = contextsRequiredByGitHubConfig.some(el => el.trim() === Config.approvalContext()); | ||
this.requiredStatuses = []; | ||
|
@@ -234,7 +229,7 @@ class StatusChecks | |
|
||
// no more required status changes or additions are expected | ||
final() { | ||
return (this.requiredStatuses.length >= this.expectedStatusCount) && | ||
return (this.requiredStatuses.length >= this._contextsRequiredByGitHubConfig.length) && | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this.requiredStatuses.length should be <= this._contextsRequiredByGitHubConfig.length because this.requiredStatuses gets only those statuses that are among this._contextsRequiredByGitHubConfig. So we can probably assert the '<=' condition here and use '==' instead for the returning condition. |
||
this.requiredStatuses.every(check => !check.pending()); | ||
} | ||
|
||
|
@@ -256,7 +251,7 @@ class StatusChecks | |
} | ||
|
||
toString() { | ||
let combinedStatus = "context: " + this.context + " expected/required/optional: " + this.expectedStatusCount + "/" + | ||
let combinedStatus = "context: " + this.context + " expected/required/optional: " + this._contextsRequiredByGitHubConfig.length + "/" + | ||
this.requiredStatuses.length + "/" + this.optionalStatuses.length + ", combined: "; | ||
if (this.failed()) | ||
combinedStatus += "failure"; | ||
|
@@ -783,8 +778,11 @@ class PullRequest { | |
|
||
this._approval = null; // future Approval object | ||
|
||
// a list of proteced branch contexts received from GitHub | ||
this._contextsRequiredByGitHubConfig = null; | ||
// a list of proteced base branch contexts received from GitHub | ||
this._contextsRequiredByGitHubConfigBase = null; | ||
|
||
// a list of proteced staging branch contexts received from GitHub | ||
this._contextsRequiredByGitHubConfigStaging = null; | ||
|
||
this._stagedPosition = null; | ||
|
||
|
@@ -942,48 +940,55 @@ class PullRequest { | |
await GH.createStatus(sha, this._approval.state, Config.approvalUrl(), this._approval.description, Config.approvalContext()); | ||
} | ||
|
||
async _loadRequiredContexts() { | ||
assert(!this._contextsRequiredByGitHubConfig); | ||
async _loadRequiredContexts(branch) { | ||
let contextsRequiredByGitHubConfig = undefined; | ||
|
||
try { | ||
this._contextsRequiredByGitHubConfig = await GH.getProtectedBranchRequiredStatusChecks(this._prBaseBranch()); | ||
contextsRequiredByGitHubConfig = await GH.getProtectedBranchRequiredStatusChecks(branch); | ||
} catch (e) { | ||
if (e.name === 'ErrorContext' && e.notFound()) | ||
this._logEx(e, "no status checks are required"); | ||
else | ||
throw e; | ||
} | ||
|
||
if (this._contextsRequiredByGitHubConfig === undefined) | ||
this._contextsRequiredByGitHubConfig = []; | ||
if (contextsRequiredByGitHubConfig === undefined) | ||
contextsRequiredByGitHubConfig = []; | ||
|
||
assert(this._contextsRequiredByGitHubConfig); | ||
this._log("required contexts found: " + this._contextsRequiredByGitHubConfig.length); | ||
assert(contextsRequiredByGitHubConfig); | ||
this._log("required contexts found: " + contextsRequiredByGitHubConfig.length); | ||
return contextsRequiredByGitHubConfig; | ||
} | ||
|
||
// returns filled StatusChecks object | ||
async _getPrStatuses() { | ||
const combinedPrStatus = await GH.getStatuses(this._prHeadSha()); | ||
let statusChecks = new StatusChecks(this._contextsRequiredByGitHubConfig.length, "PR", this._contextsRequiredByGitHubConfig); | ||
// fill with required status checks | ||
for (let st of combinedPrStatus.statuses) { | ||
if (this._contextsRequiredByGitHubConfig.some(el => el.trim() === st.context.trim())) | ||
statusChecks.addRequiredStatus(new StatusCheck(st)); | ||
else | ||
statusChecks.addOptionalStatus(new StatusCheck(st)); | ||
} | ||
|
||
async uniqueCheckRuns(sha) { | ||
// Returns whole check runs history for the commit. | ||
const checkRuns = await GH.getCheckRuns(this._prHeadSha()); | ||
const checkRuns = await GH.getCheckRuns(sha); | ||
// Filter out stale/older checks, assuming that check.id is greater in newer checks. | ||
const sortedCheckRuns = checkRuns.sort((e1, e2) => parseInt(e2.id) - parseInt(e1.id)); | ||
let uniqueCheckRuns = []; | ||
sortedCheckRuns.forEach(check => { | ||
if (!uniqueCheckRuns.some(e => e.name === check.name)) | ||
uniqueCheckRuns.push(check); | ||
}); | ||
for (let st of uniqueCheckRuns) { | ||
if (this._contextsRequiredByGitHubConfig.some(el => el.trim() === st.name.trim())) | ||
return uniqueCheckRuns; | ||
} | ||
|
||
// returns filled StatusChecks object | ||
async _getPrStatuses() { | ||
const combinedPrStatus = await GH.getStatuses(this._prHeadSha()); | ||
let statusChecks = new StatusChecks("PR", this._contextsRequiredByGitHubConfigBase); | ||
for (let st of combinedPrStatus.statuses) { | ||
const check = new StatusCheck(st); | ||
if (this._contextsRequiredByGitHubConfigBase.some(el => el.trim() === st.context.trim())) | ||
statusChecks.addRequiredStatus(check); | ||
else | ||
statusChecks.addOptionalStatus(check); | ||
} | ||
|
||
const checkRuns = await this.uniqueCheckRuns(this._prHeadSha()); | ||
|
||
for (let st of checkRuns) { | ||
if (this._contextsRequiredByGitHubConfigBase.some(el => el.trim() === st.name.trim())) | ||
statusChecks.addRequiredStatus(StatusCheck.FromCheckRun(st)); | ||
else | ||
statusChecks.addOptionalStatus(StatusCheck.FromCheckRun(st)); | ||
|
@@ -997,16 +1002,13 @@ class PullRequest { | |
async _getStagingStatuses() { | ||
const combinedStagingStatuses = await GH.getStatuses(this._stagedSha()); | ||
const genuineStatuses = combinedStagingStatuses.statuses.filter(st => !st.description.endsWith(Config.copiedDescriptionSuffix())); | ||
assert(genuineStatuses.length <= Config.stagingChecks()); | ||
let statusChecks = new StatusChecks(Config.stagingChecks(), "Staging", this._contextsRequiredByGitHubConfig); | ||
// all genuine checks, except for PR approval, are 'required' | ||
// PR approval may be either 'required' or 'optional' (depending on the GitHub settings) | ||
let statusChecks = new StatusChecks("Staging", this._contextsRequiredByGitHubConfigStaging); | ||
for (let st of genuineStatuses) { | ||
const check = new StatusCheck(st); | ||
if (check.context.trim() === Config.approvalContext()) | ||
statusChecks.setApprovalStatus(check); | ||
else | ||
if (this._contextsRequiredByGitHubConfigStaging.some(el => el.trim() === st.context.trim())) | ||
statusChecks.addRequiredStatus(check); | ||
else | ||
statusChecks.addOptionalStatus(check); | ||
} | ||
|
||
const optionalStatuses = combinedStagingStatuses.statuses.filter(st => st.description.endsWith(Config.copiedDescriptionSuffix())); | ||
|
@@ -1016,10 +1018,13 @@ class PullRequest { | |
statusChecks.addOptionalStatus(new StatusCheck(st)); | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. _getPrStatuses() and _getStagingStatuses() look almost the same now, except for this dealing with 'copied' statuses (although the diff does not show it clearly). |
||
|
||
// all check runs are 'required' | ||
const checkRuns = await GH.getCheckRuns(this._stagedSha()); | ||
for (let st of checkRuns) | ||
statusChecks.addRequiredStatus(StatusCheck.FromCheckRun(st)); | ||
const checkRuns = await this.uniqueCheckRuns(this._stagedSha()); | ||
for (let st of checkRuns) { | ||
if (this._contextsRequiredByGitHubConfigStaging.some(el => el.trim() === st.name.trim())) | ||
statusChecks.addRequiredStatus(StatusCheck.FromCheckRun(st)); | ||
else | ||
statusChecks.addOptionalStatus(StatusCheck.FromCheckRun(st)); | ||
} | ||
|
||
this._log("staging status details: " + statusChecks); | ||
return statusChecks; | ||
|
@@ -1449,7 +1454,7 @@ class PullRequest { | |
async _supplyStagingWithPrRequired() { | ||
assert(this._stagedStatuses.succeeded()); | ||
|
||
for (let requiredContext of this._contextsRequiredByGitHubConfig) { | ||
for (let requiredContext of this._contextsRequiredByGitHubConfigBase) { | ||
if (this._stagedStatuses.hasStatus(requiredContext)) { | ||
this._log("_supplyStagingWithPrRequired: skip existing " + requiredContext); | ||
continue; | ||
|
@@ -1652,7 +1657,8 @@ class PullRequest { | |
|
||
await this._loadStaged(); | ||
await this._loadRawPr(); // requires this._loadStaged() | ||
await this._loadRequiredContexts(); | ||
this._contextsRequiredByGitHubConfigBase = await this._loadRequiredContexts(this._prBaseBranch()); | ||
this._contextsRequiredByGitHubConfigStaging = await this._loadRequiredContexts(Config.stagingBranch()); | ||
await this._loadPrState(); // requires this._loadRawPr(), this._loadRequiredContexts() and this._loadLabels() | ||
this._log("PR state: " + this._prState); | ||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Now all 'extra' checks will be treated as optional (i.e., the checks that are not 'required').