Skip to content

Commit 7b1a326

Browse files
committed
1 parent 320fffc commit 7b1a326

2 files changed

Lines changed: 72 additions & 14 deletions

File tree

extensions/git/src/repository.ts

Lines changed: 53 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
import { commands, Uri, Command, EventEmitter, Event, scm, SourceControl, SourceControlInputBox, SourceControlResourceGroup, SourceControlResourceState, SourceControlResourceDecorations, SourceControlInputBoxValidation, Disposable, ProgressLocation, window, workspace, WorkspaceEdit, ThemeColor, DecorationData, Memento, SourceControlInputBoxValidationType, OutputChannel, LogLevel, env } from 'vscode';
77
import { Repository as BaseRepository, Commit, Stash, GitError, Submodule, CommitOptions, ForcePushMode } from './git';
8-
import { anyEvent, filterEvent, eventToPromise, dispose, find, isDescendant, IDisposable, onceEvent, EmptyDisposable, debounceEvent, toDisposable, combinedDisposable } from './util';
8+
import { anyEvent, filterEvent, eventToPromise, dispose, find, isDescendant, IDisposable, onceEvent, EmptyDisposable, debounceEvent, combinedDisposable, watch, IFileWatcher } from './util';
99
import { memoize, throttle, debounce } from './decorators';
1010
import { toGitUri } from './uri';
1111
import { AutoFetcher } from './autofetch';
@@ -480,6 +480,49 @@ class FileEventLogger {
480480
}
481481
}
482482

483+
class DotGitWatcher implements IFileWatcher {
484+
485+
readonly event: Event<Uri>;
486+
487+
private emitter = new EventEmitter<Uri>();
488+
private transientDisposables: IDisposable[] = [];
489+
private disposables: IDisposable[] = [];
490+
491+
constructor(private repository: Repository) {
492+
const rootWatcher = watch(repository.dotGit);
493+
this.disposables.push(rootWatcher);
494+
495+
const filteredRootWatcher = filterEvent(rootWatcher.event, uri => !/\/\.git(\/index\.lock)?$/.test(uri.path));
496+
this.event = anyEvent(filteredRootWatcher, this.emitter.event);
497+
498+
repository.onDidRunGitStatus(this.updateTransientWatchers, this, this.disposables);
499+
this.updateTransientWatchers();
500+
}
501+
502+
private updateTransientWatchers() {
503+
this.transientDisposables = dispose(this.transientDisposables);
504+
505+
if (!this.repository.HEAD || !this.repository.HEAD.upstream) {
506+
return;
507+
}
508+
509+
this.transientDisposables = dispose(this.transientDisposables);
510+
511+
const { name, remote } = this.repository.HEAD.upstream;
512+
const upstreamPath = path.join(this.repository.dotGit, 'refs', 'remotes', remote, name);
513+
514+
const upstreamWatcher = watch(upstreamPath);
515+
this.transientDisposables.push(upstreamWatcher);
516+
upstreamWatcher.event(this.emitter.fire, this.emitter, this.transientDisposables);
517+
}
518+
519+
dispose() {
520+
this.emitter.dispose();
521+
this.transientDisposables = dispose(this.transientDisposables);
522+
this.disposables = dispose(this.disposables);
523+
}
524+
}
525+
483526
export class Repository implements Disposable {
484527

485528
private _onDidChangeRepository = new EventEmitter<Uri>();
@@ -577,11 +620,14 @@ export class Repository implements Disposable {
577620
return this.repository.root;
578621
}
579622

623+
get dotGit(): string {
624+
return this.repository.dotGit;
625+
}
626+
580627
private isRepositoryHuge = false;
581628
private didWarnAboutLimit = false;
582629
private isFreshRepository: boolean | undefined = undefined;
583630

584-
585631
private disposables: Disposable[] = [];
586632

587633
constructor(
@@ -596,23 +642,19 @@ export class Repository implements Disposable {
596642
const onWorkspaceRepositoryFileChange = filterEvent(onWorkspaceFileChange, uri => isDescendant(repository.root, uri.fsPath));
597643
const onWorkspaceWorkingTreeFileChange = filterEvent(onWorkspaceRepositoryFileChange, uri => !/\/\.git($|\/)/.test(uri.path));
598644

599-
const dotGitWatcher = fs.watch(repository.dotGit);
600-
const onDotGitFileChangeEmitter = new EventEmitter<Uri>();
601-
dotGitWatcher.on('change', (_, e) => onDotGitFileChangeEmitter.fire(Uri.file(path.join(repository.dotGit, e as string))));
602-
dotGitWatcher.on('error', err => console.error(err));
603-
this.disposables.push(toDisposable(() => dotGitWatcher.close()));
604-
const onDotGitFileChange = filterEvent(onDotGitFileChangeEmitter.event, uri => !/\/\.git(\/index\.lock)?$/.test(uri.path));
645+
const dotGitFileWatcher = new DotGitWatcher(this);
646+
this.disposables.push(dotGitFileWatcher);
605647

606648
// FS changes should trigger `git status`:
607649
// - any change inside the repository working tree
608650
// - any change whithin the first level of the `.git` folder, except the folder itself and `index.lock`
609-
const onFileChange = anyEvent(onWorkspaceWorkingTreeFileChange, onDotGitFileChange);
651+
const onFileChange = anyEvent(onWorkspaceWorkingTreeFileChange, dotGitFileWatcher.event);
610652
onFileChange(this.onFileChange, this, this.disposables);
611653

612654
// Relevate repository changes should trigger virtual document change events
613-
onDotGitFileChange(this._onDidChangeRepository.fire, this._onDidChangeRepository, this.disposables);
655+
dotGitFileWatcher.event(this._onDidChangeRepository.fire, this._onDidChangeRepository, this.disposables);
614656

615-
this.disposables.push(new FileEventLogger(onWorkspaceWorkingTreeFileChange, onDotGitFileChange, outputChannel));
657+
this.disposables.push(new FileEventLogger(onWorkspaceWorkingTreeFileChange, dotGitFileWatcher.event, outputChannel));
616658

617659
const root = Uri.file(repository.root);
618660
this._sourceControl = scm.createSourceControl('git', 'Git', root);

extensions/git/src/util.ts

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@
33
* Licensed under the MIT License. See License.txt in the project root for license information.
44
*--------------------------------------------------------------------------------------------*/
55

6-
import { Event } from 'vscode';
7-
import { dirname, sep } from 'path';
6+
import { Event, EventEmitter, Uri } from 'vscode';
7+
import { dirname, sep, join } from 'path';
88
import { Readable } from 'stream';
99
import * as fs from 'fs';
1010
import * as byline from 'byline';
@@ -343,4 +343,20 @@ export function pathEquals(a: string, b: string): boolean {
343343
}
344344

345345
return a === b;
346-
}
346+
}
347+
348+
export interface IFileWatcher extends IDisposable {
349+
readonly event: Event<Uri>;
350+
}
351+
352+
export function watch(location: string): IFileWatcher {
353+
const dotGitWatcher = fs.watch(location);
354+
const onDotGitFileChangeEmitter = new EventEmitter<Uri>();
355+
dotGitWatcher.on('change', (_, e) => onDotGitFileChangeEmitter.fire(Uri.file(join(location, e as string))));
356+
dotGitWatcher.on('error', err => console.error(err));
357+
358+
return new class implements IFileWatcher {
359+
event = onDotGitFileChangeEmitter.event;
360+
dispose() { dotGitWatcher.close(); }
361+
};
362+
}

0 commit comments

Comments
 (0)