Mercurial > hg4j
comparison src/org/tmatesoft/hg/repo/HgRemoteRepository.java @ 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 | b3c16d1aede0 |
| children | 3b7d51ed4c65 |
comparison
equal
deleted
inserted
replaced
| 644:1deea2f33218 | 645:14dac192aa26 |
|---|---|
| 1 /* | 1 /* |
| 2 * Copyright (c) 2011-2012 TMate Software Ltd | 2 * Copyright (c) 2011-2013 TMate Software Ltd |
| 3 * | 3 * |
| 4 * This program is free software; you can redistribute it and/or modify | 4 * This program is free software; you can redistribute it and/or modify |
| 5 * it under the terms of the GNU General Public License as published by | 5 * it under the terms of the GNU General Public License as published by |
| 6 * the Free Software Foundation; version 2 of the License. | 6 * the Free Software Foundation; version 2 of the License. |
| 7 * | 7 * |
| 17 package org.tmatesoft.hg.repo; | 17 package org.tmatesoft.hg.repo; |
| 18 | 18 |
| 19 import static org.tmatesoft.hg.util.LogFacility.Severity.Info; | 19 import static org.tmatesoft.hg.util.LogFacility.Severity.Info; |
| 20 | 20 |
| 21 import java.io.BufferedReader; | 21 import java.io.BufferedReader; |
| 22 import java.io.ByteArrayOutputStream; | |
| 22 import java.io.File; | 23 import java.io.File; |
| 23 import java.io.FileOutputStream; | 24 import java.io.FileOutputStream; |
| 24 import java.io.IOException; | 25 import java.io.IOException; |
| 25 import java.io.InputStream; | 26 import java.io.InputStream; |
| 26 import java.io.InputStreamReader; | 27 import java.io.InputStreamReader; |
| 27 import java.io.OutputStream; | 28 import java.io.OutputStream; |
| 28 import java.io.StreamTokenizer; | 29 import java.io.StreamTokenizer; |
| 30 import java.net.ContentHandler; | |
| 31 import java.net.ContentHandlerFactory; | |
| 29 import java.net.HttpURLConnection; | 32 import java.net.HttpURLConnection; |
| 30 import java.net.MalformedURLException; | 33 import java.net.MalformedURLException; |
| 31 import java.net.URL; | 34 import java.net.URL; |
| 32 import java.net.URLConnection; | 35 import java.net.URLConnection; |
| 33 import java.security.cert.CertificateException; | 36 import java.security.cert.CertificateException; |
| 51 import javax.net.ssl.SSLContext; | 54 import javax.net.ssl.SSLContext; |
| 52 import javax.net.ssl.TrustManager; | 55 import javax.net.ssl.TrustManager; |
| 53 import javax.net.ssl.X509TrustManager; | 56 import javax.net.ssl.X509TrustManager; |
| 54 | 57 |
| 55 import org.tmatesoft.hg.core.HgBadArgumentException; | 58 import org.tmatesoft.hg.core.HgBadArgumentException; |
| 59 import org.tmatesoft.hg.core.HgIOException; | |
| 56 import org.tmatesoft.hg.core.HgRemoteConnectionException; | 60 import org.tmatesoft.hg.core.HgRemoteConnectionException; |
| 57 import org.tmatesoft.hg.core.HgRepositoryNotFoundException; | 61 import org.tmatesoft.hg.core.HgRepositoryNotFoundException; |
| 58 import org.tmatesoft.hg.core.Nodeid; | 62 import org.tmatesoft.hg.core.Nodeid; |
| 59 import org.tmatesoft.hg.core.SessionContext; | 63 import org.tmatesoft.hg.core.SessionContext; |
| 64 import org.tmatesoft.hg.internal.DataSerializer; | |
| 65 import org.tmatesoft.hg.internal.Internals; | |
| 66 import org.tmatesoft.hg.internal.DataSerializer.OutputStreamSerializer; | |
| 60 import org.tmatesoft.hg.internal.PropertyMarshal; | 67 import org.tmatesoft.hg.internal.PropertyMarshal; |
| 61 | 68 |
| 62 /** | 69 /** |
| 63 * WORK IN PROGRESS, DO NOT USE | 70 * WORK IN PROGRESS, DO NOT USE |
| 64 * | 71 * |
| 74 private final String authInfo; | 81 private final String authInfo; |
| 75 private final boolean debug; | 82 private final boolean debug; |
| 76 private HgLookup lookupHelper; | 83 private HgLookup lookupHelper; |
| 77 private final SessionContext sessionContext; | 84 private final SessionContext sessionContext; |
| 78 private Set<String> remoteCapabilities; | 85 private Set<String> remoteCapabilities; |
| 86 | |
| 87 static { | |
| 88 URLConnection.setContentHandlerFactory(new ContentHandlerFactory() { | |
| 89 | |
| 90 public ContentHandler createContentHandler(String mimetype) { | |
| 91 if ("application/mercurial-0.1".equals(mimetype)) { | |
| 92 return new ContentHandler() { | |
| 93 | |
| 94 @Override | |
| 95 public Object getContent(URLConnection urlc) throws IOException { | |
| 96 if (urlc.getContentLength() > 0) { | |
| 97 ByteArrayOutputStream bos = new ByteArrayOutputStream(); | |
| 98 InputStream is = urlc.getInputStream(); | |
| 99 int r; | |
| 100 while ((r = is.read()) != -1) { | |
| 101 bos.write(r); | |
| 102 } | |
| 103 return new String(bos.toByteArray()); | |
| 104 } | |
| 105 return "<empty>"; | |
| 106 } | |
| 107 }; | |
| 108 } | |
| 109 return null; | |
| 110 } | |
| 111 }); | |
| 112 } | |
| 79 | 113 |
| 80 HgRemoteRepository(SessionContext ctx, URL url) throws HgBadArgumentException { | 114 HgRemoteRepository(SessionContext ctx, URL url) throws HgBadArgumentException { |
| 81 if (url == null || ctx == null) { | 115 if (url == null || ctx == null) { |
| 82 throw new IllegalArgumentException(); | 116 throw new IllegalArgumentException(); |
| 83 } | 117 } |
| 190 public SessionContext getSessionContext() { | 224 public SessionContext getSessionContext() { |
| 191 return sessionContext; | 225 return sessionContext; |
| 192 } | 226 } |
| 193 | 227 |
| 194 public List<Nodeid> heads() throws HgRemoteConnectionException { | 228 public List<Nodeid> heads() throws HgRemoteConnectionException { |
| 229 HttpURLConnection c = null; | |
| 195 try { | 230 try { |
| 196 URL u = new URL(url, url.getPath() + "?cmd=heads"); | 231 URL u = new URL(url, url.getPath() + "?cmd=heads"); |
| 197 HttpURLConnection c = setupConnection(u.openConnection()); | 232 c = setupConnection(u.openConnection()); |
| 198 c.connect(); | 233 c.connect(); |
| 199 if (debug) { | 234 if (debug) { |
| 200 dumpResponseHeader(u, c); | 235 dumpResponseHeader(u, c); |
| 201 } | 236 } |
| 202 InputStreamReader is = new InputStreamReader(c.getInputStream(), "US-ASCII"); | 237 InputStreamReader is = new InputStreamReader(c.getInputStream(), "US-ASCII"); |
| 211 return parseResult; | 246 return parseResult; |
| 212 } catch (MalformedURLException ex) { | 247 } catch (MalformedURLException ex) { |
| 213 throw new HgRemoteConnectionException("Bad URL", ex).setRemoteCommand("heads").setServerInfo(getLocation()); | 248 throw new HgRemoteConnectionException("Bad URL", ex).setRemoteCommand("heads").setServerInfo(getLocation()); |
| 214 } catch (IOException ex) { | 249 } catch (IOException ex) { |
| 215 throw new HgRemoteConnectionException("Communication failure", ex).setRemoteCommand("heads").setServerInfo(getLocation()); | 250 throw new HgRemoteConnectionException("Communication failure", ex).setRemoteCommand("heads").setServerInfo(getLocation()); |
| 251 } finally { | |
| 252 if (c != null) { | |
| 253 c.disconnect(); | |
| 254 } | |
| 216 } | 255 } |
| 217 } | 256 } |
| 218 | 257 |
| 219 public List<Nodeid> between(Nodeid tip, Nodeid base) throws HgRemoteConnectionException { | 258 public List<Nodeid> between(Nodeid tip, Nodeid base) throws HgRemoteConnectionException { |
| 220 Range r = new Range(base, tip); | 259 Range r = new Range(base, tip); |
| 243 } | 282 } |
| 244 if (sb.charAt(sb.length() - 1) == '+') { | 283 if (sb.charAt(sb.length() - 1) == '+') { |
| 245 // strip last space | 284 // strip last space |
| 246 sb.setLength(sb.length() - 1); | 285 sb.setLength(sb.length() - 1); |
| 247 } | 286 } |
| 287 HttpURLConnection c = null; | |
| 248 try { | 288 try { |
| 249 boolean usePOST = ranges.size() > 3; | 289 boolean usePOST = ranges.size() > 3; |
| 250 URL u = new URL(url, url.getPath() + "?cmd=between" + (usePOST ? "" : '&' + sb.toString())); | 290 URL u = new URL(url, url.getPath() + "?cmd=between" + (usePOST ? "" : '&' + sb.toString())); |
| 251 HttpURLConnection c = setupConnection(u.openConnection()); | 291 c = setupConnection(u.openConnection()); |
| 252 if (usePOST) { | 292 if (usePOST) { |
| 253 c.setRequestMethod("POST"); | 293 c.setRequestMethod("POST"); |
| 254 c.setRequestProperty("Content-Length", String.valueOf(sb.length()/*nodeids are ASCII, bytes == characters */)); | 294 c.setRequestProperty("Content-Length", String.valueOf(sb.length()/*nodeids are ASCII, bytes == characters */)); |
| 255 c.setRequestProperty("Content-Type", "application/x-www-form-urlencoded"); | 295 c.setRequestProperty("Content-Type", "application/x-www-form-urlencoded"); |
| 256 c.setDoOutput(true); | 296 c.setDoOutput(true); |
| 312 return rv; | 352 return rv; |
| 313 } catch (MalformedURLException ex) { | 353 } catch (MalformedURLException ex) { |
| 314 throw new HgRemoteConnectionException("Bad URL", ex).setRemoteCommand("between").setServerInfo(getLocation()); | 354 throw new HgRemoteConnectionException("Bad URL", ex).setRemoteCommand("between").setServerInfo(getLocation()); |
| 315 } catch (IOException ex) { | 355 } catch (IOException ex) { |
| 316 throw new HgRemoteConnectionException("Communication failure", ex).setRemoteCommand("between").setServerInfo(getLocation()); | 356 throw new HgRemoteConnectionException("Communication failure", ex).setRemoteCommand("between").setServerInfo(getLocation()); |
| 357 } finally { | |
| 358 if (c != null) { | |
| 359 c.disconnect(); | |
| 360 } | |
| 317 } | 361 } |
| 318 } | 362 } |
| 319 | 363 |
| 320 public List<RemoteBranch> branches(List<Nodeid> nodes) throws HgRemoteConnectionException { | 364 public List<RemoteBranch> branches(List<Nodeid> nodes) throws HgRemoteConnectionException { |
| 321 StringBuilder sb = new StringBuilder(20 + nodes.size() * 41); | 365 StringBuilder sb = appendNodeidListArgument("nodes", nodes, null); |
| 322 sb.append("nodes="); | 366 HttpURLConnection c = null; |
| 323 for (Nodeid n : nodes) { | |
| 324 sb.append(n.toString()); | |
| 325 sb.append('+'); | |
| 326 } | |
| 327 if (sb.charAt(sb.length() - 1) == '+') { | |
| 328 // strip last space | |
| 329 sb.setLength(sb.length() - 1); | |
| 330 } | |
| 331 try { | 367 try { |
| 332 URL u = new URL(url, url.getPath() + "?cmd=branches&" + sb.toString()); | 368 URL u = new URL(url, url.getPath() + "?cmd=branches&" + sb.toString()); |
| 333 HttpURLConnection c = setupConnection(u.openConnection()); | 369 c = setupConnection(u.openConnection()); |
| 334 c.connect(); | 370 c.connect(); |
| 335 if (debug) { | 371 if (debug) { |
| 336 dumpResponseHeader(u, c); | 372 dumpResponseHeader(u, c); |
| 337 } | 373 } |
| 338 InputStreamReader is = new InputStreamReader(c.getInputStream(), "US-ASCII"); | 374 InputStreamReader is = new InputStreamReader(c.getInputStream(), "US-ASCII"); |
| 355 return rv; | 391 return rv; |
| 356 } catch (MalformedURLException ex) { | 392 } catch (MalformedURLException ex) { |
| 357 throw new HgRemoteConnectionException("Bad URL", ex).setRemoteCommand("branches").setServerInfo(getLocation()); | 393 throw new HgRemoteConnectionException("Bad URL", ex).setRemoteCommand("branches").setServerInfo(getLocation()); |
| 358 } catch (IOException ex) { | 394 } catch (IOException ex) { |
| 359 throw new HgRemoteConnectionException("Communication failure", ex).setRemoteCommand("branches").setServerInfo(getLocation()); | 395 throw new HgRemoteConnectionException("Communication failure", ex).setRemoteCommand("branches").setServerInfo(getLocation()); |
| 396 } finally { | |
| 397 if (c != null) { | |
| 398 c.disconnect(); | |
| 399 } | |
| 360 } | 400 } |
| 361 } | 401 } |
| 362 | 402 |
| 363 /* | 403 /* |
| 364 * XXX need to describe behavior when roots arg is empty; our RepositoryComparator code currently returns empty lists when | 404 * XXX need to describe behavior when roots arg is empty; our RepositoryComparator code currently returns empty lists when |
| 376 * (there's no header like HG10?? with the server output, though, | 416 * (there's no header like HG10?? with the server output, though, |
| 377 * as one may expect according to http://mercurial.selenic.com/wiki/BundleFormat) | 417 * as one may expect according to http://mercurial.selenic.com/wiki/BundleFormat) |
| 378 */ | 418 */ |
| 379 public HgBundle getChanges(List<Nodeid> roots) throws HgRemoteConnectionException, HgRuntimeException { | 419 public HgBundle getChanges(List<Nodeid> roots) throws HgRemoteConnectionException, HgRuntimeException { |
| 380 List<Nodeid> _roots = roots.isEmpty() ? Collections.singletonList(Nodeid.NULL) : roots; | 420 List<Nodeid> _roots = roots.isEmpty() ? Collections.singletonList(Nodeid.NULL) : roots; |
| 381 StringBuilder sb = new StringBuilder(20 + _roots.size() * 41); | 421 StringBuilder sb = appendNodeidListArgument("roots", _roots, null); |
| 382 sb.append("roots="); | 422 HttpURLConnection c = null; |
| 383 for (Nodeid n : _roots) { | |
| 384 sb.append(n.toString()); | |
| 385 sb.append('+'); | |
| 386 } | |
| 387 if (sb.charAt(sb.length() - 1) == '+') { | |
| 388 // strip last space | |
| 389 sb.setLength(sb.length() - 1); | |
| 390 } | |
| 391 try { | 423 try { |
| 392 URL u = new URL(url, url.getPath() + "?cmd=changegroup&" + sb.toString()); | 424 URL u = new URL(url, url.getPath() + "?cmd=changegroup&" + sb.toString()); |
| 393 HttpURLConnection c = setupConnection(u.openConnection()); | 425 c = setupConnection(u.openConnection()); |
| 394 c.connect(); | 426 c.connect(); |
| 395 if (debug) { | 427 if (debug) { |
| 396 dumpResponseHeader(u, c); | 428 dumpResponseHeader(u, c); |
| 397 } | 429 } |
| 398 File tf = writeBundle(c.getInputStream(), false, "HG10GZ" /*didn't see any other that zip*/); | 430 File tf = writeBundle(c.getInputStream(), false, "HG10GZ" /*didn't see any other that zip*/); |
| 405 throw new HgRemoteConnectionException("Bad URL", ex).setRemoteCommand("changegroup").setServerInfo(getLocation()); | 437 throw new HgRemoteConnectionException("Bad URL", ex).setRemoteCommand("changegroup").setServerInfo(getLocation()); |
| 406 } catch (IOException ex) { | 438 } catch (IOException ex) { |
| 407 throw new HgRemoteConnectionException("Communication failure", ex).setRemoteCommand("changegroup").setServerInfo(getLocation()); | 439 throw new HgRemoteConnectionException("Communication failure", ex).setRemoteCommand("changegroup").setServerInfo(getLocation()); |
| 408 } catch (HgRepositoryNotFoundException ex) { | 440 } catch (HgRepositoryNotFoundException ex) { |
| 409 throw new HgRemoteConnectionException("Communication failure", ex).setRemoteCommand("changegroup").setServerInfo(getLocation()); | 441 throw new HgRemoteConnectionException("Communication failure", ex).setRemoteCommand("changegroup").setServerInfo(getLocation()); |
| 442 } finally { | |
| 443 if (c != null) { | |
| 444 c.disconnect(); | |
| 445 } | |
| 446 } | |
| 447 } | |
| 448 | |
| 449 public void unbundle(HgBundle bundle, List<Nodeid> heads) throws HgRemoteConnectionException, HgRuntimeException { | |
| 450 if (heads == null) { | |
| 451 // TODO collect heads from bundle: | |
| 452 // bundle.inspectChangelog(new HeadCollector(for each c : if collected has c.p1 or c.p2, remove them. Add c)) | |
| 453 throw Internals.notImplemented(); | |
| 454 } | |
| 455 StringBuilder sb = appendNodeidListArgument("heads", heads, null); | |
| 456 | |
| 457 HttpURLConnection c = null; | |
| 458 DataSerializer.DataSource bundleData = bundle.new BundleSerializer(); | |
| 459 try { | |
| 460 URL u = new URL(url, url.getPath() + "?cmd=unbundle&" + sb.toString()); | |
| 461 c = setupConnection(u.openConnection()); | |
| 462 c.setRequestMethod("POST"); | |
| 463 c.setRequestProperty("Content-Length", String.valueOf(bundleData.serializeLength())); | |
| 464 c.setRequestProperty("Content-Type", "application/mercurial-0.1"); | |
| 465 c.setDoOutput(true); | |
| 466 c.connect(); | |
| 467 OutputStream os = c.getOutputStream(); | |
| 468 bundleData.serialize(new OutputStreamSerializer(os)); | |
| 469 os.flush(); | |
| 470 os.close(); | |
| 471 if (debug) { | |
| 472 dumpResponseHeader(u, c); | |
| 473 dumpResponse(c); | |
| 474 } | |
| 475 if (c.getResponseCode() != 200) { | |
| 476 String m = c.getResponseMessage() == null ? "unknown reason" : c.getResponseMessage(); | |
| 477 String em = String.format("Push failed: %s (HTTP error:%d)", m, c.getResponseCode()); | |
| 478 throw new HgRemoteConnectionException(em).setRemoteCommand("unbundle").setServerInfo(getLocation()); | |
| 479 } | |
| 480 } catch (MalformedURLException ex) { | |
| 481 throw new HgRemoteConnectionException("Bad URL", ex).setRemoteCommand("unbundle").setServerInfo(getLocation()); | |
| 482 } catch (IOException ex) { | |
| 483 throw new HgRemoteConnectionException("Communication failure", ex).setRemoteCommand("unbundle").setServerInfo(getLocation()); | |
| 484 } catch (HgIOException ex) { | |
| 485 throw new HgRemoteConnectionException("Communication failure", ex).setRemoteCommand("unbundle").setServerInfo(getLocation()); | |
| 486 } finally { | |
| 487 if (c != null) { | |
| 488 c.disconnect(); | |
| 489 } | |
| 410 } | 490 } |
| 411 } | 491 } |
| 412 | 492 |
| 413 @Override | 493 @Override |
| 414 public String toString() { | 494 public String toString() { |
| 421 } | 501 } |
| 422 return lookupHelper; | 502 return lookupHelper; |
| 423 } | 503 } |
| 424 | 504 |
| 425 private HttpURLConnection setupConnection(URLConnection urlConnection) { | 505 private HttpURLConnection setupConnection(URLConnection urlConnection) { |
| 426 urlConnection.setRequestProperty("User-Agent", "hg4j/0.5.0"); | 506 urlConnection.setRequestProperty("User-Agent", "hg4j/1.0.0"); |
| 427 urlConnection.addRequestProperty("Accept", "application/mercurial-0.1"); | 507 urlConnection.addRequestProperty("Accept", "application/mercurial-0.1"); |
| 428 if (authInfo != null) { | 508 if (authInfo != null) { |
| 429 urlConnection.addRequestProperty("Authorization", "Basic " + authInfo); | 509 urlConnection.addRequestProperty("Authorization", "Basic " + authInfo); |
| 430 } | 510 } |
| 431 if (sslContext != null) { | 511 if (sslContext != null) { |
| 432 ((HttpsURLConnection) urlConnection).setSSLSocketFactory(sslContext.getSocketFactory()); | 512 ((HttpsURLConnection) urlConnection).setSSLSocketFactory(sslContext.getSocketFactory()); |
| 433 } | 513 } |
| 434 return (HttpURLConnection) urlConnection; | 514 return (HttpURLConnection) urlConnection; |
| 515 } | |
| 516 | |
| 517 private StringBuilder appendNodeidListArgument(String key, List<Nodeid> values, StringBuilder sb) { | |
| 518 if (sb == null) { | |
| 519 sb = new StringBuilder(20 + values.size() * 41); | |
| 520 } | |
| 521 sb.append(key); | |
| 522 sb.append('='); | |
| 523 for (Nodeid n : values) { | |
| 524 sb.append(n.toString()); | |
| 525 sb.append('+'); | |
| 526 } | |
| 527 if (sb.charAt(sb.length() - 1) == '+') { | |
| 528 // strip last space | |
| 529 sb.setLength(sb.length() - 1); | |
| 530 } | |
| 531 return sb; | |
| 435 } | 532 } |
| 436 | 533 |
| 437 private void dumpResponseHeader(URL u, HttpURLConnection c) { | 534 private void dumpResponseHeader(URL u, HttpURLConnection c) { |
| 438 System.out.printf("Query (%d bytes):%s\n", u.getQuery().length(), u.getQuery()); | 535 System.out.printf("Query (%d bytes):%s\n", u.getQuery().length(), u.getQuery()); |
| 439 System.out.println("Response headers:"); | 536 System.out.println("Response headers:"); |
| 440 final Map<String, List<String>> headerFields = c.getHeaderFields(); | 537 final Map<String, List<String>> headerFields = c.getHeaderFields(); |
| 441 for (String s : headerFields.keySet()) { | 538 for (String s : headerFields.keySet()) { |
| 442 System.out.printf("%s: %s\n", s, c.getHeaderField(s)); | 539 System.out.printf("%s: %s\n", s, c.getHeaderField(s)); |
| 540 } | |
| 541 } | |
| 542 | |
| 543 private void dumpResponse(HttpURLConnection c) throws IOException { | |
| 544 if (c.getContentLength() > 0) { | |
| 545 final Object content = c.getContent(); | |
| 546 System.out.println(content); | |
| 443 } | 547 } |
| 444 } | 548 } |
| 445 | 549 |
| 446 private static File writeBundle(InputStream is, boolean decompress, String header) throws IOException { | 550 private static File writeBundle(InputStream is, boolean decompress, String header) throws IOException { |
| 447 InputStream zipStream = decompress ? new InflaterInputStream(is) : is; | 551 InputStream zipStream = decompress ? new InflaterInputStream(is) : is; |
