Mercurial > jhg
comparison src/org/tmatesoft/hg/repo/HgRemoteRepository.java @ 646:3b7d51ed4c65
Push: phase3 - update matching remote bookmarks
| author | Artem Tikhomirov <tikhomirov.artem@gmail.com> |
|---|---|
| date | Fri, 21 Jun 2013 18:30:35 +0200 |
| parents | 14dac192aa26 |
| children | e79cf9a8130b |
comparison
equal
deleted
inserted
replaced
| 645:14dac192aa26 | 646:3b7d51ed4c65 |
|---|---|
| 60 import org.tmatesoft.hg.core.HgRemoteConnectionException; | 60 import org.tmatesoft.hg.core.HgRemoteConnectionException; |
| 61 import org.tmatesoft.hg.core.HgRepositoryNotFoundException; | 61 import org.tmatesoft.hg.core.HgRepositoryNotFoundException; |
| 62 import org.tmatesoft.hg.core.Nodeid; | 62 import org.tmatesoft.hg.core.Nodeid; |
| 63 import org.tmatesoft.hg.core.SessionContext; | 63 import org.tmatesoft.hg.core.SessionContext; |
| 64 import org.tmatesoft.hg.internal.DataSerializer; | 64 import org.tmatesoft.hg.internal.DataSerializer; |
| 65 import org.tmatesoft.hg.internal.EncodingHelper; | |
| 65 import org.tmatesoft.hg.internal.Internals; | 66 import org.tmatesoft.hg.internal.Internals; |
| 66 import org.tmatesoft.hg.internal.DataSerializer.OutputStreamSerializer; | 67 import org.tmatesoft.hg.internal.DataSerializer.OutputStreamSerializer; |
| 67 import org.tmatesoft.hg.internal.PropertyMarshal; | 68 import org.tmatesoft.hg.internal.PropertyMarshal; |
| 69 import org.tmatesoft.hg.util.Pair; | |
| 70 import org.tmatesoft.hg.util.LogFacility.Severity; | |
| 68 | 71 |
| 69 /** | 72 /** |
| 70 * WORK IN PROGRESS, DO NOT USE | 73 * WORK IN PROGRESS, DO NOT USE |
| 71 * | 74 * |
| 72 * @see http://mercurial.selenic.com/wiki/WireProtocol | 75 * @see http://mercurial.selenic.com/wiki/WireProtocol |
| 76 * @see http://mercurial.selenic.com/wiki/HttpCommandProtocol | |
| 73 * | 77 * |
| 74 * @author Artem Tikhomirov | 78 * @author Artem Tikhomirov |
| 75 * @author TMate Software Ltd. | 79 * @author TMate Software Ltd. |
| 76 */ | 80 */ |
| 77 public class HgRemoteRepository implements SessionContext.Source { | 81 public class HgRemoteRepository implements SessionContext.Source { |
| 160 authInfo = null; | 164 authInfo = null; |
| 161 } | 165 } |
| 162 } | 166 } |
| 163 | 167 |
| 164 public boolean isInvalid() throws HgRemoteConnectionException { | 168 public boolean isInvalid() throws HgRemoteConnectionException { |
| 165 if (remoteCapabilities == null) { | 169 initCapabilities(); |
| 166 remoteCapabilities = new HashSet<String>(); | |
| 167 // say hello to server, check response | |
| 168 try { | |
| 169 URL u = new URL(url, url.getPath() + "?cmd=hello"); | |
| 170 HttpURLConnection c = setupConnection(u.openConnection()); | |
| 171 c.connect(); | |
| 172 if (debug) { | |
| 173 dumpResponseHeader(u, c); | |
| 174 } | |
| 175 BufferedReader r = new BufferedReader(new InputStreamReader(c.getInputStream(), "US-ASCII")); | |
| 176 String line = r.readLine(); | |
| 177 c.disconnect(); | |
| 178 final String capsPrefix = "capabilities:"; | |
| 179 if (line == null || !line.startsWith(capsPrefix)) { | |
| 180 // for whatever reason, some servers do not respond to hello command (e.g. svnkit) | |
| 181 // but respond to 'capabilities' instead. Try it. | |
| 182 // TODO [post-1.0] tests needed | |
| 183 u = new URL(url, url.getPath() + "?cmd=capabilities"); | |
| 184 c = setupConnection(u.openConnection()); | |
| 185 c.connect(); | |
| 186 if (debug) { | |
| 187 dumpResponseHeader(u, c); | |
| 188 } | |
| 189 r = new BufferedReader(new InputStreamReader(c.getInputStream(), "US-ASCII")); | |
| 190 line = r.readLine(); | |
| 191 c.disconnect(); | |
| 192 if (line == null || line.trim().length() == 0) { | |
| 193 return true; | |
| 194 } | |
| 195 } else { | |
| 196 line = line.substring(capsPrefix.length()).trim(); | |
| 197 } | |
| 198 String[] caps = line.split("\\s"); | |
| 199 remoteCapabilities.addAll(Arrays.asList(caps)); | |
| 200 c.disconnect(); | |
| 201 } catch (MalformedURLException ex) { | |
| 202 throw new HgRemoteConnectionException("Bad URL", ex).setRemoteCommand("hello").setServerInfo(getLocation()); | |
| 203 } catch (IOException ex) { | |
| 204 throw new HgRemoteConnectionException("Communication failure", ex).setRemoteCommand("hello").setServerInfo(getLocation()); | |
| 205 } | |
| 206 } | |
| 207 return remoteCapabilities.isEmpty(); | 170 return remoteCapabilities.isEmpty(); |
| 208 } | 171 } |
| 209 | 172 |
| 210 /** | 173 /** |
| 211 * @return human-readable address of the server, without user credentials or any other security information | 174 * @return human-readable address of the server, without user credentials or any other security information |
| 444 c.disconnect(); | 407 c.disconnect(); |
| 445 } | 408 } |
| 446 } | 409 } |
| 447 } | 410 } |
| 448 | 411 |
| 449 public void unbundle(HgBundle bundle, List<Nodeid> heads) throws HgRemoteConnectionException, HgRuntimeException { | 412 public void unbundle(HgBundle bundle, List<Nodeid> remoteHeads) throws HgRemoteConnectionException, HgRuntimeException { |
| 450 if (heads == null) { | 413 if (remoteHeads == null) { |
| 451 // TODO collect heads from bundle: | 414 // 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)) | 415 // bundle.inspectChangelog(new HeadCollector(for each c : if collected has c.p1 or c.p2, remove them. Add c)) |
| 416 // or get from remote server??? | |
| 453 throw Internals.notImplemented(); | 417 throw Internals.notImplemented(); |
| 454 } | 418 } |
| 455 StringBuilder sb = appendNodeidListArgument("heads", heads, null); | 419 StringBuilder sb = appendNodeidListArgument("heads", remoteHeads, null); |
| 456 | 420 |
| 457 HttpURLConnection c = null; | 421 HttpURLConnection c = null; |
| 458 DataSerializer.DataSource bundleData = bundle.new BundleSerializer(); | 422 DataSerializer.DataSource bundleData = bundle.new BundleSerializer(); |
| 459 try { | 423 try { |
| 460 URL u = new URL(url, url.getPath() + "?cmd=unbundle&" + sb.toString()); | 424 URL u = new URL(url, url.getPath() + "?cmd=unbundle&" + sb.toString()); |
| 470 os.close(); | 434 os.close(); |
| 471 if (debug) { | 435 if (debug) { |
| 472 dumpResponseHeader(u, c); | 436 dumpResponseHeader(u, c); |
| 473 dumpResponse(c); | 437 dumpResponse(c); |
| 474 } | 438 } |
| 475 if (c.getResponseCode() != 200) { | 439 checkResponseOk(c, "Push", "unbundle"); |
| 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) { | 440 } catch (MalformedURLException ex) { |
| 481 throw new HgRemoteConnectionException("Bad URL", ex).setRemoteCommand("unbundle").setServerInfo(getLocation()); | 441 throw new HgRemoteConnectionException("Bad URL", ex).setRemoteCommand("unbundle").setServerInfo(getLocation()); |
| 482 } catch (IOException ex) { | 442 } catch (IOException ex) { |
| 483 throw new HgRemoteConnectionException("Communication failure", ex).setRemoteCommand("unbundle").setServerInfo(getLocation()); | 443 throw new HgRemoteConnectionException("Communication failure", ex).setRemoteCommand("unbundle").setServerInfo(getLocation()); |
| 484 } catch (HgIOException ex) { | 444 } catch (HgIOException ex) { |
| 488 c.disconnect(); | 448 c.disconnect(); |
| 489 } | 449 } |
| 490 } | 450 } |
| 491 } | 451 } |
| 492 | 452 |
| 453 public List<Pair<String,Nodeid>> bookmarks() throws HgRemoteConnectionException, HgRuntimeException { | |
| 454 final String actionName = "Get remote bookmarks"; | |
| 455 final List<Pair<String, String>> values = listkeys("bookmarks", actionName); | |
| 456 ArrayList<Pair<String, Nodeid>> rv = new ArrayList<Pair<String, Nodeid>>(); | |
| 457 for (Pair<String, String> l : values) { | |
| 458 if (l.second().length() != Nodeid.SIZE_ASCII) { | |
| 459 sessionContext.getLog().dump(getClass(), Severity.Warn, "%s: bad nodeid '%s', ignored", actionName, l.second()); | |
| 460 continue; | |
| 461 } | |
| 462 Nodeid n = Nodeid.fromAscii(l.second()); | |
| 463 String bm = new String(l.first()); | |
| 464 rv.add(new Pair<String, Nodeid>(bm, n)); | |
| 465 } | |
| 466 return rv; | |
| 467 } | |
| 468 | |
| 469 public void updateBookmark(String name, Nodeid oldRev, Nodeid newRev) throws HgRemoteConnectionException, HgRuntimeException { | |
| 470 final String namespace = "bookmarks"; | |
| 471 HttpURLConnection c = null; | |
| 472 try { | |
| 473 URL u = new URL(url, String.format("%s?cmd=pushkey&namespace=%s&key=%s&old=%s&new=%s",url.getPath(), namespace, name, oldRev.toString(), newRev.toString())); | |
| 474 c = setupConnection(u.openConnection()); | |
| 475 c.connect(); | |
| 476 if (debug) { | |
| 477 dumpResponseHeader(u, c); | |
| 478 } | |
| 479 checkResponseOk(c, "Update remote bookmark", "pushkey"); | |
| 480 } catch (MalformedURLException ex) { | |
| 481 throw new HgRemoteConnectionException("Bad URL", ex).setRemoteCommand("pushkey").setServerInfo(getLocation()); | |
| 482 } catch (IOException ex) { | |
| 483 throw new HgRemoteConnectionException("Communication failure", ex).setRemoteCommand("pushkey").setServerInfo(getLocation()); | |
| 484 } finally { | |
| 485 if (c != null) { | |
| 486 c.disconnect(); | |
| 487 } | |
| 488 } | |
| 489 } | |
| 490 | |
| 491 private void phases() throws HgRemoteConnectionException, HgRuntimeException { | |
| 492 final List<Pair<String, String>> values = listkeys("phases", "Get remote phases"); | |
| 493 for (Pair<String, String> l : values) { | |
| 494 System.out.printf("%s : %s\n", l.first(), l.second()); | |
| 495 } | |
| 496 } | |
| 497 | |
| 498 public static void main(String[] args) throws Exception { | |
| 499 final HgRemoteRepository r = new HgLookup().detectRemote("http://selenic.com/hg", null); | |
| 500 if (r.isInvalid()) { | |
| 501 return; | |
| 502 } | |
| 503 System.out.println(r.remoteCapabilities); | |
| 504 r.phases(); | |
| 505 final List<Pair<String, Nodeid>> bm = r.bookmarks(); | |
| 506 for (Pair<String, Nodeid> pair : bm) { | |
| 507 System.out.println(pair); | |
| 508 } | |
| 509 } | |
| 510 | |
| 493 @Override | 511 @Override |
| 494 public String toString() { | 512 public String toString() { |
| 495 return getClass().getSimpleName() + '[' + getLocation() + ']'; | 513 return getClass().getSimpleName() + '[' + getLocation() + ']'; |
| 496 } | 514 } |
| 515 | |
| 516 | |
| 517 private void initCapabilities() throws HgRemoteConnectionException { | |
| 518 if (remoteCapabilities == null) { | |
| 519 remoteCapabilities = new HashSet<String>(); | |
| 520 // say hello to server, check response | |
| 521 try { | |
| 522 URL u = new URL(url, url.getPath() + "?cmd=hello"); | |
| 523 HttpURLConnection c = setupConnection(u.openConnection()); | |
| 524 c.connect(); | |
| 525 if (debug) { | |
| 526 dumpResponseHeader(u, c); | |
| 527 } | |
| 528 BufferedReader r = new BufferedReader(new InputStreamReader(c.getInputStream(), "US-ASCII")); | |
| 529 String line = r.readLine(); | |
| 530 c.disconnect(); | |
| 531 final String capsPrefix = "capabilities:"; | |
| 532 if (line == null || !line.startsWith(capsPrefix)) { | |
| 533 // for whatever reason, some servers do not respond to hello command (e.g. svnkit) | |
| 534 // but respond to 'capabilities' instead. Try it. | |
| 535 // TODO [post-1.0] tests needed | |
| 536 u = new URL(url, url.getPath() + "?cmd=capabilities"); | |
| 537 c = setupConnection(u.openConnection()); | |
| 538 c.connect(); | |
| 539 if (debug) { | |
| 540 dumpResponseHeader(u, c); | |
| 541 } | |
| 542 r = new BufferedReader(new InputStreamReader(c.getInputStream(), "US-ASCII")); | |
| 543 line = r.readLine(); | |
| 544 c.disconnect(); | |
| 545 if (line == null || line.trim().length() == 0) { | |
| 546 return; | |
| 547 } | |
| 548 } else { | |
| 549 line = line.substring(capsPrefix.length()).trim(); | |
| 550 } | |
| 551 String[] caps = line.split("\\s"); | |
| 552 remoteCapabilities.addAll(Arrays.asList(caps)); | |
| 553 c.disconnect(); | |
| 554 } catch (MalformedURLException ex) { | |
| 555 throw new HgRemoteConnectionException("Bad URL", ex).setRemoteCommand("hello").setServerInfo(getLocation()); | |
| 556 } catch (IOException ex) { | |
| 557 throw new HgRemoteConnectionException("Communication failure", ex).setRemoteCommand("hello").setServerInfo(getLocation()); | |
| 558 } | |
| 559 } | |
| 560 } | |
| 497 | 561 |
| 498 private HgLookup getLookupHelper() { | 562 private HgLookup getLookupHelper() { |
| 499 if (lookupHelper == null) { | 563 if (lookupHelper == null) { |
| 500 lookupHelper = new HgLookup(sessionContext); | 564 lookupHelper = new HgLookup(sessionContext); |
| 501 } | 565 } |
| 502 return lookupHelper; | 566 return lookupHelper; |
| 503 } | 567 } |
| 504 | 568 |
| 569 private List<Pair<String,String>> listkeys(String namespace, String actionName) throws HgRemoteConnectionException, HgRuntimeException { | |
| 570 HttpURLConnection c = null; | |
| 571 try { | |
| 572 URL u = new URL(url, url.getPath() + "?cmd=listkeys&namespace=" + namespace); | |
| 573 c = setupConnection(u.openConnection()); | |
| 574 c.connect(); | |
| 575 if (debug) { | |
| 576 dumpResponseHeader(u, c); | |
| 577 } | |
| 578 checkResponseOk(c, actionName, "listkeys"); | |
| 579 ArrayList<Pair<String, String>> rv = new ArrayList<Pair<String, String>>(); | |
| 580 BufferedReader r = new BufferedReader(new InputStreamReader(c.getInputStream(), EncodingHelper.getUTF8())); | |
| 581 String l; | |
| 582 while ((l = r.readLine()) != null) { | |
| 583 int sep = l.indexOf('\t'); | |
| 584 if (sep == -1) { | |
| 585 sessionContext.getLog().dump(getClass(), Severity.Warn, "%s: bad line '%s', ignored", actionName, l); | |
| 586 continue; | |
| 587 } | |
| 588 rv.add(new Pair<String,String>(l.substring(0, sep), l.substring(sep+1))); | |
| 589 } | |
| 590 r.close(); | |
| 591 return rv; | |
| 592 } catch (MalformedURLException ex) { | |
| 593 throw new HgRemoteConnectionException("Bad URL", ex).setRemoteCommand("listkeys").setServerInfo(getLocation()); | |
| 594 } catch (IOException ex) { | |
| 595 throw new HgRemoteConnectionException("Communication failure", ex).setRemoteCommand("listkeys").setServerInfo(getLocation()); | |
| 596 } finally { | |
| 597 if (c != null) { | |
| 598 c.disconnect(); | |
| 599 } | |
| 600 } | |
| 601 } | |
| 602 | |
| 603 private void checkResponseOk(HttpURLConnection c, String opName, String remoteCmd) throws HgRemoteConnectionException, IOException { | |
| 604 if (c.getResponseCode() != 200) { | |
| 605 String m = c.getResponseMessage() == null ? "unknown reason" : c.getResponseMessage(); | |
| 606 String em = String.format("%s failed: %s (HTTP error:%d)", opName, m, c.getResponseCode()); | |
| 607 throw new HgRemoteConnectionException(em).setRemoteCommand(remoteCmd).setServerInfo(getLocation()); | |
| 608 } | |
| 609 } | |
| 610 | |
| 505 private HttpURLConnection setupConnection(URLConnection urlConnection) { | 611 private HttpURLConnection setupConnection(URLConnection urlConnection) { |
| 506 urlConnection.setRequestProperty("User-Agent", "hg4j/1.0.0"); | 612 urlConnection.setRequestProperty("User-Agent", "hg4j/1.0.0"); |
| 507 urlConnection.addRequestProperty("Accept", "application/mercurial-0.1"); | 613 urlConnection.addRequestProperty("Accept", "application/mercurial-0.1"); |
| 508 if (authInfo != null) { | 614 if (authInfo != null) { |
| 509 urlConnection.addRequestProperty("Authorization", "Basic " + authInfo); | 615 urlConnection.addRequestProperty("Authorization", "Basic " + authInfo); |
| 547 } | 653 } |
| 548 } | 654 } |
| 549 | 655 |
| 550 private static File writeBundle(InputStream is, boolean decompress, String header) throws IOException { | 656 private static File writeBundle(InputStream is, boolean decompress, String header) throws IOException { |
| 551 InputStream zipStream = decompress ? new InflaterInputStream(is) : is; | 657 InputStream zipStream = decompress ? new InflaterInputStream(is) : is; |
| 552 File tf = File.createTempFile("hg-bundle-", null); | 658 File tf = File.createTempFile("hg4j-bundle-", null); |
| 553 FileOutputStream fos = new FileOutputStream(tf); | 659 FileOutputStream fos = new FileOutputStream(tf); |
| 554 fos.write(header.getBytes()); | 660 fos.write(header.getBytes()); |
| 555 int r; | 661 int r; |
| 556 byte[] buf = new byte[8*1024]; | 662 byte[] buf = new byte[8*1024]; |
| 557 while ((r = zipStream.read(buf)) != -1) { | 663 while ((r = zipStream.read(buf)) != -1) { |
