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 * as process from 'process';
import { Uri, window, workspace } from 'vscode';
import { CancellationToken, Uri, window, workspace } from 'vscode';
import { hrtime } from '@env/hrtime'; import { hrtime } from '@env/hrtime';
import { GlyphChars } from '../../../constants'; import { GlyphChars } from '../../../constants';
import { GitCommandOptions, GitErrorHandling } from '../../../git/commandOptions'; import { GitCommandOptions, GitErrorHandling } from '../../../git/commandOptions';
@ -905,7 +905,14 @@ export class Git {
return data.length === 0 ? undefined : data.trim(); 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]; const params = ['log', '-n1', '--no-renames', '--format=%H', `--find-object=${objectId}`, ref];
if (ordering) { if (ordering) {
@ -917,7 +924,12 @@ export class Git {
} }
const data = await this.git<string>( 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, ...params,
); );
return data.length === 0 ? undefined : data.trim(); 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 { resolve as resolvePath } from 'path';
import { env as process_env } from 'process'; import { env as process_env } from 'process';
import { import {
CancellationTokenSource,
Disposable, Disposable,
env, env,
Event, Event,
@ -128,7 +129,7 @@ import {
relative, relative,
splitPath, splitPath,
} from '../../../system/path'; } 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 { equalsIgnoreCase, getDurationMilliseconds, md5, splitSingle } from '../../../system/string';
import { PathTrie } from '../../../system/trie'; import { PathTrie } from '../../../system/trie';
import { compare, fromString } from '../../../system/version'; 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); const blob = await this.git.rev_parse__verify(repoPath, ref, relativePath);
if (blob == null) return GitRevision.deletedOrMissing; 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) { 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() @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 { join as joinPaths } from 'path';
import * as process from 'process'; import * as process from 'process';
import { decode } from 'iconv-lite'; import { decode } from 'iconv-lite';
import { CancellationToken } from 'vscode';
import { Logger } from '../../../logger'; import { Logger } from '../../../logger';
export const isWindows = process.platform === 'win32'; 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'> { export interface RunOptions<TEncoding = BufferEncoding | 'buffer'> {
cancellationToken?: CancellationToken;
cwd?: string; cwd?: string;
readonly env?: Record<string, any>; readonly env?: Record<string, any>;
readonly encoding?: TEncoding; 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 }; type ExitCodeOnlyRunOptions = RunOptions & { exitCodeOnly: true };
export function run( export function run(
@ -193,8 +214,11 @@ export function run(
): Promise<T> { ): Promise<T> {
const { stdin, stdinEncoding, ...opts }: RunOptions = { maxBuffer: 100 * 1024 * 1024, ...options }; const { stdin, stdinEncoding, ...opts }: RunOptions = { maxBuffer: 100 * 1024 * 1024, ...options };
let killed = false;
return new Promise<T>((resolve, reject) => { return new Promise<T>((resolve, reject) => {
const proc = execFile(command, args, opts, (error: ExecException | null, stdout, stderr) => { const proc = execFile(command, args, opts, (error: ExecException | null, stdout, stderr) => {
if (killed) return;
if (options?.exitCodeOnly) { if (options?.exitCodeOnly) {
resolve((error?.code ?? proc.exitCode) as T); 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) { if (stdin != null) {
proc.stdin?.end(stdin, (stdinEncoding ?? 'utf8') as BufferEncoding); 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 { export const enum GitErrorHandling {
Throw = 0, Throw = 0,
Ignore = 1, Ignore = 1,
@ -12,6 +14,7 @@ export interface GitCommandOptions {
local?: boolean; local?: boolean;
// Below options comes from RunOptions<BufferEncoding | 'buffer' | string> // Below options comes from RunOptions<BufferEncoding | 'buffer' | string>
cancellationToken?: CancellationToken;
cwd?: string; cwd?: string;
readonly env?: Record<string, any>; readonly env?: Record<string, any>;
readonly encoding?: BufferEncoding | 'buffer' | string; readonly encoding?: BufferEncoding | 'buffer' | string;

Loading…
Cancel
Save