Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
69 changes: 47 additions & 22 deletions copy/copy.go
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ func Copy(ctx context.Context, srcRoot, src, dstRoot, dst string, opts ...Opt) e
return err
}

c, err := newCopier(dstRoot, ci.Chown, ci.Utime, ci.Mode, ci.XAttrErrorHandler, ci.IncludePatterns, ci.ExcludePatterns, ci.ChangeFunc)
c, err := newCopier(dstRoot, ci.Chown, ci.Utime, ci.Mode, ci.XAttrErrorHandler, ci.IncludePatterns, ci.ExcludePatterns, ci.AlwaysReplaceExistingDestPaths, ci.ChangeFunc)
if err != nil {
return err
}
Expand Down Expand Up @@ -172,7 +172,11 @@ type CopyInfo struct {
IncludePatterns []string
// Exclude files/dir matching any of these patterns (even if they match an include pattern)
ExcludePatterns []string
ChangeFunc fsutil.ChangeFunc
// If true, any source path that overwrite existing destination paths will always replace
// the existing destination path, even if they are of different types (e.g. a directory will
// replace any existing symlink or file)
AlwaysReplaceExistingDestPaths bool
ChangeFunc fsutil.ChangeFunc
}

type Opt func(*CopyInfo)
Expand Down Expand Up @@ -227,16 +231,17 @@ func WithChangeNotifier(fn fsutil.ChangeFunc) Opt {
}

type copier struct {
chown Chowner
utime *time.Time
mode *int
inodes map[uint64]string
xattrErrorHandler XAttrErrorHandler
includePatternMatcher *patternmatcher.PatternMatcher
excludePatternMatcher *patternmatcher.PatternMatcher
parentDirs []parentDir
changefn fsutil.ChangeFunc
root string
chown Chowner
utime *time.Time
mode *int
inodes map[uint64]string
xattrErrorHandler XAttrErrorHandler
includePatternMatcher *patternmatcher.PatternMatcher
excludePatternMatcher *patternmatcher.PatternMatcher
parentDirs []parentDir
changefn fsutil.ChangeFunc
root string
alwaysReplaceExistingDestPaths bool
}

