Repository: otiai10/copy
Pull request #67 (2021-11-09 03:43:10)

Add PreserveOwner
Fixes https://github.com/otiai10/copy/issues/48

Many thanks to @mudler 


Patch:
diff --git a/copy.go b/copy.go
index 1201f18..ae04036 100644
--- a/copy.go
+++ b/copy.go
@@ -104,8 +104,15 @@ func fcopy(src, dest string, info os.FileInfo, opt Options) (err error) {
 		err = f.Sync()
 	}
 
+	if opt.PreserveOwner {
+		if err := preserveOwner(src, dest, info); err != nil {
+			return err
+		}
+	}
 	if opt.PreserveTimes {
-		return preserveTimes(info, dest)
+		if err := preserveTimes(info, dest); err != nil {
+			return err
+		}
 	}
 
 	return
@@ -154,7 +161,15 @@ func dcopy(srcdir, destdir string, info os.FileInfo, opt Options) (err error) {
 	}
 
 	if opt.PreserveTimes {
-		return preserveTimes(info, destdir)
+		if err := preserveTimes(info, destdir); err != nil {
+			return err
+		}
+	}
+
+	if opt.PreserveOwner {
+		if err := preserveOwner(srcdir, destdir, info); err != nil {
+			return err
+		}
 	}
 
 	return
diff --git a/fileinfo.go b/fileinfo.go
new file mode 100644
index 0000000..0b32abe
--- /dev/null
+++ b/fileinfo.go
@@ -0,0 +1,12 @@
+package copy
+
+// This is a cloned definition of os.FileInfo (go1.15) or fs.FileInfo (go1.16~)
+// A FileInfo describes a file and is returned by Stat.
+type fileInfo interface {
+	// Name() string       // base name of the file
+	// Size() int64        // length in bytes for regular files; system-dependent for others
+	// Mode() FileMode     // file mode bits
+	// ModTime() time.Time // modification time
+	// IsDir() bool        // abbreviation for Mode().IsDir()
+	Sys() interface{} // underlying data source (can return nil)
+}
diff --git a/options.go b/options.go
index 18e87ac..8df4402 100644
--- a/options.go
+++ b/options.go
@@ -28,6 +28,9 @@ type Options struct {
 	// On linux we can preserve only up to 1 millisecond accuracy.
 	PreserveTimes bool
 
+	// Preserve the uid and the gid of
+	PreserveOwner bool
+
 	// The byte size of the buffer to use for copying files.
 	// If zero, the internal default buffer of 32KB is used.
 	// See https://golang.org/pkg/io/#CopyBuffer for more information.
diff --git a/preserve_owner.go b/preserve_owner.go
new file mode 100644
index 0000000..0401d9f
--- /dev/null
+++ b/preserve_owner.go
@@ -0,0 +1,22 @@
+//+build !windows
+
+package copy
+
+import (
+	"os"
+	"syscall"
+)
+
+func preserveOwner(src, dest string, info fileInfo) (err error) {
+	if info == nil {
+		if info, err = os.Stat(src); err != nil {
+			return err
+		}
+	}
+	if stat, ok := info.Sys().(*syscall.Stat_t); ok {
+		if err := os.Chown(dest, int(stat.Uid), int(stat.Gid)); err != nil {
+			return err
+		}
+	}
+	return nil
+}
diff --git a/preserve_owner_windows.go b/preserve_owner_windows.go
new file mode 100644
index 0000000..4ebb5cf
--- /dev/null
+++ b/preserve_owner_windows.go
@@ -0,0 +1,7 @@
+//+build windows
+
+package copy
+
+func preserveOwner(src, dest string, info fileInfo) (err error) {
+	return nil
+}


Test patch:
diff --git a/all_test.go b/all_test.go
index 67f1659..f4a2069 100644
--- a/all_test.go
+++ b/all_test.go
@@ -313,3 +313,9 @@ func TestOptions_CopyBufferSize(t *testing.T) {
 	Expect(t, err).ToBe(nil)
 	Expect(t, string(content)).ToBe("case12 - README.md")
 }
+
+func TestOptions_PreserveOwner(t *testing.T) {
+	opt := Options{PreserveOwner: true}
+	err := Copy("test/data/case13", "test/data.copy/case13", opt)
+	Expect(t, err).ToBe(nil)
+}
diff --git a/test/data/case13/README.md b/test/data/case13/README.md
new file mode 100644
index 0000000..4212a7e
--- /dev/null
+++ b/test/data/case13/README.md
@@ -0,0 +1,3 @@
+# case13 - PreserveOwner
+
+https://github.com/otiai10/copy/issues/48
diff --git a/test/data/case13/foobaa/README.md b/test/data/case13/foobaa/README.md
new file mode 100644
index 0000000..a03a387
--- /dev/null
+++ b/test/data/case13/foobaa/README.md
@@ -0,0 +1,2 @@
+
+This is a file under a folder.
diff --git a/test/images/alpine.Dockerfile b/test/images/alpine.Dockerfile
index 2483e50..8e4c4e0 100644
--- a/test/images/alpine.Dockerfile
+++ b/test/images/alpine.Dockerfile
@@ -1,4 +1,4 @@
-FROM alpine:3.10
+FROM alpine:3.14
 
 RUN apk update
 RUN apk add \