Merge commit from fork

[v2] RemoveAll: do not follow a symlink at the given path
diff --git a/client.go b/client.go
index 688e811..9855d5c 100644
--- a/client.go
+++ b/client.go
@@ -933,7 +933,7 @@
 // f the path does not exist, RemoveAll returns nil (no error).
 // If there is an error, it will be of type *PathError.
 func (cl *Client) RemoveAll(pathname string) error {
-	fi, err := cl.Stat(pathname)
+	fi, err := cl.LStat(pathname)
 	if err != nil {
 		if errors.Is(err, fs.ErrNotExist) {
 			return nil
diff --git a/localfs/localfs_integration_test.go b/localfs/localfs_integration_test.go
index 4b3da75..ec2358f 100644
--- a/localfs/localfs_integration_test.go
+++ b/localfs/localfs_integration_test.go
@@ -893,6 +893,50 @@
 	}
 }
 
+func TestRemoveAllSymlink(t *testing.T) {
+	dir := t.TempDir()
+
+	target := filepath.Join(dir, "removeall-target")
+	if err := os.Mkdir(target, 0755); err != nil {
+		t.Fatal(err)
+	}
+
+	file := filepath.Join(target, "file")
+	if err := os.WriteFile(file, []byte("foo"), 0644); err != nil {
+		t.Fatal(err)
+	}
+
+	link := filepath.Join(dir, "removeall-link")
+	if err := os.Symlink(target, link); err != nil {
+		t.Skip("cannot create symlink:", err)
+	}
+
+	sub := filepath.Join(dir, "removeall-dir")
+	if err := os.Mkdir(sub, 0755); err != nil {
+		t.Fatal(err)
+	}
+
+	if err := os.Symlink(target, filepath.Join(sub, "link")); err != nil {
+		t.Fatal(err)
+	}
+
+	if err := cl.RemoveAll(toRemotePath(link)); err != nil {
+		t.Fatal(err)
+	}
+
+	if err := cl.RemoveAll(toRemotePath(sub)); err != nil {
+		t.Fatal(err)
+	}
+
+	if _, err := os.Lstat(link); !errors.Is(err, fs.ErrNotExist) {
+		t.Errorf("symlink still exists: %v", err)
+	}
+
+	if _, err := os.Stat(file); err != nil {
+		t.Errorf("file behind the symlink was removed: %v", err)
+	}
+}
+
 var benchBuf []byte
 
 func benchHelperWriteTo(b *testing.B, length int) {