/********************************************************************************
 * Copyright (C) 2017 TypeFox and others.
 *
 * This program and the accompanying materials are made available under the
 * terms of the Eclipse Public License v. 2.0 which is available at
 * http://www.eclipse.org/legal/epl-2.0.
 *
 * This Source Code may also be made available under the following Secondary
 * Licenses when the conditions for such availability set forth in the Eclipse
 * Public License v. 2.0 are satisfied: GNU General Public License, version 2
 * with the GNU Classpath Exception which is available at
 * https://www.gnu.org/software/classpath/license.html.
 *
 * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
 ********************************************************************************/ import debounce = require ( '@theia/core/shared/lodash.debounce' ) ; import { injectable , inject , postConstruct } from '@theia/core/shared/inversify' ; import { WorkspaceService } from '@theia/workspace/lib/browser/workspace-service' ; import { Emitter , Event } from '@theia/core/lib/common/event' ; import { StorageService } from '@theia/core/lib/browser/storage-service' ; import { Git , Repository } from '../common' ; import { GitCommitMessageValidator } from './git-commit-message-validator' ; import { GitScmProvider } from './git-scm-provider' ; import { ScmService } from '@theia/scm/lib/browser/scm-service' ; import { ScmRepository } from '@theia/scm/lib/browser/scm-repository' ; import { FileService } from '@theia/filesystem/lib/browser/file-service' ; export interface GitRefreshOptions { readonly maxCount : number ; } @ injectable ( ) export class GitRepositoryProvider { protected readonly onDidChangeRepositoryEmitter = new Emitter < Repository | undefined > ( ) ; protected readonly selectedRepoStorageKey = 'theia-git-selected-repository' ; protected readonly allRepoStorageKey = 'theia-git-all-repositories' ; @ inject ( GitScmProvider . Factory ) protected readonly scmProviderFactory : GitScmProvider . Factory ; @ inject ( GitCommitMessageValidator ) protected readonly commitMessageValidator : GitCommitMessageValidator ; @ inject ( Git ) protected readonly git : Git ; @ inject ( WorkspaceService ) protected readonly workspaceService : WorkspaceService ; @ inject ( ScmService ) protected readonly scmService : ScmService ; @ inject ( StorageService ) protected readonly storageService : StorageService ; @ inject ( FileService ) protected readonly fileService : FileService ; @ postConstruct ( ) protected async initialize ( ) { const [ selectedRepository , allRepositories ] = await Promise . all ( [ this . storageService . getData < Repository | undefined > ( this . selectedRepoStorageKey ) , this . storageService . getData < Repository [ ] > ( this . allRepoStorageKey ) ] ) ; this . scmService . onDidChangeSelectedRepository ( scmRepository => this . fireDidChangeRepository ( this . toGitRepository ( scmRepository ) ) ) ; if ( allRepositories ) { this . updateRepositories ( allRepositories ) ; } else { await this . refresh ( { maxCount : 1 } ) ; } this . selectedRepository = selectedRepository ; await this . refresh ( ) ; this . fileService . onDidFilesChange ( _ => this . lazyRefresh ( ) ) ; } protected lazyRefresh : ( ) => Promise < void > | undefined = debounce ( ( ) => this . refresh ( ) , 1000 ) ; /**
     * Returns with the previously selected repository, or if no repository has been selected yet,
     * it picks the first available repository from the backend and sets it as the selected one and returns with that.
     * If no repositories are available, returns `undefined`.
     */ get selectedRepository ( ) : Repository | undefined { return this . toGitRepository ( this . scmService . selectedRepository ) ; } /**
     * Sets the selected repository, but do nothing if the given repository is not a Git repository
     * registered with the SCM service.  We must be sure not to clear the selection if the selected
     * repository is managed by an SCM other than Git.
     */ set selectedRepository ( repository ) { const scmRepository = this . toScmRepository ( repository ) ; if ( scmRepository ) { this . scmService . selectedRepository = scmRepository ; } } get selectedScmRepository ( ) : GitScmRepository | undefined { return this . toGitScmRepository ( this . scmService . selectedRepository ) ; } get selectedScmProvider ( ) : GitScmProvider | undefined { return this . toGitScmProvider ( this . scmService . selectedRepository ) ; } get onDidChangeRepository ( ) : Event < Repository | undefined > { return this . onDidChangeRepositoryEmitter . event ; } protected fireDidChangeRepository ( repository ) { this . storageService . setData < Repository | undefined > ( this . selectedRepoStorageKey , repository ) ; this . onDidChangeRepositoryEmitter . fire ( repository ) ; } /**
     * Returns with all know repositories.
     */ get allRepositories ( ) : Repository [ ] { const repositories = [ ] ; for ( const scmRepository of this . scmService . repositories ) { const repository = this . toGitRepository ( scmRepository ) ; if ( repository ) { repositories . push ( repository ) ; } } return repositories ; } async refresh ( options ? ) { const repositories = [ ] ; const refreshing = [ ] ; for ( const root of await this . workspaceService . roots ) { refreshing . push ( this . git . repositories ( root . resource . toString ( ) , { ... options } ) . then ( result => { repositories . push ( ... result ) ; } , ( ) => { } ) ) ; } await Promise . all ( refreshing ) ; this . updateRepositories ( repositories ) ; } protected updateRepositories ( repositories ) { this . storageService . setData < Repository [ ] > ( this . allRepoStorageKey , repositories ) ; const registered = new Set < string > ( ) ; const toUnregister = new Map < string , ScmRepository > ( ) ; for ( const scmRepository of this . scmService . repositories ) { const repository = this . toGitRepository ( scmRepository ) ; if ( repository ) { registered . add ( repository . localUri ) ; toUnregister . set ( repository . localUri , scmRepository ) ; } } for ( const repository of repositories ) { toUnregister . delete ( repository . localUri ) ; if ( ! registered . has ( repository . localUri ) ) { registered . add ( repository . localUri ) ; this . registerScmProvider ( repository ) ; } } for ( const [ , scmRepository ] of toUnregister ) { scmRepository . dispose ( ) ; } } protected registerScmProvider ( repository ) { const provider = this . scmProviderFactory ( { repository } ) ; const scmRepository = this . scmService . registerScmProvider ( provider , { input : { placeholder : 'Message (press {0} to commit)' , validator : async ( value ) => { const issue = await this . commitMessageValidator . validate ( value ) ; return issue && { message : issue . message , type : issue . status } ; } } } ) ; provider . input = scmRepository . input ; } protected toScmRepository ( repository ) { return repository && this . scmService . repositories . find ( scmRepository => Repository . equal ( this . toGitRepository ( scmRepository ) , repository ) ) ; } protected toGitRepository ( scmRepository ) { const provider = this . toGitScmProvider ( scmRepository ) ; return provider && provider . repository ; } protected toGitScmProvider ( scmRepository ) { const gitScmRepository = this . toGitScmRepository ( scmRepository ) ; return gitScmRepository && gitScmRepository . provider ; } protected toGitScmRepository ( scmRepository ) { return GitScmRepository . is ( scmRepository ) ? scmRepository : undefined ; } } export interface GitScmRepository extends ScmRepository { readonly provider : GitScmProvider ; } export namespace GitScmRepository { export function is ( scmRepository ) { return ! ! scmRepository && scmRepository . provider instanceof GitScmProvider ; } }