mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-01-16 17:22:29 -05:00
Merge from vscode merge-base (#22780)
* Revert "Revert "Merge from vscode merge-base (#22769)" (#22779)"
This reverts commit 47a1745180.
* Fix notebook download task
* Remove done call from extensions-ci
This commit is contained in:
@@ -24,7 +24,7 @@ function getAgent(url: string | undefined = process.env.HTTPS_PROXY): Agent {
|
||||
}
|
||||
}
|
||||
|
||||
const scopes = ['repo', 'workflow'];
|
||||
const scopes = ['repo', 'workflow', 'user:email', 'read:user'];
|
||||
|
||||
export async function getSession(): Promise<AuthenticationSession> {
|
||||
return await authentication.getSession('github', scopes, { createIfNone: true });
|
||||
|
||||
@@ -7,6 +7,32 @@ import * as vscode from 'vscode';
|
||||
import { API as GitAPI } from './typings/git';
|
||||
import { publishRepository } from './publish';
|
||||
import { DisposableStore } from './util';
|
||||
import { getPermalink } from './links';
|
||||
|
||||
function getVscodeDevHost(): string {
|
||||
return `https://${vscode.env.appName.toLowerCase().includes('insiders') ? 'insiders.' : ''}vscode.dev/github`;
|
||||
}
|
||||
|
||||
async function copyVscodeDevLink(gitAPI: GitAPI, useSelection: boolean) {
|
||||
try {
|
||||
const permalink = getPermalink(gitAPI, useSelection, getVscodeDevHost());
|
||||
if (permalink) {
|
||||
return vscode.env.clipboard.writeText(permalink);
|
||||
}
|
||||
} catch (err) {
|
||||
vscode.window.showErrorMessage(err.message);
|
||||
}
|
||||
}
|
||||
|
||||
async function openVscodeDevLink(gitAPI: GitAPI): Promise<vscode.Uri | undefined> {
|
||||
try {
|
||||
const permalink = getPermalink(gitAPI, true, getVscodeDevHost());
|
||||
return permalink ? vscode.Uri.parse(permalink) : undefined;
|
||||
} catch (err) {
|
||||
vscode.window.showErrorMessage(err.message);
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
export function registerCommands(gitAPI: GitAPI): vscode.Disposable {
|
||||
const disposables = new DisposableStore();
|
||||
@@ -19,5 +45,17 @@ export function registerCommands(gitAPI: GitAPI): vscode.Disposable {
|
||||
}
|
||||
}));
|
||||
|
||||
disposables.add(vscode.commands.registerCommand('github.copyVscodeDevLink', async () => {
|
||||
return copyVscodeDevLink(gitAPI, true);
|
||||
}));
|
||||
|
||||
disposables.add(vscode.commands.registerCommand('github.copyVscodeDevLinkFile', async () => {
|
||||
return copyVscodeDevLink(gitAPI, false);
|
||||
}));
|
||||
|
||||
disposables.add(vscode.commands.registerCommand('github.openOnVscodeDev', async () => {
|
||||
return openVscodeDevLink(gitAPI);
|
||||
}));
|
||||
|
||||
return disposables;
|
||||
}
|
||||
|
||||
@@ -5,10 +5,10 @@
|
||||
|
||||
import { commands, Disposable, ExtensionContext, extensions } from 'vscode';
|
||||
import { GithubRemoteSourceProvider } from './remoteSourceProvider';
|
||||
import { GitExtension } from './typings/git';
|
||||
import { API, GitExtension } from './typings/git';
|
||||
import { registerCommands } from './commands';
|
||||
import { GithubCredentialProviderManager } from './credentialProvider';
|
||||
import { DisposableStore } from './util';
|
||||
import { DisposableStore, repositoryHasGitHubRemote } from './util';
|
||||
import { GithubPushErrorHandler } from './pushErrorHandler';
|
||||
import { GitBaseExtension } from './typings/git-base';
|
||||
import { GithubRemoteSourcePublisher } from './remoteSourcePublisher';
|
||||
@@ -48,6 +48,21 @@ function initializeGitBaseExtension(): Disposable {
|
||||
return disposables;
|
||||
}
|
||||
|
||||
function setGitHubContext(gitAPI: API, disposables: DisposableStore) {
|
||||
if (gitAPI.repositories.find(repo => repositoryHasGitHubRemote(repo))) {
|
||||
commands.executeCommand('setContext', 'github.hasGitHubRepo', true);
|
||||
} else {
|
||||
const openRepoDisposable = gitAPI.onDidOpenRepository(async e => {
|
||||
await e.status();
|
||||
if (repositoryHasGitHubRemote(e)) {
|
||||
commands.executeCommand('setContext', 'github.hasGitHubRepo', true);
|
||||
openRepoDisposable.dispose();
|
||||
}
|
||||
});
|
||||
disposables.add(openRepoDisposable);
|
||||
}
|
||||
}
|
||||
|
||||
function initializeGitExtension(): Disposable {
|
||||
const disposables = new DisposableStore();
|
||||
|
||||
@@ -64,6 +79,7 @@ function initializeGitExtension(): Disposable {
|
||||
disposables.add(new GithubCredentialProviderManager(gitAPI));
|
||||
disposables.add(gitAPI.registerPushErrorHandler(new GithubPushErrorHandler()));
|
||||
disposables.add(gitAPI.registerRemoteSourcePublisher(new GithubRemoteSourcePublisher(gitAPI)));
|
||||
setGitHubContext(gitAPI, disposables);
|
||||
|
||||
commands.executeCommand('setContext', 'git-base.gitEnabled', true);
|
||||
} else {
|
||||
|
||||
135
extensions/github/src/links.ts
Normal file
135
extensions/github/src/links.ts
Normal file
@@ -0,0 +1,135 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as vscode from 'vscode';
|
||||
import { API as GitAPI, Repository } from './typings/git';
|
||||
import { getRepositoryFromUrl } from './util';
|
||||
|
||||
export function isFileInRepo(repository: Repository, file: vscode.Uri): boolean {
|
||||
return file.path.toLowerCase() === repository.rootUri.path.toLowerCase() ||
|
||||
(file.path.toLowerCase().startsWith(repository.rootUri.path.toLowerCase()) &&
|
||||
file.path.substring(repository.rootUri.path.length).startsWith('/'));
|
||||
}
|
||||
|
||||
export function getRepositoryForFile(gitAPI: GitAPI, file: vscode.Uri): Repository | undefined {
|
||||
for (const repository of gitAPI.repositories) {
|
||||
if (isFileInRepo(repository, file)) {
|
||||
return repository;
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
enum LinkType {
|
||||
File = 1,
|
||||
Notebook = 2
|
||||
}
|
||||
|
||||
interface IFilePosition {
|
||||
type: LinkType.File;
|
||||
uri: vscode.Uri;
|
||||
range: vscode.Range | undefined;
|
||||
}
|
||||
|
||||
interface INotebookPosition {
|
||||
type: LinkType.Notebook;
|
||||
uri: vscode.Uri;
|
||||
cellIndex: number;
|
||||
range: vscode.Range | undefined;
|
||||
}
|
||||
|
||||
function getFileAndPosition(): IFilePosition | INotebookPosition | undefined {
|
||||
let uri: vscode.Uri | undefined;
|
||||
let range: vscode.Range | undefined;
|
||||
if (vscode.window.activeTextEditor) {
|
||||
uri = vscode.window.activeTextEditor.document.uri;
|
||||
|
||||
if (uri.scheme === 'vscode-notebook-cell' && vscode.window.activeNotebookEditor?.notebook.uri.fsPath === uri.fsPath) {
|
||||
// if the active editor is a notebook editor and the focus is inside any a cell text editor
|
||||
// generate deep link for text selection for the notebook cell.
|
||||
const cell = vscode.window.activeNotebookEditor.notebook.getCells().find(cell => cell.document.uri.fragment === uri?.fragment);
|
||||
const cellIndex = cell?.index ?? vscode.window.activeNotebookEditor.selection.start;
|
||||
const range = cell !== undefined ? vscode.window.activeTextEditor.selection : undefined;
|
||||
return { type: LinkType.Notebook, uri, cellIndex, range };
|
||||
} else {
|
||||
// the active editor is a text editor
|
||||
range = vscode.window.activeTextEditor.selection;
|
||||
return { type: LinkType.File, uri, range };
|
||||
}
|
||||
}
|
||||
|
||||
if (vscode.window.activeNotebookEditor) {
|
||||
// if the active editor is a notebook editor but the focus is not inside any cell text editor, generate deep link for the cell selection in the notebook document.
|
||||
return { type: LinkType.Notebook, uri: vscode.window.activeNotebookEditor.notebook.uri, cellIndex: vscode.window.activeNotebookEditor.selection.start, range: undefined };
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function rangeString(range: vscode.Range | undefined) {
|
||||
if (!range) {
|
||||
return '';
|
||||
}
|
||||
let hash = `#L${range.start.line + 1}`;
|
||||
if (range.start.line !== range.end.line) {
|
||||
hash += `-L${range.end.line + 1}`;
|
||||
}
|
||||
return hash;
|
||||
}
|
||||
|
||||
export function notebookCellRangeString(index: number | undefined, range: vscode.Range | undefined) {
|
||||
if (index === undefined) {
|
||||
return '';
|
||||
}
|
||||
|
||||
if (!range) {
|
||||
return `#C${index + 1}`;
|
||||
}
|
||||
|
||||
let hash = `#C${index + 1}:L${range.start.line + 1}`;
|
||||
if (range.start.line !== range.end.line) {
|
||||
hash += `-L${range.end.line + 1}`;
|
||||
}
|
||||
return hash;
|
||||
}
|
||||
|
||||
export function getPermalink(gitAPI: GitAPI, useSelection: boolean, hostPrefix?: string): string | undefined {
|
||||
hostPrefix = hostPrefix ?? 'https://github.com';
|
||||
const fileAndPosition = getFileAndPosition();
|
||||
if (!fileAndPosition) {
|
||||
return;
|
||||
}
|
||||
const uri = fileAndPosition.uri;
|
||||
|
||||
// Use the first repo if we cannot determine a repo from the uri.
|
||||
const gitRepo = (uri ? getRepositoryForFile(gitAPI, uri) : gitAPI.repositories[0]) ?? gitAPI.repositories[0];
|
||||
if (!gitRepo) {
|
||||
return;
|
||||
}
|
||||
let repo: { owner: string; repo: string } | undefined;
|
||||
gitRepo.state.remotes.find(remote => {
|
||||
if (remote.fetchUrl) {
|
||||
const foundRepo = getRepositoryFromUrl(remote.fetchUrl);
|
||||
if (foundRepo && (remote.name === gitRepo.state.HEAD?.upstream?.remote)) {
|
||||
repo = foundRepo;
|
||||
return;
|
||||
} else if (foundRepo && !repo) {
|
||||
repo = foundRepo;
|
||||
}
|
||||
}
|
||||
return;
|
||||
});
|
||||
if (!repo) {
|
||||
return;
|
||||
}
|
||||
|
||||
const commitHash = (gitRepo.state.HEAD?.ahead === 0) ? `/blob/${gitRepo.state.HEAD?.commit}` : '';
|
||||
const fileSegments = fileAndPosition.type === LinkType.File
|
||||
? (useSelection ? `${uri.path.substring(gitRepo.rootUri.path.length)}${rangeString(fileAndPosition.range)}` : '')
|
||||
: (useSelection ? `${uri.path.substring(gitRepo.rootUri.path.length)}${notebookCellRangeString(fileAndPosition.cellIndex, fileAndPosition.range)}` : '');
|
||||
|
||||
return `${hostPrefix}/${repo.owner}/${repo.repo}${commitHash
|
||||
}${fileSegments}`;
|
||||
}
|
||||
@@ -21,8 +21,9 @@ export function isInCodespaces(): boolean {
|
||||
async function handlePushError(repository: Repository, remote: Remote, refspec: string, owner: string, repo: string): Promise<void> {
|
||||
const yes = localize('create a fork', "Create Fork");
|
||||
const no = localize('no', "No");
|
||||
const askFork = localize('fork', "You don't have permissions to push to '{0}/{1}' on GitHub. Would you like to create a fork and push to it instead?", owner, repo);
|
||||
|
||||
const answer = await window.showInformationMessage(localize('fork', "You don't have permissions to push to '{0}/{1}' on GitHub. Would you like to create a fork and push to it instead?", owner, repo), yes, no);
|
||||
const answer = await window.showInformationMessage(askFork, yes, no);
|
||||
if (answer !== yes) {
|
||||
return;
|
||||
}
|
||||
@@ -102,18 +103,19 @@ async function handlePushError(repository: Repository, remote: Remote, refspec:
|
||||
let title = `Update ${remoteName}`;
|
||||
const head = repository.state.HEAD?.name;
|
||||
|
||||
let body: string | undefined;
|
||||
|
||||
if (head) {
|
||||
const commit = await repository.getCommit(head);
|
||||
title = commit.message.replace(/\n.*$/m, '');
|
||||
title = commit.message.split('\n')[0];
|
||||
body = commit.message.slice(title.length + 1).trim();
|
||||
}
|
||||
|
||||
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);
|
||||
const template = await pickPullRequestTemplate(repository.rootUri, templates);
|
||||
|
||||
if (template) {
|
||||
body = new TextDecoder('utf-8').decode(await workspace.fs.readFile(template));
|
||||
@@ -190,8 +192,8 @@ export async function findPullRequestTemplates(repositoryRootUri: Uri): Promise<
|
||||
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 });
|
||||
export async function pickPullRequestTemplate(repositoryRootUri: Uri, templates: Uri[]): Promise<Uri | undefined> {
|
||||
const quickPickItemFromUri = (x: Uri) => ({ label: path.relative(repositoryRootUri.path, x.path), template: x });
|
||||
const quickPickItems = [
|
||||
{
|
||||
label: localize('no pr template', "No template"),
|
||||
@@ -201,7 +203,8 @@ export async function pickPullRequestTemplate(templates: Uri[]): Promise<Uri | u
|
||||
...templates.map(quickPickItemFromUri)
|
||||
];
|
||||
const quickPickOptions: QuickPickOptions = {
|
||||
placeHolder: localize('select pr template', "Select the Pull Request template")
|
||||
placeHolder: localize('select pr template', "Select the Pull Request template"),
|
||||
ignoreFocusOut: true
|
||||
};
|
||||
const pickedTemplate = await window.showQuickPick(quickPickItems, quickPickOptions);
|
||||
return pickedTemplate?.template;
|
||||
@@ -219,7 +222,7 @@ export class GithubPushErrorHandler implements PushErrorHandler {
|
||||
return false;
|
||||
}
|
||||
|
||||
const match = /^(?:https:\/\/github\.com\/|git@github\.com:)([^/]+)\/([^/.]+)(?:\.git)?$/i.exec(remoteUrl);
|
||||
const match = /^(?:https:\/\/github\.com\/|git@github\.com:)([^\/]+)\/([^\/.]+)/i.exec(remoteUrl);
|
||||
if (!match) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -7,17 +7,7 @@ import { workspace } from 'vscode';
|
||||
import { RemoteSourceProvider, RemoteSource } from './typings/git-base';
|
||||
import { getOctokit } from './auth';
|
||||
import { Octokit } from '@octokit/rest';
|
||||
|
||||
function getRepositoryFromUrl(url: string): { owner: string; repo: string } | undefined {
|
||||
const match = /^https:\/\/github\.com\/([^/]+)\/([^/]+)\.git/i.exec(url)
|
||||
|| /^git@github\.com:([^/]+)\/([^/]+)\.git/i.exec(url);
|
||||
return match ? { owner: match[1], repo: match[2] } : undefined;
|
||||
}
|
||||
|
||||
function getRepositoryFromQuery(query: string): { owner: string; repo: string } | undefined {
|
||||
const match = /^([^/]+)\/([^/]+)$/i.exec(query);
|
||||
return match ? { owner: match[1], repo: match[2] } : undefined;
|
||||
}
|
||||
import { getRepositoryFromQuery, getRepositoryFromUrl } from './util';
|
||||
|
||||
function asRemoteSource(raw: any): RemoteSource {
|
||||
const protocol = workspace.getConfiguration('github').get<'https' | 'ssh'>('gitProtocol');
|
||||
@@ -107,7 +97,7 @@ export class GithubRemoteSourceProvider implements RemoteSourceProvider {
|
||||
let page = 1;
|
||||
|
||||
while (true) {
|
||||
let res = await octokit.repos.listBranches({ ...repository, per_page: 100, page });
|
||||
const res = await octokit.repos.listBranches({ ...repository, per_page: 100, page });
|
||||
|
||||
if (res.data.length === 0) {
|
||||
break;
|
||||
|
||||
@@ -18,15 +18,15 @@ suite('github smoke test', function () {
|
||||
|
||||
test('should find all templates', async function () {
|
||||
const expectedValuesSorted = [
|
||||
'/PULL_REQUEST_TEMPLATE/a.md',
|
||||
'/PULL_REQUEST_TEMPLATE/b.md',
|
||||
'/docs/PULL_REQUEST_TEMPLATE.md',
|
||||
'/docs/PULL_REQUEST_TEMPLATE/a.md',
|
||||
'/docs/PULL_REQUEST_TEMPLATE/b.md',
|
||||
'/.github/PULL_REQUEST_TEMPLATE.md',
|
||||
'/.github/PULL_REQUEST_TEMPLATE/a.md',
|
||||
'/.github/PULL_REQUEST_TEMPLATE/b.md',
|
||||
'/PULL_REQUEST_TEMPLATE.md'
|
||||
'PULL_REQUEST_TEMPLATE/a.md',
|
||||
'PULL_REQUEST_TEMPLATE/b.md',
|
||||
'docs/PULL_REQUEST_TEMPLATE.md',
|
||||
'docs/PULL_REQUEST_TEMPLATE/a.md',
|
||||
'docs/PULL_REQUEST_TEMPLATE/b.md',
|
||||
'.github/PULL_REQUEST_TEMPLATE.md',
|
||||
'.github/PULL_REQUEST_TEMPLATE/a.md',
|
||||
'.github/PULL_REQUEST_TEMPLATE/b.md',
|
||||
'PULL_REQUEST_TEMPLATE.md'
|
||||
];
|
||||
expectedValuesSorted.sort();
|
||||
|
||||
@@ -43,7 +43,7 @@ suite('github smoke test', function () {
|
||||
const template1 = Uri.file("some-imaginary-template-1");
|
||||
const templates = [template0, template1];
|
||||
|
||||
const pick = pickPullRequestTemplate(templates);
|
||||
const pick = pickPullRequestTemplate(Uri.file("/"), templates);
|
||||
|
||||
await commands.executeCommand('workbench.action.quickOpenSelectNext');
|
||||
await commands.executeCommand('workbench.action.quickOpenSelectNext');
|
||||
@@ -55,7 +55,7 @@ suite('github smoke test', function () {
|
||||
test('selecting first quick-pick item should return undefined', async () => {
|
||||
const templates = [Uri.file("some-imaginary-file")];
|
||||
|
||||
const pick = pickPullRequestTemplate(templates);
|
||||
const pick = pickPullRequestTemplate(Uri.file("/"), templates);
|
||||
|
||||
await commands.executeCommand('workbench.action.quickOpenSelectNext');
|
||||
await commands.executeCommand('workbench.action.acceptSelectedQuickOpenItem');
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as vscode from 'vscode';
|
||||
import { Repository } from './typings/git';
|
||||
|
||||
export class DisposableStore {
|
||||
|
||||
@@ -21,3 +22,18 @@ export class DisposableStore {
|
||||
this.disposables.clear();
|
||||
}
|
||||
}
|
||||
|
||||
export function getRepositoryFromUrl(url: string): { owner: string; repo: string } | undefined {
|
||||
const match = /^https:\/\/github\.com\/([^/]+)\/([^/]+?)(\.git)?$/i.exec(url)
|
||||
|| /^git@github\.com:([^/]+)\/([^/]+?)(\.git)?$/i.exec(url);
|
||||
return match ? { owner: match[1], repo: match[2] } : undefined;
|
||||
}
|
||||
|
||||
export function getRepositoryFromQuery(query: string): { owner: string; repo: string } | undefined {
|
||||
const match = /^([^/]+)\/([^/]+)$/i.exec(query);
|
||||
return match ? { owner: match[1], repo: match[2] } : undefined;
|
||||
}
|
||||
|
||||
export function repositoryHasGitHubRemote(repository: Repository) {
|
||||
return !!repository.state.remotes.find(remote => remote.fetchUrl ? getRepositoryFromUrl(remote.fetchUrl) : undefined);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user