147 lines
3.7 KiB
Go
147 lines
3.7 KiB
Go
package copy
|
|
|
|
import (
|
|
"io"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
)
|
|
|
|
// CopyDir recursively copies all of the files within the directory given in
|
|
// src to the directory given in dst.
|
|
//
|
|
// Both directories should already exist. If the destination directory is
|
|
// non-empty then the new files will merge in with the old, overwriting any
|
|
// files that have a relative path in common between source and destination.
|
|
//
|
|
// Recursive copying of directories is inevitably a rather opinionated sort of
|
|
// operation, so this function won't be appropriate for all use-cases. Some
|
|
// of the "opinions" it has are described in the following paragraphs:
|
|
//
|
|
// Symlinks in the source directory are recreated with the same target in the
|
|
// destination directory. If the symlink is to a directory itself, that
|
|
// directory is not recursively visited for further copying.
|
|
//
|
|
// File and directory modes are not preserved exactly, but the executable
|
|
// flag is preserved for files on operating systems where it is significant.
|
|
//
|
|
// Any "dot files" it encounters along the way are skipped, even on platforms
|
|
// that do not normally ascribe special meaning to files with names starting
|
|
// with dots.
|
|
//
|
|
// Callers may rely on the above details and other undocumented details of
|
|
// this function, so if you intend to change it be sure to review the callers
|
|
// first and make sure they are compatible with the change you intend to make.
|
|
func CopyDir(dst, src string) error {
|
|
src, err := filepath.EvalSymlinks(src)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
walkFn := func(path string, info os.FileInfo, err error) error {
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if path == src {
|
|
return nil
|
|
}
|
|
|
|
if strings.HasPrefix(filepath.Base(path), ".") {
|
|
// Skip any dot files
|
|
if info.IsDir() {
|
|
return filepath.SkipDir
|
|
} else {
|
|
return nil
|
|
}
|
|
}
|
|
|
|
// The "path" has the src prefixed to it. We need to join our
|
|
// destination with the path without the src on it.
|
|
dstPath := filepath.Join(dst, path[len(src):])
|
|
|
|
// we don't want to try and copy the same file over itself.
|
|
if eq, err := SameFile(path, dstPath); eq {
|
|
return nil
|
|
} else if err != nil {
|
|
return err
|
|
}
|
|
|
|
// If we have a directory, make that subdirectory, then continue
|
|
// the walk.
|
|
if info.IsDir() {
|
|
if path == filepath.Join(src, dst) {
|
|
// dst is in src; don't walk it.
|
|
return nil
|
|
}
|
|
|
|
if err := os.MkdirAll(dstPath, 0755); err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// If the current path is a symlink, recreate the symlink relative to
|
|
// the dst directory
|
|
if info.Mode()&os.ModeSymlink == os.ModeSymlink {
|
|
target, err := os.Readlink(path)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return os.Symlink(target, dstPath)
|
|
}
|
|
|
|
// If we have a file, copy the contents.
|
|
srcF, err := os.Open(path)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer srcF.Close()
|
|
|
|
dstF, err := os.Create(dstPath)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer dstF.Close()
|
|
|
|
if _, err := io.Copy(dstF, srcF); err != nil {
|
|
return err
|
|
}
|
|
|
|
// Chmod it
|
|
return os.Chmod(dstPath, info.Mode())
|
|
}
|
|
|
|
return filepath.Walk(src, walkFn)
|
|
}
|
|
|
|
// SameFile returns true if the two given paths refer to the same physical
|
|
// file on disk, using the unique file identifiers from the underlying
|
|
// operating system. For example, on Unix systems this checks whether the
|
|
// two files are on the same device and have the same inode.
|
|
func SameFile(a, b string) (bool, error) {
|
|
if a == b {
|
|
return true, nil
|
|
}
|
|
|
|
aInfo, err := os.Lstat(a)
|
|
if err != nil {
|
|
if os.IsNotExist(err) {
|
|
return false, nil
|
|
}
|
|
return false, err
|
|
}
|
|
|
|
bInfo, err := os.Lstat(b)
|
|
if err != nil {
|
|
if os.IsNotExist(err) {
|
|
return false, nil
|
|
}
|
|
return false, err
|
|
}
|
|
|
|
return os.SameFile(aInfo, bInfo), nil
|
|
}
|