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:
Karl Burtram
2023-04-19 21:48:46 -07:00
committed by GitHub
parent decbe8dded
commit e7d3d047ec
2389 changed files with 92155 additions and 42602 deletions

View File

@@ -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 });

View File

@@ -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;
}

View File

@@ -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 {

View 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}`;
}

View File

@@ -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;
}

View File

@@ -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;

View File

@@ -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');

View File

@@ -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);
}