/********************************************************************************
 * Copyright (C) 2018 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 { injectable , inject , optional } from '@theia/core/shared/inversify' ; import { MessageService , Emitter , Event } from '@theia/core' ; import { ConfirmDialog , QuickInputService } from '@theia/core/lib/browser' ; import { GitRepositoryTracker } from './git-repository-tracker' ; import { Git , Repository , WorkingDirectoryStatus } from '../common' ; import { GitErrorHandler } from './git-error-handler' ; @ injectable ( ) export class GitSyncService { @ inject ( Git ) protected readonly git : Git ; @ inject ( GitRepositoryTracker ) protected readonly repositoryTracker : GitRepositoryTracker ; @ inject ( MessageService ) protected readonly messageService : MessageService ; @ inject ( GitErrorHandler ) protected readonly gitErrorHandler : GitErrorHandler ; @ inject ( QuickInputService ) @ optional ( ) protected readonly quickInputService : QuickInputService ; protected readonly onDidChangeEmitter = new Emitter < void > ( ) ; readonly onDidChange : Event < void > = this . onDidChangeEmitter . event ; protected fireDidChange ( ) { this . onDidChangeEmitter . fire ( undefined ) ; } protected syncing = false ; isSyncing ( ) { return this . syncing ; } setSyncing ( syncing ) { this . syncing = syncing ; this . fireDidChange ( ) ; } canSync ( ) { if ( this . isSyncing ( ) ) { return false ; } const status = this . repositoryTracker . selectedRepositoryStatus ; return ! ! status && ! ! status . branch && ! ! status . upstreamBranch ; } async sync ( ) { const repository = this . repositoryTracker . selectedRepository ; if ( ! this . canSync ( ) || ! repository ) { return ; } this . setSyncing ( true ) ; try { await this . git . fetch ( repository ) ; let status = await this . git . status ( repository ) ; this . setSyncing ( false ) ; const method = await this . getSyncMethod ( status ) ; if ( method === undefined ) { return ; } this . setSyncing ( true ) ; if ( method === 'pull-push' || method === 'rebase-push' ) { await this . git . pull ( repository , { rebase : method === 'rebase-push' } ) ; status = await this . git . status ( repository ) ; } if ( this . shouldPush ( status ) ) { await this . git . push ( repository , { force : method === 'force-push' } ) ; } } catch ( error ) { this . gitErrorHandler . handleError ( error ) ; } finally { this . setSyncing ( false ) ; } } protected async getSyncMethod ( status ) { if ( ! status . upstreamBranch || ! status . branch ) { return undefined ; } const { branch , upstreamBranch } = status ; if ( ! this . shouldPull ( status ) && ! this . shouldPush ( status ) ) { this . messageService . info ( `${ branch } is already in sync with ${ upstreamBranch }` ) ; return undefined ; } const methods = [ { label : `Pull and push commits from and to '${ upstreamBranch }'` , warning : `This action will pull and push commits from and to '${ upstreamBranch }'.` , detail : 'pull-push' } , { label : `Fetch, rebase and push commits from and to '${ upstreamBranch }'` , warning : `This action will fetch, rebase and push commits from and to '${ upstreamBranch }'.` , detail : 'rebase-push' } , { label : `Force push commits to '${ upstreamBranch }'` , warning : `This action will override commits in '${ upstreamBranch }'.` , detail : 'force-push' } ] ; const selectedCWD = await this . quickInputService ?. showQuickPick ( methods , { placeholder : 'Select current working directory for new terminal' } ) ; if ( await this . confirm ( 'Synchronize Changes' , methods . find ( ( { detail } ) => detail === selectedCWD . detail ) ! . warning ) ) { return ( selectedCWD . detail as GitSyncService . SyncMethod ) ; } else { return ( undefined ) ; } } canPublish ( ) { if ( this . isSyncing ( ) ) { return false ; } const status = this . repositoryTracker . selectedRepositoryStatus ; return ! ! status && ! ! status . branch && ! status . upstreamBranch ; } async publish ( ) { const repository = this . repositoryTracker . selectedRepository ; const status = this . repositoryTracker . selectedRepositoryStatus ; const localBranch = status && status . branch ; if ( ! this . canPublish ( ) || ! repository || ! localBranch ) { return ; } const remote = await this . getRemote ( repository , localBranch ) ; if ( remote && await this . confirm ( 'Publish changes' , `This action will push commits to '${ remote }/${ localBranch }' and track it as an upstream branch.` ) ) { try { await this . git . push ( repository , { remote , localBranch , setUpstream : true } ) ; } catch ( error ) { this . gitErrorHandler . handleError ( error ) ; } } } protected async getRemote ( repository , branch ) { const remotes = await this . git . remote ( repository ) ; if ( remotes . length === 0 ) { this . messageService . warn ( 'Your repository has no remotes configured to publish to.' ) ; } const selectedRemote = await this . quickInputService ?. showQuickPick ( remotes . map ( remote => ( { label : remote } ) ) , { placeholder : `Pick a remote to publish the branch ${ branch } to:` } ) ; return selectedRemote . label ; } protected shouldPush ( status ) { return status . aheadBehind ? status . aheadBehind . ahead > 0 : true ; } protected shouldPull ( status ) { return status . aheadBehind ? status . aheadBehind . behind > 0 : true ; } protected async confirm ( title , msg ) { return ! ! await new ConfirmDialog ( { title , msg , } ) . open ( ) ; } } export namespace GitSyncService { export type SyncMethod = 'pull-push' | 'rebase-push' | 'force-push' ; }