Skip to content

Commit

Permalink
convert cherry pick into git cmd
Browse files Browse the repository at this point in the history
  • Loading branch information
sergiolms committed Nov 29, 2024
1 parent 92b0605 commit ef85106
Show file tree
Hide file tree
Showing 7 changed files with 106 additions and 45 deletions.
15 changes: 12 additions & 3 deletions src/commands/git/cherry-pick.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,10 @@ 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 { FlagsQuickPickItem } from '../../quickpicks/items/flags';
import { createFlagsQuickPickItem } from '../../quickpicks/items/flags';
import { Logger } from '../../system/logger';
import type { ViewsWithRepositoryFolders } from '../../views/viewBase';
import type {
PartialStepState,
Expand Down Expand Up @@ -80,8 +82,15 @@ export class CherryPickGitCommand extends QuickCommand<State> {
return false;
}

execute(state: CherryPickStepState<State<GitReference[]>>) {
state.repo.cherryPick(...state.flags, ...state.references.map(c => c.ref).reverse());
async execute(state: CherryPickStepState<State<GitReference[]>>) {
for (const ref of state.references.map(c => c.ref).reverse()) {
try {
await state.repo.git.cherryPick(ref, state.flags);
} catch (ex) {
Logger.error(ex, this.title);
void showGenericErrorMessage(ex.message);
}
}
}

override isFuzzyMatch(name: string) {
Expand Down Expand Up @@ -225,7 +234,7 @@ export class CherryPickGitCommand extends QuickCommand<State> {
}

endSteps(state);
this.execute(state as CherryPickStepState<State<GitReference[]>>);
await this.execute(state as CherryPickStepState<State<GitReference[]>>);
}

return state.counter < 0 ? StepResultBreak : undefined;
Expand Down
32 changes: 15 additions & 17 deletions src/env/node/git/git.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,8 @@ export const GitErrors = {
changesWouldBeOverwritten: /Your local changes to the following files would be overwritten/i,
commitChangesFirst: /Please, commit your changes before you can/i,
conflict: /^CONFLICT \([^)]+\): \b/m,
cherryPickUnmerged:
/error: Cherry-picking.*unmerged files\.\nhint:.*\nhint:.*make a commit\.\nfatal: cherry-pick failed/i,
failedToDeleteDirectoryNotEmpty: /failed to delete '(.*?)': Directory not empty/i,
invalidObjectName: /invalid object name: (.*)\s/i,
invalidObjectNameList: /could not open object name list: (.*)\s/i,
Expand Down Expand Up @@ -165,6 +167,12 @@ function getStdinUniqueKey(): number {
type ExitCodeOnlyGitCommandOptions = GitCommandOptions & { exitCodeOnly: true };
export type PushForceOptions = { withLease: true; ifIncludes?: boolean } | { withLease: false; ifIncludes?: never };

const cherryPickErrorAndReason = [
[GitErrors.changesWouldBeOverwritten, CherryPickErrorReason.AbortedWouldOverwrite],
[GitErrors.conflict, CherryPickErrorReason.Conflicts],
[GitErrors.cherryPickUnmerged, CherryPickErrorReason.Conflicts],
];

const tagErrorAndReason: [RegExp, TagErrorReason][] = [
[GitErrors.tagAlreadyExists, TagErrorReason.TagAlreadyExists],
[GitErrors.tagNotFound, TagErrorReason.TagNotFound],
Expand Down Expand Up @@ -617,28 +625,18 @@ export class Git {
return this.git<string>({ cwd: repoPath }, ...params);
}

async cherrypick(repoPath: string, sha: string, options: { noCommit?: boolean; errors?: GitErrorHandling } = {}) {
const params = ['cherry-pick'];
if (options?.noCommit) {
params.push('-n');
}
params.push(sha);

async cherryPick(repoPath: string, args: string[]) {
try {
await this.git<string>({ cwd: repoPath, errors: options?.errors }, ...params);
await this.git<string>({ cwd: repoPath }, 'cherry-pick', ...args);
} catch (ex) {
const msg: string = ex?.toString() ?? '';
let reason: CherryPickErrorReason = CherryPickErrorReason.Other;
if (
GitErrors.changesWouldBeOverwritten.test(msg) ||
GitErrors.changesWouldBeOverwritten.test(ex.stderr ?? '')
) {
reason = CherryPickErrorReason.AbortedWouldOverwrite;
} else if (GitErrors.conflict.test(msg) || GitErrors.conflict.test(ex.stdout ?? '')) {
reason = CherryPickErrorReason.Conflicts;
for (const [error, reason] of cherryPickErrorAndReason) {
if (error.test(msg) || error.test(ex.stderr ?? '')) {
throw new CherryPickError(reason, ex);
}
}

throw new CherryPickError(reason, ex, sha);
throw new CherryPickError(CherryPickErrorReason.Other, ex);
}
}

Expand Down
26 changes: 25 additions & 1 deletion src/env/node/git/localGitProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1076,6 +1076,30 @@ export class LocalGitProvider implements GitProvider, Disposable {
this.container.events.fire('git:cache:reset', { repoPath: repoPath, caches: ['remotes'] });
}

@log()
async cherryPick(repoPath: string, ref: string, options: { noCommit?: boolean; edit?: boolean }): Promise<void> {
const args: string[] = [];
if (options?.noCommit) {
args.push('-n');
}

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

args.push(ref);

try {
await this.git.cherryPick(repoPath, args);
} catch (ex) {
if (ex instanceof CherryPickError) {
throw ex.WithRef(ref);
}

throw ex;
}
}

@log()
async applyChangesToWorkingFile(uri: GitUri, ref1?: string, ref2?: string) {
const scope = getLogScope();
Expand Down Expand Up @@ -1225,7 +1249,7 @@ export class LocalGitProvider implements GitProvider, Disposable {

// Apply the patch using a cherry pick without committing
try {
await this.git.cherrypick(targetPath, ref, { noCommit: true, errors: GitErrorHandling.Throw });
await this.git.cherryPick(targetPath, ref, { noCommit: true, errors: GitErrorHandling.Throw });
} catch (ex) {
Logger.error(ex, scope);
if (ex instanceof CherryPickError) {
Expand Down
50 changes: 31 additions & 19 deletions src/git/errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -364,37 +364,49 @@ export class CherryPickError extends Error {

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

private static buildCherryPickErrorMessage(reason: CherryPickErrorReason | undefined, ref?: string) {
let baseMessage = `Unable to cherry-pick${ref ? ` commit '${ref}'` : ''}`;
switch (reason) {
case CherryPickErrorReason.AbortedWouldOverwrite:
baseMessage += ' as some local changes would be overwritten';
break;
case CherryPickErrorReason.Conflicts:
baseMessage += ' due to conflicts';
break;
}
return baseMessage;
}

constructor(reason?: CherryPickErrorReason, original?: Error, sha?: string);
constructor(message?: string, original?: Error);
constructor(messageOrReason: string | CherryPickErrorReason | undefined, original?: Error, sha?: string) {
let message;
const baseMessage = `Unable to cherry-pick${sha ? ` commit '${sha}'` : ''}`;
constructor(messageOrReason: string | CherryPickErrorReason | undefined, original?: Error, ref?: string) {
let reason: CherryPickErrorReason | undefined;
if (messageOrReason == null) {
message = baseMessage;
} else if (typeof messageOrReason === 'string') {
message = messageOrReason;
reason = undefined;
if (typeof messageOrReason !== 'string') {
reason = messageOrReason as CherryPickErrorReason;
} else {
reason = messageOrReason;
switch (reason) {
case CherryPickErrorReason.AbortedWouldOverwrite:
message = `${baseMessage} as some local changes would be overwritten.`;
break;
case CherryPickErrorReason.Conflicts:
message = `${baseMessage} due to conflicts.`;
break;
default:
message = baseMessage;
}
super(messageOrReason);
}

const message =
typeof messageOrReason === 'string'
? messageOrReason
: CherryPickError.buildCherryPickErrorMessage(messageOrReason as CherryPickErrorReason, ref);
super(message);

this.original = original;
this.reason = reason;
this.ref = ref;

Error.captureStackTrace?.(this, CherryPickError);
}

WithRef(ref: string) {
this.ref = ref;
this.message = CherryPickError.buildCherryPickErrorMessage(this.reason, ref);
return this;
}
}

export class WorkspaceUntrustedError extends Error {
Expand Down
2 changes: 2 additions & 0 deletions src/git/gitProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,8 @@ export interface GitProviderRepository {
pruneRemote?(repoPath: string, name: string): Promise<void>;
removeRemote?(repoPath: string, name: string): Promise<void>;

cherryPick?(repoPath: string, ref: string, options: { noCommit?: boolean; edit?: boolean }): Promise<void>;

applyUnreachableCommitForPatch?(
repoPath: string,
ref: string,
Expand Down
21 changes: 21 additions & 0 deletions src/git/gitProviderService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ import { registerCommand } from '../system/vscode/command';
import { configuration } from '../system/vscode/configuration';
import { setContext } from '../system/vscode/context';
import { getBestPath } from '../system/vscode/path';
import type { GitErrorHandling } from './commandOptions';
import type {
BranchContributorOverview,
GitCaches,
Expand Down Expand Up @@ -1334,6 +1335,26 @@ export class GitProviderService implements Disposable {
return provider.removeRemote(path, name);
}

@log()
cherryPick(repoPath: string | Uri, ref: string, flags: string[] | undefined = []): Promise<void> {
const { provider, path } = this.getProvider(repoPath);
if (provider.cherryPick == null) throw new ProviderNotSupportedError(provider.descriptor.name);

const options: { noCommit?: boolean; edit?: boolean; errors?: GitErrorHandling } = {};
for (const flag of flags) {
switch (flag) {
case '--no-commit':
options.noCommit = true;
break;
case '--edit':
options.edit = true;
break;
}
}

return provider.cherryPick(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 @@ -633,11 +633,6 @@ export class Repository implements Disposable {
}
}

@log()
cherryPick(...args: string[]) {
void this.runTerminalCommand('cherry-pick', ...args);
}

containsUri(uri: Uri) {
return this === this.container.git.getRepository(uri);
}
Expand Down

0 comments on commit ef85106

Please sign in to comment.