tame-index-0.23.1/.cargo/config.toml000064400000000000000000000055121046102023000152600ustar 00000000000000# add the below section to `.cargo/config.toml` [target.'cfg(all())'] rustflags = [ # BEGIN - Embark standard lints v6 for Rust 1.55+ # do not change or add/remove here, but one can add exceptions after this section # for more info see: "-Dunsafe_code", "-Wclippy::all", "-Wclippy::await_holding_lock", "-Wclippy::char_lit_as_u8", "-Wclippy::checked_conversions", "-Wclippy::dbg_macro", "-Wclippy::debug_assert_with_mut_call", "-Wclippy::doc_markdown", "-Wclippy::empty_enum", "-Wclippy::enum_glob_use", "-Wclippy::exit", "-Wclippy::expl_impl_clone_on_copy", "-Wclippy::explicit_deref_methods", "-Wclippy::explicit_into_iter_loop", "-Wclippy::fallible_impl_from", "-Wclippy::filter_map_next", "-Wclippy::flat_map_option", "-Wclippy::float_cmp_const", "-Wclippy::fn_params_excessive_bools", "-Wclippy::from_iter_instead_of_collect", "-Wclippy::if_let_mutex", "-Wclippy::implicit_clone", "-Wclippy::imprecise_flops", "-Wclippy::inefficient_to_string", "-Wclippy::invalid_upcast_comparisons", "-Wclippy::large_digit_groups", "-Wclippy::large_stack_arrays", "-Wclippy::large_types_passed_by_value", "-Wclippy::let_unit_value", "-Wclippy::linkedlist", "-Wclippy::lossy_float_literal", "-Wclippy::macro_use_imports", "-Wclippy::manual_ok_or", "-Wclippy::map_err_ignore", "-Wclippy::map_flatten", "-Wclippy::map_unwrap_or", "-Wclippy::match_same_arms", "-Wclippy::match_wild_err_arm", "-Wclippy::match_wildcard_for_single_variants", "-Wclippy::mem_forget", "-Wclippy::missing_enforced_import_renames", "-Wclippy::mut_mut", "-Wclippy::mutex_integer", "-Wclippy::needless_borrow", "-Wclippy::needless_continue", "-Wclippy::needless_for_each", "-Wclippy::option_option", "-Wclippy::path_buf_push_overwrite", "-Wclippy::ptr_as_ptr", "-Wclippy::rc_mutex", "-Wclippy::ref_option_ref", "-Wclippy::rest_pat_in_fully_bound_structs", "-Wclippy::same_functions_in_if_condition", "-Wclippy::semicolon_if_nothing_returned", "-Wclippy::single_match_else", "-Wclippy::string_add_assign", "-Wclippy::string_add", "-Wclippy::string_lit_as_bytes", "-Wclippy::string_to_string", "-Wclippy::todo", "-Wclippy::trait_duplication_in_bounds", "-Wclippy::unimplemented", "-Wclippy::unnested_or_patterns", "-Wclippy::unused_self", "-Wclippy::useless_transmute", "-Wclippy::verbose_file_reads", "-Wclippy::zero_sized_map_values", "-Wfuture_incompatible", "-Wnonstandard_style", "-Wrust_2018_idioms", "-Wunexpected_cfgs", # END - Embark standard lints v6 for Rust 1.55+ # We want all public items to have at least _some_ documentation "-Dmissing-docs", ] tame-index-0.23.1/.cargo_vcs_info.json0000644000000001360000000000100131520ustar { "git": { "sha1": "e228d3aad621d7df407f6fb38bae95b53ff05c8d" }, "path_in_vcs": "" }tame-index-0.23.1/.github/CODEOWNERS000064400000000000000000000000171046102023000146730ustar 00000000000000* @Jake-Shadle tame-index-0.23.1/.github/ISSUE_TEMPLATE/bug_report.md000064400000000000000000000011551046102023000201610ustar 00000000000000--- name: Bug report about: Create a report to help us improve title: '' labels: bug assignees: '' --- **Describe the bug** A clear and concise description of what the bug is. **To Reproduce** Steps to reproduce the behavior: 1. Go to '...' 2. Click on '....' 3. Scroll down to '....' 4. See error **Expected behavior** A clear and concise description of what you expected to happen. **Screenshots** If applicable, add screenshots to help explain your problem. **Device:** - OS: [e.g. iOS] - Browser [e.g. chrome, safari] - Version [e.g. 22] **Additional context** Add any other context about the problem here. tame-index-0.23.1/.github/ISSUE_TEMPLATE/feature_request.md000064400000000000000000000011341046102023000212110ustar 00000000000000--- name: Feature request about: Suggest an idea for this project title: '' labels: enhancement assignees: '' --- **Is your feature request related to a problem? Please describe.** A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] **Describe the solution you'd like** A clear and concise description of what you want to happen. **Describe alternatives you've considered** A clear and concise description of any alternative solutions or features you've considered. **Additional context** Add any other context or screenshots about the feature request here. tame-index-0.23.1/.github/pull_request_template.md000064400000000000000000000005661046102023000202520ustar 00000000000000### Checklist * [ ] I have read the [Contributor Guide](../blob/main/CONTRIBUTING.md) * [ ] I have read and agree to the [Code of Conduct](../blob/main/CODE_OF_CONDUCT.md) * [ ] I have added a description of my changes and why I'd like them included in the section below ### Description of Changes Describe your changes here ### Related Issues List related issues here tame-index-0.23.1/.github/workflows/rust-ci.yml000064400000000000000000000042151046102023000174520ustar 00000000000000on: push: branches: - main tags: - "*" pull_request: name: CI jobs: lint: name: Lint strategy: matrix: os: - ubuntu-22.04 - windows-2022 runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@stable with: components: "clippy, rustfmt" - uses: Swatinem/rust-cache@v2 # make sure all code has been formatted with rustfmt - name: check rustfmt run: cargo fmt -- --check --color always # run clippy to verify we have no warnings - run: cargo fetch - name: cargo clippy # Unfortunately we can't use --all-features since that would enable request and curl for gix which is not allowed run: cargo clippy --all-targets --features __internal_all -- -D warnings - name: sigh run: cargo clippy --all-targets --features gix-curl,sparse,local-builder -- -D warnings test: name: Test strategy: matrix: os: - ubuntu-22.04 features: ["--features git", "--features sparse", "--features local-builder,sparse"] runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@stable - run: cargo fetch - name: cargo test build run: cargo build --tests ${{ matrix.features }} - name: cargo test run: cargo test ${{ matrix.features }} - name: Test connection reuse if: ${{ matrix.features == '--features sparse' }} run: | cargo run --manifest-path tests/connect/Cargo.toml deny-check: name: cargo-deny runs-on: ubuntu-22.04 steps: - uses: actions/checkout@v4 - uses: EmbarkStudios/cargo-deny-action@v2 publish-check: name: Publish Check runs-on: ubuntu-22.04 steps: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@stable - run: cargo fetch - name: cargo publish check run: cargo publish --dry-run test_success: runs-on: ubuntu-22.04 needs: [lint, test, publish-check, deny-check] steps: - run: echo "All test jobs passed" tame-index-0.23.1/.gitignore000064400000000000000000000000631046102023000137310ustar 00000000000000**/target **/*.rs.bk Cargo.lock tests/flock/target tame-index-0.23.1/.mergify.yml000064400000000000000000000007001046102023000142020ustar 00000000000000pull_request_rules: - name: automatic merge when CI passes and 1 reviews conditions: - "#approved-reviews-by>=1" - "#review-requested=0" - "#changes-requested-reviews-by=0" - "#review-threads-unresolved=0" - base=main - label!=block-automerge actions: merge: method: squash - name: delete head branch after merge conditions: - merged actions: delete_head_branch: {} tame-index-0.23.1/CHANGELOG.md000064400000000000000000000476701046102023000135710ustar 00000000000000 # Changelog All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [Unreleased] - ReleaseDate ## [0.23.1] - 2025-10-11 ### Fixed - [PR#95](https://github.com/EmbarkStudios/tame-index/pull/95) resolved [#94](https://github.com/EmbarkStudios/tame-index/issues/94) by actually using an infinite timeout on Windows when timeout is `None`. ## [0.23.0] - 2025-08-08 ### Changed - [PR#93](https://github.com/EmbarkStudios/tame-index/pull/93) updated gix -> 0.73. ## [0.22.0] - 2025-06-11 ### Changed - [PR#90](https://github.com/EmbarkStudios/tame-index/pull/90) updated the `IndexConfig` to match the current cargo schema, thanks [nox](https://github.com/nox)! - [PR#91](https://github.com/EmbarkStudios/tame-index/pull/91) updated the source replacement code to be able to find replacements in relative paths from the project, thanks [kornelski](https://github.com/kornelski)! ## [0.21.0] - 2025-05-22 ### Changed - [PR#88](https://github.com/EmbarkStudios/tame-index/pull/88) updated gix -> 0.72. ## [0.20.1] - 2025-04-07 ### Fixed - [PR#87](https://github.com/EmbarkStudios/tame-index/pull/87) resolved [#86](https://github.com/EmbarkStudios/tame-index/issues/86) by adding a missing `unsafe` that caused the Windows target to fail to compile. ## [0.20.0] - 2025-04-07 ### Fixed - [PR#85](https://github.com/EmbarkStudios/tame-index/pull/85) updated gix -> 0.71 and tokio to 1.44.2, addressing [RUSTSEC-2025-0021](https://rustsec.org/advisories/RUSTSEC-2025-0021) and [RUSTSEC-2025-0023](https://rustsec.org/advisories/RUSTSEC-2025-0023). ## [0.19.0] - 2025-04-03 ### Changed - [PR#84](https://github.com/EmbarkStudios/tame-index/pull/84) updated crates, and moved to edition 2024. ## [0.18.1] - 2025-02-24 ### Fixed - [PR#83](https://github.com/EmbarkStudios/tame-index/pull/83) adds an additional fix for non-crates.io urls not fixed in [PR#82](https://github.com/EmbarkStudios/tame-index/pull/82) as cargo now canonicalizes all URLs. ## [0.18.0] - 2025-02-20 ### Changed - [PR#82](https://github.com/EmbarkStudios/tame-index/pull/82) resolved [#81](https://github.com/EmbarkStudios/tame-index/issues/81), updating the hash calculation to match cargo 1.85.0. The decision of the hash calculation is based on the cargo version, which can be specified by the user via `IndexLocation::cargo_version`, defaulting to retrieving the version from the current environment if not specified allowing the calculation to work regardless of which cargo version is used. - [PR#82](https://github.com/EmbarkStudios/tame-index/pull/82) added a re-export of `semver::Version`. ## [0.17.0] - 2025-01-19 ### Changed - [PR#75](https://github.com/EmbarkStudios/tame-index/pull/78) updated `gix` -> 0.70. - [PR#80](https://github.com/EmbarkStudios/tame-index/pull/80) updated dependencies. ## [0.16.0] - 2024-11-28 ### Changed - [PR#75](https://github.com/EmbarkStudios/tame-index/pull/75) updated `gix` -> 0.68. ## [0.15.0] - 2024-11-11 ### Changed - [PR#74](https://github.com/EmbarkStudios/tame-index/pull/74) updated `gix` -> 0.67. ### Fixed - [PR#74](https://github.com/EmbarkStudios/tame-index/pull/74) fixed a bug where the list of reserved crate names was not sorted correctly for binary searching. ## [0.14.0] - 2024-09-20 ### Changed - [PR#73](https://github.com/EmbarkStudios/tame-index/pull/73) updated `gix` -> 0.66. ## [0.13.2] - 2024-08-20 ### Fixed - [PR#71](https://github.com/EmbarkStudios/tame-index/pull/71) resolved [#70](https://github.com/EmbarkStudios/tame-index/issues/70) by using the correct feature flag for docs.rs. ### Changed - [PR#72](https://github.com/EmbarkStudios/tame-index/pull/72) updated crates. ## [0.13.1] - 2024-08-01 ### Fixed - [PR#69](https://github.com/EmbarkStudios/tame-index/pull/69) resolved an issue where 32-bit targets would have a different ident hash from what cargo would have due to cargo being target dependent in the hash calculation. ## [0.13.0] - 2024-07-25 ### Changed - [PR#67](https://github.com/EmbarkStudios/tame-index/pull/67) updated `gix` -> 0.64. ## [0.12.2] - 2024-07-22 ### Added - [PR#66](https://github.com/EmbarkStudios/tame-index/pull/66) added the `gix-curl` feature, which is mutually exclusive with the `git` and `gix-reqwest` features. ## [0.12.1] - 2024-06-26 ### Changed - [PR#65](https://github.com/EmbarkStudios/tame-index/pull/65) updated `toml-span` -> 0.3.0. ## [0.12.0] - 2024-05-24 ### Changed - [PR#64](https://github.com/EmbarkStudios/tame-index/pull/64) updated `gix` -> 0.63. ## [0.11.1] - 2024-05-03 - [PR#61](https://github.com/EmbarkStudios/tame-index/pull/61) addressed [#60](https://github.com/EmbarkStudios/tame-index/issues/60) by adding `IndexUrl::for_registry_name` to read a registry's index url from the config/environment. ## [0.11.0] - 2024-04-23 ### Changed - [PR#59](https://github.com/EmbarkStudios/tame-index/pull/59) updated `gix` -> 0.62, `reqwest` -> 0.12, `http` -> 1.1. ## [0.10.0] - 2024-03-21 ### Changed - [PR#54](https://github.com/EmbarkStudios/tame-index/pull/54) updated `gix` -> 0.61. - [PR#53](https://github.com/EmbarkStudios/tame-index/pull/53) updated `gix` -> 0.60. ## [0.9.9] - 2024-03-21 ### Changed - [PR#54](https://github.com/EmbarkStudios/tame-index/pull/54) updated `gix` -> 0.61. ## [0.9.8] - 2024-03-17 ### Changed - [PR#53](https://github.com/EmbarkStudios/tame-index/pull/53) updated `gix` -> 0.60. ## [0.9.7] - 2024-03-12 ### Added - [PR#52](https://github.com/EmbarkStudios/tame-index/pull/52) added `ComboIndexCache::cache_path` to retrieve the path of a particular crate's index entry. ## [0.9.6] - 2024-03-12 ### Changed - [PR#51](https://github.com/EmbarkStudios/tame-index/pull/51) updated dependencies. ## [0.9.5] - 2024-02-22 ### Changed - [PR#49](https://github.com/EmbarkStudios/tame-index/pull/49) updated `toml-span` -> 0.2 ## [0.9.4] - 2024-02-20 ### Changed - [PR#48](https://github.com/EmbarkStudios/tame-index/pull/48) replaced `toml` with `toml-span` removing several dependencies. ## [0.9.3] - 2024-02-07 ### Fixed - [PR#47](https://github.com/EmbarkStudios/tame-index/pull/47) fixed [#46](https://github.com/EmbarkStudios/tame-index/issues/46) by ensuring one full DNS lookup and request response roundtrip is made before going wide to ensure that excessive DNS lookups and connections are not made. ## [0.9.2] - 2024-01-21 ### Changed - [PR#45](https://github.com/EmbarkStudios/tame-index/pull/45) bumped `gix` -> 0.58 ## [0.9.1] - 2024-01-12 ### Changed - [PR#44](https://github.com/EmbarkStudios/tame-index/pull/44) bumped `gix` -> 0.57 ## [0.9.0] - 2023-12-13 ### Fixed - [PR#43](https://github.com/EmbarkStudios/tame-index/pull/43) fixed the file lock options from `LockOptions::cargo_package_lock` to be `exclusive` to more closely match Cargo's behavior. This would not have been a problem in practice, but is more correct now. ### Changed - [PR#43](https://github.com/EmbarkStudios/tame-index/pull/43) bumped `gix` -> 0.56 ## [0.8.0] - 2023-11-06 ### Fixed - [PR#41](https://github.com/EmbarkStudios/tame-index/pull/41) resolved [#29](https://github.com/EmbarkStudios/tame-index/issues/29) by force disabling gpg signing in test. - Commit e3c6ff1 bumped the patch version of `windows-targets` to .5 to prevent using older versions that don't compile (See [#40](https://github.com/EmbarkStudios/tame-index/issues/40)) ### Changed - [PR#41](https://github.com/EmbarkStudios/tame-index/pull/41) bumped `gix` -> 0.55 ## [0.7.2] - 2023-10-18 ### Fixed - [PR#39](https://github.com/EmbarkStudios/tame-index/pull/39) resolved [#38](https://github.com/EmbarkStudios/tame-index/issues/38) by ensuring all parent directories are created before attempting a clone with `gix`. ## [0.7.1] - 2023-09-29 ### Fixed - [PR#34](https://github.com/EmbarkStudios/tame-index/pull/33) resolved a compile issue when targeting `musl` libc. ## [0.7.0] - 2023-09-29 ### Changed - [PR#32](https://github.com/EmbarkStudios/tame-index/pull/32) resolved [#31](https://github.com/EmbarkStudios/tame-index/issues/31) by reducing the size of `Error`. - [PR#33](https://github.com/EmbarkStudios/tame-index/pull/33) updated dependencies, notably `gix` -> 0.54. - [PR#33](https://github.com/EmbarkStudios/tame-index/pull/33) added a `tame_index::utils::flock::FileLock` parameter to all methods on indices that perform disk operations. ### Added - [PR#33](https://github.com/EmbarkStudios/tame-index/pull/33) added `tame_index::utils::flock`, which contains a `FileLock` for holding an OS file lock for a particular path, as well as `LockOptions` for creating them. ### Fixed - [PR#33](https://github.com/EmbarkStudios/tame-index/pull/33) resolved [#30](https://github.com/EmbarkStudios/tame-index/issues/30) by removing the usage of `gix::lock` in favor of the aforementioned `FileLock` - [PR#33](https://github.com/EmbarkStudios/tame-index/pull/33) resolved [#17](https://github.com/EmbarkStudios/tame-index/issues/17) by adding `LockOptions::cargo_package_lock` to easily create a lock file compatible with cargo's own ($CARGO_HOME global) package lock. ## [0.6.0] - 2023-09-11 ### Changed - [PR#27](https://github.com/EmbarkStudios/tame-index/pull/27) updated `gix` to 0.53.1. Thanks [@Byron](https://github.com/Byron)! ## [0.5.6] - 2023-09-11 **yanked** ### Changed - [PR#27](https://github.com/EmbarkStudios/tame-index/pull/27) updated `gix` to 0.53.1. Thanks [@Byron](https://github.com/Byron)! ## [0.5.5] - 2023-09-06 ### Changed - [PR#26](https://github.com/EmbarkStudios/tame-index/pull/26) changed sparse index request creation to not use HTTP/2 for the version to support corporate potato proxies. This results in a slight but noticeable degradation in throughput when making many requests to a sparse index. ## [0.5.4] - 2023-08-24 ### Fixed - [PR#24](https://github.com/EmbarkStudios/tame-index/pull/24) resolved [#23](https://github.com/EmbarkStudios/tame-index/issues/23) by fixing a bug where index cache paths were not lower cased as cargo does. ## [0.5.3] - 2023-08-23 ### Fixed - [PR#22](https://github.com/EmbarkStudios/tame-index/pull/22) fixed an issue where ssh index urls would be mapped to the incorrect local directory. This issue was raised in [cargo-deny](https://github.com/EmbarkStudios/cargo-deny/issues/548). ## [0.5.2] - 2023-08-23 ### Fixed - [`d9cb55f`] fixed and issue with docs.rs documentation building. ## [0.5.1] - 2023-08-23 ### Added - [PR#20](https://github.com/EmbarkStudios/tame-index/pull/20) publicly exposed `tame_index::external::http` for easier downstream usage. ## [0.5.0] - 2023-08-23 ### Fixed - [PR#18](https://github.com/EmbarkStudios/tame-index/pull/18) resolved [#16](https://github.com/EmbarkStudios/tame-index/issues/16) by marking `ComboIndexCache` and `ComboIndex` as `#[non_exhaustive]`. This avoids build breaks if the `local` feature is enabled in one transitive dependency and not in another, as much as I hate `non_exhaustive`. ### Changed - [PR#18](https://github.com/EmbarkStudios/tame-index/pull/18) changed `SparseIndex::make_remote_request` to take an optional [ETag](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/ETag), completely avoiding disk I/O, which allows `SparseIndex` to be used for making and parsing requests without worrying about cargo's global package lock. ## [0.4.1] - 2023-08-21 ### Added - [PR#15](https://github.com/EmbarkStudios/tame-index/pull/15) added the `native-certs` feature to be able to use the OS certificate store instead of `webpki-roots`. Thanks [@Shnatsel](https://github.com/Shnatsel)! ## [0.4.0] - 2023-08-18 ### Changed - [PR#14](https://github.com/EmbarkStudios/tame-index/pull/14) added the ability to specify the repository lock policy when calling `RemoteGitIndex::with_options`. Thanks [@Shnatsel](https://github.com/Shnatsel)! ## [0.3.2] - 2023-08-15 ### Fixed - [PR#13](https://github.com/EmbarkStudios/tame-index/pull/13) fixed a bug where git repository url canonicalization was incorrect if the url was not a github.com url that ended with .git. ## [0.3.1] - 2023-08-04 ### Added - [PR#11](https://github.com/EmbarkStudios/tame-index/pull/11) added `RemoteSparseIndex::krates`, `AsyncRemoteSparseIndex::krates`, and `AsyncRemoteSparseIndex::krates_blocking` as helper methods for improving throughput when fetching index entries for many crates. ## [0.3.0] - 2023-08-03 ### Changed - [PR#10](https://github.com/EmbarkStudios/tame-index/pull/10) unfortunately had to [relax the constraint](https://github.com/rustsec/rustsec/issues/759) that crate versions in an index are always parsable as `semver::Version`. ## [0.2.5] - 2023-08-02 ### Fixed - [PR#9](https://github.com/EmbarkStudios/tame-index/pull/9) resolved [#8](https://github.com/EmbarkStudios/tame-index/issues/8) by ensuring (valid) non-official cargo build version output can also be parsed. ## [0.2.4] - 2023-07-28 ### Fixed - [PR#7](https://github.com/EmbarkStudios/tame-index/pull/7) fixed an issue where `RemoteGitIndex::fetch` could fail in environments where the git committer was not configured. ### Changed - [PR#7](https://github.com/EmbarkStudios/tame-index/pull/7) change how `RemoteGitIndex` looks up blobs. Previously fetching would actually update references, now however we write a `FETCH_HEAD` similarly to git/libgit2, and uses that (or other) reference to find the commit to use, rather than updating the HEAD to point to the same commit as the remote HEAD. ## [0.2.3] - 2023-07-26 ### Fixed - [PR#6](https://github.com/EmbarkStudios/tame-index/pull/6) fixed two bugs with git registries. 1. `cargo` does not set remotes for git registry indices, the previous code assumed there was a remote, thus failed to fetch updates 2. Updating reflogs after a fetch would fail in CI-like environments without a global git config that set the committer, `committer.name` is now set to `tame-index` ## [0.2.2] - 2023-07-26 ### Changed - [PR#5](https://github.com/EmbarkStudios/tame-index/pull/5) relaxed `rust-version` to 1.67.0. ## [0.2.1] - 2023-07-26 ### Added - [PR#4](https://github.com/EmbarkStudios/tame-index/pull/4) added `GitError::is_spurious` and `GitError::is_locked` to detect fetch errors that could potentially succeed in the future if retried. ### Changed - [PR#4](https://github.com/EmbarkStudios/tame-index/pull/4) now re-exports `reqwest` and `gix` from `tame_index::externals` for easier downstream usage. ## [0.2.0] - 2023-07-25 ### Added - [PR#3](https://github.com/EmbarkStudios/tame-index/pull/3) added support for [`Local Registry`](https://doc.rust-lang.org/cargo/reference/source-replacement.html#local-registry-sources) - [PR#3](https://github.com/EmbarkStudios/tame-index/pull/3) added [`LocalRegistry`] as an option for `ComboIndexCache` - [PR#3](https://github.com/EmbarkStudios/tame-index/pull/3) added `KrateName::cargo` and `KrateName::crates_io` options for validating crates names against the (current) constraints of cargo and crates.io respectively. ### Changed - [PR#3](https://github.com/EmbarkStudios/tame-index/pull/3) refactored how index initialization is performed by splitting out the individual pieces into a cleaner API, adding the types `IndexUrl`, `IndexPath`, and `IndexLocation` ### Fixed - [PR#3](https://github.com/EmbarkStudios/tame-index/pull/3) fixed an issue where the .cache entries for a git index were not using the same cache version of cargo, as of 1.65.0+. cargo in those versions now uses the object id of the blob the crate is read from, rather than the `HEAD` commit hash, for more granular change detection. ## [0.1.0] - 2023-07-05 ### Added - [PR#1](https://github.com/EmbarkStudios/tame-index/pull/1) added the initial working implementation for this crate ## [0.0.1] - 2023-06-19 ### Added - Initial crate squat [Unreleased]: https://github.com/EmbarkStudios/tame-index/compare/0.23.1...HEAD [0.23.1]: https://github.com/EmbarkStudios/tame-index/compare/0.23.0...0.23.1 [0.23.0]: https://github.com/EmbarkStudios/tame-index/compare/0.22.0...0.23.0 [0.22.0]: https://github.com/EmbarkStudios/tame-index/compare/0.21.0...0.22.0 [0.21.0]: https://github.com/EmbarkStudios/tame-index/compare/0.20.1...0.21.0 [0.20.1]: https://github.com/EmbarkStudios/tame-index/compare/0.20.0...0.20.1 [0.20.0]: https://github.com/EmbarkStudios/tame-index/compare/0.19.0...0.20.0 [0.19.0]: https://github.com/EmbarkStudios/tame-index/compare/0.18.1...0.19.0 [0.18.1]: https://github.com/EmbarkStudios/tame-index/compare/0.18.0...0.18.1 [0.18.0]: https://github.com/EmbarkStudios/tame-index/compare/0.17.0...0.18.0 [0.17.0]: https://github.com/EmbarkStudios/tame-index/compare/0.16.0...0.17.0 [0.16.0]: https://github.com/EmbarkStudios/tame-index/compare/0.15.0...0.16.0 [0.15.0]: https://github.com/EmbarkStudios/tame-index/compare/0.14.0...0.15.0 [0.14.0]: https://github.com/EmbarkStudios/tame-index/compare/0.13.2...0.14.0 [0.13.2]: https://github.com/EmbarkStudios/tame-index/compare/0.13.1...0.13.2 [0.13.1]: https://github.com/EmbarkStudios/tame-index/compare/0.13.0...0.13.1 [0.13.0]: https://github.com/EmbarkStudios/tame-index/compare/0.12.2...0.13.0 [0.12.2]: https://github.com/EmbarkStudios/tame-index/compare/0.12.1...0.12.2 [0.12.1]: https://github.com/EmbarkStudios/tame-index/compare/0.12.0...0.12.1 [0.12.0]: https://github.com/EmbarkStudios/tame-index/compare/0.11.1...0.12.0 [0.11.1]: https://github.com/EmbarkStudios/tame-index/compare/0.11.0...0.11.1 [0.11.0]: https://github.com/EmbarkStudios/tame-index/compare/0.10.0...0.11.0 [0.10.0]: https://github.com/EmbarkStudios/tame-index/compare/0.9.9...0.10.0 [0.9.9]: https://github.com/EmbarkStudios/tame-index/compare/0.9.8...0.9.9 [0.9.8]: https://github.com/EmbarkStudios/tame-index/compare/0.9.7...0.9.8 [0.9.7]: https://github.com/EmbarkStudios/tame-index/compare/0.9.6...0.9.7 [0.9.6]: https://github.com/EmbarkStudios/tame-index/compare/0.9.5...0.9.6 [0.9.5]: https://github.com/EmbarkStudios/tame-index/compare/0.9.4...0.9.5 [0.9.4]: https://github.com/EmbarkStudios/tame-index/compare/0.9.3...0.9.4 [0.9.3]: https://github.com/EmbarkStudios/tame-index/compare/0.9.2...0.9.3 [0.9.2]: https://github.com/EmbarkStudios/tame-index/compare/0.9.1...0.9.2 [0.9.1]: https://github.com/EmbarkStudios/tame-index/compare/0.9.0...0.9.1 [0.9.0]: https://github.com/EmbarkStudios/tame-index/compare/0.8.0...0.9.0 [0.8.0]: https://github.com/EmbarkStudios/tame-index/compare/0.7.2...0.8.0 [0.7.2]: https://github.com/EmbarkStudios/tame-index/compare/0.7.1...0.7.2 [0.7.1]: https://github.com/EmbarkStudios/tame-index/compare/0.7.0...0.7.1 [0.7.0]: https://github.com/EmbarkStudios/tame-index/compare/0.6.0...0.7.0 [0.6.0]: https://github.com/EmbarkStudios/tame-index/compare/0.5.6...0.6.0 [0.5.6]: https://github.com/EmbarkStudios/tame-index/compare/0.5.5...0.5.6 [0.5.5]: https://github.com/EmbarkStudios/tame-index/compare/0.5.4...0.5.5 [0.5.4]: https://github.com/EmbarkStudios/tame-index/compare/0.5.3...0.5.4 [0.5.3]: https://github.com/EmbarkStudios/tame-index/compare/0.5.2...0.5.3 [0.5.2]: https://github.com/EmbarkStudios/tame-index/compare/0.5.1...0.5.2 [0.5.1]: https://github.com/EmbarkStudios/tame-index/compare/0.5.0...0.5.1 [0.5.0]: https://github.com/EmbarkStudios/tame-index/compare/0.4.1...0.5.0 [0.4.1]: https://github.com/EmbarkStudios/tame-index/compare/0.4.0...0.4.1 [0.4.0]: https://github.com/EmbarkStudios/tame-index/compare/0.3.2...0.4.0 [0.3.2]: https://github.com/EmbarkStudios/tame-index/compare/0.3.1...0.3.2 [0.3.1]: https://github.com/EmbarkStudios/tame-index/compare/0.3.0...0.3.1 [0.3.0]: https://github.com/EmbarkStudios/tame-index/compare/0.2.5...0.3.0 [0.2.5]: https://github.com/EmbarkStudios/tame-index/compare/0.2.4...0.2.5 [0.2.4]: https://github.com/EmbarkStudios/tame-index/compare/0.2.3...0.2.4 [0.2.3]: https://github.com/EmbarkStudios/tame-index/compare/0.2.2...0.2.3 [0.2.2]: https://github.com/EmbarkStudios/tame-index/compare/0.2.1...0.2.2 [0.2.1]: https://github.com/EmbarkStudios/tame-index/compare/0.2.0...0.2.1 [0.2.0]: https://github.com/EmbarkStudios/tame-index/compare/0.1.0...0.2.0 [0.1.0]: https://github.com/EmbarkStudios/tame-index/compare/0.0.1...0.1.0 [0.0.1]: https://github.com/EmbarkStudios/tame-index/releases/tag/0.0.1 tame-index-0.23.1/CODE_OF_CONDUCT.md000064400000000000000000000064411046102023000145460ustar 00000000000000# Contributor Covenant Code of Conduct ## Our Pledge In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, religion, or sexual identity and orientation. ## Our Standards Examples of behavior that contributes to creating a positive environment include: * Using welcoming and inclusive language * Being respectful of differing viewpoints and experiences * Gracefully accepting constructive criticism * Focusing on what is best for the community * Showing empathy towards other community members Examples of unacceptable behavior by participants include: * The use of sexualized language or imagery and unwelcome sexual attention or advances * Trolling, insulting/derogatory comments, and personal or political attacks * Public or private harassment * Publishing others' private information, such as a physical or electronic address, without explicit permission * Other conduct which could reasonably be considered inappropriate in a professional setting ## Our Responsibilities Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. ## Scope This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. ## Enforcement Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at opensource@embark-studios.com. All complaints will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. ## Attribution This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html [homepage]: https://www.contributor-covenant.org For answers to common questions about this code of conduct, see https://www.contributor-covenant.org/faq tame-index-0.23.1/CONTRIBUTING.md000064400000000000000000000102621046102023000141740ustar 00000000000000# Embark Contributor Guidelines Welcome! This project is created by the team at [Embark Studios](https://embark.games). We're glad you're interested in contributing! We welcome contributions from people of all backgrounds who are interested in making great software with us. At Embark, we aspire to empower everyone to create interactive experiences. To do this, we're exploring and pushing the boundaries of new technologies, and sharing our learnings with the open source community. If you have ideas for collaboration, email us at opensource@embark-studios.com. We're also hiring full-time engineers to work with us in Stockholm! Check out our current job postings [here](https://www.embark-studios.com/jobs). ## Issues ### Feature Requests If you have ideas or how to improve our projects, you can suggest features by opening a GitHub issue. Make sure to include details about the feature or change, and describe any uses cases it would enable. Feature requests will be tagged as `enhancement` and their status will be updated in the comments of the issue. ### Bugs When reporting a bug or unexpected behavior in a project, make sure your issue describes steps to reproduce the behavior, including the platform you were using, what steps you took, and any error messages. Reproducible bugs will be tagged as `bug` and their status will be updated in the comments of the issue. ### Wontfix Issues will be closed and tagged as `wontfix` if we decide that we do not wish to implement it, usually due to being misaligned with the project vision or out of scope. We will comment on the issue with more detailed reasoning. ## Contribution Workflow ### Open Issues If you're ready to contribute, start by looking at our open issues tagged as [`help wanted`](../../issues?q=is%3Aopen+is%3Aissue+label%3A"help+wanted") or [`good first issue`](../../issues?q=is%3Aopen+is%3Aissue+label%3A"good+first+issue"). You can comment on the issue to let others know you're interested in working on it or to ask questions. ### Making Changes 1. Fork the repository. 2. Create a new feature branch. 3. Make your changes. Ensure that there are no build errors by running the project with your changes locally. 4. Open a pull request with a name and description of what you did. You can read more about working with pull requests on GitHub [here](https://help.github.com/en/articles/creating-a-pull-request-from-a-fork). 5. A maintainer will review your pull request and may ask you to make changes. ## Code Guidelines ### Rust You can read about our standards and recommendations for working with Rust [here](https://github.com/EmbarkStudios/rust-ecosystem/blob/main/guidelines.md). ### Python We recommend following [PEP8 conventions](https://www.python.org/dev/peps/pep-0008/) when working with Python modules. ### JavaScript & TypeScript We use [Prettier](https://prettier.io/) with the default settings to auto-format our JavaScript and TypeScript code. ## Licensing Unless otherwise specified, all Embark open source projects shall comply with the Rust standard licensing model (MIT + Apache 2.0) and are thereby licensed under a dual license, allowing licensees to choose either MIT OR Apache-2.0 at their option. ## Contributor Terms Thank you for your interest in Embark Studios’ open source project. By providing a contribution (new or modified code, other input, feedback or suggestions etc.) you agree to these Contributor Terms. You confirm that each of your contributions has been created by you and that you are the copyright owner. You also confirm that you have the right to provide the contribution to us and that you do it under the Rust dual licence model (MIT + Apache 2.0). If you want to contribute something that is not your original creation, you may submit it to Embark Studios separately from any contribution, including details of its source and of any license or other restriction (such as related patents, trademarks, agreements etc.) Please also note that our projects are released with a [Contributor Code of Conduct](CODE_OF_CONDUCT.md) to ensure that they are welcoming places for everyone to contribute. By participating in any Embark Studios open source project, you agree to keep to the Contributor Code of Conduct. tame-index-0.23.1/Cargo.lock0000644000002317060000000000100111360ustar # This file is automatically @generated by Cargo. # It is not intended for manual editing. version = 4 [[package]] name = "addr2line" version = "0.25.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b5d307320b3181d6d7954e663bd7c774a838b8220fe0593c86d9fb09f498b4b" dependencies = [ "gimli", ] [[package]] name = "adler2" version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" [[package]] name = "allocator-api2" version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" [[package]] name = "arc-swap" version = "1.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "69f7f8c3906b62b754cd5326047894316021dcfe5a194c8ea52bdd94934a3457" [[package]] name = "async-compression" version = "0.4.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a89bce6054c720275ac2432fbba080a66a2106a44a1b804553930ca6909f4e0" dependencies = [ "compression-codecs", "compression-core", "futures-core", "pin-project-lite", "tokio", ] [[package]] name = "atomic-waker" version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" [[package]] name = "backtrace" version = "0.3.76" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bb531853791a215d7c62a30daf0dde835f381ab5de4589cfe7c649d2cbe92bd6" dependencies = [ "addr2line", "cfg-if", "libc", "miniz_oxide", "object", "rustc-demangle", "windows-link 0.2.1", ] [[package]] name = "base64" version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" [[package]] name = "bitflags" version = "2.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2261d10cca569e4643e526d8dc2e62e433cc8aba21ab764233731f8d369bf394" [[package]] name = "block-buffer" version = "0.10.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" dependencies = [ "generic-array", ] [[package]] name = "borsh" version = "1.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ad8646f98db542e39fc66e68a20b2144f6a732636df7c2354e74645faaa433ce" dependencies = [ "cfg_aliases", ] [[package]] name = "bstr" version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "234113d19d0d7d613b40e86fb654acf958910802bcceab913a4f9e7cda03b1a4" dependencies = [ "memchr", "regex-automata", "serde", ] [[package]] name = "bumpalo" version = "3.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" [[package]] name = "byteorder" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" [[package]] name = "camino" version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "276a59bf2b2c967788139340c9f0c5b12d7fd6630315c15c217e559de85d2609" dependencies = [ "serde_core", ] [[package]] name = "cargo-platform" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8abf5d501fd757c2d2ee78d0cc40f606e92e3a63544420316565556ed28485e2" dependencies = [ "serde", ] [[package]] name = "cargo_metadata" version = "0.23.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "981a6f317983eec002839b90fae7411a85621410ae591a9cab2ecf5cb5744873" dependencies = [ "camino", "cargo-platform", "semver", "serde", "serde_json", "thiserror", ] [[package]] name = "cc" version = "1.2.41" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac9fe6cdbb24b6ade63616c0a0688e45bb56732262c158df3c0c4bea4ca47cb7" dependencies = [ "find-msvc-tools", "shlex", ] [[package]] name = "cfg-if" version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2fd1289c04a9ea8cb22300a459a72a385d7c73d3259e2ed7dcb2af674838cfa9" [[package]] name = "cfg_aliases" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" [[package]] name = "clru" version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cbd0f76e066e64fdc5631e3bb46381254deab9ef1158292f27c8c57e3bf3fe59" [[package]] name = "compression-codecs" version = "0.4.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ef8a506ec4b81c460798f572caead636d57d3d7e940f998160f52bd254bf2d23" dependencies = [ "compression-core", "flate2", "memchr", ] [[package]] name = "compression-core" version = "0.4.29" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e47641d3deaf41fb1538ac1f54735925e275eaf3bf4d55c81b137fba797e5cbb" [[package]] name = "core-foundation" version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" dependencies = [ "core-foundation-sys", "libc", ] [[package]] name = "core-foundation" version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6" dependencies = [ "core-foundation-sys", "libc", ] [[package]] name = "core-foundation-sys" version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" [[package]] name = "cpufeatures" version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" dependencies = [ "libc", ] [[package]] name = "crc32fast" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" dependencies = [ "cfg-if", ] [[package]] name = "crossbeam-channel" version = "0.5.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "82b8f8f868b36967f9606790d1903570de9ceaf870a7bf9fbbd3016d636a2cb2" dependencies = [ "crossbeam-utils", ] [[package]] name = "crossbeam-deque" version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51" dependencies = [ "crossbeam-epoch", "crossbeam-utils", ] [[package]] name = "crossbeam-epoch" version = "0.9.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" dependencies = [ "crossbeam-utils", ] [[package]] name = "crossbeam-utils" version = "0.8.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" [[package]] name = "crypto-common" version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" dependencies = [ "generic-array", "typenum", ] [[package]] name = "curl" version = "0.4.49" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "79fc3b6dd0b87ba36e565715bf9a2ced221311db47bd18011676f24a6066edbc" dependencies = [ "curl-sys", "libc", "openssl-probe", "openssl-sys", "schannel", "socket2", "windows-sys 0.59.0", ] [[package]] name = "curl-sys" version = "0.4.83+curl-8.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5830daf304027db10c82632a464879d46a3f7c4ba17a31592657ad16c719b483" dependencies = [ "cc", "libc", "libz-sys", "openssl-sys", "pkg-config", "vcpkg", "windows-sys 0.59.0", ] [[package]] name = "digest" version = "0.10.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ "block-buffer", "crypto-common", ] [[package]] name = "displaydoc" version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "dunce" version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" [[package]] name = "either" version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" [[package]] name = "encoding_rs" version = "0.8.35" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" dependencies = [ "cfg-if", ] [[package]] name = "equivalent" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" [[package]] name = "errno" version = "0.3.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", "windows-sys 0.61.2", ] [[package]] name = "faster-hex" version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7223ae2d2f179b803433d9c830478527e92b8117eab39460edae7f1614d9fb73" dependencies = [ "heapless", "serde", ] [[package]] name = "fastrand" version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" [[package]] name = "filetime" version = "0.2.26" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bc0505cd1b6fa6580283f6bdf70a73fcf4aba1184038c90902b92b3dd0df63ed" dependencies = [ "cfg-if", "libc", "libredox", "windows-sys 0.60.2", ] [[package]] name = "find-msvc-tools" version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "52051878f80a721bb68ebfbc930e07b65ba72f2da88968ea5c06fd6ca3d3a127" [[package]] name = "flate2" version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc5a4e564e38c699f2880d3fda590bedc2e69f3f84cd48b457bd892ce61d0aa9" dependencies = [ "crc32fast", "libz-rs-sys", "miniz_oxide", ] [[package]] name = "fnv" version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" [[package]] name = "foldhash" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" [[package]] name = "form_urlencoded" version = "1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" dependencies = [ "percent-encoding", ] [[package]] name = "futures-channel" version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" dependencies = [ "futures-core", "futures-sink", ] [[package]] name = "futures-core" version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" [[package]] name = "futures-io" version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" [[package]] name = "futures-sink" version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" [[package]] name = "futures-task" version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" [[package]] name = "futures-util" version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" dependencies = [ "futures-core", "futures-io", "futures-sink", "futures-task", "memchr", "pin-project-lite", "pin-utils", "slab", ] [[package]] name = "generic-array" version = "0.14.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" dependencies = [ "typenum", "version_check", ] [[package]] name = "getrandom" version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" dependencies = [ "cfg-if", "js-sys", "libc", "wasi 0.11.1+wasi-snapshot-preview1", "wasm-bindgen", ] [[package]] name = "getrandom" version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4" dependencies = [ "cfg-if", "js-sys", "libc", "r-efi", "wasi 0.14.7+wasi-0.2.4", "wasm-bindgen", ] [[package]] name = "gimli" version = "0.32.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e629b9b98ef3dd8afe6ca2bd0f89306cec16d43d907889945bc5d6687f2f13c7" [[package]] name = "gix" version = "0.73.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "514c29cc879bdc0286b0cbc205585a49b252809eb86c69df4ce4f855ee75f635" dependencies = [ "gix-actor", "gix-attributes", "gix-command", "gix-commitgraph", "gix-config", "gix-credentials", "gix-date", "gix-diff", "gix-discover", "gix-features", "gix-filter", "gix-fs", "gix-glob", "gix-hash", "gix-hashtable", "gix-ignore", "gix-index", "gix-lock", "gix-negotiate", "gix-object", "gix-odb", "gix-pack", "gix-path", "gix-pathspec", "gix-prompt", "gix-protocol", "gix-ref", "gix-refspec", "gix-revision", "gix-revwalk", "gix-sec", "gix-shallow", "gix-submodule", "gix-tempfile", "gix-trace", "gix-transport", "gix-traverse", "gix-url", "gix-utils", "gix-validate", "gix-worktree", "once_cell", "smallvec", "thiserror", ] [[package]] name = "gix-actor" version = "0.35.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2d36dcf9efe32b51b12dfa33cedff8414926124e760a32f9e7a6b5580d280967" dependencies = [ "bstr", "gix-date", "gix-utils", "itoa", "thiserror", "winnow", ] [[package]] name = "gix-attributes" version = "0.27.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "45442188216d08a5959af195f659cb1f244a50d7d2d0c3873633b1cd7135f638" dependencies = [ "bstr", "gix-glob", "gix-path", "gix-quote", "gix-trace", "kstring", "smallvec", "thiserror", "unicode-bom", ] [[package]] name = "gix-bitmap" version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b1db9765c69502650da68f0804e3dc2b5f8ccc6a2d104ca6c85bc40700d37540" dependencies = [ "thiserror", ] [[package]] name = "gix-chunk" version = "0.4.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b1f1d8764958699dc764e3f727cef280ff4d1bd92c107bbf8acd85b30c1bd6f" dependencies = [ "thiserror", ] [[package]] name = "gix-command" version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6b31b65ca48a352ae86312b27a514a0c661935f96b481ac8b4371f65815eb196" dependencies = [ "bstr", "gix-path", "gix-quote", "gix-trace", "shell-words", ] [[package]] name = "gix-commitgraph" version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6bb23121e952f43a5b07e3e80890336cb847297467a410475036242732980d06" dependencies = [ "bstr", "gix-chunk", "gix-hash", "memmap2", "thiserror", ] [[package]] name = "gix-config" version = "0.46.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5dfb898c5b695fd4acfc3c0ab638525a65545d47706064dcf7b5ead6cdb136c0" dependencies = [ "bstr", "gix-config-value", "gix-features", "gix-glob", "gix-path", "gix-ref", "gix-sec", "memchr", "once_cell", "smallvec", "thiserror", "unicode-bom", "winnow", ] [[package]] name = "gix-config-value" version = "0.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9f012703eb67e263c6c1fc96649fec47694dd3e5d2a91abfc65e4a6a6dc85309" dependencies = [ "bitflags", "bstr", "gix-path", "libc", "thiserror", ] [[package]] name = "gix-credentials" version = "0.30.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0039dd3ac606dd80b16353a41b61fc237ca5cb8b612f67a9f880adfad4be4e05" dependencies = [ "bstr", "gix-command", "gix-config-value", "gix-date", "gix-path", "gix-prompt", "gix-sec", "gix-trace", "gix-url", "thiserror", ] [[package]] name = "gix-date" version = "0.10.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "996b6b90bafb287330af92b274c3e64309dc78359221d8612d11cd10c8b9fe1c" dependencies = [ "bstr", "itoa", "jiff", "smallvec", "thiserror", ] [[package]] name = "gix-diff" version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "de854852010d44a317f30c92d67a983e691c9478c8a3fb4117c1f48626bcdea8" dependencies = [ "bstr", "gix-hash", "gix-object", "thiserror", ] [[package]] name = "gix-discover" version = "0.41.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ffb180c91ca1a2cf53e828bb63d8d8f8fa7526f49b83b33d7f46cbeb5d79d30a" dependencies = [ "bstr", "dunce", "gix-fs", "gix-hash", "gix-path", "gix-ref", "gix-sec", "thiserror", ] [[package]] name = "gix-features" version = "0.43.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cd1543cd9b8abcbcebaa1a666a5c168ee2cda4dea50d3961ee0e6d1c42f81e5b" dependencies = [ "bytes", "crc32fast", "flate2", "gix-path", "gix-trace", "gix-utils", "libc", "once_cell", "prodash", "thiserror", "walkdir", ] [[package]] name = "gix-filter" version = "0.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "aa6571a3927e7ab10f64279a088e0dae08e8da05547771796d7389bbe28ad9ff" dependencies = [ "bstr", "encoding_rs", "gix-attributes", "gix-command", "gix-hash", "gix-object", "gix-packetline-blocking", "gix-path", "gix-quote", "gix-trace", "gix-utils", "smallvec", "thiserror", ] [[package]] name = "gix-fs" version = "0.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9a4d90307d064fa7230e0f87b03231be28f8ba63b913fc15346f489519d0c304" dependencies = [ "bstr", "fastrand", "gix-features", "gix-path", "gix-utils", "thiserror", ] [[package]] name = "gix-glob" version = "0.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b947db8366823e7a750c254f6bb29e27e17f27e457bf336ba79b32423db62cd5" dependencies = [ "bitflags", "bstr", "gix-features", "gix-path", ] [[package]] name = "gix-hash" version = "0.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "251fad79796a731a2a7664d9ea95ee29a9e99474de2769e152238d4fdb69d50e" dependencies = [ "faster-hex", "gix-features", "sha1-checked", "thiserror", ] [[package]] name = "gix-hashtable" version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c35300b54896153e55d53f4180460931ccd69b7e8d2f6b9d6401122cdedc4f07" dependencies = [ "gix-hash", "hashbrown 0.15.5", "parking_lot", ] [[package]] name = "gix-ignore" version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "564d6fddf46e2c981f571b23d6ad40cb08bddcaf6fc7458b1d49727ad23c2870" dependencies = [ "bstr", "gix-glob", "gix-path", "gix-trace", "unicode-bom", ] [[package]] name = "gix-index" version = "0.41.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2af39fde3ce4ce11371d9ce826f2936ec347318f2d1972fe98c2e7134e267e25" dependencies = [ "bitflags", "bstr", "filetime", "fnv", "gix-bitmap", "gix-features", "gix-fs", "gix-hash", "gix-lock", "gix-object", "gix-traverse", "gix-utils", "gix-validate", "hashbrown 0.15.5", "itoa", "libc", "memmap2", "rustix", "smallvec", "thiserror", ] [[package]] name = "gix-lock" version = "18.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9fa71da90365668a621e184eb5b979904471af1b3b09b943a84bc50e8ad42ed" dependencies = [ "gix-tempfile", "gix-utils", "thiserror", ] [[package]] name = "gix-negotiate" version = "0.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1d58d4c9118885233be971e0d7a589f5cfb1a8bd6cb6e2ecfb0fc6b1b293c83b" dependencies = [ "bitflags", "gix-commitgraph", "gix-date", "gix-hash", "gix-object", "gix-revwalk", "smallvec", "thiserror", ] [[package]] name = "gix-object" version = "0.50.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d69ce108ab67b65fbd4fb7e1331502429d78baeb2eee10008bdef55765397c07" dependencies = [ "bstr", "gix-actor", "gix-date", "gix-features", "gix-hash", "gix-hashtable", "gix-path", "gix-utils", "gix-validate", "itoa", "smallvec", "thiserror", "winnow", ] [[package]] name = "gix-odb" version = "0.70.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c9d7af10fda9df0bb4f7f9bd507963560b3c66cb15a5b825caf752e0eb109ac" dependencies = [ "arc-swap", "gix-date", "gix-features", "gix-fs", "gix-hash", "gix-hashtable", "gix-object", "gix-pack", "gix-path", "gix-quote", "parking_lot", "tempfile", "thiserror", ] [[package]] name = "gix-pack" version = "0.60.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d8571df89bfca5abb49c3e3372393f7af7e6f8b8dbe2b96303593cef5b263019" dependencies = [ "clru", "gix-chunk", "gix-features", "gix-hash", "gix-hashtable", "gix-object", "gix-path", "gix-tempfile", "memmap2", "parking_lot", "smallvec", "thiserror", ] [[package]] name = "gix-packetline" version = "0.19.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2592fbd36249a2fea11056f7055cc376301ef38d903d157de41998335bbf1f93" dependencies = [ "bstr", "faster-hex", "gix-trace", "thiserror", ] [[package]] name = "gix-packetline-blocking" version = "0.19.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc4e706f328cd494cc8f932172e123a72b9a4711b0db5e411681432a89bd4c94" dependencies = [ "bstr", "faster-hex", "gix-trace", "thiserror", ] [[package]] name = "gix-path" version = "0.10.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06d37034a4c67bbdda76f7bcd037b2f7bc0fba0c09a6662b19697a5716e7b2fd" dependencies = [ "bstr", "gix-trace", "gix-validate", "home", "once_cell", "thiserror", ] [[package]] name = "gix-pathspec" version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "daedead611c9bd1f3640dc90a9012b45f790201788af4d659f28d94071da7fba" dependencies = [ "bitflags", "bstr", "gix-attributes", "gix-config-value", "gix-glob", "gix-path", "thiserror", ] [[package]] name = "gix-prompt" version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6ffa1a7a34c81710aaa666a428c142b6c5d640492fcd41267db0740d923c7906" dependencies = [ "gix-command", "gix-config-value", "parking_lot", "rustix", "thiserror", ] [[package]] name = "gix-protocol" version = "0.51.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "12b4b807c47ffcf7c1e5b8119585368a56449f3493da93b931e1d4239364e922" dependencies = [ "bstr", "gix-credentials", "gix-date", "gix-features", "gix-hash", "gix-lock", "gix-negotiate", "gix-object", "gix-ref", "gix-refspec", "gix-revwalk", "gix-shallow", "gix-trace", "gix-transport", "gix-utils", "maybe-async", "thiserror", "winnow", ] [[package]] name = "gix-quote" version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4a375a75b4d663e8bafe3bf4940a18a23755644c13582fa326e99f8f987d83fd" dependencies = [ "bstr", "gix-utils", "thiserror", ] [[package]] name = "gix-ref" version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b966f578079a42f4a51413b17bce476544cca1cf605753466669082f94721758" dependencies = [ "gix-actor", "gix-features", "gix-fs", "gix-hash", "gix-lock", "gix-object", "gix-path", "gix-tempfile", "gix-utils", "gix-validate", "memmap2", "thiserror", "winnow", ] [[package]] name = "gix-refspec" version = "0.31.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7d29cae1ae31108826e7156a5e60bffacab405f4413f5bc0375e19772cce0055" dependencies = [ "bstr", "gix-hash", "gix-revision", "gix-validate", "smallvec", "thiserror", ] [[package]] name = "gix-revision" version = "0.35.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f651f2b1742f760bb8161d6743229206e962b73d9c33c41f4e4aefa6586cbd3d" dependencies = [ "bstr", "gix-commitgraph", "gix-date", "gix-hash", "gix-object", "gix-revwalk", "thiserror", ] [[package]] name = "gix-revwalk" version = "0.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06e74f91709729e099af6721bd0fa7d62f243f2005085152301ca5cdd86ec02c" dependencies = [ "gix-commitgraph", "gix-date", "gix-hash", "gix-hashtable", "gix-object", "smallvec", "thiserror", ] [[package]] name = "gix-sec" version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09f7053ed7c66633b56c57bc6ed3377be3166eaf3dc2df9f1c5ec446df6fdf2c" dependencies = [ "bitflags", "gix-path", "libc", "windows-sys 0.59.0", ] [[package]] name = "gix-shallow" version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d936745103243ae4c510f19e0760ce73fb0f08096588fdbe0f0d7fb7ce8944b7" dependencies = [ "bstr", "gix-hash", "gix-lock", "thiserror", ] [[package]] name = "gix-submodule" version = "0.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "657cc5dd43cbc7a14d9c5aaf02cfbe9c2a15d077cded3f304adb30ef78852d3e" dependencies = [ "bstr", "gix-config", "gix-path", "gix-pathspec", "gix-refspec", "gix-url", "thiserror", ] [[package]] name = "gix-tempfile" version = "18.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "666c0041bcdedf5fa05e9bef663c897debab24b7dc1741605742412d1d47da57" dependencies = [ "gix-fs", "libc", "once_cell", "parking_lot", "tempfile", ] [[package]] name = "gix-trace" version = "0.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2ccaf54b0b1743a695b482ca0ab9d7603744d8d10b2e5d1a332fef337bee658" [[package]] name = "gix-transport" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "12f7cc0179fc89d53c54e1f9ce51229494864ab4bf136132d69db1b011741ca3" dependencies = [ "base64", "bstr", "curl", "gix-command", "gix-credentials", "gix-features", "gix-packetline", "gix-quote", "gix-sec", "gix-url", "reqwest", "thiserror", ] [[package]] name = "gix-traverse" version = "0.47.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c7cdc82509d792ba0ad815f86f6b469c7afe10f94362e96c4494525a6601bdd5" dependencies = [ "bitflags", "gix-commitgraph", "gix-date", "gix-hash", "gix-hashtable", "gix-object", "gix-revwalk", "smallvec", "thiserror", ] [[package]] name = "gix-url" version = "0.32.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b76a9d266254ad287ffd44467cd88e7868799b08f4d52e02d942b93e514d16f" dependencies = [ "bstr", "gix-features", "gix-path", "percent-encoding", "thiserror", "url", ] [[package]] name = "gix-utils" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5351af2b172caf41a3728eb4455326d84e0d70fe26fc4de74ab0bd37df4191c5" dependencies = [ "fastrand", "unicode-normalization", ] [[package]] name = "gix-validate" version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77b9e00cacde5b51388d28ed746c493b18a6add1f19b5e01d686b3b9ece66d4d" dependencies = [ "bstr", "thiserror", ] [[package]] name = "gix-worktree" version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "55f625ac9126c19bef06dbc6d2703cdd7987e21e35b497bb265ac37d383877b1" dependencies = [ "bstr", "gix-attributes", "gix-features", "gix-fs", "gix-glob", "gix-hash", "gix-ignore", "gix-index", "gix-object", "gix-path", "gix-validate", ] [[package]] name = "h2" version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f3c0b69cfcb4e1b9f1bf2f53f95f766e4661169728ec61cd3fe5a0166f2d1386" dependencies = [ "atomic-waker", "bytes", "fnv", "futures-core", "futures-sink", "http", "indexmap", "slab", "tokio", "tokio-util", "tracing", ] [[package]] name = "hash32" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "47d60b12902ba28e2730cd37e95b8c9223af2808df9e902d4df49588d1470606" dependencies = [ "byteorder", ] [[package]] name = "hashbrown" version = "0.15.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" dependencies = [ "allocator-api2", "equivalent", "foldhash", ] [[package]] name = "hashbrown" version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5419bdc4f6a9207fbeba6d11b604d481addf78ecd10c11ad51e76c2f6482748d" [[package]] name = "heapless" version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0bfb9eb618601c89945a70e254898da93b13be0388091d42117462b265bb3fad" dependencies = [ "hash32", "stable_deref_trait", ] [[package]] name = "home" version = "0.5.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589533453244b0995c858700322199b2becb13b627df2851f64a2775d024abcf" dependencies = [ "windows-sys 0.59.0", ] [[package]] name = "http" version = "1.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f4a85d31aea989eead29a3aaf9e1115a180df8282431156e533de47660892565" dependencies = [ "bytes", "fnv", "itoa", ] [[package]] name = "http-body" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" dependencies = [ "bytes", "http", ] [[package]] name = "http-body-util" version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" dependencies = [ "bytes", "futures-core", "http", "http-body", "pin-project-lite", ] [[package]] name = "httparse" version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" [[package]] name = "hyper" version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eb3aa54a13a0dfe7fbe3a59e0c76093041720fdc77b110cc0fc260fafb4dc51e" dependencies = [ "atomic-waker", "bytes", "futures-channel", "futures-core", "h2", "http", "http-body", "httparse", "itoa", "pin-project-lite", "pin-utils", "smallvec", "tokio", "want", ] [[package]] name = "hyper-rustls" version = "0.27.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3c93eb611681b207e1fe55d5a71ecf91572ec8a6705cdb6857f7d8d5242cf58" dependencies = [ "http", "hyper", "hyper-util", "rustls", "rustls-native-certs", "rustls-pki-types", "tokio", "tokio-rustls", "tower-service", "webpki-roots", ] [[package]] name = "hyper-util" version = "0.1.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c6995591a8f1380fcb4ba966a252a4b29188d51d2b89e3a252f5305be65aea8" dependencies = [ "base64", "bytes", "futures-channel", "futures-core", "futures-util", "http", "http-body", "hyper", "ipnet", "libc", "percent-encoding", "pin-project-lite", "socket2", "system-configuration", "tokio", "tower-service", "tracing", "windows-registry", ] [[package]] name = "icu_collections" version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "200072f5d0e3614556f94a9930d5dc3e0662a652823904c3a75dc3b0af7fee47" dependencies = [ "displaydoc", "potential_utf", "yoke", "zerofrom", "zerovec", ] [[package]] name = "icu_locale_core" version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0cde2700ccaed3872079a65fb1a78f6c0a36c91570f28755dda67bc8f7d9f00a" dependencies = [ "displaydoc", "litemap", "tinystr", "writeable", "zerovec", ] [[package]] name = "icu_normalizer" version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "436880e8e18df4d7bbc06d58432329d6458cc84531f7ac5f024e93deadb37979" dependencies = [ "displaydoc", "icu_collections", "icu_normalizer_data", "icu_properties", "icu_provider", "smallvec", "zerovec", ] [[package]] name = "icu_normalizer_data" version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "00210d6893afc98edb752b664b8890f0ef174c8adbb8d0be9710fa66fbbf72d3" [[package]] name = "icu_properties" version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "016c619c1eeb94efb86809b015c58f479963de65bdb6253345c1a1276f22e32b" dependencies = [ "displaydoc", "icu_collections", "icu_locale_core", "icu_properties_data", "icu_provider", "potential_utf", "zerotrie", "zerovec", ] [[package]] name = "icu_properties_data" version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "298459143998310acd25ffe6810ed544932242d3f07083eee1084d83a71bd632" [[package]] name = "icu_provider" version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "03c80da27b5f4187909049ee2d72f276f0d9f99a42c306bd0131ecfe04d8e5af" dependencies = [ "displaydoc", "icu_locale_core", "stable_deref_trait", "tinystr", "writeable", "yoke", "zerofrom", "zerotrie", "zerovec", ] [[package]] name = "idna" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" dependencies = [ "idna_adapter", "smallvec", "utf8_iter", ] [[package]] name = "idna_adapter" version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344" dependencies = [ "icu_normalizer", "icu_properties", ] [[package]] name = "indexmap" version = "2.11.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4b0f83760fb341a774ed326568e19f5a863af4a952def8c39f9ab92fd95b88e5" dependencies = [ "equivalent", "hashbrown 0.16.0", ] [[package]] name = "io-uring" version = "0.7.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "046fa2d4d00aea763528b4950358d0ead425372445dc8ff86312b3c69ff7727b" dependencies = [ "bitflags", "cfg-if", "libc", ] [[package]] name = "ipnet" version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130" [[package]] name = "iri-string" version = "0.7.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dbc5ebe9c3a1a7a5127f920a418f7585e9e758e911d0466ed004f393b0e380b2" dependencies = [ "memchr", "serde", ] [[package]] name = "itoa" version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" [[package]] name = "jiff" version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "be1f93b8b1eb69c77f24bbb0afdf66f54b632ee39af40ca21c4365a1d7347e49" dependencies = [ "jiff-static", "jiff-tzdb-platform", "log", "portable-atomic", "portable-atomic-util", "serde", "windows-sys 0.59.0", ] [[package]] name = "jiff-static" version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "03343451ff899767262ec32146f6d559dd759fdadf42ff0e227c7c48f72594b4" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "jiff-tzdb" version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c1283705eb0a21404d2bfd6eef2a7593d240bc42a0bdb39db0ad6fa2ec026524" [[package]] name = "jiff-tzdb-platform" version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "875a5a69ac2bab1a891711cf5eccbec1ce0341ea805560dcd90b7a2e925132e8" dependencies = [ "jiff-tzdb", ] [[package]] name = "js-sys" version = "0.3.81" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec48937a97411dcb524a265206ccd4c90bb711fca92b2792c407f268825b9305" dependencies = [ "once_cell", "wasm-bindgen", ] [[package]] name = "kstring" version = "2.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "558bf9508a558512042d3095138b1f7b8fe90c5467d94f9f1da28b3731c5dbd1" dependencies = [ "static_assertions", ] [[package]] name = "libc" version = "0.2.177" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976" [[package]] name = "libredox" version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "416f7e718bdb06000964960ffa43b4335ad4012ae8b99060261aa4a8088d5ccb" dependencies = [ "bitflags", "libc", "redox_syscall", ] [[package]] name = "libz-rs-sys" version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "840db8cf39d9ec4dd794376f38acc40d0fc65eec2a8f484f7fd375b84602becd" dependencies = [ "zlib-rs", ] [[package]] name = "libz-sys" version = "1.1.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b70e7a7df205e92a1a4cd9aaae7898dac0aa555503cc0a649494d0d60e7651d" dependencies = [ "cc", "libc", "pkg-config", "vcpkg", ] [[package]] name = "linux-raw-sys" version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039" [[package]] name = "litemap" version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "241eaef5fd12c88705a01fc1066c48c4b36e0dd4377dcdc7ec3942cea7a69956" [[package]] name = "lock_api" version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" dependencies = [ "scopeguard", ] [[package]] name = "log" version = "0.4.28" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432" [[package]] name = "lru-slab" version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154" [[package]] name = "maybe-async" version = "0.2.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5cf92c10c7e361d6b99666ec1c6f9805b0bea2c3bd8c78dc6fe98ac5bd78db11" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "memchr" version = "2.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" [[package]] name = "memmap2" version = "0.9.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "843a98750cd611cc2965a8213b53b43e715f13c37a9e096c6408e69990961db7" dependencies = [ "libc", ] [[package]] name = "mime" version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" [[package]] name = "miniz_oxide" version = "0.8.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" dependencies = [ "adler2", "simd-adler32", ] [[package]] name = "mio" version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78bed444cc8a2160f01cbcf811ef18cac863ad68ae8ca62092e8db51d51c761c" dependencies = [ "libc", "wasi 0.11.1+wasi-snapshot-preview1", "windows-sys 0.59.0", ] [[package]] name = "object" version = "0.37.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ff76201f031d8863c38aa7f905eca4f53abbfa15f609db4277d44cd8938f33fe" dependencies = [ "memchr", ] [[package]] name = "once_cell" version = "1.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" [[package]] name = "openssl-probe" version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" [[package]] name = "openssl-sys" version = "0.9.109" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "90096e2e47630d78b7d1c20952dc621f957103f8bc2c8359ec81290d75238571" dependencies = [ "cc", "libc", "pkg-config", "vcpkg", ] [[package]] name = "parking_lot" version = "0.12.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" dependencies = [ "lock_api", "parking_lot_core", ] [[package]] name = "parking_lot_core" version = "0.9.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" dependencies = [ "cfg-if", "libc", "redox_syscall", "smallvec", "windows-link 0.2.1", ] [[package]] name = "percent-encoding" version = "2.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" [[package]] name = "pin-project-lite" version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" [[package]] name = "pin-utils" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" [[package]] name = "pkg-config" version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" [[package]] name = "portable-atomic" version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f84267b20a16ea918e43c6a88433c2d54fa145c92a811b5b047ccbe153674483" [[package]] name = "portable-atomic-util" version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d8a2f0d8d040d7848a709caf78912debcc3f33ee4b3cac47d73d1e1069e83507" dependencies = [ "portable-atomic", ] [[package]] name = "potential_utf" version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "84df19adbe5b5a0782edcab45899906947ab039ccf4573713735ee7de1e6b08a" dependencies = [ "zerovec", ] [[package]] name = "ppv-lite86" version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" dependencies = [ "zerocopy", ] [[package]] name = "proc-macro2" version = "1.0.101" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "89ae43fd86e4158d6db51ad8e2b80f313af9cc74f5c0e03ccb87de09998732de" dependencies = [ "unicode-ident", ] [[package]] name = "prodash" version = "30.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a6efc566849d3d9d737c5cb06cc50e48950ebe3d3f9d70631490fff3a07b139" dependencies = [ "parking_lot", ] [[package]] name = "quinn" version = "0.11.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9e20a958963c291dc322d98411f541009df2ced7b5a4f2bd52337638cfccf20" dependencies = [ "bytes", "cfg_aliases", "pin-project-lite", "quinn-proto", "quinn-udp", "rustc-hash", "rustls", "socket2", "thiserror", "tokio", "tracing", "web-time", ] [[package]] name = "quinn-proto" version = "0.11.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f1906b49b0c3bc04b5fe5d86a77925ae6524a19b816ae38ce1e426255f1d8a31" dependencies = [ "bytes", "getrandom 0.3.3", "lru-slab", "rand", "ring", "rustc-hash", "rustls", "rustls-pki-types", "slab", "thiserror", "tinyvec", "tracing", "web-time", ] [[package]] name = "quinn-udp" version = "0.5.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "addec6a0dcad8a8d96a771f815f0eaf55f9d1805756410b39f5fa81332574cbd" dependencies = [ "cfg_aliases", "libc", "once_cell", "socket2", "tracing", "windows-sys 0.60.2", ] [[package]] name = "quote" version = "1.0.41" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ce25767e7b499d1b604768e7cde645d14cc8584231ea6b295e9c9eb22c02e1d1" dependencies = [ "proc-macro2", ] [[package]] name = "r-efi" version = "5.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" [[package]] name = "rand" version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" dependencies = [ "rand_chacha", "rand_core", ] [[package]] name = "rand_chacha" version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" dependencies = [ "ppv-lite86", "rand_core", ] [[package]] name = "rand_core" version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" dependencies = [ "getrandom 0.3.3", ] [[package]] name = "rayon" version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "368f01d005bf8fd9b1206fb6fa653e6c4a81ceb1466406b81792d87c5677a58f" dependencies = [ "either", "rayon-core", ] [[package]] name = "rayon-core" version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "22e18b0f0062d30d4230b2e85ff77fdfe4326feb054b9783a3460d8435c8ab91" dependencies = [ "crossbeam-deque", "crossbeam-utils", ] [[package]] name = "redox_syscall" version = "0.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" dependencies = [ "bitflags", ] [[package]] name = "regex-automata" version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "722166aa0d7438abbaa4d5cc2c649dac844e8c56d82fb3d33e9c34b5cd268fc6" [[package]] name = "reqwest" version = "0.12.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d429f34c8092b2d42c7c93cec323bb4adeb7c67698f70839adec842ec10c7ceb" dependencies = [ "async-compression", "base64", "bytes", "encoding_rs", "futures-channel", "futures-core", "futures-util", "h2", "http", "http-body", "http-body-util", "hyper", "hyper-rustls", "hyper-util", "js-sys", "log", "mime", "percent-encoding", "pin-project-lite", "quinn", "rustls", "rustls-native-certs", "rustls-pki-types", "serde", "serde_json", "serde_urlencoded", "sync_wrapper", "tokio", "tokio-rustls", "tokio-util", "tower", "tower-http", "tower-service", "url", "wasm-bindgen", "wasm-bindgen-futures", "web-sys", "webpki-roots", ] [[package]] name = "ring" version = "0.17.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" dependencies = [ "cc", "cfg-if", "getrandom 0.2.16", "libc", "untrusted", "windows-sys 0.52.0", ] [[package]] name = "rustc-demangle" version = "0.1.26" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56f7d92ca342cea22a06f2121d944b4fd82af56988c270852495420f961d4ace" [[package]] name = "rustc-hash" version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" [[package]] name = "rustc-stable-hash" version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "781442f29170c5c93b7185ad559492601acdc71d5bb0706f5868094f45cfcd08" [[package]] name = "rustix" version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cd15f8a2c5551a84d56efdc1cd049089e409ac19a3072d5037a17fd70719ff3e" dependencies = [ "bitflags", "errno", "libc", "linux-raw-sys", "windows-sys 0.61.2", ] [[package]] name = "rustls" version = "0.23.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cd3c25631629d034ce7cd9940adc9d45762d46de2b0f57193c4443b92c6d4d40" dependencies = [ "once_cell", "ring", "rustls-pki-types", "rustls-webpki", "subtle", "zeroize", ] [[package]] name = "rustls-native-certs" version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7fcff2dd52b58a8d98a70243663a0d234c4e2b79235637849d15913394a247d3" dependencies = [ "openssl-probe", "rustls-pki-types", "schannel", "security-framework", ] [[package]] name = "rustls-pki-types" version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "229a4a4c221013e7e1f1a043678c5cc39fe5171437c88fb47151a21e6f5b5c79" dependencies = [ "web-time", "zeroize", ] [[package]] name = "rustls-webpki" version = "0.103.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e10b3f4191e8a80e6b43eebabfac91e5dcecebb27a71f04e820c47ec41d314bf" dependencies = [ "ring", "rustls-pki-types", "untrusted", ] [[package]] name = "rustversion" version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" [[package]] name = "ryu" version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" [[package]] name = "same-file" version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" dependencies = [ "winapi-util", ] [[package]] name = "schannel" version = "0.1.28" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "891d81b926048e76efe18581bf793546b4c0eaf8448d72be8de2bbee5fd166e1" dependencies = [ "windows-sys 0.61.2", ] [[package]] name = "scopeguard" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] name = "security-framework" version = "3.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b3297343eaf830f66ede390ea39da1d462b6b0c1b000f420d0a83f898bbbe6ef" dependencies = [ "bitflags", "core-foundation 0.10.1", "core-foundation-sys", "libc", "security-framework-sys", ] [[package]] name = "security-framework-sys" version = "2.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cc1f0cbffaac4852523ce30d8bd3c5cdc873501d96ff467ca09b6767bb8cd5c0" dependencies = [ "core-foundation-sys", "libc", ] [[package]] name = "semver" version = "1.0.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" dependencies = [ "serde", "serde_core", ] [[package]] name = "serde" version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" dependencies = [ "serde_core", "serde_derive", ] [[package]] name = "serde_core" version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "serde_json" version = "1.0.145" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "402a6f66d8c709116cf22f558eab210f5a50187f702eb4d7e5ef38d9a7f1c79c" dependencies = [ "itoa", "memchr", "ryu", "serde", "serde_core", ] [[package]] name = "serde_urlencoded" version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" dependencies = [ "form_urlencoded", "itoa", "ryu", "serde", ] [[package]] name = "sha1" version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" dependencies = [ "cfg-if", "cpufeatures", "digest", ] [[package]] name = "sha1-checked" version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "89f599ac0c323ebb1c6082821a54962b839832b03984598375bff3975b804423" dependencies = [ "digest", "sha1", ] [[package]] name = "sha2" version = "0.10.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" dependencies = [ "cfg-if", "cpufeatures", "digest", ] [[package]] name = "shell-words" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24188a676b6ae68c3b2cb3a01be17fbf7240ce009799bb56d5b1409051e78fde" [[package]] name = "shlex" version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" [[package]] name = "simd-adler32" version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" [[package]] name = "slab" version = "0.4.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a2ae44ef20feb57a68b23d846850f861394c2e02dc425a50098ae8c90267589" [[package]] name = "smallvec" version = "1.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" [[package]] name = "smol_str" version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9676b89cd56310a87b93dec47b11af744f34d5fc9f367b829474eec0a891350d" dependencies = [ "borsh", "serde", ] [[package]] name = "socket2" version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "233504af464074f9d066d7b5416c5f9b894a5862a6506e306f7b816cdd6f1807" dependencies = [ "libc", "windows-sys 0.59.0", ] [[package]] name = "stable_deref_trait" version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" [[package]] name = "static_assertions" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" [[package]] name = "subtle" version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "syn" version = "2.0.106" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ede7c438028d4436d71104916910f5bb611972c5cfd7f89b8300a8186e6fada6" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] [[package]] name = "sync_wrapper" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" dependencies = [ "futures-core", ] [[package]] name = "synstructure" version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "system-configuration" version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b" dependencies = [ "bitflags", "core-foundation 0.9.4", "system-configuration-sys", ] [[package]] name = "system-configuration-sys" version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4" dependencies = [ "core-foundation-sys", "libc", ] [[package]] name = "tame-index" version = "0.23.1" dependencies = [ "bytes", "camino", "cargo_metadata", "crossbeam-channel", "gix", "home", "http", "libc", "memchr", "rayon", "reqwest", "rustc-stable-hash", "semver", "serde", "serde_json", "sha2", "smol_str", "tempfile", "thiserror", "tiny-bench", "tokio", "toml-span", "twox-hash", ] [[package]] name = "tempfile" version = "3.23.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2d31c77bdf42a745371d260a26ca7163f1e0924b64afa0b688e61b5a9fa02f16" dependencies = [ "fastrand", "getrandom 0.3.3", "once_cell", "rustix", "windows-sys 0.61.2", ] [[package]] name = "thiserror" version = "2.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" version = "2.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "tiny-bench" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b0a1b520125a81e27ea0dab5c8b070fdc24d93c62f1ae12da12aa60a6f1d3c" [[package]] name = "tinystr" version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5d4f6d1145dcb577acf783d4e601bc1d76a13337bb54e6233add580b07344c8b" dependencies = [ "displaydoc", "zerovec", ] [[package]] name = "tinyvec" version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bfa5fdc3bce6191a1dbc8c02d5c8bffcf557bafa17c124c5264a458f1b0613fa" 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 = "tokio" version = "1.47.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "89e49afdadebb872d3145a5638b59eb0691ea23e46ca484037cfab3b76b95038" dependencies = [ "backtrace", "bytes", "io-uring", "libc", "mio", "pin-project-lite", "slab", "socket2", "windows-sys 0.59.0", ] [[package]] name = "tokio-rustls" version = "0.26.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61" dependencies = [ "rustls", "tokio", ] [[package]] name = "tokio-util" version = "0.7.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "14307c986784f72ef81c89db7d9e28d6ac26d16213b109ea501696195e6e3ce5" dependencies = [ "bytes", "futures-core", "futures-sink", "pin-project-lite", "tokio", ] [[package]] name = "toml-span" version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2d36acfca70d66f9b5f9c4786fec60096c3594169bf77b8d4207174dc862e6a4" dependencies = [ "smallvec", ] [[package]] name = "tower" version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d039ad9159c98b70ecfd540b2573b97f7f52c3e8d9f8ad57a24b916a536975f9" dependencies = [ "futures-core", "futures-util", "pin-project-lite", "sync_wrapper", "tokio", "tower-layer", "tower-service", ] [[package]] name = "tower-http" version = "0.6.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "adc82fd73de2a9722ac5da747f12383d2bfdb93591ee6c58486e0097890f05f2" dependencies = [ "bitflags", "bytes", "futures-util", "http", "http-body", "iri-string", "pin-project-lite", "tower", "tower-layer", "tower-service", ] [[package]] name = "tower-layer" version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" [[package]] name = "tower-service" version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" [[package]] name = "tracing" version = "0.1.41" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" dependencies = [ "pin-project-lite", "tracing-core", ] [[package]] name = "tracing-core" version = "0.1.34" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9d12581f227e93f094d3af2ae690a574abb8a2b9b7a96e7cfe9647b2b617678" dependencies = [ "once_cell", ] [[package]] name = "try-lock" version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" [[package]] name = "twox-hash" version = "2.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ea3136b675547379c4bd395ca6b938e5ad3c3d20fad76e7fe85f9e0d011419c" [[package]] name = "typenum" version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" [[package]] name = "unicode-bom" version = "2.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7eec5d1121208364f6793f7d2e222bf75a915c19557537745b195b253dd64217" [[package]] name = "unicode-ident" version = "1.0.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f63a545481291138910575129486daeaf8ac54aee4387fe7906919f7830c7d9d" [[package]] name = "unicode-normalization" version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5033c97c4262335cded6d6fc3e5c18ab755e1a3dc96376350f3d8e9f009ad956" dependencies = [ "tinyvec", ] [[package]] name = "untrusted" version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" [[package]] name = "url" version = "2.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08bc136a29a3d1758e07a9cca267be308aeebf5cfd5a10f3f67ab2097683ef5b" dependencies = [ "form_urlencoded", "idna", "percent-encoding", "serde", ] [[package]] name = "utf8_iter" version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" [[package]] name = "vcpkg" version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" [[package]] name = "version_check" version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" [[package]] name = "walkdir" version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" dependencies = [ "same-file", "winapi-util", ] [[package]] name = "want" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" dependencies = [ "try-lock", ] [[package]] name = "wasi" version = "0.11.1+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" [[package]] name = "wasi" version = "0.14.7+wasi-0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "883478de20367e224c0090af9cf5f9fa85bed63a95c1abf3afc5c083ebc06e8c" dependencies = [ "wasip2", ] [[package]] name = "wasip2" version = "1.0.1+wasi-0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7" dependencies = [ "wit-bindgen", ] [[package]] name = "wasm-bindgen" version = "0.2.104" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c1da10c01ae9f1ae40cbfac0bac3b1e724b320abfcf52229f80b547c0d250e2d" dependencies = [ "cfg-if", "once_cell", "rustversion", "wasm-bindgen-macro", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-backend" version = "0.2.104" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "671c9a5a66f49d8a47345ab942e2cb93c7d1d0339065d4f8139c486121b43b19" dependencies = [ "bumpalo", "log", "proc-macro2", "quote", "syn", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-futures" version = "0.4.54" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7e038d41e478cc73bae0ff9b36c60cff1c98b8f38f8d7e8061e79ee63608ac5c" dependencies = [ "cfg-if", "js-sys", "once_cell", "wasm-bindgen", "web-sys", ] [[package]] name = "wasm-bindgen-macro" version = "0.2.104" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7ca60477e4c59f5f2986c50191cd972e3a50d8a95603bc9434501cf156a9a119" dependencies = [ "quote", "wasm-bindgen-macro-support", ] [[package]] name = "wasm-bindgen-macro-support" version = "0.2.104" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9f07d2f20d4da7b26400c9f4a0511e6e0345b040694e8a75bd41d578fa4421d7" dependencies = [ "proc-macro2", "quote", "syn", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" version = "0.2.104" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bad67dc8b2a1a6e5448428adec4c3e84c43e561d8c9ee8a9e5aabeb193ec41d1" dependencies = [ "unicode-ident", ] [[package]] name = "web-sys" version = "0.3.81" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9367c417a924a74cae129e6a2ae3b47fabb1f8995595ab474029da749a8be120" dependencies = [ "js-sys", "wasm-bindgen", ] [[package]] name = "web-time" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" dependencies = [ "js-sys", "wasm-bindgen", ] [[package]] name = "webpki-roots" version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32b130c0d2d49f8b6889abc456e795e82525204f27c42cf767cf0d7734e089b8" dependencies = [ "rustls-pki-types", ] [[package]] name = "winapi-util" version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ "windows-sys 0.61.2", ] [[package]] name = "windows-link" version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" [[package]] name = "windows-link" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" [[package]] name = "windows-registry" version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b8a9ed28765efc97bbc954883f4e6796c33a06546ebafacbabee9696967499e" dependencies = [ "windows-link 0.1.3", "windows-result", "windows-strings", ] [[package]] name = "windows-result" version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6" dependencies = [ "windows-link 0.1.3", ] [[package]] name = "windows-strings" version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57" dependencies = [ "windows-link 0.1.3", ] [[package]] name = "windows-sys" version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ "windows-targets 0.52.6", ] [[package]] name = "windows-sys" version = "0.59.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" dependencies = [ "windows-targets 0.52.6", ] [[package]] name = "windows-sys" version = "0.60.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" dependencies = [ "windows-targets 0.53.5", ] [[package]] name = "windows-sys" version = "0.61.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" dependencies = [ "windows-link 0.2.1", ] [[package]] name = "windows-targets" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ "windows_aarch64_gnullvm 0.52.6", "windows_aarch64_msvc 0.52.6", "windows_i686_gnu 0.52.6", "windows_i686_gnullvm 0.52.6", "windows_i686_msvc 0.52.6", "windows_x86_64_gnu 0.52.6", "windows_x86_64_gnullvm 0.52.6", "windows_x86_64_msvc 0.52.6", ] [[package]] name = "windows-targets" version = "0.53.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" dependencies = [ "windows-link 0.2.1", "windows_aarch64_gnullvm 0.53.1", "windows_aarch64_msvc 0.53.1", "windows_i686_gnu 0.53.1", "windows_i686_gnullvm 0.53.1", "windows_i686_msvc 0.53.1", "windows_x86_64_gnu 0.53.1", "windows_x86_64_gnullvm 0.53.1", "windows_x86_64_msvc 0.53.1", ] [[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_gnullvm" version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" [[package]] name = "windows_aarch64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" [[package]] name = "windows_aarch64_msvc" version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" [[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_gnu" version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3" [[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_gnullvm" version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" [[package]] name = "windows_i686_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" [[package]] name = "windows_i686_msvc" version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" [[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_gnu" version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" [[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_gnullvm" version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" [[package]] name = "windows_x86_64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "windows_x86_64_msvc" version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" [[package]] name = "winnow" version = "0.7.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "21a0236b59786fed61e2a80582dd500fe61f18b5dca67a4a067d0bc9039339cf" dependencies = [ "memchr", ] [[package]] name = "wit-bindgen" version = "0.46.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" [[package]] name = "writeable" version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ea2f10b9bb0928dfb1b42b65e1f9e36f7f54dbdf08457afefb38afcdec4fa2bb" [[package]] name = "yoke" version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5f41bb01b8226ef4bfd589436a297c53d118f65921786300e427be8d487695cc" dependencies = [ "serde", "stable_deref_trait", "yoke-derive", "zerofrom", ] [[package]] name = "yoke-derive" version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "38da3c9736e16c5d3c8c597a9aaa5d1fa565d0532ae05e27c24aa62fb32c0ab6" dependencies = [ "proc-macro2", "quote", "syn", "synstructure", ] [[package]] name = "zerocopy" version = "0.8.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0894878a5fa3edfd6da3f88c4805f4c8558e2b996227a3d864f47fe11e38282c" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" version = "0.8.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "88d2b8d9c68ad2b9e4340d7832716a4d21a22a1154777ad56ea55c51a9cf3831" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "zerofrom" version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5" dependencies = [ "zerofrom-derive", ] [[package]] name = "zerofrom-derive" version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" dependencies = [ "proc-macro2", "quote", "syn", "synstructure", ] [[package]] name = "zeroize" version = "1.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" [[package]] name = "zerotrie" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "36f0bbd478583f79edad978b407914f61b2972f5af6fa089686016be8f9af595" dependencies = [ "displaydoc", "yoke", "zerofrom", ] [[package]] name = "zerovec" version = "0.11.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e7aa2bd55086f1ab526693ecbe444205da57e25f4489879da80635a46d90e73b" dependencies = [ "yoke", "zerofrom", "zerovec-derive", ] [[package]] name = "zerovec-derive" version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b96237efa0c878c64bd89c436f661be4e46b2f3eff1ebb976f7ef2321d2f58f" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "zlib-rs" version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2f06ae92f42f5e5c42443fd094f245eb656abf56dd7cce9b8b263236565e00f2" tame-index-0.23.1/Cargo.toml0000644000000070130000000000100111510ustar # 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 = "2024" rust-version = "1.85.0" name = "tame-index" version = "0.23.1" build = false autolib = false autobins = false autoexamples = false autotests = false autobenches = false description = "Provides access to local and remote cargo registry indices" homepage = "https://github.com/EmbarkStudios/tame-index" documentation = "https://docs.rs/tame-index" readme = "README.md" license = "Apache-2.0 OR MIT" repository = "https://github.com/EmbarkStudios/tame-index" [package.metadata.docs.rs] features = ["__internal_all"] rustdoc-args = [ "--cfg", "docsrs", ] [features] __git = [] __internal_all = [ "git", "sparse", "local-builder", ] default = ["reqwest?/rustls-tls-webpki-roots"] git = ["gix-reqwest"] gix-curl = [ "gix/blocking-http-transport-curl", "__git", ] gix-reqwest = [ "gix/blocking-http-transport-reqwest", "dep:reqwest", "__git", ] local = [ "dep:sha2", "dep:bytes", ] local-builder = [ "local", "dep:reqwest", ] native-certs = ["reqwest?/rustls-tls-native-roots"] sparse = [ "dep:reqwest", "dep:tokio", "dep:rayon", "dep:crossbeam-channel", ] [lib] name = "tame_index" path = "src/lib.rs" [[test]] name = "cache" path = "tests/cache.rs" [[test]] name = "flock" path = "tests/flock.rs" [[test]] name = "git" path = "tests/git.rs" [[test]] name = "local" path = "tests/local.rs" [[test]] name = "sparse" path = "tests/sparse.rs" [[test]] name = "utils" path = "tests/utils.rs" [[bench]] name = "sparse" path = "benches/sparse.rs" harness = false required-features = ["sparse"] [dependencies.bytes] version = "1.10" optional = true [dependencies.camino] version = "1.1" [dependencies.crossbeam-channel] version = "0.5" optional = true [dependencies.gix] version = "0.73" features = [] optional = true default-features = false [dependencies.home] version = "0.5" [dependencies.http] version = "1.3" [dependencies.memchr] version = "2.5" [dependencies.rayon] version = "1.7" optional = true [dependencies.reqwest] version = "0.12" features = [ "blocking", "gzip", "http2", ] optional = true default-features = false [dependencies.rustc-stable-hash] version = "0.1" [dependencies.semver] version = "1.0" features = ["serde"] [dependencies.serde] version = "1.0" features = [ "derive", "rc", ] [dependencies.serde_json] version = "1.0" [dependencies.sha2] version = "0.10" features = ["std"] optional = true default-features = false [dependencies.smol_str] version = "0.3" features = ["serde"] [dependencies.thiserror] version = "2.0" [dependencies.tokio] version = "1.44" features = [ "rt-multi-thread", "time", ] optional = true default-features = false [dependencies.toml-span] version = "0.5" [dependencies.twox-hash] version = "2.1" features = ["xxhash64"] default-features = false [dev-dependencies.cargo_metadata] version = "0.23" [dev-dependencies.rayon] version = "1.7" [dev-dependencies.tempfile] version = "3.15" [dev-dependencies.tiny-bench] version = "0.4" [target."cfg(unix)".dependencies.libc] version = "0.2" [profile.dev.package.sha2] opt-level = 3 tame-index-0.23.1/Cargo.toml.orig000064400000000000000000000066571046102023000146470ustar 00000000000000[package] name = "tame-index" version = "0.23.1" edition = "2024" rust-version = "1.85.0" description = "Provides access to local and remote cargo registry indices" license = "Apache-2.0 OR MIT" documentation = "https://docs.rs/tame-index" homepage = "https://github.com/EmbarkStudios/tame-index" repository = "https://github.com/EmbarkStudios/tame-index" [features] default = ["reqwest?/rustls-tls-webpki-roots"] # Enables the built-in support for fetching and reading a git registry index git = ["gix-reqwest"] # The default is reqwest since that doesn't pull in (a ton of) C code gix-reqwest = ["gix/blocking-http-transport-reqwest", "dep:reqwest", "__git"] # Unfortunately need to support curl as well, though we don't test or care about it, in addition # to having 2 HTTP client implementations if sparse is also enabled gix-curl = ["gix/blocking-http-transport-curl", "__git"] # "private" feature flag which is the one actually used in code __git = [] # Enables the built-in support for requesting index entries from a HTTP sparse registry index sparse = ["dep:reqwest", "dep:tokio", "dep:rayon", "dep:crossbeam-channel"] # Enables local registry support local = ["dep:sha2", "dep:bytes"] # Enables helpers for building a local registry local-builder = ["local", "dep:reqwest"] # Enables the use of OS native certificate store. # Should be used with `default-features = false` to also disable webpki-roots, which is activated by default. native-certs = ["reqwest?/rustls-tls-native-roots"] # We can't use all-features because of gix-curl, so this is just an alias for my sanity __internal_all = ["git", "sparse", "local-builder"] [dependencies] bytes = { version = "1.10", optional = true } # All paths are assumed to be utf-8 for ease of use and implementation camino = "1.1" # Better channels, already a dep if rayon is pulled in crossbeam-channel = { version = "0.5", optional = true } # Used to find the location of the local `CARGO_HOME` home = "0.5" # Allows calling crates to provide their own HTTP implementation, keep aligned with reqwest/hyper http = "1.3" # Nicer scanning through bytes memchr = "2.5" rayon = { version = "1.7", optional = true } # Contains hasher used by cargo 1.85.0+ rustc-stable-hash = "0.1" # Nicer versions for users semver = { version = "1.0", features = ["serde"] } # Serialization serde = { version = "1.0", features = ["derive", "rc"] } serde_json = "1.0" sha2 = { version = "0.10", optional = true, default-features = false, features = [ "std", ] } # Smaller fixed size strings with heap fallback smol_str = { version = "0.3", features = ["serde"] } # Laziness thiserror = "2.0" tokio = { version = "1.44", default-features = false, features = [ "rt-multi-thread", "time", ], optional = true } # cargo config parsing toml-span = "0.5" # Faster hashing twox-hash = { version = "2.1", default-features = false, features = ["xxhash64"] } [dependencies.gix] optional = true version = "0.73" default-features = false features = [] # Keep version aligned with gix-transport [dependencies.reqwest] optional = true version = "0.12" default-features = false features = ["blocking", "gzip", "http2"] [target.'cfg(unix)'.dependencies] libc = "0.2" [dev-dependencies] cargo_metadata = "0.23" rayon = "1.7" tempfile = "3.15" tiny-bench = "0.4" [package.metadata.docs.rs] features = ["__internal_all"] rustdoc-args = ["--cfg", "docsrs"] [profile.dev.package.sha2] opt-level = 3 [[bench]] name = "sparse" harness = false required-features = ["sparse"] tame-index-0.23.1/LICENSE-APACHE000064400000000000000000000251421046102023000136720ustar 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. tame-index-0.23.1/LICENSE-MIT000064400000000000000000000020421046102023000133740ustar 00000000000000Copyright (c) 2019 Embark Studios Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. tame-index-0.23.1/README.md000064400000000000000000000060451046102023000132260ustar 00000000000000
# `📇 tame-index` **Small crate for interacting with [cargo registry indices](https://doc.rust-lang.org/nightly/cargo/reference/registry-index.html)** [![Embark](https://img.shields.io/badge/embark-open%20source-blueviolet.svg)](https://embark.dev) [![Embark](https://img.shields.io/badge/discord-ark-%237289da.svg?logo=discord)](https://discord.gg/dAuKfZS) [![Crates.io](https://img.shields.io/crates/v/tame-index.svg)](https://crates.io/crates/tame-index) [![Docs](https://docs.rs/tame-index/badge.svg)](https://docs.rs/tame-index) [![dependency status](https://deps.rs/repo/github/EmbarkStudios/tame-index/status.svg)](https://deps.rs/repo/github/EmbarkStudios/tame-index) [![Build status](https://github.com/EmbarkStudios/tame-index/workflows/CI/badge.svg)](https://github.com/EmbarkStudios/tame-index/actions)
## Differences from [`crates-index`][0] 1. The API exposes enough pieces where an alternative git implementation can be used if `gix` is not to your liking. 1. Sparse index support via [`reqwest`](https://crates.io/crates/reqwest) is optional, gated behind the `sparse` feature flag. 1. Local cache files are always supported regardless of features enabled 1. `ComboIndexCache` (local cache files only) and `ComboIndex` (cache + remote capabilities) are provided to wrap git indices, sparse indices, or local registries depending on the the index URL. 1. Functionality for writing cache entries to the local index cache is exposed in the public API 1. [`Local Registry`](https://doc.rust-lang.org/cargo/reference/source-replacement.html#local-registry-sources) support is available behind the `local` feature flag 1. Building of local registries is available behind the `local-builder` feature flag 1. File-based locking compatible with Cargo is available to ensure `tame-index` and Cargo can play nicely together. ## Contributing [![Contributor Covenant](https://img.shields.io/badge/contributor%20covenant-v1.4-ff69b4.svg)](CODE_OF_CONDUCT.md) We welcome community contributions to this project. Please read our [Contributor Guide](CONTRIBUTING.md) for more information on how to get started. Please also read our [Contributor Terms](CONTRIBUTING.md#contributor-terms) before you make any contributions. Any contribution intentionally submitted for inclusion in an Embark Studios project, shall comply with the Rust standard licensing model (MIT OR Apache 2.0) and therefore be dual licensed as described below, without any additional terms or conditions: ### License This contribution is dual licensed under EITHER OF - Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or ) - MIT license ([LICENSE-MIT](LICENSE-MIT) or ) at your option. For clarity, "your" refers to Embark or any other licensee/user of the contribution. [0]: https://crates.io/crates/crates-index tame-index-0.23.1/benches/sparse.rs000064400000000000000000000376701046102023000152310ustar 00000000000000//! Shutup clippy type KrateSet = std::collections::BTreeSet; fn main() { let rt = tokio::runtime::Runtime::new().unwrap(); let _rt = rt.enter(); let bdir = tempfile::tempdir().unwrap(); let adir = tempfile::tempdir().unwrap(); // The setup of the indices is the same, and is not interesting to measure let bindex = { let loc = tame_index::IndexLocation { url: tame_index::IndexUrl::CratesIoSparse, root: tame_index::IndexPath::Exact(bdir.path().to_owned().try_into().unwrap()), cargo_version: Some(semver::Version::new(1, 85, 0)), }; tame_index::index::RemoteSparseIndex::new( tame_index::SparseIndex::new(loc).unwrap(), tame_index::external::reqwest::blocking::ClientBuilder::new() .build() .unwrap(), ) }; let aindex = { let loc = tame_index::IndexLocation { url: tame_index::IndexUrl::CratesIoSparse, root: tame_index::IndexPath::Exact(adir.path().to_owned().try_into().unwrap()), cargo_version: Some(semver::Version::new(1, 85, 0)), }; tame_index::index::AsyncRemoteSparseIndex::new( tame_index::SparseIndex::new(loc).unwrap(), tame_index::external::reqwest::ClientBuilder::new() .build() .unwrap(), ) }; let ks: KrateSet = KRATES.iter().map(|s| (*s).to_owned()).collect(); let label = "sparse_fetch"; let cfg = tiny_bench::BenchmarkConfig { num_samples: 10, ..Default::default() }; tiny_bench::bench_with_setup_configuration_labeled( label, &cfg, || std::fs::remove_dir_all(bdir.path()), |_| blocking(&bindex, &ks), ); tiny_bench::bench_with_setup_configuration_labeled( label, &cfg, || std::fs::remove_dir_all(adir.path()), |_| asunc(&aindex, &ks), ); } fn blocking(rsi: &tame_index::index::RemoteSparseIndex, krates: &KrateSet) { let krates = rsi.krates( krates.clone(), true, &tame_index::index::FileLock::unlocked(), ); for (krate, res) in krates { if let Err(err) = res { panic!("failed to download '{krate}': {err}"); } } } fn asunc(rsi: &tame_index::index::AsyncRemoteSparseIndex, krates: &KrateSet) { let krates = rsi .krates_blocking( krates.clone(), true, None, &tame_index::index::FileLock::unlocked(), ) .unwrap(); for (krate, res) in krates { if let Err(err) = res { panic!("failed to download '{krate}': {err}"); } } } /// The krates we want to sync. This is a "large" number to actually surpass /// the core count of whatever machine the benchmarks are run on as well as /// actually sending enough requests to see if there is a meaningful difference /// between parallel + blocking and async const KRATES: &[&str] = &[ "ab_glyph", "ab_glyph_rasterizer", "accesskit", "addr2line", "adler", "aead", "aes", "aes-gcm", "ahash", "aho-corasick", "alsa", "alsa-sys", "ambient-authority", "android-activity", "android-properties", "anstream", "anstyle", "anstyle-parse", "anstyle-query", "anstyle-wincon", "anyhow", "anymap2", "app_dirs2", "arbitrary", "array-init", "arrayvec", "ash", "ash-molten", "ash-window", "assert-json-diff", "async-backtrace", "async-backtrace-attributes", "async-channel", "async-compression", "async-io", "async-lock", "async-recursion", "async-stream", "async-stream-impl", "async-trait", "atk-sys", "atomic_refcell", "autocfg", "autometrics", "autometrics-macros", "axum", "axum-core", "axum-extra", "axum-macros", "backtrace", "base-x", "base64", "bb8", "bb8-postgres", "bincode", "bindgen", "bit-set", "bit-vec", "bitflags", "bitvec", "block", "block-buffer", "block-sys", "block2", "bstr", "bumpalo", "bytemuck", "bytemuck_derive", "byteorder", "bytes", "bytes-varint", "cairo-sys-rs", "calloop", "camino", "cap-fs-ext", "cap-primitives", "cap-rand", "cap-std", "cap-time-ext", "cargo-manifest", "cargo-platform", "cargo_metadata", "cc", "cervo-asset", "cervo-core", "cervo-nnef", "cervo-onnx", "cervo-runtime", "cesu8", "cexpr", "cfg-expr", "cfg-if", "cfg_aliases", "cint", "cipher", "clang-sys", "clap", "clap_builder", "clap_derive", "clap_lex", "clipboard-win", "cocoa", "cocoa-foundation", "color_quant", "colorchoice", "combine", "concurrent-queue", "console", "console-api", "console-subscriber", "cookie", "copypasta", "core-foundation", "core-foundation-sys", "core-graphics", "core-graphics-types", "coreaudio-rs", "coreaudio-sys", "coremidi", "coremidi-sys", "cpp_demangle", "cpufeatures", "cranelift-bforest", "cranelift-codegen", "cranelift-codegen-meta", "cranelift-codegen-shared", "cranelift-control", "cranelift-entity", "cranelift-frontend", "cranelift-isle", "cranelift-native", "cranelift-wasm", "crash-context", "crash-handler", "crc32fast", "crossbeam-channel", "crossbeam-deque", "crossbeam-epoch", "crossbeam-utils", "crunchy", "crypto-common", "ctr", "custom_debug", "custom_debug_derive", "darling", "darling_core", "darling_macro", "dashmap", "dasp_sample", "data-encoding", "data-encoding-macro", "data-encoding-macro-internal", "data-url", "debugid", "derive-new", "derive_arbitrary", "derive_builder", "derive_builder_core", "derive_builder_macro", "derive_more", "digest", "dirs", "dirs-sys", "discord-sdk", "dispatch", "dmsort", "doc-comment", "dolly", "downcast-rs", "dyn-clone", "ecolor", "educe", "egui", "egui-winit", "either", "emath", "embed-resource", "encode_unicode", "endian-type", "enum-ordinalize", "enum-primitive-derive", "enumn", "env_logger", "epaint", "errno", "errno-dragonfly", "euclid", "event-listener", "fallible-iterator", "fastrand", "fd-lock", "fdeflate", "filetime", "findshlibs", "fixedbitset", "fixedvec", "flate2", "float-cmp", "float_eq", "float_next_after", "fnv", "foreign-types", "foreign-types-shared", "form_urlencoded", "fs-set-times", "fs2", "fsevent-sys", "funty", "futures", "futures-channel", "futures-core", "futures-executor", "futures-io", "futures-lite", "futures-macro", "futures-sink", "futures-task", "futures-util", "fxhash", "fxprof-processed-profile", "gdk-pixbuf-sys", "gdk-sys", "generator", "generic-array", "gethostname", "getrandom", "ghash", "gimli", "gio-sys", "glam", "glib-sys", "glob", "gltf", "gltf-derive", "gltf-json", "gobject-sys", "goblin", "google-cloud-gax", "google-cloud-googleapis", "google-cloud-pubsub", "google-cloud-token", "gpu-allocator", "gtk-sys", "h2", "half", "hashbag", "hashbrown", "hdrhistogram", "headers", "headers-core", "heck", "hermit-abi", "hex", "highway", "hmac", "home", "hound", "http", "http-body", "http-range-header", "httparse", "httpdate", "humantime", "hyper", "hyper-rustls", "hyper-timeout", "ident_case", "idna", "image", "include_dir", "include_dir_macros", "indexmap", "inflections", "inotify", "inotify-sys", "inout", "insta", "instant", "io-extras", "io-kit-sys", "io-lifetimes", "ipnet", "iri-string", "is-terminal", "itertools", "itoa", "ittapi", "ittapi-sys", "jni", "jni-sys", "jobserver", "js-sys", "jsonwebtoken", "kqueue", "kqueue-sys", "kstring", "lazy-bytes-cast", "lazy_static", "lazycell", "leb128", "lewton", "libc", "libloading", "libm", "libmimalloc-sys", "libudev-sys", "line-wrap", "linked-hash-map", "linux-raw-sys", "liquid", "liquid-core", "liquid-derive", "liquid-lib", "lock_api", "log", "loom", "lyon_geom", "lyon_path", "lyon_tessellation", "lz4_flex", "mach", "mach2", "malloc_buf", "maplit", "mapr", "matchers", "matchit", "matrixmultiply", "maybe-owned", "md-5", "memchr", "memfd", "memmap2", "memoffset", "metal", "metrics", "metrics-exporter-prometheus", "metrics-macros", "metrics-util", "mimalloc", "mime", "minidump-common", "minidump-writer", "minidumper", "minimal-lexical", "miniz_oxide", "mio", "mockito", "multibase", "multimap", "named_pipe", "natord", "ndarray", "ndk", "ndk-context", "ndk-sys", "nibble_vec", "nix", "no-std-compat", "nohash-hasher", "nom", "normpath", "notify", "ntapi", "nu-ansi-term", "num-bigint", "num-complex", "num-derive", "num-integer", "num-rational", "num-traits", "num_cpus", "num_enum", "num_enum_derive", "objc", "objc-foundation", "objc-sys", "objc2", "objc2-encode", "objc_exception", "objc_id", "object", "oboe", "oboe-sys", "ogg", "once_cell", "opaque-debug", "openapiv3", "opener", "openssl-probe", "opentelemetry", "opentelemetry-http", "opentelemetry-otlp", "opentelemetry-proto", "opentelemetry-semantic-conventions", "opentelemetry-zipkin", "opentelemetry_api", "opentelemetry_sdk", "orbclient", "ordered-float", "os_info", "overload", "owned_ttf_parser", "pango-sys", "paranoid-android", "parking", "parking_lot", "parking_lot_core", "paste", "path_abs", "peeking_take_while", "pem", "percent-encoding", "perchance", "pest", "pest_derive", "pest_generator", "pest_meta", "petgraph", "phf", "phf_shared", "physx", "physx-sys", "pin-project", "pin-project-internal", "pin-project-lite", "pin-utils", "pkg-config", "plain", "plist", "png", "polling", "polyval", "portable-atomic", "postgres-protocol", "postgres-types", "pprof", "ppv-lite86", "presser", "prettyplease", "proc-macro-crate", "proc-macro-error", "proc-macro-error-attr", "proc-macro2", "prost", "prost-build", "prost-derive", "prost-types", "psm", "public-api", "puffin", "puffin_egui", "puffin_http", "quanta", "quick-xml", "quickcheck", "quickcheck_macros", "quote", "radium", "radix_trie", "rand", "rand_chacha", "rand_core", "rand_distr", "range-map", "raw-cpuid", "raw-window-handle", "raw-window-metal", "rawpointer", "rayon", "rayon-core", "redis-async", "redox_syscall", "redox_users", "regalloc2", "regex", "regex-automata", "regex-syntax", "ring", "ron", "rspirv", "rspirv-reflect", "rustc-demangle", "rustc-hash", "rustc_version", "rustdoc-json", "rustdoc-types", "rustix", "rustls", "rustls-native-certs", "rustls-pemfile", "rustls-webpki", "rustversion", "rymder", "ryu", "sadness-generator", "safemem", "same-file", "scan_fmt", "schannel", "schemars", "schemars_derive", "scoped-tls", "scopeguard", "scroll", "scroll_derive", "sct", "security-framework", "security-framework-sys", "self_cell", "semver", "sentry-types", "serde", "serde_derive", "serde_derive_internals", "serde_json", "serde_path_to_error", "serde_qs", "serde_repr", "serde_spanned", "serde_urlencoded", "serde_yaml", "serial_test", "serial_test_derive", "sha1", "sha2", "sharded-slab", "shellexpand", "shlex", "signal-hook", "signal-hook-registry", "simd-adler32", "similar", "simple_asn1", "siphasher", "sketches-ddsketch", "slab", "sled", "slice-group-by", "slotmap", "smallvec", "smart-default", "socket2", "spez", "spin", "spirv", "spirv-std", "spirv-std-macros", "spirv-std-types", "sptr", "stable-vec", "stable_deref_trait", "static_assertions", "std_prelude", "stfu8", "string-interner", "stringprep", "strsim", "strum", "strum_macros", "subtle", "superluminal-perf", "superluminal-perf-sys", "symbolic-common", "symbolic-debuginfo", "symbolic-demangle", "syn", "sync_wrapper", "synstructure", "sysinfo", "system-deps", "system-interface", "tame-gcs", "tame-oauth", "tame-oidc", "tame-webpurify", "tap", "tar", "target-lexicon", "tempfile", "thiserror", "thiserror-impl", "thread_local", "time", "time-core", "time-macros", "tiny-bench", "tinyvec", "tinyvec_macros", "tokio", "tokio-io-timeout", "tokio-macros", "tokio-postgres", "tokio-retry", "tokio-rustls", "tokio-stream", "tokio-test", "tokio-tungstenite", "tokio-util", "toml", "toml_datetime", "toml_edit", "tonic", "tower", "tower-http", "tower-layer", "tower-service", "tracing", "tracing-appender", "tracing-attributes", "tracing-core", "tracing-futures", "tracing-log", "tracing-logfmt", "tracing-opentelemetry", "tracing-subscriber", "tract-core", "tract-data", "tract-hir", "tract-nnef", "tract-onnx", "tract-onnx-opl", "tracy-client", "tracy-client-sys", "try-lock", "tryhard", "ttf-parser", "tungstenite", "twox-hash", "typed-builder", "typenum", "ucd-trie", "uds", "uname", "unicode-bidi", "unicode-ident", "unicode-normalization", "unicode-segmentation", "unicode-xid", "universal-hash", "unsafe-libyaml", "untrusted", "url", "urlencoding", "utf-8", "utf8parse", "uuid", "valuable", "vec1", "vec_map", "version-compare", "version_check", "vswhom", "vswhom-sys", "waker-fn", "walkdir", "want", "wasi", "wasi-cap-std-sync", "wasi-common", "wasm-bindgen", "wasm-bindgen-backend", "wasm-bindgen-futures", "wasm-bindgen-macro", "wasm-bindgen-macro-support", "wasm-bindgen-shared", "wasmbin", "wasmbin-derive", "wasmparser", "wasmtime", "wasmtime-asm-macros", "wasmtime-cranelift", "wasmtime-cranelift-shared", "wasmtime-environ", "wasmtime-jit", "wasmtime-jit-debug", "wasmtime-jit-icache-coherence", "wasmtime-runtime", "wasmtime-types", "wasmtime-wasi", "wast", "web-sys", "webbrowser", "webpki-roots", "which", "wiggle", "wiggle-generate", "wiggle-macro", "winapi", "winapi-i686-pc-windows-gnu", "winapi-util", "winapi-wsapoll", "winapi-x86_64-pc-windows-gnu", "windows", "windows-core", "windows-sys", "windows-targets", "windows_aarch64_gnullvm", "windows_aarch64_msvc", "windows_i686_gnu", "windows_i686_msvc", "windows_x86_64_gnu", "windows_x86_64_gnullvm", "windows_x86_64_msvc", "winnow", "winreg", "winx", "witx", "wyz", "x11-clipboard", "x11-dl", "x11rb", "x11rb-protocol", "xattr", "xdg", "yaml-rust", "zip", "zstd", "zstd-safe", "zstd-sys", ]; tame-index-0.23.1/deny.toml000064400000000000000000000017051046102023000136010ustar 00000000000000[graph] targets = [ "x86_64-unknown-linux-musl", "x86_64-unknown-linux-gnu", "aarch64-unknown-linux-musl", "aarch64-unknown-linux-gnu", "aarch64-apple-darwin", "x86_64-pc-windows-msvc", ] all-features = true [advisories] yanked = "deny" ignore = [] [licenses] allow = [ "MIT", "Apache-2.0", "CDLA-Permissive-2.0", "ISC", "BSD-3-Clause", "Unicode-3.0", "Zlib", ] exceptions = [ ] [bans] multiple-versions = "deny" deny = [ "openssl", # Unfortunately we can't ban this since some people want a curl client instead of reqwest #"curl", ] skip = [ { crate = "core-foundation@0.9.4", reason = "system-configuration uses this old version" }, { crate = "getrandom@0.2.16", reason = "ring uses this old version" }, { crate = "hashbrown@0.15.5", reason = "gix uses this old version" }, ] skip-tree = [ "windows-sys", ] [sources] unknown-registry = "deny" unknown-git = "deny" allow-git = [] tame-index-0.23.1/release.toml000064400000000000000000000012531046102023000142600ustar 00000000000000pre-release-commit-message = "Release {{version}}" tag-message = "Release {{version}}" tag-name = "{{version}}" pre-release-replacements = [ { file = "CHANGELOG.md", search = "Unreleased", replace = "{{version}}" }, { file = "CHANGELOG.md", search = "\\.\\.\\.HEAD", replace = "...{{tag_name}}" }, { file = "CHANGELOG.md", search = "ReleaseDate", replace = "{{date}}" }, { file = "CHANGELOG.md", search = "", replace = "\n## [Unreleased] - ReleaseDate" }, { file = "CHANGELOG.md", search = "", replace = "\n[Unreleased]: https://github.com/EmbarkStudios/tame-index/compare/{{tag_name}}...HEAD" }, ] tame-index-0.23.1/src/error.rs000064400000000000000000000163201046102023000142320ustar 00000000000000//! Provides the various error types for this crate #[cfg(feature = "__git")] pub use crate::index::git_remote::GitError; #[cfg(feature = "local")] pub use crate::index::local::LocalRegistryError; /// The core error type for this library #[derive(Debug, thiserror::Error)] pub enum Error { /// Failed to deserialize a local cache entry #[error(transparent)] Cache(#[from] CacheError), /// This library assumes utf-8 paths in all cases, a path was provided that /// was not valid utf-8 #[error("unable to use non-utf8 path {:?}", .0)] NonUtf8Path(std::path::PathBuf), /// An environment variable was located, but had a non-utf8 value #[error("environment variable {} has a non-utf8 value", .0)] NonUtf8EnvVar(std::borrow::Cow<'static, str>), /// A user-provided string was not a valid crate name #[error(transparent)] InvalidKrateName(#[from] InvalidKrateName), /// The user specified a registry name that did not exist in any searched /// .cargo/config.toml #[error("registry '{}' was not located in any .cargo/config.toml", .0)] UnknownRegistry(String), /// An I/O error #[error(transparent)] Io(#[from] std::io::Error), /// An I/O error occurred trying to access a specific path #[error("I/O operation failed for path '{}': {}", .1, .0)] IoPath(#[source] std::io::Error, crate::PathBuf), /// A user provided URL was invalid #[error(transparent)] InvalidUrl(#[from] InvalidUrl), /// Failed to de/serialize JSON #[error(transparent)] Json(#[from] serde_json::Error), /// Failed to deserialize TOML #[error(transparent)] Toml(#[from] Box), /// An index entry did not contain any versions #[error("index entry contained no versions for the crate")] NoCrateVersions, /// Failed to handle an HTTP response or request #[error(transparent)] Http(#[from] HttpError), /// An error occurred doing a git operation #[cfg(feature = "__git")] #[error(transparent)] Git(#[from] GitError), /// Failed to parse a semver version or requirement #[error(transparent)] Semver(#[from] semver::Error), /// A local registry is invalid #[cfg(feature = "local")] #[error(transparent)] Local(#[from] LocalRegistryError), /// Failed to lock a file #[error(transparent)] Lock(#[from] crate::utils::flock::FileLockError), } impl From for Error { fn from(p: std::path::PathBuf) -> Self { Self::NonUtf8Path(p) } } /// Various kinds of reserved names disallowed by cargo #[derive(Debug, Copy, Clone)] pub enum ReservedNameKind { /// The name is a Rust keyword Keyword, /// The name conflicts with a cargo artifact directory Artifact, /// The name has a special meaning on Windows Windows, /// The name conflicts with a Rust std library name Standard, } impl std::fmt::Display for ReservedNameKind { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { Self::Keyword => f.write_str("rustlang keyword"), Self::Artifact => f.write_str("cargo artifact"), Self::Windows => f.write_str("windows reserved"), Self::Standard => f.write_str("rustlang std library"), } } } /// Errors that can occur when validating a crate name #[derive(Debug, thiserror::Error)] pub enum InvalidKrateName { /// The name had an invalid length #[error("crate name had an invalid length of '{0}'")] InvalidLength(usize), /// The name contained an invalid character #[error("invalid character '{invalid}` @ {index}")] InvalidCharacter { /// The invalid character invalid: char, /// The index of the character in the provided string index: usize, }, /// The name was one of the reserved names disallowed by cargo #[error("the name '{reserved}' is reserved as '{kind}`")] ReservedName { /// The name that was reserved reserved: &'static str, /// The kind of the reserved name kind: ReservedNameKind, }, } /// An error pertaining to a bad URL provided to the API #[derive(Debug, thiserror::Error)] #[error("the url '{url}' is invalid")] pub struct InvalidUrl { /// The invalid url pub url: String, /// The reason it is invalid pub source: InvalidUrlError, } /// The specific reason for the why the URL is invalid #[derive(Debug, thiserror::Error)] pub enum InvalidUrlError { /// Sparse HTTP registry urls must be of the form `sparse+http(s)://` #[error("sparse indices require the use of a url that starts with `sparse+http`")] MissingSparse, /// The `+://` is not supported #[error("the scheme modifier is unknown")] UnknownSchemeModifier, /// Unable to find the `://` #[error("the scheme is missing")] MissingScheme, /// Attempted to construct a git index with a sparse URL #[error("attempted to create a git index for a sparse URL")] SparseForGit, } /// Errors related to a local index cache #[derive(Debug, thiserror::Error)] pub enum CacheError { /// The cache entry is malformed #[error("the cache entry is malformed")] InvalidCacheEntry, /// The cache version is old #[error("the cache entry is an old, unsupported version")] OutdatedCacheVersion, /// The cache version is newer than the version supported by this crate #[error("the cache entry is an unknown version, possibly written by a newer cargo version")] UnknownCacheVersion, /// The index version is newer than the version supported by this crate #[error( "the cache entry's index version is unknown, possibly written by a newer cargo version" )] UnknownIndexVersion, /// The revision in the cache entry did match the requested revision /// /// This can occur when a git index is fetched and a newer revision is pulled /// from the remote index, invalidating all local cache entries #[error("the cache entry's revision does not match the current revision")] OutdatedRevision, /// A crate version in the cache file was malformed #[error("a specific version in the cache entry is malformed")] InvalidCrateVersion, } /// Errors related to HTTP requests or responses #[derive(Debug, thiserror::Error)] pub enum HttpError { /// A [`reqwest::Error`] #[cfg(any(feature = "sparse", feature = "local-builder"))] #[error(transparent)] Reqwest(#[from] reqwest::Error), /// A status code was received that indicates user error, or possibly a /// remote index that does not follow the protocol supported by this crate #[error("status code '{code}': {msg}")] StatusCode { /// The status code code: http::StatusCode, /// The reason the status code raised an error msg: &'static str, }, /// A [`http::Error`] #[error(transparent)] Http(#[from] http::Error), /// A string could not be parsed as a valid header value #[error(transparent)] InvalidHeaderValue(#[from] http::header::InvalidHeaderValue), /// Unable to complete an async request for an `AsyncRemoteSparseIndex` within /// the user allotted time #[error("request could not be completed in the allotted timeframe")] Timeout, } tame-index-0.23.1/src/index/cache.rs000064400000000000000000000250441046102023000152560ustar 00000000000000//! Provides functionality for reading and writing cargo compatible .cache entries //! that can be wrapped by another index that has logic for fetching entries //! that aren't in the cache //! //! Cargo creates small cache entries for crates when they are accessed during //! any cargo operation that accesses a registry index (update/add/etc). //! Initially this was to accelerate accessing the contents of a bare clone of //! a git registry index as it skips accessing git blobs. //! //! Now with sparse HTTP indices, these .cache files are even more important as //! they allow skipping network access if in offline mode, as well as allowing //! responses from servers to tell the client they have the latest version if //! that crate has not been changed since it was last accessed. //! //! ```txt //! +-------------------+---------------------------+------------------+---+ //! | cache version :u8 | index format version :u32 | revision :string | 0 | //! +-------------------+---------------------------+------------------+---+ //! ``` //! //! followed by 1+ //! //! ```txt //! +----------------+---+-----------+---+ //! | semver version | 0 | JSON blob | 0 | //! +----------------+---+-----------+---+ //! ``` /// The current (cargo 1.54.0+) cache version for cache entries. /// /// This value's sole purpose is in determining if cargo will read or skip (and /// probably overwrite) a .cache entry. pub const CURRENT_CACHE_VERSION: u8 = 3; /// The maximum version of the `v` field in the index this crate supports pub const INDEX_V_MAX: u32 = 2; /// The byte representation of [`INDEX_V_MAX`] const INDEX_V_MAX_BYTES: [u8; 4] = INDEX_V_MAX.to_le_bytes(); use super::FileLock; use crate::{CacheError, Error, IndexKrate, KrateName, PathBuf}; /// A wrapper around a byte buffer that has been (partially) validated to be a /// valid cache entry pub struct ValidCacheEntry<'buffer> { /// The cache entry's revision /// /// For git indices this will be the sha1 of the HEAD commit when the cache /// entry was written /// /// For sparse indicies, this will be an HTTP header from the response that /// was last written to disk, which is currently either `etag: ` or /// `last-modified: ` pub revision: &'buffer str, /// Portion of the buffer containing the individual version entries for the /// cache entry pub version_entries: &'buffer [u8], } impl<'buffer> ValidCacheEntry<'buffer> { /// Attempts to read a cache entry from a block of bytes. /// /// This can fail for a few reasons /// 1. The cache version does not match the version(s) supported /// 2. The index version is higher than that supported /// 3. There is not at least 1 version entry pub fn read(mut buffer: &'buffer [u8]) -> Result { let cache_version = *buffer.first().ok_or(CacheError::InvalidCacheEntry)?; match cache_version.cmp(&CURRENT_CACHE_VERSION) { std::cmp::Ordering::Less => return Err(CacheError::OutdatedCacheVersion), std::cmp::Ordering::Greater => return Err(CacheError::UnknownCacheVersion), std::cmp::Ordering::Equal => {} } buffer = &buffer[1..]; let index_version = u32::from_le_bytes( buffer .get(0..4) .ok_or(CacheError::InvalidCacheEntry) .and_then(|b| b.try_into().map_err(|_e| CacheError::InvalidCacheEntry))?, ); if INDEX_V_MAX > index_version { return Err(CacheError::UnknownIndexVersion); } buffer = &buffer[4..]; let mut iter = split(buffer, 0); let revision = std::str::from_utf8(iter.next().ok_or(CacheError::InvalidCacheEntry)?) .map_err(|_e| CacheError::OutdatedRevision)?; // Ensure there is at least one valid entry, it _should_ be impossible // to have an empty cache entry since you can't publish something to an // index and still have zero versions let _version = iter.next().ok_or(CacheError::InvalidCacheEntry)?; let _blob = iter.next().ok_or(CacheError::InvalidCacheEntry)?; let version_entries = &buffer[revision.len() + 1..]; Ok(Self { revision, version_entries, }) } /// Deserializes this cache entry into a [`IndexKrate`] /// /// If specified, the `revision` will be used to ignore cache entries /// that are outdated pub fn to_krate(&self, revision: Option<&str>) -> Result, Error> { if let Some(iv) = revision { if iv != self.revision { return Ok(None); } } Ok(Some(IndexKrate::from_cache(split( self.version_entries, 0, ))?)) } } impl IndexKrate { /// Reads entries from the versions portion of a cache file pub(crate) fn from_cache<'cache>( mut iter: impl Iterator + 'cache, ) -> Result { let mut versions = Vec::new(); // Each entry is a tuple of (semver, version_json) while iter.next().is_some() { let version_slice = iter .next() .ok_or(Error::Cache(CacheError::InvalidCrateVersion))?; let version: crate::IndexVersion = serde_json::from_slice(version_slice)?; versions.push(version); } Ok(Self { versions }) } /// Writes a cache entry with the specified revision to an [`std::io::Write`] /// /// Note this method creates its own internal [`std::io::BufWriter`], there /// is no need to wrap it yourself pub fn write_cache_entry( &self, writer: &mut W, revision: &str, ) -> Result<(), std::io::Error> { use std::io::Write; const SPLIT: &[u8] = &[0]; let mut w = std::io::BufWriter::new(writer); w.write_all(&[CURRENT_CACHE_VERSION])?; w.write_all(&INDEX_V_MAX_BYTES)?; w.write_all(revision.as_bytes())?; w.write_all(SPLIT)?; // crates.io limits crate names to a maximum of 64 characters, but this // only applies to crates.io and not any cargo index, so don't set a hard // limit let mut semver = String::with_capacity(64); for iv in &self.versions { semver.clear(); // SAFETY: the only way this would fail would be OOM std::fmt::write(&mut semver, format_args!("{}", iv.version)).unwrap(); w.write_all(semver.as_bytes())?; w.write_all(SPLIT)?; serde_json::to_writer(&mut w, &iv)?; w.write_all(SPLIT)?; } w.flush() } } /// Gives an iterator over the specified buffer, where each item is split by the specified /// needle value pub fn split(haystack: &[u8], needle: u8) -> impl Iterator + '_ { struct Split<'a> { haystack: &'a [u8], needle: u8, } impl<'a> Iterator for Split<'a> { type Item = &'a [u8]; #[inline] fn next(&mut self) -> Option<&'a [u8]> { if self.haystack.is_empty() { return None; } let (ret, remaining) = match memchr::memchr(self.needle, self.haystack) { Some(pos) => (&self.haystack[..pos], &self.haystack[pos + 1..]), None => (self.haystack, &[][..]), }; self.haystack = remaining; Some(ret) } } Split { haystack, needle } } /// The [`IndexCache`] allows access to the local cache entries for a remote index /// /// This implementation does no network I/O whatsoever, but does do disk I/O pub struct IndexCache { /// The root disk location of the local index pub(super) path: PathBuf, } impl IndexCache { /// Creates a local index exactly at the specified path #[inline] pub fn at_path(path: PathBuf) -> Self { Self { path } } /// Reads a crate from the local cache of the index. /// /// You may optionally pass in the revision the cache entry is expected to /// have, if it does match the cache entry will be ignored and an error returned #[inline] pub fn cached_krate( &self, name: KrateName<'_>, revision: Option<&str>, lock: &FileLock, ) -> Result, Error> { let Some(contents) = self.read_cache_file(name, lock)? else { return Ok(None); }; let valid = ValidCacheEntry::read(&contents)?; valid.to_krate(revision) } /// Writes the specified crate and revision to the cache pub fn write_to_cache( &self, krate: &IndexKrate, revision: &str, _lock: &FileLock, ) -> Result { let name = krate.name().try_into()?; let cache_path = self.cache_path(name); std::fs::create_dir_all(cache_path.parent().unwrap())?; let mut cache_file = match std::fs::File::create(&cache_path) { Ok(cf) => cf, Err(err) => return Err(Error::IoPath(err, cache_path)), }; // It's unfortunate if this fails for some reason, but // not writing the cache entry shouldn't stop the user // from getting the crate's metadata match krate.write_cache_entry(&mut cache_file, revision) { Ok(_) => Ok(cache_path), Err(err) => { drop(cache_file); // _attempt_ to delete the file, to clean up after ourselves let _ = std::fs::remove_file(&cache_path); Err(Error::IoPath(err, cache_path)) } } } /// Gets the path the crate's cache file would be located at if it exists #[inline] pub fn cache_path(&self, name: KrateName<'_>) -> PathBuf { let rel_path = name.relative_path(None); // avoid realloc on each push let mut cache_path = PathBuf::with_capacity(self.path.as_str().len() + 8 + rel_path.len()); cache_path.push(&self.path); cache_path.push(".cache"); cache_path.push(rel_path); cache_path } /// Attempts to read the cache entry for the specified crate /// /// It is recommended to use [`Self::cached_krate`] #[inline] pub fn read_cache_file( &self, name: KrateName<'_>, _lock: &FileLock, ) -> Result>, Error> { let cache_path = self.cache_path(name); match std::fs::read(&cache_path) { Ok(cb) => Ok(Some(cb)), Err(err) if err.kind() == std::io::ErrorKind::NotFound => Ok(None), Err(err) => Err(Error::IoPath(err, cache_path)), } } } tame-index-0.23.1/src/index/combo.rs000064400000000000000000000043141046102023000153070ustar 00000000000000#[cfg(feature = "local")] use crate::index::LocalRegistry; use crate::{ Error, IndexKrate, KrateName, index::{FileLock, RemoteGitIndex, RemoteSparseIndex}, }; /// A wrapper around either a [`RemoteGitIndex`] or [`RemoteSparseIndex`] #[non_exhaustive] pub enum ComboIndex { /// A standard git based registry index. No longer the default for crates.io /// as of 1.70.0 Git(Box), /// An HTTP sparse index Sparse(RemoteSparseIndex), /// A local registry #[cfg(feature = "local")] Local(LocalRegistry), } impl ComboIndex { /// Retrieves the index metadata for the specified crate name, optionally /// writing a cache entry for it if there was not already an up to date one /// /// Note no cache entry is written if this is a `Local` registry as they do /// not use .cache files #[inline] pub fn krate( &self, name: KrateName<'_>, write_cache_entry: bool, lock: &FileLock, ) -> Result, Error> { match self { Self::Git(index) => index.krate(name, write_cache_entry, lock), Self::Sparse(index) => index.krate(name, write_cache_entry, lock), #[cfg(feature = "local")] Self::Local(lr) => lr.cached_krate(name, lock), } } /// Retrieves the cached crate metadata if it exists #[inline] pub fn cached_krate( &self, name: KrateName<'_>, lock: &FileLock, ) -> Result, Error> { match self { Self::Git(index) => index.cached_krate(name, lock), Self::Sparse(index) => index.cached_krate(name, lock), #[cfg(feature = "local")] Self::Local(lr) => lr.cached_krate(name, lock), } } } impl From for ComboIndex { #[inline] fn from(index: RemoteGitIndex) -> Self { Self::Git(Box::new(index)) } } impl From for ComboIndex { #[inline] fn from(index: RemoteSparseIndex) -> Self { Self::Sparse(index) } } #[cfg(feature = "local")] impl From for ComboIndex { #[inline] fn from(local: LocalRegistry) -> Self { Self::Local(local) } } tame-index-0.23.1/src/index/git.rs000064400000000000000000000053421046102023000147750ustar 00000000000000use super::{FileLock, IndexCache}; use crate::{Error, IndexKrate, KrateName, PathBuf}; /// The URL of the crates.io index for use with git pub const CRATES_IO_INDEX: &str = "https://github.com/rust-lang/crates.io-index"; /// Allows access to a cargo git registry index /// /// Uses Cargo's cache. pub struct GitIndex { pub(super) cache: IndexCache, #[allow(dead_code)] pub(super) url: String, /// The sha-1 head commit id encoded as hex pub head: Option<[u8; 40]>, } impl GitIndex { /// Creates a new git index for the specified location #[inline] pub fn new(il: crate::index::IndexLocation<'_>) -> Result { if il.url.is_sparse() { return Err(crate::InvalidUrl { url: il.url.as_str().to_owned(), source: crate::InvalidUrlError::SparseForGit, } .into()); } let (path, url) = il.into_parts()?; Ok(Self { cache: IndexCache::at_path(path), url, head: None, }) } /// Sets the sha-1 id for the head commit. /// /// If set, this will be used to disregard cache entries that do not match #[inline] pub fn set_head_commit(&mut self, commit_id: Option<[u8; 20]>) { if let Some(id) = &commit_id { let mut hex_head = [0u8; 40]; crate::utils::encode_hex(id, &mut hex_head); self.head = Some(hex_head); } else { self.head = None; } } /// Gets the hex-encoded sha-1 id for the head commit #[inline] pub fn head_commit(&self) -> Option<&str> { self.head.as_ref().map(|hc| { // SAFETY: the buffer is always ASCII hex #[allow(unsafe_code)] unsafe { std::str::from_utf8_unchecked(hc) } }) } /// Reads a crate from the local cache of the index. /// /// There are no guarantees around freshness, and no network I/O will be /// performed. #[inline] pub fn cached_krate( &self, name: KrateName<'_>, lock: &FileLock, ) -> Result, Error> { self.cache.cached_krate(name, self.head_commit(), lock) } /// Writes the specified crate to the cache. /// /// Note that no I/O will be performed if `blob_id` or [`Self::set_head_commit`] /// has not been set to `Some` #[inline] pub fn write_to_cache( &self, krate: &IndexKrate, blob_id: Option<&str>, lock: &FileLock, ) -> Result, Error> { let Some(id) = blob_id.or_else(|| self.head_commit()) else { return Ok(None); }; self.cache.write_to_cache(krate, id, lock).map(Some) } } tame-index-0.23.1/src/index/git_remote.rs000064400000000000000000000451471046102023000163570ustar 00000000000000use super::{FileLock, GitIndex}; use crate::{Error, IndexKrate, KrateName}; use std::sync::atomic::AtomicBool; /// Uses a "bare" git index that fetches files directly from the repo instead of /// using a local checkout, the same as cargo itself. /// /// Uses cargo's cache pub struct RemoteGitIndex { index: GitIndex, repo: gix::Repository, head_commit: gix::ObjectId, } const DIR: gix::remote::Direction = gix::remote::Direction::Fetch; impl RemoteGitIndex { /// Creates a new [`Self`] that can access and write local cache entries, /// and contact the remote index to retrieve the latest index information /// /// Note that if a repository does not exist at the local disk path of the /// provided [`GitIndex`], a full clone will be performed. #[inline] pub fn new(index: GitIndex, lock: &FileLock) -> Result { Self::with_options( index, gix::progress::Discard, &gix::interrupt::IS_INTERRUPTED, lock, ) } /// Breaks [`Self`] into its component parts /// /// This method is useful if you need thread safe access to the repository #[inline] pub fn into_parts(self) -> (GitIndex, gix::Repository) { (self.index, self.repo) } /// Creates a new [`Self`] that allows showing of progress of the the potential /// fetch if the disk location is empty, as well as allowing interruption /// of the fetch operation. pub fn with_options

