Skip to content

Commit 4bb364c

Browse files
committed
github: publish workspace folder
1 parent 2aa9f3f commit 4bb364c

6 files changed

Lines changed: 155 additions & 114 deletions

File tree

extensions/github/package.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,11 @@
4343
"view": "scm",
4444
"contents": "%welcome.publishFolder%",
4545
"when": "config.git.enabled && git.state == initialized && workbenchState == folder"
46+
},
47+
{
48+
"view": "scm",
49+
"contents": "%welcome.publishWorkspaceFolder%",
50+
"when": "config.git.enabled && git.state == initialized && workbenchState == workspace && workspaceFolderCount != 0"
4651
}
4752
]
4853
},

extensions/github/package.nls.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,6 @@
22
"displayName": "GitHub",
33
"description": "GitHub",
44
"config.gitAuthentication": "Controls whether to enable automatic GitHub authentication for git commands within VS Code.",
5-
"welcome.publishFolder": "You can also directly publish this folder to a GitHub repository.\n[$(github) Publish to GitHub](command:github.publish)"
5+
"welcome.publishFolder": "You can also directly publish this folder to a GitHub repository.\n[$(github) Publish to GitHub](command:github.publish)",
6+
"welcome.publishWorkspaceFolder": "You can also directly publish a workspace folder to a GitHub repository.\n[$(github) Publish to GitHub](command:github.publish)"
67
}

extensions/github/src/commands.ts

Lines changed: 2 additions & 111 deletions
Original file line numberDiff line numberDiff line change
@@ -5,127 +5,18 @@
55

66
import * as vscode from 'vscode';
77
import { API as GitAPI } from './typings/git';
8-
import { getOctokit } from './auth';
9-
10-
function sanitizeRepositoryName(value: string): string {
11-
return value.trim().replace(/[^a-z0-9_.]/ig, '-');
12-
}
8+
import { publishRepository } from './publish';
139

1410
export function registerCommands(gitAPI: GitAPI): vscode.Disposable[] {
15-
async function publish(): Promise<void> {
16-
if (!vscode.workspace.workspaceFolders?.length) {
17-
return;
18-
}
19-
20-
const folder = vscode.workspace.workspaceFolders[0]; // TODO
21-
22-
const quickpick = vscode.window.createQuickPick<vscode.QuickPickItem & { repo?: string, auth?: 'https' | 'ssh' }>();
23-
quickpick.ignoreFocusOut = true;
24-
25-
quickpick.placeholder = 'Repository Name';
26-
quickpick.value = folder.name;
27-
quickpick.show();
28-
quickpick.busy = true;
29-
30-
const octokit = await getOctokit();
31-
const user = await octokit.users.getAuthenticated({});
32-
const owner = user.data.login;
33-
quickpick.busy = false;
34-
35-
let repo: string | undefined;
36-
37-
const onDidChangeValue = async () => {
38-
const sanitizedRepo = sanitizeRepositoryName(quickpick.value);
39-
40-
if (!sanitizedRepo) {
41-
quickpick.items = [];
42-
} else {
43-
quickpick.items = [{ label: `$(repo) Create private repository`, description: `$(github) ${owner}/${sanitizedRepo}`, alwaysShow: true, repo: sanitizedRepo }];
44-
}
45-
};
46-
47-
onDidChangeValue();
48-
49-
while (true) {
50-
const listener = quickpick.onDidChangeValue(onDidChangeValue);
51-
const pick = await getPick(quickpick);
52-
listener.dispose();
53-
54-
repo = pick?.repo;
55-
56-
if (repo) {
57-
try {
58-
quickpick.busy = true;
59-
await octokit.repos.get({ owner, repo: repo });
60-
quickpick.items = [{ label: `$(error) Repository already exists`, description: `$(github) ${owner}/${repo}`, alwaysShow: true }];
61-
} catch {
62-
break;
63-
} finally {
64-
quickpick.busy = false;
65-
}
66-
}
67-
}
68-
69-
quickpick.dispose();
70-
71-
if (!repo) {
72-
return;
73-
}
74-
75-
const githubRepository = await vscode.window.withProgress({ location: vscode.ProgressLocation.Notification, cancellable: false, title: 'Publish to GitHub' }, async progress => {
76-
progress.report({ message: 'Creating private repository in GitHub', increment: 25 });
77-
78-
const res = await octokit.repos.createForAuthenticatedUser({
79-
name: repo!,
80-
private: true
81-
});
82-
83-
const createdGithubRepository = res.data;
84-
85-
progress.report({ message: 'Creating first commit', increment: 25 });
86-
const repository = await gitAPI.init(folder.uri);
87-
88-
if (!repository) {
89-
return;
90-
}
91-
92-
await repository.commit('first commit', { all: true });
93-
94-
progress.report({ message: 'Uploading files', increment: 25 });
95-
await repository.addRemote('origin', createdGithubRepository.clone_url);
96-
await repository.push('origin', 'master', true);
97-
98-
return createdGithubRepository;
99-
});
100-
101-
if (!githubRepository) {
102-
return;
103-
}
104-
105-
const openInGitHub = 'Open In GitHub';
106-
const action = await vscode.window.showInformationMessage(`Successfully published the '${owner}/${repo}' repository on GitHub.`, openInGitHub);
107-
108-
if (action === openInGitHub) {
109-
vscode.commands.executeCommand('vscode.open', vscode.Uri.parse(githubRepository.html_url));
110-
}
111-
}
112-
11311
const disposables = [];
11412

11513
disposables.push(vscode.commands.registerCommand('github.publish', async () => {
11614
try {
117-
publish();
15+
publishRepository(gitAPI);
11816
} catch (err) {
11917
vscode.window.showErrorMessage(err.message);
12018
}
12119
}));
12220

12321
return disposables;
12422
}
125-
126-
function getPick<T extends vscode.QuickPickItem>(quickpick: vscode.QuickPick<T>): Promise<T | undefined> {
127-
return Promise.race<T | undefined>([
128-
new Promise<T>(c => quickpick.onDidAccept(() => quickpick.selectedItems.length > 0 && c(quickpick.selectedItems[0]))),
129-
new Promise<undefined>(c => quickpick.onDidHide(() => c(undefined)))
130-
]);
131-
}

