# HG changeset patch # User Artem Tikhomirov # Date 1372440446 -7200 # Node ID 3b275cc2d2aacb24bcc02d4f57767a66488ccb24 # Parent e79cf9a8130b989a51e363bfb9a4507676d284b1 Push: phase4 - settle local and remote phases, push updated phases regardless of server publishing state, do not push secret changesets diff -r e79cf9a8130b -r 3b275cc2d2aa src/org/tmatesoft/hg/core/HgPushCommand.java --- a/src/org/tmatesoft/hg/core/HgPushCommand.java Wed Jun 26 20:52:38 2013 +0200 +++ b/src/org/tmatesoft/hg/core/HgPushCommand.java Fri Jun 28 19:27:26 2013 +0200 @@ -39,6 +39,7 @@ import org.tmatesoft.hg.repo.HgRepository; import org.tmatesoft.hg.repo.HgRuntimeException; import org.tmatesoft.hg.util.CancelledException; +import org.tmatesoft.hg.util.Outcome; import org.tmatesoft.hg.util.Pair; import org.tmatesoft.hg.util.ProgressSupport; import org.tmatesoft.hg.util.LogFacility.Severity; @@ -72,14 +73,22 @@ final HgChangelog clog = repo.getChangelog(); final HgParentChildMap parentHelper = new HgParentChildMap(clog); parentHelper.init(); + final Internals implRepo = HgInternals.getImplementationRepo(repo); + final PhasesHelper phaseHelper = new PhasesHelper(implRepo, parentHelper); final RepositoryComparator comparator = new RepositoryComparator(parentHelper, remoteRepo); comparator.compare(new ProgressSupport.Sub(progress, 50), getCancelSupport(null, true)); List l = comparator.getLocalOnlyRevisions(); + final RevisionSet outgoing; + if (phaseHelper.isCapableOfPhases() && phaseHelper.withSecretRoots()) { + RevisionSet secret = phaseHelper.allSecret(); + outgoing = new RevisionSet(l).subtract(secret); + } else { + outgoing = new RevisionSet(l); + } // // prepare bundle - final Internals implRepo = HgInternals.getImplementationRepo(repo); BundleGenerator bg = new BundleGenerator(implRepo); - File bundleFile = bg.create(l); + File bundleFile = bg.create(outgoing.asList()); progress.worked(20); HgBundle b = new HgLookup(repo.getSessionContext()).loadBundle(bundleFile); // @@ -88,13 +97,12 @@ progress.worked(20); // // update phase information - PhasesHelper phaseHelper = new PhasesHelper(implRepo, parentHelper); if (phaseHelper.isCapableOfPhases()) { - RevisionSet outgoing = new RevisionSet(l); RevisionSet presentSecret = phaseHelper.allSecret(); RevisionSet presentDraft = phaseHelper.allDraft(); RevisionSet secretLeft, draftLeft; HgRemoteRepository.Phases remotePhases = remoteRepo.getPhases(); + RevisionSet remoteDrafts = knownRemoteDrafts(remotePhases, parentHelper, outgoing); if (remotePhases.isPublishingServer()) { // although it's unlikely outgoing would affect secret changesets, // it doesn't hurt to check secret roots along with draft ones @@ -102,43 +110,55 @@ draftLeft = presentDraft.subtract(outgoing); } else { // shall merge local and remote phase states - ArrayList knownRemoteDraftRoots = new ArrayList(); - for (Nodeid rdr : remotePhases.draftRoots()) { - if (clog.isKnown(rdr)) { - knownRemoteDraftRoots.add(rdr); - } - } - // childrenOf(knownRemoteDraftRoots) is everything remote may treat as Draft - RevisionSet remoteDrafts = new RevisionSet(parentHelper.childrenOf(knownRemoteDraftRoots)); - List localChildrenNotSent = parentHelper.childrenOf(outgoing.heads(parentHelper).asList()); - // remote shall know only what we've sent, subtract revisions we didn't actually sent - remoteDrafts = remoteDrafts.subtract(new RevisionSet(localChildrenNotSent)); - // if there's a remote draft root that points to revision we know is public - RevisionSet remoteDraftsLocallyPublic = remoteDrafts.subtract(presentSecret).subtract(presentDraft); - if (!remoteDraftsLocallyPublic.isEmpty()) { - // foreach remoteDraftsLocallyPublic.heads() do push Draft->Public - for (Nodeid n : remoteDraftsLocallyPublic.heads(parentHelper)) { - try { - remoteRepo.updatePhase(HgPhase.Draft, HgPhase.Public, n); - } catch (HgRemoteConnectionException ex) { - implRepo.getLog().dump(getClass(), Severity.Error, ex, String.format("Failed to update phase of %s", n.shortNotation())); - } - } - remoteDrafts = remoteDrafts.subtract(remoteDraftsLocallyPublic); - } // revisions that cease to be secret (gonna become Public), e.g. someone else pushed them RevisionSet secretGone = presentSecret.intersect(remoteDrafts); - // trace parents of these published secret revisions - RevisionSet secretMadePublic = presentSecret.parentsOf(secretGone, parentHelper); - secretLeft = presentSecret.subtract(secretGone).subtract(secretMadePublic); - // same for drafts - RevisionSet draftGone = presentDraft.intersect(remoteDrafts); - RevisionSet draftMadePublic = presentDraft.parentsOf(draftGone, parentHelper); - draftLeft = presentDraft.subtract(draftGone).subtract(draftMadePublic); + // parents of those remote drafts are public, mark them as public locally, too + RevisionSet remotePublic = presentSecret.ancestors(secretGone, parentHelper); + secretLeft = presentSecret.subtract(secretGone).subtract(remotePublic); + /* + * Revisions grow from left to right (parents to the left, children to the right) + * + * I: Set of local is subset of remote + * + * local draft + * --o---r---o---l---o-- + * remote draft + * + * Remote draft roots shall be updated + * + * + * II: Set of local is superset of remote + * + * local draft + * --o---l---o---r---o-- + * remote draft + * + * Local draft roots shall be updated + */ + RevisionSet sharedDraft = presentDraft.intersect(remoteDrafts); // (I: ~presentDraft; II: ~remoteDraft + RevisionSet localDraftRemotePublic = presentDraft.ancestors(sharedDraft, parentHelper); // I: 0; II: those treated public on remote + // forget those deemed public by remote (drafts shared by both remote and local are ok to stay) + draftLeft = presentDraft.subtract(localDraftRemotePublic); } final RevisionSet newDraftRoots = draftLeft.roots(parentHelper); final RevisionSet newSecretRoots = secretLeft.roots(parentHelper); phaseHelper.updateRoots(newDraftRoots.asList(), newSecretRoots.asList()); + // + // if there's a remote draft root that points to revision we know is public + RevisionSet remoteDraftsLocalPublic = remoteDrafts.subtract(draftLeft).subtract(secretLeft); + if (!remoteDraftsLocalPublic.isEmpty()) { + // foreach remoteDraftsLocallyPublic.heads() do push Draft->Public + for (Nodeid n : remoteDraftsLocalPublic.heads(parentHelper)) { + try { + Outcome upo = remoteRepo.updatePhase(HgPhase.Draft, HgPhase.Public, n); + if (!upo.isOk()) { + implRepo.getLog().dump(getClass(), Severity.Info, "Failed to update remote phase, reason: %s", upo.getMessage()); + } + } catch (HgRemoteConnectionException ex) { + implRepo.getLog().dump(getClass(), Severity.Error, ex, String.format("Failed to update phase of %s", n.shortNotation())); + } + } + } } progress.worked(5); // @@ -172,6 +192,25 @@ } } + private RevisionSet knownRemoteDrafts(HgRemoteRepository.Phases remotePhases, HgParentChildMap parentHelper, RevisionSet outgoing) { + ArrayList knownRemoteDraftRoots = new ArrayList(); + for (Nodeid rdr : remotePhases.draftRoots()) { + if (parentHelper.knownNode(rdr)) { + knownRemoteDraftRoots.add(rdr); + } + } + // knownRemoteDraftRoots + childrenOf(knownRemoteDraftRoots) is everything remote may treat as Draft + RevisionSet remoteDrafts = new RevisionSet(knownRemoteDraftRoots); + remoteDrafts = remoteDrafts.union(remoteDrafts.children(parentHelper)); + // 1) outgoing.children gives all local revisions accessible from outgoing. + // 2) outgoing.roots.children is equivalent with smaller intermediate set, the way we build + // childrenOf doesn't really benefits from that. + RevisionSet localChildrenNotSent = outgoing.children(parentHelper).subtract(outgoing); + // remote shall know only what we've sent, subtract revisions we didn't actually sent + remoteDrafts = remoteDrafts.subtract(localChildrenNotSent); + return remoteDrafts; + } + /* * To test, start a server: * $ hg --config web.allow_push=* --config web.push_ssl=False --config server.validate=True --debug serve diff -r e79cf9a8130b -r 3b275cc2d2aa src/org/tmatesoft/hg/internal/PhasesHelper.java --- a/src/org/tmatesoft/hg/internal/PhasesHelper.java Wed Jun 26 20:52:38 2013 +0200 +++ b/src/org/tmatesoft/hg/internal/PhasesHelper.java Fri Jun 28 19:27:26 2013 +0200 @@ -78,7 +78,10 @@ } return repoSupporsPhases.booleanValue(); } - + + public boolean withSecretRoots() { + return !secretPhaseRoots.isEmpty(); + } /** * @param cset revision to query diff -r e79cf9a8130b -r 3b275cc2d2aa src/org/tmatesoft/hg/internal/RevisionSet.java --- a/src/org/tmatesoft/hg/internal/RevisionSet.java Wed Jun 26 20:52:38 2013 +0200 +++ b/src/org/tmatesoft/hg/internal/RevisionSet.java Fri Jun 28 19:27:26 2013 +0200 @@ -92,9 +92,9 @@ } /** - * Immediate parents of the supplied children set found in this one. + * Any ancestor of an element from the supplied children set found in this one. */ - public RevisionSet parentsOf(RevisionSet children, HgParentChildMap parentHelper) { + public RevisionSet ancestors(RevisionSet children, HgParentChildMap parentHelper) { if (isEmpty()) { return this; } @@ -102,18 +102,36 @@ return children; } RevisionSet chRoots = children.roots(parentHelper); - HashSet parents = new HashSet(); - for (Nodeid n : chRoots.elements) { - Nodeid p1 = parentHelper.firstParent(n); - Nodeid p2 = parentHelper.secondParent(n); - if (p1 != null && elements.contains(p1)) { - parents.add(p1); + HashSet ancestors = new HashSet(); + Set childrenToCheck = chRoots.elements; + while (!childrenToCheck.isEmpty()) { + HashSet nextRound = new HashSet(); + for (Nodeid n : childrenToCheck) { + Nodeid p1 = parentHelper.firstParent(n); + Nodeid p2 = parentHelper.secondParent(n); + if (p1 != null && elements.contains(p1)) { + nextRound.add(p1); + } + if (p2 != null && elements.contains(p2)) { + nextRound.add(p2); + } } - if (p2 != null && elements.contains(p2)) { - parents.add(p2); - } + ancestors.addAll(nextRound); + childrenToCheck = nextRound; + } + return new RevisionSet(ancestors); + } + + /** + * Revisions that are both direct and indirect children of elements of this revision set + * as known in supplied parent-child map + */ + public RevisionSet children(HgParentChildMap parentHelper) { + if (isEmpty()) { + return this; } - return new RevisionSet(parents); + List children = parentHelper.childrenOf(elements); + return new RevisionSet(new HashSet(children)); } public RevisionSet intersect(RevisionSet other) { diff -r e79cf9a8130b -r 3b275cc2d2aa src/org/tmatesoft/hg/repo/HgParentChildMap.java --- a/src/org/tmatesoft/hg/repo/HgParentChildMap.java Wed Jun 26 20:52:38 2013 +0200 +++ b/src/org/tmatesoft/hg/repo/HgParentChildMap.java Fri Jun 28 19:27:26 2013 +0200 @@ -180,7 +180,7 @@ // @return ordered collection of all children rooted at supplied nodes. Nodes shall not be descendants of each other! // Nodeids shall belong to this revlog - public List childrenOf(List roots) { + public List childrenOf(Collection roots) { if (roots.isEmpty()) { return Collections.emptyList(); } diff -r e79cf9a8130b -r 3b275cc2d2aa src/org/tmatesoft/hg/repo/HgRemoteRepository.java --- a/src/org/tmatesoft/hg/repo/HgRemoteRepository.java Wed Jun 26 20:52:38 2013 +0200 +++ b/src/org/tmatesoft/hg/repo/HgRemoteRepository.java Fri Jun 28 19:27:26 2013 +0200 @@ -17,6 +17,8 @@ package org.tmatesoft.hg.repo; import static org.tmatesoft.hg.util.LogFacility.Severity.Info; +import static org.tmatesoft.hg.util.Outcome.Kind.Failure; +import static org.tmatesoft.hg.util.Outcome.Kind.Success; import java.io.BufferedReader; import java.io.ByteArrayOutputStream; @@ -62,14 +64,13 @@ import org.tmatesoft.hg.core.Nodeid; import org.tmatesoft.hg.core.SessionContext; import org.tmatesoft.hg.internal.DataSerializer; +import org.tmatesoft.hg.internal.DataSerializer.OutputStreamSerializer; import org.tmatesoft.hg.internal.EncodingHelper; import org.tmatesoft.hg.internal.Internals; -import org.tmatesoft.hg.internal.DataSerializer.OutputStreamSerializer; import org.tmatesoft.hg.internal.PropertyMarshal; +import org.tmatesoft.hg.util.LogFacility.Severity; import org.tmatesoft.hg.util.Outcome; import org.tmatesoft.hg.util.Pair; -import org.tmatesoft.hg.util.LogFacility.Severity; -import org.tmatesoft.hg.util.Outcome.Kind; /** * WORK IN PROGRESS, DO NOT USE @@ -497,7 +498,7 @@ return new Phases(true, Collections.emptyList()); } final List> values = listkeys("phases", "Get remote phases"); - boolean publishing = true; + boolean publishing = false; ArrayList draftRoots = new ArrayList(); for (Pair l : values) { if ("publishing".equalsIgnoreCase(l.first())) { @@ -517,10 +518,14 @@ } public Outcome updatePhase(HgPhase from, HgPhase to, Nodeid n) throws HgRemoteConnectionException, HgRuntimeException { + initCapabilities(); + if (!remoteCapabilities.contains("pushkey")) { + return new Outcome(Failure, "Server doesn't support pushkey protocol"); + } if (pushkey("phases", n.toString(), String.valueOf(from.mercurialOrdinal()), String.valueOf(to.mercurialOrdinal()))) { - return new Outcome(Kind.Success, String.format("Phase of %s updated to %s", n.shortNotation(), to.name())); + return new Outcome(Success, String.format("Phase of %s updated to %s", n.shortNotation(), to.name())); } - return new Outcome(Kind.Failure, String.format("Phase update (%s: %s -> %s) failed", n.shortNotation(), from.name(), to.name())); + return new Outcome(Failure, String.format("Phase update (%s: %s -> %s) failed", n.shortNotation(), from.name(), to.name())); } @@ -632,9 +637,10 @@ private boolean pushkey(String namespace, String key, String oldValue, String newValue) throws HgRemoteConnectionException, HgRuntimeException { HttpURLConnection c = null; try { - final String p = String.format("%s?cmd=pushkey&namespace=%s&key=%s&old=%s&new=&s", url.getPath(), namespace, key, oldValue, newValue); + final String p = String.format("%s?cmd=pushkey&namespace=%s&key=%s&old=%s&new=%s", url.getPath(), namespace, key, oldValue, newValue); URL u = new URL(url, p); c = setupConnection(u.openConnection()); + c.setRequestMethod("POST"); c.connect(); if (debug) { dumpResponseHeader(u, c); @@ -642,9 +648,6 @@ checkResponseOk(c, key, "pushkey"); final InputStream is = c.getInputStream(); int rv = is.read(); - if (is.read() != -1) { - sessionContext.getLog().dump(getClass(), Severity.Error, "Unexpected data in response to pushkey"); - } is.close(); return rv == '1'; } catch (MalformedURLException ex) {