diff src/org/tmatesoft/hg/internal/WorkingDirFileWriter.java @ 580:bd5926e24aa3

Respect unix flags for checkout/revert
author Artem Tikhomirov <tikhomirov.artem@gmail.com>
date Fri, 19 Apr 2013 20:30:34 +0200
parents becd2a1310a2
children 6526d8adbc0f
line wrap: on
line diff
--- a/src/org/tmatesoft/hg/internal/WorkingDirFileWriter.java	Wed Apr 17 16:06:10 2013 +0200
+++ b/src/org/tmatesoft/hg/internal/WorkingDirFileWriter.java	Fri Apr 19 20:30:34 2013 +0200
@@ -16,6 +16,8 @@
  */
 package org.tmatesoft.hg.internal;
 
+import static org.tmatesoft.hg.util.LogFacility.Severity.Warn;
+
 import java.io.File;
 import java.io.FileOutputStream;
 import java.io.IOException;
@@ -23,6 +25,7 @@
 import java.nio.channels.FileChannel;
 
 import org.tmatesoft.hg.repo.HgDataFile;
+import org.tmatesoft.hg.repo.HgManifest;
 import org.tmatesoft.hg.util.ByteChannel;
 import org.tmatesoft.hg.util.CancelledException;
 import org.tmatesoft.hg.util.LogFacility.Severity;
@@ -37,29 +40,65 @@
 
 	
 	private final Internals hgRepo;
+	private final boolean execCap, symlinkCap;
+	private final FileSystemHelper fileFlagsHelper;
 	private File dest;
 	private FileChannel destChannel;
 	private int totalBytesWritten;
+	private ByteArrayChannel linkChannel;
+	private int fmode;
 
 	public WorkingDirFileWriter(Internals internalRepo) {
 		hgRepo = internalRepo;
+		execCap = Internals.checkSupportsExecutables(internalRepo.getRepo().getWorkingDir());
+		symlinkCap = Internals.checkSupportsSymlinks(internalRepo.getRepo().getWorkingDir());
+		if (symlinkCap || execCap) {
+			fileFlagsHelper = new FileSystemHelper(internalRepo.getSessionContext());
+		} else  {
+			fileFlagsHelper = null;
+		}
 	}
 	
 	/**
-	 * Information purposes only, to find out trouble location if {@link #processFile(HgDataFile, int)} fails
+	 * Writes content of specified file revision into local filesystem, or create a symlink according to flags. 
+	 * Executable bit is set if specified and filesystem supports it. 
 	 */
-	public File getDestinationFile() {
-		return dest;
-	}
-	
-	public void processFile(HgDataFile df, int fileRevIndex) throws IOException {
+	public void processFile(HgDataFile df, int fileRevIndex, HgManifest.Flags flags) throws IOException {
 		try {
 			prepare(df.getPath());
+			if (flags != HgManifest.Flags.Link) {
+				destChannel = new FileOutputStream(dest).getChannel();
+			} else {
+				linkChannel = new ByteArrayChannel();
+			}
 			df.contentWithFilters(fileRevIndex, this);
 		} catch (CancelledException ex) {
 			hgRepo.getSessionContext().getLog().dump(getClass(), Severity.Error, ex, "Our impl doesn't throw cancellation");
+		} finally {
+			if (flags != HgManifest.Flags.Link) {
+				destChannel.close();
+				destChannel = null;
+				// leave dest in case anyone enquires with #getDestinationFile
+			}
 		}
-		finish();
+		if (linkChannel != null && symlinkCap) {
+			assert flags == HgManifest.Flags.Link;
+			fileFlagsHelper.createSymlink(dest.getParentFile(), dest.getName(), linkChannel.toArray());
+		} else if (flags == HgManifest.Flags.Exec && execCap) {
+			fileFlagsHelper.setExecutableBit(dest.getParentFile(), dest.getName());
+		}
+		// Although HgWCStatusCollector treats 644 (`hg manifest -v`) and 664 (my fs) the same, it's better
+		// to detect actual flags here
+		fmode = flags.fsMode(); // default to one from manifest
+		if (fileFlagsHelper != null) {
+			// if neither execBit nor link is supported by fs, it's unlikely file mode is supported, too.
+			try {
+				fmode = fileFlagsHelper.getFileMode(dest, fmode);
+			} catch (IOException ex) {
+				// Warn, we've got default value and can live with it
+				hgRepo.getSessionContext().getLog().dump(getClass(), Warn, ex, "Failed get file access rights");
+			}
+		}
 	}
 
 	public void prepare(Path fname) throws IOException {
@@ -68,22 +107,39 @@
 		if (fpath.indexOf('/') != -1) {
 			dest.getParentFile().mkdirs();
 		}
-		destChannel = new FileOutputStream(dest).getChannel();
+		destChannel = null;
+		linkChannel = null;
 		totalBytesWritten = 0;
+		fmode = 0;
 	}
 
 	public int write(ByteBuffer buffer) throws IOException, CancelledException {
-		int written = destChannel.write(buffer);
+		final int written;
+		if (linkChannel != null) {
+			written = linkChannel.write(buffer);
+		} else {
+			written = destChannel.write(buffer);
+		}
 		totalBytesWritten += written;
 		return written;
 	}
 
-	public void finish() throws IOException {
-		destChannel.close();
-		dest = null;
+	/**
+	 * Information purposes only, to find out trouble location if {@link #processFile(HgDataFile, int)} fails
+	 */
+	public File getDestinationFile() {
+		return dest;
 	}
 	
 	public int bytesWritten() {
 		return totalBytesWritten;
 	}
+
+	public int fmode() {
+		return fmode;
+	}
+
+	public int mtime() {
+		return (int) (dest.lastModified() / 1000);
+	}
 }