Skip to content

Commit 131991d

Browse files
feat: ✨ Support PR Template on pull request creation (#997)
1 parent 23dd6be commit 131991d

File tree

5 files changed

+102
-11
lines changed

5 files changed

+102
-11
lines changed

README.md

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -564,7 +564,8 @@ branches:
564564

565565
### Copy attributes from issue
566566

567-
When the App opens a new (draft) Pull Request it can also copy over the following attributes from your issue:
567+
When the App opens a new (draft) Pull Request, it can also copy over the
568+
following attributes from your issue:
568569

569570
- Description
570571
- Labels
@@ -582,6 +583,18 @@ copyIssueProjectsToPR: true
582583
copyIssueMilestoneToPR: true
583584
```
584585

586+
### Copy pull request template
587+
588+
When the App opens a new (draft) Pull Request, it can also copy over a pull
589+
request template from a file in your repository. The template must be stored in
590+
a file named `.github/pull_request_template.md`.
591+
592+
You can enable this behaviour in the configuration:
593+
594+
```yaml
595+
copyPullRequestTemplateToPR: true
596+
```
597+
585598
### Skip CI workflows
586599

587600
Automatically opening a (draft) PR for an issue requires an empty commit on the newly created branch (this is a

src/entities/Config.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ export interface Config {
1111
copyIssueProjectsToPR: boolean;
1212
copyIssueMilestoneToPR: boolean;
1313
copyIssueDescriptionToPR: boolean;
14+
copyPullRequestTemplateToPR: boolean;
1415
gitReplaceChars: string;
1516
gitSafeReplacementChar: string;
1617
commentMessage?: string;
@@ -49,6 +50,7 @@ export function getDefaultConfig(): Config {
4950
copyIssueProjectsToPR: false,
5051
copyIssueMilestoneToPR: false,
5152
copyIssueDescriptionToPR: false,
53+
copyPullRequestTemplateToPR: false,
5254
gitReplaceChars: '',
5355
gitSafeReplacementChar: '_',
5456
silent: false,

src/github.ts

Lines changed: 34 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -276,7 +276,7 @@ export async function createPr(app: Probot, ctx: Context<any>, config: Config, u
276276
await createEmptyCommit(ctx, branchName, getCommitText(ctx, config), String(branchHeadSha))
277277
}
278278
const {data: pr} = await ctx.octokit.pulls.create(
279-
{owner, repo, head: branchName, base, title, body: getPrBody(app, ctx, config), draft: draft})
279+
{owner, repo, head: branchName, base, title, body: await getPrBody(app, ctx, config), draft: draft})
280280
app.log.info(`${draft ? 'Created draft' : 'Created'} pull request ${pr.number} for branch ${branchName}`)
281281
await copyIssueAttributesToPr(app, ctx, config, pr)
282282
} catch (e: any) {
@@ -320,19 +320,43 @@ function getCommitText(ctx: Context<any>, config: Config) {
320320
}
321321
}
322322

323-
function getPrBody(app: Probot, ctx: Context<any>, config: Config) {
324-
const issueNumber = getIssueNumber(ctx)
325-
let result = ''
323+
async function getPrBody(app: Probot, ctx: Context<any>, config: Config) {
324+
const issueNumber = getIssueNumber(ctx);
325+
let result = '';
326326
if (config.copyIssueDescriptionToPR) {
327-
app.log.info('Copying issue description to PR')
328-
const issueDescription = getIssueDescription(ctx)
327+
app.log.info('Copying issue description to PR');
328+
const issueDescription = getIssueDescription(ctx);
329329
if (issueDescription) {
330-
result += formatAsExpandingMarkdown('Original issue description', issueDescription)
331-
result += '\n'
330+
result += formatAsExpandingMarkdown('Original issue description', issueDescription);
331+
result += '\n';
332332
}
333333
}
334-
result += `closes #${issueNumber}`
335-
return result
334+
if (config.copyPullRequestTemplateToPR) {
335+
app.log.info('Copying pull-request template to PR');
336+
const pullRequestTemplate = await getPullRequestTemplate(ctx);
337+
if (pullRequestTemplate) {
338+
result += pullRequestTemplate;
339+
result += '\n';
340+
}
341+
}
342+
result += `closes #${issueNumber}`;
343+
return result;
344+
}
345+
346+
async function getPullRequestTemplate(ctx: Context<any>): Promise<string | undefined> {
347+
try {
348+
const {data} = await ctx.octokit.repos.getContent({
349+
owner: getRepoOwnerLogin(ctx),
350+
repo: getRepoName(ctx),
351+
path: '.github/pull_request_template.md'
352+
}) as any;
353+
if (data.type === 'file' && data.content) {
354+
return Buffer.from(data.content, 'base64').toString('utf8');
355+
}
356+
} catch (e: any) {
357+
/* do nothing */
358+
}
359+
return undefined;
336360
}
337361

