comparison src/org/tmatesoft/hg/repo/StatusCollector.java @ 74:6f1b88693d48

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