feat: Add `delete-branch` option to delete PR branches after closing (#190)
* feat: Add `delete-branch` option to delete PR branches after closing * Fix branch ref
This commit is contained in:
parent
87c2b794b9
commit
107018c400
|
@ -51,7 +51,8 @@ const DefaultProcessorOptions: IssueProcessorOptions = Object.freeze({
|
||||||
removeStaleWhenUpdated: false,
|
removeStaleWhenUpdated: false,
|
||||||
ascending: false,
|
ascending: false,
|
||||||
skipStaleIssueMessage: false,
|
skipStaleIssueMessage: false,
|
||||||
skipStalePrMessage: false
|
skipStalePrMessage: false,
|
||||||
|
deleteBranch: false
|
||||||
});
|
});
|
||||||
|
|
||||||
test('empty issue list results in 1 operation', async () => {
|
test('empty issue list results in 1 operation', async () => {
|
||||||
|
@ -89,6 +90,7 @@ test('processing an issue with no label will make it stale and close it, if it i
|
||||||
|
|
||||||
expect(processor.staleIssues.length).toEqual(1);
|
expect(processor.staleIssues.length).toEqual(1);
|
||||||
expect(processor.closedIssues.length).toEqual(1);
|
expect(processor.closedIssues.length).toEqual(1);
|
||||||
|
expect(processor.deletedBranchIssues.length).toEqual(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('processing an issue with no label will make it stale and not close it if days-before-close is set to > 0', async () => {
|
test('processing an issue with no label will make it stale and not close it if days-before-close is set to > 0', async () => {
|
||||||
|
@ -895,3 +897,59 @@ test('not providing stalePrMessage takes precedence over skipStalePrMessage', as
|
||||||
expect(processor.removedLabelIssues.length).toEqual(0);
|
expect(processor.removedLabelIssues.length).toEqual(0);
|
||||||
expect(processor.staleIssues.length).toEqual(0);
|
expect(processor.staleIssues.length).toEqual(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('git branch is deleted when option is enabled', async () => {
|
||||||
|
const opts = {...DefaultProcessorOptions, deleteBranch: true};
|
||||||
|
const isPullRequest = true;
|
||||||
|
const TestIssueList: Issue[] = [
|
||||||
|
generateIssue(
|
||||||
|
1,
|
||||||
|
'An issue that should have its branch deleted',
|
||||||
|
'2020-01-01T17:00:00Z',
|
||||||
|
isPullRequest,
|
||||||
|
['Stale']
|
||||||
|
)
|
||||||
|
];
|
||||||
|
|
||||||
|
const processor = new IssueProcessor(
|
||||||
|
opts,
|
||||||
|
async p => (p == 1 ? TestIssueList : []),
|
||||||
|
async (num, dt) => [],
|
||||||
|
async (issue, label) => new Date().toDateString()
|
||||||
|
);
|
||||||
|
|
||||||
|
await processor.processIssues(1);
|
||||||
|
|
||||||
|
expect(processor.closedIssues.length).toEqual(1);
|
||||||
|
expect(processor.removedLabelIssues.length).toEqual(0);
|
||||||
|
expect(processor.staleIssues.length).toEqual(0);
|
||||||
|
expect(processor.deletedBranchIssues.length).toEqual(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('git branch is not deleted when issue is not pull request', async () => {
|
||||||
|
const opts = {...DefaultProcessorOptions, deleteBranch: true};
|
||||||
|
const isPullRequest = false;
|
||||||
|
const TestIssueList: Issue[] = [
|
||||||
|
generateIssue(
|
||||||
|
1,
|
||||||
|
'An issue that should not have its branch deleted',
|
||||||
|
'2020-01-01T17:00:00Z',
|
||||||
|
isPullRequest,
|
||||||
|
['Stale']
|
||||||
|
)
|
||||||
|
];
|
||||||
|
|
||||||
|
const processor = new IssueProcessor(
|
||||||
|
opts,
|
||||||
|
async p => (p == 1 ? TestIssueList : []),
|
||||||
|
async (num, dt) => [],
|
||||||
|
async (issue, label) => new Date().toDateString()
|
||||||
|
);
|
||||||
|
|
||||||
|
await processor.processIssues(1);
|
||||||
|
|
||||||
|
expect(processor.closedIssues.length).toEqual(1);
|
||||||
|
expect(processor.removedLabelIssues.length).toEqual(0);
|
||||||
|
expect(processor.staleIssues.length).toEqual(0);
|
||||||
|
expect(processor.deletedBranchIssues.length).toEqual(0);
|
||||||
|
});
|
||||||
|
|
|
@ -56,6 +56,9 @@ inputs:
|
||||||
skip-stale-issue-message:
|
skip-stale-issue-message:
|
||||||
description: 'Skip adding stale message when marking an issue as stale.'
|
description: 'Skip adding stale message when marking an issue as stale.'
|
||||||
default: false
|
default: false
|
||||||
|
delete-branch:
|
||||||
|
description: 'Delete the git branch after closing a stale pull request.'
|
||||||
|
default: false
|
||||||
runs:
|
runs:
|
||||||
using: 'node12'
|
using: 'node12'
|
||||||
main: 'dist/index.js'
|
main: 'dist/index.js'
|
||||||
|
|
|
@ -14,6 +14,13 @@ export interface Issue {
|
||||||
locked: boolean;
|
locked: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface PullRequest {
|
||||||
|
number: number;
|
||||||
|
head: {
|
||||||
|
ref: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
export interface User {
|
export interface User {
|
||||||
type: string;
|
type: string;
|
||||||
login: string;
|
login: string;
|
||||||
|
@ -54,6 +61,7 @@ export interface IssueProcessorOptions {
|
||||||
ascending: boolean;
|
ascending: boolean;
|
||||||
skipStaleIssueMessage: boolean;
|
skipStaleIssueMessage: boolean;
|
||||||
skipStalePrMessage: boolean;
|
skipStalePrMessage: boolean;
|
||||||
|
deleteBranch: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
/***
|
/***
|
||||||
|
@ -66,6 +74,7 @@ export class IssueProcessor {
|
||||||
|
|
||||||
readonly staleIssues: Issue[] = [];
|
readonly staleIssues: Issue[] = [];
|
||||||
readonly closedIssues: Issue[] = [];
|
readonly closedIssues: Issue[] = [];
|
||||||
|
readonly deletedBranchIssues: Issue[] = [];
|
||||||
readonly removedLabelIssues: Issue[] = [];
|
readonly removedLabelIssues: Issue[] = [];
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
|
@ -250,6 +259,14 @@ export class IssueProcessor {
|
||||||
`Closing ${issueType} because it was last updated on ${issue.updated_at}`
|
`Closing ${issueType} because it was last updated on ${issue.updated_at}`
|
||||||
);
|
);
|
||||||
await this.closeIssue(issue, closeMessage, closeLabel);
|
await this.closeIssue(issue, closeMessage, closeLabel);
|
||||||
|
|
||||||
|
if (this.options.deleteBranch && issue.pull_request) {
|
||||||
|
core.info(
|
||||||
|
`Deleting branch for #${issue.number} as delete-branch option was specified`
|
||||||
|
);
|
||||||
|
await this.deleteBranch(issue);
|
||||||
|
this.deletedBranchIssues.push(issue);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
core.info(
|
core.info(
|
||||||
`Stale ${issueType} is not old enough to close yet (hasComments? ${issueHasComments}, hasUpdate? ${issueHasUpdate})`
|
`Stale ${issueType} is not old enough to close yet (hasComments? ${issueHasComments}, hasUpdate? ${issueHasUpdate})`
|
||||||
|
@ -432,6 +449,61 @@ export class IssueProcessor {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async getPullRequest(
|
||||||
|
pullNumber: number
|
||||||
|
): Promise<PullRequest | undefined> {
|
||||||
|
this.operationsLeft -= 1;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const pullRequest = await this.client.pulls.get({
|
||||||
|
owner: context.repo.owner,
|
||||||
|
repo: context.repo.repo,
|
||||||
|
pull_number: pullNumber
|
||||||
|
});
|
||||||
|
|
||||||
|
return pullRequest.data;
|
||||||
|
} catch (error) {
|
||||||
|
core.error(`Error getting pull request ${pullNumber}: ${error.message}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete the branch on closed pull request
|
||||||
|
private async deleteBranch(issue: Issue): Promise<void> {
|
||||||
|
core.info(
|
||||||
|
`Delete branch from closed issue #${issue.number} - ${issue.title}`
|
||||||
|
);
|
||||||
|
|
||||||
|
if (this.options.debugOnly) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const pullRequest = await this.getPullRequest(issue.number);
|
||||||
|
|
||||||
|
if (!pullRequest) {
|
||||||
|
core.info(
|
||||||
|
`Not deleting branch as pull request not found for issue ${issue.number}`
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const branch = pullRequest.head.ref;
|
||||||
|
core.info(`Deleting branch ${branch} from closed issue #${issue.number}`);
|
||||||
|
|
||||||
|
this.operationsLeft -= 1;
|
||||||
|
|
||||||
|
try {
|
||||||
|
await this.client.git.deleteRef({
|
||||||
|
owner: context.repo.owner,
|
||||||
|
repo: context.repo.repo,
|
||||||
|
ref: `heads/${branch}`
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
core.error(
|
||||||
|
`Error deleting branch ${branch} from issue #${issue.number}: ${error.message}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Remove a label from an issue
|
// Remove a label from an issue
|
||||||
private async removeLabel(issue: Issue, label: string): Promise<void> {
|
private async removeLabel(issue: Issue, label: string): Promise<void> {
|
||||||
core.info(`Removing label from issue #${issue.number}`);
|
core.info(`Removing label from issue #${issue.number}`);
|
||||||
|
|
|
@ -42,7 +42,8 @@ function getAndValidateArgs(): IssueProcessorOptions {
|
||||||
debugOnly: core.getInput('debug-only') === 'true',
|
debugOnly: core.getInput('debug-only') === 'true',
|
||||||
ascending: core.getInput('ascending') === 'true',
|
ascending: core.getInput('ascending') === 'true',
|
||||||
skipStalePrMessage: core.getInput('skip-stale-pr-message') === 'true',
|
skipStalePrMessage: core.getInput('skip-stale-pr-message') === 'true',
|
||||||
skipStaleIssueMessage: core.getInput('skip-stale-issue-message') === 'true'
|
skipStaleIssueMessage: core.getInput('skip-stale-issue-message') === 'true',
|
||||||
|
deleteBranch: core.getInput('delete-branch') === 'true'
|
||||||
};
|
};
|
||||||
|
|
||||||
for (const numberInput of [
|
for (const numberInput of [
|
||||||
|
|
Loading…
Reference in New Issue