extensions/github/src/extension.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,6 @@ export async function activate(context: vscode.ExtensionContext) {
1414
const gitAPI = gitExtension.getAPI(1);
1515

1616
context.subscriptions.push(...registerCommands(gitAPI));
17-
context.subscriptions.push(gitAPI.registerRemoteSourceProvider(new GithubRemoteSourceProvider()));
17+
context.subscriptions.push(gitAPI.registerRemoteSourceProvider(new GithubRemoteSourceProvider(gitAPI)));
1818
context.subscriptions.push(new GithubCredentialProviderManager(gitAPI));
1919
}

extensions/github/src/publish.ts

Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
/*---------------------------------------------------------------------------------------------
2+
* Copyright (c) Microsoft Corporation. All rights reserved.
3+
* Licensed under the MIT License. See License.txt in the project root for license information.
4+
*--------------------------------------------------------------------------------------------*/
5+
6+
import * as vscode from 'vscode';
7+
import * as nls from 'vscode-nls';
8+
import { API as GitAPI, Repository } from './typings/git';
9+
import { getOctokit } from './auth';
10+
11+
const localize = nls.loadMessageBundle();
12+
13+
function sanitizeRepositoryName(value: string): string {
14+
return value.trim().replace(/[^a-z0-9_.]/ig, '-');
15+
}
16+
17+
function getPick<T extends vscode.QuickPickItem>(quickpick: vscode.QuickPick<T>): Promise<T | undefined> {
18+
return Promise.race<T | undefined>([
19+
new Promise<T>(c => quickpick.onDidAccept(() => quickpick.selectedItems.length > 0 && c(quickpick.selectedItems[0]))),
20+
new Promise<undefined>(c => quickpick.onDidHide(() => c(undefined)))
21+
]);
22+
}
23+
24+
export async function publishRepository(gitAPI: GitAPI, repository?: Repository): Promise<void> {
25+
if (!vscode.workspace.workspaceFolders?.length) {
26+
return;
27+
}
28+
29+
let folder: vscode.WorkspaceFolder;
30+
31+
if (vscode.workspace.workspaceFolders.length === 1) {
32+
folder = vscode.workspace.workspaceFolders[0];
33+
} else {
34+
const picks = vscode.workspace.workspaceFolders.map(folder => ({ label: folder.name, folder }));
35+
const placeHolder = localize('pick folder', "Pick a folder to publish to GitHub");
36+
const pick = await vscode.window.showQuickPick(picks, { placeHolder });
37+
38+
if (!pick) {
39+
return;
40+
}
41+
42+
folder = pick.folder;
43+
}
44+
45+
const quickpick = vscode.window.createQuickPick<vscode.QuickPickItem & { repo?: string, auth?: 'https' | 'ssh' }>();
46+
quickpick.ignoreFocusOut = true;
47+
48+
quickpick.placeholder = 'Repository Name';
49+
quickpick.value = folder.name;
50+
quickpick.show();
51+
quickpick.busy = true;
52+
53+
const octokit = await getOctokit();
54+
const user = await octokit.users.getAuthenticated({});
55+
const owner = user.data.login;
56+
quickpick.busy = false;
57+
58+
let repo: string | undefined;
59+
60+
const onDidChangeValue = async () => {
61+
const sanitizedRepo = sanitizeRepositoryName(quickpick.value);
62+
63+
if (!sanitizedRepo) {
64+
quickpick.items = [];
65+
} else {
66+
quickpick.items = [{ label: `$(repo) Create private repository`, description: `$(github) ${owner}/${sanitizedRepo}`, alwaysShow: true, repo: sanitizedRepo }];
67+
}
68+
};
69+
70+
onDidChangeValue();
71+
72+
while (true) {
73+
const listener = quickpick.onDidChangeValue(onDidChangeValue);
74+
const pick = await getPick(quickpick);
75+
listener.dispose();
76+
77+
repo = pick?.repo;
78+
79+
if (repo) {
80+
try {
81+
quickpick.busy = true;
82+
await octokit.repos.get({ owner, repo: repo });
83+
quickpick.items = [{ label: `$(error) Repository already exists`, description: `$(github) ${owner}/${repo}`, alwaysShow: true }];
84+
} catch {
85+
break;
86+
} finally {
87+
quickpick.busy = false;
88+
}
89+
}
90+
}
91+
92+
quickpick.dispose();
93+
94+
if (!repo) {
95+
return;
96+
}
97+
98+
const githubRepository = await vscode.window.withProgress({ location: vscode.ProgressLocation.Notification, cancellable: false, title: 'Publish to GitHub' }, async progress => {
99+
progress.report({ message: 'Creating private repository in GitHub', increment: 25 });
100+
101+
const res = await octokit.repos.createForAuthenticatedUser({
102+
name: repo!,
103+
private: true
104+
});
105+
106+
const createdGithubRepository = res.data;
107+
108+
progress.report({ message: 'Creating first commit', increment: 25 });
109+
110+
if (!repository) {
111+
repository = await gitAPI.init(folder.uri) || undefined;
112+
113+
if (!repository) {
114+
return;
115+
}
116+
117+
await repository.commit('first commit', { all: true });
118+
}
119+
120+
progress.report({ message: 'Uploading files', increment: 25 });
121+
await repository.addRemote('origin', createdGithubRepository.clone_url);
122+
await repository.push('origin', 'master', true);
123+
124+
return createdGithubRepository;
125+
});
126+
127+
if (!githubRepository) {
128+
return;
129+
}
130+
131+
const openInGitHub = 'Open In GitHub';
132+
const action = await vscode.window.showInformationMessage(`Successfully published the '${owner}/${repo}' repository on GitHub.`, openInGitHub);
133+
134+
if (action === openInGitHub) {
135+
vscode.commands.executeCommand('vscode.open', vscode.Uri.parse(githubRepository.html_url));
136+
}
137+
}

