diff --git a/extensions/git/src/actionButton.ts b/extensions/git/src/actionButton.ts index faf96100265..261d637fd0e 100644 --- a/extensions/git/src/actionButton.ts +++ b/extensions/git/src/actionButton.ts @@ -5,8 +5,9 @@ import { Command, Disposable, Event, EventEmitter, SourceControlActionButton, Uri, workspace, l10n } from 'vscode'; import { Branch, Status } from './api/git'; +import { OperationKind } from './operation'; import { CommitCommandsCenter } from './postCommitCommands'; -import { Repository, OperationKind } from './repository'; +import { Repository } from './repository'; import { dispose } from './util'; interface ActionButtonState { diff --git a/extensions/git/src/autofetch.ts b/extensions/git/src/autofetch.ts index 1cbb4f6bbfa..fbd0c821113 100644 --- a/extensions/git/src/autofetch.ts +++ b/extensions/git/src/autofetch.ts @@ -4,14 +4,10 @@ *--------------------------------------------------------------------------------------------*/ import { workspace, Disposable, EventEmitter, Memento, window, MessageItem, ConfigurationTarget, Uri, ConfigurationChangeEvent, l10n, env } from 'vscode'; -import { Repository, OperationKind } from './repository'; +import { Repository } from './repository'; import { eventToPromise, filterEvent, onceEvent } from './util'; import { GitErrorCodes } from './api/git'; -function isRemoteOperation(operation: OperationKind): boolean { - return operation === OperationKind.Pull || operation === OperationKind.Push || operation === OperationKind.Sync || operation === OperationKind.Fetch; -} - export class AutoFetcher { private static DidInformUser = 'autofetch.didInformUser'; @@ -30,7 +26,7 @@ export class AutoFetcher { workspace.onDidChangeConfiguration(this.onConfiguration, this, this.disposables); this.onConfiguration(); - const onGoodRemoteOperation = filterEvent(repository.onDidRunOperation, ({ operation, error }) => !error && isRemoteOperation(operation)); + const onGoodRemoteOperation = filterEvent(repository.onDidRunOperation, ({ operation, error }) => !error && operation.remote); const onFirstGoodRemoteOperation = onceEvent(onGoodRemoteOperation); onFirstGoodRemoteOperation(this.onFirstGoodRemoteOperation, this, this.disposables); } diff --git a/extensions/git/src/model.ts b/extensions/git/src/model.ts index 090fbcb4986..d27ef8f4f84 100644 --- a/extensions/git/src/model.ts +++ b/extensions/git/src/model.ts @@ -5,7 +5,7 @@ import { workspace, WorkspaceFoldersChangeEvent, Uri, window, Event, EventEmitter, QuickPickItem, Disposable, SourceControl, SourceControlResourceGroup, TextEditor, Memento, commands, LogOutputChannel, l10n, ProgressLocation } from 'vscode'; import TelemetryReporter from '@vscode/extension-telemetry'; -import { OperationKind, Repository, RepositoryState } from './repository'; +import { Repository, RepositoryState } from './repository'; import { memoize, sequentialize, debounce } from './decorators'; import { dispose, anyEvent, filterEvent, isDescendant, pathEquals, toDisposable, eventToPromise } from './util'; import { Git } from './git'; @@ -18,6 +18,7 @@ import { IPushErrorHandlerRegistry } from './pushError'; import { ApiRepository } from './api/api1'; import { IRemoteSourcePublisherRegistry } from './remotePublisher'; import { IPostCommitCommandsProviderRegistry } from './postCommitCommands'; +import { OperationKind } from './operation'; class RepositoryPick implements QuickPickItem { @memoize get label(): string { diff --git a/extensions/git/src/operation.ts b/extensions/git/src/operation.ts new file mode 100644 index 00000000000..53619dc9ccf --- /dev/null +++ b/extensions/git/src/operation.ts @@ -0,0 +1,249 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { LogOutputChannel } from 'vscode'; + +export const enum OperationKind { + Add = 'Add', + AddNoProgress = 'AddNoProgress', + Apply = 'Apply', + Blame = 'Blame', + Branch = 'Branch', + CheckIgnore = 'CheckIgnore', + Checkout = 'Checkout', + CheckoutTracking = 'CheckoutTracking', + CherryPick = 'CherryPick', + Clean = 'Clean', + CleanNoProgress = 'CleanNoProgress', + Commit = 'Commit', + Config = 'Config', + DeleteBranch = 'DeleteBranch', + DeleteRef = 'DeleteRef', + DeleteTag = 'DeleteTag', + Diff = 'Diff', + Fetch = 'Fetch', + FetchNoProgress = 'FetchNoProgress', + FindTrackingBranches = 'GetTracking', + GetBranch = 'GetBranch', + GetBranches = 'GetBranches', + GetCommitTemplate = 'GetCommitTemplate', + GetObjectDetails = 'GetObjectDetails', + HashObject = 'HashObject', + Ignore = 'Ignore', + Log = 'Log', + LogFile = 'LogFile', + Merge = 'Merge', + MergeAbort = 'MergeAbort', + MergeBase = 'MergeBase', + Move = 'Move', + PostCommitCommand = 'PostCommitCommand', + Pull = 'Pull', + Push = 'Push', + Remote = 'Remote', + RenameBranch = 'RenameBranch', + Remove = 'Remove', + Reset = 'Reset', + Rebase = 'Rebase', + RebaseAbort = 'RebaseAbort', + RebaseContinue = 'RebaseContinue', + RevertFiles = 'RevertFiles', + RevertFilesNoProgress = 'RevertFilesNoProgress', + SetBranchUpstream = 'SetBranchUpstream', + Show = 'Show', + Stage = 'Stage', + Status = 'Status', + Stash = 'Stash', + SubmoduleUpdate = 'SubmoduleUpdate', + Sync = 'Sync', + Tag = 'Tag', +} + +export type Operation = AddOperation | ApplyOperation | BlameOperation | BranchOperation | CheckIgnoreOperation | CherryPickOperation | + CheckoutOperation | CheckoutTrackingOperation | CleanOperation | CommitOperation | ConfigOperation | DeleteBranchOperation | + DeleteRefOperation | DeleteTagOperation | DiffOperation | FetchOperation | FindTrackingBranchesOperation | GetBranchOperation | + GetBranchesOperation | GetCommitTemplateOperation | GetObjectDetailsOperation | HashObjectOperation | IgnoreOperation | LogOperation | + LogFileOperation | MergeOperation | MergeAbortOperation | MergeBaseOperation | MoveOperation | PostCommitCommandOperation | + PullOperation | PushOperation | RemoteOperation | RenameBranchOperation | RemoveOperation | ResetOperation | RebaseOperation | + RebaseAbortOperation | RebaseContinueOperation | RevertFilesOperation | SetBranchUpstreamOperation | ShowOperation | StageOperation | + StatusOperation | StashOperation | SubmoduleUpdateOperation | SyncOperation | TagOperation; + +type BaseOperation = { kind: OperationKind; readOnly: boolean; remote: boolean; retry: boolean; showProgress: boolean }; +export type AddOperation = BaseOperation & { kind: OperationKind.Add }; +export type ApplyOperation = BaseOperation & { kind: OperationKind.Apply }; +export type BlameOperation = BaseOperation & { kind: OperationKind.Blame }; +export type BranchOperation = BaseOperation & { kind: OperationKind.Branch }; +export type CheckIgnoreOperation = BaseOperation & { kind: OperationKind.CheckIgnore }; +export type CherryPickOperation = BaseOperation & { kind: OperationKind.CherryPick }; +export type CheckoutOperation = BaseOperation & { kind: OperationKind.Checkout; refLabel: string }; +export type CheckoutTrackingOperation = BaseOperation & { kind: OperationKind.CheckoutTracking; refLabel: string }; +export type CleanOperation = BaseOperation & { kind: OperationKind.Clean }; +export type CommitOperation = BaseOperation & { kind: OperationKind.Commit }; +export type ConfigOperation = BaseOperation & { kind: OperationKind.Config }; +export type DeleteBranchOperation = BaseOperation & { kind: OperationKind.DeleteBranch }; +export type DeleteRefOperation = BaseOperation & { kind: OperationKind.DeleteRef }; +export type DeleteTagOperation = BaseOperation & { kind: OperationKind.DeleteTag }; +export type DiffOperation = BaseOperation & { kind: OperationKind.Diff }; +export type FetchOperation = BaseOperation & { kind: OperationKind.Fetch }; +export type FindTrackingBranchesOperation = BaseOperation & { kind: OperationKind.FindTrackingBranches }; +export type GetBranchOperation = BaseOperation & { kind: OperationKind.GetBranch }; +export type GetBranchesOperation = BaseOperation & { kind: OperationKind.GetBranches }; +export type GetCommitTemplateOperation = BaseOperation & { kind: OperationKind.GetCommitTemplate }; +export type GetObjectDetailsOperation = BaseOperation & { kind: OperationKind.GetObjectDetails }; +export type HashObjectOperation = BaseOperation & { kind: OperationKind.HashObject }; +export type IgnoreOperation = BaseOperation & { kind: OperationKind.Ignore }; +export type LogOperation = BaseOperation & { kind: OperationKind.Log }; +export type LogFileOperation = BaseOperation & { kind: OperationKind.LogFile }; +export type MergeOperation = BaseOperation & { kind: OperationKind.Merge }; +export type MergeAbortOperation = BaseOperation & { kind: OperationKind.MergeAbort }; +export type MergeBaseOperation = BaseOperation & { kind: OperationKind.MergeBase }; +export type MoveOperation = BaseOperation & { kind: OperationKind.Move }; +export type PostCommitCommandOperation = BaseOperation & { kind: OperationKind.PostCommitCommand }; +export type PullOperation = BaseOperation & { kind: OperationKind.Pull }; +export type PushOperation = BaseOperation & { kind: OperationKind.Push }; +export type RemoteOperation = BaseOperation & { kind: OperationKind.Remote }; +export type RenameBranchOperation = BaseOperation & { kind: OperationKind.RenameBranch }; +export type RemoveOperation = BaseOperation & { kind: OperationKind.Remove }; +export type ResetOperation = BaseOperation & { kind: OperationKind.Reset }; +export type RebaseOperation = BaseOperation & { kind: OperationKind.Rebase }; +export type RebaseAbortOperation = BaseOperation & { kind: OperationKind.RebaseAbort }; +export type RebaseContinueOperation = BaseOperation & { kind: OperationKind.RebaseContinue }; +export type RevertFilesOperation = BaseOperation & { kind: OperationKind.RevertFiles }; +export type SetBranchUpstreamOperation = BaseOperation & { kind: OperationKind.SetBranchUpstream }; +export type ShowOperation = BaseOperation & { kind: OperationKind.Show }; +export type StageOperation = BaseOperation & { kind: OperationKind.Stage }; +export type StatusOperation = BaseOperation & { kind: OperationKind.Status }; +export type StashOperation = BaseOperation & { kind: OperationKind.Stash }; +export type SubmoduleUpdateOperation = BaseOperation & { kind: OperationKind.SubmoduleUpdate }; +export type SyncOperation = BaseOperation & { kind: OperationKind.Sync }; +export type TagOperation = BaseOperation & { kind: OperationKind.Tag }; + +export const Operation = { + Add: (showProgress: boolean) => ({ kind: OperationKind.Add, readOnly: false, remote: false, retry: false, showProgress } as AddOperation), + Apply: { kind: OperationKind.Apply, readOnly: false, remote: false, retry: false, showProgress: true } as ApplyOperation, + Blame: { kind: OperationKind.Blame, readOnly: true, remote: false, retry: false, showProgress: true } as BlameOperation, + Branch: { kind: OperationKind.Branch, readOnly: false, remote: false, retry: false, showProgress: true } as BranchOperation, + CheckIgnore: { kind: OperationKind.CheckIgnore, readOnly: true, remote: false, retry: false, showProgress: false } as CheckIgnoreOperation, + CherryPick: { kind: OperationKind.CherryPick, readOnly: false, remote: false, retry: false, showProgress: true } as CherryPickOperation, + Checkout: (refLabel: string) => ({ kind: OperationKind.Checkout, readOnly: false, remote: false, retry: false, showProgress: true, refLabel } as CheckoutOperation), + CheckoutTracking: (refLabel: string) => ({ kind: OperationKind.CheckoutTracking, readOnly: false, remote: false, retry: false, showProgress: true, refLabel } as CheckoutTrackingOperation), + Clean: (showProgress: boolean) => ({ kind: OperationKind.Clean, readOnly: false, remote: false, retry: false, showProgress } as CleanOperation), + Commit: { kind: OperationKind.Commit, readOnly: false, remote: false, retry: false, showProgress: true } as CommitOperation, + Config: { kind: OperationKind.Config, readOnly: false, remote: false, retry: false, showProgress: true } as ConfigOperation, + DeleteBranch: { kind: OperationKind.DeleteBranch, readOnly: false, remote: false, retry: false, showProgress: true } as DeleteBranchOperation, + DeleteRef: { kind: OperationKind.DeleteRef, readOnly: false, remote: false, retry: false, showProgress: true } as DeleteRefOperation, + DeleteTag: { kind: OperationKind.DeleteTag, readOnly: false, remote: false, retry: false, showProgress: true } as DeleteTagOperation, + Diff: { kind: OperationKind.Diff, readOnly: true, remote: false, retry: false, showProgress: true } as DiffOperation, + Fetch: (showProgress: boolean) => ({ kind: OperationKind.Fetch, readOnly: false, remote: true, retry: true, showProgress } as FetchOperation), + FindTrackingBranches: { kind: OperationKind.FindTrackingBranches, readOnly: true, remote: false, retry: false, showProgress: true } as FindTrackingBranchesOperation, + GetBranch: { kind: OperationKind.GetBranch, readOnly: true, remote: false, retry: false, showProgress: true } as GetBranchOperation, + GetBranches: { kind: OperationKind.GetBranches, readOnly: true, remote: false, retry: false, showProgress: true } as GetBranchesOperation, + GetCommitTemplate: { kind: OperationKind.GetCommitTemplate, readOnly: true, remote: false, retry: false, showProgress: true } as GetCommitTemplateOperation, + GetObjectDetails: { kind: OperationKind.GetObjectDetails, readOnly: true, remote: false, retry: false, showProgress: false } as GetObjectDetailsOperation, + HashObject: { kind: OperationKind.HashObject, readOnly: false, remote: false, retry: false, showProgress: true } as HashObjectOperation, + Ignore: { kind: OperationKind.Ignore, readOnly: false, remote: false, retry: false, showProgress: true } as IgnoreOperation, + Log: { kind: OperationKind.Log, readOnly: true, remote: false, retry: false, showProgress: true } as LogOperation, + LogFile: { kind: OperationKind.LogFile, readOnly: true, remote: false, retry: false, showProgress: true } as LogFileOperation, + Merge: { kind: OperationKind.Merge, readOnly: false, remote: false, retry: false, showProgress: true } as MergeOperation, + MergeAbort: { kind: OperationKind.MergeAbort, readOnly: false, remote: false, retry: false, showProgress: true } as MergeAbortOperation, + MergeBase: { kind: OperationKind.MergeBase, readOnly: true, remote: false, retry: false, showProgress: true } as MergeBaseOperation, + Move: { kind: OperationKind.Move, readOnly: false, remote: false, retry: false, showProgress: true } as MoveOperation, + PostCommitCommand: { kind: OperationKind.PostCommitCommand, readOnly: false, remote: false, retry: false, showProgress: true } as PostCommitCommandOperation, + Pull: { kind: OperationKind.Pull, readOnly: false, remote: true, retry: true, showProgress: true } as PullOperation, + Push: { kind: OperationKind.Push, readOnly: false, remote: true, retry: false, showProgress: true } as PushOperation, + Remote: { kind: OperationKind.Remote, readOnly: false, remote: false, retry: false, showProgress: true } as RemoteOperation, + RenameBranch: { kind: OperationKind.RenameBranch, readOnly: false, remote: false, retry: false, showProgress: true } as RenameBranchOperation, + Remove: { kind: OperationKind.Remove, readOnly: false, remote: false, retry: false, showProgress: true } as RemoveOperation, + Reset: { kind: OperationKind.Reset, readOnly: false, remote: false, retry: false, showProgress: true } as ResetOperation, + Rebase: { kind: OperationKind.Rebase, readOnly: false, remote: false, retry: false, showProgress: true } as RebaseOperation, + RebaseAbort: { kind: OperationKind.RebaseAbort, readOnly: false, remote: false, retry: false, showProgress: true } as RebaseAbortOperation, + RebaseContinue: { kind: OperationKind.RebaseContinue, readOnly: false, remote: false, retry: false, showProgress: true } as RebaseContinueOperation, + RevertFiles: (showProgress: boolean) => ({ kind: OperationKind.RevertFiles, readOnly: false, remote: false, retry: false, showProgress } as RevertFilesOperation), + SetBranchUpstream: { kind: OperationKind.SetBranchUpstream, readOnly: false, remote: false, retry: false, showProgress: true } as SetBranchUpstreamOperation, + Show: { kind: OperationKind.Show, readOnly: true, remote: false, retry: false, showProgress: false } as ShowOperation, + Stage: { kind: OperationKind.Stage, readOnly: false, remote: false, retry: false, showProgress: true } as StageOperation, + Status: { kind: OperationKind.Status, readOnly: false, remote: false, retry: false, showProgress: true } as StatusOperation, + Stash: { kind: OperationKind.Stash, readOnly: false, remote: false, retry: false, showProgress: true } as StashOperation, + SubmoduleUpdate: { kind: OperationKind.SubmoduleUpdate, readOnly: false, remote: false, retry: false, showProgress: true } as SubmoduleUpdateOperation, + Sync: { kind: OperationKind.Sync, readOnly: false, remote: true, retry: true, showProgress: true } as SyncOperation, + Tag: { kind: OperationKind.Tag, readOnly: false, remote: false, retry: false, showProgress: true } as TagOperation +}; + +export interface OperationResult { + operation: Operation; + error: any; +} + +interface IOperationManager { + isIdle(): boolean; + getOperations(operationKind: OperationKind): Operation[]; + shouldShowProgress(): boolean; + isRunning(operationKind: OperationKind): boolean; +} + +export class OperationManager implements IOperationManager { + + private operations = new Map>(); + + constructor(private readonly logger: LogOutputChannel) { } + + start(operation: Operation): void { + if (this.operations.has(operation.kind)) { + this.operations.get(operation.kind)!.add(operation); + } else { + this.operations.set(operation.kind, new Set([operation])); + } + + this.logger.trace(`Operation start: ${operation.kind} (readOnly: ${operation.readOnly}; retry: ${operation.retry}; showProgress: ${operation.showProgress})`); + } + + end(operation: Operation): void { + const operationSet = this.operations.get(operation.kind); + if (operationSet) { + operationSet.delete(operation); + if (operationSet.size === 0) { + this.operations.delete(operation.kind); + } + } + + this.logger.trace(`Operation end: ${operation.kind} (readOnly: ${operation.readOnly}; retry: ${operation.retry}; showProgress: ${operation.showProgress})`); + } + + getOperations(operationKind: OperationKind): Operation[] { + const operationSet = this.operations.get(operationKind); + return operationSet ? Array.from(operationSet) : []; + } + + isRunning(operationKind: OperationKind): boolean { + return this.operations.has(operationKind); + } + + isIdle(): boolean { + const operationSets = this.operations.values(); + + for (const operationSet of operationSets) { + for (const operation of operationSet) { + if (!operation.readOnly) { + return false; + } + } + } + + return true; + } + + shouldShowProgress(): boolean { + const operationSets = this.operations.values(); + + for (const operationSet of operationSets) { + for (const operation of operationSet) { + if (operation.showProgress) { + return true; + } + } + } + + return false; + } +} diff --git a/extensions/git/src/postCommitCommands.ts b/extensions/git/src/postCommitCommands.ts index 3b40cee53c3..b7af453eaa4 100644 --- a/extensions/git/src/postCommitCommands.ts +++ b/extensions/git/src/postCommitCommands.ts @@ -5,9 +5,10 @@ import { Command, commands, Disposable, Event, EventEmitter, Memento, Uri, workspace, l10n } from 'vscode'; import { PostCommitCommandsProvider } from './api/git'; -import { OperationKind, Repository } from './repository'; +import { Repository } from './repository'; import { ApiRepository } from './api/api1'; import { dispose } from './util'; +import { OperationKind } from './operation'; export interface IPostCommitCommandsProviderRegistry { readonly onDidChangePostCommitCommandsProviders: Event; diff --git a/extensions/git/src/repository.ts b/extensions/git/src/repository.ts index e4470c757c0..549b94f7e4c 100644 --- a/extensions/git/src/repository.ts +++ b/extensions/git/src/repository.ts @@ -21,6 +21,7 @@ import { ApiRepository } from './api/api1'; import { IRemoteSourcePublisherRegistry } from './remotePublisher'; import { ActionButtonCommand } from './actionButton'; import { IPostCommitCommandsProviderRegistry, CommitCommandsCenter } from './postCommitCommands'; +import { Operation, OperationKind, OperationManager, OperationResult } from './operation'; const timeout = (millis: number) => new Promise(c => setTimeout(c, millis)); @@ -302,174 +303,6 @@ export class Resource implements SourceControlResourceState { } } -export const enum OperationKind { - Status = 'Status', - Config = 'Config', - Diff = 'Diff', - MergeBase = 'MergeBase', - Add = 'Add', - AddNoProgress = 'AddNoProgress', - Remove = 'Remove', - RevertFiles = 'RevertFiles', - RevertFilesNoProgress = 'RevertFilesNoProgress', - Commit = 'Commit', - PostCommitCommand = 'PostCommitCommand', - Clean = 'Clean', - CleanNoProgress = 'CleanNoProgress', - Branch = 'Branch', - GetBranch = 'GetBranch', - GetBranches = 'GetBranches', - SetBranchUpstream = 'SetBranchUpstream', - HashObject = 'HashObject', - Checkout = 'Checkout', - CheckoutTracking = 'CheckoutTracking', - Reset = 'Reset', - Remote = 'Remote', - Fetch = 'Fetch', - FetchNoProgress = 'FetchNoProgress', - Pull = 'Pull', - Push = 'Push', - CherryPick = 'CherryPick', - Sync = 'Sync', - Show = 'Show', - Stage = 'Stage', - GetCommitTemplate = 'GetCommitTemplate', - DeleteBranch = 'DeleteBranch', - RenameBranch = 'RenameBranch', - DeleteRef = 'DeleteRef', - Merge = 'Merge', - MergeAbort = 'MergeAbort', - Rebase = 'Rebase', - Ignore = 'Ignore', - Tag = 'Tag', - DeleteTag = 'DeleteTag', - Stash = 'Stash', - CheckIgnore = 'CheckIgnore', - GetObjectDetails = 'GetObjectDetails', - SubmoduleUpdate = 'SubmoduleUpdate', - RebaseAbort = 'RebaseAbort', - RebaseContinue = 'RebaseContinue', - FindTrackingBranches = 'GetTracking', - Apply = 'Apply', - Blame = 'Blame', - Log = 'Log', - LogFile = 'LogFile', - - Move = 'Move' -} - -function isReadOnly(operation: OperationKind): boolean { - switch (operation) { - case OperationKind.Blame: - case OperationKind.CheckIgnore: - case OperationKind.Diff: - case OperationKind.FindTrackingBranches: - case OperationKind.GetBranch: - case OperationKind.GetCommitTemplate: - case OperationKind.GetObjectDetails: - case OperationKind.Log: - case OperationKind.LogFile: - case OperationKind.MergeBase: - case OperationKind.Show: - return true; - default: - return false; - } -} - -function shouldShowProgress(operation: OperationKind): boolean { - switch (operation) { - case OperationKind.AddNoProgress: - case OperationKind.CleanNoProgress: - case OperationKind.FetchNoProgress: - case OperationKind.RevertFilesNoProgress: - case OperationKind.CheckIgnore: - case OperationKind.GetObjectDetails: - case OperationKind.Show: - return false; - default: - return true; - } -} - -export interface BaseOperation { - readonly kind: OperationKind; -} - -export interface CheckoutOperation extends BaseOperation { - readonly kind: OperationKind.Checkout | OperationKind.CheckoutTracking; - readonly refLabel: string; -} - -export interface Operations { - isIdle(): boolean; - getOperations(operationKind: OperationKind): BaseOperation[]; - shouldShowProgress(): boolean; - isRunning(operationKind: OperationKind): boolean; -} - -class OperationsImpl implements Operations { - - private operations = new Map>(); - - constructor(private readonly logger: LogOutputChannel) { } - - start(operation: BaseOperation): void { - if (this.operations.has(operation.kind)) { - this.operations.get(operation.kind)!.add(operation); - } else { - this.operations.set(operation.kind, new Set([operation])); - } - - this.logger.trace(`Operation start: ${operation.kind}`); - } - - end(operation: BaseOperation): void { - const operationSet = this.operations.get(operation.kind); - if (operationSet) { - operationSet.delete(operation); - if (operationSet.size === 0) { - this.operations.delete(operation.kind); - } - } - - this.logger.trace(`Operation end: ${operation.kind}`); - } - - getOperations(operationKind: OperationKind): BaseOperation[] { - const operationSet = this.operations.get(operationKind); - return operationSet ? Array.from(operationSet) : []; - } - - isRunning(operationKind: OperationKind): boolean { - return this.operations.has(operationKind); - } - - isIdle(): boolean { - const operations = this.operations.keys(); - - for (const operation of operations) { - if (!isReadOnly(operation)) { - return false; - } - } - - return true; - } - - shouldShowProgress(): boolean { - const operations = this.operations.keys(); - - for (const operation of operations) { - if (shouldShowProgress(operation)) { - return true; - } - } - - return false; - } -} - export interface GitResourceGroup extends SourceControlResourceGroup { resourceStates: Resource[]; } @@ -481,11 +314,6 @@ interface GitResourceGroups { workingTreeGroup?: Resource[]; } -export interface OperationResult { - operation: OperationKind; - error: any; -} - class ProgressManager { private enabled = false; @@ -895,8 +723,8 @@ export class Repository implements Disposable { return this._mergeInProgress; } - private _operations = new OperationsImpl(this.logger); - get operations(): Operations { return this._operations; } + private _operations = new OperationManager(this.logger); + get operations(): OperationManager { return this._operations; } private _state = RepositoryState.Idle; get state(): RepositoryState { return this._state; } @@ -1038,7 +866,7 @@ export class Repository implements Disposable { } // https://github.com/microsoft/vscode/issues/39039 - const onSuccessfulPush = filterEvent(this.onDidRunOperation, e => e.operation === OperationKind.Push && !e.error); + const onSuccessfulPush = filterEvent(this.onDidRunOperation, e => e.operation.kind === OperationKind.Push && !e.error); onSuccessfulPush(() => { const gitConfig = workspace.getConfiguration('git'); @@ -1170,89 +998,89 @@ export class Repository implements Disposable { } getConfigs(): Promise<{ key: string; value: string }[]> { - return this.run(OperationKind.Config, () => this.repository.getConfigs('local')); + return this.run(Operation.Config, () => this.repository.getConfigs('local')); } getConfig(key: string): Promise { - return this.run(OperationKind.Config, () => this.repository.config('local', key)); + return this.run(Operation.Config, () => this.repository.config('local', key)); } getGlobalConfig(key: string): Promise { - return this.run(OperationKind.Config, () => this.repository.config('global', key)); + return this.run(Operation.Config, () => this.repository.config('global', key)); } setConfig(key: string, value: string): Promise { - return this.run(OperationKind.Config, () => this.repository.config('local', key, value)); + return this.run(Operation.Config, () => this.repository.config('local', key, value)); } log(options?: LogOptions): Promise { - return this.run(OperationKind.Log, () => this.repository.log(options)); + return this.run(Operation.Log, () => this.repository.log(options)); } logFile(uri: Uri, options?: LogFileOptions): Promise { // TODO: This probably needs per-uri granularity - return this.run(OperationKind.LogFile, () => this.repository.logFile(uri, options)); + return this.run(Operation.LogFile, () => this.repository.logFile(uri, options)); } @throttle async status(): Promise { - await this.run(OperationKind.Status); + await this.run(Operation.Status); } diff(cached?: boolean): Promise { - return this.run(OperationKind.Diff, () => this.repository.diff(cached)); + return this.run(Operation.Diff, () => this.repository.diff(cached)); } diffWithHEAD(): Promise; diffWithHEAD(path: string): Promise; diffWithHEAD(path?: string | undefined): Promise; diffWithHEAD(path?: string | undefined): Promise { - return this.run(OperationKind.Diff, () => this.repository.diffWithHEAD(path)); + return this.run(Operation.Diff, () => this.repository.diffWithHEAD(path)); } diffWith(ref: string): Promise; diffWith(ref: string, path: string): Promise; diffWith(ref: string, path?: string | undefined): Promise; diffWith(ref: string, path?: string): Promise { - return this.run(OperationKind.Diff, () => this.repository.diffWith(ref, path)); + return this.run(Operation.Diff, () => this.repository.diffWith(ref, path)); } diffIndexWithHEAD(): Promise; diffIndexWithHEAD(path: string): Promise; diffIndexWithHEAD(path?: string | undefined): Promise; diffIndexWithHEAD(path?: string): Promise { - return this.run(OperationKind.Diff, () => this.repository.diffIndexWithHEAD(path)); + return this.run(Operation.Diff, () => this.repository.diffIndexWithHEAD(path)); } diffIndexWith(ref: string): Promise; diffIndexWith(ref: string, path: string): Promise; diffIndexWith(ref: string, path?: string | undefined): Promise; diffIndexWith(ref: string, path?: string): Promise { - return this.run(OperationKind.Diff, () => this.repository.diffIndexWith(ref, path)); + return this.run(Operation.Diff, () => this.repository.diffIndexWith(ref, path)); } diffBlobs(object1: string, object2: string): Promise { - return this.run(OperationKind.Diff, () => this.repository.diffBlobs(object1, object2)); + return this.run(Operation.Diff, () => this.repository.diffBlobs(object1, object2)); } diffBetween(ref1: string, ref2: string): Promise; diffBetween(ref1: string, ref2: string, path: string): Promise; diffBetween(ref1: string, ref2: string, path?: string | undefined): Promise; diffBetween(ref1: string, ref2: string, path?: string): Promise { - return this.run(OperationKind.Diff, () => this.repository.diffBetween(ref1, ref2, path)); + return this.run(Operation.Diff, () => this.repository.diffBetween(ref1, ref2, path)); } getMergeBase(ref1: string, ref2: string): Promise { - return this.run(OperationKind.MergeBase, () => this.repository.getMergeBase(ref1, ref2)); + return this.run(Operation.MergeBase, () => this.repository.getMergeBase(ref1, ref2)); } async hashObject(data: string): Promise { - return this.run(OperationKind.HashObject, () => this.repository.hashObject(data)); + return this.run(Operation.HashObject, () => this.repository.hashObject(data)); } async add(resources: Uri[], opts?: { update?: boolean }): Promise { await this.run( - this.optimisticUpdateEnabled() ? OperationKind.AddNoProgress : OperationKind.Add, + Operation.Add(!this.optimisticUpdateEnabled()), async () => { await this.repository.add(resources.map(r => r.fsPath), opts); this.closeDiffEditors([], [...resources.map(r => r.fsPath)]); @@ -1289,12 +1117,12 @@ export class Repository implements Disposable { } async rm(resources: Uri[]): Promise { - await this.run(OperationKind.Remove, () => this.repository.rm(resources.map(r => r.fsPath))); + await this.run(Operation.Remove, () => this.repository.rm(resources.map(r => r.fsPath))); } async stage(resource: Uri, contents: string): Promise { const path = relativePath(this.repository.root, resource.fsPath).replace(/\\/g, '/'); - await this.run(OperationKind.Stage, async () => { + await this.run(Operation.Stage, async () => { await this.repository.stage(path, contents); this.closeDiffEditors([], [...resource.fsPath]); }); @@ -1303,7 +1131,7 @@ export class Repository implements Disposable { async revert(resources: Uri[]): Promise { await this.run( - this.optimisticUpdateEnabled() ? OperationKind.RevertFilesNoProgress : OperationKind.RevertFiles, + Operation.RevertFiles(!this.optimisticUpdateEnabled()), async () => { await this.repository.revert('HEAD', resources.map(r => r.fsPath)); this.closeDiffEditors([...resources.length !== 0 ? @@ -1350,7 +1178,7 @@ export class Repository implements Disposable { async commit(message: string | undefined, opts: CommitOptions = Object.create(null)): Promise { if (this.rebaseCommit) { await this.run( - OperationKind.RebaseContinue, + Operation.RebaseContinue, async () => { if (opts.all) { const addOpts = opts.all === 'tracked' ? { update: true } : {}; @@ -1366,7 +1194,7 @@ export class Repository implements Disposable { this.commitCommandCenter.postCommitCommand = opts.postCommitCommand; await this.run( - OperationKind.Commit, + Operation.Commit, async () => { if (opts.all) { const addOpts = opts.all === 'tracked' ? { update: true } : {}; @@ -1386,7 +1214,7 @@ export class Repository implements Disposable { () => this.commitOperationGetOptimisticResourceGroups(opts)); // Execute post-commit command - await this.run(OperationKind.PostCommitCommand, async () => { + await this.run(Operation.PostCommitCommand, async () => { await this.commitCommandCenter.executePostCommitCommand(opts.postCommitCommand); }); } @@ -1420,7 +1248,7 @@ export class Repository implements Disposable { async clean(resources: Uri[]): Promise { await this.run( - this.optimisticUpdateEnabled() ? OperationKind.CleanNoProgress : OperationKind.Clean, + Operation.Clean(!this.optimisticUpdateEnabled()), async () => { const toClean: string[] = []; const toCheckout: string[] = []; @@ -1508,15 +1336,15 @@ export class Repository implements Disposable { } async branch(name: string, _checkout: boolean, _ref?: string): Promise { - await this.run(OperationKind.Branch, () => this.repository.branch(name, _checkout, _ref)); + await this.run(Operation.Branch, () => this.repository.branch(name, _checkout, _ref)); } async deleteBranch(name: string, force?: boolean): Promise { - await this.run(OperationKind.DeleteBranch, () => this.repository.deleteBranch(name, force)); + await this.run(Operation.DeleteBranch, () => this.repository.deleteBranch(name, force)); } async renameBranch(name: string): Promise { - await this.run(OperationKind.RenameBranch, () => this.repository.renameBranch(name)); + await this.run(Operation.RenameBranch, () => this.repository.renameBranch(name)); } @throttle @@ -1530,7 +1358,7 @@ export class Repository implements Disposable { try { // Fast-forward the branch if possible const options = { remote: branch.upstream.remote, ref: `${branch.upstream.name}:${branch.name}` }; - await this.run(OperationKind.Fetch, async () => this.repository.fetch(options)); + await this.run(Operation.Fetch(true), async () => this.repository.fetch(options)); } catch (err) { if (err.gitErrorCode === GitErrorCodes.BranchFastForwardRejected) { return; @@ -1541,50 +1369,49 @@ export class Repository implements Disposable { } async cherryPick(commitHash: string): Promise { - await this.run(OperationKind.CherryPick, () => this.repository.cherryPick(commitHash)); + await this.run(Operation.CherryPick, () => this.repository.cherryPick(commitHash)); } async move(from: string, to: string): Promise { - await this.run(OperationKind.Move, () => this.repository.move(from, to)); + await this.run(Operation.Move, () => this.repository.move(from, to)); } async getBranch(name: string): Promise { - return await this.run(OperationKind.GetBranch, () => this.repository.getBranch(name)); + return await this.run(Operation.GetBranch, () => this.repository.getBranch(name)); } async getBranches(query: BranchQuery): Promise { - return await this.run(OperationKind.GetBranches, () => this.repository.getBranches(query)); + return await this.run(Operation.GetBranches, () => this.repository.getBranches(query)); } async setBranchUpstream(name: string, upstream: string): Promise { - await this.run(OperationKind.SetBranchUpstream, () => this.repository.setBranchUpstream(name, upstream)); + await this.run(Operation.SetBranchUpstream, () => this.repository.setBranchUpstream(name, upstream)); } async merge(ref: string): Promise { - await this.run(OperationKind.Merge, () => this.repository.merge(ref)); + await this.run(Operation.Merge, () => this.repository.merge(ref)); } async mergeAbort(): Promise { - await this.run(OperationKind.MergeAbort, async () => await this.repository.mergeAbort()); + await this.run(Operation.MergeAbort, async () => await this.repository.mergeAbort()); } async rebase(branch: string): Promise { - await this.run(OperationKind.Rebase, () => this.repository.rebase(branch)); + await this.run(Operation.Rebase, () => this.repository.rebase(branch)); } async tag(name: string, message?: string): Promise { - await this.run(OperationKind.Tag, () => this.repository.tag(name, message)); + await this.run(Operation.Tag, () => this.repository.tag(name, message)); } async deleteTag(name: string): Promise { - await this.run(OperationKind.DeleteTag, () => this.repository.deleteTag(name)); + await this.run(Operation.DeleteTag, () => this.repository.deleteTag(name)); } async checkout(treeish: string, opts?: { detached?: boolean; pullBeforeCheckout?: boolean }): Promise { const refLabel = this.checkoutRefLabel(treeish, opts?.detached); - const operation: CheckoutOperation = { kind: OperationKind.Checkout, refLabel }; - await this.run(operation, + await this.run(Operation.Checkout(refLabel), async () => { if (opts?.pullBeforeCheckout && !opts?.detached) { try { @@ -1601,13 +1428,11 @@ export class Repository implements Disposable { async checkoutTracking(treeish: string, opts: { detached?: boolean } = {}): Promise { const refLabel = this.checkoutRefLabel(treeish, opts?.detached); - const operation: CheckoutOperation = { kind: OperationKind.CheckoutTracking, refLabel }; - - await this.run(operation, () => this.repository.checkout(treeish, [], { ...opts, track: true })); + await this.run(Operation.CheckoutTracking(refLabel), () => this.repository.checkout(treeish, [], { ...opts, track: true })); } async findTrackingBranches(upstreamRef: string): Promise { - return await this.run(OperationKind.FindTrackingBranches, () => this.repository.findTrackingBranches(upstreamRef)); + return await this.run(Operation.FindTrackingBranches, () => this.repository.findTrackingBranches(upstreamRef)); } async getCommit(ref: string): Promise { @@ -1615,23 +1440,23 @@ export class Repository implements Disposable { } async reset(treeish: string, hard?: boolean): Promise { - await this.run(OperationKind.Reset, () => this.repository.reset(treeish, hard)); + await this.run(Operation.Reset, () => this.repository.reset(treeish, hard)); } async deleteRef(ref: string): Promise { - await this.run(OperationKind.DeleteRef, () => this.repository.deleteRef(ref)); + await this.run(Operation.DeleteRef, () => this.repository.deleteRef(ref)); } async addRemote(name: string, url: string): Promise { - await this.run(OperationKind.Remote, () => this.repository.addRemote(name, url)); + await this.run(Operation.Remote, () => this.repository.addRemote(name, url)); } async removeRemote(name: string): Promise { - await this.run(OperationKind.Remote, () => this.repository.removeRemote(name)); + await this.run(Operation.Remote, () => this.repository.removeRemote(name)); } async renameRemote(name: string, newName: string): Promise { - await this.run(OperationKind.Remote, () => this.repository.renameRemote(name, newName)); + await this.run(Operation.Remote, () => this.repository.renameRemote(name, newName)); } @throttle @@ -1660,8 +1485,7 @@ export class Repository implements Disposable { options.prune = prune; } - const operation = options.silent === true ? OperationKind.FetchNoProgress : OperationKind.Fetch; - await this.run(operation, async () => this.repository.fetch(options)); + await this.run(Operation.Fetch(options.silent !== true), async () => this.repository.fetch(options)); } @throttle @@ -1691,7 +1515,7 @@ export class Repository implements Disposable { } async pullFrom(rebase?: boolean, remote?: string, branch?: string, unshallow?: boolean): Promise { - await this.run(OperationKind.Pull, async () => { + await this.run(Operation.Pull, async () => { await this.maybeAutoStash(async () => { const config = workspace.getConfiguration('git', Uri.file(this.root)); const fetchOnPull = config.get('fetchOnPull'); @@ -1735,23 +1559,23 @@ export class Repository implements Disposable { branch = `${head.name}:${head.upstream.name}`; } - await this.run(OperationKind.Push, () => this._push(remote, branch, undefined, undefined, forcePushMode)); + await this.run(Operation.Push, () => this._push(remote, branch, undefined, undefined, forcePushMode)); } async pushTo(remote?: string, name?: string, setUpstream = false, forcePushMode?: ForcePushMode): Promise { - await this.run(OperationKind.Push, () => this._push(remote, name, setUpstream, undefined, forcePushMode)); + await this.run(Operation.Push, () => this._push(remote, name, setUpstream, undefined, forcePushMode)); } async pushFollowTags(remote?: string, forcePushMode?: ForcePushMode): Promise { - await this.run(OperationKind.Push, () => this._push(remote, undefined, false, true, forcePushMode)); + await this.run(Operation.Push, () => this._push(remote, undefined, false, true, forcePushMode)); } async pushTags(remote?: string, forcePushMode?: ForcePushMode): Promise { - await this.run(OperationKind.Push, () => this._push(remote, undefined, false, false, forcePushMode, true)); + await this.run(Operation.Push, () => this._push(remote, undefined, false, false, forcePushMode, true)); } async blame(path: string): Promise { - return await this.run(OperationKind.Blame, () => this.repository.blame(path)); + return await this.run(Operation.Blame, () => this.repository.blame(path)); } @throttle @@ -1770,7 +1594,7 @@ export class Repository implements Disposable { pushBranch = `${head.name}:${head.upstream.name}`; } - await this.run(OperationKind.Sync, async () => { + await this.run(Operation.Sync, async () => { await this.maybeAutoStash(async () => { const config = workspace.getConfiguration('git', Uri.file(this.root)); const fetchOnPull = config.get('fetchOnPull'); @@ -1824,7 +1648,7 @@ export class Repository implements Disposable { return true; } - const maybeRebased = await this.run(OperationKind.Log, async () => { + const maybeRebased = await this.run(Operation.Log, async () => { try { const result = await this.repository.exec(['log', '--oneline', '--cherry', `${currentBranch ?? ''}...${currentBranch ?? ''}@{upstream}`, '--']); if (result.exitCode) { @@ -1865,7 +1689,7 @@ export class Repository implements Disposable { } async show(ref: string, filePath: string): Promise { - return await this.run(OperationKind.Show, async () => { + return await this.run(Operation.Show, async () => { const path = relativePath(this.repository.root, filePath).replace(/\\/g, '/'); const configFiles = workspace.getConfiguration('files', Uri.file(filePath)); const defaultEncoding = configFiles.get('encoding'); @@ -1885,22 +1709,22 @@ export class Repository implements Disposable { } async buffer(ref: string, filePath: string): Promise { - return this.run(OperationKind.Show, () => { + return this.run(Operation.Show, () => { const path = relativePath(this.repository.root, filePath).replace(/\\/g, '/'); return this.repository.buffer(`${ref}:${path}`); }); } getObjectDetails(ref: string, filePath: string): Promise<{ mode: string; object: string; size: number }> { - return this.run(OperationKind.GetObjectDetails, () => this.repository.getObjectDetails(ref, filePath)); + return this.run(Operation.GetObjectDetails, () => this.repository.getObjectDetails(ref, filePath)); } detectObjectType(object: string): Promise<{ mimetype: string; encoding?: string }> { - return this.run(OperationKind.Show, () => this.repository.detectObjectType(object)); + return this.run(Operation.Show, () => this.repository.detectObjectType(object)); } async apply(patch: string, reverse?: boolean): Promise { - return await this.run(OperationKind.Apply, () => this.repository.apply(patch, reverse)); + return await this.run(Operation.Apply, () => this.repository.apply(patch, reverse)); } async getStashes(): Promise { @@ -1913,30 +1737,30 @@ export class Repository implements Disposable { ...!staged ? this.workingTreeGroup.resourceStates.map(r => r.resourceUri.fsPath) : [], ...includeUntracked ? this.untrackedGroup.resourceStates.map(r => r.resourceUri.fsPath) : []]; - return await this.run(OperationKind.Stash, async () => { + return await this.run(Operation.Stash, async () => { await this.repository.createStash(message, includeUntracked, staged); this.closeDiffEditors(indexResources, workingGroupResources); }); } async popStash(index?: number): Promise { - return await this.run(OperationKind.Stash, () => this.repository.popStash(index)); + return await this.run(Operation.Stash, () => this.repository.popStash(index)); } async dropStash(index?: number): Promise { - return await this.run(OperationKind.Stash, () => this.repository.dropStash(index)); + return await this.run(Operation.Stash, () => this.repository.dropStash(index)); } async applyStash(index?: number): Promise { - return await this.run(OperationKind.Stash, () => this.repository.applyStash(index)); + return await this.run(Operation.Stash, () => this.repository.applyStash(index)); } async getCommitTemplate(): Promise { - return await this.run(OperationKind.GetCommitTemplate, async () => this.repository.getCommitTemplate()); + return await this.run(Operation.GetCommitTemplate, async () => this.repository.getCommitTemplate()); } async ignore(files: Uri[]): Promise { - return await this.run(OperationKind.Ignore, async () => { + return await this.run(Operation.Ignore, async () => { const ignoreFile = `${this.repository.root}${path.sep}.gitignore`; const textToAppend = files .map(uri => relativePath(this.repository.root, uri.fsPath).replace(/\\/g, '/')) @@ -1959,11 +1783,11 @@ export class Repository implements Disposable { } async rebaseAbort(): Promise { - await this.run(OperationKind.RebaseAbort, async () => await this.repository.rebaseAbort()); + await this.run(Operation.RebaseAbort, async () => await this.repository.rebaseAbort()); } checkIgnore(filePaths: string[]): Promise> { - return this.run(OperationKind.CheckIgnore, () => { + return this.run(Operation.CheckIgnore, () => { return new Promise>((resolve, reject) => { filePaths = filePaths @@ -2054,16 +1878,9 @@ export class Repository implements Disposable { } private async run( - operation: OperationKind | BaseOperation, + operation: Operation, runOperation: () => Promise = () => Promise.resolve(null), getOptimisticResourceGroups: () => GitResourceGroups | undefined = () => undefined): Promise { - return this._run(typeof operation === 'object' ? operation : { kind: operation }, runOperation, getOptimisticResourceGroups); - } - - private async _run( - operation: BaseOperation, - runOperation: () => Promise, - getOptimisticResourceGroups: () => GitResourceGroups | undefined): Promise { if (this.state !== RepositoryState.Idle) { throw new Error('Repository not initialized'); @@ -2075,9 +1892,9 @@ export class Repository implements Disposable { this._onRunOperation.fire(operation.kind); try { - const result = await this._retryRun(operation.kind, runOperation); + const result = await this.retryRun(operation, runOperation); - if (!isReadOnly(operation.kind)) { + if (!operation.readOnly) { await this.updateModelState(this.optimisticUpdateEnabled() ? getOptimisticResourceGroups() : undefined); } @@ -2092,11 +1909,11 @@ export class Repository implements Disposable { throw err; } finally { this._operations.end(operation); - this._onDidRunOperation.fire({ operation: operation.kind, error }); + this._onDidRunOperation.fire({ operation: operation, error }); } } - private async _retryRun(operation: OperationKind, runOperation: () => Promise = () => Promise.resolve(null)): Promise { + private async retryRun(operation: Operation, runOperation: () => Promise = () => Promise.resolve(null)): Promise { let attempt = 0; while (true) { @@ -2106,7 +1923,7 @@ export class Repository implements Disposable { } catch (err) { const shouldRetry = attempt <= 10 && ( (err.gitErrorCode === GitErrorCodes.RepositoryIsLocked) - || ((operation === OperationKind.Pull || operation === OperationKind.Sync || operation === OperationKind.Fetch) && (err.gitErrorCode === GitErrorCodes.CantLockRef || err.gitErrorCode === GitErrorCodes.CantRebaseMultipleBranches)) + || (operation.retry && (err.gitErrorCode === GitErrorCodes.CantLockRef || err.gitErrorCode === GitErrorCodes.CantRebaseMultipleBranches)) ); if (shouldRetry) { diff --git a/extensions/git/src/statusbar.ts b/extensions/git/src/statusbar.ts index a62027ffdaa..0a8690b87e0 100644 --- a/extensions/git/src/statusbar.ts +++ b/extensions/git/src/statusbar.ts @@ -4,10 +4,11 @@ *--------------------------------------------------------------------------------------------*/ import { Disposable, Command, EventEmitter, Event, workspace, Uri, l10n } from 'vscode'; -import { Repository, OperationKind, CheckoutOperation } from './repository'; +import { Repository } from './repository'; import { anyEvent, dispose, filterEvent } from './util'; import { Branch, RefType, RemoteSourcePublisher } from './api/git'; import { IRemoteSourcePublisherRegistry } from './remotePublisher'; +import { CheckoutOperation, CheckoutTrackingOperation, OperationKind } from './operation'; interface CheckoutStatusBarState { readonly isCheckoutRunning: boolean; @@ -42,7 +43,7 @@ class CheckoutStatusBar { get command(): Command | undefined { const operationData = [ ...this.repository.operations.getOperations(OperationKind.Checkout) as CheckoutOperation[], - ...this.repository.operations.getOperations(OperationKind.CheckoutTracking) as CheckoutOperation[] + ...this.repository.operations.getOperations(OperationKind.CheckoutTracking) as CheckoutTrackingOperation[] ]; const rebasing = !!this.repository.rebaseCommit;