diff 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
line wrap: on
line diff
--- a/src/org/tmatesoft/hg/repo/HgWorkingCopyStatusCollector.java	Thu Mar 15 16:51:46 2012 +0100
+++ b/src/org/tmatesoft/hg/repo/HgWorkingCopyStatusCollector.java	Wed Mar 21 20:40:28 2012 +0100
@@ -37,6 +37,7 @@
 import org.tmatesoft.hg.internal.ByteArrayChannel;
 import org.tmatesoft.hg.internal.Experimental;
 import org.tmatesoft.hg.internal.FilterByteChannel;
+import org.tmatesoft.hg.internal.Internals;
 import org.tmatesoft.hg.internal.ManifestRevision;
 import org.tmatesoft.hg.internal.PathScope;
 import org.tmatesoft.hg.internal.Preview;
@@ -287,27 +288,37 @@
 			// either clean or modified
 			final boolean timestampEqual = f.lastModified() == r.modificationTime(), sizeEqual = r.size() == f.length();
 			if (timestampEqual && sizeEqual) {
-				inspector.clean(fname);
+				// if flags change (chmod -x), timestamp does not change
+				if (checkFlagsEqual(f, r.mode())) {
+					inspector.clean(fname);
+				} else {
+					inspector.modified(fname); // flags are not the same
+				}
 			} else if (!sizeEqual && r.size() >= 0) {
 				inspector.modified(fname);
 			} else {
+				// size is the same or unknown, and, perhaps, different timestamp
+				// check actual content to avoid false modified files
 				try {
-					// size is the same or unknown, and, perhaps, different timestamp
-					// check actual content to avoid false modified files
-					HgDataFile df = repo.getFileNode(fname);
-					if (!df.exists()) {
-						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));
-						throw new HgInvalidFileException(msg, null).setFileName(fname);
-					}
-					Nodeid rev = getDirstateParentManifest().nodeid(fname);
-					// rev might be null here if fname comes to dirstate as a result of a merge operation
-					// where one of the parents (first parent) had no fname file, but second parent had.
-					// E.g. fork revision 3, revision 4 gets .hgtags, few modifications and merge(3,12)
-					// see Issue 14 for details
-					if (rev == null || !areTheSame(f, df, rev)) {
-						inspector.modified(df.getPath());
+					if (!checkFlagsEqual(f, r.mode())) {
+						// flags modified, no need to do expensive content check
+						inspector.modified(fname);
 					} else {
-						inspector.clean(df.getPath());
+						HgDataFile df = repo.getFileNode(fname);
+						if (!df.exists()) {
+							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));
+							throw new HgInvalidFileException(msg, null).setFileName(fname);
+						}
+						Nodeid rev = getDirstateParentManifest().nodeid(fname);
+						// rev might be null here if fname comes to dirstate as a result of a merge operation
+						// where one of the parents (first parent) had no fname file, but second parent had.
+						// E.g. fork revision 3, revision 4 gets .hgtags, few modifications and merge(3,12)
+						// see Issue 14 for details
+						if (rev == null || !areTheSame(f, df, rev)) {
+							inspector.modified(df.getPath());
+						} else {
+							inspector.clean(df.getPath());
+						}
 					}
 				} catch (HgException ex) {
 					repo.getContext().getLog().warn(getClass(), ex, null);
@@ -374,7 +385,7 @@
 				} else if (!sizeEqual && r.size() >= 0) {
 					inspector.modified(fname);
 					handled = true;
-				} else if (!todoCheckFlagsEqual(f, flags)) {
+				} else if (!checkFlagsEqual(f, flags)) {
 					// seems like flags have changed, no reason to check content further
 					inspector.modified(fname);
 					handled = true;
@@ -516,9 +527,39 @@
 		}
 	}
 
-	private static boolean todoCheckFlagsEqual(FileInfo f, HgManifest.Flags originalManifestFlags) {
-		// FIXME implement
-		return true;
+	/**
+	 * @return <code>true</code> if flags are the same
+	 */
+	private boolean checkFlagsEqual(FileInfo f, HgManifest.Flags originalManifestFlags) {
+		boolean same = true;
+		if (repoWalker.supportsLinkFlag()) {
+			if (originalManifestFlags == HgManifest.Flags.Link) {
+				return f.isSymlink();
+			}
+			// original flag is not link, hence flags are the same if file is not link, too.
+			same = !f.isSymlink();
+		} // otherwise treat flags the same
+		if (repoWalker.supportsExecFlag()) {
+			if (originalManifestFlags == HgManifest.Flags.Exec) {
+				return f.isExecutable();
+			}
+			// original flag has no executable attribute, hence file shall not be executable, too
+			same = same || !f.isExecutable();
+		}
+		return same;
+	}
+	
+	private boolean checkFlagsEqual(FileInfo f, int dirstateFileMode) {
+		// source/include/linux/stat.h
+		final int S_IFLNK = 0120000, S_IXUSR = 00100;
+		// TODO post-1.0 HgManifest.Flags.parse(int)
+		if ((dirstateFileMode & S_IFLNK) == S_IFLNK) {
+			return checkFlagsEqual(f, HgManifest.Flags.Link);
+		}
+		if ((dirstateFileMode & S_IXUSR) == S_IXUSR) {
+			return checkFlagsEqual(f, HgManifest.Flags.Exec);
+		}
+		return checkFlagsEqual(f, null); // no flags
 	}
 
 	/**
@@ -580,16 +621,19 @@
 		private final Path[] paths;
 		private int index;
 		private RegularFileInfo nextFile;
+		private final boolean execCap, linkCap;
 
 		public FileListIterator(File startDir, Path... files) {
 			dir = startDir;
 			paths = files;
 			reset();
+			execCap = Internals.checkSupportsExecutables(startDir);
+			linkCap = Internals.checkSupportsSymlinks(startDir);
 		}
 
 		public void reset() {
 			index = -1;
-			nextFile = new RegularFileInfo();
+			nextFile = new RegularFileInfo(execCap, linkCap);
 		}
 
 		public boolean hasNext() {
@@ -620,6 +664,16 @@
 			}
 			return false;
 		}
+		
+		public boolean supportsExecFlag() {
+			// TODO Auto-generated method stub
+			return false;
+		}
+		
+		public boolean supportsLinkFlag() {
+			// TODO Auto-generated method stub
+			return false;
+		}
 	}
 	
 	private static class FileIteratorFilter implements FileIterator {
@@ -670,5 +724,13 @@
 		public boolean inScope(Path file) {
 			return filter.accept(file);
 		}
+		
+		public boolean supportsExecFlag() {
+			return walker.supportsExecFlag();
+		}
+		
+		public boolean supportsLinkFlag() {
+			return walker.supportsLinkFlag();
+		}
 	}
 }