Mercurial > jhg
diff 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 |
line wrap: on
line diff
--- a/src/org/tmatesoft/hg/internal/PhasesHelper.java Wed Jul 10 11:53:19 2013 +0200 +++ b/src/org/tmatesoft/hg/internal/PhasesHelper.java Wed Jul 10 16:41:49 2013 +0200 @@ -39,6 +39,7 @@ import org.tmatesoft.hg.repo.HgInvalidStateException; import org.tmatesoft.hg.repo.HgParentChildMap; import org.tmatesoft.hg.repo.HgPhase; +import org.tmatesoft.hg.repo.HgRemoteRepository; import org.tmatesoft.hg.repo.HgRepository; import org.tmatesoft.hg.repo.HgRuntimeException; @@ -68,7 +69,7 @@ repo = internalRepo; parentHelper = pw; } - + public HgRepository getRepo() { return repo.getRepo(); } @@ -79,16 +80,19 @@ } return repoSupporsPhases.booleanValue(); } - + public boolean withSecretRoots() { return !secretPhaseRoots.isEmpty(); } /** - * @param cset revision to query + * @param cset + * revision to query * @return phase of the changeset, never <code>null</code> - * @throws HgInvalidControlFileException if failed to access revlog index/data entry. <em>Runtime exception</em> - * @throws HgRuntimeException subclass thereof to indicate other issues with the library. <em>Runtime exception</em> + * @throws HgInvalidControlFileException + * if failed to access revlog index/data entry. <em>Runtime exception</em> + * @throws HgRuntimeException + * subclass thereof to indicate other issues with the library. <em>Runtime exception</em> */ public HgPhase getPhase(HgChangeset cset) throws HgRuntimeException { final Nodeid csetRev = cset.getNodeid(); @@ -97,11 +101,15 @@ } /** - * @param csetRevIndex revision index to query - * @param csetRev revision nodeid, optional + * @param csetRevIndex + * revision index to query + * @param csetRev + * revision nodeid, optional * @return phase of the changeset, never <code>null</code> - * @throws HgInvalidControlFileException if failed to access revlog index/data entry. <em>Runtime exception</em> - * @throws HgRuntimeException subclass thereof to indicate other issues with the library. <em>Runtime exception</em> + * @throws HgInvalidControlFileException + * if failed to access revlog index/data entry. <em>Runtime exception</em> + * @throws HgRuntimeException + * subclass thereof to indicate other issues with the library. <em>Runtime exception</em> */ public HgPhase getPhase(final int csetRevIndex, Nodeid csetRev) throws HgRuntimeException { if (!isCapableOfPhases()) { @@ -111,8 +119,8 @@ if (parentHelper != null && (csetRev == null || csetRev.isNull())) { csetRev = getRepo().getChangelog().getRevision(csetRevIndex); } - - for (HgPhase phase : new HgPhase[] {HgPhase.Secret, HgPhase.Draft }) { + + for (HgPhase phase : new HgPhase[] { HgPhase.Secret, HgPhase.Draft }) { List<Nodeid> roots = getPhaseRoots(phase); if (roots.isEmpty()) { continue; @@ -138,24 +146,24 @@ return HgPhase.Public; } - /** * @return all revisions with secret phase */ public RevisionSet allSecret() { return allOf(HgPhase.Secret); } - + /** * @return all revisions with draft phase */ public RevisionSet allDraft() { return allOf(HgPhase.Draft).subtract(allOf(HgPhase.Secret)); } - + + // XXX throw HgIOException instead? public void updateRoots(Collection<Nodeid> draftRoots, Collection<Nodeid> secretRoots) throws HgInvalidControlFileException { - draftPhaseRoots = draftRoots.isEmpty() ? Collections.<Nodeid>emptyList() : new ArrayList<Nodeid>(draftRoots); - secretPhaseRoots = secretRoots.isEmpty() ? Collections.<Nodeid>emptyList() : new ArrayList<Nodeid>(secretRoots); + draftPhaseRoots = draftRoots.isEmpty() ? Collections.<Nodeid> emptyList() : new ArrayList<Nodeid>(draftRoots); + secretPhaseRoots = secretRoots.isEmpty() ? Collections.<Nodeid> emptyList() : new ArrayList<Nodeid>(secretRoots); String fmt = "%d %s\n"; File phaseroots = repo.getRepositoryFile(Phaseroots); FileWriter fw = null; @@ -203,19 +211,114 @@ } /** + * @return set of revisions that are public locally, but draft on remote. + */ + public RevisionSet synchronizeWithRemote(HgRemoteRepository.Phases remotePhases, RevisionSet sharedWithRemote) throws HgInvalidControlFileException { + assert parentHelper != null; + RevisionSet presentSecret = allSecret(); + RevisionSet presentDraft = allDraft(); + RevisionSet secretLeft, draftLeft; + RevisionSet remoteDrafts = knownRemoteDrafts(remotePhases, sharedWithRemote, presentSecret); + if (remotePhases.isPublishingServer()) { + // although it's unlikely shared revisions would affect secret changesets, + // it doesn't hurt to check secret roots along with draft ones + // + // local drafts that are known to be public now + RevisionSet draftsBecomePublic = presentDraft.intersect(sharedWithRemote); + RevisionSet secretsBecomePublic = presentSecret.intersect(sharedWithRemote); + // any ancestor of the public revision is public, too + RevisionSet draftsGone = presentDraft.ancestors(draftsBecomePublic, parentHelper); + RevisionSet secretsGone = presentSecret.ancestors(secretsBecomePublic, parentHelper); + // remove public and their ancestors from drafts + draftLeft = presentDraft.subtract(draftsGone).subtract(draftsBecomePublic); + secretLeft = presentSecret.subtract(secretsGone).subtract(secretsBecomePublic); + } else { + // shall merge local and remote phase states + // revisions that cease to be secret (gonna become Public), e.g. someone else pushed them + RevisionSet secretGone = presentSecret.intersect(remoteDrafts); + // 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 + // XXX do I really need sharedDrafts here? why not ancestors(remoteDrafts)? + RevisionSet localDraftRemotePublic = presentDraft.ancestors(sharedDraft, parentHelper); // I: 0; II: those treated public on remote + // remoteDrafts are local revisions known as draft@remote + // remoteDraftsLocalPublic - revisions that would cease to be listed as draft on remote + RevisionSet remoteDraftsLocalPublic = remoteDrafts.ancestors(sharedDraft, parentHelper); + RevisionSet remoteDraftsLeft = remoteDrafts.subtract(remoteDraftsLocalPublic); + // forget those deemed public by remote (drafts shared by both remote and local are ok to stay) + RevisionSet combinedDraft = presentDraft.union(remoteDraftsLeft); + draftLeft = combinedDraft.subtract(localDraftRemotePublic); + } + final RevisionSet newDraftRoots = draftLeft.roots(parentHelper); + final RevisionSet newSecretRoots = secretLeft.roots(parentHelper); + 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); + return remoteDraftsLocalPublic; + } + + // shared - set of revisions we've shared with remote + private RevisionSet knownRemoteDrafts(HgRemoteRepository.Phases remotePhases, RevisionSet shared, RevisionSet localSecret) { + 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); + RevisionSet localChildren = remoteDrafts.children(parentHelper); + // we didn't send any local secret revision + localChildren = localChildren.subtract(localSecret); + // draft roots are among remote drafts + remoteDrafts = remoteDrafts.union(localChildren); + // remoteDrafts is set of local revisions remote may see as Draft. However, + // need to remove from this set revisions we didn't share with remote: + // 1) shared.children gives all local revisions accessible from shared. + // 2) shared.roots.children is equivalent with smaller intermediate set, the way we build + // childrenOf doesn't really benefits from that. + RevisionSet localChildrenNotSent = shared.children(parentHelper).subtract(shared); + // remote shall know only what we've sent, subtract revisions we didn't actually sent + remoteDrafts = remoteDrafts.subtract(localChildrenNotSent); + return remoteDrafts; + } + + /** * For a given phase, collect all revisions with phase that is the same or more private (i.e. for Draft, returns Draft+Secret) - * The reason is not a nice API intention (which is awful, indeed), but an ease of implementation + * The reason is not a nice API intention (which is awful, indeed), but an ease of implementation */ private RevisionSet allOf(HgPhase phase) { assert phase != HgPhase.Public; if (!isCapableOfPhases()) { - return new RevisionSet(Collections.<Nodeid>emptyList()); + return new RevisionSet(Collections.<Nodeid> emptyList()); } final List<Nodeid> roots = getPhaseRoots(phase); if (parentHelper != null) { return new RevisionSet(roots).union(new RevisionSet(parentHelper.childrenOf(roots))); } else { - RevisionSet rv = new RevisionSet(Collections.<Nodeid>emptyList()); + RevisionSet rv = new RevisionSet(Collections.<Nodeid> emptyList()); for (RevisionDescendants rd : getPhaseDescendants(phase)) { rv = rv.union(rd.asRevisionSet()); } @@ -227,6 +330,11 @@ File phaseroots = repo.getRepositoryFile(Phaseroots); try { if (!phaseroots.exists()) { + if (repo.shallCreatePhaseroots()) { + draftPhaseRoots = Collections.<Nodeid>emptyList(); + secretPhaseRoots = Collections.<Nodeid>emptyList(); + return Boolean.TRUE; + } return Boolean.FALSE; } LineReader lr = new LineReader(phaseroots, repo.getLog()); @@ -254,8 +362,8 @@ } roots.add(rootRev); } - draftPhaseRoots = phase2roots.containsKey(Draft) ? phase2roots.get(Draft) : Collections.<Nodeid>emptyList(); - secretPhaseRoots = phase2roots.containsKey(Secret) ? phase2roots.get(Secret) : Collections.<Nodeid>emptyList(); + draftPhaseRoots = phase2roots.containsKey(Draft) ? phase2roots.get(Draft) : Collections.<Nodeid> emptyList(); + secretPhaseRoots = phase2roots.containsKey(Secret) ? phase2roots.get(Secret) : Collections.<Nodeid> emptyList(); } catch (HgIOException ex) { throw new HgInvalidControlFileException(ex, true); } @@ -264,13 +372,14 @@ private List<Nodeid> getPhaseRoots(HgPhase phase) { switch (phase) { - case Draft : return draftPhaseRoots; - case Secret : return secretPhaseRoots; + case Draft: + return draftPhaseRoots; + case Secret: + return secretPhaseRoots; } return Collections.emptyList(); } - private RevisionDescendants[] getPhaseDescendants(HgPhase phase) throws HgRuntimeException { int ordinal = phase.ordinal(); if (phaseDescendants[ordinal] == null) { @@ -288,7 +397,7 @@ } return rv; } - + private int[] toIndexes(List<Nodeid> roots) throws HgRuntimeException { int[] rv = new int[roots.size()]; for (int i = 0; i < rv.length; i++) {