Skip to content

Commit

Permalink
feat: add retry when trying to merge
Browse files Browse the repository at this point in the history
  • Loading branch information
Ricardo Souza committed Aug 4, 2020
1 parent ac42c5b commit e686145
Show file tree
Hide file tree
Showing 9 changed files with 96 additions and 39 deletions.
1 change: 1 addition & 0 deletions cSpell.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
"language": "en-US",
"version": "0.1",
"words": [
"backoff",
"codeowners",
"commitlint",
"coverallsapp",
Expand Down
59 changes: 49 additions & 10 deletions src/common/merge.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,30 +7,53 @@ import {
import { parseInputMergeMethod } from '../utilities/inputParsers';
import { logDebug, logInfo } from '../utilities/log';

export interface PullRequestDetails {
commitHeadline: string;
pullRequestId: string;
reviewEdge: { node: { state: string } } | undefined;
}

const EXPONENTIAL_BACKOFF = 2;
const WAIT_TIME_SECONDS = 1000;

const delay = async (duration: number): Promise<void> => {
return new Promise((resolve: () => void): void => {
setTimeout((): void => {
resolve();
}, duration);
});
};

/**
* Approves and merges a given Pull Request.
*/
export const merge = async (
octokit: ReturnType<typeof getOctokit>,
{
commitHeadline,
pullRequestId,
reviewEdge,
}: {
commitHeadline: string;
pullRequestId: string;
reviewEdge: { node: { state: string } } | undefined;
},
pullRequestDetails: PullRequestDetails,
): Promise<void> => {
const mergeMethod = parseInputMergeMethod();

const { commitHeadline, pullRequestId, reviewEdge } = pullRequestDetails;

const mutation =
reviewEdge === undefined
? approveAndMergePullRequestMutation(mergeMethod)
: mergePullRequestMutation(mergeMethod);

await octokit.graphql(mutation, { commitHeadline, pullRequestId });
};

export const mergeWithRetry = async (
octokit: ReturnType<typeof getOctokit>,
details: {
numberOfRetries: number;
trial: number;
} & PullRequestDetails,
): Promise<void> => {
const { trial, numberOfRetries } = details;

try {
await octokit.graphql(mutation, { commitHeadline, pullRequestId });
await merge(octokit, details);
} catch (error) {
logInfo(
'An error ocurred while merging the Pull Request. This is usually ' +
Expand All @@ -40,5 +63,21 @@ export const merge = async (
);
/* eslint-disable-next-line @typescript-eslint/no-base-to-string */
logDebug(`Original error: ${(error as Error).toString()}.`);

if (trial <= numberOfRetries) {
const nextRetryIn = trial ** EXPONENTIAL_BACKOFF * WAIT_TIME_SECONDS;

logInfo(`Retrying in ${nextRetryIn.toString()}...`);

await delay(nextRetryIn);

await mergeWithRetry(octokit, {
...details,
numberOfRetries,
trial: trial + 1,
});
} else {
throw error;
}
}
};
16 changes: 8 additions & 8 deletions src/eventHandlers/checkSuite/index.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ describe('check Suite event handler', (): void => {
});
nock('https://api.github.com').post('/graphql').reply(OK);

await checkSuiteHandle(octokit, 'dependabot-preview[bot]');
await checkSuiteHandle(octokit, 'dependabot-preview[bot]', 3);

expect(warningSpy).not.toHaveBeenCalled();
});
Expand Down Expand Up @@ -116,7 +116,7 @@ describe('check Suite event handler', (): void => {
})
.reply(OK);

await checkSuiteHandle(octokit, 'dependabot-preview[bot]');
await checkSuiteHandle(octokit, 'dependabot-preview[bot]', 3);
});

it('does not approve pull requests that are not mergeable', async (): Promise<
Expand Down Expand Up @@ -160,7 +160,7 @@ describe('check Suite event handler', (): void => {
},
});

await checkSuiteHandle(octokit, 'dependabot-preview[bot]');
await checkSuiteHandle(octokit, 'dependabot-preview[bot]', 3);

expect(infoSpy).toHaveBeenCalledWith(
'Pull request is not in a mergeable state: CONFLICTING.',
Expand Down Expand Up @@ -208,7 +208,7 @@ describe('check Suite event handler', (): void => {
},
});

await checkSuiteHandle(octokit, 'dependabot-preview[bot]');
await checkSuiteHandle(octokit, 'dependabot-preview[bot]', 3);

expect(infoSpy).toHaveBeenCalledWith('Pull request is already merged.');
});
Expand Down Expand Up @@ -254,7 +254,7 @@ describe('check Suite event handler', (): void => {
},
});

