Skip to content

Commit 9e762e3

Browse files
committed
Use promises to asynchronously run git commands
1 parent 9495d3b commit 9e762e3

File tree

6 files changed

+193
-140
lines changed

6 files changed

+193
-140
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
## 1.3.1 - 2019-02-17
44
* View the Visual Studio Code Diff of a file change in a commit, by clicking on the file in the commit details view.
5+
* All git commands are run asynchronously to improve responsiveness.
56

67
## 1.3.0 - 2019-02-16
78
* Commit details view (click on a commit to open it). This shows the full commit details, and a tree view of all file changes in the commit.

src/dataSource.ts

Lines changed: 166 additions & 118 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import * as cp from 'child_process';
22
import { Config } from './config';
3-
import { GitCommandStatus, GitCommit, GitCommitDetails, GitCommitNode, GitFileChangeType, GitRef, GitResetMode, GitUnsavedChanges } from './types';
3+
import { GitCommandStatus, GitCommit, GitCommitDetails, GitCommitNode, GitFileChangeType, GitRef, GitResetMode, GitUnsavedChangesCmdResp } from './types';
44

55
const eolRegex = /\r\n|\r|\n/g;
66
const gitLogSeparator = 'XX7Nal-YARtTpjCikii9nJxER19D6diSyk-AWkPb';
@@ -14,38 +14,41 @@ export class DataSource {
1414
this.workspaceDir = workspaceDir;
1515
}
1616

