Skip to content

Commit 3639bb5

Browse files
author
Benjamin Pasero
committed
Merge branch 'master' into ben/activity
2 parents 5059161 + bd96c22 commit 3639bb5

71 files changed

Lines changed: 2050 additions & 889 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

extensions/git/src/commands.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,7 @@ export class CommandCenter {
126126
@CommandCenter.Command('git.refresh')
127127
@CommandCenter.CatchErrors
128128
async refresh(): Promise<void> {
129-
await this.model.update();
129+
await this.model.status();
130130
}
131131

132132
@CommandCenter.Command('git.openChange')

extensions/git/src/main.ts

Lines changed: 2 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -5,44 +5,19 @@
55

66
'use strict';
77

8-
import { ExtensionContext, workspace, Uri, window, Disposable, Event } from 'vscode';
8+
import { ExtensionContext, workspace, window, Disposable } from 'vscode';
99
import { findGit, Git } from './git';
1010
import { Model } from './model';
1111
import { GitSCMProvider } from './scmProvider';
1212
import { CommandCenter } from './commands';
1313
import { CheckoutStatusBar, SyncStatusBar } from './statusbar';
14-
import { filterEvent, anyEvent, throttle } from './util';
14+
import { filterEvent, anyEvent } from './util';
1515
import { GitContentProvider } from './contentProvider';
1616
import { AutoFetcher } from './autofetch';
1717
import * as nls from 'vscode-nls';
18-
import { decorate, debounce } from 'core-decorators';
1918

2019
nls.config();
2120

22-
class Watcher {
23-
24-
private listener: Disposable;
25-
26-
constructor(private model: Model, onWorkspaceChange: Event<Uri>) {
27-
this.listener = onWorkspaceChange(this.eventuallyUpdateModel, this);
28-
}
29-
30-
@debounce(1000)
31-
private eventuallyUpdateModel(): void {
32-
this.updateModelAndWait();
33-
}
34-
35-
@decorate(throttle)
36-
private async updateModelAndWait(): Promise<void> {
37-
await this.model.update();
38-
await new Promise(c => setTimeout(c, 8000));
39-
}
40-
41-
dispose(): void {
42-
this.listener.dispose();
43-
}
44-
}
45-
4621
async function init(disposables: Disposable[]): Promise<void> {
4722
const rootPath = workspace.rootPath;
4823

extensions/git/src/model.ts

Lines changed: 107 additions & 104 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@
77

88
import { Uri, EventEmitter, Event, SCMResource, SCMResourceDecorations, SCMResourceGroup, Disposable } from 'vscode';
99
import { Repository, IRef, IBranch, IRemote, IPushOptions } from './git';
10-
import { throttle, anyEvent, eventToPromise } from './util';
10+
import { throttle, anyEvent, eventToPromise, filterEvent, mapEvent } from './util';
11+
import { watch } from './watch';
1112
import { decorate, memoize, debounce } from 'core-decorators';
1213
import * as path from 'path';
1314

@@ -242,7 +243,22 @@ export class Model {
242243
private repository: Repository,
243244
onWorkspaceChange: Event<Uri>
244245
) {
245-
onWorkspaceChange(this.onWorkspaceChange, this, this.disposables);
246+
/* We use the native Node `watch` for faster, non debounced events.
247+
* That way we hopefully get the events during the operations we're
248+
* performing, thus sparing useless `git status` calls to refresh
249+
* the model's state.
250+
*/
251+
const gitPath = path.join(_repositoryRoot, '.git');
252+
const { event, disposable } = watch(gitPath);
253+
const onGitChange = mapEvent(event, ({ filename }) => Uri.file(path.join(gitPath, filename)));
254+
const onRelevantGitChange = filterEvent(onGitChange, uri => !/\/\.git\/index\.lock$/.test(uri.fsPath));
255+
onRelevantGitChange(this.onFSChange, this, this.disposables);
256+
this.disposables.push(disposable);
257+
258+
const onNonGitChange = filterEvent(onWorkspaceChange, uri => !/\/\.git\//.test(uri.fsPath));
259+
onNonGitChange(this.onFSChange, this, this.disposables);
260+
261+
this.status();
246262
}
247263

248264
get repositoryRoot(): string {
@@ -265,90 +281,18 @@ export class Model {
265281
}
266282

267283
@decorate(throttle)
268-
async update(): Promise<void> {
269-
await this.run(Operation.Status, async () => {
270-
const status = await this.repository.getStatus();
271-
let HEAD: IBranch | undefined;
272-
273-
try {
274-
HEAD = await this.repository.getHEAD();
275-
276-
if (HEAD.name) {
277-
try {
278-
HEAD = await this.repository.getBranch(HEAD.name);
279-
} catch (err) {
280-
// noop
281-
}
282-
}
283-
} catch (err) {
284-
// noop
285-
}
286-
287-
const [refs, remotes] = await Promise.all([this.repository.getRefs(), this.repository.getRemotes()]);
288-
289-
this._HEAD = HEAD;
290-
this._refs = refs;
291-
this._remotes = remotes;
292-
293-
const index: Resource[] = [];
294-
const workingTree: Resource[] = [];
295-
const merge: Resource[] = [];
296-
297-
status.forEach(raw => {
298-
const uri = Uri.file(path.join(this.repositoryRoot, raw.path));
299-
300-
switch (raw.x + raw.y) {
301-
case '??': return workingTree.push(new Resource(uri, Status.UNTRACKED));
302-
case '!!': return workingTree.push(new Resource(uri, Status.IGNORED));
303-
case 'DD': return merge.push(new Resource(uri, Status.BOTH_DELETED));
304-
case 'AU': return merge.push(new Resource(uri, Status.ADDED_BY_US));
305-
case 'UD': return merge.push(new Resource(uri, Status.DELETED_BY_THEM));
306-
case 'UA': return merge.push(new Resource(uri, Status.ADDED_BY_THEM));
307-
case 'DU': return merge.push(new Resource(uri, Status.DELETED_BY_US));
308-
case 'AA': return merge.push(new Resource(uri, Status.BOTH_ADDED));
309-
case 'UU': return merge.push(new Resource(uri, Status.BOTH_MODIFIED));
310-
}
311-
312-
let isModifiedInIndex = false;
313-
314-
switch (raw.x) {
315-
case 'M': index.push(new Resource(uri, Status.INDEX_MODIFIED)); isModifiedInIndex = true; break;
316-
case 'A': index.push(new Resource(uri, Status.INDEX_ADDED)); break;
317-
case 'D': index.push(new Resource(uri, Status.INDEX_DELETED)); break;
318-
case 'R': index.push(new Resource(uri, Status.INDEX_RENAMED/*, raw.rename*/)); break;
319-
case 'C': index.push(new Resource(uri, Status.INDEX_COPIED)); break;
320-
}
321-
322-
switch (raw.y) {
323-
case 'M': workingTree.push(new Resource(uri, Status.MODIFIED/*, raw.rename*/)); break;
324-
case 'D': workingTree.push(new Resource(uri, Status.DELETED/*, raw.rename*/)); break;
325-
}
326-
});
327-
328-
this._mergeGroup = new MergeGroup(merge);
329-
this._indexGroup = new IndexGroup(index);
330-
this._workingTreeGroup = new WorkingTreeGroup(workingTree);
331-
332-
this._onDidChange.fire(this.resources);
333-
});
284+
async status(): Promise<void> {
285+
await this.run(Operation.Status);
334286
}
335287

336288
@decorate(throttle)
337289
async stage(...resources: Resource[]): Promise<void> {
338-
await this.run(Operation.Stage, async () => {
339-
const paths = resources.map(r => r.uri.fsPath);
340-
await this.repository.add(paths);
341-
await this.update();
342-
});
290+
await this.run(Operation.Stage, () => this.repository.add(resources.map(r => r.uri.fsPath)));
343291
}
344292

345293
@decorate(throttle)
346294
async unstage(...resources: Resource[]): Promise<void> {
347-
await this.run(Operation.Unstage, async () => {
348-
const paths = resources.map(r => r.uri.fsPath);
349-
await this.repository.revertFiles('HEAD', paths);
350-
await this.update();
351-
});
295+
await this.run(Operation.Unstage, () => this.repository.revertFiles('HEAD', resources.map(r => r.uri.fsPath)));
352296
}
353297

354298
@decorate(throttle)
@@ -359,7 +303,6 @@ export class Model {
359303
}
360304

361305
await this.repository.commit(message, opts);
362-
await this.update();
363306
});
364307
}
365308

@@ -393,72 +336,132 @@ export class Model {
393336
}
394337

395338
await Promise.all(promises);
396-
await this.update();
397339
});
398340
}
399341

