Mercurial > hg4j
changeset 698:822f3a83ff57
in, out and clone tests pass for ssh repositories. Infrastructure to decouple HgRemoteRepository from specific Connector implementation
author | Artem Tikhomirov <tikhomirov.artem@gmail.com> |
---|---|
date | Tue, 06 Aug 2013 21:18:33 +0200 |
parents | 24f4efedc9d5 |
children | a483b2b68a2e |
files | src/org/tmatesoft/hg/core/HgIncomingCommand.java src/org/tmatesoft/hg/core/SessionContext.java src/org/tmatesoft/hg/internal/RepositoryComparator.java src/org/tmatesoft/hg/internal/remote/Connector.java src/org/tmatesoft/hg/internal/remote/HttpConnector.java src/org/tmatesoft/hg/internal/remote/RemoteConnectorDescriptor.java src/org/tmatesoft/hg/internal/remote/SshConnector.java src/org/tmatesoft/hg/repo/HgLookup.java src/org/tmatesoft/hg/repo/HgRemoteRepository.java test/org/tmatesoft/hg/test/TestIncoming.java |
diffstat | 10 files changed, 280 insertions(+), 87 deletions(-) [+] |
line wrap: on
line diff
--- a/src/org/tmatesoft/hg/core/HgIncomingCommand.java Tue Aug 06 13:34:34 2013 +0200 +++ b/src/org/tmatesoft/hg/core/HgIncomingCommand.java Tue Aug 06 21:18:33 2013 +0200 @@ -155,9 +155,17 @@ public void next(int revisionNumber, Nodeid nodeid, RawChangeset cset) throws HgRuntimeException { if (parentHelper.knownNode(nodeid)) { - if (!common.contains(nodeid)) { - throw new HgInvalidStateException("Bundle shall not report known nodes other than roots we've supplied"); - } + // FIXME getCommon() and remote.changegroup do not work together nicely. + // e.g. for hgtest-annotate-merge repository and TestIncoming, common reports r0 and r5 (ancestor of r5) + // because there's a distinct branch from r0 (in addition to those after r5). + // remote.changegroup however answers with revisions that are children of either, + /// so revisions 0..5 are reported as well and the next check fails. Instead, shall pass + // not common, but 'first to load' to remote.changegroup() or use another method (e.g. getbundle) + // Note, sending r5 only (i.e. checking for ancestors in common) won't help, changegroup sends children of + // requested roots only, and doesn't look for anything else +// if (!common.contains(nodeid)) { +// throw new HgInvalidStateException("Bundle shall not report known nodes other than roots we've supplied"); +// } return; } transformer.next(localIndex++, nodeid, cset);
--- a/src/org/tmatesoft/hg/core/SessionContext.java Tue Aug 06 13:34:34 2013 +0200 +++ b/src/org/tmatesoft/hg/core/SessionContext.java Tue Aug 06 21:18:33 2013 +0200 @@ -16,6 +16,12 @@ */ package org.tmatesoft.hg.core; +import java.net.URI; + +import org.tmatesoft.hg.internal.BasicSessionContext; +import org.tmatesoft.hg.internal.Experimental; +import org.tmatesoft.hg.internal.remote.RemoteConnectorDescriptor; +import org.tmatesoft.hg.repo.HgLookup.RemoteDescriptor; import org.tmatesoft.hg.util.LogFacility; import org.tmatesoft.hg.util.Path; @@ -67,6 +73,23 @@ } /** + * Work in progress, provisional API. + * + * Provides descriptor that knows how to handle connections of specific kind + * + * FIXME Perhaps, implementation here shall return null for any URI, while the one + * in {@link BasicSessionContext} shall use our internal classes? However, + * present implementation provides support for uris handled in the library itself, and likely + * most clients need this, even if they supply own SessionContext + * + * @return <code>null</code> if supplied URI doesn't point to a remote repository or repositories of that kind are not supported + */ + @Experimental(reason="Work in progress, provisional API") + public RemoteDescriptor getRemoteDescriptor(URI uri) { + return new RemoteConnectorDescriptor.Provider().get(this, uri); + } + + /** * Providers of the context may implement */ public interface Source {
--- a/src/org/tmatesoft/hg/internal/RepositoryComparator.java Tue Aug 06 13:34:34 2013 +0200 +++ b/src/org/tmatesoft/hg/internal/RepositoryComparator.java Tue Aug 06 21:18:33 2013 +0200 @@ -23,6 +23,7 @@ import java.util.Collections; import java.util.HashMap; import java.util.HashSet; +import java.util.LinkedHashSet; import java.util.LinkedList; import java.util.List; import java.util.ListIterator; @@ -201,7 +202,7 @@ public List<BranchChain> calculateMissingBranches() throws HgRemoteConnectionException { List<Nodeid> remoteHeads = remoteRepo.heads(); LinkedList<Nodeid> common = new LinkedList<Nodeid>(); // these remotes are known in local - LinkedList<Nodeid> toQuery = new LinkedList<Nodeid>(); // these need further queries to find common + LinkedHashSet<Nodeid> toQuery = new LinkedHashSet<Nodeid>(); // these need further queries to find common for (Nodeid rh : remoteHeads) { if (localRepo.knownNode(rh)) { common.add(rh); @@ -218,7 +219,7 @@ // records relation between branch head and its parent branch, if any HashMap<Nodeid, BranchChain> head2chain = new HashMap<Nodeid, BranchChain>(); while (!toQuery.isEmpty()) { - List<RemoteBranch> remoteBranches = remoteRepo.branches(toQuery); //head, root, first parent, second parent + List<RemoteBranch> remoteBranches = remoteRepo.branches(new ArrayList<Nodeid>(toQuery)); //head, root, first parent, second parent toQuery.clear(); while(!remoteBranches.isEmpty()) { RemoteBranch rb = remoteBranches.remove(0); @@ -240,10 +241,10 @@ if (hasP1 && !localRepo.knownNode(rb.p1)) { toQuery.add(rb.p1); // we might have seen parent node already, and recorded it as a branch chain - // we shall reuse existing BC to get it completely initializer (head2chain map + // we shall reuse existing BC to get it completely initialized (head2chain map // on second put with the same key would leave first BC uninitialized. - // It seems there's no reason to be affraid (XXX although shall double-check) + // It seems there's no reason to be afraid (XXX although shall double-check) // that BC's chain would get corrupt (its p1 and p2 fields assigned twice with different values) // as parents are always the same (and likely, BC that is common would be the last unknown) BranchChain bc = head2chain.get(rb.p1); @@ -352,7 +353,7 @@ @Override public String toString() { - return String.format("BranchChain [%s, %s]", branchRoot, branchHead); + return String.format("BranchChain [root:%s, head:%s]", branchRoot, branchHead); } void dump() {
--- a/src/org/tmatesoft/hg/internal/remote/Connector.java Tue Aug 06 13:34:34 2013 +0200 +++ b/src/org/tmatesoft/hg/internal/remote/Connector.java Tue Aug 06 21:18:33 2013 +0200 @@ -18,15 +18,15 @@ import java.io.InputStream; import java.io.OutputStream; -import java.net.URL; +import java.net.URI; import java.util.Collection; import java.util.List; import org.tmatesoft.hg.core.HgRemoteConnectionException; import org.tmatesoft.hg.core.Nodeid; import org.tmatesoft.hg.core.SessionContext; +import org.tmatesoft.hg.repo.HgRemoteRepository.Range; import org.tmatesoft.hg.repo.HgRuntimeException; -import org.tmatesoft.hg.repo.HgRemoteRepository.Range; /** * @@ -46,7 +46,7 @@ static final String NS_BOOKMARKS = "bookmarks"; static final String NS_PHASES = "phases"; - void init(URL url, SessionContext sessionContext, Object globalConfig) throws HgRuntimeException; + void init(URI uri, SessionContext sessionContext, Object globalConfig) throws HgRuntimeException; String getServerLocation(); // void connect() throws HgRemoteConnectionException, HgRuntimeException;
--- a/src/org/tmatesoft/hg/internal/remote/HttpConnector.java Tue Aug 06 13:34:34 2013 +0200 +++ b/src/org/tmatesoft/hg/internal/remote/HttpConnector.java Tue Aug 06 21:18:33 2013 +0200 @@ -28,6 +28,7 @@ import java.io.SequenceInputStream; import java.net.HttpURLConnection; import java.net.MalformedURLException; +import java.net.URI; import java.net.URL; import java.net.URLConnection; import java.security.cert.CertificateException; @@ -56,6 +57,7 @@ * @author TMate Software Ltd. */ public class HttpConnector implements Connector { + private URI uri; private URL url; private SSLContext sslContext; private String authInfo; @@ -64,16 +66,16 @@ // private HttpURLConnection conn; - public void init(URL url, SessionContext sessionContext, Object globalConfig) throws HgRuntimeException { - this.url = url; + public void init(URI uri, SessionContext sessionContext, Object globalConfig) throws HgRuntimeException { + this.uri = uri; sessionCtx = sessionContext; debug = new PropertyMarshal(sessionCtx).getBoolean("hg4j.remote.debug", false); - if (url.getUserInfo() != null) { + if (uri.getUserInfo() != null) { String ai = null; try { // Hack to get Base64-encoded credentials Preferences tempNode = Preferences.userRoot().node("xxx"); - tempNode.putByteArray("xxx", url.getUserInfo().getBytes()); + tempNode.putByteArray("xxx", uri.getUserInfo().getBytes()); ai = tempNode.get("xxx", null); tempNode.removeNode(); } catch (BackingStoreException ex) { @@ -87,6 +89,11 @@ } public void connect() throws HgRemoteConnectionException, HgRuntimeException { + try { + url = uri.toURL(); + } catch (MalformedURLException ex) { + throw new HgRemoteConnectionException("Bad URL", ex); + } if ("https".equals(url.getProtocol())) { try { sslContext = SSLContext.getInstance("SSL"); @@ -132,13 +139,13 @@ } public String getServerLocation() { - if (url.getUserInfo() == null) { - return url.toExternalForm(); + if (uri.getUserInfo() == null) { + return uri.toString(); } - if (url.getPort() != -1) { - return String.format("%s://%s:%d%s", url.getProtocol(), url.getHost(), url.getPort(), url.getPath()); + if (uri.getPort() != -1) { + return String.format("%s://%s:%d%s", uri.getScheme(), uri.getHost(), uri.getPort(), uri.getPath()); } else { - return String.format("%s://%s%s", url.getProtocol(), url.getHost(), url.getPath()); + return String.format("%s://%s%s", uri.getScheme(), uri.getHost(), uri.getPath()); } } @@ -294,7 +301,6 @@ super.close(); if (debug) { dumpResponseHeader(u); - dumpResponse(); } try { checkResponseOk("Push", CMD_UNBUNDLE); @@ -395,11 +401,4 @@ System.out.printf("%s: %s\n", s, conn.getHeaderField(s)); } } - - private void dumpResponse() throws IOException { - if (conn.getContentLength() > 0) { - final Object content = conn.getContent(); - System.out.println(content); - } - } }
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/org/tmatesoft/hg/internal/remote/RemoteConnectorDescriptor.java Tue Aug 06 21:18:33 2013 +0200 @@ -0,0 +1,105 @@ +/* + * Copyright (c) 2013 TMate Software Ltd + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * For information on how to redistribute this software under + * the terms of a license other than GNU General Public License + * contact TMate Software at support@hg4j.com + */ +package org.tmatesoft.hg.internal.remote; + +import java.net.URI; +import java.util.HashMap; +import java.util.Map; + +import org.tmatesoft.hg.core.HgBadArgumentException; +import org.tmatesoft.hg.core.SessionContext; +import org.tmatesoft.hg.repo.HgLookup.Authenticator; +import org.tmatesoft.hg.repo.HgLookup.RemoteDescriptor; +import org.tmatesoft.hg.repo.HgRemoteRepository; +import org.tmatesoft.hg.util.Pair; + +/** + * Connector-aware descriptor of remote repository, i.e. descriptor that uses + * {@link Connector Connectors} to connect to a remote repository. + * + * <p>Candidate to become public API, with createConnector() method, so that {@link HgRemoteRepository} + * may accept instances of that interfact directly + * + * + * @author Artem Tikhomirov + * @author TMate Software Ltd. + */ +public class RemoteConnectorDescriptor implements RemoteDescriptor { + + private Map<String, Pair<ClassLoader, String>> connFactory; + private final URI uri; + + public RemoteConnectorDescriptor(Map<String,Pair<ClassLoader, String>> scheme2connectorMap, URI uriRemote) { + this(uriRemote); + connFactory = scheme2connectorMap; + } + + protected RemoteConnectorDescriptor(URI uriRemote) { + uri = uriRemote; + } + + public URI getURI() { + return uri; + } + + public Authenticator getAuth() { + // TODO Auto-generated method stub + return null; + } + + public Connector createConnector() throws HgBadArgumentException { + Pair<ClassLoader, String> connectorToBe = connFactory.get(uri.getScheme()); + if (connectorToBe == null || connectorToBe.second() == null) { + throw new HgBadArgumentException(String.format("Can't instantiate connector for scheme '%s'", uri.getScheme()), null); + } + try { + Class<?> connClass = connectorToBe.first().loadClass(connectorToBe.second()); + if (!Connector.class.isAssignableFrom(connClass)) { + throw new HgBadArgumentException(String.format("Connector %s for scheme '%s' is not a subclass of %s", connectorToBe.second(), uri.getScheme(), Connector.class.getName()), null); + } + final Object connector = connClass.newInstance(); + return Connector.class.cast(connector); + } catch (ClassNotFoundException ex) { + throw new HgBadArgumentException(String.format("Can't instantiate connector %s for scheme '%s'", connectorToBe.second(), uri.getScheme()), ex); + } catch (InstantiationException ex) { + throw new HgBadArgumentException(String.format("Can't instantiate connector %s for scheme '%s'", connectorToBe.second(), uri.getScheme()), ex); + } catch (IllegalAccessException ex) { + throw new HgBadArgumentException(String.format("Can't instantiate connector %s for scheme '%s'", connectorToBe.second(), uri.getScheme()), ex); + } + } + + // I don't see a reason to expose provider of RemoteDescriptors yet + // although it might not be the best idea for session context to serve as provider intermediate + public static class Provider { + private final Map<String, Pair<ClassLoader, String>> knownConnectors = new HashMap<String, Pair<ClassLoader, String>>(5); + + { + final ClassLoader cl = Provider.class.getClassLoader(); + knownConnectors.put("http", new Pair<ClassLoader, String>(cl, HttpConnector.class.getName())); + knownConnectors.put("https", new Pair<ClassLoader, String>(cl, HttpConnector.class.getName())); + // FIXME replace SshConnector.class with fqn string to avoid dependency from the trilead library in runtime + knownConnectors.put("ssh", new Pair<ClassLoader, String>(cl, SshConnector.class.getName())); + } + + public RemoteDescriptor get(SessionContext ctx, URI uri) { + if (knownConnectors.containsKey(uri.getScheme())) { + return new RemoteConnectorDescriptor(knownConnectors, uri); + } + return null; + } + } +}
--- a/src/org/tmatesoft/hg/internal/remote/SshConnector.java Tue Aug 06 13:34:34 2013 +0200 +++ b/src/org/tmatesoft/hg/internal/remote/SshConnector.java Tue Aug 06 21:18:33 2013 +0200 @@ -28,7 +28,7 @@ import java.io.InputStreamReader; import java.io.OutputStream; import java.io.SequenceInputStream; -import java.net.URL; +import java.net.URI; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; @@ -54,7 +54,7 @@ */ public class SshConnector implements Connector { private SessionContext sessionCtx; - private URL url; + private URI uri; private Connection conn; private Session session; private int sessionUse; @@ -62,14 +62,14 @@ private StreamGobbler remoteErr, remoteOut; private OutputStream remoteIn; - public void init(URL url, SessionContext sessionContext, Object globalConfig) throws HgRuntimeException { + public void init(URI uri, SessionContext sessionContext, Object globalConfig) throws HgRuntimeException { sessionCtx = sessionContext; - this.url = url; + this.uri = uri; } public void connect() throws HgRemoteConnectionException, HgRuntimeException { try { - conn = new Connection(url.getHost(), url.getPort() == -1 ? 22 : url.getPort()); + conn = new Connection(uri.getHost(), uri.getPort() == -1 ? 22 : uri.getPort()); conn.connect(); } catch (IOException ex) { throw new HgRemoteConnectionException("Failed to establish connection"); @@ -101,7 +101,7 @@ } try { session = conn.openSession(); - final String path = url.getPath(); + final String path = uri.getPath(); session.execCommand(String.format("hg -R %s serve --stdio", path.charAt(0) == '/' ? path.substring(1) : path)); remoteErr = new StreamGobbler(session.getStderr()); remoteOut = new StreamGobbler(session.getStdout()); @@ -123,7 +123,7 @@ } public String getServerLocation() { - return url.toString(); // FIXME + return uri.toString(); // FIXME } public String getCapabilities() throws HgRemoteConnectionException {
--- a/src/org/tmatesoft/hg/repo/HgLookup.java Tue Aug 06 13:34:34 2013 +0200 +++ b/src/org/tmatesoft/hg/repo/HgLookup.java Tue Aug 06 21:18:33 2013 +0200 @@ -19,8 +19,12 @@ import static org.tmatesoft.hg.util.LogFacility.Severity.Warn; import java.io.File; -import java.net.MalformedURLException; +import java.io.InputStream; +import java.net.URI; +import java.net.URISyntaxException; import java.net.URL; +import java.net.URLStreamHandler; +import java.net.URLStreamHandlerFactory; import org.tmatesoft.hg.core.HgBadArgumentException; import org.tmatesoft.hg.core.HgIOException; @@ -29,7 +33,7 @@ import org.tmatesoft.hg.internal.BasicSessionContext; import org.tmatesoft.hg.internal.ConfigFile; import org.tmatesoft.hg.internal.DataAccessProvider; -import org.tmatesoft.hg.internal.Internals; +import org.tmatesoft.hg.internal.Experimental; import org.tmatesoft.hg.internal.RequiresFile; import org.tmatesoft.hg.repo.HgRepoConfig.PathsSection; @@ -112,16 +116,16 @@ * @throws HgBadArgumentException if anything is wrong with the remote server's URL */ public HgRemoteRepository detectRemote(String key, HgRepository hgRepo) throws HgBadArgumentException { - URL url; + URI uri; Exception toReport; try { - url = new URL(key); + uri = new URI(key); toReport = null; - } catch (MalformedURLException ex) { - url = null; + } catch (URISyntaxException ex) { + uri = null; toReport = ex; } - if (url == null) { + if (uri == null) { String server = null; if (hgRepo != null && !hgRepo.isInvalid()) { PathsSection ps = hgRepo.getConfiguration().getPaths(); @@ -136,22 +140,55 @@ throw new HgBadArgumentException(String.format("Can't find server %s specification in the config", key), toReport); } try { - url = new URL(server); - } catch (MalformedURLException ex) { + uri = new URI(server); + } catch (URISyntaxException ex) { throw new HgBadArgumentException(String.format("Found %s server spec in the config, but failed to initialize with it", key), ex); } } - return new HgRemoteRepository(getSessionContext(), url); + return detectRemote(uri); } + /** + * Detect remote repository + * <p>Use of this method is discouraged, please use {@link #detectRemote(URI)} instead} + * + * @param url location of remote repository + * @return instance to interact with remote repository + * @throws HgBadArgumentException if location format is not a valid {@link URI} + * @throws IllegalArgumentException if url is <code>null</code> + */ public HgRemoteRepository detect(URL url) throws HgBadArgumentException { if (url == null) { throw new IllegalArgumentException(); } - if (Boolean.FALSE.booleanValue()) { - throw Internals.notImplemented(); + try { + return detectRemote(url.toURI()); + } catch (URISyntaxException ex) { + throw new HgBadArgumentException(String.format("Bad remote repository location: %s", url), ex); } - return new HgRemoteRepository(getSessionContext(), url); + } + + /** + * Resolves location of remote repository. + * + * <p>Choice between {@link URI URIs} and {@link URL URLs} done in favor of former because they are purely syntactical, + * while latter have semantics of {@link URL#openConnection() connection} establishing, which is not always possible. + * E.g. one can't instantiate <code>new URL("ssh://localhost/")</code> as long as there's no local {@link URLStreamHandler} + * for the protocol, which is the case for certain JREs out there. The way {@link URLStreamHandlerFactory URLStreamHandlerFactories} + * are installed (static field/method) is quite fragile, and definitely not the one to rely on from a library's code (apps can carefully + * do this, but library can't install own nor expect anyone outside there would do). Use of fake {@link URLStreamHandler} (fails to open + * every connection) is possible, of course, although it seems to be less appropriate when there is alternative with {@link URI URIs}. + * + * @param uriRemote remote repository location + * @return instance to interact with remote repository + * @throws HgBadArgumentException + */ + public HgRemoteRepository detectRemote(URI uriRemote) throws HgBadArgumentException { + RemoteDescriptor rd = getSessionContext().getRemoteDescriptor(uriRemote); + if (rd == null) { + throw new HgBadArgumentException(String.format("Unsupported remote repository location:%s", uriRemote), null); + } + return new HgRemoteRepository(getSessionContext(), rd); } private ConfigFile getGlobalConfig() { @@ -173,4 +210,41 @@ } return sessionContext; } + + + /** + * Session context ({@link SessionContext#getRemoteDescriptor(URI)} gives descriptor of remote when asked. + */ + @Experimental(reason="Work in progress") + public interface RemoteDescriptor { + URI getURI(); + Authenticator getAuth(); + } + + @Experimental(reason="Work in progress") + public interface Authenticator { + public void authenticate(RemoteDescriptor remote, AuthMethod authMethod); + } + + /** + * Clients do not implement this interface, instead, they invoke appropriate authentication method + * once they got user input + */ + @Experimental(reason="Work in progress") + public interface AuthMethod { + public void noCredentials() throws AuthFailedException; + public boolean supportsPassword(); + public void withPassword(String username, byte[] password) throws AuthFailedException; + public boolean supportsPublicKey(); + public void withPublicKey(String username, InputStream publicKey, String passphrase) throws AuthFailedException; + public boolean supportsCertificate(); + public void withCertificate() throws AuthFailedException; + } + + @SuppressWarnings("serial") + public class AuthFailedException extends Exception /*XXX HgRemoteException?*/ { + public AuthFailedException(String message, Throwable cause) { + super(message, cause); + } + } }
--- a/src/org/tmatesoft/hg/repo/HgRemoteRepository.java Tue Aug 06 13:34:34 2013 +0200 +++ b/src/org/tmatesoft/hg/repo/HgRemoteRepository.java Tue Aug 06 21:18:33 2013 +0200 @@ -21,17 +21,12 @@ import static org.tmatesoft.hg.util.Outcome.Kind.Success; import java.io.BufferedReader; -import java.io.ByteArrayOutputStream; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStream; import java.io.StreamTokenizer; -import java.net.ContentHandler; -import java.net.ContentHandlerFactory; -import java.net.URL; -import java.net.URLConnection; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; @@ -58,8 +53,8 @@ import org.tmatesoft.hg.internal.Internals; import org.tmatesoft.hg.internal.PropertyMarshal; import org.tmatesoft.hg.internal.remote.Connector; -import org.tmatesoft.hg.internal.remote.HttpConnector; -import org.tmatesoft.hg.internal.remote.SshConnector; +import org.tmatesoft.hg.internal.remote.RemoteConnectorDescriptor; +import org.tmatesoft.hg.repo.HgLookup.RemoteDescriptor; import org.tmatesoft.hg.util.LogFacility.Severity; import org.tmatesoft.hg.util.Outcome; import org.tmatesoft.hg.util.Pair; @@ -81,41 +76,14 @@ private Set<String> remoteCapabilities; private Connector remote; - static { - URLConnection.setContentHandlerFactory(new ContentHandlerFactory() { - - public ContentHandler createContentHandler(String mimetype) { - if ("application/mercurial-0.1".equals(mimetype)) { - return new ContentHandler() { - - @Override - public Object getContent(URLConnection urlc) throws IOException { - if (urlc.getContentLength() > 0) { - ByteArrayOutputStream bos = new ByteArrayOutputStream(); - InputStream is = urlc.getInputStream(); - int r; - while ((r = is.read()) != -1) { - bos.write(r); - } - return new String(bos.toByteArray()); - } - return "<empty>"; - } - }; - } - return null; - } - }); - } - - HgRemoteRepository(SessionContext ctx, URL url) throws HgBadArgumentException { - if (url == null || ctx == null) { - throw new IllegalArgumentException(); + HgRemoteRepository(SessionContext ctx, RemoteDescriptor rd) throws HgBadArgumentException { + if (false == rd instanceof RemoteConnectorDescriptor) { + throw new IllegalArgumentException(String.format("Present implementation supports remote connections via %s only", Connector.class.getName())); } sessionContext = ctx; debug = new PropertyMarshal(ctx).getBoolean("hg4j.remote.debug", false); - remote = "ssh".equals(url.getProtocol()) ? new SshConnector() : new HttpConnector(); - remote.init(url, ctx, null); + remote = ((RemoteConnectorDescriptor) rd).createConnector(); + remote.init(rd.getURI(), ctx, null); } public boolean isInvalid() throws HgRemoteConnectionException { @@ -518,6 +486,19 @@ // in fact, p1 and p2 are not supposed to be null, ever (at least for RemoteBranch created from server output) return head.equals(o.head) && root.equals(o.root) && (p1 == null && o.p1 == null || p1.equals(o.p1)) && (p2 == null && o.p2 == null || p2.equals(o.p2)); } + + @Override + public int hashCode() { + return head.hashCode() ^ root.hashCode(); + } + + @Override + public String toString() { + String none = String.valueOf(-1); + String s1 = p1 == null || p1.isNull() ? none : p1.shortNotation(); + String s2 = p2 == null || p2.isNull() ? none : p2.shortNotation(); + return String.format("RemoteBranch[root: %s, head:%s, p1:%s, p2:%s]", root.shortNotation(), head.shortNotation(), s1, s2); + } } public static final class Bookmarks implements Iterable<Pair<String, Nodeid>> {
--- a/test/org/tmatesoft/hg/test/TestIncoming.java Tue Aug 06 13:34:34 2013 +0200 +++ b/test/org/tmatesoft/hg/test/TestIncoming.java Tue Aug 06 21:18:33 2013 +0200 @@ -52,6 +52,8 @@ } public TestIncoming() { +// Configuration.get().remoteServers("http://hg.serpentine.com/tutorial/hello/"); +// Configuration.get().remoteServers("ssh://localhost/hg/hgtest-annotate-merge/"); // Configuration.get().remoteServers("http://localhost:8000/"); }