Skip to content

Commit

Permalink
convert git merge into git cmd
Browse files Browse the repository at this point in the history
  • Loading branch information
sergiolms committed Dec 2, 2024
1 parent 92b0605 commit 612d988
Show file tree
Hide file tree
Showing 8 changed files with 157 additions and 10 deletions.
13 changes: 10 additions & 3 deletions src/commands/git/merge.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 type { ViewsWithRepositoryFolders } from '../../views/viewBase';
import type {
Expand Down Expand Up @@ -76,8 +78,13 @@ export class MergeGitCommand extends QuickCommand<State> {
return false;
}

execute(state: MergeStepState) {
state.repo.merge(...state.flags, state.reference.ref);
async execute(state: MergeStepState) {
try {
await state.repo.git.merge(state.reference.ref, state.flags);
} catch (ex) {
Logger.error(ex, this.title);
void showGenericErrorMessage(ex);
}
}

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

endSteps(state);
this.execute(state as MergeStepState);
await this.execute(state as MergeStepState);
}

return state.counter < 0 ? StepResultBreak : undefined;
Expand Down
4 changes: 2 additions & 2 deletions src/commands/git/switch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ export class SwitchGitCommand extends QuickCommand<State> {
);

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

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

const worktreeResult = yield* getSteps(
Expand Down
21 changes: 21 additions & 0 deletions src/env/node/git/git.ts
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,12 @@ const tagErrorAndReason: [RegExp, TagErrorReason][] = [
[GitErrors.remoteRejected, TagErrorReason.RemoteRejected],
];

const mergeErrorAndReason: [RegExp, MergeErrorReason][] = [
[GitErrors.conflict, MergeErrorReason.Conflict],
[GitErrors.unmergedFiles, MergeErrorReason.UnmergedFiles],
[GitErrors.unstagedChanges, MergeErrorReason.UnstagedChanges],
];

export class Git {
/** Map of running git commands -- avoids running duplicate overlaping commands */
private readonly pendingCommands = new Map<string, Promise<string | Buffer>>();
Expand Down Expand Up @@ -1092,6 +1098,21 @@ export class Git {
}
}

async merge(repoPath: string, args: string[]) {
try {
await this.git<string>({ cwd: repoPath }, 'merge', ...args);
} catch (ex) {
const msg: string = ex?.toString() ?? '';
for (const [error, reason] of mergeErrorAndReason) {
if (error.test(msg) || error.test(ex.stderr ?? '')) {
throw new MergeError(reason, ex);
}
}

throw new MergeError(MergeErrorReason.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) {
Expand Down
26 changes: 26 additions & 0 deletions src/env/node/git/localGitProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1076,6 +1076,32 @@ export class LocalGitProvider implements GitProvider, Disposable {
this.container.events.fire('git:cache:reset', { repoPath: repoPath, caches: ['remotes'] });
}

@log()
async merge(
repoPath: string,
ref: string,
options?: { fastForwardOnly?: boolean; noFastForward?: boolean; noCommit?: boolean; squash?: boolean },
): Promise<void> {
const args: string[] = [];
if (options?.fastForwardOnly) {
args.push('--ff-only');
} else if (options?.noFastForward) {
args.push('--no-ff');
}

if (options?.noCommit) {
args.push('--no-commit');
}

if (options?.squash) {
args.push('--squash');
}

args.push(ref);

await this.git.merge(repoPath, args);
}

@log()
async applyChangesToWorkingFile(uri: GitUri, ref1?: string, ref2?: string) {
const scope = getLogScope();
Expand Down
67 changes: 67 additions & 0 deletions src/git/errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -567,3 +567,70 @@ export class TagError extends Error {
return this;
}
}

export const enum MergeErrorReason {
Conflict,
UnmergedFiles,
UnstagedChanges,
Other,
}

export class MergeError extends Error {
static is(ex: unknown, reason?: MergeErrorReason): ex is MergeError {
return ex instanceof MergeError && (reason == null || ex.reason === reason);
}

readonly original?: Error;
readonly reason: MergeErrorReason | undefined;
ref?: string;

private static buildMergeErrorMessage(reason?: MergeErrorReason, ref?: string): string {
let baseMessage: string;
if (ref != null) {
baseMessage = `Unable to merge ${ref}`;
} else {
baseMessage = `Unable to merge`;
}

switch (reason) {
case MergeErrorReason.Conflict:
return `${baseMessage} due to conflicts`;
case MergeErrorReason.UnmergedFiles:
return `${baseMessage} because you have unmerged files`;
case MergeErrorReason.UnstagedChanges:
return `${baseMessage} because you have unstaged changes`;
default:
return baseMessage;
}

return baseMessage;
}

constructor(reason?: MergeErrorReason, original?: Error, ref?: string);
constructor(message?: string, original?: Error);
constructor(messageOrReason: string | MergeErrorReason | undefined, original?: Error, ref?: string) {
let reason: MergeErrorReason | undefined;
if (typeof messageOrReason !== 'string') {
reason = messageOrReason as MergeErrorReason;
} else {
super(messageOrReason);
}

const message =
typeof messageOrReason === 'string'
? messageOrReason
: MergeError.buildMergeErrorMessage(messageOrReason as MergeErrorReason, ref);
super(message);

this.original = original;
this.reason = reason;
this.ref = ref;
Error.captureStackTrace?.(this, MergeError);
}

WithRef(ref: string) {
this.ref = ref;
this.message = MergeError.buildMergeErrorMessage(this.reason, ref);
return this;
}
}
5 changes: 5 additions & 0 deletions src/git/gitProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,11 @@ export interface GitProviderRepository {
addRemote?(repoPath: string, name: string, url: string, options?: { fetch?: boolean }): Promise<void>;
pruneRemote?(repoPath: string, name: string): Promise<void>;
removeRemote?(repoPath: string, name: string): Promise<void>;
merge?(
repoPath: string,
ref: string,
options?: { fastForwardOnly?: boolean; noFastForward?: boolean; noCommit?: boolean; squash?: boolean },
): Promise<void>;

applyUnreachableCommitForPatch?(
repoPath: string,
Expand Down
26 changes: 26 additions & 0 deletions src/git/gitProviderService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1334,6 +1334,32 @@ export class GitProviderService implements Disposable {
return provider.removeRemote(path, name);
}

@log()
merge(repoPath: string, ref: string, flags: string[] | undefined = []): Promise<void> {
const { provider, path } = this.getProvider(repoPath);
if (provider.merge == null) throw new ProviderNotSupportedError(provider.descriptor.name);
const options: { fastForwardOnly?: boolean; noFastForward?: boolean; noCommit?: boolean; squash?: boolean } =
{};
for (const flag of flags) {
switch (flag) {
case '--ff-only':
options.fastForwardOnly = true;
break;
case '--no-ff':
options.noFastForward = true;
break;
case '--squash':
options.squash = true;
break;
case '--no-commit':
options.noCommit = true;
break;
}
}

return provider.merge(path, ref, options);
}

@log()
applyChangesToWorkingFile(uri: GitUri, ref1?: string, ref2?: string): Promise<void> {
const { provider } = this.getProvider(uri);
Expand Down
5 changes: 0 additions & 5 deletions src/git/models/repository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -733,11 +733,6 @@ export class Repository implements Disposable {
return this.git.getWorktree(w => w.uri.toString() === url);
}

@log()
merge(...args: string[]) {
void this.runTerminalCommand('merge', ...args);
}

@gate()
@log()
async pull(options?: { progress?: boolean; rebase?: boolean }) {
Expand Down

0 comments on commit 612d988

Please sign in to comment.