aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/cli/internal/fs/copy_file.go
diff options
context:
space:
mode:
Diffstat (limited to 'cli/internal/fs/copy_file.go')
-rw-r--r--cli/internal/fs/copy_file.go81
1 files changed, 81 insertions, 0 deletions
diff --git a/cli/internal/fs/copy_file.go b/cli/internal/fs/copy_file.go
new file mode 100644
index 0000000..e7619de
--- /dev/null
+++ b/cli/internal/fs/copy_file.go
@@ -0,0 +1,81 @@
+// Adapted from https://github.com/thought-machine/please
+// Copyright Thought Machine, Inc. or its affiliates. All Rights Reserved.
+// SPDX-License-Identifier: Apache-2.0
+package fs
+
+import (
+ "errors"
+ "os"
+ "path/filepath"
+
+ "github.com/karrick/godirwalk"
+)
+
+// RecursiveCopy copies either a single file or a directory.
+// 'mode' is the mode of the destination file.
+func RecursiveCopy(from string, to string) error {
+ // Verified all callers are passing in absolute paths for from (and to)
+ statedFrom := LstatCachedFile{Path: UnsafeToAbsoluteSystemPath(from)}
+ fromType, err := statedFrom.GetType()
+ if err != nil {
+ return err
+ }
+
+ if fromType.IsDir() {
+ return WalkMode(statedFrom.Path.ToStringDuringMigration(), func(name string, isDir bool, fileType os.FileMode) error {
+ dest := filepath.Join(to, name[len(statedFrom.Path.ToString()):])
+ // name is absolute, (originates from godirwalk)
+ src := LstatCachedFile{Path: UnsafeToAbsoluteSystemPath(name), fileType: &fileType}
+ if isDir {
+ mode, err := src.GetMode()
+ if err != nil {
+ return err
+ }
+ return os.MkdirAll(dest, mode)
+ }
+ return CopyFile(&src, dest)
+ })
+ }
+ return CopyFile(&statedFrom, to)
+}
+
+// Walk implements an equivalent to filepath.Walk.
+// It's implemented over github.com/karrick/godirwalk but the provided interface doesn't use that
+// to make it a little easier to handle.
+func Walk(rootPath string, callback func(name string, isDir bool) error) error {
+ return WalkMode(rootPath, func(name string, isDir bool, mode os.FileMode) error {
+ return callback(name, isDir)
+ })
+}
+
+// WalkMode is like Walk but the callback receives an additional type specifying the file mode type.
+// N.B. This only includes the bits of the mode that determine the mode type, not the permissions.
+func WalkMode(rootPath string, callback func(name string, isDir bool, mode os.FileMode) error) error {
+ return godirwalk.Walk(rootPath, &godirwalk.Options{
+ Callback: func(name string, info *godirwalk.Dirent) error {
+ // currently we support symlinked files, but not symlinked directories:
+ // For copying, we Mkdir and bail if we encounter a symlink to a directoy
+ // For finding packages, we enumerate the symlink, but don't follow inside
+ isDir, err := info.IsDirOrSymlinkToDir()
+ if err != nil {
+ pathErr := &os.PathError{}
+ if errors.As(err, &pathErr) {
+ // If we have a broken link, skip this entry
+ return godirwalk.SkipThis
+ }
+ return err
+ }
+ return callback(name, isDir, info.ModeType())
+ },
+ ErrorCallback: func(pathname string, err error) godirwalk.ErrorAction {
+ pathErr := &os.PathError{}
+ if errors.As(err, &pathErr) {
+ return godirwalk.SkipNode
+ }
+ return godirwalk.Halt
+ },
+ Unsorted: true,
+ AllowNonDirectory: true,
+ FollowSymbolicLinks: false,
+ })
+}