Skip to content

Adds Open tag on remote option to tagView #3937

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 46 additions & 0 deletions contributions.json
Original file line number Diff line number Diff line change
@@ -629,6 +629,11 @@
]
}
},
"gitlens.copyRemoteTagUrl": {
"label": "Copy Remote Tag URL",
"icon": "$(copy)",
"commandPalette": "gitlens:repos:withRemotes"
},
"gitlens.copyShaToClipboard": {
"label": "Copy SHA",
"icon": "$(copy)",
@@ -3315,6 +3320,11 @@
]
}
},
"gitlens.openTagOnRemote": {
"label": "Open Tag on Remote",
"icon": "$(globe)",
"commandPalette": "gitlens:repos:withRemotes"
},
"gitlens.openWorkingFile": {
"label": "Open File",
"icon": "$(go-to-file)",
@@ -6206,6 +6216,10 @@
]
}
},
"gitlens.views.copyRemoteTagUrl": {
"label": "Copy Remote Tag URL",
"icon": "$(copy)"
},
"gitlens.views.copyUrl": {
"label": "Copy URL",
"icon": "$(copy)",
@@ -7762,6 +7776,38 @@
]
}
},
"gitlens.views.openTagOnRemote": {
"label": "Open Tag on Remote",
"icon": "$(globe)",
"menus": {
"view/item/context": [
{
"when": "viewItem =~ /gitlens:tag\\b/ && gitlens:repos:withRemotes",
"group": "inline",
"order": 100,
"alt": "gitlens.views.copyRemoteTagUrl"
},
{
"when": "viewItem =~ /gitlens:tag\\b/ && !listMultiSelection && gitlens:repos:withRemotes",
"group": "3_gitlens_explore",
"order": 3
}
]
}
},
"gitlens.views.openTagOnRemote.multi": {
"label": "Open Tags on Remote",
"icon": "$(globe)",
"menus": {
"view/item/context": [
{
"when": "viewItem =~ /gitlens:tag\\b/ && listMultiSelection && gitlens:repos:withRemotes",
"group": "3_gitlens_explore",
"order": 3
}
]
}
},
"gitlens.views.openUrl": {
"label": "Open URL",
"icon": "$(globe)",
76 changes: 75 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -3733,7 +3733,8 @@
"fileInCommit",
"fileInBranch",
"fileLine",
"fileRange"
"fileRange",
"tag"
],
"properties": {
"repository": {
@@ -3771,6 +3772,10 @@
"fileRange": {
"type": "string",
"markdownDescription": "Specifies the format of a range in a file URL for the custom remote service\n\nAvailable tokens\\\n`${start}` — starting line\\\n`${end}` — ending line"
},
"tag": {
"type": "string",
"markdownDescription": "Specifies the format of a tag URL for the custom remote service\n\nAvailable tokens\\\n`${repo}` — repository path\\\n`${tagName}` — name of the tag"
}
},
"additionalProperties": false
@@ -4754,6 +4759,7 @@
"default": {
"suppressCommitHasNoPreviousCommitWarning": false,
"suppressCommitNotFoundWarning": false,
"suppressTagNotFoundWarning": false,
"suppressCreatePullRequestPrompt": false,
"suppressDebugLoggingWarning": false,
"suppressFileNotUnderSourceControlWarning": false,
@@ -4780,6 +4786,11 @@
"default": false,
"description": "Commit Not Found Warning"
},
"suppressTagNotFoundWarning": {
"type": "boolean",
"default": false,
"description": "Tag Not Found Warning"
},
"suppressCreatePullRequestPrompt": {
"type": "boolean",
"default": false,
@@ -5986,6 +5997,12 @@
"title": "Copy Remote Repository URL",
"icon": "$(copy)"
},
{
"command": "gitlens.copyRemoteTagUrl",
"title": "Copy Remote Tag URL",
"category": "GitLens",
"icon": "$(copy)"
},
{
"command": "gitlens.copyShaToClipboard",
"title": "Copy SHA",
@@ -7019,6 +7036,12 @@
"icon": "$(gitlens-open-revision)",
"enablement": "gitlens:enabled && resourceScheme =~ /^(gitlens|pr)$/ "
},
{
"command": "gitlens.openTagOnRemote",
"title": "Open Tag on Remote",
"category": "GitLens",
"icon": "$(globe)"
},
{
"command": "gitlens.openWorkingFile",
"title": "Open File",
@@ -7960,6 +7983,11 @@
"title": "Copy Remote Commit URLs",
"icon": "$(copy)"
},
{
"command": "gitlens.views.copyRemoteTagUrl",
"title": "Copy Remote Tag URL",
"icon": "$(copy)"
},
{
"command": "gitlens.views.copyUrl",
"title": "Copy URL",
@@ -8435,6 +8463,16 @@
"title": "Compare Pull Request",
"icon": "$(compare-changes)"
},
{
"command": "gitlens.views.openTagOnRemote",
"title": "Open Tag on Remote",
"icon": "$(globe)"
},
{
"command": "gitlens.views.openTagOnRemote.multi",
"title": "Open Tags on Remote",
"icon": "$(globe)"
},
{
"command": "gitlens.views.openUrl",
"title": "Open URL",
@@ -10081,6 +10119,10 @@
"command": "gitlens.copyRemoteRepositoryUrl",
"when": "false"
},
{
"command": "gitlens.copyRemoteTagUrl",
"when": "gitlens:repos:withRemotes"
},
{
"command": "gitlens.copyShaToClipboard",
"when": "resource in gitlens:tabs:blameable"
@@ -10893,6 +10935,10 @@
"command": "gitlens.openRevisionFileInDiffRight",
"when": "false"
},
{
"command": "gitlens.openTagOnRemote",
"when": "gitlens:repos:withRemotes"
},
{
"command": "gitlens.openWorkingFile",
"when": "gitlens:enabled && resourceScheme =~ /^(gitlens|git|pr)$/"
@@ -11621,6 +11667,10 @@
"command": "gitlens.views.copyRemoteCommitUrl.multi",
"when": "false"
},
{
"command": "gitlens.views.copyRemoteTagUrl",
"when": "false"
},
{
"command": "gitlens.views.copyUrl",
"when": "false"
@@ -12017,6 +12067,14 @@
"command": "gitlens.views.openPullRequestComparison",
"when": "false"
},
{
"command": "gitlens.views.openTagOnRemote",
"when": "false"
},
{
"command": "gitlens.views.openTagOnRemote.multi",
"when": "false"
},
{
"command": "gitlens.views.openUrl",
"when": "false"
@@ -16884,6 +16942,12 @@
"when": "viewItem =~ /gitlens:tag\\b/ && !gitlens:hasVirtualFolders && !gitlens:readonly && !gitlens:untrusted",
"group": "inline@10"
},
{
"command": "gitlens.views.openTagOnRemote",
"when": "viewItem =~ /gitlens:tag\\b/ && gitlens:repos:withRemotes",
"group": "inline@100",
"alt": "gitlens.views.copyRemoteTagUrl"
},
{
"command": "gitlens.views.switchToTag",
"when": "viewItem =~ /gitlens:tag\\b/ && !listMultiSelection && !gitlens:hasVirtualFolders && !gitlens:readonly && !gitlens:untrusted",
@@ -16904,6 +16968,16 @@
"when": "viewItem =~ /gitlens:tag\\b/ && !listMultiSelection && !gitlens:hasVirtualFolders && !gitlens:readonly && !gitlens:untrusted",
"group": "1_gitlens_actions@3"
},
{
"command": "gitlens.views.openTagOnRemote",
"when": "viewItem =~ /gitlens:tag\\b/ && !listMultiSelection && gitlens:repos:withRemotes",
"group": "3_gitlens_explore@3"
},
{
"command": "gitlens.views.openTagOnRemote.multi",
"when": "viewItem =~ /gitlens:tag\\b/ && listMultiSelection && gitlens:repos:withRemotes",
"group": "3_gitlens_explore@3"
},
{
"command": "gitlens.views.createTag",
"when": "viewItem =~ /gitlens:tags\\b/ && !gitlens:hasVirtualFolders && !gitlens:readonly && !gitlens:untrusted",
1 change: 1 addition & 0 deletions src/commands.ts
Original file line number Diff line number Diff line change
@@ -33,6 +33,7 @@ import './commands/openBranchOnRemote';
import './commands/openCurrentBranchOnRemote';
import './commands/openChangedFiles';
import './commands/openCommitOnRemote';
import './commands/openTagOnRemote';
import './commands/openComparisonOnRemote';
import './commands/openFileFromRemote';
import './commands/openFileOnRemote';
14 changes: 7 additions & 7 deletions src/commands/openOnRemote.ts
Original file line number Diff line number Diff line change
@@ -196,13 +196,13 @@ export class OpenOnRemoteCommand extends GlCommandBase {
break;
}

// case RemoteResourceType.Tag: {
// title = getTitlePrefix('Tag');
// if (resources.length === 1) {
// title += `${pad(GlyphChars.Dot, 2, 2)}${args.resource.tag}`;
// }
// break;
// }
case RemoteResourceType.Tag: {
title = getTitlePrefix('Tag');
if (resources.length === 1) {
title += `${pad(GlyphChars.Dot, 2, 2)}${resource.tag}`;
}
break;
}
}

const pick = await showRemoteProviderPicker(title, placeholder, resources, remotes, options);
98 changes: 98 additions & 0 deletions src/commands/openTagOnRemote.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
import type { TextEditor, Uri } from 'vscode';
import { GlCommand } from '../constants.commands';
import type { Container } from '../container';
import { GitUri } from '../git/gitUri';
import { RemoteResourceType } from '../git/models/remoteResource';
import { showGenericErrorMessage } from '../messages';
import { CommandQuickPickItem } from '../quickpicks/items/common';
import { ReferencesQuickPickIncludes, showReferencePicker } from '../quickpicks/referencePicker';
import { getBestRepositoryOrShowPicker } from '../quickpicks/repositoryPicker';
import { command, executeCommand } from '../system/-webview/command';
import { Logger } from '../system/logger';
import { ActiveEditorCommand } from './commandBase';
import { getCommandUri } from './commandBase.utils';
import type { CommandContext } from './commandContext';
import { isCommandContextViewNodeHasTag } from './commandContext.utils';
import type { OpenOnRemoteCommandArgs } from './openOnRemote';

export interface OpenTagOnRemoteCommandArgs {
tag?: string;
clipboard?: boolean;
remote?: string;
}

@command()
export class OpenTagOnRemoteCommand extends ActiveEditorCommand {
constructor(private readonly container: Container) {
super([GlCommand.OpenTagOnRemote, GlCommand.CopyRemoteTagUrl]);
}

protected override preExecute(context: CommandContext, args?: OpenTagOnRemoteCommandArgs): Promise<void> {
if (isCommandContextViewNodeHasTag(context)) {
args = {
...args,
tag: context.node.tag.name,
remote: context.node.tag.name,
};
}

if (context.command === GlCommand.CopyRemoteTagUrl) {
args = { ...args, clipboard: true };
}

return this.execute(context.editor, context.uri, args);
}

async execute(editor?: TextEditor, uri?: Uri, args?: OpenTagOnRemoteCommandArgs): Promise<void> {
uri = getCommandUri(uri, editor);

const gitUri = uri != null ? await GitUri.fromUri(uri) : undefined;

const repoPath = (
await getBestRepositoryOrShowPicker(
gitUri,
editor,
args?.clipboard ? 'Copy Remote Tag URL' : 'Open Tag On Remote',
)
)?.path;
if (!repoPath) return;

args = { ...args };

try {
if (args.tag == null) {
const pick = await showReferencePicker(
repoPath,
args.clipboard ? 'Copy Remote Tag URL' : 'Open Tag On Remote',
args.clipboard ? 'Choose a Tag to copy the URL from' : 'Choose a Tag to open',
{
autoPick: true,
filter: { tags: () => true, branches: () => false },
include: ReferencesQuickPickIncludes.Tags,
sort: { tags: { current: true } },
},
);
if (pick == null || pick instanceof CommandQuickPickItem) return;

if (pick.refType === 'tag') {
args.tag = pick.name;
} else {
args.tag = pick.ref;
}
}

void (await executeCommand<OpenOnRemoteCommandArgs>(GlCommand.OpenOnRemote, {
resource: {
type: RemoteResourceType.Tag,
tag: args.tag,
},
repoPath: repoPath,
remote: args.remote,
clipboard: args.clipboard,
}));
} catch (ex) {
Logger.error(ex, 'OpenTagOnRemoteCommand');
void showGenericErrorMessage('Unable to open Tag on remote provider');
}
}
}
2 changes: 2 additions & 0 deletions src/config.ts
Original file line number Diff line number Diff line change
@@ -155,6 +155,7 @@ export const enum StatusBarCommand {
export type SuppressedMessages =
| 'suppressCommitHasNoPreviousCommitWarning'
| 'suppressCommitNotFoundWarning'
| 'suppressTagNotFoundWarning'
| 'suppressCreatePullRequestPrompt'
| 'suppressDebugLoggingWarning'
| 'suppressFileNotUnderSourceControlWarning'
@@ -626,6 +627,7 @@ export interface RemotesUrlsConfig {
readonly fileInCommit: string;
readonly fileLine: string;
readonly fileRange: string;
readonly tag: string;
}

interface StatusBarConfig {
8 changes: 8 additions & 0 deletions src/constants.commands.ts
Original file line number Diff line number Diff line change
@@ -31,6 +31,7 @@ export const enum GlCommand {
CopyRemoteBranchesUrl = 'gitlens.copyRemoteBranchesUrl',
CopyRemoteBranchUrl = 'gitlens.copyRemoteBranchUrl',
CopyRemoteCommitUrl = 'gitlens.copyRemoteCommitUrl',
CopyRemoteTagUrl = 'gitlens.copyRemoteTagUrl',
CopyRemoteComparisonUrl = 'gitlens.copyRemoteComparisonUrl',
CopyRemoteFileUrl = 'gitlens.copyRemoteFileUrlToClipboard',
CopyRemoteFileUrlWithoutRange = 'gitlens.copyRemoteFileUrlWithoutRange',
@@ -83,6 +84,7 @@ export const enum GlCommand {
OpenCurrentBranchOnRemote = 'gitlens.openCurrentBranchOnRemote',
OpenChangedFiles = 'gitlens.openChangedFiles',
OpenCommitOnRemote = 'gitlens.openCommitOnRemote',
OpenTagOnRemote = 'gitlens.openTagOnRemote',
OpenComparisonOnRemote = 'gitlens.openComparisonOnRemote',
OpenFileHistory = 'gitlens.openFileHistory',
OpenFileFromRemote = 'gitlens.openFileFromRemote',
@@ -581,6 +583,10 @@ export type TreeViewCommands = `gitlens.views.${
| 'copyRemoteCommitUrl.multi'
| 'openCommitOnRemote'
| 'openCommitOnRemote.multi'
| 'openTagOnRemote'
| 'openTagOnRemote.multi'
| 'copyRemoteTagUrl'
| 'copyRemoteTagUrl.multi'
| 'openChanges'
| 'openChangesWithWorking'
| 'openPreviousChangesWithWorking'
@@ -736,6 +742,8 @@ type GraphWebviewCommands = `graph.${
| 'cherryPick'
| 'copyRemoteCommitUrl'
| 'copyRemoteCommitUrl.multi'
| 'copyRemoteTagUrl'
| 'copyRemoteTagUrl.multi'
| 'openCommitOnRemote'
| 'openCommitOnRemote.multi'
| 'commitViaSCM'
6 changes: 5 additions & 1 deletion src/git/models/remoteResource.ts
Original file line number Diff line number Diff line change
@@ -10,7 +10,7 @@ export const enum RemoteResourceType {
File = 'file',
Repo = 'repo',
Revision = 'revision',
// Tag = 'tag',
Tag = 'tag',
}

export type RemoteResource =
@@ -58,4 +58,8 @@ export type RemoteResource =
fileName: string;
range?: Range;
sha?: string;
}
| {
type: RemoteResourceType.Tag;
tag: string;
};
4 changes: 4 additions & 0 deletions src/git/remotes/azure-devops.ts
Original file line number Diff line number Diff line change
@@ -206,4 +206,8 @@ export class AzureDevOpsRemote extends RemoteProvider {
if (branch) return this.encodeUrl(`${this.baseUrl}/?path=/${fileName}&version=GB${branch}&_a=contents${line}`);
return this.encodeUrl(`${this.baseUrl}?path=/${fileName}${line}`);
}

protected override getUrlForTag(tag: string): string {
return this.encodeUrl(`${this.baseUrl}?version=GT${tag}`);
}
}
4 changes: 4 additions & 0 deletions src/git/remotes/bitbucket-server.ts
Original file line number Diff line number Diff line change
@@ -176,4 +176,8 @@ export class BitbucketServerRemote extends RemoteProvider {
if (branch) return `${this.encodeUrl(`${this.baseUrl}/browse/${fileName}?at=${branch}`)}${line}`;
return `${this.encodeUrl(`${this.baseUrl}/browse/${fileName}`)}${line}`;
}

protected override getUrlForTag(tag: string): string {
return this.encodeUrl(`${this.baseUrl}/commits/tag/${tag}`);
}
}
4 changes: 4 additions & 0 deletions src/git/remotes/bitbucket.ts
Original file line number Diff line number Diff line change
@@ -162,4 +162,8 @@ export class BitbucketRemote extends RemoteProvider {
if (branch) return `${this.encodeUrl(`${this.baseUrl}/src/${branch}/${fileName}`)}${line}`;
return `${this.encodeUrl(`${this.baseUrl}?path=${fileName}`)}${line}`;
}

protected override getUrlForTag(tag: string): string {
return this.encodeUrl(`${this.baseUrl}/commits/tag/${tag}`);
}
}
4 changes: 4 additions & 0 deletions src/git/remotes/custom.ts
Original file line number Diff line number Diff line change
@@ -130,4 +130,8 @@ export class CustomRemote extends RemoteProvider {

return context;
}

protected override getUrlForTag(tag: string): string {
return this.getUrl(this.urls.tag, this.getContext({ tag: tag }));
}
}
4 changes: 4 additions & 0 deletions src/git/remotes/gerrit.ts
Original file line number Diff line number Diff line change
@@ -196,4 +196,8 @@ export class GerritRemote extends RemoteProvider {
if (branch) return `${this.encodeUrl(`${this.getUrlForBranch(branch)}/${fileName}`)}${line}`;
return `${this.encodeUrl(`${this.baseUrl}/+/HEAD/${fileName}`)}${line}`;
}

protected override getUrlForTag(): string | undefined {
return undefined;
}
}
4 changes: 4 additions & 0 deletions src/git/remotes/gitea.ts
Original file line number Diff line number Diff line change
@@ -160,4 +160,8 @@ export class GiteaRemote extends RemoteProvider {
// this route is deprecated but there is no alternative
return `${this.encodeUrl(`${this.baseUrl}/src/${fileName}`)}${line}`;
}

