Skip to content

Commit

Permalink
Merge pull request #37 from otiai10/develop
Browse files Browse the repository at this point in the history
Add "OnDirExists" option
  • Loading branch information
otiai10 authored Dec 23, 2020
2 parents 745c200 + fd6efc9 commit 2db1023
Show file tree
Hide file tree
Showing 14 changed files with 261 additions and 121 deletions.
54 changes: 51 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,14 +1,62 @@
# copy

[![Go Reference](https://pkg.go.dev/badge/github.com/otiai10/copy.svg)](https://pkg.go.dev/github.com/otiai10/copy)
[![Actions Status](https://github.com/otiai10/copy/workflows/Go/badge.svg)](https://github.com/otiai10/copy/actions)
[![codecov](https://codecov.io/gh/otiai10/copy/branch/master/graph/badge.svg)](https://codecov.io/gh/otiai10/copy)
[![GoDoc](https://godoc.org/github.com/otiai10/copy?status.svg)](https://godoc.org/github.com/otiai10/copy)
[![codecov](https://codecov.io/gh/otiai10/copy/branch/main/graph/badge.svg)](https://codecov.io/gh/otiai10/copy)
[![License: MIT](https://img.shields.io/badge/License-MIT-green.svg)](https://github.com/otiai10/copy/blob/main/LICENSE)
[![Go Report Card](https://goreportcard.com/badge/github.com/otiai10/copy)](https://goreportcard.com/report/github.com/otiai10/copy)
[![GitHub tag (latest SemVer)](https://img.shields.io/github/v/tag/otiai10/copy?sort=semver)](https://pkg.go.dev/github.com/otiai10/copy)

`copy` copies directories recursively.

Example:
# Example Usage

```go
err := Copy("your/directory", "your/directory.copy")
```

# Advanced Usage

```go
// Options specifies optional actions on copying.
type Options struct {

// OnSymlink can specify what to do on symlink
OnSymlink func(src string) SymlinkAction

// OnDirExists can specify what to do when there is a directory already existing in destination.
OnDirExists func(src, dest string) DirExistsAction

// Skip can specify which files should be skipped
Skip func(src string) (bool, error)

// AddPermission to every entities,
// NO MORE THAN 0777
AddPermission os.FileMode

// Sync file after copy.
// Useful in case when file must be on the disk
// (in case crash happens, for example),
// 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
PreserveTimes bool

}
```

```go
// For example...
opt := Options{
Skip: func(src string) {
return strings.HasSuffix(src, ".git")
},
}
err := Copy("your/directory", "your/directory.copy", opt)
```

# Issues

- https://github.com/otiai10/copy/issues
272 changes: 159 additions & 113 deletions all_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ func TestMain(m *testing.M) {
func setup(m *testing.M) {
os.MkdirAll("testdata.copy", os.ModePerm)
os.Symlink("testdata/case01", "testdata/case03/case01")
os.Chmod("testdata/case07/dir_0500", 0500)
os.Chmod("testdata/case07/dir_0555", 0555)
os.Chmod("testdata/case07/file_0444", 0444)
}

Expand Down Expand Up @@ -83,41 +83,6 @@ func TestCopy(t *testing.T) {
Expect(t, info.Mode()&os.ModeSymlink).Not().ToBe(0)
})

When(t, "symlink with Opt.OnSymlink provided", func(t *testing.T) {
opt := Options{OnSymlink: func(string) SymlinkAction { return Deep }}
err := Copy("testdata/case03", "testdata.copy/case03.deep", opt)
Expect(t, err).ToBe(nil)
info, err := os.Lstat("testdata.copy/case03.deep/case01")
Expect(t, err).ToBe(nil)
Expect(t, info.Mode()&os.ModeSymlink).ToBe(os.FileMode(0))

opt = Options{OnSymlink: func(string) SymlinkAction { return Shallow }}
err = Copy("testdata/case03", "testdata.copy/case03.shallow", opt)
Expect(t, err).ToBe(nil)
info, err = os.Lstat("testdata.copy/case03.shallow/case01")
Expect(t, err).ToBe(nil)
Expect(t, info.Mode()&os.ModeSymlink).Not().ToBe(os.FileMode(0))

opt = Options{OnSymlink: func(string) SymlinkAction { return Skip }}
err = Copy("testdata/case03", "testdata.copy/case03.skip", opt)
Expect(t, err).ToBe(nil)
_, err = os.Stat("testdata.copy/case03.skip/case01")
Expect(t, os.IsNotExist(err)).ToBe(true)

err = Copy("testdata/case03", "testdata.copy/case03.default")
Expect(t, err).ToBe(nil)
info, err = os.Lstat("testdata.copy/case03.default/case01")
Expect(t, err).ToBe(nil)
Expect(t, info.Mode()&os.ModeSymlink).Not().ToBe(os.FileMode(0))

opt = Options{OnSymlink: nil}
err = Copy("testdata/case03", "testdata.copy/case03.not-specified", opt)
Expect(t, err).ToBe(nil)
info, err = os.Lstat("testdata.copy/case03.not-specified/case01")
Expect(t, err).ToBe(nil)
Expect(t, info.Mode()&os.ModeSymlink).Not().ToBe(os.FileMode(0))
})

When(t, "try to copy to an existing path", func(t *testing.T) {
err := Copy("testdata/case03", "testdata.copy/case03")
Expect(t, err).Not().ToBe(nil)
Expand Down Expand Up @@ -149,101 +114,182 @@ func TestCopy(t *testing.T) {
Expect(t, err).ToBe(nil)
})

When(t, "Options.Skip provided", func(t *testing.T) {
opt := Options{Skip: func(src string) (bool, error) {
switch {
case strings.HasSuffix(src, "_skip"):
return true, nil
case strings.HasSuffix(src, ".gitfake"):
return true, nil
default:
return false, nil
}
}}
err := Copy("testdata/case06", "testdata.copy/case06", opt)
Expect(t, err).ToBe(nil)
info, err := os.Stat("./testdata.copy/case06/dir_skip")
Expect(t, info).ToBe(nil)
Expect(t, os.IsNotExist(err)).ToBe(true)
}

info, err = os.Stat("./testdata.copy/case06/file_skip")
Expect(t, info).ToBe(nil)
Expect(t, os.IsNotExist(err)).ToBe(true)
func TestOptions_OnSymlink(t *testing.T) {
opt := Options{OnSymlink: func(string) SymlinkAction { return Deep }}
err := Copy("testdata/case03", "testdata.copy/case03.deep", opt)
Expect(t, err).ToBe(nil)
info, err := os.Lstat("testdata.copy/case03.deep/case01")
Expect(t, err).ToBe(nil)
Expect(t, info.Mode()&os.ModeSymlink).ToBe(os.FileMode(0))

info, err = os.Stat("./testdata.copy/case06/README.md")
Expect(t, info).Not().ToBe(nil)
Expect(t, err).ToBe(nil)
opt = Options{OnSymlink: func(string) SymlinkAction { return Shallow }}
err = Copy("testdata/case03", "testdata.copy/case03.shallow", opt)
Expect(t, err).ToBe(nil)
info, err = os.Lstat("testdata.copy/case03.shallow/case01")
Expect(t, err).ToBe(nil)
Expect(t, info.Mode()&os.ModeSymlink).Not().ToBe(os.FileMode(0))

info, err = os.Stat("./testdata.copy/case06/repo/.gitfake")
Expect(t, info).ToBe(nil)
Expect(t, os.IsNotExist(err)).ToBe(true)
opt = Options{OnSymlink: func(string) SymlinkAction { return Skip }}
err = Copy("testdata/case03", "testdata.copy/case03.skip", opt)
Expect(t, err).ToBe(nil)
_, err = os.Stat("testdata.copy/case03.skip/case01")
Expect(t, os.IsNotExist(err)).ToBe(true)

info, err = os.Stat("./testdata.copy/case06/repo/README.md")
Expect(t, info).Not().ToBe(nil)
Expect(t, err).ToBe(nil)
err = Copy("testdata/case03", "testdata.copy/case03.default")
Expect(t, err).ToBe(nil)
info, err = os.Lstat("testdata.copy/case03.default/case01")
Expect(t, err).ToBe(nil)
Expect(t, info.Mode()&os.ModeSymlink).Not().ToBe(os.FileMode(0))

Because(t, "if Skip func returns error, Copy should be interrupted", func(t *testing.T) {
errInsideSkipFunc := errors.New("Something wrong inside Skip")
opt := Options{Skip: func(src string) (bool, error) {
return false, errInsideSkipFunc
}}
err := Copy("testdata/case06", "testdata.copy/case06.01", opt)
Expect(t, err).ToBe(errInsideSkipFunc)
files, err := ioutil.ReadDir("./testdata.copy/case06.01")
Expect(t, err).ToBe(nil)
Expect(t, len(files)).ToBe(0)
})
})
opt = Options{OnSymlink: nil}
err = Copy("testdata/case03", "testdata.copy/case03.not-specified", opt)
Expect(t, err).ToBe(nil)
info, err = os.Lstat("testdata.copy/case03.not-specified/case01")
Expect(t, err).ToBe(nil)
Expect(t, info.Mode()&os.ModeSymlink).Not().ToBe(os.FileMode(0))
}

When(t, "Options.AddPermission provided", func(t *testing.T) {
func TestOptions_Skip(t *testing.T) {
opt := Options{Skip: func(src string) (bool, error) {
switch {
case strings.HasSuffix(src, "_skip"):
return true, nil
case strings.HasSuffix(src, ".gitfake"):
return true, nil
default:
return false, nil
}
}}
err := Copy("testdata/case06", "testdata.copy/case06", opt)
Expect(t, err).ToBe(nil)
info, err := os.Stat("./testdata.copy/case06/dir_skip")
Expect(t, info).ToBe(nil)
Expect(t, os.IsNotExist(err)).ToBe(true)

info, err := os.Stat("testdata/case07/dir_0500")
Expect(t, err).ToBe(nil)
Expect(t, info.Mode()).ToBe(os.FileMode(0500) | os.ModeDir)
info, err = os.Stat("./testdata.copy/case06/file_skip")
Expect(t, info).ToBe(nil)
Expect(t, os.IsNotExist(err)).ToBe(true)

info, err = os.Stat("testdata/case07/file_0444")
Expect(t, err).ToBe(nil)
Expect(t, info.Mode()).ToBe(os.FileMode(0444))
info, err = os.Stat("./testdata.copy/case06/README.md")
Expect(t, info).Not().ToBe(nil)
Expect(t, err).ToBe(nil)

opt := Options{AddPermission: 0200}
err = Copy("testdata/case07", "testdata.copy/case07", opt)
Expect(t, err).ToBe(nil)
info, err = os.Stat("./testdata.copy/case06/repo/.gitfake")
Expect(t, info).ToBe(nil)
Expect(t, os.IsNotExist(err)).ToBe(true)

info, err = os.Stat("testdata.copy/case07/dir_0500")
Expect(t, err).ToBe(nil)
Expect(t, info.Mode()).ToBe(os.FileMode(0500|0200) | os.ModeDir)
info, err = os.Stat("./testdata.copy/case06/repo/README.md")
Expect(t, info).Not().ToBe(nil)
Expect(t, err).ToBe(nil)

info, err = os.Stat("testdata.copy/case07/file_0444")
Because(t, "if Skip func returns error, Copy should be interrupted", func(t *testing.T) {
errInsideSkipFunc := errors.New("Something wrong inside Skip")
opt := Options{Skip: func(src string) (bool, error) {
return false, errInsideSkipFunc
}}
err := Copy("testdata/case06", "testdata.copy/case06.01", opt)
Expect(t, err).ToBe(errInsideSkipFunc)
files, err := ioutil.ReadDir("./testdata.copy/case06.01")
Expect(t, err).ToBe(nil)
Expect(t, info.Mode()).ToBe(os.FileMode(0444 | 0200))
Expect(t, len(files)).ToBe(0)
})
}

When(t, "Options.Sync provided", func(t *testing.T) {
// With Sync option, each file will be flushed to storage on copying.
// TODO: Since it's a bit hard to simulate real usecases here. This testcase is nonsense.
opt := Options{Sync: true}
err = Copy("testdata/case08", "testdata.copy/case08", opt)
Expect(t, err).ToBe(nil)
})
func TestOptions_AddPermission(t *testing.T) {
info, err := os.Stat("testdata/case07/dir_0555")
Expect(t, err).ToBe(nil)
Expect(t, info.Mode()).ToBe(os.FileMode(0555) | os.ModeDir)

info, err = os.Stat("testdata/case07/file_0444")
Expect(t, err).ToBe(nil)
Expect(t, info.Mode()).ToBe(os.FileMode(0444))

opt := Options{AddPermission: 0222}
err = Copy("testdata/case07", "testdata.copy/case07", opt)
Expect(t, err).ToBe(nil)

info, err = os.Stat("testdata.copy/case07/dir_0555")
Expect(t, err).ToBe(nil)
Expect(t, info.Mode()).ToBe(os.FileMode(0555|0222) | os.ModeDir)

info, err = os.Stat("testdata.copy/case07/file_0444")
Expect(t, err).ToBe(nil)
Expect(t, info.Mode()).ToBe(os.FileMode(0444 | 0222))
}

When(t, "Options.PreserveTimes provided", func(t *testing.T) {
func TestOptions_Sync(t *testing.T) {
// With Sync option, each file will be flushed to storage on copying.
// TODO: Since it's a bit hard to simulate real usecases here. This testcase is nonsense.
opt := Options{Sync: true}
err := Copy("testdata/case08", "testdata.copy/case08", opt)
Expect(t, err).ToBe(nil)
}

err = Copy("testdata/case09", "testdata.copy/case09")
func TestOptions_PreserveTimes(t *testing.T) {
err := Copy("testdata/case09", "testdata.copy/case09")
Expect(t, err).ToBe(nil)
opt := Options{PreserveTimes: true}
err = Copy("testdata/case09", "testdata.copy/case09-preservetimes", opt)
Expect(t, err).ToBe(nil)

for _, entry := range []string{"", "README.md", "symlink"} {
orig, err := os.Stat("testdata/case09/" + entry)
Expect(t, err).ToBe(nil)
plain, err := os.Stat("testdata.copy/case09/" + entry)
Expect(t, err).ToBe(nil)
opt := Options{PreserveTimes: true}
err = Copy("testdata/case09", "testdata.copy/case09-preservetimes", opt)
preserved, err := os.Stat("testdata.copy/case09-preservetimes/" + entry)
Expect(t, err).ToBe(nil)
Expect(t, plain.ModTime().Unix()).Not().ToBe(orig.ModTime().Unix())
Expect(t, preserved.ModTime().Unix()).ToBe(orig.ModTime().Unix())
}
}

for _, entry := range []string{"", "README.md", "symlink"} {
orig, err := os.Stat("testdata/case09/" + entry)
Expect(t, err).ToBe(nil)
plain, err := os.Stat("testdata.copy/case09/" + entry)
Expect(t, err).ToBe(nil)
preserved, err := os.Stat("testdata.copy/case09-preservetimes/" + entry)
Expect(t, err).ToBe(nil)
Expect(t, plain.ModTime().Unix()).Not().ToBe(orig.ModTime().Unix())
Expect(t, preserved.ModTime().Unix()).ToBe(orig.ModTime().Unix())
}
func TestOptions_OnDirExists(t *testing.T) {
err := Copy("testdata/case10/dest", "testdata.copy/case10/dest.1")
Expect(t, err).ToBe(nil)
err = Copy("testdata/case10/dest", "testdata.copy/case10/dest.2")
Expect(t, err).ToBe(nil)
err = Copy("testdata/case10/dest", "testdata.copy/case10/dest.3")
Expect(t, err).ToBe(nil)

})
opt := Options{}

opt.OnDirExists = func(src, dest string) DirExistsAction {
return Merge
}
err = Copy("testdata/case10/src", "testdata.copy/case10/dest.1", opt)
Expect(t, err).ToBe(nil)
err = Copy("testdata/case10/src", "testdata.copy/case10/dest.1", opt)
Expect(t, err).ToBe(nil)
b, err := ioutil.ReadFile("testdata.copy/case10/dest.1/" + "foo/" + "text_aaa")
Expect(t, err).ToBe(nil)
Expect(t, string(b)).ToBe("This is text_aaa from src")
stat, err := os.Stat("testdata.copy/case10/dest.1/foo/text_eee")
Expect(t, err).ToBe(nil)
Expect(t, stat).Not().ToBe(nil)

opt.OnDirExists = func(src, dest string) DirExistsAction {
return Replace
}
err = Copy("testdata/case10/src", "testdata.copy/case10/dest.2", opt)
Expect(t, err).ToBe(nil)
err = Copy("testdata/case10/src", "testdata.copy/case10/dest.2", opt)
Expect(t, err).ToBe(nil)
b, err = ioutil.ReadFile("testdata.copy/case10/dest.2/" + "foo/" + "text_aaa")
Expect(t, err).ToBe(nil)
Expect(t, string(b)).ToBe("This is text_aaa from src")
stat, err = os.Stat("testdata.copy/case10/dest.2/foo/text_eee")
Expect(t, os.IsNotExist(err)).ToBe(true)
Expect(t, stat).ToBe(nil)

opt.OnDirExists = func(src, dest string) DirExistsAction {
return Untouchable
}
err = Copy("testdata/case10/src", "testdata.copy/case10/dest.3", opt)
Expect(t, err).ToBe(nil)
b, err = ioutil.ReadFile("testdata.copy/case10/dest.3/" + "foo/" + "text_aaa")
Expect(t, err).ToBe(nil)
Expect(t, string(b)).ToBe("This is text_aaa from dest")
}
Loading

0 comments on commit 2db1023

Please sign in to comment.