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