protected getUrlForTag(tag: string): string {
return this.encodeUrl(`${this.baseUrl}/releases/tag/${tag}`);
}
}
4 changes: 4 additions & 0 deletions src/git/remotes/github.ts
Original file line number Diff line number Diff line change
@@ -303,6 +303,10 @@ export class GitHubRemote extends RemoteProvider<GitHubRepositoryDescriptor> {
if (branch) return `${this.encodeUrl(`${this.baseUrl}/blob/${branch}/${fileName}`)}${line}`;
return `${this.encodeUrl(`${this.baseUrl}?path=${fileName}`)}${line}`;
}

protected override getUrlForTag(tag: string): string {
return this.encodeUrl(`${this.baseUrl}/releases/tag/${tag}`);
}
}

const gitHubNoReplyAddressRegex = /^(?:(\d+)\+)?([a-zA-Z\d-]{1,39})@users\.noreply\.(.*)$/i;
4 changes: 4 additions & 0 deletions src/git/remotes/gitlab.ts
Original file line number Diff line number Diff line change
@@ -387,4 +387,8 @@ export class GitLabRemote extends RemoteProvider<GitLabRepositoryDescriptor> {
if (branch) return `${this.encodeUrl(`${this.baseUrl}/-/blob/${branch}/${fileName}`)}${line}`;
return `${this.encodeUrl(`${this.baseUrl}?path=${fileName}`)}${line}`;
}

