Repository: otiai10/copy (go)

Issue:
opt.Untouchable + opt.PreserveTimes error copying subdirectory
This is very minor issue. But interesting.

Here is error log from my code, which I provide for clarity:

`Final: main.go#DoitRunApps(): error App.RunApp(), error = 
 AppLogicCustomStartAsOfDt.RunApp(): (15) Normal Daily Run utility.go#Otiai10CopyMerge() error, in=data, out=bkp/d20210305, error = 
 chtimes bkp/d20210305/downloads: no such file or directory 
`

I decided to use OnDirExists.Untouchable for first test of certain logic in my code, as opposed to 
Merge. Also using opt.PreserveTimes = true. Copying directory data=/ which contains data/downloads, data/in and data/out. Destination is bkp/d20210305 where subdirectory
d20210305 must be created as part of the copy -- which it was. The error occurred in 
subdirectory downloads/ which was not created.

So...it appears to me that switchboard() code is not liking the fact that destination
bkp/d20210305/downloads failed to exist:

`	if opt.PreserveTimes {
		spec := getTimeSpec(info)
		if err := os.Chtimes(dest, spec.Atime, spec.Mtime); err != nil {
			return err
		}
	}
`

Thus, the apparent logical difficulty: if directory does not exist then why preserve times?

I will workaround by simply not using PreserveTimes=true in this instance. I suppose
just adding a documentation note about the incompatiblity of these options would
suffice. Or, perhaps, add a line of code or two.



Pull request:
Do not preserve times when Untouchable
Fix #53

Patch:
diff --git a/copy.go b/copy.go
index 1ab4999..6273c3d 100644
--- a/copy.go
+++ b/copy.go
@@ -39,22 +39,11 @@ func switchboard(src, dest string, info os.FileInfo, opt Options) (err error) {
 	case info.IsDir():
 		err = dcopy(src, dest, info, opt)
 	case info.Mode()&os.ModeNamedPipe != 0:
-		err = pcopy(dest,info)
+		err = pcopy(dest, info)
 	default:
 		err = fcopy(src, dest, info, opt)
 	}
 
-	if err != nil {
-		return err
-	}
-
-	if opt.PreserveTimes {
-		spec := getTimeSpec(info)
-		if err := os.Chtimes(dest, spec.Atime, spec.Mtime); err != nil {
-			return err
-		}
-	}
-
 	return err
 }
 
@@ -106,6 +95,10 @@ func fcopy(src, dest string, info os.FileInfo, opt Options) (err error) {
 		err = f.Sync()
 	}
 
+	if opt.PreserveTimes {
+		return preserveTimes(info, dest)
+	}
+
 	return
 }
 
@@ -114,7 +107,8 @@ func fcopy(src, dest string, info os.FileInfo, opt Options) (err error) {
 // and pass everything to "copy" recursively.
 func dcopy(srcdir, destdir string, info os.FileInfo, opt Options) (err error) {
 
-	if opt.OnDirExists != nil && destdir != opt.intent.dest {
+	_, err = os.Stat(destdir)
+	if err == nil && opt.OnDirExists != nil && destdir != opt.intent.dest {
 		switch opt.OnDirExists(srcdir, destdir) {
 		case Replace:
 			if err := os.RemoveAll(destdir); err != nil {
@@ -122,7 +116,9 @@ func dcopy(srcdir, destdir string, info os.FileInfo, opt Options) (err error) {
 			}
 		case Untouchable:
 			return nil
-		}
+		} // case "Merge" is default behaviour. Go through.
+	} else if err != nil && !os.IsNotExist(err) {
+		return err // Unwelcome error type...!
 	}
 
 	originalMode := info.Mode()
@@ -148,6 +144,10 @@ func dcopy(srcdir, destdir string, info os.FileInfo, opt Options) (err error) {
 		}
 	}
 
+	if opt.PreserveTimes {
+		return preserveTimes(info, destdir)
+	}
+
 	return
 }
 
diff --git a/options.go b/options.go
index 88b9f5d..35c9294 100644
--- a/options.go
+++ b/options.go
@@ -24,8 +24,8 @@ type Options struct {
 	// at the expense of some performance penalty
 	Sync bool
 
-	// Preserve the atime and the mtime of the entries
-	// On linux we can preserve only up to 1 millisecond accuracy
+	// Preserve the atime and the mtime of the entries.
+	// On linux we can preserve only up to 1 millisecond accuracy.
 	PreserveTimes bool
 
 	intent struct {
diff --git a/preserve_times.go b/preserve_times.go
new file mode 100644
index 0000000..d89b128
--- /dev/null
+++ b/preserve_times.go
@@ -0,0 +1,11 @@
+package copy
+
+import "os"
+
+func preserveTimes(srcinfo os.FileInfo, dest string) error {
+	spec := getTimeSpec(srcinfo)
+	if err := os.Chtimes(dest, spec.Atime, spec.Mtime); err != nil {
+		return err
+	}
+	return nil
+}


Test patch:
diff --git a/all_test.go b/all_test.go
index 7645d30..5359777 100644
--- a/all_test.go
+++ b/all_test.go
@@ -288,4 +288,13 @@ func TestOptions_OnDirExists(t *testing.T) {
 	b, err = ioutil.ReadFile("test/data.copy/case10/dest.3/" + "foo/" + "text_aaa")
 	Expect(t, err).ToBe(nil)
 	Expect(t, string(b)).ToBe("This is text_aaa from dest")
+
+	When(t, "PreserveTimes is true with Untouchable", func(t *testing.T) {
+		opt := Options{
+			OnDirExists:   func(src, dest string) DirExistsAction { return Untouchable },
+			PreserveTimes: true,
+		}
+		err = Copy("test/data/case10/src", "test/data.copy/case10/dest.3", opt)
+		Expect(t, err).ToBe(nil)
+	})
 }