| 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 |
| } |