protected override getUrlForTag(tag: string): string {
return this.encodeUrl(`${this.baseUrl}/-/tags/${tag}`);
}
}
4 changes: 4 additions & 0 deletions src/git/remotes/google-source.ts
Original file line number Diff line number Diff line change
@@ -40,4 +40,8 @@ export class GoogleSourceRemote extends GerritRemote {
protected override get baseReviewUrl(): string {
return `${this.protocol}://${this.reviewDomain}`;
}

protected override getUrlForTag(): string | undefined {
return undefined;
}
}
6 changes: 3 additions & 3 deletions src/git/remotes/remoteProvider.ts
Original file line number Diff line number Diff line change
@@ -150,9 +150,8 @@ export abstract class RemoteProvider<T extends ResourceDescriptor = ResourceDesc
resource.sha != null ? resource.sha : undefined,
resource.range,
);
// TODO@axosoft-ramint needs to be implemented to support remote urls for tags
// case RemoteResourceType.Tag:
// return this.getUrlForTag(resource.tag);
case RemoteResourceType.Tag:
return this.getUrlForTag(resource.tag);
default:
return undefined;
}
@@ -187,6 +186,7 @@ export abstract class RemoteProvider<T extends ResourceDescriptor = ResourceDesc
compare: { branch: string; remote: { path: string; url: string } },
): string | undefined;

