changeset 697:24f4efedc9d5

Respect the fact ssh and http protocols use different compression approach to sent changegroup data
author Artem Tikhomirov <tikhomirov.artem@gmail.com>
date Tue, 06 Aug 2013 13:34:34 +0200
parents 5b5d199e2eb3
children 822f3a83ff57
files src/org/tmatesoft/hg/internal/FileUtils.java src/org/tmatesoft/hg/internal/remote/HttpConnector.java src/org/tmatesoft/hg/internal/remote/SshConnector.java src/org/tmatesoft/hg/repo/HgRemoteRepository.java
diffstat 4 files changed, 50 insertions(+), 30 deletions(-) [+]
line wrap: on
line diff
--- a/src/org/tmatesoft/hg/internal/FileUtils.java	Mon Aug 05 19:03:22 2013 +0200
+++ b/src/org/tmatesoft/hg/internal/FileUtils.java	Tue Aug 06 13:34:34 2013 +0200
@@ -23,6 +23,7 @@
 import java.io.FileInputStream;
 import java.io.FileOutputStream;
 import java.io.IOException;
+import java.io.InputStream;
 import java.nio.channels.FileChannel;
 
 import org.tmatesoft.hg.core.HgIOException;
@@ -93,6 +94,17 @@
 		 * Windows uncached run: 1.6 seconds
 		 */
 	}
+	
+	public void write(InputStream is, File file) throws IOException {
+		FileOutputStream fos = new FileOutputStream(file);
+		int r;
+		byte[] buf = new byte[8*1024];
+		while ((r = is.read(buf)) != -1) {
+			fos.write(buf, 0, r);
+		}
+		fos.flush();
+		fos.close();
+	}
 
 	public void closeQuietly(Closeable stream) {
 		closeQuietly(stream, null);
--- a/src/org/tmatesoft/hg/internal/remote/HttpConnector.java	Mon Aug 05 19:03:22 2013 +0200
+++ b/src/org/tmatesoft/hg/internal/remote/HttpConnector.java	Tue Aug 06 13:34:34 2013 +0200
@@ -19,11 +19,13 @@
 import static org.tmatesoft.hg.util.LogFacility.Severity.Info;
 
 import java.io.BufferedReader;
+import java.io.ByteArrayInputStream;
 import java.io.FilterOutputStream;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.InputStreamReader;
 import java.io.OutputStream;
+import java.io.SequenceInputStream;
 import java.net.HttpURLConnection;
 import java.net.MalformedURLException;
 import java.net.URL;
@@ -261,9 +263,13 @@
 			if (debug) {
 				dumpResponseHeader(u);
 			}
-			return conn.getInputStream();
-		} catch (MalformedURLException ex) { // XXX in fact, this exception might be better to be re-thrown as RuntimeEx,
-			// as there's little user can do about this issue (URLs are constructed by our code)
+			InputStream cg = conn.getInputStream();
+			InputStream prefix = new ByteArrayInputStream("HG10GZ".getBytes()); // didn't see any other that zip
+			return new SequenceInputStream(prefix, cg);
+		} catch (MalformedURLException ex) {
+			// although there's little user can do about this issue (URLs are constructed by our code)
+			// it's still better to throw it as checked exception than RT because url is likely malformed due to parameters
+			// and this may help user to understand the cause (and e.g. change them)
 			throw new HgRemoteConnectionException("Bad URL", ex).setRemoteCommand("changegroup").setServerInfo(getServerLocation());
 		} catch (IOException ex) {
 			throw new HgRemoteConnectionException("Communication failure", ex).setRemoteCommand("changegroup").setServerInfo(getServerLocation());
--- a/src/org/tmatesoft/hg/internal/remote/SshConnector.java	Mon Aug 05 19:03:22 2013 +0200
+++ b/src/org/tmatesoft/hg/internal/remote/SshConnector.java	Tue Aug 06 13:34:34 2013 +0200
@@ -17,6 +17,7 @@
 package org.tmatesoft.hg.internal.remote;
 
 import java.io.BufferedReader;
+import java.io.ByteArrayInputStream;
 import java.io.Closeable;
 import java.io.EOFException;
 import java.io.File;
@@ -26,6 +27,7 @@
 import java.io.InputStream;
 import java.io.InputStreamReader;
 import java.io.OutputStream;
+import java.io.SequenceInputStream;
 import java.net.URL;
 import java.util.ArrayList;
 import java.util.Collection;
@@ -99,9 +101,11 @@
 		}
 		try {
 			session = conn.openSession();
-			session.execCommand(String.format("hg -R %s serve --stdio", url.getPath()));
+			final String path = url.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());
+			remoteIn = session.getStdin();
 			sessionUse = 1;
 		} catch (IOException ex) {
 			throw new HgRemoteConnectionException("Failed to create ssh session", ex);
@@ -119,7 +123,7 @@
 	}
 
 	public String getServerLocation() {
-		return ""; // FIXME
+		return url.toString(); // FIXME
 	}
 	
 	public String getCapabilities() throws HgRemoteConnectionException {
@@ -156,7 +160,7 @@
 	}
 
 	public InputStream heads() throws HgRemoteConnectionException {
-		return executeCommand("heads", Collections.<Parameter>emptyList());
+		return executeCommand("heads", Collections.<Parameter>emptyList(), true);
 	}
 	
 	public InputStream between(Collection<Range> ranges) throws HgRemoteConnectionException {
@@ -167,17 +171,19 @@
 		if (!ranges.isEmpty()) {
 			sb.setLength(sb.length() - 1);
 		}
-		return executeCommand("between", Collections.singletonList(new Parameter("pairs", sb.toString())));
+		return executeCommand("between", Collections.singletonList(new Parameter("pairs", sb.toString())), true);
 	}
 	
 	public InputStream branches(List<Nodeid> nodes) throws HgRemoteConnectionException {
 		String l = join(nodes, ' ');
-		return executeCommand("branches", Collections.singletonList(new Parameter("nodes", l)));
+		return executeCommand("branches", Collections.singletonList(new Parameter("nodes", l)), true);
 	}
 	
 	public InputStream changegroup(List<Nodeid> roots) throws HgRemoteConnectionException, HgRuntimeException {
 		String l = join(roots, ' ');
-		return executeCommand("changegroup", Collections.singletonList(new Parameter("roots", l)));
+		InputStream cg = executeCommand("changegroup", Collections.singletonList(new Parameter("roots", l)), false);
+		InputStream prefix = new ByteArrayInputStream("HG10UN".getBytes());
+		return new SequenceInputStream(prefix, cg);
 	}
 
 	public OutputStream unbundle(long outputLen, List<Nodeid> remoteHeads) throws HgRemoteConnectionException, HgRuntimeException {
@@ -210,14 +216,14 @@
 		p.add(new Parameter("key", key));
 		p.add(new Parameter("old", oldValue));
 		p.add(new Parameter("new", newValue));
-		return executeCommand("pushkey", p);
+		return executeCommand("pushkey", p, true);
 	}
 	
 	public InputStream listkeys(String namespace, String actionName) throws HgRemoteConnectionException, HgRuntimeException {
-		return executeCommand("listkeys", Collections.singletonList(new Parameter("namespace", namespace)));
+		return executeCommand("listkeys", Collections.singletonList(new Parameter("namespace", namespace)), true);
 	}
 	
