org.uberfire.java.nio.fs.jgit.JGitFileSystemProvider.java Source code

Java tutorial

Introduction

Here is the source code for org.uberfire.java.nio.fs.jgit.JGitFileSystemProvider.java

Source

/*
 * 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);
        }
    }

}