@ -65,12 +65,14 @@ export enum GitRepoSearchBy {
Sha = 'sha'
}
export enum RepoChangedReasons {
CacheReset = 'cache-reset' ,
Remotes = 'remotes' ,
Repositories = 'Repositories' ,
Stash = 'stash' ,
Unknown = ''
export enum GitChangeReason {
GitCache = 'git-cache' ,
RemoteCache = 'remote-cache' ,
Repositories = 'repositories'
}
export interface GitChangeEvent {
reason : GitChangeReason ;
}
export class GitService extends Disposable {
@ -86,17 +88,15 @@ export class GitService extends Disposable {
return this . _onDidBlameFail . event ;
}
// TODO: Support multi-root { repo, reasons }[]?
private _onDidChangeRepo = new EventEmitter < RepoChangedReasons [ ] > ( ) ;
get onDidChangeRepo ( ) : Event < RepoChangedReasons [ ] > {
return this . _onDidChangeRepo . event ;
private _onDidChange = new EventEmitter < GitChangeEvent > ( ) ;
get onDidChange ( ) : Event < GitChangeEvent > {
return this . _onDidChange . event ;
}
private _cacheDisposable : Disposable | undefined ;
private _disposable : Disposable | undefined ;
private _documentKeyMap : Map < TextDocument , string > ;
private _gitCache : Map < string , GitCacheEntry > ;
private _pendingChanges : { repo : boolean } = { repo : false } ;
private _remotesCache : Map < string , GitRemote [ ] > ;
private _repositories : Map < string , Repository | undefined > ;
private _repositoriesPromise : Promise < void > | undefined ;
@ -140,17 +140,22 @@ export class GitService extends Disposable {
this . _versionedUriCache . clear ( ) ;
}
public get repoPath ( ) : string | undefined {
get repoPath ( ) : string | undefined {
if ( this . _repositories . size !== 1 ) return undefined ;
const repo = Iterables . first ( this . _repositories . values ( ) ) ;
return repo === undefined ? undefined : repo . path ;
}
public get UseCaching() {
get UseCaching() {
return this . config . advanced . caching . enabled ;
}
private onAnyRepositoryChanged() {
this . _gitCache . clear ( ) ;
this . _trackedCache . clear ( ) ;
}
private onConfigurationChanged() {
const encoding = workspace . getConfiguration ( 'files' ) . get < string > ( 'encoding' , 'utf8' ) ;
setDefaultEncoding ( encoding ) ;
@ -184,13 +189,40 @@ export class GitService extends Disposable {
if ( this . config . blame . ignoreWhitespace !== ignoreWhitespace ) {
this . _gitCache . clear ( ) ;
this . fireRepoChange ( RepoChangedReasons . CacheReset ) ;
this . fireChange ( GitChangeReason . GitCache ) ;
}
}
private onRemoteProvidersChanged() {
this . _remotesCache . clear ( ) ;
this . fireRepoChange ( RepoChangedReasons . Remotes ) ;
this . fireChange ( GitChangeReason . RemoteCache ) ;
}
private onTextDocumentChanged ( e : TextDocumentChangeEvent ) {
let key = this . _documentKeyMap . get ( e . document ) ;
if ( key === undefined ) {
key = this . getCacheEntryKey ( e . document . uri ) ;
this . _documentKeyMap . set ( e . document , key ) ;
}
// Don't remove broken blame on change (since otherwise we'll have to run the broken blame again)
const entry = this . _gitCache . get ( key ) ;
if ( entry === undefined || entry . hasErrors ) return ;
if ( this . _gitCache . delete ( key ) ) {
Logger . log ( ` Clear cache entry for ' ${ key } ', reason= ${ RemoveCacheReason [ RemoveCacheReason . DocumentChanged ] } ` ) ;
}
}
private onTextDocumentClosed ( document : TextDocument ) {
this . _documentKeyMap . delete ( document ) ;
const key = this . getCacheEntryKey ( document . uri ) ;
if ( this . _gitCache . delete ( key ) ) {
Logger . log ( ` Clear cache entry for ' ${ key } ', reason= ${ RemoveCacheReason [ RemoveCacheReason . DocumentClosed ] } ` ) ;
}
}
private onWindowStateChanged ( e : WindowState ) {
@ -201,17 +233,7 @@ export class GitService extends Disposable {
this . _repositories . forEach ( r = > r && r . suspend ( ) ) ;
}
const suspended = ! e . focused ;
const changed = suspended !== this . _suspended ;
this . _suspended = suspended ;
if ( suspended || ! changed ) return ;
// If we've come back into focus and we are dirty, fire the change events
if ( this . _pendingChanges . repo ) {
this . _pendingChanges . repo = false ;
this . _fireRepoChangeDebounced ! ( ) ;
}
this . _suspended = ! e . focused ;
}
private async onWorkspaceFoldersChanged ( e? : WorkspaceFoldersChangeEvent ) {
@ -234,7 +256,7 @@ export class GitService extends Disposable {
this . _repositories . set ( fsPath , undefined ) ;
}
else {
this . _repositories . set ( fsPath , new Repository ( f , rp , this . onRepoChanged . bind ( this ) , this . _suspended ) ) ;
this . _repositories . set ( fsPath , new Repository ( f , rp , this . onAny Repository Changed . bind ( this ) , this . _suspended ) ) ;
}
}
@ -253,82 +275,12 @@ export class GitService extends Disposable {
await setCommandContext ( CommandContext . HasRepository , hasRepository ) ;
if ( ! initializing ) {
this . fireRepo Change ( RepoChangedReasons . Repositories ) ;
this . fireChange ( GitChangeReason . Repositories ) ;
}
}
private onTextDocumentChanged ( e : TextDocumentChangeEvent ) {
let key = this . _documentKeyMap . get ( e . document ) ;
if ( key === undefined ) {
key = this . getCacheEntryKey ( e . document . uri ) ;
this . _documentKeyMap . set ( e . document , key ) ;
}
// Don't remove broken blame on change (since otherwise we'll have to run the broken blame again)
const entry = this . _gitCache . get ( key ) ;
if ( entry === undefined || entry . hasErrors ) return ;
if ( this . _gitCache . delete ( key ) ) {
Logger . log ( ` Clear cache entry for ' ${ key } ', reason= ${ RemoveCacheReason [ RemoveCacheReason . DocumentChanged ] } ` ) ;
}
}
private onTextDocumentClosed ( document : TextDocument ) {
this . _documentKeyMap . delete ( document ) ;
const key = this . getCacheEntryKey ( document . uri ) ;
if ( this . _gitCache . delete ( key ) ) {
Logger . log ( ` Clear cache entry for ' ${ key } ', reason= ${ RemoveCacheReason [ RemoveCacheReason . DocumentClosed ] } ` ) ;
}
}
private onRepoChanged ( uri : Uri ) {
if ( uri !== undefined && uri . path . endsWith ( 'ref/stash' ) ) {
this . fireRepoChange ( RepoChangedReasons . Stash ) ;
return ;
}
this . _gitCache . clear ( ) ;
this . _trackedCache . clear ( ) ;
this . fireRepoChange ( ) ;
}
private _fireRepoChangeDebounced : ( ( ) = > void ) | undefined = undefined ;
private _repoChangedReasons : RepoChangedReasons [ ] = [ ] ;
private fireRepoChange ( reason : RepoChangedReasons = RepoChangedReasons . Unknown ) {
if ( this . _fireRepoChangeDebounced === undefined ) {
this . _fireRepoChangeDebounced = Functions . debounce ( this . fireRepoChangeCore , 250 ) ;
}
if ( ! this . _repoChangedReasons . includes ( reason ) ) {
this . _repoChangedReasons . push ( reason ) ;
}
if ( this . _suspended ) {
this . _pendingChanges . repo = true ;
return ;
}
return this . _fireRepoChangeDebounced ( ) ;
}
private fireRepoChangeCore() {
const reasons = this . _repoChangedReasons ;
this . _repoChangedReasons = [ ] ;
this . _onDidChangeRepo . fire ( reasons ) ;
}
public async getRepositories ( ) : Promise < Repository [ ] > {
if ( this . _repositoriesPromise !== undefined ) {
await this . _repositoriesPromise ;
this . _repositoriesPromise = undefined ;
}
return [ . . . Iterables . filter ( this . _repositories . values ( ) , r = > r !== undefined ) as Iterable < Repository > ] ;
private fireChange ( reason : GitChangeReason ) {
this . _onDidChange . fire ( { reason : reason } ) ;
}
checkoutFile ( uri : GitUri , sha? : string ) {
@ -962,15 +914,8 @@ export class GitService extends Disposable {
if ( typeof filePathOrUri === 'string' ) return this . getRepoPathCore ( filePathOrUri , false ) ;
const folder = workspace . getWorkspaceFolder ( filePathOrUri ) ;
if ( folder !== undefined ) {
if ( this . _repositoriesPromise !== undefined ) {
await this . _repositoriesPromise ;
}
const rp = this . _repositories . get ( folder . uri . fsPath ) ;
if ( rp !== undefined ) return rp . path ;
}
const repo = await this . getRepository ( filePathOrUri ) ;
if ( repo !== undefined ) return repo . path ;
return this . getRepoPathCore ( filePathOrUri . fsPath , false ) ;
}
@ -979,6 +924,28 @@ export class GitService extends Disposable {
return Git . revparse_toplevel ( isDirectory ? filePath : path.dirname ( filePath ) ) ;
}
async getRepositories ( ) : Promise < Repository [ ] > {
const repositories = await this . getRepositoriesCore ( ) ;
return [ . . . Iterables . filter ( repositories . values ( ) , r = > r !== undefined ) as Iterable < Repository > ] ;
}
private async getRepositoriesCore ( ) : Promise < Map < string , Repository | undefined > > {
if ( this . _repositoriesPromise !== undefined ) {
await this . _repositoriesPromise ;
this . _repositoriesPromise = undefined ;
}
return this . _repositories ;
}
async getRepository ( uri : Uri ) : Promise < Repository | undefined > {
const folder = workspace . getWorkspaceFolder ( uri ) ;
if ( folder === undefined ) return undefined ;
const repositories = await this . getRepositoriesCore ( ) ;
return repositories . get ( folder . uri . fsPath ) ;
}
async getStashList ( repoPath : string | undefined ) : Promise < GitStash | undefined > {
Logger . log ( ` getStashList(' ${ repoPath } ') ` ) ;
if ( repoPath === undefined ) return undefined ;