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

New feature: add MERGE_COMMENT option #339

Open
wants to merge 12 commits into
base: master
Choose a base branch
from
17 changes: 17 additions & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.1.0
hooks:
- id: check-json
- id: check-merge-conflict

- repo: local
hooks:
- id: lint
name: lint
stages: [commit]
language: system
types: [javascript, jsx, ts, tsx]
entry: yarn run lint:fix
pass_filenames: false
always_run: true
30 changes: 18 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,23 +9,27 @@
Create a file, in your repository, at `.github/workflows/autoupdate.yaml` with the following:

```yaml
name: autoupdate
name: Autoupdate Open PRs

on:
# This will trigger on all pushes to all branches.
push: {}
# Alternatively, you can only trigger if commits are pushed to certain branches, e.g.:
# push:
# branches:
# - master
# - unstable
push:
branches:
- main

jobs:
autoupdate:
autoupdate-open-prs:
name: autoupdate
runs-on: ubuntu-22.04
runs-on: ubuntu-latest
steps:
- uses: docker://chinthakagodawita/autoupdate-action:v1
- uses: BondOrigination/autoupdate@master
env:
GITHUB_TOKEN: '${{ secrets.GITHUB_TOKEN }}'
GITHUB_TOKEN: "${{ secrets.GH_TOKEN_BOT_REPO_SCOPE }}"
PR_FILTER: "protected"
PR_READY_STATE: "ready_for_review"
MERGE_MSG: "Branch was auto-updated."
MERGE_COMMENT: "@bots-bot my-command"
MERGE_CONFLICT_ACTION: "ignore"

```

This will trigger on all pushes and automatically update any pull requests, if changes are pushed to their destination branch.
Expand Down Expand Up @@ -73,6 +77,8 @@ All configuration values, except `GITHUB_TOKEN`, are optional.

- `MERGE_MSG`: A custom message to use when creating the merge commit from the destination branch to your pull request's branch.

- `MERGE_COMMENT`: A custom comment to make on the pull request after a successful merge.

- `RETRY_COUNT`: The number of times a branch update should be attempted before _autoupdate_ gives up (default: `"5"`).