await checkSuiteHandle(octokit, 'dependabot-preview[bot]');
await checkSuiteHandle(octokit, 'dependabot-preview[bot]', 3);

expect(infoSpy).toHaveBeenCalledWith(
'Pull request cannot be merged cleanly. Current state: UNKNOWN.',
Expand Down Expand Up @@ -302,7 +302,7 @@ describe('check Suite event handler', (): void => {
},
});

await checkSuiteHandle(octokit, 'dependabot-preview[bot]');
await checkSuiteHandle(octokit, 'dependabot-preview[bot]', 3);

expect(infoSpy).toHaveBeenCalledWith('Pull request is not open: CLOSED.');
});
Expand All @@ -312,7 +312,7 @@ describe('check Suite event handler', (): void => {
> => {
expect.assertions(1);

await checkSuiteHandle(octokit, 'some-other-login');
await checkSuiteHandle(octokit, 'some-other-login', 3);

expect(infoSpy).toHaveBeenCalledWith(
'Pull request created by dependabot-preview[bot], not some-other-login, skipping.',
Expand All @@ -334,7 +334,7 @@ describe('check Suite event handler', (): void => {
},
});

await checkSuiteHandle(octokit, 'dependabot-preview[bot]');
await checkSuiteHandle(octokit, 'dependabot-preview[bot]', 3);

expect(warningSpy).toHaveBeenCalled();
});
Expand Down
10 changes: 7 additions & 3 deletions src/eventHandlers/checkSuite/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import { context, getOctokit } from '@actions/github';

import { merge } from '../../common/merge';
import { mergeWithRetry } from '../../common/merge';
import { findPullRequestInfo as findPullRequestInformation } from '../../graphql/queries';
import {
MergeableState,
Expand Down Expand Up @@ -91,6 +91,7 @@ const getPullRequestInformation = async (

const tryMerge = async (
octokit: ReturnType<typeof getOctokit>,
numberOfRetries: number,
{
commitMessageHeadline,
mergeStateStatus,
Expand Down Expand Up @@ -122,17 +123,20 @@ const tryMerge = async (
} else if (pullRequestState !== 'OPEN') {
logInfo(`Pull request is not open: ${pullRequestState}.`);
} else {
await merge(octokit, {
await mergeWithRetry(octokit, {
commitHeadline: commitMessageHeadline,
numberOfRetries,
pullRequestId,
reviewEdge: reviewEdges[0],
trial: 1,
});
}
};

export const checkSuiteHandle = async (
octokit: ReturnType<typeof getOctokit>,
gitHubLogin: string,
numberOfReties: number,
): Promise<void> => {
const pullRequests = context.payload.check_suite.pull_requests as Array<{
number: number;
Expand Down Expand Up @@ -168,7 +172,7 @@ export const checkSuiteHandle = async (
)}.`,
);

await tryMerge(octokit, pullRequestInformation);
await tryMerge(octokit, numberOfReties, pullRequestInformation);
}
}
};
6 changes: 3 additions & 3 deletions src/eventHandlers/pullRequest/index.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ describe('pull request event handler', (): void => {
});
nock('https://api.github.com').post('/graphql').reply(OK);

await pullRequestHandle(octokit, 'dependabot-preview[bot]');
await pullRequestHandle(octokit, 'dependabot-preview[bot]', 3);

expect(warningSpy).not.toHaveBeenCalled();
});
Expand All @@ -77,7 +77,7 @@ describe('pull request event handler', (): void => {
data: null,
});

await pullRequestHandle(octokit, 'dependabot-preview[bot]');
await pullRequestHandle(octokit, 'dependabot-preview[bot]', 3);
});

it('does not approve an already approved pull request', async (): Promise<
Expand Down Expand Up @@ -129,6 +129,6 @@ describe('pull request event handler', (): void => {
})
.reply(OK);

await pullRequestHandle(octokit, 'dependabot-preview[bot]');
await pullRequestHandle(octokit, 'dependabot-preview[bot]', 3);
});
});
7 changes: 5 additions & 2 deletions src/eventHandlers/pullRequest/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { context, getOctokit } from '@actions/github';

import { merge } from '../../common/merge';
import { mergeWithRetry } from '../../common/merge';
import { findPullRequestLastApprovedReview } from '../../graphql/queries';
import { ReviewEdges } from '../../types';
import { logInfo, logWarning } from '../../utilities/log';
Expand Down Expand Up @@ -52,6 +52,7 @@ const getPullRequestInformation = async (
export const pullRequestHandle = async (
octokit: ReturnType<typeof getOctokit>,
gitHubLogin: string,
numberOfRetries: number,
): Promise<void> => {
const { repository, pull_request: pullRequest } = context.payload;

Expand Down Expand Up @@ -86,10 +87,12 @@ export const pullRequestHandle = async (
)}.`,
);

await merge(octokit, {
await mergeWithRetry(octokit, {
commitHeadline: pullRequest.title,
numberOfRetries,
pullRequestId: pullRequest.node_id,
reviewEdge: pullRequestInformation.reviewEdges[0],
trial: 1,
});
}
};
14 changes: 7 additions & 7 deletions src/eventHandlers/push/index.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ describe('push event handler', (): void => {
});
nock('https://api.github.com').post('/graphql').reply(OK);

await pushHandle(octokit, 'dependabot-preview[bot]');
await pushHandle(octokit, 'dependabot-preview[bot]', 3);

expect(warningSpy).not.toHaveBeenCalled();
});
Expand Down Expand Up @@ -106,7 +106,7 @@ describe('push event handler', (): void => {
})
.reply(OK);

await pushHandle(octokit, 'dependabot-preview[bot]');
await pushHandle(octokit, 'dependabot-preview[bot]', 3);
});

it('does not approve pull requests that are not mergeable', async (): Promise<
Expand Down Expand Up @@ -142,7 +142,7 @@ describe('push event handler', (): void => {
},
});

await pushHandle(octokit, 'dependabot-preview[bot]');
await pushHandle(octokit, 'dependabot-preview[bot]', 3);

expect(infoSpy).toHaveBeenCalledWith(
'Pull request is not in a mergeable state: CONFLICTING.',
Expand Down Expand Up @@ -182,7 +182,7 @@ describe('push event handler', (): void => {
},
});

await pushHandle(octokit, 'dependabot-preview[bot]');
await pushHandle(octokit, 'dependabot-preview[bot]', 3);

expect(infoSpy).toHaveBeenCalledWith('Pull request is already merged.');
});
Expand Down Expand Up @@ -220,7 +220,7 @@ describe('push event handler', (): void => {
},
});