338362
async function copyIssueAttributesToPr(app: Probot, ctx: Context<any>, config: Config, pr: any) {

tests/github.test.ts

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -296,6 +296,7 @@ test('copy Issue description into PR', async () => {
296296
})
297297
})
298298

299+
299300
test('Do not copy undefined Issue description into PR', async () => {
300301
const createPR = jest.fn()
301302
const ctx = getDefaultContext()
@@ -317,6 +318,56 @@ test('Do not copy undefined Issue description into PR', async () => {
317318
})
318319
})
319320

321+
test('copy pull-request template into PR', async () => {
322+
const createPR = jest.fn();
323+
const ctx = getDefaultContext();
324+
ctx.octokit.pulls.create = createPR;
325+
ctx.octokit.graphql = jest.fn();
326+
ctx.octokit.repos.getContent = async (args: { owner: string, repo: string, path: string }) => {
327+
expect(args.path).toBe('.github/pull_request_template.md');
328+
return {data: {type: 'file', content: Buffer.from('file content').toString('base64')}};
329+
};
330+
const config = getDefaultConfig();
331+
config.copyPullRequestTemplateToPR = true;
332+
333+
await github.createPr(probot, ctx, config, 'robvanderleek', 'issue-1');
334+
335+
expect(createPR).toHaveBeenCalledWith({
336+
owner: 'robvanderleek',
337+
repo: 'create-issue-branch',
338+
head: 'issue-1',
339+
base: 'master',
340+
title: 'Hello world',
341+
body: 'file content' + '\ncloses #1',
342+
draft: false
343+
});
344+
})
345+
346+
test('pull-request template does not exist', async () => {
347+
const createPR = jest.fn();
348+
const ctx = getDefaultContext();
349+
ctx.octokit.pulls.create = createPR;
350+
ctx.octokit.graphql = jest.fn();
351+
ctx.octokit.repos.getContent = async (args: { owner: string, repo: string, path: string }) => {
352+
expect(args.path).toBe('.github/pull_request_template.md');
353+
throw {status: 404};
354+
};
355+
const config = getDefaultConfig();
356+
config.copyPullRequestTemplateToPR = true;
357+
358+
await github.createPr(probot, ctx, config, 'robvanderleek', 'issue-1');
359+
360+
expect(createPR).toHaveBeenCalledWith({
361+
owner: 'robvanderleek',
362+
repo: 'create-issue-branch',
363+
head: 'issue-1',
364+
base: 'master',
365+
title: 'Hello world',
366+
body: 'closes #1',
367+
draft: false
368+
});
369+
})
370+
320371
test('use correct source branch', async () => {
321372
const createPR = jest.fn()
322373
const ctx = getDefaultContext()

tests/test-helpers.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,7 @@ export function getDefaultContext(): any {
163163
createComment: () => {
164164
}
165165
},
166+
repos: {},
166167
graphql: (_: any, {message}: { message: string }) => {
167168
}
168169
}, //

0 commit comments

Comments
 (0)