Skip to content

Commit 0c17c53

Browse files
committed
create git reset command
1 parent ccf72bb commit 0c17c53

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 } 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
WorkspaceUntrustedError,
@@ -100,6 +102,11 @@ export const GitErrors = {
100102
tagConflict: /! \[rejected\].*\(would clobber existing tag\)/m,
101103
unmergedFiles: /is not possible because you have unmerged files/i,
102104
unstagedChanges: /You have unstaged changes/i,
105+
unmergedChanges: /error:\s*you need to resolve your current index first/i,
106+
ambiguousArgument: /fatal:\s*ambiguous argument ['"].+['"]: unknown revision or path not in the working tree/i,
107+
entryNotUpToDate: /error:\s*Entry ['"].+['"] not uptodate\. Cannot merge\./i,
108+
changesWouldBeOverwritten: /error:\s*Your local changes to the following files would be overwritten/i,
109+
refLocked: /fatal:\s*cannot lock ref ['"].+['"]: unable to create file/i,
103110
};
104111

105112
const GitWarnings = {
@@ -160,6 +167,14 @@ function getStdinUniqueKey(): number {
160167
type ExitCodeOnlyGitCommandOptions = GitCommandOptions & { exitCodeOnly: true };
161168
export type PushForceOptions = { withLease: true; ifIncludes?: boolean } | { withLease: false; ifIncludes?: never };
162169

170+
const resetErrorAndReason = [
171+
[unmergedChanges, ResetErrorReason.UnmergedChanges],
172+
[ambiguousArgument, ResetErrorReason.AmbiguousArgument],
173+
[entryNotUpToDate, ResetErrorReason.EntryNotUpToDate],
174+
[changesWouldBeOverwritten, ResetErrorReason.LocalChangesWouldBeOverwritten],
175+
[refLocked, ResetErrorReason.RefLocked],
176+
];
177+
163178
export class Git {
164179
/** Map of running git commands -- avoids running duplicate overlaping commands */
165180
private readonly pendingCommands = new Map<string, Promise<string | Buffer>>();
@@ -1571,8 +1586,19 @@ export class Git {
15711586
return this.git<string>({ cwd: repoPath }, 'remote', 'get-url', remote);
15721587
}
15731588

1574-
reset(repoPath: string | undefined, pathspecs: string[]) {
1575-
return this.git<string>({ cwd: repoPath }, 'reset', '-q', '--', ...pathspecs);
1589+
reset(repoPath: string, pathspecs: string[], ...args: string[]) {
1590+
try {
1591+
return this.git<string>({ cwd: repoPath }, 'reset', '-q', ...args, '--', ...pathspecs);
1592+
} catch (ex) {
1593+
const msg: string = ex?.toString() ?? '';
1594+
for (const [error, reason] of resetErrorAndReason) {
1595+
if (error.test(msg)) {
1596+
throw new ResetError(reason, ex);
1597+
}
1598+
}
1599+
1600+
throw new ResetError(ResetErrorReason.Other, ex);
1601+
}
15761602
}
15771603

15781604
async rev_list(

src/env/node/git/localGitProvider.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5788,6 +5788,18 @@ export class LocalGitProvider implements GitProvider, Disposable {
57885788
}
57895789
}
57905790

5791+
@log()
5792+
async reset(repoPath: string, options?: { hard?: boolean; soft?: boolean }, ref?: string): Promise<void> {
5793+
const flags = [];
5794+
if (options?.hard) {
5795+
flags.push('--hard');
5796+
} else if (options?.soft) {
5797+
flags.push('--soft');
5798+
}
5799+
5800+
await this.git.reset(repoPath, [], ...flags, ref);
5801+
}
5802+
57915803
@log({ args: { 2: false } })
57925804
async runGitCommandViaTerminal(
57935805
repoPath: string,

src/git/errors.ts

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -489,3 +489,58 @@ export class WorktreeDeleteError extends Error {
489489
Error.captureStackTrace?.(this, WorktreeDeleteError);
490490
}
491491
}
492+
493+
export const enum ResetErrorReason {
494+
UnmergedChanges,
495+
AmbiguousArgument,
496+
EntryNotUpToDate,
497+
LocalChangesWouldBeOverwritten,
498+
RefLocked,
499+
Other,
500+
}
501+
502+
export class ResetError extends Error {
503+
static is(ex: unknown, reason?: ResetErrorReason): ex is ResetError {
504+
return ex instanceof ResetError && (reason == null || ex.reason === reason);
505+
}
506+
507+
readonly original?: Error;
508+
readonly reason: ResetErrorReason | undefined;
509+
constructor(reason?: ResetErrorReason, original?: Error);
510+
constructor(message?: string, original?: Error);
511+
constructor(messageOrReason: string | ResetErrorReason | undefined, original?: Error) {
512+
let message;
513+
let reason: ResetErrorReason | undefined;
514+
if (messageOrReason == null) {
515+
message = 'Unable to reset';
516+
} else if (typeof messageOrReason === 'string') {
517+
message = messageOrReason;
518+
reason = undefined;
519+
} else {
520+
reason = messageOrReason;
521+
message = 'Unable to reset';
522+
switch (reason) {
523+
case ResetErrorReason.UnmergedChanges:
524+
message = `${message} because there are unmerged changes`;
525+
break;
526+
case ResetErrorReason.AmbiguousArgument:
527+
message = `${message} because the argument is ambiguous`;
528+
break;
529+
case ResetErrorReason.EntryNotUpToDate:
530+
message = `${message} because the entry is not up to date`;
531+
break;
532+
case ResetErrorReason.LocalChangesWouldBeOverwritten:
533+
message = `${message} because local changes would be overwritten`;
534+
break;
535+
case ResetErrorReason.RefLocked:
536+
message = `${message} because the ref is locked`;
537+
break;
538+
}
539+
}
540+
super(message);
541+
542+
this.original = original;
543+
this.reason = reason;
544+
Error.captureStackTrace?.(this, ResetError);
545+
}
546+
}

src/git/gitProvider.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,8 @@ export interface GitProviderRepository {
125125
pruneRemote?(repoPath: string, name: string): Promise<void>;
126126
removeRemote?(repoPath: string, name: string): Promise<void>;
127127

128+
reset?(repoPath: string, options?: { hard?: boolean; soft?: boolean }, ref?: string): Promise<void>;
129+
128130
applyUnreachableCommitForPatch?(
129131
repoPath: string,
130132
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
@@ -843,11 +843,6 @@ export class Repository implements Disposable {
843843
);
844844
}
845845

846-
@log()
847-
reset(...args: string[]) {
848-
void this.runTerminalCommand('reset', ...args);
849-
}
850-
851846
resume() {
852847
if (!this._suspended) return;
853848

0 commit comments

Comments
 (0)