Mercurial > hg4j
comparison src/org/tmatesoft/hg/repo/HgWorkingCopyStatusCollector.java @ 287:ed6b74a58c66
Use FileInfo abstraction with necessary subset of File functionality instead of File to facilitate other effective file system iterators
| author | Artem Tikhomirov <tikhomirov.artem@gmail.com> |
|---|---|
| date | Wed, 07 Sep 2011 09:33:27 +0200 |
| parents | 6dbbc53fc46d |
| children | 8faad08c709b |
comparison
equal
deleted
inserted
replaced
| 286:954763c82cc3 | 287:ed6b74a58c66 |
|---|---|
| 19 import static java.lang.Math.max; | 19 import static java.lang.Math.max; |
| 20 import static java.lang.Math.min; | 20 import static java.lang.Math.min; |
| 21 import static org.tmatesoft.hg.repo.HgRepository.*; | 21 import static org.tmatesoft.hg.repo.HgRepository.*; |
| 22 | 22 |
| 23 import java.io.File; | 23 import java.io.File; |
| 24 import java.io.FileInputStream; | |
| 25 import java.io.IOException; | 24 import java.io.IOException; |
| 26 import java.nio.ByteBuffer; | 25 import java.nio.ByteBuffer; |
| 27 import java.nio.channels.FileChannel; | 26 import java.nio.channels.ReadableByteChannel; |
| 28 import java.util.ArrayList; | 27 import java.util.ArrayList; |
| 29 import java.util.Collections; | 28 import java.util.Collections; |
| 30 import java.util.NoSuchElementException; | 29 import java.util.NoSuchElementException; |
| 31 import java.util.Set; | 30 import java.util.Set; |
| 32 import java.util.TreeSet; | 31 import java.util.TreeSet; |
| 39 import org.tmatesoft.hg.internal.FilterByteChannel; | 38 import org.tmatesoft.hg.internal.FilterByteChannel; |
| 40 import org.tmatesoft.hg.internal.ManifestRevision; | 39 import org.tmatesoft.hg.internal.ManifestRevision; |
| 41 import org.tmatesoft.hg.internal.PathScope; | 40 import org.tmatesoft.hg.internal.PathScope; |
| 42 import org.tmatesoft.hg.util.ByteChannel; | 41 import org.tmatesoft.hg.util.ByteChannel; |
| 43 import org.tmatesoft.hg.util.CancelledException; | 42 import org.tmatesoft.hg.util.CancelledException; |
| 43 import org.tmatesoft.hg.util.FileInfo; | |
| 44 import org.tmatesoft.hg.util.FileIterator; | 44 import org.tmatesoft.hg.util.FileIterator; |
| 45 import org.tmatesoft.hg.util.FileWalker; | 45 import org.tmatesoft.hg.util.FileWalker; |
| 46 import org.tmatesoft.hg.util.Path; | 46 import org.tmatesoft.hg.util.Path; |
| 47 import org.tmatesoft.hg.util.PathPool; | 47 import org.tmatesoft.hg.util.PathPool; |
| 48 import org.tmatesoft.hg.util.PathRewrite; | 48 import org.tmatesoft.hg.util.PathRewrite; |
| 49 import org.tmatesoft.hg.util.RegularFileInfo; | |
| 49 | 50 |
| 50 /** | 51 /** |
| 51 * | 52 * |
| 52 * @author Artem Tikhomirov | 53 * @author Artem Tikhomirov |
| 53 * @author TMate Software Ltd. | 54 * @author TMate Software Ltd. |
| 160 TreeSet<Path> knownEntries = getDirstate().all(); | 161 TreeSet<Path> knownEntries = getDirstate().all(); |
| 161 repoWalker.reset(); | 162 repoWalker.reset(); |
| 162 while (repoWalker.hasNext()) { | 163 while (repoWalker.hasNext()) { |
| 163 repoWalker.next(); | 164 repoWalker.next(); |
| 164 final Path fname = getPathPool().path(repoWalker.name()); | 165 final Path fname = getPathPool().path(repoWalker.name()); |
| 165 File f = repoWalker.file(); | 166 FileInfo f = repoWalker.file(); |
| 166 if (!f.exists()) { | 167 if (!f.exists()) { |
| 167 // file coming from iterator doesn't exist. | 168 // file coming from iterator doesn't exist. |
| 168 if (knownEntries.remove(fname)) { | 169 if (knownEntries.remove(fname)) { |
| 169 if (getDirstate().checkRemoved(fname) == null) { | 170 if (getDirstate().checkRemoved(fname) == null) { |
| 170 inspector.missing(fname); | 171 inspector.missing(fname); |
| 191 inspector.unknown(fname); | 192 inspector.unknown(fname); |
| 192 } | 193 } |
| 193 } | 194 } |
| 194 continue; | 195 continue; |
| 195 } | 196 } |
| 196 assert f.isFile(); | |
| 197 if (knownEntries.remove(fname)) { | 197 if (knownEntries.remove(fname)) { |
| 198 // tracked file. | 198 // tracked file. |
| 199 // modified, added, removed, clean | 199 // modified, added, removed, clean |
| 200 if (collect != null) { // need to check against base revision, not FS file | 200 if (collect != null) { // need to check against base revision, not FS file |
| 201 checkLocalStatusAgainstBaseRevision(baseRevFiles, collect, baseRevision, fname, f, inspector); | 201 checkLocalStatusAgainstBaseRevision(baseRevFiles, collect, baseRevision, fname, f, inspector); |
| 246 } | 246 } |
| 247 | 247 |
| 248 //******************************************** | 248 //******************************************** |
| 249 | 249 |
| 250 | 250 |
| 251 private void checkLocalStatusAgainstFile(Path fname, File f, HgStatusInspector inspector) { | 251 private void checkLocalStatusAgainstFile(Path fname, FileInfo f, HgStatusInspector inspector) { |
| 252 HgDirstate.Record r; | 252 HgDirstate.Record r; |
| 253 if ((r = getDirstate().checkNormal(fname)) != null) { | 253 if ((r = getDirstate().checkNormal(fname)) != null) { |
| 254 // either clean or modified | 254 // either clean or modified |
| 255 final boolean timestampEqual = getFileModificationTime(f) == r.time, sizeEqual = r.size == f.length(); | 255 final boolean timestampEqual = f.lastModified() == r.time, sizeEqual = r.size == f.length(); |
| 256 if (timestampEqual && sizeEqual) { | 256 if (timestampEqual && sizeEqual) { |
| 257 inspector.clean(fname); | 257 inspector.clean(fname); |
| 258 } else if (!sizeEqual && r.size >= 0) { | 258 } else if (!sizeEqual && r.size >= 0) { |
| 259 inspector.modified(fname); | 259 inspector.modified(fname); |
| 260 } else { | 260 } else { |
| 279 } else if ((r = getDirstate().checkMerged(fname)) != null) { | 279 } else if ((r = getDirstate().checkMerged(fname)) != null) { |
| 280 inspector.modified(fname); | 280 inspector.modified(fname); |
| 281 } | 281 } |
| 282 } | 282 } |
| 283 | 283 |
| 284 // return mtime analog, directly comparable to dirstate's mtime. | |
| 285 private static int getFileModificationTime(File f) { | |
| 286 return (int) (f.lastModified() / 1000); | |
| 287 } | |
| 288 | |
| 289 // XXX refactor checkLocalStatus methods in more OO way | 284 // XXX refactor checkLocalStatus methods in more OO way |
| 290 private void checkLocalStatusAgainstBaseRevision(Set<Path> baseRevNames, ManifestRevision collect, int baseRevision, Path fname, File f, HgStatusInspector inspector) { | 285 private void checkLocalStatusAgainstBaseRevision(Set<Path> baseRevNames, ManifestRevision collect, int baseRevision, Path fname, FileInfo f, HgStatusInspector inspector) { |
| 291 // fname is in the dirstate, either Normal, Added, Removed or Merged | 286 // fname is in the dirstate, either Normal, Added, Removed or Merged |
| 292 Nodeid nid1 = collect.nodeid(fname); | 287 Nodeid nid1 = collect.nodeid(fname); |
| 293 HgManifest.Flags flags = collect.flags(fname); | 288 HgManifest.Flags flags = collect.flags(fname); |
| 294 HgDirstate.Record r; | 289 HgDirstate.Record r; |
| 295 if (nid1 == null) { | 290 if (nid1 == null) { |
| 323 // was known; check whether clean or modified | 318 // was known; check whether clean or modified |
| 324 Nodeid nidFromDirstate = getDirstateParentManifest().nodeid(fname); | 319 Nodeid nidFromDirstate = getDirstateParentManifest().nodeid(fname); |
| 325 if ((r = getDirstate().checkNormal(fname)) != null && nid1.equals(nidFromDirstate)) { | 320 if ((r = getDirstate().checkNormal(fname)) != null && nid1.equals(nidFromDirstate)) { |
| 326 // regular file, was the same up to WC initialization. Check if was modified since, and, if not, report right away | 321 // regular file, was the same up to WC initialization. Check if was modified since, and, if not, report right away |
| 327 // same code as in #checkLocalStatusAgainstFile | 322 // same code as in #checkLocalStatusAgainstFile |
| 328 final boolean timestampEqual = getFileModificationTime(f) == r.time, sizeEqual = r.size == f.length(); | 323 final boolean timestampEqual = f.lastModified() == r.time, sizeEqual = r.size == f.length(); |
| 329 boolean handled = false; | 324 boolean handled = false; |
| 330 if (timestampEqual && sizeEqual) { | 325 if (timestampEqual && sizeEqual) { |
| 331 inspector.clean(fname); | 326 inspector.clean(fname); |
| 332 handled = true; | 327 handled = true; |
| 333 } else if (!sizeEqual && r.size >= 0) { | 328 } else if (!sizeEqual && r.size >= 0) { |
| 371 // changeset nodeid + hash(actual content) => entry (Nodeid) in the next Manifest | 366 // changeset nodeid + hash(actual content) => entry (Nodeid) in the next Manifest |
| 372 // then it's sufficient to check parents from dirstate, and if they do not match parents from file's baseRevision (non matching parents means different nodeids). | 367 // then it's sufficient to check parents from dirstate, and if they do not match parents from file's baseRevision (non matching parents means different nodeids). |
| 373 // The question is whether original Hg treats this case (same content, different parents and hence nodeids) as 'modified' or 'clean' | 368 // The question is whether original Hg treats this case (same content, different parents and hence nodeids) as 'modified' or 'clean' |
| 374 } | 369 } |
| 375 | 370 |
| 376 private boolean areTheSame(File f, HgDataFile dataFile, Nodeid revision) { | 371 private boolean areTheSame(FileInfo f, HgDataFile dataFile, Nodeid revision) { |
| 377 // XXX consider adding HgDataDile.compare(File/byte[]/whatever) operation to optimize comparison | 372 // XXX consider adding HgDataDile.compare(File/byte[]/whatever) operation to optimize comparison |
| 378 ByteArrayChannel bac = new ByteArrayChannel(); | 373 ByteArrayChannel bac = new ByteArrayChannel(); |
| 379 boolean ioFailed = false; | 374 boolean ioFailed = false; |
| 380 try { | 375 try { |
| 381 int localRevision = dataFile.getLocalRevision(revision); | 376 int localRevision = dataFile.getLocalRevision(revision); |
| 388 ioFailed = true; | 383 ioFailed = true; |
| 389 } | 384 } |
| 390 return !ioFailed && areTheSame(f, bac.toArray(), dataFile.getPath()); | 385 return !ioFailed && areTheSame(f, bac.toArray(), dataFile.getPath()); |
| 391 } | 386 } |
| 392 | 387 |
| 393 private boolean areTheSame(File f, final byte[] data, Path p) { | 388 private boolean areTheSame(FileInfo f, final byte[] data, Path p) { |
| 394 FileInputStream fis = null; | 389 ReadableByteChannel is = null; |
| 395 try { | 390 try { |
| 396 try { | 391 try { |
| 397 fis = new FileInputStream(f); | 392 is = f.newInputChannel(); |
| 398 FileChannel fc = fis.getChannel(); | |
| 399 ByteBuffer fb = ByteBuffer.allocate(min(1 + data.length * 2 /*to fit couple of lines appended; never zero*/, 8192)); | 393 ByteBuffer fb = ByteBuffer.allocate(min(1 + data.length * 2 /*to fit couple of lines appended; never zero*/, 8192)); |
| 400 class Check implements ByteChannel { | 394 class Check implements ByteChannel { |
| 401 final boolean debug = false; // XXX may want to add global variable to allow clients to turn | 395 final boolean debug = false; // XXX may want to add global variable to allow clients to turn |
| 402 boolean sameSoFar = true; | 396 boolean sameSoFar = true; |
| 403 int x = 0; | 397 int x = 0; |
| 429 return sameSoFar && x == data.length; | 423 return sameSoFar && x == data.length; |
| 430 } | 424 } |
| 431 }; | 425 }; |
| 432 Check check = new Check(); | 426 Check check = new Check(); |
| 433 FilterByteChannel filters = new FilterByteChannel(check, repo.getFiltersFromWorkingDirToRepo(p)); | 427 FilterByteChannel filters = new FilterByteChannel(check, repo.getFiltersFromWorkingDirToRepo(p)); |
| 434 while (fc.read(fb) != -1 && check.sameSoFar()) { | 428 while (is.read(fb) != -1 && check.sameSoFar()) { |
| 435 fb.flip(); | 429 fb.flip(); |
| 436 filters.write(fb); | 430 filters.write(fb); |
| 437 fb.compact(); | 431 fb.compact(); |
| 438 } | 432 } |
| 439 fis.close(); | |
| 440 return check.ultimatelyTheSame(); | 433 return check.ultimatelyTheSame(); |
| 441 } catch (IOException ex) { | 434 } catch (IOException ex) { |
| 442 if (fis != null) { | |
| 443 fis.close(); | |
| 444 } | |
| 445 ex.printStackTrace(); // log warn | 435 ex.printStackTrace(); // log warn |
| 436 } finally { | |
| 437 if (is != null) { | |
| 438 is.close(); | |
| 439 } | |
| 446 } | 440 } |
| 447 } catch (/*TODO typed*/Exception ex) { | 441 } catch (/*TODO typed*/Exception ex) { |
| 448 ex.printStackTrace(); | 442 ex.printStackTrace(); |
| 449 } | 443 } |
| 450 return false; | 444 return false; |
| 451 } | 445 } |
| 452 | 446 |
| 453 private static boolean todoCheckFlagsEqual(File f, HgManifest.Flags originalManifestFlags) { | 447 private static boolean todoCheckFlagsEqual(FileInfo f, HgManifest.Flags originalManifestFlags) { |
| 454 // FIXME implement | 448 // FIXME implement |
| 455 return true; | 449 return true; |
| 456 } | 450 } |
| 457 | 451 |
| 458 /** | 452 /** |
| 511 | 505 |
| 512 private static class FileListIterator implements FileIterator { | 506 private static class FileListIterator implements FileIterator { |
| 513 private final File dir; | 507 private final File dir; |
| 514 private final Path[] paths; | 508 private final Path[] paths; |
| 515 private int index; | 509 private int index; |
| 516 private File nextFile; // cache file() in case it's called more than once | 510 private RegularFileInfo nextFile; |
| 517 | 511 |
| 518 public FileListIterator(File startDir, Path... files) { | 512 public FileListIterator(File startDir, Path... files) { |
| 519 dir = startDir; | 513 dir = startDir; |
| 520 paths = files; | 514 paths = files; |
| 521 reset(); | 515 reset(); |
| 522 } | 516 } |
| 523 | 517 |
| 524 public void reset() { | 518 public void reset() { |
| 525 index = -1; | 519 index = -1; |
| 526 nextFile = null; | 520 nextFile = new RegularFileInfo(); |
| 527 } | 521 } |
| 528 | 522 |
| 529 public boolean hasNext() { | 523 public boolean hasNext() { |
| 530 return paths.length > 0 && index < paths.length-1; | 524 return paths.length > 0 && index < paths.length-1; |
| 531 } | 525 } |
| 533 public void next() { | 527 public void next() { |
| 534 index++; | 528 index++; |
| 535 if (index == paths.length) { | 529 if (index == paths.length) { |
| 536 throw new NoSuchElementException(); | 530 throw new NoSuchElementException(); |
| 537 } | 531 } |
| 538 nextFile = new File(dir, paths[index].toString()); | 532 nextFile.init(new File(dir, paths[index].toString())); |
| 539 } | 533 } |
| 540 | 534 |
| 541 public Path name() { | 535 public Path name() { |
| 542 return paths[index]; | 536 return paths[index]; |
| 543 } | 537 } |
| 544 | 538 |
| 545 public File file() { | 539 public FileInfo file() { |
| 546 return nextFile; | 540 return nextFile; |
| 547 } | 541 } |
| 548 | 542 |
| 549 public boolean inScope(Path file) { | 543 public boolean inScope(Path file) { |
| 550 for (int i = 0; i < paths.length; i++) { | 544 for (int i = 0; i < paths.length; i++) { |
| 595 | 589 |
| 596 public Path name() { | 590 public Path name() { |
| 597 return walker.name(); | 591 return walker.name(); |
| 598 } | 592 } |
| 599 | 593 |
| 600 public File file() { | 594 public FileInfo file() { |
| 601 return walker.file(); | 595 return walker.file(); |
| 602 } | 596 } |
| 603 | 597 |
| 604 public boolean inScope(Path file) { | 598 public boolean inScope(Path file) { |
| 605 return filter.accept(file); | 599 return filter.accept(file); |