-	private InputStream executeCommand(String cmd, List<Parameter> parameters) throws HgRemoteConnectionException {
+	private InputStream executeCommand(String cmd, List<Parameter> parameters, boolean expectResponseLength) throws HgRemoteConnectionException {
 		try {
 			consume(remoteOut);
 			consume(remoteErr);
@@ -225,9 +231,13 @@
 			remoteIn.write('\n');
 			writeParameters(parameters);
 			checkError();
-			int responseLen = readResponseLength();
-			checkError();
-			return new FilterStream(remoteOut, responseLen);
+			if (expectResponseLength) {
+				int responseLen = readResponseLength();
+				checkError();
+				return new FilterStream(remoteOut, responseLen);
+			} else {
+				return new FilterStream(remoteOut, Integer.MAX_VALUE);
+			}
 		} catch (IOException ex) {
 			throw new HgRemoteConnectionException("Communication failure", ex).setRemoteCommand(cmd).setServerInfo(getServerLocation());
 		}
@@ -376,6 +386,7 @@
 		}
 		@Override
 		public void close() throws IOException {
+			length = 0;
 			// INTENTIONALLY DOES NOT CLOSE THE STREAM
 		}
 	}
--- a/src/org/tmatesoft/hg/repo/HgRemoteRepository.java	Mon Aug 05 19:03:22 2013 +0200
+++ b/src/org/tmatesoft/hg/repo/HgRemoteRepository.java	Tue Aug 06 13:34:34 2013 +0200
@@ -23,7 +23,6 @@
 import java.io.BufferedReader;
 import java.io.ByteArrayOutputStream;
 import java.io.File;
-import java.io.FileOutputStream;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.InputStreamReader;
@@ -44,7 +43,6 @@
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
-import java.util.zip.InflaterInputStream;
 
 import org.tmatesoft.hg.core.HgBadArgumentException;
 import org.tmatesoft.hg.core.HgIOException;
@@ -61,6 +59,7 @@
 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.util.LogFacility.Severity;
 import org.tmatesoft.hg.util.Outcome;
 import org.tmatesoft.hg.util.Pair;
@@ -115,7 +114,7 @@
 		}
 		sessionContext = ctx;
 		debug = new PropertyMarshal(ctx).getBoolean("hg4j.remote.debug", false);
-		remote = new HttpConnector();
+		remote = "ssh".equals(url.getProtocol()) ? new SshConnector() : new HttpConnector();
 		remote.init(url, ctx, null);
 	}
 	
@@ -281,7 +280,7 @@
 		List<Nodeid> _roots = roots.isEmpty() ? Collections.singletonList(Nodeid.NULL) : roots;
 		try {
 			remote.sessionBegin();
-			File tf = writeBundle(remote.changegroup(_roots), false, "HG10GZ" /*didn't see any other that zip*/);
+			File tf = writeBundle(remote.changegroup(_roots));
 			if (debug) {
 				System.out.printf("Wrote bundle %s for roots %s\n", tf, roots);
 			}
@@ -459,18 +458,10 @@
 		}
 	}
 	
-	private static File writeBundle(InputStream is, boolean decompress, String header) throws IOException {
-		InputStream zipStream = decompress ? new InflaterInputStream(is) : is;
+	private File writeBundle(InputStream is) throws IOException {
 		File tf = File.createTempFile("hg4j-bundle-", null);
-		FileOutputStream fos = new FileOutputStream(tf);
-		fos.write(header.getBytes());
-		int r;
-		byte[] buf = new byte[8*1024];
-		while ((r = zipStream.read(buf)) != -1) {
-			fos.write(buf, 0, r);
-		}
-		fos.close();
-		zipStream.close();
+		new FileUtils(sessionContext.getLog(), this).write(is, tf);
+		is.close();
 		return tf;
 	}