diff src/org/tmatesoft/hg/core/HgPushCommand.java @ 650:3b275cc2d2aa

Push: phase4 - settle local and remote phases, push updated phases regardless of server publishing state, do not push secret changesets
author Artem Tikhomirov <tikhomirov.artem@gmail.com>
date Fri, 28 Jun 2013 19:27:26 +0200
parents e79cf9a8130b
children cd77bf51b562
line wrap: on
line diff
--- 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<HgChangelog> parentHelper = new HgParentChildMap<HgChangelog>(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<Nodeid> 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<Nodeid> knownRemoteDraftRoots = new ArrayList<Nodeid>();
-					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<Nodeid> 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<HgChangelog> parentHelper, RevisionSet outgoing) {
+		ArrayList<Nodeid> knownRemoteDraftRoots = new ArrayList<Nodeid>();
+		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