comparison src/org/tmatesoft/hg/internal/PhasesHelper.java @ 449:5787e912f60e smartgit3

Speed up changeset phase detection when no parent cache is avalable
author Artem Tikhomirov <tikhomirov.artem@gmail.com>
date Thu, 07 Jun 2012 16:01:09 +0200
parents 2e402c12ebc6
children 39fe00407937
comparison
equal deleted inserted replaced
448:2e402c12ebc6 449:5787e912f60e
16 */ 16 */
17 package org.tmatesoft.hg.internal; 17 package org.tmatesoft.hg.internal;
18 18
19 import static org.tmatesoft.hg.repo.HgPhase.Draft; 19 import static org.tmatesoft.hg.repo.HgPhase.Draft;
20 import static org.tmatesoft.hg.repo.HgPhase.Secret; 20 import static org.tmatesoft.hg.repo.HgPhase.Secret;
21 import static org.tmatesoft.hg.repo.HgRepository.BAD_REVISION;
22 21
23 import java.io.BufferedReader; 22 import java.io.BufferedReader;
24 import java.io.File; 23 import java.io.File;
25 import java.io.FileReader; 24 import java.io.FileReader;
26 import java.io.IOException; 25 import java.io.IOException;
27 import java.util.Arrays;
28 import java.util.Collections; 26 import java.util.Collections;
29 import java.util.HashMap; 27 import java.util.HashMap;
30 import java.util.HashSet;
31 import java.util.LinkedList; 28 import java.util.LinkedList;
32 import java.util.List; 29 import java.util.List;
33 30
34 import org.tmatesoft.hg.core.HgChangeset; 31 import org.tmatesoft.hg.core.HgChangeset;
35 import org.tmatesoft.hg.core.HgInvalidControlFileException; 32 import org.tmatesoft.hg.core.HgInvalidControlFileException;
45 * @author Artem Tikhomirov 42 * @author Artem Tikhomirov
46 * @author TMate Software Ltd. 43 * @author TMate Software Ltd.
47 */ 44 */
48 public final class PhasesHelper { 45 public final class PhasesHelper {
49 46
50 private final HgRepository hgRepo; 47 private final HgRepository repo;
51 private final HgChangelog.ParentWalker parentHelper; 48 private final HgChangelog.ParentWalker parentHelper;
52 private Boolean repoSupporsPhases; 49 private Boolean repoSupporsPhases;
53 private List<Nodeid> draftPhaseRoots; 50 private List<Nodeid> draftPhaseRoots;
54 private List<Nodeid> secretPhaseRoots; 51 private List<Nodeid> secretPhaseRoots;
55 private int[] earliestRevIndex = new int[HgPhase.values().length]; 52 private RevisionDescendants[][] phaseDescendants = new RevisionDescendants[HgPhase.values().length][];
56 53
57 public PhasesHelper(HgRepository repo) { 54 public PhasesHelper(HgRepository hgRepo) {
58 this(repo, null); 55 this(hgRepo, null);
59 } 56 }
60 57
61 public PhasesHelper(HgRepository repo, HgChangelog.ParentWalker pw) { 58 public PhasesHelper(HgRepository hgRepo, HgChangelog.ParentWalker pw) {
62 hgRepo = repo; 59 repo = hgRepo;
63 parentHelper = pw; 60 parentHelper = pw;
64 Arrays.fill(earliestRevIndex, BAD_REVISION);
65 } 61 }
66 62
67 public boolean isCapableOfPhases() throws HgInvalidControlFileException { 63 public boolean isCapableOfPhases() throws HgInvalidControlFileException {
68 if (null == repoSupporsPhases) { 64 if (null == repoSupporsPhases) {
69 repoSupporsPhases = readRoots(); 65 repoSupporsPhases = readRoots();
80 76
81 public HgPhase getPhase(final int csetRevIndex, Nodeid csetRev) throws HgInvalidControlFileException { 77 public HgPhase getPhase(final int csetRevIndex, Nodeid csetRev) throws HgInvalidControlFileException {
82 if (!isCapableOfPhases()) { 78 if (!isCapableOfPhases()) {
83 return HgPhase.Undefined; 79 return HgPhase.Undefined;
84 } 80 }
85 if (csetRev == null || csetRev.isNull()) { 81 // csetRev is only used when parentHelper is available
86 csetRev = hgRepo.getChangelog().getRevision(csetRevIndex); 82 if (parentHelper != null && (csetRev == null || csetRev.isNull())) {
83 csetRev = repo.getChangelog().getRevision(csetRevIndex);
87 } 84 }
88 85
89 for (HgPhase phase : new HgPhase[] {HgPhase.Secret, HgPhase.Draft }) { 86 for (HgPhase phase : new HgPhase[] {HgPhase.Secret, HgPhase.Draft }) {
90 List<Nodeid> roots = getPhaseRoots(phase); 87 List<Nodeid> roots = getPhaseRoots(phase);
91 if (roots.isEmpty()) { 88 if (roots.isEmpty()) {
92 continue; 89 continue;
93 } 90 }
94 if (roots.contains(csetRev)) {
95 return phase;
96 }
97 if (parentHelper != null) { 91 if (parentHelper != null) {
92 if (roots.contains(csetRev)) {
93 return phase;
94 }
98 if (parentHelper.childrenOf(roots).contains(csetRev)) { 95 if (parentHelper.childrenOf(roots).contains(csetRev)) {
99 return phase; 96 return phase;
100 } 97 }
101 } else { 98 } else {
102 // no parent helper 99 // no parent helper
103 // search all descendants 100 // search all descendants.RevisuionDescendats includes root as well.
104 int earliestRootRevIndex = getEarliestPhaseRevision(phase); 101 for (RevisionDescendants rd : getPhaseDescendants(phase)) {
105 if (earliestRootRevIndex > csetRevIndex) { 102 // isCandidate is to go straight to another root if changeset was added later that the current root
106 // this phase started later than our changeset was added, try another phase 103 if (rd.isCandidate(csetRevIndex) && rd.isDescendant(csetRevIndex)) {
107 continue; 104 return phase;
108 }
109 /*
110 * TODO descendants() method to build a BitSet with 1 at index of those that are descendants
111 * wrap it into a class with root nodeid to
112 * (a) collect only for a subset of repository,
113 * (b) be able to answer isDescendant(int csetRevIndex) using absolute indexing (i.e bitAt(csetRevIndex - rootRevIndex))
114 */
115 final HashSet<Nodeid> parents2consider = new HashSet<Nodeid>(roots);
116 final boolean[] result = new boolean[] { false };
117 hgRepo.getChangelog().walk(earliestRootRevIndex, csetRevIndex, new HgChangelog.ParentInspector() {
118
119 public void next(int revisionIndex, Nodeid revision, int parent1, int parent2, Nodeid nidParent1, Nodeid nidParent2) {
120 boolean descendant = false;
121 if (!nidParent1.isNull() && parents2consider.contains(nidParent1)) {
122 parents2consider.add(revision);
123 descendant = true;
124 }
125 if (!nidParent2.isNull() && parents2consider.contains(nidParent2)) {
126 parents2consider.add(revision);
127 descendant = true;
128 }
129 if (descendant && revisionIndex == csetRevIndex) {
130 // revision of interest descends from one of the roots
131 result[0] = true;
132 }
133 } 105 }
134 });
135 if (result[0]) {
136 return phase;
137 } 106 }
138 } 107 }
139 } 108 }
140 return HgPhase.Public; 109 return HgPhase.Public;
141 110
142 } 111 }
143 112
144
145 private int[] toIndexes(List<Nodeid> roots) throws HgInvalidControlFileException {
146 int[] rv = new int[roots.size()];
147 for (int i = 0; i < rv.length; i++) {
148 rv[i] = hgRepo.getChangelog().getRevisionIndex(roots.get(i));
149 }
150 return rv;
151 }
152
153 private Boolean readRoots() throws HgInvalidControlFileException { 113 private Boolean readRoots() throws HgInvalidControlFileException {
154 // FIXME shall access phaseroots through HgRepository#repoPathHelper 114 // FIXME shall access phaseroots through HgRepository#repoPathHelper
155 File phaseroots = new File(HgInternals.getRepositoryDir(hgRepo), "store/phaseroots"); 115 File phaseroots = new File(HgInternals.getRepositoryDir(repo), "store/phaseroots");
156 try { 116 try {
157 if (!phaseroots.exists()) { 117 if (!phaseroots.exists()) {
158 return Boolean.FALSE; 118 return Boolean.FALSE;
159 } 119 }
160 HashMap<HgPhase, List<Nodeid>> phase2roots = new HashMap<HgPhase, List<Nodeid>>(); 120 HashMap<HgPhase, List<Nodeid>> phase2roots = new HashMap<HgPhase, List<Nodeid>>();
164 String[] lc = line.trim().split("\\s+"); 124 String[] lc = line.trim().split("\\s+");
165 if (lc.length == 0) { 125 if (lc.length == 0) {
166 continue; 126 continue;
167 } 127 }
168 if (lc.length != 2) { 128 if (lc.length != 2) {
169 HgInternals.getContext(hgRepo).getLog().warn(getClass(), "Bad line in phaseroots:%s", line); 129 HgInternals.getContext(repo).getLog().warn(getClass(), "Bad line in phaseroots:%s", line);
170 continue; 130 continue;
171 } 131 }
172 int phaseIndex = Integer.parseInt(lc[0]); 132 int phaseIndex = Integer.parseInt(lc[0]);
173 Nodeid rootRev = Nodeid.fromAscii(lc[1]); 133 Nodeid rootRev = Nodeid.fromAscii(lc[1]);
174 HgPhase phase = HgPhase.parse(phaseIndex); 134 HgPhase phase = HgPhase.parse(phaseIndex);
191 case Draft : return draftPhaseRoots; 151 case Draft : return draftPhaseRoots;
192 case Secret : return secretPhaseRoots; 152 case Secret : return secretPhaseRoots;
193 } 153 }
194 return Collections.emptyList(); 154 return Collections.emptyList();
195 } 155 }
156
157
158 private RevisionDescendants[] getPhaseDescendants(HgPhase phase) throws HgInvalidControlFileException {
159 int ordinal = phase.ordinal();
160 if (phaseDescendants[ordinal] == null) {
161 phaseDescendants[ordinal] = buildPhaseDescendants(phase);
162 }
163 return phaseDescendants[ordinal];
164 }
165
166 private RevisionDescendants[] buildPhaseDescendants(HgPhase phase) throws HgInvalidControlFileException {
167 int[] roots = toIndexes(getPhaseRoots(phase));
168 RevisionDescendants[] rv = new RevisionDescendants[roots.length];
169 for (int i = 0; i < roots.length; i++) {
170 rv[i] = new RevisionDescendants(repo, roots[i]);
171 rv[i].build();
172 }
173 return rv;
174 }
196 175
197 private int getEarliestPhaseRevision(HgPhase phase) throws HgInvalidControlFileException { 176 private int[] toIndexes(List<Nodeid> roots) throws HgInvalidControlFileException {
198 int ordinal = phase.ordinal(); 177 int[] rv = new int[roots.size()];
199 if (earliestRevIndex[ordinal] == BAD_REVISION) { 178 for (int i = 0; i < rv.length; i++) {
200 int[] rootIndexes = toIndexes(getPhaseRoots(phase)); 179 rv[i] = repo.getChangelog().getRevisionIndex(roots.get(i));
201 Arrays.sort(rootIndexes);
202 // instead of MAX_VALUE may use clog.getLastRevision() + 1
203 earliestRevIndex[ordinal] = rootIndexes.length == 0 ? Integer.MAX_VALUE : rootIndexes[0];
204 } 180 }
205 return earliestRevIndex[ordinal]; 181 return rv;
206 } 182 }
207 } 183 }