diff --git a/CHANGELOG.md b/CHANGELOG.md index 73721a1..27af8e3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,10 @@ For a list of breaking changes, check [here](#breaking-changes). Babashka [fs](https://github.com/babashka/fs): file system utility library for Clojure +## Unreleased + +- [#122](https://github.com/babashka/fs/issues/122): `fs/copy-tree`: fix copying read-only directories with children ([@sohalt](https://github.com/sohalt)) + ## v0.5.20 (2023-12-21) - [#119](https://github.com/babashka/fs/issues/119): `fs/delete-tree`: add `:force` flag to delete read-only directories/files. Set the flag to true in `fs/with-temp-dir` ([@jlesquembre](https://github.com/jlesquembre)) diff --git a/src/babashka/fs.cljc b/src/babashka/fs.cljc index 5329255..a7e5648 100644 --- a/src/babashka/fs.cljc +++ b/src/babashka/fs.cljc @@ -209,8 +209,8 @@ (preVisitDirectory [_ dir attrs] (-> (pre-visit-dir dir attrs) file-visit-result)) - (postVisitDirectory [_ dir attrs] - (-> (post-visit-dir dir attrs) + (postVisitDirectory [_ dir ex] + (-> (post-visit-dir dir ex) file-visit-result)) (visitFile [_ path attrs] (-> (visit-file path attrs) @@ -424,6 +424,27 @@ ([path {:keys [:posix-file-permissions]}] (Files/createDirectories (as-path path) (posix->attrs posix-file-permissions)))) +(defn set-posix-file-permissions + "Sets posix file permissions on f. Accepts a string like `\"rwx------\"` or a set of PosixFilePermission." + [f posix-file-permissions] + (Files/setPosixFilePermissions (as-path f) (->posix-file-permissions posix-file-permissions))) + +(defn posix-file-permissions + "Gets f's posix file permissions. Use posix->str to view as a string." + ([f] (posix-file-permissions f nil)) + ([f {:keys [:nofollow-links]}] + (Files/getPosixFilePermissions (as-path f) (->link-opts nofollow-links)))) + +(defn- u+wx + [f] + (if win? + (.setWritable (file f) true) + (let [^HashSet perms (posix-file-permissions f) + p1 (.add perms PosixFilePermission/OWNER_WRITE) + p2 (.add perms PosixFilePermission/OWNER_EXECUTE)] + (when (or p1 p2) + (set-posix-file-permissions f perms))))) + (defn copy-tree "Copies entire file tree from src to dest. Creates dest if needed using `create-dirs`, passing it the `:posix-file-permissions` @@ -453,7 +474,9 @@ (when-not (Files/exists to-dir link-options) (Files/copy ^Path dir to-dir ^"[Ljava.nio.file.CopyOption;" - copy-options))) + copy-options) + (when-not win? + (u+wx to-dir)))) :continue) :visit-file (fn [from-path _attrs] (let [rel (relativize from from-path) @@ -462,7 +485,26 @@ ^"[Ljava.nio.file.CopyOption;" copy-options) :continue) - :continue)})))) + :continue) + :post-visit-dir (fn [dir _ex] + (let [rel (relativize from dir) + to-dir (path to rel)] + (when-not win? + (let [perms (posix-file-permissions (file dir))] + (Files/setPosixFilePermissions to-dir perms))) + :continue))})))) +(declare posix-file-permissions) +(declare set-posix-file-permissions) + +(defn- u+wx + [f] + (if win? + (.setWritable (file f) true) + (let [^HashSet perms (posix-file-permissions f) + p1 (.add perms PosixFilePermission/OWNER_WRITE) + p2 (.add perms PosixFilePermission/OWNER_EXECUTE)] + (when (or p1 p2) + (set-posix-file-permissions f perms))))) (defn temp-dir "Returns `java.io.tmpdir` property as path." @@ -559,19 +601,6 @@ [f] (Files/isSymbolicLink (as-path f))) -(declare posix-file-permissions) -(declare set-posix-file-permissions) - -(defn- u+wx - [f] - (if win? - (.setWritable (file f) true) - (let [^HashSet perms (posix-file-permissions f) - p1 (.add perms PosixFilePermission/OWNER_WRITE) - p2 (.add perms PosixFilePermission/OWNER_EXECUTE)] - (when (or p1 p2) - (set-posix-file-permissions f perms))))) - (defn delete-tree "Deletes a file tree using `walk-file-tree`. Similar to `rm -rf`. Does not follow symlinks. `force` ensures read-only directories/files are deleted. Similar to `chmod -R +wx` + `rm -rf`" @@ -633,17 +662,6 @@ (.deleteOnExit (as-file f)) f) -(defn set-posix-file-permissions - "Sets posix file permissions on f. Accepts a string like `\"rwx------\"` or a set of PosixFilePermission." - [f posix-file-permissions] - (Files/setPosixFilePermissions (as-path f) (->posix-file-permissions posix-file-permissions))) - -(defn posix-file-permissions - "Gets f's posix file permissions. Use posix->str to view as a string." - ([f] (posix-file-permissions f nil)) - ([f {:keys [:nofollow-links]}] - (Files/getPosixFilePermissions (as-path f) (->link-opts nofollow-links)))) - (defn same-file? "Returns true if this is the same file as other." [this other] diff --git a/test/babashka/fs_test.clj b/test/babashka/fs_test.clj index eabbd30..5f85da7 100644 --- a/test/babashka/fs_test.clj +++ b/test/babashka/fs_test.clj @@ -218,6 +218,18 @@ (is (fs/exists? (fs/file tmp"foo2" "foo" "1")) "The nested destination directory is not created when it doesn't exist"))) +(deftest copy-tree-nested-ro-dir-test + (fs/with-temp-dir [tmp {}] + ;; https://github.com/babashka/fs/issues/122 + (fs/create-dirs (fs/path tmp "src" "foo" "bar")) + (.setReadOnly (fs/file tmp "src" "foo")) + (fs/copy-tree (fs/path tmp "src") (fs/path tmp "dst")) + (is (fs/exists? (fs/path tmp "dst" "foo" "bar"))) + (when (not windows?) + ;; you can always write to directories on Windows, even if they are read-only + ;; https://answers.microsoft.com/en-us/windows/forum/all/all-folders-are-now-read-only-windows-10/0ca1880f-e997-46af-bd85-042a53fc078e + (is (not (fs/writable? (fs/path tmp "dst" "foo"))))))) + (deftest components-test (let [paths (map normalize (fs/components (fs/path (temp-dir) "foo")))] (is (= "foo" (last paths)))