comparison 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
comparison
equal deleted inserted replaced
412:63c5a9d7ca3f 445:d0e5dc3cae6e
1 /*
2 * Copyright (c) 2012 TMate Software Ltd
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; version 2 of the License.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 *
13 * For information on how to redistribute this software under
14 * the terms of a license other than GNU General Public License
15 * contact TMate Software at support@hg4j.com
16 */
17 package org.tmatesoft.hg.internal;
18
19 import static org.tmatesoft.hg.repo.HgPhase.Draft;
20 import static org.tmatesoft.hg.repo.HgPhase.Secret;
21
22 import java.io.BufferedReader;
23 import java.io.File;
24 import java.io.FileReader;
25 import java.io.IOException;
26 import java.util.Arrays;
27 import java.util.Collections;
28 import java.util.HashMap;
29 import java.util.HashSet;
30 import java.util.LinkedList;
31 import java.util.List;
32
33 import org.tmatesoft.hg.core.HgChangeset;
34 import org.tmatesoft.hg.core.HgInvalidControlFileException;
35 import org.tmatesoft.hg.core.Nodeid;
36 import org.tmatesoft.hg.repo.HgChangelog;
37 import org.tmatesoft.hg.repo.HgInternals;
38 import org.tmatesoft.hg.repo.HgPhase;
39 import org.tmatesoft.hg.repo.HgRepository;
40
41 /**
42 * Support to deal with phases feature fo Mercurial (as of Mercutial version 2.1)
43 *
44 * @author Artem Tikhomirov
45 * @author TMate Software Ltd.
46 */
47 public final class PhasesHelper {
48
49 private final HgRepository hgRepo;
50 private final HgChangelog.ParentWalker parentHelper;
51 private Boolean repoSupporsPhases;
52 private List<Nodeid> draftPhaseRoots;
53 private List<Nodeid> secretPhaseRoots;
54
55 public PhasesHelper(HgRepository repo) {
56 this(repo, null);
57 }
58
59 public PhasesHelper(HgRepository repo, HgChangelog.ParentWalker pw) {
60 hgRepo = repo;
61 parentHelper = pw;
62 }
63
64 public boolean isCapableOfPhases() throws HgInvalidControlFileException {
65 if (null == repoSupporsPhases) {
66 repoSupporsPhases = readRoots();
67 }
68 return repoSupporsPhases.booleanValue();
69 }
70
71
72 public HgPhase getPhase(HgChangeset cset) throws HgInvalidControlFileException {
73 final Nodeid csetRev = cset.getNodeid();
74 final int csetRevIndex = cset.getRevision();
75 return getPhase(csetRevIndex, csetRev);
76 }
77
78 public HgPhase getPhase(final int csetRevIndex, Nodeid csetRev) throws HgInvalidControlFileException {
79 if (!isCapableOfPhases()) {
80 return HgPhase.Undefined;
81 }
82 if (csetRev == null || csetRev.isNull()) {
83 csetRev = hgRepo.getChangelog().getRevision(csetRevIndex);
84 }
85
86 for (HgPhase phase : new HgPhase[] {HgPhase.Secret, HgPhase.Draft }) {
87 List<Nodeid> roots = getPhaseRoots(phase);
88 if (roots.isEmpty()) {
89 continue;
90 }
91 if (roots.contains(csetRev)) {
92 return phase;
93 }
94 if (parentHelper != null) {
95 if (parentHelper.childrenOf(roots).contains(csetRev)) {
96 return phase;
97 }
98 } else {
99 // no parent helper
100 // search all descendants
101 int[] rootIndexes = toIndexes(roots);
102 Arrays.sort(rootIndexes);
103 if (rootIndexes[0] > csetRevIndex) {
104 // this phase started later than our changeset was added, try another phase
105 continue;
106 }
107 /*
108 * TODO descendants() method to build a BitSet with 1 at index of those that are descendants
109 * wrap it into a class with root nodeid to
110 * (a) collect only for a subset of repository,
111 * (b) be able to answer isDescendant(int csetRevIndex) using absolute indexing (i.e bitAt(csetRevIndex - rootRevIndex))
112 */
113 final HashSet<Nodeid> parents2consider = new HashSet<Nodeid>(roots);
114 final boolean[] result = new boolean[] { false };
115 hgRepo.getChangelog().walk(rootIndexes[0], csetRevIndex, new HgChangelog.ParentInspector() {
116
117 public void next(int revisionIndex, Nodeid revision, int parent1, int parent2, Nodeid nidParent1, Nodeid nidParent2) {
118 boolean descendant = false;
119 if (!nidParent1.isNull() && parents2consider.contains(nidParent1)) {
120 parents2consider.add(nidParent1);
121 descendant = true;
122 }
123 if (!nidParent2.isNull() && parents2consider.contains(nidParent2)) {
124 parents2consider.add(nidParent2);
125 descendant = true;
126 }
127 if (descendant && revisionIndex == csetRevIndex) {
128 // revision of interest descends from one of the roots
129 result[0] = true;
130 }
131 }
132 });
133 if (result[0]) {
134 return phase;
135 }
136 }
137 }
138 return HgPhase.Public;
139
140 }
141
142
143 private int[] toIndexes(List<Nodeid> roots) throws HgInvalidControlFileException {
144 int[] rv = new int[roots.size()];
145 for (int i = 0; i < rv.length; i++) {
146 rv[i] = hgRepo.getChangelog().getRevisionIndex(roots.get(i));
147 }
148 return rv;
149 }
150
151 private Boolean readRoots() throws HgInvalidControlFileException {
152 // FIXME shall access phaseroots through HgRepository#repoPathHelper
153 File phaseroots = new File(HgInternals.getRepositoryDir(hgRepo), "store/phaseroots");
154 try {
155 if (!phaseroots.exists()) {
156 return Boolean.FALSE;
157 }
158 HashMap<HgPhase, List<Nodeid>> phase2roots = new HashMap<HgPhase, List<Nodeid>>();
159 BufferedReader br = new BufferedReader(new FileReader(phaseroots));
160 String line;
161 while ((line = br.readLine()) != null) {
162 String[] lc = line.trim().split("\\s+");
163 if (lc.length == 0) {
164 continue;
165 }
166 if (lc.length != 2) {
167 HgInternals.getContext(hgRepo).getLog().warn(getClass(), "Bad line in phaseroots:%s", line);
168 continue;
169 }
170 int phaseIndex = Integer.parseInt(lc[0]);
171 Nodeid rootRev = Nodeid.fromAscii(lc[1]);
172 HgPhase phase = HgPhase.parse(phaseIndex);
173 List<Nodeid> roots = phase2roots.get(phase);
174 if (roots == null) {
175 phase2roots.put(phase, roots = new LinkedList<Nodeid>());
176 }
177 roots.add(rootRev);
178 }
179 draftPhaseRoots = phase2roots.containsKey(Draft) ? phase2roots.get(Draft) : Collections.<Nodeid>emptyList();
180 secretPhaseRoots = phase2roots.containsKey(Secret) ? phase2roots.get(Secret) : Collections.<Nodeid>emptyList();
181 } catch (IOException ex) {
182 throw new HgInvalidControlFileException(ex.toString(), ex, phaseroots);
183 }
184 return Boolean.TRUE;
185 }
186
187 private List<Nodeid> getPhaseRoots(HgPhase phase) {
188 switch (phase) {
189 case Draft : return draftPhaseRoots;
190 case Secret : return secretPhaseRoots;
191 }
192 return Collections.emptyList();
193 }
194 }