diff --git a/src/git/git.ts b/src/git/git.ts index d0b2ddb..3976307 100644 --- a/src/git/git.ts +++ b/src/git/git.ts @@ -483,6 +483,15 @@ export namespace Git { } } + export function check_ignore(repoPath: string, ...files: string[]) { + return git( + { cwd: repoPath, errors: GitErrorHandling.Ignore, stdin: files.join('\0') }, + 'check-ignore', + '-z', + '--stdin' + ); + } + export function check_mailmap(repoPath: string, author: string) { return git({ cwd: repoPath, errors: GitErrorHandling.Ignore, local: true }, 'check-mailmap', author); } diff --git a/src/git/gitService.ts b/src/git/gitService.ts index 99fe208..0b720fd 100644 --- a/src/git/gitService.ts +++ b/src/git/gitService.ts @@ -605,6 +605,23 @@ export class GitService implements Disposable { } } + @log() + async excludeIgnoredUris(repoPath: string, uris: Uri[]): Promise { + const paths = new Map(uris.map(u => [Strings.normalizePath(u.fsPath), u])); + + const data = await Git.check_ignore(repoPath, ...paths.keys()); + if (data == null) return uris; + + const ignored = data.split('\0').filter(Boolean); + if (ignored.length === 0) return uris; + + for (const file of ignored) { + paths.delete(file); + } + + return [...paths.values()]; + } + @gate() @log() fetch(repoPath: string, options: { all?: boolean; prune?: boolean; remote?: string } = {}) { diff --git a/src/git/models/repository.ts b/src/git/models/repository.ts index 19e6358..c512537 100644 --- a/src/git/models/repository.ts +++ b/src/git/models/repository.ts @@ -24,6 +24,8 @@ import { Messages } from '../../messages'; import { Logger } from '../../logger'; import { runGitCommandInTerminal } from '../../terminal'; +const ignoreGitRegex = /\.git(?:\/|\\|$)/; + export enum RepositoryChange { Config = 'config', Closed = 'closed', @@ -178,7 +180,7 @@ export class Repository implements Disposable { private onFileSystemChanged(uri: Uri) { // Ignore .git changes - if (/\.git(?:\/|\\|$)/.test(uri.fsPath)) return; + if (ignoreGitRegex.test(uri.fsPath)) return; this.fireFileSystemChange(uri); } @@ -670,9 +672,16 @@ export class Repository implements Disposable { this._fireFileSystemChangeDebounced(e); } - private fireFileSystemChangeCore(e: RepositoryFileSystemChangeEvent) { + private async fireFileSystemChangeCore(e: RepositoryFileSystemChangeEvent) { this._pendingChanges.fs = undefined; + const uris = await Container.git.excludeIgnoredUris(this.path, e.uris); + if (uris.length === 0) return; + + if (uris.length !== e.uris.length) { + e = { ...e, uris: uris }; + } + this._onDidChangeFileSystem.fire(e); } diff --git a/src/system/array.ts b/src/system/array.ts index 1d83e34..7305406 100644 --- a/src/system/array.ts +++ b/src/system/array.ts @@ -1,5 +1,10 @@ 'use strict'; -import { intersectionWith as _intersectionWith, isEqual as _isEqual, xor as _xor } from 'lodash-es'; +import { + findLastIndex as _findLastIndex, + intersectionWith as _intersectionWith, + isEqual as _isEqual, + xor as _xor +} from 'lodash-es'; export namespace Arrays { export function countUniques(source: T[], accessor: (item: T) => string): { [key: string]: number } { @@ -37,6 +42,8 @@ export namespace Arrays { }, [] as any); } + export const findLastIndex = _findLastIndex; + export function groupBy(source: T[], accessor: (item: T) => string): { [key: string]: T[] } { return source.reduce((groupings, current) => { const value = accessor(current); diff --git a/src/views/nodes/repositoryNode.ts b/src/views/nodes/repositoryNode.ts index 8075752..4253fcd 100644 --- a/src/views/nodes/repositoryNode.ts +++ b/src/views/nodes/repositoryNode.ts @@ -12,7 +12,7 @@ import { RepositoryChangeEvent, RepositoryFileSystemChangeEvent } from '../../git/gitService'; -import { Dates, debug, gate, log, Strings } from '../../system'; +import { Arrays, Dates, debug, gate, log, Strings } from '../../system'; import { RepositoriesView } from '../repositoriesView'; import { CompareBranchNode } from './compareBranchNode'; import { BranchesNode } from './branchesNode'; @@ -219,10 +219,13 @@ export class RepositoryNode extends SubscribeableViewNode { @gate() @debug() - async refresh() { - this._status = this.repo.getStatus(); + async refresh(reset: boolean = false) { + if (reset) { + this._status = this.repo.getStatus(); + + this._children = undefined; + } - this._children = undefined; await this.ensureSubscription(); } @@ -270,8 +273,32 @@ export class RepositoryNode extends SubscribeableViewNode { .join(', ')}${e.uris.length > 1 ? ', ...' : ''}] }` } }) - private onFileSystemChanged(e: RepositoryFileSystemChangeEvent) { - void this.triggerChange(); + private async onFileSystemChanged(e: RepositoryFileSystemChangeEvent) { + this._status = this.repo.getStatus(); + + if (this._children !== undefined) { + const status = await this._status; + + let index = this._children.findIndex(c => c instanceof StatusFilesNode); + if (status !== undefined && (status.state.ahead || status.files.length !== 0)) { + let deleteCount = 1; + if (index === -1) { + index = Arrays.findLastIndex( + this._children, + c => c instanceof BranchTrackingStatusNode || c instanceof BranchNode + ); + deleteCount = 0; + index++; + } + + const range = status.upstream ? GitRevision.createRange(status.upstream, status.sha) : undefined; + this._children.splice(index, deleteCount, new StatusFilesNode(this.view, this, status, range)); + } else if (index !== -1) { + this._children.splice(index, 1); + } + } + + void this.triggerChange(false); } @debug({ @@ -292,7 +319,7 @@ export class RepositoryNode extends SubscribeableViewNode { e.changed(RepositoryChange.Repository) || e.changed(RepositoryChange.Config) ) { - void this.triggerChange(); + void this.triggerChange(true); return; } diff --git a/src/views/nodes/viewNode.ts b/src/views/nodes/viewNode.ts index eb17609..6d3b61f 100644 --- a/src/views/nodes/viewNode.ts +++ b/src/views/nodes/viewNode.ts @@ -219,6 +219,7 @@ export abstract class SubscribeableViewNode extends V } } + @gate() @debug() async ensureSubscription() { // We only need to subscribe if we are visible and if auto-refresh enabled (when supported)