tikhomirov@445: /* tikhomirov@445: * Copyright (c) 2012 TMate Software Ltd tikhomirov@445: * tikhomirov@445: * This program is free software; you can redistribute it and/or modify tikhomirov@445: * it under the terms of the GNU General Public License as published by tikhomirov@445: * the Free Software Foundation; version 2 of the License. tikhomirov@445: * tikhomirov@445: * This program is distributed in the hope that it will be useful, tikhomirov@445: * but WITHOUT ANY WARRANTY; without even the implied warranty of tikhomirov@445: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the tikhomirov@445: * GNU General Public License for more details. tikhomirov@445: * tikhomirov@445: * For information on how to redistribute this software under tikhomirov@445: * the terms of a license other than GNU General Public License tikhomirov@445: * contact TMate Software at support@hg4j.com tikhomirov@445: */ tikhomirov@445: package org.tmatesoft.hg.internal; tikhomirov@445: tikhomirov@445: import static org.tmatesoft.hg.repo.HgPhase.Draft; tikhomirov@445: import static org.tmatesoft.hg.repo.HgPhase.Secret; tikhomirov@447: import static org.tmatesoft.hg.repo.HgRepository.BAD_REVISION; tikhomirov@445: tikhomirov@445: import java.io.BufferedReader; tikhomirov@445: import java.io.File; tikhomirov@445: import java.io.FileReader; tikhomirov@445: import java.io.IOException; tikhomirov@445: import java.util.Arrays; tikhomirov@445: import java.util.Collections; tikhomirov@445: import java.util.HashMap; tikhomirov@445: import java.util.HashSet; tikhomirov@445: import java.util.LinkedList; tikhomirov@445: import java.util.List; tikhomirov@445: tikhomirov@445: import org.tmatesoft.hg.core.HgChangeset; tikhomirov@445: import org.tmatesoft.hg.core.HgInvalidControlFileException; tikhomirov@445: import org.tmatesoft.hg.core.Nodeid; tikhomirov@445: import org.tmatesoft.hg.repo.HgChangelog; tikhomirov@445: import org.tmatesoft.hg.repo.HgInternals; tikhomirov@445: import org.tmatesoft.hg.repo.HgPhase; tikhomirov@445: import org.tmatesoft.hg.repo.HgRepository; tikhomirov@445: tikhomirov@445: /** tikhomirov@445: * Support to deal with phases feature fo Mercurial (as of Mercutial version 2.1) tikhomirov@445: * tikhomirov@445: * @author Artem Tikhomirov tikhomirov@445: * @author TMate Software Ltd. tikhomirov@445: */ tikhomirov@445: public final class PhasesHelper { tikhomirov@445: tikhomirov@445: private final HgRepository hgRepo; tikhomirov@445: private final HgChangelog.ParentWalker parentHelper; tikhomirov@445: private Boolean repoSupporsPhases; tikhomirov@445: private List draftPhaseRoots; tikhomirov@445: private List secretPhaseRoots; tikhomirov@447: private int[] earliestRevIndex = new int[HgPhase.values().length]; tikhomirov@445: tikhomirov@445: public PhasesHelper(HgRepository repo) { tikhomirov@445: this(repo, null); tikhomirov@445: } tikhomirov@445: tikhomirov@445: public PhasesHelper(HgRepository repo, HgChangelog.ParentWalker pw) { tikhomirov@445: hgRepo = repo; tikhomirov@445: parentHelper = pw; tikhomirov@447: Arrays.fill(earliestRevIndex, BAD_REVISION); tikhomirov@445: } tikhomirov@445: tikhomirov@445: public boolean isCapableOfPhases() throws HgInvalidControlFileException { tikhomirov@445: if (null == repoSupporsPhases) { tikhomirov@445: repoSupporsPhases = readRoots(); tikhomirov@445: } tikhomirov@445: return repoSupporsPhases.booleanValue(); tikhomirov@445: } tikhomirov@445: tikhomirov@445: tikhomirov@445: public HgPhase getPhase(HgChangeset cset) throws HgInvalidControlFileException { tikhomirov@445: final Nodeid csetRev = cset.getNodeid(); tikhomirov@445: final int csetRevIndex = cset.getRevision(); tikhomirov@445: return getPhase(csetRevIndex, csetRev); tikhomirov@445: } tikhomirov@445: tikhomirov@445: public HgPhase getPhase(final int csetRevIndex, Nodeid csetRev) throws HgInvalidControlFileException { tikhomirov@445: if (!isCapableOfPhases()) { tikhomirov@445: return HgPhase.Undefined; tikhomirov@445: } tikhomirov@445: if (csetRev == null || csetRev.isNull()) { tikhomirov@445: csetRev = hgRepo.getChangelog().getRevision(csetRevIndex); tikhomirov@445: } tikhomirov@445: tikhomirov@445: for (HgPhase phase : new HgPhase[] {HgPhase.Secret, HgPhase.Draft }) { tikhomirov@445: List roots = getPhaseRoots(phase); tikhomirov@445: if (roots.isEmpty()) { tikhomirov@445: continue; tikhomirov@445: } tikhomirov@445: if (roots.contains(csetRev)) { tikhomirov@445: return phase; tikhomirov@445: } tikhomirov@445: if (parentHelper != null) { tikhomirov@445: if (parentHelper.childrenOf(roots).contains(csetRev)) { tikhomirov@445: return phase; tikhomirov@445: } tikhomirov@445: } else { tikhomirov@445: // no parent helper tikhomirov@445: // search all descendants tikhomirov@447: int earliestRootRevIndex = getEarliestPhaseRevision(phase); tikhomirov@447: if (earliestRootRevIndex > csetRevIndex) { tikhomirov@445: // this phase started later than our changeset was added, try another phase tikhomirov@445: continue; tikhomirov@445: } tikhomirov@445: /* tikhomirov@445: * TODO descendants() method to build a BitSet with 1 at index of those that are descendants tikhomirov@445: * wrap it into a class with root nodeid to tikhomirov@445: * (a) collect only for a subset of repository, tikhomirov@445: * (b) be able to answer isDescendant(int csetRevIndex) using absolute indexing (i.e bitAt(csetRevIndex - rootRevIndex)) tikhomirov@445: */ tikhomirov@445: final HashSet parents2consider = new HashSet(roots); tikhomirov@445: final boolean[] result = new boolean[] { false }; tikhomirov@448: hgRepo.getChangelog().walk(earliestRootRevIndex, csetRevIndex, new HgChangelog.ParentInspector() { tikhomirov@445: tikhomirov@445: public void next(int revisionIndex, Nodeid revision, int parent1, int parent2, Nodeid nidParent1, Nodeid nidParent2) { tikhomirov@445: boolean descendant = false; tikhomirov@445: if (!nidParent1.isNull() && parents2consider.contains(nidParent1)) { tikhomirov@446: parents2consider.add(revision); tikhomirov@445: descendant = true; tikhomirov@445: } tikhomirov@445: if (!nidParent2.isNull() && parents2consider.contains(nidParent2)) { tikhomirov@446: parents2consider.add(revision); tikhomirov@445: descendant = true; tikhomirov@445: } tikhomirov@445: if (descendant && revisionIndex == csetRevIndex) { tikhomirov@445: // revision of interest descends from one of the roots tikhomirov@445: result[0] = true; tikhomirov@445: } tikhomirov@445: } tikhomirov@445: }); tikhomirov@445: if (result[0]) { tikhomirov@445: return phase; tikhomirov@445: } tikhomirov@445: } tikhomirov@445: } tikhomirov@445: return HgPhase.Public; tikhomirov@445: tikhomirov@445: } tikhomirov@445: tikhomirov@445: tikhomirov@445: private int[] toIndexes(List roots) throws HgInvalidControlFileException { tikhomirov@445: int[] rv = new int[roots.size()]; tikhomirov@445: for (int i = 0; i < rv.length; i++) { tikhomirov@445: rv[i] = hgRepo.getChangelog().getRevisionIndex(roots.get(i)); tikhomirov@445: } tikhomirov@445: return rv; tikhomirov@445: } tikhomirov@445: tikhomirov@445: private Boolean readRoots() throws HgInvalidControlFileException { tikhomirov@445: // FIXME shall access phaseroots through HgRepository#repoPathHelper tikhomirov@445: File phaseroots = new File(HgInternals.getRepositoryDir(hgRepo), "store/phaseroots"); tikhomirov@445: try { tikhomirov@445: if (!phaseroots.exists()) { tikhomirov@445: return Boolean.FALSE; tikhomirov@445: } tikhomirov@445: HashMap> phase2roots = new HashMap>(); tikhomirov@445: BufferedReader br = new BufferedReader(new FileReader(phaseroots)); tikhomirov@445: String line; tikhomirov@445: while ((line = br.readLine()) != null) { tikhomirov@445: String[] lc = line.trim().split("\\s+"); tikhomirov@445: if (lc.length == 0) { tikhomirov@445: continue; tikhomirov@445: } tikhomirov@445: if (lc.length != 2) { tikhomirov@445: HgInternals.getContext(hgRepo).getLog().warn(getClass(), "Bad line in phaseroots:%s", line); tikhomirov@445: continue; tikhomirov@445: } tikhomirov@445: int phaseIndex = Integer.parseInt(lc[0]); tikhomirov@445: Nodeid rootRev = Nodeid.fromAscii(lc[1]); tikhomirov@445: HgPhase phase = HgPhase.parse(phaseIndex); tikhomirov@445: List roots = phase2roots.get(phase); tikhomirov@445: if (roots == null) { tikhomirov@445: phase2roots.put(phase, roots = new LinkedList()); tikhomirov@445: } tikhomirov@445: roots.add(rootRev); tikhomirov@445: } tikhomirov@445: draftPhaseRoots = phase2roots.containsKey(Draft) ? phase2roots.get(Draft) : Collections.emptyList(); tikhomirov@445: secretPhaseRoots = phase2roots.containsKey(Secret) ? phase2roots.get(Secret) : Collections.emptyList(); tikhomirov@445: } catch (IOException ex) { tikhomirov@445: throw new HgInvalidControlFileException(ex.toString(), ex, phaseroots); tikhomirov@445: } tikhomirov@445: return Boolean.TRUE; tikhomirov@445: } tikhomirov@445: tikhomirov@445: private List getPhaseRoots(HgPhase phase) { tikhomirov@445: switch (phase) { tikhomirov@445: case Draft : return draftPhaseRoots; tikhomirov@445: case Secret : return secretPhaseRoots; tikhomirov@445: } tikhomirov@445: return Collections.emptyList(); tikhomirov@445: } tikhomirov@447: tikhomirov@447: private int getEarliestPhaseRevision(HgPhase phase) throws HgInvalidControlFileException { tikhomirov@447: int ordinal = phase.ordinal(); tikhomirov@447: if (earliestRevIndex[ordinal] == BAD_REVISION) { tikhomirov@447: int[] rootIndexes = toIndexes(getPhaseRoots(phase)); tikhomirov@447: Arrays.sort(rootIndexes); tikhomirov@447: // instead of MAX_VALUE may use clog.getLastRevision() + 1 tikhomirov@447: earliestRevIndex[ordinal] = rootIndexes.length == 0 ? Integer.MAX_VALUE : rootIndexes[0]; tikhomirov@447: } tikhomirov@447: return earliestRevIndex[ordinal]; tikhomirov@447: } tikhomirov@445: }