type parentDir struct {
Expand All @@ -245,7 +250,7 @@ type parentDir struct {
copied bool
}

func newCopier(root string, chown Chowner, tm *time.Time, mode *int, xeh XAttrErrorHandler, includePatterns, excludePatterns []string, changeFunc fsutil.ChangeFunc) (*copier, error) {
func newCopier(root string, chown Chowner, tm *time.Time, mode *int, xeh XAttrErrorHandler, includePatterns, excludePatterns []string, alwaysReplaceExistingDestPaths bool, changeFunc fsutil.ChangeFunc) (*copier, error) {
if xeh == nil {
xeh = func(dst, src, key string, err error) error {
return err
Expand All @@ -271,15 +276,16 @@ func newCopier(root string, chown Chowner, tm *time.Time, mode *int, xeh XAttrEr
}

return &copier{
root: root,
inodes: map[uint64]string{},
chown: chown,
utime: tm,
xattrErrorHandler: xeh,
mode: mode,
includePatternMatcher: includePatternMatcher,
excludePatternMatcher: excludePatternMatcher,
changefn: changeFunc,
root: root,
inodes: map[uint64]string{},
chown: chown,
utime: tm,
xattrErrorHandler: xeh,
mode: mode,
includePatternMatcher: includePatternMatcher,
excludePatternMatcher: excludePatternMatcher,
changefn: changeFunc,
alwaysReplaceExistingDestPaths: alwaysReplaceExistingDestPaths,
}, nil
}

Expand Down Expand Up @@ -324,6 +330,10 @@ func (c *copier) copy(ctx context.Context, src, srcComponents, target string, ov
}

if include {
if err := c.removeTargetIfNeeded(src, target, fi, targetFi); err != nil {
return err
}

if err := c.createParentDirs(src, srcComponents, target, overwriteTargetMetadata); err != nil {
return err
}
Expand Down Expand Up @@ -440,6 +450,21 @@ func (c *copier) exclude(path string, fi os.FileInfo, parentExcludeMatchInfo pat
return m, matchInfo, nil
}

func (c *copier) removeTargetIfNeeded(src, target string, srcFi, targetFi os.FileInfo) error {
if !c.alwaysReplaceExistingDestPaths {
return nil
}
if targetFi == nil {
// already doesn't exist
return nil
}
if srcFi.IsDir() && targetFi.IsDir() {
// directories are merged, not replaced
return nil
}
return os.RemoveAll(target)
}

// Delayed creation of parent directories when a file or dir matches an include
// pattern.
func (c *copier) createParentDirs(src, srcComponents, target string, overwriteTargetMetadata bool) error {
Expand Down
54 changes: 54 additions & 0 deletions copy/copy_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -359,6 +359,60 @@ func TestCopySymlinks(t *testing.T) {
require.Equal(t, "foo.txt", link)
}

func TestCopyWithAlwaysReplaceExistingDestPaths(t *testing.T) {
destDir := t.TempDir()
require.NoError(t, fstest.Apply(
fstest.CreateDir("root", 0755),
fstest.CreateDir("root/overwritedir", 0755),
fstest.CreateFile("root/overwritedir/subfile", nil, 0755),
fstest.CreateFile("root/overwritefile", nil, 0755),
fstest.Symlink("dir", "root/overwritesymlink"),
fstest.CreateDir("root/dir", 0755),
fstest.CreateFile("root/dir/dirfile1", nil, 0755),
fstest.CreateDir("root/dir/overwritesubdir", 0755),
fstest.CreateFile("root/dir/overwritesubfile", nil, 0755),
fstest.Symlink("dirfile1", "root/dir/overwritesymlink"),
).Apply(destDir))

srcDir := t.TempDir()
require.NoError(t, fstest.Apply(
fstest.CreateDir("root", 0755),
fstest.CreateFile("root/overwritedir", nil, 0755),
fstest.CreateDir("root/overwritefile", 0755),
fstest.CreateFile("root/overwritefile/foo", nil, 0755),
fstest.CreateDir("root/overwritesymlink", 0755),
fstest.CreateDir("root/dir", 0755),
fstest.CreateFile("root/dir/dirfile2", nil, 0755),
fstest.CreateFile("root/dir/overwritesubdir", nil, 0755),
fstest.CreateDir("root/dir/overwritesubfile", 0755),
fstest.CreateDir("root/dir/overwritesymlink", 0755),
).Apply(srcDir))

expectedDir := t.TempDir()
require.NoError(t, fstest.Apply(
fstest.CreateDir("root", 0755),
fstest.CreateFile("root/overwritedir", nil, 0755),
fstest.CreateDir("root/overwritefile", 0755),
fstest.CreateFile("root/overwritefile/foo", nil, 0755),
fstest.CreateDir("root/overwritesymlink", 0755),
fstest.CreateDir("root/dir", 0755),
fstest.CreateFile("root/dir/dirfile1", nil, 0755),
fstest.CreateFile("root/dir/dirfile2", nil, 0755),
fstest.CreateFile("root/dir/overwritesubdir", nil, 0755),
fstest.CreateDir("root/dir/overwritesubfile", 0755),
fstest.CreateDir("root/dir/overwritesymlink", 0755),
).Apply(expectedDir))

err := Copy(context.TODO(), srcDir, "root", destDir, "root", WithCopyInfo(CopyInfo{
AlwaysReplaceExistingDestPaths: true,
CopyDirContents: true,
}))
require.NoError(t, err)

err = fstest.CheckDirectoryEqual(destDir, expectedDir)
require.NoError(t, err)
}

func testCopy(t *testing.T, apply fstest.Applier, exp string) error {
t1 := t.TempDir()
t2 := t.TempDir()
Expand Down