17-
public isGitRepository(): boolean {
18-
try {
19-
cp.execSync('git rev-parse --git-dir', { cwd: this.workspaceDir });
20-
return true;
21-
} catch (e) {
22-
return false;
23-
}
17+
public isGitRepository() {
18+
return new Promise<boolean>((resolve) => {
19+
cp.exec('git rev-parse --git-dir', { cwd: this.workspaceDir }, (err) => {
20+
resolve(!err);
21+
});
22+
});
2423
}
2524

26-
public getBranches(showRemoteBranches: boolean): string[] {
27-
try {
28-
let lines = cp.execSync('git branch' + (showRemoteBranches ? ' -a' : ''), { cwd: this.workspaceDir }).toString().split(eolRegex);
29-
let branches: string[] = [];
30-
for (let i = 0; i < lines.length - 1; i++) {
31-
let active = lines[i][0] === '*';
32-
let name = lines[i].substring(2).split(' ')[0];
33-
if (active) {
34-
branches.unshift(name);
25+
public getBranches(showRemoteBranches: boolean) {
26+
return new Promise<string[]>((resolve) => {
27+
cp.exec('git branch' + (showRemoteBranches ? ' -a' : ''), { cwd: this.workspaceDir }, (err, stdout) => {
28+
if (!err) {
29+
let lines = stdout.split(eolRegex);
30+
let branches: string[] = [];
31+
for (let i = 0; i < lines.length - 1; i++) {
32+
let active = lines[i][0] === '*';
33+
let name = lines[i].substring(2).split(' ')[0];
34+
if (active) {
35+
branches.unshift(name);
36+
} else {
37+
branches.push(name);
38+
}
39+
}
40+
resolve(branches);
3541
} else {
36-
branches.push(name);
42+
resolve([]);
3743
}
38-
}
39-
return branches;
40-
} catch (e) {
41-
return [];
42-
}
44+
});
45+
});
4346
}
4447

45-
public getCommits(branch: string, maxCommits: number, showRemoteBranches: boolean, currentBranch: string | null) {
48+
public async getCommits(branch: string, maxCommits: number, showRemoteBranches: boolean, currentBranch: string | null) {
4649
let i, j;
47-
let commits = this.getGitLog(branch, maxCommits + 1, showRemoteBranches);
48-
let refs = this.getRefs(showRemoteBranches);
50+
let commits = await this.getGitLog(branch, maxCommits + 1, showRemoteBranches);
51+
let refs = await this.getRefs(showRemoteBranches);
4952
let unsavedChanges = null;
5053

5154
let moreCommitsAvailable = commits.length === maxCommits + 1;
@@ -59,7 +62,7 @@ export class DataSource {
5962
}
6063
}
6164
if (currentBranchHash !== null && (branch === '' || branch === currentBranch)) {
62-
unsavedChanges = (new Config()).showUncommittedChanges() ? this.getGitUnsavedChanges() : null;
65+
unsavedChanges = (new Config()).showUncommittedChanges() ? await this.getGitUnsavedChanges() : null;
6366
if (unsavedChanges !== null) {
6467
for (j = 0; j < commits.length; j++) {
6568
if (currentBranchHash === commits[j].hash) {
@@ -99,136 +102,181 @@ export class DataSource {
99102
return { commits: commitNodes, moreCommitsAvailable: moreCommitsAvailable };
100103
}
101104

102-
public commitDetails(commitHash: string) {
105+
public async commitDetails(commitHash: string) {
103106
try {
104-
let lines = cp.execSync('git show --quiet ' + commitHash + ' --format="' + gitCommitDetailsFormat + '"', { cwd: this.workspaceDir }).toString().split(eolRegex);
105-
let commitInfo = lines[0].split(gitLogSeparator);
106-
let details: GitCommitDetails = {
107-
hash: commitInfo[0],
108-
parents: commitInfo[1].split(' '),
109-
author: commitInfo[2],
110-
email: commitInfo[3],
111-
date: parseInt(commitInfo[4]),
112-
committer: commitInfo[5],
113-
body: commitInfo[6],
114-
fileChanges: []
115-
};
116-
107+
let details = await new Promise<GitCommitDetails>((resolve, reject) => {
108+
cp.exec('git show --quiet ' + commitHash + ' --format="' + gitCommitDetailsFormat + '"', { cwd: this.workspaceDir }, (err, stdout) => {
109+
if (!err) {
110+
let lines = stdout.split(eolRegex);
111+
let commitInfo = lines[0].split(gitLogSeparator);
112+
resolve({
113+
hash: commitInfo[0],
114+
parents: commitInfo[1].split(' '),
115+
author: commitInfo[2],
116+
email: commitInfo[3],
117+
date: parseInt(commitInfo[4]),
118+
committer: commitInfo[5],
119+
body: commitInfo[6],
120+
fileChanges: []
121+
});
122+
} else {
123+
reject();
124+
}
125+
});
126+
});
117127
let fileLookup: { [file: string]: number } = {};
118-
lines = cp.execSync('git diff-tree --name-status -r -m --root --find-renames --diff-filter=AMDR ' + commitHash, { cwd: this.workspaceDir }).toString().split(eolRegex);
119-
for (let i = 1; i < lines.length - 1; i++) {
120-
let line = lines[i].split('\t');
121-
if (line.length < 2) break;
122-
let oldFilePath = line[1].replace(/\\/g, '/'), newFilePath = line[line.length-1].replace(/\\/g, '/');
123-
fileLookup[newFilePath] = details.fileChanges.length;
124-
details.fileChanges.push({ oldFilePath: oldFilePath, newFilePath: newFilePath, type: <GitFileChangeType>line[0][0], additions: null, deletions: null });
125-
}
126-
lines = cp.execSync('git diff-tree --numstat -r -m --root --find-renames --diff-filter=AMDR ' + commitHash, { cwd: this.workspaceDir }).toString().split(eolRegex);
127-
for (let i = 1; i < lines.length - 1; i++) {
128-
let line = lines[i].split('\t');
129-
if (line.length !== 3) break;
130-
let fileName = line[2].replace(/(.*){.* => (.*)}/, '$1$2').replace(/.* => (.*)/, '$1');
131-
if (typeof fileLookup[fileName] === 'number') {
132-
details.fileChanges[fileLookup[fileName]].additions = parseInt(line[0]);
133-
details.fileChanges[fileLookup[fileName]].deletions = parseInt(line[1]);
134-
}
135-
}
128+
await new Promise((resolve, reject) => {
129+
cp.exec('git diff-tree --name-status -r -m --root --find-renames --diff-filter=AMDR ' + commitHash, { cwd: this.workspaceDir }, (err, stdout) => {
130+
if (!err) {
131+
let lines = stdout.split(eolRegex);
132+
for (let i = 1; i < lines.length - 1; i++) {
133+
let line = lines[i].split('\t');
134+
if (line.length < 2) break;
135+
let oldFilePath = line[1].replace(/\\/g, '/'), newFilePath = line[line.length - 1].replace(/\\/g, '/');
136+
fileLookup[newFilePath] = details.fileChanges.length;
137+
details.fileChanges.push({ oldFilePath: oldFilePath, newFilePath: newFilePath, type: <GitFileChangeType>line[0][0], additions: null, deletions: null });
138+
}
139+
resolve();
140+
} else {
141+
reject();
142+
}
143+
});
144+
});
145+
await new Promise((resolve, reject) => {
146+
cp.exec('git diff-tree --numstat -r -m --root --find-renames --diff-filter=AMDR ' + commitHash, { cwd: this.workspaceDir }, (err, stdout) => {
147+
if (!err) {
148+
let lines = stdout.split(eolRegex);
149+
for (let i = 1; i < lines.length - 1; i++) {
150+
let line = lines[i].split('\t');
151+
if (line.length !== 3) break;
152+
let fileName = line[2].replace(/(.*){.* => (.*)}/, '$1$2').replace(/.* => (.*)/, '$1');
153+
if (typeof fileLookup[fileName] === 'number') {
154+
details.fileChanges[fileLookup[fileName]].additions = parseInt(line[0]);
155+
details.fileChanges[fileLookup[fileName]].deletions = parseInt(line[1]);
156+
}
157+
}
158+
resolve();
159+
} else {
160+
reject();
161+
}
162+
163+
});
164+
});
136165
return details;
137166
} catch (e) {
138167
return null;
139168
}
140169
}
141170

142-
public getFile(commitHash: string, filePath: string) {
143-
try {
144-
return cp.execSync('git show "' + commitHash + '":"' + filePath + '"', { cwd: this.workspaceDir }).toString();
145-
} catch (e) {
146-
return '';
147-
}
171+
public async getCommitFile(commitHash: string, filePath: string) {
172+
return new Promise<string>((resolve) => {
173+
cp.exec('git show "' + commitHash + '":"' + filePath + '"', { cwd: this.workspaceDir }, (err, stdout) => {
174+
resolve(!err ? stdout : '');
175+
});
176+
});
148177
}
149178

150-
public addTag(tagName: string, commitHash: string): GitCommandStatus {
179+
public addTag(tagName: string, commitHash: string) {
151180
return this.runGitCommand('git tag -a ' + escapeRefName(tagName) + ' -m "" ' + commitHash);
152181
}
153182

154-
public deleteTag(tagName: string): GitCommandStatus {
183+
public deleteTag(tagName: string) {
155184
return this.runGitCommand('git tag -d ' + escapeRefName(tagName));
156185
}
157186

158-
public createBranch(branchName: string, commitHash: string): GitCommandStatus {
187+
public createBranch(branchName: string, commitHash: string) {
159188
return this.runGitCommand('git branch ' + escapeRefName(branchName) + ' ' + commitHash);
160189
}
161190

162-
public checkoutBranch(branchName: string, remoteBranch: string | null): GitCommandStatus {
191+
public checkoutBranch(branchName: string, remoteBranch: string | null) {
163192
return this.runGitCommand('git checkout ' + (remoteBranch === null ? escapeRefName(branchName) : ' -b ' + escapeRefName(branchName) + ' ' + escapeRefName(remoteBranch)));
164193
}
165194

166-
public deleteBranch(branchName: string, forceDelete: boolean): GitCommandStatus {
195+
public deleteBranch(branchName: string, forceDelete: boolean) {
167196
return this.runGitCommand('git branch --delete' + (forceDelete ? ' --force' : '') + ' ' + escapeRefName(branchName));
168197
}
169198

170-
public renameBranch(oldName: string, newName: string): GitCommandStatus {
199+
public renameBranch(oldName: string, newName: string) {
171200
return this.runGitCommand('git branch -m ' + escapeRefName(oldName) + ' ' + escapeRefName(newName));
172201
}
173202

174-
public resetToCommit(commitHash: string, resetMode: GitResetMode): GitCommandStatus {
203+
public resetToCommit(commitHash: string, resetMode: GitResetMode) {
175204
return this.runGitCommand('git reset --' + resetMode + ' ' + commitHash);
176205
}
177206

178-
private runGitCommand(command: string): GitCommandStatus {
179-
try {
180-
cp.execSync(command, { cwd: this.workspaceDir });
181-
return null;
182-
} catch (e) {
183-
let lines = e.message.split(eolRegex);
184-
return lines.slice(1, lines.length - 1).join('\n');
185-
}
207+
private async runGitCommand(command: string) {
208+
return new Promise<GitCommandStatus>((resolve) => {
209+
cp.exec(command, { cwd: this.workspaceDir }, (err) => {
210+
if (!err) {
211+
resolve(null);
212+
} else {
213+
let lines = err.message.split(eolRegex);
214+
resolve(lines.slice(1, lines.length - 1).join('\n'));
215+
}
216+
});
217+
});
186218
}
187219

188-
private getRefs(showRemoteBranches: boolean): GitRef[] {
189-
try {
190-
let lines = cp.execSync('git show-ref ' + (showRemoteBranches ? '' : '--heads --tags') + ' -d', { cwd: this.workspaceDir }).toString().split(eolRegex);
191-
let refs: GitRef[] = [];
192-
for (let i = 0; i < lines.length - 1; i++) {
193-
let line = lines[i].split(' ');
194-
if (line.length < 2) continue;
195-
196-
let hash = line.shift()!;
197-
let ref = line.join(' ');
198-
199-
if (ref.startsWith('refs/heads/')) {
200-
refs.push({ hash: hash, name: ref.substring(11), type: 'head' });
201-
} else if (ref.startsWith('refs/tags/')) {
202-
refs.push({ hash: hash, name: (ref.endsWith('^{}') ? ref.substring(10, ref.length - 3) : ref.substring(10)), type: 'tag' });
203-
} else if (ref.startsWith('refs/remotes/')) {
204-
refs.push({ hash: hash, name: ref.substring(13), type: 'remote' });
220+
private async getRefs(showRemoteBranches: boolean) {
221+
return new Promise<GitRef[]>((resolve) => {
222+
cp.exec('git show-ref ' + (showRemoteBranches ? '' : '--heads --tags') + ' -d', { cwd: this.workspaceDir }, (err, stdout) => {
223+
if (!err) {
224+
let lines = stdout.split(eolRegex);
225+
let refs: GitRef[] = [];
226+
for (let i = 0; i < lines.length - 1; i++) {
227+
let line = lines[i].split(' ');
228+
if (line.length < 2) continue;
229+
230+
let hash = line.shift()!;
231+
let ref = line.join(' ');
232+
233+
if (ref.startsWith('refs/heads/')) {
234+
refs.push({ hash: hash, name: ref.substring(11), type: 'head' });
235+
} else if (ref.startsWith('refs/tags/')) {
236+
refs.push({ hash: hash, name: (ref.endsWith('^{}') ? ref.substring(10, ref.length - 3) : ref.substring(10)), type: 'tag' });
237+
} else if (ref.startsWith('refs/remotes/')) {
238+
refs.push({ hash: hash, name: ref.substring(13), type: 'remote' });
239+
}
240+
}
241+
resolve(refs);
242+
} else {
243+
resolve([]);
205244
}
206-
}
207-
return refs;
208-
} catch (e) {
209-
return [];
210-
}
245+
});
246+
});
211247
}
212248

213-
private getGitLog(branch: string, num: number, showRemoteBranches: boolean): GitCommit[] {
214-
try {
215-
let lines = cp.execSync('git log ' + (branch !== '' ? escapeRefName(branch) : '--branches' + (showRemoteBranches ? ' --remotes' : '')) + ' --max-count=' + num + ' --format="' + gitLogFormat + '"', { cwd: this.workspaceDir }).toString().split(eolRegex);
216-
let gitCommits: GitCommit[] = [];
217-
for (let i = 0; i < lines.length - 1; i++) {
218-
let line = lines[i].split(gitLogSeparator);
219-
if (line.length !== 6) break;
220-
gitCommits.push({ hash: line[0], parentHashes: line[1].split(' '), author: line[2], email: line[3], date: parseInt(line[4]), message: line[5] });
221-
}
222-
return gitCommits;
223-
} catch (e) {
224-
return [];
225-
}
249+
private async getGitLog(branch: string, num: number, showRemoteBranches: boolean) {
250+
return new Promise<GitCommit[]>((resolve) => {
251+
cp.exec('git log ' + (branch !== '' ? escapeRefName(branch) : '--branches' + (showRemoteBranches ? ' --remotes' : '')) + ' --max-count=' + num + ' --format="' + gitLogFormat + '"', { cwd: this.workspaceDir }, (err, stdout) => {
252+
if (!err) {
253+
let lines = stdout.split(eolRegex);
254+
let gitCommits: GitCommit[] = [];
255+
for (let i = 0; i < lines.length - 1; i++) {
256+
let line = lines[i].split(gitLogSeparator);
257+
if (line.length !== 6) break;
258+
gitCommits.push({ hash: line[0], parentHashes: line[1].split(' '), author: line[2], email: line[3], date: parseInt(line[4]), message: line[5] });
259+
}
260+
resolve(gitCommits);
261+
} else {
262+
resolve([]);
263+
}
264+
});
265+
});
226266
}
227267

228-
private getGitUnsavedChanges(): GitUnsavedChanges | null {
268+
private getGitUnsavedChanges() {
229269
try {
230-
let lines = cp.execSync('git status -s --branch --untracked-files --porcelain', { cwd: this.workspaceDir }).toString().split(eolRegex);
231-
return lines.length > 2 ? { branch: lines[0].substring(3).split('...')[0], changes: lines.length - 2 } : null;
270+
return new Promise<GitUnsavedChangesCmdResp>((resolve, reject) => {
271+
cp.exec('git status -s --branch --untracked-files --porcelain', { cwd: this.workspaceDir }, (err, stdout) => {
272+
if (!err) {
273+
let lines = stdout.split(eolRegex);
274+
resolve(lines.length > 2 ? { branch: lines[0].substring(3).split('...')[0], changes: lines.length - 2 } : null);
275+
} else {
276+
reject();
277+
}
278+
});
279+
});
232280
} catch (e) {
233281
return null;
234282
}

src/diffDocProvider.ts

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -25,14 +25,15 @@ export class DiffDocProvider implements vscode.TextDocumentContentProvider {
2525

2626
public provideTextDocumentContent(uri: vscode.Uri): string | Thenable<string> {
2727
let document = this.docs.get(uri.toString());
28-
if (document) {
29-
return document.value;
30-
}
28+
if (document) return document.value;
29+
if (this.dataSource === null) return '';
3130

3231
let request = decodeDiffDocUri(uri);
33-
document = new DiffDocument(this.dataSource !== null ? this.dataSource.getFile(request.commit, request.filePath) : '');
34-
this.docs.set(uri.toString(), document);
35-
return document.value;
32+
return this.dataSource.getCommitFile(request.commit, request.filePath).then((data) => {
33+
let document = new DiffDocument(data);
34+
this.docs.set(uri.toString(), document);
35+
return document.value;
36+
});
3637
}
3738
}
3839

@@ -49,9 +50,9 @@ class DiffDocument {
4950
}
5051

5152
export function encodeDiffDocUri(path: string, commit: string): vscode.Uri {
52-
return vscode.Uri.parse(DiffDocProvider.scheme + ':'+path.replace(/\\/g, '/')+'?'+commit);
53+
return vscode.Uri.parse(DiffDocProvider.scheme + ':' + path.replace(/\\/g, '/') + '?' + commit);
5354
}
5455

5556
export function decodeDiffDocUri(uri: vscode.Uri) {
56-
return {filePath: uri.path, commit:uri.query};
57+
return { filePath: uri.path, commit: uri.query };
5758
}

0 commit comments

Comments
 (0)