Mercurial > jhg
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 } |