/
copy.go
118 lines (98 loc) · 2.6 KB
/
copy.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
package shx
import (
"fmt"
"io"
"os"
"path/filepath"
)
type CopyOption int
const (
CopyDefault CopyOption = iota
// CopyNoOverwrite does not overwrite existing files in the destination
CopyNoOverwrite
CopyRecursive
)
// Copy a file or directory with the specified set of CopyOption.
// The source may use globbing, which is resolved with filepath.Glob.
// Notes:
// * Does not copy file owner/group.
func Copy(src string, dest string, opts ...CopyOption) error {
items, err := filepath.Glob(src)
if err != nil {
return err
}
if len(items) == 0 {
return fmt.Errorf("no such file or directory '%s'", src)
}
var combinedOpts CopyOption
for _, opt := range opts {
combinedOpts |= opt
}
// Check if the destination exists, e.g. if we are copying to /tmp/foo, /tmp should already exist
if _, err := os.Stat(filepath.Dir(dest)); err != nil {
return err
}
for _, item := range items {
err := copyFileOrDirectory(item, dest, combinedOpts)
if err != nil {
return err
}
}
return nil
}
func copyFileOrDirectory(src string, dest string, opts CopyOption) error {
// If the destination is a directory that exists,
// copy into the directory.
destInfo, err := os.Stat(dest)
if err == nil && destInfo.IsDir() {
dest = filepath.Join(dest, filepath.Base(src))
}
return filepath.Walk(src, func(srcPath string, srcInfo os.FileInfo, err error) error {
if err != nil {
return err
}
// Only copy the first item if CopyRecursive wasn't set
if opts&CopyRecursive != CopyRecursive && src != srcPath {
return nil
}
relPath, err := filepath.Rel(src, srcPath)
if err != nil {
return fmt.Errorf("error determining the relative path between %s and %s: %w", src, srcPath, err)
}
destPath := filepath.Join(dest, relPath)
if srcInfo.IsDir() {
return os.MkdirAll(destPath, srcInfo.Mode())
}
return copyFile(srcPath, destPath, opts)
})
}
func copyFile(src string, dest string, opts CopyOption) error {
srcInfo, err := os.Stat(src)
if err != nil {
return err
}
srcF, err := os.Open(src)
if err != nil {
return err
}
defer srcF.Close()
// Check if we should skip existing files
overwrite := opts&CopyNoOverwrite != CopyNoOverwrite
createOpts := os.O_CREATE | os.O_WRONLY | os.O_TRUNC
if !overwrite { // Return an error if the file exists
createOpts |= os.O_EXCL
}
destF, err := os.OpenFile(dest, createOpts, srcInfo.Mode())
if err != nil {
if os.IsExist(err) && !overwrite {
return nil
}
return err
}
defer destF.Close()
_, err = io.Copy(destF, srcF)
if err != nil {
return fmt.Errorf("error copying %s to %s: %w", src, dest, err)
}
return destF.Close()
}