Bump deps

```
* use go 1.17 semantics in go.mod
* pin tonistiigi/fsutil dep to match docker/docker's dep
go get -u ./...
go mod vendor
```
This commit is contained in:
Jason Hall
2022-01-21 16:58:24 +00:00
committed by GitHub
parent a59970f4ac
commit c19ffc26a2
3595 changed files with 429699 additions and 120494 deletions
+3 -1
View File
@@ -1,3 +1,5 @@
testdata.copy
test/data.copy
coverage.txt
vendor
.vagrant
.idea/
-11
View File
@@ -1,11 +0,0 @@
language: go
go:
- 1.9
- tip
before_script:
- go get -t ./...
script:
- go test ./... -v
- go test -race -coverprofile=coverage.txt -covermode=atomic
after_success:
- bash <(curl -s https://codecov.io/bash)
+67 -4
View File
@@ -1,14 +1,77 @@
# copy
[![Build Status](https://travis-ci.org/otiai10/copy.svg?branch=master)](https://travis-ci.org/otiai10/copy)
[![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 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/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)
[![FOSSA Status](https://app.fossa.com/api/projects/git%2Bgithub.com%2Fotiai10%2Fcopy.svg?type=shield)](https://app.fossa.com/projects/git%2Bgithub.com%2Fotiai10%2Fcopy?ref=badge_shield)
[![CodeQL](https://github.com/otiai10/copy/actions/workflows/codeql-analysis.yml/badge.svg)](https://github.com/otiai10/copy/actions/workflows/codeql-analysis.yml)
[![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)
[![Docker Test](https://github.com/otiai10/copy/actions/workflows/docker-test.yml/badge.svg)](https://github.com/otiai10/copy/actions/workflows/docker-test.yml)
[![Vagrant Test](https://github.com/otiai10/copy/actions/workflows/vagrant-test.yml/badge.svg)](https://github.com/otiai10/copy/actions/workflows/vagrant-test.yml)
`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 entry,
// 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
// Preserve the uid and the gid of all entries.
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.
CopyBufferSize uint
}
```
```go
// For example...
opt := Options{
Skip: func(src string) (bool, error) {
return strings.HasSuffix(src, ".git"), nil
},
}
err := Copy("your/directory", "your/directory.copy", opt)
```
# Issues
- https://github.com/otiai10/copy/issues
## License
[![FOSSA Status](https://app.fossa.com/api/projects/git%2Bgithub.com%2Fotiai10%2Fcopy.svg?type=large)](https://app.fossa.com/projects/git%2Bgithub.com%2Fotiai10%2Fcopy?ref=badge_large)
+165 -28
View File
@@ -5,6 +5,7 @@ import (
"io/ioutil"
"os"
"path/filepath"
"time"
)
const (
@@ -14,93 +15,229 @@ const (
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) error {
type timespec struct {
Mtime time.Time
Atime time.Time
Ctime time.Time
}
// 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 copy(src, dest, info)
return switchboard(src, dest, info, assure(src, dest, opt...))
}
// copy dispatches copy-funcs according to the mode.
// 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) (err error) {
switch {
case info.Mode()&os.ModeSymlink != 0:
err = onsymlink(src, dest, opt)
case info.IsDir():
err = dcopy(src, dest, info, opt)
case info.Mode()&os.ModeNamedPipe != 0:
err = pcopy(dest, info)
default:
err = fcopy(src, dest, info, opt)
}
return err
}
// copyNextOrSkip 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) error {
if info.Mode()&os.ModeSymlink != 0 {
return lcopy(src, dest, info)
func copyNextOrSkip(src, dest string, info os.FileInfo, opt Options) error {
skip, err := opt.Skip(src)
if err != nil {
return err
}
if info.IsDir() {
return dcopy(src, dest, info)
if skip {
return nil
}
return fcopy(src, dest, info)
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) error {
func fcopy(src, dest string, info os.FileInfo, opt Options) (err error) {
if err := os.MkdirAll(filepath.Dir(dest), os.ModePerm); err != nil {
return err
if err = os.MkdirAll(filepath.Dir(dest), os.ModePerm); err != nil {
return
}
f, err := os.Create(dest)
if err != nil {
return err
return
}
defer f.Close()
defer fclose(f, &err)
if err = os.Chmod(f.Name(), info.Mode()); err != nil {
return 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)
var buf []byte = nil
var w io.Writer = f
// var r io.Reader = s
if opt.CopyBufferSize != 0 {
buf = make([]byte, opt.CopyBufferSize)
// Disable using `ReadFrom` by io.CopyBuffer.
// See https://github.com/otiai10/copy/pull/60#discussion_r627320811 for more details.
w = struct{ io.Writer }{f}
// r = struct{ io.Reader }{s}
}
if _, err = io.CopyBuffer(w, s, buf); err != nil {
return err
}
defer s.Close()
_, err = io.Copy(f, s)
return err
if opt.Sync {
err = f.Sync()
}
if opt.PreserveOwner {
if err := preserveOwner(src, dest, info); err != nil {
return err
}
}
if opt.PreserveTimes {
if err := preserveTimes(info, dest); err != nil {
return err
}
}
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) error {
func dcopy(srcdir, destdir string, info os.FileInfo, opt Options) (err error) {
_, 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 {
return err
}
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()
// Make dest dir with 0755 so that everything writable.
if err := os.MkdirAll(destdir, tmpPermissionForDirectory); err != nil {
return err
if err = os.MkdirAll(destdir, tmpPermissionForDirectory); err != nil {
return
}
// Recover dir mode with original one.
defer os.Chmod(destdir, originalMode)
defer chmod(destdir, originalMode|opt.AddPermission, &err)
contents, err := ioutil.ReadDir(srcdir)
if err != nil {
return err
return
}
for _, content := range contents {
cs, cd := filepath.Join(srcdir, content.Name()), filepath.Join(destdir, content.Name())
if err := copy(cs, cd, content); err != nil {
if err = copyNextOrSkip(cs, cd, content, opt); err != nil {
// If any error, exit immediately
return
}
}
if opt.PreserveTimes {
if err := preserveTimes(info, destdir); err != nil {
return err
}
}
return nil
if opt.PreserveOwner {
if err := preserveOwner(srcdir, destdir, info); err != nil {
return err
}
}
return
}
func onsymlink(src, dest string, 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 copyNextOrSkip(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, info os.FileInfo) error {
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(src, dest string, opts ...Options) Options {
defopt := getDefaultOptions(src, dest)
if len(opts) == 0 {
return defopt
}
if opts[0].OnSymlink == nil {
opts[0].OnSymlink = defopt.OnSymlink
}
if opts[0].Skip == nil {
opts[0].Skip = defopt.Skip
}
opts[0].intent.src = defopt.intent.src
opts[0].intent.dest = defopt.intent.dest
return opts[0]
}
+17
View File
@@ -0,0 +1,17 @@
// +build !windows,!plan9,!netbsd,!aix,!illumos,!solaris
package copy
import (
"os"
"path/filepath"
"syscall"
)
// pcopy is for just named pipes
func pcopy(dest string, info os.FileInfo) error {
if err := os.MkdirAll(filepath.Dir(dest), os.ModePerm); err != nil {
return err
}
return syscall.Mkfifo(dest, uint32(info.Mode()))
}
+14
View File
@@ -0,0 +1,14 @@
// +build windows plan9 netbsd aix illumos solaris
package copy
import (
"os"
)
// TODO: check plan9 netbsd aix illumos solaris in future
// pcopy is for just named pipes. Windows doesn't support them
func pcopy(dest string, info os.FileInfo) error {
return nil
}
+12
View File
@@ -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)
}
-5
View File
@@ -1,5 +0,0 @@
module github.com/otiai10/copy
go 1.12
require github.com/otiai10/mint v1.3.0
-3
View File
@@ -1,3 +0,0 @@
github.com/otiai10/curr v0.0.0-20150429015615-9b4961190c95/go.mod h1:9qAhocn7zKJG+0mI8eUu6xqkFDYS2kb2saOteoSB3cE=
github.com/otiai10/mint v1.3.0 h1:Ady6MKVezQwHBkGzLFbrsywyp09Ah7rkmfjV3Bcr5uc=
github.com/otiai10/mint v1.3.0/go.mod h1:F5AjcsTsWUqX+Na9fpHb52P8pcRX2CI6A3ctIT91xUo=
+89
View File
@@ -0,0 +1,89 @@
package 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
// 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
// Preserve the uid and the gid of all entries.
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.
CopyBufferSize uint
intent struct {
src string
dest string
}
}
// 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
)
// DirExistsAction represents what to do on dest dir.
type DirExistsAction int
const (
// Merge preserves or overwrites existing files under the dir (default behavior).
Merge DirExistsAction = iota
// Replace deletes all contents under the dir and copy src files.
Replace
// Untouchable does nothing for the dir, and leaves it as it is.
Untouchable
)
// getDefaultOptions provides default options,
// which would be modified by usage-side.
func getDefaultOptions(src, dest string) Options {
return Options{
OnSymlink: func(string) SymlinkAction {
return Shallow // Do shallow copy
},
OnDirExists: nil, // Default behavior is "Merge".
Skip: func(string) (bool, error) {
return false, nil // Don't skip
},
AddPermission: 0, // Add nothing
Sync: false, // Do not sync
PreserveTimes: false, // Do not preserve the modification time
CopyBufferSize: 0, // Do not specify, use default bufsize (32*1024)
intent: struct {
src string
dest string
}{src, dest},
}
}
+22
View File
@@ -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
}
+7
View File
@@ -0,0 +1,7 @@
//+build windows
package copy
func preserveOwner(src, dest string, info fileInfo) (err error) {
return nil
}
+11
View File
@@ -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
}
+21
View File
@@ -0,0 +1,21 @@
// +build !windows,!darwin,!freebsd,!plan9,!netbsd
// TODO: add more runtimes
package copy
import (
"os"
"syscall"
"time"
)
func getTimeSpec(info os.FileInfo) timespec {
stat := info.Sys().(*syscall.Stat_t)
times := timespec{
Mtime: info.ModTime(),
Atime: time.Unix(int64(stat.Atim.Sec), int64(stat.Atim.Nsec)),
Ctime: time.Unix(int64(stat.Ctim.Sec), int64(stat.Ctim.Nsec)),
}
return times
}
+19
View File
@@ -0,0 +1,19 @@
// +build darwin
package copy
import (
"os"
"syscall"
"time"
)
func getTimeSpec(info os.FileInfo) timespec {
stat := info.Sys().(*syscall.Stat_t)
times := timespec{
Mtime: info.ModTime(),
Atime: time.Unix(stat.Atimespec.Sec, stat.Atimespec.Nsec),
Ctime: time.Unix(stat.Ctimespec.Sec, stat.Ctimespec.Nsec),
}
return times
}
+19
View File
@@ -0,0 +1,19 @@
// +build freebsd
package copy
import (
"os"
"syscall"
"time"
)
func getTimeSpec(info os.FileInfo) timespec {
stat := info.Sys().(*syscall.Stat_t)
times := timespec{
Mtime: info.ModTime(),
Atime: time.Unix(int64(stat.Atimespec.Sec), int64(stat.Atimespec.Nsec)),
Ctime: time.Unix(int64(stat.Ctimespec.Sec), int64(stat.Ctimespec.Nsec)),
}
return times
}
+18
View File
@@ -0,0 +1,18 @@
// +build windows
package copy
import (
"os"
"syscall"
"time"
)
func getTimeSpec(info os.FileInfo) timespec {
stat := info.Sys().(*syscall.Win32FileAttributeData)
return timespec{
Mtime: time.Unix(0, stat.LastWriteTime.Nanoseconds()),
Atime: time.Unix(0, stat.LastAccessTime.Nanoseconds()),
Ctime: time.Unix(0, stat.CreationTime.Nanoseconds()),
}
}
+17
View File
@@ -0,0 +1,17 @@
// +build plan9 netbsd
package copy
import (
"os"
)
// TODO: check plan9 netbsd in future
func getTimeSpec(info os.FileInfo) timespec {
times := timespec{
Mtime: info.ModTime(),
Atime: info.ModTime(),
Ctime: info.ModTime(),
}
return times
}
+17
View File
@@ -0,0 +1,17 @@
// +build !windows,!plan9,!netbsd,!aix,!illumos,!solaris
package copy
import (
"os"
"syscall"
"testing"
)
func setup(m *testing.M) {
os.MkdirAll("test/data.copy", os.ModePerm)
os.Symlink("test/data/case01", "test/data/case03/case01")
os.Chmod("test/data/case07/dir_0555", 0555)
os.Chmod("test/data/case07/file_0444", 0444)
syscall.Mkfifo("test/data/case11/foo/bar", 0555)
}
+15
View File
@@ -0,0 +1,15 @@
// +build windows plan9 netbsd aix illumos solaris
package copy
import (
"os"
"testing"
)
func setup(m *testing.M) {
os.MkdirAll("test/data.copy", os.ModePerm)
os.Symlink("test/data/case01", "test/data/case03/case01")
os.Chmod("test/data/case07/dir_0555", 0555)
os.Chmod("test/data/case07/file_0444", 0444)
}