await pushHandle(octokit, 'dependabot-preview[bot]');
await pushHandle(octokit, 'dependabot-preview[bot]', 3);

expect(infoSpy).toHaveBeenCalledWith('Pull request is not open: CLOSED.');
});
Expand All @@ -230,7 +230,7 @@ describe('push event handler', (): void => {
> => {
expect.assertions(1);

await pushHandle(octokit, 'some-other-login');
await pushHandle(octokit, 'some-other-login', 3);

expect(infoSpy).toHaveBeenCalledWith(
'Pull request created by dependabot-preview[bot], not some-other-login, skipping.',
Expand All @@ -254,7 +254,7 @@ describe('push event handler', (): void => {
},
});

await pushHandle(octokit, 'dependabot-preview[bot]');
await pushHandle(octokit, 'dependabot-preview[bot]', 3);

expect(warningSpy).toHaveBeenCalled();
});
Expand Down
10 changes: 7 additions & 3 deletions src/eventHandlers/push/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { context, getOctokit } from '@actions/github';

import { merge } from '../../common/merge';
import { mergeWithRetry } from '../../common/merge';
import { findPullRequestInfoAndReviews as findPullRequestInformationAndReviews } from '../../graphql/queries';
import {
CommitMessageHeadlineGroup,
Expand Down Expand Up @@ -78,6 +78,7 @@ const getPullRequestInformation = async (

const tryMerge = async (
octokit: ReturnType<typeof getOctokit>,
numberOfRetries: number,
{
commitMessageHeadline,
mergeableState,
Expand All @@ -94,17 +95,20 @@ const tryMerge = async (
} else if (pullRequestState !== 'OPEN') {
logInfo(`Pull request is not open: ${pullRequestState}.`);
} else {
await merge(octokit, {
await mergeWithRetry(octokit, {
commitHeadline: commitMessageHeadline,
numberOfRetries,
pullRequestId,
reviewEdge: reviewEdges[0],
trial: 1,
});
}
};

export const pushHandle = async (
octokit: ReturnType<typeof getOctokit>,
gitHubLogin: string,
numberOfRetries: number,
): Promise<void> => {
if (context.payload.pusher.name !== gitHubLogin) {
logInfo(
Expand All @@ -131,7 +135,7 @@ export const pushHandle = async (
)}.`,
);

await tryMerge(octokit, {
await tryMerge(octokit, numberOfRetries, {
...pullRequestInformation,
commitMessageHeadline: getCommitMessageHeadline(),
});
Expand Down
Loading

0 comments on commit e686145

Please sign in to comment.