From 4bb364cc8e4f936dae869d5d019f752ac675e010 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Moreno?= Date: Tue, 19 May 2020 11:57:34 +0200 Subject: [PATCH] github: publish workspace folder --- extensions/github/package.json | 5 + extensions/github/package.nls.json | 3 +- extensions/github/src/commands.ts | 113 +-------------- extensions/github/src/extension.ts | 2 +- extensions/github/src/publish.ts | 137 ++++++++++++++++++ extensions/github/src/remoteSourceProvider.ts | 9 +- 6 files changed, 155 insertions(+), 114 deletions(-) create mode 100644 extensions/github/src/publish.ts diff --git a/extensions/github/package.json b/extensions/github/package.json index 668debebc6c..6535ac92a3f 100644 --- a/extensions/github/package.json +++ b/extensions/github/package.json @@ -43,6 +43,11 @@ "view": "scm", "contents": "%welcome.publishFolder%", "when": "config.git.enabled && git.state == initialized && workbenchState == folder" + }, + { + "view": "scm", + "contents": "%welcome.publishWorkspaceFolder%", + "when": "config.git.enabled && git.state == initialized && workbenchState == workspace && workspaceFolderCount != 0" } ] }, diff --git a/extensions/github/package.nls.json b/extensions/github/package.nls.json index 1a1a9241762..28906a39f0d 100644 --- a/extensions/github/package.nls.json +++ b/extensions/github/package.nls.json @@ -2,5 +2,6 @@ "displayName": "GitHub", "description": "GitHub", "config.gitAuthentication": "Controls whether to enable automatic GitHub authentication for git commands within VS Code.", - "welcome.publishFolder": "You can also directly publish this folder to a GitHub repository.\n[$(github) Publish to GitHub](command:github.publish)" + "welcome.publishFolder": "You can also directly publish this folder to a GitHub repository.\n[$(github) Publish to GitHub](command:github.publish)", + "welcome.publishWorkspaceFolder": "You can also directly publish a workspace folder to a GitHub repository.\n[$(github) Publish to GitHub](command:github.publish)" } diff --git a/extensions/github/src/commands.ts b/extensions/github/src/commands.ts index 280eac9afbc..a3f0903615f 100644 --- a/extensions/github/src/commands.ts +++ b/extensions/github/src/commands.ts @@ -5,116 +5,14 @@ import * as vscode from 'vscode'; import { API as GitAPI } from './typings/git'; -import { getOctokit } from './auth'; - -function sanitizeRepositoryName(value: string): string { - return value.trim().replace(/[^a-z0-9_.]/ig, '-'); -} +import { publishRepository } from './publish'; export function registerCommands(gitAPI: GitAPI): vscode.Disposable[] { - async function publish(): Promise { - if (!vscode.workspace.workspaceFolders?.length) { - return; - } - - const folder = vscode.workspace.workspaceFolders[0]; // TODO - - const quickpick = vscode.window.createQuickPick(); - quickpick.ignoreFocusOut = true; - - quickpick.placeholder = 'Repository Name'; - quickpick.value = folder.name; - quickpick.show(); - quickpick.busy = true; - - const octokit = await getOctokit(); - const user = await octokit.users.getAuthenticated({}); - const owner = user.data.login; - quickpick.busy = false; - - let repo: string | undefined; - - const onDidChangeValue = async () => { - const sanitizedRepo = sanitizeRepositoryName(quickpick.value); - - if (!sanitizedRepo) { - quickpick.items = []; - } else { - quickpick.items = [{ label: `$(repo) Create private repository`, description: `$(github) ${owner}/${sanitizedRepo}`, alwaysShow: true, repo: sanitizedRepo }]; - } - }; - - onDidChangeValue(); - - while (true) { - const listener = quickpick.onDidChangeValue(onDidChangeValue); - const pick = await getPick(quickpick); - listener.dispose(); - - repo = pick?.repo; - - if (repo) { - try { - quickpick.busy = true; - await octokit.repos.get({ owner, repo: repo }); - quickpick.items = [{ label: `$(error) Repository already exists`, description: `$(github) ${owner}/${repo}`, alwaysShow: true }]; - } catch { - break; - } finally { - quickpick.busy = false; - } - } - } - - quickpick.dispose(); - - if (!repo) { - return; - } - - const githubRepository = await vscode.window.withProgress({ location: vscode.ProgressLocation.Notification, cancellable: false, title: 'Publish to GitHub' }, async progress => { - progress.report({ message: 'Creating private repository in GitHub', increment: 25 }); - - const res = await octokit.repos.createForAuthenticatedUser({ - name: repo!, - private: true - }); - - const createdGithubRepository = res.data; - - progress.report({ message: 'Creating first commit', increment: 25 }); - const repository = await gitAPI.init(folder.uri); - - if (!repository) { - return; - } - - await repository.commit('first commit', { all: true }); - - progress.report({ message: 'Uploading files', increment: 25 }); - await repository.addRemote('origin', createdGithubRepository.clone_url); - await repository.push('origin', 'master', true); - - return createdGithubRepository; - }); - - if (!githubRepository) { - return; - } - - const openInGitHub = 'Open In GitHub'; - const action = await vscode.window.showInformationMessage(`Successfully published the '${owner}/${repo}' repository on GitHub.`, openInGitHub); - - if (action === openInGitHub) { - vscode.commands.executeCommand('vscode.open', vscode.Uri.parse(githubRepository.html_url)); - } - } - const disposables = []; disposables.push(vscode.commands.registerCommand('github.publish', async () => { try { - publish(); + publishRepository(gitAPI); } catch (err) { vscode.window.showErrorMessage(err.message); } @@ -122,10 +20,3 @@ export function registerCommands(gitAPI: GitAPI): vscode.Disposable[] { return disposables; } - -function getPick(quickpick: vscode.QuickPick): Promise { - return Promise.race([ - new Promise(c => quickpick.onDidAccept(() => quickpick.selectedItems.length > 0 && c(quickpick.selectedItems[0]))), - new Promise(c => quickpick.onDidHide(() => c(undefined))) - ]); -} diff --git a/extensions/github/src/extension.ts b/extensions/github/src/extension.ts index 45c9d8bbc53..15cf2544f1e 100644 --- a/extensions/github/src/extension.ts +++ b/extensions/github/src/extension.ts @@ -14,6 +14,6 @@ export async function activate(context: vscode.ExtensionContext) { const gitAPI = gitExtension.getAPI(1); context.subscriptions.push(...registerCommands(gitAPI)); - context.subscriptions.push(gitAPI.registerRemoteSourceProvider(new GithubRemoteSourceProvider())); + context.subscriptions.push(gitAPI.registerRemoteSourceProvider(new GithubRemoteSourceProvider(gitAPI))); context.subscriptions.push(new GithubCredentialProviderManager(gitAPI)); } diff --git a/extensions/github/src/publish.ts b/extensions/github/src/publish.ts new file mode 100644 index 00000000000..2ea34797e2e --- /dev/null +++ b/extensions/github/src/publish.ts @@ -0,0 +1,137 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as vscode from 'vscode'; +import * as nls from 'vscode-nls'; +import { API as GitAPI, Repository } from './typings/git'; +import { getOctokit } from './auth'; + +const localize = nls.loadMessageBundle(); + +function sanitizeRepositoryName(value: string): string { + return value.trim().replace(/[^a-z0-9_.]/ig, '-'); +} + +function getPick(quickpick: vscode.QuickPick): Promise { + return Promise.race([ + new Promise(c => quickpick.onDidAccept(() => quickpick.selectedItems.length > 0 && c(quickpick.selectedItems[0]))), + new Promise(c => quickpick.onDidHide(() => c(undefined))) + ]); +} + +export async function publishRepository(gitAPI: GitAPI, repository?: Repository): Promise { + if (!vscode.workspace.workspaceFolders?.length) { + return; + } + + let folder: vscode.WorkspaceFolder; + + if (vscode.workspace.workspaceFolders.length === 1) { + folder = vscode.workspace.workspaceFolders[0]; + } else { + const picks = vscode.workspace.workspaceFolders.map(folder => ({ label: folder.name, folder })); + const placeHolder = localize('pick folder', "Pick a folder to publish to GitHub"); + const pick = await vscode.window.showQuickPick(picks, { placeHolder }); + + if (!pick) { + return; + } + + folder = pick.folder; + } + + const quickpick = vscode.window.createQuickPick(); + quickpick.ignoreFocusOut = true; + + quickpick.placeholder = 'Repository Name'; + quickpick.value = folder.name; + quickpick.show(); + quickpick.busy = true; + + const octokit = await getOctokit(); + const user = await octokit.users.getAuthenticated({}); + const owner = user.data.login; + quickpick.busy = false; + + let repo: string | undefined; + + const onDidChangeValue = async () => { + const sanitizedRepo = sanitizeRepositoryName(quickpick.value); + + if (!sanitizedRepo) { + quickpick.items = []; + } else { + quickpick.items = [{ label: `$(repo) Create private repository`, description: `$(github) ${owner}/${sanitizedRepo}`, alwaysShow: true, repo: sanitizedRepo }]; + } + }; + + onDidChangeValue(); + + while (true) { + const listener = quickpick.onDidChangeValue(onDidChangeValue); + const pick = await getPick(quickpick); + listener.dispose(); + + repo = pick?.repo; + + if (repo) { + try { + quickpick.busy = true; + await octokit.repos.get({ owner, repo: repo }); + quickpick.items = [{ label: `$(error) Repository already exists`, description: `$(github) ${owner}/${repo}`, alwaysShow: true }]; + } catch { + break; + } finally { + quickpick.busy = false; + } + } + } + + quickpick.dispose(); + + if (!repo) { + return; + } + + const githubRepository = await vscode.window.withProgress({ location: vscode.ProgressLocation.Notification, cancellable: false, title: 'Publish to GitHub' }, async progress => { + progress.report({ message: 'Creating private repository in GitHub', increment: 25 }); + + const res = await octokit.repos.createForAuthenticatedUser({ + name: repo!, + private: true + }); + + const createdGithubRepository = res.data; + + progress.report({ message: 'Creating first commit', increment: 25 }); + + if (!repository) { + repository = await gitAPI.init(folder.uri) || undefined; + + if (!repository) { + return; + } + + await repository.commit('first commit', { all: true }); + } + + progress.report({ message: 'Uploading files', increment: 25 }); + await repository.addRemote('origin', createdGithubRepository.clone_url); + await repository.push('origin', 'master', true); + + return createdGithubRepository; + }); + + if (!githubRepository) { + return; + } + + const openInGitHub = 'Open In GitHub'; + const action = await vscode.window.showInformationMessage(`Successfully published the '${owner}/${repo}' repository on GitHub.`, openInGitHub); + + if (action === openInGitHub) { + vscode.commands.executeCommand('vscode.open', vscode.Uri.parse(githubRepository.html_url)); + } +} diff --git a/extensions/github/src/remoteSourceProvider.ts b/extensions/github/src/remoteSourceProvider.ts index 179b3eac269..9d0ab4c585b 100644 --- a/extensions/github/src/remoteSourceProvider.ts +++ b/extensions/github/src/remoteSourceProvider.ts @@ -3,9 +3,10 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { RemoteSourceProvider, RemoteSource } from './typings/git'; +import { API as GitAPI, RemoteSourceProvider, RemoteSource, Repository } from './typings/git'; import { getOctokit } from './auth'; import { Octokit } from '@octokit/rest'; +import { publishRepository } from './publish'; function asRemoteSource(raw: any): RemoteSource { return { @@ -23,6 +24,8 @@ export class GithubRemoteSourceProvider implements RemoteSourceProvider { private userReposCache: RemoteSource[] = []; + constructor(private gitAPI: GitAPI) { } + async getRemoteSources(query?: string): Promise { const octokit = await getOctokit(); const [fromUser, fromQuery] = await Promise.all([ @@ -57,4 +60,8 @@ export class GithubRemoteSourceProvider implements RemoteSourceProvider { const raw = await octokit.search.repos({ q: query, sort: 'updated' }); return raw.data.items.map(asRemoteSource); } + + publishRepository(repository: Repository): Promise { + return publishRepository(this.gitAPI, repository); + } } -- GitLab