Ask user to select PR templates when forking a repository (#143733)

* Add getPullRequestTemplates method to discover templates

Signed-off-by: Babak K. Shandiz <babak.k.shandiz@gmail.com>

* Add method to quick pick for PR templates

Signed-off-by: Babak K. Shandiz <babak.k.shandiz@gmail.com>

* Handle possible PR templates

Signed-off-by: Babak K. Shandiz <babak.k.shandiz@gmail.com>

* Remove unnecessary return value assignment

Co-authored-by: João Moreno <mail@joaomoreno.com>

* Change comparison operands' order

Co-authored-by: João Moreno <mail@joaomoreno.com>

* Remove sorting template URIs in pickPullRequestTemplate

Signed-off-by: Babak K. Shandiz <babak.k.shandiz@gmail.com>

* Sort template URIs before showing quick-pick list

Signed-off-by: Babak K. Shandiz <babak.k.shandiz@gmail.com>

* Rename getPullRequestTemplates method to findPullRequestTemplates

Signed-off-by: Babak K. Shandiz <babak.k.shandiz@gmail.com>

* Find Github PR templates in-parallel using readdir/stat

Signed-off-by: Babak K. Shandiz <babak.k.shandiz@gmail.com>

* Export method for visibitliy in tests

Signed-off-by: Babak K. Shandiz <babak.k.shandiz@gmail.com>

* Add tests for Github PR template detection

Signed-off-by: Babak K. Shandiz <babak.k.shandiz@gmail.com>

* Add launcher configration to run Github tests

Signed-off-by: Babak K. Shandiz <babak.k.shandiz@gmail.com>

* 💄

* Replace stat with readDirectory for OS native case sensitivity

Signed-off-by: Babak K. Shandiz <babak.k.shandiz@gmail.com>

* Delete some files to avoid duplicate names on case insensitive envs

Signed-off-by: Babak K. Shandiz <babak.k.shandiz@gmail.com>

* Exclude deleted files from test case expected result

Signed-off-by: Babak K. Shandiz <babak.k.shandiz@gmail.com>

Co-authored-by: João Moreno <mail@joaomoreno.com>
Co-authored-by: João Moreno <joao.moreno@microsoft.com>
This commit is contained in:
Babak K. Shandiz
2022-04-01 14:07:33 +00:00
committed by GitHub
parent 62ace5901d
commit 7fc55261aa
18 changed files with 191 additions and 1 deletions

View File

@@ -3,10 +3,12 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { commands, env, ProgressLocation, Uri, window } from 'vscode';
import { TextDecoder } from 'util';
import { commands, env, ProgressLocation, Uri, window, workspace, QuickPickOptions, FileType } from 'vscode';
import * as nls from 'vscode-nls';
import { getOctokit } from './auth';
import { GitErrorCodes, PushErrorHandler, Remote, Repository } from './typings/git';
import path = require('path');
const localize = nls.loadMessageBundle();
@@ -103,10 +105,24 @@ async function handlePushError(repository: Repository, remote: Remote, refspec:
title = commit.message.replace(/\n.*$/m, '');
}
let body: string | undefined;
const templates = await findPullRequestTemplates(repository.rootUri);
if (templates.length > 0) {
templates.sort((a, b) => a.path.localeCompare(b.path));
const template = await pickPullRequestTemplate(templates);
if (template) {
body = new TextDecoder('utf-8').decode(await workspace.fs.readFile(template));
}
}
const res = await octokit.pulls.create({
owner,
repo,
title,
body,
head: `${ghRepository.owner.login}:${remoteName}`,
base: remoteName
});
@@ -128,6 +144,67 @@ async function handlePushError(repository: Repository, remote: Remote, refspec:
})();
}
const PR_TEMPLATE_FILES = [
{ dir: '.', files: ['pull_request_template.md', 'PULL_REQUEST_TEMPLATE.md'] },
{ dir: 'docs', files: ['pull_request_template.md', 'PULL_REQUEST_TEMPLATE.md'] },
{ dir: '.github', files: ['PULL_REQUEST_TEMPLATE.md', 'PULL_REQUEST_TEMPLATE.md'] }
];
const PR_TEMPLATE_DIRECTORY_NAMES = [
'PULL_REQUEST_TEMPLATE',
'docs/PULL_REQUEST_TEMPLATE',
'.github/PULL_REQUEST_TEMPLATE'
];
async function assertMarkdownFiles(dir: Uri, files: string[]): Promise<Uri[]> {
const dirFiles = await workspace.fs.readDirectory(dir);
return dirFiles
.filter(([name, type]) => Boolean(type & FileType.File) && files.indexOf(name) !== -1)
.map(([name]) => Uri.joinPath(dir, name));
}
async function findMarkdownFilesInDir(uri: Uri): Promise<Uri[]> {
const files = await workspace.fs.readDirectory(uri);
return files
.filter(([name, type]) => Boolean(type & FileType.File) && path.extname(name) === '.md')
.map(([name]) => Uri.joinPath(uri, name));
}
/**
* PR templates can be:
* - In the root, `docs`, or `.github` folders, called `pull_request_template.md` or `PULL_REQUEST_TEMPLATE.md`
* - Or, in a `PULL_REQUEST_TEMPLATE` directory directly below the root, `docs`, or `.github` folders, called `*.md`
*
* NOTE This method is a modified copy of a method with same name at microsoft/vscode-pull-request-github repository:
* https://github.com/microsoft/vscode-pull-request-github/blob/0a0c3c6c21c0b9c2f4d5ffbc3f8c6a825472e9e6/src/github/folderRepositoryManager.ts#L1061
*
*/
export async function findPullRequestTemplates(repositoryRootUri: Uri): Promise<Uri[]> {
const results = await Promise.allSettled([
...PR_TEMPLATE_FILES.map(x => assertMarkdownFiles(Uri.joinPath(repositoryRootUri, x.dir), x.files)),
...PR_TEMPLATE_DIRECTORY_NAMES.map(x => findMarkdownFilesInDir(Uri.joinPath(repositoryRootUri, x)))
]);
return results.flatMap(x => x.status === 'fulfilled' && x.value || []);
}
export async function pickPullRequestTemplate(templates: Uri[]): Promise<Uri | undefined> {
const quickPickItemFromUri = (x: Uri) => ({ label: x.path, template: x });
const quickPickItems = [
{
label: localize('no pr template', "No template"),
picked: true,
template: undefined,
},
...templates.map(quickPickItemFromUri)
];
const quickPickOptions: QuickPickOptions = {
placeHolder: localize('select pr template', "Select the Pull Request template")
};
const pickedTemplate = await window.showQuickPick(quickPickItems, quickPickOptions);
return pickedTemplate?.template;
}
export class GithubPushErrorHandler implements PushErrorHandler {
async handlePushError(repository: Repository, remote: Remote, refspec: string, error: Error & { gitErrorCode: GitErrorCodes }): Promise<boolean> {