diff --git a/extensions/git/package.json b/extensions/git/package.json index ee3a7a16d27..9743287dfb9 100644 --- a/extensions/git/package.json +++ b/extensions/git/package.json @@ -630,6 +630,12 @@ "category": "Git", "enablement": "!operationInProgress" }, + { + "command": "git.cherryPickAbort", + "title": "%command.cherryPickAbort%", + "category": "Git", + "enablement": "!operationInProgress" + }, { "command": "git.addRemote", "title": "%command.addRemote%", @@ -1203,6 +1209,10 @@ "command": "git.cherryPick", "when": "config.git.enabled && !git.missing && gitOpenRepositoryCount != 0" }, + { + "command": "git.cherryPickAbort", + "when": "config.git.enabled && !git.missing && gitOpenRepositoryCount != 0 && gitCherryPickInProgress" + }, { "command": "git.pull", "when": "config.git.enabled && !git.missing && gitOpenRepositoryCount != 0" diff --git a/extensions/git/package.nls.json b/extensions/git/package.nls.json index bad361bcea7..78a6b0f2e7a 100644 --- a/extensions/git/package.nls.json +++ b/extensions/git/package.nls.json @@ -69,6 +69,7 @@ "command.renameBranch": "Rename Branch...", "command.cherryPick": "Cherry Pick...", "command.cherryPickRef": "Cherry Pick", + "command.cherryPickAbort": "Abort Cherry Pick", "command.merge": "Merge...", "command.mergeAbort": "Abort Merge", "command.rebase": "Rebase Branch...", diff --git a/extensions/git/src/api/git.d.ts b/extensions/git/src/api/git.d.ts index 2a54c52f20c..ee45e7a893a 100644 --- a/extensions/git/src/api/git.d.ts +++ b/extensions/git/src/api/git.d.ts @@ -410,5 +410,6 @@ export const enum GitErrorCodes { BranchFastForwardRejected = 'BranchFastForwardRejected', BranchNotYetBorn = 'BranchNotYetBorn', TagConflict = 'TagConflict', - CherryPickEmpty = 'CherryPickEmpty' + CherryPickEmpty = 'CherryPickEmpty', + CherryPickConflict = 'CherryPickConflict' } diff --git a/extensions/git/src/commands.ts b/extensions/git/src/commands.ts index 3b1a848adcf..a85322a055e 100644 --- a/extensions/git/src/commands.ts +++ b/extensions/git/src/commands.ts @@ -3318,6 +3318,11 @@ export class CommandCenter { await repository.cherryPick(historyItem.id); } + @command('git.cherryPickAbort', { repository: true }) + async cherryPickAbort(repository: Repository): Promise { + await repository.cherryPickAbort(); + } + @command('git.pushTo', { repository: true }) async pushTo(repository: Repository, remote?: string, refspec?: string, setUpstream?: boolean): Promise { await this._push(repository, { pushType: PushType.PushTo, pushTo: { remote: remote, refspec: refspec, setUpstream: setUpstream } }); @@ -4409,6 +4414,12 @@ export class CommandCenter { type = 'information'; options.modal = false; break; + case GitErrorCodes.CherryPickConflict: + message = l10n.t('There were merge conflicts while cherry picking the changes. Resolve the conflicts before committing them.'); + type = 'warning'; + choices.set(l10n.t('Show Changes'), () => commands.executeCommand('workbench.view.scm')); + options.modal = false; + break; default: { const hint = (err.stderr || err.message || String(err)) .replace(/^error: /mi, '') diff --git a/extensions/git/src/git.ts b/extensions/git/src/git.ts index fadcced6faa..bb5cb7102da 100644 --- a/extensions/git/src/git.ts +++ b/extensions/git/src/git.ts @@ -2106,16 +2106,23 @@ export class Repository { await this.exec(['cherry-pick', commitHash]); } catch (err) { if (/The previous cherry-pick is now empty, possibly due to conflict resolution./.test(err.stderr ?? '')) { - // Abort the cherry-pick operation - await this.exec(['cherry-pick', '--abort']); + // Abort cherry-pick + await this.cherryPickAbort(); err.gitErrorCode = GitErrorCodes.CherryPickEmpty; + } else { + // Conflict during cherry-pick + err.gitErrorCode = GitErrorCodes.CherryPickConflict; } throw err; } } + async cherryPickAbort(): Promise { + await this.exec(['cherry-pick', '--abort']); + } + async blame(path: string): Promise { try { const args = ['blame', sanitizePath(path)]; diff --git a/extensions/git/src/repository.ts b/extensions/git/src/repository.ts index ee1a5c24552..6302a55e891 100644 --- a/extensions/git/src/repository.ts +++ b/extensions/git/src/repository.ts @@ -790,6 +790,21 @@ export class Repository implements Disposable { return this._mergeInProgress; } + private _cherryPickInProgress: boolean = false; + + set cherryPickInProgress(value: boolean) { + if (this._cherryPickInProgress === value) { + return; + } + + this._cherryPickInProgress = value; + commands.executeCommand('setContext', 'gitCherryPickInProgress', value); + } + + get cherryPickInProgress() { + return this._cherryPickInProgress; + } + private _operations = new OperationManager(this.logger); get operations(): OperationManager { return this._operations; } @@ -1434,6 +1449,10 @@ export class Repository implements Disposable { await this.run(Operation.CherryPick, () => this.repository.cherryPick(commitHash)); } + async cherryPickAbort(): Promise { + await this.run(Operation.CherryPick, () => this.repository.cherryPickAbort()); + } + async move(from: string, to: string): Promise { await this.run(Operation.Move, () => this.repository.move(from, to)); } @@ -2171,13 +2190,14 @@ export class Repository implements Disposable { this._updateResourceGroupsState(optimisticResourcesGroups); } - const [HEAD, remotes, submodules, rebaseCommit, mergeInProgress, commitTemplate] = + const [HEAD, remotes, submodules, rebaseCommit, mergeInProgress, cherryPickInProgress, commitTemplate] = await Promise.all([ this.repository.getHEADRef(), this.repository.getRemotes(), this.repository.getSubmodules(), this.getRebaseCommit(), this.isMergeInProgress(), + this.isCherryPickInProgress(), this.getInputTemplate()]); this._HEAD = HEAD; @@ -2185,6 +2205,7 @@ export class Repository implements Disposable { this._submodules = submodules!; this.rebaseCommit = rebaseCommit; this.mergeInProgress = mergeInProgress; + this.cherryPickInProgress = cherryPickInProgress; this._sourceControl.commitTemplate = commitTemplate; @@ -2412,6 +2433,11 @@ export class Repository implements Disposable { return new Promise(resolve => fs.exists(mergeHeadPath, resolve)); } + private isCherryPickInProgress(): Promise { + const cherryPickHeadPath = path.join(this.repository.root, '.git', 'CHERRY_PICK_HEAD'); + return new Promise(resolve => fs.exists(cherryPickHeadPath, resolve)); + } + private async maybeAutoStash(runOperation: () => Promise): Promise { const config = workspace.getConfiguration('git', Uri.file(this.root)); const shouldAutoStash = config.get('autoStash')