Skip to content

Commit 24f6819

Browse files
committed
convert git rebase into git cmd
1 parent 00a56b2 commit 24f6819

File tree

7 files changed

+147
-13
lines changed

7 files changed

+147
-13
lines changed

src/commands/git/rebase.ts

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,12 @@ import type { GitLog } from '../../git/models/log';
44
import type { GitReference } from '../../git/models/reference';
55
import { createRevisionRange, getReferenceLabel, isRevisionReference } from '../../git/models/reference';
66
import type { Repository } from '../../git/models/repository';
7+
import { showGenericErrorMessage } from '../../messages';
78
import type { DirectiveQuickPickItem } from '../../quickpicks/items/directive';
89
import { createDirectiveQuickPickItem, Directive } from '../../quickpicks/items/directive';
910
import type { FlagsQuickPickItem } from '../../quickpicks/items/flags';
1011
import { createFlagsQuickPickItem } from '../../quickpicks/items/flags';
12+
import { Logger } from '../../system/logger';
1113
import { pluralize } from '../../system/string';
1214
import { getEditorCommand } from '../../system/vscode/utils';
1315
import type { ViewsWithRepositoryFolders } from '../../views/viewBase';
@@ -79,15 +81,18 @@ export class RebaseGitCommand extends QuickCommand<State> {
7981
}
8082

8183
async execute(state: RebaseStepState) {
82-
let configs: string[] | undefined;
84+
const configs: { sequenceEditor?: string } = {};
8385
if (state.flags.includes('--interactive')) {
8486
await this.container.rebaseEditor.enableForNextUse();
85-
86-
const editor = getEditorCommand();
87-
configs = ['-c', `"sequence.editor=${editor}"`];
87+
configs.sequenceEditor = getEditorCommand();
8888
}
8989

90-
state.repo.rebase(configs, ...state.flags, state.destination.ref);
90+
try {
91+
await state.repo.git.rebase(state.destination.ref, configs, state.flags);
92+
} catch (ex) {
93+
Logger.error(ex, this.title);
94+
void showGenericErrorMessage(ex);
95+
}
9196
}
9297

