Переглянути джерело

Reworks PRs & autolink enhancers

Uses the default remote only
Changes to settings per usage
main
Eric Amodio 5 роки тому
джерело
коміт
5b57f5a4c7
12 змінених файлів з 326 додано та 199 видалено
  1. +24
    -6
      package.json
  2. +51
    -37
      src/annotations/autolinks.ts
  3. +81
    -44
      src/annotations/lineAnnotationController.ts
  4. +10
    -3
      src/config.ts
  5. +37
    -37
      src/git/formatters/commitFormatter.ts
  6. +16
    -64
      src/git/gitService.ts
  7. +1
    -0
      src/git/models/issue.ts
  8. +5
    -0
      src/git/models/pullRequest.ts
  9. +4
    -2
      src/git/remotes/github.ts
  10. +5
    -1
      src/github/github.ts
  11. +44
    -3
      src/hovers/hovers.ts
  12. +48
    -2
      src/system/promise.ts

+ 24
- 6
package.json Переглянути файл

@ -461,6 +461,12 @@
"markdownDescription": "Specifies the format of the current line blame annotation. See [_Commit Tokens_](https://github.com/eamodio/vscode-gitlens/wiki/Custom-Formatting#commit-tokens) in the GitLens docs. Date formatting is controlled by the `#gitlens.currentLine.dateFormat#` setting",
"scope": "window"
},
"gitlens.currentLine.pullRequests.enabled": {
"type": "boolean",
"default": true,
"markdownDescription": "Specifies whether to provide information about the Pull Request (if any) that introduced a commit in the current line blame annotation. Requires a connection to a supported remote service (e.g. GitHub)",
"scope": "window"
},
"gitlens.currentLine.scrollable": {
"type": "boolean",
"default": true,
@ -759,6 +765,24 @@
"markdownDescription": "Specifies whether to provide any hovers",
"scope": "window"
},
"gitlens.hovers.autolinks.enabled": {
"type": "boolean",
"default": true,
"markdownDescription": "Specifies whether to automatically link external resources in commit messages",
"scope": "window"
},
"gitlens.hovers.autolinks.enhanced": {
"type": "boolean",
"default": true,
"markdownDescription": "Specifies whether to lookup additional details about automatically link external resources in commit messages. Requires a connection to a supported remote service (e.g. GitHub)",
"scope": "window"
},
"gitlens.hovers.pullRequests.enabled": {
"type": "boolean",
"default": true,
"markdownDescription": "Specifies whether to provide information about the Pull Request (if any) that introduced a commit in the hovers. Requires a connection to a supported remote service (e.g. GitHub)",
"scope": "window"
},
"gitlens.insiders": {
"type": "boolean",
"default": false,
@ -1198,12 +1222,6 @@
"markdownDescription": "Specifies how much (if any) output will be sent to the GitLens output channel",
"scope": "window"
},
"gitlens.pullRequests.enabled": {
"type": "boolean",
"default": true,
"markdownDescription": "Specifies whether to provide information about the Pull Request (if any) that introduced a commit. Requires a connection to a supported remote service (e.g. GitHub)",
"scope": "window"
},
"gitlens.recentChanges.highlight.locations": {
"type": "array",
"default": [

+ 51
- 37
src/annotations/autolinks.ts Переглянути файл

@ -2,18 +2,17 @@
import { ConfigurationChangeEvent, Disposable } from 'vscode';
import { AutolinkReference, configuration } from '../configuration';
import { Container } from '../container';
import { Dates, Strings } from '../system';
import { Dates, Iterables, Promises, Strings } from '../system';
import { Logger } from '../logger';
import { GitRemote, Issue } from '../git/git';
import { RemoteProviderWithApi } from '../git/remotes/provider';
import { GlyphChars } from '../constants';
const numRegex = /<num>/g;
export interface CacheableAutolinkReference extends AutolinkReference {
linkify?: ((text: string) => string) | null;
markdownRegex?: RegExp;
textRegex?: RegExp;
messageMarkdownRegex?: RegExp;
messageRegex?: RegExp;
}
export interface DynamicAutolinkReference {
@ -48,46 +47,59 @@ export class Autolinks implements Disposable {
}
}
async getIssueLinks(text: string, remotes: GitRemote[]) {
if (remotes.length === 0) return undefined;
async getIssueLinks(message: string, remote: GitRemote, { timeout }: { timeout?: number } = {}) {
const provider = remote.provider;
if (!provider?.hasApi()) return undefined;
if (!(await provider.isConnected())) return undefined;
const issues = new Map<number, Issue>();
const ids = new Set<number>();
for (const r of remotes) {
if (!(r.provider instanceof RemoteProviderWithApi)) continue;
if (!(await r.provider.isConnected())) continue;
let match;
let num;
for (const ref of provider.autolinks) {
if (!isCacheable(ref)) continue;
for (const ref of r.provider.autolinks) {
if (!isCacheable(ref)) continue;
if (ref.messageRegex === undefined) {
ref.messageRegex = new RegExp(`(?<=^|\\s)(${ref.prefix}([0-9]+))\\b`, 'g');
}
if (ref.textRegex === undefined) {
ref.textRegex = new RegExp(`(?<=^|\\s)(${ref.prefix}([0-9]+))\\b`, 'g');
}
do {
match = ref.messageRegex.exec(message);
if (match == null) break;
let match;
let num;
let id;
let issue;
do {
match = ref.textRegex.exec(text);
if (match == null) break;
[, , num] = match;
[, , num] = match;
ids.add(Number(num));
} while (true);
}
id = Number(num);
issue = await r.provider.getIssue(id);
if (issue != null) {
issues.set(id, issue);
}
} while (true);
}
if (ids.size === 0) return undefined;
const promises = [
...Iterables.map<number, [number, Promise<Issue | undefined>]>(ids.values(), id => [
id,
provider.getIssue(id)
])
];
const promise =
timeout != null && timeout > 0
? Promises.raceAll(promises, timeout)
: Promise.all(promises.map(([, p]) => p));
const issues = new Map<number, Issue | Promises.CancellationErrorWithId<number>>();
for (const issue of await promise) {
if (issue == null) continue;
issues.set(issue.id, issue);
}
if (issues.size === 0) return undefined;
return issues;
}
linkify(text: string, remotes?: GitRemote[], issues?: Map<number, Issue>) {
linkify(text: string, remotes?: GitRemote[], issues?: Map<number, Issue | Promises.CancellationError>) {
for (const ref of this._references) {
if (this.ensureAutolinkCached(ref, issues)) {
if (ref.linkify != null) {
@ -115,13 +127,13 @@ export class Autolinks implements Disposable {
private ensureAutolinkCached(
ref: CacheableAutolinkReference | DynamicAutolinkReference,
issues?: Map<number, Issue>
issues?: Map<number, Issue | Promises.CancellationError>
): ref is CacheableAutolinkReference | DynamicAutolinkReference {
if (isDynamic(ref)) return true;
try {
if (ref.markdownRegex === undefined) {
ref.markdownRegex = new RegExp(
if (ref.messageMarkdownRegex === undefined) {
ref.messageMarkdownRegex = new RegExp(
`(?<=^|\\s)(${Strings.escapeMarkdown(ref.prefix).replace(/\\/g, '\\\\')}([0-9]+))\\b`,
'g'
);
@ -131,19 +143,21 @@ export class Autolinks implements Disposable {
const markdown = `[$1](${ref.url.replace(numRegex, '$2')}${
ref.title ? ` "${ref.title.replace(numRegex, '$2')}"` : ''
})`;
ref.linkify = (text: string) => text.replace(ref.markdownRegex!, markdown);
ref.linkify = (text: string) => text.replace(ref.messageMarkdownRegex!, markdown);
return true;
}
ref.linkify = (text: string) =>
text.replace(ref.markdownRegex!, (substring, linkText, number) => {
text.replace(ref.messageMarkdownRegex!, (substring, linkText, number) => {
const issue = issues?.get(Number(number));
return `[${linkText}](${ref.url.replace(numRegex, number)}${
ref.title
? ` "${ref.title.replace(numRegex, number)}${
issue
issue instanceof Promises.CancellationError
? `\n${GlyphChars.Dash.repeat(2)}\nDetails timed out`
: issue
? `\n${GlyphChars.Dash.repeat(2)}\n${issue.title.replace(/([")])/g, '\\$1')}\n${
issue.closed ? 'Closed' : 'Opened'
}, ${Dates.getFormatter(issue.closedDate ?? issue.date).fromNow()}`

+ 81
- 44
src/annotations/lineAnnotationController.ts Переглянути файл

@ -14,9 +14,9 @@ import { GlyphChars, isTextEditor } from '../constants';
import { Container } from '../container';
import { LinesChangeEvent } from '../trackers/gitLineTracker';
import { Annotations } from './annotations';
import { debug, log } from '../system';
import { debug, Iterables, log, Promises } from '../system';
import { Logger } from '../logger';
import { CommitFormatter, CommitPullRequest, GitRemote } from '../git/gitService';
import { CommitFormatter, GitBlameCommit, PullRequest } from '../git/gitService';
const annotationDecoration: TextEditorDecorationType = window.createTextEditorDecorationType({
after: {
@ -140,12 +140,61 @@ export class LineAnnotationController implements Disposable {
editor.setDecorations(annotationDecoration, []);
}
private async getPullRequestForCommit(ref: string, remotes: GitRemote[]) {
try {
return await Container.git.getPullRequestForCommit(ref, remotes, { timeout: 100 });
} catch {
return undefined;
private async getPullRequests(
repoPath: string,
lines: [number, GitBlameCommit][],
{ timeout }: { timeout?: number } = {}
) {
if (lines.length === 0) return undefined;
const remotes = await Container.git.getRemotes(repoPath);
const remote = remotes.find(r => r.default);
if (remote === undefined) return undefined;
const provider = remote.provider;
if (!provider?.hasApi()) return undefined;
if (!(await provider.isConnected())) return undefined;
const refs = new Set<string>();
for (const [, commit] of lines) {
refs.add(commit.ref);
}
if (refs.size === 0) return undefined;
const promises = [
...Iterables.map<string, [string, Promise<[string, PullRequest | undefined]>]>(refs.values(), ref => [
ref,
Container.git.getPullRequestForCommit(ref, remote).then(pr => [ref, pr])
])
];
const promise =
timeout != null && timeout > 0
? Promises.raceAll(promises, timeout)
: Promise.all(promises.map(([, p]) => p));
const prs = new Map<string, PullRequest | Promises.CancellationErrorWithId<string>>();
let ref;
let pr;
for (const result of await promise) {
if (result == null) continue;
if (result instanceof Promises.CancellationErrorWithId) {
prs.set(result.id, result);
} else {
[ref, pr] = result;
if (pr == null) continue;
prs.set(ref, pr);
}
}
if (prs.size === 0) return undefined;
return prs;
}
@debug({ args: false })
@ -209,61 +258,49 @@ export class LineAnnotationController implements Disposable {
cc.exitDetails = ` ${GlyphChars.Dot} line(s)=${lines.join()}`;
}
let getBranchAndTagTips;
if (CommitFormatter.has(cfg.format, 'tips')) {
getBranchAndTagTips = await Container.git.getBranchesAndTagsTipsFn(trackedDocument.uri.repoPath);
}
const commitLines = [
...Iterables.filterMap<number, [number, GitBlameCommit]>(lines, l => {
const state = Container.lineTracker.getState(l);
if (state?.commit == null) return undefined;
return [l, state.commit];
})
];
let prs;
if (
Container.config.pullRequests.enabled &&
const repoPath = trackedDocument.uri.repoPath;
const [getBranchAndTagTips, prs] = await Promise.all([
CommitFormatter.has(cfg.format, 'tips') ? Container.git.getBranchesAndTagsTipsFn(repoPath) : undefined,
repoPath != null &&
Container.config.currentLine.pullRequests.enabled &&
CommitFormatter.has(
cfg.format,
Containerclass="p">.config.currentLine.format,
'pullRequest',
'pullRequestAgo',
'pullRequestAgoOrDate',
'pullRequestDate',
'pullRequestState'
)
) {
const promises = [];
let remotes;
for (const l of lines) {
const state = Container.lineTracker.getState(l);
if (state?.commit == null || state.commit.isUncommitted || (remotes != null && remotes.length === 0)) {
continue;
}
if (remotes == null) {
remotes = await Container.git.getRemotes(state.commit.repoPath);
}
promises.push(this.getPullRequestForCommit(state.commit.ref, remotes));
}
prs = new Map<string, CommitPullRequest | undefined>();
for await (const pr of promises) {
if (pr === undefined) continue;
prs.set(pr?.ref, pr);
}
}
? this.getPullRequests(
repoPath,
commitLines.filter(([, commit]) => !commit.isUncommitted),
{ timeout: 250 }
)
: undefined
]);
const decorations = [];
for (const l of lines) {
const state = Container.lineTracker.getState(l);
if (state?.commit == null) continue;
for (const [l, commit] of commitLines) {
const decoration = Annotations.trailing(
state.commit,
commit,
// await GitUri.fromUri(editor.document.uri),
// l,
cfg.format,
{
dateFormat: cfg.dateFormat === null ? Container.config.defaultDateFormat : cfg.dateFormat,
getBranchAndTagTips: getBranchAndTagTips,
pr: prs?.get(state.commit.ref)
pullRequestOrRemote: prs?.get(commit.ref)
},
cfg.scrollable
) as DecorationOptions;

+ 10
- 3
src/config.ts Переглянути файл

@ -24,6 +24,9 @@ export interface Config {
dateFormat: string | null;
enabled: boolean;
format: string;
pullRequests: {
enabled: boolean;
};
scrollable: boolean;
};
codeLens: CodeLensConfig;
@ -56,6 +59,10 @@ export interface Config {
enabled: boolean;
over: 'line' | 'annotation';
};
autolinks: {
enabled: boolean;
enhanced: boolean;
};
currentLine: {
changes: boolean;
details: boolean;
@ -66,6 +73,9 @@ export interface Config {
changesDiff: 'line' | 'hunk';
detailsMarkdownFormat: string;
enabled: boolean;
pullRequests: {
enabled: boolean;
};
};
insiders: boolean;
keymap: KeyMap;
@ -82,9 +92,6 @@ export interface Config {
};
modes: { [key: string]: ModeConfig };
outputLevel: TraceLevel;
pullRequests: {
enabled: boolean;
};
recentChanges: {
highlight: {
locations: HighlightLocations[];

+ 37
- 37
src/git/formatters/commitFormatter.ts Переглянути файл

@ -11,7 +11,7 @@ import {
import { DateStyle, FileAnnotationType } from '../../configuration';
import { GlyphChars } from '../../constants';
import { Container } from '../../container';
import { CommitPullRequest, GitCommit, GitLogCommit, GitRemote, GitService, GitUri, Issue } from '../gitService';
import { GitCommit, GitLogCommit, GitRemote, GitService, GitUri, Issue, PullRequest } from '../gitService';
import { Strings } from '../../system';
import { FormatOptions, Formatter } from './formatter';
import { ContactPresence } from '../../vsls/vsls';
@ -25,12 +25,12 @@ const hasTokenRegexMap = new Map();
export interface CommitFormatOptions extends FormatOptions {
annotationType?: FileAnnotationType;
autolinkedIssues?: Map<number, Issue>;
autolinkedIssues?: Map<number, Issue | Promises.CancellationError>;
dateStyle?: DateStyle;
getBranchAndTagTips?: (sha: string) => string | undefined;
line?: number;
markdown?: boolean;
pr?: CommitPullRequest | Promises.CancellationError<CommitPullRequest>;
pullRequestOrRemote?: PullRequest | Promises.CancellationError | GitRemote;
presence?: ContactPresence;
previousLineDiffUris?: { current: GitUri; previous: GitUri | undefined };
remotes?: GitRemote[];
@ -105,17 +105,17 @@ export class CommitFormatter extends Formatter {
}
private get _pullRequestDate() {
const { pr } = this._options;
if (pr == null || pr instanceof Promises.CancellationError) return emptyStr;
const { pullRequestOrRemote: pr } = this._options;
if (pr == null || !PullRequest.is(pr)) return emptyStr;
return pr.pr?.formatDate(this._options.dateFormat) ?? emptyStr;
return pr.formatDate(this._options.dateFormat) ?? emptyStr;
}
private get _pullRequestDateAgo() {
const { pr } = this._options;
if (pr == null || pr instanceof Promises.CancellationError) return emptyStr;
const { pullRequestOrRemote: pr } = this._options;
if (pr == null || !PullRequest.is(pr)) return emptyStr;
return pr.pr?.formatDateFromNow() ?? emptyStr;
return pr.formatDateFromNow() ?? emptyStr;
}
private get _pullRequestDateOrAgo() {
@ -242,21 +242,19 @@ export class CommitFormatter extends Formatter {
this._item.sha
)} "Show Commit Details")${separator}`;
const { pr } = this._options;
const { pullRequestOrRemote: pr } = this._options;
if (pr != null) {
if (pr instanceof Promises.CancellationError) {
if (PullRequest.is(pr)) {
commands += `[\`PR #${pr.number}\`](${pr.url} "Open Pull Request \\#${pr.number} on ${
pr.provider
}\n${GlyphChars.Dash.repeat(2)}\n${pr.title}\n${pr.state}, ${pr.formatDateFromNow()}")${separator}`;
} else if (pr instanceof Promises.CancellationError) {
commands += `[\`PR (loading${GlyphChars.Ellipsis})\`](# "Searching for a Pull Request (if any) that introduced this commit...")${separator}`;
} else if (pr.pr != null) {
commands += `[\`PR #${pr.pr.number}\`](${pr.pr.url} "Open Pull Request \\#${pr.pr.number}${
pr.remote?.provider != null ? ` on ${pr.remote.provider.name}` : ''
}\n${GlyphChars.Dash.repeat(2)}\n${pr.pr.title}\n${
pr.pr.state
}, ${pr.pr.formatDateFromNow()}")${separator}`;
} else if (pr.remote?.provider != null) {
commands += `[\`Connect to ${pr.remote.provider.name}${
} else if (pr.provider != null) {
commands += `[\`Connect to ${pr.provider.name}${
GlyphChars.Ellipsis
}\`](${ConnectRemoteProviderCommand.getMarkdownCommandArgs(pr.remote)} "Connect to ${
pr.remote.provider.name
}\`](${ConnectRemoteProviderCommand.getMarkdownCommandArgs(pr)} "Connect to ${
pr.provider.name
} to enable the display of the Pull Request (if any) that introduced this commit")${separator}`;
}
}
@ -356,30 +354,32 @@ export class CommitFormatter extends Formatter {
return message;
}
message = Container.autolinks.linkify(
Strings.escapeMarkdown(message, { quoted: true }),
this._options.remotes,
this._options.autolinkedIssues
);
if (Container.config.hovers.autolinks.enabled) {
message = Container.autolinks.linkify(
Strings.escapeMarkdown(message, { quoted: true }),
this._options.remotes,
this._options.autolinkedIssues
);
}
return `\n> ${message}`;
}
get pullRequest() {
const { pr } = this._options;
const { pullRequestOrRemote: pr } = this._options;
if (pr == null) return emptyStr;
let text;
if (pr instanceof Promises.CancellationError) {
if (PullRequest.is(pr)) {
text = this._options.markdown
? `[PR #${pr.number}](${pr.url} "Open Pull Request \\#${pr.number} on ${
pr.provider
}\n${GlyphChars.Dash.repeat(2)}\n${pr.title}\n${pr.state}, ${pr.formatDateFromNow()}")`
: `PR #${pr.number}`;
} else if (pr instanceof Promises.CancellationError) {
text = this._options.markdown
? `[PR (loading${GlyphChars.Ellipsis})](# "Searching for a Pull Request (if any) that introduced this commit...")`
: `PR (loading${GlyphChars.Ellipsis})`;
} else if (pr.pr != null) {
text = this._options.markdown
? `[PR #${pr.pr.number}](${pr.pr.url} "Open Pull Request \\#${pr.pr.number}${
pr.remote?.provider != null ? ` on ${pr.remote.provider.name}` : ''
}\n${GlyphChars.Dash.repeat(2)}\n${pr.pr.title}\n${pr.pr.state}, ${pr.pr.formatDateFromNow()}")`
: `PR #${pr.pr.number}`;
} else {
return emptyStr;
}
@ -400,10 +400,10 @@ export class CommitFormatter extends Formatter {
}
get pullRequestState() {
const { pr } = this._options;
if (pr == null || pr instanceof Promises.CancellationError) return emptyStr;
const { pullRequestOrRemote: pr } = this._options;
if (pr == null || !PullRequest.is(pr)) return emptyStr;
return this._padOrTruncate(pr.pr?.state ?? emptyStr, this._options.tokenOptions.pullRequestState);
return this._padOrTruncate(pr.state ?? emptyStr, this._options.tokenOptions.pullRequestState);
}
get sha() {

+ 16
- 64
src/git/gitService.ts Переглянути файл

@ -92,7 +92,6 @@ export * from './formatters/formatters';
export * from './remotes/provider';
export { RemoteProviderFactory } from './remotes/factory';
const emptyArray = (Object.freeze([]) as any) as any[];
const emptyStr = '';
const slash = '/';
@ -110,12 +109,6 @@ const searchOperationRegex = /((?:=|message|@|author|#|commit|\?|file|~|change):
const emptyPromise: Promise<GitBlame | GitDiff | GitLog | undefined> = Promise.resolve(undefined);
const reflogCommands = ['merge', 'pull'];
export interface CommitPullRequest {
ref: string;
pr: PullRequest | undefined;
remote: GitRemote | undefined;
}
export type SearchOperators =
| ''
| '=:'
@ -2353,71 +2346,30 @@ export class GitService implements Disposable {
async getPullRequestForCommit(
ref: string,
remotes: GitRemote[],
remote: GitRemote,
{ timeout }: { timeout?: number } = {}
): Promise<CommitPullRequest | undefined> {
if (
!Container.config.pullRequests.enabled ||
(remotes != null && remotes.length === 0) ||
Git.isUncommitted(ref)
) {
return undefined;
}
const pr: CommitPullRequest = {
ref: ref,
pr: undefined,
remote: undefined
};
const prs: Promise<[PullRequest | undefined, GitRemote]>[] = [];
let foundConnectedDefaultSkipOthers = false;
for (const remote of GitRemote.sort(remotes)) {
if (!remote.provider?.hasApi()) continue;
if (!(await remote.provider.isConnected())) {
if (pr.remote == null) {
pr.remote = remote;
}
continue;
}
if (!foundConnectedDefaultSkipOthers) {
if (remote.default) {
foundConnectedDefaultSkipOthers = true;
}
): Promise<PullRequest | undefined> {
if (Git.isUncommitted(ref) || !remote.provider?.hasApi()) return undefined;
if (!(await remote.provider.isConnected())) return undefined;
const requestOrPR = remote.provider.getPullRequestForCommit(ref);
if (requestOrPR == null || !Promises.is(requestOrPR)) {
pr.pr = requestOrPR;
pr.remote = requestOrPR !== undefined ? remote : undefined;
break;
}
prs.push(requestOrPR.then(pr => [pr, remote]));
} else if (pr.remote !== undefined) {
break;
}
let promiseOrPR = remote.provider.getPullRequestForCommit(ref);
if (promiseOrPR == null || !Promises.is(promiseOrPR)) {
return promiseOrPR;
}
if (prs.length !== 0) {
pr.remote = undefined;
if (timeout != null && timeout > 0) {
promiseOrPR = Promises.cancellable(promiseOrPR, timeout);
}
let promise = Promises.first(prs, ([pr]) => pr != null);
if (timeout != null && timeout > 0) {
promise = Promises.cancellable(promise, timeout);
try {
return await promiseOrPR;
} catch (ex) {
if (ex instanceof Promises.CancellationError) {
throw ex;
}
try {
[pr.pr, pr.remote] = (await promise) ?? emptyArray;
} catch (ex) {
if (ex instanceof Promises.CancellationError) {
throw ex;
}
}
return undefined;
}
return pr;
}
@log()

+ 1
- 0
src/git/models/issue.ts Переглянути файл

@ -1,6 +1,7 @@
'use strict';
export interface Issue {
provider: string;
id: number;
date: Date;
title: string;

+ 5
- 0
src/git/models/pullRequest.ts Переглянути файл

@ -19,7 +19,12 @@ export enum PullRequestState {
}
export class PullRequest {
static is(pr: any): pr is PullRequest {
return pr instanceof PullRequest;
}
constructor(
public readonly provider: string,
public readonly number: number,
public readonly title: string,
public readonly url: string,

+ 4
- 2
src/git/remotes/github.ts Переглянути файл

@ -137,7 +137,7 @@ export class GitHubRemote extends RemoteProviderWithApi<{ token: string }> {
protected async onGetIssue({ token }: { token: string }, id: number): Promise<Issue | undefined> {
const [owner, repo] = this.splitPath();
return (await Container.github).getIssue(token, owner, repo, id, { baseUrl: this.apiBaseUrl });
return (await Container.github).getIssue(this.name, token, owner, repo, id, { baseUrl: this.apiBaseUrl });
}
protected async onGetPullRequestForCommit(
@ -145,6 +145,8 @@ export class GitHubRemote extends RemoteProviderWithApi<{ token: string }> {
ref: string
): Promise<PullRequest | undefined> {
const [owner, repo] = this.splitPath();
return (await Container.github).getPullRequestForCommit(token, owner, repo, ref, { baseUrl: this.apiBaseUrl });
return (await Container.github).getPullRequestForCommit(this.name, token, owner, repo, ref, {
baseUrl: this.apiBaseUrl
});
}
}

+ 5
- 1
src/github/github.ts Переглянути файл

@ -7,6 +7,7 @@ import { Issue, PullRequest, PullRequestState } from '../git/gitService';
export class GitHubApi {
@debug()
async getPullRequestForCommit(
provider: string,
token: string,
owner: string,
repo: string,
@ -57,6 +58,7 @@ export class GitHubApi {
if (pr.repository.owner.login !== owner) return undefined;
return new PullRequest(
provider,
pr.number,
pr.title,
pr.permalink,
@ -77,6 +79,7 @@ export class GitHubApi {
@debug()
async getIssue(
provider: string,
token: string,
owner: string,
repo: string,
@ -111,7 +114,8 @@ export class GitHubApi {
if (issue == null) return undefined;
return {
id: issue.number,
provider: provider,
id: number,
date: new Date(issue.createdAt),
title: issue.title,
closed: issue.closed,

+ 44
- 3
src/hovers/hovers.ts Переглянути файл

@ -156,7 +156,7 @@ export namespace Hovers {
const [previousLineDiffUris, autolinkedIssues, pr, presence] = await Promise.all([
commit.isUncommitted ? commit.getPreviousLineDiffUris(uri, editorLine, uri.sha) : undefined,
Container.autolinks.getIssueLinks(commit.message, remotes),
getAutoLinkedIssues(commit.message, remotes),
getPullRequestForCommit(commit.ref, remotes),
Container.vsls.maybeGetPresence(commit.email).catch(reason => undefined)
]);
@ -167,7 +167,7 @@ export namespace Hovers {
dateFormat: dateFormat,
line: editorLine,
markdown: true,
pr: pr,
pullRequestOrRemote: pr,
presence: presence,
previousLineDiffUris: previousLineDiffUris,
remotes: remotes
@ -188,9 +188,50 @@ export namespace Hovers {
}\n\`\`\``;
}
async function getAutoLinkedIssues(message: string, remotes: GitRemote[]) {
if (
!Container.config.hovers.autolinks.enabled ||
!Container.config.hovers.autolinks.enhanced ||
!CommitFormatter.has(Container.config.hovers.detailsMarkdownFormat, 'message')
) {
return undefined;
}
const remote = remotes.find(r => r.default && r.provider != null);
if (remote === undefined) return undefined;
try {
return await Container.autolinks.getIssueLinks(message, remote, { timeout: 250 });
} catch {
return undefined;
}
}
async function getPullRequestForCommit(ref: string, remotes: GitRemote[]) {
if (
!Container.config.hovers.pullRequests.enabled ||
!CommitFormatter.has(
Container.config.hovers.detailsMarkdownFormat,
'pullRequest',
'pullRequestAgo',
'pullRequestAgoOrDate',
'pullRequestDate',
'pullRequestState'
)
) {
return undefined;
}
const remote = remotes.find(r => r.default && r.provider != null);
if (remote === undefined) return undefined;
const provider = remote.provider;
if (provider?.hasApi() && !(await provider.isConnected())) {
return remote;
}
try {
return await Container.git.getPullRequestForCommit(ref, remotes, { timeout: 250 });
return await Container.git.getPullRequestForCommit(ref, remote, { timeout: 250 });
} catch (ex) {
if (ex instanceof Promises.CancellationError) {
return ex;

+ 48
- 2
src/system/promise.ts Переглянути файл

@ -2,12 +2,18 @@
import { CancellationToken } from 'vscode';
export namespace Promises {
export class CancellationError<T> extends Error {
constructor(public readonly promise: T, message: string) {
export class CancellationError<TPromise = any> extends Error {
constructor(public readonly promise: TPromise, message: string) {
super(message);
}
}
export class CancellationErrorWithId<T, TPromise = any> extends CancellationError<TPromise> {
constructor(public readonly id: T, promise: TPromise, message: string) {
super(promise, message);
}
}
export function cancellable<T>(
promise: Thenable<T>,
timeoutOrToken: number | CancellationToken,
@ -79,4 +85,44 @@ export namespace Promises {
export function is<T>(obj: T | Promise<T>): obj is Promise<T> {
return obj != null && typeof (obj as Promise<T>).then === 'function';
}
export function raceAll<TPromise>(
promises: Promise<TPromise>[],
timeout: number
): Promise<(TPromise | Promises.CancellationError<Promise<TPromise>>)[]>;
export function raceAll<TPromise, T>(
promises: [T, Promise<TPromise>][],
timeout: number
): Promise<(TPromise | Promises.CancellationErrorWithId<T, Promise<TPromise>>)[]>;
export function raceAll<TPromise, T>(promises: Promise<TPromise>[] | [T, Promise<TPromise>][], timeout: number) {
if (hasIds(promises)) {
return Promise.all(
promises.map(([id, p]) =>
Promise.race([
p,
new Promise<CancellationErrorWithId<T, Promise<TPromise>>>(resolve =>
setTimeout(() => resolve(new CancellationErrorWithId(id, p, 'TIMED OUT')), timeout)
)
])
)
);
}
return Promise.all(
promises.map(p =>
Promise.race([
p,
new Promise<CancellationError<Promise<TPromise>>>(resolve =>
setTimeout(() => resolve(new CancellationError(p, 'TIMED OUT')), timeout)
)
])
)
);
}
function hasIds<TPromise, T>(
promises: Promise<TPromise>[] | [T, Promise<TPromise>][]
): promises is [T, Promise<TPromise>][] {
return Array.isArray(promises[0]);
}
}

Завантаження…
Відмінити
Зберегти