Browse Source

Adds ability to cancel/kill git calls

main
Eric Amodio 2 years ago
parent
commit
317d831be9
4 changed files with 75 additions and 13 deletions
  1. +15
    -3
      src/env/node/git/git.ts
  2. +22
    -10
      src/env/node/git/localGitProvider.ts
  3. +35
    -0
      src/env/node/git/shell.ts
  4. +3
    -0
      src/git/commandOptions.ts

+ 15
- 3
src/env/node/git/git.ts View File

@ -1,5 +1,5 @@
import * as process from 'process';
import { Uri, window, workspace } from 'vscode';
import { CancellationToken, Uri, window, workspace } from 'vscode';
import { hrtime } from '@env/hrtime';
import { GlyphChars } from '../../../constants';
import { GitCommandOptions, GitErrorHandling } from '../../../git/commandOptions';
@ -905,7 +905,14 @@ export class Git {
return data.length === 0 ? undefined : data.trim();
}
async log__find_object(repoPath: string, objectId: string, ref: string, ordering: string | null, file?: string) {
async log__find_object(
repoPath: string,
objectId: string,
ref: string,
ordering: string | null,
file?: string,
token?: CancellationToken,
) {
const params = ['log', '-n1', '--no-renames', '--format=%H', `--find-object=${objectId}`, ref];
if (ordering) {
@ -917,7 +924,12 @@ export class Git {
}
const data = await this.git<string>(
{ cwd: repoPath, configs: ['-c', 'log.showSignature=false'], errors: GitErrorHandling.Ignore },
{
cancellationToken: token,
cwd: repoPath,
configs: ['-c', 'log.showSignature=false'],
errors: GitErrorHandling.Ignore,
},
...params,
);
return data.length === 0 ? undefined : data.trim();

+ 22
- 10
src/env/node/git/localGitProvider.ts View File

@ -3,6 +3,7 @@ import { homedir, hostname, userInfo } from 'os';
import { resolve as resolvePath } from 'path';
import { env as process_env } from 'process';
import {
CancellationTokenSource,
Disposable,
env,
Event,
@ -128,7 +129,7 @@ import {
relative,
splitPath,
} from '../../../system/path';
import { any, fastestSettled, PromiseOrValue, wait } from '../../../system/promise';
import { any, fastestSettled, PromiseOrValue } from '../../../system/promise';
import { equalsIgnoreCase, getDurationMilliseconds, md5, splitSingle } from '../../../system/string';
import { PathTrie } from '../../../system/trie';
import { compare, fromString } from '../../../system/version';
@ -3761,18 +3762,29 @@ export class LocalGitProvider implements GitProvider, Disposable {
const blob = await this.git.rev_parse__verify(repoPath, ref, relativePath);
if (blob == null) return GitRevision.deletedOrMissing;
let promise: Promise<string | void | undefined> = this.git.log__find_object(
repoPath,
blob,
ref,
this.container.config.advanced.commitOrdering,
relativePath,
);
let cancellation: CancellationTokenSource | undefined;
let timer: ReturnType<typeof setTimeout> | undefined;
if (options?.timeout != null) {
promise = Promise.race([promise, wait(options.timeout)]);
cancellation = new CancellationTokenSource();
timer = setTimeout(() => cancellation?.cancel(), options.timeout);
}
ref =
(await this.git.log__find_object(
repoPath,
blob,
ref,
this.container.config.advanced.commitOrdering,
relativePath,
cancellation?.token,
)) ?? ref;
cancellation?.dispose();
if (timer != null) {
clearTimeout(timer);
}
return (await promise) ?? ref;
return ref;
}
@log()

+ 35
- 0
src/env/node/git/shell.ts View File

@ -3,6 +3,7 @@ import { exists, existsSync, Stats, statSync } from 'fs';
import { join as joinPaths } from 'path';
import * as process from 'process';
import { decode } from 'iconv-lite';
import { CancellationToken } from 'vscode';
import { Logger } from '../../../logger';
export const isWindows = process.platform === 'win32';
@ -115,6 +116,7 @@ export function findExecutable(exe: string, args: string[]): { cmd: string; args
}
export interface RunOptions<TEncoding = BufferEncoding | 'buffer'> {
cancellationToken?: CancellationToken;
cwd?: string;
readonly env?: Record<string, any>;
readonly encoding?: TEncoding;
@ -171,6 +173,25 @@ export class RunError extends Error {
}
}
export class CancelledRunError extends RunError {
constructor(cmd: string, killed: boolean, code?: number | undefined, signal: NodeJS.Signals = 'SIGTERM') {
super(
{
name: 'CancelledRunError',
message: 'Cancelled',
cmd: cmd,
killed: killed,
code: code,
signal: signal,
},
'',
'',
);
Error.captureStackTrace?.(this, CancelledRunError);
}
}
type ExitCodeOnlyRunOptions = RunOptions & { exitCodeOnly: true };
export function run(
@ -193,8 +214,11 @@ export function run(
): Promise<T> {
const { stdin, stdinEncoding, ...opts }: RunOptions = { maxBuffer: 100 * 1024 * 1024, ...options };
let killed = false;
return new Promise<T>((resolve, reject) => {
const proc = execFile(command, args, opts, (error: ExecException | null, stdout, stderr) => {
if (killed) return;
if (options?.exitCodeOnly) {
resolve((error?.code ?? proc.exitCode) as T);
@ -232,6 +256,17 @@ export function run(
);
});
options?.cancellationToken?.onCancellationRequested(() => {
const success = proc.kill();
killed = true;
if (options?.exitCodeOnly) {
resolve(0 as T);
} else {
reject(new CancelledRunError(command, success));
}
});
if (stdin != null) {
proc.stdin?.end(stdin, (stdinEncoding ?? 'utf8') as BufferEncoding);
}

+ 3
- 0
src/git/commandOptions.ts View File

@ -1,3 +1,5 @@
import { CancellationToken } from 'vscode';
export const enum GitErrorHandling {
Throw = 0,
Ignore = 1,
@ -12,6 +14,7 @@ export interface GitCommandOptions {
local?: boolean;
// Below options comes from RunOptions<BufferEncoding | 'buffer' | string>
cancellationToken?: CancellationToken;
cwd?: string;
readonly env?: Record<string, any>;
readonly encoding?: BufferEncoding | 'buffer' | string;

Loading…
Cancel
Save