9398
protected async *steps(state: PartialStepState<State>): StepGenerator {

src/env/node/git/git.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ import {
2020
PullErrorReason,
2121
PushError,
2222
PushErrorReason,
23+
RebaseError,
24+
RebaseErrorReason,
2325
StashPushError,
2426
StashPushErrorReason,
2527
TagError,
@@ -173,6 +175,11 @@ const tagErrorAndReason: [RegExp, TagErrorReason][] = [
173175
[GitErrors.remoteRejected, TagErrorReason.RemoteRejected],
174176
];
175177

178+
const rebaseErrorAndReason: [RegExp, RebaseErrorReason][] = [
179+
[GitErrors.uncommittedChanges, RebaseErrorReason.WorkingChanges],
180+
[GitErrors.changesWouldBeOverwritten, RebaseErrorReason.OverwrittenChanges],
181+
];
182+
176183
export class Git {
177184
/** Map of running git commands -- avoids running duplicate overlaping commands */
178185
private readonly pendingCommands = new Map<string, Promise<string | Buffer>>();
@@ -1092,6 +1099,21 @@ export class Git {
10921099
}
10931100
}
10941101

1102+
async rebase(repoPath: string, args: string[] | undefined = [], configs: string[] | undefined = []): Promise<void> {
1103+
try {
1104+
void (await this.git<string>({ cwd: repoPath }, ...configs, 'rebase', '--autostash', ...args));
1105+
} catch (ex) {
1106+
const msg: string = ex?.toString() ?? '';
1107+
for (const [regex, reason] of rebaseErrorAndReason) {
1108+
if (regex.test(msg) || regex.test(ex.stderr ?? '')) {
1109+
throw new RebaseError(reason, ex);
1110+
}
1111+
}
1112+
1113+
throw new RebaseError(RebaseErrorReason.Other, ex);
1114+
}
1115+
}
1116+
10951117
for_each_ref__branch(repoPath: string, options: { all: boolean } = { all: false }) {
10961118
const params = ['for-each-ref', `--format=${parseGitBranchesDefaultFormat}`, 'refs/heads'];
10971119
if (options.all) {

src/env/node/git/localGitProvider.ts

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ import {
3131
PullError,
3232
PushError,
3333
PushErrorReason,
34+
RebaseError,
3435
StashApplyError,
3536
StashApplyErrorReason,
3637
StashPushError,
@@ -1658,6 +1659,37 @@ export class LocalGitProvider implements GitProvider, Disposable {
16581659
}
16591660
}
16601661

1662+
@log()
1663+
async rebase(
1664+
repoPath: string,
1665+
ref: string,
1666+
configs?: { sequenceEditor?: string },
1667+
options?: { interactive?: boolean } = {},
1668+
): Promise<void> {
1669+
const configFlags = [];
1670+
const args = [];
1671+
1672+
if (configs?.sequenceEditor != null) {
1673+
configFlags.push('-c', `sequence.editor="${configs.sequenceEditor}"`);
1674+
}
1675+
1676+
if (options?.interactive) {
1677+
args.push('--interactive');
1678+
}
1679+
1680+
args.push(ref);
1681+
1682+
try {
1683+
await this.git.rebase(repoPath, args, configFlags);
1684+
} catch (ex) {
1685+
if (RebaseError.is(ex)) {
1686+
throw ex.WithRef(ref);
1687+
}
1688+
1689+
throw ex;
1690+
}
1691+
}
1692+
16611693
private readonly toCanonicalMap = new Map<string, Uri>();
16621694
private readonly fromCanonicalMap = new Map<string, Uri>();
16631695
protected readonly unsafePaths = new Set<string>();

src/git/errors.ts

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -567,3 +567,65 @@ export class TagError extends Error {
567567
return this;
568568
}
569569
}
570+
571+
export const enum RebaseErrorReason {
572+
WorkingChanges,
573+
OverwrittenChanges,
574+
Other,
575+
}
576+
577+
export class RebaseError extends Error {
578+
static is(ex: unknown, reason?: RebaseErrorReason): ex is RebaseError {
579+
return ex instanceof RebaseError && (reason == null || ex.reason === reason);
580+
}
581+
582+
private static buildRebaseErrorMessage(reason?: RebaseErrorReason, ref?: string): string {
583+
let baseMessage: string;
584+
if (ref != null) {
585+
baseMessage = `Unable to rebase onto ${ref}`;
586+
} else {
587+
baseMessage = `Unable to rebase`;
588+
}
589+
590+
switch (reason) {
591+
case RebaseErrorReason.WorkingChanges:
592+
return `${baseMessage} because there are uncommitted changes`;
593+
case RebaseErrorReason.OverwrittenChanges:
594+
return `${baseMessage} because some local changes would be overwritten`;
595+
default:
596+
return baseMessage;
597+
}
598+
}
599+
600+
readonly original?: Error;
601+
readonly reason: RebaseErrorReason | undefined;
602+
ref?: string;
603+
604+
constructor(reason?: RebaseErrorReason, original?: Error);
605+
constructor(message?: string, original?: Error);
606+
constructor(messageOrReason: string | RebaseErrorReason | undefined, original?: Error, ref?: string) {
607+
let reason: RebaseErrorReason | undefined;
608+
if (typeof messageOrReason !== 'string') {
609+
reason = messageOrReason as RebaseErrorReason;
610+
} else {
611+
super(messageOrReason);
612+
}
613+
614+
const message =
615+
typeof messageOrReason === 'string'
616+
? messageOrReason
617+
: RebaseError.buildRebaseErrorMessage(messageOrReason as RebaseErrorReason, ref);
618+
super(message);
619+
620+
this.original = original;
621+
this.reason = reason;
622+
this.ref = ref;
623+
Error.captureStackTrace?.(this, RebaseError);
624+
}
625+
626+
WithRef(ref: string) {
627+
this.ref = ref;
628+
this.message = RebaseError.buildRebaseErrorMessage(this.reason, ref);
629+
return this;
630+
}
631+
}

src/git/gitProvider.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,12 @@ export interface GitProviderRepository {
125125
addRemote?(repoPath: string, name: string, url: string, options?: { fetch?: boolean }): Promise<void>;
126126
pruneRemote?(repoPath: string, name: string): Promise<void>;
127127
removeRemote?(repoPath: string, name: string): Promise<void>;
128+
rebase?(
129+
repoPath: string,
130+
ref: string,
131+
configs?: { sequenceEditor?: string },
132+
options?: { interactive?: boolean },
133+
): Promise<void>;
128134

129135
applyUnreachableCommitForPatch?(
130136
repoPath: string,

src/git/gitProviderService.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1342,6 +1342,21 @@ export class GitProviderService implements Disposable {
13421342
return provider.applyChangesToWorkingFile(uri, ref1, ref2);
13431343
}
13441344

1345+
@log()
1346+
rebase(repoPath: string | Uri, ref: string, configs: { sequenceEditor?: string }, args: string[] | undefined = []) {
1347+
const { provider, path } = this.getProvider(repoPath);
1348+
if (provider.rebase == null) throw new ProviderNotSupportedError(provider.descriptor.name);
1349+
1350+
const options: { interactive?: boolean } = {};
1351+
for (const arg of args) {
1352+
if (arg === '--interactive') {
1353+
options.interactive = true;
1354+
}
1355+
}
1356+
1357+
return provider.rebase(path, ref, configs, options);
1358+
}
1359+
13451360
@log()
13461361
async applyUnreachableCommitForPatch(
13471362
repoPath: string | Uri,

src/git/models/repository.ts

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -842,14 +842,6 @@ export class Repository implements Disposable {
842842
}
843843
}
844844

845-
@log()
846-
rebase(configs: string[] | undefined, ...args: string[]) {
847-
void this.runTerminalCommand(
848-
configs != null && configs.length !== 0 ? `${configs.join(' ')} rebase` : 'rebase',
849-
...args,
850-
);
851-
}
852-
853845
@log()
854846
reset(...args: string[]) {
855847
void this.runTerminalCommand('reset', ...args);

0 commit comments

Comments
 (0)