pax_global_header00006660000000000000000000000064136623651660014530gustar00rootroot0000000000000052 comment=c5f0361fe546fb0f4f26b13746afc8864ea7f6d9 copy-1.2.0/000077500000000000000000000000001366236516600125025ustar00rootroot00000000000000copy-1.2.0/.github/000077500000000000000000000000001366236516600140425ustar00rootroot00000000000000copy-1.2.0/.github/workflows/000077500000000000000000000000001366236516600160775ustar00rootroot00000000000000copy-1.2.0/.github/workflows/go.yml000066400000000000000000000013261366236516600172310ustar00rootroot00000000000000name: Go on: push: branches: [ master ] pull_request: branches: [ master ] jobs: build: name: Build runs-on: ubuntu-latest steps: - name: Set up Go 1.13 uses: actions/setup-go@v1 with: go-version: 1.13 id: go - name: Check out code into the Go module directory uses: actions/checkout@v2 - name: Get dependencies run: go get -v -t -d ./... - name: Build run: go build -v . - name: Test run: go test -v -cover -race -coverprofile=coverage.txt -covermode=atomic - name: Upload Coverage if: success() env: CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} run: bash <(curl -s https://codecov.io/bash) copy-1.2.0/.gitignore000066400000000000000000000000421366236516600144660ustar00rootroot00000000000000testdata.copy coverage.txt vendor copy-1.2.0/LICENSE000066400000000000000000000020621366236516600135070ustar00rootroot00000000000000The MIT License (MIT) Copyright (c) 2018 otiai10 Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. copy-1.2.0/README.md000066400000000000000000000011231366236516600137560ustar00rootroot00000000000000# 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) [![Go Report Card](https://goreportcard.com/badge/github.com/otiai10/copy)](https://goreportcard.com/report/github.com/otiai10/copy) `copy` copies directories recursively. Example: ```go err := Copy("your/directory", "your/directory.copy") ``` copy-1.2.0/all_test.go000066400000000000000000000162361366236516600146500ustar00rootroot00000000000000package copy import ( "errors" "io/ioutil" "os" "path/filepath" "strings" "testing" . "github.com/otiai10/mint" ) func TestMain(m *testing.M) { setup(m) code := m.Run() teardown(m) os.Exit(code) } 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/file_0444", 0444) } func teardown(m *testing.M) { os.RemoveAll("testdata/case03/case01") os.RemoveAll("testdata.copy") } func TestCopy(t *testing.T) { err := Copy("./testdata/case00", "./testdata.copy/case00") Expect(t, err).ToBe(nil) info, err := os.Stat("./testdata.copy/case00/README.md") Expect(t, err).ToBe(nil) Expect(t, info.IsDir()).ToBe(false) When(t, "specified src doesn't exist", func(t *testing.T) { err := Copy("NOT/EXISTING/SOURCE/PATH", "anywhere") Expect(t, err).Not().ToBe(nil) }) When(t, "specified src is just a file", func(t *testing.T) { err := Copy("testdata/case01/README.md", "testdata.copy/case01/README.md") Expect(t, err).ToBe(nil) }) When(t, "too long name is given", func(t *testing.T) { dest := "foobar" for i := 0; i < 8; i++ { dest = dest + dest } err := Copy("testdata/case00", filepath.Join("testdata/case00", dest)) Expect(t, err).Not().ToBe(nil) Expect(t, err).TypeOf("*os.PathError") }) When(t, "try to create not permitted location", func(t *testing.T) { err := Copy("testdata/case00", "/case00") Expect(t, err).Not().ToBe(nil) Expect(t, err).TypeOf("*os.PathError") }) When(t, "try to create a directory on existing file name", func(t *testing.T) { err := Copy("testdata/case02", "testdata.copy/case00/README.md") Expect(t, err).Not().ToBe(nil) Expect(t, err).TypeOf("*os.PathError") }) When(t, "source directory includes symbolic link", func(t *testing.T) { err := Copy("testdata/case03", "testdata.copy/case03") Expect(t, err).ToBe(nil) info, err := os.Lstat("testdata.copy/case03/case01") Expect(t, err).ToBe(nil) 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) }) When(t, "try to copy READ-not-allowed source", func(t *testing.T) { err := Copy("testdata/doesNotExist", "testdata.copy/doesNotExist") Expect(t, err).Not().ToBe(nil) }) When(t, "try to copy a file to existing path", func(t *testing.T) { err := Copy("testdata/case04/README.md", "testdata/case04") Expect(t, err).Not().ToBe(nil) err = Copy("testdata/case04/README.md", "testdata/case04/README.md/foobar") Expect(t, err).Not().ToBe(nil) }) When(t, "try to copy a directory that has no write permission and copy file inside along with it", func(t *testing.T) { src := "testdata/case05" dest := "testdata.copy/case05" err := os.Chmod(src, os.FileMode(0555)) Expect(t, err).ToBe(nil) err = Copy(src, dest) Expect(t, err).ToBe(nil) info, err := os.Lstat(dest) Expect(t, err).ToBe(nil) Expect(t, info.Mode().Perm()).ToBe(os.FileMode(0555)) err = os.Chmod(dest, 0755) 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) info, err = os.Stat("./testdata.copy/case06/README.md") Expect(t, info).Not().ToBe(nil) 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/case06/repo/README.md") Expect(t, info).Not().ToBe(nil) Expect(t, err).ToBe(nil) 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) }) }) When(t, "Options.AddPermission provided", func(t *testing.T) { 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/case07/file_0444") Expect(t, err).ToBe(nil) Expect(t, info.Mode()).ToBe(os.FileMode(0444)) opt := Options{AddPermission: 0200} err = Copy("testdata/case07", "testdata.copy/case07", opt) Expect(t, err).ToBe(nil) 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/case07/file_0444") Expect(t, err).ToBe(nil) Expect(t, info.Mode()).ToBe(os.FileMode(0444 | 0200)) }) 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) }) } copy-1.2.0/copy.go000066400000000000000000000102351366236516600140040ustar00rootroot00000000000000package copy import ( "io" "io/ioutil" "os" "path/filepath" ) const ( // tmpPermissionForDirectory makes the destination directory writable, // so that stuff can be copied recursively even if any original directory is NOT writable. // See https://github.com/otiai10/copy/pull/9 for more information. tmpPermissionForDirectory = os.FileMode(0755) ) // Copy copies src to dest, doesn't matter if src is a directory or a file. func Copy(src, dest string, opt ...Options) error { info, err := os.Lstat(src) if err != nil { return err } return switchboard(src, dest, info, assure(opt...)) } // switchboard switches proper copy functions regarding file type, etc... // If there would be anything else here, add a case to this switchboard. func switchboard(src, dest string, info os.FileInfo, opt Options) error { switch { case info.Mode()&os.ModeSymlink != 0: return onsymlink(src, dest, info, opt) case info.IsDir(): return dcopy(src, dest, info, opt) default: return fcopy(src, dest, info, opt) } } // copy decide if this src should be copied or not. // Because this "copy" could be called recursively, // "info" MUST be given here, NOT nil. func copy(src, dest string, info os.FileInfo, opt Options) error { skip, err := opt.Skip(src) if err != nil { return err } if skip { return nil } return switchboard(src, dest, info, opt) } // fcopy is for just a file, // with considering existence of parent directory // and file permission. func fcopy(src, dest string, info os.FileInfo, opt Options) (err error) { if err = os.MkdirAll(filepath.Dir(dest), os.ModePerm); err != nil { return } f, err := os.Create(dest) if err != nil { return } defer fclose(f, &err) if err = os.Chmod(f.Name(), info.Mode()|opt.AddPermission); err != nil { return } s, err := os.Open(src) if err != nil { return } defer fclose(s, &err) if _, err = io.Copy(f, s); err != nil { return } if opt.Sync { err = f.Sync() } return } // dcopy is for a directory, // with scanning contents inside the directory // and pass everything to "copy" recursively. func dcopy(srcdir, destdir string, info os.FileInfo, opt Options) (err error) { originalMode := info.Mode() // Make dest dir with 0755 so that everything writable. if err = os.MkdirAll(destdir, tmpPermissionForDirectory); err != nil { return } // Recover dir mode with original one. defer chmod(destdir, originalMode|opt.AddPermission, &err) contents, err := ioutil.ReadDir(srcdir) if err != nil { return } for _, content := range contents { cs, cd := filepath.Join(srcdir, content.Name()), filepath.Join(destdir, content.Name()) if err = copy(cs, cd, content, opt); err != nil { // If any error, exit immediately return } } return } func onsymlink(src, dest string, info os.FileInfo, opt Options) error { switch opt.OnSymlink(src) { case Shallow: return lcopy(src, dest) case Deep: orig, err := os.Readlink(src) if err != nil { return err } info, err = os.Lstat(orig) if err != nil { return err } return copy(orig, dest, info, opt) case Skip: fallthrough default: return nil // do nothing } } // lcopy is for a symlink, // with just creating a new symlink by replicating src symlink. func lcopy(src, dest string) error { src, err := os.Readlink(src) if err != nil { return err } return os.Symlink(src, dest) } // fclose ANYHOW closes file, // with asiging error raised during Close, // BUT respecting the error already reported. func fclose(f *os.File, reported *error) { if err := f.Close(); *reported == nil { *reported = err } } // chmod ANYHOW changes file mode, // with asiging error raised during Chmod, // BUT respecting the error already reported. func chmod(dir string, mode os.FileMode, reported *error) { if err := os.Chmod(dir, mode); *reported == nil { *reported = err } } // assure Options struct, should be called only once. // All optional values MUST NOT BE nil/zero after assured. func assure(opts ...Options) Options { if len(opts) == 0 { return getDefaultOptions() } defopt := getDefaultOptions() if opts[0].OnSymlink == nil { opts[0].OnSymlink = defopt.OnSymlink } if opts[0].Skip == nil { opts[0].Skip = defopt.Skip } return opts[0] } copy-1.2.0/example_test.go000066400000000000000000000014301366236516600155210ustar00rootroot00000000000000package copy import ( "fmt" "os" "strings" ) func ExampleCopy() { err := Copy("testdata/example", "testdata.copy/example") fmt.Println("Error:", err) info, _ := os.Stat("testdata.copy/example") fmt.Println("IsDir:", info.IsDir()) // Output: // Error: // IsDir: true } func ExampleOptions() { err := Copy( "testdata/example", "testdata.copy/example_with_options", Options{ Skip: func(src string) (bool, error) { return strings.HasSuffix(src, ".git-like"), nil }, OnSymlink: func(src string) SymlinkAction { return Skip }, AddPermission: 0200, }, ) fmt.Println("Error:", err) _, err = os.Stat("testdata.copy/example_with_options/.git-like") fmt.Println("Skipped:", os.IsNotExist(err)) // Output: // Error: // Skipped: true } copy-1.2.0/go.mod000066400000000000000000000001201366236516600136010ustar00rootroot00000000000000module github.com/otiai10/copy go 1.12 require github.com/otiai10/mint v1.3.1 copy-1.2.0/go.sum000066400000000000000000000007031366236516600136350ustar00rootroot00000000000000github.com/otiai10/curr v0.0.0-20150429015615-9b4961190c95/go.mod h1:9qAhocn7zKJG+0mI8eUu6xqkFDYS2kb2saOteoSB3cE= github.com/otiai10/curr v1.0.0/go.mod h1:LskTG5wDwr8Rs+nNQ+1LlxRjAtTZZjtJW4rMXl6j4vs= github.com/otiai10/mint v1.3.0/go.mod h1:F5AjcsTsWUqX+Na9fpHb52P8pcRX2CI6A3ctIT91xUo= github.com/otiai10/mint v1.3.1 h1:BCmzIS3n71sGfHB5NMNDB3lHYPz8fWSkCAErHed//qc= github.com/otiai10/mint v1.3.1/go.mod h1:/yxELlJQ0ufhjUwhshSj+wFjZ78CnZ48/1wtmBH1OTc= copy-1.2.0/options.go000066400000000000000000000022421366236516600145240ustar00rootroot00000000000000package copy import "os" // Options specifies optional actions on copying. type Options struct { // OnSymlink can specify what to do on symlink OnSymlink func(src string) SymlinkAction // 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 } // SymlinkAction represents what to do on symlink. type SymlinkAction int const ( // Deep creates hard-copy of contents. Deep SymlinkAction = iota // Shallow creates new symlink to the dest of symlink. Shallow // Skip does nothing with symlink. Skip ) // getDefaultOptions provides default options, // which would be modified by usage-side. func getDefaultOptions() Options { return Options{ OnSymlink: func(string) SymlinkAction { return Shallow // Do shallow copy }, Skip: func(string) (bool, error) { return false, nil // Don't skip }, AddPermission: 0, // Add nothing Sync: false, // Do not sync } } copy-1.2.0/testdata/000077500000000000000000000000001366236516600143135ustar00rootroot00000000000000copy-1.2.0/testdata/case00/000077500000000000000000000000001366236516600153665ustar00rootroot00000000000000copy-1.2.0/testdata/case00/README.md000066400000000000000000000000001366236516600166330ustar00rootroot00000000000000copy-1.2.0/testdata/case01/000077500000000000000000000000001366236516600153675ustar00rootroot00000000000000copy-1.2.0/testdata/case01/README.md000066400000000000000000000000001366236516600166340ustar00rootroot00000000000000copy-1.2.0/testdata/case02/000077500000000000000000000000001366236516600153705ustar00rootroot00000000000000copy-1.2.0/testdata/case02/README.md000066400000000000000000000000001366236516600166350ustar00rootroot00000000000000copy-1.2.0/testdata/case03/000077500000000000000000000000001366236516600153715ustar00rootroot00000000000000copy-1.2.0/testdata/case03/README.md000066400000000000000000000000341366236516600166450ustar00rootroot00000000000000# Case 03: including symlinkcopy-1.2.0/testdata/case04/000077500000000000000000000000001366236516600153725ustar00rootroot00000000000000copy-1.2.0/testdata/case04/README.md000066400000000000000000000000001366236516600166370ustar00rootroot00000000000000copy-1.2.0/testdata/case05/000077500000000000000000000000001366236516600153735ustar00rootroot00000000000000copy-1.2.0/testdata/case05/README.md000066400000000000000000000000001366236516600166400ustar00rootroot00000000000000copy-1.2.0/testdata/case06/000077500000000000000000000000001366236516600153745ustar00rootroot00000000000000copy-1.2.0/testdata/case06/README.md000066400000000000000000000001331366236516600166500ustar00rootroot00000000000000# Case 06 When `Options.Skip` is provided and returns `true`, src files should be skipped.copy-1.2.0/testdata/case06/dir_skip/000077500000000000000000000000001366236516600172005ustar00rootroot00000000000000copy-1.2.0/testdata/case06/dir_skip/README.md000066400000000000000000000000051366236516600204520ustar00rootroot00000000000000Hellocopy-1.2.0/testdata/case06/file_skip000066400000000000000000000000051366236516600172570ustar00rootroot00000000000000Hellocopy-1.2.0/testdata/case06/repo/000077500000000000000000000000001366236516600163415ustar00rootroot00000000000000copy-1.2.0/testdata/case06/repo/.gitfake/000077500000000000000000000000001366236516600200315ustar00rootroot00000000000000copy-1.2.0/testdata/case06/repo/.gitfake/README.md000066400000000000000000000001531366236516600213070ustar00rootroot00000000000000This `.gitfake` directory is an imitation of `.git`. In most cases, we don't want to copy `.git` directory.copy-1.2.0/testdata/case06/repo/.gitfake/file_01000066400000000000000000000000001366236516600211610ustar00rootroot00000000000000copy-1.2.0/testdata/case06/repo/.gitfake/file_02000066400000000000000000000000001366236516600211620ustar00rootroot00000000000000copy-1.2.0/testdata/case06/repo/README.md000066400000000000000000000002301366236516600176130ustar00rootroot00000000000000# repo This `repo` directory is an imitation of a repository controlled by git. In most cases when we copy repositories, we DON'T want to copy `.git`. copy-1.2.0/testdata/case07/000077500000000000000000000000001366236516600153755ustar00rootroot00000000000000copy-1.2.0/testdata/case07/README.md000066400000000000000000000001611366236516600166520ustar00rootroot00000000000000# Case 07: Add Permission `Options.AddPermission` should add permission to all the dirs and files, if specified.copy-1.2.0/testdata/case07/dir_0500/000077500000000000000000000000001366236516600166175ustar00rootroot00000000000000copy-1.2.0/testdata/case07/dir_0500/README.md000066400000000000000000000000211366236516600200670ustar00rootroot00000000000000A file under 0500copy-1.2.0/testdata/case07/file_0444000066400000000000000000000000001366236516600167000ustar00rootroot00000000000000copy-1.2.0/testdata/case08/000077500000000000000000000000001366236516600153765ustar00rootroot00000000000000copy-1.2.0/testdata/case08/README.md000066400000000000000000000001661366236516600166600ustar00rootroot00000000000000This test case does NOT represent the real use case. See https://github.com/otiai10/copy/pull/22 for more information.copy-1.2.0/testdata/example/000077500000000000000000000000001366236516600157465ustar00rootroot00000000000000copy-1.2.0/testdata/example/.git-like/000077500000000000000000000000001366236516600175315ustar00rootroot00000000000000copy-1.2.0/testdata/example/.git-like/HEAD000066400000000000000000000000261366236516600201530ustar00rootroot00000000000000ref: refs/heads/mastercopy-1.2.0/testdata/example/README.md000066400000000000000000000000161366236516600172220ustar00rootroot00000000000000# Example Test