Skip to content

Commit 7ff8b8b

Browse files
committed
Reduces repo file watching in certain cases
- Avoids repo file watching for closed repos - Avoids nested watching for .gitignore changes outside workspace Refs: #4335
1 parent e68e1dd commit 7ff8b8b

File tree

1 file changed

+76
-83
lines changed

1 file changed

+76
-83
lines changed

src/git/models/repository.ts

Lines changed: 76 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ import { getLoggableName, Logger } from '../../system/logger';
2424
import { getLogScope, startLogScope } from '../../system/logger.scope';
2525
import { updateRecordValue } from '../../system/object';
2626
import { basename, normalizePath } from '../../system/path';
27-
import type { GitProviderDescriptor } from '../gitProvider';
27+
import type { GitDir, GitProviderDescriptor } from '../gitProvider';
2828
import type { GitRepositoryService } from '../gitRepositoryService';
2929
import { getBranchNameWithoutRemote, getRemoteNameFromBranchName } from '../utils/branch.utils';
3030
import { getReferenceNameWithoutRemote, isBranchReference } from '../utils/reference.utils';
@@ -176,6 +176,7 @@ export class Repository implements Disposable {
176176
private _fireFileSystemChangeDebounced: Deferrable<() => void> | undefined = undefined;
177177
private _pendingFileSystemChange?: RepositoryFileSystemChangeEvent;
178178
private _pendingRepoChange?: RepositoryChangeEvent;
179+
private _repoWatchersDisposable: Disposable | undefined;
179180
private _suspended: boolean;
180181

181182
constructor(
@@ -200,17 +201,12 @@ export class Repository implements Disposable {
200201
}
201202
} else {
202203
this._name = basename(uri.path);
203-
204-
// TODO@eamodio should we create a fake workspace folder?
205-
// folder = {
206-
// uri: uri,
207-
// name: this.name,
208-
// index: container.git.repositoryCount,
209-
// };
210204
}
211205

212-
// Update the name if it is a worktree
213-
void this.git.config.getGitDir?.().then(gd => {
206+
void this.getGitDir().then(gd => {
207+
this.setupRepoWatchers(gd);
208+
209+
// Update the name if it is a worktree
214210
if (gd?.commonUri == null) return;
215211

216212
let path = gd.commonUri.path;
@@ -235,46 +231,7 @@ export class Repository implements Disposable {
235231
this._disposable = Disposable.from(
236232
this._onDidChange,
237233
this._onDidChangeFileSystem,
238-
this.setupRepoWatchers(),
239234
configuration.onDidChange(this.onConfigurationChanged, this),
240-
// Sending this event in the `'git:cache:reset'` below to avoid unnecessary work. While we will refresh more than needed, this doesn't happen often
241-
// container.richRemoteProviders.onAfterDidChangeConnectionState(async e => {
242-
// const uniqueKeys = new Set<string>();
243-
// for (const remote of await this.getRemotes()) {
244-
// if (remote.provider?.hasRichIntegration()) {
245-
// uniqueKeys.add(remote.provider.key);
246-
// }
247-
// }
248-
249-
// if (uniqueKeys.has(e.key)) {
250-
// this.fireChange(RepositoryChange.RemoteProviders);
251-
// }
252-
// }),
253-
);
254-
255-
this.onConfigurationChanged();
256-
if (this._orderByLastFetched) {
257-
void this.getLastFetched();
258-
}
259-
}
260-
261-
private setupRepoWatchers() {
262-
let disposable: Disposable | undefined;
263-
264-
void this.setupRepoWatchersCore().then(d => (disposable = d));
265-
266-
return {
267-
dispose: () => void disposable?.dispose(),
268-
};
269-
}
270-
271-
@debug({ singleLine: true })
272-
private async setupRepoWatchersCore() {
273-
const scope = getLogScope();
274-
275-
const disposables: Disposable[] = [];
276-
277-
disposables.push(
278235
this.container.events.on('git:cache:reset', e => {
279236
if (!e.data.repoPath || e.data.repoPath === this.path) {
280237
if (e.data.types?.includes('providers')) {
@@ -289,44 +246,15 @@ export class Repository implements Disposable {
289246
}),
290247
);
291248

292-
const watcher = workspace.createFileSystemWatcher(new RelativePattern(this.uri, '**/.gitignore'));
293-
disposables.push(
294-
watcher,
295-
watcher.onDidChange(this.onGitIgnoreChanged, this),
296-
watcher.onDidCreate(this.onGitIgnoreChanged, this),
297-
watcher.onDidDelete(this.onGitIgnoreChanged, this),
298-
);
299-
300-
function watch(this: Repository, uri: Uri, pattern: string) {
301-
Logger.debug(scope, `watching '${uri.toString(true)}' for repository changes`);
302-
303-
const watcher = workspace.createFileSystemWatcher(new RelativePattern(uri, pattern));
304-
305-
disposables.push(
306-
watcher,
307-
watcher.onDidChange(e => this.onRepositoryChanged(e, uri, 'change')),
308-
watcher.onDidCreate(e => this.onRepositoryChanged(e, uri, 'create')),
309-
watcher.onDidDelete(e => this.onRepositoryChanged(e, uri, 'delete')),
310-
);
311-
return watcher;
312-
}
313-
314-
const gitDir = await this.git.config.getGitDir?.();
315-
if (gitDir != null) {
316-
if (gitDir?.commonUri == null) {
317-
watch.call(this, gitDir.uri, dotGitWatcherGlobCombined);
318-
} else {
319-
watch.call(this, gitDir.uri, dotGitWatcherGlobRoot);
320-
watch.call(this, gitDir.commonUri, dotGitWatcherGlobCommon);
321-
}
249+
this.onConfigurationChanged();
250+
if (this._orderByLastFetched) {
251+
void this.getLastFetched();
322252
}
323-
324-
return Disposable.from(...disposables);
325253
}
326254

327255
dispose(): void {
328256
this.unWatchFileSystem(true);
329-
257+
this._repoWatchersDisposable?.dispose();
330258
this._disposable.dispose();
331259
}
332260

@@ -344,6 +272,7 @@ export class Repository implements Disposable {
344272
if (changed) {
345273
using scope = startLogScope(`${getLoggableName(this)}.closed`, false);
346274
Logger.debug(scope, `setting closed=${value}`);
275+
void this.getGitDir().then(gd => this.setupRepoWatchers(gd));
347276
this.fireChange(this._closed ? RepositoryChange.Closed : RepositoryChange.Opened);
348277
}
349278
}
@@ -601,7 +530,7 @@ export class Repository implements Disposable {
601530

602531
@log({ exit: true })
603532
async getCommonRepositoryUri(): Promise<Uri | undefined> {
604-
const gitDir = await this.git.config.getGitDir?.();
533+
const gitDir = await this.getGitDir();
605534
if (gitDir?.commonUri?.path.endsWith('/.git')) {
606535
return gitDir.commonUri.with({
607536
path: gitDir.commonUri.path.substring(0, gitDir.commonUri.path.length - 5),
@@ -1008,11 +937,75 @@ export class Repository implements Disposable {
1008937
this._onDidChangeFileSystem.fire(e);
1009938
}
1010939

940+
private _gitDirPromise: Promise<GitDir | undefined> | undefined;
941+
private async getGitDir(): Promise<GitDir | undefined> {
942+
return (this._gitDirPromise ??= this.git.config.getGitDir?.());
943+
}
944+
1011945
private async runTerminalCommand(command: string, ...args: string[]) {
1012946
await this.git.runGitCommandViaTerminal?.(command, args, { execute: true });
1013947

1014948
setTimeout(() => this.fireChange(RepositoryChange.Unknown), 2500);
1015949
}
950+
951+
@debug({ singleLine: true })
952+
private setupRepoWatchers(gitDir: GitDir | undefined): void {
953+
const scope = getLogScope();
954+
955+
if (this.closed) {
956+
if (this._repoWatchersDisposable != null) {
957+
Logger.debug(
958+
getLogScope(),
959+
`(closed) stop watching '${this.uri.toString(true)}' for repository changes`,
960+
);
961+
this._repoWatchersDisposable.dispose();
962+
this._repoWatchersDisposable = undefined;
963+
}
964+
965+
return;
966+
}
967+
968+
if (this._repoWatchersDisposable != null) return;
969+
970+
const disposables: Disposable[] = [];
971+
972+
// If the repository is not part of the workspace, then limit watching to only the .gitignore file at the root of the repository
973+
const gitIgnorePattern = this.folder != null ? '**/.gitignore' : '.gitignore';
974+
Logger.debug(scope, `watching '${this.uri.toString(true)}/${gitIgnorePattern}' for .gitignore changes`);
975+
976+
const watcher = workspace.createFileSystemWatcher(new RelativePattern(this.uri, gitIgnorePattern));
977+
disposables.push(
978+
watcher,
979+
watcher.onDidChange(this.onGitIgnoreChanged, this),
980+
watcher.onDidCreate(this.onGitIgnoreChanged, this),
981+
watcher.onDidDelete(this.onGitIgnoreChanged, this),
982+
);
983+
984+
function watch(this: Repository, uri: Uri, pattern: string) {
985+
Logger.debug(scope, `watching '${uri.toString(true)}/${pattern}' for repository changes`);
986+
987+
const watcher = workspace.createFileSystemWatcher(new RelativePattern(uri, pattern));
988+
989+
disposables.push(
990+
watcher,
991+
watcher.onDidChange(e => this.onRepositoryChanged(e, uri, 'change')),
992+
watcher.onDidCreate(e => this.onRepositoryChanged(e, uri, 'create')),
993+
watcher.onDidDelete(e => this.onRepositoryChanged(e, uri, 'delete')),
994+
);
995+
return watcher;
996+
}
997+
998+
if (gitDir != null) {
999+
if (gitDir?.commonUri == null) {
1000+
watch.call(this, gitDir.uri, dotGitWatcherGlobCombined);
1001+
} else {
1002+
watch.call(this, gitDir.uri, dotGitWatcherGlobRoot);
1003+
watch.call(this, gitDir.commonUri, dotGitWatcherGlobCommon);
1004+
}
1005+
}
1006+
1007+
this._repoWatchersDisposable = Disposable.from(...disposables);
1008+
}
10161009
}
10171010

10181011
export function isRepository(repository: unknown): repository is Repository {

0 commit comments

Comments
 (0)