Skip to content

Commit 12b150a

Browse files
committed
github: remote source provider
1 parent 1c98bbb commit 12b150a

7 files changed

Lines changed: 718 additions & 1 deletion

File tree

extensions/github/package.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,18 @@
1414
"activationEvents": [
1515
"*"
1616
],
17+
"extensionDependencies": [
18+
"vscode.git"
19+
],
1720
"main": "./out/extension.js",
1821
"scripts": {
1922
"vscode:prepublish": "npm run compile",
2023
"compile": "gulp compile-extension:github",
2124
"watch": "gulp watch-extension:github"
2225
},
2326
"dependencies": {
27+
"@octokit/rest": "^17.9.1",
28+
"tunnel": "^0.0.6",
2429
"vscode-nls": "^4.1.2"
2530
},
2631
"devDependencies": {

extensions/github/src/extension.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,12 @@
44
*--------------------------------------------------------------------------------------------*/
55

66
import * as vscode from 'vscode';
7+
import { GithubRemoteSourceProvider } from './remoteSourceProvider';
8+
import { GitExtension } from './typings/git';
79

810
export async function activate(context: vscode.ExtensionContext) {
9-
console.log('github is active!');
11+
const gitExtension = vscode.extensions.getExtension<GitExtension>('vscode.git')!.exports;
12+
const gitAPI = gitExtension.getAPI(1);
13+
14+
context.subscriptions.push(gitAPI.registerRemoteSourceProvider(new GithubRemoteSourceProvider()));
1015
}

extensions/github/src/octokit.ts

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
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 { AuthenticationSession, authentication, window } from 'vscode';
7+
import { Agent, globalAgent } from 'https';
8+
import { Octokit } from '@octokit/rest';
9+
import { httpsOverHttp } from 'tunnel';
10+
import { URL } from 'url';
11+
12+
function getAgent(url: string | undefined = process.env.HTTPS_PROXY): Agent {
13+
if (!url) {
14+
return globalAgent;
15+
}
16+
17+
try {
18+
const { hostname, port, username, password } = new URL(url);
19+
const auth = username && password && `${username}:${password}`;
20+
return httpsOverHttp({ proxy: { host: hostname, port, proxyAuth: auth } });
21+
} catch (e) {
22+
window.showErrorMessage(`HTTPS_PROXY environment variable ignored: ${e.message}`);
23+
return globalAgent;
24+
}
25+
}
26+
27+
const scopes = ['repo'];
28+
29+
async function getSession(): Promise<AuthenticationSession> {
30+
const authenticationSessions = await authentication.getSessions('github', scopes);
31+
32+
if (authenticationSessions.length) {
33+
return await authenticationSessions[0];
34+
} else {
35+
return await authentication.login('github', scopes);
36+
}
37+
}
38+
39+
let _octokit: Promise<Octokit> | undefined;
40+
41+
export function getOctokit(): Promise<Octokit> {
42+
if (!_octokit) {
43+
_octokit = getSession().then(async session => {
44+
const token = await session.getAccessToken();
45+
const agent = getAgent();
46+
47+
return new Octokit({
48+
request: { agent },
49+
userAgent: 'GitHub VSCode',
50+
auth() { return `token ${token}`; }
51+
});
52+
}).then(null, async err => {
53+
_octokit = undefined;
54+
throw err;
55+
});
56+
}
57+
58+
return _octokit;
59+
}
60+
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
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 { RemoteSourceProvider, RemoteSource } from './typings/git';
7+
import { getOctokit } from './octokit';
8+
import { Octokit } from '@octokit/rest';
9+
10+
function asRemoteSource(raw: any): RemoteSource {
11+
return {
12+
name: `$(github) ${raw.full_name}`,
13+
description: raw.description || undefined,
14+
url: raw.clone_url
15+
};
16+
}
17+
18+
export class GithubRemoteSourceProvider implements RemoteSourceProvider {
19+
20+
readonly name = 'GitHub';
21+
readonly icon = 'github';
22+
readonly supportsQuery = true;
23+
24+
private userReposCache: RemoteSource[] = [];
25+
26+
async getRemoteSources(query?: string): Promise<RemoteSource[]> {
27+
const octokit = await getOctokit();
28+
const [fromUser, fromQuery] = await Promise.all([
29+
this.getUserRemoteSources(octokit, query),
30+
this.getQueryRemoteSources(octokit, query)
31+
]);
32+
33+
const userRepos = new Set(fromUser.map(r => r.name));
34+
35+
return [
36+
...fromUser,
37+
...fromQuery.filter(r => !userRepos.has(r.name))
38+
];
39+
}
40+
41+
private async getUserRemoteSources(octokit: Octokit, query?: string): Promise<RemoteSource[]> {
42+
if (!query) {
43+
const res = await octokit.repos.list({ sort: 'pushed', per_page: 100 });
44+
this.userReposCache = res.data.map(asRemoteSource);
45+
}
46+
47+
return this.userReposCache;
48+
}
49+
50+
private async getQueryRemoteSources(octokit: Octokit, query?: string): Promise<RemoteSource[]> {
51+
if (!query) {
52+
return [];
53+
}
54+
55+
const raw = await octokit.search.repos({ q: query, sort: 'updated' });
56+
return raw.data.items.map(asRemoteSource);
57+
}
58+
}

0 commit comments

Comments
 (0)