Browse Source

Fixes duplicated autolinks/issues/prs

Adds state prop to issues & prs
Removes some enums in favor of union types
main
Eric Amodio 1 year ago
parent
commit
1b2fddd714
29 changed files with 278 additions and 283 deletions
  1. +31
    -21
      src/annotations/autolinks.ts
  2. +1
    -2
      src/cache.ts
  3. +1
    -4
      src/config.ts
  4. +10
    -6
      src/git/formatters/commitFormatter.ts
  5. +70
    -40
      src/git/models/issue.ts
  6. +4
    -53
      src/git/models/pullRequest.ts
  7. +2
    -3
      src/git/models/reference.ts
  8. +2
    -3
      src/git/remotes/azure-devops.ts
  9. +2
    -3
      src/git/remotes/bitbucket-server.ts
  10. +2
    -2
      src/git/remotes/bitbucket.ts
  11. +1
    -2
      src/git/remotes/gitea.ts
  12. +4
    -5
      src/git/remotes/gitlab.ts
  13. +0
    -5
      src/hovers/hovers.ts
  14. +9
    -1
      src/plus/github/github.ts
  15. +6
    -12
      src/plus/github/models.ts
  16. +14
    -13
      src/plus/gitlab/gitlab.ts
  17. +5
    -17
      src/plus/gitlab/models.ts
  18. +4
    -0
      src/system/string.ts
  19. +7
    -15
      src/views/nodes/autolinkedItemNode.ts
  20. +2
    -3
      src/views/nodes/branchNode.ts
  21. +0
    -5
      src/views/nodes/commitNode.ts
  22. +0
    -5
      src/views/nodes/fileRevisionAsCommitNode.ts
  23. +5
    -6
      src/views/nodes/pullRequestNode.ts
  24. +0
    -5
      src/views/nodes/rebaseStatusNode.ts
  25. +2
    -3
      src/views/nodes/worktreeNode.ts
  26. +66
    -31
      src/webviews/apps/commitDetails/components/commit-details-app.ts
  27. +26
    -11
      src/webviews/apps/shared/components/rich/issue-pull-request.ts
  28. +0
    -5
      src/webviews/commitDetails/commitDetailsWebview.ts
  29. +2
    -2
      src/webviews/settings/settingsWebview.ts

+ 31
- 21
src/annotations/autolinks.ts View File

