From 088627c899af890dba39720ab93295e619ba082f Mon Sep 17 00:00:00 2001 From: Eric Amodio <eamodio@gmail.com> Date: Tue, 10 Mar 2020 03:39:53 -0400 Subject: [PATCH] Supports issues or pull requests in autolinks --- src/annotations/autolinks.ts | 30 ++++++++++++++++++------------ src/git/formatters/commitFormatter.ts | 6 +++--- src/git/models/issue.ts | 3 ++- src/git/remotes/github.ts | 11 ++++++++--- src/git/remotes/provider.ts | 8 ++++---- src/github/github.ts | 31 +++++++++++++++++++++---------- 6 files changed, 56 insertions(+), 33 deletions(-) diff --git a/src/annotations/autolinks.ts b/src/annotations/autolinks.ts index dd6a2a5..8ae3a15 100644 --- a/src/annotations/autolinks.ts +++ b/src/annotations/autolinks.ts @@ -4,7 +4,7 @@ import { AutolinkReference, configuration } from '../configuration'; import { Container } from '../container'; import { Dates, debug, Iterables, Promises, Strings } from '../system'; import { Logger } from '../logger'; -import { GitRemote, Issue } from '../git/git'; +import { GitRemote, IssueOrPullRequest } from '../git/git'; import { GlyphChars } from '../constants'; const numRegex = /<num>/g; @@ -50,7 +50,7 @@ export class Autolinks implements Disposable { } @debug({ args: false }) - async getIssueLinks(message: string, remote: GitRemote, { timeout }: { timeout?: number } = {}) { + async getIssueOrPullRequestLinks(message: string, remote: GitRemote, { timeout }: { timeout?: number } = {}) { if (!remote.provider?.hasApi()) return undefined; const { provider } = remote; @@ -83,10 +83,16 @@ export class Autolinks implements Disposable { if (ids.size === 0) return undefined; - const issues = await Promises.raceAll(ids.values(), id => provider.getIssue(id), timeout); - if (issues.size === 0 || Iterables.every(issues.values(), pr => pr === undefined)) return undefined; + const issuesOrPullRequests = await Promises.raceAll( + ids.values(), + id => provider.getIssueOrPullRequest(id), + timeout + ); + if (issuesOrPullRequests.size === 0 || Iterables.every(issuesOrPullRequests.values(), pr => pr === undefined)) { + return undefined; + } - return issues; + return issuesOrPullRequests; } @debug({ args: false }) @@ -94,10 +100,10 @@ export class Autolinks implements Disposable { text: string, markdown: boolean, remotes?: GitRemote[], - issues?: Map<number, Issue | Promises.CancellationError | undefined> + issuesOrPullRequests?: Map<number, IssueOrPullRequest | Promises.CancellationError | undefined> ) { for (const ref of this._references) { - if (this.ensureAutolinkCached(ref, issues)) { + if (this.ensureAutolinkCached(ref, issuesOrPullRequests)) { if (ref.linkify != null) { text = ref.linkify(text, markdown); } @@ -109,7 +115,7 @@ export class Autolinks implements Disposable { if (r.provider === undefined) continue; for (const ref of r.provider.autolinks) { - if (this.ensureAutolinkCached(ref, issues)) { + if (this.ensureAutolinkCached(ref, issuesOrPullRequests)) { if (ref.linkify != null) { text = ref.linkify(text, markdown); } @@ -123,7 +129,7 @@ export class Autolinks implements Disposable { private ensureAutolinkCached( ref: CacheableAutolinkReference | DynamicAutolinkReference, - issues?: Map<number, Issue | Promises.CancellationError | undefined> + issuesOrPullRequests?: Map<number, IssueOrPullRequest | Promises.CancellationError | undefined> ): ref is CacheableAutolinkReference | DynamicAutolinkReference { if (isDynamic(ref)) return true; @@ -137,7 +143,7 @@ export class Autolinks implements Disposable { ); } - if (issues == null || issues.size === 0) { + if (issuesOrPullRequests == null || issuesOrPullRequests.size === 0) { const replacement = `[$1](${ref.url.replace(numRegex, '$2')}${ ref.title ? ` "${ref.title.replace(numRegex, '$2')}"` : '' })`; @@ -150,7 +156,7 @@ export class Autolinks implements Disposable { ref.linkify = (text: string, markdown: boolean) => { if (markdown) { return text.replace(ref.messageMarkdownRegex!, (substring, linkText, number) => { - const issue = issues?.get(Number(number)); + const issue = issuesOrPullRequests?.get(Number(number)); return `[${linkText}](${ref.url.replace(numRegex, number)}${ ref.title @@ -175,7 +181,7 @@ export class Autolinks implements Disposable { let superscript; text = text.replace(ref.messageRegex!, (substring, linkText, number) => { - const issue = issues?.get(Number(number)); + const issue = issuesOrPullRequests?.get(Number(number)); if (issue == null) return linkText; if (footnotes === undefined) { diff --git a/src/git/formatters/commitFormatter.ts b/src/git/formatters/commitFormatter.ts index 9637d6d..45ad5c8 100644 --- a/src/git/formatters/commitFormatter.ts +++ b/src/git/formatters/commitFormatter.ts @@ -11,7 +11,7 @@ import { import { DateStyle, FileAnnotationType } from '../../configuration'; import { GlyphChars } from '../../constants'; import { Container } from '../../container'; -import { GitCommit, GitLogCommit, GitRemote, GitService, GitUri, Issue, PullRequest } from '../gitService'; +import { GitCommit, GitLogCommit, GitRemote, GitService, GitUri, IssueOrPullRequest, PullRequest } from '../gitService'; import { Promises, Strings } from '../../system'; import { FormatOptions, Formatter } from './formatter'; import { ContactPresence } from '../../vsls/vsls'; @@ -24,7 +24,7 @@ const hasTokenRegexMap = new Map<string, RegExp>(); export interface CommitFormatOptions extends FormatOptions { annotationType?: FileAnnotationType; - autolinkedIssues?: Map<number, Issue | Promises.CancellationError | undefined>; + autolinkedIssuesOrPullRequests?: Map<number, IssueOrPullRequest | Promises.CancellationError | undefined>; dateStyle?: DateStyle; getBranchAndTagTips?: (sha: string) => string | undefined; line?: number; @@ -354,7 +354,7 @@ export class CommitFormatter extends Formatter<GitCommit, CommitFormatOptions> { this._options.markdown ? Strings.escapeMarkdown(message, { quoted: true }) : message, this._options.markdown ?? false, this._options.remotes, - this._options.autolinkedIssues + this._options.autolinkedIssuesOrPullRequests ); } diff --git a/src/git/models/issue.ts b/src/git/models/issue.ts index dadb9e9..c33a5da 100644 --- a/src/git/models/issue.ts +++ b/src/git/models/issue.ts @@ -1,6 +1,7 @@ 'use strict'; -export interface Issue { +export interface IssueOrPullRequest { + type: 'Issue' | 'PullRequest'; provider: string; id: number; date: Date; diff --git a/src/git/remotes/github.ts b/src/git/remotes/github.ts index 256b089..0038de8 100644 --- a/src/git/remotes/github.ts +++ b/src/git/remotes/github.ts @@ -3,7 +3,7 @@ import { Disposable, env, QuickInputButton, Range, Uri, window } from 'vscode'; import { DynamicAutolinkReference } from '../../annotations/autolinks'; import { AutolinkReference } from '../../config'; import { Container } from '../../container'; -import { Issue } from '../models/issue'; +import { IssueOrPullRequest } from '../models/issue'; import { PullRequest } from '../models/pullRequest'; import { RemoteProviderWithApi } from './provider'; @@ -136,9 +136,14 @@ export class GitHubRemote extends RemoteProviderWithApi<{ token: string }> { return `${this.baseUrl}?path=${fileName}${line}`; } - protected async onGetIssue({ token }: { token: string }, id: number): Promise<Issue | undefined> { + protected async onGetIssueOrPullRequest( + { token }: { token: string }, + id: number + ): Promise<IssueOrPullRequest | undefined> { const [owner, repo] = this.splitPath(); - return (await Container.github)?.getIssue(this.name, token, owner, repo, id, { baseUrl: this.apiBaseUrl }); + return (await Container.github)?.getIssueOrPullRequest(this.name, token, owner, repo, id, { + baseUrl: this.apiBaseUrl + }); } protected async onGetPullRequestForCommit( diff --git a/src/git/remotes/provider.ts b/src/git/remotes/provider.ts index 51b718a..090ff0b 100644 --- a/src/git/remotes/provider.ts +++ b/src/git/remotes/provider.ts @@ -6,7 +6,7 @@ import { Container } from '../../container'; import { CredentialChangeEvent, CredentialManager } from '../../credentials'; import { Logger } from '../../logger'; import { Messages } from '../../messages'; -import { Issue } from '../models/issue'; +import { IssueOrPullRequest } from '../models/issue'; import { GitLogCommit } from '../models/logCommit'; import { PullRequest } from '../models/pullRequest'; import { debug, gate, Promises } from '../../system'; @@ -246,14 +246,14 @@ export abstract class RemoteProviderWithApi<T extends string | {} = any> extends @gate() @debug() - async getIssue(id: number): Promise<Issue | undefined> { + async getIssueOrPullRequest(id: number): Promise<IssueOrPullRequest | undefined> { const cc = Logger.getCorrelationContext(); const connected = this.maybeConnected ?? (await this.isConnected()); if (!connected) return undefined; try { - return await this.onGetIssue(this._credentials!, id); + return await this.onGetIssueOrPullRequest(this._credentials!, id); } catch (ex) { Logger.error(ex, cc); @@ -276,7 +276,7 @@ export abstract class RemoteProviderWithApi<T extends string | {} = any> extends return pr.then(pr => pr ?? undefined); } - protected abstract onGetIssue(credentials: T, id: number): Promise<Issue | undefined>; + protected abstract onGetIssueOrPullRequest(credentials: T, id: number): Promise<IssueOrPullRequest | undefined>; protected abstract onGetPullRequestForCommit(credentials: T, ref: string): Promise<PullRequest | undefined>; protected _credentials: T | null | undefined; diff --git a/src/github/github.ts b/src/github/github.ts index d0da591..0ab1166 100644 --- a/src/github/github.ts +++ b/src/github/github.ts @@ -2,7 +2,7 @@ import { graphql } from '@octokit/graphql'; import { Logger } from '../logger'; import { debug } from '../system'; -import { Issue, PullRequest, PullRequestState } from '../git/gitService'; +import { IssueOrPullRequest, PullRequest, PullRequestState } from '../git/gitService'; export class GitHubApi { @debug({ @@ -86,7 +86,7 @@ export class GitHubApi { 1: token => '<token>' } }) - async getIssue( + async getIssueOrPullRequest( provider: string, token: string, owner: string, @@ -95,17 +95,26 @@ export class GitHubApi { options?: { baseUrl?: string; } - ): Promise<Issue | undefined> { + ): Promise<IssueOrPullRequest | undefined> { const cc = Logger.getCorrelationContext(); try { const query = `query pr($owner: String!, $repo: String!, $number: Int!) { repository(name: $repo, owner: $owner) { - issue(number: $number) { - createdAt - closed - closedAt - title + issueOrPullRequest(number: $number) { + __typename + ... on Issue { + createdAt + closed + closedAt + title + } + ... on PullRequest { + createdAt + closed + closedAt + title + } } } }`; @@ -118,11 +127,12 @@ export class GitHubApi { headers: { authorization: `token ${token}` }, ...options }); - const issue = rsp?.repository?.issue as GitHubIssue | undefined; + const issue = rsp?.repository?.issueOrPullRequest as GitHubIssueOrPullRequest | undefined; if (issue == null) return undefined; return { provider: provider, + type: issue.type, id: number, date: new Date(issue.createdAt), title: issue.title, @@ -136,7 +146,8 @@ export class GitHubApi { } } -interface GitHubIssue { +interface GitHubIssueOrPullRequest { + type: 'Issue' | 'PullRequest'; number: number; createdAt: string; closed: boolean;