Mercurial > hg4j
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 } |