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:
|
maintainer-can-modify:
|
||||||
description: 'Indicates whether maintainers can modify the pull request.'
|
description: 'Indicates whether maintainers can modify the pull request.'
|
||||||
default: true
|
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:
|
outputs:
|
||||||
pull-request-number:
|
pull-request-number:
|
||||||
description: 'The 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())
|
.map(s => s.trim())
|
||||||
.filter(x => x !== '');
|
.filter(x => x !== '');
|
||||||
}
|
}
|
||||||
function createOrUpdateBranch(git, commitMessage, base, branch, branchRemoteName, signoff, addPaths) {
|
function createOrUpdateBranch(git_1, commitMessage_1, base_1, branch_1, branchRemoteName_1, signoff_1, addPaths_1) {
|
||||||
return __awaiter(this, void 0, void 0, function* () {
|
return __awaiter(this, arguments, void 0, function* (git, commitMessage, base, branch, branchRemoteName, signoff, addPaths, skipIfCommitsFromOtherAuthors = false, authorEmail = '', committerEmail = '') {
|
||||||
// Get the working base.
|
// Get the working base.
|
||||||
// When a ref, it may or may not be the actual base.
|
// When a ref, it may or may not be the actual base.
|
||||||
// When a commit, we must rebase onto 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}'`);
|
core.info(`Pull request branch '${branch}' already exists as remote branch '${branchRemoteName}/${branch}'`);
|
||||||
// Checkout the pull request branch
|
// Checkout the pull request branch
|
||||||
yield git.checkout(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.
|
// Reset the branch if one of the following conditions is true.
|
||||||
// - If the branch differs from the recreated temp branch.
|
// - If the branch differs from the recreated temp branch.
|
||||||
// - If the number of commits ahead of the base branch differs between the branch and
|
// - 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');
|
outputs.set('pull-request-operation', 'none');
|
||||||
// Create or update the pull request branch
|
// Create or update the pull request branch
|
||||||
core.startGroup('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);
|
outputs.set('pull-request-head-sha', result.headSha);
|
||||||
// Set the base. It would have been '' if not specified as an input
|
// Set the base. It would have been '' if not specified as an input
|
||||||
inputs.base = result.base;
|
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) {
|
getConfigValue(configKey_1) {
|
||||||
return __awaiter(this, arguments, void 0, function* (configKey, configValue = '.') {
|
return __awaiter(this, arguments, void 0, function* (configKey, configValue = '.') {
|
||||||
const output = yield this.exec([
|
const output = yield this.exec([
|
||||||
@ -1654,7 +1716,8 @@ function run() {
|
|||||||
teamReviewers: utils.getInputAsArray('team-reviewers'),
|
teamReviewers: utils.getInputAsArray('team-reviewers'),
|
||||||
milestone: Number(core.getInput('milestone')),
|
milestone: Number(core.getInput('milestone')),
|
||||||
draft: getDraftInput(),
|
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)}`);
|
core.debug(`Inputs: ${(0, util_1.inspect)(inputs)}`);
|
||||||
if (!inputs.token) {
|
if (!inputs.token) {
|
||||||
|
|||||||
@ -173,7 +173,10 @@ export async function createOrUpdateBranch(
|
|||||||
branch: string,
|
branch: string,
|
||||||
branchRemoteName: string,
|
branchRemoteName: string,
|
||||||
signoff: boolean,
|
signoff: boolean,
|
||||||
addPaths: string[]
|
addPaths: string[],
|
||||||
|
skipIfCommitsFromOtherAuthors = false,
|
||||||
|
authorEmail = '',
|
||||||
|
committerEmail = ''
|
||||||
): Promise<CreateOrUpdateBranchResult> {
|
): Promise<CreateOrUpdateBranchResult> {
|
||||||
// Get the working base.
|
// Get the working base.
|
||||||
// When a ref, it may or may not be the actual 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
|
// Checkout the pull request branch
|
||||||
await git.checkout(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.
|
// Reset the branch if one of the following conditions is true.
|
||||||
// - If the branch differs from the recreated temp branch.
|
// - If the branch differs from the recreated temp branch.
|
||||||
// - If the number of commits ahead of the base branch differs between the branch and
|
// - If the number of commits ahead of the base branch differs between the branch and
|
||||||
|
|||||||
@ -37,6 +37,7 @@ export interface Inputs {
|
|||||||
always: boolean
|
always: boolean
|
||||||
}
|
}
|
||||||
maintainerCanModify: boolean
|
maintainerCanModify: boolean
|
||||||
|
skipIfCommitsFromOtherAuthors: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function createPullRequest(inputs: Inputs): Promise<void> {
|
export async function createPullRequest(inputs: Inputs): Promise<void> {
|
||||||
@ -194,7 +195,10 @@ export async function createPullRequest(inputs: Inputs): Promise<void> {
|
|||||||
inputs.branch,
|
inputs.branch,
|
||||||
branchRemoteName,
|
branchRemoteName,
|
||||||
inputs.signoff,
|
inputs.signoff,
|
||||||
inputs.addPaths
|
inputs.addPaths,
|
||||||
|
inputs.skipIfCommitsFromOtherAuthors,
|
||||||
|
parsedAuthor.email,
|
||||||
|
parsedCommitter.email
|
||||||
)
|
)
|
||||||
outputs.set('pull-request-head-sha', result.headSha)
|
outputs.set('pull-request-head-sha', result.headSha)
|
||||||
// Set the base. It would have been '' if not specified as an input
|
// 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> {
|
async getConfigValue(configKey: string, configValue = '.'): Promise<string> {
|
||||||
const output = await this.exec([
|
const output = await this.exec([
|
||||||
'config',
|
'config',
|
||||||
|
|||||||
@ -37,7 +37,10 @@ async function run(): Promise<void> {
|
|||||||
teamReviewers: utils.getInputAsArray('team-reviewers'),
|
teamReviewers: utils.getInputAsArray('team-reviewers'),
|
||||||
milestone: Number(core.getInput('milestone')),
|
milestone: Number(core.getInput('milestone')),
|
||||||
draft: getDraftInput(),
|
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)}`)
|
core.debug(`Inputs: ${inspect(inputs)}`)
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user