Mercurial > jhg
changeset 645:14dac192aa26
Push: phase2 - upload bundle with changes to remote server
author | Artem Tikhomirov <tikhomirov.artem@gmail.com> |
---|---|
date | Thu, 20 Jun 2013 19:15:09 +0200 |
parents | 1deea2f33218 |
children | 3b7d51ed4c65 |
files | src/org/tmatesoft/hg/core/HgPushCommand.java src/org/tmatesoft/hg/internal/BundleGenerator.java src/org/tmatesoft/hg/internal/DataSerializer.java src/org/tmatesoft/hg/internal/RepositoryComparator.java src/org/tmatesoft/hg/internal/RevlogStreamWriter.java src/org/tmatesoft/hg/repo/HgBundle.java src/org/tmatesoft/hg/repo/HgParentChildMap.java src/org/tmatesoft/hg/repo/HgRemoteRepository.java |
diffstat | 8 files changed, 341 insertions(+), 68 deletions(-) [+] |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/org/tmatesoft/hg/core/HgPushCommand.java Thu Jun 20 19:15:09 2013 +0200 @@ -0,0 +1,110 @@ +/* + * 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.core; + +import java.io.File; +import java.io.IOException; +import java.net.URL; +import java.util.List; + +import org.tmatesoft.hg.internal.BundleGenerator; +import org.tmatesoft.hg.internal.RepositoryComparator; +import org.tmatesoft.hg.repo.HgBundle; +import org.tmatesoft.hg.repo.HgChangelog; +import org.tmatesoft.hg.repo.HgInternals; +import org.tmatesoft.hg.repo.HgInvalidStateException; +import org.tmatesoft.hg.repo.HgLookup; +import org.tmatesoft.hg.repo.HgParentChildMap; +import org.tmatesoft.hg.repo.HgRemoteRepository; +import org.tmatesoft.hg.repo.HgRepository; +import org.tmatesoft.hg.repo.HgRuntimeException; +import org.tmatesoft.hg.util.CancelledException; +import org.tmatesoft.hg.util.ProgressSupport; + +/** + * + * @author Artem Tikhomirov + * @author TMate Software Ltd. + */ +public class HgPushCommand extends HgAbstractCommand<HgPushCommand> { + + private final HgRepository repo; + private HgRemoteRepository remoteRepo; + + public HgPushCommand(HgRepository hgRepo) { + repo = hgRepo; + } + + public HgPushCommand destination(HgRemoteRepository hgRemote) { + remoteRepo = hgRemote; + return this; + } + + public void execute() throws HgRemoteConnectionException, HgIOException, CancelledException, HgLibraryFailureException { + final ProgressSupport progress = getProgressSupport(null); + try { + progress.start(100); + // + // find out missing + // TODO refactor same code in HgOutgoingCommand #getComparator and #getParentHelper + final HgParentChildMap<HgChangelog> parentHelper = new HgParentChildMap<HgChangelog>(repo.getChangelog()); + parentHelper.init(); + final RepositoryComparator comparator = new RepositoryComparator(parentHelper, remoteRepo); + comparator.compare(new ProgressSupport.Sub(progress, 50), getCancelSupport(null, true)); + List<Nodeid> l = comparator.getLocalOnlyRevisions(); + // + // prepare bundle + BundleGenerator bg = new BundleGenerator(HgInternals.getImplementationRepo(repo)); + File bundleFile = bg.create(l); + progress.worked(20); + HgBundle b = new HgLookup(repo.getSessionContext()).loadBundle(bundleFile); + // + // send changes + remoteRepo.unbundle(b, comparator.getRemoteHeads()); + progress.worked(20); + // + // FIXME update phase information +// remote.listkeys("phases"); + progress.worked(5); + // + // FIXME update bookmark information +// remote.listkeys("bookmarks"); + progress.worked(5); + } catch (IOException ex) { + throw new HgIOException(ex.getMessage(), null); // XXX not a nice idea to throw IOException from BundleGenerator#create + } catch (HgRepositoryNotFoundException ex) { + final HgInvalidStateException e = new HgInvalidStateException("Failed to load a just-created bundle"); + e.initCause(ex); + throw new HgLibraryFailureException(e); + } catch (HgRuntimeException ex) { + throw new HgLibraryFailureException(ex); + } finally { + progress.done(); + } + } + + /* + * To test, start a server: + * $ hg --config web.allow_push=* --config web.push_ssl=False --config server.validate=True --debug serve + */ + public static void main(String[] args) throws Exception { + final HgLookup hgLookup = new HgLookup(); + HgRepository r = hgLookup.detect("/home/artem/hg/junit-test-repos/log-1/"); + HgRemoteRepository rr = hgLookup.detect(new URL("http://localhost:8000/")); + new HgPushCommand(r).destination(rr).execute(); + } +}
--- a/src/org/tmatesoft/hg/internal/BundleGenerator.java Wed Jun 19 16:04:24 2013 +0200 +++ b/src/org/tmatesoft/hg/internal/BundleGenerator.java Thu Jun 20 19:15:09 2013 +0200 @@ -22,17 +22,18 @@ import java.io.File; import java.io.FileOutputStream; import java.io.IOException; -import java.io.OutputStream; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.Comparator; +import java.util.HashSet; import java.util.List; +import java.util.Set; -import org.tmatesoft.hg.console.Bundle; import org.tmatesoft.hg.core.HgIOException; import org.tmatesoft.hg.core.Nodeid; +import org.tmatesoft.hg.internal.DataSerializer.OutputStreamSerializer; import org.tmatesoft.hg.internal.Patch.PatchDataSource; import org.tmatesoft.hg.repo.HgBundle; import org.tmatesoft.hg.repo.HgChangelog; @@ -72,14 +73,17 @@ final IntVector manifestRevs = new IntVector(changesets.size(), 0); final List<HgDataFile> files = new ArrayList<HgDataFile>(); clog.range(new HgChangelog.Inspector() { + private Set<String> seenFiles = new HashSet<String>(); public void next(int revisionIndex, Nodeid nodeid, RawChangeset cset) throws HgRuntimeException { clogMap.put(revisionIndex, nodeid); manifestRevs.add(manifest.getRevisionIndex(cset.manifest())); for (String f : cset.files()) { + if (seenFiles.contains(f)) { + continue; + } + seenFiles.add(f); HgDataFile df = repo.getRepo().getFileNode(f); - if (!files.contains(df)) { - files.add(df); - } + files.add(df); } } }, clogRevs); @@ -106,7 +110,8 @@ /////////////// // final File bundleFile = File.createTempFile("hg4j-", "bundle"); - final OutputStreamSerializer outRaw = new OutputStreamSerializer(new FileOutputStream(bundleFile)); + final FileOutputStream osBundle = new FileOutputStream(bundleFile); + final OutputStreamSerializer outRaw = new OutputStreamSerializer(osBundle); outRaw.write("HG10UN".getBytes(), 0, 6); // RevlogStream clogStream = repo.getImplAccess().getChangelogStream(); @@ -140,7 +145,10 @@ outRaw.writeInt(0); // null chunk for file group } } + outRaw.writeInt(0); // null chunk to indicate no more files (although BundleFormat page doesn't mention this) outRaw.done(); + osBundle.flush(); + osBundle.close(); //return new HgBundle(repo.getSessionContext(), repo.getDataAccess(), bundleFile); return bundleFile; } @@ -235,30 +243,4 @@ } } } - - private static class OutputStreamSerializer extends DataSerializer { - private final OutputStream out; - public OutputStreamSerializer(OutputStream outputStream) { - out = outputStream; - } - - @Override - public void write(byte[] data, int offset, int length) throws HgIOException { - try { - out.write(data, offset, length); - } catch (IOException ex) { - throw new HgIOException(ex.getMessage(), ex, null); - } - } - - @Override - public void done() throws HgIOException { - try { - out.close(); - super.done(); - } catch (IOException ex) { - throw new HgIOException(ex.getMessage(), ex, null); - } - } - } }
--- a/src/org/tmatesoft/hg/internal/DataSerializer.java Wed Jun 19 16:04:24 2013 +0200 +++ b/src/org/tmatesoft/hg/internal/DataSerializer.java Thu Jun 20 19:15:09 2013 +0200 @@ -17,6 +17,8 @@ package org.tmatesoft.hg.internal; import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.OutputStream; import org.tmatesoft.hg.core.HgIOException; import org.tmatesoft.hg.repo.HgRuntimeException; @@ -74,7 +76,7 @@ * Denotes an entity that wants to/could be serialized */ @Experimental(reason="Work in progress") - interface DataSource { + public interface DataSource { /** * Invoked once for a single write operation, * although the source itself may get serialized several times @@ -107,7 +109,10 @@ } } - public static class ByteArrayDataSerializer extends DataSerializer { + /** + * Serialize data to byte array + */ + public static class ByteArraySerializer extends DataSerializer { private final ByteArrayOutputStream out = new ByteArrayOutputStream(); @Override @@ -119,4 +124,26 @@ return out.toByteArray(); } } + + /** + * Bridge to the world of {@link java.io.OutputStream}. + * Caller instantiates the stream and is responsible to close it as appropriate, + * {@link #done() DataSerializer.done()} doesn't close the stream. + */ + public static class OutputStreamSerializer extends DataSerializer { + private final OutputStream out; + + public OutputStreamSerializer(OutputStream outputStream) { + out = outputStream; + } + + @Override + public void write(byte[] data, int offset, int length) throws HgIOException { + try { + out.write(data, offset, length); + } catch (IOException ex) { + throw new HgIOException(ex.getMessage(), ex, null); + } + } + } }
--- a/src/org/tmatesoft/hg/internal/RepositoryComparator.java Wed Jun 19 16:04:24 2013 +0200 +++ b/src/org/tmatesoft/hg/internal/RepositoryComparator.java Thu Jun 20 19:15:09 2013 +0200 @@ -54,6 +54,7 @@ private final HgParentChildMap<HgChangelog> localRepo; private final HgRemoteRepository remoteRepo; private List<Nodeid> common; + private List<Nodeid> remoteHeads; public RepositoryComparator(HgParentChildMap<HgChangelog> pwLocal, HgRemoteRepository hgRemote) { localRepo = pwLocal; @@ -81,11 +82,21 @@ return common; } + public List<Nodeid> getRemoteHeads() { + assert remoteHeads != null; + return remoteHeads; + } + /** * @return revisions that are children of common entries, i.e. revisions that are present on the local server and not on remote. */ public List<Nodeid> getLocalOnlyRevisions() { - return localRepo.childrenOf(getCommon()); + final List<Nodeid> c = getCommon(); + if (c.isEmpty()) { + return localRepo.all(); + } else { + return localRepo.childrenOf(c); + } } /** @@ -128,7 +139,7 @@ } private List<Nodeid> findCommonWithRemote() throws HgRemoteConnectionException { - List<Nodeid> remoteHeads = remoteRepo.heads(); + remoteHeads = remoteRepo.heads(); LinkedList<Nodeid> resultCommon = new LinkedList<Nodeid>(); // these remotes are known in local LinkedList<Nodeid> toQuery = new LinkedList<Nodeid>(); // these need further queries to find common for (Nodeid rh : remoteHeads) {
--- a/src/org/tmatesoft/hg/internal/RevlogStreamWriter.java Wed Jun 19 16:04:24 2013 +0200 +++ b/src/org/tmatesoft/hg/internal/RevlogStreamWriter.java Thu Jun 20 19:15:09 2013 +0200 @@ -25,7 +25,7 @@ import org.tmatesoft.hg.core.HgIOException; import org.tmatesoft.hg.core.Nodeid; import org.tmatesoft.hg.core.SessionContext; -import org.tmatesoft.hg.internal.DataSerializer.ByteArrayDataSerializer; +import org.tmatesoft.hg.internal.DataSerializer.ByteArraySerializer; import org.tmatesoft.hg.internal.DataSerializer.ByteArrayDataSource; import org.tmatesoft.hg.internal.DataSerializer.DataSource; import org.tmatesoft.hg.repo.HgInvalidControlFileException; @@ -142,7 +142,7 @@ } private byte[] toByteArray(DataSource content) throws HgIOException, HgRuntimeException { - ByteArrayDataSerializer ba = new ByteArrayDataSerializer(); + ByteArraySerializer ba = new ByteArraySerializer(); content.serialize(ba); return ba.toByteArray(); }
--- a/src/org/tmatesoft/hg/repo/HgBundle.java Wed Jun 19 16:04:24 2013 +0200 +++ b/src/org/tmatesoft/hg/repo/HgBundle.java Thu Jun 20 19:15:09 2013 +0200 @@ -17,9 +17,11 @@ package org.tmatesoft.hg.repo; import java.io.File; +import java.io.FileInputStream; import java.io.IOException; import java.util.ConcurrentModificationException; +import org.tmatesoft.hg.core.HgIOException; import org.tmatesoft.hg.core.Nodeid; import org.tmatesoft.hg.core.SessionContext; import org.tmatesoft.hg.internal.ByteArrayChannel; @@ -27,8 +29,10 @@ import org.tmatesoft.hg.internal.Callback; import org.tmatesoft.hg.internal.DataAccess; import org.tmatesoft.hg.internal.DataAccessProvider; +import org.tmatesoft.hg.internal.DataSerializer; import org.tmatesoft.hg.internal.DigestHelper; import org.tmatesoft.hg.internal.Experimental; +import org.tmatesoft.hg.internal.FileUtils; import org.tmatesoft.hg.internal.InflaterDataAccess; import org.tmatesoft.hg.internal.Internals; import org.tmatesoft.hg.internal.Lifecycle; @@ -50,11 +54,11 @@ private final File bundleFile; private final DataAccessProvider accessProvider; -// private final SessionContext sessionContext; + private final SessionContext ctx; private Lifecycle.BasicCallback flowControl; - HgBundle(SessionContext ctx, DataAccessProvider dap, File bundle) { -// sessionContext = ctx; + HgBundle(SessionContext sessionContext, DataAccessProvider dap, File bundle) { + ctx = sessionContext; accessProvider = dap; bundleFile = bundle; } @@ -533,4 +537,29 @@ return String.format("%s %s %s %s; patches:%d\n", node().shortNotation(), firstParent().shortNotation(), secondParent().shortNotation(), cset().shortNotation(), patchCount); } } + + @Experimental(reason="Work in progress, not an API") + public class BundleSerializer implements DataSerializer.DataSource { + + public void serialize(DataSerializer out) throws HgIOException, HgRuntimeException { + FileInputStream fis = null; + try { + fis = new FileInputStream(HgBundle.this.bundleFile); + byte[] buffer = new byte[8*1024]; + int r; + while ((r = fis.read(buffer, 0, buffer.length)) > 0) { + out.write(buffer, 0, r); + } + + } catch (IOException ex) { + throw new HgIOException("Failed to serialize bundle", HgBundle.this.bundleFile); + } finally { + new FileUtils(HgBundle.this.ctx.getLog()).closeQuietly(fis, HgBundle.this.bundleFile); + } + } + + public int serializeLength() throws HgRuntimeException { + return Internals.ltoi(HgBundle.this.bundleFile.length()); + } + } }
--- a/src/org/tmatesoft/hg/repo/HgParentChildMap.java Wed Jun 19 16:04:24 2013 +0200 +++ b/src/org/tmatesoft/hg/repo/HgParentChildMap.java Thu Jun 20 19:15:09 2013 +0200 @@ -20,6 +20,7 @@ import java.util.Arrays; import java.util.Collection; +import java.util.Collections; import java.util.HashSet; import java.util.LinkedList; import java.util.List; @@ -56,7 +57,6 @@ */ public final class HgParentChildMap<T extends Revlog> implements ParentInspector { - private Nodeid[] sequential; // natural repository order, childrenOf rely on ordering private Nodeid[] sorted; // for binary search private int[] sorted2natural; @@ -180,6 +180,9 @@ // @return ordered collection of all children rooted at supplied nodes. Nodes shall not be descendants of each other! // Nodeids shall belong to this revlog public List<Nodeid> childrenOf(List<Nodeid> roots) { + if (roots.isEmpty()) { + return Collections.emptyList(); + } HashSet<Nodeid> parents = new HashSet<Nodeid>(); LinkedList<Nodeid> result = new LinkedList<Nodeid>(); int earliestRevision = Integer.MAX_VALUE; @@ -244,4 +247,11 @@ } return false; } + + /** + * @return all revisions this map knows about + */ + public List<Nodeid> all() { + return Arrays.asList(sequential); + } } \ No newline at end of file
--- a/src/org/tmatesoft/hg/repo/HgRemoteRepository.java Wed Jun 19 16:04:24 2013 +0200 +++ b/src/org/tmatesoft/hg/repo/HgRemoteRepository.java Thu Jun 20 19:15:09 2013 +0200 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2011-2012 TMate Software Ltd + * Copyright (c) 2011-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 @@ -19,6 +19,7 @@ import static org.tmatesoft.hg.util.LogFacility.Severity.Info; import java.io.BufferedReader; +import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; @@ -26,6 +27,8 @@ import java.io.InputStreamReader; import java.io.OutputStream; import java.io.StreamTokenizer; +import java.net.ContentHandler; +import java.net.ContentHandlerFactory; import java.net.HttpURLConnection; import java.net.MalformedURLException; import java.net.URL; @@ -53,10 +56,14 @@ import javax.net.ssl.X509TrustManager; import org.tmatesoft.hg.core.HgBadArgumentException; +import org.tmatesoft.hg.core.HgIOException; import org.tmatesoft.hg.core.HgRemoteConnectionException; import org.tmatesoft.hg.core.HgRepositoryNotFoundException; import org.tmatesoft.hg.core.Nodeid; import org.tmatesoft.hg.core.SessionContext; +import org.tmatesoft.hg.internal.DataSerializer; +import org.tmatesoft.hg.internal.Internals; +import org.tmatesoft.hg.internal.DataSerializer.OutputStreamSerializer; import org.tmatesoft.hg.internal.PropertyMarshal; /** @@ -77,6 +84,33 @@ private final SessionContext sessionContext; private Set<String> remoteCapabilities; + 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(); @@ -192,9 +226,10 @@ } public List<Nodeid> heads() throws HgRemoteConnectionException { + HttpURLConnection c = null; try { URL u = new URL(url, url.getPath() + "?cmd=heads"); - HttpURLConnection c = setupConnection(u.openConnection()); + c = setupConnection(u.openConnection()); c.connect(); if (debug) { dumpResponseHeader(u, c); @@ -213,6 +248,10 @@ throw new HgRemoteConnectionException("Bad URL", ex).setRemoteCommand("heads").setServerInfo(getLocation()); } catch (IOException ex) { throw new HgRemoteConnectionException("Communication failure", ex).setRemoteCommand("heads").setServerInfo(getLocation()); + } finally { + if (c != null) { + c.disconnect(); + } } } @@ -245,10 +284,11 @@ // strip last space sb.setLength(sb.length() - 1); } + HttpURLConnection c = null; try { boolean usePOST = ranges.size() > 3; URL u = new URL(url, url.getPath() + "?cmd=between" + (usePOST ? "" : '&' + sb.toString())); - HttpURLConnection c = setupConnection(u.openConnection()); + c = setupConnection(u.openConnection()); if (usePOST) { c.setRequestMethod("POST"); c.setRequestProperty("Content-Length", String.valueOf(sb.length()/*nodeids are ASCII, bytes == characters */)); @@ -314,23 +354,19 @@ throw new HgRemoteConnectionException("Bad URL", ex).setRemoteCommand("between").setServerInfo(getLocation()); } catch (IOException ex) { throw new HgRemoteConnectionException("Communication failure", ex).setRemoteCommand("between").setServerInfo(getLocation()); + } finally { + if (c != null) { + c.disconnect(); + } } } public List<RemoteBranch> branches(List<Nodeid> nodes) throws HgRemoteConnectionException { - StringBuilder sb = new StringBuilder(20 + nodes.size() * 41); - sb.append("nodes="); - for (Nodeid n : nodes) { - sb.append(n.toString()); - sb.append('+'); - } - if (sb.charAt(sb.length() - 1) == '+') { - // strip last space - sb.setLength(sb.length() - 1); - } + StringBuilder sb = appendNodeidListArgument("nodes", nodes, null); + HttpURLConnection c = null; try { URL u = new URL(url, url.getPath() + "?cmd=branches&" + sb.toString()); - HttpURLConnection c = setupConnection(u.openConnection()); + c = setupConnection(u.openConnection()); c.connect(); if (debug) { dumpResponseHeader(u, c); @@ -357,6 +393,10 @@ throw new HgRemoteConnectionException("Bad URL", ex).setRemoteCommand("branches").setServerInfo(getLocation()); } catch (IOException ex) { throw new HgRemoteConnectionException("Communication failure", ex).setRemoteCommand("branches").setServerInfo(getLocation()); + } finally { + if (c != null) { + c.disconnect(); + } } } @@ -378,19 +418,11 @@ */ public HgBundle getChanges(List<Nodeid> roots) throws HgRemoteConnectionException, HgRuntimeException { List<Nodeid> _roots = roots.isEmpty() ? Collections.singletonList(Nodeid.NULL) : roots; - StringBuilder sb = new StringBuilder(20 + _roots.size() * 41); - sb.append("roots="); - for (Nodeid n : _roots) { - sb.append(n.toString()); - sb.append('+'); - } - if (sb.charAt(sb.length() - 1) == '+') { - // strip last space - sb.setLength(sb.length() - 1); - } + StringBuilder sb = appendNodeidListArgument("roots", _roots, null); + HttpURLConnection c = null; try { URL u = new URL(url, url.getPath() + "?cmd=changegroup&" + sb.toString()); - HttpURLConnection c = setupConnection(u.openConnection()); + c = setupConnection(u.openConnection()); c.connect(); if (debug) { dumpResponseHeader(u, c); @@ -407,6 +439,54 @@ throw new HgRemoteConnectionException("Communication failure", ex).setRemoteCommand("changegroup").setServerInfo(getLocation()); } catch (HgRepositoryNotFoundException ex) { throw new HgRemoteConnectionException("Communication failure", ex).setRemoteCommand("changegroup").setServerInfo(getLocation()); + } finally { + if (c != null) { + c.disconnect(); + } + } + } + + public void unbundle(HgBundle bundle, List<Nodeid> heads) throws HgRemoteConnectionException, HgRuntimeException { + if (heads == null) { + // TODO collect heads from bundle: + // bundle.inspectChangelog(new HeadCollector(for each c : if collected has c.p1 or c.p2, remove them. Add c)) + throw Internals.notImplemented(); + } + StringBuilder sb = appendNodeidListArgument("heads", heads, null); + + HttpURLConnection c = null; + DataSerializer.DataSource bundleData = bundle.new BundleSerializer(); + try { + URL u = new URL(url, url.getPath() + "?cmd=unbundle&" + sb.toString()); + c = setupConnection(u.openConnection()); + c.setRequestMethod("POST"); + c.setRequestProperty("Content-Length", String.valueOf(bundleData.serializeLength())); + c.setRequestProperty("Content-Type", "application/mercurial-0.1"); + c.setDoOutput(true); + c.connect(); + OutputStream os = c.getOutputStream(); + bundleData.serialize(new OutputStreamSerializer(os)); + os.flush(); + os.close(); + if (debug) { + dumpResponseHeader(u, c); + dumpResponse(c); + } + if (c.getResponseCode() != 200) { + String m = c.getResponseMessage() == null ? "unknown reason" : c.getResponseMessage(); + String em = String.format("Push failed: %s (HTTP error:%d)", m, c.getResponseCode()); + throw new HgRemoteConnectionException(em).setRemoteCommand("unbundle").setServerInfo(getLocation()); + } + } catch (MalformedURLException ex) { + throw new HgRemoteConnectionException("Bad URL", ex).setRemoteCommand("unbundle").setServerInfo(getLocation()); + } catch (IOException ex) { + throw new HgRemoteConnectionException("Communication failure", ex).setRemoteCommand("unbundle").setServerInfo(getLocation()); + } catch (HgIOException ex) { + throw new HgRemoteConnectionException("Communication failure", ex).setRemoteCommand("unbundle").setServerInfo(getLocation()); + } finally { + if (c != null) { + c.disconnect(); + } } } @@ -423,7 +503,7 @@ } private HttpURLConnection setupConnection(URLConnection urlConnection) { - urlConnection.setRequestProperty("User-Agent", "hg4j/0.5.0"); + urlConnection.setRequestProperty("User-Agent", "hg4j/1.0.0"); urlConnection.addRequestProperty("Accept", "application/mercurial-0.1"); if (authInfo != null) { urlConnection.addRequestProperty("Authorization", "Basic " + authInfo); @@ -433,6 +513,23 @@ } return (HttpURLConnection) urlConnection; } + + private StringBuilder appendNodeidListArgument(String key, List<Nodeid> values, StringBuilder sb) { + if (sb == null) { + sb = new StringBuilder(20 + values.size() * 41); + } + sb.append(key); + sb.append('='); + for (Nodeid n : values) { + sb.append(n.toString()); + sb.append('+'); + } + if (sb.charAt(sb.length() - 1) == '+') { + // strip last space + sb.setLength(sb.length() - 1); + } + return sb; + } private void dumpResponseHeader(URL u, HttpURLConnection c) { System.out.printf("Query (%d bytes):%s\n", u.getQuery().length(), u.getQuery()); @@ -443,6 +540,13 @@ } } + private void dumpResponse(HttpURLConnection c) throws IOException { + if (c.getContentLength() > 0) { + final Object content = c.getContent(); + System.out.println(content); + } + } + private static File writeBundle(InputStream is, boolean decompress, String header) throws IOException { InputStream zipStream = decompress ? new InflaterInputStream(is) : is; File tf = File.createTempFile("hg-bundle-", null);