400342
@decorate(throttle)
401343
async branch(name: string): Promise<void> {
402-
await this.run(Operation.Branch, async () => {
403-
await this.repository.branch(name, true);
404-
await this.update();
405-
});
344+
await this.run(Operation.Branch, () => this.repository.branch(name, true));
406345
}
407346

408347
@decorate(throttle)
409348
async checkout(treeish: string): Promise<void> {
410-
await this.run(Operation.Checkout, async () => {
411-
await this.repository.checkout(treeish, []);
412-
await this.update();
413-
});
349+
await this.run(Operation.Checkout, () => this.repository.checkout(treeish, []));
414350
}
415351

416352
@decorate(throttle)
417353
async fetch(): Promise<void> {
418-
await this.run(Operation.Fetch, async () => {
419-
await this.repository.fetch();
420-
await this.update();
421-
});
354+
await this.run(Operation.Fetch, () => this.repository.fetch());
422355
}
423356

424357
@decorate(throttle)
425358
async sync(): Promise<void> {
426-
await this.run(Operation.Sync, async () => {
427-
await this.repository.sync();
428-
await this.update();
429-
});
359+
await this.run(Operation.Sync, () => this.repository.sync());
430360
}
431361

432362
@decorate(throttle)
433363
async push(remote?: string, name?: string, options?: IPushOptions): Promise<void> {
434-
await this.run(Operation.Push, async () => {
435-
await this.repository.push(remote, name, options);
436-
await this.update();
437-
});
364+
await this.run(Operation.Push, () => this.repository.push(remote, name, options));
438365
}
439366