protected abstract getUrlForTag(tag: string): string | undefined;
protected abstract getUrlForFile(fileName: string, branch?: string, sha?: string, range?: Range): string;

protected getUrlForRepository(): string {
4 changes: 2 additions & 2 deletions src/git/utils/remoteResource.utils.ts
Original file line number Diff line number Diff line change
@@ -23,8 +23,8 @@ export function getNameFromRemoteResource(resource: RemoteResource): string {
return 'Repository';
case RemoteResourceType.Revision:
return 'File';
// case RemoteResourceType.Tag:
// return 'Tag';
case RemoteResourceType.Tag:
return 'Tag';
default:
return '';
}
4 changes: 4 additions & 0 deletions src/messages.ts
Original file line number Diff line number Diff line change
@@ -45,6 +45,10 @@ export function showCommitNotFoundWarningMessage(message: string): Promise<Messa
return showMessage('warn', `${message}. The commit could not be found.`, 'suppressCommitNotFoundWarning');
}

export function showTagNotFoundWarningMessage(message: string): Promise<MessageItem | undefined> {
return showMessage('warn', `${message}. The tag could not be found.`, 'suppressTagNotFoundWarning');
}

export async function showCreatePullRequestPrompt(branch: string): Promise<boolean> {
const create = { title: 'Create Pull Request...' };
const result = await showMessage(
7 changes: 6 additions & 1 deletion src/views/nodes/tagNode.ts
Original file line number Diff line number Diff line change
@@ -26,6 +26,7 @@ export class TagNode extends ViewRefNode<'tag', ViewsWithTags, GitTagReference>
view: ViewsWithTags,
public override parent: ViewNode,
public readonly tag: GitTag,
public readonly remoteUrl: string | undefined,
) {
super('tag', uri, view, parent);

@@ -81,7 +82,11 @@ export class TagNode extends ViewRefNode<'tag', ViewsWithTags, GitTagReference>
getTreeItem(): TreeItem {
const item = new TreeItem(this.label, TreeItemCollapsibleState.Collapsed);
item.id = this.id;
item.contextValue = ContextValues.Tag;
let contextValue: string = ContextValues.Tag;
if (this.remoteUrl) {
contextValue += '+remote';
}
item.contextValue = contextValue;
item.description = emojify(this.tag.message);
item.tooltip = `${this.tag.name}${pad(GlyphChars.Dash, 2, 2)}${shortenRevision(this.tag.sha, {
force: true,
12 changes: 10 additions & 2 deletions src/views/nodes/tagsNode.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { ThemeIcon, TreeItem, TreeItemCollapsibleState } from 'vscode';
import { GitUri } from '../../git/gitUri';
import { RemoteResourceType } from '../../git/models/remoteResource';
import type { Repository } from '../../git/models/repository';
import { makeHierarchical } from '../../system/array';
import { debug } from '../../system/decorators/log';
@@ -36,10 +37,17 @@ export class TagsNode extends CacheableChildrenViewNode<'tags', ViewsWithTagsNod
if (this.children == null) {
const tags = await this.repo.git.tags().getTags({ sort: true });
if (tags.values.length === 0) return [new MessageNode(this.view, this, 'No tags could be found.')];

const remote = await this.repo.git.remotes().getBestRemoteWithProvider();
// TODO@eamodio handle paging
const tagNodes = tags.values.map(
t => new TagNode(GitUri.fromRepoPath(this.uri.repoPath!, t.ref), this.view, this, t),
t =>
new TagNode(
GitUri.fromRepoPath(this.uri.repoPath!, t.ref),
this.view,
this,
t,
remote?.provider?.url({ type: RemoteResourceType.Tag, tag: t.name }),
),
);
if (this.view.config.branches.layout === 'list') return tagNodes;

27 changes: 27 additions & 0 deletions src/views/viewCommands.ts
Original file line number Diff line number Diff line change
@@ -270,6 +270,22 @@ export class ViewCommands implements Disposable {
(n, nodes) => this.openCommitOnRemote(n, nodes),
this,
),
registerViewCommand('gitlens.views.openTagOnRemote', (n, nodes) => this.openTagOnRemote(n, nodes), this),
registerViewCommand(
'gitlens.views.openTagOnRemote.multi',
(n, nodes) => this.openTagOnRemote(n, nodes),
this,
),
registerViewCommand(
'gitlens.views.copyRemoteTagUrl',
(n, nodes) => this.openTagOnRemote(n, nodes, true),
this,
),
registerViewCommand(
'gitlens.views.copyRemoteTagUrl.multi',
(n, nodes) => this.openTagOnRemote(n, nodes, true),
this,
),

registerViewCommand('gitlens.views.openChanges', this.openChanges, this),
registerViewCommand('gitlens.views.openChangesWithWorking', this.openChangesWithWorking, this),
@@ -1441,6 +1457,17 @@ export class ViewCommands implements Disposable {
});
}

@log()
private openTagOnRemote(node: ViewRefNode, nodes?: ViewRefNode[], clipboard?: boolean) {
const refs = nodes?.length ? nodes.map(n => n.ref) : [node.ref];

return executeCommand<OpenOnRemoteCommandArgs>(GlCommand.OpenOnRemote, {
repoPath: refs[0].repoPath,
resource: refs.map(r => ({ type: RemoteResourceType.Tag, tag: r.name })),
clipboard: clipboard,
});
}

@log()
private openChanges(node: ViewRefFileNode | MergeConflictFileNode | StatusFileNode) {
if (node.is('conflict-file')) {