From 24f68197dbacb5cc4ee012d9839d4dae3de626f2 Mon Sep 17 00:00:00 2001 From: Sergio Date: Tue, 3 Dec 2024 16:00:08 +0100 Subject: [PATCH 1/3] convert git rebase into git cmd --- src/commands/git/rebase.ts | 15 ++++--- src/env/node/git/git.ts | 22 ++++++++++ src/env/node/git/localGitProvider.ts | 32 ++++++++++++++ src/git/errors.ts | 62 ++++++++++++++++++++++++++++ src/git/gitProvider.ts | 6 +++ src/git/gitProviderService.ts | 15 +++++++ src/git/models/repository.ts | 8 ---- 7 files changed, 147 insertions(+), 13 deletions(-) diff --git a/src/commands/git/rebase.ts b/src/commands/git/rebase.ts index 15a3b5c779c8c..c77d1129d921f 100644 --- a/src/commands/git/rebase.ts +++ b/src/commands/git/rebase.ts @@ -4,10 +4,12 @@ import type { GitLog } from '../../git/models/log'; import type { GitReference } from '../../git/models/reference'; import { createRevisionRange, getReferenceLabel, isRevisionReference } from '../../git/models/reference'; import type { Repository } from '../../git/models/repository'; +import { showGenericErrorMessage } from '../../messages'; import type { DirectiveQuickPickItem } from '../../quickpicks/items/directive'; import { createDirectiveQuickPickItem, Directive } from '../../quickpicks/items/directive'; import type { FlagsQuickPickItem } from '../../quickpicks/items/flags'; import { createFlagsQuickPickItem } from '../../quickpicks/items/flags'; +import { Logger } from '../../system/logger'; import { pluralize } from '../../system/string'; import { getEditorCommand } from '../../system/vscode/utils'; import type { ViewsWithRepositoryFolders } from '../../views/viewBase'; @@ -79,15 +81,18 @@ export class RebaseGitCommand extends QuickCommand { } async execute(state: RebaseStepState) { - let configs: string[] | undefined; + const configs: { sequenceEditor?: string } = {}; if (state.flags.includes('--interactive')) { await this.container.rebaseEditor.enableForNextUse(); - - const editor = getEditorCommand(); - configs = ['-c', `"sequence.editor=${editor}"`]; + configs.sequenceEditor = getEditorCommand(); } - state.repo.rebase(configs, ...state.flags, state.destination.ref); + try { + await state.repo.git.rebase(state.destination.ref, configs, state.flags); + } catch (ex) { + Logger.error(ex, this.title); + void showGenericErrorMessage(ex); + } } protected async *steps(state: PartialStepState): StepGenerator { diff --git a/src/env/node/git/git.ts b/src/env/node/git/git.ts index 6f9b571ea868c..b8f1981326c4e 100644 --- a/src/env/node/git/git.ts +++ b/src/env/node/git/git.ts @@ -20,6 +20,8 @@ import { PullErrorReason, PushError, PushErrorReason, + RebaseError, + RebaseErrorReason, StashPushError, StashPushErrorReason, TagError, @@ -173,6 +175,11 @@ const tagErrorAndReason: [RegExp, TagErrorReason][] = [ [GitErrors.remoteRejected, TagErrorReason.RemoteRejected], ]; +const rebaseErrorAndReason: [RegExp, RebaseErrorReason][] = [ + [GitErrors.uncommittedChanges, RebaseErrorReason.WorkingChanges], + [GitErrors.changesWouldBeOverwritten, RebaseErrorReason.OverwrittenChanges], +]; + export class Git { /** Map of running git commands -- avoids running duplicate overlaping commands */ private readonly pendingCommands = new Map>(); @@ -1092,6 +1099,21 @@ export class Git { } } + async rebase(repoPath: string, args: string[] | undefined = [], configs: string[] | undefined = []): Promise { + try { + void (await this.git({ cwd: repoPath }, ...configs, 'rebase', '--autostash', ...args)); + } catch (ex) { + const msg: string = ex?.toString() ?? ''; + for (const [regex, reason] of rebaseErrorAndReason) { + if (regex.test(msg) || regex.test(ex.stderr ?? '')) { + throw new RebaseError(reason, ex); + } + } + + throw new RebaseError(RebaseErrorReason.Other, ex); + } + } + for_each_ref__branch(repoPath: string, options: { all: boolean } = { all: false }) { const params = ['for-each-ref', `--format=${parseGitBranchesDefaultFormat}`, 'refs/heads']; if (options.all) { diff --git a/src/env/node/git/localGitProvider.ts b/src/env/node/git/localGitProvider.ts index 75f5e9b1cbfd3..23c758691d0fc 100644 --- a/src/env/node/git/localGitProvider.ts +++ b/src/env/node/git/localGitProvider.ts @@ -31,6 +31,7 @@ import { PullError, PushError, PushErrorReason, + RebaseError, StashApplyError, StashApplyErrorReason, StashPushError, @@ -1658,6 +1659,37 @@ export class LocalGitProvider implements GitProvider, Disposable { } } + @log() + async rebase( + repoPath: string, + ref: string, + configs?: { sequenceEditor?: string }, + options?: { interactive?: boolean } = {}, + ): Promise { + const configFlags = []; + const args = []; + + if (configs?.sequenceEditor != null) { + configFlags.push('-c', `sequence.editor="${configs.sequenceEditor}"`); + } + + if (options?.interactive) { + args.push('--interactive'); + } + + args.push(ref); + + try { + await this.git.rebase(repoPath, args, configFlags); + } catch (ex) { + if (RebaseError.is(ex)) { + throw ex.WithRef(ref); + } + + throw ex; + } + } + private readonly toCanonicalMap = new Map(); private readonly fromCanonicalMap = new Map(); protected readonly unsafePaths = new Set(); diff --git a/src/git/errors.ts b/src/git/errors.ts index e1ef081fdfb25..196b0ea5f21b8 100644 --- a/src/git/errors.ts +++ b/src/git/errors.ts @@ -567,3 +567,65 @@ export class TagError extends Error { return this; } } + +export const enum RebaseErrorReason { + WorkingChanges, + OverwrittenChanges, + Other, +} + +export class RebaseError extends Error { + static is(ex: unknown, reason?: RebaseErrorReason): ex is RebaseError { + return ex instanceof RebaseError && (reason == null || ex.reason === reason); + } + + private static buildRebaseErrorMessage(reason?: RebaseErrorReason, ref?: string): string { + let baseMessage: string; + if (ref != null) { + baseMessage = `Unable to rebase onto ${ref}`; + } else { + baseMessage = `Unable to rebase`; + } + + switch (reason) { + case RebaseErrorReason.WorkingChanges: + return `${baseMessage} because there are uncommitted changes`; + case RebaseErrorReason.OverwrittenChanges: + return `${baseMessage} because some local changes would be overwritten`; + default: + return baseMessage; + } + } + + readonly original?: Error; + readonly reason: RebaseErrorReason | undefined; + ref?: string; + + constructor(reason?: RebaseErrorReason, original?: Error); + constructor(message?: string, original?: Error); + constructor(messageOrReason: string | RebaseErrorReason | undefined, original?: Error, ref?: string) { + let reason: RebaseErrorReason | undefined; + if (typeof messageOrReason !== 'string') { + reason = messageOrReason as RebaseErrorReason; + } else { + super(messageOrReason); + } + + const message = + typeof messageOrReason === 'string' + ? messageOrReason + : RebaseError.buildRebaseErrorMessage(messageOrReason as RebaseErrorReason, ref); + super(message); + + this.original = original; + this.reason = reason; + this.ref = ref; + Error.captureStackTrace?.(this, RebaseError); + } + + WithRef(ref: string) { + this.ref = ref; + this.message = RebaseError.buildRebaseErrorMessage(this.reason, ref); + return this; + } +} diff --git a/src/git/gitProvider.ts b/src/git/gitProvider.ts index 162279a814baf..94d80e9783745 100644 --- a/src/git/gitProvider.ts +++ b/src/git/gitProvider.ts @@ -125,6 +125,12 @@ export interface GitProviderRepository { addRemote?(repoPath: string, name: string, url: string, options?: { fetch?: boolean }): Promise; pruneRemote?(repoPath: string, name: string): Promise; removeRemote?(repoPath: string, name: string): Promise; + rebase?( + repoPath: string, + ref: string, + configs?: { sequenceEditor?: string }, + options?: { interactive?: boolean }, + ): Promise; applyUnreachableCommitForPatch?( repoPath: string, diff --git a/src/git/gitProviderService.ts b/src/git/gitProviderService.ts index 479657d170dd1..efbbb48032418 100644 --- a/src/git/gitProviderService.ts +++ b/src/git/gitProviderService.ts @@ -1342,6 +1342,21 @@ export class GitProviderService implements Disposable { return provider.applyChangesToWorkingFile(uri, ref1, ref2); } + @log() + rebase(repoPath: string | Uri, ref: string, configs: { sequenceEditor?: string }, args: string[] | undefined = []) { + const { provider, path } = this.getProvider(repoPath); + if (provider.rebase == null) throw new ProviderNotSupportedError(provider.descriptor.name); + + const options: { interactive?: boolean } = {}; + for (const arg of args) { + if (arg === '--interactive') { + options.interactive = true; + } + } + + return provider.rebase(path, ref, configs, options); + } + @log() async applyUnreachableCommitForPatch( repoPath: string | Uri, diff --git a/src/git/models/repository.ts b/src/git/models/repository.ts index 49c0a3c4a972c..821346e636fa2 100644 --- a/src/git/models/repository.ts +++ b/src/git/models/repository.ts @@ -842,14 +842,6 @@ export class Repository implements Disposable { } } - @log() - rebase(configs: string[] | undefined, ...args: string[]) { - void this.runTerminalCommand( - configs != null && configs.length !== 0 ? `${configs.join(' ')} rebase` : 'rebase', - ...args, - ); - } - @log() reset(...args: string[]) { void this.runTerminalCommand('reset', ...args); From 4bb6cbba012a5c836c9ca64a92bbed0b05fdbc0e Mon Sep 17 00:00:00 2001 From: Sergio Date: Thu, 5 Dec 2024 16:57:23 +0100 Subject: [PATCH 2/3] use options instead of flags in rebase command --- src/commands/git/rebase.ts | 23 ++++++++++++++--------- src/git/gitProviderService.ts | 14 ++++++-------- 2 files changed, 20 insertions(+), 17 deletions(-) diff --git a/src/commands/git/rebase.ts b/src/commands/git/rebase.ts index c77d1129d921f..de4a6587ae6ed 100644 --- a/src/commands/git/rebase.ts +++ b/src/commands/git/rebase.ts @@ -39,11 +39,13 @@ interface Context { } type Flags = '--interactive'; +type RebaseOptions = { interactive?: boolean }; interface State { repo: string | Repository; destination: GitReference; flags: Flags[]; + options: RebaseOptions; } export interface RebaseGitCommandArgs { @@ -82,13 +84,13 @@ export class RebaseGitCommand extends QuickCommand { async execute(state: RebaseStepState) { const configs: { sequenceEditor?: string } = {}; - if (state.flags.includes('--interactive')) { + if (state.options?.interactive) { await this.container.rebaseEditor.enableForNextUse(); configs.sequenceEditor = getEditorCommand(); } try { - await state.repo.git.rebase(state.destination.ref, configs, state.flags); + await state.repo.git.rebase(state.destination.ref, configs, state.options); } catch (ex) { Logger.error(ex, this.title); void showGenericErrorMessage(ex); @@ -108,8 +110,8 @@ export class RebaseGitCommand extends QuickCommand { title: this.title, }; - if (state.flags == null) { - state.flags = []; + if (state.options == null) { + state.options = {}; } let skippedStepOne = false; @@ -212,7 +214,7 @@ export class RebaseGitCommand extends QuickCommand { const result = yield* this.confirmStep(state as RebaseStepState, context); if (result === StepResultBreak) continue; - state.flags = result; + state.options = Object.assign(state.options ?? {}, ...result); endSteps(state); void this.execute(state as RebaseStepState); @@ -221,7 +223,7 @@ export class RebaseGitCommand extends QuickCommand { return state.counter < 0 ? StepResultBreak : undefined; } - private async *confirmStep(state: RebaseStepState, context: Context): AsyncStepResultGenerator { + private async *confirmStep(state: RebaseStepState, context: Context): AsyncStepResultGenerator { const counts = await this.container.git.getLeftRightCommitCount( state.repo.path, createRevisionRange(state.destination.ref, context.branch.ref, '...'), @@ -253,8 +255,9 @@ export class RebaseGitCommand extends QuickCommand { return StepResultBreak; } + const optionsArr: RebaseOptions[] = []; const rebaseItems = [ - createFlagsQuickPickItem(state.flags, ['--interactive'], { + createFlagsQuickPickItem(optionsArr, [{ interactive: true }], { label: `Interactive ${this.title}`, description: '--interactive', detail: `Will interactively update ${getReferenceLabel(context.branch, { @@ -267,7 +270,7 @@ export class RebaseGitCommand extends QuickCommand { if (behind > 0) { rebaseItems.unshift( - createFlagsQuickPickItem(state.flags, [], { + createFlagsQuickPickItem(optionsArr, [{}], { label: this.title, detail: `Will update ${getReferenceLabel(context.branch, { label: false, @@ -278,10 +281,12 @@ export class RebaseGitCommand extends QuickCommand { ); } - const step: QuickPickStep> = this.createConfirmStep( + const step: QuickPickStep> = this.createConfirmStep( appendReposToTitle(`Confirm ${title}`, state, context), rebaseItems, ); + + state.options = Object.assign(state.options, ...optionsArr); const selection: StepSelection = yield step; return canPickStepContinue(step, state, selection) ? selection[0].item : StepResultBreak; } diff --git a/src/git/gitProviderService.ts b/src/git/gitProviderService.ts index efbbb48032418..6015a71e8a0e1 100644 --- a/src/git/gitProviderService.ts +++ b/src/git/gitProviderService.ts @@ -1343,17 +1343,15 @@ export class GitProviderService implements Disposable { } @log() - rebase(repoPath: string | Uri, ref: string, configs: { sequenceEditor?: string }, args: string[] | undefined = []) { + rebase( + repoPath: string | Uri, + ref: string, + configs: { sequenceEditor?: string }, + options: { interactive?: boolean } = {}, + ): Promise { const { provider, path } = this.getProvider(repoPath); if (provider.rebase == null) throw new ProviderNotSupportedError(provider.descriptor.name); - const options: { interactive?: boolean } = {}; - for (const arg of args) { - if (arg === '--interactive') { - options.interactive = true; - } - } - return provider.rebase(path, ref, configs, options); } From 9cb5bc8606904493bdf71474a4d1c7667ba8e61c Mon Sep 17 00:00:00 2001 From: Sergio Date: Wed, 11 Dec 2024 16:42:25 +0100 Subject: [PATCH 3/3] add support to git rebase steps --- src/commands/git/rebase.ts | 51 ++++++++++++++++++++++------ src/env/node/git/git.ts | 42 ++++++++++++++++++++++- src/env/node/git/localGitProvider.ts | 41 ++++++++++++++++++---- src/git/actions/repository.ts | 2 +- src/git/errors.ts | 6 ++++ src/git/gitProvider.ts | 14 ++++++-- src/git/gitProviderService.ts | 10 +++--- 7 files changed, 141 insertions(+), 25 deletions(-) diff --git a/src/commands/git/rebase.ts b/src/commands/git/rebase.ts index de4a6587ae6ed..45f2acd0f7473 100644 --- a/src/commands/git/rebase.ts +++ b/src/commands/git/rebase.ts @@ -1,4 +1,5 @@ import type { Container } from '../../container'; +import type { RebaseOptions } from '../../git/gitProvider'; import type { GitBranch } from '../../git/models/branch'; import type { GitLog } from '../../git/models/log'; import type { GitReference } from '../../git/models/reference'; @@ -38,13 +39,9 @@ interface Context { title: string; } -type Flags = '--interactive'; -type RebaseOptions = { interactive?: boolean }; - interface State { repo: string | Repository; destination: GitReference; - flags: Flags[]; options: RebaseOptions; } @@ -90,7 +87,7 @@ export class RebaseGitCommand extends QuickCommand { } try { - await state.repo.git.rebase(state.destination.ref, configs, state.options); + await state.repo.git.rebase(null, state.destination.ref, configs, state.options); } catch (ex) { Logger.error(ex, this.title); void showGenericErrorMessage(ex); @@ -111,7 +108,9 @@ export class RebaseGitCommand extends QuickCommand { }; if (state.options == null) { - state.options = {}; + state.options = { + autostash: true, + }; } let skippedStepOne = false; @@ -214,7 +213,7 @@ export class RebaseGitCommand extends QuickCommand { const result = yield* this.confirmStep(state as RebaseStepState, context); if (result === StepResultBreak) continue; - state.options = Object.assign(state.options ?? {}, ...result); + state.options = Object.assign({ autostash: true }, ...result); endSteps(state); void this.execute(state as RebaseStepState); @@ -255,9 +254,40 @@ export class RebaseGitCommand extends QuickCommand { return StepResultBreak; } - const optionsArr: RebaseOptions[] = []; + try { + await state.repo.git.rebase(null, null, undefined, { checkActiveRebase: true }); + } catch { + const step: QuickPickStep> = this.createConfirmStep( + appendReposToTitle(title, state, context), + [ + createFlagsQuickPickItem([], [{ abort: true }], { + label: 'Abort Rebase', + description: '--abort', + detail: 'Will abort the current rebase', + }), + createFlagsQuickPickItem([], [{ continue: true }], { + label: 'Continue Rebase', + description: '--continue', + detail: 'Will continue the current rebase', + }), + createFlagsQuickPickItem([], [{ skip: true }], { + label: 'Skip Rebase', + description: '--skip', + detail: 'Will skip the current commit and continue the rebase', + }), + ], + createDirectiveQuickPickItem(Directive.Cancel, true, { + label: 'Do nothing. A rebase is already in progress', + detail: "If that is not the case, you can run `rm -rf '.git/rebase-merge'` and try again", + }), + ); + + const selection: StepSelection = yield step; + return canPickStepContinue(step, state, selection) ? selection[0].item : StepResultBreak; + } + const rebaseItems = [ - createFlagsQuickPickItem(optionsArr, [{ interactive: true }], { + createFlagsQuickPickItem([], [{ interactive: true }], { label: `Interactive ${this.title}`, description: '--interactive', detail: `Will interactively update ${getReferenceLabel(context.branch, { @@ -270,7 +300,7 @@ export class RebaseGitCommand extends QuickCommand { if (behind > 0) { rebaseItems.unshift( - createFlagsQuickPickItem(optionsArr, [{}], { + createFlagsQuickPickItem([], [{}], { label: this.title, detail: `Will update ${getReferenceLabel(context.branch, { label: false, @@ -286,7 +316,6 @@ export class RebaseGitCommand extends QuickCommand { rebaseItems, ); - state.options = Object.assign(state.options, ...optionsArr); const selection: StepSelection = yield step; return canPickStepContinue(step, state, selection) ? selection[0].item : StepResultBreak; } diff --git a/src/env/node/git/git.ts b/src/env/node/git/git.ts index b8f1981326c4e..7dd81d82da2f6 100644 --- a/src/env/node/git/git.ts +++ b/src/env/node/git/git.ts @@ -107,6 +107,8 @@ export const GitErrors = { tagNotFound: /tag .* not found/i, invalidTagName: /invalid tag name/i, remoteRejected: /rejected because the remote contains work/i, + unresolvedConflicts: /^error: could not apply .*\n^hint: Resolve all conflicts.*$/im, + rebaseMergeInProgress: /^fatal: It seems that there is already a rebase-merge directory/i, }; const GitWarnings = { @@ -178,6 +180,8 @@ const tagErrorAndReason: [RegExp, TagErrorReason][] = [ const rebaseErrorAndReason: [RegExp, RebaseErrorReason][] = [ [GitErrors.uncommittedChanges, RebaseErrorReason.WorkingChanges], [GitErrors.changesWouldBeOverwritten, RebaseErrorReason.OverwrittenChanges], + [GitErrors.unresolvedConflicts, RebaseErrorReason.UnresolvedConflicts], + [GitErrors.rebaseMergeInProgress, RebaseErrorReason.RebaseMergeInProgress], ]; export class Git { @@ -1101,7 +1105,7 @@ export class Git { async rebase(repoPath: string, args: string[] | undefined = [], configs: string[] | undefined = []): Promise { try { - void (await this.git({ cwd: repoPath }, ...configs, 'rebase', '--autostash', ...args)); + void (await this.git({ cwd: repoPath }, ...configs, 'rebase', ...args)); } catch (ex) { const msg: string = ex?.toString() ?? ''; for (const [regex, reason] of rebaseErrorAndReason) { @@ -1114,6 +1118,42 @@ export class Git { } } + async check_active_rebase(repoPath: string): Promise { + try { + const data = await this.git({ cwd: repoPath }, 'rev-parse', '--verify', 'REBASE_HEAD'); + return Boolean(data.length); + } catch { + return false; + } + } + + async check_active_cherry_pick(repoPath: string): Promise { + try { + const data = await this.git({ cwd: repoPath }, 'rev-parse', '--verify', 'CHERRY_PICK_HEAD'); + return Boolean(data.length); + } catch (_ex) { + return true; + } + } + + async check_active_merge(repoPath: string): Promise { + try { + const data = await this.git({ cwd: repoPath }, 'rev-parse', '--verify', 'MERGE_HEAD'); + return Boolean(data.length); + } catch (_ex) { + return true; + } + } + + async check_active_cherry_revert(repoPath: string): Promise { + try { + const data = await this.git({ cwd: repoPath }, 'rev-parse', '--verify', 'REVERT_HEAD'); + return Boolean(data.length); + } catch (_ex) { + return true; + } + } + for_each_ref__branch(repoPath: string, options: { all: boolean } = { all: false }) { const params = ['for-each-ref', `--format=${parseGitBranchesDefaultFormat}`, 'refs/heads']; if (options.all) { diff --git a/src/env/node/git/localGitProvider.ts b/src/env/node/git/localGitProvider.ts index 23c758691d0fc..49bd79468d9b9 100644 --- a/src/env/node/git/localGitProvider.ts +++ b/src/env/node/git/localGitProvider.ts @@ -53,6 +53,7 @@ import type { PagingOptions, PreviousComparisonUrisResult, PreviousLineComparisonUrisResult, + RebaseOptions, RepositoryCloseEvent, RepositoryInitWatcher, RepositoryOpenEvent, @@ -1662,22 +1663,50 @@ export class LocalGitProvider implements GitProvider, Disposable { @log() async rebase( repoPath: string, - ref: string, + upstream: string | null, + ref: string | null, configs?: { sequenceEditor?: string }, - options?: { interactive?: boolean } = {}, + options?: RebaseOptions = {}, ): Promise { const configFlags = []; const args = []; + if (options?.checkActiveRebase) { + if (await this.git.check_active_rebase(repoPath)) { + throw new RebaseError(RebaseErrorReason.RebaseMergeInProgress); + } + + return; + } + if (configs?.sequenceEditor != null) { configFlags.push('-c', `sequence.editor="${configs.sequenceEditor}"`); } - if (options?.interactive) { - args.push('--interactive'); - } + // These options can only be used on their own + if (options?.abort) { + args.push('--abort'); + } else if (options?.continue) { + args.push('--continue'); + } else if (options?.skip) { + args.push('--skip'); + } else { + if (options?.autostash) { + args.push('--autostash'); + } + + if (options?.interactive) { + args.push('--interactive'); + } - args.push(ref); + if (upstream) { + args.push(upstream); + } + + if (ref) { + args.push(ref); + } + } try { await this.git.rebase(repoPath, args, configFlags); diff --git a/src/git/actions/repository.ts b/src/git/actions/repository.ts index 6d07d4117d3ef..3eeb6de2dc5ef 100644 --- a/src/git/actions/repository.ts +++ b/src/git/actions/repository.ts @@ -34,7 +34,7 @@ export function push(repos?: string | string[] | Repository | Repository[], forc export function rebase(repo?: string | Repository, ref?: GitReference, interactive: boolean = true) { return executeGitCommand({ command: 'rebase', - state: { repo: repo, destination: ref, flags: interactive ? ['--interactive'] : [] }, + state: { repo: repo, destination: ref, options: { interactive: interactive, autostash: true } }, }); } diff --git a/src/git/errors.ts b/src/git/errors.ts index 196b0ea5f21b8..f5174ab302436 100644 --- a/src/git/errors.ts +++ b/src/git/errors.ts @@ -571,6 +571,8 @@ export class TagError extends Error { export const enum RebaseErrorReason { WorkingChanges, OverwrittenChanges, + UnresolvedConflicts, + RebaseMergeInProgress, Other, } @@ -592,6 +594,10 @@ export class RebaseError extends Error { return `${baseMessage} because there are uncommitted changes`; case RebaseErrorReason.OverwrittenChanges: return `${baseMessage} because some local changes would be overwritten`; + case RebaseErrorReason.UnresolvedConflicts: + return `${baseMessage} due to conflicts. Resolve the conflicts first and continue the rebase`; + case RebaseErrorReason.RebaseMergeInProgress: + return `${baseMessage} because a rebase is already in progress`; default: return baseMessage; } diff --git a/src/git/gitProvider.ts b/src/git/gitProvider.ts index 94d80e9783745..e9d5867e68fd4 100644 --- a/src/git/gitProvider.ts +++ b/src/git/gitProvider.ts @@ -117,6 +117,15 @@ export interface BranchContributorOverview { readonly contributors?: GitContributor[]; } +export type RebaseOptions = { + abort?: boolean; + autostash?: boolean; + checkActiveRebase?: boolean; + continue?: boolean; + interactive?: boolean; + skip?: boolean; +}; + export interface GitProviderRepository { createBranch?(repoPath: string, name: string, ref: string): Promise; renameBranch?(repoPath: string, oldName: string, newName: string): Promise; @@ -127,9 +136,10 @@ export interface GitProviderRepository { removeRemote?(repoPath: string, name: string): Promise; rebase?( repoPath: string, - ref: string, + upstream: string | null, + ref: string | null, configs?: { sequenceEditor?: string }, - options?: { interactive?: boolean }, + options?: RebaseOptions, ): Promise; applyUnreachableCommitForPatch?( diff --git a/src/git/gitProviderService.ts b/src/git/gitProviderService.ts index 6015a71e8a0e1..bcca72f2fd1fe 100644 --- a/src/git/gitProviderService.ts +++ b/src/git/gitProviderService.ts @@ -55,6 +55,7 @@ import type { PagingOptions, PreviousComparisonUrisResult, PreviousLineComparisonUrisResult, + RebaseOptions, RepositoryVisibility, RepositoryVisibilityInfo, ScmRepository, @@ -1345,14 +1346,15 @@ export class GitProviderService implements Disposable { @log() rebase( repoPath: string | Uri, - ref: string, - configs: { sequenceEditor?: string }, - options: { interactive?: boolean } = {}, + upstream: string | null, + ref: string | null, + configs?: { sequenceEditor?: string }, + options: RebaseOptions = {}, ): Promise { const { provider, path } = this.getProvider(repoPath); if (provider.rebase == null) throw new ProviderNotSupportedError(provider.descriptor.name); - return provider.rebase(path, ref, configs, options); + return provider.rebase(path, upstream, ref, configs, options); } @log()