extensions/github/src/remoteSourceProvider.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,10 @@
33
* Licensed under the MIT License. See License.txt in the project root for license information.
44
*--------------------------------------------------------------------------------------------*/
55

6-
import { RemoteSourceProvider, RemoteSource } from './typings/git';
6+
import { API as GitAPI, RemoteSourceProvider, RemoteSource, Repository } from './typings/git';
77
import { getOctokit } from './auth';
88
import { Octokit } from '@octokit/rest';
9+
import { publishRepository } from './publish';
910

1011
function asRemoteSource(raw: any): RemoteSource {
1112
return {
@@ -23,6 +24,8 @@ export class GithubRemoteSourceProvider implements RemoteSourceProvider {
2324

2425
private userReposCache: RemoteSource[] = [];
2526

27+
constructor(private gitAPI: GitAPI) { }
28+
2629
async getRemoteSources(query?: string): Promise<RemoteSource[]> {
2730
const octokit = await getOctokit();
2831
const [fromUser, fromQuery] = await Promise.all([
@@ -57,4 +60,8 @@ export class GithubRemoteSourceProvider implements RemoteSourceProvider {
5760
const raw = await octokit.search.repos({ q: query, sort: 'updated' });
5861
return raw.data.items.map(asRemoteSource);
5962
}
63+
64+
publishRepository(repository: Repository): Promise<void> {
65+
return publishRepository(this.gitAPI, repository);
66+
}
6067
}

0 commit comments

Comments
 (0)