- `RETRY_SLEEP`: The amount of time (in milliseconds) that _autoupdate_ should wait between branch update attempts (default: `"300"`).
Expand Down
67 changes: 63 additions & 4 deletions src/autoupdater.ts
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,7 @@ export class AutoUpdater {
let pull: PullRequestResponse['data'];
for (pull of pullsPage.data) {
ghCore.startGroup(`PR-${pull.number}`);
ghCore.debug(`Pull Request object: ${JSON.stringify(pull)}`);
const isUpdated = await this.update(owner, pull);
ghCore.endGroup();

Expand Down Expand Up @@ -230,8 +231,9 @@ export class AutoUpdater {
mergeOpts.commit_message = mergeMsg;
}

let mergeResult;
try {
return await this.merge(sourceEventOwner, pull.number, mergeOpts);
mergeResult = await this.merge(sourceEventOwner, pull.number, mergeOpts);
} catch (e: unknown) {
if (e instanceof Error) {
ghCore.error(
Expand All @@ -241,6 +243,22 @@ export class AutoUpdater {
}
return false;
}

const mergeComment = this.config.mergeComment();
if (
mergeComment !== null &&
mergeComment !== undefined &&
mergeComment.length > 0
) {
await this.octokit.rest.issues.createComment({
owner: pull.head.repo.owner.login,
issue_number: pull.number,
repo: pull.head.repo.name,
body: mergeComment,
});
}

return mergeResult;
}

async prNeedsUpdate(pull: PullRequest): Promise<boolean> {
Expand Down Expand Up @@ -272,6 +290,8 @@ export class AutoUpdater {
basehead: `${pull.head.label}...${pull.base.label}`,
});

ghCore.debug(`Comparison object: ${JSON.stringify(comparison)}`);

if (comparison.behind_by === 0) {
ghCore.info('Skipping pull request, up-to-date with base branch.');
return false;
Expand All @@ -285,6 +305,20 @@ export class AutoUpdater {
return false;
}

ghCore.info(`Checking if the PR branch '${pull.head.ref}' is protected.`);
const { data: head_branch } = await this.octokit.rest.repos.getBranch({
owner: pull.head.repo.owner.login,
repo: pull.head.repo.name,
branch: pull.head.ref,
});

ghCore.debug(`Head branch object: ${JSON.stringify(head_branch)}`);

if (head_branch.protected) {
ghCore.info('Skipping pull request, pull request branch is protected.');
return false;
}

// First check if this PR has an excluded label on it and skip further
// processing if so.
const excludedLabels = this.config.excludedLabels();
Expand Down Expand Up @@ -368,14 +402,18 @@ export class AutoUpdater {
}

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

if (branch.protected) {
ghCore.debug(`Base branch object: ${JSON.stringify(base_branch)}`);

if (base_branch.protected) {
ghCore.info(
'Pull request is against a protected branch and is behind base branch.',
);
Expand Down Expand Up @@ -477,6 +515,18 @@ export class AutoUpdater {
return false;
}

if (isRequestError(e) && e.status === 404) {
const error = e as Error;

ghCore.error(
`Unable to merge pull request #${prNumber}. Could be because the PR doesn't exist, or the token lacks write permissions for the repo. Error was: ${error.message}`,
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We encountered this "Not found" issue when configuring the action and using a token that only had read access for the repo. It doesn't need to be part of this PR of course if you prefer keeping it separate or not including it

);

setOutputFn(Output.Conflicted, false);

return false;
}

if (e.message === 'Merge conflict') {
setOutputFn(Output.Conflicted, true);

Expand All @@ -491,6 +541,15 @@ export class AutoUpdater {
}
}

if (e.message.startsWith(`protected branch`)) {
ghCore.info(
`Unable to merge pull request #${prNumber} due to the branch being protected, skipping update. This is likely due to the pull request being added to the merge queue. Error was: ${e.message}`,
);

setOutputFn(Output.Conflicted, false);
return false;
}

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

Expand Down
5 changes: 5 additions & 0 deletions src/config-loader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,11 @@ export class ConfigLoader {
return msg === '' ? null : msg;
}

mergeComment(): string {
const comment = this.getValue('MERGE_COMMENT', false, '').toString().trim();
return comment === '' ? null : comment;
}

conflictMsg(): string {
const msg = this.getValue('CONFLICT_MSG', false, '').toString().trim();
return msg === '' ? null : msg;
Expand Down
32 changes: 32 additions & 0 deletions test/autoupdate.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,13 @@ jest.mock('../src/config-loader');
beforeEach(() => {
jest.resetAllMocks();
jest.spyOn(config, 'githubToken').mockImplementation(() => 'test-token');

nock('https://api.github.com:443')
.get(`/repos/${owner}/${repo}/branches/${head}`)
.reply(200, {
protected: false,
})
.persist();
});

const emptyEvent = {} as WebhookEvent;
Expand Down Expand Up @@ -445,6 +452,31 @@ describe('test `prNeedsUpdate`', () => {
expect(config.excludedLabels).toHaveBeenCalled();
});

test('pull request branch is protected', async () => {
nock.cleanAll();

const comparePr = nock('https://api.github.com:443')
.get(`/repos/${owner}/${repo}/compare/${head}...${base}`)
.reply(200, {
behind_by: 1,
});

const getBranch = nock('https://api.github.com:443')
.get(`/repos/${owner}/${repo}/branches/${head}`)
.reply(200, {
protected: true,
});

const updater = new AutoUpdater(config, emptyEvent);
const needsUpdate = await updater.prNeedsUpdate(
validPull as unknown as PullRequestResponse['data'],
);

expect(needsUpdate).toEqual(false);
expect(comparePr.isDone()).toEqual(true);
expect(getBranch.isDone()).toEqual(true);
});

test('pull request is against branch with auto_merge enabled', async () => {
(config.pullRequestFilter as jest.Mock).mockReturnValue('auto_merge');
(config.excludedLabels as jest.Mock).mockReturnValue([]);
Expand Down
7 changes: 7 additions & 0 deletions test/config-loader.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,13 @@ const tests = [
default: null,
type: 'string',
},
{
name: 'mergeComment',
envVar: 'MERGE_COMMENT',
required: false,
default: null,
type: 'string',
},
{
name: 'conflictMsg',
envVar: 'CONFLICT_MSG',
Expand Down