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 }