subversion-0.0.8/.cargo_vcs_info.json0000644000000001360000000000100132400ustar { "git": { "sha1": "b7d4dcf14f9bb32aa741343ed691d6a7a34ecf37" }, "path_in_vcs": "" }subversion-0.0.8/.github/CODEOWNERS000064400000000000000000000000121046102023000147540ustar 00000000000000* @jelmer subversion-0.0.8/.github/FUNDING.yml000064400000000000000000000000171046102023000152030ustar 00000000000000github: jelmer subversion-0.0.8/.github/dependabot.yml000064400000000000000000000006251046102023000162230ustar 00000000000000# Please see the documentation for all configuration options: # https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates version: 2 updates: - package-ecosystem: "cargo" directory: "/" schedule: interval: "weekly" rebase-strategy: "disabled" - package-ecosystem: "github-actions" directory: "/" schedule: interval: weekly subversion-0.0.8/.github/workflows/publish.yaml000064400000000000000000000012211046102023000177530ustar 00000000000000on: push: tags: - 'v*' # Push events to every tag not containing / workflow_dispatch: name: Publish jobs: publish: name: Publish runs-on: ubuntu-latest steps: - name: Checkout sources uses: actions/checkout@v4 - name: Install apr run: sudo apt install -y libapr1-dev libaprutil1-dev libutf8proc-dev - name: Install stable toolchain uses: actions-rs/toolchain@v1 with: profile: minimal toolchain: stable override: true - run: cargo publish --token ${CRATES_TOKEN} env: CRATES_TOKEN: ${{ secrets.CRATES_TOKEN }} subversion-0.0.8/.github/workflows/rust.yml000064400000000000000000000017001046102023000171430ustar 00000000000000name: Rust on: push: pull_request: env: CARGO_TERM_COLOR: always jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Install apr run: sudo apt install -y libapr1-dev libaprutil1-dev libsvn-dev pkg-config libutf8proc-dev libsqlite3-dev subversion # Work around https://bugs.debian.org/1055242 - name: "Fix up Version: field in Subversion .pc files" run: | svnversion=$(svn --version | grep -oP 'svn, version \K\d+\.\d+\.\d+') sudo sed -i "s/^Version: \$/Version: $svnversion/" /usr/lib/*/pkgconfig/libsvn_*.pc - name: Install cargo-all-features run: cargo install cargo-all-features - name: Build run: cargo build-all-features --verbose env: RUSTFLAGS: -Dwarnings - name: Run tests run: cargo test-all-features --verbose env: RUSTFLAGS: -Dwarnings - name: Check formatting run: cargo fmt -- --check subversion-0.0.8/.gitignore000064400000000000000000000000131046102023000140120ustar 00000000000000/target *~ subversion-0.0.8/COPYING000064400000000000000000000261361046102023000130730ustar 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 APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] 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. subversion-0.0.8/Cargo.lock0000644000000437570000000000100112330ustar # This file is automatically @generated by Cargo. # It is not intended for manual editing. version = 3 [[package]] name = "aho-corasick" version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" dependencies = [ "memchr", ] [[package]] name = "apr" version = "0.1.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "512197dbe69965909173bdeba6fc77c4ccbadfec3eea32952b0861668ab046c0" dependencies = [ "bindgen", "ctor", "system-deps", ] [[package]] name = "autocfg" version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" [[package]] name = "bindgen" version = "0.70.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f49d8fed880d473ea71efb9bf597651e77201bdd4893efe54c9e5d65ae04ce6f" dependencies = [ "bitflags", "cexpr", "clang-sys", "itertools", "log", "prettyplease", "proc-macro2", "quote", "regex", "rustc-hash", "shlex", "syn", ] [[package]] name = "bitflags" version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" [[package]] name = "cexpr" version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" dependencies = [ "nom", ] [[package]] name = "cfg-expr" version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d0890061c4d3223e7267f3bad2ec40b997d64faac1c2815a4a9d95018e2b9e9c" dependencies = [ "smallvec", "target-lexicon", ] [[package]] name = "cfg-if" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "clang-sys" version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4" dependencies = [ "glob", "libc", "libloading", ] [[package]] name = "ctor" version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "edb49164822f3ee45b17acd4a208cfc1251410cf0cad9a833234c9890774dd9f" dependencies = [ "quote", "syn", ] [[package]] name = "either" version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" [[package]] name = "equivalent" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" [[package]] name = "errno" version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" dependencies = [ "libc", "windows-sys 0.52.0", ] [[package]] name = "fastrand" version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e8c02a5121d4ea3eb16a80748c74f5549a5665e4c21333c6098f283870fbdea6" [[package]] name = "form_urlencoded" version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" dependencies = [ "percent-encoding", ] [[package]] name = "glob" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" [[package]] name = "hashbrown" version = "0.14.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" [[package]] name = "heck" version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" [[package]] name = "idna" version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6" dependencies = [ "unicode-bidi", "unicode-normalization", ] [[package]] name = "indexmap" version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "68b900aa2f7301e21c36462b170ee99994de34dff39a4a6a528e80e7376d07e5" dependencies = [ "equivalent", "hashbrown", ] [[package]] name = "indoc" version = "2.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b248f5224d1d606005e02c97f5aa4e88eeb230488bcc03bc9ca4d7991399f2b5" [[package]] name = "itertools" version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" dependencies = [ "either", ] [[package]] name = "lazy_static" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "libc" version = "0.2.159" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "561d97a539a36e26a9a5fad1ea11a3039a67714694aaa379433e580854bc3dc5" [[package]] name = "libloading" version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4979f22fdb869068da03c9f7528f8297c6fd2606bc3a4affe42e6a823fdb8da4" dependencies = [ "cfg-if", "windows-targets", ] [[package]] name = "linux-raw-sys" version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" [[package]] name = "log" version = "0.4.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" [[package]] name = "memchr" version = "2.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" [[package]] name = "memoffset" version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a" dependencies = [ "autocfg", ] [[package]] name = "minimal-lexical" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] name = "nom" version = "7.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" dependencies = [ "memchr", "minimal-lexical", ] [[package]] name = "once_cell" version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" [[package]] name = "percent-encoding" version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" [[package]] name = "pkg-config" version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2" [[package]] name = "portable-atomic" version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d30538d42559de6b034bc76fd6dd4c38961b1ee5c6c56e3808c50128fdbc22ce" [[package]] name = "prettyplease" version = "0.2.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "479cf940fbbb3426c32c5d5176f62ad57549a0bb84773423ba8be9d089f5faba" dependencies = [ "proc-macro2", "syn", ] [[package]] name = "proc-macro2" version = "1.0.86" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" dependencies = [ "unicode-ident", ] [[package]] name = "pyo3" version = "0.22.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "15ee168e30649f7f234c3d49ef5a7a6cbf5134289bc46c29ff3155fa3221c225" dependencies = [ "cfg-if", "indoc", "libc", "memoffset", "once_cell", "portable-atomic", "pyo3-build-config", "pyo3-ffi", "pyo3-macros", "unindent", ] [[package]] name = "pyo3-build-config" version = "0.22.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e61cef80755fe9e46bb8a0b8f20752ca7676dcc07a5277d8b7768c6172e529b3" dependencies = [ "once_cell", "target-lexicon", ] [[package]] name = "pyo3-ffi" version = "0.22.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "67ce096073ec5405f5ee2b8b31f03a68e02aa10d5d4f565eca04acc41931fa1c" dependencies = [ "libc", "pyo3-build-config", ] [[package]] name = "pyo3-macros" version = "0.22.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2440c6d12bc8f3ae39f1e775266fa5122fd0c8891ce7520fa6048e683ad3de28" dependencies = [ "proc-macro2", "pyo3-macros-backend", "quote", "syn", ] [[package]] name = "pyo3-macros-backend" version = "0.22.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1be962f0e06da8f8465729ea2cb71a416d2257dff56cbe40a70d3e62a93ae5d1" dependencies = [ "heck", "proc-macro2", "pyo3-build-config", "quote", "syn", ] [[package]] name = "quote" version = "1.0.37" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" dependencies = [ "proc-macro2", ] [[package]] name = "regex" version = "1.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4219d74c6b67a3654a9fbebc4b419e22126d13d2f3c4a07ee0cb61ff79a79619" dependencies = [ "aho-corasick", "memchr", "regex-automata", "regex-syntax", ] [[package]] name = "regex-automata" version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "38caf58cc5ef2fed281f89292ef23f6365465ed9a41b7a7754eb4e26496c92df" dependencies = [ "aho-corasick", "memchr", "regex-syntax", ] [[package]] name = "regex-syntax" version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b" [[package]] name = "rustc-hash" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" [[package]] name = "rustix" version = "0.38.37" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8acb788b847c24f28525660c4d7758620a7210875711f79e7f663cc152726811" dependencies = [ "bitflags", "errno", "libc", "linux-raw-sys", "windows-sys 0.52.0", ] [[package]] name = "serde" version = "1.0.210" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c8e3592472072e6e22e0a54d5904d9febf8508f65fb8552499a1abc7d1078c3a" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" version = "1.0.210" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "243902eda00fad750862fc144cea25caca5e20d615af0a81bee94ca738f1df1f" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "serde_spanned" version = "0.6.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "87607cb1398ed59d48732e575a4c28a7a8ebf2454b964fe3f224f2afc07909e1" dependencies = [ "serde", ] [[package]] name = "shlex" version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" [[package]] name = "smallvec" version = "1.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" [[package]] name = "subversion" version = "0.0.8" dependencies = [ "apr", "bindgen", "ctor", "lazy_static", "pyo3", "system-deps", "tempfile", "url", ] [[package]] name = "syn" version = "2.0.77" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9f35bcdf61fd8e7be6caf75f429fdca8beb3ed76584befb503b1569faee373ed" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] [[package]] name = "system-deps" version = "7.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "66d23aaf9f331227789a99e8de4c91bf46703add012bdfd45fdecdfb2975a005" dependencies = [ "cfg-expr", "heck", "pkg-config", "toml", "version-compare", ] [[package]] name = "target-lexicon" version = "0.12.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1" [[package]] name = "tempfile" version = "3.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "04cbcdd0c794ebb0d4cf35e88edd2f7d2c4c3e9a5a6dab322839b321c6a87a64" dependencies = [ "cfg-if", "fastrand", "once_cell", "rustix", "windows-sys 0.59.0", ] [[package]] name = "tinyvec" version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "445e881f4f6d382d5f27c034e25eb92edd7c784ceab92a0937db7f2e9471b938" dependencies = [ "tinyvec_macros", ] [[package]] name = "tinyvec_macros" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "toml" version = "0.8.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a1ed1f98e3fdc28d6d910e6737ae6ab1a93bf1985935a1193e68f93eeb68d24e" dependencies = [ "serde", "serde_spanned", "toml_datetime", "toml_edit", ] [[package]] name = "toml_datetime" version = "0.6.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41" dependencies = [ "serde", ] [[package]] name = "toml_edit" version = "0.22.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ae48d6208a266e853d946088ed816055e556cc6028c5e8e2b84d9fa5dd7c7f5" dependencies = [ "indexmap", "serde", "serde_spanned", "toml_datetime", "winnow", ] [[package]] name = "unicode-bidi" version = "0.3.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08f95100a766bf4f8f28f90d77e0a5461bbdb219042e7679bebe79004fed8d75" [[package]] name = "unicode-ident" version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" [[package]] name = "unicode-normalization" version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5033c97c4262335cded6d6fc3e5c18ab755e1a3dc96376350f3d8e9f009ad956" dependencies = [ "tinyvec", ] [[package]] name = "unindent" version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c7de7d73e1754487cb58364ee906a499937a0dfabd86bcb980fa99ec8c8fa2ce" [[package]] name = "url" version = "2.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "22784dbdf76fdde8af1aeda5622b546b422b6fc585325248a2bf9f5e41e94d6c" dependencies = [ "form_urlencoded", "idna", "percent-encoding", ] [[package]] name = "version-compare" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "852e951cb7832cb45cb1169900d19760cfa39b82bc0ea9c0e5a14ae88411c98b" [[package]] name = "windows-sys" version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ "windows-targets", ] [[package]] name = "windows-sys" version = "0.59.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" dependencies = [ "windows-targets", ] [[package]] name = "windows-targets" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ "windows_aarch64_gnullvm", "windows_aarch64_msvc", "windows_i686_gnu", "windows_i686_gnullvm", "windows_i686_msvc", "windows_x86_64_gnu", "windows_x86_64_gnullvm", "windows_x86_64_msvc", ] [[package]] name = "windows_aarch64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" [[package]] name = "windows_aarch64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" [[package]] name = "windows_i686_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" [[package]] name = "windows_i686_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" [[package]] name = "windows_i686_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" [[package]] name = "windows_x86_64_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" [[package]] name = "windows_x86_64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" [[package]] name = "windows_x86_64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "winnow" version = "0.6.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "36c1fec1a2bb5866f07c25f68c26e565c4c200aebb96d7e55710c19d3e8ac49b" dependencies = [ "memchr", ] subversion-0.0.8/Cargo.toml0000644000000041750000000000100112450ustar # 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" name = "subversion" version = "0.0.8" authors = ["Jelmer Vernooij "] build = "build.rs" autobins = false autoexamples = false autotests = false autobenches = false description = "Rust bindings for Subversion" homepage = "https://github.com/jelmer/subversion-rs" documentation = "https://docs.rs/subversion" readme = "README.md" license = "Apache-2.0" repository = "https://github.com/jelmer/subversion-rs.git" [package.metadata.system-deps] libsvn_fs = ">=1.14" libsvn_repos = ">=1.14" libsvn_subr = ">=1.14" [package.metadata.system-deps.libsvn_client] feature = "client" version = ">=1.14" [package.metadata.system-deps.libsvn_delta] feature = "delta" version = ">=1.14" [package.metadata.system-deps.libsvn_ra] feature = "ra" version = ">=1.14" [package.metadata.system-deps.libsvn_wc] feature = "wc" version = ">=1.14" [lib] name = "subversion" path = "src/lib.rs" doctest = false [[bin]] name = "subversion" path = "src/main.rs" [[example]] name = "checkout" path = "examples/checkout.rs" required-features = ["client"] [[example]] name = "cat" path = "examples/cat.rs" required-features = ["client"] [dependencies.apr] version = ">=0.1.14" [dependencies.ctor] version = "0.2.8" [dependencies.lazy_static] version = "1.5.0" [dependencies.pyo3] version = ">=0.22" optional = true [dependencies.url] version = "2.5.2" optional = true [dev-dependencies.tempfile] version = "3.12.0" [build-dependencies.bindgen] version = "0.70" [build-dependencies.system-deps] version = "7.0" [features] client = [] default = [ "ra", "wc", "client", "delta", ] delta = [] pyo3 = ["dep:pyo3"] ra = ["delta"] url = ["dep:url"] wc = ["delta"] subversion-0.0.8/Cargo.toml.orig000064400000000000000000000023421046102023000147200ustar 00000000000000[package] name = "subversion" version = "0.0.8" edition = "2021" authors = ["Jelmer Vernooij "] repository = "https://github.com/jelmer/subversion-rs.git" homepage = "https://github.com/jelmer/subversion-rs" license = "Apache-2.0" description = "Rust bindings for Subversion" documentation = "https://docs.rs/subversion" [lib] doctest = false [dependencies] apr = { version = ">=0.1.14" } #apr = { path = "../apr-rs" } ctor = "0.2.8" lazy_static = "1.5.0" pyo3 = { version = ">=0.22", optional = true } url = { version = "2.5.2", optional = true } [features] default = ["ra", "wc", "client", "delta"] pyo3 = ["dep:pyo3"] url = ["dep:url"] client = [] ra = ["delta"] wc = ["delta"] delta = [] [build-dependencies] bindgen = "0.70" system-deps = "7.0" [dev-dependencies] tempfile = "3.12.0" [package.metadata.system-deps] libsvn_client = { version = ">=1.14", feature = "client" } libsvn_delta = { version = ">=1.14", feature = "delta" } libsvn_subr = ">=1.14" libsvn_repos = ">=1.14" libsvn_fs = ">=1.14" libsvn_wc = { version = ">=1.14", feature = "wc" } libsvn_ra = { version = ">=1.14", feature = "ra" } [[example]] name = "checkout" required-features = ["client"] [[example]] name = "cat" required-features = ["client"] subversion-0.0.8/README.md000064400000000000000000000012431046102023000133070ustar 00000000000000Subversion bindings for Rust ============================ This rust crate provides idiomatic bindings for the Subversion C libraries. At the moment, it only covers the "client" library but the aim is to support all of the public C API. Example: ```rust use subversion::client::CheckoutOptions; let mut ctx = subversion::client::Context::new().unwrap(); ctx.checkout( "http://svn.apache.org/repos/asf/subversion/trunk/subversion/libsvn_client", std::path::Path::new("libsvn_client"), CheckoutOptions { peg_revision: Revision::Head, revision: Revision::Head, depth: Depth::Infinity, ..default::Default() } ) .unwrap(); ``` subversion-0.0.8/build.rs000064400000000000000000000041371046102023000135020ustar 00000000000000extern crate bindgen; fn create_svn_bindings( svn_path: &std::path::Path, out_path: &std::path::Path, include_paths: &[&std::path::Path], ) { let client_feature_enabled = std::env::var("CARGO_FEATURE_CLIENT").is_ok(); let mut builder = bindgen::Builder::default() .header(svn_path.join("svn_dirent_uri.h").to_str().unwrap()) .header(svn_path.join("svn_version.h").to_str().unwrap()) .header(svn_path.join("svn_error.h").to_str().unwrap()) .header(svn_path.join("svn_opt.h").to_str().unwrap()) .header(svn_path.join("svn_repos.h").to_str().unwrap()) .header(svn_path.join("svn_time.h").to_str().unwrap()) .header(svn_path.join("svn_types.h").to_str().unwrap()) .header(svn_path.join("svn_types_impl.h").to_str().unwrap()) .header(svn_path.join("svn_wc.h").to_str().unwrap()) .header(svn_path.join("svn_props.h").to_str().unwrap()) .allowlist_file(".*/svn_.*.h") .blocklist_type("apr_.*") .derive_default(true) .clang_args( include_paths .iter() .map(|path| format!("-I{}", path.display())), ); if client_feature_enabled { builder = builder.header(svn_path.join("svn_client.h").to_str().unwrap()); } // Generate bindings using bindgen let svn_bindings = builder.generate().expect("Failed to generate bindings"); svn_bindings .write_to_file(out_path.join("subversion.rs")) .expect("Failed to write bindings"); } fn main() { let deps = system_deps::Config::new().probe().unwrap(); let svn = deps.get_by_name("libsvn_subr").unwrap(); let svn_path = svn .include_paths .iter() .find(|x| x.join("svn_config.h").exists()) .expect("Failed to find svn_config.h"); let out_path = std::path::PathBuf::from(std::env::var("OUT_DIR").unwrap()); create_svn_bindings( svn_path.as_path(), out_path.as_path(), svn.include_paths .iter() .map(|x| x.as_path()) .collect::>() .as_slice(), ); } subversion-0.0.8/disperse.conf000064400000000000000000000001171046102023000145140ustar 00000000000000# See https://github.com/jelmer/disperse timeout_days: 5 tag_name: "v$VERSION" subversion-0.0.8/examples/cat.rs000064400000000000000000000005161046102023000147650ustar 00000000000000use subversion::client::CatOptions; fn main() { let mut ctx = subversion::client::Context::new().unwrap(); let mut stdout = std::io::stdout(); ctx.cat( "http://svn.apache.org/repos/asf/subversion/trunk/subversion/libsvn_client/cat.c", &mut stdout, &CatOptions::default(), ) .unwrap(); } subversion-0.0.8/examples/checkout.rs000064400000000000000000000005101046102023000160150ustar 00000000000000use subversion::client::CheckoutOptions; fn main() { let mut ctx = subversion::client::Context::new().unwrap(); ctx.checkout( "http://svn.apache.org/repos/asf/subversion/trunk/subversion/libsvn_client", std::path::Path::new("libsvn_client"), &CheckoutOptions::default(), ) .unwrap(); } subversion-0.0.8/src/auth.rs000064400000000000000000000525561046102023000141430ustar 00000000000000use crate::Error; use apr::pool::PooledPtr; pub struct AuthBaton(PooledPtr); unsafe impl Send for AuthBaton {} pub trait Credentials { fn kind() -> &'static str; fn as_mut_ptr(&mut self) -> *mut std::ffi::c_void; fn from_raw(cred: *mut std::ffi::c_void, pool: std::rc::Rc) -> Self where Self: Sized; } pub struct SimpleCredentials(PooledPtr); impl SimpleCredentials { pub fn username(&self) -> &str { unsafe { std::ffi::CStr::from_ptr(self.0.username).to_str().unwrap() } } pub fn password(&self) -> &str { unsafe { std::ffi::CStr::from_ptr(self.0.password).to_str().unwrap() } } pub fn set_username(&mut self, username: &str) { self.0.username = apr::strings::pstrdup(username, &self.0.pool()).as_ptr() as *mut _; } pub fn may_save(&self) -> bool { self.0.may_save != 0 } pub fn new(username: String, password: String, may_save: bool) -> Self { Self( PooledPtr::initialize(|pool| { let cred: &mut crate::generated::svn_auth_cred_simple_t = pool.calloc(); cred.username = apr::strings::pstrdup(&username, pool).as_ptr() as *mut _; cred.password = apr::strings::pstrdup(&password, pool).as_ptr() as *mut _; cred.may_save = if may_save { 1 } else { 0 }; Ok::<_, Error>(cred) }) .unwrap(), ) } } impl Credentials for SimpleCredentials { fn kind() -> &'static str { std::str::from_utf8(crate::generated::SVN_AUTH_CRED_SIMPLE).unwrap() } fn as_mut_ptr(&mut self) -> *mut std::ffi::c_void { self.0.as_mut_ptr() as *mut std::ffi::c_void } fn from_raw(cred: *mut std::ffi::c_void, pool: std::rc::Rc) -> Self { unsafe { Self(PooledPtr::in_pool( pool, cred as *mut crate::generated::svn_auth_cred_simple_t, )) } } } impl AuthBaton { pub fn as_mut_ptr(&mut self) -> *mut crate::generated::svn_auth_baton_t { self.0.as_mut_ptr() } pub fn credentials(&mut self, realm: &str) -> Result, Error> { let cred_kind = std::ffi::CString::new(C::kind()).unwrap(); let realm = std::ffi::CString::new(realm).unwrap(); let mut cred = std::ptr::null_mut(); let mut state = std::ptr::null_mut(); let pool = apr::pool::Pool::new(); unsafe { let err = crate::generated::svn_auth_first_credentials( &mut cred, &mut state, cred_kind.as_ptr(), realm.as_ptr(), self.0.as_mut_ptr(), pool.as_mut_ptr(), ); Error::from_raw(err)?; let pool = std::rc::Rc::new(pool); let first_creds = C::from_raw(cred, pool.clone()); Ok(IterState( PooledPtr::in_pool(pool, state), Some(first_creds), )) } } pub fn forget_credentials( &mut self, cred_kind: Option<&str>, realm: Option<&str>, ) -> Result<(), Error> { let cred_kind = cred_kind .map(|s| std::ffi::CString::new(s).unwrap()) .map_or_else(std::ptr::null, |p| p.as_ptr()); let realmstring = realm .map(|s| std::ffi::CString::new(s).unwrap()) .map_or_else(std::ptr::null, |p| p.as_ptr()); let err = std::ptr::null_mut(); let pool = apr::pool::Pool::new(); unsafe { crate::generated::svn_auth_forget_credentials( self.0.as_mut_ptr(), cred_kind, realmstring, pool.as_mut_ptr(), ); Error::from_raw(err)?; Ok(()) } } /// Get a parameter from the auth baton. /// /// # Safety /// /// The caller must ensure that the value is valid for the lifetime of the auth baton. pub unsafe fn get_parameter(&mut self, name: &str) -> *const std::ffi::c_void { let name = std::ffi::CString::new(name).unwrap(); crate::generated::svn_auth_get_parameter(self.0.as_mut_ptr(), name.as_ptr()) } /// Set a parameter on the auth baton. /// /// # Safety /// The caller must ensure that the value is valid for the lifetime of the auth baton. pub unsafe fn set_parameter(&mut self, name: &str, value: *const std::ffi::c_void) { let name = std::ffi::CString::new(name).unwrap(); crate::generated::svn_auth_set_parameter(self.0.as_mut_ptr(), name.as_ptr(), value); } pub fn open(providers: &[impl AsAuthProvider]) -> Result { let mut baton = std::ptr::null_mut(); Ok(Self(PooledPtr::initialize(|pool| unsafe { let mut provider_array = apr::tables::ArrayHeader::< *const crate::generated::svn_auth_provider_object_t, >::new(); for provider in providers { provider_array.push(provider.as_auth_provider(pool)); } crate::generated::svn_auth_open(&mut baton, provider_array.as_ptr(), pool.as_mut_ptr()); Ok::<_, Error>(baton) })?)) } } pub struct IterState(PooledPtr, Option); impl IterState { pub fn save_credentials(&mut self) -> Result<(), Error> { let pool = apr::pool::Pool::new(); let err = unsafe { crate::generated::svn_auth_save_credentials(self.0.as_mut_ptr(), pool.as_mut_ptr()) }; Error::from_raw(err)?; Ok(()) } fn next_credentials(&mut self) -> Result, Error> { let mut cred = std::ptr::null_mut(); let pool = apr::pool::Pool::new(); unsafe { let err = crate::generated::svn_auth_next_credentials( &mut cred, self.0.as_mut_ptr(), pool.as_mut_ptr(), ); Error::from_raw(err)?; if cred.is_null() { return Ok(None); } Ok(Some(C::from_raw(cred, std::rc::Rc::new(pool)))) } } } impl Iterator for IterState { type Item = C; fn next(&mut self) -> Option { if let Some(creds) = self.1.take() { return Some(creds); } match self.next_credentials() { Ok(Some(cred)) => Some(cred), Ok(None) => None, Err(_) => None, } } } pub trait AsAuthProvider { fn as_auth_provider( &self, pool: &mut apr::pool::Pool, ) -> *const crate::generated::svn_auth_provider_object_t; } impl AsAuthProvider for *const crate::generated::svn_auth_provider_object_t { fn as_auth_provider( &self, _pool: &mut apr::pool::Pool, ) -> *const crate::generated::svn_auth_provider_object_t { *self } } impl AsAuthProvider for AuthProvider { fn as_auth_provider( &self, _pool: &mut apr::pool::Pool, ) -> *const crate::generated::svn_auth_provider_object_t { self.0.as_ptr() } } impl AsAuthProvider for &AuthProvider { fn as_auth_provider( &self, _pool: &mut apr::pool::Pool, ) -> *const crate::generated::svn_auth_provider_object_t { self.0.as_ptr() } } #[allow(dead_code)] pub struct AuthProvider(PooledPtr); unsafe impl Send for AuthProvider {} impl AuthProvider { pub fn cred_kind(&self) -> &str { let cred_kind = (unsafe { *self.0.vtable }).cred_kind; unsafe { std::ffi::CStr::from_ptr(cred_kind).to_str().unwrap() } } pub fn save_credentials( &self, credentials: &mut impl Credentials, realm: &str, ) -> Result { let realm = std::ffi::CString::new(realm).unwrap(); let creds = credentials.as_mut_ptr(); let pool = apr::pool::Pool::new(); let mut saved = crate::generated::svn_boolean_t::default(); let err = unsafe { (*self.0.vtable).save_credentials.unwrap()( &mut saved, creds, self.0.provider_baton, std::ptr::null_mut(), realm.as_ptr(), pool.as_mut_ptr(), ) }; Error::from_raw(err)?; Ok(true) } } pub fn get_username_provider() -> AuthProvider { let mut auth_provider = std::ptr::null_mut(); AuthProvider( PooledPtr::initialize(|pool| unsafe { crate::generated::svn_auth_get_username_provider(&mut auth_provider, pool.as_mut_ptr()); Ok::<_, Error>(auth_provider) }) .unwrap(), ) } pub fn get_ssl_server_trust_file_provider() -> AuthProvider { let mut auth_provider = std::ptr::null_mut(); AuthProvider( PooledPtr::initialize(|pool| unsafe { crate::generated::svn_auth_get_ssl_server_trust_file_provider( &mut auth_provider, pool.as_mut_ptr(), ); Ok::<_, Error>(auth_provider) }) .unwrap(), ) } #[allow(dead_code)] pub struct SslServerCertInfo(PooledPtr); unsafe impl Send for SslServerCertInfo {} impl SslServerCertInfo { pub fn dup(&self) -> Self { Self( PooledPtr::initialize(|pool| { Ok::<_, Error>(unsafe { crate::generated::svn_auth_ssl_server_cert_info_dup( self.0.as_ptr(), pool.as_mut_ptr(), ) }) }) .unwrap(), ) } } pub struct SslServerTrust(PooledPtr); unsafe impl Send for SslServerTrust {} impl SslServerTrust { pub fn dup(&self) -> Self { Self( PooledPtr::initialize(|pool| { let cred = pool.calloc(); unsafe { std::ptr::copy_nonoverlapping(self.0.as_ptr(), cred, 1) }; Ok::<_, Error>(cred) }) .unwrap(), ) } } pub fn get_ssl_server_trust_prompt_provider( prompt_func: &impl Fn(&'_ str, usize, &'_ SslServerCertInfo, bool) -> Result, ) -> AuthProvider { let mut auth_provider = std::ptr::null_mut(); extern "C" fn wrap_ssl_server_trust_prompt_fn( cred: *mut *mut crate::generated::svn_auth_cred_ssl_server_trust_t, baton: *mut std::ffi::c_void, realmstring: *const std::ffi::c_char, failures: apr::apr_uint32_t, cert_info: *const crate::generated::svn_auth_ssl_server_cert_info_t, may_save: crate::generated::svn_boolean_t, pool: *mut apr::apr_pool_t, ) -> *mut crate::generated::svn_error_t { let f = unsafe { &*(baton as *const &dyn Fn( &'_ str, usize, &'_ SslServerCertInfo, bool, ) -> Result) }; let realm = unsafe { std::ffi::CStr::from_ptr(realmstring).to_str().unwrap() }; let cert_info = SslServerCertInfo(unsafe { PooledPtr::in_pool( std::rc::Rc::new(apr::pool::Pool::from_raw(pool)), cert_info as *mut _, ) }); f( realm, failures.try_into().unwrap(), &cert_info, may_save != 0, ) .map(|mut creds| { unsafe { *cred = creds.0.as_mut_ptr() }; std::ptr::null_mut() }) .unwrap_or_else(|e| unsafe { e.into_raw() }) } AuthProvider( PooledPtr::initialize(|pool| unsafe { crate::generated::svn_auth_get_ssl_server_trust_prompt_provider( &mut auth_provider, Some(wrap_ssl_server_trust_prompt_fn), prompt_func as *const _ as *mut std::ffi::c_void, pool.as_mut_ptr(), ); Ok::<_, Error>(auth_provider) }) .unwrap(), ) } pub fn get_ssl_client_cert_file_provider() -> AuthProvider { let mut auth_provider = std::ptr::null_mut(); AuthProvider( PooledPtr::initialize(|pool| unsafe { crate::generated::svn_auth_get_ssl_client_cert_file_provider( &mut auth_provider, pool.as_mut_ptr(), ); Ok::<_, Error>(auth_provider) }) .unwrap(), ) } pub struct SslClientCertCredentials(PooledPtr); unsafe impl Send for SslClientCertCredentials {} impl SslClientCertCredentials { pub fn dup(&self) -> Self { Self( PooledPtr::initialize(|pool| { let cred: &mut crate::generated::svn_auth_cred_ssl_client_cert_t = pool.calloc(); cred.cert_file = apr::strings::pstrdup( unsafe { std::ffi::CStr::from_ptr(self.0.cert_file).to_str().unwrap() }, pool, ) .as_ptr() as *mut _; cred.may_save = self.0.may_save; Ok::<_, Error>(cred) }) .unwrap(), ) } } extern "C" fn wrap_client_cert_prompt_fn( cred: *mut *mut crate::generated::svn_auth_cred_ssl_client_cert_t, baton: *mut std::ffi::c_void, realmstring: *const std::ffi::c_char, may_save: crate::generated::svn_boolean_t, _pool: *mut apr::apr_pool_t, ) -> *mut crate::generated::svn_error_t { let f = unsafe { &*(baton as *const &dyn Fn(&str, bool) -> Result) }; let realm = unsafe { std::ffi::CStr::from_ptr(realmstring).to_str().unwrap() }; f(realm, may_save != 0) .map(|mut creds| { unsafe { *cred = creds.0.as_mut_ptr() }; std::ptr::null_mut() }) .unwrap_or_else(|e| unsafe { e.into_raw() }) } pub fn get_ssl_client_cert_prompt_provider( prompt_fn: &impl Fn(&str, bool) -> Result, retry_limit: usize, ) -> AuthProvider { let mut auth_provider = std::ptr::null_mut(); AuthProvider( PooledPtr::initialize(|pool| unsafe { crate::generated::svn_auth_get_ssl_client_cert_prompt_provider( &mut auth_provider, Some(wrap_client_cert_prompt_fn), prompt_fn as *const _ as *mut std::ffi::c_void, retry_limit.try_into().unwrap(), pool.as_mut_ptr(), ); Ok::<_, Error>(auth_provider) }) .unwrap(), ) } pub fn get_ssl_client_cert_pw_file_provider( prompt_fn: &impl Fn(&str) -> Result, ) -> AuthProvider { let mut auth_provider = std::ptr::null_mut(); AuthProvider( PooledPtr::initialize(|pool| unsafe { crate::generated::svn_auth_get_ssl_client_cert_pw_file_provider2( &mut auth_provider, Some(wrap_plaintext_passphrase_prompt), prompt_fn as *const _ as *mut std::ffi::c_void, pool.as_mut_ptr(), ); Ok::<_, Error>(auth_provider) }) .unwrap(), ) } pub fn get_simple_prompt_provider( prompt_fn: &impl Fn(&'_ str, Option<&'_ str>, bool) -> Result, retry_limit: usize, ) -> AuthProvider { let mut auth_provider = std::ptr::null_mut(); extern "C" fn wrap_simple_prompt_provider( credentials: *mut *mut crate::generated::svn_auth_cred_simple_t, baton: *mut std::ffi::c_void, realmstring: *const std::ffi::c_char, username: *const std::ffi::c_char, may_save: crate::generated::svn_boolean_t, _pool: *mut apr::apr_pool_t, ) -> *mut crate::generated::svn_error_t { let f = unsafe { &*(baton as *const &dyn Fn( &'_ str, Option<&'_ str>, bool, ) -> Result) }; let realm = unsafe { std::ffi::CStr::from_ptr(realmstring).to_str().unwrap() }; let username = if username.is_null() { None } else { Some(unsafe { std::ffi::CStr::from_ptr(username).to_str().unwrap() }) }; f(realm, username, may_save != 0) .map(|mut creds| { unsafe { *credentials = creds.0.as_mut_ptr() }; std::ptr::null_mut() }) .unwrap_or_else(|e| unsafe { e.into_raw() }) } AuthProvider( PooledPtr::initialize(|pool| unsafe { crate::generated::svn_auth_get_simple_prompt_provider( &mut auth_provider, Some(wrap_simple_prompt_provider), prompt_fn as *const _ as *mut std::ffi::c_void, retry_limit.try_into().unwrap(), pool.as_mut_ptr(), ); Ok::<_, Error>(auth_provider) }) .unwrap(), ) } pub fn get_username_prompt_provider( prompt_fn: &impl Fn(&str, bool) -> Result, retry_limit: usize, ) -> AuthProvider { let mut auth_provider = std::ptr::null_mut(); extern "C" fn wrap_username_prompt_provider( credentials: *mut *mut crate::generated::svn_auth_cred_username_t, baton: *mut std::ffi::c_void, realmstring: *const std::ffi::c_char, may_save: crate::generated::svn_boolean_t, _pool: *mut apr::apr_pool_t, ) -> *mut crate::generated::svn_error_t { let f = unsafe { &*(baton as *const &dyn Fn(&str, bool) -> Result) }; let realm = unsafe { std::ffi::CStr::from_ptr(realmstring).to_str().unwrap() }; f(realm, may_save != 0) .map(|username| { let username = std::ffi::CString::new(username).unwrap(); let creds = crate::generated::svn_auth_cred_username_t { username: username.as_ptr(), may_save, }; unsafe { *credentials = Box::into_raw(Box::new(creds)) }; std::ptr::null_mut() }) .unwrap_or_else(|e| unsafe { e.into_raw() }) } AuthProvider( PooledPtr::initialize(|pool| unsafe { crate::generated::svn_auth_get_username_prompt_provider( &mut auth_provider, Some(wrap_username_prompt_provider), prompt_fn as *const _ as *mut std::ffi::c_void, retry_limit.try_into().unwrap(), pool.as_mut_ptr(), ); Ok::<_, Error>(auth_provider) }) .unwrap(), ) } extern "C" fn wrap_plaintext_passphrase_prompt( may_save_plaintext: *mut crate::generated::svn_boolean_t, realmstring: *const std::ffi::c_char, baton: *mut std::ffi::c_void, _pool: *mut apr::apr_pool_t, ) -> *mut crate::generated::svn_error_t { let f = unsafe { &*(baton as *const &dyn Fn(&str) -> Result) }; let realm = unsafe { std::ffi::CStr::from_ptr(realmstring).to_str().unwrap() }; f(realm) .map(|b| { unsafe { *may_save_plaintext = if b { 1 } else { 0 } }; std::ptr::null_mut() }) .unwrap_or_else(|e| unsafe { e.into_raw() }) } pub fn get_simple_provider( plaintext_prompt_func: &impl Fn(&str) -> Result, ) -> AuthProvider { let mut auth_provider = std::ptr::null_mut(); AuthProvider( PooledPtr::initialize(|pool| unsafe { crate::generated::svn_auth_get_simple_provider2( &mut auth_provider, Some(wrap_plaintext_passphrase_prompt), plaintext_prompt_func as *const _ as *mut std::ffi::c_void, pool.as_mut_ptr(), ); Ok::<_, Error>(auth_provider) }) .unwrap(), ) } pub fn get_platform_specific_provider( provider_name: &str, provider_type: &str, ) -> Result { let mut auth_provider = std::ptr::null_mut(); let provider_name = std::ffi::CString::new(provider_name).unwrap(); let provider_type = std::ffi::CString::new(provider_type).unwrap(); let pool = apr::pool::Pool::new(); let err = unsafe { crate::generated::svn_auth_get_platform_specific_provider( &mut auth_provider, provider_name.as_ptr(), provider_type.as_ptr(), pool.as_mut_ptr(), ) }; Error::from_raw(err)?; let pool = std::rc::Rc::new(pool); Ok(AuthProvider(unsafe { PooledPtr::in_pool(pool, auth_provider) })) } pub fn get_platform_specific_client_providers() -> Result, Error> { let pool = std::rc::Rc::new(apr::pool::Pool::new()); let mut providers = std::ptr::null_mut(); let err = unsafe { crate::generated::svn_auth_get_platform_specific_client_providers( &mut providers, // TODO: pass in config std::ptr::null_mut(), pool.as_mut_ptr(), ) }; Error::from_raw(err)?; let providers = unsafe { apr::tables::ArrayHeader::<*mut crate::generated::svn_auth_provider_object_t>::from_raw_parts(&pool, providers) }; Ok(providers .iter() .map(|p| AuthProvider(unsafe { PooledPtr::in_pool(pool.clone(), p) })) .collect()) } subversion-0.0.8/src/client.rs000064400000000000000000001371421046102023000144530ustar 00000000000000use crate::dirent::AsCanonicalDirent; use crate::generated::svn_error_t; use crate::generated::{ svn_client_add5, svn_client_checkout3, svn_client_cleanup2, svn_client_commit6, svn_client_conflict_get, svn_client_create_context2, svn_client_ctx_t, svn_client_delete4, svn_client_export5, svn_client_import5, svn_client_log5, svn_client_mkdir4, svn_client_proplist4, svn_client_relocate2, svn_client_status6, svn_client_switch3, svn_client_update4, svn_client_vacuum, svn_client_version, }; use crate::io::Dirent; use crate::uri::AsCanonicalUri; use crate::{Depth, Error, LogEntry, Revision, RevisionRange, Revnum, Version}; use apr::pool::PooledPtr; use apr::Pool; use std::collections::HashMap; pub fn version() -> Version { unsafe { Version(svn_client_version()) } } extern "C" fn wrap_filter_callback( baton: *mut std::ffi::c_void, filtered: *mut crate::generated::svn_boolean_t, local_abspath: *const i8, dirent: *const crate::generated::svn_io_dirent2_t, _pool: *mut apr::apr_pool_t, ) -> *mut svn_error_t { unsafe { let callback = baton as *mut &mut dyn FnMut(&std::path::Path, &Dirent) -> Result; let mut callback = Box::from_raw(callback); let local_abspath: &std::path::Path = std::ffi::CStr::from_ptr(local_abspath) .to_str() .unwrap() .as_ref(); let ret = callback(local_abspath, &Dirent::from(dirent)); if let Ok(ret) = ret { *filtered = ret as crate::generated::svn_boolean_t; std::ptr::null_mut() } else { ret.unwrap_err().as_mut_ptr() } } } extern "C" fn wrap_status_func( baton: *mut std::ffi::c_void, path: *const i8, status: *const crate::generated::svn_client_status_t, _pool: *mut apr::apr_pool_t, ) -> *mut crate::generated::svn_error_t { unsafe { let callback = baton as *mut &mut dyn FnMut(&std::path::Path, &Status) -> Result<(), Error>; let mut callback = Box::from_raw(callback); let path: &std::path::Path = std::ffi::CStr::from_ptr(path).to_str().unwrap().as_ref(); let ret = callback(path, &Status(status)); if let Err(mut err) = ret { err.as_mut_ptr() } else { std::ptr::null_mut() } } } extern "C" fn wrap_proplist_receiver2( baton: *mut std::ffi::c_void, path: *const i8, props: *mut apr::hash::apr_hash_t, inherited_props: *mut apr::tables::apr_array_header_t, scratch_pool: *mut apr::apr_pool_t, ) -> *mut crate::generated::svn_error_t { unsafe { let scratch_pool = std::rc::Rc::new(apr::pool::Pool::from_raw(scratch_pool)); let callback = baton as *mut &mut dyn FnMut( &str, &HashMap>, Option<&[crate::InheritedItem]>, ) -> Result<(), Error>; let mut callback = Box::from_raw(callback); let path: &str = std::ffi::CStr::from_ptr(path).to_str().unwrap(); let mut props = apr::hash::Hash::<&str, *mut crate::generated::svn_string_t>::from_raw( PooledPtr::in_pool(scratch_pool.clone(), props), ); let props = props .iter() .map(|(k, v)| { ( String::from_utf8_lossy(k).to_string(), Vec::from_raw_parts((**v).data as *mut u8, (**v).len, (**v).len), ) }) .collect::>(); let inherited_props = if inherited_props.is_null() { None } else { let inherited_props = apr::tables::ArrayHeader::< *mut crate::generated::svn_prop_inherited_item_t, >::from_raw_parts(&scratch_pool, inherited_props); Some( inherited_props .iter() .map(|x| { crate::InheritedItem::from_raw(PooledPtr::in_pool(scratch_pool.clone(), x)) }) .collect::>(), ) }; let ret = callback(path, &props, inherited_props.as_deref()); if let Err(mut err) = ret { err.as_mut_ptr() } else { std::ptr::null_mut() } } } pub struct Info(*const crate::generated::svn_client_info2_t); impl Info { pub fn url(&self) -> &str { unsafe { let url = (*self.0).URL; std::ffi::CStr::from_ptr(url).to_str().unwrap() } } pub fn revision(&self) -> Revnum { unsafe { Revnum::from_raw((*self.0).rev).unwrap() } } pub fn kind(&self) -> crate::generated::svn_node_kind_t { unsafe { (*self.0).kind } } pub fn repos_root_url(&self) -> &str { unsafe { let url = (*self.0).repos_root_URL; std::ffi::CStr::from_ptr(url).to_str().unwrap() } } pub fn repos_uuid(&self) -> &str { unsafe { let uuid = (*self.0).repos_UUID; std::ffi::CStr::from_ptr(uuid).to_str().unwrap() } } pub fn last_changed_rev(&self) -> Revnum { Revnum::from_raw(unsafe { (*self.0).last_changed_rev }).unwrap() } pub fn last_changed_date(&self) -> apr::time::Time { unsafe { (*self.0).last_changed_date.into() } } pub fn last_changed_author(&self) -> &str { unsafe { let author = (*self.0).last_changed_author; std::ffi::CStr::from_ptr(author).to_str().unwrap() } } } extern "C" fn wrap_info_receiver2( baton: *mut std::ffi::c_void, abspath_or_url: *const i8, info: *const crate::generated::svn_client_info2_t, _scatch_pool: *mut apr::apr_pool_t, ) -> *mut crate::generated::svn_error_t { unsafe { let callback = baton as *mut &mut dyn FnMut(&std::path::Path, &Info) -> Result<(), Error>; let mut callback = Box::from_raw(callback); let abspath_or_url: &std::path::Path = std::ffi::CStr::from_ptr(abspath_or_url) .to_str() .unwrap() .as_ref(); let ret = callback(abspath_or_url, &Info(info)); if let Err(mut err) = ret { err.as_mut_ptr() } else { std::ptr::null_mut() } } } /// Options for cat #[derive(Debug, Clone, Copy, Default)] pub struct CatOptions { revision: Revision, peg_revision: Revision, expand_keywords: bool, } /// Options for cleanup #[derive(Debug, Clone, Copy, Default)] pub struct CleanupOptions { break_locks: bool, fix_recorded_timestamps: bool, clear_dav_cache: bool, vacuum_pristines: bool, include_externals: bool, } /// Options for proplist #[derive(Debug, Clone, Copy, Default)] pub struct ProplistOptions<'a> { peg_revision: Revision, revision: Revision, depth: Depth, changelists: Option<&'a [&'a str]>, get_target_inherited_props: bool, } /// Options for export #[derive(Debug, Clone, Copy, Default)] pub struct ExportOptions { peg_revision: Revision, revision: Revision, overwrite: bool, ignore_externals: bool, ignore_keywords: bool, depth: Depth, native_eol: crate::NativeEOL, } /// Options for vacuum #[derive(Debug, Clone, Copy, Default)] pub struct VacuumOptions { remove_unversioned_items: bool, remove_ignored_items: bool, fix_recorded_timestamps: bool, vacuum_pristines: bool, include_externals: bool, } /// Options for a checkout #[derive(Debug, Clone, Copy, Default)] pub struct CheckoutOptions { peg_revision: Revision, revision: Revision, depth: Depth, ignore_externals: bool, allow_unver_obstructions: bool, } /// Options for an update #[derive(Debug, Clone, Copy, Default)] pub struct UpdateOptions { depth: Depth, depth_is_sticky: bool, ignore_externals: bool, allow_unver_obstructions: bool, adds_as_modifications: bool, make_parents: bool, } /// Options for a switch pub struct SwitchOptions { peg_revision: Revision, revision: Revision, depth: Depth, depth_is_sticky: bool, ignore_externals: bool, allow_unver_obstructions: bool, make_parents: bool, } /// Options for add pub struct AddOptions { depth: Depth, force: bool, no_ignore: bool, no_autoprops: bool, add_parents: bool, } /// Options for delete pub struct DeleteOptions<'a> { force: bool, keep_local: bool, commit_callback: &'a dyn FnMut(&crate::CommitInfo) -> Result<(), Error>, } /// Options for commit pub struct CommitOptions<'a> { depth: Depth, keep_locks: bool, keep_changelists: bool, commit_as_operations: bool, include_file_externals: bool, include_dir_externals: bool, changelists: Option<&'a [&'a str]>, } /// Options for status pub struct StatusOptions<'a> { revision: Revision, depth: Depth, get_all: bool, check_out_of_date: bool, check_working_copy: bool, no_ignore: bool, ignore_externals: bool, depth_as_sticky: bool, changelists: Option<&'a [&'a str]>, } /// A client context. /// /// This is the main entry point for the client library. It holds client specific configuration and /// callbacks pub struct Context(apr::pool::PooledPtr); unsafe impl Send for Context {} impl Clone for Context { fn clone(&self) -> Self { let new = Context::new().unwrap(); // TODO: Copy auth_baton // TODO: Copy notify func // TODO: copy log_msg_func // TODO: copy config // TODO: copy cancel_func // TODO: copy progress_func // TODO: copy wc_ctx // TODO: copy conflict_func // TODO: copy mimetypes map // TODO: copy check_tunnel_func // TODO: copy open_tunnel_func new } } impl Context { pub fn new() -> Result { // call svn_client_create_context2 Ok(Context(apr::pool::PooledPtr::initialize(|pool| { let mut ctx = std::ptr::null_mut(); let ret = unsafe { svn_client_create_context2(&mut ctx, std::ptr::null_mut(), pool.as_mut_ptr()) }; Error::from_raw(ret)?; Ok::<_, Error>(ctx) })?)) } pub(crate) unsafe fn as_mut_ptr(&mut self) -> *mut svn_client_ctx_t { self.0.as_mut_ptr() } pub fn set_auth<'a, 'b>(&'a mut self, auth_baton: &'b mut crate::auth::AuthBaton) where 'b: 'a, { self.0.auth_baton = auth_baton.as_mut_ptr(); } /// Checkout a working copy from url to path. pub fn checkout<'a>( &mut self, url: impl AsCanonicalUri<'a>, path: impl AsCanonicalDirent<'a>, options: &CheckoutOptions, ) -> Result { let peg_revision = options.peg_revision.into(); let revision = options.revision.into(); let mut pool = Pool::default(); unsafe { let mut revnum = 0; let url = url.as_canonical_uri(&mut pool); let path = path.as_canonical_dirent(&mut pool); let err = svn_client_checkout3( &mut revnum, url.as_ptr(), path.as_ptr(), &peg_revision, &revision, options.depth.into(), options.ignore_externals.into(), options.allow_unver_obstructions.into(), &mut *self.0, pool.as_mut_ptr(), ); Error::from_raw(err)?; Ok(Revnum::from_raw(revnum).unwrap()) } } pub fn update( &mut self, paths: &[&str], revision: Revision, options: &UpdateOptions, ) -> Result, Error> { let mut pool = std::rc::Rc::new(Pool::default()); let mut result_revs = std::ptr::null_mut(); unsafe { let mut ps = apr::tables::ArrayHeader::in_pool(&pool, paths.len()); for path in paths { let path = std::ffi::CString::new(*path).unwrap(); ps.push(path.as_ptr() as *mut std::ffi::c_void); } let err = svn_client_update4( &mut result_revs, ps.as_ptr(), &revision.into(), options.depth.into(), options.depth_is_sticky.into(), options.ignore_externals.into(), options.allow_unver_obstructions.into(), options.adds_as_modifications.into(), options.make_parents.into(), &mut *self.0, std::rc::Rc::get_mut(&mut pool).unwrap().as_mut_ptr(), ); let result_revs: apr::tables::ArrayHeader = apr::tables::ArrayHeader::::from_raw_parts(&pool, result_revs); Error::from_raw(err)?; Ok(result_revs.iter().collect()) } } pub fn switch<'a>( &mut self, path: impl AsCanonicalDirent<'a>, url: impl AsCanonicalUri<'a>, options: &SwitchOptions, ) -> Result { let mut pool = Pool::default(); let mut result_rev = 0; let path = path.as_canonical_dirent(&mut pool); let url = url.as_canonical_uri(&mut pool); unsafe { let err = svn_client_switch3( &mut result_rev, path.as_ptr(), url.as_ptr(), &options.peg_revision.into(), &options.revision.into(), options.depth.into(), options.depth_is_sticky.into(), options.ignore_externals.into(), options.allow_unver_obstructions.into(), options.make_parents.into(), &mut *self.0, pool.as_mut_ptr(), ); Error::from_raw(err)?; Ok(Revnum::from_raw(result_rev).unwrap()) } } pub fn add<'a>( &mut self, path: impl AsCanonicalDirent<'a>, options: &AddOptions, ) -> Result<(), Error> { let mut pool = Pool::default(); let path = path.as_canonical_dirent(&mut pool); unsafe { let err = svn_client_add5( path.as_ptr(), options.depth.into(), options.force.into(), options.no_ignore.into(), options.no_autoprops.into(), options.add_parents.into(), &mut *self.0, pool.as_mut_ptr(), ); Error::from_raw(err)?; Ok(()) } } pub fn mkdir( &mut self, paths: &[&str], make_parents: bool, revprop_table: std::collections::HashMap<&str, &[u8]>, commit_callback: &dyn FnMut(&crate::CommitInfo) -> Result<(), Error>, ) -> Result<(), Error> { let mut pool = std::rc::Rc::new(Pool::default()); unsafe { let mut rps = apr::hash::Hash::in_pool(&pool); for (k, v) in revprop_table { rps.set(k, &v); } let mut ps = apr::tables::ArrayHeader::in_pool(&pool, paths.len()); for path in paths { let path = std::ffi::CString::new(*path).unwrap(); ps.push(path.as_ptr() as *mut std::ffi::c_void); } let commit_callback = Box::into_raw(Box::new(commit_callback)); let err = svn_client_mkdir4( ps.as_ptr(), make_parents.into(), rps.as_ptr(), Some(crate::wrap_commit_callback2), commit_callback as *mut std::ffi::c_void, &mut *self.0, std::rc::Rc::get_mut(&mut pool).unwrap().as_mut_ptr(), ); Error::from_raw(err)?; Ok(()) } } pub fn delete( &mut self, paths: &[&str], revprop_table: std::collections::HashMap<&str, &str>, options: &DeleteOptions, ) -> Result<(), Error> { let mut pool = std::rc::Rc::new(Pool::default()); unsafe { let mut rps = apr::hash::Hash::in_pool(&pool); for (k, v) in revprop_table { rps.set(k, &v); } let mut ps = apr::tables::ArrayHeader::in_pool(&pool, paths.len()); for path in paths { let path = std::ffi::CString::new(*path).unwrap(); ps.push(path.as_ptr() as *mut std::ffi::c_void); } let commit_callback = Box::into_raw(Box::new(options.commit_callback)); let err = svn_client_delete4( ps.as_ptr(), options.force.into(), options.keep_local.into(), rps.as_ptr(), Some(crate::wrap_commit_callback2), commit_callback as *mut std::ffi::c_void, &mut *self.0, std::rc::Rc::get_mut(&mut pool).unwrap().as_mut_ptr(), ); Error::from_raw(err)?; Ok(()) } } pub fn proplist( &mut self, target: &str, options: &ProplistOptions, receiver: &mut dyn FnMut( &str, &std::collections::HashMap>, Option<&[crate::InheritedItem]>, ) -> Result<(), Error>, ) -> Result<(), Error> { let pool = Pool::default(); let changelists = options.changelists.map(|cl| { cl.iter() .map(|cl| std::ffi::CString::new(*cl).unwrap()) .collect::>() }); let changelists = changelists.as_ref().map(|cl| { cl.iter() .map(|cl| cl.as_ptr() as *const i8) .collect::>() }); unsafe { let receiver = Box::into_raw(Box::new(receiver)); let err = svn_client_proplist4( target.as_ptr() as *const i8, &options.peg_revision.into(), &options.revision.into(), options.depth.into(), changelists .map(|cl| cl.as_ptr()) .unwrap_or(std::ptr::null()), options.get_target_inherited_props.into(), Some(wrap_proplist_receiver2), receiver as *mut std::ffi::c_void, &mut *self.0, pool.as_mut_ptr(), ); Error::from_raw(err)?; Ok(()) } } pub fn import<'a>( &mut self, path: impl AsCanonicalDirent<'a>, url: &str, depth: Depth, no_ignore: bool, no_autoprops: bool, ignore_unknown_node_types: bool, revprop_table: std::collections::HashMap<&str, &str>, filter_callback: &dyn FnMut(&mut bool, &std::path::Path, &Dirent) -> Result<(), Error>, commit_callback: &dyn FnMut(&crate::CommitInfo) -> Result<(), Error>, ) -> Result<(), Error> { let mut pool = std::rc::Rc::new(Pool::default()); let mut rps = apr::hash::Hash::in_pool(&pool); for (k, v) in revprop_table { rps.set(k, &v); } let path = path.as_canonical_dirent(std::rc::Rc::get_mut(&mut pool).unwrap()); unsafe { let filter_callback = Box::into_raw(Box::new(filter_callback)); let commit_callback = Box::into_raw(Box::new(commit_callback)); let err = svn_client_import5( path.as_ptr(), url.as_ptr() as *const i8, depth.into(), no_ignore.into(), no_autoprops.into(), ignore_unknown_node_types.into(), rps.as_ptr(), Some(wrap_filter_callback), filter_callback as *mut std::ffi::c_void, Some(crate::wrap_commit_callback2), commit_callback as *mut std::ffi::c_void, &mut *self.0, std::rc::Rc::get_mut(&mut pool).unwrap().as_mut_ptr(), ); Error::from_raw(err)?; Ok(()) } } pub fn export<'a>( &mut self, from_path_or_url: &str, to_path: impl AsCanonicalDirent<'a>, options: &ExportOptions, ) -> Result { let mut pool = std::rc::Rc::new(Pool::default()); let native_eol: Option<&str> = options.native_eol.into(); let native_eol = native_eol.map(|s| std::ffi::CString::new(s).unwrap()); let mut revnum = 0; let to_path = to_path.as_canonical_dirent(std::rc::Rc::get_mut(&mut pool).unwrap()); unsafe { let err = svn_client_export5( &mut revnum, from_path_or_url.as_ptr() as *const i8, to_path.as_ptr(), &options.peg_revision.into(), &options.revision.into(), options.overwrite as i32, options.ignore_externals as i32, options.ignore_keywords as i32, options.depth as i32, native_eol.map(|s| s.as_ptr()).unwrap_or(std::ptr::null()), &mut *self.0, std::rc::Rc::get_mut(&mut pool).unwrap().as_mut_ptr(), ); Error::from_raw(err)?; Ok(Revnum::from_raw(revnum).unwrap()) } } pub fn commit( &mut self, targets: &[&str], options: &CommitOptions, revprop_table: std::collections::HashMap<&str, &str>, commit_callback: &dyn FnMut(&crate::CommitInfo) -> Result<(), Error>, ) -> Result<(), Error> { let mut pool = std::rc::Rc::new(Pool::default()); let mut rps = apr::hash::Hash::in_pool(&pool); for (k, v) in revprop_table { rps.set(k, &v); } unsafe { let mut ps = apr::tables::ArrayHeader::in_pool(&pool, targets.len()); for target in targets { let target = std::ffi::CString::new(*target).unwrap(); ps.push(target.as_ptr() as *mut std::ffi::c_void); } let mut cl = apr::tables::ArrayHeader::in_pool(&pool, 0); if let Some(changelists) = options.changelists { for changelist in changelists { let changelist = std::ffi::CString::new(*changelist).unwrap(); cl.push(changelist.as_ptr() as *mut std::ffi::c_void); } } let commit_callback = Box::into_raw(Box::new(commit_callback)); let err = svn_client_commit6( ps.as_ptr(), options.depth.into(), options.keep_locks.into(), options.keep_changelists.into(), options.commit_as_operations.into(), options.include_file_externals.into(), options.include_dir_externals.into(), cl.as_ptr(), rps.as_ptr(), Some(crate::wrap_commit_callback2), commit_callback as *mut std::ffi::c_void, &mut *self.0, std::rc::Rc::get_mut(&mut pool).unwrap().as_mut_ptr(), ); Error::from_raw(err)?; Ok(()) } } pub fn status( &mut self, path: &str, options: &StatusOptions, status_func: &dyn FnMut(&'_ str, &'_ Status) -> Result<(), Error>, ) -> Result { let mut pool = std::rc::Rc::new(Pool::default()); let mut cl = apr::tables::ArrayHeader::in_pool(&pool, 0); if let Some(changelists) = options.changelists { for changelist in changelists { let changelist = std::ffi::CString::new(*changelist).unwrap(); cl.push(changelist.as_ptr() as *mut std::ffi::c_void); } } unsafe { let status_func = Box::into_raw(Box::new(status_func)); let mut revnum = 0; let err = svn_client_status6( &mut revnum, &mut *self.0, path.as_ptr() as *const i8, &options.revision.into(), options.depth.into(), options.get_all.into(), options.check_out_of_date.into(), options.check_working_copy.into(), options.no_ignore.into(), options.ignore_externals.into(), options.depth_as_sticky.into(), cl.as_ptr(), Some(wrap_status_func), status_func as *mut std::ffi::c_void, std::rc::Rc::get_mut(&mut pool).unwrap().as_mut_ptr(), ); Error::from_raw(err)?; Ok(Revnum::from_raw(revnum).unwrap()) } } pub fn log( &mut self, targets: &[&str], peg_revision: Revision, revision_ranges: &[RevisionRange], limit: i32, discover_changed_paths: bool, strict_node_history: bool, include_merged_revisions: bool, revprops: &[&str], log_entry_receiver: &dyn FnMut(&LogEntry) -> Result<(), Error>, ) -> Result<(), Error> { let mut pool = std::rc::Rc::new(Pool::default()); unsafe { let mut ps = apr::tables::ArrayHeader::in_pool(&pool, targets.len()); for target in targets { let target = std::ffi::CString::new(*target).unwrap(); ps.push(target.as_ptr() as *mut std::ffi::c_void); } let mut rrs = apr::tables::ArrayHeader::<*mut crate::generated::svn_opt_revision_range_t>::in_pool(&pool, revision_ranges.len()); for revision_range in revision_ranges { rrs.push(revision_range.to_c(std::rc::Rc::get_mut(&mut pool).unwrap())); } let mut rps = apr::tables::ArrayHeader::in_pool(&pool, revprops.len()); for revprop in revprops { let revprop = std::ffi::CString::new(*revprop).unwrap(); rps.push(revprop.as_ptr() as *mut std::ffi::c_void); } let log_entry_receiver = Box::into_raw(Box::new(log_entry_receiver)); let err = svn_client_log5( ps.as_ptr(), &peg_revision.into(), rrs.as_ptr(), limit, discover_changed_paths.into(), strict_node_history.into(), include_merged_revisions.into(), rps.as_ptr(), Some(crate::wrap_log_entry_receiver), log_entry_receiver as *mut std::ffi::c_void, &mut *self.0, std::rc::Rc::get_mut(&mut pool).unwrap().as_mut_ptr(), ); Error::from_raw(err)?; Ok(()) } } pub fn iter_logs( &mut self, targets: &[&str], peg_revision: Revision, revision_ranges: &[RevisionRange], limit: i32, discover_changed_paths: bool, strict_node_history: bool, include_merged_revisions: bool, revprops: &[&str], ) -> impl Iterator> { // Create a channel between the worker and this thread let (tx, rx) = std::sync::mpsc::channel(); let targets = targets.iter().map(|s| s.to_string()).collect::>(); let revision_ranges = revision_ranges.to_vec(); let revprops = revprops.iter().map(|s| s.to_string()).collect::>(); let mut client = self.clone(); std::thread::spawn(move || { let r = client.log( targets .iter() .map(|s| s.as_str()) .collect::>() .as_slice(), peg_revision, &revision_ranges, limit, discover_changed_paths, strict_node_history, include_merged_revisions, revprops .iter() .map(|s| s.as_str()) .collect::>() .as_slice(), &mut |log_entry| { tx.send(Ok(Some(log_entry.clone()))).unwrap(); Ok(()) }, ); if let Err(e) = r { tx.send(Err(e)).unwrap(); } tx.send(Ok(None)).unwrap(); }); // Return an iterator that reads from the channel rx.into_iter() .take_while(|x| x.is_ok() && x.as_ref().unwrap().is_some()) .map(|x| x.transpose().unwrap()) } pub fn args_to_target_array( &mut self, mut os: apr::getopt::Getopt, known_targets: &[&str], keep_last_origpath_on_truepath_collision: bool, ) -> Result, crate::Error> { let pool = apr::pool::Pool::new(); let known_targets = known_targets .iter() .map(|s| std::ffi::CString::new(*s).unwrap()) .collect::>(); let mut targets = std::ptr::null_mut(); let err = unsafe { crate::generated::svn_client_args_to_target_array2( &mut targets, os.as_mut_ptr(), known_targets .into_iter() .map(|s| s.as_ptr()) .collect::>() .as_ptr(), &mut *self.0, keep_last_origpath_on_truepath_collision.into(), pool.as_mut_ptr(), ) }; Error::from_raw(err)?; let targets = unsafe { apr::tables::ArrayHeader::<*const i8>::from_raw_parts(&std::rc::Rc::new(pool), targets) }; Ok(targets .iter() .map(|s| unsafe { std::ffi::CStr::from_ptr(*s as *const i8) }) .map(|s| s.to_str().unwrap().to_owned()) .collect::>()) } pub fn vacuum(&mut self, path: &str, options: &VacuumOptions) -> Result<(), Error> { let mut pool = std::rc::Rc::new(Pool::default()); let path = std::ffi::CString::new(path).unwrap(); unsafe { let err = svn_client_vacuum( path.as_ptr(), options.remove_unversioned_items.into(), options.remove_ignored_items.into(), options.fix_recorded_timestamps.into(), options.vacuum_pristines.into(), options.include_externals.into(), &mut *self.0, std::rc::Rc::get_mut(&mut pool).unwrap().as_mut_ptr(), ); Error::from_raw(err)?; Ok(()) } } pub fn cleanup(&mut self, path: &str, options: &CleanupOptions) -> Result<(), Error> { let mut pool = std::rc::Rc::new(Pool::default()); let path = std::ffi::CString::new(path).unwrap(); unsafe { let err = svn_client_cleanup2( path.as_ptr(), options.break_locks.into(), options.fix_recorded_timestamps.into(), options.clear_dav_cache.into(), options.vacuum_pristines.into(), options.include_externals.into(), &mut *self.0, std::rc::Rc::get_mut(&mut pool).unwrap().as_mut_ptr(), ); Error::from_raw(err)?; Ok(()) } } pub fn relocate<'a>( &mut self, path: impl AsCanonicalDirent<'a>, from: &str, to: &str, ignore_externals: bool, ) -> Result<(), Error> { let mut pool = std::rc::Rc::new(Pool::default()); let from = std::ffi::CString::new(from).unwrap(); let to = std::ffi::CString::new(to).unwrap(); let path = path.as_canonical_dirent(std::rc::Rc::get_mut(&mut pool).unwrap()); unsafe { let err = svn_client_relocate2( path.as_ptr(), from.as_ptr(), to.as_ptr(), ignore_externals.into(), &mut *self.0, std::rc::Rc::get_mut(&mut pool).unwrap().as_mut_ptr(), ); Error::from_raw(err)?; Ok(()) } } pub fn conflict_get<'a>( &mut self, local_abspath: impl AsCanonicalDirent<'a>, ) -> Result { Ok(Conflict(apr::pool::PooledPtr::initialize(|pool| { let local_abspath = local_abspath.as_canonical_dirent(pool); let mut conflict: *mut crate::generated::svn_client_conflict_t = std::ptr::null_mut(); unsafe { let err = svn_client_conflict_get( &mut conflict, local_abspath.as_ptr(), &mut *self.0, pool.as_mut_ptr(), Pool::new().as_mut_ptr(), ); Error::from_raw(err)?; Ok::<_, Error>(conflict) } })?)) } pub fn cat( &mut self, path_or_url: &str, stream: &mut dyn std::io::Write, options: &CatOptions, ) -> Result>, Error> { let mut pool = std::rc::Rc::new(Pool::default()); let path_or_url = std::ffi::CString::new(path_or_url).unwrap(); let mut s = crate::io::wrap_write(stream)?; let mut props: *mut apr::hash::apr_hash_t = std::ptr::null_mut(); unsafe { let err = crate::generated::svn_client_cat3( &mut props, s.as_mut_ptr(), path_or_url.as_ptr(), &options.peg_revision.into(), &options.revision.into(), options.expand_keywords.into(), self.as_mut_ptr(), std::rc::Rc::get_mut(&mut pool).unwrap().as_mut_ptr(), apr::pool::Pool::new().as_mut_ptr(), ); Error::from_raw(err)?; let mut props = apr::hash::Hash::<&str, &[u8]>::from_raw(PooledPtr::in_pool(pool, props)); Ok(props .iter() .map(|(k, v)| (String::from_utf8_lossy(k).to_string(), v.to_vec())) .collect()) } } pub fn lock(&mut self, targets: &[&str], comment: &str, steal_lock: bool) -> Result<(), Error> { let mut pool = std::rc::Rc::new(Pool::default()); let targets = targets .iter() .map(|s| std::ffi::CString::new(*s).unwrap()) .collect::>(); let targets = targets .iter() .map(|s| s.as_ptr()) .collect::>(); let comment = std::ffi::CString::new(comment).unwrap(); unsafe { let err = crate::generated::svn_client_lock( targets.as_ptr(), comment.as_ptr(), steal_lock.into(), self.as_mut_ptr(), std::rc::Rc::get_mut(&mut pool).unwrap().as_mut_ptr(), ); Error::from_raw(err)?; Ok(()) } } pub fn unlock(&mut self, targets: &[&str], break_lock: bool) -> Result<(), Error> { let mut pool = std::rc::Rc::new(Pool::default()); let targets = targets .iter() .map(|s| std::ffi::CString::new(*s).unwrap()) .collect::>(); let targets = targets .iter() .map(|s| s.as_ptr()) .collect::>(); unsafe { let err = crate::generated::svn_client_unlock( targets.as_ptr(), break_lock.into(), self.as_mut_ptr(), std::rc::Rc::get_mut(&mut pool).unwrap().as_mut_ptr(), ); Error::from_raw(err)?; Ok(()) } } pub fn get_wc_root<'a>( &mut self, path: impl AsCanonicalDirent<'a>, ) -> Result { let mut pool = std::rc::Rc::new(Pool::default()); let path = path.as_canonical_dirent(std::rc::Rc::get_mut(&mut pool).unwrap()); let mut wc_root: *const i8 = std::ptr::null(); unsafe { let err = crate::generated::svn_client_get_wc_root( &mut wc_root, path.as_ptr(), self.as_mut_ptr(), std::rc::Rc::get_mut(&mut pool).unwrap().as_mut_ptr(), apr::pool::Pool::new().as_mut_ptr(), ); Error::from_raw(err)?; Ok(std::ffi::CStr::from_ptr(wc_root).to_str().unwrap().into()) } } pub fn min_max_revisions<'a>( &mut self, local_abspath: impl AsCanonicalDirent<'a>, committed: bool, ) -> Result<(Revnum, Revnum), Error> { let mut scratch_pool = apr::pool::Pool::new(); let local_abspath = local_abspath.as_canonical_dirent(&mut scratch_pool); let mut min_revision: crate::generated::svn_revnum_t = 0; let mut max_revision: crate::generated::svn_revnum_t = 0; unsafe { let err = crate::generated::svn_client_min_max_revisions( &mut min_revision, &mut max_revision, local_abspath.as_ptr(), committed as i32, self.as_mut_ptr(), scratch_pool.as_mut_ptr(), ); Error::from_raw(err)?; Ok(( Revnum::from_raw(min_revision).unwrap(), Revnum::from_raw(max_revision).unwrap(), )) } } pub fn url_from_path<'a>(&mut self, path: impl AsCanonicalUri<'a>) -> Result { let mut pool = Pool::default(); let path = path.as_canonical_uri(&mut pool); let mut url: *const i8 = std::ptr::null(); unsafe { let err = crate::generated::svn_client_url_from_path2( &mut url, path.as_ptr(), self.as_mut_ptr(), pool.as_mut_ptr(), pool.as_mut_ptr(), ); Error::from_raw(err)?; Ok(std::ffi::CStr::from_ptr(url).to_str().unwrap().into()) } } pub fn get_repos_root(&mut self, path_or_url: &str) -> Result<(String, String), Error> { let pool = Pool::default(); let path_or_url = std::ffi::CString::new(path_or_url).unwrap(); let mut repos_root: *const i8 = std::ptr::null(); let mut repos_uuid: *const i8 = std::ptr::null(); unsafe { let err = crate::generated::svn_client_get_repos_root( &mut repos_root, &mut repos_uuid, path_or_url.as_ptr(), self.as_mut_ptr(), pool.as_mut_ptr(), pool.as_mut_ptr(), ); Error::from_raw(err)?; Ok(( std::ffi::CStr::from_ptr(repos_root) .to_str() .unwrap() .into(), std::ffi::CStr::from_ptr(repos_uuid) .to_str() .unwrap() .into(), )) } } #[cfg(feature = "ra")] pub fn open_raw_session( &mut self, url: &str, wri_path: &std::path::Path, ) -> Result { let url = std::ffi::CString::new(url).unwrap(); let wri_path = std::ffi::CString::new(wri_path.to_str().unwrap()).unwrap(); let session = PooledPtr::initialize(|pool| unsafe { let scratch_pool = Pool::default(); let mut session: *mut crate::generated::svn_ra_session_t = std::ptr::null_mut(); let err = crate::generated::svn_client_open_ra_session2( &mut session, url.as_ptr(), wri_path.as_ptr(), self.as_mut_ptr(), pool.as_mut_ptr(), scratch_pool.as_mut_ptr(), ); Error::from_raw(err)?; Ok::<_, Error>(session) })?; Ok(crate::ra::Session::from_raw(session)) } pub fn info( &mut self, abspath_or_url: &str, peg_revision: Revision, revision: Revision, depth: Depth, fetch_excluded: bool, fetch_actual_only: bool, include_externals: bool, changelists: Option<&[&str]>, receiver: &dyn FnMut(&Info) -> Result<(), Error>, ) -> Result<(), Error> { let pool = Pool::default(); let abspath_or_url = std::ffi::CString::new(abspath_or_url).unwrap(); let changelists = changelists.map(|cl| { cl.iter() .map(|cl| std::ffi::CString::new(*cl).unwrap()) .collect::>() }); let changelists = changelists.as_ref().map(|cl| { cl.iter() .map(|cl| cl.as_ptr()) .collect::>() }); let mut receiver = receiver; let receiver = &mut receiver as *mut _ as *mut std::ffi::c_void; unsafe { let err = crate::generated::svn_client_info4( abspath_or_url.as_ptr(), &peg_revision.into(), &revision.into(), depth.into(), fetch_excluded as i32, fetch_actual_only as i32, include_externals as i32, changelists.map_or(std::ptr::null(), |cl| cl.as_ptr()), Some(wrap_info_receiver2), receiver, self.as_mut_ptr(), pool.as_mut_ptr(), ); Error::from_raw(err)?; Ok(()) } } } impl Default for Context { fn default() -> Self { Self::new().unwrap() } } pub struct Status(pub(crate) *const crate::generated::svn_client_status_t); impl Status { pub fn kind(&self) -> crate::NodeKind { unsafe { (*self.0).kind.into() } } pub fn local_abspath(&self) -> &str { unsafe { std::ffi::CStr::from_ptr((*self.0).local_abspath) .to_str() .unwrap() } } pub fn filesize(&self) -> i64 { unsafe { (*self.0).filesize } } pub fn versioned(&self) -> bool { unsafe { (*self.0).versioned != 0 } } pub fn conflicted(&self) -> bool { unsafe { (*self.0).conflicted != 0 } } pub fn node_status(&self) -> crate::StatusKind { unsafe { (*self.0).node_status.into() } } pub fn text_status(&self) -> crate::StatusKind { unsafe { (*self.0).text_status.into() } } pub fn prop_status(&self) -> crate::StatusKind { unsafe { (*self.0).prop_status.into() } } pub fn wc_is_locked(&self) -> bool { unsafe { (*self.0).wc_is_locked != 0 } } pub fn copied(&self) -> bool { unsafe { (*self.0).copied != 0 } } pub fn repos_root_url(&self) -> &str { unsafe { std::ffi::CStr::from_ptr((*self.0).repos_root_url) .to_str() .unwrap() } } pub fn repos_uuid(&self) -> &str { unsafe { std::ffi::CStr::from_ptr((*self.0).repos_uuid) .to_str() .unwrap() } } pub fn repos_relpath(&self) -> &str { unsafe { std::ffi::CStr::from_ptr((*self.0).repos_relpath) .to_str() .unwrap() } } pub fn revision(&self) -> Revnum { Revnum::from_raw(unsafe { (*self.0).revision }).unwrap() } pub fn changed_rev(&self) -> Revnum { Revnum::from_raw(unsafe { (*self.0).changed_rev }).unwrap() } pub fn changed_date(&self) -> apr::time::Time { unsafe { apr::time::Time::from((*self.0).changed_date) } } pub fn changed_author(&self) -> &str { unsafe { std::ffi::CStr::from_ptr((*self.0).changed_author) .to_str() .unwrap() } } pub fn switched(&self) -> bool { unsafe { (*self.0).switched != 0 } } pub fn file_external(&self) -> bool { unsafe { (*self.0).file_external != 0 } } pub fn lock(&self) -> Option<&crate::Lock> { todo!() } pub fn changelist(&self) -> Option<&str> { unsafe { if (*self.0).changelist.is_null() { None } else { Some( std::ffi::CStr::from_ptr((*self.0).changelist) .to_str() .unwrap(), ) } } } pub fn depth(&self) -> crate::Depth { unsafe { (*self.0).depth.into() } } pub fn ood_kind(&self) -> crate::NodeKind { unsafe { (*self.0).ood_kind.into() } } pub fn repos_node_status(&self) -> crate::StatusKind { unsafe { (*self.0).repos_node_status.into() } } pub fn repos_text_status(&self) -> crate::StatusKind { unsafe { (*self.0).repos_text_status.into() } } pub fn repos_prop_status(&self) -> crate::StatusKind { unsafe { (*self.0).repos_prop_status.into() } } pub fn repos_lock(&self) -> Option { todo!() } pub fn ood_changed_rev(&self) -> Option { Revnum::from_raw(unsafe { (*self.0).ood_changed_rev }) } pub fn ood_changed_author(&self) -> Option<&str> { unsafe { if (*self.0).ood_changed_author.is_null() { None } else { Some( std::ffi::CStr::from_ptr((*self.0).ood_changed_author) .to_str() .unwrap(), ) } } } pub fn moved_from_abspath(&self) -> Option<&str> { unsafe { if (*self.0).moved_from_abspath.is_null() { None } else { Some( std::ffi::CStr::from_ptr((*self.0).moved_from_abspath) .to_str() .unwrap(), ) } } } pub fn moved_to_abspath(&self) -> Option<&str> { unsafe { if (*self.0).moved_to_abspath.is_null() { None } else { Some( std::ffi::CStr::from_ptr((*self.0).moved_to_abspath) .to_str() .unwrap(), ) } } } } pub struct Conflict(pub(crate) apr::pool::PooledPtr); impl Conflict { pub fn prop_get_description(&mut self) -> Result { let pool = apr::pool::Pool::new(); let mut description: *const i8 = std::ptr::null_mut(); let err = unsafe { crate::generated::svn_client_conflict_prop_get_description( &mut description, self.0.as_mut_ptr(), pool.as_mut_ptr(), pool.as_mut_ptr(), ) }; Error::from_raw(err)?; Ok(unsafe { std::ffi::CStr::from_ptr(description) } .to_str() .unwrap() .to_owned()) } } #[cfg(test)] mod tests { use super::*; #[test] fn test_version() { let version = version(); assert_eq!(version.major(), 1); } #[test] fn test_open() { let td = tempfile::tempdir().unwrap(); let repo_path = td.path().join("repo"); let mut repos = crate::repos::Repos::create(&repo_path).unwrap(); assert_eq!(repos.path(), td.path().join("repo")); let mut ctx = Context::new().unwrap(); let dirent = crate::dirent::Dirent::from(repo_path.to_str().unwrap()); let url = dirent.canonicalize().to_file_url().unwrap(); let revnum = ctx .checkout( url, &td.path().join("wc"), &CheckoutOptions { peg_revision: Revision::Head, revision: Revision::Head, depth: Depth::Infinity, ignore_externals: false, allow_unver_obstructions: false, }, ) .unwrap(); assert_eq!(revnum, Revnum(0)); } } subversion-0.0.8/src/config.rs000064400000000000000000000005321046102023000144320ustar 00000000000000pub struct Config<'pool>(apr::hash::Hash<'pool, &'pool str, *mut crate::generated::svn_config_t>); use apr::hash::apr_hash_t; impl<'pool> Config<'pool> { pub fn as_ptr(&self) -> *const apr_hash_t { unsafe { self.0.as_ptr() } } pub fn as_mut_ptr(&mut self) -> *mut apr_hash_t { unsafe { self.0.as_mut_ptr() } } } subversion-0.0.8/src/delta.rs000064400000000000000000000631461046102023000142700ustar 00000000000000use crate::Revnum; use apr::pool::Pool; pub fn version() -> crate::Version { crate::Version(unsafe { crate::generated::svn_delta_version() }) } pub struct WrapEditor( pub(crate) *const crate::generated::svn_delta_editor_t, pub(crate) apr::pool::PooledPtr, ); unsafe impl Send for WrapEditor {} impl Editor for WrapEditor { fn set_target_revision(&mut self, revision: Revnum) -> Result<(), crate::Error> { let scratch_pool = Pool::new(); let err = unsafe { ((*self.0).set_target_revision.unwrap())( self.1.as_mut_ptr(), revision.into(), scratch_pool.as_mut_ptr(), ) }; crate::Error::from_raw(err)?; Ok(()) } fn open_root<'b>( &'b mut self, base_revision: Revnum, ) -> Result, crate::Error> { let editor = apr::pool::PooledPtr::initialize(|p| unsafe { let mut baton = std::ptr::null_mut(); let err = ((*self.0).open_root.unwrap())( self.1.as_mut_ptr(), base_revision.into(), p.as_mut_ptr(), &mut baton, ); crate::Error::from_raw(err)?; Ok::<_, crate::Error>(baton) })?; Ok(Box::new(WrapDirectoryEditor(&self.0, editor))) } fn close(&mut self) -> Result<(), crate::Error> { let scratch_pool = Pool::new(); let err = unsafe { ((*self.0).close_edit.unwrap())(self.1.as_mut_ptr(), scratch_pool.as_mut_ptr()) }; crate::Error::from_raw(err)?; Ok(()) } fn abort(&mut self) -> Result<(), crate::Error> { let scratch_pool = Pool::new(); let err = unsafe { ((*self.0).abort_edit.unwrap())(self.1.as_mut_ptr(), scratch_pool.as_mut_ptr()) }; crate::Error::from_raw(err)?; Ok(()) } } pub struct WrapDirectoryEditor<'a>( pub(crate) &'a *const crate::generated::svn_delta_editor_t, pub(crate) apr::pool::PooledPtr, ); impl<'a> DirectoryEditor for WrapDirectoryEditor<'a> { fn delete_entry(&mut self, path: &str, revision: Option) -> Result<(), crate::Error> { let scratch_pool = self.1.pool().subpool(); let err = unsafe { ((*(*self.0)).delete_entry.unwrap())( path.as_ptr() as *const i8, revision.map_or(-1, |r| r.0), self.1.as_mut_ptr(), scratch_pool.as_mut_ptr(), ) }; crate::Error::from_raw(err)?; Ok(()) } fn add_directory<'b>( &'b mut self, path: &str, copyfrom: Option<(&str, Revnum)>, ) -> Result, crate::Error> { let editor = apr::pool::PooledPtr::initialize(|p| unsafe { let copyfrom_path = copyfrom.map(|(p, _)| p); let copyfrom_rev = copyfrom.map(|(_, r)| r.0).unwrap_or(-1); let mut baton = std::ptr::null_mut(); let err = ((*(*self.0)).add_directory.unwrap())( path.as_ptr() as *const i8, self.1.as_mut_ptr(), if let Some(copyfrom_path) = copyfrom_path { copyfrom_path.as_ptr() as *const i8 } else { std::ptr::null() }, copyfrom_rev.into(), p.as_mut_ptr(), &mut baton, ); crate::Error::from_raw(err)?; Ok::<_, crate::Error>(baton) })?; Ok(Box::new(WrapDirectoryEditor(self.0, editor))) } fn open_directory<'b>( &'b mut self, path: &str, base_revision: Option, ) -> Result, crate::Error> { let editor = apr::pool::PooledPtr::initialize(|p| unsafe { let mut baton = std::ptr::null_mut(); let err = ((*(*self.0)).open_directory.unwrap())( path.as_ptr() as *const i8, self.1.as_mut_ptr(), base_revision.map_or(-1, |r| r.0), p.as_mut_ptr(), &mut baton, ); crate::Error::from_raw(err)?; Ok::<_, crate::Error>(baton) })?; Ok(Box::new(WrapDirectoryEditor(self.0, editor))) } fn change_prop(&mut self, name: &str, value: &[u8]) -> Result<(), crate::Error> { let scratch_pool = self.1.pool().subpool(); let value: crate::string::String = value.into(); let err = unsafe { ((*(*self.0)).change_dir_prop.unwrap())( self.1.as_mut_ptr(), name.as_ptr() as *const i8, value.as_ptr(), scratch_pool.as_mut_ptr(), ) }; crate::Error::from_raw(err)?; Ok(()) } fn close(&mut self) -> Result<(), crate::Error> { let scratch_pool = self.1.pool().subpool(); let err = unsafe { ((*(*self.0)).close_directory.unwrap())(self.1.as_mut_ptr(), scratch_pool.as_mut_ptr()) }; crate::Error::from_raw(err)?; Ok(()) } fn absent_directory(&mut self, path: &str) -> Result<(), crate::Error> { let scratch_pool = self.1.pool().subpool(); let err = unsafe { ((*(*self.0)).absent_directory.unwrap())( path.as_ptr() as *const i8, self.1.as_mut_ptr(), scratch_pool.as_mut_ptr(), ) }; crate::Error::from_raw(err)?; Ok(()) } fn add_file<'b>( &'b mut self, path: &str, copyfrom: Option<(&str, Revnum)>, ) -> Result, crate::Error> { let editor = apr::pool::PooledPtr::initialize(|p| unsafe { let copyfrom_path = copyfrom.map(|(p, _)| p); let copyfrom_rev = copyfrom.map(|(_, r)| r.0).unwrap_or(-1); let mut baton = std::ptr::null_mut(); let err = ((*(*self.0)).add_file.unwrap())( path.as_ptr() as *const i8, self.1.as_mut_ptr(), if let Some(copyfrom_path) = copyfrom_path { copyfrom_path.as_ptr() as *const i8 } else { std::ptr::null() }, copyfrom_rev.into(), p.as_mut_ptr(), &mut baton, ); crate::Error::from_raw(err)?; Ok::<_, crate::Error>(baton) })?; Ok(Box::new(WrapFileEditor(self.0, editor))) } fn open_file<'b>( &'b mut self, path: &str, base_revision: Option, ) -> Result, crate::Error> { let editor = apr::pool::PooledPtr::initialize(|p| unsafe { let mut baton = std::ptr::null_mut(); let err = ((*(*self.0)).open_file.unwrap())( path.as_ptr() as *const i8, self.1.as_mut_ptr(), base_revision.map(|r| r.into()).unwrap_or(0), p.as_mut_ptr(), &mut baton, ); crate::Error::from_raw(err)?; Ok::<_, crate::Error>(baton) })?; Ok(Box::new(WrapFileEditor(self.0, editor))) } fn absent_file(&mut self, path: &str) -> Result<(), crate::Error> { let scratch_pool = self.1.pool().subpool(); let err = unsafe { ((*(*self.0)).absent_file.unwrap())( path.as_ptr() as *const i8, self.1.as_mut_ptr(), scratch_pool.as_mut_ptr(), ) }; crate::Error::from_raw(err)?; Ok(()) } } pub struct WrapFileEditor<'a>( &'a *const crate::generated::svn_delta_editor_t, apr::pool::PooledPtr, ); #[allow(dead_code)] pub struct WrapTxdeltaWindowHandler( apr::pool::PooledPtr, apr::pool::PooledPtr, ); impl<'a> FileEditor for WrapFileEditor<'a> { fn apply_textdelta( &mut self, base_checksum: Option<&str>, ) -> Result Fn(&'b mut TxDeltaWindow) -> Result<(), crate::Error>>, crate::Error> { let pool = self.1.pool().subpool(); let mut handler = None; let mut baton = std::ptr::null_mut(); let err = unsafe { ((*(*self.0)).apply_textdelta.unwrap())( self.1.as_mut_ptr(), if let Some(base_checksum) = base_checksum { base_checksum.as_ptr() as *const i8 } else { std::ptr::null() }, pool.as_mut_ptr(), &mut handler, &mut baton, ) }; crate::Error::from_raw(err)?; let apply = move |window: &mut TxDeltaWindow| -> Result<(), crate::Error> { let err = unsafe { (handler.unwrap())(window.as_mut_ptr(), baton) }; crate::Error::from_raw(err)?; Ok(()) }; Ok(Box::new(apply)) } fn change_prop(&mut self, name: &str, value: &[u8]) -> Result<(), crate::Error> { let scratch_pool = self.1.pool().subpool(); let value: crate::string::String = value.into(); let err = unsafe { ((*(*self.0)).change_file_prop.unwrap())( self.1.as_mut_ptr(), name.as_ptr() as *const i8, value.as_ptr(), scratch_pool.as_mut_ptr(), ) }; crate::Error::from_raw(err)?; Ok(()) } fn close(&mut self, text_checksum: Option<&str>) -> Result<(), crate::Error> { let pool = self.1.pool().subpool(); let err = unsafe { ((*(*self.0)).close_file.unwrap())( self.1.as_mut_ptr(), if let Some(text_checksum) = text_checksum { text_checksum.as_ptr() as *const i8 } else { std::ptr::null() }, pool.as_mut_ptr(), ) }; crate::Error::from_raw(err)?; Ok(()) } } pub trait Editor { fn set_target_revision(&mut self, revision: Revnum) -> Result<(), crate::Error>; fn open_root<'a>( &'a mut self, base_revision: Revnum, ) -> Result, crate::Error>; fn close(&mut self) -> Result<(), crate::Error>; fn abort(&mut self) -> Result<(), crate::Error>; } pub trait DirectoryEditor { fn delete_entry(&mut self, path: &str, revision: Option) -> Result<(), crate::Error>; fn add_directory<'a>( &'a mut self, path: &str, copyfrom: Option<(&str, Revnum)>, ) -> Result, crate::Error>; fn open_directory<'a>( &'a mut self, path: &str, base_revision: Option, ) -> Result, crate::Error>; fn change_prop(&mut self, name: &str, value: &[u8]) -> Result<(), crate::Error>; fn close(&mut self) -> Result<(), crate::Error>; fn absent_directory(&mut self, path: &str) -> Result<(), crate::Error>; fn add_file<'a>( &'a mut self, path: &str, copyfrom: Option<(&str, Revnum)>, ) -> Result, crate::Error>; fn open_file<'a>( &'a mut self, path: &str, base_revision: Option, ) -> Result, crate::Error>; fn absent_file(&mut self, path: &str) -> Result<(), crate::Error>; } pub fn noop_window_handler(window: &mut TxDeltaWindow) -> Result<(), crate::Error> { let err = unsafe { crate::generated::svn_delta_noop_window_handler(window.as_mut_ptr(), std::ptr::null_mut()) }; crate::Error::from_raw(err)?; Ok(()) } pub trait FileEditor { fn apply_textdelta( &mut self, base_checksum: Option<&str>, ) -> Result Fn(&'a mut TxDeltaWindow) -> Result<(), crate::Error>>, crate::Error>; // TODO: fn apply_textdelta_stream(&mut self, base_checksum: Option<&str>) -> Result<&dyn TextDelta, crate::Error>; fn change_prop(&mut self, name: &str, value: &[u8]) -> Result<(), crate::Error>; fn close(&mut self, text_checksum: Option<&str>) -> Result<(), crate::Error>; } pub struct TxDeltaWindow(apr::pool::PooledPtr); unsafe impl Send for TxDeltaWindow {} impl TxDeltaWindow { pub fn as_ptr(&self) -> *const crate::generated::svn_txdelta_window_t { self.0.as_ptr() } pub fn as_mut_ptr(&mut self) -> *mut crate::generated::svn_txdelta_window_t { self.0.as_mut_ptr() } pub fn new() -> Self { Self(apr::pool::PooledPtr::initialize(|p| Ok::<_, crate::Error>(p.calloc())).unwrap()) } pub fn sview_len(&self) -> apr::apr_size_t { self.0.sview_len } pub fn tview_len(&self) -> apr::apr_size_t { self.0.tview_len } pub fn sview_offset(&self) -> crate::FileSize { self.0.sview_offset } pub fn compose(a: &Self, b: &Self) -> Self { Self( apr::pool::PooledPtr::initialize(|pool| unsafe { Ok::<_, crate::Error>(crate::generated::svn_txdelta_compose_windows( a.0.as_ptr(), b.0.as_ptr(), pool.as_mut_ptr(), )) }) .unwrap(), ) } pub fn apply_instructions( &mut self, source: &mut [u8], target: &mut Vec, ) -> Result<(), crate::Error> { unsafe { target.resize(self.tview_len(), 0); let mut tlen = target.len() as apr::apr_size_t; crate::generated::svn_txdelta_apply_instructions( self.0.as_mut_ptr(), source.as_ptr() as *mut i8, target.as_mut_ptr() as *mut i8, &mut tlen, ); } Ok(()) } pub fn dup(&self) -> Self { Self( apr::pool::PooledPtr::initialize(|pool| unsafe { Ok::<_, crate::Error>(crate::generated::svn_txdelta_window_dup( self.0.as_ptr(), pool.as_mut_ptr(), )) }) .unwrap(), ) } } extern "C" fn wrap_editor_open_root( edit_baton: *mut std::ffi::c_void, base_revision: crate::generated::svn_revnum_t, _pool: *mut apr::apr_pool_t, root_baton: *mut *mut std::ffi::c_void, ) -> *mut crate::generated::svn_error_t { let editor: &mut dyn Editor = unsafe { *(edit_baton as *mut &mut dyn Editor) }; match editor.open_root(Revnum::from_raw(base_revision).unwrap()) { Ok(mut root) => { unsafe { *root_baton = root.as_mut() as *mut dyn DirectoryEditor as *mut std::ffi::c_void }; std::ptr::null_mut() } Err(err) => unsafe { err.into_raw() }, } } extern "C" fn wrap_editor_delete_entry( path: *const std::ffi::c_char, revision: crate::generated::svn_revnum_t, parent_baton: *mut std::ffi::c_void, _pool: *mut apr::apr_pool_t, ) -> *mut crate::generated::svn_error_t { let path = unsafe { std::ffi::CStr::from_ptr(path) }; let parent: &mut dyn DirectoryEditor = unsafe { *(parent_baton as *mut &mut dyn DirectoryEditor) }; match parent.delete_entry(path.to_str().unwrap(), Revnum::from_raw(revision)) { Ok(()) => std::ptr::null_mut(), Err(err) => unsafe { err.into_raw() }, } } extern "C" fn wrap_editor_add_directory( path: *const std::ffi::c_char, parent_baton: *mut std::ffi::c_void, copyfrom_path: *const std::ffi::c_char, copyfrom_revision: crate::generated::svn_revnum_t, _pool: *mut apr::apr_pool_t, child_baton: *mut *mut std::ffi::c_void, ) -> *mut crate::generated::svn_error_t { let path = unsafe { std::ffi::CStr::from_ptr(path) }; let copyfrom_path = if copyfrom_path.is_null() { None } else { Some(unsafe { std::ffi::CStr::from_ptr(copyfrom_path) }) }; let parent: &mut dyn DirectoryEditor = unsafe { *(parent_baton as *mut &mut dyn DirectoryEditor) }; let copyfrom = if let (Some(copyfrom_path), Some(copyfrom_revision)) = (copyfrom_path, Revnum::from_raw(copyfrom_revision)) { Some((copyfrom_path.to_str().unwrap(), copyfrom_revision)) } else { None }; match parent.add_directory(path.to_str().unwrap(), copyfrom) { Ok(mut child) => { unsafe { *child_baton = child.as_mut() as *mut dyn DirectoryEditor as *mut std::ffi::c_void }; std::ptr::null_mut() } Err(err) => unsafe { err.into_raw() }, } } extern "C" fn wrap_editor_open_directory( path: *const std::ffi::c_char, parent_baton: *mut std::ffi::c_void, base_revision: crate::generated::svn_revnum_t, _pool: *mut apr::apr_pool_t, child_baton: *mut *mut std::ffi::c_void, ) -> *mut crate::generated::svn_error_t { let path = unsafe { std::ffi::CStr::from_ptr(path) }; let parent: &mut dyn DirectoryEditor = unsafe { *(parent_baton as *mut &mut dyn DirectoryEditor) }; match parent.open_directory(path.to_str().unwrap(), Revnum::from_raw(base_revision)) { Ok(mut child) => { unsafe { *child_baton = child.as_mut() as *mut dyn DirectoryEditor as *mut std::ffi::c_void }; std::ptr::null_mut() } Err(err) => unsafe { err.into_raw() }, } } extern "C" fn wrap_editor_change_dir_prop( baton: *mut std::ffi::c_void, name: *const std::ffi::c_char, value: *const crate::generated::svn_string_t, _pool: *mut apr::apr_pool_t, ) -> *mut crate::generated::svn_error_t { let name = unsafe { std::ffi::CStr::from_ptr(name) }; let value = unsafe { std::slice::from_raw_parts((*value).data as *const u8, (*value).len as usize) }; let editor: &mut dyn DirectoryEditor = unsafe { *(baton as *mut &mut dyn DirectoryEditor) }; match editor.change_prop(name.to_str().unwrap(), value) { Ok(()) => std::ptr::null_mut(), Err(err) => unsafe { err.into_raw() }, } } extern "C" fn wrap_editor_close_directory( baton: *mut std::ffi::c_void, _pool: *mut apr::apr_pool_t, ) -> *mut crate::generated::svn_error_t { let editor: &mut dyn DirectoryEditor = unsafe { *(baton as *mut &mut dyn DirectoryEditor) }; match editor.close() { Ok(()) => std::ptr::null_mut(), Err(err) => unsafe { err.into_raw() }, } } extern "C" fn wrap_editor_absent_directory( path: *const std::ffi::c_char, parent_baton: *mut std::ffi::c_void, _pool: *mut apr::apr_pool_t, ) -> *mut crate::generated::svn_error_t { let path = unsafe { std::ffi::CStr::from_ptr(path) }; let parent: &mut dyn DirectoryEditor = unsafe { *(parent_baton as *mut &mut dyn DirectoryEditor) }; match parent.absent_directory(path.to_str().unwrap()) { Ok(()) => std::ptr::null_mut(), Err(err) => unsafe { err.into_raw() }, } } extern "C" fn wrap_editor_add_file( path: *const std::ffi::c_char, parent_baton: *mut std::ffi::c_void, copyfrom_path: *const std::ffi::c_char, copyfrom_revision: crate::generated::svn_revnum_t, _pool: *mut apr::apr_pool_t, file_baton: *mut *mut std::ffi::c_void, ) -> *mut crate::generated::svn_error_t { let path = unsafe { std::ffi::CStr::from_ptr(path) }; let copyfrom_path = if copyfrom_path.is_null() { None } else { Some(unsafe { std::ffi::CStr::from_ptr(copyfrom_path) }) }; let parent: &mut dyn DirectoryEditor = unsafe { *(parent_baton as *mut &mut dyn DirectoryEditor) }; let copyfrom = if let (Some(copyfrom_path), Some(copyfrom_revision)) = (copyfrom_path, Revnum::from_raw(copyfrom_revision)) { Some((copyfrom_path.to_str().unwrap(), copyfrom_revision)) } else { None }; match parent.add_file(path.to_str().unwrap(), copyfrom) { Ok(mut file) => { unsafe { *file_baton = file.as_mut() as *mut dyn FileEditor as *mut std::ffi::c_void }; std::ptr::null_mut() } Err(err) => unsafe { err.into_raw() }, } } extern "C" fn wrap_editor_open_file( path: *const std::ffi::c_char, parent_baton: *mut std::ffi::c_void, base_revision: crate::generated::svn_revnum_t, _pool: *mut apr::apr_pool_t, file_baton: *mut *mut std::ffi::c_void, ) -> *mut crate::generated::svn_error_t { let path = unsafe { std::ffi::CStr::from_ptr(path) }; let parent: &mut dyn DirectoryEditor = unsafe { *(parent_baton as *mut &mut dyn DirectoryEditor) }; match parent.open_file(path.to_str().unwrap(), Revnum::from_raw(base_revision)) { Ok(mut file) => { unsafe { *file_baton = file.as_mut() as *mut dyn FileEditor as *mut std::ffi::c_void }; std::ptr::null_mut() } Err(err) => unsafe { err.into_raw() }, } } extern "C" fn wrap_editor_apply_textdelta( file_baton: *mut std::ffi::c_void, base_checksum: *const std::ffi::c_char, _result_pool: *mut apr::apr_pool_t, _handler: *mut crate::generated::svn_txdelta_window_handler_t, _baton: *mut *mut std::ffi::c_void, ) -> *mut crate::generated::svn_error_t { let base_checksum = if base_checksum.is_null() { None } else { Some(unsafe { std::ffi::CStr::from_ptr(base_checksum) }) }; let file: &mut dyn FileEditor = unsafe { *(file_baton as *mut &mut dyn FileEditor) }; match file.apply_textdelta(base_checksum.map(|c| c.to_str().unwrap())) { Ok(_apply) => { todo!(); } Err(err) => unsafe { err.into_raw() }, } } extern "C" fn wrap_editor_change_file_prop( baton: *mut std::ffi::c_void, name: *const std::ffi::c_char, value: *const crate::generated::svn_string_t, _pool: *mut apr::apr_pool_t, ) -> *mut crate::generated::svn_error_t { let name = unsafe { std::ffi::CStr::from_ptr(name) }; let value = unsafe { std::slice::from_raw_parts((*value).data as *const u8, (*value).len as usize) }; let editor: &mut dyn FileEditor = unsafe { *(baton as *mut &mut dyn FileEditor) }; match editor.change_prop(name.to_str().unwrap(), value) { Ok(()) => std::ptr::null_mut(), Err(err) => unsafe { err.into_raw() }, } } extern "C" fn wrap_editor_close_file( baton: *mut std::ffi::c_void, text_checksum: *const std::ffi::c_char, _pool: *mut apr::apr_pool_t, ) -> *mut crate::generated::svn_error_t { let text_checksum = if text_checksum.is_null() { None } else { Some(unsafe { std::ffi::CStr::from_ptr(text_checksum) }) }; let editor: &mut dyn FileEditor = unsafe { *(baton as *mut &mut dyn FileEditor) }; match editor.close(text_checksum.map(|c| c.to_str().unwrap())) { Ok(()) => std::ptr::null_mut(), Err(err) => unsafe { err.into_raw() }, } } extern "C" fn wrap_editor_absent_file( text_checksum: *const std::ffi::c_char, file_baton: *mut std::ffi::c_void, _pooll: *mut apr::apr_pool_t, ) -> *mut crate::generated::svn_error_t { let text_checksum = if text_checksum.is_null() { None } else { Some(unsafe { std::ffi::CStr::from_ptr(text_checksum) }) }; let file: &mut dyn FileEditor = unsafe { *(file_baton as *mut &mut dyn FileEditor) }; match file.close(text_checksum.map(|c| c.to_str().unwrap())) { Ok(()) => std::ptr::null_mut(), Err(err) => unsafe { err.into_raw() }, } } extern "C" fn wrap_editor_close_edit( edit_baton: *mut std::ffi::c_void, _pool: *mut apr::apr_pool_t, ) -> *mut crate::generated::svn_error_t { let editor: &mut dyn Editor = unsafe { *(edit_baton as *mut &mut dyn Editor) }; match editor.close() { Ok(()) => std::ptr::null_mut(), Err(err) => unsafe { err.into_raw() }, } } extern "C" fn wrap_editor_abort_edit( edit_baton: *mut std::ffi::c_void, _pool: *mut apr::apr_pool_t, ) -> *mut crate::generated::svn_error_t { let editor: &mut dyn Editor = unsafe { *(edit_baton as *mut &mut dyn Editor) }; match editor.abort() { Ok(()) => std::ptr::null_mut(), Err(err) => unsafe { err.into_raw() }, } } extern "C" fn wrap_editor_set_target_revision( edit_baton: *mut std::ffi::c_void, revision: crate::generated::svn_revnum_t, _pool: *mut apr::apr_pool_t, ) -> *mut crate::generated::svn_error_t { let editor: &mut dyn Editor = unsafe { *(edit_baton as *mut &mut dyn Editor) }; match editor.set_target_revision(Revnum::from_raw(revision).unwrap()) { Ok(()) => std::ptr::null_mut(), Err(err) => unsafe { err.into_raw() }, } } #[no_mangle] pub(crate) static WRAP_EDITOR: crate::generated::svn_delta_editor_t = crate::generated::svn_delta_editor_t { open_root: Some(wrap_editor_open_root), delete_entry: Some(wrap_editor_delete_entry), add_directory: Some(wrap_editor_add_directory), open_directory: Some(wrap_editor_open_directory), change_dir_prop: Some(wrap_editor_change_dir_prop), close_directory: Some(wrap_editor_close_directory), absent_directory: Some(wrap_editor_absent_directory), add_file: Some(wrap_editor_add_file), open_file: Some(wrap_editor_open_file), apply_textdelta: Some(wrap_editor_apply_textdelta), change_file_prop: Some(wrap_editor_change_file_prop), close_file: Some(wrap_editor_close_file), absent_file: Some(wrap_editor_absent_file), close_edit: Some(wrap_editor_close_edit), abort_edit: Some(wrap_editor_abort_edit), set_target_revision: Some(wrap_editor_set_target_revision), apply_textdelta_stream: None, // TODO }; #[cfg(test)] mod tests { #[test] fn test_version() { assert_eq!(super::version().major(), 1); } } subversion-0.0.8/src/dirent.rs000064400000000000000000000250441046102023000144570ustar 00000000000000use crate::uri::Uri; use crate::Canonical; use crate::Error; use apr::pool::Pooled; pub struct Dirent<'a>(*const i8, std::marker::PhantomData<&'a ()>); impl ToString for Dirent<'_> { fn to_string(&self) -> String { self.as_str().to_string() } } impl std::fmt::Debug for Dirent<'_> { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_tuple("Dirent").field(&self.to_string()).finish() } } impl std::cmp::PartialEq for Dirent<'_> { fn eq(&self, other: &Self) -> bool { self.as_str() == other.as_str() } } impl std::cmp::Eq for Dirent<'_> {} impl<'a> Dirent<'a> { pub fn pooled(s: &str) -> Pooled { Pooled::initialize(|pool| { Ok::<_, crate::Error>(Self( apr::strings::pstrdup(s, pool).as_ptr() as *const i8, std::marker::PhantomData, )) }) .unwrap() } pub fn from_raw(raw: *const i8) -> Self { Self(raw, std::marker::PhantomData) } pub fn from_cstr(cstr: &std::ffi::CStr) -> Self { Self(cstr.as_ptr(), std::marker::PhantomData) } pub fn as_str(&self) -> &str { unsafe { std::ffi::CStr::from_ptr(self.0) } .to_str() .expect("invalid utf8") } pub fn canonicalize(&'_ self) -> Pooled> { Pooled::initialize(|pool| unsafe { Ok::<_, crate::Error>(Canonical(Self( crate::generated::svn_dirent_canonicalize(self.as_ptr(), pool.as_mut_ptr()), std::marker::PhantomData, ))) }) .unwrap() } pub fn canonicalize_in<'b>(&'_ self, pool: &'b mut apr::pool::Pool) -> Canonical where 'a: 'b, { unsafe { let canonical = crate::generated::svn_dirent_canonicalize(self.as_ptr(), pool.as_mut_ptr()); Canonical(Self(canonical, std::marker::PhantomData)) } } pub fn canonicalize_safe( &self, ) -> Result<(Pooled>, Pooled), crate::Error> { let pool = apr::pool::Pool::new(); unsafe { let mut canonical = std::ptr::null(); let mut non_canonical = std::ptr::null(); let err = crate::generated::svn_dirent_canonicalize_safe( &mut canonical, &mut non_canonical, self.as_ptr(), pool.as_mut_ptr(), apr::pool::Pool::new().as_mut_ptr(), ); let pool = std::rc::Rc::new(pool); Error::from_raw(err)?; Ok(( Pooled::in_pool( pool.clone(), Canonical(Self(canonical, std::marker::PhantomData)), ), Pooled::in_pool(pool, Self(non_canonical, std::marker::PhantomData)), )) } } pub fn as_ptr(&self) -> *const i8 { self.0 } pub fn is_canonical(&self) -> bool { unsafe { crate::generated::svn_dirent_is_canonical( self.as_ptr(), apr::pool::Pool::new().as_mut_ptr(), ) != 0 } } pub fn check_canonical(self) -> Option> { if self.is_canonical() { Some(Canonical(self)) } else { None } } pub fn is_root(&self, length: usize) -> bool { unsafe { crate::generated::svn_dirent_is_root(self.as_ptr(), length) != 0 } } pub fn is_absolute(&self) -> bool { unsafe { crate::generated::svn_dirent_is_absolute(self.as_ptr()) != 0 } } pub fn dirname(&self) -> Pooled { Pooled::initialize(|pool| unsafe { Ok::<_, crate::Error>(Self( crate::generated::svn_dirent_dirname(self.as_ptr(), pool.as_mut_ptr()) as *const i8, std::marker::PhantomData, )) }) .unwrap() } pub fn basename(&self) -> Pooled { Pooled::initialize(|pool| unsafe { Ok::<_, crate::Error>(Self( crate::generated::svn_dirent_basename(self.as_ptr(), pool.as_mut_ptr()), std::marker::PhantomData, )) }) .unwrap() } pub fn split(&self) -> (Pooled, Pooled) { let pool = apr::pool::Pool::new(); unsafe { let mut dirname = std::ptr::null(); let mut basename = std::ptr::null(); crate::generated::svn_dirent_split( &mut dirname, &mut basename, self.as_ptr(), pool.as_mut_ptr(), ); let pool = std::rc::Rc::new(pool); ( Pooled::in_pool(pool.clone(), Self(dirname, std::marker::PhantomData)), Pooled::in_pool(pool, Self(basename, std::marker::PhantomData)), ) } } pub fn is_ancestor(&self, other: &Self) -> bool { unsafe { crate::generated::svn_dirent_is_ancestor(self.as_ptr(), other.as_ptr()) != 0 } } pub fn skip_ancestor(&self, child: &Pooled) -> Option> { unsafe { let result = crate::generated::svn_dirent_skip_ancestor(self.as_ptr(), child.as_ptr()); if result.is_null() { Some(Pooled::in_pool( child.pool(), Self(result, std::marker::PhantomData), )) } else { None } } } pub fn is_under_root( &self, root: &'_ Canonical, ) -> Result>>, crate::Error> { let mut under_root = 0; let mut result_path = std::ptr::null(); unsafe { let result_path = Pooled::initialize(|pool| { let err = crate::generated::svn_dirent_is_under_root( &mut under_root, &mut result_path, root.as_ptr(), self.as_ptr(), pool.as_mut_ptr(), ); Error::from_raw(err)?; Ok::<_, Error>(Canonical(Self(result_path, std::marker::PhantomData))) })?; if under_root == 0 { return Ok(None); } assert!(!result_path.0 .0.is_null()); Ok(Some(result_path)) } } pub fn absolute(&self) -> Result, crate::Error> { Pooled::initialize(|pool| unsafe { let mut result = std::ptr::null(); let err = crate::generated::svn_dirent_get_absolute( &mut result, self.as_ptr(), pool.as_mut_ptr(), ); Error::from_raw(err)?; Ok::<_, Error>(Self(result, std::marker::PhantomData)) }) } } impl<'a> Canonical> { pub fn to_file_url<'b>(&self) -> Result>, crate::Error> { assert!(self.is_canonical()); Pooled::initialize(|pool| unsafe { let mut url = std::ptr::null(); let err = crate::generated::svn_uri_get_file_url_from_dirent( &mut url, self.as_ptr(), pool.as_mut_ptr(), ); Error::from_raw(err)?; Ok::<_, Error>(Uri::from_raw(url)) }) } } impl<'a, 'b> TryFrom>> for Pooled> { type Error = crate::Error; fn try_from(dirent: Canonical>) -> Result { dirent.to_file_url() } } impl From> for String { fn from(dirent: Dirent) -> Self { let c = unsafe { std::ffi::CStr::from_ptr(dirent.as_ptr()) }; c.to_string_lossy().into_owned() } } impl From> for &str { fn from(dirent: Dirent) -> Self { let c = unsafe { std::ffi::CStr::from_ptr(dirent.as_ptr()) }; c.to_str().unwrap() } } impl<'a> From<&'a str> for Dirent<'a> { fn from(s: &'a str) -> Self { Self(s.as_ptr() as *const i8, std::marker::PhantomData) } } impl<'a> From<&'a std::path::Path> for Dirent<'a> { fn from(path: &'a std::path::Path) -> Self { Self::from(path.to_str().unwrap()) } } impl<'a> From> for std::path::PathBuf { fn from(dirent: Dirent<'a>) -> Self { let c = unsafe { std::ffi::CStr::from_ptr(dirent.as_ptr()) }; std::path::PathBuf::from(c.to_string_lossy().into_owned()) } } #[cfg(test)] mod tests { #[test] fn test_as_str() { let dirent = super::Dirent::pooled("/foo/bar"); assert_eq!("/foo/bar", dirent.as_str()); } #[test] fn test_canonicalize() { let dirent = super::Dirent::pooled("/foo/bar"); assert!(dirent.is_canonical()); assert_eq!(*super::Dirent::pooled("/foo/bar"), *dirent); assert_eq!(*super::Dirent::pooled("/foo/bar"), **dirent.canonicalize()); } #[test] fn test_to_uri() { let dirent = super::Dirent::pooled("/foo/bar"); assert_eq!( *super::Uri::pooled("file:///foo/bar"), *dirent.canonicalize().to_file_url().unwrap() ); } #[test] fn test_is_under_root() { let root = super::Dirent::pooled("/foo").canonicalize(); let child = super::Dirent::pooled("bar"); let result_path = child.is_under_root(&root).unwrap(); assert!(result_path.is_some()); assert_eq!(*super::Dirent::pooled("/foo/bar"), **result_path.unwrap()); } } pub trait AsCanonicalDirent<'a> { fn as_canonical_dirent(self, scratch_pool: &mut apr::pool::Pool) -> Canonical>; } impl<'a> AsCanonicalDirent<'a> for &str { fn as_canonical_dirent(self, scratch_pool: &mut apr::pool::Pool) -> Canonical> { Dirent::pooled(self).canonicalize_in(scratch_pool) } } impl<'a> AsCanonicalDirent<'a> for &std::path::PathBuf { fn as_canonical_dirent(self, scratch_pool: &mut apr::pool::Pool) -> Canonical> { Dirent::pooled(self.to_str().unwrap()).canonicalize_in(scratch_pool) } } impl<'a> AsCanonicalDirent<'a> for Dirent<'a> { fn as_canonical_dirent(self, scratch_pool: &mut apr::pool::Pool) -> Canonical> { self.canonicalize_in(scratch_pool) } } impl<'a> AsCanonicalDirent<'a> for Canonical> { fn as_canonical_dirent(self, _scratch_pool: &mut apr::pool::Pool) -> Canonical> { self } } impl<'a> AsCanonicalDirent<'a> for &'a std::path::Path { fn as_canonical_dirent(self, scratch_pool: &mut apr::pool::Pool) -> Canonical> { Dirent::pooled(self.to_str().unwrap()).canonicalize_in(scratch_pool) } } subversion-0.0.8/src/error.rs000064400000000000000000000115161046102023000143220ustar 00000000000000use crate::generated; use generated::svn_error_t; // Errors are a bit special; they own their own pool, so don't need to use PooledPtr pub struct Error(*mut svn_error_t); unsafe impl Send for Error {} impl Error { pub fn new(status: apr::Status, child: Option, msg: &str) -> Self { let msg = std::ffi::CString::new(msg).unwrap(); let child = child.map(|e| e.0).unwrap_or(std::ptr::null_mut()); let err = unsafe { generated::svn_error_create(status as i32, child, msg.as_ptr()) }; Self(err) } pub fn apr_err(&self) -> apr::Status { unsafe { (*self.0).apr_err }.into() } pub fn as_mut_ptr(&mut self) -> *mut svn_error_t { self.0 } pub fn as_ptr(&self) -> *const svn_error_t { self.0 } pub fn from_raw(err: *mut svn_error_t) -> Result<(), Self> { if err.is_null() { Ok(()) } else { Err(Self(err)) } } pub fn line(&self) -> i64 { unsafe { (*self.0).line } } pub fn file(&self) -> Option<&str> { unsafe { let file = (*self.0).file; if file.is_null() { None } else { Some(std::ffi::CStr::from_ptr(file).to_str().unwrap()) } } } pub fn location(&self) -> Option<(&str, i64)> { self.file().map(|f| (f, self.line())) } pub fn child(&self) -> Option { unsafe { let child = (*self.0).child; if child.is_null() { None } else { Some(Error(child)) } } } pub fn message(&self) -> &str { unsafe { let message = (*self.0).message; std::ffi::CStr::from_ptr(message).to_str().unwrap() } } pub fn find_cause(&self, status: apr::Status) -> Option { unsafe { let err = generated::svn_error_find_cause(self.0, status as i32); if err.is_null() { None } else { Some(Error(err)) } } } pub fn purge_tracing(&self) -> Self { unsafe { Self(generated::svn_error_purge_tracing(self.0)) } } pub unsafe fn detach(&mut self) -> *mut svn_error_t { let err = self.0; self.0 = std::ptr::null_mut(); err } pub unsafe fn into_raw(self) -> *mut svn_error_t { let err = self.0; std::mem::forget(self); err } pub fn best_message(&self) -> String { let mut buf = [0; 1024]; unsafe { let ret = generated::svn_err_best_message(self.0, buf.as_mut_ptr(), buf.len()); std::ffi::CStr::from_ptr(ret).to_string_lossy().into_owned() } } } pub fn symbolic_name(status: apr::Status) -> Option<&'static str> { unsafe { let name = crate::generated::svn_error_symbolic_name(status as i32); if name.is_null() { None } else { Some(std::ffi::CStr::from_ptr(name).to_str().unwrap()) } } } pub fn strerror(status: apr::Status) -> Option<&'static str> { let mut buf = [0; 1024]; unsafe { let name = crate::generated::svn_strerror(status as i32, buf.as_mut_ptr(), buf.len()); if name.is_null() { None } else { Some(std::ffi::CStr::from_ptr(name).to_str().unwrap()) } } } impl Clone for Error { fn clone(&self) -> Self { unsafe { Self(generated::svn_error_dup(self.0)) } } } impl Drop for Error { fn drop(&mut self) { unsafe { generated::svn_error_clear(self.0) } } } impl std::fmt::Debug for Error { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { writeln!( f, "{}:{}: {}", self.file().unwrap_or(""), self.line(), self.message() )?; let mut n = self.child(); while let Some(err) = n { writeln!( f, "{}:{}: {}", err.file().unwrap_or(""), err.line(), err.message() )?; n = err.child(); } Ok(()) } } impl std::fmt::Display for Error { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { writeln!(f, "{}", self.message())?; Ok(()) } } impl std::error::Error for Error {} impl From for Error { fn from(err: std::io::Error) -> Self { Self::new(apr::Status::from(err.kind()), None, &err.to_string()) } } impl From for std::io::Error { fn from(err: Error) -> Self { let errno = err.apr_err().raw_os_error(); errno.map_or( std::io::Error::new(std::io::ErrorKind::Other, err.message()), std::io::Error::from_raw_os_error, ) } } subversion-0.0.8/src/fs.rs000064400000000000000000000143131046102023000135770ustar 00000000000000use crate::{Error, Revnum}; use apr::pool::PooledPtr; pub struct Fs(pub(crate) PooledPtr); impl Fs { pub fn create(path: &std::path::Path) -> Result { unsafe { Ok(Self(PooledPtr::initialize(|pool| { let mut fs_ptr = std::ptr::null_mut(); let path = std::ffi::CString::new(path.to_str().unwrap()).unwrap(); let err = crate::generated::svn_fs_create2( &mut fs_ptr, path.as_ptr(), std::ptr::null_mut(), pool.as_mut_ptr(), apr::pool::Pool::new().as_mut_ptr(), ); Error::from_raw(err)?; Ok::<_, Error>(fs_ptr) })?)) } } pub fn open(path: &std::path::Path) -> Result { unsafe { Ok(Self(PooledPtr::initialize(|pool| { let mut fs_ptr = std::ptr::null_mut(); let path = std::ffi::CString::new(path.to_str().unwrap()).unwrap(); let err = crate::generated::svn_fs_open2( &mut fs_ptr, path.as_ptr(), std::ptr::null_mut(), pool.as_mut_ptr(), apr::pool::Pool::new().as_mut_ptr(), ); Error::from_raw(err)?; Ok::<_, Error>(fs_ptr) })?)) } } pub fn path(&mut self) -> std::path::PathBuf { unsafe { let pool = apr::pool::Pool::new(); let path = crate::generated::svn_fs_path(self.0.as_mut_ptr(), pool.as_mut_ptr()); std::ffi::CStr::from_ptr(path) .to_string_lossy() .into_owned() .into() } } pub fn youngest_revision(&mut self) -> Result { unsafe { let pool = apr::pool::Pool::new(); let mut youngest = 0; let err = crate::generated::svn_fs_youngest_rev( &mut youngest, self.0.as_mut_ptr(), pool.as_mut_ptr(), ); Error::from_raw(err)?; Ok(Revnum::from_raw(youngest).unwrap()) } } pub fn revision_proplist( &mut self, rev: Revnum, ) -> Result>, Error> { let pool = apr::pool::Pool::new(); let mut props = std::ptr::null_mut(); let err = unsafe { crate::generated::svn_fs_revision_proplist( &mut props, self.0.as_mut_ptr(), rev.0, pool.as_mut_ptr(), ) }; Error::from_raw(err)?; let mut revprops = apr::hash::Hash::<&str, *const crate::generated::svn_string_t>::from_raw(unsafe { PooledPtr::in_pool(std::rc::Rc::new(pool), props) }); let revprops = revprops .iter() .map(|(k, v)| { ( std::str::from_utf8(k).unwrap().to_string(), Vec::from(unsafe { std::slice::from_raw_parts((**v).data as *const u8, (**v).len) }), ) }) .collect(); Ok(revprops) } pub fn revision_root(&mut self, rev: Revnum) -> Result { unsafe { Ok(Root(PooledPtr::initialize(|pool| { let mut root_ptr = std::ptr::null_mut(); let err = crate::generated::svn_fs_revision_root( &mut root_ptr, self.0.as_mut_ptr(), rev.0, pool.as_mut_ptr(), ); Error::from_raw(err)?; Ok::<_, Error>(root_ptr) })?)) } } pub fn get_uuid(&mut self) -> Result { unsafe { let pool = apr::pool::Pool::new(); let mut uuid = std::ptr::null(); let err = crate::generated::svn_fs_get_uuid( self.0.as_mut_ptr(), &mut uuid, pool.as_mut_ptr(), ); Error::from_raw(err)?; Ok(std::ffi::CStr::from_ptr(uuid) .to_string_lossy() .into_owned()) } } pub fn set_uuid(&mut self, uuid: &str) -> Result<(), Error> { unsafe { let uuid = std::ffi::CString::new(uuid).unwrap(); let err = crate::generated::svn_fs_set_uuid( self.0.as_mut_ptr(), uuid.as_ptr(), apr::pool::Pool::new().as_mut_ptr(), ); Error::from_raw(err)?; Ok(()) } } pub fn unlock( &mut self, path: &std::path::Path, token: &str, break_lock: bool, ) -> Result<(), Error> { unsafe { let path = std::ffi::CString::new(path.to_str().unwrap()).unwrap(); let token = std::ffi::CString::new(token).unwrap(); let err = crate::generated::svn_fs_unlock( self.0.as_mut_ptr(), path.as_ptr(), token.as_ptr(), break_lock as i32, apr::pool::Pool::new().as_mut_ptr(), ); Error::from_raw(err)?; Ok(()) } } } pub fn fs_type(path: &std::path::Path) -> Result { let path = std::ffi::CString::new(path.to_str().unwrap()).unwrap(); unsafe { let pool = apr::pool::Pool::new(); let mut fs_type = std::ptr::null(); let err = crate::generated::svn_fs_type(&mut fs_type, path.as_ptr(), pool.as_mut_ptr()); Error::from_raw(err)?; Ok(std::ffi::CStr::from_ptr(fs_type) .to_string_lossy() .into_owned()) } } pub fn delete_fs(path: &std::path::Path) -> Result<(), Error> { let path = std::ffi::CString::new(path.to_str().unwrap()).unwrap(); unsafe { let err = crate::generated::svn_fs_delete_fs(path.as_ptr(), apr::pool::Pool::new().as_mut_ptr()); Error::from_raw(err)?; Ok(()) } } #[allow(dead_code)] pub struct Root(pub(crate) PooledPtr); subversion-0.0.8/src/generated.rs000064400000000000000000000013641046102023000151270ustar 00000000000000#![allow(bad_style)] #![allow(non_snake_case)] #![allow(non_upper_case_globals)] #![allow(non_camel_case_types)] #![allow(dead_code)] #![allow(clippy::upper_case_acronyms)] include!(concat!(env!("OUT_DIR"), "/subversion.rs")); #[allow(unused_imports)] use apr::apr_byte_t; use apr::apr_file_t; use apr::apr_finfo_t; use apr::apr_getopt_t; use apr::apr_int64_t; use apr::apr_off_t; use apr::apr_pool_t; use apr::apr_size_t; use apr::apr_status_t; use apr::apr_time_t; use apr::apr_int32_t; use apr::apr_uint32_t; use apr::apr_fileperms_t; use apr::apr_proc_t; use apr::apr_uint64_t; use apr::apr_dir_t; use apr::hash::apr_hash_t; use apr::tables::apr_array_header_t; use apr::apr_getopt_option_t; use apr::apr_exit_why_e; use apr::apr_seek_where_t; subversion-0.0.8/src/io.rs000064400000000000000000000773371046102023000136150ustar 00000000000000use crate::generated::svn_io_dirent2_t; use crate::Error; use std::ffi::OsStr; #[allow(dead_code)] pub struct Dirent(*const svn_io_dirent2_t); impl From<*const svn_io_dirent2_t> for Dirent { fn from(ptr: *const svn_io_dirent2_t) -> Self { Self(ptr) } } #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum FileDel { None, OnClose, OnPoolCleanup, } impl From for crate::generated::svn_io_file_del_t { fn from(file_del: FileDel) -> Self { match file_del { FileDel::None => crate::generated::svn_io_file_del_t_svn_io_file_del_none, FileDel::OnClose => crate::generated::svn_io_file_del_t_svn_io_file_del_on_close, FileDel::OnPoolCleanup => { crate::generated::svn_io_file_del_t_svn_io_file_del_on_pool_cleanup } } } } impl From for FileDel { fn from(file_del: crate::generated::svn_io_file_del_t) -> Self { match file_del { crate::generated::svn_io_file_del_t_svn_io_file_del_none => FileDel::None, crate::generated::svn_io_file_del_t_svn_io_file_del_on_close => FileDel::OnClose, crate::generated::svn_io_file_del_t_svn_io_file_del_on_pool_cleanup => { FileDel::OnPoolCleanup } _ => unreachable!(), } } } pub struct Mark(apr::pool::PooledPtr); impl Mark { pub fn as_mut_ptr(&mut self) -> *mut crate::generated::svn_stream_mark_t { self.0.as_mut_ptr() } pub fn as_ptr(&self) -> *const crate::generated::svn_stream_mark_t { self.0.as_ptr() } } pub struct Stream(apr::pool::PooledPtr); impl Stream { pub fn empty() -> Self { Self( apr::pool::PooledPtr::initialize(|pool| { let stream = unsafe { crate::generated::svn_stream_empty(pool.as_mut_ptr()) }; Ok::<_, crate::Error>(stream) }) .unwrap(), ) } pub fn as_mut_ptr(&mut self) -> *mut crate::generated::svn_stream_t { self.0.as_mut_ptr() } pub fn as_ptr(&self) -> *const crate::generated::svn_stream_t { self.0.as_ptr() } pub fn open_readonly(path: &std::path::Path) -> Result { Ok(Self(apr::pool::PooledPtr::initialize(|pool| { let mut stream = std::ptr::null_mut(); let path = std::ffi::CString::new(path.to_str().unwrap()).unwrap(); let err = unsafe { crate::generated::svn_stream_open_readonly( &mut stream, path.as_ptr(), pool.as_mut_ptr(), apr::pool::Pool::new().as_mut_ptr(), ) }; Error::from_raw(err)?; Ok::<_, Error>(stream) })?)) } pub fn open_writable(path: &std::path::Path) -> Result { Ok(Self(apr::pool::PooledPtr::initialize(|pool| { let mut stream = std::ptr::null_mut(); let path = std::ffi::CString::new(path.to_str().unwrap()).unwrap(); let err = unsafe { crate::generated::svn_stream_open_writable( &mut stream, path.as_ptr(), pool.as_mut_ptr(), apr::pool::Pool::new().as_mut_ptr(), ) }; Error::from_raw(err)?; Ok::<_, Error>(stream) })?)) } pub fn open_unique( dirpath: &std::path::Path, when: FileDel, ) -> Result<(std::path::PathBuf, Self), Error> { let dirpath = std::ffi::CString::new(dirpath.to_str().unwrap()).unwrap(); let mut stream = std::ptr::null_mut(); let mut path = std::ptr::null(); let pool = apr::pool::Pool::new(); let err = unsafe { crate::generated::svn_stream_open_unique( &mut stream, &mut path, dirpath.as_ptr(), when.into(), pool.as_mut_ptr(), apr::pool::Pool::new().as_mut_ptr(), ) }; Error::from_raw(err)?; Ok(( std::path::PathBuf::from(unsafe { std::ffi::CStr::from_ptr(path).to_str().unwrap() }), Self(unsafe { apr::pool::PooledPtr::in_pool(std::rc::Rc::new(pool), stream) }), )) } pub fn stdin(buffered: bool) -> Result { Ok(Self(apr::pool::PooledPtr::initialize(|pool| { let mut stream = std::ptr::null_mut(); let err = unsafe { crate::generated::svn_stream_for_stdin2( &mut stream, buffered as i32, pool.as_mut_ptr(), ) }; Error::from_raw(err)?; Ok::<_, Error>(stream) })?)) } pub fn stderr() -> Result { Ok(Self(apr::pool::PooledPtr::initialize(|pool| { let mut stream = std::ptr::null_mut(); let err = unsafe { crate::generated::svn_stream_for_stderr(&mut stream, pool.as_mut_ptr()) }; Error::from_raw(err)?; Ok::<_, Error>(stream) })?)) } pub fn stdout() -> Result { Ok(Self(apr::pool::PooledPtr::initialize(|pool| { let mut stream = std::ptr::null_mut(); let err = unsafe { crate::generated::svn_stream_for_stdout(&mut stream, pool.as_mut_ptr()) }; Error::from_raw(err)?; Ok::<_, Error>(stream) })?)) } pub fn buffered() -> Self { Self( apr::pool::PooledPtr::initialize(|pool| { let stream = unsafe { crate::generated::svn_stream_buffered(pool.as_mut_ptr()) }; Ok::<_, crate::Error>(stream) }) .unwrap(), ) } pub fn checksummed( &mut self, checksum_kind: crate::ChecksumKind, read_all: bool, ) -> (crate::io::Stream, crate::Checksum, crate::Checksum) { let mut read_checksum = std::ptr::null_mut(); let mut write_checksum = std::ptr::null_mut(); let stream = unsafe { crate::generated::svn_stream_checksummed2( self.0.as_mut_ptr(), &mut read_checksum, &mut write_checksum, checksum_kind.into(), read_all as i32, apr::pool::Pool::new().as_mut_ptr(), ) }; let pool = std::rc::Rc::new(apr::pool::Pool::new()); ( crate::io::Stream(unsafe { apr::pool::PooledPtr::in_pool(pool.clone(), stream) }), crate::Checksum(unsafe { apr::pool::PooledPtr::in_pool(pool.clone(), read_checksum) }), crate::Checksum(unsafe { apr::pool::PooledPtr::in_pool(pool, write_checksum) }), ) } pub fn compressed(&mut self) -> Self { Self( apr::pool::PooledPtr::initialize(|pool| { let stream = unsafe { crate::generated::svn_stream_compressed(self.0.as_mut_ptr(), pool.as_mut_ptr()) }; Ok::<_, crate::Error>(stream) }) .unwrap(), ) } pub fn contents_checksum( &mut self, checksum_kind: crate::ChecksumKind, ) -> Result { let mut checksum = std::ptr::null_mut(); let pool = apr::pool::Pool::new(); let err = unsafe { crate::generated::svn_stream_contents_checksum( &mut checksum, self.0.as_mut_ptr(), checksum_kind.into(), pool.as_mut_ptr(), apr::pool::Pool::new().as_mut_ptr(), ) }; Error::from_raw(err)?; Ok(crate::Checksum(unsafe { apr::pool::PooledPtr::in_pool(std::rc::Rc::new(apr::pool::Pool::new()), checksum) })) } pub fn read_full(&mut self, buf: &mut [u8]) -> Result { let mut len = buf.len(); let err = unsafe { crate::generated::svn_stream_read_full( self.0.as_mut_ptr(), buf.as_mut_ptr() as *mut i8, &mut len, ) }; Error::from_raw(err)?; Ok(len) } pub fn supports_partial_read(&mut self) -> bool { unsafe { crate::generated::svn_stream_supports_partial_read(self.0.as_mut_ptr()) != 0 } } pub fn skip(&mut self, len: usize) -> Result<(), Error> { let err = unsafe { crate::generated::svn_stream_skip(self.0.as_mut_ptr(), len) }; Error::from_raw(err)?; Ok(()) } pub fn read(&mut self, buf: &mut [u8]) -> Result { let mut len = buf.len(); let err = unsafe { crate::generated::svn_stream_read2( self.0.as_mut_ptr(), buf.as_mut_ptr() as *mut i8, &mut len, ) }; Error::from_raw(err)?; Ok(len) } pub fn write(&mut self, buf: &[u8]) -> Result<(), Error> { let mut len = buf.len(); let err = unsafe { crate::generated::svn_stream_write( self.0.as_mut_ptr(), buf.as_ptr() as *const i8, &mut len, ) }; Error::from_raw(err)?; Ok(()) } pub fn close(&mut self) -> Result<(), Error> { let err = unsafe { crate::generated::svn_stream_close(self.0.as_mut_ptr()) }; Error::from_raw(err)?; Ok(()) } pub fn supports_mark(&mut self) -> bool { unsafe { crate::generated::svn_stream_supports_mark(self.0.as_mut_ptr()) != 0 } } pub fn mark(&mut self) -> Result { let mut mark = std::ptr::null_mut(); let pool = apr::pool::Pool::new(); let err = unsafe { crate::generated::svn_stream_mark(self.0.as_mut_ptr(), &mut mark, pool.as_mut_ptr()) }; Error::from_raw(err)?; Ok(Mark(unsafe { apr::pool::PooledPtr::in_pool(std::rc::Rc::new(pool), mark) })) } pub fn seek(&mut self, mark: &Mark) -> Result<(), Error> { let err = unsafe { crate::generated::svn_stream_seek(self.0.as_mut_ptr(), mark.as_ptr()) }; Error::from_raw(err)?; Ok(()) } pub fn supports_reset(&mut self) -> bool { unsafe { crate::generated::svn_stream_supports_reset(self.0.as_mut_ptr()) != 0 } } pub fn reset(&mut self) -> Result<(), Error> { let err = unsafe { crate::generated::svn_stream_reset(self.0.as_mut_ptr()) }; Error::from_raw(err)?; Ok(()) } pub fn data_available(&mut self) -> Result { let mut data_available = 0; let err = unsafe { crate::generated::svn_stream_data_available(self.0.as_mut_ptr(), &mut data_available) }; Error::from_raw(err)?; Ok(data_available != 0) } pub fn puts(&mut self, s: &str) -> Result<(), Error> { let s = std::ffi::CString::new(s).unwrap(); let err = unsafe { crate::generated::svn_stream_puts(self.0.as_mut_ptr(), s.as_ptr()) }; Error::from_raw(err)?; Ok(()) } pub fn readline(&mut self, eol: &str) -> Result { let eol = std::ffi::CString::new(eol).unwrap(); let mut line = std::ptr::null_mut(); let pool = apr::pool::Pool::new(); let mut eof = 0; let err = unsafe { crate::generated::svn_stream_readline( self.0.as_mut_ptr(), &mut line, eol.as_ptr(), &mut eof, pool.as_mut_ptr(), ) }; Error::from_raw(err)?; let data = (unsafe { (*line).data }) as *const i8; let len = unsafe { (*line).len }; Ok(unsafe { std::str::from_utf8(std::slice::from_raw_parts(data as *const u8, len)) .unwrap() .to_string() }) } } impl std::io::Write for Stream { fn write(&mut self, buf: &[u8]) -> std::io::Result { let mut len = 0; let err = unsafe { crate::generated::svn_stream_write( self.0.as_mut_ptr(), buf.as_ptr() as *const i8, &mut len, ) }; Error::from_raw(err)?; Ok(len) } fn flush(&mut self) -> std::io::Result<()> { Ok(()) } } impl std::io::Read for Stream { fn read(&mut self, buf: &mut [u8]) -> std::io::Result { let mut len = 0; let err = unsafe { crate::generated::svn_stream_read( self.0.as_mut_ptr(), buf.as_mut_ptr() as *mut i8, &mut len, ) }; Error::from_raw(err)?; Ok(len) } } pub fn tee(out1: &mut Stream, out2: &mut Stream) -> Result { Ok(Stream(apr::pool::PooledPtr::initialize(|pool| { let stream = unsafe { crate::generated::svn_stream_tee( out1.0.as_mut_ptr(), out2.0.as_mut_ptr(), pool.as_mut_ptr(), ) }; Ok::<_, crate::Error>(stream) })?)) } impl From<&[u8]> for Stream { fn from(bytes: &[u8]) -> Self { Self( apr::pool::PooledPtr::initialize(|pool| { let buf = crate::generated::svn_string_t { data: bytes.as_ptr() as *mut i8, len: bytes.len(), }; let stream = unsafe { crate::generated::svn_stream_from_string(&buf, pool.as_mut_ptr()) }; Ok::<_, crate::Error>(stream) }) .unwrap(), ) } } pub fn remove_file(path: &std::path::Path, ignore_enoent: bool) -> Result<(), Error> { let path = std::ffi::CString::new(path.to_str().unwrap()).unwrap(); let err = unsafe { crate::generated::svn_io_remove_file2( path.as_ptr(), ignore_enoent as i32, apr::pool::Pool::new().as_mut_ptr(), ) }; Error::from_raw(err)?; Ok(()) } pub fn wrap_write(write: &mut dyn std::io::Write) -> Result { let write = Box::into_raw(Box::new(write)); let mut stream = apr::pool::PooledPtr::initialize(|pool| { let stream = unsafe { crate::generated::svn_stream_create(write as *mut std::ffi::c_void, pool.as_mut_ptr()) }; Ok::<_, Error>(stream) })?; extern "C" fn write_fn( baton: *mut std::ffi::c_void, buffer: *const i8, len: *mut usize, ) -> *mut crate::generated::svn_error_t { let write = unsafe { Box::from_raw(baton as *mut &mut dyn std::io::Write) }; let write = Box::leak(write); let buffer = unsafe { std::slice::from_raw_parts(buffer as *const u8, *len) }; match write.write(buffer) { Ok(_) => std::ptr::null_mut(), Err(e) => { let mut e: crate::Error = e.into(); unsafe { e.detach() } } } } extern "C" fn close_fn(baton: *mut std::ffi::c_void) -> *mut crate::generated::svn_error_t { let write = unsafe { Box::from_raw(baton as *mut &mut dyn std::io::Write) }; match write.flush() { Ok(_) => std::ptr::null_mut(), Err(e) => { let mut e: crate::Error = e.into(); unsafe { e.detach() } } } } unsafe { crate::generated::svn_stream_set_write(stream.as_mut_ptr(), Some(write_fn)); crate::generated::svn_stream_set_close(stream.as_mut_ptr(), Some(close_fn)); } Ok(Stream(stream)) } pub fn create_uniqe_link( path: &std::path::Path, dest: &std::path::Path, suffix: &OsStr, ) -> Result { let path = std::ffi::CString::new(path.to_str().unwrap()).unwrap(); let dest = std::ffi::CString::new(dest.to_str().unwrap()).unwrap(); let suffix = std::ffi::CString::new(suffix.to_str().unwrap()).unwrap(); let pool = apr::pool::Pool::new(); let mut result = std::ptr::null(); let err = unsafe { crate::generated::svn_io_create_unique_link( &mut result, path.as_ptr(), dest.as_ptr(), suffix.as_ptr(), pool.as_mut_ptr(), ) }; Error::from_raw(err)?; Ok(std::path::PathBuf::from(unsafe { std::ffi::CStr::from_ptr(result).to_str().unwrap() })) } pub fn read_link(path: &std::path::Path) -> Result { let path = std::ffi::CString::new(path.to_str().unwrap()).unwrap(); let mut target = std::ptr::null_mut(); let err = unsafe { crate::generated::svn_io_read_link( &mut target, path.as_ptr(), apr::pool::Pool::new().as_mut_ptr(), ) }; Error::from_raw(err)?; let ptr = unsafe { (*target).data as *const u8 }; let len = unsafe { (*target).len }; let target = unsafe { std::slice::from_raw_parts(ptr, len) }; Ok(std::path::PathBuf::from( std::str::from_utf8(target).unwrap(), )) } pub fn temp_dir() -> Result { let pool = apr::pool::Pool::new(); let mut path = std::ptr::null(); let err = unsafe { crate::generated::svn_io_temp_dir(&mut path, pool.as_mut_ptr()) }; Error::from_raw(err)?; Ok(std::path::PathBuf::from(unsafe { std::ffi::CStr::from_ptr(path).to_str().unwrap() })) } pub fn copy_file( src: &std::path::Path, dest: &std::path::Path, copy_perms: bool, ) -> Result<(), Error> { let src = std::ffi::CString::new(src.to_str().unwrap()).unwrap(); let dest = std::ffi::CString::new(dest.to_str().unwrap()).unwrap(); let err = unsafe { crate::generated::svn_io_copy_file( src.as_ptr(), dest.as_ptr(), copy_perms as i32, apr::pool::Pool::new().as_mut_ptr(), ) }; Error::from_raw(err)?; Ok(()) } pub fn copy_perms(src: &std::path::Path, dest: &std::path::Path) -> Result<(), Error> { let src = std::ffi::CString::new(src.to_str().unwrap()).unwrap(); let dest = std::ffi::CString::new(dest.to_str().unwrap()).unwrap(); let err = unsafe { crate::generated::svn_io_copy_perms( src.as_ptr(), dest.as_ptr(), apr::pool::Pool::new().as_mut_ptr(), ) }; Error::from_raw(err)?; Ok(()) } pub fn copy_link(src: &std::path::Path, dest: &std::path::Path) -> Result<(), Error> { let src = std::ffi::CString::new(src.to_str().unwrap()).unwrap(); let dest = std::ffi::CString::new(dest.to_str().unwrap()).unwrap(); let err = unsafe { crate::generated::svn_io_copy_link( src.as_ptr(), dest.as_ptr(), apr::pool::Pool::new().as_mut_ptr(), ) }; Error::from_raw(err)?; Ok(()) } pub fn copy_dir_recursively( src: &std::path::Path, dst_path: &std::path::Path, dst_basename: &std::ffi::OsStr, copy_perms: bool, cancel_func: Option<&impl Fn() -> Result<(), Error>>, ) -> Result<(), Error> { use std::os::unix::ffi::OsStrExt; let src = std::ffi::CString::new(src.to_str().unwrap()).unwrap(); let dst_path = std::ffi::CString::new(dst_path.to_str().unwrap()).unwrap(); let dst_basename = std::ffi::CString::new(dst_basename.as_bytes()).unwrap(); let err = unsafe { crate::generated::svn_io_copy_dir_recursively( src.as_ptr(), dst_path.as_ptr(), dst_basename.as_ptr(), copy_perms as i32, if cancel_func.is_some() { Some(crate::wrap_cancel_func) } else { None }, if let Some(cancel_func) = cancel_func { &cancel_func as *const _ as *mut std::ffi::c_void } else { std::ptr::null_mut() }, apr::pool::Pool::new().as_mut_ptr(), ) }; Error::from_raw(err)?; Ok(()) } pub fn make_dir_recursively(path: &std::path::Path) -> Result<(), Error> { let path = std::ffi::CString::new(path.to_str().unwrap()).unwrap(); let err = unsafe { crate::generated::svn_io_make_dir_recursively( path.as_ptr(), apr::pool::Pool::new().as_mut_ptr(), ) }; Error::from_raw(err)?; Ok(()) } pub fn dir_empty(path: &std::path::Path) -> Result { let path = std::ffi::CString::new(path.to_str().unwrap()).unwrap(); let mut empty = 0; let err = unsafe { crate::generated::svn_io_dir_empty( &mut empty, path.as_ptr(), apr::pool::Pool::new().as_mut_ptr(), ) }; Error::from_raw(err)?; Ok(empty != 0) } pub fn append_file(src: &std::path::Path, dest: &std::path::Path) -> Result<(), Error> { let src = std::ffi::CString::new(src.to_str().unwrap()).unwrap(); let dest = std::ffi::CString::new(dest.to_str().unwrap()).unwrap(); let err = unsafe { crate::generated::svn_io_append_file( src.as_ptr(), dest.as_ptr(), apr::pool::Pool::new().as_mut_ptr(), ) }; Error::from_raw(err)?; Ok(()) } pub fn set_file_read_only(path: &std::path::Path, ignore_enoent: bool) -> Result<(), Error> { let path = std::ffi::CString::new(path.to_str().unwrap()).unwrap(); let err = unsafe { crate::generated::svn_io_set_file_read_only( path.as_ptr(), ignore_enoent as i32, apr::pool::Pool::new().as_mut_ptr(), ) }; Error::from_raw(err)?; Ok(()) } pub fn set_file_read_write(path: &std::path::Path, ignore_enoent: bool) -> Result<(), Error> { let path = std::ffi::CString::new(path.to_str().unwrap()).unwrap(); let err = unsafe { crate::generated::svn_io_set_file_read_write( path.as_ptr(), ignore_enoent as i32, apr::pool::Pool::new().as_mut_ptr(), ) }; Error::from_raw(err)?; Ok(()) } pub fn is_file_executable(path: &std::path::Path) -> Result { let path = std::ffi::CString::new(path.to_str().unwrap()).unwrap(); let mut executable = 0; let err = unsafe { crate::generated::svn_io_is_file_executable( &mut executable, path.as_ptr(), apr::pool::Pool::new().as_mut_ptr(), ) }; Error::from_raw(err)?; Ok(executable != 0) } pub fn file_affected_time(path: &std::path::Path) -> Result { let path = std::ffi::CString::new(path.to_str().unwrap()).unwrap(); let mut affected_time = 0; let err = unsafe { crate::generated::svn_io_file_affected_time( &mut affected_time, path.as_ptr(), apr::pool::Pool::new().as_mut_ptr(), ) }; Error::from_raw(err)?; Ok(apr::time::Time::from(affected_time).into()) } pub fn set_file_affected_time( path: &std::path::Path, affected_time: apr::time::Time, ) -> Result<(), Error> { let path = std::ffi::CString::new(path.to_str().unwrap()).unwrap(); let affected_time = affected_time.into(); let err = unsafe { crate::generated::svn_io_set_file_affected_time( affected_time, path.as_ptr(), apr::pool::Pool::new().as_mut_ptr(), ) }; Error::from_raw(err)?; Ok(()) } pub fn sleep_for_timestamps(path: &std::path::Path) { let path = std::ffi::CString::new(path.to_str().unwrap()).unwrap(); unsafe { crate::generated::svn_io_sleep_for_timestamps( path.as_ptr(), apr::pool::Pool::new().as_mut_ptr(), ) } } pub fn filesizes_different_p( file1: &std::path::Path, file2: &std::path::Path, ) -> Result { let file1 = std::ffi::CString::new(file1.to_str().unwrap()).unwrap(); let file2 = std::ffi::CString::new(file2.to_str().unwrap()).unwrap(); let mut different = 0; let err = unsafe { crate::generated::svn_io_filesizes_different_p( &mut different, file1.as_ptr(), file2.as_ptr(), apr::pool::Pool::new().as_mut_ptr(), ) }; Error::from_raw(err)?; Ok(different != 0) } pub fn filesizes_three_different_p( file1: &std::path::Path, file2: &std::path::Path, file3: &std::path::Path, ) -> Result<(bool, bool, bool), Error> { let file1 = std::ffi::CString::new(file1.to_str().unwrap()).unwrap(); let file2 = std::ffi::CString::new(file2.to_str().unwrap()).unwrap(); let file3 = std::ffi::CString::new(file3.to_str().unwrap()).unwrap(); let mut different1 = 0; let mut different2 = 0; let mut different3 = 0; let err = unsafe { crate::generated::svn_io_filesizes_three_different_p( &mut different1, &mut different2, &mut different3, file1.as_ptr(), file2.as_ptr(), file3.as_ptr(), apr::pool::Pool::new().as_mut_ptr(), ) }; Error::from_raw(err)?; Ok((different1 != 0, different2 != 0, different3 != 0)) } pub fn file_checksum( file: &std::path::Path, checksum_kind: crate::ChecksumKind, ) -> Result { let file = std::ffi::CString::new(file.to_str().unwrap()).unwrap(); let mut checksum = std::ptr::null_mut(); let pool = apr::pool::Pool::new(); let err = unsafe { crate::generated::svn_io_file_checksum2( &mut checksum, file.as_ptr(), checksum_kind.into(), pool.as_mut_ptr(), ) }; Error::from_raw(err)?; Ok(crate::Checksum(unsafe { apr::pool::PooledPtr::in_pool(std::rc::Rc::new(pool), checksum) })) } pub fn files_contents_same_p( file1: &std::path::Path, file2: &std::path::Path, ) -> Result { let file1 = std::ffi::CString::new(file1.to_str().unwrap()).unwrap(); let file2 = std::ffi::CString::new(file2.to_str().unwrap()).unwrap(); let mut same = 0; let err = unsafe { crate::generated::svn_io_files_contents_same_p( &mut same, file1.as_ptr(), file2.as_ptr(), apr::pool::Pool::new().as_mut_ptr(), ) }; Error::from_raw(err)?; Ok(same != 0) } pub fn files_contents_three_same_p( file1: &std::path::Path, file2: &std::path::Path, file3: &std::path::Path, ) -> Result<(bool, bool, bool), Error> { let file1 = std::ffi::CString::new(file1.to_str().unwrap()).unwrap(); let file2 = std::ffi::CString::new(file2.to_str().unwrap()).unwrap(); let file3 = std::ffi::CString::new(file3.to_str().unwrap()).unwrap(); let mut same1 = 0; let mut same2 = 0; let mut same3 = 0; let err = unsafe { crate::generated::svn_io_files_contents_three_same_p( &mut same1, &mut same2, &mut same3, file1.as_ptr(), file2.as_ptr(), file3.as_ptr(), apr::pool::Pool::new().as_mut_ptr(), ) }; Error::from_raw(err)?; Ok((same1 != 0, same2 != 0, same3 != 0)) } pub fn file_create(path: &std::path::Path, contents: &str) -> Result<(), Error> { let path = std::ffi::CString::new(path.to_str().unwrap()).unwrap(); let contents = std::ffi::CString::new(contents).unwrap(); let err = unsafe { crate::generated::svn_io_file_create( path.as_ptr(), contents.as_ptr(), apr::pool::Pool::new().as_mut_ptr(), ) }; Error::from_raw(err)?; Ok(()) } pub fn file_create_bytes(path: &std::path::Path, contents: &[u8]) -> Result<(), Error> { let path = std::ffi::CString::new(path.to_str().unwrap()).unwrap(); let err = unsafe { crate::generated::svn_io_file_create_bytes( path.as_ptr(), contents.as_ptr() as *const std::ffi::c_void, contents.len(), apr::pool::Pool::new().as_mut_ptr(), ) }; Error::from_raw(err)?; Ok(()) } pub fn file_create_empty(path: &std::path::Path) -> Result<(), Error> { let path = std::ffi::CString::new(path.to_str().unwrap()).unwrap(); let err = unsafe { crate::generated::svn_io_file_create_empty( path.as_ptr(), apr::pool::Pool::new().as_mut_ptr(), ) }; Error::from_raw(err)?; Ok(()) } pub fn file_lock(path: &std::path::Path, exclusive: bool, nonblocking: bool) -> Result<(), Error> { let path = std::ffi::CString::new(path.to_str().unwrap()).unwrap(); let err = unsafe { crate::generated::svn_io_file_lock2( path.as_ptr(), exclusive as i32, nonblocking as i32, apr::pool::Pool::new().as_mut_ptr(), ) }; Error::from_raw(err)?; Ok(()) } pub fn dir_file_copy( src_path: &std::path::Path, dest_path: &std::path::Path, file: &std::ffi::OsStr, ) -> Result<(), Error> { use std::os::unix::ffi::OsStrExt; let src_path = std::ffi::CString::new(src_path.to_str().unwrap()).unwrap(); let dest_path = std::ffi::CString::new(dest_path.to_str().unwrap()).unwrap(); let file = std::ffi::CString::new(file.as_bytes()).unwrap(); let err = unsafe { crate::generated::svn_io_dir_file_copy( src_path.as_ptr(), dest_path.as_ptr(), file.as_ptr(), apr::pool::Pool::new().as_mut_ptr(), ) }; Error::from_raw(err)?; Ok(()) } pub fn stream_copy( from: &mut Stream, to: &mut Stream, cancel_func: Option<&impl Fn() -> Result<(), Error>>, ) -> Result<(), Error> { let err = unsafe { crate::generated::svn_stream_copy3( from.0.as_mut_ptr(), to.0.as_mut_ptr(), if cancel_func.is_some() { Some(crate::wrap_cancel_func) } else { None }, if let Some(cancel_func) = cancel_func { &cancel_func as *const _ as *mut std::ffi::c_void } else { std::ptr::null_mut() }, apr::pool::Pool::new().as_mut_ptr(), ) }; Error::from_raw(err)?; Ok(()) } pub fn stream_contents_same(stream1: &mut Stream, stream2: &mut Stream) -> Result { let mut same = 0; let err = unsafe { crate::generated::svn_stream_contents_same( &mut same, stream1.0.as_mut_ptr(), stream2.0.as_mut_ptr(), apr::pool::Pool::new().as_mut_ptr(), ) }; Error::from_raw(err)?; Ok(same != 0) } pub fn string_from_stream(stream: &mut Stream) -> Result { let mut str = std::ptr::null_mut(); let pool = apr::pool::Pool::new(); let err = unsafe { crate::generated::svn_string_from_stream( &mut str, stream.0.as_mut_ptr(), pool.as_mut_ptr(), apr::pool::Pool::new().as_mut_ptr(), ) }; Error::from_raw(err)?; let data = (unsafe { (*str).data }) as *const i8; let len = unsafe { (*str).len }; Ok(unsafe { std::str::from_utf8(std::slice::from_raw_parts(data as *const u8, len)) .unwrap() .to_string() }) } pub fn remove_dir( path: &std::path::Path, ignore_enoent: bool, cancel_func: Option<&impl Fn() -> Result<(), Error>>, ) -> Result<(), Error> { let path = std::ffi::CString::new(path.to_str().unwrap()).unwrap(); let err = unsafe { crate::generated::svn_io_remove_dir2( path.as_ptr(), ignore_enoent as i32, if cancel_func.is_some() { Some(crate::wrap_cancel_func) } else { None }, if let Some(cancel_func) = cancel_func { &cancel_func as *const _ as *mut std::ffi::c_void } else { std::ptr::null_mut() }, apr::pool::Pool::new().as_mut_ptr(), ) }; Error::from_raw(err)?; Ok(()) } subversion-0.0.8/src/lib.rs000064400000000000000000000642341046102023000137440ustar 00000000000000pub mod auth; #[cfg(feature = "client")] pub mod client; pub mod config; #[cfg(feature = "delta")] pub mod delta; pub mod dirent; pub mod error; pub mod fs; mod generated; pub mod io; pub mod mergeinfo; pub mod props; #[cfg(feature = "ra")] pub mod ra; pub mod repos; pub mod string; pub mod time; pub mod uri; pub mod version; #[cfg(feature = "wc")] pub mod wc; use crate::generated::{svn_opt_revision_t, svn_opt_revision_value_t}; use apr::pool::PooledPtr; use std::str::FromStr; pub use version::Version; #[derive(Debug, Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord, std::hash::Hash)] pub struct Revnum(generated::svn_revnum_t); impl From for generated::svn_revnum_t { fn from(revnum: Revnum) -> Self { revnum.0 } } impl From for Revnum { fn from(revnum: usize) -> Self { Self(revnum as _) } } impl From for Revnum { fn from(revnum: u32) -> Self { Self(revnum as _) } } impl From for Revnum { fn from(revnum: u64) -> Self { Self(revnum as _) } } impl From for usize { fn from(revnum: Revnum) -> Self { revnum.0 as _ } } impl From for u32 { fn from(revnum: Revnum) -> Self { revnum.0 as _ } } impl From for u64 { fn from(revnum: Revnum) -> Self { revnum.0 as _ } } impl apr::hash::IntoHashKey<'_> for &Revnum { fn into_hash_key(self) -> &'static [u8] { unsafe { std::slice::from_raw_parts( &self.0 as *const _ as *const u8, std::mem::size_of::(), ) } } } impl Revnum { fn from_raw(raw: generated::svn_revnum_t) -> Option { if raw < 0 { None } else { Some(Self(raw)) } } } pub use error::Error; #[derive(Debug, Default, Clone, Copy)] pub enum Revision { #[default] Unspecified, Number(Revnum), Date(i64), Committed, Previous, Base, Working, Head, } impl FromStr for Revision { type Err = String; fn from_str(rev: &str) -> Result { if rev == "unspecified" { Ok(Revision::Unspecified) } else if rev == "committed" { Ok(Revision::Committed) } else if rev == "previous" { Ok(Revision::Previous) } else if rev == "base" { Ok(Revision::Base) } else if rev == "working" { Ok(Revision::Working) } else if rev == "head" { Ok(Revision::Head) } else if let Some(rest) = rev.strip_prefix("number:") { Ok(Revision::Number(Revnum( rest.parse::() .map_err(|e| e.to_string())?, ))) } else if let Some(rest) = rev.strip_prefix("date:") { Ok(Revision::Date( rest.parse::().map_err(|e| e.to_string())?, )) } else { Err(format!("Invalid revision: {}", rev)) } } } #[cfg(feature = "pyo3")] impl pyo3::FromPyObject<'_> for Revision { fn extract_bound(ob: &pyo3::Bound) -> pyo3::PyResult { use pyo3::prelude::*; if ob.is_instance_of::() { let rev = ob.extract::()?; return Revision::from_str(&rev).map_err(|e| { pyo3::exceptions::PyValueError::new_err(format!("Invalid revision: {}", e)) }); } else if ob.is_instance_of::() { let rev = ob.extract::()?; return Ok(Revision::Number(Revnum::from_raw(rev).unwrap())); } else { Err(pyo3::exceptions::PyTypeError::new_err(format!( "Invalid revision: {:?}", ob ))) } } } impl From for Revision { fn from(revnum: Revnum) -> Self { Revision::Number(revnum) } } impl From for svn_opt_revision_t { fn from(revision: Revision) -> Self { match revision { Revision::Unspecified => svn_opt_revision_t { kind: generated::svn_opt_revision_kind_svn_opt_revision_unspecified, value: svn_opt_revision_value_t::default(), }, Revision::Number(revnum) => { let mut uf = crate::generated::__BindgenUnionField::::new(); unsafe { *uf.as_mut() = revnum.0; } svn_opt_revision_t { kind: generated::svn_opt_revision_kind_svn_opt_revision_number, value: svn_opt_revision_value_t { number: uf, ..Default::default() }, } } Revision::Date(date) => { let mut uf = crate::generated::__BindgenUnionField::::new(); unsafe { *uf.as_mut() = date; } svn_opt_revision_t { kind: generated::svn_opt_revision_kind_svn_opt_revision_date, value: svn_opt_revision_value_t { date: uf, ..Default::default() }, } } Revision::Committed => svn_opt_revision_t { kind: generated::svn_opt_revision_kind_svn_opt_revision_committed, value: svn_opt_revision_value_t::default(), }, Revision::Previous => svn_opt_revision_t { kind: generated::svn_opt_revision_kind_svn_opt_revision_previous, value: svn_opt_revision_value_t::default(), }, Revision::Base => svn_opt_revision_t { kind: generated::svn_opt_revision_kind_svn_opt_revision_base, value: svn_opt_revision_value_t::default(), }, Revision::Working => svn_opt_revision_t { kind: generated::svn_opt_revision_kind_svn_opt_revision_working, value: svn_opt_revision_value_t::default(), }, Revision::Head => svn_opt_revision_t { kind: generated::svn_opt_revision_kind_svn_opt_revision_head, value: svn_opt_revision_value_t::default(), }, } } } #[derive(Debug, Clone, Copy, Default)] pub enum Depth { #[default] Unknown, Exclude, Empty, Files, Immediates, Infinity, } impl From for generated::svn_depth_t { fn from(depth: Depth) -> Self { match depth { Depth::Unknown => generated::svn_depth_t_svn_depth_unknown, Depth::Exclude => generated::svn_depth_t_svn_depth_exclude, Depth::Empty => generated::svn_depth_t_svn_depth_empty, Depth::Files => generated::svn_depth_t_svn_depth_files, Depth::Immediates => generated::svn_depth_t_svn_depth_immediates, Depth::Infinity => generated::svn_depth_t_svn_depth_infinity, } } } impl From for Depth { fn from(depth: generated::svn_depth_t) -> Self { match depth { generated::svn_depth_t_svn_depth_unknown => Depth::Unknown, generated::svn_depth_t_svn_depth_exclude => Depth::Exclude, generated::svn_depth_t_svn_depth_empty => Depth::Empty, generated::svn_depth_t_svn_depth_files => Depth::Files, generated::svn_depth_t_svn_depth_immediates => Depth::Immediates, generated::svn_depth_t_svn_depth_infinity => Depth::Infinity, n => panic!("Unknown depth: {:?}", n), } } } impl std::str::FromStr for Depth { type Err = String; fn from_str(depth: &str) -> Result { match depth { "unknown" => Ok(Depth::Unknown), "exclude" => Ok(Depth::Exclude), "empty" => Ok(Depth::Empty), "files" => Ok(Depth::Files), "immediates" => Ok(Depth::Immediates), "infinity" => Ok(Depth::Infinity), _ => Err(format!("Invalid depth: {}", depth)), } } } #[cfg(feature = "pyo3")] impl pyo3::FromPyObject<'_> for Depth { fn extract_bound(ob: &pyo3::Bound) -> pyo3::PyResult { use pyo3::prelude::*; if ob.is_instance_of::() { let depth = ob.extract::()?; return Depth::from_str(&depth).map_err(|e| { pyo3::exceptions::PyValueError::new_err(format!("Invalid depth: {}", e)) }); } else if ob.is_instance_of::() { let depth = ob.extract::()?; return Ok(if depth { Depth::Infinity } else { Depth::Empty }); } else { Err(pyo3::exceptions::PyTypeError::new_err(format!( "Invalid depth: {:?}", ob ))) } } } pub struct CommitInfo(PooledPtr); unsafe impl Send for CommitInfo {} impl CommitInfo { pub fn revision(&self) -> Revnum { Revnum::from_raw(self.0.revision).unwrap() } pub fn date(&self) -> &str { unsafe { let date = self.0.date; std::ffi::CStr::from_ptr(date).to_str().unwrap() } } pub fn author(&self) -> &str { unsafe { let author = self.0.author; std::ffi::CStr::from_ptr(author).to_str().unwrap() } } pub fn post_commit_err(&self) -> Option<&str> { unsafe { let err = self.0.post_commit_err; if err.is_null() { None } else { Some(std::ffi::CStr::from_ptr(err).to_str().unwrap()) } } } pub fn repos_root(&self) -> &str { unsafe { let repos_root = self.0.repos_root; std::ffi::CStr::from_ptr(repos_root).to_str().unwrap() } } } impl Clone for CommitInfo { fn clone(&self) -> Self { unsafe { Self( PooledPtr::initialize(|pool| { Ok::<_, Error>(crate::generated::svn_commit_info_dup( self.0.as_ptr(), pool.as_mut_ptr(), )) }) .unwrap(), ) } } } #[derive(Debug, Clone, Copy, Default)] pub struct RevisionRange { pub start: Revision, pub end: Revision, } impl From<&RevisionRange> for generated::svn_opt_revision_range_t { fn from(range: &RevisionRange) -> Self { Self { start: range.start.into(), end: range.end.into(), } } } impl RevisionRange { pub unsafe fn to_c(&self, pool: &mut apr::Pool) -> *mut generated::svn_opt_revision_range_t { let range = pool.calloc::(); *range = self.into(); range } } pub struct LogEntry(apr::pool::PooledPtr); unsafe impl Send for LogEntry {} impl Clone for LogEntry { fn clone(&self) -> Self { Self( apr::pool::PooledPtr::initialize(|pool| { Ok::<_, Error>(unsafe { crate::generated::svn_log_entry_dup(self.0.as_ptr(), pool.as_mut_ptr()) }) }) .unwrap(), ) } } pub type FileSize = crate::generated::svn_filesize_t; #[derive(Debug, Clone, Copy, Default)] pub enum NativeEOL { #[default] Standard, LF, CRLF, CR, } impl TryFrom> for NativeEOL { type Error = crate::Error; fn try_from(eol: Option<&str>) -> Result { match eol { None => Ok(NativeEOL::Standard), Some("LF") => Ok(NativeEOL::LF), Some("CRLF") => Ok(NativeEOL::CRLF), Some("CR") => Ok(NativeEOL::CR), _ => Err(crate::Error::new( crate::generated::svn_errno_t_SVN_ERR_IO_UNKNOWN_EOL.into(), None, "Unknown eol marker", )), } } } impl From for Option<&str> { fn from(eol: NativeEOL) -> Self { match eol { NativeEOL::Standard => None, NativeEOL::LF => Some("LF"), NativeEOL::CRLF => Some("CRLF"), NativeEOL::CR => Some("CR"), } } } pub struct InheritedItem(apr::pool::PooledPtr); impl InheritedItem { pub fn from_raw(ptr: apr::pool::PooledPtr) -> Self { Self(ptr) } pub fn path_or_url(&self) -> &str { unsafe { let path_or_url = self.0.path_or_url; std::ffi::CStr::from_ptr(path_or_url).to_str().unwrap() } } } pub struct Canonical(T); impl std::ops::Deref for Canonical { type Target = T; fn deref(&self) -> &Self::Target { &self.0 } } impl std::fmt::Debug for Canonical { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_tuple("Canonical").field(&self.to_string()).finish() } } impl PartialEq for Canonical where T: PartialEq, { fn eq(&self, other: &Self) -> bool { self.0.eq(&other.0) } } impl Eq for Canonical where T: Eq {} #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum NodeKind { None, File, Dir, Unknown, Symlink, } impl From for NodeKind { fn from(kind: generated::svn_node_kind_t) -> Self { match kind { generated::svn_node_kind_t_svn_node_none => NodeKind::None, generated::svn_node_kind_t_svn_node_file => NodeKind::File, generated::svn_node_kind_t_svn_node_dir => NodeKind::Dir, generated::svn_node_kind_t_svn_node_unknown => NodeKind::Unknown, generated::svn_node_kind_t_svn_node_symlink => NodeKind::Symlink, n => panic!("Unknown node kind: {:?}", n), } } } impl From for generated::svn_node_kind_t { fn from(kind: NodeKind) -> Self { match kind { NodeKind::None => generated::svn_node_kind_t_svn_node_none, NodeKind::File => generated::svn_node_kind_t_svn_node_file, NodeKind::Dir => generated::svn_node_kind_t_svn_node_dir, NodeKind::Unknown => generated::svn_node_kind_t_svn_node_unknown, NodeKind::Symlink => generated::svn_node_kind_t_svn_node_symlink, } } } #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum StatusKind { None, Unversioned, Normal, Added, Missing, Deleted, Replaced, Modified, Merged, Conflicted, Ignored, Obstructed, External, Incomplete, } impl From for StatusKind { fn from(kind: generated::svn_wc_status_kind) -> Self { match kind { generated::svn_wc_status_kind_svn_wc_status_none => StatusKind::None, generated::svn_wc_status_kind_svn_wc_status_unversioned => StatusKind::Unversioned, generated::svn_wc_status_kind_svn_wc_status_normal => StatusKind::Normal, generated::svn_wc_status_kind_svn_wc_status_added => StatusKind::Added, generated::svn_wc_status_kind_svn_wc_status_missing => StatusKind::Missing, generated::svn_wc_status_kind_svn_wc_status_deleted => StatusKind::Deleted, generated::svn_wc_status_kind_svn_wc_status_replaced => StatusKind::Replaced, generated::svn_wc_status_kind_svn_wc_status_modified => StatusKind::Modified, generated::svn_wc_status_kind_svn_wc_status_merged => StatusKind::Merged, generated::svn_wc_status_kind_svn_wc_status_conflicted => StatusKind::Conflicted, generated::svn_wc_status_kind_svn_wc_status_ignored => StatusKind::Ignored, generated::svn_wc_status_kind_svn_wc_status_obstructed => StatusKind::Obstructed, generated::svn_wc_status_kind_svn_wc_status_external => StatusKind::External, generated::svn_wc_status_kind_svn_wc_status_incomplete => StatusKind::Incomplete, n => panic!("Unknown status kind: {:?}", n), } } } impl From for generated::svn_wc_status_kind { fn from(kind: StatusKind) -> Self { match kind { StatusKind::None => generated::svn_wc_status_kind_svn_wc_status_none, StatusKind::Unversioned => generated::svn_wc_status_kind_svn_wc_status_unversioned, StatusKind::Normal => generated::svn_wc_status_kind_svn_wc_status_normal, StatusKind::Added => generated::svn_wc_status_kind_svn_wc_status_added, StatusKind::Missing => generated::svn_wc_status_kind_svn_wc_status_missing, StatusKind::Deleted => generated::svn_wc_status_kind_svn_wc_status_deleted, StatusKind::Replaced => generated::svn_wc_status_kind_svn_wc_status_replaced, StatusKind::Modified => generated::svn_wc_status_kind_svn_wc_status_modified, StatusKind::Merged => generated::svn_wc_status_kind_svn_wc_status_merged, StatusKind::Conflicted => generated::svn_wc_status_kind_svn_wc_status_conflicted, StatusKind::Ignored => generated::svn_wc_status_kind_svn_wc_status_ignored, StatusKind::Obstructed => generated::svn_wc_status_kind_svn_wc_status_obstructed, StatusKind::External => generated::svn_wc_status_kind_svn_wc_status_external, StatusKind::Incomplete => generated::svn_wc_status_kind_svn_wc_status_incomplete, } } } pub struct Lock(PooledPtr); unsafe impl Send for Lock {} impl Lock { pub fn path(&self) -> &str { unsafe { let path = (*self.0).path; std::ffi::CStr::from_ptr(path).to_str().unwrap() } } pub fn dup(&self) -> Self { Self( apr::pool::PooledPtr::initialize(|pool| { Ok::<_, Error>(unsafe { crate::generated::svn_lock_dup(self.0.as_ptr(), pool.as_mut_ptr()) }) }) .unwrap(), ) } pub fn token(&self) -> &str { unsafe { let token = (*self.0).token; std::ffi::CStr::from_ptr(token).to_str().unwrap() } } pub fn owner(&self) -> &str { unsafe { let owner = (*self.0).owner; std::ffi::CStr::from_ptr(owner).to_str().unwrap() } } pub fn comment(&self) -> &str { unsafe { let comment = (*self.0).comment; std::ffi::CStr::from_ptr(comment).to_str().unwrap() } } pub fn is_dav_comment(&self) -> bool { (*self.0).is_dav_comment == 1 } pub fn creation_date(&self) -> i64 { (*self.0).creation_date } pub fn expiration_date(&self) -> apr::time::Time { apr::time::Time::from((*self.0).expiration_date) } pub fn create() -> Self { Self( apr::pool::PooledPtr::initialize(|pool| { Ok::<_, Error>(unsafe { crate::generated::svn_lock_create(pool.as_mut_ptr()) }) }) .unwrap(), ) } } pub enum ChecksumKind { MD5, SHA1, Fnv1a32, Fnv1a32x4, } impl From for ChecksumKind { fn from(kind: crate::generated::svn_checksum_kind_t) -> Self { match kind { crate::generated::svn_checksum_kind_t_svn_checksum_md5 => ChecksumKind::MD5, crate::generated::svn_checksum_kind_t_svn_checksum_sha1 => ChecksumKind::SHA1, crate::generated::svn_checksum_kind_t_svn_checksum_fnv1a_32 => ChecksumKind::Fnv1a32, crate::generated::svn_checksum_kind_t_svn_checksum_fnv1a_32x4 => { ChecksumKind::Fnv1a32x4 } n => panic!("Unknown checksum kind: {:?}", n), } } } impl From for crate::generated::svn_checksum_kind_t { fn from(kind: ChecksumKind) -> Self { match kind { ChecksumKind::MD5 => crate::generated::svn_checksum_kind_t_svn_checksum_md5, ChecksumKind::SHA1 => crate::generated::svn_checksum_kind_t_svn_checksum_sha1, ChecksumKind::Fnv1a32 => crate::generated::svn_checksum_kind_t_svn_checksum_fnv1a_32, ChecksumKind::Fnv1a32x4 => { crate::generated::svn_checksum_kind_t_svn_checksum_fnv1a_32x4 } } } } pub struct LocationSegment(PooledPtr); unsafe impl Send for LocationSegment {} impl LocationSegment { pub fn dup(&self) -> Self { Self( apr::pool::PooledPtr::initialize(|pool| { Ok::<_, Error>(unsafe { crate::generated::svn_location_segment_dup(self.0.as_ptr(), pool.as_mut_ptr()) }) }) .unwrap(), ) } pub fn range(&self) -> std::ops::Range { Revnum::from_raw(self.0.range_end).unwrap()..Revnum::from_raw(self.0.range_start).unwrap() } pub fn path(&self) -> &str { unsafe { let path = self.0.path; std::ffi::CStr::from_ptr(path).to_str().unwrap() } } } #[cfg(any(feature = "ra", feature = "client"))] pub(crate) extern "C" fn wrap_commit_callback2( commit_info: *const crate::generated::svn_commit_info_t, baton: *mut std::ffi::c_void, pool: *mut apr::apr_pool_t, ) -> *mut crate::generated::svn_error_t { unsafe { let callback = baton as *mut &mut dyn FnMut(&crate::CommitInfo) -> Result<(), Error>; let mut callback = Box::from_raw(callback); match callback(&crate::CommitInfo(PooledPtr::in_pool( std::rc::Rc::new(apr::pool::Pool::from_raw(pool)), commit_info as *mut crate::generated::svn_commit_info_t, ))) { Ok(()) => std::ptr::null_mut(), Err(mut err) => err.as_mut_ptr(), } } } #[cfg(any(feature = "ra", feature = "client"))] pub(crate) extern "C" fn wrap_log_entry_receiver( baton: *mut std::ffi::c_void, log_entry: *mut crate::generated::svn_log_entry_t, pool: *mut apr::apr_pool_t, ) -> *mut crate::generated::svn_error_t { unsafe { let callback = baton as *mut &mut dyn FnMut(&LogEntry) -> Result<(), Error>; let mut callback = Box::from_raw(callback); let pool = apr::pool::Pool::from_raw(pool); let ret = callback(&LogEntry(apr::pool::PooledPtr::in_pool( std::rc::Rc::new(pool), log_entry, ))); if let Err(mut err) = ret { err.as_mut_ptr() } else { std::ptr::null_mut() } } } extern "C" fn wrap_cancel_func( cancel_baton: *mut std::ffi::c_void, ) -> *mut crate::generated::svn_error_t { let cancel_check = unsafe { &*(cancel_baton as *const Box Result<(), Error>>) }; match cancel_check() { Ok(()) => std::ptr::null_mut(), Err(e) => unsafe { e.into_raw() }, } } pub struct Checksum(PooledPtr); impl Checksum { pub fn kind(&self) -> ChecksumKind { ChecksumKind::from(self.0.kind) } pub fn size(&self) -> usize { unsafe { crate::generated::svn_checksum_size(self.0.as_ptr()) } } pub fn is_empty(&mut self) -> bool { unsafe { crate::generated::svn_checksum_is_empty_checksum(self.0.as_mut_ptr()) == 1 } } pub fn digest(&self) -> &[u8] { unsafe { let digest = self.0.digest; std::slice::from_raw_parts(digest, self.size() as usize) } } pub fn parse_hex(kind: ChecksumKind, hex: &str) -> Result { let mut checksum = std::ptr::null_mut(); let kind = kind.into(); let hex = std::ffi::CString::new(hex).unwrap(); let pool = apr::pool::Pool::new(); unsafe { Error::from_raw(crate::generated::svn_checksum_parse_hex( &mut checksum, kind, hex.as_ptr(), pool.as_mut_ptr(), ))?; Ok(Self(PooledPtr::in_pool(std::rc::Rc::new(pool), checksum))) } } pub fn empty(kind: ChecksumKind) -> Result { let kind = kind.into(); let pool = apr::pool::Pool::new(); unsafe { let checksum = crate::generated::svn_checksum_empty_checksum(kind, pool.as_mut_ptr()); Ok(Self(PooledPtr::in_pool(std::rc::Rc::new(pool), checksum))) } } } pub struct ChecksumContext(PooledPtr); impl ChecksumContext { pub fn new(kind: ChecksumKind) -> Result { let kind = kind.into(); let pool = apr::pool::Pool::new(); unsafe { let cc = crate::generated::svn_checksum_ctx_create(kind, pool.as_mut_ptr()); Ok(Self(PooledPtr::in_pool(std::rc::Rc::new(pool), cc))) } } pub fn reset(&mut self) -> Result<(), Error> { let err = unsafe { crate::generated::svn_checksum_ctx_reset(self.0.as_mut_ptr()) }; Error::from_raw(err)?; Ok(()) } pub fn update(&mut self, data: &[u8]) -> Result<(), Error> { let err = unsafe { crate::generated::svn_checksum_update( self.0.as_mut_ptr(), data.as_ptr() as *const std::ffi::c_void, data.len(), ) }; Error::from_raw(err)?; Ok(()) } pub fn finish(&self) -> Result { let mut checksum = std::ptr::null_mut(); let pool = apr::pool::Pool::new(); unsafe { Error::from_raw(crate::generated::svn_checksum_final( &mut checksum, self.0.as_ptr(), pool.as_mut_ptr(), ))?; Ok(Checksum(PooledPtr::in_pool( std::rc::Rc::new(pool), checksum, ))) } } } pub fn checksum(kind: ChecksumKind, data: &[u8]) -> Result { let mut checksum = std::ptr::null_mut(); let kind = kind.into(); let pool = apr::pool::Pool::new(); unsafe { Error::from_raw(crate::generated::svn_checksum( &mut checksum, kind, data.as_ptr() as *const std::ffi::c_void, data.len(), pool.as_mut_ptr(), ))?; Ok(Checksum(PooledPtr::in_pool( std::rc::Rc::new(pool), checksum, ))) } } subversion-0.0.8/src/main.rs000064400000000000000000000000551046102023000141110ustar 00000000000000fn main() { println!("Hello, world!"); } subversion-0.0.8/src/mergeinfo.rs000064400000000000000000000030621046102023000151410ustar 00000000000000use crate::generated::svn_mergeinfo_t; use apr::pool::PooledPtr; #[allow(dead_code)] pub struct Mergeinfo(pub(crate) PooledPtr); #[derive(Debug, PartialEq)] pub enum MergeinfoInheritance { Explicit, Inherited, NearestAncestor, } impl From for MergeinfoInheritance { fn from(value: crate::generated::svn_mergeinfo_inheritance_t) -> Self { match value { crate::generated::svn_mergeinfo_inheritance_t_svn_mergeinfo_nearest_ancestor => { MergeinfoInheritance::NearestAncestor } crate::generated::svn_mergeinfo_inheritance_t_svn_mergeinfo_explicit => { MergeinfoInheritance::Explicit } crate::generated::svn_mergeinfo_inheritance_t_svn_mergeinfo_inherited => { MergeinfoInheritance::Inherited } _ => unreachable!(), } } } impl From for crate::generated::svn_mergeinfo_inheritance_t { fn from(value: MergeinfoInheritance) -> Self { match value { MergeinfoInheritance::NearestAncestor => { crate::generated::svn_mergeinfo_inheritance_t_svn_mergeinfo_nearest_ancestor } MergeinfoInheritance::Explicit => { crate::generated::svn_mergeinfo_inheritance_t_svn_mergeinfo_explicit } MergeinfoInheritance::Inherited => { crate::generated::svn_mergeinfo_inheritance_t_svn_mergeinfo_inherited } } } } subversion-0.0.8/src/path.rs000064400000000000000000000000001046102023000141070ustar 00000000000000subversion-0.0.8/src/props.rs000064400000000000000000000042131046102023000143300ustar 00000000000000#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub enum Kind { Entry, Wc, Regular, } impl From for Kind { fn from(kind: crate::generated::svn_prop_kind_t) -> Self { match kind { crate::generated::svn_prop_kind_svn_prop_entry_kind => Kind::Entry, crate::generated::svn_prop_kind_svn_prop_wc_kind => Kind::Wc, crate::generated::svn_prop_kind_svn_prop_regular_kind => Kind::Regular, _ => panic!("Unknown property kind"), } } } pub fn kind(name: &str) -> Kind { let name = std::ffi::CString::new(name).unwrap(); unsafe { crate::generated::svn_property_kind2(name.as_ptr()) }.into() } pub fn is_svn_prop(name: &str) -> bool { let name = std::ffi::CString::new(name).unwrap(); unsafe { crate::generated::svn_prop_is_svn_prop(name.as_ptr()) != 0 } } pub fn is_boolean(name: &str) -> bool { let name = std::ffi::CString::new(name).unwrap(); unsafe { crate::generated::svn_prop_is_boolean(name.as_ptr()) != 0 } } pub fn is_known_svn_rev_prop(name: &str) -> bool { let name = std::ffi::CString::new(name).unwrap(); unsafe { crate::generated::svn_prop_is_known_svn_rev_prop(name.as_ptr()) != 0 } } pub fn is_known_svn_node_prop(name: &str) -> bool { let name = std::ffi::CString::new(name).unwrap(); unsafe { crate::generated::svn_prop_is_known_svn_node_prop(name.as_ptr()) != 0 } } pub fn is_known_svn_file_prop(name: &str) -> bool { let name = std::ffi::CString::new(name).unwrap(); unsafe { crate::generated::svn_prop_is_known_svn_file_prop(name.as_ptr()) != 0 } } pub fn is_known_svn_dir_prop(name: &str) -> bool { let name = std::ffi::CString::new(name).unwrap(); unsafe { crate::generated::svn_prop_is_known_svn_dir_prop(name.as_ptr()) != 0 } } pub fn needs_translation(name: &str) -> bool { let name = std::ffi::CString::new(name).unwrap(); unsafe { crate::generated::svn_prop_needs_translation(name.as_ptr()) != 0 } } pub fn name_is_valid(name: &str) -> bool { let name = std::ffi::CString::new(name).unwrap(); unsafe { crate::generated::svn_prop_name_is_valid(name.as_ptr()) != 0 } } subversion-0.0.8/src/ra.rs000064400000000000000000001271541046102023000136010ustar 00000000000000use crate::config::Config; use crate::delta::Editor; use crate::generated::svn_ra_session_t; use crate::{Depth, Error, Revnum}; use apr::pool::{Pool, PooledPtr}; use std::collections::HashMap; pub struct Session(PooledPtr); unsafe impl Send for Session {} pub(crate) extern "C" fn wrap_dirent_receiver( rel_path: *const std::os::raw::c_char, dirent: *mut crate::generated::svn_dirent_t, baton: *mut std::os::raw::c_void, pool: *mut apr::apr_pool_t, ) -> *mut crate::generated::svn_error_t { let rel_path = unsafe { std::ffi::CStr::from_ptr(rel_path) }; let baton = unsafe { &*(baton as *const _ as *const &dyn Fn(&str, &Dirent) -> Result<(), crate::Error>) }; let pool = Pool::from_raw(pool); match baton( rel_path.to_str().unwrap(), &Dirent(unsafe { PooledPtr::in_pool(std::rc::Rc::new(pool), dirent) }), ) { Ok(()) => std::ptr::null_mut(), Err(mut e) => e.as_mut_ptr(), } } extern "C" fn wrap_location_segment_receiver( svn_location_segment: *mut crate::generated::svn_location_segment_t, baton: *mut std::os::raw::c_void, pool: *mut apr::apr_pool_t, ) -> *mut crate::generated::svn_error_t { let baton = unsafe { &*(baton as *const _ as *const &dyn Fn(&crate::LocationSegment) -> Result<(), crate::Error>) }; let pool = Pool::from_raw(pool); match baton(&crate::LocationSegment(unsafe { PooledPtr::in_pool(std::rc::Rc::new(pool), svn_location_segment) })) { Ok(()) => std::ptr::null_mut(), Err(mut e) => e.as_mut_ptr(), } } extern "C" fn wrap_lock_func( lock_baton: *mut std::os::raw::c_void, path: *const std::os::raw::c_char, do_lock: i32, lock: *const crate::generated::svn_lock_t, error: *mut crate::generated::svn_error_t, pool: *mut apr::apr_pool_t, ) -> *mut crate::generated::svn_error_t { let lock_baton = unsafe { &mut *(lock_baton as *mut &mut dyn Fn(&str, bool, &crate::Lock, Option<&Error>) -> Result<(), Error>) }; let path = unsafe { std::ffi::CStr::from_ptr(path) }; let pool = Pool::from_raw(pool); let error = Error::from_raw(error).err(); let lock = crate::Lock(unsafe { PooledPtr::in_pool(std::rc::Rc::new(pool), lock as *mut _) }); match lock_baton(path.to_str().unwrap(), do_lock != 0, &lock, error.as_ref()) { Ok(()) => std::ptr::null_mut(), Err(e) => unsafe { e.into_raw() }, } } impl Session { pub fn open( url: &str, uuid: Option<&str>, mut callbacks: Option<&mut Callbacks>, mut config: Option<&mut Config>, ) -> Result<(Self, Option, Option), Error> { let url = std::ffi::CString::new(url).unwrap(); let mut corrected_url = std::ptr::null(); let mut redirect_url = std::ptr::null(); let pool = Pool::new(); let mut session = std::ptr::null_mut(); let uuid = uuid.map(|uuid| std::ffi::CString::new(uuid).unwrap()); let err = unsafe { crate::generated::svn_ra_open5( &mut session, &mut corrected_url, &mut redirect_url, url.as_ptr(), if let Some(uuid) = uuid { uuid.as_ptr() } else { std::ptr::null() }, if let Some(callbacks) = callbacks.as_mut() { callbacks.as_mut_ptr() } else { Callbacks::default().as_mut_ptr() }, std::ptr::null_mut(), if let Some(config) = config.as_mut() { config.as_mut_ptr() } else { std::ptr::null_mut() }, pool.as_mut_ptr(), ) }; Error::from_raw(err)?; Ok(( Self::from_raw(unsafe { PooledPtr::in_pool(std::rc::Rc::new(pool), session) }), if corrected_url.is_null() { None } else { Some( unsafe { std::ffi::CStr::from_ptr(corrected_url) } .to_str() .unwrap() .to_string(), ) }, if redirect_url.is_null() { None } else { Some( unsafe { std::ffi::CStr::from_ptr(redirect_url) } .to_str() .unwrap() .to_string(), ) }, )) } pub fn reparent(&mut self, url: &str) -> Result<(), Error> { let url = std::ffi::CString::new(url).unwrap(); let pool = Pool::new(); let err = unsafe { crate::generated::svn_ra_reparent(self.0.as_mut_ptr(), url.as_ptr(), pool.as_mut_ptr()) }; Error::from_raw(err)?; Ok(()) } pub fn from_raw(raw: PooledPtr) -> Self { Self(raw) } pub fn get_session_url(&mut self) -> Result { let pool = Pool::new(); let mut url = std::ptr::null(); let err = unsafe { crate::generated::svn_ra_get_session_url( self.0.as_mut_ptr(), &mut url, pool.as_mut_ptr(), ) }; Error::from_raw(err)?; let url = unsafe { std::ffi::CStr::from_ptr(url) }; Ok(url.to_string_lossy().into_owned()) } pub fn get_path_relative_to_session(&mut self, url: &str) -> Result { let url = std::ffi::CString::new(url).unwrap(); let pool = Pool::new(); let mut path = std::ptr::null(); let err = unsafe { crate::generated::svn_ra_get_path_relative_to_session( self.0.as_mut_ptr(), &mut path, url.as_ptr(), pool.as_mut_ptr(), ) }; Error::from_raw(err)?; let path = unsafe { std::ffi::CStr::from_ptr(path) }; Ok(path.to_string_lossy().into_owned()) } pub fn get_path_relative_to_root(&mut self, url: &str) -> String { let url = std::ffi::CString::new(url).unwrap(); let pool = Pool::new(); let mut path = std::ptr::null(); unsafe { crate::generated::svn_ra_get_path_relative_to_root( self.0.as_mut_ptr(), &mut path, url.as_ptr(), pool.as_mut_ptr(), ); } let path = unsafe { std::ffi::CStr::from_ptr(path) }; path.to_string_lossy().into_owned() } pub fn get_latest_revnum(&mut self) -> Result { let pool = Pool::new(); let mut revnum = 0; let err = unsafe { crate::generated::svn_ra_get_latest_revnum( self.0.as_mut_ptr(), &mut revnum, pool.as_mut_ptr(), ) }; Error::from_raw(err)?; Ok(Revnum::from_raw(revnum).unwrap()) } pub fn get_dated_revision(&mut self, tm: impl apr::time::IntoTime) -> Result { let pool = Pool::new(); let mut revnum = 0; let err = unsafe { crate::generated::svn_ra_get_dated_revision( self.0.as_mut_ptr(), &mut revnum, tm.as_apr_time().into(), pool.as_mut_ptr(), ) }; Error::from_raw(err)?; Ok(Revnum::from_raw(revnum).unwrap()) } pub fn change_revprop( &mut self, rev: Revnum, name: &str, old_value: Option<&[u8]>, new_value: &[u8], ) -> Result<(), Error> { let name = std::ffi::CString::new(name).unwrap(); let pool = Pool::new(); let new_value = crate::generated::svn_string_t { data: new_value.as_ptr() as *mut _, len: new_value.len(), }; let old_value = old_value.map(|v| crate::generated::svn_string_t { data: v.as_ptr() as *mut _, len: v.len(), }); let err = unsafe { crate::generated::svn_ra_change_rev_prop2( self.0.as_mut_ptr(), rev.into(), name.as_ptr(), &old_value.map_or(std::ptr::null(), |v| &v), &new_value, pool.as_mut_ptr(), ) }; Error::from_raw(err)?; Ok(()) } pub fn rev_proplist(&mut self, rev: Revnum) -> Result>, Error> { let pool = Pool::new(); let mut props = std::ptr::null_mut(); let err = unsafe { crate::generated::svn_ra_rev_proplist( self.0.as_mut_ptr(), rev.into(), &mut props, pool.as_mut_ptr(), ) }; let mut hash = apr::hash::Hash::<&str, *const crate::generated::svn_string_t>::from_raw(unsafe { PooledPtr::in_pool(std::rc::Rc::new(pool), props) }); Error::from_raw(err)?; Ok(hash .iter() .map(|(k, v)| { ( String::from_utf8_lossy(k).into_owned(), Vec::from(unsafe { std::slice::from_raw_parts((**v).data as *const u8, (**v).len) }), ) }) .collect()) } pub fn rev_prop(&mut self, rev: Revnum, name: &str) -> Result>, Error> { let name = std::ffi::CString::new(name).unwrap(); let pool = Pool::new(); let mut value = std::ptr::null_mut(); let err = unsafe { crate::generated::svn_ra_rev_prop( self.0.as_mut_ptr(), rev.into(), name.as_ptr(), &mut value, pool.as_mut_ptr(), ) }; Error::from_raw(err)?; if value.is_null() { Ok(None) } else { Ok(Some(Vec::from(unsafe { std::slice::from_raw_parts((*value).data as *const u8, (*value).len) }))) } } pub fn get_commit_editor( &mut self, revprop_table: HashMap>, commit_callback: &dyn FnMut(&crate::CommitInfo) -> Result<(), Error>, lock_tokens: HashMap, keep_locks: bool, ) -> Result, Error> { let pool = std::rc::Rc::new(Pool::new()); let commit_callback = Box::into_raw(Box::new(commit_callback)); let mut editor = std::ptr::null(); let mut edit_baton = std::ptr::null_mut(); let mut hash_revprop_table = apr::hash::Hash::<&str, *const crate::generated::svn_string_t>::in_pool(&pool); for (k, v) in revprop_table.iter() { let v: crate::string::String = v.as_slice().into(); hash_revprop_table.set(k, &v.as_ptr()); } let mut hash_lock_tokens = apr::hash::Hash::<&str, *const std::os::raw::c_char>::in_pool(&pool); for (k, v) in lock_tokens.iter() { hash_lock_tokens.set(k, &(v.as_ptr() as *const _)); } let result_pool = Pool::new(); let err = unsafe { crate::generated::svn_ra_get_commit_editor3( self.0.as_mut_ptr(), &mut editor, &mut edit_baton, hash_revprop_table.as_mut_ptr(), Some(crate::wrap_commit_callback2), commit_callback as *mut _ as *mut _, hash_lock_tokens.as_mut_ptr(), keep_locks.into(), result_pool.as_mut_ptr(), ) }; Error::from_raw(err)?; Ok(Box::new(crate::delta::WrapEditor(editor, unsafe { PooledPtr::in_pool(std::rc::Rc::new(result_pool), edit_baton) }))) } pub fn get_file( &mut self, path: &str, rev: Revnum, stream: &mut crate::io::Stream, ) -> Result<(Revnum, HashMap>), Error> { let path = std::ffi::CString::new(path).unwrap(); let pool = Pool::new(); let mut props = std::ptr::null_mut(); let mut fetched_rev = 0; let err = unsafe { crate::generated::svn_ra_get_file( self.0.as_mut_ptr(), path.as_ptr(), rev.into(), stream.as_mut_ptr(), &mut fetched_rev, &mut props, pool.as_mut_ptr(), ) }; crate::Error::from_raw(err)?; let mut hash = apr::hash::Hash::<&str, *const crate::generated::svn_string_t>::from_raw(unsafe { PooledPtr::in_pool(std::rc::Rc::new(pool), props) }); Ok(( Revnum::from_raw(fetched_rev).unwrap(), hash.iter() .map(|(k, v)| { ( String::from_utf8_lossy(k).into_owned(), Vec::from(unsafe { std::slice::from_raw_parts((**v).data as *const u8, (**v).len) }), ) }) .collect(), )) } pub fn get_dir( &mut self, path: &str, rev: Revnum, ) -> Result<(Revnum, HashMap, HashMap>), Error> { let path = std::ffi::CString::new(path).unwrap(); let pool = Pool::new(); let mut props = std::ptr::null_mut(); let mut fetched_rev = 0; let mut dirents = std::ptr::null_mut(); let dirent_fields = crate::generated::SVN_DIRENT_KIND | crate::generated::SVN_DIRENT_SIZE | crate::generated::SVN_DIRENT_HAS_PROPS | crate::generated::SVN_DIRENT_CREATED_REV | crate::generated::SVN_DIRENT_TIME | crate::generated::SVN_DIRENT_LAST_AUTHOR; let err = unsafe { crate::generated::svn_ra_get_dir2( self.0.as_mut_ptr(), &mut dirents, &mut fetched_rev, &mut props, path.as_ptr(), rev.into(), dirent_fields, pool.as_mut_ptr(), ) }; let pool = std::rc::Rc::new(pool); crate::Error::from_raw(err)?; let mut props_hash = apr::hash::Hash::<&str, *const crate::generated::svn_string_t>::from_raw(unsafe { PooledPtr::in_pool(pool.clone(), props) }); let mut dirents_hash = apr::hash::Hash::<&str, *mut crate::generated::svn_dirent_t>::from_raw(unsafe { PooledPtr::in_pool(pool.clone(), dirents) }); let props = props_hash .iter() .map(|(k, v)| { ( String::from_utf8_lossy(k).into_owned(), Vec::from(unsafe { std::slice::from_raw_parts((**v).data as *const u8, (**v).len) }), ) }) .collect(); let dirents = dirents_hash .iter() .map(|(k, v)| { ( String::from_utf8_lossy(k).into_owned(), Dirent(unsafe { PooledPtr::in_pool(pool.clone(), *v) }), ) }) .collect(); Ok((Revnum::from_raw(fetched_rev).unwrap(), dirents, props)) } pub fn list( &mut self, path: &str, rev: Revnum, patterns: Option<&[&str]>, depth: Depth, dirent_receiver: impl Fn(&str, &Dirent) -> Result<(), crate::Error>, ) -> Result<(), Error> { let path = std::ffi::CString::new(path).unwrap(); let pool = Pool::new(); let patterns: Option> = patterns.map(|patterns| { patterns .iter() .map(|pattern| pattern.as_ptr() as _) .collect() }); let dirent_fields = crate::generated::SVN_DIRENT_KIND | crate::generated::SVN_DIRENT_SIZE | crate::generated::SVN_DIRENT_HAS_PROPS | crate::generated::SVN_DIRENT_CREATED_REV | crate::generated::SVN_DIRENT_TIME | crate::generated::SVN_DIRENT_LAST_AUTHOR; let err = unsafe { crate::generated::svn_ra_list( self.0.as_mut_ptr(), path.as_ptr(), rev.into(), if let Some(patterns) = patterns.as_ref() { patterns.as_ptr() } else { std::ptr::null() }, depth.into(), dirent_fields, Some(wrap_dirent_receiver), &dirent_receiver as *const _ as *mut _, pool.as_mut_ptr(), ) }; Error::from_raw(err)?; Ok(()) } pub fn get_mergeinfo( &mut self, paths: &[&str], revision: Revnum, inherit: crate::mergeinfo::MergeinfoInheritance, include_descendants: bool, ) -> Result, Error> { let paths: apr::tables::ArrayHeader<*const std::os::raw::c_char> = paths.iter().map(|path| path.as_ptr() as _).collect(); let pool = Pool::new(); let mut mergeinfo = std::ptr::null_mut(); let err = unsafe { crate::generated::svn_ra_get_mergeinfo( self.0.as_mut_ptr(), &mut mergeinfo, paths.as_ptr(), revision.into(), inherit.into(), include_descendants.into(), pool.as_mut_ptr(), ) }; Error::from_raw(err)?; let pool = std::rc::Rc::new(pool); let mut mergeinfo = apr::hash::Hash::<&[u8], *mut crate::generated::svn_mergeinfo_t>::from_raw(unsafe { PooledPtr::in_pool(pool.clone(), mergeinfo) }); Ok(mergeinfo .iter() .map(|(k, v)| { ( String::from_utf8_lossy(k).into_owned(), crate::mergeinfo::Mergeinfo(unsafe { PooledPtr::in_pool(pool.clone(), *v) }), ) }) .collect()) } pub fn do_update( &mut self, revision_to_update_to: Revnum, update_target: &str, depth: Depth, send_copyfrom_args: bool, ignore_ancestry: bool, editor: &mut dyn Editor, ) -> Result, Error> { let pool = Pool::new(); let scratch_pool = Pool::new(); let mut reporter = std::ptr::null(); let mut report_baton = std::ptr::null_mut(); let err = unsafe { crate::generated::svn_ra_do_update3( self.0.as_mut_ptr(), &mut reporter, &mut report_baton, revision_to_update_to.into(), update_target.as_ptr() as *const _, depth.into(), send_copyfrom_args.into(), ignore_ancestry.into(), &crate::delta::WRAP_EDITOR, editor as *mut _ as *mut std::ffi::c_void, scratch_pool.as_mut_ptr(), pool.as_mut_ptr(), ) }; Error::from_raw(err)?; Ok(Box::new(WrapReporter(reporter, unsafe { PooledPtr::in_pool(std::rc::Rc::new(pool), report_baton) })) as Box) } pub fn do_switch( &mut self, revision_to_switch_to: Revnum, switch_target: &str, depth: Depth, switch_url: &str, send_copyfrom_args: bool, ignore_ancestry: bool, editor: &mut dyn Editor, ) -> Result, Error> { let switch_target = std::ffi::CString::new(switch_target).unwrap(); let pool = Pool::new(); let scratch_pool = Pool::new(); let mut reporter = std::ptr::null(); let mut report_baton = std::ptr::null_mut(); let err = unsafe { crate::generated::svn_ra_do_switch3( self.0.as_mut_ptr(), &mut reporter, &mut report_baton, revision_to_switch_to.into(), switch_target.as_ptr() as *const _, depth.into(), switch_url.as_ptr() as *const _, send_copyfrom_args.into(), ignore_ancestry.into(), &crate::delta::WRAP_EDITOR, editor as *mut _ as *mut std::ffi::c_void, scratch_pool.as_mut_ptr(), pool.as_mut_ptr(), ) }; Error::from_raw(err)?; Ok(Box::new(WrapReporter(reporter, unsafe { PooledPtr::in_pool(std::rc::Rc::new(pool), report_baton) })) as Box) } pub fn check_path(&mut self, path: &str, rev: Revnum) -> Result { let path = std::ffi::CString::new(path).unwrap(); let mut kind = 0; let pool = Pool::new(); let err = unsafe { crate::generated::svn_ra_check_path( self.0.as_mut_ptr(), path.as_ptr(), rev.into(), &mut kind, pool.as_mut_ptr(), ) }; Error::from_raw(err)?; Ok(crate::NodeKind::from(kind)) } pub fn do_status( &mut self, status_target: &str, revision: Revnum, depth: Depth, status_editor: &mut dyn Editor, ) -> Result<(), Error> { let status_target = std::ffi::CString::new(status_target).unwrap(); let pool = Pool::new(); let mut reporter = std::ptr::null(); let mut report_baton = std::ptr::null_mut(); let err = unsafe { crate::generated::svn_ra_do_status2( self.0.as_mut_ptr(), &mut reporter, &mut report_baton, status_target.as_ptr() as *const _, revision.into(), depth.into(), &crate::delta::WRAP_EDITOR, status_editor as *mut _ as *mut std::ffi::c_void, pool.as_mut_ptr(), ) }; Error::from_raw(err)?; Ok(()) } pub fn stat(&mut self, path: &str, rev: Revnum) -> Result { let path = std::ffi::CString::new(path).unwrap(); let mut dirent = std::ptr::null_mut(); let pool = Pool::new(); let err = unsafe { crate::generated::svn_ra_stat( self.0.as_mut_ptr(), path.as_ptr(), rev.into(), &mut dirent, pool.as_mut_ptr(), ) }; Error::from_raw(err)?; Ok(Dirent(unsafe { PooledPtr::in_pool(std::rc::Rc::new(pool), dirent) })) } pub fn get_uuid(&mut self) -> Result { let pool = Pool::new(); let mut uuid = std::ptr::null(); let err = unsafe { crate::generated::svn_ra_get_uuid2(self.0.as_mut_ptr(), &mut uuid, pool.as_mut_ptr()) }; Error::from_raw(err)?; let uuid = unsafe { std::ffi::CStr::from_ptr(uuid) }; Ok(uuid.to_string_lossy().into_owned()) } pub fn get_repos_root(&mut self) -> Result { let pool = Pool::new(); let mut url = std::ptr::null(); let err = unsafe { crate::generated::svn_ra_get_repos_root2( self.0.as_mut_ptr(), &mut url, pool.as_mut_ptr(), ) }; Error::from_raw(err)?; let url = unsafe { std::ffi::CStr::from_ptr(url) }; Ok(url.to_str().unwrap().to_string()) } pub fn get_deleted_rev( &mut self, path: &str, peg_revision: Revnum, end_revision: Revnum, ) -> Result { let path = std::ffi::CString::new(path).unwrap(); let mut rev = 0; let pool = Pool::new(); let err = unsafe { crate::generated::svn_ra_get_deleted_rev( self.0.as_mut_ptr(), path.as_ptr(), peg_revision.into(), end_revision.into(), &mut rev, pool.as_mut_ptr(), ) }; Error::from_raw(err)?; Ok(Revnum::from_raw(rev).unwrap()) } pub fn has_capability(&mut self, capability: &str) -> Result { let capability = std::ffi::CString::new(capability).unwrap(); let mut has = 0; let pool = Pool::new(); let err = unsafe { crate::generated::svn_ra_has_capability( self.0.as_mut_ptr(), &mut has, capability.as_ptr(), pool.as_mut_ptr(), ) }; Error::from_raw(err)?; Ok(has != 0) } pub fn diff( &mut self, revision: Revnum, diff_target: &str, depth: Depth, ignore_ancestry: bool, text_deltas: bool, versus_url: &str, diff_editor: &mut dyn Editor, ) -> Result, Error> { let diff_target = std::ffi::CString::new(diff_target).unwrap(); let versus_url = std::ffi::CString::new(versus_url).unwrap(); let pool = Pool::new(); let mut reporter = std::ptr::null(); let mut report_baton = std::ptr::null_mut(); let err = unsafe { crate::generated::svn_ra_do_diff3( self.0.as_mut_ptr(), &mut reporter, &mut report_baton, revision.into(), diff_target.as_ptr() as *const _, depth.into(), ignore_ancestry.into(), text_deltas.into(), versus_url.as_ptr() as *const _, &crate::delta::WRAP_EDITOR, diff_editor as *mut _ as *mut std::ffi::c_void, pool.as_mut_ptr(), ) }; Error::from_raw(err)?; Ok(Box::new(WrapReporter(reporter, unsafe { PooledPtr::in_pool(std::rc::Rc::new(pool), report_baton) })) as Box) } pub fn get_log( &mut self, paths: &[&str], start: Revnum, end: Revnum, limit: usize, discover_changed_paths: bool, strict_node_history: bool, include_merged_revisions: bool, revprops: &[&str], log_receiver: &dyn FnMut(&crate::CommitInfo) -> Result<(), Error>, ) -> Result<(), Error> { let paths: apr::tables::ArrayHeader<*const std::os::raw::c_char> = paths.iter().map(|path| path.as_ptr() as _).collect(); let revprops: apr::tables::ArrayHeader<*const std::os::raw::c_char> = revprops .iter() .map(|revprop| revprop.as_ptr() as _) .collect(); let pool = Pool::new(); let log_receiver = Box::new(log_receiver); let err = unsafe { crate::generated::svn_ra_get_log2( self.0.as_mut_ptr(), paths.as_ptr(), start.into(), end.into(), limit as _, discover_changed_paths.into(), strict_node_history.into(), include_merged_revisions.into(), revprops.as_ptr(), Some(crate::wrap_log_entry_receiver), &log_receiver as *const _ as *mut _, pool.as_mut_ptr(), ) }; Error::from_raw(err)?; Ok(()) } pub fn get_locations( &mut self, path: &str, peg_revision: Revnum, location_revisions: &[Revnum], ) -> Result, Error> { let path = std::ffi::CString::new(path).unwrap(); let location_revisions: apr::tables::ArrayHeader = location_revisions.iter().map(|rev| (*rev).into()).collect(); let pool = Pool::new(); let mut locations = std::ptr::null_mut(); let err = unsafe { crate::generated::svn_ra_get_locations( self.0.as_mut_ptr(), &mut locations, path.as_ptr(), peg_revision.into(), location_revisions.as_ptr(), pool.as_mut_ptr(), ) }; Error::from_raw(err)?; let mut iter = apr::hash::Hash::<&Revnum, *const std::os::raw::c_char>::from_raw(unsafe { PooledPtr::in_pool(std::rc::Rc::new(pool), locations) }); let mut locations = HashMap::new(); for (k, v) in iter.iter() { let revnum = k.as_ptr() as *const Revnum; locations.insert(unsafe { *revnum }, unsafe { std::ffi::CStr::from_ptr(*v).to_string_lossy().into_owned() }); } Ok(locations) } pub fn get_location_segments( &mut self, path: &str, peg_revision: Revnum, start: Revnum, end: Revnum, location_receiver: &dyn Fn(&crate::LocationSegment) -> Result<(), Error>, ) -> Result<(), Error> { let path = std::ffi::CString::new(path).unwrap(); let pool = Pool::new(); let err = unsafe { crate::generated::svn_ra_get_location_segments( self.0.as_mut_ptr(), path.as_ptr(), peg_revision.into(), start.into(), end.into(), Some(wrap_location_segment_receiver), &location_receiver as *const _ as *mut _, pool.as_mut_ptr(), ) }; Error::from_raw(err)?; Ok(()) } pub fn lock( &mut self, path_revs: &HashMap, commnt: &str, steal_lock: bool, mut lock_func: impl Fn(&str, bool, &crate::Lock, Option<&Error>) -> Result<(), Error>, ) -> Result<(), Error> { let pool = Pool::new(); let scratch_pool = std::rc::Rc::new(Pool::new()); let mut hash = apr::hash::Hash::<&str, crate::generated::svn_revnum_t>::in_pool(&scratch_pool); for (k, v) in path_revs.iter() { hash.set(k, &v.0); } let commnt = std::ffi::CString::new(commnt).unwrap(); let err = unsafe { crate::generated::svn_ra_lock( self.0.as_mut_ptr(), hash.as_mut_ptr(), commnt.as_ptr(), steal_lock.into(), Some(wrap_lock_func), &mut lock_func as *mut _ as *mut std::ffi::c_void, pool.as_mut_ptr(), ) }; Error::from_raw(err)?; Ok(()) } pub fn unlock( &mut self, path_tokens: &HashMap, break_lock: bool, mut lock_func: impl Fn(&str, bool, &crate::Lock, Option<&Error>) -> Result<(), Error>, ) -> Result<(), Error> { let pool = Pool::new(); let scratch_pool = std::rc::Rc::new(Pool::new()); let mut hash = apr::hash::Hash::<&str, *const std::os::raw::c_char>::in_pool(&scratch_pool); for (k, v) in path_tokens.iter() { hash.set(k, &(v.as_ptr() as *const _)); } let err = unsafe { crate::generated::svn_ra_unlock( self.0.as_mut_ptr(), hash.as_mut_ptr(), break_lock.into(), Some(wrap_lock_func), &mut lock_func as *mut _ as *mut std::ffi::c_void, pool.as_mut_ptr(), ) }; Error::from_raw(err)?; Ok(()) } pub fn get_lock(&mut self, path: &str) -> Result { let path = std::ffi::CString::new(path).unwrap(); let mut lock = std::ptr::null_mut(); let pool = Pool::new(); let err = unsafe { crate::generated::svn_ra_get_lock( self.0.as_mut_ptr(), &mut lock, path.as_ptr(), pool.as_mut_ptr(), ) }; Error::from_raw(err)?; Ok(crate::Lock(unsafe { PooledPtr::in_pool(std::rc::Rc::new(pool), lock) })) } pub fn get_locks( &mut self, path: &str, depth: Depth, ) -> Result, Error> { let path = std::ffi::CString::new(path).unwrap(); let mut locks = std::ptr::null_mut(); let pool = Pool::new(); let err = unsafe { crate::generated::svn_ra_get_locks2( self.0.as_mut_ptr(), &mut locks, path.as_ptr(), depth.into(), pool.as_mut_ptr(), ) }; Error::from_raw(err)?; let pool = std::rc::Rc::new(pool); let mut hash = apr::hash::Hash::<&str, *mut crate::generated::svn_lock_t>::from_raw(unsafe { PooledPtr::in_pool(pool.clone(), locks) }); Ok(hash .iter() .map(|(k, v)| { ( String::from_utf8_lossy(k).into_owned(), crate::Lock(unsafe { PooledPtr::in_pool(pool.clone(), *v) }), ) }) .collect()) } pub fn replay_range( &mut self, start_revision: Revnum, end_revision: Revnum, low_water_mark: Revnum, send_deltas: bool, replay_revstart_callback: &dyn FnMut( Revnum, &HashMap>, ) -> Result, Error>, replay_revend_callback: &dyn FnMut( Revnum, &dyn crate::delta::Editor, &HashMap>, ) -> Result<(), Error>, ) -> Result<(), Error> { extern "C" fn wrap_replay_revstart_callback( revision: crate::generated::svn_revnum_t, replay_baton: *mut std::ffi::c_void, editor: *mut *const crate::generated::svn_delta_editor_t, edit_baton: *mut *mut std::ffi::c_void, rev_props: *mut apr::hash::apr_hash_t, pool: *mut apr::apr_pool_t, ) -> *mut crate::generated::svn_error_t { let baton = unsafe { (replay_baton as *mut ( &mut dyn FnMut( Revnum, &HashMap>, ) -> Result, Error>, &mut dyn FnMut( Revnum, &dyn crate::delta::Editor, &HashMap>, ) -> Result<(), Error>, )) .as_mut() .unwrap() }; let mut revprops = apr::hash::Hash::<&str, *const crate::generated::svn_string_t>::from_raw(unsafe { PooledPtr::in_pool(std::rc::Rc::new(Pool::from_raw(pool)), rev_props) }); let revprops = revprops .iter() .map(|(k, v)| { ( std::str::from_utf8(k).unwrap().to_string(), Vec::from(unsafe { std::slice::from_raw_parts((**v).data as *const u8, (**v).len) }), ) }) .collect(); match ((*baton).0)(Revnum::from_raw(revision).unwrap(), &revprops) { Ok(mut e) => { unsafe { *editor = &crate::delta::WRAP_EDITOR }; unsafe { *edit_baton = e.as_mut() as *mut _ as *mut std::ffi::c_void }; std::ptr::null_mut() } Err(err) => unsafe { err.into_raw() }, } } extern "C" fn wrap_replay_revend_callback( revision: crate::generated::svn_revnum_t, replay_baton: *mut std::ffi::c_void, editor: *const crate::generated::svn_delta_editor_t, edit_baton: *mut std::ffi::c_void, rev_props: *mut apr::hash::apr_hash_t, pool: *mut apr::apr_pool_t, ) -> *mut crate::generated::svn_error_t { let baton = unsafe { (replay_baton as *mut ( &mut dyn FnMut( Revnum, &HashMap>, ) -> Result, Error>, &mut dyn FnMut( Revnum, &dyn crate::delta::Editor, &HashMap>, ) -> Result<(), Error>, )) .as_mut() .unwrap() }; let mut editor = crate::delta::WrapEditor(editor as *const _, unsafe { PooledPtr::in_pool(std::rc::Rc::new(Pool::from_raw(pool)), edit_baton) }); let mut revprops = apr::hash::Hash::<&str, *const crate::generated::svn_string_t>::from_raw(unsafe { PooledPtr::in_pool(std::rc::Rc::new(Pool::from_raw(pool)), rev_props) }); let revprops = revprops .iter() .map(|(k, v)| { ( std::str::from_utf8(k).unwrap().to_string(), Vec::from(unsafe { std::slice::from_raw_parts((**v).data as *const u8, (**v).len) }), ) }) .collect(); match ((*baton).1)(Revnum::from_raw(revision).unwrap(), &mut editor, &revprops) { Ok(_) => std::ptr::null_mut(), Err(err) => unsafe { err.into_raw() }, } } let pool = Pool::new(); let err = unsafe { crate::generated::svn_ra_replay_range( self.0.as_mut_ptr(), start_revision.into(), end_revision.into(), low_water_mark.into(), send_deltas.into(), Some(wrap_replay_revstart_callback), Some(wrap_replay_revend_callback), &(replay_revstart_callback, replay_revend_callback) as *const _ as *mut _, pool.as_mut_ptr(), ) }; Error::from_raw(err)?; Ok(()) } pub fn replay( &mut self, revision: Revnum, low_water_mark: Revnum, send_deltas: bool, editor: &mut dyn crate::delta::Editor, ) -> Result<(), Error> { let pool = Pool::new(); let err = unsafe { crate::generated::svn_ra_replay( self.0.as_mut_ptr(), revision.into(), low_water_mark.into(), send_deltas.into(), &crate::delta::WRAP_EDITOR, editor as *mut _ as *mut std::ffi::c_void, pool.as_mut_ptr(), ) }; Error::from_raw(err)?; Ok(()) } } pub fn modules() -> Result { let pool = Pool::new(); let buf = unsafe { crate::generated::svn_stringbuf_create( std::ffi::CStr::from_bytes_with_nul(b"").unwrap().as_ptr(), pool.as_mut_ptr(), ) }; let err = unsafe { crate::generated::svn_ra_print_modules(buf, pool.as_mut_ptr()) }; Error::from_raw(err)?; Ok(unsafe { std::ffi::CStr::from_ptr((*buf).data) .to_string_lossy() .into_owned() }) } pub struct WrapReporter( *const crate::generated::svn_ra_reporter3_t, PooledPtr, ); unsafe impl Send for WrapReporter {} impl Reporter for WrapReporter { fn set_path( &mut self, path: &str, rev: Revnum, depth: Depth, start_empty: bool, lock_token: &str, ) -> Result<(), Error> { let path = std::ffi::CString::new(path).unwrap(); let lock_token = std::ffi::CString::new(lock_token).unwrap(); let pool = Pool::new(); let err = unsafe { (*self.0).set_path.unwrap()( self.1.as_mut_ptr(), path.as_ptr(), rev.into(), depth.into(), start_empty.into(), lock_token.as_ptr(), pool.as_mut_ptr(), ) }; Error::from_raw(err)?; Ok(()) } fn delete_path(&mut self, path: &str) -> Result<(), Error> { let path = std::ffi::CString::new(path).unwrap(); let pool = Pool::new(); let err = unsafe { (*self.0).delete_path.unwrap()(self.1.as_mut_ptr(), path.as_ptr(), pool.as_mut_ptr()) }; Error::from_raw(err)?; Ok(()) } fn link_path( &mut self, path: &str, url: &str, rev: Revnum, depth: Depth, start_empty: bool, lock_token: &str, ) -> Result<(), Error> { let path = std::ffi::CString::new(path).unwrap(); let url = std::ffi::CString::new(url).unwrap(); let lock_token = std::ffi::CString::new(lock_token).unwrap(); let pool = Pool::new(); let err = unsafe { (*self.0).link_path.unwrap()( self.1.as_mut_ptr(), path.as_ptr(), url.as_ptr(), rev.into(), depth.into(), start_empty.into(), lock_token.as_ptr(), pool.as_mut_ptr(), ) }; Error::from_raw(err)?; Ok(()) } fn finish_report(&mut self) -> Result<(), Error> { let pool = Pool::new(); let err = unsafe { (*self.0).finish_report.unwrap()(self.1.as_mut_ptr(), pool.as_mut_ptr()) }; Error::from_raw(err)?; Ok(()) } fn abort_report(&mut self) -> Result<(), Error> { let pool = Pool::new(); let err = unsafe { (*self.0).abort_report.unwrap()(self.1.as_mut_ptr(), pool.as_mut_ptr()) }; Error::from_raw(err)?; Ok(()) } } #[allow(dead_code)] pub struct Dirent(PooledPtr); unsafe impl Send for Dirent {} pub fn version() -> crate::Version { unsafe { crate::Version(crate::generated::svn_ra_version()) } } pub trait Reporter { fn set_path( &mut self, path: &str, rev: Revnum, depth: Depth, start_empty: bool, lock_token: &str, ) -> Result<(), Error>; fn delete_path(&mut self, path: &str) -> Result<(), Error>; fn link_path( &mut self, path: &str, url: &str, rev: Revnum, depth: Depth, start_empty: bool, lock_token: &str, ) -> Result<(), Error>; fn finish_report(&mut self) -> Result<(), Error>; fn abort_report(&mut self) -> Result<(), Error>; } pub struct Callbacks(PooledPtr); impl Default for Callbacks { fn default() -> Self { Self::new().unwrap() } } impl Callbacks { pub fn new() -> Result { Ok(Callbacks(PooledPtr::initialize(|pool| unsafe { let mut callbacks = std::ptr::null_mut(); let err = crate::generated::svn_ra_create_callbacks(&mut callbacks, pool.as_mut_ptr()); Error::from_raw(err)?; Ok::<_, crate::Error>(callbacks) })?)) } fn as_mut_ptr(&mut self) -> *mut crate::generated::svn_ra_callbacks2_t { self.0.as_mut_ptr() } } subversion-0.0.8/src/repos.rs000064400000000000000000000474571046102023000143360ustar 00000000000000use crate::generated::{svn_repos_create, svn_repos_find_root_path, svn_repos_t}; use crate::{Error, Revnum}; use apr::pool::PooledPtr; #[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] pub enum LoadUUID { #[default] /// Only update UUID if the repos has no revisions Default, /// Never update UUID Ignore, /// Always update UUID Force, } impl From for LoadUUID { fn from(load_uuid: crate::generated::svn_repos_load_uuid) -> Self { match load_uuid { crate::generated::svn_repos_load_uuid_svn_repos_load_uuid_default => Self::Default, crate::generated::svn_repos_load_uuid_svn_repos_load_uuid_ignore => Self::Ignore, crate::generated::svn_repos_load_uuid_svn_repos_load_uuid_force => Self::Force, _ => unreachable!(), } } } impl From for crate::generated::svn_repos_load_uuid { fn from(load_uuid: LoadUUID) -> Self { match load_uuid { LoadUUID::Default => crate::generated::svn_repos_load_uuid_svn_repos_load_uuid_default, LoadUUID::Ignore => crate::generated::svn_repos_load_uuid_svn_repos_load_uuid_ignore, LoadUUID::Force => crate::generated::svn_repos_load_uuid_svn_repos_load_uuid_force, } } } pub fn find_root_path(path: &std::path::Path) -> Option { let path = std::ffi::CString::new(path.to_str().unwrap()).unwrap(); let pool = apr::Pool::new(); let ret = unsafe { svn_repos_find_root_path(path.as_ptr(), pool.as_mut_ptr()) }; if ret.is_null() { let root_path = unsafe { std::ffi::CStr::from_ptr(ret) }; Some(std::path::PathBuf::from(root_path.to_str().unwrap())) } else { None } } pub struct Repos(PooledPtr); impl Repos { pub fn create(path: &std::path::Path) -> Result { // TODO: Support config, fs_config let path = std::ffi::CString::new(path.to_str().unwrap()).unwrap(); let config = std::ptr::null_mut(); let fs_config = std::ptr::null_mut(); Ok(Self(PooledPtr::initialize(|pool| unsafe { let mut repos: *mut svn_repos_t = std::ptr::null_mut(); let ret = svn_repos_create( &mut repos, path.as_ptr(), std::ptr::null(), std::ptr::null(), config, fs_config, pool.as_mut_ptr(), ); Error::from_raw(ret)?; Ok::<_, Error>(repos) })?)) } pub fn open(path: &std::path::Path) -> Result { let path = std::ffi::CString::new(path.to_str().unwrap()).unwrap(); Ok(Self(PooledPtr::initialize(|pool| { let mut repos: *mut svn_repos_t = std::ptr::null_mut(); let ret = unsafe { crate::generated::svn_repos_open(&mut repos, path.as_ptr(), pool.as_mut_ptr()) }; Error::from_raw(ret)?; Ok::<_, Error>(repos) })?)) } pub fn capabilities(&mut self) -> Result, Error> { let mut capabilities = apr::hash::Hash::<&str, &str>::from_raw(PooledPtr::initialize(|pool| { let mut capabilities: *mut apr::hash::apr_hash_t = std::ptr::null_mut(); let scratch_pool = apr::Pool::new(); let ret = unsafe { crate::generated::svn_repos_capabilities( &mut capabilities, self.0.as_mut_ptr(), pool.as_mut_ptr(), scratch_pool.as_mut_ptr(), ) }; Error::from_raw(ret)?; Ok::<_, Error>(capabilities) })?); Ok(capabilities .keys() .map(|k| String::from_utf8_lossy(k).to_string()) .collect::>()) } pub fn has_capability(&mut self, capability: &str) -> Result { let capability = std::ffi::CString::new(capability).unwrap(); let pool = apr::Pool::new(); unsafe { let mut has: crate::generated::svn_boolean_t = 0; let ret = crate::generated::svn_repos_has_capability( self.0.as_mut_ptr(), &mut has, capability.as_ptr(), pool.as_mut_ptr(), ); Error::from_raw(ret)?; Ok(has != 0) } } pub fn remember_client_capabilities(&mut self, capabilities: &[&str]) -> Result<(), Error> { let capabilities = capabilities .iter() .map(|c| std::ffi::CString::new(*c).unwrap()) .collect::>(); let capabilities_array = capabilities .iter() .map(|c| c.as_ptr()) .collect::>(); let ret = unsafe { crate::generated::svn_repos_remember_client_capabilities( self.0.as_mut_ptr(), capabilities_array.as_ptr(), ) }; Error::from_raw(ret)?; Ok(()) } pub fn fs(&mut self) -> Option { let fs = unsafe { crate::generated::svn_repos_fs(self.0.as_mut_ptr()) }; if fs.is_null() { None } else { Some(crate::fs::Fs(unsafe { PooledPtr::in_pool(self.0.pool(), fs as *mut _) })) } } pub fn fs_type(&mut self) -> String { let pool = apr::Pool::new(); let ret = unsafe { crate::generated::svn_repos_fs_type(self.0.as_mut_ptr(), pool.as_mut_ptr()) }; let fs_type = unsafe { std::ffi::CStr::from_ptr(ret) }; fs_type.to_str().unwrap().to_string() } pub fn path(&mut self) -> std::path::PathBuf { let pool = apr::Pool::new(); let ret = unsafe { crate::generated::svn_repos_path(self.0.as_mut_ptr(), pool.as_mut_ptr()) }; let path = unsafe { std::ffi::CStr::from_ptr(ret) }; std::path::PathBuf::from(path.to_str().unwrap()) } pub fn db_env(&mut self) -> std::path::PathBuf { let pool = apr::Pool::new(); let ret = unsafe { crate::generated::svn_repos_db_env(self.0.as_mut_ptr(), pool.as_mut_ptr()) }; let db_env = unsafe { std::ffi::CStr::from_ptr(ret) }; std::path::PathBuf::from(db_env.to_str().unwrap()) } pub fn conf_dir(&mut self) -> std::path::PathBuf { let pool = apr::Pool::new(); let ret = unsafe { crate::generated::svn_repos_conf_dir(self.0.as_mut_ptr(), pool.as_mut_ptr()) }; let conf_dir = unsafe { std::ffi::CStr::from_ptr(ret) }; std::path::PathBuf::from(conf_dir.to_str().unwrap()) } pub fn svnserve_conf(&mut self) -> std::path::PathBuf { let pool = apr::Pool::new(); let ret = unsafe { crate::generated::svn_repos_svnserve_conf(self.0.as_mut_ptr(), pool.as_mut_ptr()) }; let svnserve_conf = unsafe { std::ffi::CStr::from_ptr(ret) }; std::path::PathBuf::from(svnserve_conf.to_str().unwrap()) } pub fn lock_dir(&mut self) -> std::path::PathBuf { let pool = apr::Pool::new(); let ret = unsafe { crate::generated::svn_repos_lock_dir(self.0.as_mut_ptr(), pool.as_mut_ptr()) }; let lock_dir = unsafe { std::ffi::CStr::from_ptr(ret) }; std::path::PathBuf::from(lock_dir.to_str().unwrap()) } pub fn db_lockfile(&mut self) -> std::path::PathBuf { let pool = apr::Pool::new(); let ret = unsafe { crate::generated::svn_repos_db_lockfile(self.0.as_mut_ptr(), pool.as_mut_ptr()) }; let db_lockfile = unsafe { std::ffi::CStr::from_ptr(ret) }; std::path::PathBuf::from(db_lockfile.to_str().unwrap()) } pub fn db_logs_lockfile(&mut self) -> std::path::PathBuf { let pool = apr::Pool::new(); let ret = unsafe { crate::generated::svn_repos_db_logs_lockfile(self.0.as_mut_ptr(), pool.as_mut_ptr()) }; let logs_lockfile = unsafe { std::ffi::CStr::from_ptr(ret) }; std::path::PathBuf::from(logs_lockfile.to_str().unwrap()) } pub fn hook_dir(&mut self) -> std::path::PathBuf { let pool = apr::Pool::new(); let ret = unsafe { crate::generated::svn_repos_hook_dir(self.0.as_mut_ptr(), pool.as_mut_ptr()) }; let hook_dir = unsafe { std::ffi::CStr::from_ptr(ret) }; std::path::PathBuf::from(hook_dir.to_str().unwrap()) } pub fn load_fs( &mut self, dumpstream: &mut crate::io::Stream, start_rev: Revnum, end_rev: Revnum, uuid_action: LoadUUID, parent_dir: &std::path::Path, use_pre_commit_hook: bool, use_post_commit_hook: bool, cancel_check: Option<&impl Fn() -> bool>, validate_props: bool, ignore_dates: bool, normalize_props: bool, notify_func: Option<&impl Fn(&Notify)>, ) -> Result<(), Error> { let pool = apr::Pool::new(); let ret = unsafe { crate::generated::svn_repos_load_fs6( self.0.as_mut_ptr(), dumpstream.as_mut_ptr(), start_rev.0, end_rev.0, uuid_action.into(), parent_dir.to_str().unwrap().as_ptr() as *const i8, use_pre_commit_hook.into(), use_post_commit_hook.into(), validate_props.into(), ignore_dates.into(), normalize_props.into(), if notify_func.is_some() { Some(wrap_notify_func) } else { None }, notify_func .map(|notify_func| { Box::into_raw(Box::new(notify_func)) as *mut std::ffi::c_void }) .unwrap_or(std::ptr::null_mut()), if cancel_check.is_some() { Some(crate::wrap_cancel_func) } else { None }, cancel_check .map(|cancel_check| { Box::into_raw(Box::new(cancel_check)) as *mut std::ffi::c_void }) .unwrap_or(std::ptr::null_mut()), pool.as_mut_ptr(), ) }; Error::from_raw(ret)?; Ok(()) } pub fn verify_fs( &mut self, start_rev: Revnum, end_rev: Revnum, check_normalization: bool, metadata_only: bool, notify_func: Option<&impl Fn(&Notify)>, callback_func: &impl Fn(Revnum, &Error) -> Result<(), Error>, cancel_func: Option<&impl Fn() -> Result<(), Error>>, ) -> Result<(), Error> { extern "C" fn verify_callback( baton: *mut std::ffi::c_void, revision: crate::generated::svn_revnum_t, verify_err: *mut crate::generated::svn_error_t, _pool: *mut apr::apr_pool_t, ) -> *mut crate::generated::svn_error_t { let baton = unsafe { &mut *(baton as *mut Box Result<(), Error>>) }; let verify_err = Error::from_raw(verify_err).unwrap_err(); match baton(Revnum::from_raw(revision).unwrap(), &verify_err) { Ok(()) => std::ptr::null_mut(), Err(e) => unsafe { e.into_raw() }, } } let pool = apr::Pool::new(); let ret = unsafe { crate::generated::svn_repos_verify_fs3( self.0.as_mut_ptr(), start_rev.0, end_rev.0, check_normalization.into(), metadata_only.into(), if notify_func.is_some() { Some(wrap_notify_func) } else { None }, notify_func .map(|notify_func| { Box::into_raw(Box::new(notify_func)) as *mut std::ffi::c_void }) .unwrap_or(std::ptr::null_mut()), Some(verify_callback), Box::into_raw(Box::new(callback_func)) as *mut std::ffi::c_void, if cancel_func.is_some() { Some(crate::wrap_cancel_func) } else { None }, cancel_func .map(|cancel_func| { Box::into_raw(Box::new(cancel_func)) as *mut std::ffi::c_void }) .unwrap_or(std::ptr::null_mut()), pool.as_mut_ptr(), ) }; Error::from_raw(ret)?; Ok(()) } pub fn pack_fs( &mut self, notify_func: Option<&impl Fn(&Notify)>, cancel_func: Option<&impl Fn() -> Result<(), Error>>, ) -> Result<(), Error> { let pool = apr::Pool::new(); let ret = unsafe { crate::generated::svn_repos_fs_pack2( self.0.as_mut_ptr(), if notify_func.is_some() { Some(wrap_notify_func) } else { None }, notify_func .map(|notify_func| { Box::into_raw(Box::new(notify_func)) as *mut std::ffi::c_void }) .unwrap_or(std::ptr::null_mut()), if cancel_func.is_some() { Some(crate::wrap_cancel_func) } else { None }, cancel_func .map(|cancel_func| { Box::into_raw(Box::new(cancel_func)) as *mut std::ffi::c_void }) .unwrap_or(std::ptr::null_mut()), pool.as_mut_ptr(), ) }; Error::from_raw(ret)?; Ok(()) } } pub fn recover( path: &str, nonblocking: bool, notify_func: Option<&impl Fn(&Notify)>, cance_func: Option<&impl Fn() -> Result<(), Error>>, ) -> Result<(), Error> { let path = std::ffi::CString::new(path).unwrap(); let pool = apr::Pool::new(); let ret = unsafe { crate::generated::svn_repos_recover4( path.as_ptr(), nonblocking.into(), if notify_func.is_some() { Some(wrap_notify_func) } else { None }, notify_func .map(|notify_func| Box::into_raw(Box::new(notify_func)) as *mut std::ffi::c_void) .unwrap_or(std::ptr::null_mut()), if cance_func.is_some() { Some(crate::wrap_cancel_func) } else { None }, cance_func .map(|cancel_func| Box::into_raw(Box::new(cancel_func)) as *mut std::ffi::c_void) .unwrap_or(std::ptr::null_mut()), pool.as_mut_ptr(), ) }; Error::from_raw(ret)?; Ok(()) } extern "C" fn wrap_freeze_func( baton: *mut std::ffi::c_void, _pool: *mut apr::apr_pool_t, ) -> *mut crate::generated::svn_error_t { let freeze_func = unsafe { &*(baton as *const Box Result<(), Error>>) }; match freeze_func() { Ok(()) => std::ptr::null_mut(), Err(e) => unsafe { e.into_raw() }, } } pub fn freeze( paths: &[&str], freeze_func: Option<&impl Fn(&str) -> Result<(), Error>>, ) -> Result<(), Error> { let paths = paths .iter() .map(|p| std::ffi::CString::new(*p).unwrap()) .collect::>(); let paths_array = paths .iter() .map(|p| p.as_ptr()) .collect::>(); let pool = apr::Pool::new(); let ret = unsafe { crate::generated::svn_repos_freeze( paths_array.as_ptr(), if freeze_func.is_some() { Some(wrap_freeze_func) } else { None }, freeze_func .map(|freeze_func| Box::into_raw(Box::new(freeze_func)) as *mut std::ffi::c_void) .unwrap_or(std::ptr::null_mut()), pool.as_mut_ptr(), ) }; Error::from_raw(ret)?; Ok(()) } #[allow(dead_code)] pub struct Notify(PooledPtr); extern "C" fn wrap_notify_func( baton: *mut std::ffi::c_void, notify: *const crate::generated::svn_repos_notify_t, pool: *mut apr::apr_pool_t, ) { let pool = apr::Pool::from_raw(pool); let notify = unsafe { &*notify }; let baton = unsafe { &mut *(baton as *mut Box) }; unsafe { baton(&Notify(PooledPtr::in_pool( std::rc::Rc::new(pool), notify as *const _ as *mut _, ))); } } pub fn upgrade( path: &std::path::Path, nonblocking: bool, notify_func: Option<&mut dyn FnMut(&Notify)>, ) -> Result<(), Error> { let path = std::ffi::CString::new(path.to_str().unwrap()).unwrap(); let notify_func = notify_func.map(|_notify_func| wrap_notify_func as _); let notify_baton = Box::into_raw(Box::new(notify_func)).cast(); let pool = apr::Pool::new(); let ret = unsafe { crate::generated::svn_repos_upgrade2( path.as_ptr(), nonblocking as i32, notify_func, notify_baton, pool.as_mut_ptr(), ) }; Error::from_raw(ret)?; Ok(()) } pub fn delete(path: &std::path::Path) -> Result<(), Error> { let path = std::ffi::CString::new(path.to_str().unwrap()).unwrap(); let pool = apr::Pool::new(); let ret = unsafe { crate::generated::svn_repos_delete(path.as_ptr(), pool.as_mut_ptr()) }; Error::from_raw(ret)?; Ok(()) } pub fn version() -> crate::Version { unsafe { crate::Version(crate::generated::svn_repos_version()) } } pub fn hotcopy( src_path: &str, dst_path: &str, clean_logs: bool, incremental: bool, notify_func: Option<&impl Fn(&Notify)>, cancel_func: Option<&impl Fn() -> Result<(), Error>>, ) -> Result<(), Error> { let src_path = std::ffi::CString::new(src_path).unwrap(); let dst_path = std::ffi::CString::new(dst_path).unwrap(); let pool = apr::Pool::new(); let ret = unsafe { crate::generated::svn_repos_hotcopy3( src_path.as_ptr(), dst_path.as_ptr(), clean_logs.into(), incremental.into(), if notify_func.is_some() { Some(wrap_notify_func) } else { None }, notify_func .map(|notify_func| Box::into_raw(Box::new(notify_func)) as *mut std::ffi::c_void) .unwrap_or(std::ptr::null_mut()), if cancel_func.is_some() { Some(crate::wrap_cancel_func) } else { None }, cancel_func .map(|cancel_func| Box::into_raw(Box::new(cancel_func)) as *mut std::ffi::c_void) .unwrap_or(std::ptr::null_mut()), pool.as_mut_ptr(), ) }; Error::from_raw(ret)?; Ok(()) } #[cfg(test)] mod tests { #[test] fn test_create_delete() { let td = tempfile::tempdir().unwrap(); super::Repos::create(td.path()).unwrap(); super::Repos::open(td.path()).unwrap(); super::delete(td.path()).unwrap(); } #[test] fn test_capabilities() { let td = tempfile::tempdir().unwrap(); let mut repos = super::Repos::create(td.path()).unwrap(); assert!(repos.capabilities().unwrap().contains("mergeinfo")); assert!(!repos.capabilities().unwrap().contains("mergeinfo2")); assert!(repos.has_capability("mergeinfo").unwrap()); assert!(repos.has_capability("unknown").is_err()); } #[test] fn test_version() { assert!(super::version().major() >= 1); } } subversion-0.0.8/src/string.rs000064400000000000000000000034541046102023000145010ustar 00000000000000use crate::generated::svn_string_t; use apr::pool::PooledPtr; pub struct String(PooledPtr); impl String { pub fn as_ptr(&self) -> *const svn_string_t { self.0.as_ptr() } pub fn as_mut_ptr(&mut self) -> *mut svn_string_t { self.0.as_mut_ptr() } pub fn as_slice(&self) -> &[u8] { let ptr = unsafe { (*self.as_ptr()).data }; let len = unsafe { (*self.as_ptr()).len as usize }; unsafe { std::slice::from_raw_parts(ptr as *const u8, len) } } } impl From for Vec { fn from(s: String) -> Self { s.as_slice().to_vec() } } impl From> for String { fn from(ptr: PooledPtr) -> Self { String(ptr) } } impl From<&str> for String { fn from(s: &str) -> Self { let ptr = apr::pool::PooledPtr::initialize(|pool| { let cstr = s.as_ptr(); let len = s.len(); let ptr = unsafe { crate::generated::svn_string_ncreate(cstr as *const i8, len, pool.as_mut_ptr()) }; Ok::<_, crate::Error>(ptr) }) .unwrap(); String(ptr) } } impl From for String { fn from(s: std::string::String) -> Self { s.as_str().into() } } impl From<&[u8]> for String { fn from(s: &[u8]) -> Self { let ptr = apr::pool::PooledPtr::initialize(|pool| { let cstr = s.as_ptr(); let len = s.len(); let ptr = unsafe { crate::generated::svn_string_ncreate(cstr as *const i8, len, pool.as_mut_ptr()) }; Ok::<_, crate::Error>(ptr) }) .unwrap(); String(ptr) } } impl From> for String { fn from(s: Vec) -> Self { s.as_slice().into() } } subversion-0.0.8/src/time.rs000064400000000000000000000026211046102023000141240ustar 00000000000000use crate::generated::{svn_time_from_cstring, svn_time_to_cstring, svn_time_to_human_cstring}; use crate::Error; use apr::time::Time; pub fn to_cstring(time: Time) -> String { let x = unsafe { svn_time_to_cstring(time.into(), apr::pool::Pool::new().as_mut_ptr()) }; let s = unsafe { std::ffi::CStr::from_ptr(x) }; s.to_string_lossy().into_owned() } pub fn from_cstring(s: &str) -> Result { let mut t = apr::apr_time_t::default(); let s = std::ffi::CString::new(s).unwrap(); let err = unsafe { svn_time_from_cstring(&mut t, s.as_ptr(), apr::pool::Pool::new().as_mut_ptr()) }; Error::from_raw(err)?; Ok(Time::from(t)) } pub fn to_human_cstring(time: Time) -> String { let x = unsafe { svn_time_to_human_cstring(time.into(), apr::pool::Pool::new().as_mut_ptr()) }; let s = unsafe { std::ffi::CStr::from_ptr(x) }; s.to_string_lossy().into_owned() } pub fn parse_date(now: Time, date: &str) -> Result<(bool, Time), crate::Error> { let mut t = apr::apr_time_t::default(); let mut matched: i32 = 0; let date = std::ffi::CString::new(date).unwrap(); let err = unsafe { crate::generated::svn_parse_date( &mut matched, &mut t, date.as_ptr(), now.into(), apr::pool::Pool::new().as_mut_ptr(), ) }; Error::from_raw(err)?; Ok((matched != 0, Time::from(t))) } subversion-0.0.8/src/uri.rs000064400000000000000000000164671046102023000140020ustar 00000000000000use crate::dirent::Dirent; use crate::Canonical; use crate::Error; use apr::pool::Pooled; pub struct Uri<'a>(*const i8, std::marker::PhantomData<&'a ()>); impl ToString for Uri<'_> { fn to_string(&self) -> String { let t = unsafe { std::ffi::CStr::from_ptr(self.as_ptr()) }; t.to_str().unwrap().to_string() } } impl std::fmt::Debug for Uri<'_> { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_tuple("Uri").field(&self.to_string()).finish() } } impl PartialEq for Uri<'_> { fn eq(&self, other: &Self) -> bool { self.to_string() == other.to_string() } } impl Eq for Uri<'_> {} impl<'a> Uri<'a> { pub fn from_raw(raw: *const i8) -> Self { Self(raw, std::marker::PhantomData) } pub fn from_cstr(cstr: &std::ffi::CStr) -> Self { Self(cstr.as_ptr(), std::marker::PhantomData) } pub fn pooled(s: &str) -> Pooled { Pooled::initialize(|pool| { Ok::<_, crate::Error>(Self( apr::strings::pstrdup(s, pool).as_ptr() as *const i8, std::marker::PhantomData, )) }) .unwrap() } pub fn is_root(&self, length: usize) -> bool { unsafe { crate::generated::svn_uri_is_root(self.as_ptr(), length) != 0 } } pub fn as_ptr(&self) -> *const i8 { self.0 } pub fn dirname(&self) -> Pooled { Pooled::initialize(|pool| unsafe { Ok::<_, crate::Error>(Self( crate::generated::svn_uri_dirname(self.as_ptr(), pool.as_mut_ptr()) as *const i8, std::marker::PhantomData, )) }) .unwrap() } pub fn basename(&self) -> Pooled { Pooled::initialize(|pool| unsafe { Ok::<_, crate::Error>(Self( crate::generated::svn_uri_basename(self.as_ptr(), pool.as_mut_ptr()), std::marker::PhantomData, )) }) .unwrap() } pub fn split(&self) -> (Pooled, Pooled) { let pool = apr::pool::Pool::new(); unsafe { let mut dirname = std::ptr::null(); let mut basename = std::ptr::null(); crate::generated::svn_uri_split( &mut dirname, &mut basename, self.as_ptr(), pool.as_mut_ptr(), ); let pool = std::rc::Rc::new(pool); ( Pooled::in_pool(pool.clone(), Self(dirname, std::marker::PhantomData)), Pooled::in_pool(pool, Self(basename, std::marker::PhantomData)), ) } } pub fn canonicalize(&self) -> Pooled> { Pooled::initialize(|pool| unsafe { Ok::<_, crate::Error>(Canonical(Self( crate::generated::svn_uri_canonicalize(self.as_ptr(), pool.as_mut_ptr()), std::marker::PhantomData, ))) }) .unwrap() } pub fn canonicalize_in<'b>(&'_ self, pool: &'b mut apr::pool::Pool) -> Canonical where 'a: 'b, { Canonical(Self( unsafe { crate::generated::svn_uri_canonicalize(self.as_ptr(), pool.as_mut_ptr()) }, std::marker::PhantomData, )) } pub fn canonicalize_safe( &self, ) -> Result<(Pooled>, Pooled), crate::Error> { let pool = apr::pool::Pool::new(); unsafe { let mut canonical = std::ptr::null(); let mut non_canonical = std::ptr::null(); let err = crate::generated::svn_uri_canonicalize_safe( &mut canonical, &mut non_canonical, self.as_ptr(), pool.as_mut_ptr(), apr::pool::Pool::new().as_mut_ptr(), ); Error::from_raw(err)?; let pool = std::rc::Rc::new(pool); Ok(( Pooled::in_pool( pool.clone(), Canonical(Self(canonical, std::marker::PhantomData)), ), Pooled::in_pool(pool, Self(non_canonical, std::marker::PhantomData)), )) } } pub fn is_canonical(&self) -> bool { unsafe { crate::generated::svn_uri_is_canonical( self.as_ptr(), apr::pool::Pool::new().as_mut_ptr(), ) != 0 } } pub fn check_canonical(self) -> Option> { if self.is_canonical() { Some(Canonical(self)) } else { None } } fn to_dirent<'b>(&self) -> Result>, crate::Error> { Pooled::initialize(|pool| unsafe { let mut dirent = std::ptr::null(); let err = crate::generated::svn_uri_get_dirent_from_file_url( &mut dirent, self.as_ptr(), pool.as_mut_ptr(), ); Error::from_raw(err)?; Ok::<_, Error>(Dirent::from_raw(dirent)) }) } } impl<'a, 'b> TryFrom>> for Pooled> { type Error = crate::Error; fn try_from(uri: Canonical>) -> Result>, Self::Error> { uri.to_dirent() } } impl<'a> From<&'a str> for Uri<'a> { fn from(s: &'a str) -> Self { Self( std::ffi::CString::new(s).unwrap().into_raw(), std::marker::PhantomData, ) } } impl<'a> From<&Uri<'a>> for &'a str { fn from(uri: &Uri<'a>) -> Self { let t = unsafe { std::ffi::CStr::from_ptr(uri.as_ptr()) }; t.to_str().unwrap() } } #[cfg(feature = "url")] impl TryFrom> for url::Url { type Error = url::ParseError; fn try_from(uri: Uri) -> Result { let uri = uri.to_string(); Ok(url::Url::parse(&uri)?) } } pub trait AsCanonicalUri<'a> { fn as_canonical_uri(self, scratch_pool: &mut apr::pool::Pool) -> Canonical>; } impl<'a> AsCanonicalUri<'a> for Uri<'a> { fn as_canonical_uri(self, scratch_pool: &mut apr::pool::Pool) -> Canonical> { self.canonicalize_in(scratch_pool) } } impl<'a> AsCanonicalUri<'a> for Canonical> { fn as_canonical_uri(self, _scratch_pool: &mut apr::pool::Pool) -> Canonical> { self } } #[cfg(feature = "url")] impl<'a> AsCanonicalUri<'a> for url::Url { fn as_canonical_uri(self, scratch_pool: &mut apr::pool::Pool) -> Canonical> { Uri::pooled(self.as_str()).canonicalize_in(scratch_pool) } } #[cfg(feature = "url")] impl<'a> AsCanonicalUri<'a> for &'a url::Url { fn as_canonical_uri(self, scratch_pool: &mut apr::pool::Pool) -> Canonical> { Uri::pooled(self.as_str()).canonicalize_in(scratch_pool) } } impl<'a> AsCanonicalUri<'a> for &'a str { fn as_canonical_uri(self, scratch_pool: &mut apr::pool::Pool) -> Canonical> { Uri::pooled(self).canonicalize_in(scratch_pool) } } impl<'a> AsCanonicalUri<'a> for Pooled> { fn as_canonical_uri(self, scratch_pool: &mut apr::pool::Pool) -> Canonical> { self.canonicalize_in(scratch_pool) } } impl<'a> AsCanonicalUri<'a> for Pooled>> { fn as_canonical_uri(self, _scratch_pool: &mut apr::pool::Pool) -> Canonical> { Canonical(Uri(self.0 .0, std::marker::PhantomData)) } } subversion-0.0.8/src/version.rs000064400000000000000000000020211046102023000146450ustar 00000000000000use crate::generated::svn_version_t; pub struct Version(pub(crate) *const svn_version_t); impl Version { fn equal(&self, other: &Version) -> bool { !matches!( unsafe { crate::generated::svn_ver_equal(self.0, other.0) }, 0 ) } pub fn compatible(&self, other: &Version) -> bool { !matches!( unsafe { crate::generated::svn_ver_compatible(self.0, other.0) }, 0 ) } pub fn major(&self) -> i32 { unsafe { self.0.as_ref().unwrap().major } } pub fn minor(&self) -> i32 { unsafe { self.0.as_ref().unwrap().minor } } pub fn patch(&self) -> i32 { unsafe { self.0.as_ref().unwrap().patch } } pub fn tag(&self) -> &str { unsafe { let tag = self.0.as_ref().unwrap().tag; std::ffi::CStr::from_ptr(tag).to_str().unwrap() } } } impl PartialEq for Version { fn eq(&self, other: &Version) -> bool { self.equal(other) } } impl Eq for Version {} subversion-0.0.8/src/wc.rs000064400000000000000000000122721046102023000136020ustar 00000000000000use crate::generated::{svn_wc_context_t, svn_wc_version}; use crate::Error; use apr::pool::PooledPtr; pub fn version() -> crate::Version { unsafe { crate::Version(svn_wc_version()) } } pub struct Context(apr::pool::PooledPtr); impl Context { pub fn new() -> Result { Ok(Self(PooledPtr::initialize(|pool| { let mut ctx = std::ptr::null_mut(); let err = unsafe { crate::generated::svn_wc_context_create( &mut ctx, std::ptr::null_mut(), pool.as_mut_ptr(), apr::pool::Pool::new().as_mut_ptr(), ) }; Error::from_raw(err)?; Ok::<_, Error>(ctx) })?)) } pub fn check_wc(&mut self, path: &str) -> Result { let path = std::ffi::CString::new(path).unwrap(); let mut wc_format = 0; let err = unsafe { crate::generated::svn_wc_check_wc2( &mut wc_format, self.0.as_mut_ptr(), path.as_ptr(), apr::pool::Pool::new().as_mut_ptr(), ) }; Error::from_raw(err)?; Ok(wc_format) } pub fn text_modified(&mut self, path: &str) -> Result { let path = std::ffi::CString::new(path).unwrap(); let mut modified = 0; let err = unsafe { crate::generated::svn_wc_text_modified_p2( &mut modified, self.0.as_mut_ptr(), path.as_ptr(), 0, apr::pool::Pool::new().as_mut_ptr(), ) }; Error::from_raw(err)?; Ok(modified != 0) } pub fn props_modified(&mut self, path: &str) -> Result { let path = std::ffi::CString::new(path).unwrap(); let mut modified = 0; let err = unsafe { crate::generated::svn_wc_props_modified_p2( &mut modified, self.0.as_mut_ptr(), path.as_ptr(), apr::pool::Pool::new().as_mut_ptr(), ) }; Error::from_raw(err)?; Ok(modified != 0) } pub fn conflicted(&mut self, path: &str) -> Result<(bool, bool, bool), crate::Error> { let path = std::ffi::CString::new(path).unwrap(); let mut text_conflicted = 0; let mut prop_conflicted = 0; let mut tree_conflicted = 0; let err = unsafe { crate::generated::svn_wc_conflicted_p3( &mut text_conflicted, &mut prop_conflicted, &mut tree_conflicted, self.0.as_mut_ptr(), path.as_ptr(), apr::pool::Pool::new().as_mut_ptr(), ) }; Error::from_raw(err)?; Ok(( text_conflicted != 0, prop_conflicted != 0, tree_conflicted != 0, )) } pub fn ensure_adm( &mut self, local_abspath: &str, url: &str, repos_root_url: &str, repos_uuid: &str, revision: crate::Revnum, depth: crate::Depth, ) -> Result<(), crate::Error> { let local_abspath = std::ffi::CString::new(local_abspath).unwrap(); let url = std::ffi::CString::new(url).unwrap(); let repos_root_url = std::ffi::CString::new(repos_root_url).unwrap(); let repos_uuid = std::ffi::CString::new(repos_uuid).unwrap(); let err = unsafe { crate::generated::svn_wc_ensure_adm4( self.0.as_mut_ptr(), local_abspath.as_ptr(), url.as_ptr(), repos_root_url.as_ptr(), repos_uuid.as_ptr(), revision.0, depth.into(), apr::pool::Pool::new().as_mut_ptr(), ) }; Error::from_raw(err)?; Ok(()) } pub fn locked(&mut self, path: &str) -> Result<(bool, bool), crate::Error> { let path = std::ffi::CString::new(path).unwrap(); let mut locked = 0; let mut locked_here = 0; let scratch_pool = apr::pool::Pool::new(); let err = unsafe { crate::generated::svn_wc_locked2( &mut locked_here, &mut locked, self.0.as_mut_ptr(), path.as_ptr(), scratch_pool.as_mut_ptr(), ) }; Error::from_raw(err)?; Ok((locked != 0, locked_here != 0)) } } impl Drop for Context { fn drop(&mut self) { unsafe { crate::generated::svn_wc_context_destroy(self.0.as_mut_ptr()); } } } pub fn set_adm_dir(name: &str) -> Result<(), crate::Error> { let name = std::ffi::CString::new(name).unwrap(); let err = unsafe { crate::generated::svn_wc_set_adm_dir(name.as_ptr(), apr::pool::Pool::new().as_mut_ptr()) }; Error::from_raw(err)?; Ok(()) } pub fn get_adm_dir() -> String { let pool = apr::pool::Pool::new(); let name = unsafe { crate::generated::svn_wc_get_adm_dir(pool.as_mut_ptr()) }; unsafe { std::ffi::CStr::from_ptr(name) } .to_string_lossy() .into_owned() }