Mercurial > hg4j
comparison src/org/tmatesoft/hg/repo/HgDirstate.java @ 293:9774f47d904d
Issue 13: Status reports filenames with case other than in dirstate incorrectly
author | Artem Tikhomirov <tikhomirov.artem@gmail.com> |
---|---|
date | Wed, 14 Sep 2011 04:11:37 +0200 |
parents | 1483e57541ef |
children | 981f9f50bb6c |
comparison
equal
deleted
inserted
replaced
292:a415fe296a50 | 293:9774f47d904d |
---|---|
21 import java.io.BufferedReader; | 21 import java.io.BufferedReader; |
22 import java.io.File; | 22 import java.io.File; |
23 import java.io.FileReader; | 23 import java.io.FileReader; |
24 import java.io.IOException; | 24 import java.io.IOException; |
25 import java.util.Collections; | 25 import java.util.Collections; |
26 import java.util.HashMap; | |
26 import java.util.LinkedHashMap; | 27 import java.util.LinkedHashMap; |
27 import java.util.Map; | 28 import java.util.Map; |
28 import java.util.TreeSet; | 29 import java.util.TreeSet; |
29 | 30 |
30 import org.tmatesoft.hg.core.HgBadStateException; | 31 import org.tmatesoft.hg.core.HgBadStateException; |
55 private final PathRewrite canonicalPathRewrite; | 56 private final PathRewrite canonicalPathRewrite; |
56 private Map<Path, Record> normal; | 57 private Map<Path, Record> normal; |
57 private Map<Path, Record> added; | 58 private Map<Path, Record> added; |
58 private Map<Path, Record> removed; | 59 private Map<Path, Record> removed; |
59 private Map<Path, Record> merged; | 60 private Map<Path, Record> merged; |
60 private Map<Path, Path> canonical2dirstate; // map of canonicalized file names to their originals from dirstate file | 61 /* map of canonicalized file names to their originals from dirstate file. |
62 * Note, only those canonical names that differ from their dirstate counterpart are recorded here | |
63 */ | |
64 private Map<Path, Path> canonical2dirstateName; | |
61 private Pair<Nodeid, Nodeid> parents; | 65 private Pair<Nodeid, Nodeid> parents; |
62 private String currentBranch; | 66 private String currentBranch; |
63 | 67 |
64 // canonicalPath may be null if we don't need to check for names other than in dirstate | 68 // canonicalPath may be null if we don't need to check for names other than in dirstate |
65 /*package-local*/ HgDirstate(HgRepository hgRepo, File dirstate, PathPool pathPool, PathRewrite canonicalPath) { | 69 /*package-local*/ HgDirstate(HgRepository hgRepo, File dirstate, PathPool pathPool, PathRewrite canonicalPath) { |
81 // not sure linked is really needed here, just for ease of debug | 85 // not sure linked is really needed here, just for ease of debug |
82 normal = new LinkedHashMap<Path, Record>(); | 86 normal = new LinkedHashMap<Path, Record>(); |
83 added = new LinkedHashMap<Path, Record>(); | 87 added = new LinkedHashMap<Path, Record>(); |
84 removed = new LinkedHashMap<Path, Record>(); | 88 removed = new LinkedHashMap<Path, Record>(); |
85 merged = new LinkedHashMap<Path, Record>(); | 89 merged = new LinkedHashMap<Path, Record>(); |
90 if (canonicalPathRewrite != null) { | |
91 canonical2dirstateName = new HashMap<Path,Path>(); | |
92 } else { | |
93 canonical2dirstateName = Collections.emptyMap(); | |
94 } | |
86 try { | 95 try { |
87 parents = internalReadParents(da); | 96 parents = internalReadParents(da); |
88 // hg init; hg up produces an empty repository where dirstate has parents (40 bytes) only | 97 // hg init; hg up produces an empty repository where dirstate has parents (40 bytes) only |
89 while (!da.isEmpty()) { | 98 while (!da.isEmpty()) { |
90 final byte state = da.readByte(); | 99 final byte state = da.readByte(); |
104 } | 113 } |
105 if (fn1 == null) { | 114 if (fn1 == null) { |
106 fn1 = new String(name); | 115 fn1 = new String(name); |
107 } | 116 } |
108 Record r = new Record(fmode, size, time, pathPool.path(fn1), fn2 == null ? null : pathPool.path(fn2)); | 117 Record r = new Record(fmode, size, time, pathPool.path(fn1), fn2 == null ? null : pathPool.path(fn2)); |
118 if (canonicalPathRewrite != null) { | |
119 Path canonicalPath = pathPool.path(canonicalPathRewrite.rewrite(fn1).toString()); | |
120 if (canonicalPath != r.name()) { // == as they come from the same pool | |
121 assert !canonical2dirstateName.containsKey(canonicalPath); // otherwise there's already a file with same canonical name | |
122 // which can't happen for case-insensitive file system (or there's erroneous PathRewrite, perhaps doing smth else) | |
123 canonical2dirstateName.put(canonicalPath, r.name()); | |
124 } | |
125 if (fn2 != null) { | |
126 // not sure I need copy origin in the map, I don't seem to use it anywhere, | |
127 // but I guess I'll have to use it some day. | |
128 canonicalPath = pathPool.path(canonicalPathRewrite.rewrite(fn2).toString()); | |
129 if (canonicalPath != r.copySource()) { | |
130 canonical2dirstateName.put(canonicalPath, r.copySource()); | |
131 } | |
132 } | |
133 } | |
109 if (state == 'n') { | 134 if (state == 'n') { |
110 normal.put(r.name1, r); | 135 normal.put(r.name1, r); |
111 } else if (state == 'a') { | 136 } else if (state == 'a') { |
112 added.put(r.name1, r); | 137 added.put(r.name1, r); |
113 } else if (state == 'r') { | 138 } else if (state == 'r') { |
215 } | 240 } |
216 return rv; | 241 return rv; |
217 } | 242 } |
218 | 243 |
219 /*package-local*/ Record checkNormal(Path fname) { | 244 /*package-local*/ Record checkNormal(Path fname) { |
220 return normal.get(fname); | 245 return internalCheck(normal, fname); |
221 } | 246 } |
222 | 247 |
223 /*package-local*/ Record checkAdded(Path fname) { | 248 /*package-local*/ Record checkAdded(Path fname) { |
224 return added.get(fname); | 249 return internalCheck(added, fname); |
225 } | 250 } |
226 /*package-local*/ Record checkRemoved(Path fname) { | 251 /*package-local*/ Record checkRemoved(Path fname) { |
227 return removed.get(fname); | 252 return internalCheck(removed, fname); |
228 } | 253 } |
229 /*package-local*/ Record checkMerged(Path fname) { | 254 /*package-local*/ Record checkMerged(Path fname) { |
230 return merged.get(fname); | 255 return internalCheck(merged, fname); |
231 } | 256 } |
232 | 257 |
233 | 258 |
259 // return non-null if fname is known, either as is, or its canonical form. in latter case, this canonical form is return value | |
260 /*package-local*/ Path known(Path fname) { | |
261 Path fnameCanonical = null; | |
262 if (canonicalPathRewrite != null) { | |
263 fnameCanonical = pathPool.path(canonicalPathRewrite.rewrite(fname).toString()); | |
264 if (fnameCanonical != fname && canonical2dirstateName.containsKey(fnameCanonical)) { | |
265 // we know right away there's name in dirstate with alternative canonical form | |
266 return canonical2dirstateName.get(fnameCanonical); | |
267 } | |
268 } | |
269 @SuppressWarnings("unchecked") | |
270 Map<Path, Record>[] all = new Map[] { normal, added, removed, merged }; | |
271 for (int i = 0; i < all.length; i++) { | |
272 if (all[i].containsKey(fname)) { | |
273 return fname; | |
274 } | |
275 if (fnameCanonical != null && all[i].containsKey(fnameCanonical)) { | |
276 return fnameCanonical; | |
277 } | |
278 } | |
279 return null; | |
280 } | |
281 | |
282 private Record internalCheck(Map<Path, Record> map, Path fname) { | |
283 Record rv = map.get(fname); | |
284 if (rv != null || canonicalPathRewrite == null) { | |
285 return rv; | |
286 } | |
287 Path fnameCanonical = pathPool.path(canonicalPathRewrite.rewrite(fname).toString()); | |
288 if (fnameCanonical != fname) { | |
289 // case when fname = /a/B/c, and dirstate is /a/b/C | |
290 if (canonical2dirstateName.containsKey(fnameCanonical)) { | |
291 return map.get(canonical2dirstateName.get(fnameCanonical)); | |
292 } | |
293 // try canonical directly, fname = /a/B/C, dirstate has /a/b/c | |
294 if ((rv = map.get(fnameCanonical)) != null) { | |
295 return rv; | |
296 } | |
297 } | |
298 return null; | |
299 } | |
234 | 300 |
235 | 301 |
236 /*package-local*/ void dump() { | 302 /*package-local*/ void dump() { |
237 read(); | 303 read(); |
238 @SuppressWarnings("unchecked") | 304 @SuppressWarnings("unchecked") |
265 } | 331 } |
266 } | 332 } |
267 } | 333 } |
268 | 334 |
269 public interface Inspector { | 335 public interface Inspector { |
336 /** | |
337 * Invoked for each entry in the directory state file | |
338 * @param kind file record kind | |
339 * @param entry file record. Note, do not cache instance as it may be reused between the calls | |
340 * @return <code>true</code> to indicate further records are still of interest, <code>false</code> to stop iteration | |
341 */ | |
270 boolean next(EntryKind kind, Record entry); | 342 boolean next(EntryKind kind, Record entry); |
271 } | 343 } |
272 | 344 |
273 public static final class Record { | 345 public static final class Record implements Cloneable { |
274 private final int mode, size, time; | 346 private final int mode, size, time; |
275 // Dirstate keeps local file size (i.e. that with any filters already applied). | 347 // Dirstate keeps local file size (i.e. that with any filters already applied). |
276 // Thus, can't compare directly to HgDataFile.length() | 348 // Thus, can't compare directly to HgDataFile.length() |
277 private final Path name1, name2; | 349 private final Path name1, name2; |
278 | 350 |
301 } | 373 } |
302 | 374 |
303 public int size() { | 375 public int size() { |
304 return size; | 376 return size; |
305 } | 377 } |
378 | |
379 @Override | |
380 public Record clone() { | |
381 try { | |
382 return (Record) super.clone(); | |
383 } catch (CloneNotSupportedException ex) { | |
384 throw new InternalError(ex.toString()); | |
385 } | |
386 } | |
306 } | 387 } |
307 } | 388 } |