Skip to content

Commit

Permalink
Merge pull request #10 from alejandrohdezma/master
Browse files Browse the repository at this point in the history
Add `require_passed_checks` input to control if should skip checks step by @alejandrohdezma
  • Loading branch information
zhiyelee committed Apr 5, 2021
2 parents 7033c5a + eba2a02 commit 83c61e2
Show file tree
Hide file tree
Showing 5 changed files with 126 additions and 105 deletions.
13 changes: 10 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@
The job of the action is to help click "Update branch" button for you. Designed to work with the `auto-merge` and ["Require branches to be up to date before merging"](https://docs.github.com/en/github/administering-a-repository/about-protected-branches#require-status-checks-before-merging) options. It will update the newest open PR that match the below conditions

- The PR has the `auto-merge` option enabled
- The PR has 2 approvals and no changes-requested review
- The PR has all checks passed
- The PR has 2 approvals and no changes-requested review (configurable)
- The PR has all checks passed (configurable)
- The PR branch has no conflicts with the base branch
- The PR branch is behind the base branch

Expand Down Expand Up @@ -54,7 +54,13 @@ The action will skip PRs that have less approvals than `required_approval_count`

We could retrieve this value from the repo settings through an API call but that will incur one more request. GitHub has [rate limit](https://docs.github.com/en/actions/reference/usage-limits-billing-and-administration#usage-limits) on API usage of GitHub actions.

> API requests - You can execute up to 1000 API requests in an hour across all actions within a repository. If exceeded, additional API calls will fail, which might cause jobs to fail.
### `require_passed_checks`

**Optional**

Default: true

The action will skip PRs that have failed checks.

## Example usage

Expand All @@ -75,6 +81,7 @@ jobs:
token: ${{ secrets.ACTION_USER_TOKEN }}
base: 'master'
required_approval_count: 2
require_passed_checks: false
```

Replace the `VERSION_YOU_WANT_TO_USE` with the actual version you want to use, check the version format [here](https://docs.github.com/en/actions/reference/workflow-syntax-for-github-actions#jobsjob_idstepsuses)
Expand Down
4 changes: 4 additions & 0 deletions action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@ inputs:
required: true
description: 'The action will skip PRs that have less approvals than this value'
default: '2'
require_passed_checks:
required: false
description: "If the action should skip PRs that have failed checks, defaults to `true`"
default: "true"
runs:
using: 'node12'
main: 'dest/index.js'
155 changes: 57 additions & 98 deletions dest/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -1461,7 +1461,7 @@ exports.Octokit = Octokit;

Object.defineProperty(exports, "__esModule", ({ value: true }));

var isPlainObject = __nccwpck_require__(558);
var isPlainObject = __nccwpck_require__(287);
var universalUserAgent = __nccwpck_require__(429);

function lowercaseKeys(object) {
Expand Down Expand Up @@ -1849,52 +1849,6 @@ exports.endpoint = endpoint;
//# sourceMappingURL=index.js.map


/***/ }),

/***/ 558:
/***/ ((__unused_webpack_module, exports) => {

"use strict";


Object.defineProperty(exports, "__esModule", ({ value: true }));

/*!
* is-plain-object <https://github.com/jonschlinkert/is-plain-object>
*
* Copyright (c) 2014-2017, Jon Schlinkert.
* Released under the MIT License.
*/

function isObject(o) {
return Object.prototype.toString.call(o) === '[object Object]';
}

function isPlainObject(o) {
var ctor,prot;

if (isObject(o) === false) return false;

// If has modified constructor
ctor = o.constructor;
if (ctor === undefined) return true;

// If has modified prototype
prot = ctor.prototype;
if (isObject(prot) === false) return false;

// If constructor does not have an Object-specific method
if (prot.hasOwnProperty('isPrototypeOf') === false) {
return false;
}

// Most likely a plain Object
return true;
}

exports.isPlainObject = isPlainObject;


/***/ }),

/***/ 668:
Expand Down Expand Up @@ -3382,7 +3336,7 @@ function _interopDefault (ex) { return (ex && (typeof ex === 'object') && 'defau

var endpoint = __nccwpck_require__(440);
var universalUserAgent = __nccwpck_require__(429);
var isPlainObject = __nccwpck_require__(62);
var isPlainObject = __nccwpck_require__(287);
var nodeFetch = _interopDefault(__nccwpck_require__(467));
var requestError = __nccwpck_require__(537);

Expand Down Expand Up @@ -3524,52 +3478,6 @@ exports.request = request;
//# sourceMappingURL=index.js.map


/***/ }),

/***/ 62:
/***/ ((__unused_webpack_module, exports) => {

"use strict";


Object.defineProperty(exports, "__esModule", ({ value: true }));

/*!
* is-plain-object <https://github.com/jonschlinkert/is-plain-object>
*
* Copyright (c) 2014-2017, Jon Schlinkert.
* Released under the MIT License.
*/

function isObject(o) {
return Object.prototype.toString.call(o) === '[object Object]';
}

function isPlainObject(o) {
var ctor,prot;

if (isObject(o) === false) return false;

// If has modified constructor
ctor = o.constructor;
if (ctor === undefined) return true;

// If has modified prototype
prot = ctor.prototype;
if (isObject(prot) === false) return false;

// If constructor does not have an Object-specific method
if (prot.hasOwnProperty('isPrototypeOf') === false) {
return false;
}

// Most likely a plain Object
return true;
}

exports.isPlainObject = isPlainObject;


/***/ }),

/***/ 682:
Expand Down Expand Up @@ -3775,6 +3683,52 @@ class Deprecation extends Error {
exports.Deprecation = Deprecation;


/***/ }),

/***/ 287:
/***/ ((__unused_webpack_module, exports) => {

"use strict";


Object.defineProperty(exports, "__esModule", ({ value: true }));

/*!
* is-plain-object <https://github.com/jonschlinkert/is-plain-object>
*
* Copyright (c) 2014-2017, Jon Schlinkert.
* Released under the MIT License.
*/

function isObject(o) {
return Object.prototype.toString.call(o) === '[object Object]';
}

function isPlainObject(o) {
var ctor,prot;

if (isObject(o) === false) return false;

// If has modified constructor
ctor = o.constructor;
if (ctor === undefined) return true;

// If has modified prototype
prot = ctor.prototype;
if (isObject(prot) === false) return false;

// If constructor does not have an Object-specific method
if (prot.hasOwnProperty('isPrototypeOf') === false) {
return false;
}

// Most likely a plain Object
return true;
}

exports.isPlainObject = isPlainObject;


/***/ }),

/***/ 467:
Expand Down Expand Up @@ -5978,6 +5932,8 @@ const getAutoUpdateCandidate = async (openPRs) => {
if (!openPRs) return null;

const requiredApprovalCount = github_core.getInput('required_approval_count');
const requirePassedChecks = /true/i.test(github_core.getInput('require_passed_checks'));

// only update `auto merge` enabled PRs
const autoMergeEnabledPRs = openPRs.filter((item) => item.auto_merge);
log(`Count of auto-merge enabled PRs: ${autoMergeEnabledPRs.length}`);
Expand Down Expand Up @@ -6024,12 +5980,15 @@ const getAutoUpdateCandidate = async (openPRs) => {

/**
* #3 check whether the pr has failed checks
* only happens if require_passed_checks input is true
* need to note: the mergeable, and mergeable_state don't reflect the checks status
*/
const didChecksPass = await areAllChecksPassed(sha);
if (!didChecksPass) {
printFailReason(pullNumber, 'The PR has failed or ongoing check(s)');
continue;
if (requirePassedChecks) {
const didChecksPass = await areAllChecksPassed(sha);
if (!didChecksPass) {
printFailReason(pullNumber, 'The PR has failed or ongoing check(s)');
continue;
}
}

return pr;
Expand Down
13 changes: 9 additions & 4 deletions src/lib/github.js
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,8 @@ export const getAutoUpdateCandidate = async (openPRs) => {
if (!openPRs) return null;

const requiredApprovalCount = core.getInput('required_approval_count');
const requirePassedChecks = /true/i.test(core.getInput('require_passed_checks'));

// only update `auto merge` enabled PRs
const autoMergeEnabledPRs = openPRs.filter((item) => item.auto_merge);
log(`Count of auto-merge enabled PRs: ${autoMergeEnabledPRs.length}`);
Expand Down Expand Up @@ -183,12 +185,15 @@ export const getAutoUpdateCandidate = async (openPRs) => {

/**
* #3 check whether the pr has failed checks
* only happens if require_passed_checks input is true
* need to note: the mergeable, and mergeable_state don't reflect the checks status
*/
const didChecksPass = await areAllChecksPassed(sha);
if (!didChecksPass) {
printFailReason(pullNumber, 'The PR has failed or ongoing check(s)');
continue;
if (requirePassedChecks) {
const didChecksPass = await areAllChecksPassed(sha);
if (!didChecksPass) {
printFailReason(pullNumber, 'The PR has failed or ongoing check(s)');
continue;
}
}

return pr;
Expand Down
46 changes: 46 additions & 0 deletions src/lib/github.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ const fakeEnv = {
token,
base,
required_approval_count: requiredApprovalCount,
require_passed_checks: 'true'
};

const oldEnv = process.env;
Expand Down Expand Up @@ -443,6 +444,51 @@ describe('getAutoUpdateCanidate()', () => {
);
expect(res).toBe(null);
});
test('PR with failed checks will be selected if require_passed_checks is false', async () => {
process.env.require_passed_checks = 'false';

// has 2 approvals, no request for change review
const reviews = {
...reviewsList,
data: [
{ ...reviewsList.data[0], state: 'APPROVED' },
{ ...reviewsList.data[0], state: 'APPROVED' },
],
};
// pr mergeable: true, merge_state: clean
const prData = {
data: {
...prMetaData.data,
...{ mergeable: true, mergeable_state: 'behind' },
},
};

const check = checksList.data.check_runs[0];
const checks = {
...checksList,
data: {
total_count: 2,
check_runs: [
{ ...check, conclusion: 'success' },
{ ...check, conclusion: 'failure' },
],
},
};

// has auto-merge PR
const prList = [{ ...pullsList.data[0], auto_merge: {} }];
const mockedListReviews = jest.fn().mockResolvedValue(reviews);
const mockedGet = jest.fn().mockResolvedValue(prData);
const mockedListForRef = jest.fn().mockResolvedValue(checks);

github.getOctokit.mockReturnValue({
pulls: { listReviews: mockedListReviews, get: mockedGet },
checks: { listForRef: mockedListForRef },
});

const res = await gitLib.getAutoUpdateCandidate(prList);
expect(res).toBe(prList[0]);
});
test('Should return the first PR if it is all good', async () => {
// has 2 approvals, no request for change review
const reviews = {
Expand Down

0 comments on commit 83c61e2

Please sign in to comment.