diff --git a/extensions/git/src/artifactProvider.ts b/extensions/git/src/artifactProvider.ts index 3dbd5131d53..fb699bdb52f 100644 --- a/extensions/git/src/artifactProvider.ts +++ b/extensions/git/src/artifactProvider.ts @@ -4,23 +4,11 @@ *--------------------------------------------------------------------------------------------*/ import { LogOutputChannel, SourceControlArtifactProvider, SourceControlArtifactGroup, SourceControlArtifact, Event, EventEmitter, ThemeIcon, l10n, workspace, Uri, Disposable, Command } from 'vscode'; -import { dispose, filterEvent, IDisposable } from './util'; +import { coalesce, dispose, filterEvent, IDisposable } from './util'; import { Repository } from './repository'; import { Commit, Ref, RefType } from './api/git'; import { OperationKind } from './operation'; -function getArtifactDescription(commit: string | undefined, commitDetails: Commit | undefined, shortCommitLength: number): string { - const segments: string[] = []; - if (commit) { - segments.push(commit.substring(0, shortCommitLength)); - } - if (commitDetails?.message) { - segments.push(commitDetails.message.split('\n')[0]); - } - - return segments.join(' \u2022 '); -} - /** * Sorts refs like a directory tree: refs with more path segments (directories) appear first * and are sorted alphabetically, while refs at the same level (files) maintain insertion order. @@ -134,7 +122,10 @@ export class GitArtifactProvider implements SourceControlArtifactProvider, IDisp return refs.sort(sortRefByName).map(r => ({ id: `refs/heads/${r.name}`, name: r.name ?? r.commit ?? '', - description: getArtifactDescription(r.commit, r.commitDetails, shortCommitLength), + description: coalesce([ + r.commit?.substring(0, shortCommitLength), + r.commitDetails?.message.split('\n')[0] + ]).join(' \u2022 '), icon: this.repository.HEAD?.type === RefType.Head && r.name === this.repository.HEAD?.name ? new ThemeIcon('target') : new ThemeIcon('git-branch'), @@ -147,7 +138,10 @@ export class GitArtifactProvider implements SourceControlArtifactProvider, IDisp return refs.sort(sortRefByName).map(r => ({ id: `refs/tags/${r.name}`, name: r.name ?? r.commit ?? '', - description: getArtifactDescription(r.commit, r.commitDetails, shortCommitLength), + description: coalesce([ + r.commit?.substring(0, shortCommitLength), + r.commitDetails?.message.split('\n')[0] + ]).join(' \u2022 '), icon: this.repository.HEAD?.type === RefType.Tag && r.name === this.repository.HEAD?.name ? new ThemeIcon('target') : new ThemeIcon('tag'), @@ -170,19 +164,17 @@ export class GitArtifactProvider implements SourceControlArtifactProvider, IDisp } else if (group === 'worktrees') { const worktrees = await this.repository.getWorktreeDetails(); - return worktrees.sort(sortByCommitDateDesc).map(w => { - const description = getArtifactDescription(w.commitDetails?.hash, w.commitDetails, shortCommitLength); - - return { - id: w.path, - name: w.name, - description: w.detached - ? `${l10n.t('detached')} \u2022 ${description}` - : `${w.ref.substring(11)} \u2022 ${description}`, - icon: new ThemeIcon('list-tree'), - timestamp: w.commitDetails?.commitDate?.getTime(), - }; - }); + return worktrees.sort(sortByCommitDateDesc).map(w => ({ + id: w.path, + name: w.name, + description: coalesce([ + w.detached ? l10n.t('detached') : w.ref.substring(11), + w.commitDetails?.hash.substring(0, shortCommitLength), + w.commitDetails?.message.split('\n')[0] + ]).join(' \u2022 '), + icon: new ThemeIcon('list-tree'), + timestamp: w.commitDetails?.commitDate?.getTime(), + })); } } catch (err) { this.logger.error(`[GitArtifactProvider][provideArtifacts] Error while providing artifacts for group '${group}': `, err); diff --git a/extensions/git/src/commands.ts b/extensions/git/src/commands.ts index 6c23744227e..ec90bc10a9d 100644 --- a/extensions/git/src/commands.ts +++ b/extensions/git/src/commands.ts @@ -14,7 +14,7 @@ import { Model } from './model'; import { GitResourceGroup, Repository, Resource, ResourceGroupType } from './repository'; import { DiffEditorSelectionHunkToolbarContext, LineChange, applyLineChanges, getIndexDiffInformation, getModifiedRange, getWorkingTreeDiffInformation, intersectDiffWithRange, invertLineChange, toLineChanges, toLineRanges, compareLineChanges } from './staging'; import { fromGitUri, toGitUri, isGitUri, toMergeUris, toMultiFileDiffEditorUris } from './uri'; -import { DiagnosticSeverityConfig, dispose, fromNow, getHistoryItemDisplayName, getStashDescription, grep, isDefined, isDescendant, isLinuxSnap, isRemote, isWindows, pathEquals, relativePath, subject, toDiagnosticSeverity, truncate } from './util'; +import { coalesce, DiagnosticSeverityConfig, dispose, fromNow, getHistoryItemDisplayName, getStashDescription, grep, isDefined, isDescendant, isLinuxSnap, isRemote, isWindows, pathEquals, relativePath, subject, toDiagnosticSeverity, truncate } from './util'; import { GitTimelineItem } from './timelineProvider'; import { ApiRepository } from './api/api1'; import { getRemoteSourceActions, pickRemoteSource } from './remoteSource'; @@ -58,19 +58,6 @@ class RefItemSeparator implements QuickPickItem { constructor(private readonly refType: RefType) { } } -class WorktreeItem implements QuickPickItem { - - get label(): string { - return `$(list-tree) ${this.worktree.name}`; - } - - get description(): string { - return this.worktree.path; - } - - constructor(readonly worktree: Worktree) { } -} - class RefItem implements QuickPickItem { get label(): string { @@ -240,7 +227,40 @@ class RemoteTagDeleteItem extends RefItem { } } +class WorktreeItem implements QuickPickItem { + + get label(): string { + return `$(list-tree) ${this.worktree.name}`; + } + + get description(): string | undefined { + return this.worktree.path; + } + + constructor(readonly worktree: Worktree) { } +} + class WorktreeDeleteItem extends WorktreeItem { + override get description(): string | undefined { + if (!this.worktree.commitDetails) { + return undefined; + } + + return coalesce([ + this.worktree.detached ? l10n.t('detached') : this.worktree.ref.substring(11), + this.worktree.commitDetails.hash.substring(0, this.shortCommitLength), + this.worktree.commitDetails.message.split('\n')[0] + ]).join(' \u2022 '); + } + + get detail(): string { + return this.worktree.path; + } + + constructor(worktree: Worktree, private readonly shortCommitLength: number) { + super(worktree); + } + async run(mainRepository: Repository): Promise { if (!this.worktree.path) { return; @@ -3666,11 +3686,14 @@ export class CommandCenter { @command('git.deleteWorktree', { repository: true, repositoryFilter: ['repository', 'submodule'] }) async deleteWorktreeFromPalette(repository: Repository): Promise { + const config = workspace.getConfiguration('git', Uri.file(repository.root)); + const commitShortHashLength = config.get('commitShortHashLength') ?? 7; + const worktreePicks = async (): Promise => { - const worktrees = await repository.getWorktrees(); + const worktrees = await repository.getWorktreeDetails(); return worktrees.length === 0 ? [{ label: l10n.t('$(info) This repository has no worktrees.') }] - : worktrees.map(worktree => new WorktreeDeleteItem(worktree)); + : worktrees.map(worktree => new WorktreeDeleteItem(worktree, commitShortHashLength)); }; const placeHolder = l10n.t('Select a worktree to delete'); diff --git a/extensions/git/src/util.ts b/extensions/git/src/util.ts index e488a7a4fef..d7b0a07eeb4 100644 --- a/extensions/git/src/util.ts +++ b/extensions/git/src/util.ts @@ -141,6 +141,9 @@ export function groupBy(arr: T[], fn: (el: T) => string): { [key: string]: T[ }, Object.create(null)); } +export function coalesce(array: ReadonlyArray): T[] { + return array.filter((e): e is T => !!e); +} export async function mkdirp(path: string, mode?: number): Promise { const mkdir = async () => {