From 0b0574a24871cc96563b23eaa8a0fedcb8c0e9fe Mon Sep 17 00:00:00 2001 From: Technote Date: Wed, 25 Nov 2020 03:14:57 +0900 Subject: [PATCH 1/2] feat: consider failing merge --- __tests__/utils/command.test.ts | 28 +------- __tests__/utils/process2.test.ts | 108 ++++++++++++++++++++++++++++++- src/utils/command.ts | 7 +- src/utils/misc.ts | 2 +- src/utils/process.ts | 11 +++- 5 files changed, 124 insertions(+), 32 deletions(-) diff --git a/__tests__/utils/command.test.ts b/__tests__/utils/command.test.ts index 63d7c63..9ea7b0f 100644 --- a/__tests__/utils/command.test.ts +++ b/__tests__/utils/command.test.ts @@ -345,23 +345,8 @@ describe('getChangedFiles', () => { commitEmail: 'example@example.com', }))).toEqual({ files: [], - output: [ - { - command: 'sudo npm install -g npm-check-updates', - stdout: [], - stderr: [], - }, - { - command: 'npm install --save test1 test2', - stdout: [], - stderr: [], - }, - { - command: 'npm update', - stdout: [], - stderr: [], - }, - ], + output: [], + aborted: true, }); stdoutCalledWith(mockStdout, [ '::group::Fetching...', @@ -392,15 +377,6 @@ describe('getChangedFiles', () => { '::endgroup::', '::group::Aborting merge...', '[command]git merge --abort', - '::endgroup::', - '::group::Running commands...', - '[command]sudo npm install -g npm-check-updates', - '[command]npm install --save test1 test2', - '[command]npm update', - '::endgroup::', - '::group::Checking diff...', - '[command]git add --all', - '[command]git status --short -uno', ]); }); diff --git a/__tests__/utils/process2.test.ts b/__tests__/utils/process2.test.ts index 717adc5..d632b4c 100644 --- a/__tests__/utils/process2.test.ts +++ b/__tests__/utils/process2.test.ts @@ -1289,7 +1289,7 @@ describe('execute', () => { ]); }); - it('should resolve conflicts', async() => { + it('should resolve conflicts 1', async() => { process.env.GITHUB_WORKSPACE = workDir; process.env.GITHUB_REPOSITORY = 'octocat/Hello-World'; process.env.INPUT_GITHUB_TOKEN = 'test-token'; @@ -1377,6 +1377,112 @@ describe('execute', () => { ]); }); + it('should resolve conflicts 2', async() => { + process.env.GITHUB_WORKSPACE = workDir; + process.env.INPUT_GITHUB_TOKEN = 'test-token'; + const mockStdout = spyOnStdout(); + setChildProcessParams({ + stdout: (command: string): string => { + if (command.includes(' rev-parse')) { + return 'change/new-topic1'; + } + if (command.startsWith('git merge --no-edit')) { + return 'Auto-merging merge.txt\nCONFLICT (content): Merge conflict in merge.txt\nAutomatic merge failed; fix conflicts and then commit the result.'; + } + if (command.includes('--name-only')) { + return 'package.json'; + } + return ''; + }, + }); + setExists(true); + + nock('https://api.github.com') + .persist() + .get('/repos/octocat/Hello-World/pulls?sort=created&direction=asc') + .reply(200, () => getApiFixture(rootDir, 'pulls.list')) + .get('/repos/octocat/Hello-World/pulls?head=' + encodeURIComponent('octocat:change/new-topic1')) + .reply(200, () => getApiFixture(rootDir, 'pulls.list.state.open')) + .get('/repos/octocat/Hello-World/pulls?head=' + encodeURIComponent('octocat:change/new-topic2')) + .reply(200, () => []) + .get('/repos/octocat/Hello-World/pulls/1347') + .reply(200, () => getApiFixture(rootDir, 'pulls.get.mergeable.false')) + .patch('/repos/octocat/Hello-World/pulls/1347') + .reply(200, () => getApiFixture(rootDir, 'pulls.update')) + .delete('/repos/octocat/Hello-World/git/refs/' + encodeURIComponent('heads/change/new-topic1')) + .reply(204); + + await expect(execute(octokit, getActionContext(context('', 'schedule'), { + prBranchPrefix: 'change/', + prBranchName: 'test-${PR_ID}', + checkDefaultBranch: false, + }))).rejects.toThrow('There is a failed process.'); + + stdoutCalledWith(mockStdout, [ + '::group::Target PullRequest Ref [change/new-topic1]', + '> Fetching...', + '[command]git remote add origin', + '[command]git fetch --no-tags origin \'refs/heads/change/new-topic1:refs/remotes/origin/change/new-topic1\'', + '[command]git reset --hard', + '> Switching branch to [change/new-topic1]...', + '[command]git checkout -b change/new-topic1 origin/change/new-topic1', + '[command]git checkout change/new-topic1', + '[command]git rev-parse --abbrev-ref HEAD', + ' >> change/new-topic1', + '[command]git merge --no-edit origin/change/new-topic1', + ' >> Auto-merging merge.txt', + ' >> CONFLICT (content): Merge conflict in merge.txt', + ' >> Automatic merge failed; fix conflicts and then commit the result.', + '[command]ls -la', + '> Merging [origin/master] branch...', + '[command]git remote add origin', + '[command]git fetch --no-tags origin \'refs/heads/master:refs/remotes/origin/master\'', + '[command]git config \'user.name\' test-actor', + '[command]git config \'user.email\' \'test-actor@users.noreply.github.com\'', + '[command]git merge --no-edit origin/master', + ' >> Auto-merging merge.txt', + ' >> CONFLICT (content): Merge conflict in merge.txt', + ' >> Automatic merge failed; fix conflicts and then commit the result.', + '> Aborting merge...', + '[command]git merge --abort', + '> There is no diff.', + '> Checking references diff...', + '[command]git fetch --prune --no-tags --no-recurse-submodules origin +refs/heads/master:refs/remotes/origin/master', + '[command]git diff \'HEAD..origin/master\' --name-only', + '> This PR is not mergeable.', + '> Merging [origin/master] branch...', + '[command]git remote add origin', + '[command]git fetch --no-tags origin \'refs/heads/master:refs/remotes/origin/master\'', + '[command]git config \'user.name\' test-actor', + '[command]git config \'user.email\' \'test-actor@users.noreply.github.com\'', + '[command]git merge --no-edit origin/master', + ' >> Auto-merging merge.txt', + ' >> CONFLICT (content): Merge conflict in merge.txt', + ' >> Automatic merge failed; fix conflicts and then commit the result.', + '> Initializing working directory...', + '[command]rm -rdf [Working Directory]', + '[command]git remote add origin', + '[command]git fetch --no-tags origin \'refs/heads/master:refs/remotes/origin/master\'', + '[command]git checkout -b master origin/master', + '[command]git checkout master', + '[command]git checkout -b change/new-topic1', + '> Running commands...', + '> Checking diff...', + '[command]git add --all', + '[command]git status --short -uno', + '> Closing PullRequest... [change/new-topic1]', + '> Deleting reference... [refs/heads/change/new-topic1]', + '::endgroup::', + '::group::Target PullRequest Ref [change/new-topic2]', + '::endgroup::', + '::group::Total:2 Succeeded:1 Failed:1 Skipped:0', + '> \x1b[32;40m✔\x1b[0m\t[change/new-topic1] has been closed because there is no diff', + '> \x1b[31;40m×\x1b[0m\t[change/new-topic2] not found', + '::set-output name=result::failed', + '::endgroup::', + ]); + }); + it('should throw error if push branch not found', async() => { process.env.GITHUB_WORKSPACE = workDir; process.env.INPUT_GITHUB_TOKEN = 'test-token'; diff --git a/src/utils/command.ts b/src/utils/command.ts index a545e6a..86505db 100644 --- a/src/utils/command.ts +++ b/src/utils/command.ts @@ -325,11 +325,13 @@ const runCommands = async(helper: GitHelper, logger: Logger, context: ActionCont export const getChangedFiles = async(helper: GitHelper, logger: Logger, octokit: Octokit, context: ActionContext): Promise<{ files: string[]; output: CommandOutput[]; + aborted?: boolean; }> => { await clone(helper, logger, octokit, context); if (await checkBranch(helper, logger, octokit, context)) { if (!await merge(getContextBranch(context), helper, logger, context)) { await abortMerge(helper, logger); + return {files: [], output: [], aborted: true}; } } @@ -352,7 +354,7 @@ export const getChangedFilesForRebase = async(helper: GitHelper, logger: Logger, export const closePR = async(branchName: string, logger: Logger, context: ActionContext, message?: string): Promise => getApiHelper(getOctokit(getApiToken()), context, logger).closePR(branchName, message ?? context.actionDetail.prCloseMessage); -export const resolveConflicts = async(branchName: string, helper: GitHelper, logger: Logger, octokit: Octokit, context: ActionContext): Promise => { +export const resolveConflicts = async(branchName: string, helper: GitHelper, logger: Logger, octokit: Octokit, context: ActionContext): Promise => { if (await merge(getContextBranch(context), helper, logger, context)) { // succeeded to merge await push(branchName, helper, logger, context); @@ -361,7 +363,7 @@ export const resolveConflicts = async(branchName: string, helper: GitHelper, log const {files, output} = await getChangedFilesForRebase(helper, logger, octokit, context); if (!files.length) { await closePR(branchName, logger, context); - return; + return 'has been closed because there is no diff'; } await commit(helper, logger, context); await forcePush(branchName, helper, logger, context); @@ -370,6 +372,7 @@ export const resolveConflicts = async(branchName: string, helper: GitHelper, log body: await getPrBody(false, files, output, helper, octokit, context), }); } + return 'updated'; }; export const getDefaultBranch = async(octokit: Octokit, context: ActionContext): Promise => getCache(getCacheKey('repos', { diff --git a/src/utils/misc.ts b/src/utils/misc.ts index 2a4df25..4bec6d9 100644 --- a/src/utils/misc.ts +++ b/src/utils/misc.ts @@ -214,7 +214,7 @@ export const isActiveTriggerWorkflow = (context: ActionContext): boolean => isSe export const getTriggerWorkflowMessage = (context: ActionContext): string => context.actionDetail.triggerWorkflowMessage ?? DEFAULT_TRIGGER_WORKFLOW_MESSAGE; // eslint-disable-next-line no-magic-numbers -export const getAutoMergeThresholdDays = (context: ActionContext): number => context.actionDetail.autoMergeThresholdDays && /^\d+$/.test(context.actionDetail.autoMergeThresholdDays) ? Number(context.actionDetail.autoMergeThresholdDays) : 0; +export const getAutoMergeThresholdDays = (context: ActionContext): number => context.actionDetail.autoMergeThresholdDays && /^\d+$/.test(context.actionDetail.autoMergeThresholdDays) ? Number(context.actionDetail.autoMergeThresholdDays) : -1; type ChecksListSuitesForRefResponseItem = { id: number; diff --git a/src/utils/process.ts b/src/utils/process.ts index 8e27ec2..b9d050f 100644 --- a/src/utils/process.ts +++ b/src/utils/process.ts @@ -126,7 +126,7 @@ const createCommit = async(addComment: boolean, isClose: boolean, logger: Logger const helper = getHelper(context); const branchName = await getPrBranchName(helper, octokit, context); - const {files, output} = await getChangedFiles(helper, logger, octokit, context); + const {files, output, aborted} = await getChangedFiles(helper, logger, octokit, context); if (!files.length) { logger.info('There is no diff.'); if (context.isBatchProcess) { @@ -140,6 +140,13 @@ const createCommit = async(addComment: boolean, isClose: boolean, logger: Logger if (pr && await autoMerge(pr, logger, octokit, context)) { return getResult('succeeded', 'has been auto merged', context); } + + if (pr && aborted && !await isMergeable(pr.number, octokit, context)) { + // not mergeable + logger.info('This PR is not mergeable.'); + // Resolve conflicts if PR is not mergeable + return getResult('succeeded', await resolveConflicts(branchName, helper, logger, octokit, context), context); + } } return getResult('not changed', 'There is no diff', context); @@ -278,7 +285,7 @@ const createPr = async(makeGroup: boolean, isClose: boolean, helper: GitHelper, if (!mergeable) { // Resolve conflicts if PR is not mergeable - await resolveConflicts(branchName, helper, logger, octokit, context); + detail = await resolveConflicts(branchName, helper, logger, octokit, context); } return getResult(result, detail, context); From f98226cf27235093c8a539515c95edc2cde09915 Mon Sep 17 00:00:00 2001 From: Technote Date: Wed, 25 Nov 2020 03:17:30 +0900 Subject: [PATCH 2/2] feat: update package version --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 5f63be6..4ccb14e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@technote-space/github-action-pr-helper", - "version": "2.0.4", + "version": "2.1.0", "description": "PullRequest Helper for GitHub Actions.", "keywords": [ "github",