comparison src/org/tmatesoft/hg/internal/PhasesHelper.java @ 663:46b56864b483

Pull: phase2 - update phases from remote, fncache with added files. Tests
author Artem Tikhomirov <tikhomirov.artem@gmail.com>
date Wed, 10 Jul 2013 16:41:49 +0200
parents 12a4f60ea972
children
comparison
equal deleted inserted replaced
662:af5223b86dd3 663:46b56864b483
37 import org.tmatesoft.hg.repo.HgChangelog; 37 import org.tmatesoft.hg.repo.HgChangelog;
38 import org.tmatesoft.hg.repo.HgInvalidControlFileException; 38 import org.tmatesoft.hg.repo.HgInvalidControlFileException;
39 import org.tmatesoft.hg.repo.HgInvalidStateException; 39 import org.tmatesoft.hg.repo.HgInvalidStateException;
40 import org.tmatesoft.hg.repo.HgParentChildMap; 40 import org.tmatesoft.hg.repo.HgParentChildMap;
41 import org.tmatesoft.hg.repo.HgPhase; 41 import org.tmatesoft.hg.repo.HgPhase;
42 import org.tmatesoft.hg.repo.HgRemoteRepository;
42 import org.tmatesoft.hg.repo.HgRepository; 43 import org.tmatesoft.hg.repo.HgRepository;
43 import org.tmatesoft.hg.repo.HgRuntimeException; 44 import org.tmatesoft.hg.repo.HgRuntimeException;
44 45
45 /** 46 /**
46 * Support to deal with Mercurial phases feature (as of Mercurial version 2.1) 47 * Support to deal with Mercurial phases feature (as of Mercurial version 2.1)
66 67
67 public PhasesHelper(Internals internalRepo, HgParentChildMap<HgChangelog> pw) { 68 public PhasesHelper(Internals internalRepo, HgParentChildMap<HgChangelog> pw) {
68 repo = internalRepo; 69 repo = internalRepo;
69 parentHelper = pw; 70 parentHelper = pw;
70 } 71 }
71 72
72 public HgRepository getRepo() { 73 public HgRepository getRepo() {
73 return repo.getRepo(); 74 return repo.getRepo();
74 } 75 }
75 76
76 public boolean isCapableOfPhases() throws HgRuntimeException { 77 public boolean isCapableOfPhases() throws HgRuntimeException {
77 if (null == repoSupporsPhases) { 78 if (null == repoSupporsPhases) {
78 repoSupporsPhases = readRoots(); 79 repoSupporsPhases = readRoots();
79 } 80 }
80 return repoSupporsPhases.booleanValue(); 81 return repoSupporsPhases.booleanValue();
81 } 82 }
82 83
83 public boolean withSecretRoots() { 84 public boolean withSecretRoots() {
84 return !secretPhaseRoots.isEmpty(); 85 return !secretPhaseRoots.isEmpty();
85 } 86 }
86 87
87 /** 88 /**
88 * @param cset revision to query 89 * @param cset
90 * revision to query
89 * @return phase of the changeset, never <code>null</code> 91 * @return phase of the changeset, never <code>null</code>
90 * @throws HgInvalidControlFileException if failed to access revlog index/data entry. <em>Runtime exception</em> 92 * @throws HgInvalidControlFileException
91 * @throws HgRuntimeException subclass thereof to indicate other issues with the library. <em>Runtime exception</em> 93 * if failed to access revlog index/data entry. <em>Runtime exception</em>
94 * @throws HgRuntimeException
95 * subclass thereof to indicate other issues with the library. <em>Runtime exception</em>
92 */ 96 */
93 public HgPhase getPhase(HgChangeset cset) throws HgRuntimeException { 97 public HgPhase getPhase(HgChangeset cset) throws HgRuntimeException {
94 final Nodeid csetRev = cset.getNodeid(); 98 final Nodeid csetRev = cset.getNodeid();
95 final int csetRevIndex = cset.getRevisionIndex(); 99 final int csetRevIndex = cset.getRevisionIndex();
96 return getPhase(csetRevIndex, csetRev); 100 return getPhase(csetRevIndex, csetRev);
97 } 101 }
98 102
99 /** 103 /**
100 * @param csetRevIndex revision index to query 104 * @param csetRevIndex
101 * @param csetRev revision nodeid, optional 105 * revision index to query
106 * @param csetRev
107 * revision nodeid, optional
102 * @return phase of the changeset, never <code>null</code> 108 * @return phase of the changeset, never <code>null</code>
103 * @throws HgInvalidControlFileException if failed to access revlog index/data entry. <em>Runtime exception</em> 109 * @throws HgInvalidControlFileException
104 * @throws HgRuntimeException subclass thereof to indicate other issues with the library. <em>Runtime exception</em> 110 * if failed to access revlog index/data entry. <em>Runtime exception</em>
111 * @throws HgRuntimeException
112 * subclass thereof to indicate other issues with the library. <em>Runtime exception</em>
105 */ 113 */
106 public HgPhase getPhase(final int csetRevIndex, Nodeid csetRev) throws HgRuntimeException { 114 public HgPhase getPhase(final int csetRevIndex, Nodeid csetRev) throws HgRuntimeException {
107 if (!isCapableOfPhases()) { 115 if (!isCapableOfPhases()) {
108 return HgPhase.Undefined; 116 return HgPhase.Undefined;
109 } 117 }
110 // csetRev is only used when parentHelper is available 118 // csetRev is only used when parentHelper is available
111 if (parentHelper != null && (csetRev == null || csetRev.isNull())) { 119 if (parentHelper != null && (csetRev == null || csetRev.isNull())) {
112 csetRev = getRepo().getChangelog().getRevision(csetRevIndex); 120 csetRev = getRepo().getChangelog().getRevision(csetRevIndex);
113 } 121 }
114 122
115 for (HgPhase phase : new HgPhase[] {HgPhase.Secret, HgPhase.Draft }) { 123 for (HgPhase phase : new HgPhase[] { HgPhase.Secret, HgPhase.Draft }) {
116 List<Nodeid> roots = getPhaseRoots(phase); 124 List<Nodeid> roots = getPhaseRoots(phase);
117 if (roots.isEmpty()) { 125 if (roots.isEmpty()) {
118 continue; 126 continue;
119 } 127 }
120 if (parentHelper != null) { 128 if (parentHelper != null) {
136 } 144 }
137 } 145 }
138 return HgPhase.Public; 146 return HgPhase.Public;
139 } 147 }
140 148
141
142 /** 149 /**
143 * @return all revisions with secret phase 150 * @return all revisions with secret phase
144 */ 151 */
145 public RevisionSet allSecret() { 152 public RevisionSet allSecret() {
146 return allOf(HgPhase.Secret); 153 return allOf(HgPhase.Secret);
147 } 154 }
148 155
149 /** 156 /**
150 * @return all revisions with draft phase 157 * @return all revisions with draft phase
151 */ 158 */
152 public RevisionSet allDraft() { 159 public RevisionSet allDraft() {
153 return allOf(HgPhase.Draft).subtract(allOf(HgPhase.Secret)); 160 return allOf(HgPhase.Draft).subtract(allOf(HgPhase.Secret));
154 } 161 }
155 162
163 // XXX throw HgIOException instead?
156 public void updateRoots(Collection<Nodeid> draftRoots, Collection<Nodeid> secretRoots) throws HgInvalidControlFileException { 164 public void updateRoots(Collection<Nodeid> draftRoots, Collection<Nodeid> secretRoots) throws HgInvalidControlFileException {
157 draftPhaseRoots = draftRoots.isEmpty() ? Collections.<Nodeid>emptyList() : new ArrayList<Nodeid>(draftRoots); 165 draftPhaseRoots = draftRoots.isEmpty() ? Collections.<Nodeid> emptyList() : new ArrayList<Nodeid>(draftRoots);
158 secretPhaseRoots = secretRoots.isEmpty() ? Collections.<Nodeid>emptyList() : new ArrayList<Nodeid>(secretRoots); 166 secretPhaseRoots = secretRoots.isEmpty() ? Collections.<Nodeid> emptyList() : new ArrayList<Nodeid>(secretRoots);
159 String fmt = "%d %s\n"; 167 String fmt = "%d %s\n";
160 File phaseroots = repo.getRepositoryFile(Phaseroots); 168 File phaseroots = repo.getRepositoryFile(Phaseroots);
161 FileWriter fw = null; 169 FileWriter fw = null;
162 try { 170 try {
163 fw = new FileWriter(phaseroots); 171 fw = new FileWriter(phaseroots);
201 throw new HgInvalidStateException(String.format("Unexpected phase %s for new commits", newCommitPhase)); 209 throw new HgInvalidStateException(String.format("Unexpected phase %s for new commits", newCommitPhase));
202 } 210 }
203 } 211 }
204 212
205 /** 213 /**
214 * @return set of revisions that are public locally, but draft on remote.
215 */
216 public RevisionSet synchronizeWithRemote(HgRemoteRepository.Phases remotePhases, RevisionSet sharedWithRemote) throws HgInvalidControlFileException {
217 assert parentHelper != null;
218 RevisionSet presentSecret = allSecret();
219 RevisionSet presentDraft = allDraft();
220 RevisionSet secretLeft, draftLeft;
221 RevisionSet remoteDrafts = knownRemoteDrafts(remotePhases, sharedWithRemote, presentSecret);
222 if (remotePhases.isPublishingServer()) {
223 // although it's unlikely shared revisions would affect secret changesets,
224 // it doesn't hurt to check secret roots along with draft ones
225 //
226 // local drafts that are known to be public now
227 RevisionSet draftsBecomePublic = presentDraft.intersect(sharedWithRemote);
228 RevisionSet secretsBecomePublic = presentSecret.intersect(sharedWithRemote);
229 // any ancestor of the public revision is public, too
230 RevisionSet draftsGone = presentDraft.ancestors(draftsBecomePublic, parentHelper);
231 RevisionSet secretsGone = presentSecret.ancestors(secretsBecomePublic, parentHelper);
232 // remove public and their ancestors from drafts
233 draftLeft = presentDraft.subtract(draftsGone).subtract(draftsBecomePublic);
234 secretLeft = presentSecret.subtract(secretsGone).subtract(secretsBecomePublic);
235 } else {
236 // shall merge local and remote phase states
237 // revisions that cease to be secret (gonna become Public), e.g. someone else pushed them
238 RevisionSet secretGone = presentSecret.intersect(remoteDrafts);
239 // parents of those remote drafts are public, mark them as public locally, too
240 RevisionSet remotePublic = presentSecret.ancestors(secretGone, parentHelper);
241 secretLeft = presentSecret.subtract(secretGone).subtract(remotePublic);
242 /*
243 * Revisions grow from left to right (parents to the left, children to the right)
244 *
245 * I: Set of local is subset of remote
246 *
247 * local draft
248 * --o---r---o---l---o--
249 * remote draft
250 *
251 * Remote draft roots shall be updated
252 *
253 *
254 * II: Set of local is superset of remote
255 *
256 * local draft
257 * --o---l---o---r---o--
258 * remote draft
259 *
260 * Local draft roots shall be updated
261 */
262 RevisionSet sharedDraft = presentDraft.intersect(remoteDrafts); // (I: ~presentDraft; II: ~remoteDraft
263 // XXX do I really need sharedDrafts here? why not ancestors(remoteDrafts)?
264 RevisionSet localDraftRemotePublic = presentDraft.ancestors(sharedDraft, parentHelper); // I: 0; II: those treated public on remote
265 // remoteDrafts are local revisions known as draft@remote
266 // remoteDraftsLocalPublic - revisions that would cease to be listed as draft on remote
267 RevisionSet remoteDraftsLocalPublic = remoteDrafts.ancestors(sharedDraft, parentHelper);
268 RevisionSet remoteDraftsLeft = remoteDrafts.subtract(remoteDraftsLocalPublic);
269 // forget those deemed public by remote (drafts shared by both remote and local are ok to stay)
270 RevisionSet combinedDraft = presentDraft.union(remoteDraftsLeft);
271 draftLeft = combinedDraft.subtract(localDraftRemotePublic);
272 }
273 final RevisionSet newDraftRoots = draftLeft.roots(parentHelper);
274 final RevisionSet newSecretRoots = secretLeft.roots(parentHelper);
275 updateRoots(newDraftRoots.asList(), newSecretRoots.asList());
276 //
277 // if there's a remote draft root that points to revision we know is public
278 RevisionSet remoteDraftsLocalPublic = remoteDrafts.subtract(draftLeft).subtract(secretLeft);
279 return remoteDraftsLocalPublic;
280 }
281
282 // shared - set of revisions we've shared with remote
283 private RevisionSet knownRemoteDrafts(HgRemoteRepository.Phases remotePhases, RevisionSet shared, RevisionSet localSecret) {
284 ArrayList<Nodeid> knownRemoteDraftRoots = new ArrayList<Nodeid>();
285 for (Nodeid rdr : remotePhases.draftRoots()) {
286 if (parentHelper.knownNode(rdr)) {
287 knownRemoteDraftRoots.add(rdr);
288 }
289 }
290 // knownRemoteDraftRoots + childrenOf(knownRemoteDraftRoots) is everything remote may treat as Draft
291 RevisionSet remoteDrafts = new RevisionSet(knownRemoteDraftRoots);
292 RevisionSet localChildren = remoteDrafts.children(parentHelper);
293 // we didn't send any local secret revision
294 localChildren = localChildren.subtract(localSecret);
295 // draft roots are among remote drafts
296 remoteDrafts = remoteDrafts.union(localChildren);
297 // remoteDrafts is set of local revisions remote may see as Draft. However,
298 // need to remove from this set revisions we didn't share with remote:
299 // 1) shared.children gives all local revisions accessible from shared.
300 // 2) shared.roots.children is equivalent with smaller intermediate set, the way we build
301 // childrenOf doesn't really benefits from that.
302 RevisionSet localChildrenNotSent = shared.children(parentHelper).subtract(shared);
303 // remote shall know only what we've sent, subtract revisions we didn't actually sent
304 remoteDrafts = remoteDrafts.subtract(localChildrenNotSent);
305 return remoteDrafts;
306 }
307
308 /**
206 * For a given phase, collect all revisions with phase that is the same or more private (i.e. for Draft, returns Draft+Secret) 309 * For a given phase, collect all revisions with phase that is the same or more private (i.e. for Draft, returns Draft+Secret)
207 * The reason is not a nice API intention (which is awful, indeed), but an ease of implementation 310 * The reason is not a nice API intention (which is awful, indeed), but an ease of implementation
208 */ 311 */
209 private RevisionSet allOf(HgPhase phase) { 312 private RevisionSet allOf(HgPhase phase) {
210 assert phase != HgPhase.Public; 313 assert phase != HgPhase.Public;
211 if (!isCapableOfPhases()) { 314 if (!isCapableOfPhases()) {
212 return new RevisionSet(Collections.<Nodeid>emptyList()); 315 return new RevisionSet(Collections.<Nodeid> emptyList());
213 } 316 }
214 final List<Nodeid> roots = getPhaseRoots(phase); 317 final List<Nodeid> roots = getPhaseRoots(phase);
215 if (parentHelper != null) { 318 if (parentHelper != null) {
216 return new RevisionSet(roots).union(new RevisionSet(parentHelper.childrenOf(roots))); 319 return new RevisionSet(roots).union(new RevisionSet(parentHelper.childrenOf(roots)));
217 } else { 320 } else {
218 RevisionSet rv = new RevisionSet(Collections.<Nodeid>emptyList()); 321 RevisionSet rv = new RevisionSet(Collections.<Nodeid> emptyList());
219 for (RevisionDescendants rd : getPhaseDescendants(phase)) { 322 for (RevisionDescendants rd : getPhaseDescendants(phase)) {
220 rv = rv.union(rd.asRevisionSet()); 323 rv = rv.union(rd.asRevisionSet());
221 } 324 }
222 return rv; 325 return rv;
223 } 326 }
225 328
226 private Boolean readRoots() throws HgRuntimeException { 329 private Boolean readRoots() throws HgRuntimeException {
227 File phaseroots = repo.getRepositoryFile(Phaseroots); 330 File phaseroots = repo.getRepositoryFile(Phaseroots);
228 try { 331 try {
229 if (!phaseroots.exists()) { 332 if (!phaseroots.exists()) {
333 if (repo.shallCreatePhaseroots()) {
334 draftPhaseRoots = Collections.<Nodeid>emptyList();
335 secretPhaseRoots = Collections.<Nodeid>emptyList();
336 return Boolean.TRUE;
337 }
230 return Boolean.FALSE; 338 return Boolean.FALSE;
231 } 339 }
232 LineReader lr = new LineReader(phaseroots, repo.getLog()); 340 LineReader lr = new LineReader(phaseroots, repo.getLog());
233 final Collection<String> lines = lr.read(new LineReader.SimpleLineCollector(), new LinkedList<String>()); 341 final Collection<String> lines = lr.read(new LineReader.SimpleLineCollector(), new LinkedList<String>());
234 HashMap<HgPhase, List<Nodeid>> phase2roots = new HashMap<HgPhase, List<Nodeid>>(); 342 HashMap<HgPhase, List<Nodeid>> phase2roots = new HashMap<HgPhase, List<Nodeid>>();
252 if (roots == null) { 360 if (roots == null) {
253 phase2roots.put(phase, roots = new LinkedList<Nodeid>()); 361 phase2roots.put(phase, roots = new LinkedList<Nodeid>());
254 } 362 }
255 roots.add(rootRev); 363 roots.add(rootRev);
256 } 364 }
257 draftPhaseRoots = phase2roots.containsKey(Draft) ? phase2roots.get(Draft) : Collections.<Nodeid>emptyList(); 365 draftPhaseRoots = phase2roots.containsKey(Draft) ? phase2roots.get(Draft) : Collections.<Nodeid> emptyList();
258 secretPhaseRoots = phase2roots.containsKey(Secret) ? phase2roots.get(Secret) : Collections.<Nodeid>emptyList(); 366 secretPhaseRoots = phase2roots.containsKey(Secret) ? phase2roots.get(Secret) : Collections.<Nodeid> emptyList();
259 } catch (HgIOException ex) { 367 } catch (HgIOException ex) {
260 throw new HgInvalidControlFileException(ex, true); 368 throw new HgInvalidControlFileException(ex, true);
261 } 369 }
262 return Boolean.TRUE; 370 return Boolean.TRUE;
263 } 371 }
264 372
265 private List<Nodeid> getPhaseRoots(HgPhase phase) { 373 private List<Nodeid> getPhaseRoots(HgPhase phase) {
266 switch (phase) { 374 switch (phase) {
267 case Draft : return draftPhaseRoots; 375 case Draft:
268 case Secret : return secretPhaseRoots; 376 return draftPhaseRoots;
377 case Secret:
378 return secretPhaseRoots;
269 } 379 }
270 return Collections.emptyList(); 380 return Collections.emptyList();
271 } 381 }
272
273 382
274 private RevisionDescendants[] getPhaseDescendants(HgPhase phase) throws HgRuntimeException { 383 private RevisionDescendants[] getPhaseDescendants(HgPhase phase) throws HgRuntimeException {
275 int ordinal = phase.ordinal(); 384 int ordinal = phase.ordinal();
276 if (phaseDescendants[ordinal] == null) { 385 if (phaseDescendants[ordinal] == null) {
277 phaseDescendants[ordinal] = buildPhaseDescendants(phase); 386 phaseDescendants[ordinal] = buildPhaseDescendants(phase);
286 rv[i] = new RevisionDescendants(getRepo(), roots[i]); 395 rv[i] = new RevisionDescendants(getRepo(), roots[i]);
287 rv[i].build(); 396 rv[i].build();
288 } 397 }
289 return rv; 398 return rv;
290 } 399 }
291 400
292 private int[] toIndexes(List<Nodeid> roots) throws HgRuntimeException { 401 private int[] toIndexes(List<Nodeid> roots) throws HgRuntimeException {
293 int[] rv = new int[roots.size()]; 402 int[] rv = new int[roots.size()];
294 for (int i = 0; i < rv.length; i++) { 403 for (int i = 0; i < rv.length; i++) {
295 rv[i] = getRepo().getChangelog().getRevisionIndex(roots.get(i)); 404 rv[i] = getRepo().getChangelog().getRevisionIndex(roots.get(i));
296 } 405 }