git: run status without buffering whole output

This commit is contained in:
Joao Moreno
2017-04-18 17:16:20 +02:00
parent d2c40f06b3
commit 6146b9739a
8 changed files with 252 additions and 79 deletions

View File

@@ -9,11 +9,9 @@ import * as fs from 'fs';
import * as path from 'path';
import * as os from 'os';
import * as cp from 'child_process';
import { EventEmitter } from 'events';
import { assign, uniqBy, groupBy, denodeify, IDisposable, toDisposable, dispose, mkdirp } from './util';
import { EventEmitter, Event } from 'vscode';
import * as nls from 'vscode-nls';
const localize = nls.loadMessageBundle();
const readdir = denodeify<string[]>(fs.readdir);
const readfile = denodeify<string>(fs.readFile);
@@ -280,8 +278,8 @@ export class Git {
private version: string;
private env: any;
private _onOutput = new EventEmitter<string>();
get onOutput(): Event<string> { return this._onOutput.event; }
private _onOutput = new EventEmitter();
get onOutput(): EventEmitter { return this._onOutput; }
constructor(options: IGitOptions) {
this.gitPath = options.gitPath;
@@ -394,7 +392,7 @@ export class Git {
}
private log(output: string): void {
this._onOutput.fire(output);
this._onOutput.emit('log', output);
}
}
@@ -403,6 +401,72 @@ export interface Commit {
message: string;
}
export class GitStatusParser {
private lastRaw = '';
private result: IFileStatus[] = [];
get status(): IFileStatus[] {
return this.result;
}
update(raw: string): void {
let i = 0;
let nextI: number | undefined;
raw = this.lastRaw + raw;
while ((nextI = this.parseEntry(raw, i)) !== undefined) {
i = nextI;
}
this.lastRaw = raw.substr(i);
}
private parseEntry(raw: string, i: number): number | undefined {
if (i + 4 >= raw.length) {
return;
}
let lastIndex: number;
const entry: IFileStatus = {
x: raw.charAt(i++),
y: raw.charAt(i++),
rename: undefined,
path: ''
};
// space
i++;
if (entry.x === 'R') {
lastIndex = raw.indexOf('\0', i);
if (lastIndex === -1) {
return;
}
entry.rename = raw.substring(i, lastIndex);
i = lastIndex + 1;
}
lastIndex = raw.indexOf('\0', i);
if (lastIndex === -1) {
return;
}
entry.path = raw.substring(i, lastIndex);
// If path ends with slash, it must be a nested git repo
if (entry.path[entry.path.length - 1] !== '/') {
this.result.push(entry);
}
return lastIndex + 1;
}
}
export class Repository {
constructor(
@@ -452,7 +516,7 @@ export class Repository {
const child = this.stream(['show', object]);
if (!child.stdout) {
return Promise.reject<string>(localize('errorBuffer', "Can't open file from git"));
return Promise.reject<string>('Can\'t open file from git');
}
return await this.doBuffer(object);
@@ -717,44 +781,24 @@ export class Repository {
}
}
async getStatus(): Promise<IFileStatus[]> {
const executionResult = await this.run(['status', '-z', '-u']);
const status = executionResult.stdout;
const result: IFileStatus[] = [];
let current: IFileStatus;
let i = 0;
getStatus(): Promise<IFileStatus[]> {
return new Promise((c, e) => {
const parser = new GitStatusParser();
const child = this.stream(['status', '-z', '-u']);
child.stdout.setEncoding('utf8');
child.stdout.on('data', (raw: string) => {
parser.update(raw);
console.log('got', parser.status.length);
});
child.on('error', e);
child.on('exit', exitCode => {
if (exitCode !== 0) {
e(new GitError({ message: 'Could not get git status.', exitCode }));
}
function readName(): string {
const start = i;
let c: string;
while ((c = status.charAt(i)) !== '\u0000') { i++; }
return status.substring(start, i++);
}
while (i < status.length) {
current = {
x: status.charAt(i++),
y: status.charAt(i++),
path: ''
};
i++;
if (current.x === 'R') {
current.rename = readName();
}
current.path = readName();
// If path ends with slash, it must be a nested git repo
if (current.path[current.path.length - 1] === '/') {
continue;
}
result.push(current);
}
return result;
c(parser.status);
});
});
}
async getHEAD(): Promise<Ref> {