( mut index: GitIndex, progress: P, should_interrupt: &AtomicBool, _lock: &FileLock, ) -> Result where P: gix::NestedProgress, P::SubProgress: 'static, { let open_or_clone_repo = || -> Result<_, GitError> { let mut mapping = gix::sec::trust::Mapping::default(); let open_with_complete_config = gix::open::Options::default().permissions(gix::open::Permissions { config: gix::open::permissions::Config { // Be sure to get all configuration, some of which is only known by the git binary. // That way we are sure to see all the systems credential helpers git_binary: true, ..Default::default() }, ..Default::default() }); mapping.reduced = open_with_complete_config.clone(); mapping.full = open_with_complete_config.clone(); // Attempt to open the repository, if it fails for any reason, // attempt to perform a fresh clone instead let repo = gix::ThreadSafeRepository::discover_opts( &index.cache.path, gix::discover::upwards::Options::default().apply_environment(), mapping, ) .ok() .map(|repo| repo.to_thread_local()) .filter(|repo| { // The `cargo` standard registry clone has no configured origin (when created with `git2`). repo.find_remote("origin").map_or(true, |remote| { remote .url(DIR) .is_some_and(|remote_url| remote_url.to_bstring() == index.url) }) }) .or_else(|| gix::open_opts(&index.cache.path, open_with_complete_config).ok()); let res = if let Some(repo) = repo { (repo, None) } else { // We need to create the directory chain ourselves, gix will fail // if any parent directory is missing if !index.cache.path.exists() { std::fs::create_dir_all(&index.cache.path).map_err(|source| { GitError::ClonePrep(Box::new(gix::clone::Error::Init( gix::init::Error::Init(gix::create::Error::CreateDirectory { source, path: index.cache.path.clone().into(), }), ))) })?; } let (repo, out) = gix::prepare_clone_bare(index.url.as_str(), &index.cache.path) .map_err(Box::new)? .with_remote_name("origin") .map_err(Box::new)? .configure_remote(|remote| { Ok(remote.with_refspecs(["+HEAD:refs/remotes/origin/HEAD"], DIR)?) }) .fetch_only(progress, should_interrupt) .map_err(|err| GitError::from(Box::new(err)))?; (repo, Some(out)) }; Ok(res) }; let (mut repo, fetch_outcome) = open_or_clone_repo()?; if let Some(fetch_outcome) = fetch_outcome { crate::utils::git::write_fetch_head( &repo, &fetch_outcome, &repo.find_remote("origin").unwrap(), )?; } repo.object_cache_size_if_unset(4 * 1024 * 1024); let head_commit = Self::set_head(&mut index, &repo)?; Ok(Self { repo, index, head_commit, }) } /// Gets the local index #[inline] pub fn local(&self) -> &GitIndex { &self.index } /// Get the configuration of the index. /// /// See the [cargo docs](https://doc.rust-lang.org/cargo/reference/registry-index.html#index-configuration) pub fn index_config(&self) -> Result { let blob = self.read_blob("config.json")?.ok_or_else(|| { Error::Io(std::io::Error::new( std::io::ErrorKind::NotFound, "unable to find config.json", )) })?; Ok(serde_json::from_slice(&blob.data)?) } /// Sets the head commit in the wrapped index so that cache entries can be /// properly filtered #[inline] fn set_head(index: &mut GitIndex, repo: &gix::Repository) -> Result { let find_remote_head = || -> Result { const CANDIDATE_REFS: &[&str] = &[ "FETCH_HEAD", /* the location with the most-recent updates, as written by git2 */ "origin/HEAD", /* typical refspecs update this symbolic ref to point to the actual remote ref with the fetched commit */ "origin/master", /* for good measure, resolve this branch by hand in case origin/HEAD is broken */ "HEAD", ]; let mut candidates: Vec<_> = CANDIDATE_REFS .iter() .enumerate() .filter_map(|(i, refname)| { let ref_id = repo .find_reference(*refname) .ok()? .into_fully_peeled_id() .ok()?; let commit = ref_id.object().ok()?.try_into_commit().ok()?; let commit_time = commit.time().ok()?.seconds; Some((i, commit.id, commit_time)) }) .collect(); // Sort from oldest to newest, the last one will be the best reference // we could reasonably locate, and since we are on second resolution, // prefer the ordering of candidates if times are equal. // // This allows FETCH_HEAD to be authoritative, unless one of the other // references is more up to date, which can occur in (at least) 2 scenarios: // // 1. The repo is a fresh clone by cargo either via git or libgit2, // neither of which write FETCH_HEAD during clone // 2. A fetch was performed by an external crate/program to cargo or // ourselves that didn't update FETCH_HEAD candidates.sort_by(|a, b| match a.2.cmp(&b.2) { std::cmp::Ordering::Equal => b.0.cmp(&a.0), o => o, }); // get the most recent commit, the one with most time passed since unix epoch. Ok(candidates .last() .ok_or_else(|| GitError::UnableToFindRemoteHead)? .1) }; let gix::ObjectId::Sha1(sha1) = find_remote_head()?; index.set_head_commit(Some(sha1)); Ok(gix::ObjectId::Sha1(sha1)) } /// Attempts to read the specified crate's index metadata /// /// An attempt is first made to read the cache entry for the crate, and /// falls back to reading the metadata from the git blob it is stored in /// /// This method does no network I/O pub fn krate( &self, name: KrateName<'_>, write_cache_entry: bool, lock: &FileLock, ) -> Result, Error> { if let Ok(Some(cached)) = self.cached_krate(name, lock) { return Ok(Some(cached)); } let Some(blob) = self.read_blob(&name.relative_path(None))? else { return Ok(None); }; let krate = IndexKrate::from_slice(&blob.data)?; if write_cache_entry { // It's unfortunate if fail to write to the cache, but we still were // able to retrieve the contents from git let mut hex_id = [0u8; 40]; let gix::ObjectId::Sha1(sha1) = blob.id; let blob_id = crate::utils::encode_hex(&sha1, &mut hex_id); let _ = self.index.write_to_cache(&krate, Some(blob_id), lock); } Ok(Some(krate)) } fn read_blob(&self, path: &str) -> Result, GitError> { let tree = self .repo .find_object(self.head_commit) .map_err(Box::new)? .try_into_commit()? .tree()?; let Some(entry) = tree .lookup_entry_by_path(path) .map_err(|err| GitError::BlobLookup(Box::new(err)))? else { return Ok(None); }; let blob = entry .object() .map_err(|err| GitError::BlobLookup(Box::new(err)))?; // Sanity check this is a blob, it _shouldn't_ be possible to get anything // else (like a subtree), but better safe than sorry if blob.kind != gix::object::Kind::Blob { return Ok(None); } Ok(Some(blob.detach())) } /// Attempts to read the locally cached crate information /// /// Note this method has improvements over using [`GitIndex::cached_krate`]. /// /// In older versions of cargo, only the head commit hash is used as the version /// for cached crates, which means a fetch invalidates _all_ cached crates, /// even if they have not been modified in any commits since the previous /// fetch. /// /// This method does the same thing as cargo, which is to allow _either_ /// the head commit oid _or_ the blob oid as a version, which is more /// granular and means the cached crate can remain valid as long as it is /// not updated in a subsequent fetch. [`GitIndex::cached_krate`] cannot take /// advantage of that though as it does not have access to git and thus /// cannot know the blob id. #[inline] pub fn cached_krate( &self, name: KrateName<'_>, lock: &FileLock, ) -> Result, Error> { let Some(cached) = self.index.cache.read_cache_file(name, lock)? else { return Ok(None); }; let valid = crate::index::cache::ValidCacheEntry::read(&cached)?; if Some(valid.revision) != self.index.head_commit() { let Some(blob) = self.read_blob(&name.relative_path(None))? else { return Ok(None); }; let mut hex_id = [0u8; 40]; let gix::ObjectId::Sha1(sha1) = blob.id; let blob_id = crate::utils::encode_hex(&sha1, &mut hex_id); if valid.revision != blob_id { return Ok(None); } } valid.to_krate(None) } /// Performs a fetch from the remote index repository. /// /// This method performs network I/O. #[inline] pub fn fetch(&mut self, lock: &FileLock) -> Result<(), Error> { self.fetch_with_options( gix::progress::Discard, &gix::interrupt::IS_INTERRUPTED, lock, ) } /// Same as [`Self::fetch`] but allows specifying a progress implementation /// and allows interruption of the network operations pub fn fetch_with_options

