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