fix(workflow): prevent PR overwrite when branch modified by others
This commit is contained in:
parent
0edc001d28
commit
076b9948be
@ -82,6 +82,11 @@ inputs:
|
||||
maintainer-can-modify:
|
||||
description: 'Indicates whether maintainers can modify the pull request.'
|
||||
default: true
|
||||
skip-if-commits-from-other-authors:
|
||||
description: >
|
||||
Skip updating the pull request branch if it contains commits from authors other than the configured action author/committer.
|
||||
This prevents overwriting changes made by other users to the branch.
|
||||
default: false
|
||||
outputs:
|
||||
pull-request-number:
|
||||
description: 'The pull request number'
|
||||
|
||||
71
dist/index.js
vendored
71
dist/index.js
vendored
@ -168,8 +168,8 @@ function splitLines(multilineString) {
|
||||
.map(s => s.trim())
|
||||
.filter(x => x !== '');
|
||||
}
|
||||
function createOrUpdateBranch(git, commitMessage, base, branch, branchRemoteName, signoff, addPaths) {
|
||||
return __awaiter(this, void 0, void 0, function* () {
|
||||
function createOrUpdateBranch(git_1, commitMessage_1, base_1, branch_1, branchRemoteName_1, signoff_1, addPaths_1) {
|
||||
return __awaiter(this, arguments, void 0, function* (git, commitMessage, base, branch, branchRemoteName, signoff, addPaths, skipIfCommitsFromOtherAuthors = false, authorEmail = '', committerEmail = '') {
|
||||
// Get the working base.
|
||||
// When a ref, it may or may not be the actual base.
|
||||
// When a commit, we must rebase onto the actual base.
|
||||
@ -268,6 +268,52 @@ 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);
|
||||
// Check if the branch has commits from other authors
|
||||
if (skipIfCommitsFromOtherAuthors && authorEmail && committerEmail) {
|
||||
core.info('Checking if branch has commits from other authors...');
|
||||
const branchCommitsAheadCount = yield commitsAhead(git, base, branch);
|
||||
if (branchCommitsAheadCount > 0) {
|
||||
try {
|
||||
const commitAuthors = yield git.getCommitAuthors(`${base}..${branch}`);
|
||||
const hasOtherAuthors = commitAuthors.some(commit => commit.authorEmail !== authorEmail &&
|
||||
commit.committerEmail !== committerEmail);
|
||||
if (hasOtherAuthors) {
|
||||
core.info(`Branch '${branch}' has commits from other authors. Skipping update to prevent overwriting their changes.`);
|
||||
core.info(`Configured author: ${authorEmail}, committer: ${committerEmail}`);
|
||||
const otherAuthors = commitAuthors.filter(commit => commit.authorEmail !== authorEmail ||
|
||||
commit.committerEmail !== committerEmail);
|
||||
core.info(`Found commits from: ${otherAuthors.map(c => `${c.authorEmail} (committer: ${c.committerEmail})`).join(', ')}`);
|
||||
action = 'not-updated';
|
||||
hasDiffWithBase = yield isAhead(git, base, branch);
|
||||
const baseSha = yield git.revParse(base);
|
||||
const baseCommit = yield git.getCommit(baseSha);
|
||||
const headSha = yield git.revParse(branch);
|
||||
let branchCommits = [];
|
||||
if (hasDiffWithBase) {
|
||||
branchCommits = yield buildBranchCommits(git, base, branch);
|
||||
}
|
||||
yield git.exec(['branch', '--delete', '--force', tempBranch]);
|
||||
yield git.checkout(workingBase);
|
||||
if (stashed) {
|
||||
yield git.stashPop();
|
||||
}
|
||||
return {
|
||||
action: action,
|
||||
base: base,
|
||||
hasDiffWithBase: hasDiffWithBase,
|
||||
baseCommit: baseCommit,
|
||||
headSha: headSha,
|
||||
branchCommits: branchCommits
|
||||
};
|
||||
}
|
||||
core.info('No commits from other authors found. Proceeding with update.');
|
||||
}
|
||||
catch (error) {
|
||||
core.warning(`Failed to check commit authors: ${utils.getErrorMessage(error)}`);
|
||||
core.info('Proceeding with update despite check failure.');
|
||||
}
|
||||
}
|
||||
}
|
||||
// Reset the branch if one of the following conditions is true.
|
||||
// - If the branch differs from the recreated temp branch.
|
||||
// - If the number of commits ahead of the base branch differs between the branch and
|
||||
@ -505,7 +551,7 @@ function createPullRequest(inputs) {
|
||||
outputs.set('pull-request-operation', 'none');
|
||||
// Create or update the pull request branch
|
||||
core.startGroup('Create or update the pull request branch');
|
||||
const result = yield (0, create_or_update_branch_1.createOrUpdateBranch)(git, inputs.commitMessage, inputs.base, inputs.branch, branchRemoteName, inputs.signoff, inputs.addPaths);
|
||||
const result = yield (0, create_or_update_branch_1.createOrUpdateBranch)(git, inputs.commitMessage, inputs.base, inputs.branch, branchRemoteName, inputs.signoff, inputs.addPaths, inputs.skipIfCommitsFromOtherAuthors, parsedAuthor.email, parsedCommitter.email);
|
||||
outputs.set('pull-request-head-sha', result.headSha);
|
||||
// Set the base. It would have been '' if not specified as an input
|
||||
inputs.base = result.base;
|
||||
@ -815,6 +861,22 @@ class GitCommandManager {
|
||||
};
|
||||
});
|
||||
}
|
||||
getCommitAuthors(commitRange) {
|
||||
return __awaiter(this, void 0, void 0, function* () {
|
||||
const output = yield this.exec(['log', '--format=%ae%n%ce%n###COMMIT###', commitRange], { suppressGitCmdOutput: true });
|
||||
const commits = [];
|
||||
const lines = output.stdout.split('\n').filter(x => x !== '');
|
||||
for (let i = 0; i < lines.length; i += 3) {
|
||||
if (lines[i + 2] === '###COMMIT###') {
|
||||
commits.push({
|
||||
authorEmail: lines[i],
|
||||
committerEmail: lines[i + 1]
|
||||
});
|
||||
}
|
||||
}
|
||||
return commits;
|
||||
});
|
||||
}
|
||||
getConfigValue(configKey_1) {
|
||||
return __awaiter(this, arguments, void 0, function* (configKey, configValue = '.') {
|
||||
const output = yield this.exec([
|
||||
@ -1654,7 +1716,8 @@ function run() {
|
||||
teamReviewers: utils.getInputAsArray('team-reviewers'),
|
||||
milestone: Number(core.getInput('milestone')),
|
||||
draft: getDraftInput(),
|
||||
maintainerCanModify: core.getBooleanInput('maintainer-can-modify')
|
||||
maintainerCanModify: core.getBooleanInput('maintainer-can-modify'),
|
||||
skipIfCommitsFromOtherAuthors: core.getBooleanInput('skip-if-commits-from-other-authors')
|
||||
};
|
||||
core.debug(`Inputs: ${(0, util_1.inspect)(inputs)}`);
|
||||
if (!inputs.token) {
|
||||
|
||||
@ -173,7 +173,10 @@ export async function createOrUpdateBranch(
|
||||
branch: string,
|
||||
branchRemoteName: string,
|
||||
signoff: boolean,
|
||||
addPaths: string[]
|
||||
addPaths: string[],
|
||||
skipIfCommitsFromOtherAuthors = false,
|
||||
authorEmail = '',
|
||||
committerEmail = ''
|
||||
): Promise<CreateOrUpdateBranchResult> {
|
||||
// Get the working base.
|
||||
// When a ref, it may or may not be the actual base.
|
||||
@ -294,6 +297,68 @@ export async function createOrUpdateBranch(
|
||||
// Checkout the pull request branch
|
||||
await git.checkout(branch)
|
||||
|
||||
// Check if the branch has commits from other authors
|
||||
if (skipIfCommitsFromOtherAuthors && authorEmail && committerEmail) {
|
||||
core.info('Checking if branch has commits from other authors...')
|
||||
const branchCommitsAheadCount = await commitsAhead(git, base, branch)
|
||||
if (branchCommitsAheadCount > 0) {
|
||||
try {
|
||||
const commitAuthors = await git.getCommitAuthors(`${base}..${branch}`)
|
||||
const hasOtherAuthors = commitAuthors.some(
|
||||
commit =>
|
||||
commit.authorEmail !== authorEmail &&
|
||||
commit.committerEmail !== committerEmail
|
||||
)
|
||||
if (hasOtherAuthors) {
|
||||
core.info(
|
||||
`Branch '${branch}' has commits from other authors. Skipping update to prevent overwriting their changes.`
|
||||
)
|
||||
core.info(
|
||||
`Configured author: ${authorEmail}, committer: ${committerEmail}`
|
||||
)
|
||||
const otherAuthors = commitAuthors.filter(
|
||||
commit =>
|
||||
commit.authorEmail !== authorEmail ||
|
||||
commit.committerEmail !== committerEmail
|
||||
)
|
||||
core.info(
|
||||
`Found commits from: ${otherAuthors.map(c => `${c.authorEmail} (committer: ${c.committerEmail})`).join(', ')}`
|
||||
)
|
||||
action = 'not-updated'
|
||||
hasDiffWithBase = await isAhead(git, base, branch)
|
||||
const baseSha = await git.revParse(base)
|
||||
const baseCommit = await git.getCommit(baseSha)
|
||||
const headSha = await git.revParse(branch)
|
||||
let branchCommits: Commit[] = []
|
||||
if (hasDiffWithBase) {
|
||||
branchCommits = await buildBranchCommits(git, base, branch)
|
||||
}
|
||||
await git.exec(['branch', '--delete', '--force', tempBranch])
|
||||
await git.checkout(workingBase)
|
||||
if (stashed) {
|
||||
await git.stashPop()
|
||||
}
|
||||
return {
|
||||
action: action,
|
||||
base: base,
|
||||
hasDiffWithBase: hasDiffWithBase,
|
||||
baseCommit: baseCommit,
|
||||
headSha: headSha,
|
||||
branchCommits: branchCommits
|
||||
}
|
||||
}
|
||||
core.info(
|
||||
'No commits from other authors found. Proceeding with update.'
|
||||
)
|
||||
} catch (error) {
|
||||
core.warning(
|
||||
`Failed to check commit authors: ${utils.getErrorMessage(error)}`
|
||||
)
|
||||
core.info('Proceeding with update despite check failure.')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Reset the branch if one of the following conditions is true.
|
||||
// - If the branch differs from the recreated temp branch.
|
||||
// - If the number of commits ahead of the base branch differs between the branch and
|
||||
|
||||
@ -37,6 +37,7 @@ export interface Inputs {
|
||||
always: boolean
|
||||
}
|
||||
maintainerCanModify: boolean
|
||||
skipIfCommitsFromOtherAuthors: boolean
|
||||
}
|
||||
|
||||
export async function createPullRequest(inputs: Inputs): Promise<void> {
|
||||
@ -194,7 +195,10 @@ export async function createPullRequest(inputs: Inputs): Promise<void> {
|
||||
inputs.branch,
|
||||
branchRemoteName,
|
||||
inputs.signoff,
|
||||
inputs.addPaths
|
||||
inputs.addPaths,
|
||||
inputs.skipIfCommitsFromOtherAuthors,
|
||||
parsedAuthor.email,
|
||||
parsedCommitter.email
|
||||
)
|
||||
outputs.set('pull-request-head-sha', result.headSha)
|
||||
// Set the base. It would have been '' if not specified as an input
|
||||
|
||||
@ -208,6 +208,26 @@ export class GitCommandManager {
|
||||
}
|
||||
}
|
||||
|
||||
async getCommitAuthors(
|
||||
commitRange: string
|
||||
): Promise<{authorEmail: string; committerEmail: string}[]> {
|
||||
const output = await this.exec(
|
||||
['log', '--format=%ae%n%ce%n###COMMIT###', commitRange],
|
||||
{suppressGitCmdOutput: true}
|
||||
)
|
||||
const commits: {authorEmail: string; committerEmail: string}[] = []
|
||||
const lines = output.stdout.split('\n').filter(x => x !== '')
|
||||
for (let i = 0; i < lines.length; i += 3) {
|
||||
if (lines[i + 2] === '###COMMIT###') {
|
||||
commits.push({
|
||||
authorEmail: lines[i],
|
||||
committerEmail: lines[i + 1]
|
||||
})
|
||||
}
|
||||
}
|
||||
return commits
|
||||
}
|
||||
|
||||
async getConfigValue(configKey: string, configValue = '.'): Promise<string> {
|
||||
const output = await this.exec([
|
||||
'config',
|
||||
|
||||
@ -37,7 +37,10 @@ async function run(): Promise<void> {
|
||||
teamReviewers: utils.getInputAsArray('team-reviewers'),
|
||||
milestone: Number(core.getInput('milestone')),
|
||||
draft: getDraftInput(),
|
||||
maintainerCanModify: core.getBooleanInput('maintainer-can-modify')
|
||||
maintainerCanModify: core.getBooleanInput('maintainer-can-modify'),
|
||||
skipIfCommitsFromOtherAuthors: core.getBooleanInput(
|
||||
'skip-if-commits-from-other-authors'
|
||||
)
|
||||
}
|
||||
core.debug(`Inputs: ${inspect(inputs)}`)
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user