diff --git a/src/env/node/git/git.ts b/src/env/node/git/git.ts index 26c605c..5ffd82a 100644 --- a/src/env/node/git/git.ts +++ b/src/env/node/git/git.ts @@ -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( - { 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(); diff --git a/src/env/node/git/localGitProvider.ts b/src/env/node/git/localGitProvider.ts index 9d3347c..0352689 100644 --- a/src/env/node/git/localGitProvider.ts +++ b/src/env/node/git/localGitProvider.ts @@ -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 = this.git.log__find_object( - repoPath, - blob, - ref, - this.container.config.advanced.commitOrdering, - relativePath, - ); + let cancellation: CancellationTokenSource | undefined; + let timer: ReturnType | 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() diff --git a/src/env/node/git/shell.ts b/src/env/node/git/shell.ts index 21212c6..c7c2efb 100644 --- a/src/env/node/git/shell.ts +++ b/src/env/node/git/shell.ts @@ -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 { + cancellationToken?: CancellationToken; cwd?: string; readonly env?: Record; 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 { const { stdin, stdinEncoding, ...opts }: RunOptions = { maxBuffer: 100 * 1024 * 1024, ...options }; + let killed = false; return new Promise((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); } diff --git a/src/git/commandOptions.ts b/src/git/commandOptions.ts index 34c360c..ac02e3e 100644 --- a/src/git/commandOptions.ts +++ b/src/git/commandOptions.ts @@ -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 + cancellationToken?: CancellationToken; cwd?: string; readonly env?: Record; readonly encoding?: BufferEncoding | 'buffer' | string;