diff --git a/extensions/git/src/commands.ts b/extensions/git/src/commands.ts index 06bc8e31b4e..a5a1822f26a 100644 --- a/extensions/git/src/commands.ts +++ b/extensions/git/src/commands.ts @@ -36,7 +36,8 @@ class CheckoutItem implements QuickPickItem { const config = workspace.getConfiguration('git', Uri.file(this.repository.root)); const pullBeforeCheckout = config.get('pullBeforeCheckout', false) === true; - await this.repository.checkout(this.ref.name, { ...opts, pullBeforeCheckout }); + const treeish = opts?.detached ? this.ref.commit ?? this.ref.name : this.ref.name; + await this.repository.checkout(treeish, { ...opts, pullBeforeCheckout }); } } @@ -69,7 +70,7 @@ class CheckoutRemoteHeadItem extends CheckoutItem { } if (opts?.detached) { - await this.repository.checkout(this.ref.name, opts); + await this.repository.checkout(this.ref.commit ?? this.ref.name, opts); return; } @@ -249,7 +250,7 @@ async function categorizeResourceByResolution(resources: Resource[]): Promise<{ return { merge, resolved, unresolved, deletionConflicts }; } -function createCheckoutItems(repository: Repository): CheckoutItem[] { +function createCheckoutItems(repository: Repository, detached = false): CheckoutItem[] { const config = workspace.getConfiguration('git'); const checkoutTypeConfig = config.get('checkoutType'); let checkoutTypes: string[]; @@ -262,6 +263,11 @@ function createCheckoutItems(repository: Repository): CheckoutItem[] { checkoutTypes = checkoutTypeConfig; } + if (detached) { + // Remove tags when in detached mode + checkoutTypes = checkoutTypes.filter(t => t !== 'tags'); + } + const processors = checkoutTypes.map(type => getCheckoutProcessor(repository, type)) .filter(p => !!p) as CheckoutProcessor[]; @@ -2019,12 +2025,12 @@ export class CommandCenter { picks.push(createBranch, createBranchFrom, checkoutDetached); } - picks.push(...createCheckoutItems(repository)); + picks.push(...createCheckoutItems(repository, opts?.detached)); const quickpick = window.createQuickPick(); quickpick.items = picks; quickpick.placeholder = opts?.detached - ? l10n.t('Select a branch or tag to checkout in detached mode') + ? l10n.t('Select a branch to checkout in detached mode') : l10n.t('Select a branch or tag to checkout'); quickpick.show(); diff --git a/extensions/git/src/git.ts b/extensions/git/src/git.ts index f0c4887f104..ee89db10ed2 100644 --- a/extensions/git/src/git.ts +++ b/extensions/git/src/git.ts @@ -2077,17 +2077,22 @@ export class Repository { } } - async getHEADBranch(): Promise { + async getHEADRef(): Promise { let HEAD: Branch | undefined; try { HEAD = await this.getHEAD(); if (HEAD.name) { - try { - HEAD = await this.getBranch(HEAD.name); - } catch (err) { - // noop + // Branch + HEAD = await this.getBranch(HEAD.name); + } else if (HEAD.commit) { + // Tag || Commit + const tags = await this.getRefs({ pattern: 'refs/tags/*' }); + const tag = tags.find(tag => tag.commit === HEAD!.commit); + + if (tag) { + HEAD = { ...HEAD, name: tag.name, type: RefType.Tag }; } } } catch (err) { diff --git a/extensions/git/src/repository.ts b/extensions/git/src/repository.ts index e939c2ade2d..4b706db9b4b 100644 --- a/extensions/git/src/repository.ts +++ b/extensions/git/src/repository.ts @@ -8,7 +8,7 @@ import * as path from 'path'; import * as picomatch from 'picomatch'; import { CancellationToken, Command, Disposable, Event, EventEmitter, Memento, ProgressLocation, ProgressOptions, scm, SourceControl, SourceControlInputBox, SourceControlInputBoxValidation, SourceControlInputBoxValidationType, SourceControlResourceDecorations, SourceControlResourceGroup, SourceControlResourceState, ThemeColor, Uri, window, workspace, WorkspaceEdit, FileDecoration, commands, Tab, TabInputTextDiff, TabInputNotebookDiff, RelativePattern, CancellationTokenSource, LogOutputChannel, LogLevel, CancellationError, l10n } from 'vscode'; import TelemetryReporter from '@vscode/extension-telemetry'; -import { Branch, Change, ForcePushMode, GitErrorCodes, LogOptions, Ref, RefType, Remote, Status, CommitOptions, BranchQuery, FetchOptions } from './api/git'; +import { Branch, Change, ForcePushMode, GitErrorCodes, LogOptions, Ref, Remote, Status, CommitOptions, BranchQuery, FetchOptions } from './api/git'; import { AutoFetcher } from './autofetch'; import { debounce, memoize, throttle } from './decorators'; import { Commit, GitError, Repository as BaseRepository, Stash, Submodule, LogFileOptions, PullOptions } from './git'; @@ -667,13 +667,6 @@ export class Repository implements Disposable { return HEAD.name; } - const tag = this.refs.filter(iref => iref.type === RefType.Tag && iref.commit === HEAD.commit)[0]; - const tagName = tag && tag.name; - - if (tagName) { - return tagName; - } - return (HEAD.commit || '').substr(0, 8); } @@ -1410,7 +1403,7 @@ export class Repository implements Disposable { } async checkout(treeish: string, opts?: { detached?: boolean; pullBeforeCheckout?: boolean }): Promise { - const refLabel = this.checkoutRefLabel(treeish, opts?.detached); + const refLabel = opts?.detached ? treeish.substring(0, 8) : treeish; await this.run(Operation.Checkout(refLabel), async () => { @@ -1428,7 +1421,7 @@ export class Repository implements Disposable { } async checkoutTracking(treeish: string, opts: { detached?: boolean } = {}): Promise { - const refLabel = this.checkoutRefLabel(treeish, opts?.detached); + const refLabel = opts.detached ? treeish.substring(0, 8) : treeish; await this.run(Operation.CheckoutTracking(refLabel), () => this.repository.checkout(treeish, [], { ...opts, track: true })); } @@ -1971,7 +1964,7 @@ export class Repository implements Disposable { const [HEAD, remotes, submodules, rebaseCommit, mergeInProgress, commitTemplate] = await Promise.all([ - this.repository.getHEADBranch(), + this.repository.getHEADRef(), this.repository.getRemotes(), this.repository.getSubmodules(), this.getRebaseCommit(), @@ -2284,9 +2277,7 @@ export class Repository implements Disposable { return ''; } - const tag = this.refs.filter(iref => iref.type === RefType.Tag && iref.commit === HEAD.commit)[0]; - const tagName = tag && tag.name; - const head = HEAD.name || tagName || (HEAD.commit || '').substr(0, 8); + const head = HEAD.name || (HEAD.commit || '').substr(0, 8); return head + (this.workingTreeGroup.resourceStates.length + this.untrackedGroup.resourceStates.length > 0 ? '*' : '') @@ -2395,13 +2386,6 @@ export class Repository implements Disposable { return true; } - private checkoutRefLabel(treeish: string, detached?: boolean): string { - if (!detached) { return treeish; } - - const ref = this.refs.filter(r => r.name === treeish); - return ref[0]?.commit?.substring(0, 8) ?? treeish; - } - public isBranchProtected(name = this.HEAD?.name ?? ''): boolean { return this.isBranchProtectedMatcher ? this.isBranchProtectedMatcher(name) : false; } diff --git a/extensions/git/src/statusbar.ts b/extensions/git/src/statusbar.ts index 0a8690b87e0..590e7ce2a2a 100644 --- a/extensions/git/src/statusbar.ts +++ b/extensions/git/src/statusbar.ts @@ -69,12 +69,12 @@ class CheckoutStatusBar { } // Branch - if (this.repository.HEAD?.name) { + if (this.repository.HEAD.type !== RefType.Tag) { return this.repository.isBranchProtected() ? '$(lock)' : '$(git-branch)'; } // Tag - if (this.repository.HEAD?.commit && this.repository.refs.filter(iref => iref.type === RefType.Tag && iref.commit === this.repository.HEAD!.commit).length) { + if (this.repository.HEAD.type === RefType.Tag) { return '$(tag)'; }