diff --git a/__test__/create-or-update-branch.int.test.ts b/__test__/create-or-update-branch.int.test.ts index 397cfbc..523bbe9 100644 --- a/__test__/create-or-update-branch.int.test.ts +++ b/__test__/create-or-update-branch.int.test.ts @@ -543,6 +543,74 @@ describe('create-or-update-branch tests', () => { ).toBeTruthy() }) + it('tests create, squash merge, and update with identical changes', async () => { + // Branches that have been squash merged appear to have a diff with the base due to + // different commits for the same changes. To prevent creating pull requests + // unnecessarily we reset (rebase) the pull request branch when a reset would result + // in no diff with the base. This will reset any undeleted branches after merging. + + // Create tracked and untracked file changes + const changes = await createChanges() + const commitMessage = uuidv4() + const result = await createOrUpdateBranch( + git, + commitMessage, + '', + BRANCH, + REMOTE_NAME, + false + ) + expect(result.action).toEqual('created') + expect(await getFileContent(TRACKED_FILE)).toEqual(changes.tracked) + expect(await getFileContent(UNTRACKED_FILE)).toEqual(changes.untracked) + expect( + await gitLogMatches([commitMessage, INIT_COMMIT_MESSAGE]) + ).toBeTruthy() + + // Push pull request branch to remote + await git.push([ + '--force-with-lease', + REMOTE_NAME, + `HEAD:refs/heads/${BRANCH}` + ]) + + await afterTest(false) + await beforeTest() + + // Create a commit on the base with the same changes as the branch + // This simulates squash merge of the pull request + const commits = await createCommits( + git, + 1, + changes.tracked, + changes.untracked + ) + await git.push([ + '--force', + REMOTE_NAME, + `HEAD:refs/heads/${DEFAULT_BRANCH}` + ]) + + // Create the same tracked and untracked file changes (no change on update) + const _changes = await createChanges(changes.tracked, changes.untracked) + const _commitMessage = uuidv4() + const _result = await createOrUpdateBranch( + git, + _commitMessage, + '', + BRANCH, + REMOTE_NAME, + false + ) + expect(_result.action).toEqual('updated') + expect(_result.hasDiffWithBase).toBeFalsy() + expect(await getFileContent(TRACKED_FILE)).toEqual(_changes.tracked) + expect(await getFileContent(UNTRACKED_FILE)).toEqual(_changes.untracked) + expect( + await gitLogMatches([...commits.commitMsgs, INIT_COMMIT_MESSAGE]) + ).toBeTruthy() + }) + it('tests create and update with commits on the working base (during the workflow)', async () => { // Create commits on the working base const commits = await createCommits(git) @@ -1213,6 +1281,80 @@ describe('create-or-update-branch tests', () => { ).toBeTruthy() }) + it('tests create, squash merge, and update with identical changes (WBNB)', async () => { + // Branches that have been squash merged appear to have a diff with the base due to + // different commits for the same changes. To prevent creating pull requests + // unnecessarily we reset (rebase) the pull request branch when a reset would result + // in no diff with the base. This will reset any undeleted branches after merging. + + // Set the working base to a branch that is not the pull request base + await git.checkout(NOT_BASE_BRANCH) + + // Create tracked and untracked file changes + const changes = await createChanges() + const commitMessage = uuidv4() + const result = await createOrUpdateBranch( + git, + commitMessage, + BASE, + BRANCH, + REMOTE_NAME, + false + ) + expect(result.action).toEqual('created') + expect(await getFileContent(TRACKED_FILE)).toEqual(changes.tracked) + expect(await getFileContent(UNTRACKED_FILE)).toEqual(changes.untracked) + expect( + await gitLogMatches([commitMessage, INIT_COMMIT_MESSAGE]) + ).toBeTruthy() + + // Push pull request branch to remote + await git.push([ + '--force-with-lease', + REMOTE_NAME, + `HEAD:refs/heads/${BRANCH}` + ]) + + await afterTest(false) + await beforeTest() + + // Create a commit on the base with the same changes as the branch + // This simulates squash merge of the pull request + const commits = await createCommits( + git, + 1, + changes.tracked, + changes.untracked + ) + await git.push([ + '--force', + REMOTE_NAME, + `HEAD:refs/heads/${DEFAULT_BRANCH}` + ]) + + // Set the working base to a branch that is not the pull request base + await git.checkout(NOT_BASE_BRANCH) + + // Create the same tracked and untracked file changes (no change on update) + const _changes = await createChanges(changes.tracked, changes.untracked) + const _commitMessage = uuidv4() + const _result = await createOrUpdateBranch( + git, + _commitMessage, + BASE, + BRANCH, + REMOTE_NAME, + false + ) + expect(_result.action).toEqual('updated') + expect(_result.hasDiffWithBase).toBeFalsy() + expect(await getFileContent(TRACKED_FILE)).toEqual(_changes.tracked) + expect(await getFileContent(UNTRACKED_FILE)).toEqual(_changes.untracked) + expect( + await gitLogMatches([...commits.commitMsgs, INIT_COMMIT_MESSAGE]) + ).toBeTruthy() + }) + it('tests create and update with commits on the working base (during the workflow) (WBNB)', async () => { // Set the working base to a branch that is not the pull request base await git.checkout(NOT_BASE_BRANCH) diff --git a/dist/index.js b/dist/index.js index 1b848f2..a9aeed8 100644 --- a/dist/index.js +++ b/dist/index.js @@ -3080,9 +3080,16 @@ function createOrUpdateBranch(git, commitMessage, base, branch, branchRemoteName core.info(`Pull request branch '${branch}' already exists as remote branch '${branchRemoteName}/${branch}'`); // Checkout the pull request branch yield git.checkout(branch); - if (yield hasDiff(git, branch, tempBranch)) { - // If the branch differs from the recreated temp version then the branch is reset - // For changes on base this action is similar to a rebase of the pull request branch + // Reset the branch if one of the following conditions is true. + // - If the branch differs from the recreated temp branch. + // - If the recreated temp branch is not ahead of the base. This means there will be + // no pull request diff after the branch is reset. This will reset any undeleted + // branches after merging. In particular, it catches a case where the branch was + // squash merged but not deleted. We need to reset to make sure it doesn't appear + // to have a diff with the base due to different commits for the same changes. + // For changes on base this reset is equivalent to a rebase of the pull request branch. + if ((yield hasDiff(git, branch, tempBranch)) || + !(yield isAhead(git, base, tempBranch))) { core.info(`Resetting '${branch}'`); // Alternatively, git switch -C branch tempBranch yield git.checkout(branch, tempBranch); diff --git a/src/create-or-update-branch.ts b/src/create-or-update-branch.ts index bd67a6a..0b074fc 100644 --- a/src/create-or-update-branch.ts +++ b/src/create-or-update-branch.ts @@ -196,9 +196,18 @@ export async function createOrUpdateBranch( // Checkout the pull request branch await git.checkout(branch) - if (await hasDiff(git, branch, tempBranch)) { - // If the branch differs from the recreated temp version then the branch is reset - // For changes on base this action is similar to a rebase of the pull request branch + // Reset the branch if one of the following conditions is true. + // - If the branch differs from the recreated temp branch. + // - If the recreated temp branch is not ahead of the base. This means there will be + // no pull request diff after the branch is reset. This will reset any undeleted + // branches after merging. In particular, it catches a case where the branch was + // squash merged but not deleted. We need to reset to make sure it doesn't appear + // to have a diff with the base due to different commits for the same changes. + // For changes on base this reset is equivalent to a rebase of the pull request branch. + if ( + (await hasDiff(git, branch, tempBranch)) || + !(await isAhead(git, base, tempBranch)) + ) { core.info(`Resetting '${branch}'`) // Alternatively, git switch -C branch tempBranch await git.checkout(branch, tempBranch)