Skip to content

Commit d9a9e7c

Browse files
committed
create git reset command
1 parent 279a32b commit d9a9e7c

File tree

7 files changed

+129
-10
lines changed

7 files changed

+129
-10
lines changed

src/commands/git/reset.ts

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,10 @@ import type { GitLog } from '../../git/models/log';
44
import type { GitReference, GitRevisionReference, GitTagReference } from '../../git/models/reference';
55
import { getReferenceLabel } 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,
@@ -69,8 +71,13 @@ export class ResetGitCommand extends QuickCommand<State> {
6971
return this._canSkipConfirm;
7072
}
7173

72-
execute(state: ResetStepState) {
73-
state.repo.reset(...state.flags, state.reference.ref);
74+
async execute(state: ResetStepState) {
75+
try {
76+
await state.repo.git.reset(state.flags, state.reference.ref);
77+
} catch (ex) {
78+
Logger.error(ex, this.title);
79+
void showGenericErrorMessage(ex.message);
80+
}
7481
}
7582

7683
protected async *steps(state: PartialStepState<State>): StepGenerator {
@@ -156,7 +163,7 @@ export class ResetGitCommand extends QuickCommand<State> {
156163
}
157164

158165
endSteps(state);
159-
this.execute(state as ResetStepState);
166+
await this.execute(state as ResetStepState);
160167
}
161168

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

src/env/node/git/git.ts

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ import {
2020
PullErrorReason,
2121
PushError,
2222
PushErrorReason,
23+
ResetError,
24+
ResetErrorReason,
2325
StashPushError,
2426
StashPushErrorReason,
2527
TagError,
@@ -105,6 +107,11 @@ export const GitErrors = {
105107
tagNotFound: /tag .* not found/i,
106108
invalidTagName: /invalid tag name/i,
107109
remoteRejected: /rejected because the remote contains work/i,
110+
unmergedChanges: /error:\s*you need to resolve your current index first/i,
111+
ambiguousArgument: /fatal:\s*ambiguous argument ['"].+['"]: unknown revision or path not in the working tree/i,
112+
entryNotUpToDate: /error:\s*Entry ['"].+['"] not uptodate\. Cannot merge\./i,
113+
changesWouldBeOverwritten: /error:\s*Your local changes to the following files would be overwritten/i,
114+
refLocked: /fatal:\s*cannot lock ref ['"].+['"]: unable to create file/i,
108115
};
109116

110117
const GitWarnings = {
@@ -173,6 +180,14 @@ const tagErrorAndReason: [RegExp, TagErrorReason][] = [
173180
[GitErrors.remoteRejected, TagErrorReason.RemoteRejected],
174181
];
175182

183+
const resetErrorAndReason = [
184+
[unmergedChanges, ResetErrorReason.UnmergedChanges],
185+
[ambiguousArgument, ResetErrorReason.AmbiguousArgument],
186+
[entryNotUpToDate, ResetErrorReason.EntryNotUpToDate],
187+
[changesWouldBeOverwritten, ResetErrorReason.LocalChangesWouldBeOverwritten],
188+
[refLocked, ResetErrorReason.RefLocked],
189+
];
190+
176191
export class Git {
177192
/** Map of running git commands -- avoids running duplicate overlaping commands */
178193
private readonly pendingCommands = new Map<string, Promise<string | Buffer>>();
@@ -1584,8 +1599,19 @@ export class Git {
15841599
return this.git<string>({ cwd: repoPath }, 'remote', 'get-url', remote);
15851600
}
15861601

1587-
reset(repoPath: string | undefined, pathspecs: string[]) {
1588-
return this.git<string>({ cwd: repoPath }, 'reset', '-q', '--', ...pathspecs);
1602+
reset(repoPath: string, pathspecs: string[], ...args: string[]) {
1603+
try {
1604+
return this.git<string>({ cwd: repoPath }, 'reset', '-q', ...args, '--', ...pathspecs);
1605+
} catch (ex) {
1606+
const msg: string = ex?.toString() ?? '';
1607+
for (const [error, reason] of resetErrorAndReason) {
1608+
if (error.test(msg)) {
1609+
throw new ResetError(reason, ex);
1610+
}
1611+
}
1612+
1613+
throw new ResetError(ResetErrorReason.Other, ex);
1614+
}
15891615
}
15901616

15911617
async rev_list(

src/env/node/git/localGitProvider.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5814,6 +5814,18 @@ export class LocalGitProvider implements GitProvider, Disposable {
58145814
}
58155815
}
58165816

5817+
@log()
5818+
async reset(repoPath: string, options?: { hard?: boolean; soft?: boolean }, ref?: string): Promise<void> {
5819+
const flags = [];
5820+
if (options?.hard) {
5821+
flags.push('--hard');
5822+
} else if (options?.soft) {
5823+
flags.push('--soft');
5824+
}
5825+
5826+
await this.git.reset(repoPath, [], ...flags, ref);
5827+
}
5828+
58175829
@log({ args: { 2: false } })
58185830
async runGitCommandViaTerminal(
58195831
repoPath: string,

src/git/errors.ts

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -567,3 +567,58 @@ export class TagError extends Error {
567567
return this;
568568
}
569569
}
570+
571+
export const enum ResetErrorReason {
572+
UnmergedChanges,
573+
AmbiguousArgument,
574+
EntryNotUpToDate,
575+
LocalChangesWouldBeOverwritten,
576+
RefLocked,
577+
Other,
578+
}
579+
580+
export class ResetError extends Error {
581+
static is(ex: unknown, reason?: ResetErrorReason): ex is ResetError {
582+
return ex instanceof ResetError && (reason == null || ex.reason === reason);
583+
}
584+
585+
readonly original?: Error;
586+
readonly reason: ResetErrorReason | undefined;
587+
constructor(reason?: ResetErrorReason, original?: Error);
588+
constructor(message?: string, original?: Error);
589+
constructor(messageOrReason: string | ResetErrorReason | undefined, original?: Error) {
590+
let message;
591+
let reason: ResetErrorReason | undefined;
592+
if (messageOrReason == null) {
593+
message = 'Unable to reset';
594+
} else if (typeof messageOrReason === 'string') {
595+
message = messageOrReason;
596+
reason = undefined;
597+
} else {
598+
reason = messageOrReason;
599+
message = 'Unable to reset';
600+
switch (reason) {
601+
case ResetErrorReason.UnmergedChanges:
602+
message = `${message} because there are unmerged changes`;
603+
break;
604+
case ResetErrorReason.AmbiguousArgument:
605+
message = `${message} because the argument is ambiguous`;
606+
break;
607+
case ResetErrorReason.EntryNotUpToDate:
608+
message = `${message} because the entry is not up to date`;
609+
break;
610+
case ResetErrorReason.LocalChangesWouldBeOverwritten:
611+
message = `${message} because local changes would be overwritten`;
612+
break;
613+
case ResetErrorReason.RefLocked:
614+
message = `${message} because the ref is locked`;
615+
break;
616+
}
617+
}
618+
super(message);
619+
620+
this.original = original;
621+
this.reason = reason;
622+
Error.captureStackTrace?.(this, ResetError);
623+
}
624+
}

src/git/gitProvider.ts

Lines changed: 2 additions & 0 deletions
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+
reset?(repoPath: string, options?: { hard?: boolean; soft?: boolean }, ref?: string): Promise<void>;
130+
129131
applyUnreachableCommitForPatch?(
130132
repoPath: string,
131133
ref: string,

src/git/gitProviderService.ts

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

1337+
@log()
1338+
async reset(repoPath: string, flags: string[], ref?: string): Promise<void> {
1339+
const { provider, path } = this.getProvider(repoPath);
1340+
if (provider.reset == null) throw new ProviderNotSupportedError(provider.descriptor.name);
1341+
1342+
const options: { hard?: boolean; soft?: boolean } = {};
1343+
for (const flag of flags) {
1344+
switch (flag) {
1345+
case '--hard':
1346+
options.hard = true;
1347+
break;
1348+
case '--soft':
1349+
options.soft = true;
1350+
break;
1351+
default:
1352+
break;
1353+
}
1354+
}
1355+
1356+
return provider.reset(path, options, ref);
1357+
}
1358+
13371359
@log()
13381360
applyChangesToWorkingFile(uri: GitUri, ref1?: string, ref2?: string): Promise<void> {
13391361
const { provider } = this.getProvider(uri);

src/git/models/repository.ts

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -849,11 +849,6 @@ export class Repository implements Disposable {
849849
);
850850
}
851851

852-
@log()
853-
reset(...args: string[]) {
854-
void this.runTerminalCommand('reset', ...args);
855-
}
856-
857852
resume() {
858853
if (!this._suspended) return;
859854

0 commit comments

Comments
 (0)