From 124710c5ffc06cedba2e4975d89f3fabcc0dada9 Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Tue, 28 Oct 2025 07:43:12 +0100 Subject: [PATCH] Git - add command to compare a tag (#273694) * Git - update command label * Git - add command to compare a tag --- extensions/git/package.json | 23 +++++- extensions/git/package.nls.json | 3 +- extensions/git/src/commands.ts | 130 +++++++++++++++++--------------- 3 files changed, 91 insertions(+), 65 deletions(-) diff --git a/extensions/git/package.json b/extensions/git/package.json index bc8b9d3d37b..775a19bd5c6 100644 --- a/extensions/git/package.json +++ b/extensions/git/package.json @@ -515,8 +515,8 @@ "enablement": "!operationInProgress" }, { - "command": "git.graph.compareBranches", - "title": "%command.graphCompareBranches%", + "command": "git.graph.compareBranch", + "title": "%command.graphCompareBranch%", "category": "Git", "enablement": "!operationInProgress" }, @@ -592,6 +592,12 @@ "category": "Git", "enablement": "!operationInProgress" }, + { + "command": "git.graph.compareTag", + "title": "%command.graphCompareTag%", + "category": "Git", + "enablement": "!operationInProgress" + }, { "command": "git.deleteRemoteTag", "title": "%command.deleteRemoteTag%", @@ -1601,13 +1607,17 @@ "when": "false" }, { - "command": "git.graph.compareBranches", + "command": "git.graph.compareBranch", "when": "false" }, { "command": "git.graph.deleteTag", "when": "false" }, + { + "command": "git.graph.compareTag", + "when": "false" + }, { "command": "git.graph.cherryPick", "when": "false" @@ -2276,7 +2286,7 @@ "group": "2_branch@2" }, { - "command": "git.graph.compareBranches", + "command": "git.graph.compareBranch", "when": "scmProvider == git && scmHistoryItemRef =~ /^refs\\/heads\\//", "group": "2_branch@3" }, @@ -2284,6 +2294,11 @@ "command": "git.graph.deleteTag", "when": "scmProvider == git && scmHistoryItemRef =~ /^refs\\/tags\\//", "group": "3_tag@2" + }, + { + "command": "git.graph.compareTag", + "when": "scmProvider == git && scmHistoryItemRef =~ /^refs\\/tags\\//", + "group": "3_tag@3" } ], "editor/title": [ diff --git a/extensions/git/package.nls.json b/extensions/git/package.nls.json index dd41ad67f38..924d219a095 100644 --- a/extensions/git/package.nls.json +++ b/extensions/git/package.nls.json @@ -135,9 +135,10 @@ "command.graphCheckout": "Checkout", "command.graphCheckoutDetached": "Checkout (Detached)", "command.graphCherryPick": "Cherry Pick", - "command.graphCompareBranches": "Compare Branches...", + "command.graphCompareBranch": "Compare Branch...", "command.graphDeleteBranch": "Delete Branch", "command.graphDeleteTag": "Delete Tag", + "command.graphCompareTag": "Compare Tag...", "command.blameToggleEditorDecoration": "Toggle Git Blame Editor Decoration", "command.blameToggleStatusBarItem": "Toggle Git Blame Status Bar Item", "command.api.getRepositories": "Get Repositories", diff --git a/extensions/git/src/commands.ts b/extensions/git/src/commands.ts index 4a59b05ee36..0fd7fe5e095 100644 --- a/extensions/git/src/commands.ts +++ b/extensions/git/src/commands.ts @@ -5,7 +5,7 @@ import * as os from 'os'; import * as path from 'path'; -import { Command, commands, Disposable, MessageOptions, Position, QuickPickItem, Range, SourceControlResourceState, TextDocumentShowOptions, TextEditor, Uri, ViewColumn, window, workspace, WorkspaceEdit, WorkspaceFolder, TimelineItem, env, Selection, TextDocumentContentProvider, InputBoxValidationSeverity, TabInputText, TabInputTextMerge, QuickPickItemKind, TextDocument, LogOutputChannel, l10n, Memento, UIKind, QuickInputButton, ThemeIcon, SourceControlHistoryItem, SourceControl, InputBoxValidationMessage, Tab, TabInputNotebook, QuickInputButtonLocation, languages, } from 'vscode'; +import { Command, commands, Disposable, MessageOptions, Position, QuickPickItem, Range, SourceControlResourceState, TextDocumentShowOptions, TextEditor, Uri, ViewColumn, window, workspace, WorkspaceEdit, WorkspaceFolder, TimelineItem, env, Selection, TextDocumentContentProvider, InputBoxValidationSeverity, TabInputText, TabInputTextMerge, QuickPickItemKind, TextDocument, LogOutputChannel, l10n, Memento, UIKind, QuickInputButton, ThemeIcon, SourceControlHistoryItem, SourceControl, InputBoxValidationMessage, Tab, TabInputNotebook, QuickInputButtonLocation, languages } from 'vscode'; import TelemetryReporter from '@vscode/extension-telemetry'; import { uniqueNamesGenerator, adjectives, animals, colors, NumberDictionary } from '@joaomoreno/unique-names-generator'; import { ForcePushMode, GitErrorCodes, RefType, Status, CommitOptions, RemoteSourcePublisher, Remote, Branch, Ref } from './api/git'; @@ -3109,70 +3109,16 @@ export class CommandCenter { await this._deleteBranch(repository, remoteName, refName, { remote: true }); } + @command('git.graph.compareBranch', { repository: true }) + async compareBranch(repository: Repository, historyItem?: SourceControlHistoryItem, historyItemRefId?: string): Promise { + await this._compareRef(repository, historyItem, historyItemRefId); + } + @command('git.deleteRemoteBranch', { repository: true }) async deleteRemoteBranch(repository: Repository): Promise { await this._deleteBranch(repository, undefined, undefined, { remote: true }); } - @command('git.graph.compareBranches', { repository: true }) - async compareBranches(repository: Repository, historyItem?: SourceControlHistoryItem, historyItemRefId?: string): Promise { - const historyItemRef = historyItem?.references?.find(r => r.id === historyItemRefId); - if (!historyItemRefId || !historyItemRef) { - return; - } - - const config = workspace.getConfiguration('git'); - const showRefDetails = config.get('showReferenceDetails') === true; - - const getBranchPicks = async () => { - const refs = await repository.getRefs({ includeCommitDetails: showRefDetails }); - const processors = [ - new RefProcessor(RefType.Head, BranchItem), - new RefProcessor(RefType.Tag, BranchItem) - ]; - - const itemsProcessor = new RefItemsProcessor(repository, processors); - return itemsProcessor.processRefs(refs); - }; - - const placeHolder = l10n.t('Select a source reference to compare to'); - const sourceRef = await this.pickRef(getBranchPicks(), placeHolder); - - if (!(sourceRef instanceof BranchItem)) { - return; - } - - if (historyItemRefId === sourceRef.refId) { - window.showInformationMessage(l10n.t('The selected references are the same.')); - return; - } - - try { - const changes = await repository.diffTrees(historyItemRefId, sourceRef.refId); - - if (changes.length === 0) { - window.showInformationMessage(l10n.t('The selected references have no differences.')); - return; - } - - const resources = changes.map(change => toMultiFileDiffEditorUris(change, sourceRef.refId, historyItemRefId)); - - const title = `${sourceRef.ref.name} ↔ ${historyItemRef.name}`; - const multiDiffSourceUri = Uri.from({ - scheme: 'git-branch-compare', - path: `${repository.root}/${sourceRef.refId}..${historyItemRefId}` - }); - - await commands.executeCommand('_workbench.openMultiDiffEditor', { - multiDiffSourceUri, - title, - resources - }); - } catch (err) { - throw new Error(l10n.t('Failed to compare references: {0}', err.message ?? err)); - } - } - private async _deleteBranch(repository: Repository, remote: string | undefined, name: string | undefined, options: { remote: boolean; force?: boolean }): Promise { let run: (force?: boolean) => Promise; @@ -3806,6 +3752,11 @@ export class CommandCenter { await repository.deleteTag(historyItemRef.name); } + @command('git.graph.compareTag', { repository: true }) + async compareTags(repository: Repository, historyItem?: SourceControlHistoryItem, historyItemRefId?: string): Promise { + await this._compareRef(repository, historyItem, historyItemRefId); + } + @command('git.deleteRemoteTag', { repository: true }) async deleteRemoteTag(repository: Repository): Promise { const config = workspace.getConfiguration('git'); @@ -5145,6 +5096,65 @@ export class CommandCenter { config.update(setting, !enabled, true); } + async _compareRef(repository: Repository, historyItem?: SourceControlHistoryItem, historyItemRefId?: string): Promise { + const historyItemRef = historyItem?.references?.find(r => r.id === historyItemRefId); + if (!historyItemRefId || !historyItemRef) { + return; + } + + const config = workspace.getConfiguration('git'); + const showRefDetails = config.get('showReferenceDetails') === true; + + const getRefPicks = async () => { + const refs = await repository.getRefs({ includeCommitDetails: showRefDetails }); + const processors = [ + new RefProcessor(RefType.Head, BranchItem), + new RefProcessor(RefType.RemoteHead, BranchItem), + new RefProcessor(RefType.Tag, BranchItem) + ]; + + const itemsProcessor = new RefItemsProcessor(repository, processors); + return itemsProcessor.processRefs(refs); + }; + + const placeHolder = l10n.t('Select a source reference to compare to'); + const sourceRef = await this.pickRef(getRefPicks(), placeHolder); + + if (!(sourceRef instanceof BranchItem)) { + return; + } + + if (historyItemRef.id === sourceRef.refId) { + window.showInformationMessage(l10n.t('The selected references are the same.')); + return; + } + + try { + const changes = await repository.diffTrees(historyItemRef.id, sourceRef.refId); + + if (changes.length === 0) { + window.showInformationMessage(l10n.t('The selected references have no differences.')); + return; + } + + const resources = changes.map(change => toMultiFileDiffEditorUris(change, sourceRef.refId, historyItemRef.id)); + + const title = `${sourceRef.ref.name} ↔ ${historyItemRef.name}`; + const multiDiffSourceUri = Uri.from({ + scheme: 'git-ref-compare', + path: `${repository.root}/${sourceRef.refId}..${historyItemRef.id}` + }); + + await commands.executeCommand('_workbench.openMultiDiffEditor', { + multiDiffSourceUri, + title, + resources + }); + } catch (err) { + throw new Error(l10n.t('Failed to compare references: {0}', err.message ?? err)); + } + } + private createCommand(id: string, key: string, method: Function, options: ScmCommandOptions): (...args: any[]) => any { const result = (...args: any[]) => { let result: Promise;