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 }