@ -15,7 +15,7 @@ import { debug } from '../system/decorators/log';
import { encodeUrl } from '../system/encoding';
import { join, map } from '../system/iterable';
import { Logger } from '../system/logger';
import { encodeHtmlWeak, escapeMarkdown, escapeRegex, getSuperscript } from '../system/string';
import { capitalize, encodeHtmlWeak, escapeMarkdown, escapeRegex, getSuperscript } from '../system/string';
const emptyAutolinkMap = Object.freeze(new Map<string, Autolink>());
@ -69,6 +69,7 @@ export interface CacheableAutolinkReference extends AutolinkReference {
outputFormat: 'html' | 'markdown' | 'plaintext',
tokenMapping: Map<string, string>,
enrichedAutolinks?: Map<string, MaybeEnrichedAutolink>,
prs?: Set<string>,
footnotes?: Map<number, string>,
) => string)
| null;
@ -85,6 +86,7 @@ export interface DynamicAutolinkReference {
outputFormat: 'html' | 'markdown' | 'plaintext',
tokenMapping: Map<string, string>,
enrichedAutolinks?: Map<string, MaybeEnrichedAutolink>,
prs?: Set<string>,
footnotes?: Map<number, string>,
) => string)
| null;
@ -260,6 +262,7 @@ export class Autolinks implements Disposable {
outputFormat: 'html' | 'markdown' | 'plaintext',
remotes?: GitRemote[],
enrichedAutolinks?: Map<string, MaybeEnrichedAutolink>,
prs?: Set<string>,
footnotes?: Map<number, string>,
): string {
const includeFootnotesInText = outputFormat === 'plaintext' && footnotes == null;
@ -272,7 +275,7 @@ export class Autolinks implements Disposable {
for (const ref of this._references) {
if (this.ensureAutolinkCached(ref)) {
if (ref.tokenize != null) {
text = ref.tokenize(text, outputFormat, tokenMapping, enrichedAutolinks, footnotes);
text = ref.tokenize(text, outputFormat, tokenMapping, enrichedAutolinks, prs, footnotes);
}
}
}
@ -289,7 +292,7 @@ export class Autolinks implements Disposable {
for (const ref of r.provider.autolinks) {
if (this.ensureAutolinkCached(ref)) {
if (ref.tokenize != null) {
text = ref.tokenize(text, outputFormat, tokenMapping, enrichedAutolinks, footnotes);
text = ref.tokenize(text, outputFormat, tokenMapping, enrichedAutolinks, prs, footnotes);
}
}
}
@ -324,6 +327,7 @@ export class Autolinks implements Disposable {
outputFormat: 'html' | 'markdown' | 'plaintext',
tokenMapping: Map<string, string>,
enrichedAutolinks?: Map<string, MaybeEnrichedAutolink>,
prs?: Set<string>,
footnotes?: Map<number, string>,
) => {
let footnoteIndex: number;
@ -343,7 +347,7 @@ export class Autolinks implements Disposable {
const issueResult = enrichedAutolinks?.get(num)?.[0];
if (issueResult?.value != null) {
if (issueResult.paused) {
if (footnotes != null) {
if (footnotes != null && !prs?.has(num)) {
let name = ref.description?.replace(numRegex, num);
if (name == null) {
name = `Custom Autolink ${ref.prefix}${num}`;
@ -361,7 +365,7 @@ export class Autolinks implements Disposable {
const issueTitle = escapeMarkdown(issue.title.trim());
const issueTitleQuoteEscaped = issueTitle.replace(/"/g, '\\"');
if (footnotes != null) {
if (footnotes != null && !prs?.has(num)) {
footnoteIndex = footnotes.size + 1;
footnotes.set(
footnoteIndex,
@ -369,17 +373,19 @@ export class Autolinks implements Disposable {
issue,
)} **${issueTitle}**](${url}${title}")\\\n${GlyphChars.Space.repeat(
5,
)}${linkText} ${issue.closed ? 'closed' : 'opened'} ${fromNow(
)}${linkText} ${issue.state} ${fromNow(
issue.closedDate ?? issue.date,
)}`,
);
}
title += `\n${GlyphChars.Dash.repeat(2)}\n${issueTitleQuoteEscaped}\n${
issue.closed ? 'Closed' : 'Opened'
}, ${fromNow(issue.closedDate ?? issue.date)}`;
title += `\n${GlyphChars.Dash.repeat(
2,
)}\n${issueTitleQuoteEscaped}\n${capitalize(issue.state)}, ${fromNow(
issue.closedDate ?? issue.date,
)}`;
}
} else if (footnotes != null) {
} else if (footnotes != null && !prs?.has(num)) {
let name = ref.description?.replace(numRegex, num);
if (name == null) {
name = `Custom Autolink ${ref.prefix}${num}`;
@ -413,7 +419,7 @@ export class Autolinks implements Disposable {
const issueResult = enrichedAutolinks?.get(num)?.[0];
if (issueResult?.value != null) {
if (issueResult.paused) {
if (footnotes != null) {
if (footnotes != null && !prs?.has(num)) {
let name = ref.description?.replace(numRegex, num);
if (name == null) {
name = `Custom Autolink ${ref.prefix}${num}`;
@ -431,7 +437,7 @@ export class Autolinks implements Disposable {
const issueTitle = encodeHtmlWeak(issue.title.trim());
const issueTitleQuoteEscaped = issueTitle.replace(/"/g, '&quot;');
if (footnotes != null) {
if (footnotes != null && !prs?.has(num)) {
footnoteIndex = footnotes.size + 1;
footnotes.set(
footnoteIndex,
@ -439,17 +445,19 @@ export class Autolinks implements Disposable {
issue,
)} <b>${issueTitle}</b></a><br /><span>${GlyphChars.Space.repeat(
5,
)}${linkText} ${issue.closed ? 'closed' : 'opened'} ${fromNow(
)}${linkText} ${issue.state} ${fromNow(
issue.closedDate ?? issue.date,
)}</span>`,
);
}
title += `\n${GlyphChars.Dash.repeat(2)}\n${issueTitleQuoteEscaped}\n${
issue.closed ? 'Closed' : 'Opened'
}, ${fromNow(issue.closedDate ?? issue.date)}`;
title += `\n${GlyphChars.Dash.repeat(
2,
)}\n${issueTitleQuoteEscaped}\n${capitalize(issue.state)}, ${fromNow(
issue.closedDate ?? issue.date,
)}`;
}
} else if (footnotes != null) {
} else if (footnotes != null && !prs?.has(num)) {
let name = ref.description?.replace(numRegex, num);
if (name == null) {
name = `Custom Autolink ${ref.prefix}${num}`;
@ -477,16 +485,18 @@ export class Autolinks implements Disposable {
const issueResult = enrichedAutolinks?.get(num)?.[0];
if (issueResult?.value == null) return linkText;
if (footnotes != null) {
if (footnotes != null && !prs?.has(num)) {
footnoteIndex = footnotes.size + 1;
footnotes.set(
footnoteIndex,
`${linkText}: ${
issueResult.paused
? 'Loading...'
: `${issueResult.value.title} ${GlyphChars.Dot} ${
issueResult.value.closed ? 'Closed' : 'Opened'
}, ${fromNow(issueResult.value.closedDate ?? issueResult.value.date)}`
: `${issueResult.value.title} ${GlyphChars.Dot} ${capitalize(
issueResult.value.state,
)}, ${fromNow(
issueResult.value.closedDate ?? issueResult.value.date,
)}`
}`,
);
}

+ 1
- 2
src/cache.ts View File

@ -4,7 +4,6 @@ import type { Container } from './container';
import type { DefaultBranch } from './git/models/defaultBranch';
import type { IssueOrPullRequest } from './git/models/issue';
import type { PullRequest } from './git/models/pullRequest';
import { PullRequestState } from './git/models/pullRequest';
import type { GitRemote } from './git/models/remote';
import type { RepositoryMetadata } from './git/models/repositoryMetadata';
import type { RemoteProvider } from './git/remotes/remoteProvider';
@ -201,7 +200,7 @@ function getExpiresAt(cache: T, value: CacheValue | undefine
// Open prs expire after 1 hour, but closed/merge prs expire after 12 hours unless recently updated and then expire in 1 hour
const pr = value as CacheValue<'prsBySha'>;
if (pr.state === PullRequestState.Open) return defaultExpiresAt;
if (pr.state === 'opened') return defaultExpiresAt;
const updatedAgo = now - (pr.closedDate ?? pr.mergedDate ?? pr.date).getTime();
return now + (updatedAgo > 14 * 24 * 60 * 60 * 1000 ? 12 : 1) * 60 * 60 * 1000;

+ 1
- 4
src/config.ts View File

@ -206,10 +206,7 @@ export const enum AnnotationsToggleMode {
Window = 'window',
}
export const enum AutolinkType {
Issue = 'Issue',
PullRequest = 'PullRequest',
}
export type AutolinkType = 'issue' | 'pullrequest';
export interface AutolinkReference {
prefix: string;

+ 10
- 6
src/git/formatters/commitFormatter.ts View File

@ -33,6 +33,7 @@ import type { PreviousLineComparisonUrisResult } from '../gitProvider';
import type { GitCommit } from '../models/commit';
import { isCommit } from '../models/commit';
import { uncommitted, uncommittedStaged } from '../models/constants';
import { getIssueOrPullRequestMarkdownIcon } from '../models/issue';
import { PullRequest } from '../models/pullRequest';
import { getReferenceFromRevision, isUncommittedStaged, shortenRevision } from '../models/reference';
import { GitRemote } from '../models/remote';
@ -657,6 +658,9 @@ export class CommitFormatter extends Formatter {
outputFormat,
this._options.remotes,
this._options.enrichedAutolinks,
this._options.pullRequest != null && !isPromise(this._options.pullRequest)
? new Set([this._options.pullRequest.id])
: undefined,
this._options.footnotes,
);
}
@ -687,8 +691,6 @@ export class CommitFormatter extends Formatter {
let text;
if (PullRequest.is(pr)) {
if (this._options.outputFormat === 'markdown') {
const prTitle = escapeMarkdown(pr.title).replace(/"/g, '\\"').trim();
text = `PR [**#${pr.id}**](${getMarkdownActionCommand<OpenPullRequestActionContext>('openPullRequest', {
repoPath: this._item.repoPath,
provider: { id: pr.provider.id, name: pr.provider.name, domain: pr.provider.domain },
@ -700,14 +702,16 @@ export class CommitFormatter extends Formatter {
}, ${pr.formatDateFromNow()}")`;
if (this._options.footnotes != null) {
const prTitle = escapeMarkdown(pr.title).replace(/"/g, '\\"').trim();
const index = this._options.footnotes.size + 1;
this._options.footnotes.set(
index,
`${PullRequest.getMarkdownIcon(pr)} [**${prTitle}**](${pr.url} "Open Pull Request \\#${
pr.id
} on ${pr.provider.name}")\\\n${GlyphChars.Space.repeat(4)} #${
`${getIssueOrPullRequestMarkdownIcon(pr)} [**${prTitle}**](${pr.url} "Open Pull Request \\#${
pr.id
} ${pr.state.toLocaleLowerCase()} ${pr.formatDateFromNow()}`,
} on ${pr.provider.name}")\\\n${GlyphChars.Space.repeat(4)} #${pr.id} ${
pr.state
} ${pr.formatDateFromNow()}`,
);
}
} else if (this._options.footnotes != null) {

+ 70
- 40
src/git/models/issue.ts View File

@ -2,10 +2,8 @@ import { ColorThemeKind, ThemeColor, ThemeIcon, window } from 'vscode';
import type { Colors } from '../../constants';
import type { RemoteProviderReference } from './remoteProvider';
export const enum IssueOrPullRequestType {
Issue = 'Issue',
PullRequest = 'PullRequest',
}
export type IssueOrPullRequestType = 'issue' | 'pullrequest';
export type IssueOrPullRequestState = 'opened' | 'closed' | 'merged';
export interface IssueOrPullRequest {
readonly type: IssueOrPullRequestType;
@ -16,6 +14,7 @@ export interface IssueOrPullRequest {
readonly date: Date;
readonly closedDate?: Date;
readonly closed: boolean;
readonly state: IssueOrPullRequestState;
}
export interface IssueLabel {
@ -64,6 +63,7 @@ export function serializeIssueOrPullRequest(value: IssueOrPullRequest): IssueOrP
date: value.date,
closedDate: value.closedDate,
closed: value.closed,
state: value.state,
};
return serialized;
}
@ -75,25 +75,33 @@ export function getIssueOrPullRequestHtmlIcon(issue?: IssueOrPullRequest): strin
};"></span>`;
}
if (issue.type === IssueOrPullRequestType.PullRequest) {
if (issue.type === 'pullrequest') {
switch (issue.state) {
case 'merged':
return `<span class="codicon codicon-git-merge" style="color:${
window.activeColorTheme.kind === ColorThemeKind.Dark ? '#a371f7' : '#8250df'
};"></span>`;
case 'closed':
return `<span class="codicon codicon-git-pull-request-closed" style="color:${
window.activeColorTheme.kind === ColorThemeKind.Dark ? '#f85149' : '#cf222e'
};"></span>`;
case 'opened':
return `<span class="codicon codicon-git-pull-request" style="color:${
window.activeColorTheme.kind === ColorThemeKind.Dark ? '#3fb950' : '#1a7f37'
};"></span>`;
default:
return `<span class="codicon codicon-git-pull-request"></span>`;
}
} else {
if (issue.closed) {
return `<span class="codicon codicon-git-pull-request" style="color:${
return `<span class="codicon codicon-pass" style="color:${
window.activeColorTheme.kind === ColorThemeKind.Dark ? '#a371f7' : '#8250df'
};"></span>`;
}
return `<span class="codicon codicon-git-pull-request" style="color:${
return `<span class="codicon codicon-issues" style="color:${
window.activeColorTheme.kind === ColorThemeKind.Dark ? '#3fb950' : '#1a7f37'
};"></span>`;
}
if (issue.closed) {
return `<span class="codicon codicon-pass" style="color:${
window.activeColorTheme.kind === ColorThemeKind.Dark ? '#a371f7' : '#8250df'
};"></span>`;
}
return `<span class="codicon codicon-issues" style="color:${
window.activeColorTheme.kind === ColorThemeKind.Dark ? '#3fb950' : '#1a7f37'
};"></span>`;
}
export function getIssueOrPullRequestMarkdownIcon(issue?: IssueOrPullRequest): string {
@ -103,25 +111,33 @@ export function getIssueOrPullRequestMarkdownIcon(issue?: IssueOrPullRequest): s
};">$(link)</span>`;
}
if (issue.type === IssueOrPullRequestType.PullRequest) {
if (issue.type === 'pullrequest') {
switch (issue.state) {
case 'merged':
return `<span style="color:${
window.activeColorTheme.kind === ColorThemeKind.Dark ? '#a371f7' : '#8250df'
};">$(git-merge)</span>`;
case 'closed':
return `<span style="color:${
window.activeColorTheme.kind === ColorThemeKind.Dark ? '#f85149' : '#cf222e'
};">$(git-pull-request-closed)</span>`;
case 'opened':
return `<span style="color:${
window.activeColorTheme.kind === ColorThemeKind.Dark ? '#3fb950' : '#1a7f37'
};">$(git-pull-request)</span>`;
default:
return `$(git-pull-request)`;
}
} else {
if (issue.closed) {
return `<span style="color:${
window.activeColorTheme.kind === ColorThemeKind.Dark ? '#a371f7' : '#8250df'
};">$(git-pull-request)</span>`;
};">$(pass)</span>`;
}
return `<span style="color:${
window.activeColorTheme.kind === ColorThemeKind.Dark ? '#3fb950' : '#1a7f37'
};">$(git-pull-request)</span>`;
};">$(issues)</span>`;
}
if (issue.closed) {
return `<span style="color:${
window.activeColorTheme.kind === ColorThemeKind.Dark ? '#a371f7' : '#8250df'
};">$(pass)</span>`;
}
return `<span style="color:${
window.activeColorTheme.kind === ColorThemeKind.Dark ? '#3fb950' : '#1a7f37'
};">$(issues)</span>`;
}
export function getIssueOrPullRequestThemeIcon(issue?: IssueOrPullRequest): ThemeIcon {
@ -129,20 +145,32 @@ export function getIssueOrPullRequestThemeIcon(issue?: IssueOrPullRequest): Them
return new ThemeIcon('link', new ThemeColor('gitlens.closedAutolinkedIssueIconColor' satisfies Colors));
}
if (issue.type === IssueOrPullRequestType.PullRequest) {
if (issue.type === 'pullrequest') {
switch (issue.state) {
case 'merged':
return new ThemeIcon(
'git-merge',
new ThemeColor('gitlens.mergedPullRequestIconColor' satisfies Colors),
);
case 'closed':
return new ThemeIcon(
'git-pull-request-closed',
new ThemeColor('gitlens.closedPullRequestIconColor' satisfies Colors),
);
case 'opened':
return new ThemeIcon(
'git-pull-request',
new ThemeColor('gitlens.openPullRequestIconColor' satisfies Colors),
);
default:
return new ThemeIcon('git-pull-request');
}
} else {
if (issue.closed) {
return new ThemeIcon(
'git-pull-request',
new ThemeColor('gitlens.mergedPullRequestIconColor' satisfies Colors),
);
return new ThemeIcon('pass', new ThemeColor('gitlens.closedAutolinkedIssueIconColor' satisfies Colors));
}
return new ThemeIcon('git-pull-request', new ThemeColor('gitlens.openPullRequestIconColor' satisfies Colors));
}
if (issue.closed) {
return new ThemeIcon('pass', new ThemeColor('gitlens.closedAutolinkedIssueIconColor' satisfies Colors));
return new ThemeIcon('issues', new ThemeColor('gitlens.openAutolinkedIssueIconColor' satisfies Colors));
}
return new ThemeIcon('issues', new ThemeColor('gitlens.openAutolinkedIssueIconColor' satisfies Colors));
}
export function serializeIssue(value: IssueShape): IssueShape {
@ -160,6 +188,7 @@ export function serializeIssue(value: IssueShape): IssueShape {
date: value.date,
closedDate: value.closedDate,
closed: value.closed,
state: value.state,
updatedDate: value.updatedDate,
author: {
name: value.author.name,
@ -189,7 +218,7 @@ export function serializeIssue(value: IssueShape): IssueShape {
}
export class Issue implements IssueShape {
readonly type = IssueOrPullRequestType.Issue;
readonly type = 'issue';
constructor(
public readonly provider: RemoteProviderReference,
@ -198,6 +227,7 @@ export class Issue implements IssueShape {
public readonly url: string,
public readonly date: Date,
public readonly closed: boolean,
public readonly state: IssueOrPullRequestState,
public readonly updatedDate: Date,
public readonly author: IssueMember,
public readonly repository: IssueRepository,

+ 4
- 53
src/git/models/pullRequest.ts View File

@ -1,18 +1,11 @@
import { ColorThemeKind, ThemeColor, ThemeIcon, window } from 'vscode';
import { DateStyle } from '../../config';
import type { Colors } from '../../constants';
import { Container } from '../../container';
import { formatDate, fromNow } from '../../system/date';
import { memoize } from '../../system/decorators/memoize';
import type { IssueOrPullRequest } from './issue';
import { IssueOrPullRequestType } from './issue';
import type { IssueOrPullRequest, IssueOrPullRequestState as PullRequestState } from './issue';
import type { RemoteProviderReference } from './remoteProvider';
export const enum PullRequestState {
Open = 'Open',
Closed = 'Closed',
Merged = 'Merged',
}
export type { PullRequestState };
export const enum PullRequestReviewDecision {
Approved = 'Approved',
@ -54,7 +47,6 @@ export interface PullRequestReviewer {
export interface PullRequestShape extends IssueOrPullRequest {
readonly author: PullRequestMember;
readonly state: PullRequestState;
readonly mergedDate?: Date;
readonly refs?: PullRequestRefs;
readonly isDraft?: boolean;
@ -132,48 +124,7 @@ export class PullRequest implements PullRequestShape {
return pr instanceof PullRequest;
}
static getMarkdownIcon(pullRequest: PullRequest): string {
switch (pullRequest.state) {
case PullRequestState.Open:
return `<span style="color:${
window.activeColorTheme.kind === ColorThemeKind.Dark ? '#3fb950' : '#1a7f37'
};">$(git-pull-request)</span>`;
case PullRequestState.Closed:
return `<span style="color:${
window.activeColorTheme.kind === ColorThemeKind.Dark ? '#f85149' : '#cf222e'
};">$(git-pull-request-closed)</span>`;
case PullRequestState.Merged:
return `<span style="color:${
window.activeColorTheme.kind === ColorThemeKind.Dark ? '#a371f7' : '#8250df'
};">$(git-merge)</span>`;
default:
return '$(git-pull-request)';
}
}
static getThemeIcon(pullRequest: PullRequest): ThemeIcon {
switch (pullRequest.state) {
case PullRequestState.Open:
return new ThemeIcon(
'git-pull-request',
new ThemeColor('gitlens.openPullRequestIconColor' satisfies Colors),
);
case PullRequestState.Closed:
return new ThemeIcon(
'git-pull-request-closed',
new ThemeColor('gitlens.closedPullRequestIconColor' satisfies Colors),
);
case PullRequestState.Merged:
return new ThemeIcon(
'git-merge',
new ThemeColor('gitlens.mergedPullRequestIconColor' satisfies Colors),
);
default:
return new ThemeIcon('git-pull-request');
}
}
readonly type = IssueOrPullRequestType.PullRequest;
readonly type = 'pullrequest';
constructor(
public readonly provider: RemoteProviderReference,
@ -201,7 +152,7 @@ export class PullRequest implements PullRequestShape {
) {}
get closed(): boolean {
return this.state === PullRequestState.Closed;
return this.state === 'closed';
}
get formattedDate(): string {

+ 2
- 3
src/git/models/reference.ts View File

@ -1,5 +1,6 @@
import { GlyphChars } from '../../constants';
import { configuration } from '../../system/configuration';
import { capitalize } from '../../system/string';
import { getBranchNameWithoutRemote, getRemoteNameFromBranchName, getRemoteNameSlashIndex } from './branch';
import { deletedOrMissing, uncommitted, uncommittedStaged } from './constants';
@ -379,9 +380,7 @@ export function getReferenceLabel(
}
}
return options.capitalize && options.expand && options.label !== false
? `${result[0].toLocaleUpperCase()}${result.substring(1)}`
: result;
return options.capitalize && options.expand && options.label !== false ? capitalize(result) : result;
}
const expanded = options.expand ? ` (${refs.map(r => r.name).join(', ')})` : '';

+ 2
- 3
src/git/remotes/azure-devops.ts View File

@ -1,7 +1,6 @@
import type { Range, Uri } from 'vscode';
import type { DynamicAutolinkReference } from '../../annotations/autolinks';
import type { AutolinkReference } from '../../config';
import { AutolinkType } from '../../config';
import type { Repository } from '../models/repository';
import { RemoteProvider } from './remoteProvider';
@ -53,7 +52,7 @@ export class AzureDevOpsRemote extends RemoteProvider {
url: `${workUrl}/_workitems/edit/<num>`,
title: `Open Work Item #<num> on ${this.name}`,
type: AutolinkType.Issue,
type: 'issue',
description: `${this.name} Work Item #<num>`,
},
{
@ -62,7 +61,7 @@ export class AzureDevOpsRemote extends RemoteProvider {
url: `${this.baseUrl}/pullrequest/<num>`,
title: `Open Pull Request #<num> on ${this.name}`,
type: AutolinkType.PullRequest,
type: 'pullrequest',
description: `${this.name} Pull Request #<num>`,
},
];

+ 2
- 3
src/git/remotes/bitbucket-server.ts View File

@ -1,7 +1,6 @@
import type { Range, Uri } from 'vscode';
import type { DynamicAutolinkReference } from '../../annotations/autolinks';
import type { AutolinkReference } from '../../config';
import { AutolinkType } from '../../config';
import { isSha } from '../models/reference';
import type { Repository } from '../models/repository';
import { RemoteProvider } from './remoteProvider';
@ -23,7 +22,7 @@ export class BitbucketServerRemote extends RemoteProvider {
url: `${this.baseUrl}/issues/<num>`,
title: `Open Issue #<num> on ${this.name}`,
type: AutolinkType.Issue,
type: 'issue',
description: `${this.name} Issue #<num>`,
},
{
@ -32,7 +31,7 @@ export class BitbucketServerRemote extends RemoteProvider {
url: `${this.baseUrl}/pull-requests/<num>`,
title: `Open Pull Request #<num> on ${this.name}`,
type: AutolinkType.PullRequest,
type: 'pullrequest',
description: `${this.name} Pull Request #<num>`,
},
];

+ 2
- 2
src/git/remotes/bitbucket.ts View File

@ -23,7 +23,7 @@ export class BitbucketRemote extends RemoteProvider {
url: `${this.baseUrl}/issues/<num>`,
title: `Open Issue #<num> on ${this.name}`,
type: AutolinkType.Issue,
type: 'issue',
description: `${this.name} Issue #<num>`,
},
{
@ -31,7 +31,7 @@ export class BitbucketRemote extends RemoteProvider {
url: `${this.baseUrl}/pull-requests/<num>`,
title: `Open Pull Request #<num> on ${this.name}`,
type: AutolinkType.PullRequest,
type: 'pullrequest',
description: `${this.name} Pull Request #<num>`,
},
];

+ 1
- 2
src/git/remotes/gitea.ts View File

@ -1,7 +1,6 @@
import type { Range, Uri } from 'vscode';
import type { DynamicAutolinkReference } from '../../annotations/autolinks';
import type { AutolinkReference } from '../../config';
import { AutolinkType } from '../../config';
import { isSha } from '../models/reference';
import type { Repository } from '../models/repository';
import { RemoteProvider } from './remoteProvider';
@ -23,7 +22,7 @@ export class GiteaRemote extends RemoteProvider {
url: `${this.baseUrl}/issues/<num>`,
title: `Open Issue #<num> on ${this.name}`,
type: AutolinkType.Issue,
type: 'issue',
description: `${this.name} Issue #<num>`,
},
];

+ 4
- 5
src/git/remotes/gitlab.ts View File

@ -2,7 +2,6 @@ import type { AuthenticationSession, Disposable, QuickInputButton, Range } from
import { env, ThemeIcon, Uri, window } from 'vscode';
import type { Autolink, DynamicAutolinkReference } from '../../annotations/autolinks';
import type { AutolinkReference } from '../../config';
import { AutolinkType } from '../../config';
import type { Container } from '../../container';
import type {
IntegrationAuthenticationProvider,
@ -57,7 +56,7 @@ export class GitLabRemote extends RichRemoteProvider {
url: `${this.baseUrl}/-/issues/<num>`,
title: `Open Issue #<num> on ${this.name}`,
type: AutolinkType.Issue,
type: 'issue',
description: `${this.name} Issue #<num>`,
},
{
@ -65,7 +64,7 @@ export class GitLabRemote extends RichRemoteProvider {
url: `${this.baseUrl}/-/merge_requests/<num>`,
title: `Open Merge Request !<num> on ${this.name}`,
type: AutolinkType.PullRequest,
type: 'pullrequest',
description: `${this.name} Merge Request !<num>`,
},
{
@ -108,7 +107,7 @@ export class GitLabRemote extends RichRemoteProvider {
url: `${this.protocol}://${this.domain}/${repo}/-/issues/${num}`,
title: `Open Issue #<num> from ${repo} on ${this.name}`,
type: AutolinkType.Issue,
type: 'issue',
description: `${this.name} Issue ${repo}#${num}`,
});
} while (true);
@ -159,7 +158,7 @@ export class GitLabRemote extends RichRemoteProvider {
url: `${this.protocol}://${this.domain}/${repo}/-/merge_requests/${num}`,
title: `Open Merge Request !<num> from ${repo} on ${this.name}`,
type: AutolinkType.PullRequest,
type: 'pullrequest',
description: `Merge Request !${num} from ${repo} on ${this.name}`,
});
} while (true);

+ 0
- 5
src/hovers/hovers.ts View File

@ -268,11 +268,6 @@ export async function detailsMessage(
const presence = getSettledValue(presenceResult);
const previousLineComparisonUris = getSettledValue(previousLineComparisonUrisResult);
// Remove possible duplicate pull request
if (pr?.value != null && !pr.paused && enrichedResult?.value != null && !enrichedResult.paused) {
enrichedResult.value?.delete(pr.value.id);
}
const details = await CommitFormatter.fromTemplateAsync(options.format, commit, {
enrichedAutolinks: enrichedResult?.value != null && !enrichedResult.paused ? enrichedResult.value : undefined,
dateFormat: options.dateFormat === null ? 'MMMM Do, YYYY h:mma' : options.dateFormat,

+ 9
- 1
src/plus/github/github.ts View File

@ -59,7 +59,12 @@ import type {
GitHubPullRequestState,
GitHubTag,
} from './models';
import { fromGitHubIssueDetailed, fromGitHubPullRequest, fromGitHubPullRequestDetailed } from './models';
import {
fromGitHubIssueDetailed,
fromGitHubPullRequest,
fromGitHubPullRequestDetailed,
fromGitHubPullRequestState,
} from './models';
const emptyPagedResult: PagedResult<any> = Object.freeze({ values: [] });
const emptyBlameResult: GitHubBlame = Object.freeze({ ranges: [] });
@ -487,6 +492,7 @@ export class GitHubApi implements Disposable {
closedAt
title
url
state
}
... on PullRequest {
createdAt
@ -494,6 +500,7 @@ export class GitHubApi implements Disposable {
closedAt
title
url
state
}
}
}
@ -524,6 +531,7 @@ export class GitHubApi implements Disposable {
closed: issue.closed,
closedDate: issue.closedAt == null ? undefined : new Date(issue.closedAt),
url: issue.url,
state: fromGitHubPullRequestState(issue.state),
};
} catch (ex) {
if (ex instanceof ProviderRequestNotFoundError) return undefined;

+ 6
- 12
src/plus/github/models.ts View File

@ -2,12 +2,8 @@ import type { Endpoints } from '@octokit/types';
import { GitFileIndexStatus } from '../../git/models/file';
import type { IssueLabel, IssueMember, IssueOrPullRequestType } from '../../git/models/issue';
import { Issue } from '../../git/models/issue';
import {
PullRequest,
PullRequestMergeableState,
PullRequestReviewDecision,
PullRequestState,
} from '../../git/models/pullRequest';
import type { PullRequestState } from '../../git/models/pullRequest';
import { PullRequest, PullRequestMergeableState, PullRequestReviewDecision } from '../../git/models/pullRequest';
import type { RichRemoteProvider } from '../../git/remotes/richRemoteProvider';
export interface GitHubBlame {
@ -56,6 +52,7 @@ export interface GitHubIssueOrPullRequest {
closedAt: string | null;
title: string;
url: string;
state: GitHubPullRequestState;
}
export interface GitHubPagedResult<T> {
@ -185,15 +182,11 @@ export function fromGitHubPullRequest(pr: GitHubPullRequest, provider: RichRemot
}
export function fromGitHubPullRequestState(state: GitHubPullRequestState): PullRequestState {
return state === 'MERGED'
? PullRequestState.Merged
: state === 'CLOSED'
? PullRequestState.Closed
: PullRequestState.Open;
return state === 'MERGED' ? 'merged' : state === 'CLOSED' ? 'closed' : 'opened';
}
export function toGitHubPullRequestState(state: PullRequestState): GitHubPullRequestState {
return state === PullRequestState.Merged ? 'MERGED' : state === PullRequestState.Closed ? 'CLOSED' : 'OPEN';
return state === 'merged' ? 'MERGED' : state === 'closed' ? 'CLOSED' : 'OPEN';
}
export function fromGitHubPullRequestReviewDecision(
@ -320,6 +313,7 @@ export function fromGitHubIssueDetailed(value: GitHubIssueDetailed, provider: Ri
value.url,
new Date(value.createdAt),
value.closed,
fromGitHubPullRequestState(value.state),
new Date(value.updatedAt),
{
name: value.author.login,

+ 14
- 13
src/plus/gitlab/gitlab.ts View File

@ -18,7 +18,6 @@ import {
import type { Account } from '../../git/models/author';
import type { DefaultBranch } from '../../git/models/defaultBranch';
import type { IssueOrPullRequest } from '../../git/models/issue';
import { IssueOrPullRequestType } from '../../git/models/issue';
import { PullRequest } from '../../git/models/pullRequest';
import type { RepositoryMetadata } from '../../git/models/repositoryMetadata';
import type { RichRemoteProvider } from '../../git/remotes/richRemoteProvider';
@ -38,10 +37,11 @@ import type {
GitLabIssue,
GitLabMergeRequest,
GitLabMergeRequestREST,
GitLabMergeRequestState,
GitLabProjectREST,
GitLabUser,
} from './models';
import { fromGitLabMergeRequestREST, fromGitLabMergeRequestState, GitLabMergeRequestState } from './models';
import { fromGitLabMergeRequestREST, fromGitLabMergeRequestState } from './models';
export class GitLabApi implements Disposable {
private readonly _disposable: Disposable;
@ -318,13 +318,14 @@ export class GitLabApi implements Disposable {
const issue = rsp.data.project.issue;
return {
provider: provider,
type: IssueOrPullRequestType.Issue,
type: 'issue',
id: issue.iid,
date: new Date(issue.createdAt),
title: issue.title,
closed: issue.state === 'closed',
closedDate: issue.closedAt == null ? undefined : new Date(issue.closedAt),
url: issue.webUrl,
state: issue.state === 'locked' ? 'closed' : issue.state,
};
}
@ -332,7 +333,7 @@ export class GitLabApi implements Disposable {
const mergeRequest = rsp.data.project.mergeRequest;
return {
provider: provider,
type: IssueOrPullRequestType.PullRequest,
type: 'pullrequest',
id: mergeRequest.iid,
date: new Date(mergeRequest.createdAt),
title: mergeRequest.title,
@ -340,6 +341,7 @@ export class GitLabApi implements Disposable {
// TODO@eamodio this isn't right, but GitLab doesn't seem to provide a closedAt on merge requests in GraphQL
closedDate: mergeRequest.state === 'closed' ? new Date(mergeRequest.updatedAt) : undefined,
url: mergeRequest.webUrl,
state: mergeRequest.state === 'locked' ? 'closed' : mergeRequest.state,
};
}
@ -417,21 +419,21 @@ export class GitLabApi implements Disposable {
: ''
}
${
options?.include?.includes(GitLabMergeRequestState.OPEN)
options?.include?.includes('opened')
? `opened: mergeRequests(sourceBranches: $branches state: opened sort: UPDATED_DESC first: 1) {
${fragment}
}`
: ''
}
${
options?.include?.includes(GitLabMergeRequestState.MERGED)
options?.include?.includes('merged')
? `merged: mergeRequests(sourceBranches: $branches state: merged sort: UPDATED_DESC first: 1) {
${fragment}
}`
: ''
}
${
options?.include?.includes(GitLabMergeRequestState.CLOSED)
options?.include?.includes('closed')
? `closed: mergeRequests(sourceBranches: $branches state: closed sort: UPDATED_DESC first: 1) {
${fragment}
}`
@ -461,11 +463,11 @@ export class GitLabApi implements Disposable {
} else {
for (const state of options.include) {
let mr;
if (state === GitLabMergeRequestState.OPEN) {
if (state === 'opened') {
mr = rsp?.data?.project?.opened?.nodes?.[0];
} else if (state === GitLabMergeRequestState.MERGED) {
} else if (state === 'merged') {
mr = rsp?.data?.project?.merged?.nodes?.[0];
} else if (state === GitLabMergeRequestState.CLOSED) {
} else if (state === 'closed') {
mr = rsp?.data?.project?.closed?.nodes?.[0];
}
@ -490,7 +492,7 @@ export class GitLabApi implements Disposable {
fromGitLabMergeRequestState(pr.state),
new Date(pr.updatedAt),
// TODO@eamodio this isn't right, but GitLab doesn't seem to provide a closedAt on merge requests in GraphQL
pr.state !== GitLabMergeRequestState.CLOSED ? undefined : new Date(pr.updatedAt),
pr.state !== 'closed' ? undefined : new Date(pr.updatedAt),
pr.mergedAt == null ? undefined : new Date(pr.mergedAt),
);
} catch (ex) {
@ -536,8 +538,7 @@ export class GitLabApi implements Disposable {
if (mrs.length > 1) {
mrs.sort(
(a, b) =>
(a.state === GitLabMergeRequestState.OPEN ? -1 : 1) -
(b.state === GitLabMergeRequestState.OPEN ? -1 : 1) ||
(a.state === 'opened' ? -1 : 1) - (b.state === 'opened' ? -1 : 1) ||
new Date(b.updated_at).getTime() - new Date(a.updated_at).getTime(),
);
}

+ 5
- 17
src/plus/gitlab/models.ts View File

@ -1,4 +1,5 @@
import { PullRequest, PullRequestState } from '../../git/models/pullRequest';
import type { PullRequestState } from '../../git/models/pullRequest';
import { PullRequest } from '../../git/models/pullRequest';
import type { RichRemoteProvider } from '../../git/remotes/richRemoteProvider';
export interface GitLabUser {
@ -60,27 +61,14 @@ export interface GitLabMergeRequest {
webUrl: string;
}
export enum GitLabMergeRequestState {
OPEN = 'opened',
CLOSED = 'closed',
MERGED = 'merged',
LOCKED = 'locked',
}
export type GitLabMergeRequestState = 'opened' | 'closed' | 'locked' | 'merged';
export function fromGitLabMergeRequestState(state: GitLabMergeRequestState): PullRequestState {
return state === GitLabMergeRequestState.MERGED
? PullRequestState.Merged
: state === GitLabMergeRequestState.CLOSED || state === GitLabMergeRequestState.LOCKED
? PullRequestState.Closed
: PullRequestState.Open;
return state === 'locked' ? 'closed' : state;
}
export function toGitLabMergeRequestState(state: PullRequestState): GitLabMergeRequestState {
return state === PullRequestState.Merged
? GitLabMergeRequestState.MERGED
: state === PullRequestState.Closed
? GitLabMergeRequestState.CLOSED
: GitLabMergeRequestState.OPEN;
return state;
}
export interface GitLabMergeRequestREST {

+ 4
- 0
src/system/string.ts View File

@ -4,6 +4,10 @@ import { CharCode } from '../constants';
export { fromBase64, base64 } from '@env/base64';
export function capitalize(s: string) {
return `${s[0].toLocaleUpperCase()}${s.slice(1)}`;
}
let compareCollator: Intl.Collator | undefined;
export function compareIgnoreCase(a: string, b: string): 0 | -1 | 1 {
if (compareCollator == null) {

+ 7
- 15
src/views/nodes/autolinkedItemNode.ts View File

@ -1,13 +1,8 @@
import { MarkdownString, ThemeIcon, TreeItem, TreeItemCollapsibleState } from 'vscode';
import type { Autolink } from '../../annotations/autolinks';
import { AutolinkType } from '../../config';
import { GitUri } from '../../git/gitUri';
import type { IssueOrPullRequest } from '../../git/models/issue';
import {
getIssueOrPullRequestMarkdownIcon,
getIssueOrPullRequestThemeIcon,
IssueOrPullRequestType,
} from '../../git/models/issue';
import { getIssueOrPullRequestMarkdownIcon, getIssueOrPullRequestThemeIcon } from '../../git/models/issue';
import { fromNow } from '../../system/date';
import { isPromise } from '../../system/promise';
import type { ViewsWithCommits } from '../viewBase';
@ -62,7 +57,7 @@ export class AutolinkedItemNode extends ViewNode {
? 'loading~spin'
: autolink.type == null
? 'link'
: autolink.type === AutolinkType.PullRequest
: autolink.type === 'pullrequest'
? 'git-pull-request'
: 'issues',
);
@ -74,7 +69,7 @@ export class AutolinkedItemNode extends ViewNode {
: `${
autolink.type == null
? 'Autolinked'
: autolink.type === AutolinkType.PullRequest
: autolink.type === 'pullrequest'
? 'Autolinked Pull Request'
: 'Autolinked Issue'
} ${autolink.prefix}${autolink.id}`
@ -88,14 +83,11 @@ export class AutolinkedItemNode extends ViewNode {
const item = new TreeItem(`${enriched.id}: ${enriched.title}`, TreeItemCollapsibleState.None);
item.description = relativeTime;
item.iconPath = getIssueOrPullRequestThemeIcon(enriched);
item.contextValue =
enriched.type === IssueOrPullRequestType.PullRequest
? ContextValues.PullRequest
: ContextValues.AutolinkedIssue;
item.contextValue = enriched.type === 'pullrequest' ? ContextValues.PullRequest : ContextValues.AutolinkedIssue;
const linkTitle = ` "Open ${
enriched.type === IssueOrPullRequestType.PullRequest ? 'Pull Request' : 'Issue'
} \\#${enriched.id} on ${enriched.provider.name}"`;
const linkTitle = ` "Open ${enriched.type === 'pullrequest' ? 'Pull Request' : 'Issue'} \\#${enriched.id} on ${
enriched.provider.name
}"`;
const tooltip = new MarkdownString(
`${getIssueOrPullRequestMarkdownIcon(enriched)} [**${enriched.title.trim()}**](${
enriched.url

+ 2
- 3
src/views/nodes/branchNode.ts View File

@ -6,8 +6,7 @@ import { GlyphChars } from '../../constants';
import type { GitUri } from '../../git/gitUri';
import type { GitBranch } from '../../git/models/branch';
import type { GitLog } from '../../git/models/log';
import type { PullRequest } from '../../git/models/pullRequest';
import { PullRequestState } from '../../git/models/pullRequest';
import type { PullRequest , PullRequestState } from '../../git/models/pullRequest';
import type { GitBranchReference } from '../../git/models/reference';
import { GitRemote, GitRemoteType } from '../../git/models/remote';
import type { Repository } from '../../git/models/repository';
@ -152,7 +151,7 @@ export class BranchNode extends ViewRefNode
onCompleted = defer<void>();
const prPromise = this.getAssociatedPullRequest(
branch,
this.root ? { include: [PullRequestState.Open, PullRequestState.Merged] } : undefined,
this.root ? { include: ['opened', 'merged'] } : undefined,
);
queueMicrotask(async () => {

+ 0
- 5
src/views/nodes/commitNode.ts View File

@ -256,11 +256,6 @@ export class CommitNode extends ViewRefNode
enrichedAutolinks = getSettledValue(enrichedAutolinksResult)?.value;
pr = getSettledValue(prResult);
// Remove possible duplicate pull request
if (pr != null) {
enrichedAutolinks?.delete(pr.id);
}
}
const tooltip = await CommitFormatter.fromTemplateAsync(this.view.config.formats.commits.tooltip, this.commit, {

+ 0
- 5
src/views/nodes/fileRevisionAsCommitNode.ts View File

@ -223,11 +223,6 @@ export class FileRevisionAsCommitNode extends ViewRefFileNode
enrichedAutolinks = getSettledValue(enrichedAutolinksResult)?.value;
pr = getSettledValue(prResult);
// Remove possible duplicate pull request
if (pr != null) {
enrichedAutolinks?.delete(pr.id);
}
}
const status = StatusFileFormatter.fromTemplate(

+ 5
- 6
src/views/nodes/pullRequestNode.ts View File

@ -2,7 +2,8 @@ import { MarkdownString, TreeItem, TreeItemCollapsibleState } from 'vscode';
import { GitUri } from '../../git/gitUri';
import { GitBranch } from '../../git/models/branch';
import type { GitCommit } from '../../git/models/commit';
import { PullRequest, PullRequestState } from '../../git/models/pullRequest';
import { getIssueOrPullRequestMarkdownIcon, getIssueOrPullRequestThemeIcon } from '../../git/models/issue';
import type { PullRequest } from '../../git/models/pullRequest';
import type { ViewsWithCommits } from '../viewBase';
import { ContextValues, getViewNodeId, ViewNode } from './viewNode';
@ -55,7 +56,7 @@ export class PullRequestNode extends ViewNode {
item.id = this.id;
item.contextValue = ContextValues.PullRequest;
item.description = `${this.pullRequest.state}, ${this.pullRequest.formatDateFromNow()}`;
item.iconPath = PullRequest.getThemeIcon(this.pullRequest);
item.iconPath = getIssueOrPullRequestThemeIcon(this.pullRequest);
const tooltip = new MarkdownString('', true);
tooltip.supportHtml = true;
@ -69,15 +70,13 @@ export class PullRequestNode extends ViewNode {
const linkTitle = ` "Open Pull Request \\#${this.pullRequest.id} on ${this.pullRequest.provider.name}"`;
tooltip.appendMarkdown(
`${PullRequest.getMarkdownIcon(this.pullRequest)} [**${this.pullRequest.title.trim()}**](${
`${getIssueOrPullRequestMarkdownIcon(this.pullRequest)} [**${this.pullRequest.title.trim()}**](${
this.pullRequest.url
}${linkTitle}) \\\n[#${this.pullRequest.id}](${this.pullRequest.url}${linkTitle}) by [@${
this.pullRequest.author.name
}](${this.pullRequest.author.url} "Open @${this.pullRequest.author.name} on ${
this.pullRequest.provider.name
}") was ${
this.pullRequest.state === PullRequestState.Open ? 'opened' : this.pullRequest.state.toLowerCase()
} ${this.pullRequest.formatDateFromNow()}`,
}") was ${this.pullRequest.state.toLowerCase()} ${this.pullRequest.formatDateFromNow()}`,
);
item.tooltip = tooltip;

+ 0
- 5
src/views/nodes/rebaseStatusNode.ts View File

@ -215,11 +215,6 @@ export class RebaseCommitNode extends ViewRefNode
enrichedAutolinks = getSettledValue(enrichedAutolinksResult)?.value;
pr = getSettledValue(prResult);
// Remove possible duplicate pull request
if (pr != null) {
enrichedAutolinks?.delete(pr.id);
}
}
const tooltip = await CommitFormatter.fromTemplateAsync(

+ 2
- 3
src/views/nodes/worktreeNode.ts View File

@ -3,8 +3,7 @@ import { GlyphChars } from '../../constants';
import type { GitUri } from '../../git/gitUri';
import type { GitBranch } from '../../git/models/branch';
import type { GitLog } from '../../git/models/log';
import type { PullRequest } from '../../git/models/pullRequest';
import { PullRequestState } from '../../git/models/pullRequest';
import type { PullRequest , PullRequestState } from '../../git/models/pullRequest';
import { shortenRevision } from '../../git/models/reference';
import { GitRemote, GitRemoteType } from '../../git/models/remote';
import type { GitWorktree } from '../../git/models/worktree';
@ -80,7 +79,7 @@ export class WorktreeNode extends ViewNode {
if (pullRequest === undefined && this.getState('pendingPullRequest') === undefined) {
onCompleted = defer<void>();
const prPromise = this.getAssociatedPullRequest(branch, {
include: [PullRequestState.Open, PullRequestState.Merged],
include: ['opened', 'merged'],
});
queueMicrotask(async () => {

+ 66
- 31
src/webviews/apps/commitDetails/components/commit-details-app.ts View File

@ -3,7 +3,10 @@ import { html, LitElement, nothing } from 'lit';
import { customElement, property, state } from 'lit/decorators.js';
import { unsafeHTML } from 'lit/directives/unsafe-html.js';
import { when } from 'lit/directives/when.js';
import type { Autolink } from '../../../../annotations/autolinks';
import { ViewFilesLayout } from '../../../../config';
import type { IssueOrPullRequest } from '../../../../git/models/issue';
import type { PullRequestShape } from '../../../../git/models/pullRequest';
import type { HierarchicalItem } from '../../../../system/array';
import { makeHierarchical } from '../../../../system/array';
import type { Serialized } from '../../../../system/serialize';
@ -147,21 +150,44 @@ export class GlCommitDetailsApp extends LitElement {
return undefined;
}
const autolinkedIssuesCount = this.state?.autolinkedIssues?.length ?? 0;
let autolinksCount = this.state?.selected?.autolinks?.length ?? 0;
let count = autolinksCount;
const hasPullRequest = this.state?.pullRequest != null;
const hasAutolinks = hasPullRequest || autolinkedIssuesCount > 0 || autolinksCount > 0;
let dedupedAutolinks = this.state?.selected?.autolinks;
if (hasAutolinks) {
if (dedupedAutolinks?.length && autolinkedIssuesCount) {
dedupedAutolinks = dedupedAutolinks.filter(
autolink => !this.state?.autolinkedIssues?.some(issue => issue.url === autolink.url),
);
autolinksCount = dedupedAutolinks?.length ?? 0;
count = (hasPullRequest ? 1 : 0) + autolinkedIssuesCount + autolinksCount;
const deduped = new Map<
string,
| { type: 'autolink'; value: Serialized<Autolink> }
| { type: 'issue'; value: Serialized<IssueOrPullRequest> }
| { type: 'pr'; value: Serialized<PullRequestShape> }
>();
if (this.state?.selected?.autolinks != null) {
for (const autolink of this.state.selected.autolinks) {
deduped.set(autolink.id, { type: 'autolink', value: autolink });
}
}
if (this.state?.autolinkedIssues != null) {
for (const issue of this.state.autolinkedIssues) {
deduped.set(issue.id, { type: 'issue', value: issue });
}
}
if (this.state?.pullRequest != null) {
deduped.set(this.state.pullRequest.id, { type: 'pr', value: this.state.pullRequest });
}
const autolinks: Serialized<Autolink>[] = [];
const issues: Serialized<IssueOrPullRequest>[] = [];
const prs: Serialized<PullRequestShape>[] = [];
for (const item of deduped.values()) {
switch (item.type) {
case 'autolink':
autolinks.push(item.value);
break;
case 'issue':
issues.push(item.value);
break;
case 'pr':
prs.push(item.value);
break;
}
}
@ -174,7 +200,7 @@ export class GlCommitDetailsApp extends LitElement {
>
<span slot="title">Autolinks</span>
<span slot="subtitle" data-region="autolink-count"
>${this.state?.includeRichContent || autolinksCount ? `${count} found ` : ''}${this.state
>${this.state?.includeRichContent || deduped.size ? `${deduped.size} found ` : ''}${this.state
?.includeRichContent
? ''
: '…'}</span
@ -195,7 +221,7 @@ export class GlCommitDetailsApp extends LitElement {
</div>
`,
() => {
if (!hasAutolinks || count === 0) {
if (deduped.size === 0) {
return html`
<div class="section" data-region="rich-info">
<p>
@ -211,20 +237,21 @@ export class GlCommitDetailsApp extends LitElement {
}
return html`
<div class="section" data-region="autolinks">
${dedupedAutolinksan> != null && dedupedAutolinks.length > 0
${autolinks.length
? html`
<section
class="auto-link"
aria-label="Custom Autolinks"
data-region="custom-autolinks"
>
${dedupedAutolinks.map(autolink => {
${autolinks.map(autolink => {
let name = autolink.description ?? autolink.title;
if (name === undefined) {
name = `Custom Autolink ${autolink.prefix}${autolink.id}`;
}
return html`
<issue-pull-request
type="autolink"
name="${name}"
url="${autolink.url}"
key="${autolink.prefix}${autolink.id}"
@ -235,35 +262,43 @@ export class GlCommitDetailsApp extends LitElement {
</section>
`
: undefined}
${hasPullRequest
${prs.length
? html`
<section
class="pull-request"
aria-label="Pull request"
data-region="pull-request"
>
<issue-pull-request
name="${this.state!.pullRequest!.title}"
url="${this.state!.pullRequest!.url}"
key="#${this.state!.pullRequest!.id}"
status="${this.state!.pullRequest!.state}"
date=${this.state!.pullRequest!.date}
dateFormat="${this.state!.dateFormat}"
></issue-pull-request>
${prs.map(
pr => html`
<issue-pull-request
type="pr"
name="${pr.title}"
url="${pr.url}"
key="#${pr.id}"
status="${pr.state}"
date=${pr.date}
dateFormat="${this.state!.dateFormat}"
></issue-pull-request>
</section>
`,
)}
</section>
`
: undefined}
${this.state?.autolinkedIssues?.length
${issues.length
? html`
<section class="issue" aria-label="Issue" data-region="issue">
${this.state.autolinkedIssues.map(
${issues.map(
issue => html`
<issue-pull-request
type="issue"
name="${issue.title}"
url="${issue.url}"
key="${issue.id}"
status="${issue.closed ? 'closed' : 'opened'}"
status="${issue.state}"
date="${issue.closed ? issue.closedDate : issue.date}"
dateFormat="${this.state!.dateFormat}"
></issue-pull-request>
`,
)}

+ 26
- 11
src/webviews/apps/shared/components/rich/issue-pull-request.ts View File

@ -50,10 +50,13 @@ export class IssuePullRequest extends LitElement {
date = '';
@property()
status = 'merged';
status: 'opened' | 'closed' | 'merged' = 'merged';
@property()
key = '#1999';
type: 'autolink' | 'issue' | 'pr' = 'autolink';
@property()
key = '';
renderDate() {
if (this.date === '') {
@ -63,16 +66,28 @@ export class IssuePullRequest extends LitElement {
}
override render() {
let icon = 'issues';
switch (this.status.toLowerCase()) {
case '':
icon = 'link';
let icon;
switch (this.type) {
case 'issue':
icon = this.status === 'closed' ? 'pass' : 'issues';
break;
case 'merged':
icon = 'git-merge';
case 'pr':
switch (this.status) {
case 'merged':
icon = 'git-merge';
break;
case 'closed':
icon = 'git-pull-request-closed';
break;
case 'opened':
default:
icon = 'git-pull-request';
break;
}
break;
case 'closed':
icon = 'pass';
case 'autolink':
default:
icon = 'link';
break;
}
@ -81,7 +96,7 @@ export class IssuePullRequest extends LitElement {
<p class="title">
<a href="${this.url}">${this.name}</a>
</p>
<p class="date">${this.key} ${this.status === '' ? this.status : nothing} ${this.renderDate()}</p>
<p class="date">${this.key} ${this.status ? this.status : nothing} ${this.renderDate()}</p>
`;
}
}

+ 0
- 5
src/webviews/commitDetails/commitDetailsWebview.ts View File

@ -523,11 +523,6 @@ export class CommitDetailsWebviewProvider implements WebviewProvider
const formattedMessage = this.getFormattedMessage(commit, remote, enrichedAutolinks);
// Remove possible duplicate pull request
if (pr != null) {
enrichedAutolinks?.delete(pr.id);
}
this.updatePendingContext({
richStateLoaded: true,
formattedMessage: formattedMessage,

+ 2
- 2
src/webviews/settings/settingsWebview.ts View File

@ -6,7 +6,7 @@ import type { Container } from '../../container';
import { CommitFormatter } from '../../git/formatters/commitFormatter';
import { GitCommit, GitCommitIdentity } from '../../git/models/commit';
import { GitFileChange, GitFileIndexStatus } from '../../git/models/file';
import { PullRequest, PullRequestState } from '../../git/models/pullRequest';
import { PullRequest } from '../../git/models/pullRequest';
import type { ConfigPath } from '../../system/configuration';
import { configuration } from '../../system/configuration';
import { map } from '../../system/iterable';
@ -202,7 +202,7 @@ export class SettingsWebviewProvider implements WebviewProvider {
'1',
'Supercharged',
'https://github.com/gitkraken/vscode-gitlens/pulls/1',
PullRequestState.Merged,
'merged',
new Date('Sat, 12 Nov 2016 19:41:00 GMT'),
undefined,
new Date('Sat, 12 Nov 2016 20:41:00 GMT'),

Loading…
Cancel
Save