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;