Git - extract history item detail provider (#238041)
* Initial refactor of hover commands * Delete old code for hover commands * More refactoringpull/238045/head
parent
47cff90e28
commit
eaba97f995
|
@ -3,9 +3,9 @@
|
|||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { Command, Disposable, commands } from 'vscode';
|
||||
import { Disposable, commands } from 'vscode';
|
||||
import { Model } from '../model';
|
||||
import { getRemoteSourceActions, getRemoteSourceControlHistoryItemCommands, pickRemoteSource, provideRemoteSourceLinks } from '../remoteSource';
|
||||
import { getRemoteSourceActions, pickRemoteSource } from '../remoteSource';
|
||||
import { GitBaseExtensionImpl } from './extension';
|
||||
import { API, PickRemoteSourceOptions, PickRemoteSourceResult, RemoteSourceAction, RemoteSourceProvider } from './git-base';
|
||||
|
||||
|
@ -21,14 +21,6 @@ export class ApiImpl implements API {
|
|||
return getRemoteSourceActions(this._model, url);
|
||||
}
|
||||
|
||||
getRemoteSourceControlHistoryItemCommands(url: string): Promise<Command[] | undefined> {
|
||||
return getRemoteSourceControlHistoryItemCommands(this._model, url);
|
||||
}
|
||||
|
||||
provideRemoteSourceLinks(url: string, content: string): Promise<string> {
|
||||
return provideRemoteSourceLinks(this._model, url, content);
|
||||
}
|
||||
|
||||
registerRemoteSourceProvider(provider: RemoteSourceProvider): Disposable {
|
||||
return this._model.registerRemoteSourceProvider(provider);
|
||||
}
|
||||
|
|
|
@ -9,8 +9,6 @@ export { ProviderResult } from 'vscode';
|
|||
export interface API {
|
||||
registerRemoteSourceProvider(provider: RemoteSourceProvider): Disposable;
|
||||
getRemoteSourceActions(url: string): Promise<RemoteSourceAction[]>;
|
||||
getRemoteSourceControlHistoryItemCommands(url: string): Promise<Command[] | undefined>;
|
||||
provideRemoteSourceLinks(url: string, content: string): Promise<string | undefined>;
|
||||
pickRemoteSource(options: PickRemoteSourceOptions): Promise<string | PickRemoteSourceResult | undefined>;
|
||||
}
|
||||
|
||||
|
@ -83,8 +81,6 @@ export interface RemoteSourceProvider {
|
|||
|
||||
getBranches?(url: string): ProviderResult<string[]>;
|
||||
getRemoteSourceActions?(url: string): ProviderResult<RemoteSourceAction[]>;
|
||||
getRemoteSourceControlHistoryItemCommands?(url: string): ProviderResult<Command[]>;
|
||||
getRecentRemoteSources?(query?: string): ProviderResult<RecentRemoteSource[]>;
|
||||
getRemoteSources(query?: string): ProviderResult<RemoteSource[]>;
|
||||
provideRemoteSourceLinks?(url: string, content: string): Promise<string | undefined>;
|
||||
}
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { QuickPickItem, window, QuickPick, QuickPickItemKind, l10n, Disposable, Command } from 'vscode';
|
||||
import { QuickPickItem, window, QuickPick, QuickPickItemKind, l10n, Disposable } from 'vscode';
|
||||
import { RemoteSourceProvider, RemoteSource, PickRemoteSourceOptions, PickRemoteSourceResult, RemoteSourceAction } from './api/git-base';
|
||||
import { Model } from './model';
|
||||
import { throttle, debounce } from './decorators';
|
||||
|
@ -123,32 +123,6 @@ export async function getRemoteSourceActions(model: Model, url: string): Promise
|
|||
return remoteSourceActions;
|
||||
}
|
||||
|
||||
export async function getRemoteSourceControlHistoryItemCommands(model: Model, url: string): Promise<Command[] | undefined> {
|
||||
const providers = model.getRemoteProviders();
|
||||
|
||||
const remoteSourceCommands = [];
|
||||
for (const provider of providers) {
|
||||
remoteSourceCommands.push(...(await provider.getRemoteSourceControlHistoryItemCommands?.(url) ?? []));
|
||||
}
|
||||
|
||||
return remoteSourceCommands.length > 0 ? remoteSourceCommands : undefined;
|
||||
}
|
||||
|
||||
export async function provideRemoteSourceLinks(model: Model, url: string, content: string): Promise<string> {
|
||||
const providers = model.getRemoteProviders();
|
||||
|
||||
for (const provider of providers) {
|
||||
const parsedContent = await provider.provideRemoteSourceLinks?.(url, content);
|
||||
if (!parsedContent) {
|
||||
continue;
|
||||
}
|
||||
|
||||
content = parsedContent;
|
||||
}
|
||||
|
||||
return content;
|
||||
}
|
||||
|
||||
export async function pickRemoteSource(model: Model, options: PickRemoteSourceOptions & { branch?: false | undefined }): Promise<string | undefined>;
|
||||
export async function pickRemoteSource(model: Model, options: PickRemoteSourceOptions & { branch: true }): Promise<PickRemoteSourceResult | undefined>;
|
||||
export async function pickRemoteSource(model: Model, options: PickRemoteSourceOptions = {}): Promise<string | PickRemoteSourceResult | undefined> {
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
|
||||
import { Model } from '../model';
|
||||
import { Repository as BaseRepository, Resource } from '../repository';
|
||||
import { InputBox, Git, API, Repository, Remote, RepositoryState, Branch, ForcePushMode, Ref, Submodule, Commit, Change, RepositoryUIState, Status, LogOptions, APIState, CommitOptions, RefType, CredentialsProvider, BranchQuery, PushErrorHandler, PublishEvent, FetchOptions, RemoteSourceProvider, RemoteSourcePublisher, PostCommitCommandsProvider, RefQuery, BranchProtectionProvider, InitOptions } from './git';
|
||||
import { InputBox, Git, API, Repository, Remote, RepositoryState, Branch, ForcePushMode, Ref, Submodule, Commit, Change, RepositoryUIState, Status, LogOptions, APIState, CommitOptions, RefType, CredentialsProvider, BranchQuery, PushErrorHandler, PublishEvent, FetchOptions, RemoteSourceProvider, RemoteSourcePublisher, PostCommitCommandsProvider, RefQuery, BranchProtectionProvider, InitOptions, SourceControlHistoryItemDetailProvider } from './git';
|
||||
import { Event, SourceControlInputBox, Uri, SourceControl, Disposable, commands, CancellationToken } from 'vscode';
|
||||
import { combinedDisposable, filterEvent, mapEvent } from '../util';
|
||||
import { toGitUri } from '../uri';
|
||||
|
@ -414,6 +414,10 @@ export class ApiImpl implements API {
|
|||
return this.#model.registerPushErrorHandler(handler);
|
||||
}
|
||||
|
||||
registerSourceControlHistoryItemDetailProvider(provider: SourceControlHistoryItemDetailProvider): Disposable {
|
||||
return this.#model.registerSourceControlHistoryItemDetailProvider(provider);
|
||||
}
|
||||
|
||||
registerBranchProtectionProvider(root: Uri, provider: BranchProtectionProvider): Disposable {
|
||||
return this.#model.registerBranchProtectionProvider(root, provider);
|
||||
}
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { Uri, Event, Disposable, ProviderResult, Command, CancellationToken } from 'vscode';
|
||||
import { Uri, Event, Disposable, ProviderResult, Command, CancellationToken, SourceControlHistoryItem } from 'vscode';
|
||||
export { ProviderResult } from 'vscode';
|
||||
|
||||
export interface Git {
|
||||
|
@ -326,6 +326,11 @@ export interface BranchProtectionProvider {
|
|||
provideBranchProtection(): BranchProtection[];
|
||||
}
|
||||
|
||||
export interface SourceControlHistoryItemDetailProvider {
|
||||
provideHoverCommands(repository: Repository): ProviderResult<Command[]>;
|
||||
provideMessageLinks(repository: Repository, message: string): ProviderResult<string>;
|
||||
}
|
||||
|
||||
export type APIState = 'uninitialized' | 'initialized';
|
||||
|
||||
export interface PublishEvent {
|
||||
|
@ -353,6 +358,7 @@ export interface API {
|
|||
registerPostCommitCommandsProvider(provider: PostCommitCommandsProvider): Disposable;
|
||||
registerPushErrorHandler(handler: PushErrorHandler): Disposable;
|
||||
registerBranchProtectionProvider(root: Uri, provider: BranchProtectionProvider): Disposable;
|
||||
registerSourceControlHistoryItemDetailProvider(provider: SourceControlHistoryItemDetailProvider): Disposable;
|
||||
}
|
||||
|
||||
export interface GitExtension {
|
||||
|
|
|
@ -12,7 +12,7 @@ import { BlameInformation, Commit } from './git';
|
|||
import { fromGitUri, isGitUri } from './uri';
|
||||
import { emojify, ensureEmojis } from './emoji';
|
||||
import { getWorkingTreeAndIndexDiffInformation, getWorkingTreeDiffInformation } from './staging';
|
||||
import { getRemoteSourceControlHistoryItemCommands, provideRemoteSourceLinks } from './remoteSource';
|
||||
import { provideSourceControlHistoryItemHoverCommands, provideSourceControlHistoryItemMessageLinks } from './historyItemDetailProvider';
|
||||
|
||||
function lineRangesContainLine(changes: readonly TextEditorChange[], lineNumber: number): boolean {
|
||||
return changes.some(c => c.modified.startLineNumber <= lineNumber && lineNumber < c.modified.endLineNumberExclusive);
|
||||
|
@ -204,9 +204,9 @@ export class GitBlameController {
|
|||
}
|
||||
|
||||
async getBlameInformationHover(documentUri: Uri, blameInformation: BlameInformation, includeCommitDetails = false): Promise<MarkdownString> {
|
||||
const remoteHoverCommands: Command[] = [];
|
||||
let commitInformation: Commit | undefined;
|
||||
let commitMessageWithLinks: string | undefined;
|
||||
const remoteSourceCommands: Command[] = [];
|
||||
|
||||
const repository = this._model.getRepository(documentUri);
|
||||
if (repository) {
|
||||
|
@ -217,16 +217,15 @@ export class GitBlameController {
|
|||
} catch { }
|
||||
}
|
||||
|
||||
// Remote commands
|
||||
// Remote hover commands
|
||||
const unpublishedCommits = await repository.getUnpublishedCommits();
|
||||
if (!unpublishedCommits.has(blameInformation.hash)) {
|
||||
remoteSourceCommands.push(...await getRemoteSourceControlHistoryItemCommands(repository));
|
||||
remoteHoverCommands.push(...await provideSourceControlHistoryItemHoverCommands(this._model, repository) ?? []);
|
||||
}
|
||||
|
||||
// Link provider
|
||||
commitMessageWithLinks = await provideRemoteSourceLinks(
|
||||
repository,
|
||||
commitInformation?.message ?? blameInformation.subject ?? '');
|
||||
// Message links
|
||||
commitMessageWithLinks = await provideSourceControlHistoryItemMessageLinks(
|
||||
this._model, repository, commitInformation?.message ?? blameInformation.subject ?? '');
|
||||
}
|
||||
|
||||
const markdownString = new MarkdownString();
|
||||
|
@ -289,11 +288,11 @@ export class GitBlameController {
|
|||
markdownString.appendMarkdown(' ');
|
||||
markdownString.appendMarkdown(`[$(copy)](command:git.copyContentToClipboard?${encodeURIComponent(JSON.stringify(hash))} "${l10n.t('Copy Commit Hash')}")`);
|
||||
|
||||
// Remote commands
|
||||
if (remoteSourceCommands.length > 0) {
|
||||
// Remote hover commands
|
||||
if (remoteHoverCommands.length > 0) {
|
||||
markdownString.appendMarkdown(' | ');
|
||||
|
||||
const remoteCommandsMarkdown = remoteSourceCommands
|
||||
const remoteCommandsMarkdown = remoteHoverCommands
|
||||
.map(command => `[${command.title}](command:${command.command}?${encodeURIComponent(JSON.stringify([...command.arguments ?? [], hash]))} "${command.tooltip}")`);
|
||||
markdownString.appendMarkdown(remoteCommandsMarkdown.join(' '));
|
||||
}
|
||||
|
|
|
@ -0,0 +1,46 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { Command, Disposable } from 'vscode';
|
||||
import { SourceControlHistoryItemDetailProvider } from './api/git';
|
||||
import { Repository } from './repository';
|
||||
import { ApiRepository } from './api/api1';
|
||||
|
||||
export interface ISourceControlHistoryItemDetailProviderRegistry {
|
||||
registerSourceControlHistoryItemDetailProvider(provider: SourceControlHistoryItemDetailProvider): Disposable;
|
||||
getSourceControlHistoryItemDetailProviders(): SourceControlHistoryItemDetailProvider[];
|
||||
}
|
||||
|
||||
export async function provideSourceControlHistoryItemHoverCommands(
|
||||
registry: ISourceControlHistoryItemDetailProviderRegistry,
|
||||
repository: Repository
|
||||
): Promise<Command[] | undefined> {
|
||||
for (const provider of registry.getSourceControlHistoryItemDetailProviders()) {
|
||||
const result = await provider.provideHoverCommands(new ApiRepository(repository));
|
||||
|
||||
if (result) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
export async function provideSourceControlHistoryItemMessageLinks(
|
||||
registry: ISourceControlHistoryItemDetailProviderRegistry,
|
||||
repository: Repository,
|
||||
message: string
|
||||
): Promise<string | undefined> {
|
||||
for (const provider of registry.getSourceControlHistoryItemDetailProviders()) {
|
||||
const result = await provider.provideMessageLinks(
|
||||
new ApiRepository(repository), message);
|
||||
|
||||
if (result) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
|
@ -12,7 +12,7 @@ import { Branch, LogOptions, Ref, RefType } from './api/git';
|
|||
import { emojify, ensureEmojis } from './emoji';
|
||||
import { Commit } from './git';
|
||||
import { OperationKind, OperationResult } from './operation';
|
||||
import { provideRemoteSourceLinks } from './remoteSource';
|
||||
import { ISourceControlHistoryItemDetailProviderRegistry, provideSourceControlHistoryItemMessageLinks } from './historyItemDetailProvider';
|
||||
|
||||
function toSourceControlHistoryItemRef(repository: Repository, ref: Ref): SourceControlHistoryItemRef {
|
||||
const rootUri = Uri.file(repository.root);
|
||||
|
@ -97,7 +97,11 @@ export class GitHistoryProvider implements SourceControlHistoryProvider, FileDec
|
|||
|
||||
private disposables: Disposable[] = [];
|
||||
|
||||
constructor(protected readonly repository: Repository, private readonly logger: LogOutputChannel) {
|
||||
constructor(
|
||||
private historyItemDetailProviderRegistry: ISourceControlHistoryItemDetailProviderRegistry,
|
||||
private readonly repository: Repository,
|
||||
private readonly logger: LogOutputChannel
|
||||
) {
|
||||
const onDidRunWriteOperation = filterEvent(repository.onDidRunOperation, e => !e.operation.readOnly);
|
||||
this.disposables.push(onDidRunWriteOperation(this.onDidRunWriteOperation, this));
|
||||
|
||||
|
@ -268,7 +272,8 @@ export class GitHistoryProvider implements SourceControlHistoryProvider, FileDec
|
|||
const historyItems: SourceControlHistoryItem[] = [];
|
||||
for (const commit of commits) {
|
||||
const message = emojify(commit.message);
|
||||
const messageWithLinks = await provideRemoteSourceLinks(this.repository, message) ?? message;
|
||||
const messageWithLinks = await provideSourceControlHistoryItemMessageLinks(
|
||||
this.historyItemDetailProviderRegistry, this.repository, message) ?? message;
|
||||
|
||||
const newLineIndex = message.indexOf('\n');
|
||||
const subject = newLineIndex !== -1
|
||||
|
|
|
@ -12,13 +12,14 @@ import { Git } from './git';
|
|||
import * as path from 'path';
|
||||
import * as fs from 'fs';
|
||||
import { fromGitUri } from './uri';
|
||||
import { APIState as State, CredentialsProvider, PushErrorHandler, PublishEvent, RemoteSourcePublisher, PostCommitCommandsProvider, BranchProtectionProvider } from './api/git';
|
||||
import { APIState as State, CredentialsProvider, PushErrorHandler, PublishEvent, RemoteSourcePublisher, PostCommitCommandsProvider, BranchProtectionProvider, SourceControlHistoryItemDetailProvider } from './api/git';
|
||||
import { Askpass } from './askpass';
|
||||
import { IPushErrorHandlerRegistry } from './pushError';
|
||||
import { ApiRepository } from './api/api1';
|
||||
import { IRemoteSourcePublisherRegistry } from './remotePublisher';
|
||||
import { IPostCommitCommandsProviderRegistry } from './postCommitCommands';
|
||||
import { IBranchProtectionProviderRegistry } from './branchProtection';
|
||||
import { ISourceControlHistoryItemDetailProviderRegistry } from './historyItemDetailProvider';
|
||||
|
||||
class RepositoryPick implements QuickPickItem {
|
||||
@memoize get label(): string {
|
||||
|
@ -170,7 +171,7 @@ class UnsafeRepositoriesManager {
|
|||
}
|
||||
}
|
||||
|
||||
export class Model implements IRepositoryResolver, IBranchProtectionProviderRegistry, IRemoteSourcePublisherRegistry, IPostCommitCommandsProviderRegistry, IPushErrorHandlerRegistry {
|
||||
export class Model implements IRepositoryResolver, IBranchProtectionProviderRegistry, IRemoteSourcePublisherRegistry, IPostCommitCommandsProviderRegistry, IPushErrorHandlerRegistry, ISourceControlHistoryItemDetailProviderRegistry {
|
||||
|
||||
private _onDidOpenRepository = new EventEmitter<Repository>();
|
||||
readonly onDidOpenRepository: Event<Repository> = this._onDidOpenRepository.event;
|
||||
|
@ -236,6 +237,7 @@ export class Model implements IRepositoryResolver, IBranchProtectionProviderRegi
|
|||
readonly onDidChangeBranchProtectionProviders = this._onDidChangeBranchProtectionProviders.event;
|
||||
|
||||
private pushErrorHandlers = new Set<PushErrorHandler>();
|
||||
private historyItemDetailProviders = new Set<SourceControlHistoryItemDetailProvider>();
|
||||
|
||||
private _unsafeRepositoriesManager: UnsafeRepositoriesManager;
|
||||
get unsafeRepositories(): string[] {
|
||||
|
@ -633,7 +635,7 @@ export class Model implements IRepositoryResolver, IBranchProtectionProviderRegi
|
|||
|
||||
// Open repository
|
||||
const [dotGit, repositoryRootRealPath] = await Promise.all([this.git.getRepositoryDotGit(repositoryRoot), this.getRepositoryRootRealPath(repositoryRoot)]);
|
||||
const repository = new Repository(this.git.open(repositoryRoot, repositoryRootRealPath, dotGit, this.logger), this, this, this, this, this, this.globalState, this.logger, this.telemetryReporter);
|
||||
const repository = new Repository(this.git.open(repositoryRoot, repositoryRootRealPath, dotGit, this.logger), this, this, this, this, this, this, this.globalState, this.logger, this.telemetryReporter);
|
||||
|
||||
this.open(repository);
|
||||
this._closedRepositoriesManager.deleteRepository(repository.root);
|
||||
|
@ -1002,6 +1004,15 @@ export class Model implements IRepositoryResolver, IBranchProtectionProviderRegi
|
|||
return [...this.pushErrorHandlers];
|
||||
}
|
||||
|
||||
registerSourceControlHistoryItemDetailProvider(provider: SourceControlHistoryItemDetailProvider): Disposable {
|
||||
this.historyItemDetailProviders.add(provider);
|
||||
return toDisposable(() => this.historyItemDetailProviders.delete(provider));
|
||||
}
|
||||
|
||||
getSourceControlHistoryItemDetailProviders(): SourceControlHistoryItemDetailProvider[] {
|
||||
return [...this.historyItemDetailProviders];
|
||||
}
|
||||
|
||||
getUnsafeRepositoryPath(repository: string): string | undefined {
|
||||
return this._unsafeRepositoriesManager.getRepositoryPath(repository);
|
||||
}
|
||||
|
|
|
@ -3,10 +3,8 @@
|
|||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { Command } from 'vscode';
|
||||
import { PickRemoteSourceOptions, PickRemoteSourceResult } from './typings/git-base';
|
||||
import { GitBaseApi } from './git-base';
|
||||
import { Repository } from './repository';
|
||||
|
||||
export async function pickRemoteSource(options: PickRemoteSourceOptions & { branch?: false | undefined }): Promise<string | undefined>;
|
||||
export async function pickRemoteSource(options: PickRemoteSourceOptions & { branch: true }): Promise<PickRemoteSourceResult | undefined>;
|
||||
|
@ -17,36 +15,3 @@ export async function pickRemoteSource(options: PickRemoteSourceOptions = {}): P
|
|||
export async function getRemoteSourceActions(url: string) {
|
||||
return GitBaseApi.getAPI().getRemoteSourceActions(url);
|
||||
}
|
||||
|
||||
export async function getRemoteSourceControlHistoryItemCommands(repository: Repository): Promise<Command[]> {
|
||||
if (repository.remotes.length === 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const getCommands = async (repository: Repository, remoteName: string): Promise<Command[] | undefined> => {
|
||||
const remote = repository.remotes.find(r => r.name === remoteName && r.fetchUrl);
|
||||
return remote ? GitBaseApi.getAPI().getRemoteSourceControlHistoryItemCommands(remote.fetchUrl!) : undefined;
|
||||
};
|
||||
|
||||
// upstream -> origin -> first
|
||||
return await getCommands(repository, 'upstream')
|
||||
?? await getCommands(repository, 'origin')
|
||||
?? await getCommands(repository, repository.remotes[0].name)
|
||||
?? [];
|
||||
}
|
||||
|
||||
export async function provideRemoteSourceLinks(repository: Repository, content: string): Promise<string | undefined> {
|
||||
if (repository.remotes.length === 0) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const getDocumentLinks = async (repository: Repository, remoteName: string): Promise<string | undefined> => {
|
||||
const remote = repository.remotes.find(r => r.name === remoteName && r.fetchUrl);
|
||||
return remote ? GitBaseApi.getAPI().provideRemoteSourceLinks(remote.fetchUrl!, content) : undefined;
|
||||
};
|
||||
|
||||
// upstream -> origin -> first
|
||||
return await getDocumentLinks(repository, 'upstream')
|
||||
?? await getDocumentLinks(repository, 'origin')
|
||||
?? await getDocumentLinks(repository, repository.remotes[0].name);
|
||||
}
|
||||
|
|
|
@ -26,6 +26,7 @@ import { toGitUri } from './uri';
|
|||
import { anyEvent, combinedDisposable, debounceEvent, dispose, EmptyDisposable, eventToPromise, filterEvent, find, getCommitShortHash, IDisposable, isDescendant, onceEvent, pathEquals, relativePath } from './util';
|
||||
import { IFileWatcher, watch } from './watch';
|
||||
import { detectEncoding } from './encoding';
|
||||
import { ISourceControlHistoryItemDetailProviderRegistry } from './historyItemDetailProvider';
|
||||
|
||||
const timeout = (millis: number) => new Promise(c => setTimeout(c, millis));
|
||||
|
||||
|
@ -855,6 +856,7 @@ export class Repository implements Disposable {
|
|||
remoteSourcePublisherRegistry: IRemoteSourcePublisherRegistry,
|
||||
postCommitCommandsProviderRegistry: IPostCommitCommandsProviderRegistry,
|
||||
private readonly branchProtectionProviderRegistry: IBranchProtectionProviderRegistry,
|
||||
historyItemDetailProviderRegistry: ISourceControlHistoryItemDetailProviderRegistry,
|
||||
globalState: Memento,
|
||||
private readonly logger: LogOutputChannel,
|
||||
private telemetryReporter: TelemetryReporter
|
||||
|
@ -893,7 +895,7 @@ export class Repository implements Disposable {
|
|||
|
||||
this._sourceControl.quickDiffProvider = this;
|
||||
|
||||
this._historyProvider = new GitHistoryProvider(this, logger);
|
||||
this._historyProvider = new GitHistoryProvider(historyItemDetailProviderRegistry, this, logger);
|
||||
this._sourceControl.historyProvider = this._historyProvider;
|
||||
this.disposables.push(this._historyProvider);
|
||||
|
||||
|
|
|
@ -12,7 +12,7 @@ import { CommandCenter } from './commands';
|
|||
import { OperationKind, OperationResult } from './operation';
|
||||
import { getCommitShortHash } from './util';
|
||||
import { CommitShortStat } from './git';
|
||||
import { getRemoteSourceControlHistoryItemCommands, provideRemoteSourceLinks } from './remoteSource';
|
||||
import { provideSourceControlHistoryItemHoverCommands, provideSourceControlHistoryItemMessageLinks } from './historyItemDetailProvider';
|
||||
|
||||
export class GitTimelineItem extends TimelineItem {
|
||||
static is(item: TimelineItem): item is GitTimelineItem {
|
||||
|
@ -216,7 +216,7 @@ export class GitTimelineProvider implements TimelineProvider {
|
|||
const openComparison = l10n.t('Open Comparison');
|
||||
|
||||
const unpublishedCommits = await repo.getUnpublishedCommits();
|
||||
const remoteSourceCommands = await getRemoteSourceControlHistoryItemCommands(repo);
|
||||
const remoteHoverCommands = await provideSourceControlHistoryItemHoverCommands(this.model, repo);
|
||||
|
||||
const items: GitTimelineItem[] = [];
|
||||
for (let index = 0; index < commits.length; index++) {
|
||||
|
@ -232,8 +232,8 @@ export class GitTimelineProvider implements TimelineProvider {
|
|||
item.description = c.authorName;
|
||||
}
|
||||
|
||||
const commitRemoteSourceCommands = !unpublishedCommits.has(c.hash) ? remoteSourceCommands : [];
|
||||
const messageWithLinks = await provideRemoteSourceLinks(repo, message) ?? message;
|
||||
const commitRemoteSourceCommands = !unpublishedCommits.has(c.hash) ? remoteHoverCommands : [];
|
||||
const messageWithLinks = await provideSourceControlHistoryItemMessageLinks(this.model, repo, message) ?? message;
|
||||
|
||||
item.setItemDetails(uri, c.hash, c.authorName!, c.authorEmail, dateFormatter.format(date), messageWithLinks, c.shortStat, commitRemoteSourceCommands);
|
||||
|
||||
|
|
|
@ -9,9 +9,7 @@ export { ProviderResult } from 'vscode';
|
|||
export interface API {
|
||||
registerRemoteSourceProvider(provider: RemoteSourceProvider): Disposable;
|
||||
getRemoteSourceActions(url: string): Promise<RemoteSourceAction[]>;
|
||||
getRemoteSourceControlHistoryItemCommands(url: string): Promise<Command[] | undefined>;
|
||||
pickRemoteSource(options: PickRemoteSourceOptions): Promise<string | PickRemoteSourceResult | undefined>;
|
||||
provideRemoteSourceLinks(url: string, content: string): Promise<string | undefined>;
|
||||
}
|
||||
|
||||
export interface GitBaseExtension {
|
||||
|
@ -83,7 +81,6 @@ export interface RemoteSourceProvider {
|
|||
|
||||
getBranches?(url: string): ProviderResult<string[]>;
|
||||
getRemoteSourceActions?(url: string): ProviderResult<RemoteSourceAction[]>;
|
||||
getRemoteSourceControlHistoryItemCommands?(url: string): ProviderResult<Command[]>;
|
||||
getRecentRemoteSources?(query?: string): ProviderResult<RecentRemoteSource[]>;
|
||||
getRemoteSources(query?: string): ProviderResult<RemoteSource[]>;
|
||||
}
|
||||
|
|
|
@ -85,7 +85,7 @@ export function registerCommands(gitAPI: GitAPI): vscode.Disposable {
|
|||
return;
|
||||
}
|
||||
|
||||
// Default remote (upstream -> origin -> first)
|
||||
// upstream -> origin -> first
|
||||
const remote = remotes.find(r => r.name === 'upstream')
|
||||
?? remotes.find(r => r.name === 'origin')
|
||||
?? remotes[0];
|
||||
|
|
|
@ -16,6 +16,7 @@ import { GithubRemoteSourcePublisher } from './remoteSourcePublisher';
|
|||
import { GithubBranchProtectionProviderManager } from './branchProtection';
|
||||
import { GitHubCanonicalUriProvider } from './canonicalUriProvider';
|
||||
import { VscodeDevShareProvider } from './shareProviders';
|
||||
import { GitHubSourceControlHistoryItemDetailProvider } from './historyItemDetailProvider';
|
||||
|
||||
export function activate(context: ExtensionContext): void {
|
||||
const disposables: Disposable[] = [];
|
||||
|
@ -100,6 +101,7 @@ function initializeGitExtension(context: ExtensionContext, telemetryReporter: Te
|
|||
disposables.add(new GithubBranchProtectionProviderManager(gitAPI, context.globalState, logger, telemetryReporter));
|
||||
disposables.add(gitAPI.registerPushErrorHandler(new GithubPushErrorHandler(telemetryReporter)));
|
||||
disposables.add(gitAPI.registerRemoteSourcePublisher(new GithubRemoteSourcePublisher(gitAPI)));
|
||||
disposables.add(gitAPI.registerSourceControlHistoryItemDetailProvider(new GitHubSourceControlHistoryItemDetailProvider()));
|
||||
disposables.add(new GitHubCanonicalUriProvider(gitAPI));
|
||||
disposables.add(new VscodeDevShareProvider(gitAPI));
|
||||
setGitHubContext(gitAPI, disposables);
|
||||
|
|
|
@ -0,0 +1,50 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { Command, l10n } from 'vscode';
|
||||
import { Repository, SourceControlHistoryItemDetailProvider } from './typings/git';
|
||||
import { getRepositoryDefaultRemote, getRepositoryDefaultRemoteUrl } from './util';
|
||||
|
||||
const ISSUE_EXPRESSION = /(([A-Za-z0-9_.\-]+)\/([A-Za-z0-9_.\-]+))?(#|GH-)([1-9][0-9]*)($|\b)/g;
|
||||
|
||||
export class GitHubSourceControlHistoryItemDetailProvider implements SourceControlHistoryItemDetailProvider {
|
||||
async provideHoverCommands(repository: Repository): Promise<Command[] | undefined> {
|
||||
const url = getRepositoryDefaultRemoteUrl(repository);
|
||||
if (!url) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return [{
|
||||
title: l10n.t('{0} Open on GitHub', '$(github)'),
|
||||
tooltip: l10n.t('Open on GitHub'),
|
||||
command: 'github.openOnGitHub',
|
||||
arguments: [url]
|
||||
}];
|
||||
}
|
||||
|
||||
async provideMessageLinks(repository: Repository, message: string): Promise<string | undefined> {
|
||||
const descriptor = getRepositoryDefaultRemote(repository);
|
||||
if (!descriptor) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return message.replace(
|
||||
ISSUE_EXPRESSION,
|
||||
(match, _group1, owner: string | undefined, repo: string | undefined, _group2, number: string | undefined) => {
|
||||
if (!number || Number.isNaN(parseInt(number))) {
|
||||
return match;
|
||||
}
|
||||
|
||||
const label = owner && repo
|
||||
? `${owner}/${repo}#${number}`
|
||||
: `#${number}`;
|
||||
|
||||
owner = owner ?? descriptor.owner;
|
||||
repo = repo ?? descriptor.repo;
|
||||
|
||||
return `[${label}](https://github.com/${owner}/${repo}/issues/${number})`;
|
||||
});
|
||||
}
|
||||
}
|
|
@ -3,11 +3,11 @@
|
|||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { Command, Uri, env, l10n, workspace } from 'vscode';
|
||||
import { Uri, env, l10n, workspace } from 'vscode';
|
||||
import { RemoteSourceProvider, RemoteSource, RemoteSourceAction } from './typings/git-base';
|
||||
import { getOctokit } from './auth';
|
||||
import { Octokit } from '@octokit/rest';
|
||||
import { getRepositoryFromQuery, getRepositoryFromUrl, ISSUE_EXPRESSION } from './util';
|
||||
import { getRepositoryFromQuery, getRepositoryFromUrl } from './util';
|
||||
import { getBranchLink, getVscodeDevHost } from './links';
|
||||
|
||||
function asRemoteSource(raw: any): RemoteSource {
|
||||
|
@ -136,42 +136,4 @@ export class GithubRemoteSourceProvider implements RemoteSourceProvider {
|
|||
}
|
||||
}];
|
||||
}
|
||||
|
||||
async getRemoteSourceControlHistoryItemCommands(url: string): Promise<Command[] | undefined> {
|
||||
const repository = getRepositoryFromUrl(url);
|
||||
if (!repository) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return [{
|
||||
title: l10n.t('{0} Open on GitHub', '$(github)'),
|
||||
tooltip: l10n.t('Open on GitHub'),
|
||||
command: 'github.openOnGitHub',
|
||||
arguments: [url]
|
||||
}];
|
||||
}
|
||||
|
||||
provideRemoteSourceLinks(url: string, content: string): string | undefined {
|
||||
const repository = getRepositoryFromUrl(url);
|
||||
if (!repository) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return content.replace(
|
||||
ISSUE_EXPRESSION,
|
||||
(match, _group1, owner: string | undefined, repo: string | undefined, _group2, number: string | undefined) => {
|
||||
if (!number || Number.isNaN(parseInt(number))) {
|
||||
return match;
|
||||
}
|
||||
|
||||
const label = owner && repo
|
||||
? `${owner}/${repo}#${number}`
|
||||
: `#${number}`;
|
||||
|
||||
owner = owner ?? repository.owner;
|
||||
repo = repo ?? repository.repo;
|
||||
|
||||
return `[${label}](https://github.com/${owner}/${repo}/issues/${number})`;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,9 +9,7 @@ export { ProviderResult } from 'vscode';
|
|||
export interface API {
|
||||
registerRemoteSourceProvider(provider: RemoteSourceProvider): Disposable;
|
||||
getRemoteSourceActions(url: string): Promise<RemoteSourceAction[]>;
|
||||
getRemoteSourceControlHistoryItemCommands(url: string): Promise<Command[] | undefined>;
|
||||
pickRemoteSource(options: PickRemoteSourceOptions): Promise<string | PickRemoteSourceResult | undefined>;
|
||||
provideRemoteSourceLinks(url: string, content: string): ProviderResult<string>;
|
||||
}
|
||||
|
||||
export interface GitBaseExtension {
|
||||
|
@ -83,8 +81,6 @@ export interface RemoteSourceProvider {
|
|||
|
||||
getBranches?(url: string): ProviderResult<string[]>;
|
||||
getRemoteSourceActions?(url: string): ProviderResult<RemoteSourceAction[]>;
|
||||
getRemoteSourceControlHistoryItemCommands?(url: string): ProviderResult<Command[]>;
|
||||
getRecentRemoteSources?(query?: string): ProviderResult<RecentRemoteSource[]>;
|
||||
getRemoteSources(query?: string): ProviderResult<RemoteSource[]>;
|
||||
provideRemoteSourceLinks?(url: string, content: string): ProviderResult<string>;
|
||||
}
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { Uri, Event, Disposable, ProviderResult, Command } from 'vscode';
|
||||
import { Uri, Event, Disposable, ProviderResult, Command, SourceControlHistoryItem } from 'vscode';
|
||||
export { ProviderResult } from 'vscode';
|
||||
|
||||
export interface Git {
|
||||
|
@ -289,6 +289,11 @@ export interface BranchProtectionProvider {
|
|||
provideBranchProtection(): BranchProtection[];
|
||||
}
|
||||
|
||||
export interface SourceControlHistoryItemDetailProvider {
|
||||
provideHoverCommands(repository: Repository): Promise<Command[] | undefined>;
|
||||
provideMessageLinks(repository: Repository, message: string): Promise<string | undefined>;
|
||||
}
|
||||
|
||||
export type APIState = 'uninitialized' | 'initialized';
|
||||
|
||||
export interface PublishEvent {
|
||||
|
@ -316,6 +321,7 @@ export interface API {
|
|||
registerPostCommitCommandsProvider(provider: PostCommitCommandsProvider): Disposable;
|
||||
registerPushErrorHandler(handler: PushErrorHandler): Disposable;
|
||||
registerBranchProtectionProvider(root: Uri, provider: BranchProtectionProvider): Disposable;
|
||||
registerSourceControlHistoryItemDetailProvider(provider: SourceControlHistoryItemDetailProvider): Disposable;
|
||||
}
|
||||
|
||||
export interface GitExtension {
|
||||
|
|
|
@ -38,4 +38,23 @@ export function repositoryHasGitHubRemote(repository: Repository) {
|
|||
return !!repository.state.remotes.find(remote => remote.fetchUrl ? getRepositoryFromUrl(remote.fetchUrl) : undefined);
|
||||
}
|
||||
|
||||
export const ISSUE_EXPRESSION = /(([A-Za-z0-9_.\-]+)\/([A-Za-z0-9_.\-]+))?(#|GH-)([1-9][0-9]*)($|\b)/g;
|
||||
export function getRepositoryDefaultRemoteUrl(repository: Repository): string | undefined {
|
||||
const remotes = repository.state.remotes
|
||||
.filter(remote => remote.fetchUrl && getRepositoryFromUrl(remote.fetchUrl));
|
||||
|
||||
if (remotes.length === 0) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// upstream -> origin -> first
|
||||
const remote = remotes.find(remote => remote.name === 'upstream')
|
||||
?? remotes.find(remote => remote.name === 'origin')
|
||||
?? remotes[0];
|
||||
|
||||
return remote.fetchUrl;
|
||||
}
|
||||
|
||||
export function getRepositoryDefaultRemote(repository: Repository): { owner: string; repo: string } | undefined {
|
||||
const fetchUrl = getRepositoryDefaultRemoteUrl(repository);
|
||||
return fetchUrl ? getRepositoryFromUrl(fetchUrl) : undefined;
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue