tikhomirov@74: /* tikhomirov@578: * Copyright (c) 2010-2013 TMate Software Ltd tikhomirov@74: * tikhomirov@74: * This program is free software; you can redistribute it and/or modify tikhomirov@74: * it under the terms of the GNU General Public License as published by tikhomirov@74: * the Free Software Foundation; version 2 of the License. tikhomirov@74: * tikhomirov@74: * This program is distributed in the hope that it will be useful, tikhomirov@74: * but WITHOUT ANY WARRANTY; without even the implied warranty of tikhomirov@74: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the tikhomirov@74: * GNU General Public License for more details. tikhomirov@74: * tikhomirov@74: * For information on how to redistribute this software under tikhomirov@74: * the terms of a license other than GNU General Public License tikhomirov@102: * contact TMate Software at support@hg4j.com tikhomirov@2: */ tikhomirov@74: package org.tmatesoft.hg.repo; tikhomirov@2: tikhomirov@456: import static org.tmatesoft.hg.util.LogFacility.Severity.Warn; tikhomirov@456: tikhomirov@2: import java.io.File; tikhomirov@698: import java.net.URI; tikhomirov@698: import java.net.URISyntaxException; tikhomirov@170: import java.net.URL; tikhomirov@698: import java.net.URLStreamHandler; tikhomirov@698: import java.net.URLStreamHandlerFactory; tikhomirov@148: tikhomirov@181: import org.tmatesoft.hg.core.HgBadArgumentException; tikhomirov@628: import org.tmatesoft.hg.core.HgIOException; tikhomirov@423: import org.tmatesoft.hg.core.HgRepositoryNotFoundException; tikhomirov@295: import org.tmatesoft.hg.core.SessionContext; tikhomirov@295: import org.tmatesoft.hg.internal.BasicSessionContext; tikhomirov@181: import org.tmatesoft.hg.internal.ConfigFile; tikhomirov@169: import org.tmatesoft.hg.internal.DataAccessProvider; tikhomirov@578: import org.tmatesoft.hg.internal.RequiresFile; tikhomirov@503: import org.tmatesoft.hg.repo.HgRepoConfig.PathsSection; tikhomirov@2: tikhomirov@2: /** tikhomirov@148: * Utility methods to find Mercurial repository at a given location tikhomirov@74: * tikhomirov@74: * @author Artem Tikhomirov tikhomirov@74: * @author TMate Software Ltd. tikhomirov@2: */ tikhomirov@618: public class HgLookup implements SessionContext.Source { tikhomirov@2: tikhomirov@181: private ConfigFile globalCfg; tikhomirov@295: private SessionContext sessionContext; tikhomirov@295: tikhomirov@295: public HgLookup() { tikhomirov@295: } tikhomirov@295: tikhomirov@295: public HgLookup(SessionContext ctx) { tikhomirov@295: sessionContext = ctx; tikhomirov@295: } tikhomirov@181: tikhomirov@423: public HgRepository detectFromWorkingDir() throws HgRepositoryNotFoundException { tikhomirov@2: return detect(System.getProperty("user.dir")); tikhomirov@2: } tikhomirov@2: tikhomirov@423: public HgRepository detect(String location) throws HgRepositoryNotFoundException { tikhomirov@95: return detect(new File(location)); tikhomirov@95: } tikhomirov@95: tikhomirov@578: /** tikhomirov@578: * Look up repository in specified location and above tikhomirov@578: * tikhomirov@578: * @param location where to look for .hg directory, never null tikhomirov@578: * @return repository object, never null tikhomirov@578: * @throws HgRepositoryNotFoundException if no repository found, or repository version is not supported tikhomirov@578: */ tikhomirov@423: public HgRepository detect(File location) throws HgRepositoryNotFoundException { tikhomirov@492: File dir = location.getAbsoluteFile(); tikhomirov@492: File repository; tikhomirov@492: do { tikhomirov@492: repository = new File(dir, ".hg"); tikhomirov@492: if (repository.exists() && repository.isDirectory()) { tikhomirov@492: break; tikhomirov@2: } tikhomirov@492: repository = null; tikhomirov@492: dir = dir.getParentFile(); tikhomirov@492: tikhomirov@492: } while(dir != null); tikhomirov@492: if (repository == null) { tikhomirov@492: throw new HgRepositoryNotFoundException(String.format("Can't locate .hg/ directory of Mercurial repository in %s nor in parent dirs", location)).setLocation(location.getPath()); tikhomirov@148: } tikhomirov@628: try { tikhomirov@628: String repoPath = repository.getParentFile().getAbsolutePath(); tikhomirov@628: HgRepository rv = new HgRepository(getSessionContext(), repoPath, repository); tikhomirov@628: int requiresFlags = rv.getImplHelper().getRequiresFlags(); tikhomirov@628: if ((requiresFlags & RequiresFile.REVLOGV1) == 0) { tikhomirov@628: throw new HgRepositoryNotFoundException(String.format("%s: repository version is not supported (Mercurial <0.9?)", repoPath)).setLocation(location.getPath()); tikhomirov@628: } tikhomirov@628: return rv; tikhomirov@628: } catch (HgRuntimeException ex) { tikhomirov@628: HgRepositoryNotFoundException e = new HgRepositoryNotFoundException("Failed to initialize Hg4J library").setLocation(location.getPath()); tikhomirov@628: e.initCause(ex); tikhomirov@628: throw e; tikhomirov@578: } tikhomirov@4: } tikhomirov@169: tikhomirov@425: public HgBundle loadBundle(File location) throws HgRepositoryNotFoundException { tikhomirov@169: if (location == null || !location.canRead()) { tikhomirov@425: throw new HgRepositoryNotFoundException(String.format("Can't read file %s", location)).setLocation(String.valueOf(location)); tikhomirov@169: } tikhomirov@618: return new HgBundle(getSessionContext(), new DataAccessProvider(getSessionContext()), location).link(); tikhomirov@169: } tikhomirov@181: tikhomirov@181: /** tikhomirov@503: * Try to instantiate remote server using an immediate url or an url from configuration files tikhomirov@503: * tikhomirov@503: * @param key either URL or a key from configuration file that points to remote server; if null or empty string, default remote location of the supplied repository (if any) is looked up tikhomirov@503: * @param hgRepo optional local repository to get default or otherwise configured remote location tikhomirov@181: * @return an instance featuring access to remote repository, check {@link HgRemoteRepository#isInvalid()} before actually using it tikhomirov@181: * @throws HgBadArgumentException if anything is wrong with the remote server's URL tikhomirov@181: */ tikhomirov@181: public HgRemoteRepository detectRemote(String key, HgRepository hgRepo) throws HgBadArgumentException { tikhomirov@698: URI uri; tikhomirov@181: Exception toReport; tikhomirov@181: try { tikhomirov@698: uri = new URI(key); tikhomirov@181: toReport = null; tikhomirov@698: } catch (URISyntaxException ex) { tikhomirov@698: uri = null; tikhomirov@181: toReport = ex; tikhomirov@181: } tikhomirov@698: if (uri == null) { tikhomirov@503: String server = null; tikhomirov@503: if (hgRepo != null && !hgRepo.isInvalid()) { tikhomirov@503: PathsSection ps = hgRepo.getConfiguration().getPaths(); tikhomirov@622: server = key == null || key.trim().length() == 0 ? ps.getDefault() : ps.getString(key, null); // XXX Java 1.5 isEmpty() tikhomirov@503: } else if (key == null || key.trim().length() == 0) { tikhomirov@503: throw new HgBadArgumentException("Can't look up empty key in a global configuration", null); tikhomirov@503: } tikhomirov@503: if (server == null) { tikhomirov@503: server = getGlobalConfig().getSection("paths").get(key); tikhomirov@503: } tikhomirov@181: if (server == null) { tikhomirov@181: throw new HgBadArgumentException(String.format("Can't find server %s specification in the config", key), toReport); tikhomirov@181: } tikhomirov@181: try { tikhomirov@698: uri = new URI(server); tikhomirov@698: } catch (URISyntaxException ex) { tikhomirov@181: throw new HgBadArgumentException(String.format("Found %s server spec in the config, but failed to initialize with it", key), ex); tikhomirov@181: } tikhomirov@181: } tikhomirov@698: return detectRemote(uri); tikhomirov@181: } tikhomirov@181: tikhomirov@698: /** tikhomirov@698: * Detect remote repository tikhomirov@698: *

Use of this method is discouraged, please use {@link #detectRemote(URI)} instead} tikhomirov@698: * tikhomirov@698: * @param url location of remote repository tikhomirov@698: * @return instance to interact with remote repository tikhomirov@698: * @throws HgBadArgumentException if location format is not a valid {@link URI} tikhomirov@698: * @throws IllegalArgumentException if url is null tikhomirov@698: */ tikhomirov@295: public HgRemoteRepository detect(URL url) throws HgBadArgumentException { tikhomirov@171: if (url == null) { tikhomirov@171: throw new IllegalArgumentException(); tikhomirov@171: } tikhomirov@698: try { tikhomirov@698: return detectRemote(url.toURI()); tikhomirov@698: } catch (URISyntaxException ex) { tikhomirov@698: throw new HgBadArgumentException(String.format("Bad remote repository location: %s", url), ex); tikhomirov@170: } tikhomirov@698: } tikhomirov@698: tikhomirov@698: /** tikhomirov@698: * Resolves location of remote repository. tikhomirov@698: * tikhomirov@698: *

Choice between {@link URI URIs} and {@link URL URLs} done in favor of former because they are purely syntactical, tikhomirov@698: * while latter have semantics of {@link URL#openConnection() connection} establishing, which is not always possible. tikhomirov@698: * E.g. one can't instantiate new URL("ssh://localhost/") as long as there's no local {@link URLStreamHandler} tikhomirov@698: * for the protocol, which is the case for certain JREs out there. The way {@link URLStreamHandlerFactory URLStreamHandlerFactories} tikhomirov@698: * are installed (static field/method) is quite fragile, and definitely not the one to rely on from a library's code (apps can carefully tikhomirov@698: * do this, but library can't install own nor expect anyone outside there would do). Use of fake {@link URLStreamHandler} (fails to open tikhomirov@698: * every connection) is possible, of course, although it seems to be less appropriate when there is alternative with {@link URI URIs}. tikhomirov@698: * tikhomirov@698: * @param uriRemote remote repository location tikhomirov@698: * @return instance to interact with remote repository tikhomirov@698: * @throws HgBadArgumentException tikhomirov@698: */ tikhomirov@698: public HgRemoteRepository detectRemote(URI uriRemote) throws HgBadArgumentException { tikhomirov@699: HgRemoteRepository.RemoteDescriptor rd = getSessionContext().getRemoteDescriptor(uriRemote); tikhomirov@698: if (rd == null) { tikhomirov@698: throw new HgBadArgumentException(String.format("Unsupported remote repository location:%s", uriRemote), null); tikhomirov@698: } tikhomirov@698: return new HgRemoteRepository(getSessionContext(), rd); tikhomirov@170: } tikhomirov@181: tikhomirov@181: private ConfigFile getGlobalConfig() { tikhomirov@181: if (globalCfg == null) { tikhomirov@618: globalCfg = new ConfigFile(getSessionContext()); tikhomirov@295: try { tikhomirov@295: globalCfg.addLocation(new File(System.getProperty("user.home"), ".hgrc")); tikhomirov@628: } catch (HgIOException ex) { tikhomirov@295: // XXX perhaps, makes sense to let caller/client know that we've failed to read global config? tikhomirov@618: getSessionContext().getLog().dump(getClass(), Warn, ex, null); tikhomirov@295: } tikhomirov@181: } tikhomirov@181: return globalCfg; tikhomirov@181: } tikhomirov@295: tikhomirov@618: public SessionContext getSessionContext() { tikhomirov@295: if (sessionContext == null) { tikhomirov@431: sessionContext = new BasicSessionContext(null); tikhomirov@295: } tikhomirov@295: return sessionContext; tikhomirov@295: } tikhomirov@2: }