( &mut self, mut progress: P, should_interrupt: &AtomicBool, _lock: &FileLock, ) -> Result<(), Error> where P: gix::NestedProgress, P::SubProgress: 'static, { // We're updating the reflog which requires a committer be set, which might // not be the case, particular in a CI environment, but also would default // the the git config for the current directory/global, which on a normal // user machine would show the user was the one who updated the database which // is kind of misleading, so we just override the config for this operation let mut config = self.repo.config_snapshot_mut(); config .set_raw_value(&"committer.name", "tame-index") .map_err(GitError::from)?; // Note we _have_ to set the email as well, but luckily gix does not actually // validate if it's a proper email or not :) config .set_raw_value(&"committer.email", "") .map_err(GitError::from)?; let repo = config .commit_auto_rollback() .map_err(|err| GitError::from(Box::new(err)))?; let mut remote = repo.find_remote("origin").ok().unwrap_or_else(|| { repo.remote_at(self.index.url.as_str()) .expect("owned URL is always valid") }); remote .replace_refspecs(Some("+HEAD:refs/remotes/origin/HEAD"), DIR) .expect("valid statically known refspec"); // Perform the actual fetch let outcome = remote .connect(DIR) .map_err(|err| GitError::from(Box::new(err)))? .prepare_fetch(&mut progress, Default::default()) .map_err(|err| GitError::from(Box::new(err)))? .receive(&mut progress, should_interrupt) .map_err(|err| GitError::from(Box::new(err)))?; crate::utils::git::write_fetch_head(&repo, &outcome, &remote)?; self.head_commit = Self::set_head(&mut self.index, &repo)?; Ok(()) } } /// Errors that can occur during a git operation #[derive(Debug, thiserror::Error)] #[allow(missing_docs)] pub enum GitError { #[error(transparent)] ClonePrep(#[from] Box), #[error(transparent)] CloneFetch(#[from] Box), #[error(transparent)] Connect(#[from] Box), #[error(transparent)] FetchPrep(#[from] Box), #[error(transparent)] Fetch(#[from] Box), #[error(transparent)] Open(#[from] Box), #[error(transparent)] Commit(#[from] gix::object::commit::Error), #[error(transparent)] InvalidObject(#[from] gix::object::try_into::Error), #[error(transparent)] ReferenceLookup(#[from] Box), #[error(transparent)] BlobLookup(#[from] Box), #[error(transparent)] RemoteLookup(#[from] Box), #[error(transparent)] Lock(#[from] gix::lock::acquire::Error), #[error(transparent)] RemoteName(#[from] Box), #[error(transparent)] Config(#[from] Box), #[error(transparent)] ConfigValue(#[from] gix::config::file::set_raw_value::Error), #[error("unable to locate remote HEAD")] UnableToFindRemoteHead, #[error("unable to update HEAD to remote HEAD")] UnableToUpdateHead, } impl GitError { /// Returns true if the error is a (potentially) spurious network error that /// indicates a retry of the operation could succeed #[inline] pub fn is_spurious(&self) -> bool { use gix::protocol::transport::IsSpuriousError; match self { Self::Fetch(fe) => return fe.is_spurious(), Self::CloneFetch(cf) => { if let gix::clone::fetch::Error::Fetch(fe) = &**cf { return fe.is_spurious(); } } _ => {} } false } /// Returns true if a fetch could not be completed successfully due to the /// repo being locked, and could succeed if retried #[inline] pub fn is_locked(&self) -> bool { let ure = match self { Self::Fetch(fe) => { if let gix::remote::fetch::Error::UpdateRefs(ure) = &**fe { ure } else { return false; } } Self::CloneFetch(cf) => { if let gix::clone::fetch::Error::Fetch(gix::remote::fetch::Error::UpdateRefs(ure)) = &**cf { ure } else { return false; } } Self::Lock(le) => { return !matches!(le, gix::lock::acquire::Error::PermanentlyLocked { .. }); } _ => return false, }; if let gix::remote::fetch::refs::update::Error::EditReferences(ere) = ure { match ere { gix::reference::edit::Error::FileTransactionPrepare(ftpe) => { use gix::refs::file::transaction::prepare::Error as PrepError; if let PrepError::LockAcquire { source, .. } | PrepError::PackedTransactionAcquire(source) = ftpe { // currently this is either io or permanentlylocked, but just in case // more variants are added, we just assume it's possible to retry // in anything but the permanentlylocked variant !matches!(source, gix::lock::acquire::Error::PermanentlyLocked { .. }) } else { false } } gix::reference::edit::Error::FileTransactionCommit(ftce) => { matches!( ftce, gix::refs::file::transaction::commit::Error::LockCommit { .. } ) } _ => false, } } else { false } } } tame-index-0.23.1/src/index/local/builder.rs000064400000000000000000000024401046102023000167260ustar 00000000000000//! Adds functionality for retrieving crate files from a remote registry use crate::Error; /// Wrapper around a [`reqwest::blocking::Client`] to condition it correctly /// for making requests to a remote registry #[derive(Clone)] pub struct Client { inner: reqwest::blocking::Client, } impl Client { /// Creates a client from the specified builder pub fn build(builder: reqwest::blocking::ClientBuilder) -> Result { // Crates are _usually_ just stored as content-type: application/gzip // without a content-encoding, but _just in case_ we disable gzip so that // they aren't automatically decompressed by reqwest, screwing up the // checksum computation let inner = builder.no_gzip().build()?; Ok(Self { inner }) } } impl<'iv> super::ValidKrate<'iv> { /// Downloads and validates a .crate from the specified index pub fn download( client: &Client, config: &crate::index::IndexConfig, version: &'iv crate::IndexVersion, ) -> Result { let url = config.download_url(version.name.as_str().try_into()?, version.version.as_ref()); let res = client.inner.get(url).send()?.error_for_status()?; let body = res.bytes()?; Self::validate(body, version) } } tame-index-0.23.1/src/index/local.rs000064400000000000000000000260771046102023000153140ustar 00000000000000//! Contains code for reading and writing [local registries](https://doc.rust-lang.org/cargo/reference/source-replacement.html#local-registry-sources) use super::FileLock; use crate::{Error, IndexKrate, KrateName, Path, PathBuf}; use smol_str::SmolStr; #[cfg(feature = "local-builder")] pub mod builder; /// An error that can occur when validating or creating a [`LocalRegistry`] #[derive(Debug, thiserror::Error)] pub enum LocalRegistryError { /// A .crate file has a version that isnot in the index #[error("missing version {version} for crate {name}")] MissingVersion { /// The name of the crate name: String, /// The specific crate version version: SmolStr, }, /// A .crate file's checksum did not match the checksum in the index for that version #[error("checksum mismatch for {name}-{version}.crate")] ChecksumMismatch { /// The name of the crate name: String, /// The specific crate version version: SmolStr, }, } /// A [local registry](https://doc.rust-lang.org/cargo/reference/source-replacement.html#local-registry-sources) /// implementation pub struct LocalRegistry { path: PathBuf, } impl LocalRegistry { /// Opens an existing local registry, optionally validating it pub fn open(path: PathBuf, validate: bool) -> Result { if validate { Self::validate(&path)?; } Ok(Self { path }) } /// Validates the specified path contains a local registry /// /// Validation ensures every crate file matches the expected according to /// the index entry for the crate pub fn validate(path: &Path) -> Result<(), Error> { let rd = std::fs::read_dir(path).map_err(|err| Error::IoPath(err, path.to_owned()))?; // There _should_ only be one directory in the root path, `index` let index_root = path.join("index"); if !index_root.exists() { return Err(Error::IoPath( std::io::Error::new( std::io::ErrorKind::NotFound, "unable to find index directory", ), index_root, )); } // Don't bother deserializing multiple times if there are multiple versions // of the same crate let mut indexed = std::collections::BTreeMap::new(); for entry in rd { let Ok(entry) = entry else { continue; }; if entry.file_type().map_or(true, |ft| !ft.is_file()) { continue; } let Ok(path) = PathBuf::from_path_buf(entry.path()) else { continue; }; let Some(fname) = path.file_name() else { continue; }; let Some((crate_name, version)) = crate_file_components(fname) else { continue; }; let index_entry = if let Some(ie) = indexed.get(crate_name) { ie } else { let krate_name: crate::KrateName<'_> = crate_name.try_into()?; let path = index_root.join(krate_name.relative_path(None)); let index_contents = std::fs::read(&path).map_err(|err| Error::IoPath(err, path.clone()))?; let ik = IndexKrate::from_slice(&index_contents)?; indexed.insert(crate_name.to_owned(), ik); indexed.get(crate_name).unwrap() }; let index_vers = index_entry .versions .iter() .find(|kv| kv.version == version) .ok_or_else(|| LocalRegistryError::MissingVersion { name: crate_name.to_owned(), version: version.into(), })?; // Read the crate file from disk and verify its checksum matches let file = std::fs::File::open(&path).map_err(|err| Error::IoPath(err, path.clone()))?; if !validate_checksum::<{ 8 * 1024 }>(&file, &index_vers.checksum) .map_err(|err| Error::IoPath(err, path.clone()))? { return Err(LocalRegistryError::ChecksumMismatch { name: crate_name.to_owned(), version: version.into(), } .into()); } } Ok(()) } /// Gets the index information for the crate /// /// Note this naming is just to be consistent with [`crate::SparseIndex`] and /// [`crate::GitIndex`], local registries do not have a .cache in the index #[inline] pub fn cached_krate( &self, name: KrateName<'_>, _lock: &FileLock, ) -> Result, Error> { let index_path = self.krate_path(name); let buf = match std::fs::read(&index_path) { Ok(buf) => buf, Err(err) if err.kind() == std::io::ErrorKind::NotFound => return Ok(None), Err(err) => return Err(Error::IoPath(err, index_path)), }; Ok(Some(IndexKrate::from_slice(&buf)?)) } /// Gets the path to the index entry for the krate. /// /// Note that unlike .cache entries for git and sparse indices, these are not /// binary files, they are just the JSON line format #[inline] pub fn krate_path(&self, name: KrateName<'_>) -> PathBuf { make_path(&self.path, name) } } /// Allows the building of a local registry from a [`RemoteGitIndex`] or [`RemoteSparseIndex`] pub struct LocalRegistryBuilder { path: PathBuf, } impl LocalRegistryBuilder { /// Creates a builder for the specified directory. /// /// The directory is required to be empty, but it will /// be created if it doesn't exist pub fn create(path: PathBuf) -> Result { if path.exists() { let count = std::fs::read_dir(&path)?.count(); if count != 0 { return Err(Error::IoPath( std::io::Error::new( std::io::ErrorKind::AlreadyExists, format!("{count} entries already exist at the specified path"), ), path, )); } } else { std::fs::create_dir_all(&path)?; } std::fs::create_dir_all(path.join("index"))?; Ok(Self { path }) } /// Inserts the specified crate index entry and one or more crates files /// into the registry /// /// This will fail if the specified crate is already located in the index, it /// is your responsibility to insert the crate and all the versions you want /// only once pub fn insert(&self, krate: &IndexKrate, krates: &[ValidKrate<'_>]) -> Result { let index_path = make_path(&self.path, krate.name().try_into()?); if index_path.exists() { return Err(Error::IoPath( std::io::Error::new( std::io::ErrorKind::AlreadyExists, "crate has already been inserted", ), index_path, )); } let mut written = { if let Err(err) = std::fs::create_dir_all(index_path.parent().unwrap()) { return Err(Error::IoPath(err, index_path)); } let mut index_entry = std::fs::File::create(&index_path).map_err(|err| Error::IoPath(err, index_path))?; krate.write_json_lines(&mut index_entry)?; // This _should_ never fail, but even if it does, just ignore it use std::io::Seek; index_entry.stream_position().unwrap_or_default() }; for krate in krates { let krate_fname = format!("{}-{}.crate", krate.iv.name, krate.iv.version); let krate_path = self.path.join(krate_fname); std::fs::write(&krate_path, &krate.buff) .map_err(|err| Error::IoPath(err, krate_path))?; written += krate.buff.len() as u64; } Ok(written) } /// Consumes the builder and opens a [`LocalRegistry`] #[inline] pub fn finalize(self, validate: bool) -> Result { LocalRegistry::open(self.path, validate) } } /// A wrapper around the raw byte buffer for a .crate response from a remote /// index pub struct ValidKrate<'iv> { buff: bytes::Bytes, iv: &'iv crate::IndexVersion, } impl<'iv> ValidKrate<'iv> { /// Given a buffer, validates its checksum matches the specified version pub fn validate( buff: impl Into, expected: &'iv crate::IndexVersion, ) -> Result { let buff = buff.into(); let computed = { use sha2::{Digest, Sha256}; let mut hasher = Sha256::new(); hasher.update(&buff); hasher.finalize() }; if computed.as_slice() != expected.checksum.0 { return Err(LocalRegistryError::ChecksumMismatch { name: expected.name.to_string(), version: expected.version.clone(), } .into()); } Ok(Self { buff, iv: expected }) } } /// Ensures the specified stream's sha-256 matches the specified checksum #[inline] pub fn validate_checksum( mut stream: impl std::io::Read, chksum: &crate::krate::Chksum, ) -> Result { use sha2::{Digest, Sha256}; let mut buffer = [0u8; N]; let mut hasher = Sha256::new(); loop { let read = stream.read(&mut buffer)?; if read == 0 { break; } hasher.update(&buffer[..read]); } let computed = hasher.finalize(); Ok(computed.as_slice() == chksum.0) } /// Splits a crate package name into its component parts /// /// `-.crate` /// /// The naming is a bit annoying for these since the separator between /// the crate name and version is a `-` which can be present in both /// the crate name as well as the semver, so we have to take those into account #[inline] pub fn crate_file_components(name: &str) -> Option<(&str, &str)> { let name = name.strip_suffix(".crate")?; // The first `.` should be after the major version let dot = name.find('.')?; let dash_sep = name[..dot].rfind('-')?; Some((&name[..dash_sep], &name[dash_sep + 1..])) } #[inline] fn make_path(root: &Path, name: KrateName<'_>) -> PathBuf { let rel_path = name.relative_path(None); let mut index_path = PathBuf::with_capacity(root.as_str().len() + 7 + rel_path.len()); index_path.push(root); index_path.push("index"); index_path.push(rel_path); index_path } #[cfg(test)] mod test { #[test] fn gets_components() { use super::crate_file_components as cfc; assert_eq!(cfc("cc-1.0.75.crate").unwrap(), ("cc", "1.0.75")); assert_eq!( cfc("cfg-expr-0.1.0-dev+1234.crate").unwrap(), ("cfg-expr", "0.1.0-dev+1234") ); assert_eq!( cfc("android_system_properties-0.1.5.crate").unwrap(), ("android_system_properties", "0.1.5") ); } } tame-index-0.23.1/src/index/location.rs000064400000000000000000000407421046102023000160250ustar 00000000000000//! Helpers for initializing the remote and local disk location of an index use crate::{Error, Path, PathBuf}; use std::borrow::Cow; /// A remote index url #[derive(Default, Debug)] pub enum IndexUrl<'iu> { /// The canonical crates.io HTTP sparse index. /// /// See [`crate::CRATES_IO_HTTP_INDEX`] #[default] CratesIoSparse, /// The canonical crates.io git index. /// /// See [`crate::CRATES_IO_INDEX`] CratesIoGit, /// A non-crates.io index. /// /// This variant uses the url to determine the index kind (sparse or git) by /// inspecting the url's scheme. This is because sparse indices are required /// to have the `sparse+` scheme modifier NonCratesIo(Cow<'iu, str>), /// A [local registry](crate::index::LocalRegistry) Local(Cow<'iu, Path>), } impl<'iu> IndexUrl<'iu> { /// Gets the url as a string pub fn as_str(&'iu self) -> &'iu str { match self { Self::CratesIoSparse => crate::CRATES_IO_HTTP_INDEX, Self::CratesIoGit => crate::CRATES_IO_INDEX, Self::NonCratesIo(url) => url, Self::Local(pb) => pb.as_str(), } } /// Returns true if the url points to a sparse registry pub fn is_sparse(&self) -> bool { match self { Self::CratesIoSparse => true, Self::CratesIoGit | Self::Local(..) => false, Self::NonCratesIo(url) => url.starts_with("sparse+http"), } } /// Gets the [`IndexUrl`] for crates.io, depending on the local environment. /// /// 1. Determines if the crates.io registry has been [replaced](https://doc.rust-lang.org/cargo/reference/source-replacement.html) /// 2. Determines if the protocol was explicitly [configured](https://doc.rust-lang.org/cargo/reference/config.html#registriescrates-ioprotocol) by the user /// 3. Otherwise, detects the version of cargo (see [`crate::utils::cargo_version`]), and uses that to determine the appropriate default pub fn crates_io( config_root: Option, cargo_home: Option<&Path>, cargo_version: Option<&str>, ) -> Result { // If the crates.io registry has been replaced it doesn't matter what // the protocol for it has been changed to if let Some(replacement) = get_source_replacement( config_root.clone(), cargo_home, crate::CRATES_IO_INDEX, "crates-io", )? { return Ok(replacement); } let sparse_index = match std::env::var("CARGO_REGISTRIES_CRATES_IO_PROTOCOL") .ok() .as_deref() { Some("sparse") => true, Some("git") => false, _ => { let sparse_index = read_cargo_config(config_root, cargo_home, |_, config| { match config .pointer("/registries/crates-io/protocol") .and_then(|p| p.as_str())? { "sparse" => Some(true), "git" => Some(false), _ => None, } })?; if let Some(si) = sparse_index { si } else { let vers = match cargo_version { Some(v) => v.trim().parse()?, None => crate::utils::cargo_version(None)?, }; vers >= semver::Version::new(1, 70, 0) } } }; Ok(if sparse_index { Self::CratesIoSparse } else { Self::CratesIoGit }) } /// Creates an [`IndexUrl`] for the specified registry name /// /// 1. Checks if [`CARGO_REGISTRIES__INDEX`](https://doc.rust-lang.org/cargo/reference/config.html#registriesnameindex) is set /// 2. Checks if the source for the registry has been [replaced](https://doc.rust-lang.org/cargo/reference/source-replacement.html) /// 3. Uses the value of [`registries..index`](https://doc.rust-lang.org/cargo/reference/config.html#registriesnameindex) otherwise pub fn for_registry_name( config_root: Option, cargo_home: Option<&Path>, registry_name: &str, ) -> Result { // Check if the index was explicitly specified let mut env = String::with_capacity(17 + registry_name.len() + 6); env.push_str("CARGO_REGISTRIES_"); if registry_name.is_ascii() { for c in registry_name.chars() { if c == '-' { env.push('_'); } else { env.push(c.to_ascii_uppercase()); } } } else { let mut upper = registry_name.to_uppercase(); if upper.contains('-') { upper = upper.replace('-', "_"); } env.push_str(&upper); } env.push_str("_INDEX"); match std::env::var(&env) { Ok(index) => return Ok(Self::NonCratesIo(index.into())), Err(err) => { if let std::env::VarError::NotUnicode(_nu) = err { return Err(Error::NonUtf8EnvVar(env.into())); } } } let registry_url = read_cargo_config(config_root.clone(), cargo_home, |_, config| { let path = format!("/registries/{registry_name}/index"); config.pointer(&path)?.as_str().map(String::from) })? .ok_or_else(|| Error::UnknownRegistry(registry_name.into()))?; if let Some(replacement) = get_source_replacement( config_root.clone(), cargo_home, ®istry_url, registry_name, )? { return Ok(replacement); } Ok(Self::NonCratesIo(registry_url.into())) } } impl<'iu> From<&'iu str> for IndexUrl<'iu> { #[inline] fn from(s: &'iu str) -> Self { Self::NonCratesIo(s.into()) } } /// The local disk location to place an index #[derive(Default)] pub enum IndexPath { /// The default cargo home root path #[default] CargoHome, /// User-specified root path UserSpecified(PathBuf), /// An exact path on disk where an index is located. /// /// Unlike the other two variants, this variant won't take the index's url /// into account to calculate the unique url hash as part of the full path Exact(PathBuf), } impl From> for IndexPath { /// Converts an optional path to a rooted path. /// /// This never constructs a [`Self::Exact`], that can only be done explicitly fn from(pb: Option) -> Self { if let Some(pb) = pb { Self::UserSpecified(pb) } else { Self::CargoHome } } } /// Helper for constructing an index location, consisting of the remote url for /// the index and the local location on disk #[derive(Default)] pub struct IndexLocation<'il> { /// The remote url of the registry index pub url: IndexUrl<'il>, /// The local disk path of the index pub root: IndexPath, /// The index location depends on the version of cargo used, as 1.85.0 /// introduced a change to how the url is hashed. Not specifying the version /// will acquire the cargo version pertaining to the current environment. pub cargo_version: Option, } impl<'il> IndexLocation<'il> { /// Constructs an index with the specified url located in the default cargo /// home pub fn new(url: IndexUrl<'il>) -> Self { Self { url, root: IndexPath::CargoHome, cargo_version: None, } } /// Changes the root location of the index on the local disk. /// /// If not called, or set to [`None`], the default cargo home disk location /// is used as the root pub fn with_root(mut self, root: Option) -> Self { self.root = root.into(); self } /// Obtains the full local disk path and URL of this index location pub fn into_parts(self) -> Result<(PathBuf, String), Error> { let url = self.url.as_str(); let root = match self.root { IndexPath::CargoHome => crate::utils::cargo_home()?, IndexPath::UserSpecified(root) => root, IndexPath::Exact(path) => return Ok((path, url.to_owned())), }; let vers = if let Some(v) = self.cargo_version { v } else { crate::utils::cargo_version(None)? }; let stable = vers >= semver::Version::new(1, 85, 0); let (path, mut url) = crate::utils::get_index_details(url, Some(root), stable)?; if !url.ends_with('/') { url.push('/'); } Ok((path, url)) } } /// Calls the specified function for each cargo config located according to /// cargo's standard hierarchical structure /// /// Note that this only supports the use of `.cargo/config.toml`, which is not /// supported below cargo 1.39.0 /// /// See pub(crate) fn read_cargo_config( root: Option, cargo_home: Option<&Path>, callback: impl Fn(&Path, &toml_span::value::Value<'_>) -> Option, ) -> Result, Error> { if let Some(mut path) = root.or_else(|| { std::env::current_dir() .ok() .and_then(|pb| PathBuf::from_path_buf(pb).ok()) }) { loop { path.push(".cargo/config.toml"); if path.exists() { let contents = match std::fs::read_to_string(&path) { Ok(c) => c, Err(err) => return Err(Error::IoPath(err, path)), }; let toml = toml_span::parse(&contents).map_err(Box::new)?; if let Some(value) = callback(&path, &toml) { return Ok(Some(value)); } } path.pop(); path.pop(); // Walk up to the next potential config root if !path.pop() { break; } } } if let Some(home) = cargo_home .map(Cow::Borrowed) .or_else(|| crate::utils::cargo_home().ok().map(Cow::Owned)) { let path = home.join("config.toml"); if path.exists() { let fc = std::fs::read_to_string(&path)?; let toml = toml_span::parse(&fc).map_err(Box::new)?; if let Some(value) = callback(&path, &toml) { return Ok(Some(value)); } } } Ok(None) } /// Gets the url of a replacement registry for the specified registry if one has been configured /// /// See #[inline] pub(crate) fn get_source_replacement<'iu>( root: Option, cargo_home: Option<&Path>, registry_url: &str, registry_name: &str, ) -> Result>, Error> { read_cargo_config(root, cargo_home, |config_path, config| { let sources = config.as_table()?.get("source")?.as_table()?; let repw = sources.iter().find_map(|(source_name, source)| { let source = source.as_table()?; let matches = registry_name == source_name.name || registry_url == source.get("registry")?.as_str()?; matches.then_some(source.get("replace-with")?.as_str()?) })?; let sources = config.pointer("/source")?.as_table()?; let replace_src = sources.get(repw)?.as_table()?; if let Some(rr) = replace_src.get("registry") { rr.as_str() .map(|r| IndexUrl::NonCratesIo(r.to_owned().into())) } else if let Some(rlr) = replace_src.get("local-registry") { let rel_path = rlr.as_str()?; Some(IndexUrl::Local( config_path.parent()?.parent()?.join(rel_path).into(), )) } else { None } }) } #[cfg(test)] mod test { // Current stable is 1.70.0 #[test] fn opens_sparse() { assert!(std::env::var_os("CARGO_REGISTRIES_CRATES_IO_PROTOCOL").is_none()); assert!(matches!( crate::index::ComboIndexCache::new(super::IndexLocation::new( super::IndexUrl::crates_io(None, None, None).unwrap() )) .unwrap(), crate::index::ComboIndexCache::Sparse(_) )); } /// Verifies we can parse .cargo/config.toml files to either use the crates-io /// protocol set, or source replacements #[test] fn parses_from_file() { assert!(std::env::var_os("CARGO_REGISTRIES_CRATES_IO_PROTOCOL").is_none()); let td = tempfile::tempdir().unwrap(); let root = crate::PathBuf::from_path_buf(td.path().to_owned()).unwrap(); let cfg_toml = td.path().join(".cargo/config.toml"); std::fs::create_dir_all(cfg_toml.parent().unwrap()).unwrap(); const GIT: &str = r#"[registries.crates-io] protocol = "git" "#; // First just set the protocol from the sparse default to git std::fs::write(&cfg_toml, GIT).unwrap(); let iurl = super::IndexUrl::crates_io(Some(root.clone()), None, None).unwrap(); assert_eq!(iurl.as_str(), crate::CRATES_IO_INDEX); assert!(!iurl.is_sparse()); // Next set replacement registries for (i, (kind, url)) in [ ( "registry", "sparse+https://sparse-registry-parses-from-file.com", ), ("registry", "https://sparse-registry-parses-from-file.git"), ("local-registry", root.as_str()), ] .iter() .enumerate() { std::fs::write(&cfg_toml, format!("{GIT}\n[source.crates-io]\nreplace-with = 'replacement'\n[source.replacement]\n{kind} = '{url}'")).unwrap(); let iurl = super::IndexUrl::crates_io(Some(root.clone()), None, None).unwrap(); assert_eq!(i == 0, iurl.is_sparse()); assert_eq!(iurl.as_str(), *url); } } #[test] #[allow(unsafe_code)] fn custom() { assert!(std::env::var_os("CARGO_REGISTRIES_TAME_INDEX_TEST_INDEX").is_none()); let td = tempfile::tempdir().unwrap(); let root = crate::PathBuf::from_path_buf(td.path().to_owned()).unwrap(); let cfg_toml = td.path().join(".cargo/config.toml"); std::fs::create_dir_all(cfg_toml.parent().unwrap()).unwrap(); const SPARSE: &str = r#"[registries.tame-index-test] index = "sparse+https://some-url.com" "#; const GIT: &str = r#"[registries.tame-index-test] index = "https://some-url.com" "#; unsafe { std::fs::write(&cfg_toml, SPARSE).unwrap(); let iurl = super::IndexUrl::for_registry_name(Some(root.clone()), None, "tame-index-test") .unwrap(); assert_eq!(iurl.as_str(), "sparse+https://some-url.com"); assert!(iurl.is_sparse()); std::env::set_var( "CARGO_REGISTRIES_TAME_INDEX_TEST_INDEX", "sparse+https://some-other-url.com", ); let iurl = super::IndexUrl::for_registry_name(Some(root.clone()), None, "tame-index-test") .unwrap(); assert_eq!(iurl.as_str(), "sparse+https://some-other-url.com"); assert!(iurl.is_sparse()); std::env::remove_var("CARGO_REGISTRIES_TAME_INDEX_TEST_INDEX"); } unsafe { std::fs::write(&cfg_toml, GIT).unwrap(); let iurl = super::IndexUrl::for_registry_name(Some(root.clone()), None, "tame-index-test") .unwrap(); assert_eq!(iurl.as_str(), "https://some-url.com"); assert!(!iurl.is_sparse()); std::env::set_var( "CARGO_REGISTRIES_TAME_INDEX_TEST_INDEX", "https://some-other-url.com", ); let iurl = super::IndexUrl::for_registry_name(Some(root.clone()), None, "tame-index-test") .unwrap(); assert_eq!(iurl.as_str(), "https://some-other-url.com"); assert!(!iurl.is_sparse()); std::env::remove_var("CARGO_REGISTRIES_TAME_INDEX_TEST_INDEX"); } #[allow(unused_variables)] { let err = crate::Error::UnknownRegistry("non-existant".to_owned()); assert!(matches!( super::IndexUrl::for_registry_name(Some(root.clone()), None, "non-existant"), Err(err), )); } } } tame-index-0.23.1/src/index/sparse.rs000064400000000000000000000227531046102023000155140ustar 00000000000000use super::{FileLock, IndexCache, cache::ValidCacheEntry}; use crate::{Error, HttpError, IndexKrate, KrateName}; /// The default URL of the crates.io HTTP index pub const CRATES_IO_HTTP_INDEX: &str = "sparse+https://index.crates.io/"; /// Wrapper around managing a sparse HTTP index, re-using Cargo's local disk caches. /// /// This implementation does no network I/O at all. If you want to make requests /// to the remote index you may use the [`Self::make_remote_request`] and /// [`Self::parse_remote_response`] methods, or you can enable the `sparse` feature /// and and use [`RemoteSparseIndex`](crate::index::RemoteSparseIndex) or /// [`AsyncRemoteSparseIndex`](crate::index::AsyncRemoteSparseIndex) pub struct SparseIndex { cache: IndexCache, url: String, } impl SparseIndex { /// Creates a new sparse index for the specified location #[inline] pub fn new(il: crate::index::IndexLocation<'_>) -> Result { if !il.url.is_sparse() { return Err(crate::InvalidUrl { url: il.url.as_str().to_owned(), source: crate::InvalidUrlError::MissingSparse, } .into()); } let (path, url) = il.into_parts()?; Ok(Self { cache: IndexCache::at_path(path), url, }) } /// Get the configuration of the index. /// /// See the [cargo docs](https://doc.rust-lang.org/cargo/reference/registry-index.html#index-configuration) pub fn index_config(&self) -> Result { let path = self.cache.path.join("config.json"); let bytes = std::fs::read(&path).map_err(|err| Error::IoPath(err, path))?; Ok(serde_json::from_slice(&bytes)?) } /// Get the URL that can be used to fetch the index entry for the specified /// crate /// /// The body of a successful response for the returned URL can be parsed /// via [`IndexKrate::from_slice`] /// /// See [`Self::make_remote_request`] for a way to make a complete request #[inline] pub fn crate_url(&self, name: KrateName<'_>) -> String { let rel_path = name.relative_path(Some('/')); format!("{}{rel_path}", self.url()) } /// The HTTP url of the index #[inline] pub fn url(&self) -> &str { self.url.strip_prefix("sparse+").unwrap_or(&self.url) } /// Gets the accessor to the local index cache #[inline] pub fn cache(&self) -> &IndexCache { &self.cache } /// Attempts to read the locally cached crate information #[inline] pub fn cached_krate( &self, name: KrateName<'_>, lock: &FileLock, ) -> Result, Error> { self.cache.cached_krate(name, None, lock) } /// Creates an HTTP request that can be sent via your HTTP client of choice /// to retrieve the current metadata for the specified crate /// /// If specified, the etag is used instead of the possible etag stored in /// a local cache entry, resulting in no disk I/O being performed by this /// method /// /// See [`Self::parse_remote_response`] processing the response from the remote /// index /// /// It is highly recommended to assume HTTP/2 when making requests to remote /// indices, at least crates.io pub fn make_remote_request( &self, name: KrateName<'_>, etag: Option<&str>, lock: &FileLock, ) -> Result, Error> { use http::header; let url = self.crate_url(name); let mut req = http::Request::get(url); { let headers = req.headers_mut().unwrap(); // AFAICT this does not affect responses at the moment, but could in // the future if there are changes to the protocol headers.insert( "cargo-protocol", header::HeaderValue::from_static("version=1"), ); // All index entries are just files with lines of JSON headers.insert( header::ACCEPT, header::HeaderValue::from_static("text/plain"), ); // We need to accept both identity and gzip, as otherwise cloudfront will // always respond to requests with strong etag's, which will differ from // cache entries generated by cargo headers.insert( header::ACCEPT_ENCODING, header::HeaderValue::from_static("gzip"), ); // If we have a local cache entry, include its version with the // appropriate header, this allows the server to respond with a // cached, or even better, empty response if its version matches // the local one making the request/response loop basically free // If we're unable to get the cache version we can just ignore setting the // header, guaranteeing we'll get the full index contents if the crate exists let set_cache_version = |headers: &mut header::HeaderMap| -> Option<()> { let contents = self.cache.read_cache_file(name, lock).ok()??; let valid = ValidCacheEntry::read(&contents).ok()?; let (key, value) = valid.revision.split_once(':')?; let value = header::HeaderValue::from_str(value.trim()).ok()?; let name = if key == header::ETAG { header::IF_NONE_MATCH } else if key == header::LAST_MODIFIED { header::IF_MODIFIED_SINCE } else { // We could error here, but that's kind of pointless // since the response will be sent in full if we haven't // specified one of the above headers. Though it does // potentially indicate something weird is going on return None; }; headers.insert(name, value); None }; if let Some(etag) = etag { let hv = header::HeaderValue::from_str(etag.trim()).map_err(crate::HttpError::from)?; headers.insert(header::IF_NONE_MATCH, hv); } else { // Use the etag (or last modified, though crates.io does not use this AFAICT) // from the cache entry if it exists let _ = set_cache_version(headers); } } Ok(req.body(()).unwrap()) } /// Process the response to a request created by [`Self::make_remote_request`] /// /// This handles both the scenario where the local cache is missing the specified /// crate, or it is out of date, as well as the local entry being up to date /// and can just be read from disk /// /// You may specify whether an updated index entry is written locally to the /// cache or not /// /// Note that responses from sparse HTTP indices, at least crates.io, may /// send responses with `gzip` compression, it is your responsibility to /// decompress it before sending to this function pub fn parse_remote_response( &self, name: KrateName<'_>, response: http::Response>, write_cache_entry: bool, lock: &FileLock, ) -> Result, Error> { use http::{StatusCode, header}; let (parts, body) = response.into_parts(); match parts.status { // The server responded with the full contents of the index entry StatusCode::OK => { let krate = IndexKrate::from_slice(&body)?; if write_cache_entry { // The same as cargo, prefer etag over last-modified let version = if let Some(etag) = parts.headers.get(header::ETAG) { etag.to_str() .ok() .map(|etag| format!("{}: {etag}", header::ETAG)) } else if let Some(lm) = parts.headers.get(header::LAST_MODIFIED) { lm.to_str() .ok() .map(|lm| format!("{}: {lm}", header::LAST_MODIFIED)) } else { None }; let revision = version.unwrap_or_else(|| "Unknown".to_owned()); // It's unfortunate if we can't write to the cache, but we // don't treat it as a hard error since we still have the // index metadata let _err = self.cache.write_to_cache(&krate, &revision, lock); } Ok(Some(krate)) } // The local cache entry is up to date with the latest entry on the // server, we can just return the local one StatusCode::NOT_MODIFIED => self.cache.cached_krate(name, None, lock), // The server requires authorization but the user didn't provide it StatusCode::UNAUTHORIZED => Err(HttpError::StatusCode { code: StatusCode::UNAUTHORIZED, msg: "the request was not authorized", } .into()), // The crate does not exist, or has been removed StatusCode::NOT_FOUND | StatusCode::GONE | StatusCode::UNAVAILABLE_FOR_LEGAL_REASONS => Ok(None), code => Err(HttpError::StatusCode { code, msg: "the status code is invalid for this protocol", } .into()), } } } tame-index-0.23.1/src/index/sparse_remote.rs000064400000000000000000000275661046102023000170760ustar 00000000000000use super::{FileLock, SparseIndex}; use crate::{Error, IndexKrate, KrateName}; pub use reqwest::Client as AsyncClient; pub use reqwest::blocking::Client; use std::collections::{BTreeMap, BTreeSet}; /// Allows **blocking** access to a remote HTTP sparse registry index pub struct RemoteSparseIndex { /// The local index this remote is wrapping pub index: SparseIndex, /// The client used to make requests to the remote index pub client: Client, } impl RemoteSparseIndex { /// Creates a new [`Self`] that can access and write local cache entries, /// and contact the remote index to retrieve the latest index information #[inline] pub fn new(index: SparseIndex, client: Client) -> Self { Self { index, client } } /// Gets the latest index metadata for the crate /// /// Network I/O is _always_ performed when calling this method, however the /// response from the remote registry will be empty of contents other than /// headers if the local cache entry for the crate is up to date with the /// latest in the index pub fn krate( &self, name: KrateName<'_>, write_cache_entry: bool, lock: &FileLock, ) -> Result, Error> { let req = self.index.make_remote_request(name, None, lock)?; let ( http::request::Parts { method, uri, version, headers, .. }, _, ) = req.into_parts(); let mut req = self.client.request(method, uri.to_string()); req = req.version(version); req = req.headers(headers); let res = self.client.execute(req.build()?)?; let mut builder = http::Response::builder() .status(res.status()) .version(res.version()); builder .headers_mut() .unwrap() .extend(res.headers().iter().map(|(k, v)| (k.clone(), v.clone()))); let body = res.bytes()?; let res = builder.body(body.to_vec())?; self.index .parse_remote_response(name, res, write_cache_entry, lock) } /// Attempts to read the locally cached crate information /// /// This method does no network I/O unlike [`Self::krate`], but does not /// guarantee that the cache information is up to date with the latest in /// the remote index #[inline] pub fn cached_krate( &self, name: KrateName<'_>, lock: &FileLock, ) -> Result, Error> { self.index.cached_krate(name, lock) } /// Helper method for downloading multiple crates in parallel /// /// Note that in most cases using [`AsyncRemoteSparseIndex::krates_blocking`] /// will outperform this method, especially on lower core counts #[inline] pub fn krates( &self, mut krates: BTreeSet, write_cache_entries: bool, lock: &FileLock, ) -> BTreeMap, Error>> { let Some(prep_krate) = krates.pop_last() else { return Default::default(); }; let prep = || { let name = prep_krate.as_str().try_into()?; self.krate(name, write_cache_entries, lock) }; let prep_krate_res = prep(); use rayon::prelude::*; let mut results: BTreeMap<_, _> = krates .into_par_iter() .map(|kname| { let res = || { let name = kname.as_str().try_into()?; self.krate(name, write_cache_entries, lock) }; let res = res(); (kname, res) }) .collect(); results.insert(prep_krate, prep_krate_res); results } } /// Allows **async** access to a remote HTTP sparse registry index pub struct AsyncRemoteSparseIndex { /// The local index this remote is wrapping pub index: SparseIndex, /// The client used to make requests to the remote index pub client: AsyncClient, } impl AsyncRemoteSparseIndex { /// Creates a new [`Self`] that can access and write local cache entries, /// and contact the remote index to retrieve the latest index information #[inline] pub fn new(index: SparseIndex, client: AsyncClient) -> Self { Self { index, client } } /// Async version of [`RemoteSparseIndex::krate`] pub async fn krate_async( &self, name: KrateName<'_>, write_cache_entry: bool, lock: &FileLock, ) -> Result, Error> { let req = self.index.make_remote_request(name, None, lock)?; let ( http::request::Parts { method, uri, version, headers, .. }, _, ) = req.into_parts(); let mut req = self.client.request(method, uri.to_string()); req = req.version(version); req = req.headers(headers); let res = Self::exec_request(&self.client, req.build()?).await?; self.index .parse_remote_response(name, res, write_cache_entry, lock) } async fn exec_request( client: &AsyncClient, req: reqwest::Request, ) -> Result>, Error> { // This is unfortunate, but we always make a copy in case we need to retry let res = loop { let reqc = req.try_clone().unwrap(); let res = client.execute(reqc).await; match res { Err(err) if err.is_connect() || err.is_timeout() || err.is_request() => {} Err(err) => return Err(err.into()), Ok(res) => break res, } }; let mut builder = http::Response::builder() .status(res.status()) .version(res.version()); builder .headers_mut() .unwrap() .extend(res.headers().iter().map(|(k, v)| (k.clone(), v.clone()))); let body = res.bytes().await?; Ok(builder.body(body.to_vec())?) } /// Attempts to read the locally cached crate information /// /// This method does no network I/O unlike [`Self::krate_async`], but does not /// guarantee that the cache information is up to date with the latest in /// the remote index #[inline] pub fn cached_krate( &self, name: KrateName<'_>, lock: &FileLock, ) -> Result, Error> { self.index.cached_krate(name, lock) } /// Helper method for downloading multiples crates concurrently /// /// This method will generally perform better than [`RemoteSparseIndex::krates`] /// /// One notable difference with this method is that you can specify a maximum /// duration that each individual krate request can take before it is timed out. /// This is because certain [errors](https://github.com/seanmonstar/reqwest/issues/1748) /// can occur when making many concurrent requests, which we detect and retry /// automatically, but with (by default) no upper bound in number of /// retries/time. /// /// You can also run this entire operation with a single timeout if you wish, /// via something like [`tokio::time::timeout`](https://docs.rs/tokio/latest/tokio/time/fn.timeout.html) pub async fn krates( &self, mut krates: BTreeSet, write_cache_entries: bool, individual_timeout: Option, lock: &FileLock, ) -> BTreeMap, Error>> { let Some(prep_krate) = krates.pop_last() else { return Default::default(); }; let create_req = |kname: &str| -> Result { let name = kname.try_into()?; let req = self.index.make_remote_request(name, None, lock)?; let ( http::request::Parts { method, uri, version, headers, .. }, _, ) = req.into_parts(); let mut req = self.client.request(method, uri.to_string()); req = req.version(version); req = req.headers(headers); Ok(req.build()?) }; let mut results = BTreeMap::new(); { let result; match create_req(&prep_krate) { Ok(req) => match Self::exec_request(&self.client, req).await { Ok(res) => { result = self.index.parse_remote_response( prep_krate.as_str().try_into().unwrap(), res, write_cache_entries, lock, ); } Err(err) => result = Err(err), }, Err(err) => result = Err(err), } results.insert(prep_krate, result); } let mut tasks = tokio::task::JoinSet::new(); for kname in krates { match create_req(kname.as_str()) { Ok(req) => { let client = self.client.clone(); tasks.spawn(async move { let res = if let Some(to) = individual_timeout { match tokio::time::timeout(to, Self::exec_request(&client, req)).await { Ok(res) => res, Err(_) => Err(Error::Http(crate::HttpError::Timeout)), } } else { Self::exec_request(&client, req).await }; (kname, res) }); } Err(err) => { results.insert(kname, Err(err)); } } } let (tx, rx) = crossbeam_channel::unbounded(); while let Some(res) = tasks.join_next().await { let Ok(res) = res else { continue; }; let _ = tx.send(res); } drop(tx); let results = std::sync::Mutex::new(results); rayon::scope(|s| { while let Ok((kname, res)) = rx.recv() { s.spawn(|_s| { let res = res.and_then(|res| { let name = kname .as_str() .try_into() .expect("this was already validated"); self.index .parse_remote_response(name, res, write_cache_entries, lock) }); results.lock().unwrap().insert(kname, res); }); } }); results.into_inner().unwrap() } /// A non-async version of [`Self::krates`] /// /// Using this method requires that there is an active tokio runtime as /// described [here](https://docs.rs/tokio/latest/tokio/runtime/struct.Handle.html#method.current) pub fn krates_blocking( &self, krates: BTreeSet, write_cache_entries: bool, individual_timeout: Option, lock: &FileLock, ) -> Result, Error>>, tokio::runtime::TryCurrentError> { let current = tokio::runtime::Handle::try_current()?; Ok(current.block_on(async { self.krates(krates, write_cache_entries, individual_timeout, lock) .await })) } } impl From for Error { #[inline] fn from(e: reqwest::Error) -> Self { Self::Http(crate::HttpError::Reqwest(e)) } } impl From for Error { #[inline] fn from(e: http::Error) -> Self { Self::Http(crate::HttpError::Http(e)) } } tame-index-0.23.1/src/index.rs000064400000000000000000000216661046102023000142210ustar 00000000000000//! Provides functionality for interacting with both local and remote registry //! indices pub mod cache; #[cfg(all(feature = "__git", feature = "sparse"))] mod combo; #[allow(missing_docs)] pub mod git; #[cfg(feature = "__git")] pub(crate) mod git_remote; #[cfg(feature = "local")] pub mod local; pub mod location; #[allow(missing_docs)] pub mod sparse; #[cfg(feature = "sparse")] mod sparse_remote; pub use cache::IndexCache; #[cfg(all(feature = "__git", feature = "sparse"))] pub use combo::ComboIndex; pub use git::GitIndex; #[cfg(feature = "__git")] pub use git_remote::RemoteGitIndex; #[cfg(feature = "local")] pub use local::LocalRegistry; pub use location::{IndexLocation, IndexPath, IndexUrl}; pub use sparse::SparseIndex; #[cfg(feature = "sparse")] pub use sparse_remote::{AsyncRemoteSparseIndex, RemoteSparseIndex}; pub use crate::utils::flock::FileLock; /// Global configuration of an index, reflecting the [contents of config.json](https://doc.rust-lang.org/cargo/reference/registries.html#index-format). #[derive(Eq, PartialEq, PartialOrd, Ord, Clone, Debug, serde::Deserialize, serde::Serialize)] pub struct IndexConfig { /// Pattern for creating download URLs. See [`Self::download_url`]. pub dl: String, #[serde(default)] /// Base URL for publishing, etc. pub api: Option, /// Indicates whether this is a private registry that requires all /// operations to be authenticated including API requests, crate downloads /// and sparse index updates. #[serde(default, rename = "auth-required")] pub auth_required: bool, } impl IndexConfig { /// Gets the download url for the specified crate version /// /// See /// for more info pub fn download_url(&self, name: crate::KrateName<'_>, version: &str) -> String { // Special case crates.io which will easily be the most common case in // almost all scenarios, we just use the _actual_ url directly, which // avoids a 301 redirect, though obviously this will be bad if crates.io // ever changes the redirect, this has been stable since 1.0 (at least) // so it's unlikely to ever change, and if it does, it would be easy to // update, though obviously would be broken on previously published versions if self.dl == "https://crates.io/api/v1/crates" { return format!("https://static.crates.io/crates/{name}/{name}-{version}.crate"); } let mut dl = self.dl.clone(); if dl.contains('{') { while let Some(start) = dl.find("{crate}") { dl.replace_range(start..start + 7, name.0); } while let Some(start) = dl.find("{version}") { dl.replace_range(start..start + 9, version); } if dl.contains("{prefix}") || dl.contains("{lowerprefix}") { let mut prefix = String::with_capacity(6); name.prefix(&mut prefix, '/'); while let Some(start) = dl.find("{prefix}") { dl.replace_range(start..start + 8, &prefix); } if dl.contains("{lowerprefix}") { prefix.make_ascii_lowercase(); while let Some(start) = dl.find("{lowerprefix}") { dl.replace_range(start..start + 13, &prefix); } } } } else { // If none of the markers are present, then the value /{crate}/{version}/download is appended to the end if !dl.ends_with('/') { dl.push('/'); } dl.push_str(name.0); dl.push('/'); dl.push_str(version); dl.push('/'); dl.push_str("download"); } dl } } use crate::Error; /// Provides simpler access to the cache for an index, regardless of the registry kind #[non_exhaustive] pub enum ComboIndexCache { /// A git index Git(GitIndex), /// A sparse HTTP index Sparse(SparseIndex), /// A local registry #[cfg(feature = "local")] Local(LocalRegistry), } impl ComboIndexCache { /// Retrieves the index metadata for the specified crate name #[inline] pub fn cached_krate( &self, name: crate::KrateName<'_>, lock: &FileLock, ) -> Result, Error> { match self { Self::Git(index) => index.cached_krate(name, lock), Self::Sparse(index) => index.cached_krate(name, lock), #[cfg(feature = "local")] Self::Local(lr) => lr.cached_krate(name, lock), } } /// Gets the path to the cache entry for the specified crate pub fn cache_path(&self, name: crate::KrateName<'_>) -> crate::PathBuf { match self { Self::Git(index) => index.cache.cache_path(name), Self::Sparse(index) => index.cache().cache_path(name), #[cfg(feature = "local")] Self::Local(lr) => lr.krate_path(name), } } /// Constructs a [`Self`] for the specified index. /// /// See [`Self::crates_io`] if you want to create a crates.io index based /// upon other information in the user's environment pub fn new(il: IndexLocation<'_>) -> Result { #[cfg(feature = "local")] { if let IndexUrl::Local(path) = il.url { return Ok(Self::Local(LocalRegistry::open(path.into(), true)?)); } } let index = if il.url.is_sparse() { let sparse = SparseIndex::new(il)?; Self::Sparse(sparse) } else { let git = GitIndex::new(il)?; Self::Git(git) }; Ok(index) } } impl From for ComboIndexCache { #[inline] fn from(si: SparseIndex) -> Self { Self::Sparse(si) } } impl From for ComboIndexCache { #[inline] fn from(gi: GitIndex) -> Self { Self::Git(gi) } } #[cfg(test)] mod test { use super::IndexConfig; use crate::kn; /// Validates we get the non-redirect url for crates.io downloads #[test] fn download_url_crates_io() { let crates_io = IndexConfig { dl: "https://crates.io/api/v1/crates".into(), api: Some("https://crates.io".into()), auth_required: false, }; assert_eq!( crates_io.download_url(kn!("a"), "1.0.0"), "https://static.crates.io/crates/a/a-1.0.0.crate" ); assert_eq!( crates_io.download_url(kn!("aB"), "0.1.0"), "https://static.crates.io/crates/aB/aB-0.1.0.crate" ); assert_eq!( crates_io.download_url(kn!("aBc"), "0.1.0"), "https://static.crates.io/crates/aBc/aBc-0.1.0.crate" ); assert_eq!( crates_io.download_url(kn!("aBc-123"), "0.1.0"), "https://static.crates.io/crates/aBc-123/aBc-123-0.1.0.crate" ); } /// Validates we get a simple non-crates.io download #[test] fn download_url_non_crates_io() { let ic = IndexConfig { dl: "https://dl.cloudsmith.io/public/embark/deny/cargo/{crate}-{version}.crate".into(), api: Some("https://cargo.cloudsmith.io/embark/deny".into()), auth_required: false, }; assert_eq!( ic.download_url(kn!("a"), "1.0.0"), "https://dl.cloudsmith.io/public/embark/deny/cargo/a-1.0.0.crate" ); assert_eq!( ic.download_url(kn!("aB"), "0.1.0"), "https://dl.cloudsmith.io/public/embark/deny/cargo/aB-0.1.0.crate" ); assert_eq!( ic.download_url(kn!("aBc"), "0.1.0"), "https://dl.cloudsmith.io/public/embark/deny/cargo/aBc-0.1.0.crate" ); assert_eq!( ic.download_url(kn!("aBc-123"), "0.1.0"), "https://dl.cloudsmith.io/public/embark/deny/cargo/aBc-123-0.1.0.crate" ); } /// Validates we get a more complicated non-crates.io download, exercising all /// of the possible replacement components #[test] fn download_url_complex() { let ic = IndexConfig { dl: "https://complex.io/ohhi/embark/rust/cargo/{lowerprefix}/{crate}/{crate}/{prefix}-{version}".into(), api: None, auth_required: false, }; assert_eq!( ic.download_url(kn!("a"), "1.0.0"), "https://complex.io/ohhi/embark/rust/cargo/1/a/a/1-1.0.0" ); assert_eq!( ic.download_url(kn!("aB"), "0.1.0"), "https://complex.io/ohhi/embark/rust/cargo/2/aB/aB/2-0.1.0" ); assert_eq!( ic.download_url(kn!("ABc"), "0.1.0"), "https://complex.io/ohhi/embark/rust/cargo/3/a/ABc/ABc/3/A-0.1.0" ); assert_eq!( ic.download_url(kn!("aBc-123"), "0.1.0"), "https://complex.io/ohhi/embark/rust/cargo/ab/c-/aBc-123/aBc-123/aB/c--0.1.0" ); } } tame-index-0.23.1/src/krate/dedupe.rs000064400000000000000000000046511046102023000154610ustar 00000000000000//! Contains helpers for deduplicating dependencies and features for a single //! crate during parsing as each individual version of a crate tends to be mostly //! the same //! //! Copied from use crate::krate::IndexDependency; use std::{ hash::{BuildHasherDefault, Hash, Hasher}, sync::Arc, }; type XxSet = std::collections::HashSet>; use super::FeatureMap; /// Many crate versions have the same features and dependencies #[derive(Default)] pub(crate) struct DedupeContext { features: XxSet, deps: XxSet>, } impl DedupeContext { pub(crate) fn features(&mut self, features: &mut Arc) { let features_to_dedupe = HashedFeatureMap::new(Arc::clone(features)); if let Some(has_feats) = self.features.get(&features_to_dedupe) { *features = Arc::clone(&has_feats.map); } else { // keeps peak memory low (must clear, remove is leaving tombstones) if self.features.len() > 16 * 1024 { self.features.clear(); } self.features.insert(features_to_dedupe); } } pub(crate) fn deps(&mut self, deps: &mut Arc<[IndexDependency]>) { if let Some(has_deps) = self.deps.get(&*deps) { *deps = Arc::clone(has_deps); } else { // keeps peak memory low (must clear, remove is leaving tombstones) if self.deps.len() > 16 * 1024 { self.deps.clear(); } self.deps.insert(Arc::clone(deps)); } } } /// Newtype that caches hash of the hashmap (the default hashmap has a random order of the keys, so it's not cheap to hash) #[derive(PartialEq, Eq)] pub struct HashedFeatureMap { pub map: Arc, hash: u64, } impl Hash for HashedFeatureMap { #[inline] fn hash(&self, hasher: &mut H) where H: Hasher, { hasher.write_u64(self.hash); } } impl HashedFeatureMap { #[inline] pub(crate) fn new(map: Arc) -> Self { let mut hash = 0; for (k, v) in map.iter() { let mut hasher = twox_hash::XxHash64::default(); k.hash(&mut hasher); v.hash(&mut hasher); hash ^= hasher.finish(); // XOR makes it order-independent } Self { map, hash } } } tame-index-0.23.1/src/krate.rs000064400000000000000000000416731046102023000142200ustar 00000000000000//! Provides types for the structured metadata stored in a registry index mod dedupe; use crate::Error; use dedupe::DedupeContext; use semver::Version; use serde::{Deserialize, Serialize}; use smol_str::SmolStr; use std::{collections::BTreeMap, sync::Arc}; /// A mapping of feature name to the features it enables pub type FeatureMap = BTreeMap>; /// A single version of a crate (package) published to the index #[derive(Serialize, Deserialize, Clone, Debug, Eq, PartialEq)] pub struct IndexVersion { /// [Name](https://doc.rust-lang.org/cargo/reference/manifest.html#the-name-field) pub name: SmolStr, /// [Version](https://doc.rust-lang.org/cargo/reference/manifest.html#the-version-field) #[serde(rename = "vers")] pub version: SmolStr, /// [Dependencies](https://doc.rust-lang.org/cargo/reference/specifying-dependencies.html) pub deps: Arc<[IndexDependency]>, /// The SHA-256 for this crate version's tarball #[serde(rename = "cksum")] pub checksum: Chksum, /// [Features](https://doc.rust-lang.org/cargo/reference/features.html) features: Arc, /// Version 2 of the index includes this field /// #[serde(default, skip_serializing_if = "Option::is_none")] features2: Option>, /// Whether the crate is yanked from the remote index or not #[serde(default)] pub yanked: bool, /// [Links](https://doc.rust-lang.org/cargo/reference/manifest.html#the-links-field) #[serde(skip_serializing_if = "Option::is_none")] pub links: Option>, /// [Rust Version](https://doc.rust-lang.org/cargo/reference/manifest.html#the-rust-version-field) #[serde(skip_serializing_if = "Option::is_none")] pub rust_version: Option, /// The index version, 1 if not set, v2 indicates presence of feature2 field #[serde(skip_serializing_if = "Option::is_none")] v: Option, } impl IndexVersion { /// Test functionality #[doc(hidden)] pub fn fake(name: &str, version: impl Into) -> Self { Self { name: name.into(), version: version.into(), deps: Arc::new([]), features: Arc::default(), features2: None, links: None, rust_version: None, checksum: Chksum(Default::default()), yanked: false, v: None, } } /// Dependencies for this version #[inline] pub fn dependencies(&self) -> &[IndexDependency] { &self.deps } /// Checksum of the package for this version /// /// SHA256 of the .crate file #[inline] pub fn checksum(&self) -> &[u8; 32] { &self.checksum.0 } /// Explicit feature set for this crate. /// /// This list is not exhaustive, because any optional dependency becomes a /// feature automatically. /// /// `default` is a special feature name for implicitly enabled features. #[inline] pub fn features(&self) -> impl Iterator)> { self.features.iter().chain( self.features2 .as_ref() .map(|f| f.iter()) .into_iter() .flatten(), ) } /// Exclusivity flag. If this is a sys crate, it informs it /// conflicts with any other crate with the same links string. /// /// It does not involve linker or libraries in any way. #[inline] pub fn links(&self) -> Option<&str> { self.links.as_ref().map(|s| s.as_str()) } /// Whether this version was [yanked](http://doc.crates.io/crates-io.html#cargo-yank) from the /// index #[inline] pub fn is_yanked(&self) -> bool { self.yanked } /// Required version of rust /// /// Corresponds to `package.rust-version`. /// /// Added in 2023 (see ), /// can be `None` if published before then or if not set in the manifest. #[inline] pub fn rust_version(&self) -> Option<&str> { self.rust_version.as_deref() } /// Retrieves the URL this crate version's tarball can be downloaded from #[inline] pub fn download_url(&self, index: &crate::index::IndexConfig) -> Option { Some(index.download_url(self.name.as_str().try_into().ok()?, self.version.as_ref())) } } /// A single dependency of a specific crate version #[derive(Serialize, Deserialize, Clone, Debug, Eq, PartialEq, Hash)] pub struct IndexDependency { /// Dependency's arbitrary nickname (it may be an alias). Use [`Self::crate_name`] for actual crate name. pub name: SmolStr, /// The version requirement, as a string pub req: SmolStr, /// Double indirection to remove size from this struct, since the features are rarely set pub features: Box>, /// If it is an optional dependency pub optional: bool, /// True if the default features are enabled pub default_features: bool, /// Cfg expression applied to the dependency pub target: Option>, /// The kind of the dependency #[serde(skip_serializing_if = "Option::is_none")] pub kind: Option, /// The name of the actual crate, if it was renamed in the crate's manifest #[serde(skip_serializing_if = "Option::is_none")] pub package: Option>, } impl IndexDependency { /// Gets the version requirement for the dependency as a [`semver::VersionReq`] #[inline] pub fn version_requirement(&self) -> semver::VersionReq { self.req.parse().unwrap() } /// Features unconditionally enabled when using this dependency, in addition /// to [`Self::has_default_features`] and features enabled through the /// parent crate's feature list. #[inline] pub fn features(&self) -> &[String] { &self.features } /// If it's optional, it implies a feature of its [`Self::name`], and /// can be enabled through the parent crate's features. #[inline] pub fn is_optional(&self) -> bool { self.optional } /// If `true` (default), enable `default` feature of this dependency #[inline] pub fn has_default_features(&self) -> bool { self.default_features } /// This dependency is only used when compiling for this `cfg` expression #[inline] pub fn target(&self) -> Option<&str> { self.target.as_ref().map(|s| s.as_str()) } /// The kind of the dependency #[inline] pub fn kind(&self) -> DependencyKind { self.kind.unwrap_or_default() } /// Set if dependency's crate name is different from the `name` (alias) #[inline] pub fn package(&self) -> Option<&str> { self.package.as_ref().map(|s| s.as_str()) } /// Returns the name of the crate providing the dependency. /// This is equivalent to `name()` unless `self.package()` /// is not `None`, in which case it's equal to `self.package()`. /// /// Basically, you can define a dependency in your `Cargo.toml` /// like this: /// /// ```toml /// serde_lib = { version = "1", package = "serde" } /// ``` /// /// ...which means that it uses the crate `serde` but imports /// it under the name `serde_lib`. #[inline] pub fn crate_name(&self) -> &str { match &self.package { Some(s) => s, None => &self.name, } } } /// Section in which this dependency was defined #[derive(Debug, Copy, Clone, Serialize, Deserialize, Eq, PartialEq, Hash, Default)] #[serde(rename_all = "lowercase")] pub enum DependencyKind { /// Used at run time #[default] Normal, /// Not fetched and not used, except for when used direclty in a workspace Dev, /// Used at build time, not available at run time Build, } /// A whole crate with all its versions #[derive(Serialize, Deserialize, Clone, Debug, Eq, PartialEq)] pub struct IndexKrate { /// All versions of the crate, sorted chronologically by when it was published pub versions: Vec, } impl IndexKrate { /// The highest version as per semantic versioning specification /// /// Note this may be a pre-release or yanked, use [`Self::highest_normal_version`] /// to filter to the highest version that is not one of those #[inline] pub fn highest_version(&self) -> &IndexVersion { self.versions .iter() .max_by_key(|v| Version::parse(&v.version).ok()) // SAFETY: Versions inside the index will always adhere to // semantic versioning. If a crate is inside the index, at // least one version is available. .unwrap() } /// Returns crate version with the highest version number according to semver, /// but excludes pre-release and yanked versions. /// /// 0.x.y versions are included. /// /// May return `None` if the crate has only pre-release or yanked versions. #[inline] pub fn highest_normal_version(&self) -> Option<&IndexVersion> { self.versions .iter() .filter_map(|v| { if v.is_yanked() { return None; } v.version .parse::() .ok() .filter(|v| v.pre.is_empty()) .map(|vs| (v, vs)) }) .max_by(|a, b| a.1.cmp(&b.1)) .map(|(v, _vs)| v) } /// The crate's unique registry name. Case-sensitive, mostly. #[inline] pub fn name(&self) -> &str { &self.versions[0].name } /// The last release by date, even if it's yanked or less than highest version. /// /// See [`Self::highest_normal_version`] #[inline] pub fn most_recent_version(&self) -> &IndexVersion { &self.versions[self.versions.len() - 1] } /// First version ever published. May be yanked. /// /// It is not guaranteed to be the lowest version number. #[inline] pub fn earliest_version(&self) -> &IndexVersion { &self.versions[0] } } impl IndexKrate { /// Parse an index file with all of crate's versions. /// /// The file must contain at least one version. #[inline] pub fn new(index_path: impl AsRef) -> Result { let lines = std::fs::read(index_path.as_ref())?; Self::from_slice(&lines) } /// Parse a crate from in-memory JSON-lines data #[inline] pub fn from_slice(bytes: &[u8]) -> Result { let mut dedupe = DedupeContext::default(); Self::from_slice_with_context(bytes, &mut dedupe) } /// Parse a [`Self`] file from in-memory JSON data pub(crate) fn from_slice_with_context( mut bytes: &[u8], dedupe: &mut DedupeContext, ) -> Result { use crate::index::cache::split; // Trim last newline(s) so we don't need to special case the split while bytes.last() == Some(&b'\n') { bytes = &bytes[..bytes.len() - 1]; } let num_versions = split(bytes, b'\n').count(); let mut versions = Vec::with_capacity(num_versions); for line in split(bytes, b'\n') { let mut version: IndexVersion = serde_json::from_slice(line)?; // Many versions have identical dependencies and features dedupe.deps(&mut version.deps); dedupe.features(&mut version.features); if let Some(features2) = &mut version.features2 { dedupe.features(features2); } versions.push(version); } if versions.is_empty() { return Err(Error::NoCrateVersions); } Ok(Self { versions }) } /// Writes this crate into a JSON-lines formatted buffer /// /// Note this creates its own internal [`std::io::BufWriter`], there is no /// need to wrap it in your own pub fn write_json_lines(&self, writer: &mut W) -> Result<(), Error> { use std::io::{BufWriter, Write}; let mut w = BufWriter::new(writer); for iv in &self.versions { serde_json::to_writer(&mut w, &iv)?; w.write_all(b"\n")?; } Ok(w.flush()?) } } /// A SHA-256 checksum, this is used by cargo to verify the contents of a crate's /// tarball #[derive(Clone, Eq, PartialEq)] pub struct Chksum(pub [u8; 32]); use std::fmt; impl fmt::Debug for Chksum { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let mut hex = [0; 64]; let hs = crate::utils::encode_hex(&self.0, &mut hex); f.debug_struct("Chksum").field("sha-256", &hs).finish() } } impl fmt::Display for Chksum { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let mut hex = [0; 64]; let hs = crate::utils::encode_hex(&self.0, &mut hex); f.write_str(hs) } } /// Errors that can occur parsing a sha-256 hex string #[derive(Debug)] pub enum ChksumParseError { /// The checksum string had an invalid length InvalidLength(usize), /// The checksum string contained a non-hex character InvalidValue(char), } impl std::error::Error for ChksumParseError {} impl fmt::Display for ChksumParseError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Self::InvalidLength(len) => { write!(f, "expected string with length 64 but got length {len}") } Self::InvalidValue(c) => write!(f, "encountered non-hex character '{c}'"), } } } impl std::str::FromStr for Chksum { type Err = ChksumParseError; fn from_str(data: &str) -> Result { if data.len() != 64 { return Err(ChksumParseError::InvalidLength(data.len())); } let mut array = [0u8; 32]; for (ind, chunk) in data.as_bytes().chunks(2).enumerate() { #[inline] fn parse_hex(b: u8) -> Result { Ok(match b { b'A'..=b'F' => b - b'A' + 10, b'a'..=b'f' => b - b'a' + 10, b'0'..=b'9' => b - b'0', c => { return Err(ChksumParseError::InvalidValue(c as char)); } }) } let mut cur = parse_hex(chunk[0])?; cur <<= 4; cur |= parse_hex(chunk[1])?; array[ind] = cur; } Ok(Self(array)) } } impl<'de> Deserialize<'de> for Chksum { fn deserialize(deserializer: D) -> Result where D: serde::Deserializer<'de>, { use serde::de::Error; struct HexStrVisitor; impl<'de> serde::de::Visitor<'de> for HexStrVisitor { type Value = Chksum; fn expecting(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "a hex encoded string") } fn visit_str(self, data: &str) -> Result { data.parse().map_err(|err| match err { ChksumParseError::InvalidLength(len) => { serde::de::Error::invalid_length(len, &"a string with 64 characters") } ChksumParseError::InvalidValue(c) => serde::de::Error::invalid_value( serde::de::Unexpected::Char(c), &"a hexadecimal character", ), }) } fn visit_borrowed_str(self, data: &'de str) -> Result { self.visit_str(data) } } deserializer.deserialize_str(HexStrVisitor) } } impl Serialize for Chksum { fn serialize(&self, serializer: S) -> Result where S: serde::Serializer, { let mut raw = [0u8; 64]; let s = crate::utils::encode_hex(&self.0, &mut raw); serializer.serialize_str(s) } } #[cfg(test)] mod test { #[test] fn krate_versions() { use super::IndexVersion as iv; let ik = super::IndexKrate { versions: vec![ iv::fake("vers", "0.1.0"), iv::fake("vers", "0.1.1"), iv::fake("vers", "0.1.0"), iv::fake("vers", "0.2.0"), iv::fake("vers", "0.3.0"), // These are ordered this way to actually test the methods correctly iv::fake("vers", "0.4.0"), iv::fake("vers", "0.4.0-alpha.00"), { let mut iv = iv::fake("vers", "0.5.0"); iv.yanked = true; iv }, ], }; assert_eq!(ik.earliest_version().version, "0.1.0"); assert_eq!(ik.most_recent_version().version, "0.5.0"); assert_eq!(ik.highest_version().version, "0.5.0"); assert_eq!(ik.highest_normal_version().unwrap().version, "0.4.0"); } } tame-index-0.23.1/src/krate_name.rs000064400000000000000000000326011046102023000152070ustar 00000000000000use crate::error::{Error, InvalidKrateName}; #[cfg(test)] /// Create a `KrateName` from a string literal. #[macro_export] macro_rules! kn { ($kn:literal) => { $crate::KrateName($kn) }; } /// Used to wrap user-provided strings so that bad crate names are required to /// be handled separately from things more outside the user control such as I/O /// errors #[derive(Copy, Clone)] pub struct KrateName<'name>(pub(crate) &'name str); impl<'name> KrateName<'name> { /// Ensures the specified string is a valid crates.io crate name, according /// to the (current) crates.io name restrictions /// /// 1. Non-empty /// 2. May not start with a digit /// 3. Maximum of 64 characters in length /// 4. Must be ASCII alphanumeric, `-`, or `_` /// 5. May not be a reserved name /// * A Rust keyword /// * Name of a Cargo output artifact /// * Name of a std library crate (or `test`) /// * A reserved Windows name (such as `nul`) #[inline] pub fn crates_io(name: &'name str) -> Result { Self::validated(name, Some(64)) } /// Ensures the specified string is a valid crate name according to [cargo](https://github.com/rust-lang/cargo/blob/00b8da63269420610758464c02fc46584e373dd3/src/cargo/ops/cargo_new.rs#L167-L264) /// /// 1. Non-empty /// 2. May not start with a digit /// 3. Must be ASCII alphanumeric, `-`, or `_` /// 4. May not be a reserved name /// * A Rust keyword /// * Name of a Cargo output artifact /// * Name of a std library crate (or `test`) /// * A reserved Windows name (such as `nul`) #[inline] pub fn cargo(name: &'name str) -> Result { Self::validated(name, None) } fn validated(name: &'name str, max_len: Option) -> Result { if name.is_empty() { return Err(InvalidKrateName::InvalidLength(0).into()); } let mut chars = name.chars().enumerate(); while let Some((i, c)) = chars.next() { if i == 0 && c != '_' && !c.is_ascii_alphabetic() { return Err(InvalidKrateName::InvalidCharacter { invalid: c, index: i, } .into()); } if max_len == Some(i) { return Err(InvalidKrateName::InvalidLength(i + 1 + chars.count()).into()); } if c != '-' && c != '_' && !c.is_ascii_alphanumeric() { return Err(InvalidKrateName::InvalidCharacter { invalid: c, index: i, } .into()); } } // This is a single table, binary sorted so that we can more easily just // check matches and move on // // 1. Rustlang keywords, see https://doc.rust-lang.org/reference/keywords.html // 2. Windows reserved, see https://github.com/rust-lang/cargo/blob/b40be8bdcf2eff9ed81702594d44bf96c27973a6/src/cargo/util/restricted_names.rs#L26-L32 // 3. Cargo artifacts, see https://github.com/rust-lang/cargo/blob/b40be8bdcf2eff9ed81702594d44bf96c27973a6/src/cargo/util/restricted_names.rs#L35-L37 // 4. Rustlang std, see https://github.com/rust-lang/cargo/blob/b40be8bdcf2eff9ed81702594d44bf96c27973a6/src/cargo/ops/cargo_new.rs#L225-L239 use crate::error::ReservedNameKind::{Artifact, Keyword, Standard, Windows}; const DISALLOWED: &[(&str, crate::error::ReservedNameKind)] = &[ ("Self", Keyword), ("abstract", Keyword), ("alloc", Standard), ("as", Keyword), ("async", Keyword), ("aux", Windows), ("await", Keyword), ("become", Keyword), ("box", Keyword), ("break", Keyword), ("build", Artifact), ("com1", Windows), ("com2", Windows), ("com3", Windows), ("com4", Windows), ("com5", Windows), ("com6", Windows), ("com7", Windows), ("com8", Windows), ("com9", Windows), ("con", Windows), ("const", Keyword), ("continue", Keyword), ("core", Standard), ("crate", Keyword), ("deps", Artifact), ("do", Keyword), ("dyn", Keyword), ("else", Keyword), ("enum", Keyword), ("examples", Artifact), ("extern", Keyword), ("false", Keyword), ("final", Keyword), ("fn", Keyword), ("for", Keyword), ("if", Keyword), ("impl", Keyword), ("in", Keyword), ("incremental", Artifact), ("let", Keyword), ("loop", Keyword), ("lpt1", Windows), ("lpt2", Windows), ("lpt3", Windows), ("lpt4", Windows), ("lpt5", Windows), ("lpt6", Windows), ("lpt7", Windows), ("lpt8", Windows), ("lpt9", Windows), ("macro", Keyword), ("match", Keyword), ("mod", Keyword), ("move", Keyword), ("mut", Keyword), ("nul", Windows), ("override", Keyword), ("priv", Keyword), ("prn", Windows), ("proc-macro", Standard), ("proc_macro", Standard), ("pub", Keyword), ("ref", Keyword), ("return", Keyword), ("self", Keyword), ("static", Keyword), ("std", Standard), ("struct", Keyword), ("super", Keyword), ("test", Standard), ("trait", Keyword), ("true", Keyword), ("try", Keyword), ("type", Keyword), ("typeof", Keyword), ("unsafe", Keyword), ("unsized", Keyword), ("use", Keyword), ("virtual", Keyword), ("where", Keyword), ("while", Keyword), ("yield", Keyword), ]; if let Ok(i) = DISALLOWED.binary_search_by_key(&name, |(k, _v)| k) { let (reserved, kind) = DISALLOWED[i]; Err(InvalidKrateName::ReservedName { reserved, kind }.into()) } else { Ok(Self(name)) } } } /// The simplest way to create a crate name, this just ensures that the crate name /// is non-empty, and ASCII alphanumeric, `-`, or, `-`, the minimum requirements /// for this crate impl<'name> TryFrom<&'name str> for KrateName<'name> { type Error = Error; #[inline] fn try_from(s: &'name str) -> Result { if s.is_empty() { Err(InvalidKrateName::InvalidLength(0).into()) } else if let Some((index, invalid)) = s .chars() .enumerate() .find(|(_i, c)| *c != '-' && *c != '_' && !c.is_ascii_alphanumeric()) { Err(InvalidKrateName::InvalidCharacter { invalid, index }.into()) } else { Ok(Self(s)) } } } impl KrateName<'_> { /// Writes the crate's prefix to the specified string /// /// Cargo uses a simple prefix in the registry index so that crate's can be /// partitioned, particularly on disk without running up against potential OS /// specific issues when hundreds of thousands of files are located with a single /// directory /// /// The separator should be [`std::path::MAIN_SEPARATOR`] in disk cases and /// '/' when used for urls pub fn prefix(&self, acc: &mut String, sep: char) { let name = self.0; match name.len() { 0 => unreachable!(), 1 => acc.push('1'), 2 => acc.push('2'), 3 => { acc.push('3'); acc.push(sep); acc.push_str(&name[..1]); } _ => { acc.push_str(&name[..2]); acc.push(sep); acc.push_str(&name[2..4]); } } } /// Gets the relative path to a crate /// /// This will be of the form [`Self::prefix`] + `` + `` /// /// If not specified, the separator is [`std::path::MAIN_SEPARATOR`] /// /// ``` /// let crate_name: tame_index::KrateName = "tame-index".try_into().unwrap(); /// assert_eq!(crate_name.relative_path(Some('/')), "ta/me/tame-index"); /// ``` pub fn relative_path(&self, sep: Option) -> String { let name = self.0; // Preallocate with the maximum possible width of a crate prefix `aa/bb/` let mut rel_path = String::with_capacity(name.len() + 6); let sep = sep.unwrap_or(std::path::MAIN_SEPARATOR); self.prefix(&mut rel_path, sep); rel_path.push(sep); rel_path.push_str(name); // A valid krate name is ASCII only, we don't need to worry about // lowercasing utf-8 rel_path.make_ascii_lowercase(); rel_path } } use std::fmt; impl fmt::Display for KrateName<'_> { #[inline] fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.write_str(self.0) } } impl fmt::Debug for KrateName<'_> { #[inline] fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.write_str(self.0) } } #[cfg(test)] mod test { use super::KrateName; use crate::error::{Error, InvalidKrateName, ReservedNameKind}; /// Validates that all ways to create a krate name validate the basics of /// not empty and allowed characters #[test] fn rejects_simple() { assert!(matches!( TryInto::>::try_into("").unwrap_err(), Error::InvalidKrateName(InvalidKrateName::InvalidLength(0)) )); assert!(matches!( KrateName::crates_io("").unwrap_err(), Error::InvalidKrateName(InvalidKrateName::InvalidLength(0)) )); assert!(matches!( TryInto::>::try_into("no.pe").unwrap_err(), Error::InvalidKrateName(InvalidKrateName::InvalidCharacter { index: 2, invalid: '.', }) )); assert!(matches!( KrateName::crates_io("no.pe").unwrap_err(), Error::InvalidKrateName(InvalidKrateName::InvalidCharacter { index: 2, invalid: '.', }) )); } /// Validates that crate names can't start with digit #[test] fn rejects_leading_digit() { assert!(matches!( KrateName::crates_io("3nop").unwrap_err(), Error::InvalidKrateName(InvalidKrateName::InvalidCharacter { index: 0, invalid: '3', }) )); } /// Validates the crate name doesn't exceed the crates.io limit #[test] fn rejects_too_long() { assert!(matches!( KrateName::crates_io( "aaaaaaaabbbbbbbbccccccccddddddddaaaaaaaabbbbbbbbccccccccddddddddxxxxxxx" ) .unwrap_err(), Error::InvalidKrateName(InvalidKrateName::InvalidLength(71)) )); assert!( KrateName::cargo( "aaaaaaaabbbbbbbbccccccccddddddddaaaaaaaabbbbbbbbccccccccddddddddxxxxxxx" ) .is_ok() ); } /// Validates the crate name can't be a reserved name #[test] fn rejects_reserved() { assert!(matches!( KrateName::cargo("nul").unwrap_err(), Error::InvalidKrateName(InvalidKrateName::ReservedName { reserved: "nul", kind: ReservedNameKind::Windows }) )); assert!(matches!( KrateName::cargo("deps").unwrap_err(), Error::InvalidKrateName(InvalidKrateName::ReservedName { reserved: "deps", kind: ReservedNameKind::Artifact }) )); assert!(matches!( KrateName::cargo("Self").unwrap_err(), Error::InvalidKrateName(InvalidKrateName::ReservedName { reserved: "Self", kind: ReservedNameKind::Keyword }) )); assert!(matches!( KrateName::cargo("yield").unwrap_err(), Error::InvalidKrateName(InvalidKrateName::ReservedName { reserved: "yield", kind: ReservedNameKind::Keyword }) )); assert!(matches!( KrateName::cargo("proc-macro").unwrap_err(), Error::InvalidKrateName(InvalidKrateName::ReservedName { reserved: "proc-macro", kind: ReservedNameKind::Standard }) )); assert!(matches!( KrateName::cargo("proc_macro").unwrap_err(), Error::InvalidKrateName(InvalidKrateName::ReservedName { reserved: "proc_macro", kind: ReservedNameKind::Standard }) )); } #[inline] fn rp(n: &str) -> String { KrateName(n).relative_path(Some('/')) } /// Validates we get the correct relative path to crate #[test] fn relative_path() { assert_eq!(rp("a"), "1/a"); assert_eq!(rp("ab"), "2/ab"); assert_eq!(rp("abc"), "3/a/abc"); assert_eq!(rp("AbCd"), "ab/cd/abcd"); assert_eq!(rp("normal"), "no/rm/normal"); assert_eq!(rp("_boop-"), "_b/oo/_boop-"); assert_eq!(rp("Inflector"), "in/fl/inflector"); } } tame-index-0.23.1/src/lib.rs000064400000000000000000000016361046102023000136530ustar 00000000000000#![cfg_attr(docsrs, feature(doc_auto_cfg))] #![doc = include_str!("../README.md")] pub mod error; pub mod index; pub mod krate; mod krate_name; pub mod utils; pub use camino::{Utf8Path as Path, Utf8PathBuf as PathBuf}; pub use error::{CacheError, Error, HttpError, InvalidUrl, InvalidUrlError}; pub use index::{ GitIndex, IndexCache, IndexLocation, IndexPath, IndexUrl, SparseIndex, git::CRATES_IO_INDEX, sparse::CRATES_IO_HTTP_INDEX, }; pub use krate::{IndexDependency, IndexKrate, IndexVersion}; pub use krate_name::KrateName; pub use semver::Version; /// Reexports of some crates for easier downstream usage without requiring adding /// your own dependencies pub mod external { #[cfg(feature = "__git")] pub use gix; pub use http; #[cfg(any(feature = "sparse", feature = "local-builder"))] pub use reqwest; #[cfg(any(feature = "sparse", feature = "local-builder"))] pub use tokio; } tame-index-0.23.1/src/utils/flock/bindings.toml000064400000000000000000000007761046102023000174730ustar 00000000000000output = "win_bindings.rs" binds = [ "CloseHandle", "CreateEventA", "ERROR_INVALID_FUNCTION", "ERROR_IO_PENDING", "ERROR_LOCK_VIOLATION", "FILE_FLAG_OVERLAPPED", "INFINITE", "LockFileEx", "LOCKFILE_EXCLUSIVE_LOCK", "LOCKFILE_FAIL_IMMEDIATELY", "UnlockFile", "WaitForSingleObject", "WAIT_IO_COMPLETION", "WAIT_OBJECT_0", "WAIT_TIMEOUT", ] [bind-mode] mode = "minwin" [bind-mode.config] enum-style = "minwin" fix-naming = true use-rust-casing = true tame-index-0.23.1/src/utils/flock/unix.rs000064400000000000000000000124031046102023000163200ustar 00000000000000#![allow(unsafe_code)] use super::LockState; use std::{fs::File, io::Error, os::unix::io::AsRawFd, time::Duration}; type Result = std::io::Result<()>; macro_rules! flock_flag { ($state:expr) => { match $state { LockState::Shared => libc::LOCK_SH, LockState::Exclusive => libc::LOCK_EX, _ => unreachable!(), } }; } macro_rules! error { ($func:expr) => { if $func != 0 { return Err(Error::last_os_error()); } }; } #[inline] pub(super) fn open_opts(exclusive: bool) -> std::fs::OpenOptions { let mut o = std::fs::OpenOptions::new(); o.read(true); if exclusive { o.write(true).create(true); } o } #[inline] pub(super) fn try_lock(file: &File, state: LockState) -> Result { flock(file, flock_flag!(state) | libc::LOCK_NB) } #[inline] pub(super) fn lock(file: &File, state: LockState, timeout: Option) -> Result { if let Some(timeout) = timeout { static SIG_HANDLER: std::sync::Once = std::sync::Once::new(); // Unfortunately due the global nature of signal handlers, we need to // register a signal handler that just ignores the signal we use to kill // the thread performing the lock if it is timed out, as otherwise the // default signal handler will terminate the process, not just interrupt // the one thread the signal was sent to. SIG_HANDLER.call_once(|| { unsafe { let mut act: libc::sigaction = std::mem::zeroed(); if libc::sigemptyset(&mut act.sa_mask) != 0 { eprintln!("unable to clear action mask: {:?}", Error::last_os_error()); return; } unsafe extern "C" fn on_sig( _sig: libc::c_int, _info: *mut libc::siginfo_t, _uc: *mut libc::c_void, ) { } act.sa_flags = libc::SA_SIGINFO; act.sa_sigaction = on_sig as usize; // Register the action with the signal handler if libc::sigaction(libc::SIGUSR1, &act, std::ptr::null_mut()) != 0 { eprintln!( "unable to register signal handler: {:?}", Error::last_os_error() ); } } }); // We _could_ do this with a timer sending a SIGALRM to interupt the // syscall, but that involves global state, and is just generally not // nice, so we use a simpler approach of just spawning a thread and sending // a signal to it ourselves. Less efficient probably, but IMO cleaner let file_ptr = file as *const _ as usize; let mut thread_id: libc::pthread_t = unsafe { std::mem::zeroed() }; let tid = &mut thread_id as *mut _ as usize; let (tx, rx) = std::sync::mpsc::channel(); let lock_thread = std::thread::Builder::new() .name("flock wait".into()) .spawn(move || unsafe { *(tid as *mut _) = libc::pthread_self(); let res = flock(&*(file_ptr as *const _), flock_flag!(state)); tx.send(res).unwrap(); })?; match rx.recv_timeout(timeout) { Ok(res) => { let _ = lock_thread.join(); res } Err(std::sync::mpsc::RecvTimeoutError::Timeout) => { // Send a signal to interrupt the lock syscall // Note that there is an edge case here, where the thread could be // finished and we just got unlucky by timing out at the exact // same moment. According to https://man7.org/linux/man-pages/man3/pthread_kill.3.html, // at least for glibc, pthread_kill _should_ set ESRCH if the thread // has already finished, but other implementations _might_ just // cause a SIGSEGV unsafe { libc::pthread_kill(thread_id, libc::SIGUSR1) }; // Now that we've sent the signal, we can be fairly sure the thread // syscall will finish/has already finished so we block this time let res = rx.recv().unwrap(); let _ = lock_thread.join(); res } Err(_) => unreachable!(), } } else { flock(file, flock_flag!(state)) } } #[inline] pub(super) fn unlock(file: &File) -> Result { flock(file, libc::LOCK_UN) } #[inline] fn flock(file: &File, flag: libc::c_int) -> Result { error!(unsafe { libc::flock(file.as_raw_fd(), flag) }); Ok(()) } #[inline] pub(super) fn is_unsupported(err: &std::io::Error) -> bool { match err.raw_os_error() { // Unfortunately, depending on the target, these may or may not be the same. // For targets in which they are the same, the duplicate pattern causes a warning. #[allow(unreachable_patterns)] Some(libc::ENOTSUP | libc::EOPNOTSUPP | libc::ENOSYS) => true, _ => false, } } #[inline] pub(super) fn is_contended(err: &Error) -> bool { err.raw_os_error() == Some(libc::EWOULDBLOCK) } #[inline] pub(super) fn is_timed_out(err: &Error) -> bool { err.raw_os_error() == Some(libc::EINTR) } tame-index-0.23.1/src/utils/flock/win_bindings.rs000064400000000000000000000042361046102023000200140ustar 00000000000000//! Bindings generated by `minwin` 0.1.0 #![allow( non_snake_case, non_upper_case_globals, non_camel_case_types, clippy::upper_case_acronyms )] #[link(name = "kernel32")] unsafe extern "system" { #[link_name = "CloseHandle"] pub fn close_handle(object: Handle) -> Bool; #[link_name = "CreateEventA"] pub fn create_event_a( event_attributes: *const ::core::ffi::c_void, manual_reset: Bool, initial_state: Bool, name: Pcstr, ) -> Handle; #[link_name = "LockFileEx"] pub fn lock_file_ex( file: Handle, flags: LockFileFlags::Enum, reserved: u32, number_of_bytes_to_lock_low: u32, number_of_bytes_to_lock_high: u32, overlapped: *mut Overlapped, ) -> Bool; #[link_name = "UnlockFile"] pub fn unlock_file( file: Handle, file_offset_low: u32, file_offset_high: u32, number_of_bytes_to_unlock_low: u32, number_of_bytes_to_unlock_high: u32, ) -> Bool; #[link_name = "WaitForSingleObject"] pub fn wait_for_single_object(handle: Handle, milliseconds: u32) -> Win32Error::Enum; } pub const Infinite: u32 = 4294967295; pub type Bool = i32; pub mod FileFlagsAndAttributes { pub type Enum = u32; pub const FileFlagOverlapped: Enum = 1073741824; } pub type Handle = isize; pub mod LockFileFlags { pub type Enum = u32; pub const LockfileFailImmediately: Enum = 1; pub const LockfileExclusiveLock: Enum = 2; } #[repr(C)] pub struct Overlapped { pub internal: usize, pub internal_high: usize, pub anonymous: Overlapped_0, pub event: Handle, } #[repr(C)] pub union Overlapped_0 { pub anonymous: ::std::mem::ManuallyDrop, pub pointer: *mut ::core::ffi::c_void, } #[repr(C)] pub struct Overlapped_0_0 { pub offset: u32, pub offset_high: u32, } pub type Pcstr = *const u8; pub mod Win32Error { pub type Enum = u32; pub const WaitObject0: Enum = 0; pub const ErrorInvalidFunction: Enum = 1; pub const ErrorLockViolation: Enum = 33; pub const WaitIoCompletion: Enum = 192; pub const WaitTimeout: Enum = 258; pub const ErrorIoPending: Enum = 997; } tame-index-0.23.1/src/utils/flock/windows.rs000064400000000000000000000071471046102023000170400ustar 00000000000000#![allow(unsafe_code)] //! Support for windows file locking. This implementation mainly pulls from //! //! in addition to cargo use super::LockState; use std::{fs::File, io::Error, os::windows::io::AsRawHandle, time::Duration}; type Result = std::io::Result<()>; #[path = "win_bindings.rs"] mod bindings; use bindings::*; macro_rules! flock_flag { ($state:expr) => { match $state { LockState::Shared => 0, LockState::Exclusive => LockFileFlags::LockfileExclusiveLock, _ => unreachable!(), } }; } #[inline] pub(super) fn open_opts(exclusive: bool) -> std::fs::OpenOptions { let mut o = std::fs::OpenOptions::new(); o.read(true); if exclusive { o.write(true).create(true); } // Since we do async I/O with waits, we need to open the file with overlapped // as otherwise LockFileEx will just hang until it can take the lock use std::os::windows::fs::OpenOptionsExt; o.custom_flags(FileFlagsAndAttributes::FileFlagOverlapped); o } #[inline] pub(super) fn try_lock(file: &File, state: LockState) -> Result { flock( file, flock_flag!(state) | LockFileFlags::LockfileFailImmediately, None, ) } #[inline] pub(super) fn lock(file: &File, state: LockState, timeout: Option) -> Result { flock(file, flock_flag!(state), timeout) } fn flock(file: &File, flags: u32, timeout: Option) -> Result { unsafe { let mut overlapped: Overlapped = std::mem::zeroed(); overlapped.event = create_event_a(std::ptr::null(), 0, 0, std::ptr::null()); if overlapped.event == 0 { return Err(Error::last_os_error()); } let res = if lock_file_ex( file.as_raw_handle() as Handle, flags, 0, !0, !0, &mut overlapped, ) == 0 { let err = Error::last_os_error(); if err.raw_os_error() == Some(Win32Error::ErrorIoPending as i32) { let timeout = timeout.map_or(u32::MAX, |dur| { let millis = dur.as_millis(); if millis >= Infinite as u128 { u32::MAX } else { millis as u32 } }); match wait_for_single_object(overlapped.event, timeout) { Win32Error::WaitObject0 => Ok(()), Win32Error::WaitTimeout => { Err(Error::from_raw_os_error(Win32Error::WaitTimeout as _)) } _ => Err(Error::last_os_error()), } } else { Err(err) } } else { Ok(()) }; close_handle(overlapped.event); res } } pub(super) fn unlock(file: &File) -> Result { unsafe { let ret = unlock_file(file.as_raw_handle() as Handle, 0, 0, !0, !0); if ret == 0 { Err(Error::last_os_error()) } else { Ok(()) } } } #[inline] pub(super) fn is_contended(err: &Error) -> bool { err.raw_os_error() == Some(Win32Error::ErrorLockViolation as i32) } #[inline] pub(super) fn is_unsupported(err: &Error) -> bool { err.raw_os_error() == Some(Win32Error::ErrorInvalidFunction as i32) } #[inline] pub(super) fn is_timed_out(err: &Error) -> bool { err.raw_os_error().is_some_and(|x| { x == Win32Error::WaitTimeout as i32 || x == Win32Error::WaitIoCompletion as i32 }) } tame-index-0.23.1/src/utils/flock.rs000064400000000000000000000244531046102023000153450ustar 00000000000000//! Provides facilities for file locks on unix and windows use crate::{Error, Path, PathBuf}; use std::{fs, time::Duration}; #[cfg_attr(unix, path = "flock/unix.rs")] #[cfg_attr(windows, path = "flock/windows.rs")] mod sys; /// An error pertaining to a failed file lock #[derive(Debug, thiserror::Error)] #[error("failed to obtain lock file '{path}'")] pub struct FileLockError { /// The path of the file lock pub path: PathBuf, /// The underlying failure reason pub source: LockError, } /// Errors that can occur when attempting to acquire a [`FileLock`] #[derive(Debug, thiserror::Error)] pub enum LockError { /// An I/O error occurred attempting to open the lock file #[error(transparent)] Open(std::io::Error), /// Exclusive locks cannot be take on read-only file systems #[error("attempted to take an exclusive lock on a read-only path")] Readonly, /// Failed to create parents directories to lock file #[error("failed to create parent directories for lock path")] CreateDir(std::io::Error), /// Locking is not supported if the lock file is on an NFS, though note this /// is a bit more nuanced as `NFSv4` _does_ support file locking, but is out /// of scope, at least for now #[error("NFS do not support locking")] Nfs, /// This could happen on eg. _extremely_ old and outdated OSes or some filesystems /// and is only present for completeness #[error("locking is not supported on the filesystem and/or in the kernel")] NotSupported, /// An I/O error occurred attempting to un/lock the file #[error("failed to acquire or release file lock")] Lock(std::io::Error), /// The lock could not be acquired within the caller provided timeout #[error("failed to acquire lock within the specified duration")] TimedOut, /// The lock is currently held by another #[error("the lock is currently held by another")] Contested, } /// Provides options for creating a [`FileLock`] pub struct LockOptions<'pb> { path: std::borrow::Cow<'pb, Path>, exclusive: bool, shared_fallback: bool, } impl<'pb> LockOptions<'pb> { /// Creates a new [`Self`] for the specified path #[inline] pub fn new(path: &'pb Path) -> Self { Self { path: path.into(), exclusive: false, shared_fallback: false, } } /// Creates a new [`Self`] for locking cargo's global package lock /// /// If specified, the path is used as the root, otherwise it is rooted at /// the path determined by `$CARGO_HOME` #[inline] pub fn cargo_package_lock(root: Option) -> Result { let mut path = if let Some(root) = root { root } else { crate::utils::cargo_home()? }; path.push(".package-cache"); Ok(Self { path: path.into(), exclusive: true, shared_fallback: false, }) } /// Will attempt to acquire a shared lock rather than an exclusive one #[inline] pub fn shared(mut self) -> Self { self.exclusive = false; self } /// Will attempt to acquire an exclusive lock, which can optionally fallback /// to a shared lock if the lock file is for a read only filesystem #[inline] pub fn exclusive(mut self, shared_fallback: bool) -> Self { self.exclusive = true; self.shared_fallback = shared_fallback; self } /// Attempts to acquire a lock, but fails immediately if the lock is currently /// held #[inline] pub fn try_lock(&self) -> Result { self.open_and_lock(Option:: Option>::None) } /// Attempts to acquire a lock, waiting if the lock is currently held. /// /// Unlike [`Self::try_lock`], if the lock is currently held, the specified /// callback is called to inform the caller that a wait is about to /// be performed, then waits for the amount of time specified by the return /// of the callback, or infinitely in the case of `None`. #[inline] pub fn lock(&self, wait: impl Fn(&Path) -> Option) -> Result { self.open_and_lock(Some(wait)) } fn open(&self, opts: &fs::OpenOptions) -> Result { opts.open(self.path.as_std_path()).or_else(|err| { if err.kind() == std::io::ErrorKind::NotFound && self.exclusive { fs::create_dir_all(self.path.parent().unwrap()).map_err(|e| FileLockError { path: self.path.parent().unwrap().to_owned(), source: LockError::CreateDir(e), })?; self.open(opts) } else { // Note we just use the 30 EROFS constant here, which won't work on WASI, Haiku, or some other // niche targets, but none of them are intended targets for this crate, but can be fixed later // if someone actually uses them let source = if err.kind() == std::io::ErrorKind::PermissionDenied || cfg!(unix) && err.raw_os_error() == Some(30 /* EROFS */) { LockError::Readonly } else { LockError::Open(err) }; Err(FileLockError { path: self.path.as_ref().to_owned(), source, }) } }) } fn open_and_lock( &self, wait: Option Option>, ) -> Result { let (state, file) = if self.exclusive { match self.open(&sys::open_opts(true)) { Ok(file) => (LockState::Exclusive, file), Err(err) => { // If the user requested it, check if the error is due to a read only error, // and if so, fallback to a shared lock instead of an exclusive lock, just // as cargo does // // https://github.com/rust-lang/cargo/blob/0b6cc3c75f1813df857fb54421edf7f8fee548e3/src/cargo/util/config/mod.rs#L1907-L1935 if self.shared_fallback && matches!(err.source, LockError::Readonly) { (LockState::Shared, self.open(&sys::open_opts(false))?) } else { return Err(err.into()); } } } } else { (LockState::Shared, self.open(&sys::open_opts(false))?) }; self.do_lock(state, &file, wait) .map_err(|source| FileLockError { path: self.path.as_ref().to_owned(), source, })?; Ok(FileLock { file: Some(file), state, }) } fn do_lock( &self, state: LockState, file: &fs::File, wait: Option Option>, ) -> Result<(), LockError> { #[cfg(all(target_os = "linux", not(target_env = "musl")))] fn is_on_nfs_mount(path: &crate::Path) -> bool { use std::os::unix::prelude::*; let path = match std::ffi::CString::new(path.as_os_str().as_bytes()) { Ok(path) => path, Err(_) => return false, }; #[allow(unsafe_code)] unsafe { let mut buf: libc::statfs = std::mem::zeroed(); let r = libc::statfs(path.as_ptr(), &mut buf); r == 0 && buf.f_type as u32 == libc::NFS_SUPER_MAGIC as u32 } } #[cfg(any(not(target_os = "linux"), target_env = "musl"))] fn is_on_nfs_mount(_path: &crate::Path) -> bool { false } // File locking on Unix is currently implemented via `flock`, which is known // to be broken on NFS. We could in theory just ignore errors that happen on // NFS, but apparently the failure mode [1] for `flock` on NFS is **blocking // forever**, even if the "non-blocking" flag is passed! // // As a result, we just skip all file locks entirely on NFS mounts. That // should avoid calling any `flock` functions at all, and it wouldn't work // there anyway. // // [1]: https://github.com/rust-lang/cargo/issues/2615 if is_on_nfs_mount(&self.path) { return Err(LockError::Nfs); } match sys::try_lock(file, state) { Ok(()) => return Ok(()), // In addition to ignoring NFS which is commonly not working we also // just ignore locking on filesystems that look like they don't // implement file locking. Err(e) if sys::is_unsupported(&e) => return Err(LockError::NotSupported), Err(e) => { if !sys::is_contended(&e) { return Err(LockError::Lock(e)); } } } // Signal to the caller that we are about to enter a blocking operation // and whether they want to assign a timeout to it if let Some(wait) = wait { let timeout = wait(&self.path); sys::lock(file, state, timeout).map_err(|e| { if sys::is_timed_out(&e) { LockError::TimedOut } else { LockError::Lock(e) } }) } else { Err(LockError::Contested) } } } #[derive(PartialEq, Copy, Clone, Debug)] enum LockState { Exclusive, Shared, Unlocked, } /// A currently held file lock. /// /// The lock is released when this is dropped, or the program exits for any reason, /// including `SIGKILL` or power loss pub struct FileLock { file: Option, state: LockState, } impl FileLock { /// Creates a [`Self`] in an unlocked state. /// /// This allows for easy testing or use in situations where you don't care /// about file locking, or have other ways to ensure something is uncontested pub fn unlocked() -> Self { Self { file: None, state: LockState::Unlocked, } } } impl Drop for FileLock { fn drop(&mut self) { if self.state != LockState::Unlocked { if let Some(f) = self.file.take() { let _ = sys::unlock(&f); } } } } tame-index-0.23.1/src/utils/git.rs000064400000000000000000000122741046102023000150300ustar 00000000000000//! Utilities for working with gix that might be useful for downstream users use crate::{Error, error::GitError}; /// Writes the `FETCH_HEAD` for the specified fetch outcome to the specified git /// repository /// /// This function is narrowly focused on on writing a `FETCH_HEAD` that contains /// exactly two pieces of information, the id of the commit pointed to by the /// remote `HEAD`, and, if it exists, the same id with the remote branch whose /// `HEAD` is the same. This focus gives use two things: /// 1. `FETCH_HEAD` that can be parsed to the correct remote HEAD by /// [`gix`](https://github.com/Byron/gitoxide/commit/eb2b513bd939f6b59891d0a4cf5465b1c1e458b3) /// 1. A `FETCH_HEAD` that closely (or even exactly) matches that created by /// cargo via git or git2 when fetching only `+HEAD:refs/remotes/origin/HEAD` /// /// Calling this function for the fetch outcome of a clone will write `FETCH_HEAD` /// just as if a normal fetch had occurred, but note that AFAICT neither git nor /// git2 does this, ie. a fresh clone will not have a `FETCH_HEAD` present. I don't /// _think_ that has negative implications, but if it does...just don't call this /// function on the result of a clone :) /// /// Note that the remote provided should be the same remote used for the fetch /// operation. The reason this is not just grabbed from the repo is because /// repositories may not have the configured remote, or the remote was modified /// (eg. replacing refspecs) before the fetch operation pub fn write_fetch_head( repo: &gix::Repository, fetch: &gix::remote::fetch::Outcome, remote: &gix::Remote<'_>, ) -> Result { use gix::{bstr::ByteSlice, protocol::handshake::Ref}; use std::fmt::Write; // Find the remote head commit let (head_target_branch, oid) = fetch .ref_map .mappings .iter() .find_map(|mapping| { let gix::remote::fetch::refmap::Source::Ref(rref) = &mapping.remote else { return None; }; let Ref::Symbolic { full_ref_name, target, object, .. } = rref else { return None; }; (full_ref_name == "HEAD").then_some((target, object)) }) .ok_or_else(|| GitError::UnableToFindRemoteHead)?; let remote_url = { let ru = remote .url(gix::remote::Direction::Fetch) .expect("can't fetch without a fetch url"); let s = ru.to_bstring(); let v = s.into(); String::from_utf8(v).expect("remote url was not utf-8 :-/") }; let fetch_head = { let mut hex_id = [0u8; 40]; let gix::ObjectId::Sha1(sha1) = oid; let commit_id = crate::utils::encode_hex(sha1, &mut hex_id); let mut fetch_head = String::new(); let remote_name = remote .name() .and_then(|n| { let gix::remote::Name::Symbol(name) = n else { return None; }; Some(name.as_ref()) }) .unwrap_or("origin"); // We write the remote HEAD first, but _only_ if it was explicitly requested if remote .refspecs(gix::remote::Direction::Fetch) .iter() .any(|rspec| { let rspec = rspec.to_ref(); if !rspec.remote().is_some_and(|r| r.ends_with(b"HEAD")) { return false; } rspec.local().is_some_and(|l| { l.to_str().ok().and_then(|l| { l.strip_prefix("refs/remotes/") .and_then(|l| l.strip_suffix("/HEAD")) }) == Some(remote_name) }) }) { writeln!(&mut fetch_head, "{commit_id}\t\t{remote_url}").unwrap(); } // Attempt to get the branch name, but if it looks suspect just skip this, // it _should_ be fine, or at least, we've already written the only thing // that gix can currently parse if let Some(branch_name) = head_target_branch .to_str() .ok() .and_then(|s| s.strip_prefix("refs/heads/")) { writeln!( &mut fetch_head, "{commit_id}\t\tbranch '{branch_name}' of {remote_url}" ) .unwrap(); } fetch_head }; // We _could_ also emit other branches/tags like git does, however it's more // complicated than just our limited use case of writing remote HEAD // // 1. Remote branches are always emitted, however in gix those aren't part // of the ref mappings if they haven't been updated since the last fetch // 2. Conversely, tags are _not_ written by git unless they have been changed // added, but gix _does_ always place those in the fetch mappings if fetch_head.is_empty() { return Err(GitError::UnableToFindRemoteHead.into()); } let fetch_head_path = crate::PathBuf::from_path_buf(repo.path().join("FETCH_HEAD"))?; std::fs::write(&fetch_head_path, fetch_head) .map_err(|io| Error::IoPath(io, fetch_head_path))?; Ok(*oid) } tame-index-0.23.1/src/utils.rs000064400000000000000000000325131046102023000142430ustar 00000000000000//! Provides several useful functions for determining the disk location of a //! remote registry index use crate::{Error, InvalidUrl, InvalidUrlError, PathBuf}; pub mod flock; #[cfg(feature = "__git")] pub mod git; /// Returns the storage directory (in utf-8) used by Cargo, often known as /// `.cargo` or `CARGO_HOME` #[inline] pub fn cargo_home() -> Result { Ok(crate::PathBuf::from_path_buf(home::cargo_home()?)?) } /// Encodes a slice of bytes into a hexadecimal string to the specified buffer pub(crate) fn encode_hex<'out, const I: usize, const O: usize>( input: &[u8; I], output: &'out mut [u8; O], ) -> &'out str { assert_eq!(I * 2, O); const CHARS: &[u8] = b"0123456789abcdef"; for (i, &byte) in input.iter().enumerate() { let i = i * 2; output[i] = CHARS[(byte >> 4) as usize]; output[i + 1] = CHARS[(byte & 0xf) as usize]; } // SAFETY: we only emit ASCII hex characters #[allow(unsafe_code)] unsafe { std::str::from_utf8_unchecked(output) } } /// The details for a remote url pub struct UrlDir { /// The unique directory name for the url pub dir_name: String, /// The canonical url for the remote url pub canonical: String, } /// Canonicalizes a `git+` url the same as cargo. /// /// This is similar to cargo's `CanonicalUrl`, which previously was only used for /// git+ url's, but since cargo 1.85.0 is now used as part of the hash for all /// sources. Note that cargo removes queries and fragments _only_ from git+ URLs /// and that happens before canonicalization, so this function does not handle them /// specifically as we only care about sparse and git registry URLs pub fn canonicalize_url(mut url: &str) -> Result { let scheme_ind = url.find("://").map(|i| i + 3).ok_or_else(|| InvalidUrl { url: url.to_owned(), source: InvalidUrlError::MissingScheme, })?; // Could use the Url crate for this, but it's simple enough and we don't // need to deal with every possible url (I hope...) let (host, path_length) = match url[scheme_ind..].find('/') { Some(end) => ( &url[scheme_ind..scheme_ind + end], url.len() - (end + scheme_ind), ), None => (&url[scheme_ind..], 0), }; // trim port let host = host.split(':').next().unwrap(); if path_length > 1 && url.ends_with('/') { url = &url[..url.len() - 1]; } if url.ends_with(".git") { url = &url[..url.len() - 4]; } // cargo special cases github.com for reasons, so do the same Ok(if host == "github.com" { url.to_lowercase() } else { url.to_owned() }) } /// Converts a url into a relative path and its canonical form /// /// Cargo uses a small algorithm to create unique directory names for any url /// so that they can be located in the same root without clashing /// /// This function currently only supports 2 different URL kinds /// /// * `(?:registry+)?` /// * `sparse+` #[allow(deprecated)] pub fn url_to_local_dir(url: &str, stable: bool) -> Result { use std::hash::{Hash, Hasher, SipHasher}; // This is extremely irritating, but we need to use usize for the kind, which // impacts the hash calculation, making it different based on pointer size. // // The reason for this is that cargo just uses #[derive(Hash)] for the SourceKind // https://github.com/rust-lang/cargo/blob/88b4b3bcd3bbb66873734d97ae412a6bcf9b75ee/crates/cargo-util-schemas/src/core/source_kind.rs#L4-L5, // which then uses https://doc.rust-lang.org/core/intrinsics/fn.discriminant_value.html // to get the discriminant and add to the hash...and that is pointer width :( // // Note that these are isize instead of usize because contrary to what one // would expect from the automatic discriminant assigned by rustc starting // at 0 and incrementing by 1 each time...it's actually signed, which can // be seen by overriding `Hasher::write_isize` and hashing a discriminant // // This is unfortunately a hard requirement because of https://github.com/rust-lang/rustc-stable-hash/blob/24e9848c89917abca155c8f854118e6d00ad4a30/src/stable_hasher.rs#L263-L299 // where it specializes _only_ isize to only write a u8 if the value is less // than 0xff, something that doesn't happen for usize, which of course affects // the calculated hash const GIT_REGISTRY: isize = 2; const SPARSE_REGISTRY: isize = 3; // Ensure we have a registry or bare url let (url, scheme_ind, kind) = { let mut scheme_ind = url.find("://").ok_or_else(|| InvalidUrl { url: url.to_owned(), source: InvalidUrlError::MissingScheme, })?; let scheme_str = &url[..scheme_ind]; let (url, kind) = match scheme_str.split_once('+') { Some(("sparse", _)) => (url, SPARSE_REGISTRY), // If there is no scheme modifier, assume git registry, same as cargo None => (url, GIT_REGISTRY), Some(("registry", _)) => { scheme_ind -= 9; (&url[9..], GIT_REGISTRY) } Some((_, _)) => { return Err(InvalidUrl { url: url.to_owned(), source: InvalidUrlError::UnknownSchemeModifier, } .into()); } }; (url, scheme_ind + 3, kind) }; let (dir_name, url) = if stable { let canonical = canonicalize_url(url)?; let hash = { let mut hasher = rustc_stable_hash::StableSipHasher128::new(); kind.hash(&mut hasher); canonical.hash(&mut hasher); Hasher::finish(&hasher) }; let mut raw_ident = [0u8; 16]; let ident = encode_hex(&hash.to_le_bytes(), &mut raw_ident); let dir_name = { let host = match url[scheme_ind..].find('/') { Some(end) => &url[scheme_ind..scheme_ind + end], None => &url[scheme_ind..], }; // trim port let host = host.split(':').next().unwrap(); host.split_once('@').map_or(host, |(_user, host)| host) }; (format!("{dir_name}-{ident}"), canonical) } else { let hash = { let mut hasher = SipHasher::new(); kind.hash(&mut hasher); url.hash(&mut hasher); hasher.finish() }; let mut raw_ident = [0u8; 16]; let ident = encode_hex(&hash.to_le_bytes(), &mut raw_ident); // Could use the Url crate for this, but it's simple enough and we don't // need to deal with every possible url (I hope...) let host = match url[scheme_ind..].find('/') { Some(end) => &url[scheme_ind..scheme_ind + end], None => &url[scheme_ind..], }; // trim port let host = host.split(':').next().unwrap(); let host = host.split_once('@').map_or(host, |(_user, host)| host); (format!("{host}-{ident}"), url.to_owned()) }; Ok(UrlDir { dir_name, canonical: url, }) } /// Get the disk location of the specified url, as well as its canonical form /// /// If not specified, the root directory is the user's default cargo home pub fn get_index_details( url: &str, root: Option, stable: bool, ) -> Result<(PathBuf, String), Error> { let url_dir = url_to_local_dir(url, stable)?; let mut path = match root { Some(path) => path, None => cargo_home()?, }; path.push("registry"); path.push("index"); path.push(url_dir.dir_name); Ok((path, url_dir.canonical)) } use std::io; /// Parses the output of `cargo -V` to get the semver /// /// This handles the 2? cases that I am aware of /// /// 1. Official cargo prints `cargo (?:-)? ( )` /// 2. Non-official builds may drop the additional metadata and just print `cargo ` #[inline] fn parse_cargo_semver(s: &str) -> Result { let semver = s.trim().split(' ').nth(1).ok_or_else(|| { io::Error::new( io::ErrorKind::InvalidData, "cargo version information was in an invalid format", ) })?; Ok(semver.parse()?) } /// Retrieves the current version of cargo being used pub fn cargo_version(working_dir: Option<&crate::Path>) -> Result { let mut cargo = std::process::Command::new( std::env::var_os("CARGO") .as_deref() .unwrap_or(std::ffi::OsStr::new("cargo")), ); cargo.arg("-V"); if let Some(wd) = working_dir { cargo.current_dir(wd); } cargo.stdout(std::process::Stdio::piped()); let output = cargo.output()?; if !output.status.success() { return Err(io::Error::other("failed to request cargo version information").into()); } let stdout = String::from_utf8(output.stdout) .map_err(|err| io::Error::new(io::ErrorKind::InvalidData, err))?; parse_cargo_semver(&stdout) } #[cfg(test)] mod test { use super::{get_index_details, url_to_local_dir}; use crate::PathBuf; #[test] #[cfg(all(target_pointer_width = "64", target_endian = "little"))] fn matches_cargo() { assert_eq!( get_index_details(crate::CRATES_IO_INDEX, Some(PathBuf::new()), false).unwrap(), ( "registry/index/github.com-1ecc6299db9ec823".into(), crate::CRATES_IO_INDEX.to_owned() ) ); assert_eq!( get_index_details(crate::CRATES_IO_HTTP_INDEX, Some(PathBuf::new()), false).unwrap(), ( "registry/index/index.crates.io-6f17d22bba15001f".into(), crate::CRATES_IO_HTTP_INDEX.to_owned(), ) ); const NON_CRATES_IO_GITHUB: &str = "https://github.com/EmbarkStudios/cargo-test-index"; assert_eq!( get_index_details(NON_CRATES_IO_GITHUB, Some(PathBuf::new()), false).unwrap(), ( "registry/index/github.com-655148e0a865c9e0".into(), NON_CRATES_IO_GITHUB.to_owned(), ) ); const NON_GITHUB_INDEX: &str = "https://dl.cloudsmith.io/public/embark/deny/cargo/index.git"; assert_eq!( get_index_details(NON_GITHUB_INDEX, Some(PathBuf::new()), false).unwrap(), ( "registry/index/dl.cloudsmith.io-955e041deb7d37e6".into(), NON_GITHUB_INDEX.to_owned(), ) ); // Just verifies that any non git+ or sparse+ url is treated as a git // registry for purposes of hashing const FAKE_REGISTRY: &str = "https://github.com/RustSec/advisory-db"; assert_eq!( url_to_local_dir(FAKE_REGISTRY, false).unwrap().dir_name, "github.com-a946fc29ac602819" ); } #[test] fn matches_cargo_1850() { assert_eq!( get_index_details(crate::CRATES_IO_HTTP_INDEX, Some(PathBuf::new()), true).unwrap(), ( "registry/index/index.crates.io-1949cf8c6b5b557f".into(), crate::CRATES_IO_HTTP_INDEX.to_owned(), ) ); assert_eq!( get_index_details(crate::CRATES_IO_INDEX, Some(PathBuf::new()), true).unwrap(), ( "registry/index/github.com-25cdd57fae9f0462".into(), crate::CRATES_IO_INDEX.to_owned(), ) ); assert_eq!( get_index_details( "https://github.com/EmbarkStudios/cargo-test-index", Some(PathBuf::new()), true ) .unwrap(), ( "registry/index/github.com-513223c940e0f1e9".into(), "https://github.com/embarkstudios/cargo-test-index".to_owned(), ) ); assert_eq!( get_index_details( "sparse+https://cargo.cloudsmith.io/embark/deny/", Some(PathBuf::new()), true ) .unwrap(), ( "registry/index/cargo.cloudsmith.io-2fc1f5411e6e72fd".into(), "sparse+https://cargo.cloudsmith.io/embark/deny".to_owned(), ) ); } #[test] #[cfg(all(target_pointer_width = "32", target_endian = "little"))] fn matches_cargo_32bit() { assert_eq!( get_index_details(crate::CRATES_IO_HTTP_INDEX, Some(PathBuf::new()), false).unwrap(), ( "registry/index/index.crates.io-1cd66030c949c28d".into(), crate::CRATES_IO_HTTP_INDEX.to_owned(), ) ); } #[test] fn gets_cargo_version() { const MINIMUM: semver::Version = semver::Version::new(1, 70, 0); let version = super::cargo_version(None).unwrap(); assert!(version >= MINIMUM); } #[test] fn parses_cargo_semver() { use super::parse_cargo_semver as pcs; assert_eq!( pcs("cargo 1.71.0 (cfd3bbd8f 2023-06-08)\n").unwrap(), semver::Version::new(1, 71, 0) ); assert_eq!( pcs("cargo 1.73.0-nightly (7ac9416d8 2023-07-24)\n").unwrap(), "1.73.0-nightly".parse().unwrap() ); assert_eq!( pcs("cargo 1.70.0\n").unwrap(), semver::Version::new(1, 70, 0) ); } } tame-index-0.23.1/tests/cache.rs000064400000000000000000000065001046102023000145160ustar 00000000000000#![cfg(target_pointer_width = "64")] //! Note we only run these tests if _built_ for 64-bit, as the only test targets //! this project cares about are 64 bit, and the issue is that, to match cargo //! we need to calculate the hash of the index the same, the rub is that when //! running a 32-bit target (eg. i686) on a 64-bit host where the cargo on the //! host is built for the 64-bit target as well, the local directories won't match mod utils; use tame_index::{IndexCache, index::cache::ValidCacheEntry, utils::get_index_details}; /// Validates that we can parse the current version of cache entries emitted by /// cargo by reading the cache entry that should be there for one of this crate's /// dependencies. This test should only fail if you have built the crate, wiped /// the crates.io cache, then run the test executable without using cargo. If /// you do that, that is your fault. #[test] fn parses_current_cargo_cache() { let stable = tame_index::utils::cargo_version(None).unwrap() >= semver::Version::new(1, 85, 0); let (path, _url) = get_index_details(tame_index::CRATES_IO_HTTP_INDEX, None, stable).unwrap(); let cache = IndexCache::at_path(path); let lock = &utils::unlocked(); let cached = cache .read_cache_file("camino".try_into().unwrap(), lock) .expect("failed to read cache file") .expect("cache file not found"); let vce = ValidCacheEntry::read(&cached) .expect("failed to parse cargo cache file, we might need to update this crate"); assert!( vce.revision.starts_with("etag:"), "cargo rarely/never writes anything but etag revisions to the cache" ); let camino = vce .to_krate(None) .expect("failed to parse cache entry") .unwrap(); // The initial version of camino used by this crate, this version will _always_ // exist in the index, even if it yanked in the future, and is otherwise immutable let camino_version = camino .versions .iter() .find(|iv| iv.version == "1.1.4") .expect("failed to find expected version"); assert_eq!( "c530edf18f37068ac2d977409ed5cd50d53d73bc653c7647b48eb78976ac9ae2", camino_version.checksum.to_string() ); } /// Validates we can write cache files the exact same as the current version of cargo #[test] fn serializes_current_cargo_cache() { let stable = tame_index::utils::cargo_version(None).unwrap() >= semver::Version::new(1, 85, 0); let (path, _url) = get_index_details(tame_index::CRATES_IO_HTTP_INDEX, None, stable).unwrap(); let cache = IndexCache::at_path(path); let lock = &utils::unlocked(); let cargos = cache .read_cache_file("camino".try_into().unwrap(), lock) .expect("failed to read cache file") .expect("cache file not found"); let cargo_ce = ValidCacheEntry::read(&cargos) .expect("failed to parse cargo cache file, we might need to update this crate"); let cargos_krate = cargo_ce.to_krate(None).unwrap().unwrap(); let mut ours = Vec::new(); cargos_krate .write_cache_entry(&mut ours, cargo_ce.revision) .expect("failed to write"); let our_ce = ValidCacheEntry::read(&ours).expect("failed to parse our cache file"); assert_eq!(cargo_ce.revision, our_ce.revision); assert_eq!(cargo_ce.version_entries, our_ce.version_entries); } tame-index-0.23.1/tests/flock.rs000064400000000000000000000106411046102023000145520ustar 00000000000000#![allow(missing_docs)] use std::time::Duration; use tame_index::utils::flock::LockOptions; mod utils; enum LockKind { Exclusive, Shared, } impl LockKind { fn as_str(&self) -> &'static str { match self { Self::Exclusive => "exclusive", Self::Shared => "shared", } } } fn spawn(kind: LockKind, path: &tame_index::Path) -> std::process::Child { let mut cmd = std::process::Command::new("cargo"); cmd.env("RUST_BACKTRACE", "1") .args([ "run", "-q", "--manifest-path", "tests/flock/Cargo.toml", "--", ]) .stdout(std::process::Stdio::piped()) .arg(kind.as_str()) .arg(path); let mut child = cmd.spawn().expect("failed to spawn flock"); // Wait for the child to actually take the lock { use std::io::Read; let mut output = child.stdout.take().unwrap(); let mut buff = [0u8; 4]; output.read_exact(&mut buff).expect("failed to read output"); assert_eq!( 'đź”’', char::from_u32(u32::from_le_bytes(buff)).expect("invalid char") ); } child } fn kill(mut child: std::process::Child) { child.kill().expect("failed to kill child"); child.wait().expect("failed to wait for child"); } /// Validates we can take a lock we know is uncontended #[test] fn can_take_lock() { let td = utils::tempdir(); let ctl = td.path().join("can-take-lock"); let lo = LockOptions::new(&ctl).exclusive(false); let _lf = lo .lock(|_p| unreachable!("lock is uncontested")) .expect("failed to acquire lock"); } /// Validates we can create parent directories for a lock file if they don't exist #[test] fn can_take_lock_in_non_existant_directory() { let td = utils::tempdir(); let ctl = td.path().join("sub/dir/can-take-lock"); let lo = LockOptions::new(&ctl).exclusive(false); let _lf = lo.try_lock().expect("failed to acquire lock"); } /// Validates we can take multiple shared locks of the same file #[test] fn can_take_shared_lock() { let td = utils::tempdir(); let ctl = td.path().join("can-take-shared-lock"); let _ = std::fs::OpenOptions::new() .create(true) .truncate(true) .write(true) .open(&ctl) .expect("failed to create lock file"); let child = spawn(LockKind::Shared, &ctl); let mut lo = LockOptions::new(&ctl); lo = lo.shared(); lo.try_lock().expect("failed to acquire shared lock"); lo = lo.exclusive(false); if lo.try_lock().is_ok() { panic!("we acquired an exclusive lock but we shouldn't have been able to"); } kill(child); lo.try_lock().expect("failed to acquire exclusive lock"); } /// Validates we can wait for a lock to be released #[test] fn waits_lock() { let td = utils::tempdir(); let ctl = td.path().join("waits-lock"); let _ = std::fs::OpenOptions::new() .create(true) .truncate(true) .write(true) .open(&ctl) .expect("failed to create lock file"); let child = spawn(LockKind::Exclusive, &ctl); std::thread::scope(|s| { s.spawn(|| { LockOptions::new(&ctl) .lock(|_p| { println!("waiting on lock"); Some(Duration::from_millis(200)) }) .expect("failed to acquire shared lock"); }); s.spawn(|| { std::thread::sleep(Duration::from_millis(100)); kill(child); }); }); } /// Ensures we can timeout if it takes too long to acquire the lock #[test] fn wait_lock_times_out() { let td = utils::tempdir(); let ctl = td.path().join("wait-lock-times-out"); let _ = std::fs::OpenOptions::new() .create(true) .truncate(true) .write(true) .open(&ctl) .expect("failed to create lock file"); let child = spawn(LockKind::Exclusive, &ctl); if let Err(err) = LockOptions::new(&ctl).shared().lock(|_p| { println!("waiting on lock"); Some(Duration::from_millis(100)) }) { let tame_index::Error::Lock(le) = err else { panic!("unexpected error type {err:#?}"); }; assert!(matches!( le.source, tame_index::utils::flock::LockError::TimedOut )); } else { panic!("we should not be able to take the lock"); } kill(child); } tame-index-0.23.1/tests/git.rs000064400000000000000000000372331046102023000142450ustar 00000000000000#![allow(missing_docs)] #![cfg(feature = "__git")] mod utils; use tame_index::{ GitIndex, IndexKrate, IndexLocation, IndexPath, IndexUrl, Path, index::RemoteGitIndex, }; fn remote_index( path: impl AsRef, url: impl AsRef, ) -> RemoteGitIndex { RemoteGitIndex::new( GitIndex::new(IndexLocation { url: IndexUrl::NonCratesIo(url.as_ref().as_str().into()), root: IndexPath::Exact(path.as_ref().join("sub/dir")), cargo_version: None, }) .unwrap(), &utils::unlocked(), ) .unwrap() } enum UpdateEntry { Blob(gix::ObjectId), Tree(UpdateTree), } type UpdateTree = std::collections::BTreeMap; struct TreeUpdateBuilder { update_tree: UpdateTree, } impl TreeUpdateBuilder { fn new() -> Self { Self { update_tree: UpdateTree::new(), } } fn upsert_blob(&mut self, path: &Path, oid: gix::ObjectId) { let ancestors = path.parent().unwrap(); let file_name = path.file_name().unwrap(); let mut ct = &mut self.update_tree; for comp in ancestors.components().filter_map(|com| { if let camino::Utf8Component::Normal(n) = com { Some(n) } else { None } }) { let entry = ct .entry(comp.to_owned()) .or_insert_with(|| UpdateEntry::Tree(UpdateTree::new())); if let UpdateEntry::Tree(t) = entry { ct = t; } else { panic!("blob already inserted"); } } if ct.contains_key(file_name) { panic!("tree already inserted with same filename as blob"); } ct.insert(file_name.to_owned(), UpdateEntry::Blob(oid)); } fn create_updated(self, repo: &gix::Repository) -> gix::ObjectId { let head_tree = repo.head_commit().unwrap().tree().unwrap(); Self::create_inner(self.update_tree, &head_tree, repo) } fn create_inner( tree: UpdateTree, current: &gix::Tree<'_>, repo: &gix::Repository, ) -> gix::ObjectId { use gix::objs::{ Tree, tree::{Entry, EntryKind}, }; let mut nt = Tree::empty(); let tree_ref = current.decode().unwrap(); // Since they are stored in a btreemap we don't have to worry about // sorting here to satisfy the constraints of Tree for (name, entry) in tree { let filename = name.as_str().into(); match entry { UpdateEntry::Blob(oid) => { nt.entries.push(Entry { mode: EntryKind::Blob.into(), oid, filename, }); } UpdateEntry::Tree(ut) => { // Check if there is already an existing tree let current_tree = tree_ref.entries.iter().find_map(|tre| { if tre.filename == name && tre.mode.is_tree() { Some(repo.find_object(tre.oid).unwrap().into_tree()) } else { None } }); let current_tree = current_tree.unwrap_or_else(|| repo.empty_tree()); let oid = Self::create_inner(ut, ¤t_tree, repo); nt.entries.push(Entry { mode: EntryKind::Tree.into(), oid, filename, }); } } } // Insert all the entries from the old tree that weren't added/modified // in this builder for entry in tree_ref.entries { if let Err(i) = nt .entries .binary_search_by_key(&entry.filename, |e| e.filename.as_ref()) { nt.entries.insert( i, Entry { mode: entry.mode, oid: entry.oid.into(), filename: entry.filename.to_owned(), }, ); } } repo.write_object(nt).unwrap().detach() } } /// For testing purposes we create a local git repository as the remote for tests /// so that we avoid /// /// 1. Using the crates.io git registry. It's massive and slow. /// 2. Using some other external git registry, could fail for any number of /// network etc related issues /// 3. Needing to maintain a blessed remote of any kind struct FakeRemote { repo: gix::Repository, td: utils::TempDir, // The parent commit parent: gix::ObjectId, commits: u32, } impl FakeRemote { fn new() -> Self { let td = utils::tempdir(); let mut repo = gix::init_bare(&td).expect("failed to create remote repo"); // Create an empty initial commit so we always have _something_ let parent = { let empty_tree_id = repo .write_object(gix::objs::Tree::empty()) .unwrap() .detach(); let repo = Self::snapshot(&mut repo); repo.commit( "HEAD", "initial commit", empty_tree_id, gix::commit::NO_PARENT_IDS, ) .unwrap() .detach() }; Self { td, repo, parent, commits: 0, } } #[inline] fn snapshot(repo: &mut gix::Repository) -> gix::config::CommitAutoRollback<'_> { let mut config = repo.config_snapshot_mut(); config .set_raw_value(&"author.name", "Integration Test") .unwrap(); config .set_raw_value(&"committer.name", "Integration Test") .unwrap(); config .set_raw_value(&"author.email", "tests@integration.se") .unwrap(); config .set_raw_value(&"committer.email", "tests@integration.se") .unwrap(); // Disable GPG signing, it breaks testing if the user has it enabled config.set_raw_value(&"commit.gpgsign", "false").unwrap(); config.commit_auto_rollback().unwrap() } fn commit(&mut self, krate: &IndexKrate) -> gix::ObjectId { let repo = Self::snapshot(&mut self.repo); let mut serialized = Vec::new(); krate.write_json_lines(&mut serialized).unwrap(); let name: tame_index::KrateName<'_> = krate.name().try_into().unwrap(); let rel_path = tame_index::PathBuf::from(name.relative_path(None)); let blob_id = repo.write_blob(serialized).unwrap().into(); let mut tub = TreeUpdateBuilder::new(); tub.upsert_blob(Path::new(&rel_path), blob_id); let tree_id = tub.create_updated(&repo); self.commits += 1; let parent = repo .commit( "HEAD", format!("{} - {}", krate.name(), self.commits), tree_id, [self.parent], ) .unwrap() .detach(); self.parent = parent; parent } fn local(&self) -> (RemoteGitIndex, utils::TempDir) { let td = utils::tempdir(); let rgi = remote_index(&td, &self.td); (rgi, td) } } /// Validates we can clone a new index repo #[test] fn clones_new() { let remote = FakeRemote::new(); let lock = &utils::unlocked(); let (rgi, _td) = remote.local(); assert!( rgi.cached_krate("clones_new".try_into().unwrap(), lock) .unwrap() .is_none() ); } /// Validates we can open an existing index repo #[test] fn opens_existing() { let mut remote = FakeRemote::new(); let lock = &utils::unlocked(); let krate = utils::fake_krate("opens-existing", 4); let expected_head = remote.commit(&krate); let (first, td) = remote.local(); assert_eq!( first.local().head_commit().unwrap(), expected_head.to_hex().to_string() ); // This should not be in the cache assert_eq!( first .krate("opens-existing".try_into().unwrap(), true, lock) .expect("failed to read git blob") .expect("expected krate"), krate, ); let second = remote_index(&td, &remote.td); assert_eq!( second.local().head_commit().unwrap(), expected_head.to_hex().to_string() ); // This should be in the cache as it is file based not memory based assert_eq!( first .cached_krate("opens-existing".try_into().unwrap(), lock) .expect("failed to read cache file") .expect("expected cached krate"), krate, ); } /// Validates that cache entries can be created and used #[test] fn updates_cache() { let mut remote = FakeRemote::new(); let lock = &utils::unlocked(); let krate = utils::fake_krate("updates-cache", 4); let expected_head = remote.commit(&krate); let (rgi, _td) = remote.local(); assert_eq!( rgi.local().head_commit().unwrap(), expected_head.to_hex().to_string() ); // This should not be in the cache assert_eq!( rgi.krate("updates-cache".try_into().unwrap(), true, lock) .expect("failed to read git blob") .expect("expected krate"), krate, ); assert_eq!( rgi.cached_krate("updates-cache".try_into().unwrap(), lock) .expect("failed to read cache file") .expect("expected krate"), krate, ); } /// Validates we can fetch updates from the remote and invalidate only the cache /// entries for the crates that have changed #[test] fn fetch_invalidates_cache() { let mut remote = FakeRemote::new(); let lock = &utils::unlocked(); let krate = utils::fake_krate("invalidates-cache", 4); let same = utils::fake_krate("will-be-cached", 2); remote.commit(&krate); let expected_head = remote.commit(&same); let (mut rgi, _td) = remote.local(); assert_eq!( rgi.local().head_commit().unwrap(), expected_head.to_hex().to_string() ); // These should not be in the cache assert_eq!( rgi.krate("invalidates-cache".try_into().unwrap(), true, lock) .expect("failed to read git blob") .expect("expected krate"), krate, ); assert_eq!( rgi.krate("will-be-cached".try_into().unwrap(), true, lock) .expect("failed to read git blob") .expect("expected krate"), same, ); // Update the remote let new_krate = utils::fake_krate("invalidates-cache", 5); let new_head = remote.commit(&new_krate); assert_eq!( rgi.cached_krate("invalidates-cache".try_into().unwrap(), lock) .expect("failed to read cache file") .expect("expected krate"), krate, ); assert_eq!( rgi.cached_krate("will-be-cached".try_into().unwrap(), lock) .expect("failed to read cache file") .expect("expected krate"), same, ); // Perform fetch, which should invalidate the cache rgi.fetch(lock).unwrap(); assert_eq!( rgi.local().head_commit().unwrap(), new_head.to_hex().to_string() ); assert!( rgi.cached_krate("invalidates-cache".try_into().unwrap(), lock) .unwrap() .is_none() ); assert_eq!( rgi.krate("invalidates-cache".try_into().unwrap(), true, lock) .expect("failed to read git blob") .expect("expected krate"), new_krate, ); // This crate _should_ still be cached as it was not changed in the fetch assert_eq!( rgi.cached_krate("will-be-cached".try_into().unwrap(), lock) .expect("failed to read cache file") .expect("expected krate"), same, ); // We haven't made new commits, so the fetch should not move HEAD and thus // cache entries should still be valid rgi.fetch(lock).unwrap(); assert_eq!( rgi.cached_krate("invalidates-cache".try_into().unwrap(), lock) .unwrap() .unwrap(), new_krate ); let krate3 = utils::fake_krate("krate-3", 3); remote.commit(&krate3); let krate4 = utils::fake_krate("krate-4", 4); let expected_head = remote.commit(&krate4); rgi.fetch(lock).unwrap(); assert_eq!( rgi.local().head_commit().unwrap(), expected_head.to_hex().to_string() ); assert_eq!( rgi.cached_krate("invalidates-cache".try_into().unwrap(), lock) .expect("failed to read cache file") .expect("expected krate"), new_krate, ); assert_eq!( rgi.cached_krate("will-be-cached".try_into().unwrap(), lock) .expect("failed to read cache file") .expect("expected krate"), same, ); } /// gix uses a default branch name of `main`, but most cargo git indexes on users /// disks use the master branch, so just ensure that we support that as well #[test] fn non_main_local_branch() { let mut remote = FakeRemote::new(); let local_td = utils::tempdir(); // Set up the local repo as if it was an already existing index // created by cargo { // Do that actual init let mut cmd = std::process::Command::new("git"); cmd.args(["init", "--bare", "-b", "master"]); cmd.arg(local_td.path()); assert!( cmd.status().expect("failed to run git").success(), "git failed to init directory" ); // Add a fake commit so that we have a local HEAD let mut repo = gix::open(local_td.path()).unwrap(); let commit = { let snap = FakeRemote::snapshot(&mut repo); let empty_tree_id = snap .write_object(gix::objs::Tree::empty()) .unwrap() .detach(); snap.commit( "refs/heads/master", "initial commit", empty_tree_id, gix::commit::NO_PARENT_IDS, ) .unwrap() .detach() }; use gix::refs::transaction as tx; repo.edit_references([ tx::RefEdit { change: tx::Change::Update { log: tx::LogChange { mode: tx::RefLog::AndReference, force_create_reflog: false, message: "".into(), }, expected: tx::PreviousValue::Any, new: gix::refs::Target::Object(commit), }, name: "refs/heads/master".try_into().unwrap(), deref: false, }, tx::RefEdit { change: tx::Change::Update { log: tx::LogChange { mode: tx::RefLog::AndReference, force_create_reflog: false, message: "".into(), }, expected: tx::PreviousValue::Any, new: gix::refs::Target::Symbolic("refs/heads/master".try_into().unwrap()), }, name: "HEAD".try_into().unwrap(), deref: false, }, ]) .unwrap(); assert_eq!(commit, repo.head_commit().unwrap().id); } let mut rgi = remote_index(&local_td, &remote.td); let lock = &utils::unlocked(); let first = utils::fake_krate("first", 1); remote.commit(&first); rgi.fetch(lock).unwrap(); assert_eq!( rgi.krate("first".try_into().unwrap(), true, lock) .unwrap() .unwrap(), first ); } tame-index-0.23.1/tests/local.rs000064400000000000000000000145701046102023000145530ustar 00000000000000#![allow(missing_docs)] #![cfg(all(feature = "local-builder", feature = "sparse"))] mod utils; use rayon::prelude::{IndexedParallelIterator, IntoParallelIterator, ParallelIterator}; use tame_index::index::local; #[test] fn builds_local_registry() { let sparse = tame_index::index::RemoteSparseIndex::new( tame_index::SparseIndex::new(tame_index::IndexLocation::new( tame_index::IndexUrl::CratesIoSparse, )) .unwrap(), reqwest::blocking::Client::new(), ); let mut mdc = cargo_metadata::MetadataCommand::new(); mdc.features(cargo_metadata::CargoOpt::AllFeatures); mdc.manifest_path("Cargo.toml"); let md = mdc.exec().expect("failed to gather metadata"); let mut krates = std::collections::BTreeMap::new(); struct IndexPkg { ik: tame_index::IndexKrate, versions: Vec, } let lock = tame_index::utils::flock::FileLock::unlocked(); for pkg in &md.packages { if pkg.name.as_ref() == "tame-index" { continue; } let ip = krates.entry(pkg.name.clone()).or_insert_with(|| { let ik = sparse .cached_krate(pkg.name.as_str().try_into().unwrap(), &lock) .map_err(|e| { panic!("failed to read cache entry for {}: {e}", pkg.name); }) .unwrap() .ok_or_else(|| { panic!("no cache entry for {}", pkg.name); }) .unwrap(); IndexPkg { ik, versions: Vec::new(), } }); ip.versions.push(pkg.version.to_string().into()); } let client = local::builder::Client::build(reqwest::blocking::ClientBuilder::new()).unwrap(); let lrb_td = utils::tempdir(); let lrb = local::LocalRegistryBuilder::create(lrb_td.path().to_owned()).unwrap(); let config = sparse.index.index_config().unwrap(); krates.into_par_iter().for_each(|(_krate, ipkg)| { let mut crate_files = Vec::with_capacity(ipkg.versions.len()); ipkg.versions .into_par_iter() .map(|vers| { let iv = ipkg .ik .versions .iter() .find(|iv| iv.version == vers) .unwrap(); local::ValidKrate::download(&client, &config, iv).unwrap() }) .collect_into_vec(&mut crate_files); lrb.insert(&ipkg.ik, &crate_files).unwrap(); }); let _lr = lrb.finalize(true).unwrap(); // Create a fake project and override the crates.io registry to point to // the local one we just created, it should get the same metadata let fake_project = utils::tempdir(); std::fs::copy("Cargo.toml", fake_project.path().join("Cargo.toml")).unwrap(); std::fs::copy("Cargo.lock", fake_project.path().join("Cargo.lock")).unwrap(); let mut config = fake_project.path().join(".cargo"); std::fs::create_dir(&config).unwrap(); config.push("config.toml"); // Windows is terrible let local_path = lrb_td.path().as_str(); let local_path = local_path.replace('\\', "/"); std::fs::write( &config, format!( r#" [source.crates-io] replace-with = "test-registry" [source.test-registry] local-registry = "{local_path}""#, ), ) .unwrap(); // We also need to create a fake lib.rs otherwise cargo will be sad { let librs = fake_project.path().join("src/lib.rs"); std::fs::create_dir_all(librs.parent().unwrap()).unwrap(); std::fs::write(librs, "").unwrap(); } let mut mdc = cargo_metadata::MetadataCommand::new(); mdc.features(cargo_metadata::CargoOpt::AllFeatures); mdc.manifest_path("Cargo.toml"); // We need to override this otherwise our config won't be picked up mdc.current_dir(fake_project.path()); // This will fail if we've missed one or more versions in the local registry mdc.exec().expect("failed to gather metadata"); } /// Validates we get the correct checksum for a crate #[test] fn downloads_and_verifies() { let client = reqwest::blocking::Client::builder() .no_gzip() .build() .unwrap(); let res = client .get("https://static.crates.io/crates/wasm-bindgen/wasm-bindgen-0.2.87.crate") .send() .unwrap() .error_for_status() .unwrap(); let body = res.bytes().unwrap(); use bytes::Buf; assert!( local::validate_checksum::<{ 16 * 1024 }>( body.reader(), &("7706a72ab36d8cb1f80ffbf0e071533974a60d0a308d01a5d0375bf60499a342" .parse() .unwrap()), ) .unwrap() ); } #[test] fn finds_source_replacement() { let td = tempfile::tempdir().unwrap(); let cfg_toml = td.path().join(".cargo/config.toml"); std::fs::create_dir_all(cfg_toml.parent().unwrap()).unwrap(); std::fs::write( &cfg_toml, r#" [registries.imaginary-registry] credential-provider = ["cargo:token"] index = "sparse+https://imaginary-registry.example.net/index/" [source._real_cargo_ignores_this_] registry = "sparse+https://imaginary-registry.example.net/index/" replace-with = "actual-registry-on-disk" [source.actual-registry-on-disk] local-registry = "test-local-registry" "#, ) .unwrap(); let meta_path = td .path() .join("test-local-registry/index/ex/am/example-dependency"); std::fs::create_dir_all(meta_path.parent().unwrap()).unwrap(); std::fs::write(&meta_path, r#"{"name":"example-dependency","vers":"2.0.0","deps":[],"cksum":"a89f6827d6042043aa77282b091280d2ebb47365e3ff097d07f45b5a797dff63","features":{},"yanked":false}"#).unwrap(); let subdir = td.path().join("test-subdirectory"); std::fs::create_dir_all(&subdir).unwrap(); let url = tame_index::IndexUrl::for_registry_name( Some(subdir.try_into().unwrap()), None, "imaginary-registry", ) .unwrap(); assert!(matches!(url, tame_index::IndexUrl::Local(_))); let index = tame_index::index::ComboIndexCache::new(tame_index::IndexLocation::new(url)).unwrap(); let lock = tame_index::utils::flock::FileLock::unlocked(); let krate = index .cached_krate("example-dependency".try_into().unwrap(), &lock) .unwrap() .unwrap(); assert_eq!("2.0.0", krate.versions[0].version); } tame-index-0.23.1/tests/sparse.rs000064400000000000000000000272531046102023000147600ustar 00000000000000#![allow(missing_docs)] mod utils; use http::header; use tame_index::{IndexLocation, IndexUrl, SparseIndex}; #[inline] fn crates_io(path: impl AsRef) -> SparseIndex { SparseIndex::new( IndexLocation::new(IndexUrl::CratesIoSparse).with_root(Some(path.as_ref().to_owned())), ) .unwrap() } /// Validates the we get a valid root and krate url #[test] fn opens_crates_io() { let index = crates_io(env!("CARGO_MANIFEST_DIR")); assert_eq!(index.url(), "https://index.crates.io/"); assert_eq!( index.crate_url("autocfg".try_into().unwrap()), "https://index.crates.io/au/to/autocfg" ); } /// Validates a request can be made for a crate that doesn't have a local cache entry #[test] fn make_request_without_cache() { let index = crates_io(env!("CARGO_MANIFEST_DIR")); let lock = &utils::unlocked(); let req = index .make_remote_request("serde".try_into().unwrap(), None, lock) .unwrap(); let hdrs = req.headers(); // Ensure neither of the possible cache headers are set assert!( hdrs.get(header::IF_MODIFIED_SINCE).is_none() && hdrs.get(header::IF_NONE_MATCH).is_none() ); assert_eq!(hdrs.get("cargo-protocol").unwrap(), "version=1"); assert_eq!(hdrs.get(header::ACCEPT).unwrap(), "text/plain"); assert_eq!(hdrs.get(header::ACCEPT_ENCODING).unwrap(), "gzip"); } const ETAG: &str = "W/\"fa62f662c9aae1f21cab393950d4ae23\""; const DATE: &str = "Thu, 22 Oct 2023 09:40:03 GMT"; /// Validates appropriate headers are set when a cache entry does exist #[test] fn make_request_with_cache() { let td = utils::tempdir(); let index = crates_io(&td); let lock = &utils::unlocked(); { let etag_krate = utils::fake_krate("etag-krate", 2); index .cache() .write_to_cache(&etag_krate, &format!("{}: {ETAG}", header::ETAG), lock) .unwrap(); let req = index .make_remote_request("etag-krate".try_into().unwrap(), None, lock) .unwrap(); assert_eq!(req.headers().get(header::IF_NONE_MATCH).unwrap(), ETAG); } { let req = index .make_remote_request("etag-specified-krate".try_into().unwrap(), Some(ETAG), lock) .unwrap(); assert_eq!(req.headers().get(header::IF_NONE_MATCH).unwrap(), ETAG); } { let modified_krate = utils::fake_krate("modified-krate", 2); index .cache() .write_to_cache( &modified_krate, &format!("{}: {DATE}", header::LAST_MODIFIED), lock, ) .unwrap(); let req = index .make_remote_request("modified-krate".try_into().unwrap(), None, lock) .unwrap(); assert_eq!(req.headers().get(header::IF_MODIFIED_SINCE).unwrap(), DATE); } } /// Validates we can parse a response where the local cache version is up to date #[test] fn parse_unmodified_response() { let td = utils::tempdir(); let index = crates_io(&td); let lock = &utils::unlocked(); let etag_krate = utils::fake_krate("etag-krate", 2); index .cache() .write_to_cache(&etag_krate, &format!("{}: {ETAG}", header::ETAG), lock) .unwrap(); let response = http::Response::builder() .status(http::StatusCode::NOT_MODIFIED) .header(header::ETAG, ETAG) .body(Vec::new()) .unwrap(); let cached_krate = index .parse_remote_response("etag-krate".try_into().unwrap(), response, true, lock) .unwrap() .expect("cached krate"); assert_eq!(etag_krate, cached_krate); } /// Validates we can parse a modified response #[test] fn parse_modified_response() { let td = utils::tempdir(); let index = crates_io(&td); let lock = &utils::unlocked(); { let etag_krate = utils::fake_krate("etag-krate", 3); let mut serialized = Vec::new(); etag_krate.write_json_lines(&mut serialized).unwrap(); let response = http::Response::builder() .status(http::StatusCode::OK) .header(header::ETAG, ETAG) .body(serialized) .unwrap(); let new_krate = index .parse_remote_response("etag-krate".try_into().unwrap(), response, true, lock) .unwrap() .expect("new response"); assert_eq!(etag_krate, new_krate); let cached_krate = index .cache() .cached_krate( "etag-krate".try_into().unwrap(), Some(&format!("{}: {ETAG}", header::ETAG)), lock, ) .unwrap() .expect("cached krate"); assert_eq!(etag_krate, cached_krate); } { let modified_krate = utils::fake_krate("modified-krate", 3); let mut serialized = Vec::new(); modified_krate.write_json_lines(&mut serialized).unwrap(); let response = http::Response::builder() .status(http::StatusCode::OK) .header(header::LAST_MODIFIED, DATE) .body(serialized) .unwrap(); let new_krate = index .parse_remote_response("modified-krate".try_into().unwrap(), response, true, lock) .unwrap() .expect("new response"); assert_eq!(modified_krate, new_krate); let cached_krate = index .cache() .cached_krate( "modified-krate".try_into().unwrap(), Some(&format!("{}: {DATE}", header::LAST_MODIFIED)), lock, ) .unwrap() .expect("cached krate"); assert_eq!(modified_krate, cached_krate); } } #[cfg(feature = "sparse")] mod remote { use super::*; /// Ensure we can actually send a request to crates.io and parse the response #[test] fn end_to_end() { let td = utils::tempdir(); let index = crates_io(&td); let lock = &utils::unlocked(); let client = reqwest::blocking::Client::builder().build().unwrap(); let rsi = tame_index::index::RemoteSparseIndex::new(index, client); let spdx_krate = rsi .krate("spdx".try_into().unwrap(), true, lock) .expect("failed to retrieve spdx") .expect("failed to find spdx"); spdx_krate .versions .iter() .find(|iv| iv.version == "0.10.1") .expect("failed to find expected version"); } // cargo metadata --format-version=1 | jq -r '.packages[].name | "\"\(.)\","' const KRATES: &[&str] = &[ "addr2line", "adler", "async-compression", "autocfg", "backtrace", "base64", "bitflags", "bitflags", "bumpalo", "bytes", "camino", "cargo-platform", "cargo_metadata", "cc", "cfg-if", "core-foundation", "core-foundation-sys", "crc32fast", "crossbeam-deque", "crossbeam-epoch", "crossbeam-utils", "either", "encoding_rs", "equivalent", "errno", "fastrand", "flate2", "fnv", "form_urlencoded", "futures-channel", "futures-core", "futures-io", "futures-sink", "futures-task", "futures-util", "getrandom", "gimli", "h2", "hashbrown", "hermit-abi", "home", "http", "http-body", "httparse", "httpdate", "hyper", "hyper-rustls", "idna", "indexmap", "ipnet", "itoa", "js-sys", "libc", "linux-raw-sys", "log", "memchr", "mime", "miniz_oxide", "mio", "num_cpus", "object", "once_cell", "percent-encoding", "pin-project-lite", "pin-utils", "proc-macro2", "quote", "rayon", "rayon-core", "reqwest", "ring", "rustc-demangle", "rustix", "rustls", "rustls-pemfile", "rustls-webpki", "ryu", "sct", "semver", "serde", "serde_derive", "serde_json", "serde_spanned", "serde_urlencoded", "slab", "smol_str", "socket2", "spin", "static_assertions", "syn", "sync_wrapper", "system-configuration", "system-configuration-sys", "tame-index", "tempfile", "thiserror", "thiserror-impl", "tiny-bench", "tinyvec", "tinyvec_macros", "tokio", "tokio-rustls", "tokio-util", "toml", "toml_datetime", "toml_edit", "tower-service", "tracing", "tracing-core", "try-lock", "twox-hash", "unicode-bidi", "unicode-ident", "unicode-normalization", "untrusted", "url", "want", "wasi", "wasm-bindgen", "wasm-bindgen-backend", "wasm-bindgen-futures", "wasm-bindgen-macro", "wasm-bindgen-macro-support", "wasm-bindgen-shared", "web-sys", "webpki-roots", "windows-sys", "windows-sys", "windows-targets", "windows-targets", "windows_aarch64_gnullvm", "windows_aarch64_gnullvm", "windows_aarch64_msvc", "windows_aarch64_msvc", "windows_i686_gnu", "windows_i686_gnu", "windows_i686_msvc", "windows_i686_msvc", "windows_x86_64_gnu", "windows_x86_64_gnu", "windows_x86_64_gnullvm", "windows_x86_64_gnullvm", "windows_x86_64_msvc", "windows_x86_64_msvc", "winnow", "winreg", ]; fn ensure_no_errors( results: std::collections::BTreeMap< String, Result, tame_index::Error>, >, ) { use std::fmt::Write; let mut errors = String::new(); for (name, res) in results { match res { Ok(Some(_)) => {} Ok(None) => writeln!(&mut errors, "{name}:\tfailed to locate").unwrap(), Err(err) => writeln!(&mut errors, "{name}:\t{err}").unwrap(), } } assert!(errors.is_empty(), "{errors}"); } /// Reuses connections. This test is intended to be run under strace to /// validate that connections are not being created /// #[test] fn reuses_connection() { let td = utils::tempdir(); let index = crates_io(&td); let lock = &utils::unlocked(); let client = reqwest::blocking::Client::builder().build().unwrap(); let rsi = tame_index::index::RemoteSparseIndex::new(index, client); let results = rsi.krates( KRATES.iter().map(|s| (*s).to_string()).collect(), false, lock, ); ensure_no_errors(results); } // Ditto, but for async #[test] fn async_reuses_connection() { let rt = tokio::runtime::Runtime::new().unwrap(); let _guard = rt.enter(); let td = utils::tempdir(); let index = crates_io(&td); let lock = &utils::unlocked(); let client = reqwest::Client::builder().build().unwrap(); let rsi = tame_index::index::AsyncRemoteSparseIndex::new(index, client); let results = rsi .krates_blocking( KRATES.iter().map(|s| (*s).to_string()).collect(), false, None, lock, ) .unwrap(); ensure_no_errors(results); } } tame-index-0.23.1/tests/utils.rs000064400000000000000000000032301046102023000146100ustar 00000000000000#![allow(dead_code)] #![allow(missing_docs)] pub use tame_index::{IndexKrate, Path, PathBuf}; pub struct TempDir { pub td: tempfile::TempDir, } impl TempDir { #[inline] pub fn path(&self) -> &Path { Path::from_path(self.td.path()).unwrap() } } impl Default for TempDir { #[inline] fn default() -> Self { Self { td: tempfile::TempDir::new_in(env!("CARGO_TARGET_TMPDIR")).unwrap(), } } } impl AsRef for TempDir { #[inline] fn as_ref(&self) -> &Path { self.path() } } impl AsRef for TempDir { #[inline] fn as_ref(&self) -> &std::path::Path { self.td.path() } } impl<'td> From<&'td TempDir> for PathBuf { fn from(td: &'td TempDir) -> Self { td.path().to_owned() } } #[inline] pub fn tempdir() -> TempDir { TempDir::default() } #[inline] pub fn unlocked() -> tame_index::index::FileLock { tame_index::index::FileLock::unlocked() } pub fn fake_krate(name: &str, num_versions: u8) -> IndexKrate { assert!(num_versions > 0); let mut version = semver::Version::new(0, 0, 0); let mut versions = Vec::new(); for v in 0..num_versions { match v % 3 { 0 => version.patch += 1, 1 => { version.patch = 0; version.minor += 1; } 2 => { version.patch = 0; version.minor = 0; version.major += 1; } _ => unreachable!(), } let iv = tame_index::IndexVersion::fake(name, version.to_string()); versions.push(iv); } IndexKrate { versions } }