comparison src/org/tmatesoft/hg/repo/HgStatusCollector.java @ 94:af1f3b78b918

*StatusCollector renamed to Hg*StatusCollector
author Artem Tikhomirov <tikhomirov.artem@gmail.com>
date Thu, 27 Jan 2011 21:18:47 +0100
parents src/org/tmatesoft/hg/repo/StatusCollector.java@d55d4eedfc57
children a3a2e5deb320
comparison
equal deleted inserted replaced
93:d55d4eedfc57 94:af1f3b78b918
1 /*
2 * Copyright (c) 2011 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@svnkit.com
16 */
17 package org.tmatesoft.hg.repo;
18
19 import static org.tmatesoft.hg.repo.HgRepository.BAD_REVISION;
20 import static org.tmatesoft.hg.repo.HgRepository.TIP;
21
22 import java.util.Collection;
23 import java.util.Collections;
24 import java.util.LinkedHashMap;
25 import java.util.LinkedList;
26 import java.util.List;
27 import java.util.Map;
28 import java.util.TreeMap;
29 import java.util.TreeSet;
30
31 import org.tmatesoft.hg.core.Nodeid;
32 import org.tmatesoft.hg.core.Path;
33 import org.tmatesoft.hg.util.PathPool;
34 import org.tmatesoft.hg.util.PathRewrite;
35
36
37 /**
38 * RevisionWalker?
39 *
40 * @author Artem Tikhomirov
41 * @author TMate Software Ltd.
42 */
43 public class HgStatusCollector {
44
45 private final HgRepository repo;
46 private final Map<Integer, ManifestRevisionInspector> cache; // sparse array, in fact
47 private PathPool pathPool;
48
49 public HgStatusCollector(HgRepository hgRepo) {
50 this.repo = hgRepo;
51 cache = new TreeMap<Integer, ManifestRevisionInspector>();
52 ManifestRevisionInspector emptyFakeState = new ManifestRevisionInspector();
53 emptyFakeState.begin(-1, null);
54 emptyFakeState.end(-1); // FIXME HgRepo.TIP == -1 as well, need to distinguish fake "prior to first" revision from "the very last"
55 cache.put(-1, emptyFakeState);
56 }
57
58 public HgRepository getRepo() {
59 return repo;
60 }
61
62 private ManifestRevisionInspector get(int rev) {
63 ManifestRevisionInspector i = cache.get(rev);
64 if (i == null) {
65 i = new ManifestRevisionInspector();
66 cache.put(rev, i);
67 repo.getManifest().walk(rev, rev, i);
68 }
69 return i;
70 }
71
72 /*package-local*/ ManifestRevisionInspector raw(int rev) {
73 return get(rev);
74 }
75 /*package-local*/ PathPool getPathPool() {
76 if (pathPool == null) {
77 pathPool = new PathPool(new PathRewrite.Empty());
78 }
79 return pathPool;
80 }
81
82 public void setPathPool(PathPool pathPool) {
83 this.pathPool = pathPool;
84 }
85
86
87 // hg status --change <rev>
88 public void change(int rev, HgStatusInspector inspector) {
89 int[] parents = new int[2];
90 repo.getChangelog().parents(rev, parents, null, null);
91 walk(parents[0], rev, inspector);
92 }
93
94 // I assume revision numbers are the same for changelog and manifest - here
95 // user would like to pass changelog revision numbers, and I use them directly to walk manifest.
96 // if this assumption is wrong, fix this (lookup manifest revisions from changeset).
97 public void walk(int rev1, int rev2, HgStatusInspector inspector) {
98 if (rev1 == rev2) {
99 throw new IllegalArgumentException();
100 }
101 if (inspector == null) {
102 throw new IllegalArgumentException();
103 }
104 if (inspector instanceof Record) {
105 ((Record) inspector).init(rev1, rev2, this);
106 }
107 if (rev1 == TIP) {
108 rev1 = repo.getManifest().getRevisionCount() - 1;
109 }
110 if (rev2 == TIP) {
111 rev2 = repo.getManifest().getRevisionCount() - 1; // XXX add Revlog.tip() func ?
112 }
113 // in fact, rev1 and rev2 are often next (or close) to each other,
114 // thus, we can optimize Manifest reads here (manifest.walk(rev1, rev2))
115 ManifestRevisionInspector r1, r2 ;
116 if (!cache.containsKey(rev1) && !cache.containsKey(rev2) && Math.abs(rev1 - rev2) < 5 /*subjective equivalent of 'close enough'*/) {
117 int minRev = rev1 < rev2 ? rev1 : rev2;
118 int maxRev = minRev == rev1 ? rev2 : rev1;
119 if (minRev > 0) {
120 minRev--; // expand range a bit
121 // XXX perhaps, if revlog.baseRevision is cheap, shall expand minRev up to baseRevision
122 // which gonna be read anyway
123 }
124
125 repo.getManifest().walk(minRev, maxRev, new HgManifest.Inspector() {
126 private ManifestRevisionInspector delegate;
127
128 public boolean begin(int revision, Nodeid nid) {
129 cache.put(revision, delegate = new ManifestRevisionInspector());
130 delegate.begin(revision, nid);
131 return true;
132 }
133
134 public boolean next(Nodeid nid, String fname, String flags) {
135 delegate.next(nid, fname, flags);
136 return true;
137 }
138
139 public boolean end(int revision) {
140 delegate.end(revision);
141 delegate = null;
142 return true;
143 }
144 });
145 }
146 r1 = get(rev1);
147 r2 = get(rev2);
148
149 PathPool pp = getPathPool();
150
151 TreeSet<String> r1Files = new TreeSet<String>(r1.files());
152 for (String fname : r2.files()) {
153 if (r1Files.remove(fname)) {
154 Nodeid nidR1 = r1.nodeid(fname);
155 Nodeid nidR2 = r2.nodeid(fname);
156 String flagsR1 = r1.flags(fname);
157 String flagsR2 = r2.flags(fname);
158 if (nidR1.equals(nidR2) && ((flagsR2 == null && flagsR1 == null) || flagsR2.equals(flagsR1))) {
159 inspector.clean(pp.path(fname));
160 } else {
161 inspector.modified(pp.path(fname));
162 }
163 } else {
164 String copyOrigin = getOriginIfCopy(repo, fname, r1Files, rev1);
165 if (copyOrigin != null) {
166 inspector.copied(pp.path(copyOrigin), pp.path(fname));
167 } else {
168 inspector.added(pp.path(fname));
169 }
170 }
171 }
172 for (String left : r1Files) {
173 inspector.removed(pp.path(left));
174 }
175 }
176
177 public Record status(int rev1, int rev2) {
178 Record rv = new Record();
179 walk(rev1, rev2, rv);
180 return rv;
181 }
182
183 /*package-local*/static String getOriginIfCopy(HgRepository hgRepo, String fname, Collection<String> originals, int originalChangelogRevision) {
184 HgDataFile df = hgRepo.getFileNode(fname);
185 while (df.isCopy()) {
186 Path original = df.getCopySourceName();
187 if (originals.contains(original.toString())) {
188 df = hgRepo.getFileNode(original);
189 int changelogRevision = df.getChangesetLocalRevision(0);
190 if (changelogRevision <= originalChangelogRevision) {
191 // copy/rename source was known prior to rev1
192 // (both r1Files.contains is true and original was created earlier than rev1)
193 // without r1Files.contains changelogRevision <= rev1 won't suffice as the file
194 // might get removed somewhere in between (changelogRevision < R < rev1)
195 return original.toString();
196 }
197 break; // copy/rename done later
198 }
199 df = hgRepo.getFileNode(original); // try more steps away
200 }
201 return null;
202 }
203
204 // XXX for r1..r2 status, only modified, added, removed (and perhaps, clean) make sense
205 // XXX Need to specify whether copy targets are in added or not (@see Inspector#copied above)
206 public static class Record implements HgStatusInspector {
207 private List<Path> modified, added, removed, clean, missing, unknown, ignored;
208 private Map<Path, Path> copied;
209
210 private int startRev, endRev;
211 private HgStatusCollector statusHelper;
212
213 // XXX StatusCollector may additionally initialize Record instance to speed lookup of changed file revisions
214 // here I need access to ManifestRevisionInspector via #raw(). Perhaps, non-static class (to get
215 // implicit reference to StatusCollector) may be better?
216 // Since users may want to reuse Record instance we've once created (and initialized), we need to
217 // ensure functionality is correct for each/any call (#walk checks instanceof Record and fixes it up)
218 // Perhaps, distinct helper (sc.getRevisionHelper().nodeid(fname)) would be better, just not clear
219 // how to supply [start..end] values there easily
220 /*package-local*/void init(int startRevision, int endRevision, HgStatusCollector self) {
221 startRev = startRevision;
222 endRev = endRevision;
223 statusHelper = self;
224 }
225
226 public Nodeid nodeidBeforeChange(Path fname) {
227 if (statusHelper == null || startRev == BAD_REVISION) {
228 return null;
229 }
230 if ((modified == null || !modified.contains(fname)) && (removed == null || !removed.contains(fname))) {
231 return null;
232 }
233 return statusHelper.raw(startRev).nodeid(fname.toString());
234 }
235 public Nodeid nodeidAfterChange(Path fname) {
236 if (statusHelper == null || endRev == BAD_REVISION) {
237 return null;
238 }
239 if ((modified == null || !modified.contains(fname)) && (added == null || !added.contains(fname))) {
240 return null;
241 }
242 return statusHelper.raw(endRev).nodeid(fname.toString());
243 }
244
245 public List<Path> getModified() {
246 return proper(modified);
247 }
248
249 public List<Path> getAdded() {
250 return proper(added);
251 }
252
253 public List<Path> getRemoved() {
254 return proper(removed);
255 }
256
257 public Map<Path,Path> getCopied() {
258 if (copied == null) {
259 return Collections.emptyMap();
260 }
261 return Collections.unmodifiableMap(copied);
262 }
263
264 public List<Path> getClean() {
265 return proper(clean);
266 }
267
268 public List<Path> getMissing() {
269 return proper(missing);
270 }
271
272 public List<Path> getUnknown() {
273 return proper(unknown);
274 }
275
276 public List<Path> getIgnored() {
277 return proper(ignored);
278 }
279
280 private List<Path> proper(List<Path> l) {
281 if (l == null) {
282 return Collections.emptyList();
283 }
284 return Collections.unmodifiableList(l);
285 }
286
287 //
288 //
289
290 public void modified(Path fname) {
291 modified = doAdd(modified, fname);
292 }
293
294 public void added(Path fname) {
295 added = doAdd(added, fname);
296 }
297
298 public void copied(Path fnameOrigin, Path fnameAdded) {
299 if (copied == null) {
300 copied = new LinkedHashMap<Path, Path>();
301 }
302 added(fnameAdded);
303 copied.put(fnameAdded, fnameOrigin);
304 }
305
306 public void removed(Path fname) {
307 removed = doAdd(removed, fname);
308 }
309
310 public void clean(Path fname) {
311 clean = doAdd(clean, fname);
312 }
313
314 public void missing(Path fname) {
315 missing = doAdd(missing, fname);
316 }
317
318 public void unknown(Path fname) {
319 unknown = doAdd(unknown, fname);
320 }
321
322 public void ignored(Path fname) {
323 ignored = doAdd(ignored, fname);
324 }
325
326 private static List<Path> doAdd(List<Path> l, Path p) {
327 if (l == null) {
328 l = new LinkedList<Path>();
329 }
330 l.add(p);
331 return l;
332 }
333 }
334
335 /*package-local*/ static final class ManifestRevisionInspector implements HgManifest.Inspector {
336 private final TreeMap<String, Nodeid> idsMap;
337 private final TreeMap<String, String> flagsMap;
338
339 public ManifestRevisionInspector() {
340 idsMap = new TreeMap<String, Nodeid>();
341 flagsMap = new TreeMap<String, String>();
342 }
343
344 public Collection<String> files() {
345 return idsMap.keySet();
346 }
347
348 public Nodeid nodeid(String fname) {
349 return idsMap.get(fname);
350 }
351
352 public String flags(String fname) {
353 return flagsMap.get(fname);
354 }
355
356 //
357
358 public boolean next(Nodeid nid, String fname, String flags) {
359 idsMap.put(fname, nid);
360 flagsMap.put(fname, flags);
361 return true;
362 }
363
364 public boolean end(int revision) {
365 // in fact, this class cares about single revision
366 return false;
367 }
368
369 public boolean begin(int revision, Nodeid nid) {
370 return true;
371 }
372 }
373
374 }