comparison 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
comparison
equal deleted inserted replaced
649:e79cf9a8130b 650:3b275cc2d2aa
37 import org.tmatesoft.hg.repo.HgPhase; 37 import org.tmatesoft.hg.repo.HgPhase;
38 import org.tmatesoft.hg.repo.HgRemoteRepository; 38 import org.tmatesoft.hg.repo.HgRemoteRepository;
39 import org.tmatesoft.hg.repo.HgRepository; 39 import org.tmatesoft.hg.repo.HgRepository;
40 import org.tmatesoft.hg.repo.HgRuntimeException; 40 import org.tmatesoft.hg.repo.HgRuntimeException;
41 import org.tmatesoft.hg.util.CancelledException; 41 import org.tmatesoft.hg.util.CancelledException;
42 import org.tmatesoft.hg.util.Outcome;
42 import org.tmatesoft.hg.util.Pair; 43 import org.tmatesoft.hg.util.Pair;
43 import org.tmatesoft.hg.util.ProgressSupport; 44 import org.tmatesoft.hg.util.ProgressSupport;
44 import org.tmatesoft.hg.util.LogFacility.Severity; 45 import org.tmatesoft.hg.util.LogFacility.Severity;
45 46
46 /** 47 /**
70 // find out missing 71 // find out missing
71 // TODO refactor same code in HgOutgoingCommand #getComparator and #getParentHelper 72 // TODO refactor same code in HgOutgoingCommand #getComparator and #getParentHelper
72 final HgChangelog clog = repo.getChangelog(); 73 final HgChangelog clog = repo.getChangelog();
73 final HgParentChildMap<HgChangelog> parentHelper = new HgParentChildMap<HgChangelog>(clog); 74 final HgParentChildMap<HgChangelog> parentHelper = new HgParentChildMap<HgChangelog>(clog);
74 parentHelper.init(); 75 parentHelper.init();
76 final Internals implRepo = HgInternals.getImplementationRepo(repo);
77 final PhasesHelper phaseHelper = new PhasesHelper(implRepo, parentHelper);
75 final RepositoryComparator comparator = new RepositoryComparator(parentHelper, remoteRepo); 78 final RepositoryComparator comparator = new RepositoryComparator(parentHelper, remoteRepo);
76 comparator.compare(new ProgressSupport.Sub(progress, 50), getCancelSupport(null, true)); 79 comparator.compare(new ProgressSupport.Sub(progress, 50), getCancelSupport(null, true));
77 List<Nodeid> l = comparator.getLocalOnlyRevisions(); 80 List<Nodeid> l = comparator.getLocalOnlyRevisions();
81 final RevisionSet outgoing;
82 if (phaseHelper.isCapableOfPhases() && phaseHelper.withSecretRoots()) {
83 RevisionSet secret = phaseHelper.allSecret();
84 outgoing = new RevisionSet(l).subtract(secret);
85 } else {
86 outgoing = new RevisionSet(l);
87 }
78 // 88 //
79 // prepare bundle 89 // prepare bundle
80 final Internals implRepo = HgInternals.getImplementationRepo(repo);
81 BundleGenerator bg = new BundleGenerator(implRepo); 90 BundleGenerator bg = new BundleGenerator(implRepo);
82 File bundleFile = bg.create(l); 91 File bundleFile = bg.create(outgoing.asList());
83 progress.worked(20); 92 progress.worked(20);
84 HgBundle b = new HgLookup(repo.getSessionContext()).loadBundle(bundleFile); 93 HgBundle b = new HgLookup(repo.getSessionContext()).loadBundle(bundleFile);
85 // 94 //
86 // send changes 95 // send changes
87 remoteRepo.unbundle(b, comparator.getRemoteHeads()); 96 remoteRepo.unbundle(b, comparator.getRemoteHeads());
88 progress.worked(20); 97 progress.worked(20);
89 // 98 //
90 // update phase information 99 // update phase information
91 PhasesHelper phaseHelper = new PhasesHelper(implRepo, parentHelper);
92 if (phaseHelper.isCapableOfPhases()) { 100 if (phaseHelper.isCapableOfPhases()) {
93 RevisionSet outgoing = new RevisionSet(l);
94 RevisionSet presentSecret = phaseHelper.allSecret(); 101 RevisionSet presentSecret = phaseHelper.allSecret();
95 RevisionSet presentDraft = phaseHelper.allDraft(); 102 RevisionSet presentDraft = phaseHelper.allDraft();
96 RevisionSet secretLeft, draftLeft; 103 RevisionSet secretLeft, draftLeft;
97 HgRemoteRepository.Phases remotePhases = remoteRepo.getPhases(); 104 HgRemoteRepository.Phases remotePhases = remoteRepo.getPhases();
105 RevisionSet remoteDrafts = knownRemoteDrafts(remotePhases, parentHelper, outgoing);
98 if (remotePhases.isPublishingServer()) { 106 if (remotePhases.isPublishingServer()) {
99 // although it's unlikely outgoing would affect secret changesets, 107 // although it's unlikely outgoing would affect secret changesets,
100 // it doesn't hurt to check secret roots along with draft ones 108 // it doesn't hurt to check secret roots along with draft ones
101 secretLeft = presentSecret.subtract(outgoing); 109 secretLeft = presentSecret.subtract(outgoing);
102 draftLeft = presentDraft.subtract(outgoing); 110 draftLeft = presentDraft.subtract(outgoing);
103 } else { 111 } else {
104 // shall merge local and remote phase states 112 // shall merge local and remote phase states
105 ArrayList<Nodeid> knownRemoteDraftRoots = new ArrayList<Nodeid>();
106 for (Nodeid rdr : remotePhases.draftRoots()) {
107 if (clog.isKnown(rdr)) {
108 knownRemoteDraftRoots.add(rdr);
109 }
110 }
111 // childrenOf(knownRemoteDraftRoots) is everything remote may treat as Draft
112 RevisionSet remoteDrafts = new RevisionSet(parentHelper.childrenOf(knownRemoteDraftRoots));
113 List<Nodeid> localChildrenNotSent = parentHelper.childrenOf(outgoing.heads(parentHelper).asList());
114 // remote shall know only what we've sent, subtract revisions we didn't actually sent
115 remoteDrafts = remoteDrafts.subtract(new RevisionSet(localChildrenNotSent));
116 // if there's a remote draft root that points to revision we know is public
117 RevisionSet remoteDraftsLocallyPublic = remoteDrafts.subtract(presentSecret).subtract(presentDraft);
118 if (!remoteDraftsLocallyPublic.isEmpty()) {
119 // foreach remoteDraftsLocallyPublic.heads() do push Draft->Public
120 for (Nodeid n : remoteDraftsLocallyPublic.heads(parentHelper)) {
121 try {
122 remoteRepo.updatePhase(HgPhase.Draft, HgPhase.Public, n);
123 } catch (HgRemoteConnectionException ex) {
124 implRepo.getLog().dump(getClass(), Severity.Error, ex, String.format("Failed to update phase of %s", n.shortNotation()));
125 }
126 }
127 remoteDrafts = remoteDrafts.subtract(remoteDraftsLocallyPublic);
128 }
129 // revisions that cease to be secret (gonna become Public), e.g. someone else pushed them 113 // revisions that cease to be secret (gonna become Public), e.g. someone else pushed them
130 RevisionSet secretGone = presentSecret.intersect(remoteDrafts); 114 RevisionSet secretGone = presentSecret.intersect(remoteDrafts);
131 // trace parents of these published secret revisions 115 // parents of those remote drafts are public, mark them as public locally, too
132 RevisionSet secretMadePublic = presentSecret.parentsOf(secretGone, parentHelper); 116 RevisionSet remotePublic = presentSecret.ancestors(secretGone, parentHelper);
133 secretLeft = presentSecret.subtract(secretGone).subtract(secretMadePublic); 117 secretLeft = presentSecret.subtract(secretGone).subtract(remotePublic);
134 // same for drafts 118 /*
135 RevisionSet draftGone = presentDraft.intersect(remoteDrafts); 119 * Revisions grow from left to right (parents to the left, children to the right)
136 RevisionSet draftMadePublic = presentDraft.parentsOf(draftGone, parentHelper); 120 *
137 draftLeft = presentDraft.subtract(draftGone).subtract(draftMadePublic); 121 * I: Set of local is subset of remote
122 *
123 * local draft
124 * --o---r---o---l---o--
125 * remote draft
126 *
127 * Remote draft roots shall be updated
128 *
129 *
130 * II: Set of local is superset of remote
131 *
132 * local draft
133 * --o---l---o---r---o--
134 * remote draft
135 *
136 * Local draft roots shall be updated
137 */
138 RevisionSet sharedDraft = presentDraft.intersect(remoteDrafts); // (I: ~presentDraft; II: ~remoteDraft
139 RevisionSet localDraftRemotePublic = presentDraft.ancestors(sharedDraft, parentHelper); // I: 0; II: those treated public on remote
140 // forget those deemed public by remote (drafts shared by both remote and local are ok to stay)
141 draftLeft = presentDraft.subtract(localDraftRemotePublic);
138 } 142 }
139 final RevisionSet newDraftRoots = draftLeft.roots(parentHelper); 143 final RevisionSet newDraftRoots = draftLeft.roots(parentHelper);
140 final RevisionSet newSecretRoots = secretLeft.roots(parentHelper); 144 final RevisionSet newSecretRoots = secretLeft.roots(parentHelper);
141 phaseHelper.updateRoots(newDraftRoots.asList(), newSecretRoots.asList()); 145 phaseHelper.updateRoots(newDraftRoots.asList(), newSecretRoots.asList());
146 //
147 // if there's a remote draft root that points to revision we know is public
148 RevisionSet remoteDraftsLocalPublic = remoteDrafts.subtract(draftLeft).subtract(secretLeft);
149 if (!remoteDraftsLocalPublic.isEmpty()) {
150 // foreach remoteDraftsLocallyPublic.heads() do push Draft->Public
151 for (Nodeid n : remoteDraftsLocalPublic.heads(parentHelper)) {
152 try {
153 Outcome upo = remoteRepo.updatePhase(HgPhase.Draft, HgPhase.Public, n);
154 if (!upo.isOk()) {
155 implRepo.getLog().dump(getClass(), Severity.Info, "Failed to update remote phase, reason: %s", upo.getMessage());
156 }
157 } catch (HgRemoteConnectionException ex) {
158 implRepo.getLog().dump(getClass(), Severity.Error, ex, String.format("Failed to update phase of %s", n.shortNotation()));
159 }
160 }
161 }
142 } 162 }
143 progress.worked(5); 163 progress.worked(5);
144 // 164 //
145 // update bookmark information 165 // update bookmark information
146 HgBookmarks localBookmarks = repo.getBookmarks(); 166 HgBookmarks localBookmarks = repo.getBookmarks();
170 } finally { 190 } finally {
171 progress.done(); 191 progress.done();
172 } 192 }
173 } 193 }
174 194
195 private RevisionSet knownRemoteDrafts(HgRemoteRepository.Phases remotePhases, HgParentChildMap<HgChangelog> parentHelper, RevisionSet outgoing) {
196 ArrayList<Nodeid> knownRemoteDraftRoots = new ArrayList<Nodeid>();
197 for (Nodeid rdr : remotePhases.draftRoots()) {
198 if (parentHelper.knownNode(rdr)) {
199 knownRemoteDraftRoots.add(rdr);
200 }
201 }
202 // knownRemoteDraftRoots + childrenOf(knownRemoteDraftRoots) is everything remote may treat as Draft
203 RevisionSet remoteDrafts = new RevisionSet(knownRemoteDraftRoots);
204 remoteDrafts = remoteDrafts.union(remoteDrafts.children(parentHelper));
205 // 1) outgoing.children gives all local revisions accessible from outgoing.
206 // 2) outgoing.roots.children is equivalent with smaller intermediate set, the way we build
207 // childrenOf doesn't really benefits from that.
208 RevisionSet localChildrenNotSent = outgoing.children(parentHelper).subtract(outgoing);
209 // remote shall know only what we've sent, subtract revisions we didn't actually sent
210 remoteDrafts = remoteDrafts.subtract(localChildrenNotSent);
211 return remoteDrafts;
212 }
213
175 /* 214 /*
176 * To test, start a server: 215 * To test, start a server:
177 * $ hg --config web.allow_push=* --config web.push_ssl=False --config server.validate=True --debug serve 216 * $ hg --config web.allow_push=* --config web.push_ssl=False --config server.validate=True --debug serve
178 */ 217 */
179 public static void main(String[] args) throws Exception { 218 public static void main(String[] args) throws Exception {