Skip to content

Commit 612d988

Browse files
committed
convert git merge into git cmd
1 parent 92b0605 commit 612d988

File tree

8 files changed

+157
-10
lines changed

8 files changed

+157
-10
lines changed

src/commands/git/merge.ts

Lines changed: 10 additions & 3 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 type { ViewsWithRepositoryFolders } from '../../views/viewBase';
1315
import type {
@@ -76,8 +78,13 @@ export class MergeGitCommand extends QuickCommand<State> {
7678
return false;
7779
}
7880

79-
execute(state: MergeStepState) {
80-
state.repo.merge(...state.flags, state.reference.ref);
81+
async execute(state: MergeStepState) {
82+
try {
83+
await state.repo.git.merge(state.reference.ref, state.flags);
84+
} catch (ex) {
85+
Logger.error(ex, this.title);
86+
void showGenericErrorMessage(ex);
87+
}
8188
}
8289

8390
protected async *steps(state: PartialStepState<State>): StepGenerator {
@@ -200,7 +207,7 @@ export class MergeGitCommand extends QuickCommand<State> {
200207
state.flags = result;
201208

202209
endSteps(state);
203-
this.execute(state as MergeStepState);
210+
await this.execute(state as MergeStepState);
204211
}
205212

206213
return state.counter < 0 ? StepResultBreak : undefined;

src/commands/git/switch.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@ export class SwitchGitCommand extends QuickCommand<State> {
104104
);
105105

106106
if (state.fastForwardTo != null) {
107-
state.repos[0].merge('--ff-only', state.fastForwardTo.ref);
107+
await state.repos[0].git.merge(state.fastForwardTo.ref, ['--ff-only']);
108108
}
109109
}
110110

@@ -211,7 +211,7 @@ export class SwitchGitCommand extends QuickCommand<State> {
211211
);
212212
if (worktree != null && !worktree.isDefault) {
213213
if (state.fastForwardTo != null) {
214-
state.repos[0].merge('--ff-only', state.fastForwardTo.ref);
214+
await state.repos[0].git.merge(state.fastForwardTo.ref, ['--ff-only']);
215215
}
216216

217217
const worktreeResult = yield* getSteps(

src/env/node/git/git.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,12 @@ const tagErrorAndReason: [RegExp, TagErrorReason][] = [
173173
[GitErrors.remoteRejected, TagErrorReason.RemoteRejected],
174174
];
175175

176+
const mergeErrorAndReason: [RegExp, MergeErrorReason][] = [
177+
[GitErrors.conflict, MergeErrorReason.Conflict],
178+
[GitErrors.unmergedFiles, MergeErrorReason.UnmergedFiles],
179+
[GitErrors.unstagedChanges, MergeErrorReason.UnstagedChanges],
180+
];
181+
176182
export class Git {
177183
/** Map of running git commands -- avoids running duplicate overlaping commands */
178184
private readonly pendingCommands = new Map<string, Promise<string | Buffer>>();
@@ -1092,6 +1098,21 @@ export class Git {
10921098
}
10931099
}
10941100

1101+
async merge(repoPath: string, args: string[]) {
1102+
try {
1103+
await this.git<string>({ cwd: repoPath }, 'merge', ...args);
1104+
} catch (ex) {
1105+
const msg: string = ex?.toString() ?? '';
1106+
for (const [error, reason] of mergeErrorAndReason) {
1107+
if (error.test(msg) || error.test(ex.stderr ?? '')) {
1108+
throw new MergeError(reason, ex);
1109+
}
1110+
}
1111+
1112+
throw new MergeError(MergeErrorReason.Other, ex);
1113+
}
1114+
}
1115+
10951116
for_each_ref__branch(repoPath: string, options: { all: boolean } = { all: false }) {
10961117
const params = ['for-each-ref', `--format=${parseGitBranchesDefaultFormat}`, 'refs/heads'];
10971118
if (options.all) {

src/env/node/git/localGitProvider.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1076,6 +1076,32 @@ export class LocalGitProvider implements GitProvider, Disposable {
10761076
this.container.events.fire('git:cache:reset', { repoPath: repoPath, caches: ['remotes'] });
10771077
}
10781078

1079+
@log()
1080+
async merge(
1081+
repoPath: string,
1082+
ref: string,
1083+
options?: { fastForwardOnly?: boolean; noFastForward?: boolean; noCommit?: boolean; squash?: boolean },
1084+
): Promise<void> {
1085+
const args: string[] = [];
1086+
if (options?.fastForwardOnly) {
1087+
args.push('--ff-only');
1088+
} else if (options?.noFastForward) {
1089+
args.push('--no-ff');
1090+
}
1091+
1092+
if (options?.noCommit) {
1093+
args.push('--no-commit');
1094+
}
1095+
1096+
if (options?.squash) {
1097+
args.push('--squash');
1098+
}
1099+
1100+
args.push(ref);
1101+
1102+
await this.git.merge(repoPath, args);
1103+
}
1104+
10791105
@log()
10801106
async applyChangesToWorkingFile(uri: GitUri, ref1?: string, ref2?: string) {
10811107
const scope = getLogScope();

src/git/errors.ts

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -567,3 +567,70 @@ export class TagError extends Error {
567567
return this;
568568
}
569569
}
570+
571+
export const enum MergeErrorReason {
572+
Conflict,
573+
UnmergedFiles,
574+
UnstagedChanges,
575+
Other,
576+
}
577+
578+
export class MergeError extends Error {
579+
static is(ex: unknown, reason?: MergeErrorReason): ex is MergeError {
580+
return ex instanceof MergeError && (reason == null || ex.reason === reason);
581+
}
582+
583+
readonly original?: Error;
584+
readonly reason: MergeErrorReason | undefined;
585+
ref?: string;
586+
587+
private static buildMergeErrorMessage(reason?: MergeErrorReason, ref?: string): string {
588+
let baseMessage: string;
589+
if (ref != null) {
590+
baseMessage = `Unable to merge ${ref}`;
591+
} else {
592+
baseMessage = `Unable to merge`;
593+
}
594+
595+
switch (reason) {
596+
case MergeErrorReason.Conflict:
597+
return `${baseMessage} due to conflicts`;
598+
case MergeErrorReason.UnmergedFiles:
599+
return `${baseMessage} because you have unmerged files`;
600+
case MergeErrorReason.UnstagedChanges:
601+
return `${baseMessage} because you have unstaged changes`;
602+
default:
603+
return baseMessage;
604+
}
605+
606+
return baseMessage;
607+
}
608+
609+
constructor(reason?: MergeErrorReason, original?: Error, ref?: string);
610+
constructor(message?: string, original?: Error);
611+
constructor(messageOrReason: string | MergeErrorReason | undefined, original?: Error, ref?: string) {
612+
let reason: MergeErrorReason | undefined;
613+
if (typeof messageOrReason !== 'string') {
614+
reason = messageOrReason as MergeErrorReason;
615+
} else {
616+
super(messageOrReason);
617+
}
618+
619+
const message =
620+
typeof messageOrReason === 'string'
621+
? messageOrReason
622+
: MergeError.buildMergeErrorMessage(messageOrReason as MergeErrorReason, ref);
623+
super(message);
624+
625+
this.original = original;
626+
this.reason = reason;
627+
this.ref = ref;
628+
Error.captureStackTrace?.(this, MergeError);
629+
}
630+
631+
WithRef(ref: string) {
632+
this.ref = ref;
633+
this.message = MergeError.buildMergeErrorMessage(this.reason, ref);
634+
return this;
635+
}
636+
}

src/git/gitProvider.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,11 @@ 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+
merge?(
129+
repoPath: string,
130+
ref: string,
131+
options?: { fastForwardOnly?: boolean; noFastForward?: boolean; noCommit?: boolean; squash?: boolean },
132+
): Promise<void>;
128133

129134
applyUnreachableCommitForPatch?(
130135
repoPath: string,

src/git/gitProviderService.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1334,6 +1334,32 @@ export class GitProviderService implements Disposable {
13341334
return provider.removeRemote(path, name);
13351335
}
13361336

1337+
@log()
1338+
merge(repoPath: string, ref: string, flags: string[] | undefined = []): Promise<void> {
1339+
const { provider, path } = this.getProvider(repoPath);
1340+
if (provider.merge == null) throw new ProviderNotSupportedError(provider.descriptor.name);
1341+
const options: { fastForwardOnly?: boolean; noFastForward?: boolean; noCommit?: boolean; squash?: boolean } =
1342+
{};
1343+
for (const flag of flags) {
1344+
switch (flag) {
1345+
case '--ff-only':
1346+
options.fastForwardOnly = true;
1347+
break;
1348+
case '--no-ff':
1349+
options.noFastForward = true;
1350+
break;
1351+
case '--squash':
1352+
options.squash = true;
1353+
break;
1354+
case '--no-commit':
1355+
options.noCommit = true;
1356+
break;
1357+
}
1358+
}
1359+
1360+
return provider.merge(path, ref, options);
1361+
}
1362+
13371363
@log()
13381364
applyChangesToWorkingFile(uri: GitUri, ref1?: string, ref2?: string): Promise<void> {
13391365
const { provider } = this.getProvider(uri);

src/git/models/repository.ts

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -733,11 +733,6 @@ export class Repository implements Disposable {
733733
return this.git.getWorktree(w => w.uri.toString() === url);
734734
}
735735

736-
@log()
737-
merge(...args: string[]) {
738-
void this.runTerminalCommand('merge', ...args);
739-
}
740-
741736
@gate()
742737
@log()
743738
async pull(options?: { progress?: boolean; rebase?: boolean }) {

0 commit comments

Comments
 (0)