Skip to content
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

Add tests #26

Merged
merged 12 commits into from
Jul 24, 2020
1 change: 1 addition & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
.git/
.github/
node_modules/
coverage/
Dockerfile
18 changes: 18 additions & 0 deletions .eslintrc.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
env:
es2020: true
node: true
jest/globals: true
extends:
- airbnb-base
- prettier
plugins:
- jest
parserOptions:
ecmaVersion: 2020
sourceType: module
rules:
no-console: error
no-plusplus: off
no-await-in-loop: off
no-constant-condition: off
no-restricted-syntax: off
File renamed without changes.
21 changes: 21 additions & 0 deletions .github/workflows/tests.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
name: Tests
on: push
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Set up Node.js 12.x
uses: actions/setup-node@v1
with:
node-version: 12.x
- name: Install dependencies
run: yarn install --dev
- name: Lint files
run: yarn run lint
- name: Run tests
run: yarn run test
env:
CI: true
- name: Upload coverage
uses: codecov/codecov-action@v1
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
# Node
node_modules/

# Test code
# Testing
/events
bin/local.js
/coverage
2 changes: 2 additions & 0 deletions .prettierignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# Test code
bin/local.js
6 changes: 6 additions & 0 deletions .prettierrc.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
printWidth: 80
useTabs: false
tabWidth: 2
singleQuote: true
trailingComma: all
arrowParens: always
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
# autoupdate
![Tests](https://github.com/chinthakagodawita/autoupdate/workflows/Tests/badge.svg?event=push) [![codecov](https://codecov.io/gh/chinthakagodawita/autoupdate/branch/master/graph/badge.svg)](https://codecov.io/gh/chinthakagodawita/autoupdate)

**autoupdate** is a GitHub Action that auto-updates pull requests branches whenever changes land on their destination branch.

## Usage
Expand Down
1 change: 0 additions & 1 deletion TRIGGERFILE

This file was deleted.

25 changes: 6 additions & 19 deletions bin/cli.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,12 @@ const fs = require('fs');

const ghCore = require('@actions/core');

const AutoUpdater = require('../src/autoupdater');
const Router = require('../src/router');
const config = require('../src/config-loader');

async function main() {
const eventPath = process.env['GITHUB_EVENT_PATH'];
const eventName = process.env['GITHUB_EVENT_NAME'];
const eventPath = process.env.GITHUB_EVENT_PATH;
const eventName = process.env.GITHUB_EVENT_NAME;

const rawEventData = fs.readFileSync(eventPath, 'utf8');
const eventData = JSON.parse(rawEventData);
Expand All @@ -18,25 +18,12 @@ async function main() {

if (config.dryRun()) {
ghCore.info(
`Detected DRY_RUN=true, running in dry mode - no merges will be made.`
'Detected DRY_RUN=true, running in dry mode - no merges will be made.',
);
}

const updater = new AutoUpdater(
config,
eventData,
true,
);

if (eventName === 'pull_request') {
await updater.handlePullRequest();
} else if (eventName === 'push') {
await updater.handlePush();
} else {
throw new Error(
`Unknown event type '${eventName}', only 'push' and 'pull_request' are supported.`
);
}
const router = new Router(config, eventData);
await router.route(eventName);
}

if (require.main === module) {
Expand Down
24 changes: 22 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,21 +1,41 @@
{
"name": "autoupdate-action",
"version": "0.0.1",
"version": "1.0.0",
"description": "A GitHub Action that auto-updates PRs with changes from their base branch.",
"main": "src/autoupdater.js",
"repository": "https://github.com/chinthakagodawita/autoupdate-action",
"repository": "https://github.com/chinthakagodawita/autoupdate",
"author": "Chin Godawita <chin.godawita@me.com>",
"license": "MIT",
"bin": {
"autoupdate-action": "bin/cli.js"
},
"scripts": {
"lint": "eslint . && prettier --list-different bin/** src/**",
"test": "jest",
"test:watch": "jest --watchAll",
"build": "ncc build bin/cli.js --out dist"
},
"dependencies": {
"@actions/core": "^1.2.0",
"@actions/github": "^2.0.0",
"@zeit/ncc": "^0.21.0",
"argparse": "^1.0.10"
},
"devDependencies": {
"eslint": "^7.4.0",
"eslint-config-airbnb-base": "^14.2.0",
"eslint-config-prettier": "^6.11.0",
"eslint-plugin-import": "^2.22.0",
"eslint-plugin-jest": "^23.18.0",
"jest": "^26.1.0",
"nock": "^13.0.2",
"prettier": "2.0.5"
},
"jest": {
"clearMocks": true,
"collectCoverage": true,
"coverageDirectory": "coverage",
"coverageProvider": "v8",
"testEnvironment": "node"
}
}
90 changes: 47 additions & 43 deletions src/autoupdater.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,30 +15,24 @@ class AutoUpdater {

if (!ref.startsWith('refs/heads/')) {
ghCore.warning('Push event was not on a branch, skipping.');
return;
return 0;
}

const baseBranch = ref.replace('refs/heads/', '');

const pulls = await this.octokit.pulls.list({
let updated = 0;
const paginatorOpts = this.octokit.pulls.list.endpoint.merge({
owner: repository.owner.name,
repo: repository.name,
base: baseBranch,
state: 'open',
sort: 'updated',
direction: 'desc',
});

if (pulls.data.length === 0) {
ghCore.info(
`Base branch '${baseBranch}' has no pull requests that point to it, skipping autoupdate.`
);
return;
}

let updated = 0;
for await (const pullsPage of this.octokit.paginate.iterator(pulls)) {
for (const pull of pulls.data) {
for await (const pullsPage of this.octokit.paginate.iterator(
paginatorOpts,
)) {
for (const pull of pullsPage.data) {
ghCore.startGroup(`PR-${pull.number}`);
const isUpdated = await this.update(pull);
ghCore.endGroup();
Expand All @@ -50,8 +44,10 @@ class AutoUpdater {
}

ghCore.info(
`Auto update complete, ${updated} pull request(s) that point to base branch '${baseBranch}' were updated.`
`Auto update complete, ${updated} pull request(s) that point to base branch '${baseBranch}' were updated.`,
);

return updated;
}

async handlePullRequest() {
Expand All @@ -62,11 +58,13 @@ class AutoUpdater {
const isUpdated = await this.update(this.eventData.pull_request);
if (isUpdated) {
ghCore.info(
`Auto update complete, pull request branch was updated with changes from the base branch.`
'Auto update complete, pull request branch was updated with changes from the base branch.',
);
} else {
ghCore.info(`Auto update complete, no changes were made.`);
ghCore.info('Auto update complete, no changes were made.');
}

return isUpdated;
}

async update(pull) {
Expand All @@ -81,12 +79,12 @@ class AutoUpdater {
const baseRef = pull.base.ref;
const headRef = pull.head.ref;
ghCore.info(
`Updating branch '${ref}' on pull request #${pull.number} with changes from ref '${baseRef}'.`
`Updating branch '${ref}' on pull request #${pull.number} with changes from ref '${baseRef}'.`,
);

if (this.config.dryRun()) {
ghCore.warning(
`Would have merged ref '${headRef}' into ref '${baseRef}' but DRY_RUN was enabled.`
`Would have merged ref '${headRef}' into ref '${baseRef}' but DRY_RUN was enabled.`,
);
return true;
}
Expand All @@ -111,12 +109,12 @@ class AutoUpdater {

async prNeedsUpdate(pull) {
if (pull.merged === true) {
ghCore.warning(`Skipping pull request, already merged.`);
ghCore.warning('Skipping pull request, already merged.');
return false;
}
if (pull.state !== 'open') {
ghCore.warning(
`Skipping pull request, no longer open (current state: ${pull.state}).`
`Skipping pull request, no longer open (current state: ${pull.state}).`,
);
return false;
}
Expand All @@ -132,67 +130,67 @@ class AutoUpdater {
});

if (comparison.behind_by === 0) {
ghCore.info(`Skipping pull request, up-to-date with base branch.`);
ghCore.info('Skipping pull request, up-to-date with base branch.');
return false;
}

const prFilter = this.config.pullRequestFilter();

ghCore.info(
`PR_FILTER=${prFilter}, checking if this PR's branch needs to be updated.`
`PR_FILTER=${prFilter}, checking if this PR's branch needs to be updated.`,
);

if (prFilter === 'labelled') {
const labels = this.config.pullRequestLabels();
if (labels.length === 0) {
ghCore.warning(
`Skipping pull request, no labels were defined (env var PR_LABELS is empty or not defined).`
'Skipping pull request, no labels were defined (env var PR_LABELS is empty or not defined).',
);
return false;
}
ghCore.info(
`Checking if this PR has a label in our list (${labels.join(', ')}).`
`Checking if this PR has a label in our list (${labels.join(', ')}).`,
);

if (pull.labels.length === 0) {
ghCore.info(`Skipping pull request, it has no labels.`);
ghCore.info('Skipping pull request, it has no labels.');
return false;
}

for (const label of pull.labels) {
if (labels.includes(label.name)) {
ghCore.info(
`Pull request has label '${label.name}' and PR branch is behind base branch.`
`Pull request has label '${label.name}' and PR branch is behind base branch.`,
);
return true;
}
}

ghCore.info(
`Pull request does not match any of the defined labels, skipping update.`
'Pull request does not match any of the defined labels, skipping update.',
);
return false;
}

if (this.config.pullRequestFilter() === 'protected') {
ghCore.info('Checking if this PR is against a protected branch.');
const { data: branch } = this.octokit.repos.getBranch({
const { data: branch } = await this.octokit.repos.getBranch({
owner: pull.head.repo.owner.login,
repo: pull.head.repo.name,
branch: pull.base.ref,
});

if (branch.protected) {
ghCore.info(
`Pull request is against a protected branch and is behind base branch.`
'Pull request is against a protected branch and is behind base branch.',
);
return true;
} else {
ghCore.info(
`Pull request is not against a protected branch, skipping update.`
);
return false;
}

ghCore.info(
'Pull request is not against a protected branch, skipping update.',
);
return false;
}

ghCore.info('All checks pass and PR branch is behind base branch.');
Expand All @@ -210,13 +208,15 @@ class AutoUpdater {
const mergeResp = await this.octokit.repos.merge(mergeOpts);

// See https://developer.github.com/v3/repos/merging/#perform-a-merge
const status = mergeResp.status;
const { status } = mergeResp;
if (status === 200) {
ghCore.info(
`Branch update succesful, new branch HEAD: ${mergeResp.data.sha}.`
`Branch update succesful, new branch HEAD: ${mergeResp.data.sha}.`,
);
} else if (status === 204) {
ghCore.info(`Branch update not required, branch is already up-to-date.`);
ghCore.info(
'Branch update not required, branch is already up-to-date.',
);
}

return true;
Expand All @@ -225,7 +225,7 @@ class AutoUpdater {
const retryCount = this.config.retryCount();
const retrySleep = this.config.retrySleep();
const mergeConflictAction = this.config.mergeConflictAction();

let retries = 0;

while (true) {
Expand All @@ -234,19 +234,23 @@ class AutoUpdater {
await doMerge();
break;
} catch (e) {
if (e.message === "Merge conflict" && mergeConflictAction === "ignore") {
if (
e.message === 'Merge conflict' &&
mergeConflictAction === 'ignore'
) {
ghCore.info('Merge conflict detected, skipping update.');
return;
} else if (e.message === "Merge conflict") {
ghCore.error("Merge conflict error trying to update branch");
break;
}
if (e.message === 'Merge conflict') {
ghCore.error('Merge conflict error trying to update branch');
throw e;
}

ghCore.error(`Caught error trying to update branch: ${e.message}`);

if (retries < retryCount) {
ghCore.info(
`Branch update failed, will retry in ${retrySleep}ms, retry #${retries} of ${retryCount}.`
`Branch update failed, will retry in ${retrySleep}ms, retry #${retries} of ${retryCount}.`,
);

retries++;
Expand Down
Loading