Browse Source

Supports issues or pull requests in autolinks

main
Eric Amodio 5 years ago
parent
commit
088627c899
6 changed files with 56 additions and 33 deletions
  1. +18
    -12
      src/annotations/autolinks.ts
  2. +3
    -3
      src/git/formatters/commitFormatter.ts
  3. +2
    -1
      src/git/models/issue.ts
  4. +8
    -3
      src/git/remotes/github.ts
  5. +4
    -4
      src/git/remotes/provider.ts
  6. +21
    -10
      src/github/github.ts

+ 18
- 12
src/annotations/autolinks.ts View File

@ -4,7 +4,7 @@ import { AutolinkReference, configuration } from '../configuration';
import { Container } from '../container'; import { Container } from '../container';
import { Dates, debug, Iterables, Promises, Strings } from '../system'; import { Dates, debug, Iterables, Promises, Strings } from '../system';
import { Logger } from '../logger'; import { Logger } from '../logger';
import { GitRemote, Issue } from '../git/git'; import { GitRemote, IssueOrPullRequest } from '../git/git';
import { GlyphChars } from '../constants'; import { GlyphChars } from '../constants';
const numRegex = /<num>/g; const numRegex = /<num>/g;
@ -50,7 +50,7 @@ export class Autolinks implements Disposable {
} }
@debug({ args: false }) @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; if (!remote.provider?.hasApi()) return undefined;
const { provider } = remote; const { provider } = remote;
@ -83,10 +83,16 @@ export class Autolinks implements Disposable {
if (ids.size === 0) return undefined; if (ids.size === 0) return undefined;
const issues = await Promises.raceAll(ids.values(), id => provider.getIssue(id), timeout); const issuesOrPullRequests = await Promises.raceAll(
if (issues.size === 0 || Iterables.every(issues.values(), pr => pr === undefined)) return undefined; 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 }) @debug({ args: false })
@ -94,10 +100,10 @@ export class Autolinks implements Disposable {
text: string, text: string,
markdown: boolean, markdown: boolean,
remotes?: GitRemote[], remotes?: GitRemote[],
issues?: Map<number, Issue | Promises.CancellationError | undefined> issuesOrPullRequests?: Map<number, IssueOrPullRequest | Promises.CancellationError | undefined>
) { ) {
for (const ref of this._references) { for (const ref of this._references) {
if (this.ensureAutolinkCached(ref, issues)) { if (this.ensureAutolinkCached(ref, issuesOrPullRequests)) {
if (ref.linkify != null) { if (ref.linkify != null) {
text = ref.linkify(text, markdown); text = ref.linkify(text, markdown);
} }
@ -109,7 +115,7 @@ export class Autolinks implements Disposable {
if (r.provider === undefined) continue; if (r.provider === undefined) continue;
for (const ref of r.provider.autolinks) { for (const ref of r.provider.autolinks) {
if (this.ensureAutolinkCached(ref, issues)) { if (this.ensureAutolinkCached(ref, issuesOrPullRequests)) {
if (ref.linkify != null) { if (ref.linkify != null) {
text = ref.linkify(text, markdown); text = ref.linkify(text, markdown);
} }
@ -123,7 +129,7 @@ export class Autolinks implements Disposable {
private ensureAutolinkCached( private ensureAutolinkCached(
ref: CacheableAutolinkReference | DynamicAutolinkReference, ref: CacheableAutolinkReference | DynamicAutolinkReference,
issues?: Map<number, Issue | Promises.CancellationError | undefined> issuesOrPullRequests?: Map<number, IssueOrPullRequest | Promises.CancellationError | undefined>
): ref is CacheableAutolinkReference | DynamicAutolinkReference { ): ref is CacheableAutolinkReference | DynamicAutolinkReference {
if (isDynamic(ref)) return true; 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')}${ const replacement = `[$1](${ref.url.replace(numRegex, '$2')}${
ref.title ? ` "${ref.title.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) => { ref.linkify = (text: string, markdown: boolean) => {
if (markdown) { if (markdown) {
return text.replace(ref.messageMarkdownRegex!, (substring, linkText, number) => { 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)}${ return `[${linkText}](${ref.url.replace(numRegex, number)}${
ref.title ref.title
@ -175,7 +181,7 @@ export class Autolinks implements Disposable {
let superscript; let superscript;
text = text.replace(ref.messageRegex!, (substring, linkText, number) => { 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 (issue == null) return linkText;
if (footnotes === undefined) { if (footnotes === undefined) {

+ 3
- 3
src/git/formatters/commitFormatter.ts View File

@ -11,7 +11,7 @@ import {
import { DateStyle, FileAnnotationType } from '../../configuration'; import { DateStyle, FileAnnotationType } from '../../configuration';
import { GlyphChars } from '../../constants'; import { GlyphChars } from '../../constants';
import { Container } from '../../container'; 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 { Promises, Strings } from '../../system';
import { FormatOptions, Formatter } from './formatter'; import { FormatOptions, Formatter } from './formatter';
import { ContactPresence } from '../../vsls/vsls'; import { ContactPresence } from '../../vsls/vsls';
@ -24,7 +24,7 @@ const hasTokenRegexMap = new Map();
export interface CommitFormatOptions extends FormatOptions { export interface CommitFormatOptions extends FormatOptions {
annotationType?: FileAnnotationType; annotationType?: FileAnnotationType;
autolinkedIssues?: Map<number, Issue | Promises.CancellationError | undefined>; autolinkedIssuesOrPullRequests?: Map<number, IssueOrPullRequest | Promises.CancellationError | undefined>;
dateStyle?: DateStyle; dateStyle?: DateStyle;
getBranchAndTagTips?: (sha: string) => string | undefined; getBranchAndTagTips?: (sha: string) => string | undefined;
line?: number; line?: number;
@ -354,7 +354,7 @@ export class CommitFormatter extends Formatter {
this._options.markdown ? Strings.escapeMarkdown(message, { quoted: true }) : message, this._options.markdown ? Strings.escapeMarkdown(message, { quoted: true }) : message,
this._options.markdown ?? false, this._options.markdown ?? false,
this._options.remotes, this._options.remotes,
this._options.autolinkedIssues this._options.autolinkedIssuesOrPullRequests
); );
} }

+ 2
- 1
src/git/models/issue.ts View File

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

+ 8
- 3
src/git/remotes/github.ts View File

@ -3,7 +3,7 @@ import { Disposable, env, QuickInputButton, Range, Uri, window } from 'vscode';
import { DynamicAutolinkReference } from '../../annotations/autolinks'; import { DynamicAutolinkReference } from '../../annotations/autolinks';
import { AutolinkReference } from '../../config'; import { AutolinkReference } from '../../config';
import { Container } from '../../container'; import { Container } from '../../container';
import { Issue } from '../models/issue'; import { IssueOrPullRequest } from '../models/issue';
import { PullRequest } from '../models/pullRequest'; import { PullRequest } from '../models/pullRequest';
import { RemoteProviderWithApi } from './provider'; import { RemoteProviderWithApi } from './provider';
@ -136,9 +136,14 @@ export class GitHubRemote extends RemoteProviderWithApi<{ token: string }> {
return `${this.baseUrl}?path=${fileName}${line}`; 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(); 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( protected async onGetPullRequestForCommit(

+ 4
- 4
src/git/remotes/provider.ts View File

@ -6,7 +6,7 @@ import { Container } from '../../container';
import { CredentialChangeEvent, CredentialManager } from '../../credentials'; import { CredentialChangeEvent, CredentialManager } from '../../credentials';
import { Logger } from '../../logger'; import { Logger } from '../../logger';
import { Messages } from '../../messages'; import { Messages } from '../../messages';
import { Issue } from '../models/issue'; import { IssueOrPullRequest } from '../models/issue';
import { GitLogCommit } from '../models/logCommit'; import { GitLogCommit } from '../models/logCommit';
import { PullRequest } from '../models/pullRequest'; import { PullRequest } from '../models/pullRequest';
import { debug, gate, Promises } from '../../system'; import { debug, gate, Promises } from '../../system';
@ -246,14 +246,14 @@ export abstract class RemoteProviderWithApi extends
@gate() @gate()
@debug() @debug()
async getIssue(id: number): Promise<Issue | undefined> { async getIssueOrPullRequest(id: number): Promise<IssueOrPullRequest | undefined> {
const cc = Logger.getCorrelationContext(); const cc = Logger.getCorrelationContext();
const connected = this.maybeConnected ?? (await this.isConnected()); const connected = this.maybeConnected ?? (await this.isConnected());
if (!connected) return undefined; if (!connected) return undefined;
try { try {
return await this.onGetIssue(this._credentials!, id); return await this.onGetIssueOrPullRequest(this._credentials!, id);
} catch (ex) { } catch (ex) {
Logger.error(ex, cc); Logger.error(ex, cc);
@ -276,7 +276,7 @@ export abstract class RemoteProviderWithApi extends
return pr.then(pr => pr ?? undefined); 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 abstract onGetPullRequestForCommit(credentials: T, ref: string): Promise<PullRequest | undefined>;
protected _credentials: T | null | undefined; protected _credentials: T | null | undefined;

+ 21
- 10
src/github/github.ts View File

@ -2,7 +2,7 @@
import { graphql } from '@octokit/graphql'; import { graphql } from '@octokit/graphql';
import { Logger } from '../logger'; import { Logger } from '../logger';
import { debug } from '../system'; import { debug } from '../system';
import { Issue, PullRequest, PullRequestState } from '../git/gitService'; import { IssueOrPullRequest, PullRequest, PullRequestState } from '../git/gitService';
export class GitHubApi { export class GitHubApi {
@debug({ @debug({
@ -86,7 +86,7 @@ export class GitHubApi {
1: token => '<token>' 1: token => '<token>'
} }
}) })
async getIssue( async getIssueOrPullRequest(
provider: string, provider: string,
token: string, token: string,
owner: string, owner: string,
@ -95,17 +95,26 @@ export class GitHubApi {
options?: { options?: {
baseUrl?: string; baseUrl?: string;
} }
): Promise<Issue | undefined> { ): Promise<IssueOrPullRequest | undefined> {
const cc = Logger.getCorrelationContext(); const cc = Logger.getCorrelationContext();
try { try {
const query = `query pr($owner: String!, $repo: String!, $number: Int!) { const query = `query pr($owner: String!, $repo: String!, $number: Int!) {
repository(name: $repo, owner: $owner) { repository(name: $repo, owner: $owner) {
issue(number: $number) { issueOrPullRequest(number: $number) {
createdAt __typename
closed ... on Issue {
closedAt createdAt
title closed
closedAt
title
}
... on PullRequest {
createdAt
closed
closedAt
title
}
} }
} }
}`; }`;
@ -118,11 +127,12 @@ export class GitHubApi {
headers: { authorization: `token ${token}` }, headers: { authorization: `token ${token}` },
...options ...options
}); });
const issue = rsp?.repository?.issue as GitHubIssue | undefined; const issue = rsp?.repository?.issueOrPullRequest as GitHubIssueOrPullRequest | undefined;
if (issue == null) return undefined; if (issue == null) return undefined;
return { return {
provider: provider, provider: provider,
type: issue.type,
id: number, id: number,
date: new Date(issue.createdAt), date: new Date(issue.createdAt),
title: issue.title, title: issue.title,
@ -136,7 +146,8 @@ export class GitHubApi {
} }
} }
interface GitHubIssue { interface GitHubIssueOrPullRequest {
type: 'Issue' | 'PullRequest';
number: number; number: number;
createdAt: string; createdAt: string;
closed: boolean; closed: boolean;

||||||
x
 
000:0
Loading…
Cancel
Save