Skip to content

Commit ef85106

Browse files
committed
convert cherry pick into git cmd
1 parent 92b0605 commit ef85106

File tree

7 files changed

+106
-45
lines changed

7 files changed

+106
-45
lines changed

src/commands/git/cherry-pick.ts

+12-3
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,10 @@ 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 { FlagsQuickPickItem } from '../../quickpicks/items/flags';
89
import { createFlagsQuickPickItem } from '../../quickpicks/items/flags';
10+
import { Logger } from '../../system/logger';
911
import type { ViewsWithRepositoryFolders } from '../../views/viewBase';
1012
import type {
1113
PartialStepState,
@@ -80,8 +82,15 @@ export class CherryPickGitCommand extends QuickCommand<State> {
8082
return false;
8183
}
8284

83-
execute(state: CherryPickStepState<State<GitReference[]>>) {
84-
state.repo.cherryPick(...state.flags, ...state.references.map(c => c.ref).reverse());
85+
async execute(state: CherryPickStepState<State<GitReference[]>>) {
86+
for (const ref of state.references.map(c => c.ref).reverse()) {
87+
try {
88+
await state.repo.git.cherryPick(ref, state.flags);
89+
} catch (ex) {
90+
Logger.error(ex, this.title);
91+
void showGenericErrorMessage(ex.message);
92+
}
93+
}
8594
}
8695

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

227236
endSteps(state);
228-
this.execute(state as CherryPickStepState<State<GitReference[]>>);
237+
await this.execute(state as CherryPickStepState<State<GitReference[]>>);
229238
}
230239

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

src/env/node/git/git.ts

+15-17
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,8 @@ export const GitErrors = {
7878
changesWouldBeOverwritten: /Your local changes to the following files would be overwritten/i,
7979
commitChangesFirst: /Please, commit your changes before you can/i,
8080
conflict: /^CONFLICT \([^)]+\): \b/m,
81+
cherryPickUnmerged:
82+
/error: Cherry-picking.*unmerged files\.\nhint:.*\nhint:.*make a commit\.\nfatal: cherry-pick failed/i,
8183
failedToDeleteDirectoryNotEmpty: /failed to delete '(.*?)': Directory not empty/i,
8284
invalidObjectName: /invalid object name: (.*)\s/i,
8385
invalidObjectNameList: /could not open object name list: (.*)\s/i,
@@ -165,6 +167,12 @@ function getStdinUniqueKey(): number {
165167
type ExitCodeOnlyGitCommandOptions = GitCommandOptions & { exitCodeOnly: true };
166168
export type PushForceOptions = { withLease: true; ifIncludes?: boolean } | { withLease: false; ifIncludes?: never };
167169

170+
const cherryPickErrorAndReason = [
171+
[GitErrors.changesWouldBeOverwritten, CherryPickErrorReason.AbortedWouldOverwrite],
172+
[GitErrors.conflict, CherryPickErrorReason.Conflicts],
173+
[GitErrors.cherryPickUnmerged, CherryPickErrorReason.Conflicts],
174+
];
175+
168176
const tagErrorAndReason: [RegExp, TagErrorReason][] = [
169177
[GitErrors.tagAlreadyExists, TagErrorReason.TagAlreadyExists],
170178
[GitErrors.tagNotFound, TagErrorReason.TagNotFound],
@@ -617,28 +625,18 @@ export class Git {
617625
return this.git<string>({ cwd: repoPath }, ...params);
618626
}
619627

620-
async cherrypick(repoPath: string, sha: string, options: { noCommit?: boolean; errors?: GitErrorHandling } = {}) {
621-
const params = ['cherry-pick'];
622-
if (options?.noCommit) {
623-
params.push('-n');
624-
}
625-
params.push(sha);
626-
628+
async cherryPick(repoPath: string, args: string[]) {
627629
try {
628-
await this.git<string>({ cwd: repoPath, errors: options?.errors }, ...params);
630+
await this.git<string>({ cwd: repoPath }, 'cherry-pick', ...args);
629631
} catch (ex) {
630632
const msg: string = ex?.toString() ?? '';
631-
let reason: CherryPickErrorReason = CherryPickErrorReason.Other;
632-
if (
633-
GitErrors.changesWouldBeOverwritten.test(msg) ||
634-
GitErrors.changesWouldBeOverwritten.test(ex.stderr ?? '')
635-
) {
636-
reason = CherryPickErrorReason.AbortedWouldOverwrite;
637-
} else if (GitErrors.conflict.test(msg) || GitErrors.conflict.test(ex.stdout ?? '')) {
638-
reason = CherryPickErrorReason.Conflicts;
633+
for (const [error, reason] of cherryPickErrorAndReason) {
634+
if (error.test(msg) || error.test(ex.stderr ?? '')) {
635+
throw new CherryPickError(reason, ex);
636+
}
639637
}
640638

641-
throw new CherryPickError(reason, ex, sha);
639+
throw new CherryPickError(CherryPickErrorReason.Other, ex);
642640
}
643641
}
644642

src/env/node/git/localGitProvider.ts

+25-1
Original file line numberDiff line numberDiff line change
@@ -1076,6 +1076,30 @@ export class LocalGitProvider implements GitProvider, Disposable {
10761076
this.container.events.fire('git:cache:reset', { repoPath: repoPath, caches: ['remotes'] });
10771077
}
10781078

1079+
@log()
1080+
async cherryPick(repoPath: string, ref: string, options: { noCommit?: boolean; edit?: boolean }): Promise<void> {
1081+
const args: string[] = [];
1082+
if (options?.noCommit) {
1083+
args.push('-n');
1084+
}
1085+
1086+
if (options?.edit) {
1087+
args.push('-e');
1088+
}
1089+
1090+
args.push(ref);
1091+
1092+
try {
1093+
await this.git.cherryPick(repoPath, args);
1094+
} catch (ex) {
1095+
if (ex instanceof CherryPickError) {
1096+
throw ex.WithRef(ref);
1097+
}
1098+
1099+
throw ex;
1100+
}
1101+
}
1102+
10791103
@log()
10801104
async applyChangesToWorkingFile(uri: GitUri, ref1?: string, ref2?: string) {
10811105
const scope = getLogScope();
@@ -1225,7 +1249,7 @@ export class LocalGitProvider implements GitProvider, Disposable {
12251249

12261250
// Apply the patch using a cherry pick without committing
12271251
try {
1228-
await this.git.cherrypick(targetPath, ref, { noCommit: true, errors: GitErrorHandling.Throw });
1252+
await this.git.cherryPick(targetPath, ref, { noCommit: true, errors: GitErrorHandling.Throw });
12291253
} catch (ex) {
12301254
Logger.error(ex, scope);
12311255
if (ex instanceof CherryPickError) {

src/git/errors.ts

+31-19
Original file line numberDiff line numberDiff line change
@@ -364,37 +364,49 @@ export class CherryPickError extends Error {
364364

365365
readonly original?: Error;
366366
readonly reason: CherryPickErrorReason | undefined;
367+
ref?: string;
368+
369+
private static buildCherryPickErrorMessage(reason: CherryPickErrorReason | undefined, ref?: string) {
370+
let baseMessage = `Unable to cherry-pick${ref ? ` commit '${ref}'` : ''}`;
371+
switch (reason) {
372+
case CherryPickErrorReason.AbortedWouldOverwrite:
373+
baseMessage += ' as some local changes would be overwritten';
374+
break;
375+
case CherryPickErrorReason.Conflicts:
376+
baseMessage += ' due to conflicts';
377+
break;
378+
}
379+
return baseMessage;
380+
}
367381

368382
constructor(reason?: CherryPickErrorReason, original?: Error, sha?: string);
369383
constructor(message?: string, original?: Error);
370-
constructor(messageOrReason: string | CherryPickErrorReason | undefined, original?: Error, sha?: string) {
371-
let message;
372-
const baseMessage = `Unable to cherry-pick${sha ? ` commit '${sha}'` : ''}`;
384+
constructor(messageOrReason: string | CherryPickErrorReason | undefined, original?: Error, ref?: string) {
373385
let reason: CherryPickErrorReason | undefined;
374-
if (messageOrReason == null) {
375-
message = baseMessage;
376-
} else if (typeof messageOrReason === 'string') {
377-
message = messageOrReason;
378-
reason = undefined;
386+
if (typeof messageOrReason !== 'string') {
387+
reason = messageOrReason as CherryPickErrorReason;
379388
} else {
380-
reason = messageOrReason;
381-
switch (reason) {
382-
case CherryPickErrorReason.AbortedWouldOverwrite:
383-
message = `${baseMessage} as some local changes would be overwritten.`;
384-
break;
385-
case CherryPickErrorReason.Conflicts:
386-
message = `${baseMessage} due to conflicts.`;
387-
break;
388-
default:
389-
message = baseMessage;
390-
}
389+
super(messageOrReason);
391390
}
391+
392+
const message =
393+
typeof messageOrReason === 'string'
394+
? messageOrReason
395+
: CherryPickError.buildCherryPickErrorMessage(messageOrReason as CherryPickErrorReason, ref);
392396
super(message);
393397

394398
this.original = original;
395399
this.reason = reason;
400+
this.ref = ref;
401+
396402
Error.captureStackTrace?.(this, CherryPickError);
397403
}
404+
405+
WithRef(ref: string) {
406+
this.ref = ref;
407+
this.message = CherryPickError.buildCherryPickErrorMessage(this.reason, ref);
408+
return this;
409+
}
398410
}
399411

400412
export class WorkspaceUntrustedError extends Error {

src/git/gitProvider.ts

+2
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,8 @@ export interface GitProviderRepository {
126126
pruneRemote?(repoPath: string, name: string): Promise<void>;
127127
removeRemote?(repoPath: string, name: string): Promise<void>;
128128

129+
cherryPick?(repoPath: string, ref: string, options: { noCommit?: boolean; edit?: boolean }): Promise<void>;
130+
129131
applyUnreachableCommitForPatch?(
130132
repoPath: string,
131133
ref: string,

src/git/gitProviderService.ts

+21
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ import { registerCommand } from '../system/vscode/command';
4242
import { configuration } from '../system/vscode/configuration';
4343
import { setContext } from '../system/vscode/context';
4444
import { getBestPath } from '../system/vscode/path';
45+
import type { GitErrorHandling } from './commandOptions';
4546
import type {
4647
BranchContributorOverview,
4748
GitCaches,
@@ -1334,6 +1335,26 @@ export class GitProviderService implements Disposable {
13341335
return provider.removeRemote(path, name);
13351336
}
13361337

1338+
@log()
1339+
cherryPick(repoPath: string | Uri, ref: string, flags: string[] | undefined = []): Promise<void> {
1340+
const { provider, path } = this.getProvider(repoPath);
1341+
if (provider.cherryPick == null) throw new ProviderNotSupportedError(provider.descriptor.name);
1342+
1343+
const options: { noCommit?: boolean; edit?: boolean; errors?: GitErrorHandling } = {};
1344+
for (const flag of flags) {
1345+
switch (flag) {
1346+
case '--no-commit':
1347+
options.noCommit = true;
1348+
break;
1349+
case '--edit':
1350+
options.edit = true;
1351+
break;
1352+
}
1353+
}
1354+
1355+
return provider.cherryPick(path, ref, options);
1356+
}
1357+
13371358
@log()
13381359
applyChangesToWorkingFile(uri: GitUri, ref1?: string, ref2?: string): Promise<void> {
13391360
const { provider } = this.getProvider(uri);

src/git/models/repository.ts

-5
Original file line numberDiff line numberDiff line change
@@ -633,11 +633,6 @@ export class Repository implements Disposable {
633633
}
634634
}
635635

636-
@log()
637-
cherryPick(...args: string[]) {
638-
void this.runTerminalCommand('cherry-pick', ...args);
639-
}
640-
641636
containsUri(uri: Uri) {
642637
return this === this.container.git.getRepository(uri);
643638
}

0 commit comments

Comments
 (0)