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 }