fix: handle update after force pushing base to a new commit (#1307)
This commit is contained in:
parent
ee93d78b55
commit
d7db273d6c
|
@ -631,6 +631,69 @@ describe('create-or-update-branch tests', () => {
|
||||||
).toBeTruthy()
|
).toBeTruthy()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('tests create, force push of base branch, and update with identical changes', async () => {
|
||||||
|
// If the base branch is force pushed to a different commit when there is an open
|
||||||
|
// pull request, the branch must be reset to rebase the changes on the base.
|
||||||
|
|
||||||
|
// Create tracked and untracked file changes
|
||||||
|
const changes = await createChanges()
|
||||||
|
const commitMessage = uuidv4()
|
||||||
|
const result = await createOrUpdateBranch(
|
||||||
|
git,
|
||||||
|
commitMessage,
|
||||||
|
'',
|
||||||
|
BRANCH,
|
||||||
|
REMOTE_NAME,
|
||||||
|
false,
|
||||||
|
ADD_PATHS_DEFAULT
|
||||||
|
)
|
||||||
|
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()
|
||||||
|
|
||||||
|
// Force push the base branch to a different commit
|
||||||
|
const amendedCommitMessage = uuidv4()
|
||||||
|
await git.commit(['--amend', '-m', amendedCommitMessage])
|
||||||
|
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,
|
||||||
|
ADD_PATHS_DEFAULT
|
||||||
|
)
|
||||||
|
expect(_result.action).toEqual('updated')
|
||||||
|
expect(_result.hasDiffWithBase).toBeTruthy()
|
||||||
|
expect(await getFileContent(TRACKED_FILE)).toEqual(_changes.tracked)
|
||||||
|
expect(await getFileContent(UNTRACKED_FILE)).toEqual(_changes.untracked)
|
||||||
|
expect(
|
||||||
|
await gitLogMatches([_commitMessage, amendedCommitMessage])
|
||||||
|
).toBeTruthy()
|
||||||
|
})
|
||||||
|
|
||||||
it('tests create and update with commits on the working base (during the workflow)', async () => {
|
it('tests create and update with commits on the working base (during the workflow)', async () => {
|
||||||
// Create commits on the working base
|
// Create commits on the working base
|
||||||
const commits = await createCommits(git)
|
const commits = await createCommits(git)
|
||||||
|
@ -1519,6 +1582,75 @@ describe('create-or-update-branch tests', () => {
|
||||||
).toBeTruthy()
|
).toBeTruthy()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('tests create, force push of base branch, and update with identical changes (WBNB)', async () => {
|
||||||
|
// If the base branch is force pushed to a different commit when there is an open
|
||||||
|
// pull request, the branch must be reset to rebase the changes on the base.
|
||||||
|
|
||||||
|
// 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,
|
||||||
|
ADD_PATHS_DEFAULT
|
||||||
|
)
|
||||||
|
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()
|
||||||
|
|
||||||
|
// Force push the base branch to a different commit
|
||||||
|
const amendedCommitMessage = uuidv4()
|
||||||
|
await git.commit(['--amend', '-m', amendedCommitMessage])
|
||||||
|
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,
|
||||||
|
ADD_PATHS_DEFAULT
|
||||||
|
)
|
||||||
|
expect(_result.action).toEqual('updated')
|
||||||
|
expect(_result.hasDiffWithBase).toBeTruthy()
|
||||||
|
expect(await getFileContent(TRACKED_FILE)).toEqual(_changes.tracked)
|
||||||
|
expect(await getFileContent(UNTRACKED_FILE)).toEqual(_changes.untracked)
|
||||||
|
expect(
|
||||||
|
await gitLogMatches([_commitMessage, amendedCommitMessage])
|
||||||
|
).toBeTruthy()
|
||||||
|
})
|
||||||
|
|
||||||
it('tests create and update with commits on the working base (during the workflow) (WBNB)', async () => {
|
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
|
// Set the working base to a branch that is not the pull request base
|
||||||
await git.checkout(NOT_BASE_BRANCH)
|
await git.checkout(NOT_BASE_BRANCH)
|
||||||
|
|
29
dist/index.js
vendored
29
dist/index.js
vendored
|
@ -74,18 +74,30 @@ function tryFetch(git, remote, branch) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
exports.tryFetch = tryFetch;
|
exports.tryFetch = tryFetch;
|
||||||
|
// Return the number of commits that branch2 is ahead of branch1
|
||||||
|
function commitsAhead(git, branch1, branch2) {
|
||||||
|
return __awaiter(this, void 0, void 0, function* () {
|
||||||
|
const result = yield git.revList([`${branch1}...${branch2}`], ['--right-only', '--count']);
|
||||||
|
return Number(result);
|
||||||
|
});
|
||||||
|
}
|
||||||
// Return true if branch2 is ahead of branch1
|
// Return true if branch2 is ahead of branch1
|
||||||
function isAhead(git, branch1, branch2) {
|
function isAhead(git, branch1, branch2) {
|
||||||
return __awaiter(this, void 0, void 0, function* () {
|
return __awaiter(this, void 0, void 0, function* () {
|
||||||
const result = yield git.revList([`${branch1}...${branch2}`], ['--right-only', '--count']);
|
return (yield commitsAhead(git, branch1, branch2)) > 0;
|
||||||
return Number(result) > 0;
|
});
|
||||||
|
}
|
||||||
|
// Return the number of commits that branch2 is behind branch1
|
||||||
|
function commitsBehind(git, branch1, branch2) {
|
||||||
|
return __awaiter(this, void 0, void 0, function* () {
|
||||||
|
const result = yield git.revList([`${branch1}...${branch2}`], ['--left-only', '--count']);
|
||||||
|
return Number(result);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
// Return true if branch2 is behind branch1
|
// Return true if branch2 is behind branch1
|
||||||
function isBehind(git, branch1, branch2) {
|
function isBehind(git, branch1, branch2) {
|
||||||
return __awaiter(this, void 0, void 0, function* () {
|
return __awaiter(this, void 0, void 0, function* () {
|
||||||
const result = yield git.revList([`${branch1}...${branch2}`], ['--left-only', '--count']);
|
return (yield commitsBehind(git, branch1, branch2)) > 0;
|
||||||
return Number(result) > 0;
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
// Return true if branch2 is even with branch1
|
// Return true if branch2 is even with branch1
|
||||||
|
@ -214,9 +226,16 @@ function createOrUpdateBranch(git, commitMessage, base, branch, branchRemoteName
|
||||||
// branches after merging. In particular, it catches a case where the branch was
|
// 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
|
// 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.
|
// to have a diff with the base due to different commits for the same changes.
|
||||||
|
// - If the number of commits ahead of the base branch differs between the branch and
|
||||||
|
// temp branch. This catches a case where the base branch has been force pushed to
|
||||||
|
// a new commit.
|
||||||
// For changes on base this reset is equivalent to a rebase of the pull request branch.
|
// For changes on base this reset is equivalent to a rebase of the pull request branch.
|
||||||
|
const tempBranchCommitsAhead = yield commitsAhead(git, base, tempBranch);
|
||||||
|
const branchCommitsAhead = yield commitsAhead(git, base, branch);
|
||||||
if ((yield git.hasDiff([`${branch}..${tempBranch}`])) ||
|
if ((yield git.hasDiff([`${branch}..${tempBranch}`])) ||
|
||||||
!(yield isAhead(git, base, tempBranch))) {
|
branchCommitsAhead != tempBranchCommitsAhead ||
|
||||||
|
!(tempBranchCommitsAhead > 0) // !isAhead
|
||||||
|
) {
|
||||||
core.info(`Resetting '${branch}'`);
|
core.info(`Resetting '${branch}'`);
|
||||||
// Alternatively, git switch -C branch tempBranch
|
// Alternatively, git switch -C branch tempBranch
|
||||||
yield git.checkout(branch, tempBranch);
|
yield git.checkout(branch, tempBranch);
|
||||||
|
|
|
@ -43,17 +43,39 @@ export async function tryFetch(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Return the number of commits that branch2 is ahead of branch1
|
||||||
|
async function commitsAhead(
|
||||||
|
git: GitCommandManager,
|
||||||
|
branch1: string,
|
||||||
|
branch2: string
|
||||||
|
): Promise<number> {
|
||||||
|
const result = await git.revList(
|
||||||
|
[`${branch1}...${branch2}`],
|
||||||
|
['--right-only', '--count']
|
||||||
|
)
|
||||||
|
return Number(result)
|
||||||
|
}
|
||||||
|
|
||||||
// Return true if branch2 is ahead of branch1
|
// Return true if branch2 is ahead of branch1
|
||||||
async function isAhead(
|
async function isAhead(
|
||||||
git: GitCommandManager,
|
git: GitCommandManager,
|
||||||
branch1: string,
|
branch1: string,
|
||||||
branch2: string
|
branch2: string
|
||||||
): Promise<boolean> {
|
): Promise<boolean> {
|
||||||
|
return (await commitsAhead(git, branch1, branch2)) > 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return the number of commits that branch2 is behind branch1
|
||||||
|
async function commitsBehind(
|
||||||
|
git: GitCommandManager,
|
||||||
|
branch1: string,
|
||||||
|
branch2: string
|
||||||
|
): Promise<number> {
|
||||||
const result = await git.revList(
|
const result = await git.revList(
|
||||||
[`${branch1}...${branch2}`],
|
[`${branch1}...${branch2}`],
|
||||||
['--right-only', '--count']
|
['--left-only', '--count']
|
||||||
)
|
)
|
||||||
return Number(result) > 0
|
return Number(result)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Return true if branch2 is behind branch1
|
// Return true if branch2 is behind branch1
|
||||||
|
@ -62,11 +84,7 @@ async function isBehind(
|
||||||
branch1: string,
|
branch1: string,
|
||||||
branch2: string
|
branch2: string
|
||||||
): Promise<boolean> {
|
): Promise<boolean> {
|
||||||
const result = await git.revList(
|
return (await commitsBehind(git, branch1, branch2)) > 0
|
||||||
[`${branch1}...${branch2}`],
|
|
||||||
['--left-only', '--count']
|
|
||||||
)
|
|
||||||
return Number(result) > 0
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Return true if branch2 is even with branch1
|
// Return true if branch2 is even with branch1
|
||||||
|
@ -226,10 +244,16 @@ export async function createOrUpdateBranch(
|
||||||
// branches after merging. In particular, it catches a case where the branch was
|
// 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
|
// 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.
|
// to have a diff with the base due to different commits for the same changes.
|
||||||
|
// - If the number of commits ahead of the base branch differs between the branch and
|
||||||
|
// temp branch. This catches a case where the base branch has been force pushed to
|
||||||
|
// a new commit.
|
||||||
// For changes on base this reset is equivalent to a rebase of the pull request branch.
|
// For changes on base this reset is equivalent to a rebase of the pull request branch.
|
||||||
|
const tempBranchCommitsAhead = await commitsAhead(git, base, tempBranch)
|
||||||
|
const branchCommitsAhead = await commitsAhead(git, base, branch)
|
||||||
if (
|
if (
|
||||||
(await git.hasDiff([`${branch}..${tempBranch}`])) ||
|
(await git.hasDiff([`${branch}..${tempBranch}`])) ||
|
||||||
!(await isAhead(git, base, tempBranch))
|
branchCommitsAhead != tempBranchCommitsAhead ||
|
||||||
|
!(tempBranchCommitsAhead > 0) // !isAhead
|
||||||
) {
|
) {
|
||||||
core.info(`Resetting '${branch}'`)
|
core.info(`Resetting '${branch}'`)
|
||||||
// Alternatively, git switch -C branch tempBranch
|
// Alternatively, git switch -C branch tempBranch
|
||||||
|
|
Loading…
Reference in a new issue