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) {