diff --git a/weed/filer/filer_rename.go b/weed/filer/filer_rename.go index b39d9872e1..1a9c48fc59 100644 --- a/weed/filer/filer_rename.go +++ b/weed/filer/filer_rename.go @@ -10,7 +10,7 @@ import ( func (f *Filer) CanRename(ctx context.Context, source, target util.FullPath, oldName string) error { sourcePath := source.Child(oldName) - if strings.HasPrefix(string(target), string(sourcePath)) { + if movesIntoItself(sourcePath, target) { return fmt.Errorf("mv: can not move directory to a subdirectory of itself") } @@ -33,6 +33,13 @@ func (f *Filer) CanRename(ctx context.Context, source, target util.FullPath, old return nil } +// movesIntoItself reports whether target, the new parent directory, is +// sourcePath or lies under it. A sibling that only shares a name prefix, +// such as /a.d for /a, does not. +func movesIntoItself(sourcePath, target util.FullPath) bool { + return target == sourcePath || target.IsUnder(sourcePath) +} + func (f *Filer) DetectBucket(source util.FullPath) (bucket string) { if strings.HasPrefix(string(source), f.DirBucketsPath+"/") { bucketAndObjectKey := string(source)[len(f.DirBucketsPath)+1:] diff --git a/weed/filer/filer_rename_test.go b/weed/filer/filer_rename_test.go index d7096963fa..5df9429c14 100644 --- a/weed/filer/filer_rename_test.go +++ b/weed/filer/filer_rename_test.go @@ -29,3 +29,27 @@ func TestDetectBucket(t *testing.T) { }) } } + +func TestMovesIntoItself(t *testing.T) { + tests := []struct { + name string + sourcePath string + target string + want bool + }{ + {"into itself", "/dir/a", "/dir/a", true}, + {"into a child", "/dir/a", "/dir/a/b", true}, + {"into a deeper descendant", "/dir/a", "/dir/a/b/c", true}, + {"into a sibling sharing the name prefix", "/dir/a", "/dir/a.d", false}, + {"into a descendant of that sibling", "/dir/a", "/dir/ab/c", false}, + {"into the parent", "/dir/a", "/dir", false}, + {"into an unrelated directory", "/dir/a", "/other", false}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := movesIntoItself(util.FullPath(tt.sourcePath), util.FullPath(tt.target)); got != tt.want { + t.Errorf("movesIntoItself(%q, %q) = %v, want %v", tt.sourcePath, tt.target, got, tt.want) + } + }) + } +}