440-
private async run(operation: Operation, fn: () => Promise<void>): Promise<void> {
367+
private async run(operation: Operation, fn: () => Promise<void> = () => Promise.resolve()): Promise<void> {
441368
this._operations = this._operations.start(operation);
442369
this._onRunOperation.fire(operation);
443370

444371
try {
445372
await fn();
373+
await this.update();
446374
} finally {
447375
this._operations = this._operations.end(operation);
448376
this._onDidRunOperation.fire(operation);
449377
}
450378
}
451379

380+
@decorate(throttle)
381+
private async update(): Promise<void> {
382+
const status = await this.repository.getStatus();
383+
let HEAD: IBranch | undefined;
384+
385+
try {
386+
HEAD = await this.repository.getHEAD();
387+
388+
if (HEAD.name) {
389+
try {
390+
HEAD = await this.repository.getBranch(HEAD.name);
391+
} catch (err) {
392+
// noop
393+
}
394+
}
395+
} catch (err) {
396+
// noop
397+
}
398+
399+
const [refs, remotes] = await Promise.all([this.repository.getRefs(), this.repository.getRemotes()]);
400+
401+
this._HEAD = HEAD;
402+
this._refs = refs;
403+
this._remotes = remotes;
404+
405+
const index: Resource[] = [];
406+
const workingTree: Resource[] = [];
407+
const merge: Resource[] = [];
408+
409+
status.forEach(raw => {
410+
const uri = Uri.file(path.join(this.repositoryRoot, raw.path));
411+
412+
switch (raw.x + raw.y) {
413+
case '??': return workingTree.push(new Resource(uri, Status.UNTRACKED));
414+
case '!!': return workingTree.push(new Resource(uri, Status.IGNORED));
415+
case 'DD': return merge.push(new Resource(uri, Status.BOTH_DELETED));
416+
case 'AU': return merge.push(new Resource(uri, Status.ADDED_BY_US));
417+
case 'UD': return merge.push(new Resource(uri, Status.DELETED_BY_THEM));
418+
case 'UA': return merge.push(new Resource(uri, Status.ADDED_BY_THEM));
419+
case 'DU': return merge.push(new Resource(uri, Status.DELETED_BY_US));
420+
case 'AA': return merge.push(new Resource(uri, Status.BOTH_ADDED));
421+
case 'UU': return merge.push(new Resource(uri, Status.BOTH_MODIFIED));
422+
}
423+
424+
let isModifiedInIndex = false;
425+
426+
switch (raw.x) {
427+
case 'M': index.push(new Resource(uri, Status.INDEX_MODIFIED)); isModifiedInIndex = true; break;
428+
case 'A': index.push(new Resource(uri, Status.INDEX_ADDED)); break;
429+
case 'D': index.push(new Resource(uri, Status.INDEX_DELETED)); break;
430+
case 'R': index.push(new Resource(uri, Status.INDEX_RENAMED/*, raw.rename*/)); break;
431+
case 'C': index.push(new Resource(uri, Status.INDEX_COPIED)); break;
432+
}
433+
434+
switch (raw.y) {
435+
case 'M': workingTree.push(new Resource(uri, Status.MODIFIED/*, raw.rename*/)); break;
436+
case 'D': workingTree.push(new Resource(uri, Status.DELETED/*, raw.rename*/)); break;
437+
}
438+
});
439+
440+
this._mergeGroup = new MergeGroup(merge);
441+
this._indexGroup = new IndexGroup(index);
442+
this._workingTreeGroup = new WorkingTreeGroup(workingTree);
443+
444+
this._onDidChange.fire(this.resources);
445+
}
446+
447+
private onFSChange(uri: Uri): void {
448+
if (!this.operations.isIdle()) {
449+
return;
450+
}
451+
452+
this.eventuallyUpdateWhenIdleAndWait();
453+
}
454+
452455
@debounce(1000)
453-
private onWorkspaceChange(): void {
456+
private eventuallyUpdateWhenIdleAndWait(): void {
454457
this.updateWhenIdleAndWait();
455458
}
456459

457460
@decorate(throttle)
458461
private async updateWhenIdleAndWait(): Promise<void> {
459462
await this.whenIdle();
460-
await this.update();
461-
await new Promise(c => setTimeout(c, 7000));
463+
await this.status();
464+
await new Promise(c => setTimeout(c, 5000));
462465
}
463466

464467
private async whenIdle(): Promise<void> {

extensions/git/src/scmProvider.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@ export class GitSCMProvider implements SCMProvider {
1818
get label(): string { return 'Git'; }
1919

2020
constructor(private model: Model, private commandCenter: CommandCenter) {
21-
model.update();
2221
scm.registerSCMProvider('git', this);
2322
}
2423

extensions/git/src/watch.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
/*---------------------------------------------------------------------------------------------
2+
* Copyright (c) Microsoft Corporation. All rights reserved.
3+
* Licensed under the MIT License. See License.txt in the project root for license information.
4+
*--------------------------------------------------------------------------------------------*/
5+
6+
'use strict';
7+
8+
import { EventEmitter, Event, Disposable } from 'vscode';
9+
import * as fs from 'fs';
10+
11+
export interface FSEvent {
12+
eventType: string;
13+
filename: string;
14+
}
15+
16+
export function watch(path: string): { event: Event<FSEvent>; disposable: Disposable; } {
17+
const emitter = new EventEmitter<FSEvent>();
18+
const event = emitter.event;
19+
const watcher = fs.watch(path, (eventType, filename) => emitter.fire({ eventType, filename }));
20+
const disposable = new Disposable(() => watcher.close());
21+
22+
return { event, disposable };
23+
}

src/vs/base/browser/ui/iconLabel/iconlabel.css

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77

88
.monaco-icon-label {
99
display: inline-block; /* required for icons support :before rule */
10+
overflow: hidden;
11+
text-overflow: ellipsis;
1012
}
1113

1214
.monaco-icon-label::before {

0 commit comments

Comments
 (0)