-
Notifications
You must be signed in to change notification settings - Fork 1.4k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
3530 Change revert
terminal-run commands into normal commands w/ proper error handling
#3789
base: main
Are you sure you want to change the base?
3530 Change revert
terminal-run commands into normal commands w/ proper error handling
#3789
Conversation
b3280b6
to
5d90ed8
Compare
bf93067
to
87b29d1
Compare
src/git/gitProviderService.ts
Outdated
async revert(repoPath: string | Uri, ref: string, flags: string[] | undefined = []): Promise<void> { | ||
const { provider, path } = this.getProvider(repoPath); | ||
if (provider.revert == null) throw new ProviderNotSupportedError(provider.descriptor.name); | ||
|
||
const options: { edit?: boolean } = {}; | ||
for (const flag of flags) { | ||
switch (flag) { | ||
case '--edit': | ||
options.edit = true; | ||
break; | ||
case '--no-edit': | ||
options.edit = false; | ||
break; | ||
default: | ||
break; | ||
} | ||
} | ||
|
||
return provider.revert(path, ref, options); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure if this is a pattern in other places or not but, why at the git provider service level should the caller provide flags instead of just passing in options? Flags feel like low-level terminal args that should be abstracted down to the git.ts
level and anything coming in from above should really only be dealing with a list of options.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, we could parse that in src/commands/git/*
directly. Since currently this method is only being called from commands I didn't have preference on where to perform that conversion, but we can absolutely do that before and send the options directly to the method. Even better, maybe we can have an alternative
createFlagsQuickPickItem<Flags>(state.flags, ['--no-edit'], {
label: this.title,
description: '--no-edit',
detail: `Will revert ${getReferenceLabel(state.references)}`,
}),
That accepts and returns an option set so we don't have to parse the flags every time. Let me think this through
async revert(repoPath: string, ...args: string[]) { | ||
try { | ||
await this.git<string>({ cwd: repoPath }, 'revert', ...args); | ||
} catch (ex) { | ||
const msg: string = ex?.toString() ?? ''; | ||
for (const [error, reason] of revertErrorAndReason) { | ||
if (error.test(msg) || error.test(ex.stderr ?? '')) { | ||
throw new RevertError(reason, ex); | ||
} | ||
} | ||
|
||
throw new RevertError(RevertErrorReason.Other, ex); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should take constrained options, generate the flags and params necessary off of those options, and then run the command with the args it generated.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That is being performed at localGitProvider
. From my understanding, localGitProvider
is an abstraction from GitLens that uses the local implementation for git
(the git binary in our case). As such, it should receive the options (business logic) and make the proper adaptations to call the local git implementation (the git
methods).
The current implementation keeps the methods in git
as atomic as possible and separated from our business logic (we could even move the error parsing to localGitProvider
if necessary).
If you think it would be clearer to perform that here, I could move that logic here and leave the revert
at localGitProvider
with just the git call. Is this what you mean?
// localGitProvider.ts
export class LocalGitProvider implements GitProvider, Disposable {
// ...
revert(repoPath: string, ref: string, options?: { edit?: boolean }): Promise<void> {
return this.git.revert(repoPath, ref, options);
}
}
// git.ts
export class Git {
// ...
async revert(repoPath: string, ref: string, options?: { edit?: boolean }) {
const args = [];
if (options.edit !== undefined) {
args.push(options.edit ? '--edit' : '--no-edit');
}
args.push(ref);
try {
await this.git<string>({ cwd: repoPath }, 'revert', ...args);
} catch (ex) {
const msg: string = ex?.toString() ?? '';
for (const [error, reason] of revertErrorAndReason) {
if (error.test(msg) || error.test(ex.stderr ?? '')) {
throw new RevertError(reason, ex, ref);
}
}
throw new RevertError(RevertErrorReason.Other, ex, ref);
}
}
}
src/commands/git/revert.ts
Outdated
for (const ref of state.references.reverse()) { | ||
try { | ||
await state.repo.git.revert(ref.ref, state.flags); | ||
} catch (ex) { | ||
if (ex instanceof RevertError) { | ||
let shouldRetry = false; | ||
if (ex.reason === RevertErrorReason.LocalChangesWouldBeOverwritten) { | ||
const response = await showShouldCommitOrStashPrompt(); | ||
if (response === 'Stash') { | ||
await executeCommand(Commands.GitCommandsStashPush); | ||
shouldRetry = true; | ||
} else if (response === 'Commit') { | ||
await executeCoreCommand('workbench.view.scm'); | ||
shouldRetry = true; | ||
} else { | ||
continue; | ||
} | ||
} | ||
|
||
if (shouldRetry) { | ||
try { | ||
await state.repo.git.revert(ref.ref, state.flags); | ||
} catch (ex) { | ||
Logger.error(ex, this.title); | ||
void showGenericErrorMessage(ex.message); | ||
} | ||
} | ||
|
||
continue; | ||
} | ||
|
||
Logger.error(ex, this.title); | ||
void showGenericErrorMessage(ex.message); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is probably going to result in me receiving a crash course on git revert flows, but for my own education, can you explain what this is doing?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The revert command could return a "changes would be overwritten" error when the user had WIP while reverting. With this piece of code, we would catch that, let the user know they have WIP they should either commit or stash, and ask them what to do. Then, depending on the option they chose, we run & wait for the command and retry the revert action (unless they cancelled).
EDIT: Just tried it, and autostash is only available for rebase. I've seen some inconsistencies with the retry, since it fires before the other command finishes. Removing it now.
87b29d1
to
4f921f8
Compare
4f921f8
to
028b32d
Compare
Description
This task creates the git revert commands to move away from runTerminalCommand
Solves #3530
Checklist
Fixes $XXX -
orCloses #XXX -
prefix to auto-close the issue that your PR addresses