Package org.uberfire.java.nio.fs.jgit

Source Code of org.uberfire.java.nio.fs.jgit.JGitFileSystemProvider

/*
* Copyright 2012 JBoss Inc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*       http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.uberfire.java.nio.fs.jgit;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FilenameFilter;
import java.io.FilterOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.RandomAccessFile;
import java.io.UnsupportedEncodingException;
import java.net.InetSocketAddress;
import java.net.URI;
import java.net.URLEncoder;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Set;
import java.util.TimeZone;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;

import org.apache.commons.httpclient.URIException;
import org.apache.commons.httpclient.util.URIUtil;
import org.eclipse.jgit.api.Git;
import org.eclipse.jgit.api.ListBranchCommand;
import org.eclipse.jgit.diff.DiffEntry;
import org.eclipse.jgit.errors.RepositoryNotFoundException;
import org.eclipse.jgit.internal.storage.file.WindowCache;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.Ref;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.storage.file.WindowCacheConfig;
import org.eclipse.jgit.transport.CredentialsProvider;
import org.eclipse.jgit.transport.PostReceiveHook;
import org.eclipse.jgit.transport.PreReceiveHook;
import org.eclipse.jgit.transport.ReceiveCommand;
import org.eclipse.jgit.transport.ReceivePack;
import org.eclipse.jgit.transport.ServiceMayNotContinueException;
import org.eclipse.jgit.transport.UsernamePasswordCredentialsProvider;
import org.eclipse.jgit.transport.resolver.ReceivePackFactory;
import org.eclipse.jgit.transport.resolver.RepositoryResolver;
import org.eclipse.jgit.transport.resolver.ServiceNotAuthorizedException;
import org.eclipse.jgit.transport.resolver.ServiceNotEnabledException;
import org.eclipse.jgit.util.FileUtils;
import org.uberfire.commons.cluster.ClusterService;
import org.uberfire.commons.data.Pair;
import org.uberfire.commons.message.MessageType;
import org.uberfire.java.nio.IOException;
import org.uberfire.java.nio.base.AbstractPath;
import org.uberfire.java.nio.base.BasicFileAttributesImpl;
import org.uberfire.java.nio.base.ExtendedAttributeView;
import org.uberfire.java.nio.base.FileSystemState;
import org.uberfire.java.nio.base.SeekableByteChannelFileBasedImpl;
import org.uberfire.java.nio.base.WatchContext;
import org.uberfire.java.nio.base.dotfiles.DotFileOption;
import org.uberfire.java.nio.base.options.CherryPickCopyOption;
import org.uberfire.java.nio.base.options.CommentedOption;
import org.uberfire.java.nio.base.version.VersionAttributeView;
import org.uberfire.java.nio.base.version.VersionAttributes;
import org.uberfire.java.nio.channels.AsynchronousFileChannel;
import org.uberfire.java.nio.channels.SeekableByteChannel;
import org.uberfire.java.nio.file.AccessDeniedException;
import org.uberfire.java.nio.file.AccessMode;
import org.uberfire.java.nio.file.AtomicMoveNotSupportedException;
import org.uberfire.java.nio.file.CopyOption;
import org.uberfire.java.nio.file.DeleteOption;
import org.uberfire.java.nio.file.DirectoryNotEmptyException;
import org.uberfire.java.nio.file.DirectoryStream;
import org.uberfire.java.nio.file.FileAlreadyExistsException;
import org.uberfire.java.nio.file.FileStore;
import org.uberfire.java.nio.file.FileSystem;
import org.uberfire.java.nio.file.FileSystemAlreadyExistsException;
import org.uberfire.java.nio.file.FileSystemNotFoundException;
import org.uberfire.java.nio.file.LinkOption;
import org.uberfire.java.nio.file.NoSuchFileException;
import org.uberfire.java.nio.file.NotDirectoryException;
import org.uberfire.java.nio.file.NotLinkException;
import org.uberfire.java.nio.file.OpenOption;
import org.uberfire.java.nio.file.Option;
import org.uberfire.java.nio.file.Path;
import org.uberfire.java.nio.file.StandardCopyOption;
import org.uberfire.java.nio.file.StandardDeleteOption;
import org.uberfire.java.nio.file.StandardOpenOption;
import org.uberfire.java.nio.file.StandardWatchEventKind;
import org.uberfire.java.nio.file.WatchEvent;
import org.uberfire.java.nio.file.attribute.BasicFileAttributeView;
import org.uberfire.java.nio.file.attribute.BasicFileAttributes;
import org.uberfire.java.nio.file.attribute.FileAttribute;
import org.uberfire.java.nio.file.attribute.FileAttributeView;
import org.uberfire.java.nio.file.spi.FileSystemProvider;
import org.uberfire.java.nio.fs.jgit.daemon.git.Daemon;
import org.uberfire.java.nio.fs.jgit.daemon.git.DaemonClient;
import org.uberfire.java.nio.fs.jgit.daemon.ssh.BaseGitCommand;
import org.uberfire.java.nio.fs.jgit.daemon.ssh.GitSSHService;
import org.uberfire.java.nio.fs.jgit.util.CommitContent;
import org.uberfire.java.nio.fs.jgit.util.CopyCommitContent;
import org.uberfire.java.nio.fs.jgit.util.DefaultCommitContent;
import org.uberfire.java.nio.fs.jgit.util.JGitUtil;
import org.uberfire.java.nio.fs.jgit.util.MoveCommitContent;
import org.uberfire.java.nio.fs.jgit.util.RevertCommitContent;
import org.uberfire.java.nio.security.AuthorizationManager;
import org.uberfire.java.nio.security.SecurityAware;
import org.uberfire.java.nio.security.UserPassAuthenticator;

import static org.eclipse.jgit.api.ListBranchCommand.ListMode.*;
import static org.eclipse.jgit.lib.Constants.*;
import static org.uberfire.commons.validation.Preconditions.*;
import static org.uberfire.java.nio.base.dotfiles.DotFileUtils.*;
import static org.uberfire.java.nio.file.StandardOpenOption.*;
import static org.uberfire.java.nio.fs.jgit.util.JGitUtil.*;
import static org.uberfire.java.nio.fs.jgit.util.JGitUtil.PathType.*;

public class JGitFileSystemProvider implements FileSystemProvider,
                                               SecurityAware {

    protected static final String DEFAULT_IO_SERVICE_NAME = "default";

    public static final String GIT_DEFAULT_REMOTE_NAME = DEFAULT_REMOTE_NAME;
    public static final String GIT_LIST_ROOT_BRANCH_MODE = "listMode";

    private static final String SCHEME = "git";

    public static final String REPOSITORIES_ROOT_DIR = ".niogit";
    public static final String SSH_FILE_CERT_ROOT_DIR = ".security";
    public static final String DEFAULT_HOST_NAME = "localhost";
    public static final String DEFAULT_HOST_ADDR = "127.0.0.1";
    public static final boolean DAEMON_DEFAULT_ENABLED = true;
    public static final int DAEMON_DEFAULT_PORT = 9418;
    public static final boolean SSH_DEFAULT_ENABLED = true;
    public static final int SSH_DEFAULT_PORT = 8001;

    private static final String GIT_ENV_PROP_DEST_PATH = "out-dir";

    public static File FILE_REPOSITORIES_ROOT;
    public static boolean DAEMON_ENABLED;
    public static int DAEMON_PORT;
    private static String DAEMON_HOST_ADDR;
    private static String DAEMON_HOST_NAME;

    private static boolean SSH_ENABLED;
    private static int SSH_PORT;
    private static String SSH_HOST_ADDR;
    private static String SSH_HOST_NAME;
    private static File SSH_FILE_CERT_DIR;

    public static final String USER_NAME = "username";
    public static final String PASSWORD = "password";
    public static final String INIT = "init";

    public static final int SCHEME_SIZE = ( SCHEME + "://" ).length();
    public static final int DEFAULT_SCHEME_SIZE = ( "default://" ).length();

    private final Map<String, JGitFileSystem> fileSystems = new ConcurrentHashMap<String, JGitFileSystem>();
    private final Set<JGitFileSystem> closedFileSystems = new HashSet<JGitFileSystem>();
    private final Map<Repository, JGitFileSystem> repoIndex = new ConcurrentHashMap<Repository, JGitFileSystem>();
    private final Map<Repository, ClusterService> clusterMap = new ConcurrentHashMap<Repository, ClusterService>();

    private final Map<String, String> fullHostNames = new HashMap<String, String>();

    private boolean isDefault;

    private final Map<JGitFileSystem, Map<String, NotificationModel>> oldHeadsOfPendingDiffs = new HashMap<JGitFileSystem, Map<String, NotificationModel>>();

    private Daemon daemonService = null;
    private GitSSHService gitSSHService = null;
    private UserPassAuthenticator authenticator;
    private AuthorizationManager authorizationManager;

    private void loadConfig() {
        final String bareReposDir = System.getProperty( "org.uberfire.nio.git.dir" );
        final String enabled = System.getProperty( "org.uberfire.nio.git.daemon.enabled" );
        final String host = System.getProperty( "org.uberfire.nio.git.daemon.host" );
        final String hostName = System.getProperty( "org.uberfire.nio.git.daemon.hostname" );
        final String port = System.getProperty( "org.uberfire.nio.git.daemon.port" );

        final String sshEnabled = System.getProperty( "org.uberfire.nio.git.ssh.enabled" );
        final String sshHost = System.getProperty( "org.uberfire.nio.git.ssh.host" );
        final String sshHostName = System.getProperty( "org.uberfire.nio.git.ssh.hostname" );
        final String sshPort = System.getProperty( "org.uberfire.nio.git.ssh.port" );
        final String sshCertDir = System.getProperty( "org.uberfire.nio.git.ssh.cert.dir" );

        if ( bareReposDir == null || bareReposDir.trim().isEmpty() ) {
            FILE_REPOSITORIES_ROOT = new File( REPOSITORIES_ROOT_DIR );
        } else {
            FILE_REPOSITORIES_ROOT = new File( bareReposDir.trim(), REPOSITORIES_ROOT_DIR );
        }

        if ( enabled == null || enabled.trim().isEmpty() ) {
            DAEMON_ENABLED = DAEMON_DEFAULT_ENABLED;
        } else {
            try {
                DAEMON_ENABLED = Boolean.valueOf( enabled );
            } catch ( Exception ex ) {
                DAEMON_ENABLED = DAEMON_DEFAULT_ENABLED;
            }
        }

        if ( port == null || port.trim().isEmpty() ) {
            DAEMON_PORT = DAEMON_DEFAULT_PORT;
        } else {
            DAEMON_PORT = Integer.valueOf( port );
        }
        if ( host == null || host.trim().isEmpty() ) {
            DAEMON_HOST_ADDR = DEFAULT_HOST_ADDR;
        } else {
            DAEMON_HOST_ADDR = host;
        }
        if ( hostName == null || hostName.trim().isEmpty() ) {
            if ( host != null && !host.trim().isEmpty() ) {
                DAEMON_HOST_NAME = host;
            } else {
                DAEMON_HOST_NAME = DEFAULT_HOST_NAME;
            }
        } else {
            DAEMON_HOST_NAME = hostName;
        }

        if ( sshEnabled == null || sshEnabled.trim().isEmpty() ) {
            SSH_ENABLED = SSH_DEFAULT_ENABLED;
        } else {
            try {
                SSH_ENABLED = Boolean.valueOf( sshEnabled );
            } catch ( Exception ex ) {
                SSH_ENABLED = SSH_DEFAULT_ENABLED;
            }
        }

        if ( sshPort == null || sshPort.trim().isEmpty() ) {
            SSH_PORT = SSH_DEFAULT_PORT;
        } else {
            SSH_PORT = Integer.valueOf( sshPort );
        }
        if ( sshHost == null || sshHost.trim().isEmpty() ) {
            SSH_HOST_ADDR = DEFAULT_HOST_ADDR;
        } else {
            SSH_HOST_ADDR = sshHost;
        }
        if ( sshHostName == null || sshHostName.trim().isEmpty() ) {
            if ( sshHost != null && !sshHost.trim().isEmpty() ) {
                SSH_HOST_NAME = sshHost;
            } else {
                SSH_HOST_NAME = DEFAULT_HOST_NAME;
            }
        } else {
            SSH_HOST_NAME = sshHostName;
        }

        if ( sshCertDir == null || sshCertDir.trim().isEmpty() ) {
            SSH_FILE_CERT_DIR = new File( SSH_FILE_CERT_ROOT_DIR );
        } else {
            SSH_FILE_CERT_DIR = new File( sshCertDir.trim(), SSH_FILE_CERT_ROOT_DIR );
        }

    }

    public void onCloseFileSystem( final JGitFileSystem fileSystem ) {
        closedFileSystems.add( fileSystem );
        oldHeadsOfPendingDiffs.remove( fileSystem );
        if ( closedFileSystems.size() == fileSystems.size() ) {
            if ( daemonService != null ) {
                daemonService.stop();
            }
            if ( gitSSHService != null ) {
                gitSSHService.stop();
            }
        }
    }

    public void onDisposeFileSystem( final JGitFileSystem fileSystem ) {
        onCloseFileSystem( fileSystem );
        closedFileSystems.remove( fileSystem );
        fileSystems.remove( fileSystem.id() );

        repoIndex.remove( fileSystem.gitRepo().getRepository() );
        clusterMap.remove( fileSystem.gitRepo().getRepository() );
    }

    private static JGitFileSystemProvider provider = null;

    public static JGitFileSystemProvider getInstance() {
        if ( provider == null ) {
            provider = new JGitFileSystemProvider();
        }
        return provider;
    }

    @Override
    public void setUserPassAuthenticator( final UserPassAuthenticator authenticator ) {
        this.authenticator = authenticator;
        if ( gitSSHService != null ) {
            gitSSHService.setUserPassAuthenticator( authenticator );
        }
    }

    @Override
    public void setAuthorizationManager( AuthorizationManager authorizationManager ) {
        this.authorizationManager = authorizationManager;
        if ( gitSSHService != null ) {
            gitSSHService.setAuthorizationManager( authorizationManager );
        }
    }

    public final class RepositoryResolverImpl<T> implements RepositoryResolver<T> {

        @Override
        public Repository open( final T client,
                                final String name )
                throws RepositoryNotFoundException,
                ServiceNotAuthorizedException, ServiceNotEnabledException,
                ServiceMayNotContinueException {
            final JGitFileSystem fs = fileSystems.get( name );
            if ( fs == null ) {
                throw new RepositoryNotFoundException( name );
            }
            return fs.gitRepo().getRepository();
        }

        public JGitFileSystem resolveFileSystem( final Repository repository ) {
            return repoIndex.get( repository );
        }
    }

    public JGitFileSystemProvider() {
        loadConfig();
        CredentialsProvider.setDefault( new UsernamePasswordCredentialsProvider( "guest", "" ) );

        if ( DAEMON_ENABLED ) {
            fullHostNames.put( "git", DAEMON_HOST_NAME + ":" + DAEMON_PORT );
        }
        if ( SSH_ENABLED ) {
            fullHostNames.put( "ssh", SSH_HOST_NAME + ":" + SSH_PORT );
        }

        final String[] repos = FILE_REPOSITORIES_ROOT.list( new FilenameFilter() {
            @Override
            public boolean accept( final File dir,
                                   String name ) {
                return name.endsWith( DOT_GIT_EXT );
            }
        } );
        if ( repos != null ) {
            for ( final String repo : repos ) {
                final File repoDir = new File( FILE_REPOSITORIES_ROOT, repo );
                if ( repoDir.isDirectory() ) {
                    final String name = repoDir.getName().substring( 0, repoDir.getName().indexOf( DOT_GIT_EXT ) );
                    final JGitFileSystem fs = new JGitFileSystem( this, fullHostNames, newRepository( repoDir, true ), name, ALL, buildCredential( null ) );
                    fileSystems.put( name, fs );
                    repoIndex.put( fs.gitRepo().getRepository(), fs );
                }
            }
        }
        if ( DAEMON_ENABLED ) {
            buildAndStartDaemon();
        } else {
            daemonService = null;
        }

        if ( SSH_ENABLED ) {
            buildAndStartSSH();
        } else {
            gitSSHService = null;
        }
    }

    private void buildAndStartSSH() {
        final ReceivePackFactory receivePackFactory = new ReceivePackFactory<BaseGitCommand>() {
            @Override
            public ReceivePack create( final BaseGitCommand req,
                                       final Repository db ) throws ServiceNotEnabledException, ServiceNotAuthorizedException {

                return new ReceivePack( db ) {{
                    final ClusterService clusterService = clusterMap.get( db );
                    final JGitFileSystem fs = repoIndex.get( db );
                    final Map<String, RevCommit> oldTreeRefs = new HashMap<String, RevCommit>();

                    setPreReceiveHook( new PreReceiveHook() {
                        @Override
                        public void onPreReceive( final ReceivePack rp,
                                                  final Collection<ReceiveCommand> commands ) {
                            if ( clusterService != null ) {
                                clusterService.lock();
                            }

                            for ( final ReceiveCommand command : commands ) {
                                final RevCommit lastCommit = JGitUtil.getLastCommit( fs.gitRepo(), command.getRefName() );
                                oldTreeRefs.put( command.getRefName(), lastCommit );
                            }
                        }
                    } );

                    setPostReceiveHook( new PostReceiveHook() {
                        @Override
                        public void onPostReceive( final ReceivePack rp,
                                                   final Collection<ReceiveCommand> commands ) {
                            for ( Map.Entry<String, RevCommit> oldTreeRef : oldTreeRefs.entrySet() ) {
                                final List<RevCommit> commits = JGitUtil.getCommits( fs, oldTreeRef.getKey(), oldTreeRef.getValue(), JGitUtil.getLastCommit( fs.gitRepo(), oldTreeRef.getKey() ) );
                                for ( final RevCommit revCommit : commits ) {
                                    notifyDiffs( fs, oldTreeRef.getKey(), "<ssh>", req.getUser().getName(), revCommit.getFullMessage(), revCommit.getParent( 0 ).getTree(), revCommit.getTree() );
                                }
                            }

                            if ( clusterService != null ) {
                                //TODO {porcelli} hack, that should be addressed in future
                                clusterService.broadcast( DEFAULT_IO_SERVICE_NAME,
                                                          new MessageType() {

                                                              @Override
                                                              public String toString() {
                                                                  return "SYNC_FS";
                                                              }

                                                              @Override
                                                              public int hashCode() {
                                                                  return "SYNC_FS".hashCode();
                                                              }
                                                          },
                                                          new HashMap<String, String>() {{
                                                              put( "fs_scheme", "git" );
                                                              put( "fs_id", fs.id() );
                                                              put( "fs_uri", fs.toString() );
                                                          }}
                                                        );

                                clusterService.unlock();
                            }
                        }
                    } );
                }};
            }
        };

        gitSSHService = new GitSSHService();

        gitSSHService.setup( SSH_FILE_CERT_DIR, SSH_HOST_ADDR, SSH_PORT, authenticator, authorizationManager, receivePackFactory, new RepositoryResolverImpl<BaseGitCommand>() );

        gitSSHService.start();
    }

    void buildAndStartDaemon() {
        if ( daemonService == null || !daemonService.isRunning() ) {
            daemonService = new Daemon( new InetSocketAddress( DAEMON_HOST_ADDR, DAEMON_PORT ) );
            daemonService.setRepositoryResolver( new RepositoryResolverImpl<DaemonClient>() );
            try {
                daemonService.start();
            } catch ( java.io.IOException e ) {
                throw new IOException( e );
            }
        }
    }

    void forceStopDaemon() {
        if ( daemonService != null && daemonService.isRunning() ) {
            daemonService.stop();
        }
    }

    @Override
    public synchronized void forceAsDefault() {
        this.isDefault = true;
    }

    @Override
    public boolean isDefault() {
        return isDefault;
    }

    @Override
    public String getScheme() {
        return SCHEME;
    }

    @Override
    public FileSystem newFileSystem( final Path path,
                                     final Map<String, ?> env )
            throws IllegalArgumentException, UnsupportedOperationException, IOException, SecurityException {
        throw new UnsupportedOperationException();
    }

    @Override
    public FileSystem newFileSystem( final URI uri,
                                     final Map<String, ?> env )
            throws IllegalArgumentException, IOException, SecurityException, FileSystemAlreadyExistsException {
        checkNotNull( "uri", uri );
        checkCondition( "uri scheme not supported", uri.getScheme().equals( getScheme() ) || uri.getScheme().equals( "default" ) );
        checkURI( "uri", uri );
        checkNotNull( "env", env );

        final String name = extractRepoName( uri );

        if ( fileSystems.containsKey( name ) ) {
            throw new FileSystemAlreadyExistsException();
        }

        ListBranchCommand.ListMode listMode;
        if ( env.containsKey( GIT_LIST_ROOT_BRANCH_MODE ) ) {
            try {
                listMode = ListBranchCommand.ListMode.valueOf( (String) env.get( GIT_LIST_ROOT_BRANCH_MODE ) );
            } catch ( Exception ex ) {
                listMode = null;
            }
        } else {
            listMode = null;
        }

        final Git git;
        final CredentialsProvider credential;

        boolean bare = true;
        final String outPath = (String) env.get( GIT_ENV_PROP_DEST_PATH );

        final File repoDest;
        if ( outPath != null ) {
            repoDest = new File( outPath, name + DOT_GIT_EXT );
        } else {
            repoDest = new File( FILE_REPOSITORIES_ROOT, name + DOT_GIT_EXT );
        }

        if ( env.containsKey( GIT_DEFAULT_REMOTE_NAME ) ) {
            final String originURI = env.get( GIT_DEFAULT_REMOTE_NAME ).toString();
            credential = buildCredential( env );
            git = cloneRepository( repoDest, originURI, bare, credential );
        } else {
            credential = buildCredential( null );
            git = newRepository( repoDest, bare );
        }

        final JGitFileSystem fs = new JGitFileSystem( this, fullHostNames, git, name, listMode, credential );
        fileSystems.put( name, fs );
        repoIndex.put( fs.gitRepo().getRepository(), fs );

        boolean init = false;

        if ( env.containsKey( INIT ) && Boolean.valueOf( env.get( INIT ).toString() ) ) {
            init = true;
        }

        if ( !env.containsKey( GIT_DEFAULT_REMOTE_NAME ) && init ) {
            try {
                final URI initURI = URI.create( getScheme() + "://master@" + name + "/readme.md" );
                final CommentedOption op = setupOp( env );
                final OutputStream stream = newOutputStream( getPath( initURI ), op );
                final String _init = "Repository Init Content\n" +
                        "=======================\n" +
                        "\n" +
                        "Your project description here.";
                stream.write( _init.getBytes() );
                stream.close();
            } catch ( final Exception e ) {
            }
            if ( !bare ) {
                //todo: checkout
            }
        }

        final Object _clusterService = env.get( "clusterService" );
        if ( _clusterService != null && _clusterService instanceof ClusterService ) {
            clusterMap.put( git.getRepository(), (ClusterService) _clusterService );
        }

        if ( DAEMON_ENABLED && daemonService != null && !daemonService.isRunning() ) {
            buildAndStartDaemon();
        }

        return fs;
    }

    private CommentedOption setupOp( final Map<String, ?> env ) {
        return null;
    }

    @Override
    public FileSystem getFileSystem( final URI uri )
            throws IllegalArgumentException, FileSystemNotFoundException, SecurityException {
        checkNotNull( "uri", uri );
        checkCondition( "uri scheme not supported", uri.getScheme().equals( getScheme() ) || uri.getScheme().equals( "default" ) );
        checkURI( "uri", uri );

        final JGitFileSystem fileSystem = fileSystems.get( extractRepoName( uri ) );

        if ( fileSystem == null ) {
            throw new FileSystemNotFoundException( "No filesystem for uri (" + uri + ") found." );
        }

        if ( hasSyncFlag( uri ) ) {
            try {
                final String treeRef = "master";
                final ObjectId oldHead = JGitUtil.getTreeRefObjectId( fileSystem.gitRepo().getRepository(), treeRef );
                final Map<String, String> params = getQueryParams( uri );
                syncRepository( fileSystem.gitRepo(), fileSystem.getCredential(), params.get( "sync" ), hasForceFlag( uri ) );
                final ObjectId newHead = JGitUtil.getTreeRefObjectId( fileSystem.gitRepo().getRepository(), treeRef );
                notifyDiffs( fileSystem, treeRef, "<system>", "<system>", "", oldHead, newHead );
            } catch ( final Exception ex ) {
                throw new IOException( ex );
            }
        }
        if ( hasPushFlag( uri ) ) {
            try {
                final Map<String, String> params = getQueryParams( uri );
                pushRepository( fileSystem.gitRepo(), fileSystem.getCredential(), params.get( "push" ), hasForceFlag( uri ) );
            } catch ( final Exception ex ) {
                throw new IOException( ex );
            }
        }

        return fileSystem;
    }

    @Override
    public Path getPath( final URI uri )
            throws IllegalArgumentException, FileSystemNotFoundException, SecurityException {
        checkNotNull( "uri", uri );
        checkCondition( "uri scheme not supported", uri.getScheme().equals( getScheme() ) || uri.getScheme().equals( "default" ) );
        checkURI( "uri", uri );

        final JGitFileSystem fileSystem = fileSystems.get( extractRepoName( uri ) );

        if ( fileSystem == null ) {
            throw new FileSystemNotFoundException();
        }

        return JGitPathImpl.create( fileSystem, extractPath( uri ), extractHost( uri ), false );
    }

    @Override
    public InputStream newInputStream( final Path path,
                                       final OpenOption... options )
            throws IllegalArgumentException, UnsupportedOperationException, NoSuchFileException, IOException, SecurityException {
        checkNotNull( "path", path );

        final JGitPathImpl gPath = toPathImpl( path );

        return resolveInputStream( gPath.getFileSystem().gitRepo(), gPath.getRefTree(), gPath.getPath() );
    }

    @Override
    public OutputStream newOutputStream( final Path path,
                                         final OpenOption... options )
            throws IllegalArgumentException, UnsupportedOperationException, IOException, SecurityException {
        checkNotNull( "path", path );

        final JGitPathImpl gPath = toPathImpl( path );

        final Pair<PathType, ObjectId> result = checkPath( gPath.getFileSystem().gitRepo(), gPath.getRefTree(), gPath.getPath() );

        if ( result.getK1().equals( PathType.DIRECTORY ) ) {
            throw new IOException();
        }

        try {
            final File file = File.createTempFile( "gitz", "woot" );
            return new FilterOutputStream( new FileOutputStream( file ) ) {
                public void close() throws java.io.IOException {
                    super.close();

                    commit( gPath, buildCommitInfo( null, Arrays.asList( options ) ), new DefaultCommitContent( new HashMap<String, File>() {{
                        put( gPath.getPath(), file );
                    }} ) );
                }
            };
        } catch ( java.io.IOException e ) {
            throw new IOException( e );
        }
    }

    private CommitInfo buildCommitInfo( final String defaultMessage,
                                        final Collection<? extends Option> options ) {
        String sessionId = null;
        String name = null;
        String email = null;
        String message = defaultMessage;
        TimeZone timeZone = null;
        Date when = null;

        if ( options != null && !options.isEmpty() ) {
            final CommentedOption op = extractCommentedOption( options );
            if ( op != null ) {
                sessionId = op.getSessionId();
                name = op.getName();
                email = op.getEmail();
                if ( op.getMessage() != null && !op.getMessage().trim().isEmpty() ) {
                    message = op.getMessage();
                }
                timeZone = op.getTimeZone();
                when = op.getWhen();
            }
        }

        return new CommitInfo( sessionId, name, email, message, timeZone, when );
    }

    final CommentedOption extractCommentedOption( final Collection<? extends Option> options ) {
        for ( final Option option : options ) {
            if ( option instanceof CommentedOption ) {
                return (CommentedOption) option;
            }
        }
        return null;
    }

    @Override
    public FileChannel newFileChannel( final Path path,
                                       Set<? extends OpenOption> options,
                                       final FileAttribute<?>... attrs )
            throws IllegalArgumentException, UnsupportedOperationException, IOException, SecurityException {
        throw new UnsupportedOperationException();
    }

    @Override
    public AsynchronousFileChannel newAsynchronousFileChannel( final Path path,
                                                               final Set<? extends OpenOption> options,
                                                               final ExecutorService executor,
                                                               FileAttribute<?>... attrs )
            throws IllegalArgumentException, UnsupportedOperationException, IOException, SecurityException {
        throw new UnsupportedOperationException();
    }

    @Override
    public SeekableByteChannel newByteChannel( final Path path,
                                               final Set<? extends OpenOption> options,
                                               final FileAttribute<?>... attrs )
            throws IllegalArgumentException, UnsupportedOperationException, FileAlreadyExistsException, IOException, SecurityException {
        final JGitPathImpl gPath = toPathImpl( path );

        if ( exists( path ) ) {
            if ( !shouldCreateOrOpenAByteChannel( options ) ) {
                throw new FileAlreadyExistsException( path.toString() );
            }
        }

        final Pair<PathType, ObjectId> result = checkPath( gPath.getFileSystem().gitRepo(), gPath.getRefTree(), gPath.getPath() );

        if ( result.getK1().equals( PathType.DIRECTORY ) ) {
            throw new IOException();
        }

        try {
            if ( options != null && options.contains( READ ) ) {
                return openAByteChannel( path );
            } else {
                return createANewByteChannel( path, options, gPath, attrs );
            }
        } catch ( java.io.IOException e ) {
            throw new IOException( e );
        } finally {
            ( (AbstractPath) path ).clearCache();
        }
    }

    private SeekableByteChannel createANewByteChannel( final Path path,
                                                       final Set<? extends OpenOption> options,
                                                       final JGitPathImpl gPath,
                                                       final FileAttribute<?>[] attrs ) throws java.io.IOException {
        final File file = File.createTempFile( "gitz", "woot" );

        return new SeekableByteChannelFileBasedImpl( new RandomAccessFile( file, "rw" ).getChannel() ) {
            @Override
            public void close() throws java.io.IOException {
                super.close();

                File tempDot = null;
                final boolean hasDotContent;
                if ( options != null && options.contains( new DotFileOption() ) ) {
                    deleteIfExists( dot( path ), extractCommentedOption( options ) );
                    tempDot = File.createTempFile( "meta", "dot" );
                    hasDotContent = buildDotFile( path, new FileOutputStream( tempDot ), attrs );
                } else {
                    hasDotContent = false;
                }

                final File dotfile = tempDot;

                commit( gPath, buildCommitInfo( null, options ), new DefaultCommitContent( new HashMap<String, File>() {{
                    put( gPath.getPath(), file );
                    if ( hasDotContent ) {
                        put( toPathImpl( dot( gPath ) ).getPath(), dotfile );
                    }
                }} ) );
            }

        };
    }

    private SeekableByteChannelFileBasedImpl openAByteChannel( Path path ) throws FileNotFoundException {
        return new SeekableByteChannelFileBasedImpl( new RandomAccessFile( path.toFile(), "r" ).getChannel() );
    }

    private boolean shouldCreateOrOpenAByteChannel( Set<? extends OpenOption> options ) {
        return ( options != null && ( options.contains( TRUNCATE_EXISTING ) || options.contains( READ ) ) );
    }

    protected boolean exists( final Path path ) {
        try {
            readAttributes( path, BasicFileAttributes.class );
            return true;
        } catch ( final Exception ignored ) {
        }
        return false;
    }

    @Override
    public DirectoryStream<Path> newDirectoryStream( final Path path,
                                                     final DirectoryStream.Filter<Path> pfilter )
            throws NotDirectoryException, IOException, SecurityException {
        checkNotNull( "path", path );
        final DirectoryStream.Filter<Path> filter;
        if ( pfilter == null ) {
            filter = new DirectoryStream.Filter<Path>() {
                @Override
                public boolean accept( final Path entry ) throws IOException {
                    return true;
                }
            };
        } else {
            filter = pfilter;
        }

        final JGitPathImpl gPath = toPathImpl( path );

        final Pair<PathType, ObjectId> result = checkPath( gPath.getFileSystem().gitRepo(), gPath.getRefTree(), gPath.getPath() );

        if ( !result.getK1().equals( PathType.DIRECTORY ) ) {
            throw new NotDirectoryException( path.toString() );
        }

        final List<JGitPathInfo> pathContent = listPathContent( gPath.getFileSystem().gitRepo(), gPath.getRefTree(), gPath.getPath() );

        return new DirectoryStream<Path>() {
            boolean isClosed = false;

            @Override
            public void close() throws IOException {
                if ( isClosed ) {
                    throw new IOException();
                }
                isClosed = true;
            }

            @Override
            public Iterator<Path> iterator() {
                if ( isClosed ) {
                    throw new IOException();
                }
                return new Iterator<Path>() {
                    private int i = -1;
                    private Path nextEntry = null;
                    public boolean atEof = false;

                    @Override
                    public boolean hasNext() {
                        if ( nextEntry == null && !atEof ) {
                            nextEntry = readNextEntry();
                        }
                        return nextEntry != null;
                    }

                    @Override
                    public Path next() {
                        final Path result;
                        if ( nextEntry == null && !atEof ) {
                            result = readNextEntry();
                        } else {
                            result = nextEntry;
                            nextEntry = null;
                        }
                        if ( result == null ) {
                            throw new NoSuchElementException();
                        }
                        return result;
                    }

                    private Path readNextEntry() {
                        if ( atEof ) {
                            return null;
                        }

                        Path result = null;
                        while ( true ) {
                            i++;
                            if ( i >= pathContent.size() ) {
                                atEof = true;
                                break;
                            }

                            final JGitPathInfo content = pathContent.get( i );
                            final Path path = JGitPathImpl.create( gPath.getFileSystem(), "/" + content.getPath(), gPath.getHost(), content.getObjectId(), gPath.isRealPath() );
                            if ( filter.accept( path ) ) {
                                result = path;
                                break;
                            }
                        }

                        return result;
                    }

                    @Override
                    public void remove() {
                        throw new UnsupportedOperationException();
                    }
                };
            }
        };
    }

    @Override
    public void createDirectory( final Path path,
                                 final FileAttribute<?>... attrs )
            throws UnsupportedOperationException, FileAlreadyExistsException, IOException, SecurityException {
        checkNotNull( "path", path );

        final JGitPathImpl gPath = toPathImpl( path );

        final Pair<PathType, ObjectId> result = checkPath( gPath.getFileSystem().gitRepo(), gPath.getRefTree(), gPath.getPath() );

        if ( !result.getK1().equals( NOT_FOUND ) ) {
            throw new FileAlreadyExistsException( path.toString() );
        }

        try {
            final OutputStream outputStream = newOutputStream( path.resolve( ".gitignore" ) );
            outputStream.write( "# empty\n".getBytes() );
            outputStream.close();
        } catch ( final Exception e ) {
            throw new IOException( e );
        }
    }

    @Override
    public void createSymbolicLink( final Path link,
                                    final Path target,
                                    final FileAttribute<?>... attrs )
            throws UnsupportedOperationException, FileAlreadyExistsException, IOException, SecurityException {
        throw new UnsupportedOperationException();
    }

    @Override
    public void createLink( final Path link,
                            final Path existing )
            throws UnsupportedOperationException, FileAlreadyExistsException, IOException, SecurityException {
        throw new UnsupportedOperationException();
    }

    @Override
    public void delete( final Path path,
                        final DeleteOption... options )
            throws DirectoryNotEmptyException, NoSuchFileException, IOException, SecurityException {
        checkNotNull( "path", path );

        if ( path instanceof JGitFSPath ) {
            deleteRepo( path.getFileSystem() );
            return;
        }

        final JGitPathImpl gPath = toPathImpl( path );

        if ( isBranch( gPath ) ) {
            deleteBranch( gPath );
            return;
        }

        deleteAsset( gPath, options );
    }

    private boolean deleteRepo( final FileSystem fileSystem ) {
        final File gitDir = ( (JGitFileSystem) fileSystem ).gitRepo().getRepository().getDirectory();
        fileSystem.close();
        fileSystem.dispose();

        try {
            if ( System.getProperty( "os.name" ).toLowerCase().contains( "windows" ) ) {
                //this operation forces a cache clean freeing any lock -> windows only issue!
                WindowCache.reconfigure( new WindowCacheConfig() );
            }
            FileUtils.delete( gitDir, FileUtils.RECURSIVE | FileUtils.RETRY );
            return true;
        } catch ( java.io.IOException e ) {
            throw new IOException( e );
        }
    }

    public void deleteAsset( final JGitPathImpl path,
                             final DeleteOption... options ) {
        final Pair<PathType, ObjectId> result = checkPath( path.getFileSystem().gitRepo(), path.getRefTree(), path.getPath() );

        if ( result.getK1().equals( PathType.DIRECTORY ) ) {
            if ( deleteNonEmptyDirectory( options ) ) {
                deleteResource( path, options );
                return;
            }
            final List<JGitPathInfo> content = listPathContent( path.getFileSystem().gitRepo(), path.getRefTree(), path.getPath() );
            if ( content.size() == 1 && content.get( 0 ).getPath().equals( path.getPath().substring( 1 ) + "/.gitignore" ) ) {
                delete( path.resolve( ".gitignore" ) );
                deleteResource( path, options );
                return;
            }
            throw new DirectoryNotEmptyException( path.toString() );
        }

        if ( result.getK1().equals( NOT_FOUND ) ) {
            throw new NoSuchFileException( path.toString() );
        }

        deleteResource( path, options );
    }

    void deleteResource( final JGitPathImpl path,
                         final DeleteOption... options ) {
        delete( path, buildCommitInfo( "delete {" + path.getPath() + "}", Arrays.asList( options ) ) );
    }

    private boolean deleteNonEmptyDirectory( final DeleteOption... options ) {

        for ( final DeleteOption option : options ) {
            if ( option.equals( StandardDeleteOption.NON_EMPTY_DIRECTORIES ) ) {
                return true;
            }
        }

        return false;
    }

    public void deleteBranch( final JGitPathImpl path ) {
        final Ref branch = getBranch( path.getFileSystem().gitRepo(), path.getRefTree() );

        if ( branch == null ) {
            throw new NoSuchFileException( path.toString() );
        }

        JGitUtil.deleteBranch( path.getFileSystem().gitRepo(), branch );
    }

    @Override
    public boolean deleteIfExists( final Path path,
                                   final DeleteOption... options )
            throws DirectoryNotEmptyException, IOException, SecurityException {
        checkNotNull( "path", path );

        if ( path instanceof JGitFSPath ) {
            return deleteRepo( path.getFileSystem() );
        }

        final JGitPathImpl gPath = toPathImpl( path );

        if ( isBranch( gPath ) ) {
            return deleteBranchIfExists( gPath );
        }

        return deleteAssetIfExists( gPath, options );
    }

    public boolean deleteBranchIfExists( final JGitPathImpl path ) {
        final Ref branch = getBranch( path.getFileSystem().gitRepo(), path.getRefTree() );

        if ( branch == null ) {
            return false;
        }

        JGitUtil.deleteBranch( path.getFileSystem().gitRepo(), branch );
        return true;
    }

    public boolean deleteAssetIfExists( final JGitPathImpl path,
                                        final DeleteOption... options ) {
        final Pair<PathType, ObjectId> result = checkPath( path.getFileSystem().gitRepo(), path.getRefTree(), path.getPath() );

        if ( result.getK1().equals( PathType.DIRECTORY ) ) {
            if ( deleteNonEmptyDirectory( options ) ) {
                deleteResource( path, options );
                return true;
            }
            final List<JGitPathInfo> content = listPathContent( path.getFileSystem().gitRepo(), path.getRefTree(), path.getPath() );
            if ( content.size() == 1 && content.get( 0 ).getPath().equals( path.getPath().substring( 1 ) + "/.gitignore" ) ) {
                delete( path.resolve( ".gitignore" ) );
                return true;
            }
            throw new DirectoryNotEmptyException( path.toString() );
        }

        if ( result.getK1().equals( NOT_FOUND ) ) {
            return false;
        }

        deleteResource( path, options );
        return true;
    }

    private String deleteCommitMessage( final String path,
                                        final DeleteOption... options ) {
        return "delete {" + path + "}";
    }

    @Override
    public Path readSymbolicLink( final Path link )
            throws UnsupportedOperationException, NotLinkException, IOException, SecurityException {
        checkNotNull( "link", link );
        throw new UnsupportedOperationException();
    }

    @Override
    public void copy( final Path source,
                      final Path target,
                      final CopyOption... options )
            throws UnsupportedOperationException, FileAlreadyExistsException, DirectoryNotEmptyException, IOException, SecurityException {
        checkNotNull( "source", source );
        checkNotNull( "target", target );

        final JGitPathImpl gSource = toPathImpl( source );
        final JGitPathImpl gTarget = toPathImpl( target );
        final boolean isBranch = isBranch( gSource ) && isBranch( gTarget );

        if ( options.length == 1 && options[ 0 ] instanceof CherryPickCopyOption ) {
            if ( !isBranch ) {
                throw new IOException( "Cherry pick needs source and target as root." );
            }
            final String[] commits = ( (CherryPickCopyOption) options[ 0 ] ).getCommits();
            if ( commits == null || commits.length == 0 ) {
                throw new IOException( "Cherry pick needs at least one commit id." );
            }
            cherryPick( gSource, gTarget, commits );
        } else {
            if ( isBranch ) {
                copyBranch( gSource, gTarget );
                return;
            }
            copyAsset( gSource, gTarget, options );
        }
    }

    private void cherryPick( final JGitPathImpl source,
                             final JGitPathImpl target,
                             final String... commits ) {
        JGitUtil.cherryPick( source.getFileSystem().gitRepo().getRepository(), target.getRefTree(), commits );
    }

    private void copyBranch( final JGitPathImpl source,
                             final JGitPathImpl target ) {
        checkCondition( "source and taget should have same setup", !hasSameFileSystem( source, target ) );
        if ( existsBranch( target ) ) {
            throw new FileAlreadyExistsException( target.toString() );
        }
        if ( !existsBranch( source ) ) {
            throw new NoSuchFileException( target.toString() );
        }
        createBranch( source, target );
    }

    private void copyAsset( final JGitPathImpl source,
                            final JGitPathImpl target,
                            final CopyOption... options ) {
        final Pair<PathType, ObjectId> sourceResult = checkPath( source.getFileSystem().gitRepo(), source.getRefTree(), source.getPath() );
        final Pair<PathType, ObjectId> targetResult = checkPath( target.getFileSystem().gitRepo(), target.getRefTree(), target.getPath() );

        if ( !isRoot( target ) && targetResult.getK1() != NOT_FOUND ) {
            if ( !contains( options, StandardCopyOption.REPLACE_EXISTING ) ) {
                throw new FileAlreadyExistsException( target.toString() );
            }
        }

        if ( sourceResult.getK1() == NOT_FOUND ) {
            throw new NoSuchFileException( target.toString() );
        }

        if ( !source.getRefTree().equals( target.getRefTree() ) ) {
            copyAssetContent( source, target, options );
        } else {
            final Map<JGitPathImpl, JGitPathImpl> sourceDest = new HashMap<JGitPathImpl, JGitPathImpl>();
            if ( sourceResult.getK1() == DIRECTORY ) {
                sourceDest.putAll( mapDirectoryContent( source, target, options ) );
            } else {
                sourceDest.put( source, target );
            }

            copyFiles( source, target, sourceDest, options );
        }
    }

    private void copyAssetContent( final JGitPathImpl source,
                                   final JGitPathImpl target,
                                   final CopyOption... options ) {
        final Pair<PathType, ObjectId> sourceResult = checkPath( source.getFileSystem().gitRepo(), source.getRefTree(), source.getPath() );
        final Pair<PathType, ObjectId> targetResult = checkPath( target.getFileSystem().gitRepo(), target.getRefTree(), target.getPath() );

        if ( !isRoot( target ) && targetResult.getK1() != NOT_FOUND ) {
            if ( !contains( options, StandardCopyOption.REPLACE_EXISTING ) ) {
                throw new FileAlreadyExistsException( target.toString() );
            }
        }

        if ( sourceResult.getK1() == NOT_FOUND ) {
            throw new NoSuchFileException( target.toString() );
        }

        if ( sourceResult.getK1() == DIRECTORY ) {
            copyDirectory( source, target, options );
            return;
        }

        copyFile( source, target, options );
    }

    private boolean contains( final CopyOption[] options,
                              final CopyOption opt ) {
        for ( final CopyOption option : options ) {
            if ( option.equals( opt ) ) {
                return true;
            }
        }
        return false;
    }

    private void copyDirectory( final JGitPathImpl source,
                                final JGitPathImpl target,
                                final CopyOption... options ) {
        final List<JGitPathImpl> directories = new ArrayList<JGitPathImpl>();
        for ( final Path path : newDirectoryStream( source, null ) ) {
            final JGitPathImpl gPath = toPathImpl( path );
            final Pair<PathType, ObjectId> pathResult = checkPath( gPath.getFileSystem().gitRepo(), gPath.getRefTree(), gPath.getPath() );
            if ( pathResult.getK1() == DIRECTORY ) {
                directories.add( gPath );
                continue;
            }
            final JGitPathImpl gTarget = composePath( target, (JGitPathImpl) gPath.getFileName() );

            copyFile( gPath, gTarget );
        }
        for ( final JGitPathImpl directory : directories ) {
            createDirectory( composePath( target, (JGitPathImpl) directory.getFileName() ) );
        }
    }

    private JGitPathImpl composePath( final JGitPathImpl directory,
                                      final JGitPathImpl fileName,
                                      final CopyOption... options ) {
        if ( directory.getPath().endsWith( "/" ) ) {
            return toPathImpl( getPath( URI.create( directory.toUri().toString() + uriEncode( fileName.toString( false ) ) ) ) );
        }
        return toPathImpl( getPath( URI.create( directory.toUri().toString() + "/" + uriEncode( fileName.toString( false ) ) ) ) );
    }

    private String uriEncode( final String s ) {
        try {
            return URLEncoder.encode( s, "UTF-8" );
        } catch ( UnsupportedEncodingException e ) {
            return s;
        }
    }

    private void copyFile( final JGitPathImpl source,
                           final JGitPathImpl target,
                           final CopyOption... options ) {

        final InputStream in = newInputStream( source, convert( options ) );
        final SeekableByteChannel out = newByteChannel( target, new HashSet<OpenOption>() {{
            add( StandardOpenOption.TRUNCATE_EXISTING );
            for ( final CopyOption _option : options ) {
                if ( _option instanceof OpenOption ) {
                    add( (OpenOption) _option );
                }
            }
        }} );

        try {
            int count;
            byte[] buffer = new byte[ 8192 ];
            while ( ( count = in.read( buffer ) ) > 0 ) {
                out.write( ByteBuffer.wrap( buffer, 0, count ) );
            }
        } catch ( Exception e ) {
            throw new IOException( e );
        } finally {
            try {
                out.close();
            } catch ( java.io.IOException e ) {
                throw new IOException( e );
            } finally {
                try {
                    in.close();
                } catch ( java.io.IOException e ) {
                    throw new IOException( e );
                }
            }
        }
    }

    private OpenOption[] convert( CopyOption... options ) {
        if ( options == null || options.length == 0 ) {
            return new OpenOption[ 0 ];
        }
        final List<OpenOption> newOptions = new ArrayList<OpenOption>( options.length );
        for ( final CopyOption option : options ) {
            if ( option instanceof OpenOption ) {
                newOptions.add( (OpenOption) option );
            }
        }

        return newOptions.toArray( new OpenOption[ newOptions.size() ] );
    }

    private void createBranch( final JGitPathImpl source,
                               final JGitPathImpl target ) {
        JGitUtil.createBranch( source.getFileSystem().gitRepo(), source.getRefTree(), target.getRefTree() );
    }

    private boolean existsBranch( final JGitPathImpl path ) {
        return hasBranch( path.getFileSystem().gitRepo(), path.getRefTree() );
    }

    private boolean isBranch( final JGitPathImpl path ) {
        return path.getPath().length() == 1 && path.getPath().equals( "/" );
    }

    private boolean isRoot( final JGitPathImpl path ) {
        return isBranch( path );
    }

    private boolean hasSameFileSystem( final JGitPathImpl source,
                                       final JGitPathImpl target ) {
        return source.getFileSystem().equals( target );
    }

    @Override
    public void move( final Path source,
                      final Path target,
                      final CopyOption... options )
            throws DirectoryNotEmptyException, AtomicMoveNotSupportedException, IOException, SecurityException {
        checkNotNull( "source", source );
        checkNotNull( "target", target );

        final JGitPathImpl gSource = toPathImpl( source );
        final JGitPathImpl gTarget = toPathImpl( target );

        final boolean isSourceBranch = isBranch( gSource );
        final boolean isTargetBranch = isBranch( gTarget );

        if ( isSourceBranch && isTargetBranch ) {
            moveBranch( gSource, gTarget, options );
            return;
        }
        moveAsset( gSource, gTarget, options );
    }

    private void moveBranch( final JGitPathImpl source,
                             final JGitPathImpl target,
                             final CopyOption... options ) {
        checkCondition( "source and taget should have same setup", !hasSameFileSystem( source, target ) );

        if ( !exists( source ) ) {
            throw new NoSuchFileException( target.toString() );
        }

        boolean targetExists = existsBranch( target );
        if ( targetExists && !contains( options, StandardCopyOption.REPLACE_EXISTING ) ) {
            throw new FileAlreadyExistsException( target.toString() );
        }

        if ( !targetExists ) {
            createBranch( source, target );
            deleteBranch( source );
        } else {
            commit( target, buildCommitInfo( "reverting from {" + source.getPath() + "}", Arrays.asList( options ) ), new RevertCommitContent( source.getRefTree() ) );
        }
    }

    private void moveAsset( final JGitPathImpl source,
                            final JGitPathImpl target,
                            final CopyOption... options ) {
        final Pair<PathType, ObjectId> sourceResult = checkPath( source.getFileSystem().gitRepo(), source.getRefTree(), source.getPath() );
        final Pair<PathType, ObjectId> targetResult = checkPath( target.getFileSystem().gitRepo(), target.getRefTree(), target.getPath() );

        if ( !isRoot( target ) && targetResult.getK1() != NOT_FOUND ) {
            if ( !contains( options, StandardCopyOption.REPLACE_EXISTING ) ) {
                throw new FileAlreadyExistsException( target.toString() );
            }
        }

        if ( sourceResult.getK1() == NOT_FOUND ) {
            throw new NoSuchFileException( target.toString() );
        }

        if ( !source.getRefTree().equals( target.getRefTree() ) ) {
            copy( source, target, options );
            delete( source );
        } else {
            final Map<JGitPathImpl, JGitPathImpl> fromTo = new HashMap<JGitPathImpl, JGitPathImpl>();
            if ( sourceResult.getK1() == DIRECTORY ) {
                fromTo.putAll( mapDirectoryContent( source, target, options ) );
            } else {
                fromTo.put( source, target );
            }

            moveFiles( source, target, fromTo, options );
        }
    }

    private Map<JGitPathImpl, JGitPathImpl> mapDirectoryContent( final JGitPathImpl source,
                                                                 final JGitPathImpl target,
                                                                 final CopyOption... options ) {
        final Map<JGitPathImpl, JGitPathImpl> fromTo = new HashMap<JGitPathImpl, JGitPathImpl>();
        for ( final Path path : newDirectoryStream( source, null ) ) {
            final JGitPathImpl gPath = toPathImpl( path );
            final Pair<PathType, ObjectId> pathResult = checkPath( gPath.getFileSystem().gitRepo(), gPath.getRefTree(), gPath.getPath() );
            if ( pathResult.getK1() == DIRECTORY ) {
                fromTo.putAll( mapDirectoryContent( gPath, composePath( target, (JGitPathImpl) gPath.getFileName() ) ) );
            } else {
                final JGitPathImpl gTarget = composePath( target, (JGitPathImpl) gPath.getFileName() );
                fromTo.put( gPath, gTarget );
            }
        }

        return fromTo;
    }

    private void moveFiles( final JGitPathImpl source,
                            final JGitPathImpl target,
                            final Map<JGitPathImpl, JGitPathImpl> fromTo,
                            final CopyOption... options ) {
        final Map<String, String> result = new HashMap<String, String>( fromTo.size() );
        for ( final Map.Entry<JGitPathImpl, JGitPathImpl> fromToEntry : fromTo.entrySet() ) {
            result.put( fixPath( fromToEntry.getKey().getPath() ), fixPath( fromToEntry.getValue().getPath() ) );
        }
        commit( source, buildCommitInfo( "moving from {" + source.getPath() + "} to {" + target.getPath() + "}", Arrays.asList( options ) ), new MoveCommitContent( result ) );
    }

    private void copyFiles( final JGitPathImpl source,
                            final JGitPathImpl target,
                            final Map<JGitPathImpl, JGitPathImpl> sourceDest,
                            final CopyOption... options ) {
        final Map<String, String> result = new HashMap<String, String>( sourceDest.size() );
        for ( final Map.Entry<JGitPathImpl, JGitPathImpl> sourceDestEntry : sourceDest.entrySet() ) {
            result.put( fixPath( sourceDestEntry.getKey().getPath() ), fixPath( sourceDestEntry.getValue().getPath() ) );
        }
        commit( source, buildCommitInfo( "copy from {" + source.getPath() + "} to {" + target.getPath() + "}", Arrays.asList( options ) ), new CopyCommitContent( result ) );
    }

    @Override
    public boolean isSameFile( final Path pathA,
                               final Path pathB )
            throws IOException, SecurityException {
        checkNotNull( "pathA", pathA );
        checkNotNull( "pathB", pathB );

        final JGitPathImpl gPathA = toPathImpl( pathA );
        final JGitPathImpl gPathB = toPathImpl( pathB );

        final Pair<PathType, ObjectId> resultA = checkPath( gPathA.getFileSystem().gitRepo(), gPathA.getRefTree(), gPathA.getPath() );
        final Pair<PathType, ObjectId> resultB = checkPath( gPathB.getFileSystem().gitRepo(), gPathB.getRefTree(), gPathB.getPath() );

        if ( resultA.getK1() == PathType.FILE && resultA.getK2().equals( resultB.getK2() ) ) {
            return true;
        }

        return pathA.equals( pathB );
    }

    @Override
    public boolean isHidden( final Path path )
            throws IllegalArgumentException, IOException, SecurityException {
        checkNotNull( "path", path );

        final JGitPathImpl gPath = toPathImpl( path );

        if ( gPath.getFileName() == null ) {
            return false;
        }

        return toPathImpl( path.getFileName() ).toString( false ).startsWith( "." );
    }

    @Override
    public FileStore getFileStore( final Path path )
            throws IOException, SecurityException {
        checkNotNull( "path", path );

        return new JGitFileStore( toPathImpl( path ).getFileSystem().gitRepo().getRepository() );
    }

    @Override
    public void checkAccess( final Path path,
                             final AccessMode... modes )
            throws UnsupportedOperationException, NoSuchFileException, AccessDeniedException, IOException, SecurityException {
        checkNotNull( "path", path );

        final JGitPathImpl gPath = toPathImpl( path );

        final Pair<PathType, ObjectId> result = checkPath( gPath.getFileSystem().gitRepo(), gPath.getRefTree(), gPath.getPath() );

        if ( result.getK1().equals( NOT_FOUND ) ) {
            throw new NoSuchFileException( path.toString() );
        }
    }

    @Override
    public <V extends FileAttributeView> V getFileAttributeView( final Path path,
                                                                 final Class<V> type,
                                                                 final LinkOption... options )
            throws NoSuchFileException {
        checkNotNull( "path", path );
        checkNotNull( "type", type );

        final JGitPathImpl gPath = toPathImpl( path );

        final Pair<PathType, ObjectId> pathResult = checkPath( gPath.getFileSystem().gitRepo(), gPath.getRefTree(), gPath.getPath() );
        if ( pathResult.getK1().equals( NOT_FOUND ) ) {
            throw new NoSuchFileException( path.toString() );
        }

        final V resultView = gPath.getAttrView( type );

        if ( resultView == null ) {
            if ( type == BasicFileAttributeView.class || type == JGitBasicAttributeView.class ) {
                final V newView = (V) new JGitBasicAttributeView( gPath );
                gPath.addAttrView( newView );
                return newView;
            } else if ( type == VersionAttributeView.class || type == JGitVersionAttributeView.class ) {
                final V newView = (V) new JGitVersionAttributeView( gPath );
                gPath.addAttrView( newView );
                return newView;
            }
        }

        return resultView;
    }

    private ExtendedAttributeView getFileAttributeView( final JGitPathImpl path,
                                                        final String name,
                                                        final LinkOption... options ) {
        final ExtendedAttributeView view = path.getAttrView( name );

        if ( view == null ) {

            if ( name.equals( "basic" ) ) {
                final JGitBasicAttributeView newView = new JGitBasicAttributeView( path );
                path.addAttrView( newView );
                return newView;

            } else if ( name.equals( "version" ) ) {
                final JGitVersionAttributeView newView = new JGitVersionAttributeView( path );
                path.addAttrView( newView );
                return newView;
            }
        }
        return view;
    }

    @Override
    public <A extends BasicFileAttributes> A readAttributes( final Path path,
                                                             final Class<A> type,
                                                             final LinkOption... options )
            throws NoSuchFileException, UnsupportedOperationException, IOException, SecurityException {
        checkNotNull( "path", path );
        checkNotNull( "type", type );

        final JGitPathImpl gPath = toPathImpl( path );

        final Pair<PathType, ObjectId> pathResult = checkPath( gPath.getFileSystem().gitRepo(), gPath.getRefTree(), gPath.getPath() );
        if ( pathResult.getK1().equals( NOT_FOUND ) ) {
            throw new NoSuchFileException( path.toString() );
        }

        if ( type == VersionAttributes.class ) {
            final JGitVersionAttributeView view = getFileAttributeView( path, JGitVersionAttributeView.class, options );
            return (A) view.readAttributes();
        } else if ( type == BasicFileAttributesImpl.class || type == BasicFileAttributes.class ) {
            final JGitBasicAttributeView view = getFileAttributeView( path, JGitBasicAttributeView.class, options );
            return (A) view.readAttributes();
        }

        return null;
    }

    @Override
    public Map<String, Object> readAttributes( final Path path,
                                               final String attributes,
                                               final LinkOption... options )
            throws UnsupportedOperationException, IllegalArgumentException, IOException, SecurityException {
        checkNotNull( "path", path );
        checkNotEmpty( "attributes", attributes );

        final String[] s = split( attributes );
        if ( s[ 0 ].length() == 0 ) {
            throw new IllegalArgumentException( attributes );
        }

        final ExtendedAttributeView view = getFileAttributeView( toPathImpl( path ), s[ 0 ], options );
        if ( view == null ) {
            throw new UnsupportedOperationException( "View '" + s[ 0 ] + "' not available" );
        }

        return view.readAttributes( s[ 1 ].split( "," ) );
    }

    @Override
    public void setAttribute( final Path path,
                              final String attribute,
                              final Object value,
                              final LinkOption... options )
            throws UnsupportedOperationException, IllegalArgumentException, ClassCastException, IOException, SecurityException {
        checkNotNull( "path", path );
        checkNotEmpty( "attributes", attribute );

        if ( attribute.equals( FileSystemState.FILE_SYSTEM_STATE_ATTR ) ) {
            JGitFileSystem fileSystem = (JGitFileSystem) path.getFileSystem();

            if ( value instanceof CommentedOption ) {
                fileSystem.setBatchCommitInfo( "Batch mode", (CommentedOption) value );
                return;
            }

            final boolean isOriginalStateBatch = fileSystem.isOnBatch();

            fileSystem.setState( value.toString() );
            FileSystemState.valueOf( value.toString() );

            if ( isOriginalStateBatch && !fileSystem.isOnBatch() ) {
                fileSystem.setBatchCommitInfo( null );
                notifyAllDiffs();
            }
            fileSystem.setHadCommitOnBatchState( false );
            return;
        }

        final String[] s = split( attribute );
        if ( s[ 0 ].length() == 0 ) {
            throw new IllegalArgumentException( attribute );
        }
        final ExtendedAttributeView view = getFileAttributeView( toPathImpl( path ), s[ 0 ], options );
        if ( view == null ) {
            throw new UnsupportedOperationException( "View '" + s[ 0 ] + "' not available" );
        }

        view.setAttribute( s[ 1 ], value );
    }

    private void checkURI( final String paramName,
                           final URI uri )
            throws IllegalArgumentException {
        checkNotNull( "uri", uri );

        if ( uri.getAuthority() == null || uri.getAuthority().isEmpty() ) {
            throw new IllegalArgumentException( "Parameter named '" + paramName + "' is invalid, missing host repository!" );
        }

        int atIndex = uri.getPath().indexOf( "@" );
        if ( atIndex != -1 && !uri.getAuthority().contains( "@" ) ) {
            if ( uri.getPath().indexOf( "/", atIndex ) == -1 ) {
                throw new IllegalArgumentException( "Parameter named '" + paramName + "' is invalid, missing host repository!" );
            }
        }

    }

    private String extractHost( final URI uri ) {
        checkNotNull( "uri", uri );

        int atIndex = uri.getPath().indexOf( "@" );
        if ( atIndex != -1 && !uri.getAuthority().contains( "@" ) ) {
            return uri.getAuthority() + uri.getPath().substring( 0, uri.getPath().indexOf( "/", atIndex ) );
        }

        return uri.getAuthority();
    }

    private String extractRepoName( final URI uri ) {
        checkNotNull( "uri", uri );

        final String host = extractHost( uri );

        int index = host.indexOf( '@' );
        if ( index != -1 ) {
            return host.substring( index + 1 );
        }

        return host;
    }

    private boolean hasSyncFlag( final URI uri ) {
        checkNotNull( "uri", uri );

        return uri.getQuery() != null && uri.getQuery().contains( "sync" );
    }

    private boolean hasForceFlag( URI uri ) {
        checkNotNull( "uri", uri );

        return uri.getQuery() != null && uri.getQuery().contains( "force" );
    }

    private boolean hasPushFlag( final URI uri ) {
        checkNotNull( "uri", uri );

        return uri.getQuery() != null && uri.getQuery().contains( "push" );
    }

    //by spec, it should be a list of pairs, but here we're just uisng a map.
    private static Map<String, String> getQueryParams( final URI uri ) {
        final String[] params = uri.getQuery().split( "&" );
        return new HashMap<String, String>( params.length ) {{
            for ( String param : params ) {
                final String[] kv = param.split( "=" );
                final String name = kv[ 0 ];
                final String value;
                if ( kv.length == 2 ) {
                    value = kv[ 1 ];
                } else {
                    value = "";
                }

                put( name, value );
            }
        }};
    }

    private String extractPath( final URI uri ) {
        checkNotNull( "uri", uri );

        final String host = extractHost( uri );

        final String path;
        try {
            path = URIUtil.decode( uri.toString() ).substring( getSchemeSize( uri ) + host.length() );
        } catch ( URIException e ) {
            return null;
        }

        if ( path.startsWith( "/:" ) ) {
            return path.substring( 2 );
        }

        return path;
    }

    private CredentialsProvider buildCredential( final Map<String, ?> env ) {
        if ( env != null ) {
            if ( env.containsKey( USER_NAME ) ) {
                if ( env.containsKey( PASSWORD ) ) {
                    return new UsernamePasswordCredentialsProvider( env.get( USER_NAME ).toString(), env.get( PASSWORD ).toString() );
                }
                return new UsernamePasswordCredentialsProvider( env.get( USER_NAME ).toString(), "" );
            }
        }
        return CredentialsProvider.getDefault();
    }

    private JGitPathImpl toPathImpl( final Path path ) {
        if ( path instanceof JGitPathImpl ) {
            return (JGitPathImpl) path;
        }
        throw new IllegalArgumentException( "Path not supported by current provider." );
    }

    private String[] split( final String attribute ) {
        final String[] s = new String[ 2 ];
        final int pos = attribute.indexOf( ':' );
        if ( pos == -1 ) {
            s[ 0 ] = "basic";
            s[ 1 ] = attribute;
        } else {
            s[ 0 ] = attribute.substring( 0, pos );
            s[ 1 ] = ( pos == attribute.length() ) ? "" : attribute.substring( pos + 1 );
        }
        return s;
    }

    private int getSchemeSize( final URI uri ) {
        if ( uri.getScheme().equals( SCHEME ) ) {
            return SCHEME_SIZE;
        }
        return DEFAULT_SCHEME_SIZE;
    }

    private void delete( final JGitPathImpl path,
                         final CommitInfo commitInfo ) {
        commit( path, commitInfo, new DefaultCommitContent( new HashMap<String, File>() {{
            put( path.getPath(), null );
        }} ) );
    }

    private void commit( final JGitPathImpl path,
                         final CommitInfo commitInfo,
                         final CommitContent commitContent ) {
        final Git git = path.getFileSystem().gitRepo();
        final String branchName = path.getRefTree();
        JGitFileSystem fileSystem = path.getFileSystem();
        final boolean batchState = fileSystem.isOnBatch();
        final boolean amend = batchState && fileSystem.isHadCommitOnBatchState();

        final ObjectId oldHead = JGitUtil.getTreeRefObjectId( path.getFileSystem().gitRepo().getRepository(), branchName );

        final boolean hasCommit;
        if ( batchState && fileSystem.getBatchCommitInfo() != null ) {
            hasCommit = JGitUtil.commit( git, branchName, fileSystem.getBatchCommitInfo(), amend, commitContent );
        } else {
            hasCommit = JGitUtil.commit( git, branchName, commitInfo, amend, commitContent );
        }

        if ( !batchState ) {
            final ObjectId newHead = JGitUtil.getTreeRefObjectId( path.getFileSystem().gitRepo().getRepository(), branchName );

            notifyDiffs( path.getFileSystem(), branchName, commitInfo.getSessionId(), commitInfo.getName(), commitInfo.getMessage(), oldHead, newHead );
        } else if ( !oldHeadsOfPendingDiffs.containsKey( path.getFileSystem() ) ||
                !oldHeadsOfPendingDiffs.get( path.getFileSystem() ).containsKey( branchName ) ) {

            if ( !oldHeadsOfPendingDiffs.containsKey( path.getFileSystem() ) ) {
                oldHeadsOfPendingDiffs.put( path.getFileSystem(), new HashMap<String, NotificationModel>() );
            }

            if ( fileSystem.getBatchCommitInfo() != null ) {
                oldHeadsOfPendingDiffs.get( path.getFileSystem() ).put( branchName, new NotificationModel( oldHead, fileSystem.getBatchCommitInfo().getSessionId(), fileSystem.getBatchCommitInfo().getName(), fileSystem.getBatchCommitInfo().getMessage() ) );
            } else {
                oldHeadsOfPendingDiffs.get( path.getFileSystem() ).put( branchName, new NotificationModel( oldHead, commitInfo.getSessionId(), commitInfo.getName(), commitInfo.getMessage() ) );
            }
        }

        if ( path.getFileSystem().isOnBatch() && !fileSystem.isHadCommitOnBatchState() ) {
            fileSystem.setHadCommitOnBatchState( hasCommit );
        }
    }

    private void notifyAllDiffs() {
        for ( Map.Entry<JGitFileSystem, Map<String, NotificationModel>> jGitFileSystemMapEntry : oldHeadsOfPendingDiffs.entrySet() ) {
            for ( Map.Entry<String, NotificationModel> branchNameNotificationModelEntry : jGitFileSystemMapEntry.getValue().entrySet() ) {
                final ObjectId newHead = JGitUtil.getTreeRefObjectId( jGitFileSystemMapEntry.getKey().gitRepo().getRepository(), branchNameNotificationModelEntry.getKey() );
                notifyDiffs( jGitFileSystemMapEntry.getKey(),
                             branchNameNotificationModelEntry.getKey(),
                             branchNameNotificationModelEntry.getValue().getSessionId(),
                             branchNameNotificationModelEntry.getValue().getUserName(),
                             branchNameNotificationModelEntry.getValue().getMessage(),
                             branchNameNotificationModelEntry.getValue().getOriginalHead(),
                             newHead );
            }
        }
        oldHeadsOfPendingDiffs.clear();
    }

    private void notifyDiffs( final JGitFileSystem fs,
                              final String _tree,
                              final String sessionId,
                              final String userName,
                              final String message,
                              final ObjectId oldHead,
                              final ObjectId newHead ) {

        final String tree;
        if ( _tree.startsWith( "refs/" ) ) {
            tree = _tree.substring( _tree.lastIndexOf( "/" ) + 1 );
        } else {
            tree = _tree;
        }

        final String host = tree + "@" + fs.getName();
        final Path root = JGitPathImpl.createRoot( fs, "/", host, false );

        final List<DiffEntry> diff = JGitUtil.getDiff( fs.gitRepo().getRepository(), oldHead, newHead );
        final List<WatchEvent<?>> events = new ArrayList<WatchEvent<?>>( diff.size() );

        for ( final DiffEntry diffEntry : diff ) {
            final Path oldPath;
            if ( !diffEntry.getOldPath().equals( DiffEntry.DEV_NULL ) ) {
                oldPath = JGitPathImpl.create( fs, "/" + diffEntry.getOldPath(), host, null, false );
            } else {
                oldPath = null;
            }

            final Path newPath;
            if ( !diffEntry.getNewPath().equals( DiffEntry.DEV_NULL ) ) {
                JGitPathInfo pathInfo = resolvePath( fs.gitRepo(), tree, diffEntry.getNewPath() );
                newPath = JGitPathImpl.create( fs, "/" + pathInfo.getPath(), host, pathInfo.getObjectId(), false );
            } else {
                newPath = null;
            }

            events.add( new WatchEvent() {
                @Override
                public Kind kind() {
                    switch ( diffEntry.getChangeType() ) {
                        case ADD:
                        case COPY:
                            return StandardWatchEventKind.ENTRY_CREATE;
                        case DELETE:
                            return StandardWatchEventKind.ENTRY_DELETE;
                        case MODIFY:
                            return StandardWatchEventKind.ENTRY_MODIFY;
                        case RENAME:
                            return StandardWatchEventKind.ENTRY_RENAME;
                        default:
                            throw new RuntimeException();
                    }
                }

                @Override
                public int count() {
                    return 1;
                }

                @Override
                public Object context() {
                    return new WatchContext() {

                        @Override
                        public Path getPath() {
                            return newPath;
                        }

                        @Override
                        public Path getOldPath() {
                            return oldPath;
                        }

                        @Override
                        public String getSessionId() {
                            return sessionId;
                        }

                        @Override
                        public String getMessage() {
                            return message;
                        }

                        @Override
                        public String getUser() {
                            return userName;
                        }
                    };
                }

                @Override
                public String toString() {
                    return "WatchEvent{" +
                            "newPath=" + newPath +
                            ", oldPath=" + oldPath +
                            ", sessionId='" + sessionId + '\'' +
                            ", userName='" + userName + '\'' +
                            ", message='" + message + '\'' +
                            ", changeType=" + diffEntry.getChangeType() +
                            '}';
                }
            } );
        }
        if ( !events.isEmpty() ) {
            fs.publishEvents( root, events );
        }
    }

}
TOP

Related Classes of org.uberfire.java.nio.fs.jgit.JGitFileSystemProvider

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.