Skip to content

Commit

Permalink
sbt#125: Preserve the original file permission by default
Browse files Browse the repository at this point in the history
  • Loading branch information
xerial committed Jan 23, 2018
1 parent 5efad01 commit ddd1c1e
Show file tree
Hide file tree
Showing 4 changed files with 54 additions and 15 deletions.
25 changes: 16 additions & 9 deletions io/src/main/contraband-scala/sbt/io/CopyOptions.scala
Original file line number Diff line number Diff line change
Expand Up @@ -16,22 +16,25 @@ final class CopyOptions private (
/** If `true` the last modified times are copied. */
val preserveLastModified: Boolean,
/** If `true` the executable properties are copied. */
val preserveExecutable: Boolean) extends Serializable {
val preserveExecutable: Boolean,
/** If `true` the file permissions are copied. Setting this `true` disregards the effect of preserveExcecutable flag */
val preservePermission: Boolean) extends Serializable {

private def this() = this(false, false, true)
private def this() = this(false, false, true, true)
private def this(overwrite: Boolean, preserveLastModified: Boolean, preserveExecutable: Boolean) = this(overwrite, preserveLastModified, preserveExecutable, true)

override def equals(o: Any): Boolean = o match {
case x: CopyOptions => (this.overwrite == x.overwrite) && (this.preserveLastModified == x.preserveLastModified) && (this.preserveExecutable == x.preserveExecutable)
case x: CopyOptions => (this.overwrite == x.overwrite) && (this.preserveLastModified == x.preserveLastModified) && (this.preserveExecutable == x.preserveExecutable) && (this.preservePermission == x.preservePermission)
case _ => false
}
override def hashCode: Int = {
37 * (37 * (37 * (37 * (17 + "sbt.io.CopyOptions".##) + overwrite.##) + preserveLastModified.##) + preserveExecutable.##)
37 * (37 * (37 * (37 * (37 * (17 + "sbt.io.CopyOptions".##) + overwrite.##) + preserveLastModified.##) + preserveExecutable.##) + preservePermission.##)
}
override def toString: String = {
"CopyOptions(" + overwrite + ", " + preserveLastModified + ", " + preserveExecutable + ")"
"CopyOptions(" + overwrite + ", " + preserveLastModified + ", " + preserveExecutable + ", " + preservePermission + ")"
}
protected[this] def copy(overwrite: Boolean = overwrite, preserveLastModified: Boolean = preserveLastModified, preserveExecutable: Boolean = preserveExecutable): CopyOptions = {
new CopyOptions(overwrite, preserveLastModified, preserveExecutable)
protected[this] def copy(overwrite: Boolean = overwrite, preserveLastModified: Boolean = preserveLastModified, preserveExecutable: Boolean = preserveExecutable, preservePermission: Boolean = preservePermission): CopyOptions = {
new CopyOptions(overwrite, preserveLastModified, preserveExecutable, preservePermission)
}
def withOverwrite(overwrite: Boolean): CopyOptions = {
copy(overwrite = overwrite)
Expand All @@ -42,9 +45,13 @@ final class CopyOptions private (
def withPreserveExecutable(preserveExecutable: Boolean): CopyOptions = {
copy(preserveExecutable = preserveExecutable)
}
def withPreservePermission(preservePermission: Boolean): CopyOptions = {
copy(preservePermission = preservePermission)
}
}
object CopyOptions {

def apply(): CopyOptions = new CopyOptions(false, false, true)
def apply(overwrite: Boolean, preserveLastModified: Boolean, preserveExecutable: Boolean): CopyOptions = new CopyOptions(overwrite, preserveLastModified, preserveExecutable)
def apply(): CopyOptions = new CopyOptions(false, false, true, true)
def apply(overwrite: Boolean, preserveLastModified: Boolean, preserveExecutable: Boolean): CopyOptions = new CopyOptions(overwrite, preserveLastModified, preserveExecutable, true)
def apply(overwrite: Boolean, preserveLastModified: Boolean, preserveExecutable: Boolean, preservePermission: Boolean): CopyOptions = new CopyOptions(overwrite, preserveLastModified, preserveExecutable, preservePermission)
}
2 changes: 2 additions & 0 deletions io/src/main/contraband/io.contra
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,6 @@ type CopyOptions {
## If `true` the executable properties are copied.
preserveExecutable: boolean! = true @since("0.0.1")

## If `true` the file permissions are copied. Setting this `true` disregards the effect of preserveExcecutable flag
preservePermission: boolean! = true @since("0.0.2")
}
26 changes: 20 additions & 6 deletions io/src/main/scala/sbt/io/IO.scala
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
package sbt.io

import Using._

import sbt.internal.io.ErrorHandling.translate

import java.io.{
Expand All @@ -23,15 +24,18 @@ import java.nio.file.attribute.PosixFilePermissions
import java.util.Properties
import java.util.jar.{ Attributes, JarEntry, JarOutputStream, Manifest }
import java.util.zip.{ CRC32, ZipEntry, ZipInputStream, ZipOutputStream }

import sbt.internal.io.ErrorHandling.translate
import sbt.internal.io.Milli

import scala.Function.tupled
import scala.annotation.tailrec
import scala.collection.JavaConverters._
import scala.collection.immutable.TreeSet
import scala.collection.mutable.{ HashMap, HashSet }
import scala.reflect.{ Manifest => SManifest }
import scala.util.control.NonFatal
import scala.util.control.Exception._
import scala.collection.JavaConverters._
import Function.tupled
import sbt.internal.io.Milli
import scala.util.control.NonFatal

/** A collection of File, URL, and I/O utility methods.*/
object IO {
Expand Down Expand Up @@ -701,7 +705,8 @@ object IO {
sourceFile: File,
targetFile: File,
preserveLastModified: Boolean = false,
preserveExecutable: Boolean = true
preserveExecutable: Boolean = true,
preservePermission: Boolean = true
): Unit = {
// NOTE: when modifying this code, test with larger values of CopySpec.MaxFileSizeBits than default

Expand Down Expand Up @@ -731,12 +736,21 @@ object IO {
copyLastModified(sourceFile, targetFile)
()
}
if (preserveExecutable) {

// Copy the original file permission
if (preservePermission) {
copyPermission(sourceFile, targetFile)
} else if (preserveExecutable) {
copyExecutable(sourceFile, targetFile)
()
}
}

/** Transfers posix file permissions of `sourceFile` to `targetFile` **/
def copyPermission(sourceFile: File, targetFile: File) = {
new RichFile(targetFile).setPermissions(new RichFile(sourceFile).permissions)
}

/** Transfers the executable property of `sourceFile` to `targetFile`. */
def copyExecutable(sourceFile: File, targetFile: File) = {
val executable = sourceFile.canExecute
Expand Down
16 changes: 16 additions & 0 deletions io/src/test/scala/sbt/io/CopyDirectorySpec.scala
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
package sbt.io

import java.nio.file._
import java.nio.file.attribute.PosixFilePermissions

import org.scalatest.{ FlatSpec, Matchers }
import sbt.io.syntax._
import scala.collection.JavaConverters._

class CopyDirectorySpec extends FlatSpec with Matchers {
it should "copy symlinks" in IO.withTemporaryDirectory { dir =>
Expand All @@ -17,6 +20,7 @@ class CopyDirectorySpec extends FlatSpec with Matchers {
val srcFile2 = dir / "src" / "lib" / "a.txt"

IO.write(srcFile1, "this is the file contents")
srcFile1.setPermissions(PosixFilePermissions.fromString("rwxrwxr-x").asScala.toSet) // 775

IO.createDirectory(srcFile2.getParentFile)
Files.createSymbolicLink(srcFile2.toPath, Paths.get("../actual/a.txt"))
Expand All @@ -26,5 +30,17 @@ class CopyDirectorySpec extends FlatSpec with Matchers {

// Then: dst/lib/a.txt should have been created and have the correct contents
IO.read(dir / "dst" / "lib" / "a.txt") shouldBe "this is the file contents"

// Preserve the file permissions
val dstFile1 = dir / "dst" / "lib" / "a.txt"
dstFile1.isOwnerReadable shouldBe srcFile1.isOwnerReadable
dstFile1.isOwnerWritable shouldBe srcFile1.isOwnerWritable
dstFile1.isOwnerExecutable shouldBe srcFile1.isOwnerExecutable
dstFile1.isGroupReadable shouldBe srcFile1.isGroupReadable
dstFile1.isGroupWritable shouldBe srcFile1.isGroupWritable
dstFile1.isGroupExecutable shouldBe srcFile1.isGroupExecutable
dstFile1.isOthersReadable shouldBe srcFile1.isOthersReadable
dstFile1.isOthersWritable shouldBe srcFile1.isOthersWritable
dstFile1.isOthersExecutable shouldBe srcFile1.isOthersExecutable
}
}

0 comments on commit ddd1c1e

Please sign in to comment.