Mercurial > jhg
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; |