Mercurial > hg4j
comparison src/org/tmatesoft/hg/repo/HgWorkingCopyStatusCollector.java @ 413:7f27122011c3
Support and respect for symbolic links and executable flag, with /bin/ls backed implementation to discover these
author | Artem Tikhomirov <tikhomirov.artem@gmail.com> |
---|---|
date | Wed, 21 Mar 2012 20:40:28 +0100 |
parents | 866fc3b597a0 |
children | ee8264d80747 |
comparison
equal
deleted
inserted
replaced
406:d56ea1a2537a | 413:7f27122011c3 |
---|---|
35 import org.tmatesoft.hg.core.HgInvalidFileException; | 35 import org.tmatesoft.hg.core.HgInvalidFileException; |
36 import org.tmatesoft.hg.core.Nodeid; | 36 import org.tmatesoft.hg.core.Nodeid; |
37 import org.tmatesoft.hg.internal.ByteArrayChannel; | 37 import org.tmatesoft.hg.internal.ByteArrayChannel; |
38 import org.tmatesoft.hg.internal.Experimental; | 38 import org.tmatesoft.hg.internal.Experimental; |
39 import org.tmatesoft.hg.internal.FilterByteChannel; | 39 import org.tmatesoft.hg.internal.FilterByteChannel; |
40 import org.tmatesoft.hg.internal.Internals; | |
40 import org.tmatesoft.hg.internal.ManifestRevision; | 41 import org.tmatesoft.hg.internal.ManifestRevision; |
41 import org.tmatesoft.hg.internal.PathScope; | 42 import org.tmatesoft.hg.internal.PathScope; |
42 import org.tmatesoft.hg.internal.Preview; | 43 import org.tmatesoft.hg.internal.Preview; |
43 import org.tmatesoft.hg.util.Adaptable; | 44 import org.tmatesoft.hg.util.Adaptable; |
44 import org.tmatesoft.hg.util.ByteChannel; | 45 import org.tmatesoft.hg.util.ByteChannel; |
285 HgDirstate.Record r; | 286 HgDirstate.Record r; |
286 if ((r = getDirstateImpl().checkNormal(fname)) != null) { | 287 if ((r = getDirstateImpl().checkNormal(fname)) != null) { |
287 // either clean or modified | 288 // either clean or modified |
288 final boolean timestampEqual = f.lastModified() == r.modificationTime(), sizeEqual = r.size() == f.length(); | 289 final boolean timestampEqual = f.lastModified() == r.modificationTime(), sizeEqual = r.size() == f.length(); |
289 if (timestampEqual && sizeEqual) { | 290 if (timestampEqual && sizeEqual) { |
290 inspector.clean(fname); | 291 // if flags change (chmod -x), timestamp does not change |
292 if (checkFlagsEqual(f, r.mode())) { | |
293 inspector.clean(fname); | |
294 } else { | |
295 inspector.modified(fname); // flags are not the same | |
296 } | |
291 } else if (!sizeEqual && r.size() >= 0) { | 297 } else if (!sizeEqual && r.size() >= 0) { |
292 inspector.modified(fname); | 298 inspector.modified(fname); |
293 } else { | 299 } else { |
300 // size is the same or unknown, and, perhaps, different timestamp | |
301 // check actual content to avoid false modified files | |
294 try { | 302 try { |
295 // size is the same or unknown, and, perhaps, different timestamp | 303 if (!checkFlagsEqual(f, r.mode())) { |
296 // check actual content to avoid false modified files | 304 // flags modified, no need to do expensive content check |
297 HgDataFile df = repo.getFileNode(fname); | 305 inspector.modified(fname); |
298 if (!df.exists()) { | |
299 String msg = String.format("File %s known as normal in dirstate (%d, %d), doesn't exist at %s", fname, r.modificationTime(), r.size(), repo.getStoragePath(df)); | |
300 throw new HgInvalidFileException(msg, null).setFileName(fname); | |
301 } | |
302 Nodeid rev = getDirstateParentManifest().nodeid(fname); | |
303 // rev might be null here if fname comes to dirstate as a result of a merge operation | |
304 // where one of the parents (first parent) had no fname file, but second parent had. | |
305 // E.g. fork revision 3, revision 4 gets .hgtags, few modifications and merge(3,12) | |
306 // see Issue 14 for details | |
307 if (rev == null || !areTheSame(f, df, rev)) { | |
308 inspector.modified(df.getPath()); | |
309 } else { | 306 } else { |
310 inspector.clean(df.getPath()); | 307 HgDataFile df = repo.getFileNode(fname); |
308 if (!df.exists()) { | |
309 String msg = String.format("File %s known as normal in dirstate (%d, %d), doesn't exist at %s", fname, r.modificationTime(), r.size(), repo.getStoragePath(df)); | |
310 throw new HgInvalidFileException(msg, null).setFileName(fname); | |
311 } | |
312 Nodeid rev = getDirstateParentManifest().nodeid(fname); | |
313 // rev might be null here if fname comes to dirstate as a result of a merge operation | |
314 // where one of the parents (first parent) had no fname file, but second parent had. | |
315 // E.g. fork revision 3, revision 4 gets .hgtags, few modifications and merge(3,12) | |
316 // see Issue 14 for details | |
317 if (rev == null || !areTheSame(f, df, rev)) { | |
318 inspector.modified(df.getPath()); | |
319 } else { | |
320 inspector.clean(df.getPath()); | |
321 } | |
311 } | 322 } |
312 } catch (HgException ex) { | 323 } catch (HgException ex) { |
313 repo.getContext().getLog().warn(getClass(), ex, null); | 324 repo.getContext().getLog().warn(getClass(), ex, null); |
314 inspector.invalid(fname, ex); | 325 inspector.invalid(fname, ex); |
315 } | 326 } |
372 inspector.clean(fname); | 383 inspector.clean(fname); |
373 handled = true; | 384 handled = true; |
374 } else if (!sizeEqual && r.size() >= 0) { | 385 } else if (!sizeEqual && r.size() >= 0) { |
375 inspector.modified(fname); | 386 inspector.modified(fname); |
376 handled = true; | 387 handled = true; |
377 } else if (!todoCheckFlagsEqual(f, flags)) { | 388 } else if (!checkFlagsEqual(f, flags)) { |
378 // seems like flags have changed, no reason to check content further | 389 // seems like flags have changed, no reason to check content further |
379 inspector.modified(fname); | 390 inspector.modified(fname); |
380 handled = true; | 391 handled = true; |
381 } | 392 } |
382 if (handled) { | 393 if (handled) { |
514 } | 525 } |
515 } | 526 } |
516 } | 527 } |
517 } | 528 } |
518 | 529 |
519 private static boolean todoCheckFlagsEqual(FileInfo f, HgManifest.Flags originalManifestFlags) { | 530 /** |
520 // FIXME implement | 531 * @return <code>true</code> if flags are the same |
521 return true; | 532 */ |
533 private boolean checkFlagsEqual(FileInfo f, HgManifest.Flags originalManifestFlags) { | |
534 boolean same = true; | |
535 if (repoWalker.supportsLinkFlag()) { | |
536 if (originalManifestFlags == HgManifest.Flags.Link) { | |
537 return f.isSymlink(); | |
538 } | |
539 // original flag is not link, hence flags are the same if file is not link, too. | |
540 same = !f.isSymlink(); | |
541 } // otherwise treat flags the same | |
542 if (repoWalker.supportsExecFlag()) { | |
543 if (originalManifestFlags == HgManifest.Flags.Exec) { | |
544 return f.isExecutable(); | |
545 } | |
546 // original flag has no executable attribute, hence file shall not be executable, too | |
547 same = same || !f.isExecutable(); | |
548 } | |
549 return same; | |
550 } | |
551 | |
552 private boolean checkFlagsEqual(FileInfo f, int dirstateFileMode) { | |
553 // source/include/linux/stat.h | |
554 final int S_IFLNK = 0120000, S_IXUSR = 00100; | |
555 // TODO post-1.0 HgManifest.Flags.parse(int) | |
556 if ((dirstateFileMode & S_IFLNK) == S_IFLNK) { | |
557 return checkFlagsEqual(f, HgManifest.Flags.Link); | |
558 } | |
559 if ((dirstateFileMode & S_IXUSR) == S_IXUSR) { | |
560 return checkFlagsEqual(f, HgManifest.Flags.Exec); | |
561 } | |
562 return checkFlagsEqual(f, null); // no flags | |
522 } | 563 } |
523 | 564 |
524 /** | 565 /** |
525 * Configure status collector to consider only subset of a working copy tree. Tries to be as effective as possible, and to | 566 * Configure status collector to consider only subset of a working copy tree. Tries to be as effective as possible, and to |
526 * traverse only relevant part of working copy on the filesystem. | 567 * traverse only relevant part of working copy on the filesystem. |
578 private static class FileListIterator implements FileIterator { | 619 private static class FileListIterator implements FileIterator { |
579 private final File dir; | 620 private final File dir; |
580 private final Path[] paths; | 621 private final Path[] paths; |
581 private int index; | 622 private int index; |
582 private RegularFileInfo nextFile; | 623 private RegularFileInfo nextFile; |
624 private final boolean execCap, linkCap; | |
583 | 625 |
584 public FileListIterator(File startDir, Path... files) { | 626 public FileListIterator(File startDir, Path... files) { |
585 dir = startDir; | 627 dir = startDir; |
586 paths = files; | 628 paths = files; |
587 reset(); | 629 reset(); |
630 execCap = Internals.checkSupportsExecutables(startDir); | |
631 linkCap = Internals.checkSupportsSymlinks(startDir); | |
588 } | 632 } |
589 | 633 |
590 public void reset() { | 634 public void reset() { |
591 index = -1; | 635 index = -1; |
592 nextFile = new RegularFileInfo(); | 636 nextFile = new RegularFileInfo(execCap, linkCap); |
593 } | 637 } |
594 | 638 |
595 public boolean hasNext() { | 639 public boolean hasNext() { |
596 return paths.length > 0 && index < paths.length-1; | 640 return paths.length > 0 && index < paths.length-1; |
597 } | 641 } |
616 for (int i = 0; i < paths.length; i++) { | 660 for (int i = 0; i < paths.length; i++) { |
617 if (paths[i].equals(file)) { | 661 if (paths[i].equals(file)) { |
618 return true; | 662 return true; |
619 } | 663 } |
620 } | 664 } |
665 return false; | |
666 } | |
667 | |
668 public boolean supportsExecFlag() { | |
669 // TODO Auto-generated method stub | |
670 return false; | |
671 } | |
672 | |
673 public boolean supportsLinkFlag() { | |
674 // TODO Auto-generated method stub | |
621 return false; | 675 return false; |
622 } | 676 } |
623 } | 677 } |
624 | 678 |
625 private static class FileIteratorFilter implements FileIterator { | 679 private static class FileIteratorFilter implements FileIterator { |
668 } | 722 } |
669 | 723 |
670 public boolean inScope(Path file) { | 724 public boolean inScope(Path file) { |
671 return filter.accept(file); | 725 return filter.accept(file); |
672 } | 726 } |
727 | |
728 public boolean supportsExecFlag() { | |
729 return walker.supportsExecFlag(); | |
730 } | |
731 | |
732 public boolean supportsLinkFlag() { | |
733 return walker.supportsLinkFlag(); | |
734 } | |
673 } | 735 } |
674 } | 736 } |