mirror of
https://github.com/microsoft/vscode.git
synced 2026-05-03 15:01:57 +01:00
💄 git clone progress
This commit is contained in:
@@ -16,9 +16,8 @@ import { CancellationToken, Progress } from 'vscode';
|
||||
import { URI } from 'vscode-uri';
|
||||
import { detectEncoding } from './encoding';
|
||||
import { Ref, RefType, Branch, Remote, GitErrorCodes, LogOptions, Change, Status } from './api/git';
|
||||
import * as nls from 'vscode-nls';
|
||||
|
||||
const localize = nls.loadMessageBundle();
|
||||
import * as byline from 'byline';
|
||||
import { StringDecoder } from 'string_decoder';
|
||||
|
||||
// https://github.com/microsoft/vscode/issues/65693
|
||||
const MAX_CLI_LENGTH = 30000;
|
||||
@@ -166,10 +165,10 @@ export interface SpawnOptions extends cp.SpawnOptions {
|
||||
encoding?: string;
|
||||
log?: boolean;
|
||||
cancellationToken?: CancellationToken;
|
||||
progress?: Progress<{ message?: string, increment: number }>;
|
||||
onSpawn?: (childProcess: cp.ChildProcess) => void;
|
||||
}
|
||||
|
||||
async function exec(child: cp.ChildProcess, cancellationToken?: CancellationToken, progress?: Progress<{ message?: string, increment: number }>): Promise<IExecutionResult<Buffer>> {
|
||||
async function exec(child: cp.ChildProcess, cancellationToken?: CancellationToken): Promise<IExecutionResult<Buffer>> {
|
||||
if (!child.stdout || !child.stderr) {
|
||||
throw new GitError({ message: 'Failed to get stdout or stderr from git process.' });
|
||||
}
|
||||
@@ -190,9 +189,6 @@ async function exec(child: cp.ChildProcess, cancellationToken?: CancellationToke
|
||||
disposables.push(toDisposable(() => ee.removeListener(name, fn)));
|
||||
};
|
||||
|
||||
const cloneProgressOutput = ['Receiving objects', 'Resolving deltas'];
|
||||
let prevInc = 0;
|
||||
|
||||
let result = Promise.all<any>([
|
||||
new Promise<number>((c, e) => {
|
||||
once(child, 'error', cpErrorHandler(e));
|
||||
@@ -205,27 +201,7 @@ async function exec(child: cp.ChildProcess, cancellationToken?: CancellationToke
|
||||
}),
|
||||
new Promise<string>(c => {
|
||||
const buffers: Buffer[] = [];
|
||||
on(child.stderr, 'data', (b: Buffer) => {
|
||||
buffers.push(b);
|
||||
const s = b.toString();
|
||||
|
||||
// Check for git clone progress reporting
|
||||
cloneProgressOutput.forEach(cloneOutput => {
|
||||
if (s.startsWith(cloneOutput)) {
|
||||
const idx = s.indexOf('%');
|
||||
const inc = parseInt(s.slice(idx - 3, idx));
|
||||
|
||||
if (progress) {
|
||||
progress.report({
|
||||
message: localize(cloneOutput.toLowerCase(), cloneOutput) + ': ' + inc + '%',
|
||||
increment: inc - prevInc
|
||||
});
|
||||
|
||||
prevInc = inc;
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
on(child.stderr, 'data', (b: Buffer) => buffers.push(b));
|
||||
once(child.stderr, 'close', () => c(Buffer.concat(buffers).toString('utf8')));
|
||||
})
|
||||
]) as Promise<[number, Buffer, string]>;
|
||||
@@ -368,7 +344,7 @@ export class Git {
|
||||
return;
|
||||
}
|
||||
|
||||
async clone(url: string, parentPath: string, progress: Progress<{ message?: string, increment: number }>, cancellationToken?: CancellationToken): Promise<string> {
|
||||
async clone(url: string, parentPath: string, progress: Progress<{ increment: number }>, cancellationToken?: CancellationToken): Promise<string> {
|
||||
let baseFolderName = decodeURI(url).replace(/[\/]+$/, '').replace(/^.*[\/\\]/, '').replace(/\.git$/, '') || 'repository';
|
||||
let folderName = baseFolderName;
|
||||
let folderPath = path.join(parentPath, folderName);
|
||||
@@ -381,8 +357,36 @@ export class Git {
|
||||
|
||||
await mkdirp(parentPath);
|
||||
|
||||
const onSpawn = (child: cp.ChildProcess) => {
|
||||
const decoder = new StringDecoder('utf8');
|
||||
const lineStream = new byline.LineStream({ encoding: 'utf8' });
|
||||
child.stderr.on('data', (buffer: Buffer) => lineStream.write(decoder.write(buffer)));
|
||||
|
||||
let totalProgress = 0;
|
||||
let previousProgress = 0;
|
||||
|
||||
lineStream.on('data', (line: string) => {
|
||||
let match: RegExpMatchArray | null = null;
|
||||
|
||||
if (match = /Counting objects:\s*(\d+)%/i.exec(line)) {
|
||||
totalProgress = Math.floor(parseInt(match[1]) * 0.1);
|
||||
} else if (match = /Compressing objects:\s*(\d+)%/i.exec(line)) {
|
||||
totalProgress = 10 + Math.floor(parseInt(match[1]) * 0.1);
|
||||
} else if (match = /Receiving objects:\s*(\d+)%/i.exec(line)) {
|
||||
totalProgress = 20 + Math.floor(parseInt(match[1]) * 0.4);
|
||||
} else if (match = /Resolving deltas:\s*(\d+)%/i.exec(line)) {
|
||||
totalProgress = 60 + Math.floor(parseInt(match[1]) * 0.4);
|
||||
}
|
||||
|
||||
if (totalProgress !== previousProgress) {
|
||||
progress.report({ increment: totalProgress - previousProgress });
|
||||
previousProgress = totalProgress;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
try {
|
||||
await this.exec(parentPath, ['clone', url.includes(' ') ? encodeURI(url) : url, folderPath, '--progress'], { cancellationToken, progress });
|
||||
await this.exec(parentPath, ['clone', url.includes(' ') ? encodeURI(url) : url, folderPath, '--progress'], { cancellationToken, onSpawn });
|
||||
} catch (err) {
|
||||
if (err.stderr) {
|
||||
err.stderr = err.stderr.replace(/^Cloning.+$/m, '').trim();
|
||||
@@ -428,11 +432,15 @@ export class Git {
|
||||
private async _exec(args: string[], options: SpawnOptions = {}): Promise<IExecutionResult<string>> {
|
||||
const child = this.spawn(args, options);
|
||||
|
||||
if (options.onSpawn) {
|
||||
options.onSpawn(child);
|
||||
}
|
||||
|
||||
if (options.input) {
|
||||
child.stdin.end(options.input, 'utf8');
|
||||
}
|
||||
|
||||
const bufferResult = await exec(child, options.cancellationToken, options.progress);
|
||||
const bufferResult = await exec(child, options.cancellationToken);
|
||||
|
||||
if (options.log !== false && bufferResult.stderr.length > 0) {
|
||||
this.log(`${bufferResult.stderr}\n`);
|
||||
|
||||
Reference in New Issue
Block a user