Skip to content

Commit 7fa7629

Browse files
committed
Adds default branch autolink to git providers
1 parent b5cbe84 commit 7fa7629

File tree

11 files changed

+111
-23
lines changed

11 files changed

+111
-23
lines changed

src/annotations/autolinks.ts

Lines changed: 49 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,7 @@ export interface CacheableAutolinkReference extends AutolinkReference {
9292
messageHtmlRegex?: RegExp;
9393
messageMarkdownRegex?: RegExp;
9494
messageRegex?: RegExp;
95+
branchNameRegex?: RegExp;
9596
}
9697

9798
export interface DynamicAutolinkReference {
@@ -155,6 +156,11 @@ export class Autolinks implements Disposable {
155156
}
156157
}
157158

159+
async getAutolinks(
160+
branchName: string,
161+
remote?: GitRemote,
162+
options?: { isBranchName: true },
163+
): Promise<Map<string, Autolink>>;
158164
async getAutolinks(message: string, remote?: GitRemote): Promise<Map<string, Autolink>>;
159165
async getAutolinks(
160166
message: string,
@@ -169,9 +175,9 @@ export class Autolinks implements Disposable {
169175
},
170176
})
171177
async getAutolinks(
172-
message: string,
178+
messageOrBranchName: string,
173179
remote?: GitRemote,
174-
options?: { excludeCustom?: boolean },
180+
options?: { excludeCustom?: boolean; isBranchName?: boolean },
175181
): Promise<Map<string, Autolink>> {
176182
const refsets: [
177183
ProviderReference | undefined,
@@ -212,26 +218,52 @@ export class Autolinks implements Disposable {
212218

213219
const autolinks = new Map<string, Autolink>();
214220

221+
const matchRef = (ref: RequireSome<CacheableAutolinkReference, 'messageRegex' | 'branchNameRegex'>) =>
222+
options?.isBranchName
223+
? ref.branchNameRegex.exec(messageOrBranchName)
224+
: ref.messageRegex.exec(messageOrBranchName);
225+
215226
let match;
216227
let num;
217228
for (const [provider, refs] of refsets) {
218229
for (const ref of refs) {
219230
if (!isCacheable(ref)) {
220231
if (isDynamic(ref)) {
221-
ref.parse(message, autolinks);
232+
ref.parse(messageOrBranchName, autolinks);
222233
}
223234
continue;
224235
}
225236

237+
if (ref.referenceType) {
238+
if (ref.referenceType !== 'branchName' && options?.isBranchName) {
239+
continue;
240+
}
241+
if (ref.referenceType !== 'message' && !options?.isBranchName) {
242+
continue;
243+
}
244+
}
245+
226246
ensureCachedRegex(ref, 'plaintext');
227247

228248
do {
229-
match = ref.messageRegex.exec(message);
230-
if (match == null) break;
231-
232-
[, , , num] = match;
249+
match = matchRef(ref);
250+
if (!match?.groups) break;
251+
252+
num = match.groups.issueKeyNumber;
253+
let key = num;
254+
if (autolinks.has(key)) {
255+
const prevAutolink = autolinks.get(key)!;
256+
if (!ref.prefix) {
257+
continue;
258+
} else if (!prevAutolink.prefix && ref.prefix) {
259+
/** override */
260+
} else {
261+
// add more autolinks
262+
key = ref.prefix + num;
263+
}
264+
}
233265

234-
autolinks.set(num, {
266+
autolinks.set(key, {
235267
provider: provider,
236268
id: num,
237269
prefix: ref.prefix,
@@ -615,27 +647,30 @@ function ensureCachedRegex(
615647
function ensureCachedRegex(
616648
ref: CacheableAutolinkReference,
617649
outputFormat: 'plaintext',
618-
): asserts ref is RequireSome<CacheableAutolinkReference, 'messageRegex'>;
650+
): asserts ref is RequireSome<CacheableAutolinkReference, 'messageRegex' | 'branchNameRegex'>;
619651
function ensureCachedRegex(ref: CacheableAutolinkReference, outputFormat: 'html' | 'markdown' | 'plaintext') {
620652
// Regexes matches the ref prefix followed by a token (e.g. #1234)
621653
if (outputFormat === 'markdown' && ref.messageMarkdownRegex == null) {
622654
// Extra `\\\\` in `\\\\\\[` is because the markdown is escaped
623655
ref.messageMarkdownRegex = new RegExp(
624-
`(^|\\s|\\(|\\[|\\{)(${escapeRegex(encodeHtmlWeak(escapeMarkdown(ref.prefix)))}(${
625-
ref.alphanumeric ? '\\w' : '\\d'
626-
}+))\\b`,
656+
`(^|\\s|\\(|\\[|\\{)(?<issueKeyNumber>${escapeRegex(
657+
encodeHtmlWeak(escapeMarkdown(ref.prefix)),
658+
)}(?<issueKeyNumber>${ref.alphanumeric ? '\\w' : '\\d'}+))\\b`,
627659
ref.ignoreCase ? 'gi' : 'g',
628660
);
629661
} else if (outputFormat === 'html' && ref.messageHtmlRegex == null) {
630662
ref.messageHtmlRegex = new RegExp(
631-
`(^|\\s|\\(|\\[|\\{)(${escapeRegex(encodeHtmlWeak(ref.prefix))}(${ref.alphanumeric ? '\\w' : '\\d'}+))\\b`,
663+
`(^|\\s|\\(|\\[|\\{)(${escapeRegex(encodeHtmlWeak(ref.prefix))}(?<issueKeyNumber>${
664+
ref.alphanumeric ? '\\w' : '\\d'
665+
}+))\\b`,
632666
ref.ignoreCase ? 'gi' : 'g',
633667
);
634668
} else if (ref.messageRegex == null) {
635669
ref.messageRegex = new RegExp(
636-
`(^|\\s|\\(|\\[|\\{)(${escapeRegex(ref.prefix)}(${ref.alphanumeric ? '\\w' : '\\d'}+))\\b`,
670+
`(^|\\s|\\(|\\[|\\{)(${escapeRegex(ref.prefix)}(?<issueKeyNumber>${ref.alphanumeric ? '\\w' : '\\d'}+))\\b`,
637671
ref.ignoreCase ? 'gi' : 'g',
638672
);
673+
ref.branchNameRegex = new RegExp(`(^|\\-|_)(?<prefix>${ref.prefix})(?<issueKeyNumber>\\d+)`, 'gi');
639674
}
640675

641676
return true;

src/config.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -253,6 +253,7 @@ export interface Config {
253253

254254
export type AnnotationsToggleMode = 'file' | 'window';
255255
export type AutolinkType = 'issue' | 'pullrequest';
256+
export type AutolinkReferenceType = 'message' | 'branchName';
256257

257258
export interface AutolinkReference {
258259
readonly prefix: string;
@@ -261,6 +262,9 @@ export interface AutolinkReference {
261262
readonly alphanumeric?: boolean;
262263
readonly ignoreCase?: boolean;
263264

265+
/** used to split some autolinks logic, consider that default undefined value means that the autolink is applicable for all reference types */
266+
readonly referenceType?: AutolinkReferenceType;
267+
264268
readonly type?: AutolinkType;
265269
readonly description?: string;
266270
readonly descriptor?: ResourceDescriptor;

src/git/remotes/azure-devops.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,15 +56,20 @@ export class AzureDevOpsRemote extends RemoteProvider {
5656
this.project = repoProject;
5757
}
5858

59+
protected override get issueLinkPattern(): string {
60+
const workUrl = this.baseUrl.replace(gitRegex, '/');
61+
return `${workUrl}/_workitems/edit/<num>`;
62+
}
63+
5964
private _autolinks: (AutolinkReference | DynamicAutolinkReference)[] | undefined;
6065
override get autolinks(): (AutolinkReference | DynamicAutolinkReference)[] {
6166
if (this._autolinks === undefined) {
6267
// Strip off any `_git` part from the repo url
63-
const workUrl = this.baseUrl.replace(gitRegex, '/');
6468
this._autolinks = [
69+
...super.autolinks,
6570
{
6671
prefix: '#',
67-
url: `${workUrl}/_workitems/edit/<num>`,
72+
url: this.issueLinkPattern,
6873
title: `Open Work Item #<num> on ${this.name}`,
6974

7075
type: 'issue',

src/git/remotes/bitbucket-server.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,18 @@ export class BitbucketServerRemote extends RemoteProvider {
1616
super(domain, path, protocol, name, custom);
1717
}
1818

19+
protected override get issueLinkPattern(): string {
20+
return `${this.baseUrl}/issues/<num>`;
21+
}
22+
1923
private _autolinks: (AutolinkReference | DynamicAutolinkReference)[] | undefined;
2024
override get autolinks(): (AutolinkReference | DynamicAutolinkReference)[] {
2125
if (this._autolinks === undefined) {
2226
this._autolinks = [
27+
...super.autolinks,
2328
{
2429
prefix: 'issue #',
25-
url: `${this.baseUrl}/issues/<num>`,
30+
url: this.issueLinkPattern,
2631
title: `Open Issue #<num> on ${this.name}`,
2732

2833
type: 'issue',

src/git/remotes/bitbucket.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,18 @@ export class BitbucketRemote extends RemoteProvider {
1616
super(domain, path, protocol, name, custom);
1717
}
1818

19+
protected override get issueLinkPattern(): string {
20+
return `${this.baseUrl}/issues/<num>`;
21+
}
22+
1923
private _autolinks: (AutolinkReference | DynamicAutolinkReference)[] | undefined;
2024
override get autolinks(): (AutolinkReference | DynamicAutolinkReference)[] {
2125
if (this._autolinks === undefined) {
2226
this._autolinks = [
27+
...super.autolinks,
2328
{
2429
prefix: 'issue #',
25-
url: `${this.baseUrl}/issues/<num>`,
30+
url: this.issueLinkPattern,
2631
title: `Open Issue #<num> on ${this.name}`,
2732

2833
type: 'issue',

src/git/remotes/custom.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,11 @@ export class CustomRemote extends RemoteProvider {
1414
this.urls = urls;
1515
}
1616

17+
protected override get issueLinkPattern(): string {
18+
// TODO: if it's ok, think about passing issue link to cfg.urls or using optional
19+
throw new Error('Method not implemented.');
20+
}
21+
1722
get id(): RemoteProviderId {
1823
return 'custom';
1924
}

src/git/remotes/gerrit.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,11 @@ export class GerritRemote extends RemoteProvider {
3434
super(domain, path, protocol, name, custom);
3535
}
3636

37+
protected override get issueLinkPattern(): string {
38+
// TODO: if it's ok, think about passing issue link to cfg.urls or using optional
39+
throw new Error('Method not implemented.');
40+
}
41+
3742
private _autolinks: (AutolinkReference | DynamicAutolinkReference)[] | undefined;
3843
override get autolinks(): (AutolinkReference | DynamicAutolinkReference)[] {
3944
if (this._autolinks === undefined) {

src/git/remotes/gitea.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,18 @@ export class GiteaRemote extends RemoteProvider {
1515
super(domain, path, protocol, name, custom);
1616
}
1717

18+
protected override get issueLinkPattern(): string {
19+
return `${this.baseUrl}/issues/<num>`;
20+
}
21+
1822
private _autolinks: (AutolinkReference | DynamicAutolinkReference)[] | undefined;
1923
override get autolinks(): (AutolinkReference | DynamicAutolinkReference)[] {
2024
if (this._autolinks === undefined) {
2125
this._autolinks = [
26+
...super.autolinks,
2227
{
2328
prefix: '#',
24-
url: `${this.baseUrl}/issues/<num>`,
29+
url: this.issueLinkPattern,
2530
title: `Open Issue #<num> on ${this.name}`,
2631

2732
type: 'issue',

src/git/remotes/github.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,20 +33,25 @@ export class GitHubRemote extends RemoteProvider<GitHubRepositoryDescriptor> {
3333
return this.custom ? `${this.protocol}://${this.domain}/api/v3` : `https://api.${this.domain}`;
3434
}
3535

36+
protected override get issueLinkPattern(): string {
37+
return `${this.baseUrl}/issues/<num>`;
38+
}
39+
3640
private _autolinks: (AutolinkReference | DynamicAutolinkReference)[] | undefined;
3741
override get autolinks(): (AutolinkReference | DynamicAutolinkReference)[] {
3842
if (this._autolinks === undefined) {
3943
this._autolinks = [
44+
...super.autolinks,
4045
{
4146
prefix: '#',
42-
url: `${this.baseUrl}/issues/<num>`,
47+
url: this.issueLinkPattern,
4348
title: `Open Issue or Pull Request #<num> on ${this.name}`,
4449

4550
description: `${this.name} Issue or Pull Request #<num>`,
4651
},
4752
{
4853
prefix: 'gh-',
49-
url: `${this.baseUrl}/issues/<num>`,
54+
url: this.issueLinkPattern,
5055
title: `Open Issue or Pull Request #<num> on ${this.name}`,
5156
ignoreCase: true,
5257

src/git/remotes/gitlab.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,13 +33,18 @@ export class GitLabRemote extends RemoteProvider<GitLabRepositoryDescriptor> {
3333
return this.custom ? `${this.protocol}://${this.domain}/api` : `https://${this.domain}/api`;
3434
}
3535

36+
protected override get issueLinkPattern(): string {
37+
return `${this.baseUrl}/-/issues/<num>`;
38+
}
39+
3640
private _autolinks: (AutolinkReference | DynamicAutolinkReference)[] | undefined;
3741
override get autolinks(): (AutolinkReference | DynamicAutolinkReference)[] {
3842
if (this._autolinks === undefined) {
3943
this._autolinks = [
44+
...super.autolinks,
4045
{
4146
prefix: '#',
42-
url: `${this.baseUrl}/-/issues/<num>`,
47+
url: this.issueLinkPattern,
4348
title: `Open Issue #<num> on ${this.name}`,
4449

4550
type: 'issue',

0 commit comments

Comments
 (0)