gix-fs-0.8.0/.cargo_vcs_info.json0000644000000001440000000000100122350ustar { "git": { "sha1": "68e54326e527a55dd5b5079921fc251615833040" }, "path_in_vcs": "gix-fs" }gix-fs-0.8.0/CHANGELOG.md000064400000000000000000000471511046102023000126470ustar 00000000000000# Changelog All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## 0.8.0 (2023-10-12) A maintenance release without user-facing changes. ### Commit Statistics - 1 commit contributed to the release. - 17 days passed between releases. - 0 commits were understood as [conventional](https://www.conventionalcommits.org). - 0 issues like '(#ID)' were seen in commit messages ### Commit Details
view details * **Uncategorized** - Prepare changelogs prior to release ([`1347a54`](https://github.com/Byron/gitoxide/commit/1347a54f84599d8f0aa935d6e64b16c2298d25cf))
## 0.7.0 (2023-09-24) ### New Features - add `DerefMut` implementation for `FileSnapshot`. This allows to leverage `make_mut()`. ### Commit Statistics - 4 commits contributed to the release over the course of 1 calendar day. - 16 days passed between releases. - 1 commit was understood as [conventional](https://www.conventionalcommits.org). - 0 issues like '(#ID)' were seen in commit messages ### Commit Details
view details * **Uncategorized** - Release gix-features v0.35.0, gix-actor v0.27.0, gix-object v0.37.0, gix-glob v0.13.0, gix-attributes v0.19.0, gix-filter v0.5.0, gix-fs v0.7.0, gix-commitgraph v0.21.0, gix-revwalk v0.8.0, gix-traverse v0.33.0, gix-worktree-stream v0.5.0, gix-archive v0.5.0, gix-tempfile v10.0.0, gix-lock v10.0.0, gix-ref v0.37.0, gix-config v0.30.0, gix-url v0.24.0, gix-credentials v0.20.0, gix-diff v0.36.0, gix-discover v0.25.0, gix-ignore v0.8.0, gix-index v0.25.0, gix-mailmap v0.19.0, gix-negotiate v0.8.0, gix-pack v0.43.0, gix-odb v0.53.0, gix-pathspec v0.3.0, gix-transport v0.37.0, gix-protocol v0.40.0, gix-revision v0.22.0, gix-refspec v0.18.0, gix-status v0.1.0, gix-submodule v0.4.0, gix-worktree v0.26.0, gix-worktree-state v0.3.0, gix v0.54.0, gitoxide-core v0.32.0, gitoxide v0.30.0, safety bump 37 crates ([`7891fb1`](https://github.com/Byron/gitoxide/commit/7891fb17348ec2f4c997665f9a25be36e2713da4)) - Prepare changelogs prior to release ([`8a60d5b`](https://github.com/Byron/gitoxide/commit/8a60d5b80877c213c3b646d3061e8a33e0e433ec)) - Merge branch 'reset' ([`54a8495`](https://github.com/Byron/gitoxide/commit/54a849545140f7f1c0c7564c418071c0a76a34e7)) - Add `DerefMut` implementation for `FileSnapshot`. ([`7a96a25`](https://github.com/Byron/gitoxide/commit/7a96a258cc8a5bad6414367cdbce390b7ac88b19))
## 0.6.0 (2023-09-08) ### Bug Fixes (BREAKING) - use `dyn` trait where possible. This reduces compile time due to avoiding duplication. ### Commit Statistics - 5 commits contributed to the release over the course of 17 calendar days. - 17 days passed between releases. - 1 commit was understood as [conventional](https://www.conventionalcommits.org). - 0 issues like '(#ID)' were seen in commit messages ### Commit Details
view details * **Uncategorized** - Release gix-date v0.8.0, gix-hash v0.13.0, gix-features v0.34.0, gix-actor v0.26.0, gix-object v0.36.0, gix-path v0.10.0, gix-glob v0.12.0, gix-attributes v0.18.0, gix-packetline-blocking v0.16.6, gix-filter v0.4.0, gix-fs v0.6.0, gix-commitgraph v0.20.0, gix-hashtable v0.4.0, gix-revwalk v0.7.0, gix-traverse v0.32.0, gix-worktree-stream v0.4.0, gix-archive v0.4.0, gix-config-value v0.14.0, gix-tempfile v9.0.0, gix-lock v9.0.0, gix-ref v0.36.0, gix-sec v0.10.0, gix-config v0.29.0, gix-prompt v0.7.0, gix-url v0.23.0, gix-credentials v0.19.0, gix-diff v0.35.0, gix-discover v0.24.0, gix-ignore v0.7.0, gix-index v0.24.0, gix-macros v0.1.0, gix-mailmap v0.18.0, gix-negotiate v0.7.0, gix-pack v0.42.0, gix-odb v0.52.0, gix-pathspec v0.2.0, gix-packetline v0.16.6, gix-transport v0.36.0, gix-protocol v0.39.0, gix-revision v0.21.0, gix-refspec v0.17.0, gix-submodule v0.3.0, gix-worktree v0.25.0, gix-worktree-state v0.2.0, gix v0.53.0, safety bump 39 crates ([`8bd0456`](https://github.com/Byron/gitoxide/commit/8bd045676bb2cdc02624ab93e73ff8518064ca38)) - Prepare changelogs for release ([`375db06`](https://github.com/Byron/gitoxide/commit/375db06a8442378c3f7a922fae38e2a6694d9d04)) - Merge branch `dyn`ification ([`f658fcc`](https://github.com/Byron/gitoxide/commit/f658fcc52dc2200ae34ca53dc10be97fb9012057)) - Use `dyn` trait where possible. ([`072ee32`](https://github.com/Byron/gitoxide/commit/072ee32f693a31161cd6a843da6582d13efbb20b)) - Merge branch 'gix-submodule' ([`363ee77`](https://github.com/Byron/gitoxide/commit/363ee77400805f473c9ad66eadad9214e7ab66f4))
## 0.5.0 (2023-08-22) ### New Features - add `FileSnapshot::new()` to create free-floating instances. ### Commit Statistics - 4 commits contributed to the release over the course of 1 calendar day. - 30 days passed between releases. - 1 commit was understood as [conventional](https://www.conventionalcommits.org). - 0 issues like '(#ID)' were seen in commit messages ### Commit Details
view details * **Uncategorized** - Release gix-date v0.7.3, gix-hash v0.12.0, gix-features v0.33.0, gix-actor v0.25.0, gix-object v0.35.0, gix-path v0.9.0, gix-glob v0.11.0, gix-quote v0.4.7, gix-attributes v0.17.0, gix-command v0.2.9, gix-packetline-blocking v0.16.5, gix-filter v0.3.0, gix-fs v0.5.0, gix-commitgraph v0.19.0, gix-hashtable v0.3.0, gix-revwalk v0.6.0, gix-traverse v0.31.0, gix-worktree-stream v0.3.0, gix-archive v0.3.0, gix-config-value v0.13.0, gix-tempfile v8.0.0, gix-lock v8.0.0, gix-ref v0.35.0, gix-sec v0.9.0, gix-config v0.28.0, gix-prompt v0.6.0, gix-url v0.22.0, gix-credentials v0.18.0, gix-diff v0.34.0, gix-discover v0.23.0, gix-ignore v0.6.0, gix-bitmap v0.2.7, gix-index v0.22.0, gix-mailmap v0.17.0, gix-negotiate v0.6.0, gix-pack v0.41.0, gix-odb v0.51.0, gix-pathspec v0.1.0, gix-packetline v0.16.5, gix-transport v0.35.0, gix-protocol v0.38.0, gix-revision v0.20.0, gix-refspec v0.16.0, gix-submodule v0.2.0, gix-worktree v0.24.0, gix-worktree-state v0.1.0, gix v0.52.0, gitoxide-core v0.31.0, gitoxide v0.29.0, safety bump 41 crates ([`30b2761`](https://github.com/Byron/gitoxide/commit/30b27615047692d3ced1b2d9c2ac15a80f79fbee)) - Update changelogs prior to release ([`f23ea88`](https://github.com/Byron/gitoxide/commit/f23ea8828f2d9ba7559973daca388c9591bcc5fc)) - Merge branch 'submodule-in-gix' ([`36f7b78`](https://github.com/Byron/gitoxide/commit/36f7b783c67b8a087076a130f5ee9b90b23bc3cc)) - Add `FileSnapshot::new()` to create free-floating instances. ([`2650843`](https://github.com/Byron/gitoxide/commit/2650843c19ba2f41fe188c7db93c3536e19b2507))
## 0.4.1 (2023-07-22) ### New Features - add `is_executable()` function to determine if metadata of a file is executable. ### Commit Statistics - 7 commits contributed to the release over the course of 1 calendar day. - 3 days passed between releases. - 1 commit was understood as [conventional](https://www.conventionalcommits.org). - 0 issues like '(#ID)' were seen in commit messages ### Commit Details
view details * **Uncategorized** - Release gix-features v0.32.1, gix-actor v0.24.1, gix-validate v0.7.7, gix-object v0.33.1, gix-path v0.8.4, gix-glob v0.10.1, gix-quote v0.4.6, gix-attributes v0.16.0, gix-command v0.2.8, gix-packetline-blocking v0.16.4, gix-filter v0.2.0, gix-fs v0.4.1, gix-chunk v0.4.4, gix-commitgraph v0.18.1, gix-hashtable v0.2.4, gix-revwalk v0.4.1, gix-traverse v0.30.1, gix-worktree-stream v0.2.0, gix-archive v0.2.0, gix-config-value v0.12.5, gix-tempfile v7.0.1, gix-utils v0.1.5, gix-lock v7.0.2, gix-ref v0.33.1, gix-sec v0.8.4, gix-prompt v0.5.4, gix-url v0.21.1, gix-credentials v0.17.1, gix-diff v0.33.1, gix-discover v0.22.1, gix-ignore v0.5.1, gix-bitmap v0.2.6, gix-index v0.21.1, gix-mailmap v0.16.1, gix-negotiate v0.5.1, gix-pack v0.40.1, gix-odb v0.50.1, gix-packetline v0.16.4, gix-transport v0.34.1, gix-protocol v0.36.1, gix-revision v0.18.1, gix-refspec v0.14.1, gix-worktree v0.23.0, gix v0.50.0, safety bump 5 crates ([`16295b5`](https://github.com/Byron/gitoxide/commit/16295b58e2581d2e8b8b762816f52baabe871c75)) - Prepare more changelogs ([`c4cc5f2`](https://github.com/Byron/gitoxide/commit/c4cc5f261d29f712a101033a18293a97a9d4ae85)) - Release gix-date v0.7.1, gix-hash v0.11.4, gix-trace v0.1.3, gix-features v0.32.0, gix-actor v0.24.0, gix-validate v0.7.7, gix-object v0.33.0, gix-path v0.8.4, gix-glob v0.10.0, gix-quote v0.4.6, gix-attributes v0.15.0, gix-command v0.2.7, gix-packetline-blocking v0.16.3, gix-filter v0.1.0, gix-fs v0.4.0, gix-chunk v0.4.4, gix-commitgraph v0.18.0, gix-hashtable v0.2.4, gix-revwalk v0.4.0, gix-traverse v0.30.0, gix-worktree-stream v0.2.0, gix-archive v0.2.0, gix-config-value v0.12.4, gix-tempfile v7.0.1, gix-utils v0.1.5, gix-lock v7.0.2, gix-ref v0.33.0, gix-sec v0.8.4, gix-prompt v0.5.3, gix-url v0.21.0, gix-credentials v0.17.0, gix-diff v0.33.0, gix-discover v0.22.0, gix-ignore v0.5.0, gix-bitmap v0.2.6, gix-index v0.21.0, gix-mailmap v0.16.0, gix-negotiate v0.5.0, gix-pack v0.40.0, gix-odb v0.50.0, gix-packetline v0.16.4, gix-transport v0.34.0, gix-protocol v0.36.0, gix-revision v0.18.0, gix-refspec v0.14.0, gix-worktree v0.22.0, gix v0.49.1 ([`5cb3589`](https://github.com/Byron/gitoxide/commit/5cb3589b74fc5376e02cbfe151e71344e1c417fe)) - Update changelogs prior to release ([`2fc66b5`](https://github.com/Byron/gitoxide/commit/2fc66b55097ed494b72d1af939ba5561f71fde97)) - Merge branch 'gix-archive' ([`1dda48b`](https://github.com/Byron/gitoxide/commit/1dda48ba2fccb93ebac00fe3460e923af43c86ce)) - Add `is_executable()` function to determine if metadata of a file is executable. ([`a9eab8d`](https://github.com/Byron/gitoxide/commit/a9eab8d59442be19e05be4912c756188675b2bda)) - Update license field following SPDX 2.1 license expression standard ([`9064ea3`](https://github.com/Byron/gitoxide/commit/9064ea31fae4dc59a56bdd3a06c0ddc990ee689e))
## 0.4.0 (2023-07-19) A maintenance release without user-facing changes. ### New Features - add `is_executable()` function to determine if metadata of a file is executable. ### Commit Statistics - 3 commits contributed to the release. - 26 days passed between releases. - 0 commits were understood as [conventional](https://www.conventionalcommits.org). - 0 issues like '(#ID)' were seen in commit messages ### Commit Details
view details * **Uncategorized** - Release gix-features v0.32.0, gix-actor v0.24.0, gix-glob v0.10.0, gix-attributes v0.15.0, gix-commitgraph v0.18.0, gix-config-value v0.12.4, gix-fs v0.4.0, gix-object v0.33.0, gix-ref v0.33.0, gix-config v0.26.0, gix-command v0.2.7, gix-url v0.21.0, gix-credentials v0.17.0, gix-diff v0.33.0, gix-discover v0.22.0, gix-filter v0.1.0, gix-ignore v0.5.0, gix-revwalk v0.4.0, gix-traverse v0.30.0, gix-index v0.21.0, gix-mailmap v0.16.0, gix-negotiate v0.5.0, gix-pack v0.40.0, gix-odb v0.50.0, gix-transport v0.34.0, gix-protocol v0.36.0, gix-revision v0.18.0, gix-refspec v0.14.0, gix-worktree v0.22.0, gix v0.49.0 ([`68ae3ff`](https://github.com/Byron/gitoxide/commit/68ae3ff9d642ec56f088a6a682a073dc16f4e8ca)) - Adjust package versions (by cargo-smart-release) ([`c70e54f`](https://github.com/Byron/gitoxide/commit/c70e54f163c312c87753a506eeaad462e8579bfb)) - Prepare changelogs prior to release ([`e4dded0`](https://github.com/Byron/gitoxide/commit/e4dded05138562f9737a7dcfb60570c55769486d))
## 0.3.0 (2023-06-22) A maintenance release without user-facing changes. ### Commit Statistics - 4 commits contributed to the release over the course of 5 calendar days. - 15 days passed between releases. - 0 commits were understood as [conventional](https://www.conventionalcommits.org). - 0 issues like '(#ID)' were seen in commit messages ### Commit Details
view details * **Uncategorized** - Release gix-date v0.6.0, gix-hash v0.11.3, gix-trace v0.1.1, gix-features v0.31.0, gix-actor v0.22.0, gix-path v0.8.2, gix-glob v0.9.0, gix-quote v0.4.5, gix-attributes v0.14.0, gix-chunk v0.4.3, gix-commitgraph v0.17.0, gix-config-value v0.12.2, gix-fs v0.3.0, gix-tempfile v7.0.0, gix-utils v0.1.3, gix-lock v7.0.0, gix-validate v0.7.6, gix-object v0.31.0, gix-ref v0.31.0, gix-sec v0.8.2, gix-config v0.24.0, gix-command v0.2.6, gix-prompt v0.5.2, gix-url v0.20.0, gix-credentials v0.16.0, gix-diff v0.31.0, gix-discover v0.20.0, gix-hashtable v0.2.2, gix-ignore v0.4.0, gix-bitmap v0.2.5, gix-revwalk v0.2.0, gix-traverse v0.28.0, gix-index v0.19.0, gix-mailmap v0.14.0, gix-negotiate v0.3.0, gix-pack v0.38.0, gix-odb v0.48.0, gix-packetline v0.16.3, gix-transport v0.33.0, gix-protocol v0.34.0, gix-revision v0.16.0, gix-refspec v0.12.0, gix-worktree v0.20.0, gix v0.47.0, gitoxide-core v0.29.0, gitoxide v0.27.0, safety bump 30 crates ([`ea9f942`](https://github.com/Byron/gitoxide/commit/ea9f9424e777f10da0e33bb9ffbbefd01c4c5a74)) - Prepare changelogs prior to release ([`18b0a37`](https://github.com/Byron/gitoxide/commit/18b0a371941aa2d4d62512437d5daa351ba99ffd)) - Merge branch 'corpus' ([`aa16c8c`](https://github.com/Byron/gitoxide/commit/aa16c8ce91452a3e3063cf1cf0240b6014c4743f)) - Change MSRV to 1.65 ([`4f635fc`](https://github.com/Byron/gitoxide/commit/4f635fc4429350bae2582d25de86429969d28f30))
## 0.2.0 (2023-06-06) ### Chore - inline format args ### Commit Statistics - 9 commits contributed to the release over the course of 12 calendar days. - 41 days passed between releases. - 1 commit was understood as [conventional](https://www.conventionalcommits.org). - 0 issues like '(#ID)' were seen in commit messages ### Commit Details
view details * **Uncategorized** - Release gix-date v0.5.1, gix-hash v0.11.2, gix-features v0.30.0, gix-actor v0.21.0, gix-path v0.8.1, gix-glob v0.8.0, gix-quote v0.4.4, gix-attributes v0.13.0, gix-chunk v0.4.2, gix-commitgraph v0.16.0, gix-config-value v0.12.1, gix-fs v0.2.0, gix-tempfile v6.0.0, gix-utils v0.1.2, gix-lock v6.0.0, gix-validate v0.7.5, gix-object v0.30.0, gix-ref v0.30.0, gix-sec v0.8.1, gix-config v0.23.0, gix-command v0.2.5, gix-prompt v0.5.1, gix-url v0.19.0, gix-credentials v0.15.0, gix-diff v0.30.0, gix-discover v0.19.0, gix-hashtable v0.2.1, gix-ignore v0.3.0, gix-bitmap v0.2.4, gix-traverse v0.26.0, gix-index v0.17.0, gix-mailmap v0.13.0, gix-revision v0.15.0, gix-negotiate v0.2.0, gix-pack v0.36.0, gix-odb v0.46.0, gix-packetline v0.16.2, gix-transport v0.32.0, gix-protocol v0.33.0, gix-refspec v0.11.0, gix-worktree v0.18.0, gix v0.45.0, safety bump 29 crates ([`9a9fa96`](https://github.com/Byron/gitoxide/commit/9a9fa96fa8a722bddc5c3b2270b0edf8f6615141)) - Prepare changelogs prior to release ([`8f15cec`](https://github.com/Byron/gitoxide/commit/8f15cec1ec7d5a9d56bb158f155011ef2bb3539b)) - Merge branch 'fix-docs' ([`420553a`](https://github.com/Byron/gitoxide/commit/420553a10d780e0b2dc466cac120989298a5f187)) - Cleaning up documentation ([`2578e57`](https://github.com/Byron/gitoxide/commit/2578e576bfa365d194a23a1fb0bf09be230873de)) - Merge branch 'main' into auto-clippy ([`3ef5c90`](https://github.com/Byron/gitoxide/commit/3ef5c90aebce23385815f1df674c1d28d58b4b0d)) - Merge pull request #864 from nyurik/lint-fmt ([`279dc09`](https://github.com/Byron/gitoxide/commit/279dc09446f41d7f1d76350fbfafb444e53cd7da)) - Merge branch 'blinxen/main' ([`9375cd7`](https://github.com/Byron/gitoxide/commit/9375cd75b01aa22a0e2eed6305fe45fabfd6c1ac)) - Inline format args ([`dbc6cbb`](https://github.com/Byron/gitoxide/commit/dbc6cbb4363c2532f81b0bd6e351c4577bb9e9a3)) - Include license files in all crates ([`facaaf6`](https://github.com/Byron/gitoxide/commit/facaaf633f01c857dcf2572c6dbe0a92b7105c1c))
## 0.1.1 (2023-04-26) A maintenance release without user-facing changes. ### Commit Statistics - 4 commits contributed to the release over the course of 1 calendar day. - 7 days passed between releases. - 0 commits were understood as [conventional](https://www.conventionalcommits.org). - 0 issues like '(#ID)' were seen in commit messages ### Commit Details
view details * **Uncategorized** - Release gix-hash v0.11.1, gix-path v0.7.4, gix-glob v0.6.0, gix-attributes v0.11.0, gix-config-value v0.11.0, gix-fs v0.1.1, gix-tempfile v5.0.3, gix-utils v0.1.1, gix-lock v5.0.1, gix-object v0.29.1, gix-ref v0.28.0, gix-sec v0.7.0, gix-config v0.21.0, gix-prompt v0.4.0, gix-url v0.17.0, gix-credentials v0.13.0, gix-diff v0.29.0, gix-discover v0.17.0, gix-hashtable v0.2.0, gix-ignore v0.1.0, gix-bitmap v0.2.3, gix-traverse v0.25.0, gix-index v0.16.0, gix-mailmap v0.12.0, gix-pack v0.34.0, gix-odb v0.44.0, gix-packetline v0.16.0, gix-transport v0.30.0, gix-protocol v0.31.0, gix-revision v0.13.0, gix-refspec v0.10.0, gix-worktree v0.16.0, gix v0.44.0, safety bump 7 crates ([`91134a1`](https://github.com/Byron/gitoxide/commit/91134a11c8ba0e942f692488ec9bce9fa1086324)) - Prepare changelogs prior to release ([`30a1a71`](https://github.com/Byron/gitoxide/commit/30a1a71f36f24faac0e0b362ffdfedea7f9cdbf1)) - Merge branch 'index-entries-attrs' ([`f37a930`](https://github.com/Byron/gitoxide/commit/f37a930aefa27e67f0b693ba9669cc26d49044fa)) - Add remaining docs to get `gix-fs` into 'early' mode. ([`5783df2`](https://github.com/Byron/gitoxide/commit/5783df24df627cf6993a59e5dbaedef4e31a2d0b))
## 0.1.0 (2023-04-19) The initial release. ### Commit Statistics - 7 commits contributed to the release over the course of 2 calendar days. - 0 commits were understood as [conventional](https://www.conventionalcommits.org). - 0 issues like '(#ID)' were seen in commit messages ### Commit Details
view details * **Uncategorized** - Release gix-fs v0.1.0 ([`1d64ef6`](https://github.com/Byron/gitoxide/commit/1d64ef65258ffd693a1d42d1c15f33e7e86f4464)) - Fix `gix-fs` manifest ([`d1c7605`](https://github.com/Byron/gitoxide/commit/d1c7605fc9deb3f4bbdafa043ecc6523a6917de0)) - Release gix-utils v0.1.0, gix-hash v0.11.0, gix-date v0.5.0, gix-features v0.29.0, gix-actor v0.20.0, gix-object v0.29.0, gix-archive v0.1.0, gix-fs v0.1.0, safety bump 25 crates ([`8dbd0a6`](https://github.com/Byron/gitoxide/commit/8dbd0a60557a85acfa231800a058cbac0271a8cf)) - Prepare changelog prior to release ([`7f06458`](https://github.com/Byron/gitoxide/commit/7f064583bd0e1b078df89a7750f5a25deb70f516)) - Make fmt ([`5d2b5d0`](https://github.com/Byron/gitoxide/commit/5d2b5d02c3869e07dc2507a8f2519ee1df633df7)) - Merge branch 'main' into dev ([`cdef398`](https://github.com/Byron/gitoxide/commit/cdef398c4a3bd01baf0be2c27a3f77a400172b0d)) - Create new `gix-fs` crate to consolidate all filesystem utilities ([`f8cc33c`](https://github.com/Byron/gitoxide/commit/f8cc33cb372dd2b4bbe4a09cf4f64916681ab1dd))
gix-fs-0.8.0/Cargo.toml0000644000000015660000000000100102440ustar # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies. # # If you are reading this file be aware that the original Cargo.toml # will likely look very different (and much more reasonable). # See Cargo.toml.orig for the original contents. [package] edition = "2021" rust-version = "1.65" name = "gix-fs" version = "0.8.0" authors = ["Sebastian Thiel "] description = "A crate providing file system specific utilities to `gitoxide`" license = "MIT OR Apache-2.0" repository = "https://github.com/Byron/gitoxide" [lib] doctest = false [dependencies.gix-features] version = "^0.36.0" [dev-dependencies.tempfile] version = "3.5.0" gix-fs-0.8.0/Cargo.toml.orig000064400000000000000000000006731046102023000137230ustar 00000000000000[package] name = "gix-fs" version = "0.8.0" repository = "https://github.com/Byron/gitoxide" license = "MIT OR Apache-2.0" description = "A crate providing file system specific utilities to `gitoxide`" authors = ["Sebastian Thiel "] edition = "2021" rust-version = "1.65" [lib] doctest = false [dependencies] gix-features = { version = "^0.36.0", path = "../gix-features" } [dev-dependencies] tempfile = "3.5.0" gix-fs-0.8.0/LICENSE-APACHE000064400000000000000000000251221046102023000127540ustar 00000000000000 Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS Copyright 2018-2021 Sebastian Thiel, and [contributors](https://github.com/byron/gitoxide/contributors) Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. gix-fs-0.8.0/LICENSE-MIT000064400000000000000000000021721046102023000124640ustar 00000000000000MIT License Copyright (c) 2018-2021 Sebastian Thiel, and [contributors](https://github.com/byron/gitoxide/contributors). Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. gix-fs-0.8.0/src/capabilities.rs000064400000000000000000000075251046102023000146250ustar 00000000000000// TODO: tests use std::path::Path; use crate::Capabilities; #[cfg(windows)] impl Default for Capabilities { fn default() -> Self { Capabilities { precompose_unicode: false, ignore_case: true, executable_bit: false, symlink: false, } } } #[cfg(target_os = "macos")] impl Default for Capabilities { fn default() -> Self { Capabilities { precompose_unicode: true, ignore_case: true, executable_bit: true, symlink: true, } } } #[cfg(all(unix, not(target_os = "macos")))] impl Default for Capabilities { fn default() -> Self { Capabilities { precompose_unicode: false, ignore_case: false, executable_bit: true, symlink: true, } } } impl Capabilities { /// try to determine all values in this context by probing them in the given `git_dir`, which /// should be on the file system the git repository is located on. /// `git_dir` is a typical git repository, expected to be populated with the typical files like `config`. /// /// All errors are ignored and interpreted on top of the default for the platform the binary is compiled for. pub fn probe(git_dir: &Path) -> Self { let ctx = Capabilities::default(); Capabilities { symlink: Self::probe_symlink(git_dir).unwrap_or(ctx.symlink), ignore_case: Self::probe_ignore_case(git_dir).unwrap_or(ctx.ignore_case), precompose_unicode: Self::probe_precompose_unicode(git_dir).unwrap_or(ctx.precompose_unicode), executable_bit: Self::probe_file_mode(git_dir).unwrap_or(ctx.executable_bit), } } #[cfg(unix)] fn probe_file_mode(root: &Path) -> std::io::Result { use std::os::unix::fs::{MetadataExt, OpenOptionsExt}; // test it exactly as we typically create executable files, not using chmod. let test_path = root.join("_test_executable_bit"); let res = std::fs::OpenOptions::new() .create_new(true) .write(true) .mode(0o777) .open(&test_path) .and_then(|f| f.metadata().map(|m| m.mode() & 0o100 == 0o100)); std::fs::remove_file(test_path)?; res } #[cfg(not(unix))] fn probe_file_mode(_root: &Path) -> std::io::Result { Ok(false) } fn probe_ignore_case(git_dir: &Path) -> std::io::Result { std::fs::metadata(git_dir.join("cOnFiG")).map(|_| true).or_else(|err| { if err.kind() == std::io::ErrorKind::NotFound { Ok(false) } else { Err(err) } }) } fn probe_precompose_unicode(root: &Path) -> std::io::Result { let precomposed = "ä"; let decomposed = "a\u{308}"; let precomposed = root.join(precomposed); std::fs::OpenOptions::new() .create_new(true) .write(true) .open(&precomposed)?; let res = root.join(decomposed).symlink_metadata().map(|_| true); std::fs::remove_file(precomposed)?; res } fn probe_symlink(root: &Path) -> std::io::Result { let src_path = root.join("__link_src_file"); std::fs::OpenOptions::new() .create_new(true) .write(true) .open(&src_path)?; let link_path = root.join("__file_link"); if crate::symlink::create(&src_path, &link_path).is_err() { std::fs::remove_file(&src_path)?; return Ok(false); } let res = std::fs::symlink_metadata(&link_path).map(|m| m.file_type().is_symlink()); let cleanup = crate::symlink::remove(&link_path).or_else(|_| std::fs::remove_file(&link_path)); std::fs::remove_file(&src_path).and(cleanup)?; res } } gix-fs-0.8.0/src/dir/create.rs000064400000000000000000000167721046102023000142210ustar 00000000000000//! use std::path::Path; /// The amount of retries to do during various aspects of the directory creation. #[derive(Debug, Clone, Copy, Ord, PartialOrd, Eq, PartialEq)] pub struct Retries { /// How many times the whole directory can be created in the light of racy interference. /// This count combats racy situations where another process is trying to remove a directory that we want to create, /// and is deliberately higher than those who do deletion. That way, creation usually wins. pub to_create_entire_directory: usize, /// The amount of times we can try to create a directory because we couldn't as the parent didn't exist. /// This amounts to the maximum subdirectory depth we allow to be created. Counts once per attempt to create the entire directory. pub on_create_directory_failure: usize, /// How often to retry to create a single directory if an interrupt happens, as caused by signals. pub on_interrupt: usize, } impl Default for Retries { fn default() -> Self { Retries { on_interrupt: 10, to_create_entire_directory: 5, on_create_directory_failure: 25, } } } mod error { use std::{fmt, path::Path}; use crate::dir::create::Retries; /// The error returned by [all()][super::all()]. #[allow(missing_docs)] #[derive(Debug)] pub enum Error<'a> { /// A failure we will probably recover from by trying again. Intermediate { dir: &'a Path, kind: std::io::ErrorKind }, /// A failure that ends the operation. Permanent { dir: &'a Path, err: std::io::Error, /// The retries left after running the operation retries_left: Retries, /// The original amount of retries to allow determining how many were actually used retries: Retries, }, } impl<'a> fmt::Display for Error<'a> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Error::Intermediate { dir, kind } => write!( f, "Intermediae failure creating {:?} with error: {:?}", dir.display(), kind ), Error::Permanent { err: _, dir, retries_left, retries, } => write!( f, "Permanently failing to create directory {dir:?} ({retries_left:?} of {retries:?})", ), } } } impl<'a> std::error::Error for Error<'a> { fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { match self { Error::Permanent { err, .. } => Some(err), _ => None, } } } } pub use error::Error; enum State { CurrentlyCreatingDirectories, SearchingUpwardsForExistingDirectory, } /// A special iterator which communicates its operation through results where… /// /// * `Some(Ok(created_directory))` is yielded once or more success, followed by `None` /// * `Some(Err(Error::Intermediate))` is yielded zero or more times while trying to create the directory. /// * `Some(Err(Error::Permanent))` is yielded exactly once on failure. pub struct Iter<'a> { cursors: Vec<&'a Path>, retries: Retries, original_retries: Retries, state: State, } /// Construction impl<'a> Iter<'a> { /// Create a new instance that creates `target` when iterated with the default amount of [`Retries`]. pub fn new(target: &'a Path) -> Self { Self::new_with_retries(target, Default::default()) } /// Create a new instance that creates `target` when iterated with the specified amount of `retries`. pub fn new_with_retries(target: &'a Path, retries: Retries) -> Self { Iter { cursors: vec![target], original_retries: retries, retries, state: State::SearchingUpwardsForExistingDirectory, } } } impl<'a> Iter<'a> { fn permanent_failure( &mut self, dir: &'a Path, err: impl Into, ) -> Option>> { self.cursors.clear(); Some(Err(Error::Permanent { err: err.into(), dir, retries_left: self.retries, retries: self.original_retries, })) } fn intermediate_failure(&self, dir: &'a Path, err: std::io::Error) -> Option>> { Some(Err(Error::Intermediate { dir, kind: err.kind() })) } } impl<'a> Iterator for Iter<'a> { type Item = Result<&'a Path, Error<'a>>; fn next(&mut self) -> Option { use std::io::ErrorKind::*; match self.cursors.pop() { Some(dir) => match std::fs::create_dir(dir) { Ok(()) => { self.state = State::CurrentlyCreatingDirectories; Some(Ok(dir)) } Err(err) => match err.kind() { AlreadyExists if dir.is_dir() => { self.state = State::CurrentlyCreatingDirectories; Some(Ok(dir)) } AlreadyExists => self.permanent_failure(dir, err), // is non-directory NotFound => { self.retries.on_create_directory_failure -= 1; if let State::CurrentlyCreatingDirectories = self.state { self.state = State::SearchingUpwardsForExistingDirectory; self.retries.to_create_entire_directory -= 1; if self.retries.to_create_entire_directory < 1 { return self.permanent_failure(dir, NotFound); } self.retries.on_create_directory_failure = self.original_retries.on_create_directory_failure; } if self.retries.on_create_directory_failure < 1 { return self.permanent_failure(dir, NotFound); }; self.cursors.push(dir); self.cursors.push(match dir.parent() { None => return self.permanent_failure(dir, InvalidInput), Some(parent) => parent, }); self.intermediate_failure(dir, err) } Interrupted => { self.retries.on_interrupt -= 1; if self.retries.on_interrupt <= 1 { return self.permanent_failure(dir, Interrupted); }; self.cursors.push(dir); self.intermediate_failure(dir, err) } _unexpected_kind => self.permanent_failure(dir, err), }, }, None => None, } } } /// Create all directories leading to `dir` including `dir` itself with the specified amount of `retries`. /// Returns the input `dir` on success that make it useful in expressions. pub fn all(dir: &Path, retries: Retries) -> std::io::Result<&Path> { for res in Iter::new_with_retries(dir, retries) { match res { Err(Error::Permanent { err, .. }) => return Err(err), Err(Error::Intermediate { .. }) | Ok(_) => continue, } } Ok(dir) } gix-fs-0.8.0/src/dir/mod.rs000064400000000000000000000000501046102023000135130ustar 00000000000000/// pub mod create; /// pub mod remove; gix-fs-0.8.0/src/dir/remove.rs000064400000000000000000000075631046102023000142510ustar 00000000000000//! use std::path::{Path, PathBuf}; /// A special iterator which communicates its operation through results where… /// /// * `Some(Ok(removed_directory))` is yielded once or more success, followed by `None` /// * `Some(Err(std::io::Error))` is yielded exactly once on failure. pub struct Iter<'a> { cursor: Option<&'a Path>, boundary: &'a Path, } /// Construction impl<'a> Iter<'a> { /// Create a new instance that deletes `target` but will stop at `boundary`, without deleting the latter. /// Returns an error if `boundary` doesn't contain `target` /// /// **Note** that we don't canonicalize the path for performance reasons. pub fn new(target: &'a Path, boundary: &'a Path) -> std::io::Result { if !target.starts_with(boundary) { return Err(std::io::Error::new( std::io::ErrorKind::InvalidInput, format!("Removal target {target:?} must be contained in boundary {boundary:?}"), )); } let cursor = if target == boundary { None } else if target.exists() { Some(target) } else { None }; Ok(Iter { cursor, boundary }) } } impl<'a> Iterator for Iter<'a> { type Item = std::io::Result<&'a Path>; fn next(&mut self) -> Option { match self.cursor.take() { Some(dir) => { let next = match std::fs::remove_dir(dir) { Ok(()) => Some(Ok(dir)), Err(err) => match err.kind() { std::io::ErrorKind::NotFound => Some(Ok(dir)), _other_error_kind => return Some(Err(err)), }, }; self.cursor = match dir.parent() { Some(parent) => (parent != self.boundary).then_some(parent), None => { unreachable!("directory {:?} ran out of parents, this really shouldn't happen before hitting the boundary {:?}", dir, self.boundary) } }; next } None => None, } } } /// Delete all empty directories from `delete_dir` upward and until (not including) the `boundary_dir`. /// /// Note that `boundary_dir` must contain `delete_dir` or an error is returned, otherwise `delete_dir` is returned on success. pub fn empty_upward_until_boundary<'a>(delete_dir: &'a Path, boundary_dir: &'a Path) -> std::io::Result<&'a Path> { for item in Iter::new(delete_dir, boundary_dir)? { match item { Ok(_dir) => continue, Err(err) => return Err(err), } } Ok(delete_dir) } /// Delete all empty directories reachable from `delete_dir` from empty leaves moving upward to and including `delete_dir`. /// /// If any encountered directory contains a file the entire operation is aborted. /// Please note that this is inherently racy and no attempts are made to counter that, which will allow creators to win /// as long as they retry. pub fn empty_depth_first(delete_dir: PathBuf) -> std::io::Result<()> { if let Ok(()) = std::fs::remove_dir(&delete_dir) { return Ok(()); } let mut stack = vec![delete_dir]; let mut next_to_push = Vec::new(); while let Some(dir_to_delete) = stack.pop() { let mut num_entries = 0; for entry in std::fs::read_dir(&dir_to_delete)? { num_entries += 1; let entry = entry?; if entry.file_type()?.is_dir() { next_to_push.push(entry.path()); } else { return Err(std::io::Error::new(std::io::ErrorKind::Other, "Directory not empty")); } } if num_entries == 0 { std::fs::remove_dir(&dir_to_delete)?; } else { stack.push(dir_to_delete); stack.append(&mut next_to_push); } } Ok(()) } gix-fs-0.8.0/src/lib.rs000064400000000000000000000052771046102023000127440ustar 00000000000000//! A crate with file-system specific utilities. #![deny(rust_2018_idioms, missing_docs)] #![forbid(unsafe_code)] use std::path::PathBuf; /// Common knowledge about the worktree that is needed across most interactions with the work tree #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone, Copy)] pub struct Capabilities { /// If true, the filesystem will store paths as decomposed unicode, i.e. `ä` becomes `"a\u{308}"`, which means that /// we have to turn these forms back from decomposed to precomposed unicode before storing it in the index or generally /// using it. This also applies to input received from the command-line, so callers may have to be aware of this and /// perform conversions accordingly. /// If false, no conversions will be performed. pub precompose_unicode: bool, /// If true, the filesystem ignores the case of input, which makes `A` the same file as `a`. /// This is also called case-folding. pub ignore_case: bool, /// If true, we assume the executable bit is honored as part of the files mode. If false, we assume the file system /// ignores the executable bit, hence it will be reported as 'off' even though we just tried to set it to be on. pub executable_bit: bool, /// If true, the file system supports symbolic links and we should try to create them. Otherwise symbolic links will be checked /// out as files which contain the link as text. pub symlink: bool, } mod capabilities; mod snapshot; pub use snapshot::{FileSnapshot, SharedFileSnapshot, SharedFileSnapshotMut}; /// pub mod symlink; /// pub mod dir; /// A stack of path components with the delegation of side-effects as the currently set path changes, component by component. #[derive(Clone)] pub struct Stack { /// The prefix/root for all paths we handle. root: PathBuf, /// the most recent known cached that we know is valid. current: PathBuf, /// The relative portion of `valid` that was added previously. current_relative: PathBuf, /// The amount of path components of 'current' beyond the roots components. valid_components: usize, /// If set, we assume the `current` element is a directory to affect calls to `(push|pop)_directory()`. current_is_directory: bool, } #[cfg(unix)] /// Returns whether a a file has the executable permission set. pub fn is_executable(metadata: &std::fs::Metadata) -> bool { use std::os::unix::fs::MetadataExt; (metadata.mode() & 0o100) != 0 } #[cfg(not(unix))] /// Returns whether a a file has the executable permission set. pub fn is_executable(_metadata: &std::fs::Metadata) -> bool { false } /// pub mod stack; gix-fs-0.8.0/src/snapshot.rs000064400000000000000000000133601046102023000140250ustar 00000000000000// TODO: tests use std::ops::Deref; use gix_features::threading::{get_mut, get_ref, MutableOnDemand, OwnShared}; /// A structure holding enough information to reload a value if its on-disk representation changes as determined by its modified time. #[derive(Debug)] pub struct FileSnapshot { value: T, modified: std::time::SystemTime, } /// Lifecycle impl FileSnapshot { /// A way for users to create 'fake' snapshot from `value` that isn't actually linked to a file on disk. /// /// This is useful if there are alternative ways of obtaining the contained instance as fallback to trying /// to read it from disk. pub fn new(value: T) -> Self { FileSnapshot { value, modified: std::time::UNIX_EPOCH, } } } impl From for FileSnapshot { fn from(value: T) -> Self { FileSnapshot::new(value) } } impl Clone for FileSnapshot { fn clone(&self) -> Self { Self { value: self.value.clone(), modified: self.modified, } } } /// A snapshot of a resource which is up-to-date in the moment it is retrieved. pub type SharedFileSnapshot = OwnShared>; /// Use this type for fields in structs that are to store the [`FileSnapshot`], typically behind an [`OwnShared`]. /// /// Note that the resource itself is behind another [`OwnShared`] to allow it to be used without holding any kind of lock, hence /// without blocking updates while it is used. #[derive(Debug, Default)] pub struct SharedFileSnapshotMut(pub MutableOnDemand>>); impl Deref for FileSnapshot { type Target = T; fn deref(&self) -> &Self::Target { &self.value } } impl std::ops::DerefMut for FileSnapshot { fn deref_mut(&mut self) -> &mut Self::Target { &mut self.value } } impl Deref for SharedFileSnapshotMut { type Target = MutableOnDemand>>; fn deref(&self) -> &Self::Target { &self.0 } } impl SharedFileSnapshotMut { /// Create a new instance of this type. /// /// Useful in case `Default::default()` isn't working for some reason. pub fn new() -> Self { SharedFileSnapshotMut(MutableOnDemand::new(None)) } /// Refresh `state` forcefully by re-`open`ing the resource. Note that `open()` returns `None` if the resource isn't /// present on disk, and that it's critical that the modified time is obtained _before_ opening the resource. pub fn force_refresh( &self, open: impl FnOnce() -> Result, E>, ) -> Result<(), E> { let mut state = get_mut(&self.0); *state = open()?.map(|(modified, value)| OwnShared::new(FileSnapshot { value, modified })); Ok(()) } /// Assure that the resource in `state` is up-to-date by comparing the `current_modification_time` with the one we know in `state` /// and by acting accordingly. /// Returns the potentially updated/reloaded resource if it is still present on disk, which then represents a snapshot that is up-to-date /// in that very moment, or `None` if the underlying file doesn't exist. /// /// Note that even though this is racy, each time a request is made there is a chance to see the actual state. pub fn recent_snapshot( &self, mut current_modification_time: impl FnMut() -> Option, open: impl FnOnce() -> Result, E>, ) -> Result>, E> { let state = get_ref(self); let recent_modification = current_modification_time(); let buffer = match (&*state, recent_modification) { (None, None) => (*state).clone(), (Some(_), None) => { drop(state); let mut state = get_mut(self); *state = None; (*state).clone() } (Some(snapshot), Some(modified_time)) => { if snapshot.modified < modified_time { drop(state); let mut state = get_mut(self); if let (Some(_snapshot), Some(modified_time)) = (&*state, current_modification_time()) { *state = open()?.map(|value| { OwnShared::new(FileSnapshot { value, modified: modified_time, }) }); } (*state).clone() } else { // Note that this relies on sub-section precision or else is a race when the packed file was just changed. // It's nothing we can know though, so… up to the caller unfortunately. Some(snapshot.clone()) } } (None, Some(_modified_time)) => { drop(state); let mut state = get_mut(self); // Still in the same situation? If so, load the buffer. This compensates for the trampling herd // during lazy-loading at the expense of another mtime check. if let (None, Some(modified_time)) = (&*state, current_modification_time()) { *state = open()?.map(|value| { OwnShared::new(FileSnapshot { value, modified: modified_time, }) }); } (*state).clone() } }; Ok(buffer) } } gix-fs-0.8.0/src/stack.rs000064400000000000000000000104271046102023000132740ustar 00000000000000use std::path::{Path, PathBuf}; use crate::Stack; /// Access impl Stack { /// Returns the top-level path of the stack. pub fn root(&self) -> &Path { &self.root } /// Returns the absolute path the currently set path. pub fn current(&self) -> &Path { &self.current } /// Returns the currently set path relative to the [`root()`][Stack::root()]. pub fn current_relative(&self) -> &Path { &self.current_relative } } /// A delegate for use in a [`Stack`]. pub trait Delegate { /// Called whenever we push a directory on top of the stack, after the fact. /// /// It is also called if the currently acted on path is a directory in itself. /// Use `stack.current()` to see the directory. fn push_directory(&mut self, stack: &Stack) -> std::io::Result<()>; /// Called after any component was pushed, with the path available at `stack.current()`. /// /// `is_last_component` is true if the path is completely built. fn push(&mut self, is_last_component: bool, stack: &Stack) -> std::io::Result<()>; /// Called right after a directory-component was popped off the stack. /// /// Use it to pop information off internal data structures. fn pop_directory(&mut self); } impl Stack { /// Create a new instance with `root` being the base for all future paths we handle, assuming it to be valid which includes /// symbolic links to be included in it as well. pub fn new(root: PathBuf) -> Self { Stack { current: root.clone(), current_relative: PathBuf::with_capacity(128), valid_components: 0, root, current_is_directory: true, } } /// Set the current stack to point to the `relative` path and call `push_comp()` each time a new path component is popped /// along with the stacks state for inspection to perform an operation that produces some data. /// /// The full path to `relative` will be returned along with the data returned by `push_comp`. /// Note that this only works correctly for the delegate's `push_directory()` and `pop_directory()` methods if /// `relative` paths are terminal, so point to their designated file or directory. pub fn make_relative_path_current(&mut self, relative: &Path, delegate: &mut dyn Delegate) -> std::io::Result<()> { debug_assert!( relative.is_relative(), "only index paths are handled correctly here, must be relative" ); debug_assert!(!relative.to_string_lossy().is_empty(), "empty paths are not allowed"); if self.valid_components == 0 { delegate.push_directory(self)?; } let mut components = relative.components().peekable(); let mut existing_components = self.current_relative.components(); let mut matching_components = 0; while let (Some(existing_comp), Some(new_comp)) = (existing_components.next(), components.peek()) { if existing_comp == *new_comp { components.next(); matching_components += 1; } else { break; } } for _ in 0..self.valid_components - matching_components { self.current.pop(); self.current_relative.pop(); if self.current_is_directory { delegate.pop_directory(); } self.current_is_directory = true; } self.valid_components = matching_components; if !self.current_is_directory && components.peek().is_some() { delegate.push_directory(self)?; } while let Some(comp) = components.next() { let is_last_component = components.peek().is_none(); self.current_is_directory = !is_last_component; self.current.push(comp); self.current_relative.push(comp); self.valid_components += 1; let res = delegate.push(is_last_component, self); if self.current_is_directory { delegate.push_directory(self)?; } if let Err(err) = res { self.current.pop(); self.current_relative.pop(); self.valid_components -= 1; return Err(err); } } Ok(()) } } gix-fs-0.8.0/src/symlink.rs000064400000000000000000000044741046102023000136620ustar 00000000000000use std::{io, io::ErrorKind::AlreadyExists, path::Path}; #[cfg(not(windows))] /// Create a new symlink at `link` which points to `original`. pub fn create(original: &Path, link: &Path) -> io::Result<()> { std::os::unix::fs::symlink(original, link) } #[cfg(not(windows))] /// Remove a symlink. /// /// Note that on only on windows this is special. pub fn remove(path: &Path) -> io::Result<()> { std::fs::remove_file(path) } // TODO: use the `symlink` crate once it can delete directory symlinks /// Remove a symlink. #[cfg(windows)] pub fn remove(path: &Path) -> io::Result<()> { if let Ok(meta) = std::fs::metadata(path) { if meta.is_file() { std::fs::remove_file(path) // this removes the link itself } else { std::fs::remove_dir(path) // however, this sees the destination directory, which isn't the right thing actually } } else { std::fs::remove_file(path).or_else(|_| std::fs::remove_dir(path)) } } #[cfg(windows)] /// Create a new symlink at `link` which points to `original`. pub fn create(original: &Path, link: &Path) -> io::Result<()> { use std::os::windows::fs::{symlink_dir, symlink_file}; // TODO: figure out if links to links count as files or whatever they point at if std::fs::metadata(link.parent().expect("dir for link").join(original))?.is_dir() { symlink_dir(original, link) } else { symlink_file(original, link) } } #[cfg(not(windows))] /// Return true if `err` indicates that a file collision happened, i.e. a symlink couldn't be created as the `link` /// already exists as filesystem object. pub fn is_collision_error(err: &std::io::Error) -> bool { // TODO: use ::IsDirectory as well when stabilized instead of raw_os_error(), and ::FileSystemLoop respectively err.kind() == AlreadyExists || err.raw_os_error() == Some(21) || err.raw_os_error() == Some(62) // no-follow on symlnk on mac-os || err.raw_os_error() == Some(40) // no-follow on symlnk on ubuntu } #[cfg(windows)] /// Return true if `err` indicates that a file collision happened, i.e. a symlink couldn't be created as the `link` /// already exists as filesystem object. pub fn is_collision_error(err: &std::io::Error) -> bool { err.kind() == AlreadyExists || err.kind() == std::io::ErrorKind::PermissionDenied } gix-fs-0.8.0/tests/capabilities/mod.rs000064400000000000000000000010461046102023000157470ustar 00000000000000#[test] fn probe() { let dir = tempfile::tempdir().unwrap(); std::fs::File::create(dir.path().join("config")).unwrap(); let ctx = gix_fs::Capabilities::probe(dir.path()); dbg!(ctx); let entries: Vec<_> = std::fs::read_dir(dir.path()) .unwrap() .filter_map(Result::ok) .filter(|e| e.file_name().to_str() != Some("config")) .map(|e| e.path()) .collect(); assert_eq!( entries.len(), 0, "there should be no left-over files after probing, found {entries:?}" ); } gix-fs-0.8.0/tests/dir/create.rs000064400000000000000000000161701046102023000145640ustar 00000000000000mod all { use gix_fs::dir::create; #[test] fn a_deeply_nested_directory() -> crate::Result { let dir = tempfile::tempdir()?; let target = &dir.path().join("1").join("2").join("3").join("4").join("5").join("6"); let dir = create::all(target, Default::default())?; assert_eq!(dir, target, "all subdirectories can be created"); Ok(()) } } mod iter { pub use std::io::ErrorKind::*; use gix_fs::dir::{ create, create::{Error::*, Retries}, }; #[test] fn an_existing_directory_causes_immediate_success() -> crate::Result { let dir = tempfile::tempdir()?; let mut it = create::Iter::new(dir.path()); assert_eq!( it.next().expect("item").expect("success"), dir.path(), "first iteration is immediately successful" ); assert!(it.next().is_none(), "iterator exhausted afterwards"); Ok(()) } #[test] fn a_single_directory_can_be_created_too() -> crate::Result { let dir = tempfile::tempdir()?; let new_dir = dir.path().join("new"); let mut it = create::Iter::new(&new_dir); assert_eq!( it.next().expect("item").expect("success"), &new_dir, "first iteration is immediately successful" ); assert!(it.next().is_none(), "iterator exhausted afterwards"); assert!(new_dir.is_dir(), "the directory exists"); Ok(()) } #[test] fn multiple_intermediate_directories_are_created_automatically() -> crate::Result { let dir = tempfile::tempdir()?; let new_dir = dir.path().join("s1").join("s2").join("new"); let mut it = create::Iter::new(&new_dir); assert!( matches!(it.next(), Some(Err(Intermediate{dir, kind: k})) if k == NotFound && dir == new_dir), "dir is not present" ); assert!( matches!(it.next(), Some(Err(Intermediate{dir, kind:k})) if k == NotFound && dir == new_dir.parent().unwrap()), "parent dir is not present" ); assert_eq!( it.next().expect("item").expect("success"), new_dir.parent().unwrap().parent().unwrap(), "first subdir is created" ); assert_eq!( it.next().expect("item").expect("success"), new_dir.parent().unwrap(), "second subdir is created" ); assert_eq!( it.next().expect("item").expect("success"), new_dir, "target directory is created" ); assert!(it.next().is_none(), "iterator depleted"); assert!(new_dir.is_dir(), "the directory exists"); Ok(()) } #[test] fn multiple_intermediate_directories_are_created_up_to_retries_limit() -> crate::Result { let dir = tempfile::tempdir()?; let new_dir = dir.path().join("s1").join("s2").join("new"); let mut it = create::Iter::new_with_retries( &new_dir, Retries { on_create_directory_failure: 1, ..Default::default() }, ); assert!( matches!(it.next(), Some(Err(Permanent{ retries_left, dir, err, ..})) if retries_left.on_create_directory_failure == 0 && err.kind() == NotFound && dir == new_dir), "parent dir is not present and we run out of attempts" ); assert!(it.next().is_none(), "iterator depleted"); assert!(!new_dir.is_dir(), "the wasn't created"); Ok(()) } #[test] fn an_existing_file_makes_directory_creation_fail_permanently() -> crate::Result { let dir = tempfile::tempdir()?; let new_dir = dir.path().join("also-file"); std::fs::write(&new_dir, [42])?; assert!(new_dir.is_file()); let mut it = create::Iter::new(&new_dir); assert!( matches!(it.next(), Some(Err(Permanent{ dir, err, .. })) if err.kind() == AlreadyExists && dir == new_dir), "parent dir is not present and we run out of attempts" ); assert!(it.next().is_none(), "iterator depleted"); assert!(new_dir.is_file(), "file is untouched"); Ok(()) } #[test] fn racy_directory_creation_with_new_directory_being_deleted_not_enough_retries() -> crate::Result { let dir = tempfile::tempdir()?; let new_dir = dir.path().join("a").join("new"); let parent_dir = new_dir.parent().unwrap(); let mut it = create::Iter::new_with_retries( &new_dir, Retries { to_create_entire_directory: 2, on_create_directory_failure: 2, ..Default::default() }, ); assert!( matches!(it.nth(1), Some(Ok(dir)) if dir == parent_dir), "parent dir is created" ); // Someone deletes the new directory std::fs::remove_dir(parent_dir)?; assert!( matches!(it.nth(1), Some(Ok(dir)) if dir == parent_dir), "parent dir is created" ); // Someone deletes the new directory, again std::fs::remove_dir(parent_dir)?; assert!( matches!(it.next(), Some(Err(Permanent{ retries_left, dir, err, .. })) if retries_left.to_create_entire_directory == 0 && retries_left.on_create_directory_failure == 1 && err.kind() == NotFound && dir == new_dir), "we run out of attempts to retry to combat against raciness" ); Ok(()) } #[test] fn racy_directory_creation_with_new_directory_being_deleted() -> crate::Result { let dir = tempfile::tempdir()?; let new_dir = dir.path().join("a").join("new"); let parent_dir = new_dir.parent().unwrap(); let mut it = create::Iter::new(&new_dir); assert!( matches!(it.next(), Some(Err(Intermediate{dir, kind:k})) if k == NotFound && dir == new_dir), "dir is not present, and we go up a level" ); assert!( matches!(it.next(), Some(Ok(dir)) if dir == parent_dir), "parent dir is created" ); // Someone deletes the new directory std::fs::remove_dir(parent_dir)?; assert!( matches!(it.next(), Some(Err(Intermediate{dir, kind:k})) if k == NotFound && dir == new_dir), "now when it tries the actual dir its not found" ); assert!( matches!(it.next(), Some(Ok(dir)) if dir == parent_dir), "parent dir is created as it retries" ); assert!( matches!(it.next(), Some(Ok(dir)) if dir == new_dir), "target dir is created successfully" ); assert!(it.next().is_none(), "iterator depleted"); assert!(new_dir.is_dir()); Ok(()) } } gix-fs-0.8.0/tests/dir/mod.rs000064400000000000000000000000301046102023000140640ustar 00000000000000mod create; mod remove; gix-fs-0.8.0/tests/dir/remove.rs000064400000000000000000000134151046102023000146150ustar 00000000000000mod empty_upwards_until_boundary { use std::{io, path::Path}; use gix_fs::dir::remove; #[test] fn boundary_must_contain_target_dir() -> crate::Result { let dir = tempfile::tempdir()?; let (target, boundary) = (dir.path().join("a"), dir.path().join("b")); std::fs::create_dir(&target)?; std::fs::create_dir(&boundary)?; assert!(matches!(remove::empty_upward_until_boundary(&target, &boundary), Err(err) if err.kind() == io::ErrorKind::InvalidInput)); assert!(target.is_dir()); assert!(boundary.is_dir()); Ok(()) } #[test] fn target_directory_non_existing_causes_existing_parents_not_to_be_deleted() -> crate::Result { let dir = tempfile::tempdir()?; let parent = dir.path().join("a"); std::fs::create_dir(&parent)?; let target = parent.join("not-existing"); assert_eq!(remove::empty_upward_until_boundary(&target, dir.path())?, target); assert!( parent.is_dir(), "the parent wasn't touched if the target already wasn't present" ); Ok(()) } #[test] fn target_directory_being_a_file_immediately_fails() -> crate::Result { let dir = tempfile::tempdir()?; let target = dir.path().join("actually-a-file"); std::fs::write(&target, [42])?; assert!(remove::empty_upward_until_boundary(&target, dir.path()).is_err()); // TODO: check for IsNotADirectory when it becomes stable assert!(target.is_file(), "it didn't touch the file"); assert!(dir.path().is_dir(), "it won't touch the boundary"); Ok(()) } #[test] fn boundary_being_the_target_dir_always_succeeds_and_we_do_nothing() -> crate::Result { let dir = tempfile::tempdir()?; assert_eq!(remove::empty_upward_until_boundary(dir.path(), dir.path())?, dir.path()); assert!(dir.path().is_dir(), "it won't touch the boundary"); Ok(()) } #[test] fn a_directory_which_doesnt_exist_to_start_with_is_ok() -> crate::Result { let dir = tempfile::tempdir()?; let target = dir.path().join("does-not-exist"); assert_eq!(remove::empty_upward_until_boundary(&target, dir.path())?, target); assert!(dir.path().is_dir(), "it won't touch the boundary"); Ok(()) } #[test] fn boundary_directory_doesnt_have_to_exist_either_if_the_target_doesnt() -> crate::Result { let boundary = Path::new("/boundary"); let target = Path::new("/boundary/target"); assert_eq!(remove::empty_upward_until_boundary(target, boundary)?, target); Ok(()) } #[test] fn nested_directory_deletion_works() -> crate::Result { let dir = tempfile::tempdir()?; let nested = dir.path().join("a").join("b").join("to-delete"); std::fs::create_dir_all(&nested)?; assert_eq!(remove::empty_upward_until_boundary(&nested, dir.path())?, nested); assert!(!nested.is_dir(), "it actually deleted the nested directory"); assert!(!nested.parent().unwrap().is_dir(), "parent one was deleted"); assert!( !nested.parent().unwrap().parent().unwrap().is_dir(), "parent two was deleted" ); assert!(dir.path().is_dir(), "it won't touch the boundary"); Ok(()) } } mod empty_depth_first { use std::{ fs::{create_dir, create_dir_all}, path::Path, }; #[test] fn non_empty_anywhere_and_deletion_fails() -> crate::Result { let dir = tempfile::TempDir::new()?; let touch = |base: &Path, name: &str| create_dir_all(base).and_then(|_| std::fs::write(base.join(name), b"")); let nested_parent = dir.path().join("a"); touch(&nested_parent, "hello.ext")?; let tree_parent = dir.path().join("tree"); touch(&tree_parent.join("a").join("b"), "hello.ext")?; create_dir_all(tree_parent.join("one").join("two").join("empty"))?; assert!(gix_fs::dir::remove::empty_depth_first(nested_parent).is_err()); Ok(()) } #[test] fn nested_empty_and_single_empty_delete_successfully() { let dir = tempfile::TempDir::new().unwrap(); let nested_parent = dir.path().join("a"); let nested = nested_parent.join("b").join("leaf"); create_dir_all(nested).unwrap(); let single_parent = dir.path().join("single"); create_dir(&single_parent).unwrap(); let tree_parent = dir.path().join("tree"); create_dir_all(tree_parent.join("a").join("b")).unwrap(); create_dir_all(tree_parent.join("one").join("two").join("three")).unwrap(); create_dir_all(tree_parent.join("c")).unwrap(); for empty in &[nested_parent, single_parent, tree_parent] { gix_fs::dir::remove::empty_depth_first(empty.into()).unwrap(); } } } /// We assume that all checks above also apply to the iterator, so won't repeat them here /// Test outside interference only mod iter { use gix_fs::dir::remove; #[test] fn racy_directory_creation_during_deletion_always_wins_immediately() -> crate::Result { let dir = tempfile::tempdir()?; let nested = dir.path().join("a").join("b").join("to-delete"); std::fs::create_dir_all(&nested)?; let mut it = remove::Iter::new(&nested, dir.path())?; assert_eq!(it.next().expect("item")?, nested, "delete leaves directory"); // recreate the deleted directory in racy fashion, causing the next-to-delete directory not to be empty. std::fs::create_dir(&nested)?; assert!( it.next().expect("err item").is_err(), "cannot delete non-empty directory" // TODO: check for IsADirectory when it becomes stable ); assert!(it.next().is_none(), "iterator is depleted"); Ok(()) } } gix-fs-0.8.0/tests/fs.rs000064400000000000000000000002101046102023000131370ustar 00000000000000type Result = std::result::Result>; mod capabilities; mod dir; mod stack; gix-fs-0.8.0/tests/stack/mod.rs000064400000000000000000000064661046102023000144360ustar 00000000000000use std::path::{Path, PathBuf}; use gix_fs::Stack; #[derive(Debug, Default, Eq, PartialEq)] struct Record { push_dir: usize, dirs: Vec, push: usize, } impl gix_fs::stack::Delegate for Record { fn push_directory(&mut self, stack: &Stack) -> std::io::Result<()> { self.push_dir += 1; self.dirs.push(stack.current().into()); Ok(()) } fn push(&mut self, _is_last_component: bool, _stack: &Stack) -> std::io::Result<()> { self.push += 1; Ok(()) } fn pop_directory(&mut self) { self.dirs.pop(); } } #[test] fn delegate_calls_are_consistent() -> crate::Result { let root = PathBuf::from("."); let mut s = Stack::new(root.clone()); assert_eq!(s.current(), root); assert_eq!(s.current_relative(), Path::new("")); let mut r = Record::default(); s.make_relative_path_current("a/b".as_ref(), &mut r)?; let mut dirs = vec![root.clone(), root.join("a")]; assert_eq!( r, Record { push_dir: 2, dirs: dirs.clone(), push: 2, } ); s.make_relative_path_current("a/b2".as_ref(), &mut r)?; assert_eq!( r, Record { push_dir: 2, dirs: dirs.clone(), push: 3, } ); s.make_relative_path_current("c/d/e".as_ref(), &mut r)?; dirs.pop(); dirs.extend([root.join("c"), root.join("c").join("d")]); assert_eq!( r, Record { push_dir: 4, dirs: dirs.clone(), push: 6, } ); dirs.push(root.join("c").join("d").join("x")); s.make_relative_path_current("c/d/x/z".as_ref(), &mut r)?; assert_eq!( r, Record { push_dir: 5, dirs: dirs.clone(), push: 8, } ); dirs.drain(dirs.len() - 3..).count(); s.make_relative_path_current("f".as_ref(), &mut r)?; assert_eq!(s.current_relative(), Path::new("f")); assert_eq!( r, Record { push_dir: 5, dirs: dirs.clone(), push: 9, } ); dirs.push(root.join("x")); s.make_relative_path_current("x/z".as_ref(), &mut r)?; assert_eq!( r, Record { push_dir: 6, dirs: dirs.clone(), push: 11, } ); dirs.push(root.join("x").join("z")); s.make_relative_path_current("x/z/a".as_ref(), &mut r)?; assert_eq!( r, Record { push_dir: 7, dirs: dirs.clone(), push: 12, } ); dirs.push(root.join("x").join("z").join("a")); dirs.push(root.join("x").join("z").join("a").join("b")); s.make_relative_path_current("x/z/a/b/c".as_ref(), &mut r)?; assert_eq!( r, Record { push_dir: 9, dirs: dirs.clone(), push: 14, } ); dirs.drain(dirs.len() - 2..).count(); s.make_relative_path_current("x/z".as_ref(), &mut r)?; assert_eq!( r, Record { push_dir: 9, dirs: dirs.clone(), push: 14, } ); assert_eq!( dirs.last(), Some(&PathBuf::from("./x/z")), "the stack is state so keeps thinking it's a directory which is consistent. Git does it differently though." ); Ok(()) }