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