diff options
Diffstat (limited to 'vendor/gix-config')
48 files changed, 10048 insertions, 0 deletions
diff --git a/vendor/gix-config/.cargo-checksum.json b/vendor/gix-config/.cargo-checksum.json new file mode 100644 index 000000000..973e82241 --- /dev/null +++ b/vendor/gix-config/.cargo-checksum.json @@ -0,0 +1 @@ +{"files":{"CHANGELOG.md":"1ed0411cb6220e3bccbb86812ead61eca51db3194e1f0cf0b09380005e8ab12e","Cargo.toml":"980090f2bdac1cb3f34ce8e0a77c36d13ef3db5e2a40c287fedd36eb51007023","LICENSE-APACHE":"cb4780590812826851ba250f90bed0ed19506ec98f6865a0e2e20bbf62391ff9","LICENSE-MIT":"49df47913ab2beafe8dc45607877ae64198bf0eee64aaad3e82ed9e4d27424e8","README.md":"83f6ef126630abc0e0a5eec8b51c559dbd56211e3a66e3ec021bcea04434f452","src/file/access/comfort.rs":"00a236fcfd4b2b37bf2d4c0763573c13a430564f01af417828d412f4827c0f45","src/file/access/mod.rs":"89141297ba7e1651c60a3637760598f8509fe651a80dc5801aace66bd2c52292","src/file/access/mutate.rs":"7396aa250717bc0a15b9da3701746c3cab0a3c6cb09342922062b26d61b0a1c1","src/file/access/raw.rs":"93267ddfe0ee7c6f41841d0346fdddb4b9eec9522701dc50c09908aac0a170d5","src/file/access/read_only.rs":"218ba05c1f014b4a2a86fcfc6cf4e8e8a34967deded0fa934d07e0119ab496c8","src/file/impls.rs":"c066fdeaeca00a8899704216be060d2b461aa21bd9800883d29e3cc0a59f5fd8","src/file/includes/mod.rs":"f82ed5445391c8442da672b7f8c92f85844ba58d35aa0dfcaf2dfa8c209307d2","src/file/includes/types.rs":"0c1c7c52cee624d840e356f0db0c7791392f89cc6ab0fc7cf074bd05d952c85a","src/file/init/comfort.rs":"0b5742d28fbb789c9bc99222fb4aa7f59c8c612e4c28ba389e19494247b60e8b","src/file/init/from_env.rs":"db5ec43667e0cd3f35d53e9d2137dbdbcd661fe8e0452c28e91ac8172c1d3af7","src/file/init/from_paths.rs":"3bac62208d6c975e489849af4f7fb5e5ece44cd0ca83647eb254f8f6da32bdc7","src/file/init/mod.rs":"3432a5eac10605c86c6fcff6dab169a8e3ddba8fe8f90603559a9e94f1ac3474","src/file/init/types.rs":"cd8d34cc012837b1acf8b55586c11d22768ce04a03874aadf044ca7cf3ed6da6","src/file/meta.rs":"02c46267b6088e7d1be3f7402929b3b98cdab71c682e1efae2c0eebd88ac97ad","src/file/mod.rs":"9e3a0a327fab36de1e83fb3d2057b43afa3e83712e00e5ab8c559e72d1d9f375","src/file/mutable/mod.rs":"ac64e210920df986e15ff4136bc7966ba15c2a2dc246fc34320ad2f6fec19ab0","src/file/mutable/multi_value.rs":"b3824387af097c410c8a9627239036e15525af5d4113ebcfbb4c48e6f8591337","src/file/mutable/section.rs":"7ac4774a3115e3446a4eb41170f835e796f561e61c7e16ae9231d0f13441ac0c","src/file/mutable/value.rs":"4211ff648477e37c4f8bcf7fa497233b09a24a8b411e57fc4be441d793377f4d","src/file/section/body.rs":"28a07cd0ec5eb580fdaf9d0199fb4d5e0e7f0ccaa80a4203036873a056adba3a","src/file/section/mod.rs":"dd79241648ee1dceb10d567dd564853062336bf428856cf9a52fafa1ffce4611","src/file/tests.rs":"cceccb786694f29e858e54a6fdc6769a64ae58989809bdd60e09041156050f1e","src/file/util.rs":"2b9c52b242c45cf7075cddf5383523c095c01ce612b9da139a853039beff6bc8","src/file/write.rs":"63e16eb5324b3482654dce16761a0b894b8bfe370aacefa21b7cd52c8bc3cab5","src/lib.rs":"b0d5ec429067741a1e3b0e5820e24d1af614282f14bd818fe3919acb3239ba44","src/lookup.rs":"205df00cabf25f0fd3709325e75cdc2c1cd2af4ef86bbd15253543a9bb962e62","src/parse/comment.rs":"dfe1ad56385490062792c59b3068552e847b9c55b7a91ee480b7ea6026f12889","src/parse/error.rs":"7e3ce30c7036cb6900be5e751657b879fc8a7dcab760f10ea481d4a57a90b347","src/parse/event.rs":"02110fb38c7f1ea3eff1cecccc6c39f4a60958f18ab6d73220b104c2d6bd7213","src/parse/events.rs":"2b1fb245c00a66810a7ac52e477acb734c82b20811e4a61940110cd56103237c","src/parse/key.rs":"6922c4d0293546bc6b9a97364a6dc97b9cdb0c8ba8ffdc44c059be63ce4c16c7","src/parse/mod.rs":"c7f3d4fa2c3888e1390de84864f23b3368f08c1480817c9b4cb76b6d07025bb0","src/parse/nom/mod.rs":"b7525ffd344f1e149978822d1cca369186cabc3986be43a54e4336043d0bf8b6","src/parse/nom/tests.rs":"6841d40eb8580a166bd5f3fdcacdcde5702527126e886239847fd29d87df2cf7","src/parse/section/header.rs":"0d7a1a4d892ac5c0abd86c4951f4806f159ea7c37dbe89bfc86b67dfe76014ec","src/parse/section/mod.rs":"5be22e4d120a12a59be38fcb25a95ce7b0e581ded53e99311ffb0f3780ac841a","src/parse/section/unvalidated.rs":"ffbc552e5c6f78fca05ad05f703fef9889777c22facb526ea5fca9c0121ae8c4","src/parse/tests.rs":"911f6972060feb521448f4e6ec9824ff524a9bc51d80fe1460548a6b88036dc4","src/source.rs":"19871a85b0cd0ccee37e33cbbc6c1ba7c92cb4137a6828f877faacd42cb5609a","src/types.rs":"9f4b61b86e1e438cd31518c478b16c9f97f626f1bcea8b93fee7797c82d6be0d","src/value/mod.rs":"1e46a250fc69cecb11d762290fa0c5a2b8f51451541e9e457d0bacafd160e54d","src/value/normalize.rs":"54ca650fdaaf24124df0c6d9137eb90dfda314b28386778970aa25e6b01397f5"},"package":"52c62e26ce11f607712e4f49a0a192ed87675d30187fd61be070abbd607d12f1"}
\ No newline at end of file diff --git a/vendor/gix-config/CHANGELOG.md b/vendor/gix-config/CHANGELOG.md new file mode 100644 index 000000000..874fb2a14 --- /dev/null +++ b/vendor/gix-config/CHANGELOG.md @@ -0,0 +1,2167 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## 0.18.0 (2023-03-04) + +A maintenance release without user-facing changes. + +### Commit Statistics + +<csr-read-only-do-not-edit/> + + - 2 commits contributed to the release. + - 3 days passed between releases. + - 0 commits were understood as [conventional](https://www.conventionalcommits.org). + - 0 issues like '(#ID)' were seen in commit messages + +### Commit Details + +<csr-read-only-do-not-edit/> + +<details><summary>view details</summary> + + * **Uncategorized** + - Prepare changelogs prior to release ([`895e482`](https://github.com/Byron/gitoxide/commit/895e482badf01e953bb9144001eebd5e1b1c4d84)) + - Release gix-features v0.28.0, gix-actor v0.19.0, gix-object v0.28.0, gix-diff v0.28.0, gix-traverse v0.24.0, gix-pack v0.32.0, safety bump 20 crates ([`0f411e9`](https://github.com/Byron/gitoxide/commit/0f411e93ec812592bb9d3a52b751399dd86f76f7)) +</details> + +## 0.17.0 (2023-03-01) + +A maintenance release without user-facing changes. + +### Commit Statistics + +<csr-read-only-do-not-edit/> + + - 6 commits contributed to the release over the course of 3 calendar days. + - 8 days passed between releases. + - 0 commits were understood as [conventional](https://www.conventionalcommits.org). + - 0 issues like '(#ID)' were seen in commit messages + +### Commit Details + +<csr-read-only-do-not-edit/> + +<details><summary>view details</summary> + + * **Uncategorized** + - Release gix-tempfile v4.1.0, gix-lock v4.0.0, gix-ref v0.25.0, gix-config v0.17.0, gix-url v0.14.0, gix-credentials v0.10.0, gix-diff v0.27.0, gix-discover v0.14.0, gix-hashtable v0.1.2, gix-bitmap v0.2.2, gix-traverse v0.23.0, gix-index v0.13.0, gix-mailmap v0.10.0, gix-pack v0.31.0, gix-odb v0.41.0, gix-transport v0.26.0, gix-protocol v0.27.0, gix-revision v0.11.0, gix-refspec v0.8.0, gix-worktree v0.13.0, gix v0.38.0, safety bump 6 crates ([`ea9fd1d`](https://github.com/Byron/gitoxide/commit/ea9fd1d9b60e1e9e17042e9e37c06525823c40a5)) + - Release gix-features v0.27.0, gix-actor v0.18.0, gix-quote v0.4.3, gix-attributes v0.9.0, gix-object v0.27.0, gix-ref v0.25.0, gix-config v0.17.0, gix-url v0.14.0, gix-credentials v0.10.0, gix-diff v0.27.0, gix-discover v0.14.0, gix-hashtable v0.1.2, gix-bitmap v0.2.2, gix-traverse v0.23.0, gix-index v0.13.0, gix-mailmap v0.10.0, gix-pack v0.31.0, gix-odb v0.41.0, gix-transport v0.26.0, gix-protocol v0.27.0, gix-revision v0.11.0, gix-refspec v0.8.0, gix-worktree v0.13.0, gix v0.38.0 ([`e6cc618`](https://github.com/Byron/gitoxide/commit/e6cc6184a7a49dbc2503c1c1bdd3688ca5cec5fe)) + - Adjust manifests prior to release ([`addd789`](https://github.com/Byron/gitoxide/commit/addd78958fdd1e54eb702854e96079539d01965a)) + - Prepare changelogs prior to release ([`94c99c7`](https://github.com/Byron/gitoxide/commit/94c99c71520f33269cc8dbc26f82a74747cc7e16)) + - Prepare for git-tempfile release ([`56c005b`](https://github.com/Byron/gitoxide/commit/56c005b13c44376f71e61781e73c0bf93416d0e4)) + - Make fmt ([`8ef1cb2`](https://github.com/Byron/gitoxide/commit/8ef1cb293434c7b9e1fda4a6963368e0435920a9)) +</details> + +## 0.16.3 (2023-02-21) + +A maintenance release to restore MSRV (1.64) support. + +### Commit Statistics + +<csr-read-only-do-not-edit/> + + - 3 commits contributed to the release. + - 0 commits were understood as [conventional](https://www.conventionalcommits.org). + - 0 issues like '(#ID)' were seen in commit messages + +### Commit Details + +<csr-read-only-do-not-edit/> + +<details><summary>view details</summary> + + * **Uncategorized** + - Release gix-config v0.16.3, gix v0.37.1 ([`a3c283f`](https://github.com/Byron/gitoxide/commit/a3c283ff0e3f21cedb3ba7cd464fdfa0f5133af0)) + - Prepare changelogs prior to release ([`362d659`](https://github.com/Byron/gitoxide/commit/362d659f946ca1ff2cbf915766113a34a9df97b3)) + - Restore msrv compatibility by removing sole `if let ... else` ([`9160659`](https://github.com/Byron/gitoxide/commit/91606597b714a6e9b3a2c071bdb08baeacd6056b)) +</details> + +## 0.16.2 (2023-02-20) + +### New Features + + - <csr-id-4b424ab73d20833deb0c674eeeeaaa8cc2358932/> add `File::write_to_filter()` to have a filter select the section to write. + This way, one can selectively write sections to a file, based on the section's origin + or other meta-data. + + For convenience sake, most mutating methods on `SectionMut` can now be chained as well. + - <csr-id-3fa86e1d01a75688cd8948a39ba415bcebfef02d/> add `File::set_meta()` to affect what metadata new sections receive. + That way it's possible to better control the metadata associated with new sections, + given that the config file metadata may not truly reflect its identity anymore since it + can be a combination from many different files with various sources. + +### Bug Fixes + + - <csr-id-e14dc7d475373d2c266e84ff8f1826c68a34ab92/> note that crates have been renamed from `git-*` to `gix-*`. + This also means that the `git-*` prefixed crates of the `gitoxide` project + are effectively unmaintained. + Use the crates with the `gix-*` prefix instead. + + If you were using `git-repository`, then `gix` is its substitute. + - <csr-id-8836f5ada0078febf8aeb0e6b59d0fc4c588bc28/> `File::section(...)` could panic due to invlid expectation. + This has been fixed to prevent panics. + - <csr-id-135d317065aae87af302beb6c26bb6ca8e30b6aa/> compatibility with `bstr` v1.3, use `*.as_bytes()` instead of `.as_ref()`. + `as_ref()` relies on a known target type which isn't always present. However, once + there is only one implementation, that's no problem, but when that changes compilation + fails due to ambiguity. + +### Commit Statistics + +<csr-read-only-do-not-edit/> + + - 3 commits contributed to the release. + - 3 days passed between releases. + - 1 commit was understood as [conventional](https://www.conventionalcommits.org). + - 0 issues like '(#ID)' were seen in commit messages + +### Commit Details + +<csr-read-only-do-not-edit/> + +<details><summary>view details</summary> + + * **Uncategorized** + - Release gix-date v0.4.3, gix-hash v0.10.3, gix-features v0.26.5, gix-actor v0.17.2, gix-glob v0.5.5, gix-path v0.7.2, gix-quote v0.4.2, gix-attributes v0.8.3, gix-validate v0.7.3, gix-object v0.26.2, gix-ref v0.24.1, gix-config v0.16.2, gix-command v0.2.4, gix-url v0.13.3, gix-credentials v0.9.2, gix-discover v0.13.1, gix-index v0.12.4, gix-mailmap v0.9.3, gix-pack v0.30.3, gix-packetline v0.14.3, gix-transport v0.25.6, gix-protocol v0.26.4, gix-revision v0.10.4, gix-refspec v0.7.3, gix-worktree v0.12.3, gix v0.36.1 ([`9604783`](https://github.com/Byron/gitoxide/commit/96047839a20a657a559376b0b14c65aeab96acbd)) + - Compatibility with `bstr` v1.3, use `*.as_bytes()` instead of `.as_ref()`. ([`135d317`](https://github.com/Byron/gitoxide/commit/135d317065aae87af302beb6c26bb6ca8e30b6aa)) + - Release gix-glob v0.5.4 ([`c56d336`](https://github.com/Byron/gitoxide/commit/c56d3365fde21120cf6101cf34f8b5669804977c)) +</details> + +## 0.16.1 (2023-02-17) + +<csr-id-69ec5940d3f37eb4dace8f1ed7616b5988984d15/> +<csr-id-9cd99337333f5ef4b30e0ec9461fc087699576e6/> +<csr-id-0076dcf9b37f1d633bdad5573b40d34a9fbaba90/> +<csr-id-a8604a237782f8d60a185d4730db57bad81424a6/> +<csr-id-55c00d880535a1f8c37cb7d4405d39ff5a7654a0/> +<csr-id-32d5b3c695d868ba93755123a25b276bfbe55e0a/> +<csr-id-949622e461eb2116393ec6f4633ec0cb2e1695b5/> +<csr-id-bcacfc9bcf19a0339541b24e84de68d95291c62b/> +<csr-id-41f118d2aa560188fd3399d2390aa43794b0af75/> +<csr-id-f7f136dbe4f86e7dee1d54835c420ec07c96cd78/> + +A maintenance release without user-facing changes. + +### Other (BREAKING) + + - <csr-id-69ec5940d3f37eb4dace8f1ed7616b5988984d15/> `File::set_raw_[multi_]value()` to `::set_existing_raw_[multi_]value`. + This makes clear that the method will fail if the value doesn't yet + exist. + - <csr-id-9cd99337333f5ef4b30e0ec9461fc087699576e6/> `File::raw_multi_value()` to `File::raw_values()` + - <csr-id-0076dcf9b37f1d633bdad5573b40d34a9fbaba90/> `File::raw_multi_value_mut()` to `File::raw_values_mut()` + - <csr-id-a8604a237782f8d60a185d4730db57bad81424a6/> `File::multi_value()` to `File::values()`. + The latter is better in line with `string()/strings()` + +### Bug Fixes (BREAKING) + + - <csr-id-0c98ec8fc7d8cc3195472a04fde4a681f620725f/> subsections are identified as `&BStr` in entire API. + Technically they can be any value (except for newlines and unescaped double quotes), + and these values might be paths and everything that comes with it, like + illformed UTF8. In order to be able to represent everything that + git can represent, we don't enforce UTF8 anymore for subsection names. + + Note that section names and key names are required to be valid UTF8 + (and even alphanumeric ascii), which makes illformed UTF8 very unlikely + there. + - <csr-id-a93a156655d640ae63ff7c35b0a1f5d67a5ca20f/> Simplify specifying keys when mutating config values. + - <csr-id-895ce40aabbe6d6af5b681a0d0942303fd6549a2/> `File::rename_section()` with validation of input arguments. + - <csr-id-4a01d983f54a7713dea523f6032cbf5bb2b9dde8/> improve normalization; assure no extra copies are made on query. + We now return our own content, rather than the originals with their + lifetimes, meaning we bind lifetimes of returned values to our own + `File` instance. This allows them to be referenced more often, and + smarter normalization assures we don't copy in the simple cases + either. + + More tests were added as well. + This is breaking as lifetime changes can cause distruptions, and + `values?_as()` was removed as well as it's somewhat duplicate + to higher-level APIs and it wasn't tested at all. + - <csr-id-c9933c0b0f51d21dc8244b2acc33d7dc8a33f6ce/> Remove `git-config` test utilities from `git-path`. + +### New Features (BREAKING) + + - <csr-id-2faad43d11283ff06381c51d2466307cfb8736ff/> transfer knowledge about configuration and its usage into the type system. + That way it's possible to use configuration overrides, even though ultimately being strings, + in a type-safe manner and leverage code-completion while at it. + + In that process, we also change `Repository::(committer|Author)()` to return + `Option<Result<...>>` to be able to account for date parse errors. + - <csr-id-2b36d99eaf3ed24ce4cb736a3dd48440dc0c73b7/> `File::new_section()` and related now returns their `id` as well. + That way it's possible to more easily interact with it later, for instance + when one wants to delete it. + - <csr-id-3d8fa8fef9800b1576beab8a5bc39b821157a5ed/> upgrade edition to 2021 in most crates. + MSRV for this is 1.56, and we are now at 1.60 so should be compatible. + This isn't more than a patch release as it should break nobody + who is adhering to the MSRV, but let's be careful and mark it + breaking. + + Note that `git-features` and `git-pack` are still on edition 2018 + as they make use of a workaround to support (safe) mutable access + to non-overlapping entries in a slice which doesn't work anymore + in edition 2021. + - <csr-id-b6cd6ace412b0c0df4bacbe7ed7ef6608f27909c/> `file::SectionMut::push()` now supports values without key-value separator. + These make a difference as those without `=` are considered boolean + true. + Currently pushing onto a section is the only way to write them. + - <csr-id-d003c0f139d61e3bd998a0283a9c7af25a60db02/> Support for `lossy` load mode. + There is a lot of breaking changes as `file::from_paths::Options` now + became `file::init::Options`, and the same goes for the error type. + - <csr-id-1ea26d80f392114349d25ebf88a7b260ee822aa1/> add `_filter()` versions to most access methods. + That way it's possible to filter values by their origin. + + Note that the `remove_section()` methods now return the entire + removed section, not just the body, which yields more information + than before including section metadata. + - <csr-id-cfd974f46d2cbb99e7784a05f5e358fed0d4bcab/> section names are now validated. + - <csr-id-6ba2f8060768978ad7204e162fb2253ca8843879/> filtering supportort for `parse::Events`. + That way it's possible to construct Files which are not destined to be + written back as they only keep events necessary for value access, + greatly reducing allocations. + - <csr-id-311d4b447daf8d4364670382a20901468748d34d/> change mostily internal uses of [u8] to BString/BStr + - <csr-id-edd226719cd04a480274cb7d983b6d5d8bfdbb13/> Path-interpolation makes `home-dir` configurable. + That way the caller has full control over how the environment is used, + which also allows more fine-grained control over which config files + can be included. + - <csr-id-266d4379e9132fd7dd21e6c8fccb36e125069d6e/> Make `realpath()` easier to use by introducing `realpath_opt()`. + That way there is consistency about how many symlinks to follow. + +### Changed (BREAKING) + + - <csr-id-27fb1ce27d2985eb1ee8bee5fffaf759902571fb/> Add `Kind::GitInstallation` for a way to obtain special git-installation configuration paths. + Note that these are lazily cached as they call the `git` binary. + - <csr-id-99905bacace8aed42b16d43f0f04cae996cb971c/> upgrade `bstr` to `1.0.1` + - <csr-id-2b2357e9cc54539e0dbe7c0e22802f2b884160d8/> Add `File::set_raw_value()` to unconditionally set single values, and make the value itself easier to provide. + - <csr-id-17c83d55f8942788aac5eb1bea22a48daa045bf4/> add `File::resolve_includes()` and move its error type to `file::includes`. + - <csr-id-5221676e28f2b6cc1a7ef1bdd5654b880965f38c/> add `File::from_bytes_owned()` and remove `File::from_path_with_buf()` + - <csr-id-98d45c2f59863fdee033b38e757cec09593f6892/> remove `File::from_env_paths()`. + It's replaced by its more comfortable `new_globals()`. + - <csr-id-230a523593afcfb8720db965ff56265aaceea772/> untangle `file::init::…` `Option` and `Error` types. + This moves types to where they belong which is more specific instead + of having a catch-all `Error` and `Options` type. + - <csr-id-3f3ff11a6ebe9775ee5ae7fc0ec18a94b5b46d61/> rename `parse::Comment::(comment_tag|comment)` to `::tag|text` and `parse::Section::section_header` to `::header`. + - <csr-id-6f4eea936d64fb9827277c160f989168e7b1dba2/> Associate `file::Metadata` with each `File`. + This is the first step towards knowing more about the source of each + value to filter them based on some properties. + + This breaks various methods handling the instantiation of configuration + files as `file::Metadata` typically has to be provided by the caller + now or be associated with each path to read configuration from. + - <csr-id-b672ed7667a334be3d45c59f4727f12797b340da/> rename `file::SectionBody` to `file::section::Body`. + - <csr-id-3bea26d7d2a9b5751c6c15e1fa9a924b67e0159e/> Remove `File::sections_by_name_with_header()` as `::sections_by_name()` now returns entire sections. + - <csr-id-41b3e622ee71943c285eadc518150fc7b6c92361/> create `resolve_includes` options to make space for more options when loading paths. + - <csr-id-cabc8ef0e31c954642525e7693009a7fe4b4c465/> rename `path::Options` into `path::Context`. + It's not an option if it's required context to perform a certain + operation. + - <csr-id-3de0cfd81523e4ba7cc362d8625f85ebf8fd9172/> All accessors in `File` are now using `impl AsRef<str>` where possible for added comfort. + - <csr-id-3d25fe6c7a52529488fab19c927d64a1bc75838f/> Much more comfortable API `file::*Mut` types thanks to `impl Into/AsRef`. + - <csr-id-393b392d515661e5c3e60629319fdab771c3d3f0/> Rename `Mutable*` into `$1Mut` for consistency. + - <csr-id-0a7391a6575f4035c51a46d34fa20c69e9d078e9/> conform APIs of `file::MutableValue` and `file::MutableMultiValue`. + There are more renames and removals than worth mentioning here given the + current adoption of the crate. + - <csr-id-83a0922f06081312b79908835dac2b7f4e849bb3/> rename `file::MutableSection::set_leading_space()` to `set_leading_whitespace()`. + The corresponding getter was renamed as well to `leading_whitespace()`. + - <csr-id-219cf7ae0b35b3ac92f97974be52cd022698e01f/> Enforce `parse::section::Header::new()` by making its fields private. + - <csr-id-4f6cd8cf65c2d8698bffe327a19031c342b229a6/> Add `File::write_to()` and `File::to_bstring()`; remove some `TryFrom` impls. + Now `File` can be serialized in a streaming fashion and without the + possibility for UTF8 conversion issues. + + Note that `Display` is still imlpemented with the usual caveats. + - <csr-id-0e392f81e99c8c0ff29f41b9b86afd57cd99c245/> remove `Integer::to_bstring()` as well as some `TryFrom` impls. + Note that it can still display itself like before via + `std::fmt::Display`. + - <csr-id-b22732a2ab17213c4a1020859ec41f25ccabfbfc/> remove `Boolean::to_bstring()` along with a few `From` impls. + These were superfluous and aren't useful in practice. + Note that serialization is still implemented via `Display`. + - <csr-id-65c520c4de8187884f87059adf5cef9cbdcd90a2/> allocation free `File::sections_by_name()` and `File::sections_by_name_with_header()`. + - <csr-id-ac57c4479e7b6867e8b8e71f7cf76de759dc64a2/> `Path::interpolate()` now takes `path::interpolate::Options` instead of three parameters. + - <csr-id-0915051798dd782b40617a1aa16abd71f6db1175/> remove `String` type in favor of referring to the `File::string()` method. + The wrapper had no effect whatsoever except for adding complexity. + - <csr-id-9cadc6f0cbaad0ac23f5469db2f040aecfbfb82c/> Simplify `Boolean` to be a wrapper around `bool`. + Previously it tried hard not to degenerate information, making it a + complicated type. + + However, in practice nobody cares about the exact makeup of the boolean, + and there is no need to serialize a boolean faithfully either. + + Instead, those who want to set a value just set any value as a string, + no need for type safety there, and we take care of escaping values + properly on write. + - <csr-id-703922dd4e1e5b27835298217ff4eb8ef1dc57ce/> Use bitflags for `color::Attribute` instead of `Vec` of enums. + This is less wasteful and sufficient for git, so it should be sufficient + for us, especially since attributes are indeed a set and declaring + one twice has no effect. + - <csr-id-3fc4ac04f46f869c6e3a94ce4bb8a5737aa0c524/> simplify `Color` API. + For now we only parse and serialize for display, but more uses are + enabled when needed and trivially. + - <csr-id-14149eea54e2e8a25ac0ccdb2f6efe624f6eaa22/> remove `parse::Events::from_path` and `File::at` + The latter has been replaced with `File::from_path_with_buf(…)` and + is a low-level way to load just a single config file, purposefully + uncomfortable as it will not resolve includes. + + The initialization API will need some time to stabilize. + - <csr-id-73adceeae12270c0d470d4b7271c1fd6089d5c2d/> Slim down API surface of `parse::Events`. + It's more of a 'dumb' structure now than before, merely present + to facilitate typical parsing than something special on its own. + - <csr-id-2e47167e4a963743494b2df6b0c15800cb876dd0/> remove `File::new()` method in favor of `File::default()`. + - <csr-id-ea6765093b5475912ba1aa81d4440cbf5dd49fb6/> rename `parse::event::List` to `parse::Events` + - <csr-id-89f5fca843d999c5bea35fb3fe2a03dc3588f74e/> rename `parse::State` to `parse::event::List` + - <csr-id-3cdb0890b71e62cfa92b1ed1760c88cb547ec729/> move `value::*` into the crate root, except for `Error` and `normalize_*()`. + - <csr-id-748d921efd7469d5c19e40ddcb9099e2462e3bbc/> rename `value::parse::Error` to `value::Error`. + - <csr-id-7e8a22590297f2f4aab76b53be512353637fb651/> rename `value::TrueVariant` to `value::boolean::True` + - <csr-id-8bcaec0599cf085a73b344f4f53fc023f6e31430/> rename `IntegerSuffix` to `integer::Suffix` + - <csr-id-d085037ad9c067af7ce3ba3ab6e5d5ddb45b4057/> rename `value::Color(Attribute|Value)` to `value::color::Attribute` and `value::color::Name`. + - <csr-id-a0f6252343a62b0b55eef02888ac00c09100687a/> Turn `parse::ParseOrIoError` into `parse::state::from_path::Error` + - <csr-id-b6b31e9c8dd8b3dc4860431069bb1cf5eacd1702/> rename `parse::ParsedComment` into `parse::Comment` + - <csr-id-239cbfb450a8cddfc5bec1de21f3dc54fab914ce/> rename `parse::Section*` related types. + These are now located in `section::*`. + - <csr-id-60af4c9ecb1b99f21df0e8facc33e5f6fc70c424/> rename `parse::Parser` to `parse::State`. + Furthermore, make `State` the entry point for all parsing, removing + all free-standing functions that returned a `State`. + - <csr-id-3724850e0411f1f76e52c6c767fd8cebe8aea0f6/> rename `parser` module to `parse` + - <csr-id-58b22152a0295998935abb43563e9096589ef53e/> rename `normalize_cow()` to `normalize()` and move all `normalize*` functions from `values` to the `value` module + - <csr-id-767bedccdae1f3e6faf853d59ecf884a06cc3827/> move `Path` from `values` to `value` module + - <csr-id-6033f3f93d2356399a661567353a83a044662699/> Move `Boolean` and `String` from `values` into `value` module + - <csr-id-d4444e18042891b0fe5b9c6e6813fed26df6c560/> move `values::Integer` into `value` module + - <csr-id-38f31174e8c117af675cdfbc21926133b821ec38/> move `Color` to own `value` module + - <csr-id-aa630ad6ec2c6306d3307d5c77e272cb24b00ddd/> remove `values::Bytes` - use `values::String` instead. + Note that these values are always normalized and it's only possible + to get a raw values using the `raw_value()` API. + - <csr-id-553f87225363903e6acdb3e7eaa8cc66a91110f1/> `File::len()` -> `File::num_values()` + The same is true for `Section::len()` which now is + `Section::num_values()`. + - <csr-id-c7fcb5e1db225aefc3eeab4f29f3fb85c670894a/> `GitConfig::from_paths(…, <option>)` is now owned. + The type is `Copy`, so no need to pass it by reference. + - <csr-id-38dfdcf80f9b7368ccaa10f4b78b2129849848d0/> remove `values::*Error` in favor of `value::parse::Error`. + This makes it easier to work with errors in practice, we are either + interested in the value that failed to parse to try something else + or want a nice user message. + + Having one decode error type facilitates that. + - <csr-id-a98a7a7af69482e9ef63f106184049049939459d/> switch from quickerror to thiserror. + This allows for generic types for sources of errors and allows to + workaround a limitation with associated type constraints in the MSRV + of 1.54. + + Using thiserror makes this work and brings the crate more closely + to the rest of the gitoxide crates (which now prefer thiserror over + quickerror). + - <csr-id-a86b2541561674df5dbef4120d3e03483cb80117/> remove all `get_` prefixes from methods + That way the API is more idiomatic and fits better into the + existing `gitoxide` crates. + - <csr-id-f9aaac11f0734afbd791132369eb5601bfc7efe9/> use `lookup::Error` and `lookup::existing::Error` + Use the newly introduced structured error to replace the 'catch-all' + `GitConfigError` while getting closer to naming conventions in other + `gitoxide` crates. + +### Refactor + + - <csr-id-55c00d880535a1f8c37cb7d4405d39ff5a7654a0/> remove `git_config::values::Value`; use `Bytes` in its place. + +### Other + + - <csr-id-32d5b3c695d868ba93755123a25b276bfbe55e0a/> :Events::from_bytes()` with `filter` support. + - <csr-id-949622e461eb2116393ec6f4633ec0cb2e1695b5/> Include benches in crate to allow publishing to work + - <csr-id-bcacfc9bcf19a0339541b24e84de68d95291c62b/> remove clippy-cargo lint until there are no warnings + Please feel free to re-add once all other git-* crates have been + adjusted. It's interesting to see how a crate lint spills into the + workspace. + + Personally I am surprised that the keywords value is supposed + to repeat the crate name as it seems redundant. + - <csr-id-41f118d2aa560188fd3399d2390aa43794b0af75/> remove redundant lines from git-ignore file + +### Bug Fixes + + - <csr-id-8836f5ada0078febf8aeb0e6b59d0fc4c588bc28/> `File::section(...)` could panic due to invlid expectation. + This has been fixed to prevent panics. + - <csr-id-839f776454d7a8522c0f2887c90fc41b20576bd9/> `File::boolean()` now correctly handles implicit bools across sections. + This means it will return the last implicit value as true, instead of + ignoring it to fall back onto an explicit boolean value that might be + false. + - <csr-id-7d92c61abc2c54f4a64c35b37bcd1843a8d1da9c/> `file::SectionMut::push()` now properly creates empty values. + Previously, when calling `.push("key", None)`, the resulting internal + state would be slighly wrong causing certain invariants and expectations + to be unfulfilled even though it might have looked OK to most users. + + Now it will uphold the invariant for empty values, those without an + `=` character. + - <csr-id-9c1e639979a9615fd8334ce0e3a809df137776f6/> greatly improve whitespace handling when removing values. + Previously, newlines would remain past a value, and whitespace could + remain before one. + + Now both are removed to simulate removing an actual line. + - <csr-id-e533993e8f861ba7a6600aab114ddfecc8a85ee2/> `File::remove_section()` was fixed to allow re-adding a similarly named section. + We also add `File::remove_section_by_id()` to make it possible to remove + specific sections. + - <csr-id-08c50a47fa901457194539c7db74ad47ab2f8b60/> Properly handle boolean values such that `a` is true but `a=` is false. + This is even consistent when no booleans are used, such that `a` has no + value as if it is not present, it's only available for booleans which + must be specified. + - <csr-id-7c585162454c476fe93f032c8a2329cffd7c054f/> Keep track of a severe limitation and prepare tests for fixing it. + This also changes behaviour, but merely removes a hack in `Boolean` + which considered empty strings true, even though they are supposed to be + false. + - <csr-id-6c1588fd1a2fa80fd866787cbf4bcc6e5b51abe6/> maintain insertion order of includes on per-section basis at least. + Note that git inserts values right after the include directive, + 'splitting' the section, but we don't do that and insert new values + after the section. Probably no issue in practice while keeping + our implementation simple. + - <csr-id-f7bd2caceb87a179288030e0771da2e4ed6bd1e4/> maintain newline format depending on what's present or use platform default. + Previously implicit newlines when adding new sections or keys to + sections was always `\n` which isn't correct on windows. + + Now the newline style is detected and used according to what's present, + or in the lack of content, defaults to what's correct for the platform. + - <csr-id-0d07ef1aa4a9e238c20249d4ae2ed19e6740308a/> validate incoming conifguration keys when interpreting envirnoment variables. + - <csr-id-6b901843cb18b3d31f8b0b84bb9ebbae279aff19/> `Boolean` can use numbers to indicate true or false, drops support for `one` and `zero`. + - <csr-id-94dde44e8dd1a0b8d4e11f2627a3f6b345a15989/> `file::MutableSection::remove()` now actually removes keys _and_ values. + - <csr-id-048b92531eb877a5a128e702504891bf1e31becf/> `file::MutableMultiValue` escapes input values and maintains key separator specific whitespace. + - <csr-id-f911707b455ba6f3800b85f667f91e4d56027b91/> value normalization (via `value::normalize()` handles escape sequences. + The latter ones are `\n`, `\t` and `\b` which are the only supported + ones in values of git-config files. + - <csr-id-44dfec07480cc2ac6fd01674b748cc03af51fed1/> stable sort order for `File::sections_by_name_with_header()` + - <csr-id-1ea919d5ff81ab7b01b8201386ef63c7e081b537/> count newlines (for error display) in multi-line values as well + - <csr-id-1e71e71c984289f0d7e0a39379ee6728918b7dc5/> auto-normalize string values to support quote removal in case of strings. + Related to https://github.com/starship/starship/pull/3883 . + - <csr-id-36e2fc0aff4e6aaa35335da90108918882d4cd16/> Use `std::env::var_os()` to avoid potential decode errors + - <csr-id-469406dc0d9fece4a06230ef0d8018846202f0ad/> fix usage example in README.md + +### New Features + +<csr-id-61af06b905926849abce19677ff4b9ac05d625a3/> + + - <csr-id-4b424ab73d20833deb0c674eeeeaaa8cc2358932/> add `File::write_to_filter()` to have a filter select the section to write. + This way, one can selectively write sections to a file, based on the section's origin + or other meta-data. + + For convenience sake, most mutating methods on `SectionMut` can now be chained as well. + - <csr-id-3fa86e1d01a75688cd8948a39ba415bcebfef02d/> add `File::set_meta()` to affect what metadata new sections receive. + That way it's possible to better control the metadata associated with new sections, + given that the config file metadata may not truly reflect its identity anymore since it + can be a combination from many different files with various sources. + - <csr-id-5b9bffe8a5eec738e892224a7e18f98c8430d8a4/> `SectionMut::push_with_comment(key, comment)` to add a new variable with a comment. + This is useful for providing more information about a value at hand, especially if it was + added programmatically and then shows up in the configuration. + - <csr-id-e4bf8f0072e60a7a2df94690c8d0b13b1f3038bb/> Add the `Source::EnvOverride` to have a place for 'terminal' overrides. + That way environment variables represented via git-configuration + can be integrated into git configuration, making clearer what's + going to happen even when looking at the configuration via + `gix config`. + + The implementation has to be careful though about assureing there + is no more specific configuration key, like `http.<URL>.proxy` that + would override the one from the environment, which always has + the final word. + - <csr-id-5fa95460db843f7dcfe68002b303b8b7649846dd/> comfort API like `string_by_key(key)` takes a key like `"remote.origin.url"`, add `section_by_key("remote.origin")` as well. + That way it's the most comfortable way to query values and very + similar to how git does it, too. + + Additionally, sections can be obtained by section key, both mutably and immutably for completeness. + - <csr-id-7d7bd02d4e0678565f58c5da83fd1ad88c60e911/> read worktree specific configuration of main worktrees. + Supporting this is useful when interacting with worktrees that have been + created with sparse worktree support, which moves some configuration + values into those for the worktree at hand. + + Note that linked worktrees are not supported - for that use + `git-repository` instead. + - <csr-id-aa5d66f60bd6c9ef404ebc120b613e0cf055b2c9/> add `parse::section::header::is_valid_subsection()` function. + It can be useful to validate subsection names without having to + construct an entire `Header` (which also includes a name). + - <csr-id-5df2a2a5a9addbda7dcc68b2f8f7f4a48d9720c6/> Add various methods to iterate sections along with their id, and mutate them. + As section names are not unique, it was previously not possible to + iterate sections and then mutate them as one wouldn't be able to refer + to the exact section that was just traversed, after all, there can be + many sections named `remote "origin"`. + + With the new methods it's possible to uniquely refer to each section + for mutation and removal. + - <csr-id-9937d0e00df3a523484c7ae2850be2712a1a4c9a/> `File::set_raw_value_filter()` to set values only in sections passing a filter. + - <csr-id-17455c9d93ad38bfee2560f5a4e60324dee3b4e5/> `File::section_mut_or_create_new_filter()` to allow chosing which sections to add values to. + - <csr-id-5902f54b93101a6290fcf89f9f13fdbea3678e00/> `File::section_mut_or_create_new(…)` to obtain an existing or new section for mutation. + - <csr-id-b1c40b0364ef092cd52d03b34f491b254816b18d/> use docsrs feature in code to show what is feature-gated automatically on docs.rs + - <csr-id-517677147f1c17304c62cf97a1dd09f232ebf5db/> pass --cfg docsrs when compiling for https://docs.rs + - <csr-id-1bc96bf378d198b012efce9ec9e5b244a91f62bc/> following includes is now non-fatal by default + Otherwise it would be relatively easy to fail gitoxide startup, + and we want to be closer to the behaviour in git which ignores + most of the errors. + - <csr-id-f9ce1b5411f1ac788f71060ecf785dda9dfd87bf/> `File::from_git_dir()` as comfortable way to instantiate most complete git configuration. + - <csr-id-14a68a6a78a09f8ae56e30e3b7501de66ef31fdc/> `File` now compares actual content, ignoring whitespace and comments. + - <csr-id-7dadfd82494d47e36d3f570988eaf3c6b628977f/> `File::new_environment_overrides()` to easily instantiate overrides from the environment. + - <csr-id-146eeb064822839bc46fd37a247a1b9a84f64e40/> `File::new_globals()` can instantiate non-local configuration with zero-configuration. + - <csr-id-e701e053fd05850973930be0cefe73e8f3604d40/> `Source::storage_location()` to know where files should be located. + - <csr-id-fff088485dd5067976cc93d525903b39aafea76a/> `file::ValueMut::(section|into_section_mut)()` to go from value to the owning section. + This can be useful if the value was obtained using `raw_value_mut()`. + - <csr-id-f5f2d9b3fef98d9100d713f9291510fa4aa27867/> `Source::is_in_repository()` to find out if a source is in the repository. + - <csr-id-91e718f0e116052b64ca436d7c74cea79529e696/> `parse::key` to parse a `remote.origin.url`-like key to identify a value + - <csr-id-26147a7a61a695eda680808ee4aab44a890b2964/> Add `File::detect_newline_style()`, which does at it says. + - <csr-id-0ad1c9a5280cc172432b5258e0f79898721bac68/> `File::frontmatter()` and `File::sections_and_postmatter()`. + - <csr-id-fc7e311b423c5fffb8240d9d0f917ae7139a6133/> `parse::Event::to_bstr_lossy()` to get a glimpse at event content. + - <csr-id-09966a8ea4eaa3e0805e04188de86dd1bac9f388/> `File::append()` can append one file to another rather losslessly. + The loss happens as we, maybe for the wrong reasons, automatically + insert newlines where needed which can only be done while we still know + the file boundaries. + - <csr-id-56ae5744e8957e617f3a0ebc4d725846b18d93f8/> `file::Section::meta()` to access a section's metadata. + - <csr-id-6f97bf0c3e7164855cf5aa53462dbc39c430e03f/> `File::sections()` to obtain an iterator over all sections, in order. + - <csr-id-5418bc70e67476f8778656f2d577f1f9aa65ffbe/> place spaces around `key = value` pairs, or whatever is used in the source configuration. + - <csr-id-8118644625dc25b616e5f33c85f5100d600766e4/> proper escaping of value bytes to allow round-tripping after mutation + - <csr-id-9f59356b4f6a1f5f7f35a62c9fbe4859bf8e8e5f/> whitespace in newly pushed keys is derived from first section value. + That way, newly added key-value pairs look like they should assuming + all keys have the same indentation as the first key in the section. + + If there is no key, then the default whitespace will be double-tabs + like what's commmon in git. + - <csr-id-db1f34dfb855058ac08e97d4715876b5db712f61/> `File::from_str()` implementation, to support `let config: File = "[core]".parse()?` + - <csr-id-9157717c2fb143b5decbdf60d18cc2bd99dde775/> whitespace in mutable sections can be finely controlled, and is derived from existing sections + - <csr-id-ae3895c7882e0a543a44693faee5f760b49b54d7/> `parse::Header::new(…)` with sub-section name validation + - <csr-id-d087f12eec73626eb327eaacef8ebb3836b02381/> Add `parse::(Event|section::Header|Comment)::write_to(…)`. + Now it's possible to serialize these types in a streaming fashion and + without arbitrarily enforcing UTF-8 on it + - <csr-id-5a8f242ee98793e2467e7bc9806f8780b9d320ce/> `serde1` feature to add limited serde support + - <csr-id-4726bb524c1b0935d35770c907d40a0a16dbb8b5/> `GitConfig::integers()` + Get multiple fully validated integer values, with their suffix + interpreted and checked for overflow. + - <csr-id-ae22a4de486676f11469cec84be403903758b48b/> add `GitConfig::integer()` + A way to quickly obtain a valid integer with suffixes resolved + and overflow checked. + - <csr-id-bfc263797226d027e04daaf6426e57183773d7c3/> `GitConfig::strings()` for multi-value strings. + - <csr-id-7c75eac149c6ecb99c3dd7355d76d8d3e8b59cd0/> `GitConfig::path()` for direct access to paths. + Very similar to `string()`, but as path, whose query can never fail. + - <csr-id-dc3dc3b41b5de3ec17429769747bf99bb2bdd03d/> support for `try_value()`, `boolean()` and `string()` access`. + Support for a convenient way of knowing if a value does or doesn't exist + via `try_value()`, which can only fail if the conversion fails. + + Lastly, `string()` is a special case which doesn't fail as there is + no conversion, and `boolean()` allows to obtain a plain boolean value + if it was a valid boolean representation. + - <csr-id-13554f8d21beb241e0fbdeb56b8414957cbee28a/> new hierarchical errors for value lookup + - <csr-id-031bd2f401199a05d6465c0260ceed3cc849c7ac/> add suppport for android + Do not interpolate `~user/` on Android (Termux). + There is no meaning of it. It is single user system. + - <csr-id-e4d6685064ad2b433f8acd3a74b320bf0169a994/> Add `git_config::values::Path` for a typesafe git path + Add a `Path` type to the `git_config::values` which + can be interpolated according to gits own path interpolation + rules. + - <csr-id-e822f566dcff3f6c784c206dff2fbc5f82d543be/> implement a draft for subsection fetching + - introduce method `sections_by_name_with_header` to allow iterating over tuples of + section header and section body + +### Documentation + + - <csr-id-39ed9eda62b7718d5109135e5ad406fb1fe2978c/> fix typos + +### Chore + + - <csr-id-f7f136dbe4f86e7dee1d54835c420ec07c96cd78/> uniformize deny attributes + +### Changed + + - <csr-id-5ad296577d837b0699b4718fa2be3d0978c4e342/> `git-config` now uses `git-config-value`. + - <csr-id-b04a3465ed20d8f3088e5d3faf11e98e5595f219/> `GitConfig::from_paths(<paths>, …)` accepts more inputs + `<paths>` is more flexible and is easier to use. + +### Commit Statistics + +<csr-read-only-do-not-edit/> + + - 952 commits contributed to the release over the course of 865 calendar days. + - 149 commits were understood as [conventional](https://www.conventionalcommits.org). + - 18 unique issues were worked on: [#198](https://github.com/Byron/gitoxide/issues/198), [#213](https://github.com/Byron/gitoxide/issues/213), [#241](https://github.com/Byron/gitoxide/issues/241), [#254](https://github.com/Byron/gitoxide/issues/254), [#266](https://github.com/Byron/gitoxide/issues/266), [#298](https://github.com/Byron/gitoxide/issues/298), [#301](https://github.com/Byron/gitoxide/issues/301), [#319](https://github.com/Byron/gitoxide/issues/319), [#331](https://github.com/Byron/gitoxide/issues/331), [#386](https://github.com/Byron/gitoxide/issues/386), [#404](https://github.com/Byron/gitoxide/issues/404), [#436](https://github.com/Byron/gitoxide/issues/436), [#450](https://github.com/Byron/gitoxide/issues/450), [#470](https://github.com/Byron/gitoxide/issues/470), [#482](https://github.com/Byron/gitoxide/issues/482), [#691](https://github.com/Byron/gitoxide/issues/691), [#737](https://github.com/Byron/gitoxide/issues/737), [#XXX](https://github.com/Byron/gitoxide/issues/XXX) + +### Commit Details + +<csr-read-only-do-not-edit/> + +<details><summary>view details</summary> + + * **[#198](https://github.com/Byron/gitoxide/issues/198)** + - Maintenance release note to avoid being fully generated ([`56ef363`](https://github.com/Byron/gitoxide/commit/56ef363f0e7a8b9106765d96d0636e38b2bed550)) + - Changlog for git-config ([`abdfe58`](https://github.com/Byron/gitoxide/commit/abdfe588030b0fbdd4d69a73c5739ef4a83e3616)) + - Use correct title for github release to match name of tag ([`90f39ad`](https://github.com/Byron/gitoxide/commit/90f39ad693e0998bc3307bf553fccdc37c8dc0c8)) + * **[#213](https://github.com/Byron/gitoxide/issues/213)** + - Refactor ([`e906d37`](https://github.com/Byron/gitoxide/commit/e906d37e0b4e088b7973728db386a23ea7645fc9)) + - Remove environment variable after test passed ([`7a3ff29`](https://github.com/Byron/gitoxide/commit/7a3ff293048dd6bebec492bd79b12d7889fee3a1)) + * **[#241](https://github.com/Byron/gitoxide/issues/241)** + - Improve usability of the pack-cache environment variable ([`47d8162`](https://github.com/Byron/gitoxide/commit/47d81629a0bfa2eccf75cbe081de55d80d0abd59)) + * **[#254](https://github.com/Byron/gitoxide/issues/254)** + - Adjust changelogs prior to git-pack release ([`6776a3f`](https://github.com/Byron/gitoxide/commit/6776a3ff9fa5a283da06c9ec5723d13023a0b267)) + * **[#266](https://github.com/Byron/gitoxide/issues/266)** + - Upgrade dependencies ([`8adf0d8`](https://github.com/Byron/gitoxide/commit/8adf0d80bbd5c4e81ccd0b5363dbce6609a6c90a)) + * **[#298](https://github.com/Byron/gitoxide/issues/298)** + - Upgrade dependencies ([`b039d39`](https://github.com/Byron/gitoxide/commit/b039d39613bb14d49670c4d8b586f76ffb420d03)) + - Prepare changelog prior to release ([`fc8f52d`](https://github.com/Byron/gitoxide/commit/fc8f52d91c89fdc1130990e4392f151a30d1899c)) + - Support for simple BString powered string values ([`2381c5d`](https://github.com/Byron/gitoxide/commit/2381c5d3b91e3a071c887d9e1e166625977d5830)) + - Minor refactor ([`2f0234c`](https://github.com/Byron/gitoxide/commit/2f0234c05d1a3e1e3b96dff9680189c67cb6c9ff)) + * **[#301](https://github.com/Byron/gitoxide/issues/301)** + - Update changelogs prior to release ([`84cb256`](https://github.com/Byron/gitoxide/commit/84cb25614a5fcddff297c1713eba4efbb6ff1596)) + - Finished refactoring ([`4163c7f`](https://github.com/Byron/gitoxide/commit/4163c7fe0a98b77998fa263458d06bdeb435996d)) + - Refactor ([`a359cfd`](https://github.com/Byron/gitoxide/commit/a359cfd86ffae9feab11b45e3167fe28f22dbac8)) + - `GitConfig::from_paths(…, <option>)` is now owned. ([`c7fcb5e`](https://github.com/Byron/gitoxide/commit/c7fcb5e1db225aefc3eeab4f29f3fb85c670894a)) + - Use `std::env::var_os()` to avoid potential decode errors ([`36e2fc0`](https://github.com/Byron/gitoxide/commit/36e2fc0aff4e6aaa35335da90108918882d4cd16)) + - `GitConfig::from_paths(<paths>, …)` accepts more inputs ([`b04a346`](https://github.com/Byron/gitoxide/commit/b04a3465ed20d8f3088e5d3faf11e98e5595f219)) + - Refactor unconditional include tests and stabilize them ([`72a5a02`](https://github.com/Byron/gitoxide/commit/72a5a027dd8120b27909efea339dfc7919a865be)) + - `GitConfig::integers()` ([`4726bb5`](https://github.com/Byron/gitoxide/commit/4726bb524c1b0935d35770c907d40a0a16dbb8b5)) + - Add `GitConfig::integer()` ([`ae22a4d`](https://github.com/Byron/gitoxide/commit/ae22a4de486676f11469cec84be403903758b48b)) + - Refactor ([`c139479`](https://github.com/Byron/gitoxide/commit/c13947977205828dcda177686362e25867fdfe44)) + - Refactor ([`4408f17`](https://github.com/Byron/gitoxide/commit/4408f17736052c899a9c98af41485d7dde9a297f)) + - `GitConfig::strings()` for multi-value strings. ([`bfc2637`](https://github.com/Byron/gitoxide/commit/bfc263797226d027e04daaf6426e57183773d7c3)) + - Refactor ([`7ea17e1`](https://github.com/Byron/gitoxide/commit/7ea17e1e16346239032844b8f4be9e9c22c6be8e)) + - Initial refactoring ([`43a34a5`](https://github.com/Byron/gitoxide/commit/43a34a5bdae53fbb53d3ae095f03c9456115a013)) + - Some notes about of 'path' will soon have to be amended with more safety ([`97e53f6`](https://github.com/Byron/gitoxide/commit/97e53f63df2c0262f23af3d7d997f148d23474be)) + - `GitConfig::path()` for direct access to paths. ([`7c75eac`](https://github.com/Byron/gitoxide/commit/7c75eac149c6ecb99c3dd7355d76d8d3e8b59cd0)) + - Remove `values::*Error` in favor of `value::parse::Error`. ([`38dfdcf`](https://github.com/Byron/gitoxide/commit/38dfdcf80f9b7368ccaa10f4b78b2129849848d0)) + - A sketch of what can be a general value decode error ([`4612fca`](https://github.com/Byron/gitoxide/commit/4612fca79446c6f92f0e6a4163bc895fc346b30d)) + - Remove IntegerSuffix error which wasn't ever used ([`732c0fa`](https://github.com/Byron/gitoxide/commit/732c0fa6e1832efcc0de4adc894e820b3bd27b8f)) + - Support for `try_value()`, `boolean()` and `string()` access`. ([`dc3dc3b`](https://github.com/Byron/gitoxide/commit/dc3dc3b41b5de3ec17429769747bf99bb2bdd03d)) + - Fix build warnings ([`4496b5a`](https://github.com/Byron/gitoxide/commit/4496b5a26abaf91fd4844e0494aaa1b4cce73628)) + - Switch from quickerror to thiserror. ([`a98a7a7`](https://github.com/Byron/gitoxide/commit/a98a7a7af69482e9ef63f106184049049939459d)) + - Remove all #[inline] attributes ([`8aef1d3`](https://github.com/Byron/gitoxide/commit/8aef1d313dc9d3ac0004e790b6f91ad0c7ac99b0)) + - Remove all `get_` prefixes from methods ([`a86b254`](https://github.com/Byron/gitoxide/commit/a86b2541561674df5dbef4120d3e03483cb80117)) + - Use `lookup::Error` and `lookup::existing::Error` ([`f9aaac1`](https://github.com/Byron/gitoxide/commit/f9aaac11f0734afbd791132369eb5601bfc7efe9)) + - New hierarchical errors for value lookup ([`13554f8`](https://github.com/Byron/gitoxide/commit/13554f8d21beb241e0fbdeb56b8414957cbee28a)) + - Adapt to changes in git-path ([`cc2d810`](https://github.com/Byron/gitoxide/commit/cc2d81012d107da7a61bf4de5b28342dea5083b7)) + - Adapt to all changes in git-path with bstr support ([`f158648`](https://github.com/Byron/gitoxide/commit/f158648aef8ad94d86550ceb2eeb20efb3df7596)) + - Use `git-path` crate instead of `git_features::path` ([`47e607d`](https://github.com/Byron/gitoxide/commit/47e607dc256a43a3411406c645eb7ff04239dd3a)) + - Make fmt ([`5fc5459`](https://github.com/Byron/gitoxide/commit/5fc5459b17b623726f99846c432a70106464e970)) + - Make fmt ([`50ff7aa`](https://github.com/Byron/gitoxide/commit/50ff7aa7fa86e5e2a94fb15aab86470532ac3f51)) + * **[#319](https://github.com/Byron/gitoxide/issues/319)** + - Update changelog prior to release ([`858ec8c`](https://github.com/Byron/gitoxide/commit/858ec8cc25f18c435465baee762def3013743f0b)) + - Adjust docs ([`38c201c`](https://github.com/Byron/gitoxide/commit/38c201c505ce2ea4257cdd0255713154745a330c)) + - An example to illustrate the problem ([`c47e8f8`](https://github.com/Byron/gitoxide/commit/c47e8f8ee8ea79f8f654f6c28e54e0b0b1fff1b6)) + * **[#331](https://github.com/Byron/gitoxide/issues/331)** + - Final documentation review + adjustments prior to release candidate ([`06b86e0`](https://github.com/Byron/gitoxide/commit/06b86e05dd9a712d26456b43c8da0a11870f08df)) + - Refactor ([`4dc6594`](https://github.com/Byron/gitoxide/commit/4dc6594686478d9d6cd09e2ba02048624c3577e7)) + - Exclude particular assertion which fails on the linux CI. ([`5e0f889`](https://github.com/Byron/gitoxide/commit/5e0f889c1edb862d698a2d344a61f12ab3b6ade7)) + - First sketch of using configuration and environment variables for author/committer ([`330d0a1`](https://github.com/Byron/gitoxide/commit/330d0a19d54aabac868b76ef6281fffdbdcde53c)) + - Remove `Permissions` as there is no need for that here. ([`1954ef0`](https://github.com/Byron/gitoxide/commit/1954ef096a58aedb9f568a01e439d5a5cb46c40d)) + - Following includes is now non-fatal by default ([`1bc96bf`](https://github.com/Byron/gitoxide/commit/1bc96bf378d198b012efce9ec9e5b244a91f62bc)) + - Allow to skip non-existing input paths without error ([`989603e`](https://github.com/Byron/gitoxide/commit/989603efcdf0064e2bb7d48100391cabc810204d)) + - `File::from_git_dir()` as comfortable way to instantiate most complete git configuration. ([`f9ce1b5`](https://github.com/Byron/gitoxide/commit/f9ce1b5411f1ac788f71060ecf785dda9dfd87bf)) + - Add a way to load multiple configuration files without allocating a read buffer ([`acb4520`](https://github.com/Byron/gitoxide/commit/acb4520a88ab083640c80a7f23a56a2ca3cda335)) + - Refactor ([`ec21e95`](https://github.com/Byron/gitoxide/commit/ec21e95f4d9ffac771410947923f27187e88321a)) + - Move `Env` test utility into `git-testtools` ([`bd3f4d0`](https://github.com/Byron/gitoxide/commit/bd3f4d014dd7df7a1e25defa8eea7253eec1560a)) + - Refactor ([`b073e29`](https://github.com/Byron/gitoxide/commit/b073e2930bed60ccedadd1709cfaa8889e02ffe3)) + - Another failing tests that can't be fixed without a refactor ([`e4d8fd7`](https://github.com/Byron/gitoxide/commit/e4d8fd72f1f648a29e56e487827f2328bfc08d03)) + - An attempt to hack newline handling into place for windows newlines ([`dac1463`](https://github.com/Byron/gitoxide/commit/dac146343a0fbe96b6c0990f4fd4e976e0359a7e)) + - Serialize lossily-read configuration files correctly anyway. ([`cfda0c3`](https://github.com/Byron/gitoxide/commit/cfda0c335d759cae0b23cef51f7b85a5f4b11e82)) + - Multi-path include test ([`3d89a46`](https://github.com/Byron/gitoxide/commit/3d89a46bf88b1fb5b4aa5da9fd12c7e310be3f9d)) + - Refactor ([`8a7fb15`](https://github.com/Byron/gitoxide/commit/8a7fb15f78ce16d5caedd7656e8aa98e72f248a6)) + - Fix windows tests ([`fbcf40e`](https://github.com/Byron/gitoxide/commit/fbcf40e16b8fc1ff97dbed2bc22b64bd44a8b99d)) + - Finally proper whitespace handling in all the right places for perfect roundtripping to/from string ([`97e5ede`](https://github.com/Byron/gitoxide/commit/97e5ededb0390c1b4f296a35903433de9c519821)) + - Serializations maintains some invariants about whitespace where possible. ([`ee10dd5`](https://github.com/Byron/gitoxide/commit/ee10dd5a8ae0dabfee21c1ce146e92c3c9635e8a)) + - Refactor ([`9c248ee`](https://github.com/Byron/gitoxide/commit/9c248eeb015495f910f48ce5df3c8fcce905dba7)) + - `File` now compares actual content, ignoring whitespace and comments. ([`14a68a6`](https://github.com/Byron/gitoxide/commit/14a68a6a78a09f8ae56e30e3b7501de66ef31fdc)) + - Maintain insertion order of includes on per-section basis at least. ([`6c1588f`](https://github.com/Byron/gitoxide/commit/6c1588fd1a2fa80fd866787cbf4bcc6e5b51abe6)) + - Allow insertion of sections while preserving order ([`f5580a3`](https://github.com/Byron/gitoxide/commit/f5580a3635289d96e662aab00e60d801c4e34e1c)) + - A test showing that include ordering isn't correct compared to the including config. ([`4e47df5`](https://github.com/Byron/gitoxide/commit/4e47df5332810f6e46ab682a68e870220ba3a6fb)) + - Add `File::resolve_includes()` and move its error type to `file::includes`. ([`17c83d5`](https://github.com/Byron/gitoxide/commit/17c83d55f8942788aac5eb1bea22a48daa045bf4)) + - Add `File::from_bytes_owned()` and remove `File::from_path_with_buf()` ([`5221676`](https://github.com/Byron/gitoxide/commit/5221676e28f2b6cc1a7ef1bdd5654b880965f38c)) + - Make it necessary to deal with the possibility of no-input in `from_paths_metadata()` . ([`612645f`](https://github.com/Byron/gitoxide/commit/612645f74ffc49229ccd783361b4d455e2284ac0)) + - Don't fail on empty input on the comfort level ([`61ecaca`](https://github.com/Byron/gitoxide/commit/61ecaca43fb871eaff5cf94a8e7f9cc9413a5a77)) + - `File::new_environment_overrides()` to easily instantiate overrides from the environment. ([`7dadfd8`](https://github.com/Byron/gitoxide/commit/7dadfd82494d47e36d3f570988eaf3c6b628977f)) + - Prepare for supporting comfortable version of environment overrides ([`45c964a`](https://github.com/Byron/gitoxide/commit/45c964a3f581dc7d3090bbbe26f188d553783fb3)) + - Remove `File::from_env_paths()`. ([`98d45c2`](https://github.com/Byron/gitoxide/commit/98d45c2f59863fdee033b38e757cec09593f6892)) + - `File::new_globals()` can instantiate non-local configuration with zero-configuration. ([`146eeb0`](https://github.com/Byron/gitoxide/commit/146eeb064822839bc46fd37a247a1b9a84f64e40)) + - Classify `Source` in accordance for what git actually does. ([`97374e4`](https://github.com/Byron/gitoxide/commit/97374e4d867e82d7be04da2eaa6ef553e0d9a7ff)) + - `Source::storage_location()` to know where files should be located. ([`e701e05`](https://github.com/Byron/gitoxide/commit/e701e053fd05850973930be0cefe73e8f3604d40)) + - `file::ValueMut::(section|into_section_mut)()` to go from value to the owning section. ([`fff0884`](https://github.com/Byron/gitoxide/commit/fff088485dd5067976cc93d525903b39aafea76a)) + - `Source::is_in_repository()` to find out if a source is in the repository. ([`f5f2d9b`](https://github.com/Byron/gitoxide/commit/f5f2d9b3fef98d9100d713f9291510fa4aa27867)) + - `parse::key` to parse a `remote.origin.url`-like key to identify a value ([`91e718f`](https://github.com/Byron/gitoxide/commit/91e718f0e116052b64ca436d7c74cea79529e696)) + - Maintain newline format depending on what's present or use platform default. ([`f7bd2ca`](https://github.com/Byron/gitoxide/commit/f7bd2caceb87a179288030e0771da2e4ed6bd1e4)) + - Prepare for passing through newline ([`3c06f88`](https://github.com/Byron/gitoxide/commit/3c06f8889854860b731735a8ce2bf532366003ef)) + - Add `File::detect_newline_style()`, which does at it says. ([`26147a7`](https://github.com/Byron/gitoxide/commit/26147a7a61a695eda680808ee4aab44a890b2964)) + - Fix docs ([`78e85d9`](https://github.com/Byron/gitoxide/commit/78e85d9786a541aa43ad7266e85dc1da5e71a412)) + - A test for lossy File parsing ([`5e8127b`](https://github.com/Byron/gitoxide/commit/5e8127b395bd564129b20a1db2d59d39307a2857)) + - 'lossy' is now inherited by includes processing ([`88c6b18`](https://github.com/Byron/gitoxide/commit/88c6b185b2e51858b140e4378a5b5730b5cb4075)) + - Untangle `file::init::…` `Option` and `Error` types. ([`230a523`](https://github.com/Byron/gitoxide/commit/230a523593afcfb8720db965ff56265aaceea772)) + - Support for `lossy` load mode. ([`d003c0f`](https://github.com/Byron/gitoxide/commit/d003c0f139d61e3bd998a0283a9c7af25a60db02)) + - :Events::from_bytes()` with `filter` support. ([`32d5b3c`](https://github.com/Byron/gitoxide/commit/32d5b3c695d868ba93755123a25b276bfbe55e0a)) + - Try to fix attributes, once more ([`a50a396`](https://github.com/Byron/gitoxide/commit/a50a3964dbf01982b5a2c9a8ccd469332b6f9ca1)) + - `File::frontmatter()` and `File::sections_and_postmatter()`. ([`0ad1c9a`](https://github.com/Byron/gitoxide/commit/0ad1c9a5280cc172432b5258e0f79898721bac68)) + - Add `_filter()` versions to most access methods. ([`1ea26d8`](https://github.com/Byron/gitoxide/commit/1ea26d80f392114349d25ebf88a7b260ee822aa1)) + - Even better handling of newlines ([`50c1753`](https://github.com/Byron/gitoxide/commit/50c1753c6389f29279d278fbab1afbd9ded34a76)) + - Refactor ([`df94c67`](https://github.com/Byron/gitoxide/commit/df94c6737ba642fff40623f406df0764d5bd3c43)) + - Rename `parse::Comment::(comment_tag|comment)` to `::tag|text` and `parse::Section::section_header` to `::header`. ([`3f3ff11`](https://github.com/Byron/gitoxide/commit/3f3ff11a6ebe9775ee5ae7fc0ec18a94b5b46d61)) + - `parse::Event::to_bstr_lossy()` to get a glimpse at event content. ([`fc7e311`](https://github.com/Byron/gitoxide/commit/fc7e311b423c5fffb8240d9d0f917ae7139a6133)) + - Finally fix newline behaviour ([`c70e135`](https://github.com/Byron/gitoxide/commit/c70e135ecbbce8c696a6ab542ae20f5b5981dfdf)) + - Be smarter about which newline style to use by guessing it based onprior events ([`25ed92e`](https://github.com/Byron/gitoxide/commit/25ed92e66bf4345f852e7e84741079c61ae896c8)) + - `File::append()` can append one file to another rather losslessly. ([`09966a8`](https://github.com/Byron/gitoxide/commit/09966a8ea4eaa3e0805e04188de86dd1bac9f388)) + - A test to validate frontmatter isn't currently handled correctly when appending ([`4665e87`](https://github.com/Byron/gitoxide/commit/4665e876df4ac6ab9135c10ee69b5408b89b5313)) + - `file::Section::meta()` to access a section's metadata. ([`56ae574`](https://github.com/Byron/gitoxide/commit/56ae5744e8957e617f3a0ebc4d725846b18d93f8)) + - Refactor ([`d60025e`](https://github.com/Byron/gitoxide/commit/d60025e317d2b5f34f3569f321845bbb557ba2e7)) + - `File::sections()` to obtain an iterator over all sections, in order. ([`6f97bf0`](https://github.com/Byron/gitoxide/commit/6f97bf0c3e7164855cf5aa53462dbc39c430e03f)) + - Associate `file::Metadata` with each `File`. ([`6f4eea9`](https://github.com/Byron/gitoxide/commit/6f4eea936d64fb9827277c160f989168e7b1dba2)) + - Rename `file::SectionBody` to `file::section::Body`. ([`b672ed7`](https://github.com/Byron/gitoxide/commit/b672ed7667a334be3d45c59f4727f12797b340da)) + - Remove `File::sections_by_name_with_header()` as `::sections_by_name()` now returns entire sections. ([`3bea26d`](https://github.com/Byron/gitoxide/commit/3bea26d7d2a9b5751c6c15e1fa9a924b67e0159e)) + - A way to more easily set interpolation even without following includes. ([`9aa5acd`](https://github.com/Byron/gitoxide/commit/9aa5acdec12a0721543c6bcc39ffe6bd734f9a69)) + - Create `resolve_includes` options to make space for more options when loading paths. ([`41b3e62`](https://github.com/Byron/gitoxide/commit/41b3e622ee71943c285eadc518150fc7b6c92361)) + - Rename `path::Options` into `path::Context`. ([`cabc8ef`](https://github.com/Byron/gitoxide/commit/cabc8ef0e31c954642525e7693009a7fe4b4c465)) + - Try to fix attributes, once more ([`207e483`](https://github.com/Byron/gitoxide/commit/207e483620b29efb029c6ee742c0bb48d54be020)) + - Validate incoming conifguration keys when interpreting envirnoment variables. ([`0d07ef1`](https://github.com/Byron/gitoxide/commit/0d07ef1aa4a9e238c20249d4ae2ed19e6740308a)) + - Try to fix filter settings, but it doesn't seem to work ([`9750b7a`](https://github.com/Byron/gitoxide/commit/9750b7a1f01d6f0690221c6091b16c51784df0a3)) + - Sketch new section and metadata ([`9cb9acb`](https://github.com/Byron/gitoxide/commit/9cb9acb7b7ebada4d6bb3eef199337912ceeaa36)) + - Add `Source` type to allow knowing where a particular value is from. ([`c92d5c6`](https://github.com/Byron/gitoxide/commit/c92d5c6a223e377c10c2ca6b822e7eeb9070e12c)) + - `Boolean` can use numbers to indicate true or false, drops support for `one` and `zero`. ([`6b90184`](https://github.com/Byron/gitoxide/commit/6b901843cb18b3d31f8b0b84bb9ebbae279aff19)) + - All accessors in `File` are now using `impl AsRef<str>` where possible for added comfort. ([`3de0cfd`](https://github.com/Byron/gitoxide/commit/3de0cfd81523e4ba7cc362d8625f85ebf8fd9172)) + - Much more comfortable API `file::*Mut` types thanks to `impl Into/AsRef`. ([`3d25fe6`](https://github.com/Byron/gitoxide/commit/3d25fe6c7a52529488fab19c927d64a1bc75838f)) + - Rename `Mutable*` into `$1Mut` for consistency. ([`393b392`](https://github.com/Byron/gitoxide/commit/393b392d515661e5c3e60629319fdab771c3d3f0)) + - `file::MutableSection::remove()` now actually removes keys _and_ values. ([`94dde44`](https://github.com/Byron/gitoxide/commit/94dde44e8dd1a0b8d4e11f2627a3f6b345a15989)) + - Many more tests for MutableSection ([`ac843cb`](https://github.com/Byron/gitoxide/commit/ac843cbef4a6322be706b978e6691bc36c5e458f)) + - Refactor ([`701266e`](https://github.com/Byron/gitoxide/commit/701266e6e52456c0c1938732c260be19ec8029c9)) + - Conform APIs of `file::MutableValue` and `file::MutableMultiValue`. ([`0a7391a`](https://github.com/Byron/gitoxide/commit/0a7391a6575f4035c51a46d34fa20c69e9d078e9)) + - `file::MutableMultiValue` escapes input values and maintains key separator specific whitespace. ([`048b925`](https://github.com/Byron/gitoxide/commit/048b92531eb877a5a128e702504891bf1e31becf)) + - Place spaces around `key = value` pairs, or whatever is used in the source configuration. ([`5418bc7`](https://github.com/Byron/gitoxide/commit/5418bc70e67476f8778656f2d577f1f9aa65ffbe)) + - Avoid extra copies when setting values and escaping them ([`a7eff01`](https://github.com/Byron/gitoxide/commit/a7eff0166f200a403d4dba320280f20a70e9afc7)) + - Refactor ([`15cd1d2`](https://github.com/Byron/gitoxide/commit/15cd1d2ba447ff27819f6cf398d31e96ff11b213)) + - More empty-value tests ([`511985a`](https://github.com/Byron/gitoxide/commit/511985a8084f2a00e0550e5f2a85c93779385a1b)) + - Default space is just a single tab, not two ones ([`7e03b83`](https://github.com/Byron/gitoxide/commit/7e03b835bd6f0f5b3f00dbc63e7960ce6364eaef)) + - Proper escaping of value bytes to allow round-tripping after mutation ([`8118644`](https://github.com/Byron/gitoxide/commit/8118644625dc25b616e5f33c85f5100d600766e4)) + - Refactor ([`afa736a`](https://github.com/Byron/gitoxide/commit/afa736aba385bd52e7f11fd89538aea99787ac9d)) + - A few tests for `MutableValue` showing that it's too buggy right now ([`5e6f9d9`](https://github.com/Byron/gitoxide/commit/5e6f9d909db41926e829e464abc53ef05fbf620b)) + - Rename `file::MutableSection::set_leading_space()` to `set_leading_whitespace()`. ([`83a0922`](https://github.com/Byron/gitoxide/commit/83a0922f06081312b79908835dac2b7f4e849bb3)) + - Whitespace in newly pushed keys is derived from first section value. ([`9f59356`](https://github.com/Byron/gitoxide/commit/9f59356b4f6a1f5f7f35a62c9fbe4859bf8e8e5f)) + - `File::from_str()` implementation, to support `let config: File = "[core]".parse()?` ([`db1f34d`](https://github.com/Byron/gitoxide/commit/db1f34dfb855058ac08e97d4715876b5db712f61)) + - Whitespace in mutable sections can be finely controlled, and is derived from existing sections ([`9157717`](https://github.com/Byron/gitoxide/commit/9157717c2fb143b5decbdf60d18cc2bd99dde775)) + - Refactor ([`c88eea8`](https://github.com/Byron/gitoxide/commit/c88eea87d7ece807ca5b1753b47ce89d3ad6a502)) + - Refactor ([`a0d6caa`](https://github.com/Byron/gitoxide/commit/a0d6caa243aa293386d4ad164e1604f0e71c2cf3)) + - Auto-compute whitespace for sections, even though it probably needs to be better than that ([`ee9ac95`](https://github.com/Byron/gitoxide/commit/ee9ac953180886cc483e1125b7f4e172af92c3ce)) + - Validation for Keys and header names ([`59ec7f7`](https://github.com/Byron/gitoxide/commit/59ec7f7bf019d269573f8cc69f6d34b9458b1f1a)) + - Simplify specifying keys when mutating config values. ([`a93a156`](https://github.com/Byron/gitoxide/commit/a93a156655d640ae63ff7c35b0a1f5d67a5ca20f)) + - `File::rename_section()` with validation of input arguments. ([`895ce40`](https://github.com/Byron/gitoxide/commit/895ce40aabbe6d6af5b681a0d0942303fd6549a2)) + - Re-add newlines after multi-line values ([`9a2f597`](https://github.com/Byron/gitoxide/commit/9a2f59742cf94643c5b9967b76042bcc7a4e1a71)) + - More header escaping tests ([`12cf005`](https://github.com/Byron/gitoxide/commit/12cf0052d92ee5bee1926f50c879526b5903c175)) + - Enforce `parse::section::Header::new()` by making its fields private. ([`219cf7a`](https://github.com/Byron/gitoxide/commit/219cf7ae0b35b3ac92f97974be52cd022698e01f)) + - `parse::Header::new(…)` with sub-section name validation ([`ae3895c`](https://github.com/Byron/gitoxide/commit/ae3895c7882e0a543a44693faee5f760b49b54d7)) + - Section names are now validated. ([`cfd974f`](https://github.com/Byron/gitoxide/commit/cfd974f46d2cbb99e7784a05f5e358fed0d4bcab)) + - Prepare for validation of `parse::section::Header` ([`00592f6`](https://github.com/Byron/gitoxide/commit/00592f6b80abe15a32a890ddc2b1fbf6701798d8)) + - Basic escaping of subsection names during serialization ([`00d1a9b`](https://github.com/Byron/gitoxide/commit/00d1a9b741845b49d8691262bef6e5c21876567e)) + - Refactor ([`9fac8e0`](https://github.com/Byron/gitoxide/commit/9fac8e0066c9b1845d9e06fb30b61ca9e9d64555)) + - New roundtrip test on file level ([`78bb93c`](https://github.com/Byron/gitoxide/commit/78bb93cf35b6a990bac64bbfc56144799ad36243)) + - Add `File::write_to()` and `File::to_bstring()`; remove some `TryFrom` impls. ([`4f6cd8c`](https://github.com/Byron/gitoxide/commit/4f6cd8cf65c2d8698bffe327a19031c342b229a6)) + - Remove `Integer::to_bstring()` as well as some `TryFrom` impls. ([`0e392f8`](https://github.com/Byron/gitoxide/commit/0e392f81e99c8c0ff29f41b9b86afd57cd99c245)) + - Remove `Boolean::to_bstring()` along with a few `From` impls. ([`b22732a`](https://github.com/Byron/gitoxide/commit/b22732a2ab17213c4a1020859ec41f25ccabfbfc)) + - Add `parse::(Event|section::Header|Comment)::write_to(…)`. ([`d087f12`](https://github.com/Byron/gitoxide/commit/d087f12eec73626eb327eaacef8ebb3836b02381)) + - Fix tests on windows ([`3d7fc18`](https://github.com/Byron/gitoxide/commit/3d7fc188914337074775863acc1d6c15f47e913c)) + - Value normalization (via `value::normalize()` handles escape sequences. ([`f911707`](https://github.com/Byron/gitoxide/commit/f911707b455ba6f3800b85f667f91e4d56027b91)) + - Refactor normalization and more tests ([`cf3bf4a`](https://github.com/Byron/gitoxide/commit/cf3bf4a3bde6cdf20c63ffee1a5ae55a1f4e1742)) + - More escape characters for normalization ([`b92bd58`](https://github.com/Byron/gitoxide/commit/b92bd580de45cb58cd2b3c4af430273e96139c79)) + - Review docs of `file::mutating` ([`2d5703e`](https://github.com/Byron/gitoxide/commit/2d5703e5909946e4327e0372097273facaeca759)) + - Stable sort order for `File::sections_by_name_with_header()` ([`44dfec0`](https://github.com/Byron/gitoxide/commit/44dfec07480cc2ac6fd01674b748cc03af51fed1)) + - Review `file::raw` module ([`6acf4a4`](https://github.com/Byron/gitoxide/commit/6acf4a43fd63c1c5e24b2e21702dc79827e3d11e)) + - Don't over-normalize in comfort layer - all values are normalized now ([`b979a3b`](https://github.com/Byron/gitoxide/commit/b979a3b318faada23a6cf073953b13f7828398af)) + - Docs for comfort level File API ([`eafc6ce`](https://github.com/Byron/gitoxide/commit/eafc6ce14a9f3d3dbc585e34e465609385f07f69)) + - Review and refactor 'File::value' module ([`7aa8a0b`](https://github.com/Byron/gitoxide/commit/7aa8a0b66f3508336e8c20a1a0d2b481e7b9bde8)) + - Allocation free `File::sections_by_name()` and `File::sections_by_name_with_header()`. ([`65c520c`](https://github.com/Byron/gitoxide/commit/65c520c4de8187884f87059adf5cef9cbdcd90a2)) + - Refactor ([`2abffd6`](https://github.com/Byron/gitoxide/commit/2abffd6f2224edd98f806b5dbd4fc0e1c60019c5)) + - Refactor ([`539c2f6`](https://github.com/Byron/gitoxide/commit/539c2f67bede1247478ce75429690c2904915a89)) + - Refactor ([`f1668e9`](https://github.com/Byron/gitoxide/commit/f1668e9d9e94f166fa05164612eab9ee26357d12)) + - Refactor ([`2599680`](https://github.com/Byron/gitoxide/commit/2599680f7479e18612b4379efbe918139dde2345)) + - Refactor ([`879fad5`](https://github.com/Byron/gitoxide/commit/879fad5afdcd90e248934e9c3b973d7bd438d1f9)) + - Fix docs ([`b2b82da`](https://github.com/Byron/gitoxide/commit/b2b82da6c6d3c71b249c9ff2055cd98a58f1d988)) + - Once again zero-allocation for SectionBodyIter ([`ba69124`](https://github.com/Byron/gitoxide/commit/ba691243778b3eb89452fd1277c50dfe83d0075f)) + - Refactor ([`33efef6`](https://github.com/Byron/gitoxide/commit/33efef6de375e399fe33a02e7b6dace1a679ac7e)) + - Docs and refactor ([`700d6aa`](https://github.com/Byron/gitoxide/commit/700d6aa34f2604ee72e619afb15c1bb6ce1697f2)) + - `Path::interpolate()` now takes `path::interpolate::Options` instead of three parameters. ([`ac57c44`](https://github.com/Byron/gitoxide/commit/ac57c4479e7b6867e8b8e71f7cf76de759dc64a2)) + - Refactor `from_env` ([`c8693f9`](https://github.com/Byron/gitoxide/commit/c8693f9058765671804c93ead1eea1175a94f87c)) + - Make fmt ([`a7d7751`](https://github.com/Byron/gitoxide/commit/a7d7751822a1a8ac89930031707af57ad95d9cbd)) + - More doc adjustments ([`95fc20a`](https://github.com/Byron/gitoxide/commit/95fc20a377aeb914d6b527c1d1b8e75d8c42c608)) + - Review docs of 'parse' module; refactor ([`a361c7f`](https://github.com/Byron/gitoxide/commit/a361c7ff290cdae071a12351330013ad0043b517)) + - Refactor ([`8e84fda`](https://github.com/Byron/gitoxide/commit/8e84fdadfc49ba61f258286acb0a707bfb2a396b)) + - `File::raw_multi_value()` to `File::raw_values()` ([`9cd9933`](https://github.com/Byron/gitoxide/commit/9cd99337333f5ef4b30e0ec9461fc087699576e6)) + - `File::raw_multi_value_mut()` to `File::raw_values_mut()` ([`0076dcf`](https://github.com/Byron/gitoxide/commit/0076dcf9b37f1d633bdad5573b40d34a9fbaba90)) + - `File::multi_value()` to `File::values()`. ([`a8604a2`](https://github.com/Byron/gitoxide/commit/a8604a237782f8d60a185d4730db57bad81424a6)) + - Remove `String` type in favor of referring to the `File::string()` method. ([`0915051`](https://github.com/Byron/gitoxide/commit/0915051798dd782b40617a1aa16abd71f6db1175)) + - Fix docs ([`8fa7600`](https://github.com/Byron/gitoxide/commit/8fa7600847da6946784466213cea4c32ff9f7f92)) + - Refactor ([`b78e3fa`](https://github.com/Byron/gitoxide/commit/b78e3fa792fad4f3e3f9d5c668afccd75bc551e0)) + - Change! Add `home_for_user` in `Path::interpolate(…)`. ([`f9e0ef3`](https://github.com/Byron/gitoxide/commit/f9e0ef38e97fbc1e123d310dc696270d496438b6)) + - Simplify `Boolean` to be a wrapper around `bool`. ([`9cadc6f`](https://github.com/Byron/gitoxide/commit/9cadc6f0cbaad0ac23f5469db2f040aecfbfb82c)) + - Use bitflags for `color::Attribute` instead of `Vec` of enums. ([`703922d`](https://github.com/Byron/gitoxide/commit/703922dd4e1e5b27835298217ff4eb8ef1dc57ce)) + - A bitflag version of color attributes ([`23ec673`](https://github.com/Byron/gitoxide/commit/23ec673baaf666fc38fda2f3b1ace9a8cf6816b8)) + - Refactor ([`4f21d1e`](https://github.com/Byron/gitoxide/commit/4f21d1ed145bfd0d56d31be73fade25b104bab53)) + - Simplify `Color` API. ([`3fc4ac0`](https://github.com/Byron/gitoxide/commit/3fc4ac04f46f869c6e3a94ce4bb8a5737aa0c524)) + - Deduplicate ([`c1b9cd4`](https://github.com/Byron/gitoxide/commit/c1b9cd443ec103a01daee8b8226a53f560d62498)) + - First tests for colors specifically; fix space between tokens ([`e2bd055`](https://github.com/Byron/gitoxide/commit/e2bd0557d9ab68a02216c252ab20aaec2e4efd4e)) + - Count newlines (for error display) in multi-line values as well ([`1ea919d`](https://github.com/Byron/gitoxide/commit/1ea919d5ff81ab7b01b8201386ef63c7e081b537)) + - Zero-copy for section names ([`25b9760`](https://github.com/Byron/gitoxide/commit/25b9760f9a6a79c6e28393f032150e37d5ae831e)) + - Prepare for copy-on-write subsections ([`7474997`](https://github.com/Byron/gitoxide/commit/7474997216df2616a034fb9adc0938590f3ab7ed)) + - Another normalization case ([`637fe8f`](https://github.com/Byron/gitoxide/commit/637fe8fca2ce36e07ad671a4454da512b709045c)) + - Allow backspaces in value parser ([`199e546`](https://github.com/Byron/gitoxide/commit/199e5461cb85b11ce0b9a0e727fab40a49b78456)) + - Another failing test pointing at issues with normalization/escaping in parser ([`3c29321`](https://github.com/Byron/gitoxide/commit/3c2932167aa45a89974be79123932bc964fe3ea9)) + - Found failing test with complex multi-line value ([`117401d`](https://github.com/Byron/gitoxide/commit/117401ddb9dea1d78b867ddbafe57c2b37ec10f4)) + - Review `git-config::File` docs and rename some internal symbols ([`5a8b111`](https://github.com/Byron/gitoxide/commit/5a8b111b9a3bba2c01d7d5e32fc58fd8a64b81ad)) + - More correctness for sub-section parsing ([`910af94`](https://github.com/Byron/gitoxide/commit/910af94fe11bc6e1c270c5512af9124f8a2e0049)) + - Reduce top-level docs ([`cdfb13f`](https://github.com/Byron/gitoxide/commit/cdfb13f5984c92c8e7f234e7751b66930291b461)) + - Refactor; remove unnecessary docs ([`c95e0b9`](https://github.com/Byron/gitoxide/commit/c95e0b9331282e029ef6188880d11a892ed1b4bf)) + - Assure no important docs are missed ([`f5026fb`](https://github.com/Byron/gitoxide/commit/f5026fb3b64bccf26bc8d5a74dbc5e89b98d9959)) + - Filtering supportort for `parse::Events`. ([`6ba2f80`](https://github.com/Byron/gitoxide/commit/6ba2f8060768978ad7204e162fb2253ca8843879)) + - Deduplicate events instantiation ([`ead757c`](https://github.com/Byron/gitoxide/commit/ead757c2a4b737d2f617cf23c370e2ca5c46b08b)) + - Unclutter lifetime declarations ([`e571fdb`](https://github.com/Byron/gitoxide/commit/e571fdb4630ff373ece02efcd963724c05978ede)) + - Remove redundant documentation about errors ([`183c7ae`](https://github.com/Byron/gitoxide/commit/183c7ae0d5f44bb468954a7ad18cc02a01d717bc)) + - Adjust to changes in `git-config` ([`c52cb95`](https://github.com/Byron/gitoxide/commit/c52cb958f85b533e791ec6b38166a9d819f12dd4)) + - Remove `parse::Events::from_path` and `File::at` ([`14149ee`](https://github.com/Byron/gitoxide/commit/14149eea54e2e8a25ac0ccdb2f6efe624f6eaa22)) + - Try to strike a balance between allocations and memory footprint ([`52bd1e7`](https://github.com/Byron/gitoxide/commit/52bd1e7455d2b09811ea0ac5140c3693d3c1e1f7)) + - Allocation-free parsing as callback is passed through ([`ed00e22`](https://github.com/Byron/gitoxide/commit/ed00e22cbdfea1d69d1d4c2b829effc26b493185)) + - Foundation for allocation free (and smallvec free) parsing ([`307c1af`](https://github.com/Byron/gitoxide/commit/307c1afebfba952a4931a69796686b8a998c4cd9)) + - Slim down API surface of `parse::Events`. ([`73adcee`](https://github.com/Byron/gitoxide/commit/73adceeae12270c0d470d4b7271c1fd6089d5c2d)) + - Remove `File::new()` method in favor of `File::default()`. ([`2e47167`](https://github.com/Byron/gitoxide/commit/2e47167e4a963743494b2df6b0c15800cb876dd0)) + - A greatly simplified Events->File conversion ([`c5c4398`](https://github.com/Byron/gitoxide/commit/c5c4398a56d4300c83c5be2ba66664bd11f49d5e)) + - Fix docs ([`5022be3`](https://github.com/Byron/gitoxide/commit/5022be3bb7fa54c97e5110f74aaded9e2f1b6ca5)) + - About 30% faster parsing due to doing no less allocations for section events ([`050d0f0`](https://github.com/Byron/gitoxide/commit/050d0f0dee9a64597855e85417460f6e84672b02)) + - Allocation-free fuzzing, with optimized footprints ([`2e149b9`](https://github.com/Byron/gitoxide/commit/2e149b982ec57689c161924dd1d0b22c4fcb681f)) + - Allocation-free sections ([`d3a0c53`](https://github.com/Byron/gitoxide/commit/d3a0c53864ccc9f8d2851d06f0154b9e8f9bcda7)) + - Allocation-free frontmatter ([`6c3f326`](https://github.com/Byron/gitoxide/commit/6c3f3264911042e88afa0819414eb543a3626d11)) + - Remove last duplicate of top-level parse function ([`cd7a21f`](https://github.com/Byron/gitoxide/commit/cd7a21f8381385833f5353925dc57c05c07e718d)) + - Workaround lack of GAT! ([`4fb327c`](https://github.com/Byron/gitoxide/commit/4fb327c247f1c0260cb3a3443d81063b71e87fe4)) + - Remove duplication of top-level parser ([`0f5c99b`](https://github.com/Byron/gitoxide/commit/0f5c99bffdb61e4665e83472275c5c8b0383650b)) + - A minimally invasive sketch of a parse Delegate ([`5958ffb`](https://github.com/Byron/gitoxide/commit/5958ffbfec7724c1a47be8db210df03cf54c9374)) + - Fix docs ([`2186456`](https://github.com/Byron/gitoxide/commit/218645618429258e48cb0fdb2bbfba3daa32ee2d)) + - Fix fuzz crash in parser ([`86e1a76`](https://github.com/Byron/gitoxide/commit/86e1a76484be50f83d06d6c8a176107f8cb3dea6)) + - Rename `parse::event::List` to `parse::Events` ([`ea67650`](https://github.com/Byron/gitoxide/commit/ea6765093b5475912ba1aa81d4440cbf5dd49fb6)) + - Rename `parse::State` to `parse::event::List` ([`89f5fca`](https://github.com/Byron/gitoxide/commit/89f5fca843d999c5bea35fb3fe2a03dc3588f74e)) + - Update fuzz instructions and make it work ([`19300d5`](https://github.com/Byron/gitoxide/commit/19300d5f37c201aba921a6bff9760996fec2108e)) + - Improve normalization; assure no extra copies are made on query. ([`4a01d98`](https://github.com/Byron/gitoxide/commit/4a01d983f54a7713dea523f6032cbf5bb2b9dde8)) + - Refactor; assure `normalize` doesn't copy unnecessarily ([`ce069ca`](https://github.com/Byron/gitoxide/commit/ce069ca0b6b44cd734f4d8b4525916d1ddb0de0b)) + - Normalize values in all the right places ([`91ba2dd`](https://github.com/Byron/gitoxide/commit/91ba2ddcd3de63aa22dc6e863b26ce1893a36995)) + - Avoid unnecessary clones ([`e684488`](https://github.com/Byron/gitoxide/commit/e68448831a94574ee3ca2fa36788f603c91d57a0)) + - Adapt to changes in `git-config` ([`363a826`](https://github.com/Byron/gitoxide/commit/363a826144ad59518b5c1a3dbbc82d04e4fc062d)) + - Move `value::*` into the crate root, except for `Error` and `normalize_*()`. ([`3cdb089`](https://github.com/Byron/gitoxide/commit/3cdb0890b71e62cfa92b1ed1760c88cb547ec729)) + - Rename `value::parse::Error` to `value::Error`. ([`748d921`](https://github.com/Byron/gitoxide/commit/748d921efd7469d5c19e40ddcb9099e2462e3bbc)) + - Rename `value::TrueVariant` to `value::boolean::True` ([`7e8a225`](https://github.com/Byron/gitoxide/commit/7e8a22590297f2f4aab76b53be512353637fb651)) + - Rename `IntegerSuffix` to `integer::Suffix` ([`8bcaec0`](https://github.com/Byron/gitoxide/commit/8bcaec0599cf085a73b344f4f53fc023f6e31430)) + - Rename `value::Color(Attribute|Value)` to `value::color::Attribute` and `value::color::Name`. ([`d085037`](https://github.com/Byron/gitoxide/commit/d085037ad9c067af7ce3ba3ab6e5d5ddb45b4057)) + - Refactor ([`a0f7f44`](https://github.com/Byron/gitoxide/commit/a0f7f44c4fca20d3c9b95a3fafe65cef84c760e7)) + - Refactor ([`0845c84`](https://github.com/Byron/gitoxide/commit/0845c84b6f694d97519d5f86a97bca49739df8bf)) + - Keep str in value API ([`ef5b48c`](https://github.com/Byron/gitoxide/commit/ef5b48c71e0e78fa602699a2f8ca8563c10455c4)) + - Keep BStr even though str could be used. ([`aeca6cc`](https://github.com/Byron/gitoxide/commit/aeca6cce7b4cfe67b18cd80abb600f1271ad6057)) + - Turn `parse::ParseOrIoError` into `parse::state::from_path::Error` ([`a0f6252`](https://github.com/Byron/gitoxide/commit/a0f6252343a62b0b55eef02888ac00c09100687a)) + - Rename `parse::ParsedComment` into `parse::Comment` ([`b6b31e9`](https://github.com/Byron/gitoxide/commit/b6b31e9c8dd8b3dc4860431069bb1cf5eacd1702)) + - Allocation-free hashing for section keys and names ([`44d0061`](https://github.com/Byron/gitoxide/commit/44d00611178a4e2f6a080574c41355a50b79b181)) + - Allocation-free case-inequality tests for section keys and names ([`94608db`](https://github.com/Byron/gitoxide/commit/94608db648cd717af43a97785ea842bc75361b7e)) + - Rename `parse::Section*` related types. ([`239cbfb`](https://github.com/Byron/gitoxide/commit/239cbfb450a8cddfc5bec1de21f3dc54fab914ce)) + - Adjustments required due to changed in `git-config` ([`41bfd3b`](https://github.com/Byron/gitoxide/commit/41bfd3b4122e37370d268608b60cb00a671a8879)) + - Rename `parse::Parser` to `parse::State`. ([`60af4c9`](https://github.com/Byron/gitoxide/commit/60af4c9ecb1b99f21df0e8facc33e5f6fc70c424)) + - Rename `parser` module to `parse` ([`3724850`](https://github.com/Byron/gitoxide/commit/3724850e0411f1f76e52c6c767fd8cebe8aea0f6)) + - Fix docs ([`b05aed1`](https://github.com/Byron/gitoxide/commit/b05aed1cfc15a2e29d7796bad4c9a6d4019f4353)) + - Refactor ([`8bd9cd6`](https://github.com/Byron/gitoxide/commit/8bd9cd695d608d05859d8bff4033883e71ce7caa)) + - Refactor ([`90dd2ce`](https://github.com/Byron/gitoxide/commit/90dd2cec8ea88980365bfd08a16614d145e87095)) + - Fix docs ([`0d1be2b`](https://github.com/Byron/gitoxide/commit/0d1be2b893574f2a9d4ba35ac4f2b3da710d4b03)) + - Rename `normalize_cow()` to `normalize()` and move all `normalize*` functions from `values` to the `value` module ([`58b2215`](https://github.com/Byron/gitoxide/commit/58b22152a0295998935abb43563e9096589ef53e)) + - Documentation for feature flags ([`26e4a9c`](https://github.com/Byron/gitoxide/commit/26e4a9c83af7550eab1acaf0256099774be97965)) + - `serde1` feature to add limited serde support ([`5a8f242`](https://github.com/Byron/gitoxide/commit/5a8f242ee98793e2467e7bc9806f8780b9d320ce)) + - Remove unused serde feature ([`66a8237`](https://github.com/Byron/gitoxide/commit/66a8237ff284c2cf7f80cc909c7b613b599e1358)) + - Move `Path` from `values` to `value` module ([`767bedc`](https://github.com/Byron/gitoxide/commit/767bedccdae1f3e6faf853d59ecf884a06cc3827)) + - Move `Boolean` and `String` from `values` into `value` module ([`6033f3f`](https://github.com/Byron/gitoxide/commit/6033f3f93d2356399a661567353a83a044662699)) + - Move `values::Integer` into `value` module ([`d4444e1`](https://github.com/Byron/gitoxide/commit/d4444e18042891b0fe5b9c6e6813fed26df6c560)) + - Move `Color` to own `value` module ([`38f3117`](https://github.com/Byron/gitoxide/commit/38f31174e8c117af675cdfbc21926133b821ec38)) + - Make symlink tests so that they test real-path conversion ([`d4fbf2e`](https://github.com/Byron/gitoxide/commit/d4fbf2ea71ee1f285c195dd00bfa4e21bf429922)) + - Adjustments due to breaking changes in `git_path` ([`4420ae9`](https://github.com/Byron/gitoxide/commit/4420ae932d5b20a9662a6d36353a27111b5cd672)) + - A test to validate relative includepaths aren't valid for includeIf ([`7d27dd5`](https://github.com/Byron/gitoxide/commit/7d27dd5e3558a22865e0c9159d269577431097f3)) + - Reuse the initialized environment for a little speed ([`6001613`](https://github.com/Byron/gitoxide/commit/600161324edc370707613841ce9228320c700bf6)) + - Also test against git baseline ([`adcddb0`](https://github.com/Byron/gitoxide/commit/adcddb0056c14302f0133de251fa07e877b6f509)) + - Refactor ([`0229e25`](https://github.com/Byron/gitoxide/commit/0229e2583ed7beccaf59dc0c82893c5b67c285dd)) + - Prevent race when calling `git` around `GIT_CONFIG_*` env vars ([`53efbf5`](https://github.com/Byron/gitoxide/commit/53efbf54364c373426a7790c28c74c787670877a)) + - Remove duplicate gitdir tests that don't have a baseline ([`5c71394`](https://github.com/Byron/gitoxide/commit/5c713946b1f35675bacb27bd5392addf25010942)) + - Remove unmotivated forward-slash conversion ([`3af09e5`](https://github.com/Byron/gitoxide/commit/3af09e5800648df87cdaf22191dd4d1dc4b278a3)) + - Improved slash/backslash handling on windows ([`a3b7828`](https://github.com/Byron/gitoxide/commit/a3b7828e8bf9d90775f10b0d996fc7ad82f92466)) + - Fix build warnings on windows ([`9d48b2f`](https://github.com/Byron/gitoxide/commit/9d48b2f51777de37cc996ad54261f2d20f417901)) + - Fix windows test ([`a922f0a`](https://github.com/Byron/gitoxide/commit/a922f0a817d290ef4a539bbf99238a4f96d443f9)) + - Refactor ([`d76aee2`](https://github.com/Byron/gitoxide/commit/d76aee22498cb980ab0b53295a2e51af04a8cb7c)) + - Conforming subsection parsing handling backslashes like git ([`6366148`](https://github.com/Byron/gitoxide/commit/6366148f538ee03314dd866e083157de810d4ad4)) + - Only copy pattern if required ([`b3a752a`](https://github.com/Byron/gitoxide/commit/b3a752a0a873cf9d685e1893c8d35255d7f7323a)) + - Refactor ([`92fe564`](https://github.com/Byron/gitoxide/commit/92fe56486c349a4b08bcefa3e3355c591e281afb)) + - Remove untested error case in integger parsing ([`2b21a35`](https://github.com/Byron/gitoxide/commit/2b21a35e1ba31caea227515ddebc7608cdcca245)) + - Validate underflow as well ([`83eda34`](https://github.com/Byron/gitoxide/commit/83eda3443a1b64ff7bc672fbfe16e3a69def1c6d)) + - Case-insensitive integer suffix handling ([`9034bd4`](https://github.com/Byron/gitoxide/commit/9034bd45bba0aa7c6c5691c2e592c389949dd5d6)) + - Refactor tests ([`f943d2a`](https://github.com/Byron/gitoxide/commit/f943d2aeb0773752adbb68d731305586ab2ce686)) + - Update changelog prior to release ([`1d07934`](https://github.com/Byron/gitoxide/commit/1d079346e789b0acc9a4bdf7577b21c1c37b6106)) + - Minor refactor of tests ([`ebe551f`](https://github.com/Byron/gitoxide/commit/ebe551fc16a98d4101799b0d657b42f445f5b16e)) + - Refactor ([`845fe37`](https://github.com/Byron/gitoxide/commit/845fe373bf25de497f01209f4bed5132cc8eae65)) + - Refactor include path recursion logic ([`2862a07`](https://github.com/Byron/gitoxide/commit/2862a0718e3bc03e4edda60f1b359dd6068e8d1d)) + - Ignore subsections when resolving include.path keys ([`baa300f`](https://github.com/Byron/gitoxide/commit/baa300f844eadf8db6ca5503a0b426235d4ee6fb)) + - Assure `from_env()` include paths only use paths of the correct key ([`0d84ce8`](https://github.com/Byron/gitoxide/commit/0d84ce8b229bcbbaa7ee1b3682bd3f374e803dff)) + - Remove unnecessary doc comments; remove unused field in Options ([`e94ded4`](https://github.com/Byron/gitoxide/commit/e94ded49d8913bd74aff556ee745e83c5dffc3ac)) + - Add TODO to not forget reworking the 'fs' module ([`0b032e4`](https://github.com/Byron/gitoxide/commit/0b032e44c1c124c80039e48cdf539e2bda68607c)) + - Use the same BOM bytes as in git-attributes ([`7204755`](https://github.com/Byron/gitoxide/commit/7204755a4e800dfc58cc667f4e751359badf548b)) + - Refactor ([`85be984`](https://github.com/Byron/gitoxide/commit/85be98437be80d8f79fbfbc032972e4395f19ef0)) + - Add from_paths::Options::default(); minor refactor ([`bcd038c`](https://github.com/Byron/gitoxide/commit/bcd038cccc197cca9012db268dd7502d05c88369)) + - Implement include.path support ([`a392988`](https://github.com/Byron/gitoxide/commit/a3929880e1639eba448aec15333dfaf08ac2dd28)) + - Fix docs ([`3e7ef3e`](https://github.com/Byron/gitoxide/commit/3e7ef3e6bb5915126da5486ef627e4edf6a727ff)) + - More descriptive test names ([`049b243`](https://github.com/Byron/gitoxide/commit/049b2434dfbc97fa5734d852ebc8d07b18265e8a)) + - Turn PathError into path::interpolate::Error; refactor ([`27085e0`](https://github.com/Byron/gitoxide/commit/27085e0e7a1d5067cbc5a8083953446bc6926c5d)) + - Work with std::path::* during interpolation ([`f0ff687`](https://github.com/Byron/gitoxide/commit/f0ff6879d0453be2fa2700f5a2432c3a5c830c31)) + - Fix build ([`f6d9693`](https://github.com/Byron/gitoxide/commit/f6d969370b8ef05b3b29983dcd9f6fa11d6225f2)) + - Make `Path::interpolate()` more useful by returning an actual `PathBuf` ([`86aa7b3`](https://github.com/Byron/gitoxide/commit/86aa7b3a98f933d9eff377fc426f37a22bf473be)) + - Don't interpolate on Path creation due to missing context ([`a071ce8`](https://github.com/Byron/gitoxide/commit/a071ce8f49cd70802776effbd25777a4e65d036c)) + - Add AsRef and Deref for values::Path; additional assertions ([`0666a35`](https://github.com/Byron/gitoxide/commit/0666a358b3b7aadda504979e543cc2058b478bfe)) + - Add `git_config::values::Path` for a typesafe git path ([`e4d6685`](https://github.com/Byron/gitoxide/commit/e4d6685064ad2b433f8acd3a74b320bf0169a994)) + * **[#386](https://github.com/Byron/gitoxide/issues/386)** + - Sketch `Permissions` for git-config ([`8443330`](https://github.com/Byron/gitoxide/commit/8443330b051c109742fe55928e2afd36fc0172fd)) + * **[#404](https://github.com/Byron/gitoxide/issues/404)** + - Add test to clarify underscores in sections headers aren't allowed ([`47079d4`](https://github.com/Byron/gitoxide/commit/47079d470e44b1adf515ae4df2bed945b7e91108)) + * **[#436](https://github.com/Byron/gitoxide/issues/436)** + - Remove outdated examples ([`cb9529e`](https://github.com/Byron/gitoxide/commit/cb9529e18b222b9fd9f8c1bb0dba8038a6ea1d4b)) + * **[#450](https://github.com/Byron/gitoxide/issues/450)** + - `File::boolean()` now correctly handles implicit bools across sections. ([`839f776`](https://github.com/Byron/gitoxide/commit/839f776454d7a8522c0f2887c90fc41b20576bd9)) + - Be sure to globally order section ids when traversed by name. ([`21a5229`](https://github.com/Byron/gitoxide/commit/21a522902257efb82eeea277d31e0b5ca67d6ef4)) + - `file::SectionMut::push()` now properly creates empty values. ([`7d92c61`](https://github.com/Byron/gitoxide/commit/7d92c61abc2c54f4a64c35b37bcd1843a8d1da9c)) + - Refactor ([`041ede9`](https://github.com/Byron/gitoxide/commit/041ede9fe1581a733ea47033ba58265c2ef88bcd)) + - Adapt to changes in `git-ref` ([`d40beb3`](https://github.com/Byron/gitoxide/commit/d40beb3b5744139b56ed68de4caa62a242df2d3a)) + - Add `Kind::GitInstallation` for a way to obtain special git-installation configuration paths. ([`27fb1ce`](https://github.com/Byron/gitoxide/commit/27fb1ce27d2985eb1ee8bee5fffaf759902571fb)) + - Upgrade `bstr` to `1.0.1` ([`99905ba`](https://github.com/Byron/gitoxide/commit/99905bacace8aed42b16d43f0f04cae996cb971c)) + - `git-config` now uses `git-config-value`. ([`5ad2965`](https://github.com/Byron/gitoxide/commit/5ad296577d837b0699b4718fa2be3d0978c4e342)) + - Port tests over as well ([`9b28df2`](https://github.com/Byron/gitoxide/commit/9b28df22b858b6f1c9ca9b07a5a1c0cc300b50f0)) + - Properly handle boolean values such that `a` is true but `a=` is false. ([`08c50a4`](https://github.com/Byron/gitoxide/commit/08c50a47fa901457194539c7db74ad47ab2f8b60)) + - Fix config tests on windows ([`7a871c2`](https://github.com/Byron/gitoxide/commit/7a871c2a5691ae973804ff66af9608070733b366)) + - Keep track of a severe limitation and prepare tests for fixing it. ([`7c58516`](https://github.com/Byron/gitoxide/commit/7c585162454c476fe93f032c8a2329cffd7c054f)) + - `file::SectionMut::push()` now supports values without key-value separator. ([`b6cd6ac`](https://github.com/Byron/gitoxide/commit/b6cd6ace412b0c0df4bacbe7ed7ef6608f27909c)) + - Refactor ([`5415449`](https://github.com/Byron/gitoxide/commit/541544953c52ff3df8c8e21f6aca366840faca3e)) + - `File::set_raw_value_filter()` to set values only in sections passing a filter. ([`9937d0e`](https://github.com/Byron/gitoxide/commit/9937d0e00df3a523484c7ae2850be2712a1a4c9a)) + - `File::section_mut_or_create_new_filter()` to allow chosing which sections to add values to. ([`17455c9`](https://github.com/Byron/gitoxide/commit/17455c9d93ad38bfee2560f5a4e60324dee3b4e5)) + - Add `File::set_raw_value()` to unconditionally set single values, and make the value itself easier to provide. ([`2b2357e`](https://github.com/Byron/gitoxide/commit/2b2357e9cc54539e0dbe7c0e22802f2b884160d8)) + - `File::section_mut_or_create_new(…)` to obtain an existing or new section for mutation. ([`5902f54`](https://github.com/Byron/gitoxide/commit/5902f54b93101a6290fcf89f9f13fdbea3678e00)) + - `File::set_raw_[multi_]value()` to `::set_existing_raw_[multi_]value`. ([`69ec594`](https://github.com/Byron/gitoxide/commit/69ec5940d3f37eb4dace8f1ed7616b5988984d15)) + * **[#470](https://github.com/Byron/gitoxide/issues/470)** + - Update changelogs prior to release ([`caa7a1b`](https://github.com/Byron/gitoxide/commit/caa7a1bdef74d7d3166a7e38127a59f5ab3cfbdd)) + * **[#482](https://github.com/Byron/gitoxide/issues/482)** + - Refactor ([`1ee9918`](https://github.com/Byron/gitoxide/commit/1ee991847a5adeaaeb6e80ae626c28b0ba89e0af)) + * **[#691](https://github.com/Byron/gitoxide/issues/691)** + - Set `rust-version` to 1.64 ([`55066ce`](https://github.com/Byron/gitoxide/commit/55066ce5fd71209abb5d84da2998b903504584bb)) + * **[#737](https://github.com/Byron/gitoxide/issues/737)** + - Add `File::write_to_filter()` to have a filter select the section to write. ([`4b424ab`](https://github.com/Byron/gitoxide/commit/4b424ab73d20833deb0c674eeeeaaa8cc2358932)) + - Add `File::set_meta()` to affect what metadata new sections receive. ([`3fa86e1`](https://github.com/Byron/gitoxide/commit/3fa86e1d01a75688cd8948a39ba415bcebfef02d)) + - `File::section(...)` could panic due to invlid expectation. ([`8836f5a`](https://github.com/Byron/gitoxide/commit/8836f5ada0078febf8aeb0e6b59d0fc4c588bc28)) + * **[#XXX](https://github.com/Byron/gitoxide/issues/XXX)** + - Prepare changelogs prior to release ([`8c0bca3`](https://github.com/Byron/gitoxide/commit/8c0bca37ff9fbaadbe55561fb2b0d649980c95b1)) + * **Uncategorized** + - Release gix-config v0.16.1, gix-command v0.2.3, gix-prompt v0.3.2, gix-url v0.13.2, gix-credentials v0.9.1, gix-diff v0.26.1, gix-discover v0.13.0, gix-hashtable v0.1.1, gix-bitmap v0.2.1, gix-traverse v0.22.1, gix-index v0.12.3, gix-mailmap v0.9.2, gix-chunk v0.4.1, gix-pack v0.30.2, gix-odb v0.40.2, gix-packetline v0.14.2, gix-transport v0.25.4, gix-protocol v0.26.3, gix-revision v0.10.3, gix-refspec v0.7.2, gix-worktree v0.12.2, gix v0.36.0 ([`41d57b9`](https://github.com/Byron/gitoxide/commit/41d57b98964094fc1528adb09f69ca824229bf25)) + - Release gix-attributes v0.8.2, gix-config-value v0.10.1, gix-tempfile v3.0.2, gix-lock v3.0.2, gix-validate v0.7.2, gix-object v0.26.1, gix-ref v0.24.0, gix-sec v0.6.2, gix-config v0.16.1, gix-command v0.2.3, gix-prompt v0.3.2, gix-url v0.13.2, gix-credentials v0.9.1, gix-diff v0.26.1, gix-discover v0.13.0, gix-hashtable v0.1.1, gix-bitmap v0.2.1, gix-traverse v0.22.1, gix-index v0.12.3, gix-mailmap v0.9.2, gix-chunk v0.4.1, gix-pack v0.30.2, gix-odb v0.40.2, gix-packetline v0.14.2, gix-transport v0.25.4, gix-protocol v0.26.3, gix-revision v0.10.3, gix-refspec v0.7.2, gix-worktree v0.12.2, gix v0.36.0 ([`e313112`](https://github.com/Byron/gitoxide/commit/e31311257bd138b52042dea5fc40c3abab7f269b)) + - Release gix-features v0.26.4, gix-actor v0.17.1, gix-glob v0.5.3, gix-path v0.7.1, gix-quote v0.4.1, gix-attributes v0.8.2, gix-config-value v0.10.1, gix-tempfile v3.0.2, gix-lock v3.0.2, gix-validate v0.7.2, gix-object v0.26.1, gix-ref v0.24.0, gix-sec v0.6.2, gix-config v0.16.1, gix-command v0.2.3, gix-prompt v0.3.2, gix-url v0.13.2, gix-credentials v0.9.1, gix-diff v0.26.1, gix-discover v0.13.0, gix-hashtable v0.1.1, gix-bitmap v0.2.1, gix-traverse v0.22.1, gix-index v0.12.3, gix-mailmap v0.9.2, gix-chunk v0.4.1, gix-pack v0.30.2, gix-odb v0.40.2, gix-packetline v0.14.2, gix-transport v0.25.4, gix-protocol v0.26.3, gix-revision v0.10.3, gix-refspec v0.7.2, gix-worktree v0.12.2, gix v0.36.0 ([`6efd0d3`](https://github.com/Byron/gitoxide/commit/6efd0d31fbeca31ab7319aa2ac97bb31dc4ce055)) + - Release gix-date v0.4.2, gix-hash v0.10.2, gix-features v0.26.4, gix-actor v0.17.1, gix-glob v0.5.3, gix-path v0.7.1, gix-quote v0.4.1, gix-attributes v0.8.2, gix-config-value v0.10.1, gix-tempfile v3.0.2, gix-lock v3.0.2, gix-validate v0.7.2, gix-object v0.26.1, gix-ref v0.24.0, gix-sec v0.6.2, gix-config v0.16.1, gix-command v0.2.3, gix-prompt v0.3.2, gix-url v0.13.2, gix-credentials v0.9.1, gix-diff v0.26.1, gix-discover v0.13.0, gix-hashtable v0.1.1, gix-bitmap v0.2.1, gix-traverse v0.22.1, gix-index v0.12.3, gix-mailmap v0.9.2, gix-chunk v0.4.1, gix-pack v0.30.2, gix-odb v0.40.2, gix-packetline v0.14.2, gix-transport v0.25.4, gix-protocol v0.26.3, gix-revision v0.10.3, gix-refspec v0.7.2, gix-worktree v0.12.2, gix v0.36.0 ([`6ccc88a`](https://github.com/Byron/gitoxide/commit/6ccc88a8e4a56973b1a358cf72dc012ee3c75d56)) + - Merge branch 'rename-crates' into inform-about-gix-rename ([`c9275b9`](https://github.com/Byron/gitoxide/commit/c9275b99ea43949306d93775d9d78c98fb86cfb1)) + - Rename `git-testtools` to `gix-testtools` ([`b65c33d`](https://github.com/Byron/gitoxide/commit/b65c33d256cfed65d11adeff41132e3e58754089)) + - Adjust to renaming of `git-pack` to `gix-pack` ([`1ee81ad`](https://github.com/Byron/gitoxide/commit/1ee81ad310285ee4aa118118a2be3810dbace574)) + - Adjust to renaming of `git-odb` to `gix-odb` ([`476e2ad`](https://github.com/Byron/gitoxide/commit/476e2ad1a64e9e3f0d7c8651d5bcbee36cd78241)) + - Adjust to renaming of `git-index` to `gix-index` ([`86db5e0`](https://github.com/Byron/gitoxide/commit/86db5e09fc58ce66b252dc13b8d7e2c48e4d5062)) + - Adjust to renaming of `git-diff` to `gix-diff` ([`49a163e`](https://github.com/Byron/gitoxide/commit/49a163ec8b18f0e5fcd05a315de16d5d8be7650e)) + - Adjust to renaming of `git-commitgraph` to `gix-commitgraph` ([`f1dd0a3`](https://github.com/Byron/gitoxide/commit/f1dd0a3366e31259af029da73228e8af2f414244)) + - Adjust to renaming of `git-mailmap` to `gix-mailmap` ([`2e28c56`](https://github.com/Byron/gitoxide/commit/2e28c56bb9f70de6f97439818118d3a25859698f)) + - Adjust to renaming of `git-discover` to `gix-discover` ([`53adfe1`](https://github.com/Byron/gitoxide/commit/53adfe1c34e9ea3b27067a97b5e7ac80b351c441)) + - Adjust to renaming of `git-lfs` to `gix-lfs` ([`b9225c8`](https://github.com/Byron/gitoxide/commit/b9225c830daf1388484ee7e05f727990fdeff43c)) + - Adjust to renaming of `git-chunk` to `gix-chunk` ([`59194e3`](https://github.com/Byron/gitoxide/commit/59194e3a07853eae0624ebc4907478d1de4f7599)) + - Adjust to renaming of `git-bitmap` to `gix-bitmap` ([`75f2a07`](https://github.com/Byron/gitoxide/commit/75f2a079b17489f62bc43e1f1d932307375c4f9d)) + - Adjust to renaming for `git-protocol` to `gix-protocol` ([`823795a`](https://github.com/Byron/gitoxide/commit/823795addea3810243cab7936cd8ec0137cbc224)) + - Adjust to renaming of `git-refspec` to `gix-refspec` ([`c958802`](https://github.com/Byron/gitoxide/commit/c9588020561577736faa065e7e5b5bb486ca8fe1)) + - Adjust to renaming of `git-revision` to `gix-revision` ([`ee0ee84`](https://github.com/Byron/gitoxide/commit/ee0ee84607c2ffe11ee75f27a31903db68afed02)) + - Adjust to renaming of `git-transport` to `gix-transport` ([`b2ccf71`](https://github.com/Byron/gitoxide/commit/b2ccf716dc4425bb96651d4d58806a3cc2da219e)) + - Adjust to renaming of `git-credentials` to `gix-credentials` ([`6b18abc`](https://github.com/Byron/gitoxide/commit/6b18abcf2856f02ab938d535a65e51ac282bf94a)) + - Adjust to renaming of `git-prompt` to `gix-prompt` ([`6a4654e`](https://github.com/Byron/gitoxide/commit/6a4654e0d10ab773dd219cb4b731c0fc1471c36d)) + - Adjust to renaming of `git-command` to `gix-command` ([`d26b8e0`](https://github.com/Byron/gitoxide/commit/d26b8e046496894ae06b0bbfdba77196976cd975)) + - Adjust to renaming of `git-packetline` to `gix-packetline` ([`5cbd22c`](https://github.com/Byron/gitoxide/commit/5cbd22cf42efb760058561c6c3bbcd4dab8c8be1)) + - Adjust to renaming of `git-worktree` to `gix-worktree` ([`73a1282`](https://github.com/Byron/gitoxide/commit/73a12821b3d9b66ec1714d07dd27eb7a73e3a544)) + - Adjust to renamining of `git-hashtable` to `gix-hashtable` ([`26a0c98`](https://github.com/Byron/gitoxide/commit/26a0c98d0a389b03e3dc7bfc758b37155e285244)) + - Adjust to renamining of `git-worktree` to `gix-worktree` ([`108bb1a`](https://github.com/Byron/gitoxide/commit/108bb1a634f4828853fb590e9fc125f79441dd38)) + - Adjust to renaming of `git-url` to `gix-url` ([`b50817a`](https://github.com/Byron/gitoxide/commit/b50817aadb143e19f61f64e19b19ec1107d980c6)) + - Adjust to renaming of `git-date` to `gix-date` ([`9a79ff2`](https://github.com/Byron/gitoxide/commit/9a79ff2d5cc74c1efad9f41e21095ae498cce00b)) + - Adjust to renamining of `git-attributes` to `gix-attributes` ([`4a8b3b8`](https://github.com/Byron/gitoxide/commit/4a8b3b812ac26f2a2aee8ce8ca81591273383c84)) + - Adjust to renaminig of `git-quote` to `gix-quote` ([`648025b`](https://github.com/Byron/gitoxide/commit/648025b7ca94411fdd0d90c53e5faede5fde6c8d)) + - Adjust to renaming of `git-config` to `gix-config` ([`3a861c8`](https://github.com/Byron/gitoxide/commit/3a861c8f049f6502d3bcbdac752659aa1aeda46a)) + - Rename `git-config` to `gix-config` ([`dd6d63e`](https://github.com/Byron/gitoxide/commit/dd6d63e1decb91836b489c94c19b0283b50a05de)) + - Adjust to renaming of `git-ref` to `gix-ref` ([`1f5f695`](https://github.com/Byron/gitoxide/commit/1f5f695407b034377d94b172465ff573562b3fc3)) + - Adjust to renaming of `git-lock` to `gix-lock` ([`2028e78`](https://github.com/Byron/gitoxide/commit/2028e7884ae1821edeec81612f501e88e4722b17)) + - Adjust to renaming of `git-tempfile` to `gix-tempfile` ([`b6cc3eb`](https://github.com/Byron/gitoxide/commit/b6cc3ebb5137084a6327af16a7d9364d8f092cc9)) + - Adjust to renaming of `git-object` to `gix-object` ([`fc86a1e`](https://github.com/Byron/gitoxide/commit/fc86a1e710ad7bf076c25cc6f028ddcf1a5a4311)) + - Adjust to renaming of `git-actor` to `gix-actor` ([`4dc9b44`](https://github.com/Byron/gitoxide/commit/4dc9b44dc52f2486ffa2040585c6897c1bf55df4)) + - Adjust to renaming of `git-validate` to `gix-validate` ([`5e40ad0`](https://github.com/Byron/gitoxide/commit/5e40ad078af3d08cbc2ca81ce755c0ed8a065b4f)) + - Adjust to renaming of `git-hash` to `gix-hash` ([`4a9d025`](https://github.com/Byron/gitoxide/commit/4a9d0257110c3efa61d08c8457c4545b200226d1)) + - Adjust to renaming of `git-features` to `gix-features` ([`e2dd68a`](https://github.com/Byron/gitoxide/commit/e2dd68a417aad229e194ff20dbbfd77668096ec6)) + - Adjust to renaming of `git-glob` to `gix-glob` ([`35b2a3a`](https://github.com/Byron/gitoxide/commit/35b2a3acbc8f2a03f151bc0a3863163844e0ca86)) + - Adjust to renaming of `git-sec` to `gix-sec` ([`eabbb92`](https://github.com/Byron/gitoxide/commit/eabbb923bd5a32fc80fa80f96cfdc2ab7bb2ed17)) + - Adapt to renaming of `git-path` to `gix-path` ([`d3bbcfc`](https://github.com/Byron/gitoxide/commit/d3bbcfccad80fc44ea8e7bf819f23adaca06ba2d)) + - Adjust to rename of `git-config-value` to `gix-config-value` ([`622b3e1`](https://github.com/Byron/gitoxide/commit/622b3e1d0bffa0f8db73697960f9712024fac430)) + - Release git-features v0.26.4 ([`109f434`](https://github.com/Byron/gitoxide/commit/109f434e66559a791d541f86876ded8df10766f1)) + - Release git-features v0.26.3 ([`1ecfb7f`](https://github.com/Byron/gitoxide/commit/1ecfb7f8bfb24432690d8f31367488f2e59a642a)) + - Release git-config v0.16.1, git-revision v0.10.3, gix v0.35.0 ([`74390ba`](https://github.com/Byron/gitoxide/commit/74390baf9d177a1abe3c7c35f1d9bc67faba1e97)) + - Prepare changelogs prior to release ([`446f866`](https://github.com/Byron/gitoxide/commit/446f866d146e255ab8302b89f87bf28f2c5f3733)) + - Merge branch 'rename-crates' ([`6461c3d`](https://github.com/Byron/gitoxide/commit/6461c3da4d6daee857606d94294c3f87fc36965a)) + - Rename `git-repository` to `gix` ([`7bed2a9`](https://github.com/Byron/gitoxide/commit/7bed2a96604397fa990f427b1a970ddeb6f09f1c)) + - Release git-date v0.4.2, git-hash v0.10.2, git-features v0.26.2, git-actor v0.17.1, git-glob v0.5.3, git-path v0.7.1, git-quote v0.4.1, git-attributes v0.8.2, git-config-value v0.10.1, git-tempfile v3.0.2, git-lock v3.0.2, git-validate v0.7.2, git-object v0.26.1, git-ref v0.24.0, git-sec v0.6.2, git-config v0.16.0, git-command v0.2.3, git-prompt v0.3.2, git-url v0.13.2, git-credentials v0.9.1, git-diff v0.26.1, git-discover v0.13.0, git-hashtable v0.1.1, git-bitmap v0.2.1, git-traverse v0.22.1, git-index v0.12.3, git-mailmap v0.9.2, git-chunk v0.4.1, git-pack v0.30.2, git-odb v0.40.2, git-packetline v0.14.2, git-transport v0.25.4, git-protocol v0.26.3, git-revision v0.10.2, git-refspec v0.7.2, git-worktree v0.12.2, git-repository v0.34.0, safety bump 3 crates ([`c196d20`](https://github.com/Byron/gitoxide/commit/c196d206d57a310b1ce974a1cf0e7e6d6db5c4d6)) + - Prepare changelogs prior to release ([`7c846d2`](https://github.com/Byron/gitoxide/commit/7c846d2102dc767366771925212712ef8cc9bf07)) + - Merge branch 'Lioness100/main' ([`1e544e8`](https://github.com/Byron/gitoxide/commit/1e544e82455bf9ecb5e3c2146280eaf7ecd81f16)) + - Fix typos ([`39ed9ed`](https://github.com/Byron/gitoxide/commit/39ed9eda62b7718d5109135e5ad406fb1fe2978c)) + - Upgrade serial-test to 1.0 ([`9ab3e25`](https://github.com/Byron/gitoxide/commit/9ab3e25e4c1ff6d504a79521cfa9453a7e7e7465)) + - Thanks clippy ([`bac57dd`](https://github.com/Byron/gitoxide/commit/bac57dd05ea2d5a4ee45ef9350fa3f2e19474bc0)) + - Merge branch 'adjustments-for-cargo' ([`7bba270`](https://github.com/Byron/gitoxide/commit/7bba2709488b7eb999b8136dbab03af977241678)) + - Transfer knowledge about configuration and its usage into the type system. ([`2faad43`](https://github.com/Byron/gitoxide/commit/2faad43d11283ff06381c51d2466307cfb8736ff)) + - Break cyclical dev dependencies ([`1fea18f`](https://github.com/Byron/gitoxide/commit/1fea18f5f8b4189a23dc4fa3f041a672f6fbcfb3)) + - Release git-date v0.4.1, git-features v0.26.1, git-glob v0.5.2, git-attributes v0.8.1, git-tempfile v3.0.1, git-ref v0.23.1, git-sec v0.6.1, git-config v0.15.1, git-prompt v0.3.1, git-url v0.13.1, git-discover v0.12.1, git-index v0.12.2, git-mailmap v0.9.1, git-pack v0.30.1, git-odb v0.40.1, git-transport v0.25.3, git-protocol v0.26.2, git-revision v0.10.1, git-refspec v0.7.1, git-worktree v0.12.1, git-repository v0.33.0 ([`5b5b380`](https://github.com/Byron/gitoxide/commit/5b5b3809faa71c658db38b40dfc410224d08a367)) + - Prepare changelogs prior to release ([`93bef97`](https://github.com/Byron/gitoxide/commit/93bef97b3c0c75d4bf7119fdd787516e1efc77bf)) + - Merge branch 'patch-1' ([`b93f0c4`](https://github.com/Byron/gitoxide/commit/b93f0c49fc677b6c19aea332cbfc1445ce475375)) + - Fix underflow ([`4fc214a`](https://github.com/Byron/gitoxide/commit/4fc214a02f8b83f5f5bcefcead0a1ee22c4f8088)) + - Thanks clippy ([`b34c9fe`](https://github.com/Byron/gitoxide/commit/b34c9fe58223862712eacc1cb7353e497a4b9778)) + - Thanks clippy ([`9e04685`](https://github.com/Byron/gitoxide/commit/9e04685dd3f109bfb27663f9dc7c04102e660bf2)) + - Uprgade serial_test to 0.10 ([`8623b5a`](https://github.com/Byron/gitoxide/commit/8623b5a36f3e82cd694f7772798e36656cbfcd0a)) + - Release git-ref v0.23.0, git-config v0.15.0, git-command v0.2.2, git-diff v0.26.0, git-discover v0.12.0, git-mailmap v0.9.0, git-pack v0.30.0, git-odb v0.40.0, git-transport v0.25.2, git-protocol v0.26.1, git-revision v0.10.0, git-refspec v0.7.0, git-worktree v0.12.0, git-repository v0.32.0 ([`ffb5b6a`](https://github.com/Byron/gitoxide/commit/ffb5b6a21cb415315db6fd5294940c7c6deb4538)) + - Prepare changelogs prior to release ([`4381a03`](https://github.com/Byron/gitoxide/commit/4381a03a34c305f31713cce234c2afbf8ac60f01)) + - Merge branch 'signature-resolution' ([`0f7edc1`](https://github.com/Byron/gitoxide/commit/0f7edc1ec87774917c8683dfea2c4989d0762648)) + - Adapt to changes in `git-repository` ([`d9b7df9`](https://github.com/Byron/gitoxide/commit/d9b7df937ef7196068c3e163536f2aa35a38495b)) + - Release git-date v0.4.0, git-actor v0.17.0, git-object v0.26.0, git-traverse v0.22.0, git-index v0.12.0, safety bump 15 crates ([`0e3d0a5`](https://github.com/Byron/gitoxide/commit/0e3d0a56d7e6a60c6578138f2690b4fa54a2072d)) + - Release git-features v0.26.0, git-actor v0.16.0, git-attributes v0.8.0, git-object v0.25.0, git-ref v0.22.0, git-config v0.14.0, git-command v0.2.1, git-url v0.13.0, git-credentials v0.9.0, git-diff v0.25.0, git-discover v0.11.0, git-traverse v0.21.0, git-index v0.11.0, git-mailmap v0.8.0, git-pack v0.29.0, git-odb v0.39.0, git-transport v0.25.0, git-protocol v0.26.0, git-revision v0.9.0, git-refspec v0.6.0, git-worktree v0.11.0, git-repository v0.31.0, safety bump 24 crates ([`5ac9fbe`](https://github.com/Byron/gitoxide/commit/5ac9fbe265a5b61c533a2a6b3abfed2bdf7f89ad)) + - Prepare changelogs prior to release ([`30d8ca1`](https://github.com/Byron/gitoxide/commit/30d8ca19284049dcfbb0de2698cafae1d1a16b0c)) + - Release git-date v0.3.1, git-features v0.25.0, git-actor v0.15.0, git-glob v0.5.1, git-path v0.7.0, git-attributes v0.7.0, git-config-value v0.10.0, git-lock v3.0.1, git-validate v0.7.1, git-object v0.24.0, git-ref v0.21.0, git-sec v0.6.0, git-config v0.13.0, git-prompt v0.3.0, git-url v0.12.0, git-credentials v0.8.0, git-diff v0.24.0, git-discover v0.10.0, git-traverse v0.20.0, git-index v0.10.0, git-mailmap v0.7.0, git-pack v0.28.0, git-odb v0.38.0, git-packetline v0.14.1, git-transport v0.24.0, git-protocol v0.25.0, git-revision v0.8.0, git-refspec v0.5.0, git-worktree v0.10.0, git-repository v0.30.0, safety bump 26 crates ([`e6b9906`](https://github.com/Byron/gitoxide/commit/e6b9906c486b11057936da16ed6e0ec450a0fb83)) + - Prepare chnagelogs prior to git-repository release ([`7114bbb`](https://github.com/Byron/gitoxide/commit/7114bbb6732aa8571d4ab74f28ed3e26e9fbe4d0)) + - Merge branch 'main' into read-split-index ([`c57bdde`](https://github.com/Byron/gitoxide/commit/c57bdde6de37eca9672ea715962bbd02aa3eb055)) + - Merge branch 'adjustments-for-cargo' ([`083909b`](https://github.com/Byron/gitoxide/commit/083909bc7eb902eeee2002034fdb6ed88280dc5c)) + - Adjust to changes in `git-testtools` ([`4eb842c`](https://github.com/Byron/gitoxide/commit/4eb842c7150b980e1c2637217e1f9657a671cea7)) + - Make fmt ([`747008d`](https://github.com/Byron/gitoxide/commit/747008d9d370844574dda94e5bec1648c4deb57e)) + - Merge branch 'main' into http-config ([`6b9632e`](https://github.com/Byron/gitoxide/commit/6b9632e16c416841ffff1b767ee7a6c89b421220)) + - `File::new_section()` and related now returns their `id` as well. ([`2b36d99`](https://github.com/Byron/gitoxide/commit/2b36d99eaf3ed24ce4cb736a3dd48440dc0c73b7)) + - `SectionMut::push_with_comment(key, comment)` to add a new variable with a comment. ([`5b9bffe`](https://github.com/Byron/gitoxide/commit/5b9bffe8a5eec738e892224a7e18f98c8430d8a4)) + - Release git-features v0.24.1, git-actor v0.14.1, git-index v0.9.1 ([`7893502`](https://github.com/Byron/gitoxide/commit/789350208efc9d5fc6f9bc4f113f77f9cb445156)) + - Add the `Source::EnvOverride` to have a place for 'terminal' overrides. ([`e4bf8f0`](https://github.com/Byron/gitoxide/commit/e4bf8f0072e60a7a2df94690c8d0b13b1f3038bb)) + - Thanks clippy ([`10f4f21`](https://github.com/Byron/gitoxide/commit/10f4f2149830734cc551ea96a3d087f07d43fe29)) + - Comfort API like `string_by_key(key)` takes a key like `"remote.origin.url"`, add `section_by_key("remote.origin")` as well. ([`5fa9546`](https://github.com/Byron/gitoxide/commit/5fa95460db843f7dcfe68002b303b8b7649846dd)) + - Subsections are identified as `&BStr` in entire API. ([`0c98ec8`](https://github.com/Byron/gitoxide/commit/0c98ec8fc7d8cc3195472a04fde4a681f620725f)) + - Merge branch 'main' into http-config ([`bcd9654`](https://github.com/Byron/gitoxide/commit/bcd9654e56169799eb706646da6ee1f4ef2021a9)) + - Release git-hash v0.10.0, git-features v0.24.0, git-date v0.3.0, git-actor v0.14.0, git-glob v0.5.0, git-path v0.6.0, git-quote v0.4.0, git-attributes v0.6.0, git-config-value v0.9.0, git-tempfile v3.0.0, git-lock v3.0.0, git-validate v0.7.0, git-object v0.23.0, git-ref v0.20.0, git-sec v0.5.0, git-config v0.12.0, git-command v0.2.0, git-prompt v0.2.0, git-url v0.11.0, git-credentials v0.7.0, git-diff v0.23.0, git-discover v0.9.0, git-bitmap v0.2.0, git-traverse v0.19.0, git-index v0.9.0, git-mailmap v0.6.0, git-chunk v0.4.0, git-pack v0.27.0, git-odb v0.37.0, git-packetline v0.14.0, git-transport v0.23.0, git-protocol v0.24.0, git-revision v0.7.0, git-refspec v0.4.0, git-worktree v0.9.0, git-repository v0.29.0, git-commitgraph v0.11.0, gitoxide-core v0.21.0, gitoxide v0.19.0, safety bump 28 crates ([`b2c301e`](https://github.com/Byron/gitoxide/commit/b2c301ef131ffe1871314e19f387cf10a8d2ac16)) + - Prepare changelogs prior to release ([`e4648f8`](https://github.com/Byron/gitoxide/commit/e4648f827c97e9d13636d1bbdc83dd63436e6e5c)) + - Merge branch 'version2021' ([`0e4462d`](https://github.com/Byron/gitoxide/commit/0e4462df7a5166fe85c23a779462cdca8ee013e8)) + - Read worktree specific configuration of main worktrees. ([`7d7bd02`](https://github.com/Byron/gitoxide/commit/7d7bd02d4e0678565f58c5da83fd1ad88c60e911)) + - Refactor ([`747b9e9`](https://github.com/Byron/gitoxide/commit/747b9e9ee6c467c85a6fd4246ad0fea216176cbc)) + - Upgrade edition to 2021 in most crates. ([`3d8fa8f`](https://github.com/Byron/gitoxide/commit/3d8fa8fef9800b1576beab8a5bc39b821157a5ed)) + - Release git-glob v0.4.2, git-config-value v0.8.2, git-lock v2.2.0, git-ref v0.19.0, git-config v0.11.0, git-discover v0.8.0, git-index v0.8.0, git-transport v0.22.0, git-protocol v0.23.0, git-worktree v0.8.0, git-repository v0.28.0, gitoxide-core v0.20.0, gitoxide v0.18.0, safety bump 9 crates ([`0c253b1`](https://github.com/Byron/gitoxide/commit/0c253b15143dcedfe4c66d64ab1ea6e097030651)) + - Prepare changelogs prior to release ([`fe5721f`](https://github.com/Byron/gitoxide/commit/fe5721f888c64c79fe9a734a9e33b94a282f8d97)) + - Order matters ([`166f349`](https://github.com/Byron/gitoxide/commit/166f349b387c219431e5ef0410d9b1402e58dc09)) + - Update docs ([`aab6a33`](https://github.com/Byron/gitoxide/commit/aab6a3359fea2858e54d38073b8714d61be2c699)) + - Naive approach to loading worktree configs… ([`9d8cb1f`](https://github.com/Byron/gitoxide/commit/9d8cb1f5689cabe5535888b439437581261d9c3b)) + - Release git-features v0.23.1, git-glob v0.4.1, git-config-value v0.8.1, git-tempfile v2.0.6, git-object v0.22.1, git-ref v0.18.0, git-sec v0.4.2, git-config v0.10.0, git-prompt v0.1.1, git-url v0.10.1, git-credentials v0.6.1, git-diff v0.21.0, git-discover v0.7.0, git-index v0.7.0, git-pack v0.25.0, git-odb v0.35.0, git-transport v0.21.1, git-protocol v0.22.0, git-refspec v0.3.1, git-worktree v0.7.0, git-repository v0.26.0, git-commitgraph v0.10.0, gitoxide-core v0.19.0, gitoxide v0.17.0, safety bump 9 crates ([`d071583`](https://github.com/Byron/gitoxide/commit/d071583c5576fdf5f7717765ffed5681792aa81f)) + - Prepare changelogs prior to release ([`423af90`](https://github.com/Byron/gitoxide/commit/423af90c8202d62dc1ea4a76a0df6421d1f0aa06)) + - Merge branch 'main' into write-sparse-index (upgrade to Rust 1.65) ([`5406630`](https://github.com/Byron/gitoxide/commit/5406630466145990b5adbdadb59151036993060d)) + - Thanks clippy ([`04cfa63`](https://github.com/Byron/gitoxide/commit/04cfa635a65ae34ad6d22391f2febd2ca7eabca9)) + - Adjust memory-size expectations to deal with Rust 1.65 and below ([`a93c470`](https://github.com/Byron/gitoxide/commit/a93c4703699ea61a646c82b861c9345715a6c057)) + - Merge branch 'main' into write-sparse-index ([`c4e6849`](https://github.com/Byron/gitoxide/commit/c4e68496c368611ebe17c6693d06c8147c28c717)) + - Make fmt ([`ea2136b`](https://github.com/Byron/gitoxide/commit/ea2136b065979cecb3a1fdbf7b20ed7514128d9a)) + - Merge branch 'gix-clone' ([`def53b3`](https://github.com/Byron/gitoxide/commit/def53b36c3dec26fa78939ab0584fe4ff930909c)) + - Thanks clippy ([`d2f56df`](https://github.com/Byron/gitoxide/commit/d2f56df5405f6c27ebf7d51f33381f2c548433fb)) + - Release git-hash v0.9.11, git-features v0.23.0, git-actor v0.13.0, git-attributes v0.5.0, git-object v0.22.0, git-ref v0.17.0, git-sec v0.4.1, git-config v0.9.0, git-url v0.10.0, git-credentials v0.6.0, git-diff v0.20.0, git-discover v0.6.0, git-traverse v0.18.0, git-index v0.6.0, git-mailmap v0.5.0, git-pack v0.24.0, git-odb v0.34.0, git-packetline v0.13.1, git-transport v0.21.0, git-protocol v0.21.0, git-revision v0.6.0, git-refspec v0.3.0, git-worktree v0.6.0, git-repository v0.25.0, safety bump 24 crates ([`104d922`](https://github.com/Byron/gitoxide/commit/104d922add61ab21c534c24ce8ed37cddf3e275a)) + - Prepare changelogs for release ([`d232567`](https://github.com/Byron/gitoxide/commit/d23256701a95284857dc8d1cb37c7c94cada973c)) + - Merge branch 'fix-smart-release' ([`aa80b60`](https://github.com/Byron/gitoxide/commit/aa80b606e5570f327660cca42ea81581a6e9d5e3)) + - Make fmt ([`7b9c065`](https://github.com/Byron/gitoxide/commit/7b9c06547b75929e3e5bf4240f43c7e9bc7d54e0)) + - Merge branch 'clone' ([`507dc7e`](https://github.com/Byron/gitoxide/commit/507dc7e706cb3c9d89d048b3aff5df239a9b6788)) + - Add `parse::section::header::is_valid_subsection()` function. ([`aa5d66f`](https://github.com/Byron/gitoxide/commit/aa5d66f60bd6c9ef404ebc120b613e0cf055b2c9)) + - Greatly improve whitespace handling when removing values. ([`9c1e639`](https://github.com/Byron/gitoxide/commit/9c1e639979a9615fd8334ce0e3a809df137776f6)) + - Add various methods to iterate sections along with their id, and mutate them. ([`5df2a2a`](https://github.com/Byron/gitoxide/commit/5df2a2a5a9addbda7dcc68b2f8f7f4a48d9720c6)) + - `File::remove_section()` was fixed to allow re-adding a similarly named section. ([`e533993`](https://github.com/Byron/gitoxide/commit/e533993e8f861ba7a6600aab114ddfecc8a85ee2)) + - Improve clarity docs related to mutating sections ([`769e897`](https://github.com/Byron/gitoxide/commit/769e89790d8c4146263c84d4d5c9dff7d5018919)) + - Merge branch 'main' into new-http-impl ([`702a161`](https://github.com/Byron/gitoxide/commit/702a161ef11fc959611bf44b70e9ffe04561c7ad)) + - Merge branch 'fetch-pack' ([`3c49400`](https://github.com/Byron/gitoxide/commit/3c49400809c7c2120f4ce704c19a0421545b5acd)) + - Merge branch 'fix-git-features' ([`82fd251`](https://github.com/Byron/gitoxide/commit/82fd251ac80d07bc9da8a4d36e517aa35580d188)) + - Merge branch 'diff' ([`25a7726`](https://github.com/Byron/gitoxide/commit/25a7726377fbe400ea3c4927d04e9dec99802b7b)) + - Release git-hash v0.9.10, git-features v0.22.5, git-date v0.2.0, git-actor v0.12.0, git-glob v0.4.0, git-path v0.5.0, git-quote v0.3.0, git-attributes v0.4.0, git-config-value v0.8.0, git-tempfile v2.0.5, git-validate v0.6.0, git-object v0.21.0, git-ref v0.16.0, git-sec v0.4.0, git-config v0.8.0, git-discover v0.5.0, git-traverse v0.17.0, git-index v0.5.0, git-worktree v0.5.0, git-testtools v0.9.0, git-command v0.1.0, git-prompt v0.1.0, git-url v0.9.0, git-credentials v0.5.0, git-diff v0.19.0, git-mailmap v0.4.0, git-chunk v0.3.2, git-pack v0.23.0, git-odb v0.33.0, git-packetline v0.13.0, git-transport v0.20.0, git-protocol v0.20.0, git-revision v0.5.0, git-refspec v0.2.0, git-repository v0.24.0, git-commitgraph v0.9.0, gitoxide-core v0.18.0, gitoxide v0.16.0, safety bump 28 crates ([`29a043b`](https://github.com/Byron/gitoxide/commit/29a043be6808a3e9199a9b26bd076fe843afe4f4)) + - Make fmt ([`429cccc`](https://github.com/Byron/gitoxide/commit/429cccc5831c25a7205a12dc7a0443ac48616e2c)) + - Merge branch 'filter-refs' ([`3773b92`](https://github.com/Byron/gitoxide/commit/3773b92b8372c9a40a74d281149ca65b057a7da9)) + - Merge branch 'filter-refs' ([`fd14489`](https://github.com/Byron/gitoxide/commit/fd14489f729172d615d0fa1e8dbd605e9eacf69d)) + - Release git-features v0.22.6 ([`c9eda72`](https://github.com/Byron/gitoxide/commit/c9eda729d8f8bc266c7516c613d38acfb83a4743)) + - Upgrade all dependencies, except for `windows` ([`2968181`](https://github.com/Byron/gitoxide/commit/29681819ffe53d3926d631dc482f71d6200cb549)) + - Make fmt ([`535e967`](https://github.com/Byron/gitoxide/commit/535e967666c6da657ff1b7eff7c64ab27cafb182)) + - Merge branch 'filter-refs-by-spec' ([`5c05198`](https://github.com/Byron/gitoxide/commit/5c051986bd89590a9287d85d84c713d83dfab83a)) + - Merge branch 'main' into filter-refs-by-spec ([`9aa1d3d`](https://github.com/Byron/gitoxide/commit/9aa1d3dc46d4b1c76af257f573aff3aeef2d3fa8)) + - Release git-features v0.22.4, git-url v0.8.0, safety bump 4 crates ([`1d4600a`](https://github.com/Byron/gitoxide/commit/1d4600ae51475c2e225f96c16c41e2c4a2b3f2aa)) + - Merge branch 'main' into index-from-tree ([`bc64b96`](https://github.com/Byron/gitoxide/commit/bc64b96a2ec781c72d1d4daad38aa7fb8b74f99b)) + - Release git-path v0.4.2, git-config-value v0.7.0 ([`c48fb31`](https://github.com/Byron/gitoxide/commit/c48fb3107d29f9a06868b0c6de40567063a656d1)) + - Merge branch 'main' into filter-refs-by-spec ([`cef0b51`](https://github.com/Byron/gitoxide/commit/cef0b51ade2a3301fa09ede7a425aa1fe3527e78)) + - Release git-object v0.20.3, git-ref v0.15.4, git-config v0.7.1, git-diff v0.18.0, git-traverse v0.16.3, git-pack v0.22.0, git-odb v0.32.0, git-url v0.7.3, git-transport v0.19.3, git-protocol v0.19.1, git-refspec v0.1.1, git-repository v0.23.0, safety bump 6 crates ([`85a3bed`](https://github.com/Byron/gitoxide/commit/85a3bedd68d2e5f36592a2f691c977dc55298279)) + - Release git-attributes v0.3.3, git-ref v0.15.3, git-index v0.4.3, git-worktree v0.4.3, git-testtools v0.8.0 ([`baad4ce`](https://github.com/Byron/gitoxide/commit/baad4ce51fe0e8c0c1de1b08148d8303878ca37b)) + - Release git-features v0.22.3, git-revision v0.4.4 ([`c2660e2`](https://github.com/Byron/gitoxide/commit/c2660e2503323531ba02519eaa51124ee22fec51)) + - Merge branch 'main' into filter-refs-by-spec ([`cfa1440`](https://github.com/Byron/gitoxide/commit/cfa144031dbcac2707ab0cec012bc35e78f9c475)) + - Release git-date v0.0.5, git-hash v0.9.8, git-features v0.22.2, git-actor v0.11.3, git-glob v0.3.2, git-quote v0.2.1, git-attributes v0.3.2, git-tempfile v2.0.4, git-lock v2.1.1, git-validate v0.5.5, git-object v0.20.2, git-ref v0.15.2, git-sec v0.3.1, git-config v0.7.0, git-credentials v0.4.0, git-diff v0.17.2, git-discover v0.4.1, git-bitmap v0.1.2, git-index v0.4.2, git-mailmap v0.3.2, git-chunk v0.3.1, git-traverse v0.16.2, git-pack v0.21.2, git-odb v0.31.2, git-packetline v0.12.7, git-url v0.7.2, git-transport v0.19.2, git-protocol v0.19.0, git-revision v0.4.2, git-refspec v0.1.0, git-worktree v0.4.2, git-repository v0.22.0, safety bump 4 crates ([`4974eca`](https://github.com/Byron/gitoxide/commit/4974eca96d525d1ee4f8cad79bb713af7a18bf9d)) + - Release git-path v0.4.1 ([`5e82346`](https://github.com/Byron/gitoxide/commit/5e823462b3deb904f5d6154a7bf114cef1988224)) + - Merge branch 'remote-ls-refs' ([`39d585d`](https://github.com/Byron/gitoxide/commit/39d585d9f9ac6f3ecf51359c8e37f0a50e21ed45)) + - Thanks clippy ([`2770431`](https://github.com/Byron/gitoxide/commit/2770431f8742d6249574f53f06ec0026f9d241d6)) + - Merge branch 'main' into remote-ls-refs ([`e2ee3de`](https://github.com/Byron/gitoxide/commit/e2ee3ded97e5c449933712883535b30d151c7c78)) + - Merge branch 'docsrs-show-features' ([`31c2351`](https://github.com/Byron/gitoxide/commit/31c235140cad212d16a56195763fbddd971d87ce)) + - Use docsrs feature in code to show what is feature-gated automatically on docs.rs ([`b1c40b0`](https://github.com/Byron/gitoxide/commit/b1c40b0364ef092cd52d03b34f491b254816b18d)) + - Uniformize deny attributes ([`f7f136d`](https://github.com/Byron/gitoxide/commit/f7f136dbe4f86e7dee1d54835c420ec07c96cd78)) + - Pass --cfg docsrs when compiling for https://docs.rs ([`5176771`](https://github.com/Byron/gitoxide/commit/517677147f1c17304c62cf97a1dd09f232ebf5db)) + - Merge branch 'main' into remote-ls-refs ([`bd5f3e8`](https://github.com/Byron/gitoxide/commit/bd5f3e8db7e0bb4abfb7b0f79f585ab82c3a14ab)) + - Release git-date v0.0.3, git-actor v0.11.1, git-attributes v0.3.1, git-tempfile v2.0.3, git-object v0.20.1, git-ref v0.15.1, git-config v0.6.1, git-diff v0.17.1, git-discover v0.4.0, git-bitmap v0.1.1, git-index v0.4.1, git-mailmap v0.3.1, git-traverse v0.16.1, git-pack v0.21.1, git-odb v0.31.1, git-packetline v0.12.6, git-url v0.7.1, git-transport v0.19.1, git-protocol v0.18.1, git-revision v0.4.0, git-worktree v0.4.1, git-repository v0.21.0, safety bump 5 crates ([`c96473d`](https://github.com/Byron/gitoxide/commit/c96473dce21c3464aacbc0a62d520c1a33172611)) + - Prepare changelogs prior to reelase ([`c06ae1c`](https://github.com/Byron/gitoxide/commit/c06ae1c606b6af9c2a12021103d99c2810750d60)) + - Merge branch 'submodule-open' ([`8f5f3ab`](https://github.com/Byron/gitoxide/commit/8f5f3ab588cf0165d50a82365119ad5804745017)) + - Release git-hash v0.9.7, git-features v0.22.1 ([`232784a`](https://github.com/Byron/gitoxide/commit/232784a59ded3e8016e4257c7e146ad385cdd64a)) + - Merge branch 'main' into write-index-v2 ([`a938986`](https://github.com/Byron/gitoxide/commit/a938986877302c197d1aed087594c5605416fe5f)) + - Merge branch 'main' into remote-ls-refs ([`de61c4d`](https://github.com/Byron/gitoxide/commit/de61c4db7855d6925d66961f62ae3d12cc4acf78)) + - Thanks clippy ([`4bd747c`](https://github.com/Byron/gitoxide/commit/4bd747cb3e126fe5b1d540270cfbd731cffd42ef)) + - Merge pull request #2 from SidneyDouw/main ([`ce885ad`](https://github.com/Byron/gitoxide/commit/ce885ad4c3324c09c83751c32e014f246c748766)) + - Merge branch 'Byron:main' into main ([`9b9ea02`](https://github.com/Byron/gitoxide/commit/9b9ea0275f8ff5862f24cf5a4ca53bb1cd610709)) + - Merge branch 'main' into rev-parse-delegate ([`6da8250`](https://github.com/Byron/gitoxide/commit/6da82507588d3bc849217c11d9a1d398b67f2ed6)) + - Commit strange changes to be able to do anything. ([`237b21d`](https://github.com/Byron/gitoxide/commit/237b21da8465200dcb8b5f7dc324a97bf653a23d)) + - Merge branch 'main' into pathspec ([`7b61506`](https://github.com/Byron/gitoxide/commit/7b615060712565f515515e35a3e8346278ad770c)) + - Commit strange crlf-file that makes everything impossible ([`1dd9f9a`](https://github.com/Byron/gitoxide/commit/1dd9f9a9320813fe5b40578ee4826a1da575c05c)) + - Make fmt ([`47724c0`](https://github.com/Byron/gitoxide/commit/47724c0edb382c036a3fc99884becfd2b0740d4b)) + - Merge branch 'kianmeng-fix-typos' ([`4e7b343`](https://github.com/Byron/gitoxide/commit/4e7b34349c0a01ad8686bbb4eb987e9338259d9c)) + - Fix typos ([`e9fcb70`](https://github.com/Byron/gitoxide/commit/e9fcb70e429edb2974afa3f58d181f3ef14c3da3)) + - Release git-config v0.6.0, git-credentials v0.3.0, git-diff v0.17.0, git-discover v0.3.0, git-index v0.4.0, git-mailmap v0.3.0, git-traverse v0.16.0, git-pack v0.21.0, git-odb v0.31.0, git-url v0.7.0, git-transport v0.19.0, git-protocol v0.18.0, git-revision v0.3.0, git-worktree v0.4.0, git-repository v0.20.0, git-commitgraph v0.8.0, gitoxide-core v0.15.0, gitoxide v0.13.0 ([`aa639d8`](https://github.com/Byron/gitoxide/commit/aa639d8c43f3098cc4a5b50614c5ae94a8156928)) + - Release git-hash v0.9.6, git-features v0.22.0, git-date v0.0.2, git-actor v0.11.0, git-glob v0.3.1, git-path v0.4.0, git-attributes v0.3.0, git-tempfile v2.0.2, git-object v0.20.0, git-ref v0.15.0, git-sec v0.3.0, git-config v0.6.0, git-credentials v0.3.0, git-diff v0.17.0, git-discover v0.3.0, git-index v0.4.0, git-mailmap v0.3.0, git-traverse v0.16.0, git-pack v0.21.0, git-odb v0.31.0, git-url v0.7.0, git-transport v0.19.0, git-protocol v0.18.0, git-revision v0.3.0, git-worktree v0.4.0, git-repository v0.20.0, git-commitgraph v0.8.0, gitoxide-core v0.15.0, gitoxide v0.13.0, safety bump 22 crates ([`4737b1e`](https://github.com/Byron/gitoxide/commit/4737b1eea1d4c9a8d5a69fb63ecac5aa5d378ae5)) + - Prepare changelog prior to release ([`3c50625`](https://github.com/Byron/gitoxide/commit/3c50625fa51350ec885b0f38ec9e92f9444df0f9)) + - Merge branch 'config-cascade' ([`f144eaf`](https://github.com/Byron/gitoxide/commit/f144eaf5863ae5cac63103f0db51c35fcf03a948)) + - Thanks clippy ([`fddc720`](https://github.com/Byron/gitoxide/commit/fddc7206476423a6964d61acd060305572ecd02b)) + - Thanks fuzzy ([`15a379a`](https://github.com/Byron/gitoxide/commit/15a379a85d59d83f3a0512b9e9fbff1774c9f561)) + - Thanks clippy ([`15fee74`](https://github.com/Byron/gitoxide/commit/15fee74fdfb5fc84349ac103cd5727332f3d2230)) + - Thanks clippy ([`0b05be8`](https://github.com/Byron/gitoxide/commit/0b05be850d629124f027af993e316b9018912337)) + - Merge branch 'config-sec-access' ([`b420eba`](https://github.com/Byron/gitoxide/commit/b420eba520ecc31fb2d07c939fa64f1a7be5737e)) + - Merge branch 'config-reduce-events' ([`fd046f4`](https://github.com/Byron/gitoxide/commit/fd046f463a9200d0d8f1a5c3e5f452792f015bd5)) + - Thanks clippy ([`693e304`](https://github.com/Byron/gitoxide/commit/693e304a2c38130ed936d5e4544faaa858665872)) + - Fix git-config/tests/.gitattributes ([`a741766`](https://github.com/Byron/gitoxide/commit/a7417664ca1e41936f9de8cf066e13aeaf9b0d75)) + - Merge branch 'config-metadata' ([`453e9bc`](https://github.com/Byron/gitoxide/commit/453e9bca8f4af12e49222c7e3a46d6222580c7b2)) + - Forced checkin to fix strange crlf issue ([`5d0a5c0`](https://github.com/Byron/gitoxide/commit/5d0a5c0712fbd8fcc00aff54563c83281afc9476)) + - Thanks clippy ([`e5ba0f5`](https://github.com/Byron/gitoxide/commit/e5ba0f532bf9bfee46d2dab24e6a6503df4d239d)) + - Thanks clippy ([`00bfbca`](https://github.com/Byron/gitoxide/commit/00bfbca21e2361008c2e81b54424a9c6f09e76e9)) + - Thanks clippy ([`09e2374`](https://github.com/Byron/gitoxide/commit/09e23743035b9d4463f438378aed54677c03311f)) + - Thanks clippy ([`e842633`](https://github.com/Byron/gitoxide/commit/e84263362fe0631935379a0b4e8d8b1fcf6ac81b)) + - Merge pull request #1 from Byron/main ([`085e76b`](https://github.com/Byron/gitoxide/commit/085e76b121291ed9bd324139105d2bd4117bedf8)) + - Merge branch 'config-comfort' ([`84b98d9`](https://github.com/Byron/gitoxide/commit/84b98d94177ceaf931aaa521e44eca0fa484d2d3)) + - Thanks clippy ([`3ca8027`](https://github.com/Byron/gitoxide/commit/3ca8027e07a835e84a704688778cfb82c956643b)) + - Make fmt ([`aa9fdb0`](https://github.com/Byron/gitoxide/commit/aa9fdb0febfb29f906eb81e4378f07ef01b03e05)) + - Merge branch 'config-output' ([`20e188f`](https://github.com/Byron/gitoxide/commit/20e188ff3b06ac7e866956ea5216b00dcffd1307)) + - Thanks clippy ([`c9a2390`](https://github.com/Byron/gitoxide/commit/c9a239095511ae95fb5efbbc9207293641b623f7)) + - Thanks clippy ([`badd00c`](https://github.com/Byron/gitoxide/commit/badd00c402b59994614e653b28bb3e6c5b70d9d1)) + - Make fmt ([`0700b09`](https://github.com/Byron/gitoxide/commit/0700b09d6828849fa2470df89af1f75a67bfb27d)) + - Thanks clippy ([`b246f0a`](https://github.com/Byron/gitoxide/commit/b246f0ade5aa42413cc387470b35df357b1136bc)) + - Thanks clippy ([`08441de`](https://github.com/Byron/gitoxide/commit/08441def5d1738bbf13b68979f2d1ff7ff3b4153)) + - Thanks clippy ([`8b29dda`](https://github.com/Byron/gitoxide/commit/8b29ddaa627048b9ca130b52221709a575f50d3a)) + - Thanks clippy ([`cff6e01`](https://github.com/Byron/gitoxide/commit/cff6e018a8f0c3b6c78425f99a204d29d72a65aa)) + - Thanks clippy ([`f7be3b0`](https://github.com/Byron/gitoxide/commit/f7be3b0f79bf19faf5a3b68032f764c0b7a12d7e)) + - Thanks clippy ([`7a2a31e`](https://github.com/Byron/gitoxide/commit/7a2a31e5758a2be8434f22cd9401ac00539f2bd9)) + - Allow backslashes in subsections ([`6f4f325`](https://github.com/Byron/gitoxide/commit/6f4f325a42656800c8c76c8eae075508c31657be)) + - Fix build after changes to `git-url` and `git-config` ([`1f02420`](https://github.com/Byron/gitoxide/commit/1f0242034071ce317743df75cc685e7428b604b0)) + - Thanks clippy ([`9b6a67b`](https://github.com/Byron/gitoxide/commit/9b6a67bf369fcf51c6a3289784c3ef8ab366bee7)) + - Remove `values::Bytes` - use `values::String` instead. ([`aa630ad`](https://github.com/Byron/gitoxide/commit/aa630ad6ec2c6306d3307d5c77e272cb24b00ddd)) + - Change mostily internal uses of [u8] to BString/BStr ([`311d4b4`](https://github.com/Byron/gitoxide/commit/311d4b447daf8d4364670382a20901468748d34d)) + - Definitely don't unconditionally convert to forward slashes ([`146eb0c`](https://github.com/Byron/gitoxide/commit/146eb0c831ce0a96e215b1ec6499a86bbf5902c9)) + - Avoid panics and provide errors instead of just not matching ([`a0f842c`](https://github.com/Byron/gitoxide/commit/a0f842c7f449a6a7aedc2742f7fc4f74a12fdd17)) + - Merge branch 'main' into pathspec ([`f4fe879`](https://github.com/Byron/gitoxide/commit/f4fe879ab3161fdb135354939b85408197d8a953)) + - Try to fix git-config tests on windows even harder ([`16778d4`](https://github.com/Byron/gitoxide/commit/16778d478d6941ab86571de0bd99aaab816ffe67)) + - Merge branch 'main' into pathspec ([`89ea12b`](https://github.com/Byron/gitoxide/commit/89ea12b558bcc056b892193ee8fb44b8664b5da4)) + - Try once more to get failing tests under control on windows ([`c26c2e9`](https://github.com/Byron/gitoxide/commit/c26c2e962aa6a93c0e06b900dc719f9cd92f6137)) + - Thanks clippy ([`27b2dde`](https://github.com/Byron/gitoxide/commit/27b2dde9a299aca112347f988fa21d797f64552b)) + - Fix test with brute force; take some notes for later ([`2eda529`](https://github.com/Byron/gitoxide/commit/2eda5296ad9ee58756d564225e98e64a800f46d7)) + - Merge branch 'main' into cont_include_if ([`daa71c3`](https://github.com/Byron/gitoxide/commit/daa71c3b753c6d76a3d652c29237906b3e28728f)) + - Take GitEnv by ref. ([`937d7ee`](https://github.com/Byron/gitoxide/commit/937d7eea84e92467fcc8a6a72c78fe6c060dd95d)) + - Merge branch 'normalize-values' ([`4e8cc7a`](https://github.com/Byron/gitoxide/commit/4e8cc7a5b447656c744cd84e6521e620d0479acb)) + - Remove leftover debug printing ([`7d1cf34`](https://github.com/Byron/gitoxide/commit/7d1cf34e4535721db97f566734f68014ebc7c3e8)) + - Auto-normalize string values to support quote removal in case of strings. ([`1e71e71`](https://github.com/Byron/gitoxide/commit/1e71e71c984289f0d7e0a39379ee6728918b7dc5)) + - Refactor ([`02fba2c`](https://github.com/Byron/gitoxide/commit/02fba2c124f3665112102469d41d476b6cf48dcd)) + - Refactor ([`1d6ba9b`](https://github.com/Byron/gitoxide/commit/1d6ba9bd719ad01ce22573cabd8022ddb675c5fc)) + - Avoid unwrap() more as the test code matures ([`c2d7e80`](https://github.com/Byron/gitoxide/commit/c2d7e800abe022f5a2663176f0f6b3ac90eacf0e)) + - Refactor ([`b5c0b30`](https://github.com/Byron/gitoxide/commit/b5c0b3011d2c0e63c933be42753aea65b88ca569)) + - Make '..' related tests work ([`5f11318`](https://github.com/Byron/gitoxide/commit/5f11318dc55b8dd8da016a4053cc4ad34b13fa97)) + - Find a few cases that aren't according to spec by failing (and ignored) tests ([`f0e6ea9`](https://github.com/Byron/gitoxide/commit/f0e6ea9086ebfa134044568114bb578120eb5da9)) + - Refactor ([`62e5396`](https://github.com/Byron/gitoxide/commit/62e5396ac9221f13437c87f06715c98989981785)) + - Generally avoid using `target_os = "windows"` in favor of `cfg(windows)` and negations ([`91d5402`](https://github.com/Byron/gitoxide/commit/91d54026a61c2aae5e3e1341d271acf16478cd83)) + - Invoke git only when necessary ([`556c7cf`](https://github.com/Byron/gitoxide/commit/556c7cff5f813e885598b4bd858c6c22cedf688b)) + - Also use git_path::realpath() in other places that used canonicalize before ([`08af648`](https://github.com/Byron/gitoxide/commit/08af648923c226a0330f0025784c42914d4fea7f)) + - Our own git_path::realpath doesn't have the questionmark? issue on windows ([`cfe196b`](https://github.com/Byron/gitoxide/commit/cfe196b23051e639cb1332f88f1ec917608fbbe1)) + - Fix windows tests ([`47f10fe`](https://github.com/Byron/gitoxide/commit/47f10feb2b143b9b429237cf6a4a7424c2b9ab13)) + - More debugging for windows failures ([`e0a72e6`](https://github.com/Byron/gitoxide/commit/e0a72e65e4bbe76755aea1a905d69d74d01f543a)) + - No need for serial anymore ([`34bb715`](https://github.com/Byron/gitoxide/commit/34bb7152ca5992fc35be5f51016565a568916e7c)) + - Make a note to be sure we use the home-dir correctly in git-repository; avoid `dirs` crate ([`0e8cf19`](https://github.com/Byron/gitoxide/commit/0e8cf19d7f742f9400afa4863d302ba18a452adc)) + - Finally all tests work without the need for dirs::home_dir() ([`180ce99`](https://github.com/Byron/gitoxide/commit/180ce99a016c17641990eb41b6bbe3b2407ab271)) + - Refactor ([`00ba5d8`](https://github.com/Byron/gitoxide/commit/00ba5d8a53aae1c4adbb379c076651756e1af68d)) + - Refactor ([`0eb7ced`](https://github.com/Byron/gitoxide/commit/0eb7ced6ec49fe6303659bdcab29952c5cea41bd)) + - Path-interpolation makes `home-dir` configurable. ([`edd2267`](https://github.com/Byron/gitoxide/commit/edd226719cd04a480274cb7d983b6d5d8bfdbb13)) + - Refactor ([`aab9865`](https://github.com/Byron/gitoxide/commit/aab98656ee5c4abf65f79d403c1f0cb36fd0ee88)) + - Change last test to new simplified symlink setup ([`a40e3c9`](https://github.com/Byron/gitoxide/commit/a40e3c999baf203c92d0e5e53ee61c0032e32e51)) + - Refactor ([`67677b0`](https://github.com/Byron/gitoxide/commit/67677b0edfa1faa0c011a225d41d78dbde3c5f15)) + - Assure the IDE doesn't confuse a module with a test ([`7be0b05`](https://github.com/Byron/gitoxide/commit/7be0b05ff3a5bbea9d9712e4d13ee08cf9979861)) + - Refactor ([`1203a14`](https://github.com/Byron/gitoxide/commit/1203a14eba79d335137c96d4ee573739df30b067)) + - Refactor ([`a721efe`](https://github.com/Byron/gitoxide/commit/a721efecd36984064b4b31c715bbe011df2538ad)) + - Refactor ([`2c8c6e5`](https://github.com/Byron/gitoxide/commit/2c8c6e53fd4681289c9fa2308735c779ed4eace5)) + - Refactor ([`eb0ace1`](https://github.com/Byron/gitoxide/commit/eb0ace14a92899002749d6dbd99dac3a35d73c25)) + - Refactor ([`8f8f873`](https://github.com/Byron/gitoxide/commit/8f8f873ae711eb5ae62f192f6731653f2bb7ff4b)) + - Merge branch 'main' into cont_include_if ([`41ea8ba`](https://github.com/Byron/gitoxide/commit/41ea8ba78e74f5c988148367386a1f4f304cb951)) + - Release git-path v0.3.0, safety bump 14 crates ([`400c9be`](https://github.com/Byron/gitoxide/commit/400c9bec49e4ec5351dc9357b246e7677a63ea35)) + - Remove `git-config` test utilities from `git-path`. ([`c9933c0`](https://github.com/Byron/gitoxide/commit/c9933c0b0f51d21dc8244b2acc33d7dc8a33f6ce)) + - Add repo_dir to EnvOverwrite. ([`ed5c442`](https://github.com/Byron/gitoxide/commit/ed5c442cc4f0c546834f2e0e9dc553a221b6985d)) + - Use EnvOverwrite struct. ([`f2e124f`](https://github.com/Byron/gitoxide/commit/f2e124f60f8f9a0d517fddb029d795fa91bcda5a)) + - Tempdir lives long enough for sure. ([`a41002f`](https://github.com/Byron/gitoxide/commit/a41002fe4004485fac429d904bc4e8b6842eaf3c)) + - Disable symlink tests on windows. ([`8de6b3d`](https://github.com/Byron/gitoxide/commit/8de6b3d42c89c741195e4add273a2d1e7b48fad9)) + - Release git-date v0.0.1, git-hash v0.9.5, git-features v0.21.1, git-actor v0.10.1, git-path v0.2.0, git-attributes v0.2.0, git-ref v0.14.0, git-sec v0.2.0, git-config v0.5.0, git-credentials v0.2.0, git-discover v0.2.0, git-pack v0.20.0, git-odb v0.30.0, git-url v0.6.0, git-transport v0.18.0, git-protocol v0.17.0, git-revision v0.2.1, git-worktree v0.3.0, git-repository v0.19.0, safety bump 13 crates ([`a417177`](https://github.com/Byron/gitoxide/commit/a41717712578f590f04a33d27adaa63171f25267)) + - Update changelogs prior to release ([`bb424f5`](https://github.com/Byron/gitoxide/commit/bb424f51068b8a8e762696890a55ab48900ab980)) + - Make fmt ([`cd4f727`](https://github.com/Byron/gitoxide/commit/cd4f7279678678fa6f2e55d4e7681a2075f1d6cf)) + - Temp ignore symlink tests. ([`ec40b94`](https://github.com/Byron/gitoxide/commit/ec40b94bffda14b7b991dd57cd36d893f1f6962b)) + - Fmt. ([`82ea726`](https://github.com/Byron/gitoxide/commit/82ea7261cfb75a01992489aa7631e2e6d807be06)) + - Use `dirs::home_dir()` ([`5767a50`](https://github.com/Byron/gitoxide/commit/5767a505f2f2cc3515eb604e39da673fa2e09454)) + - Try fix windows home. ([`393758e`](https://github.com/Byron/gitoxide/commit/393758e14a1b5ff14301f153807fe45623d9f973)) + - Add more tests. ([`db1204d`](https://github.com/Byron/gitoxide/commit/db1204d74b16ff7e905fb5b2d91d9ecb109bca07)) + - Add debug output. ([`52db5e8`](https://github.com/Byron/gitoxide/commit/52db5e8894c5033ec3d58894a7cf17b4f29e03f4)) + - Tests like git: https://github.com/git/git/blob/master/t/t1305-config-include.sh ([`c3a0454`](https://github.com/Byron/gitoxide/commit/c3a04548b08b6972ea0999b0030017d1a6002de2)) + - Start extracting gitdir tests cont. ([`22e5cbe`](https://github.com/Byron/gitoxide/commit/22e5cbece0206da6cf8890a831fd82847526396a)) + - Remove `pwd` crate dependency in favor of using libc directly ([`4adfa11`](https://github.com/Byron/gitoxide/commit/4adfa11d70cf78bed541fa59707e8a5082dda245)) + - Drop non-existent config paths before parsing ([`475d6fa`](https://github.com/Byron/gitoxide/commit/475d6fab2467ad0499db7df2d4c99f74e43682fc)) + - Start extracting gitdir tests. ([`5aaf7ba`](https://github.com/Byron/gitoxide/commit/5aaf7ba93857f1e5570f64f4a9539cd3d547b81d)) + - Thanks clippy ([`cfa577f`](https://github.com/Byron/gitoxide/commit/cfa577f84c45c7fbed27e6d59ef361f9ac5c2614)) + - Refactor ([`da23958`](https://github.com/Byron/gitoxide/commit/da239580fca76011f91a45ae502af88c67d429a4)) + - Finalize onbranch tests; remove mixed ones in favor of specific cases ([`26680c4`](https://github.com/Byron/gitoxide/commit/26680c48951a82d5119f54c57b4e7045d2c20649)) + - Refactor ([`11c417f`](https://github.com/Byron/gitoxide/commit/11c417fdc03331db2c4a778bc3e8038ffd0aff89)) + - More tests for branch matching prior to making tests clearer ([`31e6db8`](https://github.com/Byron/gitoxide/commit/31e6db8cdc959549a6c2754692d2471103ada64f)) + - Basic test-setup for more specialized tests ([`b4374d2`](https://github.com/Byron/gitoxide/commit/b4374d21882eca637ddbb80cdde1dac7bc68560e)) + - Refactor ([`04da720`](https://github.com/Byron/gitoxide/commit/04da7207a7e44175dc96e4ea850274b2cc5a6d84)) + - Fix including .. path. ([`8891fea`](https://github.com/Byron/gitoxide/commit/8891feac0341960a6339ee86c671fc80c3133b4e)) + - Fix case-insensitive. ([`ca05802`](https://github.com/Byron/gitoxide/commit/ca058024e1e19818261fea39099c893d666928dc)) + - Fix \\ test. ([`ab555b5`](https://github.com/Byron/gitoxide/commit/ab555b557f4bd68b491a552a14cd4549c6a625bc)) + - Fix tests on windows ([`bb3b4f0`](https://github.com/Byron/gitoxide/commit/bb3b4f013c862a4c017c65075919e1df59cc1986)) + - Refactor ([`e1ba36f`](https://github.com/Byron/gitoxide/commit/e1ba36fab772417d9b60bf89cc49b45fbb7252f9)) + - Merge branch 'main' into svetli-n-cont_include_if ([`315c87e`](https://github.com/Byron/gitoxide/commit/315c87e18c6cac0fafa7b4e59fdd3c076a58a45a)) + - Refactor ([`e47fb41`](https://github.com/Byron/gitoxide/commit/e47fb412a136d087c79710e7490d3e1c97d1f955)) + - Refactor ([`56eadc8`](https://github.com/Byron/gitoxide/commit/56eadc8b565b2f8a272080bc8814d6665b3f1205)) + - Refactor ([`0ccd8ae`](https://github.com/Byron/gitoxide/commit/0ccd8ae0ab01cdb5ae33dd79f486edfcee2b176a)) + - Try fix windows test. ([`e2e94db`](https://github.com/Byron/gitoxide/commit/e2e94db2cee237168d5c56db5c5e94a8b4317991)) + - Refactor include sequence test. ([`b4e657e`](https://github.com/Byron/gitoxide/commit/b4e657ed02cf062b1c2cb1f6c15abdf5d777c177)) + - Extract include_paths. ([`c078671`](https://github.com/Byron/gitoxide/commit/c0786717c4979810002365a68d31abbf21d90f2d)) + - Make `realpath()` easier to use by introducing `realpath_opt()`. ([`266d437`](https://github.com/Byron/gitoxide/commit/266d4379e9132fd7dd21e6c8fccb36e125069d6e)) + - Adjust test structure to mirror the new code structure. ([`984b58e`](https://github.com/Byron/gitoxide/commit/984b58ee1dac58fe0dfd0b80f990ca37d323cad7)) + - Refact. ([`d5d81bc`](https://github.com/Byron/gitoxide/commit/d5d81bc16116b4c58f628e0e5c66d5d0a59b7816)) + - Read include and incideIf sections in correct order. ([`a4a7ebd`](https://github.com/Byron/gitoxide/commit/a4a7ebdb6fcb5f6183917719d6c93f54eea72e85)) + - Refact. ([`a342e53`](https://github.com/Byron/gitoxide/commit/a342e53dac58cea1787a94eaa1a9d24fb1389df2)) + - Merge branch 'main' into SidneyDouw-pathspec ([`a22b1d8`](https://github.com/Byron/gitoxide/commit/a22b1d88a21311d44509018729c3ef1936cf052a)) + - Release git-path v0.1.2, git-sec v0.1.1, git-config v0.4.0, git-discover v0.1.1, git-pack v0.19.1, git-repository v0.18.0, cargo-smart-release v0.10.0, safety bump 2 crates ([`ceb6dff`](https://github.com/Byron/gitoxide/commit/ceb6dff13362a2b4318a551893217c1d11643b9f)) + - Fix benchmark compilation ([`53adcfe`](https://github.com/Byron/gitoxide/commit/53adcfea1942e9dd32a7d84d02a83c9a08408fad)) + - Merge branch 'svetli-n-git_includeif' ([`cf24fbe`](https://github.com/Byron/gitoxide/commit/cf24fbe4b62d67b06138243d470dcc1805ebd55b)) + - Bring init functions back to `File` type ([`f1f69d8`](https://github.com/Byron/gitoxide/commit/f1f69d8f983e2505990e7ee21cbd7f64ac7ba766)) + - Disallow Rust 2018 idioms ([`81aca45`](https://github.com/Byron/gitoxide/commit/81aca458f4b7f6768e14da5719ada772f419f1b5)) + - Fix most of docs ([`1fe053f`](https://github.com/Byron/gitoxide/commit/1fe053f60fa4843e7da6a6328fc293b4bcd25277)) + - Thanks clippy ([`409a95b`](https://github.com/Byron/gitoxide/commit/409a95b6505db8568bfea24bc62c92640a5c3cbf)) + - Dissolve git_config module in favor of `file` module ([`2d4a19b`](https://github.com/Byron/gitoxide/commit/2d4a19b0c72c4aab79cd3b18430710909ba1af5f)) + - Refactor ([`6cc5c20`](https://github.com/Byron/gitoxide/commit/6cc5c20aba825a5a712b33740ea2c7f44387f3f9)) + - Refactor ([`3471f95`](https://github.com/Byron/gitoxide/commit/3471f95b5e490d22bb42b6c4204446c52812e4fc)) + - `File::len()` -> `File::num_values()` ([`553f872`](https://github.com/Byron/gitoxide/commit/553f87225363903e6acdb3e7eaa8cc66a91110f1)) + - Refactor ([`2626e0c`](https://github.com/Byron/gitoxide/commit/2626e0ca58947eb846128507ffb254e9ebd91ee1)) + - Refactor ([`07e0f5e`](https://github.com/Byron/gitoxide/commit/07e0f5e91b3c41614b9182cf9716120fe41ddf40)) + - Split git_config into modules. ([`a85d864`](https://github.com/Byron/gitoxide/commit/a85d8643cbfbfc4bd4d4c1fb17ae3672b8b36931)) + - Fix linux test. ([`e0d063e`](https://github.com/Byron/gitoxide/commit/e0d063ebdfa8effabd53c6a51818617abe4a0b4e)) + - Fix test. ([`ed5de9e`](https://github.com/Byron/gitoxide/commit/ed5de9e8d2e225313ef8e60003797c5466d81273)) + - Temp ignore test. ([`9b70eca`](https://github.com/Byron/gitoxide/commit/9b70eca08aaa36e3f803da1685ac85bab40f0b03)) + - Tryfix windows test. Includes module. ([`b02d147`](https://github.com/Byron/gitoxide/commit/b02d147468f902597d4022c1fce3424213cb9eb8)) + - Tryfix windows test. ([`4098278`](https://github.com/Byron/gitoxide/commit/40982788f88267f0885513fffb112467e2f3b370)) + - Tryfix windows test. ([`17a296f`](https://github.com/Byron/gitoxide/commit/17a296ffc5af08c6c0455b3028d275b9ebe7c18c)) + - Tryfix windows test. ([`a29657a`](https://github.com/Byron/gitoxide/commit/a29657a8118300f11db4e0783800eeadf838532c)) + - Fix merge. ([`07bc9a8`](https://github.com/Byron/gitoxide/commit/07bc9a869d501b78c060e4ed18d4003c287560a8)) + - Merge branch 'main' into git_includeif ([`598c853`](https://github.com/Byron/gitoxide/commit/598c853087fcf8f77299aa5b9803bcec705c0cd0)) + - Tryfix windows test. ([`300ecbc`](https://github.com/Byron/gitoxide/commit/300ecbc75fbf8d94d7e21a35e12b93f0a954515d)) + - Release git-hash v0.9.4, git-features v0.21.0, git-actor v0.10.0, git-glob v0.3.0, git-path v0.1.1, git-attributes v0.1.0, git-sec v0.1.0, git-config v0.3.0, git-credentials v0.1.0, git-validate v0.5.4, git-object v0.19.0, git-diff v0.16.0, git-lock v2.1.0, git-ref v0.13.0, git-discover v0.1.0, git-index v0.3.0, git-mailmap v0.2.0, git-traverse v0.15.0, git-pack v0.19.0, git-odb v0.29.0, git-packetline v0.12.5, git-url v0.5.0, git-transport v0.17.0, git-protocol v0.16.0, git-revision v0.2.0, git-worktree v0.2.0, git-repository v0.17.0, safety bump 20 crates ([`654cf39`](https://github.com/Byron/gitoxide/commit/654cf39c92d5aa4c8d542a6cadf13d4acef6a78e)) + - Temp ignore test ([`6f35866`](https://github.com/Byron/gitoxide/commit/6f35866725f52eee368042a4293ac74a82752331)) + - Update lifetime ([`fd24e2c`](https://github.com/Byron/gitoxide/commit/fd24e2cc1c10ef5c65a5b923ad30806e91427117)) + - Add includeIf test with symlink. ([`5d74404`](https://github.com/Byron/gitoxide/commit/5d744049286632f3141ec07fa3f128093480d1c0)) + - Refactor condition match. ([`15ac22a`](https://github.com/Byron/gitoxide/commit/15ac22a9b28577d2c4175bc752eb7099a3b128fa)) + - Fix realpath tests. ([`0426f4d`](https://github.com/Byron/gitoxide/commit/0426f4deb5d73fd88529530f9a6d01ba55eeadc4)) + - Thanks clippy ([`da13aff`](https://github.com/Byron/gitoxide/commit/da13affabe34c3d691b18a70ce61eb00319668c5)) + - Merge branch 'main' into git_includeif ([`05eb340`](https://github.com/Byron/gitoxide/commit/05eb34023933918c51c03cf2afd774db89cc5a33)) + - Merge branch 'main' into msrv-for-windows ([`7cb1972`](https://github.com/Byron/gitoxide/commit/7cb19729133325bdfacedf44cdc0500cbcf36684)) + - Vec -> Option in tests ([`538de54`](https://github.com/Byron/gitoxide/commit/538de54aab0ab0352fbff95e0334c89c415627e9)) + - Tests use `options_with_git_dir()`. ([`9abbac1`](https://github.com/Byron/gitoxide/commit/9abbac1b1a1e7af7c5219f84a9edc1594deda55a)) + - Thanks clippy ([`60da03c`](https://github.com/Byron/gitoxide/commit/60da03c3edc38d14601ac2dfbeb3b3045958f860)) + - Thanks clippy ([`4d0e29c`](https://github.com/Byron/gitoxide/commit/4d0e29c25fd53421487a624b90072c8553509d45)) + - Merge branch 'git_includeif' of https://github.com/svetli-n/gitoxide into svetli-n-git_includeif ([`0e01da7`](https://github.com/Byron/gitoxide/commit/0e01da74dffedaa46190db6a7b60a2aaff190d81)) + - Merge branch 'worktree-stack' ([`98da8ba`](https://github.com/Byron/gitoxide/commit/98da8ba52cef8ec27f705fcbc84773e5bacc4e10)) + - Thanks clippy ([`5bf6b52`](https://github.com/Byron/gitoxide/commit/5bf6b52cd51bef19079e87230e5ac463f8f881c0)) + - Thanks clippy ([`53f27e0`](https://github.com/Byron/gitoxide/commit/53f27e04dd186c32eaa8c03615a58a10938cab8d)) + - Thanks clippy ([`1e2b239`](https://github.com/Byron/gitoxide/commit/1e2b239abee7e8889fe2060c79c00f2e506023e1)) + - Onbranch uses wildmatch ([`8382df2`](https://github.com/Byron/gitoxide/commit/8382df2a7cb9cb12113085b9310560f63c51447f)) + - Gitdir/i support. ([`5dd3f92`](https://github.com/Byron/gitoxide/commit/5dd3f92964cd530f435f2dfb81a10fb236dd5334)) + - Fail fast include condition if it's raw value contains backslash. ([`2c78e48`](https://github.com/Byron/gitoxide/commit/2c78e4866e10141b23980aa2db6405644ad92f34)) + - Replace \\ in pattern after expanding relative paths. ([`a955449`](https://github.com/Byron/gitoxide/commit/a9554497f81b2e2ecdd2ea7a14c347b1f136a688)) + - Replace \\ in pattern after interpolation. ([`d774485`](https://github.com/Byron/gitoxide/commit/d77448510bd276db83802bd7b183a757d6a48db3)) + - Handle relative path patterns. Update tests. ([`546ec2c`](https://github.com/Byron/gitoxide/commit/546ec2c30fd6d1ed1eea1f1497251513940e82ac)) + - Use `std::path::MAIN_SEPARATOR` when adding ** globbing. ([`b85e706`](https://github.com/Byron/gitoxide/commit/b85e7066fe86d822b6912101c7eb499998d2c4cd)) + - Use `std::path::MAIN_SEPARATOR` when adding ** globbing. ([`cc42edf`](https://github.com/Byron/gitoxide/commit/cc42edf1e5c11ad25fe3ffc9dbc170868748cf66)) + - Use `git-glob` for pattern matching. ([`6066701`](https://github.com/Byron/gitoxide/commit/6066701f1c852b61203aa46399bd7731834c79bf)) + - Refact. ([`35f955a`](https://github.com/Byron/gitoxide/commit/35f955a3cd881359b573f4abd92239e18701aa34)) + - Fix out of order whne reading includeIf sections. ([`e6ef931`](https://github.com/Byron/gitoxide/commit/e6ef931567888e2794d17f2e0fa598a04ac1ef49)) + - Fix out of order whne reading includeIf sections. ([`293e86e`](https://github.com/Byron/gitoxide/commit/293e86ec96864fcdf5f42ba0d5b4d3892574e7ec)) + - Test WIP. ([`7a59791`](https://github.com/Byron/gitoxide/commit/7a59791181c21927340137340b17cd9715755722)) + - PR feedback. ([`fd2b085`](https://github.com/Byron/gitoxide/commit/fd2b085a856ea4665976e5f662a4fad3d7cb5090)) + - Use new git-ref API. ([`32c5729`](https://github.com/Byron/gitoxide/commit/32c5729c5a42ade2e881de5d5575e670b5808f33)) + - IncludeIf condition and gitdir tests. ([`892b77a`](https://github.com/Byron/gitoxide/commit/892b77a8ae09c61391cb637051ea4576b66cf450)) + - Nop includeIf. ([`5d86a02`](https://github.com/Byron/gitoxide/commit/5d86a02dd7617488285b6d0bd43d13ebfa3fb67a)) + - Get values for a section across all subsections. ([`aff2777`](https://github.com/Byron/gitoxide/commit/aff2777baaffa08d1d8ad2e1da34f47e0fe01f7f)) + - Merge branch 'worktree-stack' ([`39046e9`](https://github.com/Byron/gitoxide/commit/39046e98098da7d490757477986479126a45b3e5)) + - Merge branch 'main' into repo-status ([`9679d6b`](https://github.com/Byron/gitoxide/commit/9679d6b0e68c28438e22cb65c554d0b31dfaf159)) + - Merge branch 'git-sec' ([`cd723b5`](https://github.com/Byron/gitoxide/commit/cd723b5ae11148e7e9fd07daf28bc04455d5c46f)) + - Merge branch 'main' into repo-status ([`4086335`](https://github.com/Byron/gitoxide/commit/40863353a739ec971b49410fbc2ba048b2762732)) + - Add suppport for android ([`031bd2f`](https://github.com/Byron/gitoxide/commit/031bd2f401199a05d6465c0260ceed3cc849c7ac)) + - Merge branch 'worktree-stack' ([`e90d3fd`](https://github.com/Byron/gitoxide/commit/e90d3fd0a9764511e6280596f21d3a0494ed7021)) + - Thanks clippy ([`273895a`](https://github.com/Byron/gitoxide/commit/273895a06ddfff33c6197799d7e63e8382b4b5e3)) + - Update doc comment. ([`322f825`](https://github.com/Byron/gitoxide/commit/322f82529c1b5fb22406a1392217af5d53dcdac4)) + - Handle overflow. ([`61c5285`](https://github.com/Byron/gitoxide/commit/61c52853e61a4cbb356cc607f970e150c827d679)) + - Add doc comment. ([`001862a`](https://github.com/Byron/gitoxide/commit/001862abde9cbb717c83fdf49a6ddf89a4db16e2)) + - Canonicalize ´git_config::values::Integer` values as simple decimal numbers. ([`03f360a`](https://github.com/Byron/gitoxide/commit/03f360a19d365f614e71948df7e8b0c62d13cff4)) + - Refactor values tests. ([`ee4ad7e`](https://github.com/Byron/gitoxide/commit/ee4ad7eadd6675959c9759bb43a08159e3e0daa9)) + - Merge branch 'mzr-fix_empty_values' ([`09c8628`](https://github.com/Byron/gitoxide/commit/09c8628d68ab82d234921b5c6cf80cda4d21802e)) + - Do not treat empty values in sections like multi-line values ([`8b9432c`](https://github.com/Byron/gitoxide/commit/8b9432c22186a290fd05b6272490dad2bccb7f63)) + - Release git-config v0.2.1, git-diff v0.15.0, git-traverse v0.14.0, git-pack v0.18.0, git-odb v0.28.0, git-ref v0.12.1, git-revision v0.1.0, git-repository v0.16.0, gitoxide-core v0.14.0, gitoxide v0.12.0, safety bump 6 crates ([`b612021`](https://github.com/Byron/gitoxide/commit/b612021683ba709b693bd48aef3e2e3c2f5b9ead)) + - Thanks clippy ([`7887d8b`](https://github.com/Byron/gitoxide/commit/7887d8b5bedc49890bd73beb058a9828aa734729)) + - Release git-config v0.2.0 ([`ddfe833`](https://github.com/Byron/gitoxide/commit/ddfe833c13a9fd46aa96283bc3bb372e3f7d82ce)) + - Release git-features v0.20.0, git-config v0.2.0 ([`a6460db`](https://github.com/Byron/gitoxide/commit/a6460db80ba3c49ea37c712465c7cbdefa5c32b6)) + - Release git-hash v0.9.3, git-features v0.20.0, git-config v0.2.0, safety bump 12 crates ([`f0cbb24`](https://github.com/Byron/gitoxide/commit/f0cbb24b2e3d8f028be0e773f9da530da2656257)) + - Derive PartialEq, Eq and Debug on ResolvedGitConfig ([`b2a88a5`](https://github.com/Byron/gitoxide/commit/b2a88a5af259ec07c51d873cac172bb60d7575aa)) + - Merge branch 'svetli-n-refactor_git_config_tests' ([`babaa9f`](https://github.com/Byron/gitoxide/commit/babaa9f5725ab8cdf14e0c7e002c2e1de09de103)) + - Refactor git_config tests. ([`714ef5c`](https://github.com/Byron/gitoxide/commit/714ef5c2cdea2af4026dba91119845ff68298d8d)) + - Make fmt ([`7cf3545`](https://github.com/Byron/gitoxide/commit/7cf354509b545f7e7c99e159b5989ddfbe86273d)) + - Merge branch 'main' into mailmap ([`b2df941`](https://github.com/Byron/gitoxide/commit/b2df941feaf5ae9fa170fa49270189f3527f2eab)) + - Thanks clippy ([`a87844a`](https://github.com/Byron/gitoxide/commit/a87844ab8b03357a52bea6a36002c8f1f1c3a5bb)) + - Revert "Move tests out of git_config." ([`3cbe072`](https://github.com/Byron/gitoxide/commit/3cbe072b024848c0133b6800dc84e68f58eee621)) + - Move tests out of git_config. ([`7fd8369`](https://github.com/Byron/gitoxide/commit/7fd83692411d8c5d392875c877f3f25985123f00)) + - Relative include path from env is error. ([`e303466`](https://github.com/Byron/gitoxide/commit/e303466857484eab5110a11d90f482f32943f74a)) + - Refactor and add skip bom when reading config. ([`2d5768d`](https://github.com/Byron/gitoxide/commit/2d5768dc9b40e0b830e0cc5aefb77f5e030bb8f8)) + - Replace `GitConfigFromEnvError` with `from_env::Error`. ([`e1f8b52`](https://github.com/Byron/gitoxide/commit/e1f8b527067cb3dcc74a33c238ba4edfafa95789)) + - Add path include to `from_env`. Follow duplicate include paths until max include depth is exceeded. ([`2295dc5`](https://github.com/Byron/gitoxide/commit/2295dc5c8fbbd6e27292dfd7a489ad0567421155)) + - Return error when max allowed nested includes depth is passed. ([`9692694`](https://github.com/Byron/gitoxide/commit/969269475d76a3bad323a9cc6e5b9d0f436ddc37)) + - Remove `git_config::values::Value`; use `Bytes` in its place. ([`55c00d8`](https://github.com/Byron/gitoxide/commit/55c00d880535a1f8c37cb7d4405d39ff5a7654a0)) + - Merge branch 'svetli-n-path_value' ([`e8383ca`](https://github.com/Byron/gitoxide/commit/e8383caf6db211beb57d70019fe4ad13ce9066ee)) + - Minor fixes ([`c72ca00`](https://github.com/Byron/gitoxide/commit/c72ca0098e4daa153186789143a192ed38e9656c)) + - Make fmt; fix build ([`ae4f122`](https://github.com/Byron/gitoxide/commit/ae4f122d191f1e4ee63bd11971fd61dfdd60bc8f)) + - Use context in PathError. ([`3b55f25`](https://github.com/Byron/gitoxide/commit/3b55f257a7ff1b89eea6616d61dfd51d409c797b)) + - Small refactoring and documentation. ([`fefb01b`](https://github.com/Byron/gitoxide/commit/fefb01b84f954700b19e010282c4b413de8e03d2)) + - Release git-config v0.1.11 ([`a605b67`](https://github.com/Byron/gitoxide/commit/a605b67294773628590220600f5017c63911f620)) + - Fix usage example in README.md ([`469406d`](https://github.com/Byron/gitoxide/commit/469406dc0d9fece4a06230ef0d8018846202f0ad)) + - Merge branch 'sassman-config-subsection-iter' ([`e348b2b`](https://github.com/Byron/gitoxide/commit/e348b2bc1c6dd33009709dee366d01c9dde38d1f)) + - Implement a draft for subsection fetching ([`e822f56`](https://github.com/Byron/gitoxide/commit/e822f566dcff3f6c784c206dff2fbc5f82d543be)) + - Release git-actor v0.8.0, git-config v0.1.10, git-object v0.17.0, git-diff v0.13.0, git-tempfile v1.0.4, git-chunk v0.3.0, git-traverse v0.12.0, git-pack v0.16.0, git-odb v0.26.0, git-packetline v0.12.3, git-url v0.3.5, git-transport v0.15.0, git-protocol v0.14.0, git-ref v0.11.0, git-repository v0.14.0, cargo-smart-release v0.8.0 ([`8f57c29`](https://github.com/Byron/gitoxide/commit/8f57c297d7d6ed68cf51415ea7ede4bf9263326e)) + - Release git-features v0.19.1, git-actor v0.8.0, git-config v0.1.10, git-object v0.17.0, git-diff v0.13.0, git-tempfile v1.0.4, git-chunk v0.3.0, git-traverse v0.12.0, git-pack v0.16.0, git-odb v0.26.0, git-packetline v0.12.3, git-url v0.3.5, git-transport v0.15.0, git-protocol v0.14.0, git-ref v0.11.0, git-repository v0.14.0, cargo-smart-release v0.8.0 ([`d78aab7`](https://github.com/Byron/gitoxide/commit/d78aab7b9c4b431d437ac70a0ef96263acb64e46)) + - Release git-hash v0.9.1, git-features v0.19.1, git-actor v0.8.0, git-config v0.1.10, git-object v0.17.0, git-diff v0.13.0, git-tempfile v1.0.4, git-chunk v0.3.0, git-traverse v0.12.0, git-pack v0.16.0, git-odb v0.26.0, git-packetline v0.12.3, git-url v0.3.5, git-transport v0.15.0, git-protocol v0.14.0, git-ref v0.11.0, git-repository v0.14.0, cargo-smart-release v0.8.0, safety bump 4 crates ([`373cbc8`](https://github.com/Byron/gitoxide/commit/373cbc877f7ad60dac682e57c52a7b90f108ebe3)) + - Prepar changelogs for cargo-smart-release release ([`8900d69`](https://github.com/Byron/gitoxide/commit/8900d699226eb0995be70d66249827ce348261df)) + - Compatibility with Rust <1.53 ([`61af06b`](https://github.com/Byron/gitoxide/commit/61af06b905926849abce19677ff4b9ac05d625a3)) + - Thanks clippy ([`7dd2313`](https://github.com/Byron/gitoxide/commit/7dd2313d980fe7c058319ae66d313b3097e3ae5f)) + - Release git-actor v0.7.0, git-config v0.1.9, git-object v0.16.0, git-diff v0.12.0, git-traverse v0.11.0, git-pack v0.15.0, git-odb v0.25.0, git-packetline v0.12.2, git-transport v0.14.0, git-protocol v0.13.0, git-ref v0.10.0, git-repository v0.13.0, cargo-smart-release v0.7.0 ([`d3f9227`](https://github.com/Byron/gitoxide/commit/d3f922781a81e8fbb81aa47afdbe9afeb06d666b)) + - Release git-features v0.18.0, git-actor v0.7.0, git-config v0.1.9, git-object v0.16.0, git-diff v0.12.0, git-traverse v0.11.0, git-pack v0.15.0, git-odb v0.25.0, git-packetline v0.12.2, git-transport v0.14.0, git-protocol v0.13.0, git-ref v0.10.0, git-repository v0.13.0, cargo-smart-release v0.7.0, safety bump 12 crates ([`acd3737`](https://github.com/Byron/gitoxide/commit/acd37371dcd92ebac3d1f039224d02f2b4e9fa0b)) + - Adjust changelogs prior to release ([`ec38950`](https://github.com/Byron/gitoxide/commit/ec3895005d141abe79764eaff7c0f04153e38d73)) + - Fix build warnings related to pin-project lite ([`126aeec`](https://github.com/Byron/gitoxide/commit/126aeec1f4cb358c7d24fec4fb0a92e7ff9319e8)) + - Thanks clippy ([`db1bb99`](https://github.com/Byron/gitoxide/commit/db1bb99101a9248b464b0df9f526067b8f2a184e)) + - Add `GitConfig::from_env_paths` with git-like sequence resolution ([`aec51a2`](https://github.com/Byron/gitoxide/commit/aec51a2240c548a0737e61aeaebc2997945af197)) + - Release git-config v0.1.8, git-object v0.15.1, git-diff v0.11.1, git-traverse v0.10.1, git-pack v0.14.0, git-odb v0.24.0, git-packetline v0.12.1, git-transport v0.13.1, git-protocol v0.12.1, git-ref v0.9.1, git-repository v0.12.0, cargo-smart-release v0.6.0 ([`f606fa9`](https://github.com/Byron/gitoxide/commit/f606fa9a0ca338534252df8921cd5e9d3875bf94)) + - Better changelog descriptions. ([`f69b2d6`](https://github.com/Byron/gitoxide/commit/f69b2d627099639bc144fd94fde678d84a10d6f7)) + - Adjusting changelogs prior to release of git-config v0.1.8, git-object v0.15.1, git-diff v0.11.1, git-traverse v0.10.1, git-pack v0.14.0, git-odb v0.24.0, git-packetline v0.12.1, git-transport v0.13.1, git-protocol v0.12.1, git-ref v0.9.1, git-repository v0.12.0, cargo-smart-release v0.6.0, safety bump 5 crates ([`39b40c8`](https://github.com/Byron/gitoxide/commit/39b40c8c3691029cc146b893fa0d8d25d56d0819)) + - Remove stale clippy allow ([`8441e92`](https://github.com/Byron/gitoxide/commit/8441e9217def0c77cfb69a75d98644ec6a9b46d9)) + - Note, not zero-copy nor alloc ([`75879b0`](https://github.com/Byron/gitoxide/commit/75879b0997afe87af96ccdff44b2c1a696aa223e)) + - Comment ([`4b00d68`](https://github.com/Byron/gitoxide/commit/4b00d6898bd21a7bd924b39c0ddb90f7c36e014b)) + - Lint ([`e700284`](https://github.com/Byron/gitoxide/commit/e7002844fbab0d415b9656395450402f2de7539b)) + - Format ([`960dcdc`](https://github.com/Byron/gitoxide/commit/960dcdc6752685e19b97e56f3fae9bc45a9ced4c)) + - Add multi value test ([`f3bcefb`](https://github.com/Byron/gitoxide/commit/f3bcefbd83d5c6f78a710b031c93342658b4a3a1)) + - Assert error kind ([`763266d`](https://github.com/Byron/gitoxide/commit/763266d24746247dc333916761561a12a210a767)) + - Assert io error ([`03541c5`](https://github.com/Byron/gitoxide/commit/03541c579027dc4b00745f573bb41c043cea087a)) + - Not mutable ([`0cfe8a4`](https://github.com/Byron/gitoxide/commit/0cfe8a40c7087fa744cd0b51878c7369f89a3801)) + - Rename test ([`bfcad07`](https://github.com/Byron/gitoxide/commit/bfcad07650398e83bb27201b25e61342ad20a03e)) + - Assert invalid paths ([`be4a4ea`](https://github.com/Byron/gitoxide/commit/be4a4ea2735060aee59f23cc742b6d97a324cb79)) + - Assert config len ([`bbce210`](https://github.com/Byron/gitoxide/commit/bbce210326311ef1d10b12d19c35b39a7606412b)) + - Remove debug print ([`cdf88e6`](https://github.com/Byron/gitoxide/commit/cdf88e6f8a1dab3c7dbd24314232908ae4a7b8ad)) + - First pass ([`a424d5a`](https://github.com/Byron/gitoxide/commit/a424d5adff97adf421aa9b1a3da9c39148c12144)) + - Release git-hash v0.7.0, git-features v0.16.5, git-actor v0.5.3, git-config v0.1.7, git-validate v0.5.3, git-object v0.14.1, git-diff v0.10.0, git-tempfile v1.0.3, git-lock v1.0.1, git-traverse v0.9.0, git-pack v0.12.0, git-odb v0.22.0, git-packetline v0.11.0, git-url v0.3.4, git-transport v0.12.0, git-protocol v0.11.0, git-ref v0.8.0, git-repository v0.10.0, cargo-smart-release v0.4.0 ([`59ffbd9`](https://github.com/Byron/gitoxide/commit/59ffbd9f15583c8248b7f48b3f55ec6faffe7cfe)) + - Remove after ([`cb72bef`](https://github.com/Byron/gitoxide/commit/cb72befbe08590f29489bde1b85c1582d729e0c4)) + - Mark tests as serial ([`0456142`](https://github.com/Byron/gitoxide/commit/0456142c2ee79c39fd738b5e0ef5a258e56d524f)) + - Add a test for multiple sections ([`0ad6438`](https://github.com/Byron/gitoxide/commit/0ad6438b0c19ef6cd7db469cb3f45f3f820665fd)) + - Format ([`d743ef8`](https://github.com/Byron/gitoxide/commit/d743ef8a4fb08511100650b7cbd027491ecb54de)) + - Add a test for a single key value pair ([`a64d312`](https://github.com/Byron/gitoxide/commit/a64d312313ae9f268747e400ba78cd6254d91426)) + - Add a test case for GIT_CONFIG_COUNT parse error ([`a864812`](https://github.com/Byron/gitoxide/commit/a86481207c592eef9abf3b382fe658370657d296)) + - Add test case for GIT_CONFIG_COUNT=0 ([`c33b498`](https://github.com/Byron/gitoxide/commit/c33b498cac29f04d260e361622a4ee86c035a9c1)) + - Release git-config v0.1.6 ([`b1b6fe0`](https://github.com/Byron/gitoxide/commit/b1b6fe0af52d0ec133cd9ec9ffd5a173ba14a5d2)) + - [repository #185] rustfmt ([`dfbb015`](https://github.com/Byron/gitoxide/commit/dfbb015a89db47c79015135870013ecc384c4aea)) + - [config #185] refactor ([`509c938`](https://github.com/Byron/gitoxide/commit/509c938dd061060141756ee791cdcb6017934fe2)) + - [config #185] Count lines correctly on windows… ([`57203ce`](https://github.com/Byron/gitoxide/commit/57203ce5d5e3c481b69c3ca173e4b00f11aaf7d7)) + - [config #185] add test for handling windows formatted files… ([`2a2a89f`](https://github.com/Byron/gitoxide/commit/2a2a89f68cc45e27a1cf0d33fc644ebabc762302)) + - [config #185] flyby refactor ([`9b9ffa3`](https://github.com/Byron/gitoxide/commit/9b9ffa3c1d5ccbea22aa38b740daa8a349494395)) + - Release git-config v0.1.5 ([`150ed76`](https://github.com/Byron/gitoxide/commit/150ed760c8b357e5c40ec0bd8d0cd849b39c34c0)) + - [various #184] configure docs.rs build features ([`cc50249`](https://github.com/Byron/gitoxide/commit/cc502492c512293e93e95610ca80a71896076ded)) + - [object #177] fix docs ([`2fd23ed`](https://github.com/Byron/gitoxide/commit/2fd23ed9ad556b8e46cf650e23f0c6726e304708)) + - [actor #173] rename immutable::Signature to SignatureRef! ([`96461ac`](https://github.com/Byron/gitoxide/commit/96461ace776d6b351b313d4f2697f2d95b9e196e)) + - Merge branch 'Byron:main' into main ([`dc58eca`](https://github.com/Byron/gitoxide/commit/dc58eca510e5a067acdeaad4b595a34b4598a0cd)) + - Upgrade to nom-7 ([`f0aa3e1`](https://github.com/Byron/gitoxide/commit/f0aa3e1b5b407b2afd187c9cb622676fcddaf706)) + - [smart-release #162] format everything ([`8ff83e5`](https://github.com/Byron/gitoxide/commit/8ff83e5c511ae29979348789bd6e7a2f72b16f1c)) + - Release git-config v0.1.4 ([`535ff79`](https://github.com/Byron/gitoxide/commit/535ff79d6d28d3f08572f4353a8db4da2b658473)) + - [git-config] Resolved config construction ([`1ab44c0`](https://github.com/Byron/gitoxide/commit/1ab44c06b30b745711bda3711b5ce92dfae306be)) + - [config] Allow certain warnings during development, fix docs ([`1a2f408`](https://github.com/Byron/gitoxide/commit/1a2f408d045b48925062646bf014d419bd753086)) + - Don't enable resolved module yet ([`0bd05b2`](https://github.com/Byron/gitoxide/commit/0bd05b22c86b366bdd01be747ffd5207434ece0d)) + - Disable all git-config lints ([`05687b4`](https://github.com/Byron/gitoxide/commit/05687b471cb1cbaa8785ec09177c949773dac05a)) + - Disable lint ([`b4302cd`](https://github.com/Byron/gitoxide/commit/b4302cd257e6c76cd85c3af5f28457a1ed91f098)) + - Rustfmt git-config for consistency ([`b559dd0`](https://github.com/Byron/gitoxide/commit/b559dd0eda1b210eb996b3e9518d6264e614035f)) + - Add todos ([`dbcd79a`](https://github.com/Byron/gitoxide/commit/dbcd79a0b9776ad2e9f5ca0ff2ed965d3d52c104)) + - Fix contains_key ([`50f9122`](https://github.com/Byron/gitoxide/commit/50f91225b903c8d45a7f3c4a3754b03bc80ccc45)) + - Add IntoIterator for SectionBody ([`d37b17c`](https://github.com/Byron/gitoxide/commit/d37b17c55d35b76cd831e51f18c3b0942bc53724)) + - Document GitConfigFromEnvError ([`eb44cf6`](https://github.com/Byron/gitoxide/commit/eb44cf675cb49a313220377b05f0eded422f7e09)) + - More git-config docs ([`f05a669`](https://github.com/Byron/gitoxide/commit/f05a66905c12844515860d0d5e5e113e05df54cb)) + - Release git-config v0.1.3 ([`319a4ae`](https://github.com/Byron/gitoxide/commit/319a4ae2b71f4e847757aa46f1d9fcc4b4ee12ca)) + - [config] pacify clippy ([`ad41ba6`](https://github.com/Byron/gitoxide/commit/ad41ba6c96da28d704163a455c3185aec7050db5)) + - Fix bench path ([`70f9403`](https://github.com/Byron/gitoxide/commit/70f94032ca7fcad5eaa9cd0064720d72569f9c17)) + - Fix bench path ([`bf0004e`](https://github.com/Byron/gitoxide/commit/bf0004e05ede404921073755aadd8ab8f75273c9)) + - Bump git-config to 0.1.2 ([`9c275dc`](https://github.com/Byron/gitoxide/commit/9c275dc6f1a07ebd6c4cc8ae0edae382bd13c0cf)) + - Use newtyped Index and Size ([`15ae2d7`](https://github.com/Byron/gitoxide/commit/15ae2d76bb1b4bd64d3ee50021a359a777e95538)) + - Re-export everything in git-config::file::mod ([`392c131`](https://github.com/Byron/gitoxide/commit/392c13175892ecf6e543ee6a1fd47c62a38f09cb)) + - Fix rustdoc links ([`042eaf4`](https://github.com/Byron/gitoxide/commit/042eaf4b4625ed47b417a9012556ef6fc69aa2d6)) + - Use AsRef<Path> when opening from path ([`515d256`](https://github.com/Byron/gitoxide/commit/515d2564e430da77c092ceb9414a3b3e7071c158)) + - Add GitConfig::from_env ([`17e30a1`](https://github.com/Byron/gitoxide/commit/17e30a1ede39326cda6c64989ab37d979c9c4a29)) + - Add GitConfig::from_path ([`27df3d1`](https://github.com/Byron/gitoxide/commit/27df3d1d5de1e7660beaf599e4931c3cf7c1f99a)) + - Add is_empty and len to GitConfig ([`aa86594`](https://github.com/Byron/gitoxide/commit/aa865942559ee48d7998adb211a6a8f4e0760375)) + - Split file.rs into module ([`da40593`](https://github.com/Byron/gitoxide/commit/da40593b3e4d35dcdf8003123cefc0e367367734)) + - Clippy on tests and thanks clippy ([`a77a71c`](https://github.com/Byron/gitoxide/commit/a77a71cf02d328a2a964388928d6b2a235a0aa85)) + - Thanks clippy ([`e1964e4`](https://github.com/Byron/gitoxide/commit/e1964e43979b3e32a5d4bfbe377a842d2c0b10ea)) + - Merge branch 'parser-into-iter-without-alloc' ([`a799ca8`](https://github.com/Byron/gitoxide/commit/a799ca8d6c2e51303512160ddef7477e176ab01b)) + - Implement Parser::into_iter without extra allocation ([`aa79924`](https://github.com/Byron/gitoxide/commit/aa79924b36c0d717cc65d7471fedd27eb41e83a5)) + - Clippy cleanup; fix CI build ([`3e943f2`](https://github.com/Byron/gitoxide/commit/3e943f2afd5f0cfe7294a21cca8e0344c7dd0216)) + - Thanks clippy ([`6200ed9`](https://github.com/Byron/gitoxide/commit/6200ed9ac5609c74de4254ab663c19cfe3591402)) + - [git-config] Annotate more functions with inline ([`2006acb`](https://github.com/Byron/gitoxide/commit/2006acb381a3a9e807575991a8eeab1ea010af60)) + - (cargo-release) version 0.1.1 ([`e583f70`](https://github.com/Byron/gitoxide/commit/e583f70947803b5b6885a4eb22cd515263177b5b)) + - Thanks clippy ([`17258cc`](https://github.com/Byron/gitoxide/commit/17258cc58767caa6e71227898decd160ad0cdf13)) + - TODO's about 'Iterator::reduce()' are probably not applicable ([`ac1a433`](https://github.com/Byron/gitoxide/commit/ac1a4333612c7ef238b84d15a194d4bc4685cd3a)) + - Thank cargo-doc ([`ca96be1`](https://github.com/Byron/gitoxide/commit/ca96be1654a175606a4af6032b2ace4875334231)) + - [git-config] Finish cleaning up 1.51 clippy lints ([`aec7240`](https://github.com/Byron/gitoxide/commit/aec7240036750c98796b8ef4075758f6b825d293)) + - [git-config] Fix various 1.51 clippy lints; inline ([`d899df0`](https://github.com/Byron/gitoxide/commit/d899df0d9feec1f38b60be73af80113958dfa7d1)) + - [git-config] Fix must_use lints ([`71aff75`](https://github.com/Byron/gitoxide/commit/71aff75d02329caf78c61d3c1dd8ab3c33b8597d)) + - Slim down git-config with cargo-diet ([`1c555e0`](https://github.com/Byron/gitoxide/commit/1c555e04d395eadb6b22639afd41c0892d48fa0d)) + - [git-config] add parse test from git remote ([`63bee9c`](https://github.com/Byron/gitoxide/commit/63bee9c3217689df5fbe36d79857db7cdd349d84)) + - [git-config] Add sections_by_name ([`1f7a533`](https://github.com/Byron/gitoxide/commit/1f7a53357d0f1f2f8164b59e8b276ae61fff552f)) + - [git-config] Add to_owned for parser::Error ([`e316c8c`](https://github.com/Byron/gitoxide/commit/e316c8c7a8864daf2ade0ec8fdf42aa20694805f)) + - [git-config] Add coercion into owned variants ([`6387aea`](https://github.com/Byron/gitoxide/commit/6387aeaefccb2c80f9a276f3a8978be28f23bdfb)) + - Include benches in crate to allow publishing to work ([`949622e`](https://github.com/Byron/gitoxide/commit/949622e461eb2116393ec6f4633ec0cb2e1695b5)) + - Remove clippy-cargo lint until there are no warnings ([`bcacfc9`](https://github.com/Byron/gitoxide/commit/bcacfc9bcf19a0339541b24e84de68d95291c62b)) + - Remove redundant lines from git-ignore file ([`41f118d`](https://github.com/Byron/gitoxide/commit/41f118d2aa560188fd3399d2390aa43794b0af75)) + - Fix format ([`1655b56`](https://github.com/Byron/gitoxide/commit/1655b56b34618d28f67b1ef08b41c598eaf3530e)) + - Remove release-profile ([`3d62449`](https://github.com/Byron/gitoxide/commit/3d62449feed68cef213df31268dfbb9fc54f2d62)) + - Rename_section ([`4975fff`](https://github.com/Byron/gitoxide/commit/4975fff3edc67a39bd0046870ac8c572c09d0f78)) + - Return key iterator ([`adfa460`](https://github.com/Byron/gitoxide/commit/adfa460f8ae0cdbbe8a3b91e0d0c55e46bb9b9ce)) + - Add push_section ([`81271e2`](https://github.com/Byron/gitoxide/commit/81271e24bcf9fc9f7241bbd70d11e0cbbab789a1)) + - More work on sections ([`84e959d`](https://github.com/Byron/gitoxide/commit/84e959da3a19abb451be82d290fa8329ee1df015)) + - Remove offset newtype ([`41da7ed`](https://github.com/Byron/gitoxide/commit/41da7edc1b8661e2e3f53b4b061f7e52c9604a06)) + - Remove section for owned section type ([`11cf526`](https://github.com/Byron/gitoxide/commit/11cf526769998915e542a93d01023f67b3329fa6)) + - More functionality to mutablesection ([`b0cf849`](https://github.com/Byron/gitoxide/commit/b0cf849f32fb6b0e612efa12f279a6e0bb40d49d)) + - Optimize section pushing ([`f409931`](https://github.com/Byron/gitoxide/commit/f4099310dfad85c087bae45a9af2a441569c58fa)) + - Section API ([`0aad0f1`](https://github.com/Byron/gitoxide/commit/0aad0f12ed82caa784abde36a9dd4fe2f0cc83bf)) + - Section stuct ([`21b4fe1`](https://github.com/Byron/gitoxide/commit/21b4fe11001fa4f09718bc5cacc140a0a97e8ab3)) + - Fix example ([`357a761`](https://github.com/Byron/gitoxide/commit/357a76137c8c309b6c16809af32641d1f52a1222)) + - Update readme ([`c2fa869`](https://github.com/Byron/gitoxide/commit/c2fa869ffb37180f67c37f2c229b7b26390d6957)) + - Fix macro comment gen ([`a19c17f`](https://github.com/Byron/gitoxide/commit/a19c17f6d64bd87ec42ec975a9b4a8e641288816)) + - Implement case insensitivity for names ([`c39ff33`](https://github.com/Byron/gitoxide/commit/c39ff332415a7c546af14bf925c9cc5c60b36622)) + - Test MutableMultiValue ([`8cfe67d`](https://github.com/Byron/gitoxide/commit/8cfe67df4f322d7ebfee9f5c7de206cacf08f5ed)) + - More tests, fix mutablevalue ([`377532c`](https://github.com/Byron/gitoxide/commit/377532c4b6a3bf2ac8cc3ca7c3c661cfc954a16c)) + - Fix lints ([`bb7a544`](https://github.com/Byron/gitoxide/commit/bb7a5445272e8abd825751212762a76e7876e9d1)) + - Enable requiring docs ([`68320ca`](https://github.com/Byron/gitoxide/commit/68320ca5ba2d3665bc64580ff9ab12d86b719e56)) + - Clippy fix ([`e7bad2e`](https://github.com/Byron/gitoxide/commit/e7bad2eb2633b60f1bf12b7f53dcda1f431172d4)) + - Docs ([`a1f833c`](https://github.com/Byron/gitoxide/commit/a1f833ccc7c07b37b5285c109aefaabf9a97f202)) + - Add into bytes for gitconfig ([`9b54a5b`](https://github.com/Byron/gitoxide/commit/9b54a5b9df699f2508cf47411293f74252a02473)) + - Docs ([`4d7da4e`](https://github.com/Byron/gitoxide/commit/4d7da4e9ef60a90360e51d5425580d9b309db151)) + - Mutableevent interface ([`014776f`](https://github.com/Byron/gitoxide/commit/014776f8be482d2d61ef532d1cea84e26a42d893)) + - Remove serde code for now ([`fc4ee85`](https://github.com/Byron/gitoxide/commit/fc4ee8585372c023b47c0f28d0c746ff9f8eac5b)) + - Disable serde ([`7c01808`](https://github.com/Byron/gitoxide/commit/7c01808c4fdfee35e339c2d939ef6b013b430f38)) + - Multablemultivalue ([`faa1b93`](https://github.com/Byron/gitoxide/commit/faa1b9368b8dbcfbef10a3bbb027bdef81a377a0)) + - Better test formatting ([`635e5c1`](https://github.com/Byron/gitoxide/commit/635e5c15e39b24767e793e6ab4f230b8913a0760)) + - Add get_multi_value ([`79eeca1`](https://github.com/Byron/gitoxide/commit/79eeca128a3b146e2900cc90f34877cef4fa6e52)) + - Check all sections for lookup before failing ([`01b617d`](https://github.com/Byron/gitoxide/commit/01b617d74f10eab8f87e1032aebeeb6f56a2ae10)) + - Misc improvements ([`87057c9`](https://github.com/Byron/gitoxide/commit/87057c9f03516d6659cb2cc54f330ba4072d6563)) + - Benchmarks ([`b0ff69e`](https://github.com/Byron/gitoxide/commit/b0ff69e9f5260c30ecfd2879b3346437b38cec83)) + - Crate level docs ([`a909bcf`](https://github.com/Byron/gitoxide/commit/a909bcf90fa7ad3995fa15f00417257f66ccb6d0)) + - Integration tests for value extraction ([`d45af63`](https://github.com/Byron/gitoxide/commit/d45af630ab25cfc3fc9b4b10c5038132f23b5c95)) + - Fix drain ([`4f425a7`](https://github.com/Byron/gitoxide/commit/4f425a72cad6b0159e3747ce6a42c4d4343b8e61)) + - Use memrchr ([`ccadf89`](https://github.com/Byron/gitoxide/commit/ccadf89aa13051c55debd6aa7c709b138bab6167)) + - Use drain instead ([`6e5b67b`](https://github.com/Byron/gitoxide/commit/6e5b67b0d5dec90f22cf05b1597201c8a8b9ab80)) + - Use mutablevalue for mut entries ([`13fdda5`](https://github.com/Byron/gitoxide/commit/13fdda5e01cb494f5992be0bb82464392016d4ed)) + - Normalize get_raw_value ([`5952cab`](https://github.com/Byron/gitoxide/commit/5952cab59c3aab7f35683f959737a5c8bef29fb1)) + - Fix get_raw_value, fix returning refs to cows ([`ba982b9`](https://github.com/Byron/gitoxide/commit/ba982b971aa97eaa17d58d7b00f20923171d89eb)) + - Cleanup docs ([`e0a8b8d`](https://github.com/Byron/gitoxide/commit/e0a8b8d808cbed2d7ca902304e6819bfba8f6715)) + - Add tests and docs ([`7caf012`](https://github.com/Byron/gitoxide/commit/7caf012b05c6819f5a897918fc50eb61d76517de)) + - Rename config mod to file ([`a965ebc`](https://github.com/Byron/gitoxide/commit/a965ebcfc08c71255389c62fe43ff479960e7921)) + - Pendantic clippy lints ([`18c9dff`](https://github.com/Byron/gitoxide/commit/18c9dff7c0bde99d4c1d4a7263f86fd4a656d1c0)) + - Remove unnecessarily lifetimes ([`7d0e6b4`](https://github.com/Byron/gitoxide/commit/7d0e6b4fe1cb8e3d752cd5b42fa9167b552d6320)) + - Use str in most cases ([`9fc8993`](https://github.com/Byron/gitoxide/commit/9fc8993a54950e88aa05b0fe85962ee124a86891)) + - Fully comment values ([`8e32d56`](https://github.com/Byron/gitoxide/commit/8e32d5609d81087e17a5dcd15dbe7ed22594aa50)) + - More normalize docs ([`9767b5b`](https://github.com/Byron/gitoxide/commit/9767b5be5a4d45c44e2aba6c2164c800e17ec437)) + - Collaspe if block ([`1cf1f3b`](https://github.com/Byron/gitoxide/commit/1cf1f3be284afb1b0ed3d8f06439a5effc553be0)) + - Better doc ([`ec63ce6`](https://github.com/Byron/gitoxide/commit/ec63ce633578af6adf6239d76f5802aaa842941c)) + - Implement unquoting in normalize ([`7e8ae93`](https://github.com/Byron/gitoxide/commit/7e8ae932f888707fe7466e93ef6c8289749d04d2)) + - Add normalize ([`6c245dc`](https://github.com/Byron/gitoxide/commit/6c245dc654bfd7bc6b82226abd900ba2e8a312cd)) + - Dedup multivar docs ([`236d37b`](https://github.com/Byron/gitoxide/commit/236d37b608b8a912fc005c956c87d251172fff0f)) + - Add todo ([`bc63005`](https://github.com/Byron/gitoxide/commit/bc630057c6f138647f2f8a2d93f8a09e4f4494c3)) + - Implement get_value for GitConfig ([`ca7c1dc`](https://github.com/Byron/gitoxide/commit/ca7c1dca5539bf71e524ba0ee4b40c60bd80f0ad)) + - Use traits instead of from_str ([`ce9b7bf`](https://github.com/Byron/gitoxide/commit/ce9b7bfbb9abe5e74dbef1d3c637876e7d996e52)) + - Use traits instead of shadowing from_str ([`a4ce9b0`](https://github.com/Byron/gitoxide/commit/a4ce9b04012060ab06dabd6658f3e518f994831b)) + - Remove falsevariant ([`e10a4a2`](https://github.com/Byron/gitoxide/commit/e10a4a298371b6641e6184fa8d61bebe8c783923)) + - More tests ([`24a2dfd`](https://github.com/Byron/gitoxide/commit/24a2dfd2d7dd0c1b28b62b7dbebeb539e086016e)) + - Remove unreachable variants ([`93b85e3`](https://github.com/Byron/gitoxide/commit/93b85e38ff46f0f8fdaabcf2c7210bb9e0421254)) + - Use mut vec reference ([`8b68fdb`](https://github.com/Byron/gitoxide/commit/8b68fdb2aeac6dd7211f31333489012647e65f55)) + - Don't use mutex ([`4027daf`](https://github.com/Byron/gitoxide/commit/4027daf93bb931d9b839057b88afcfa849a4ed8c)) + - Documented parsererror ([`0c226ad`](https://github.com/Byron/gitoxide/commit/0c226ad112f7ff70dee20669419ba8f7eae3f0c7)) + - Clippy fixes ([`8618c22`](https://github.com/Byron/gitoxide/commit/8618c2233abada5fb101258cffc8c046b155134b)) + - Don't use stack for error handling ([`819a1d3`](https://github.com/Byron/gitoxide/commit/819a1d3a0bf47c95dc469c7bbf80b3452ef9918d)) + - Very rough error handling ([`45d5250`](https://github.com/Byron/gitoxide/commit/45d52502fb91635f3db3d4b09f69f5d7b2a29e09)) + - Add error trait impl for ParserError ([`d173b4b`](https://github.com/Byron/gitoxide/commit/d173b4bcc855365640d8931460d0bed748264817)) + - Basic error reporting ([`f293334`](https://github.com/Byron/gitoxide/commit/f293334d984755053b532706134df88a5c57a43f)) + - Move fully_sumed to test_util ([`41245eb`](https://github.com/Byron/gitoxide/commit/41245ebd74a0ee40bbc91a2e2f9bca670da02b16)) + - Don't immediately drop fuzzer values ([`568d360`](https://github.com/Byron/gitoxide/commit/568d36084f2c4f94a4757588ca17078ce523a6ac)) + - Add more fields to cargo.toml ([`89791fd`](https://github.com/Byron/gitoxide/commit/89791fd0f3bba9d22418302fe862896a6ccacdc4)) + - Add basic fuzzer ([`cf41bb3`](https://github.com/Byron/gitoxide/commit/cf41bb300fe8213a7b4b02329cd987fd55d2ac9c)) + - Add from_bytes variants for parser ([`954f433`](https://github.com/Byron/gitoxide/commit/954f4338e7496208b1e8b13a105eec9f5c07ba76)) + - Exclude fuzz folder from cargo ([`afe4ac7`](https://github.com/Byron/gitoxide/commit/afe4ac754f0843d8350504200a2a833bf158c335)) + - Add tests for boolean ([`0353033`](https://github.com/Byron/gitoxide/commit/03530334a956e4337d3e92d2bb1a2be5c0278014)) + - Use lto and single codegen unit for release ([`53077bd`](https://github.com/Byron/gitoxide/commit/53077bda06aeed84b3985f941e7f4660b5e0d5e8)) + - Select nom features ([`79dc19f`](https://github.com/Byron/gitoxide/commit/79dc19f7a7517f0953a1b14b180e54cfe110bab8)) + - Make serde optional, clippy lints ([`5defc4a`](https://github.com/Byron/gitoxide/commit/5defc4a64a9ee37910c2caa9f23253adead1ab6f)) + - Add ColorValue tests ([`5b7cc13`](https://github.com/Byron/gitoxide/commit/5b7cc13e62aff1014369aa7a8d64dc9eaad1f0cd)) + - Add tests for ColorAttribute ([`0cc9cd6`](https://github.com/Byron/gitoxide/commit/0cc9cd6bd1d383bbca97610f6a1a67119a90ab56)) + - Fix docs ([`b0fc08b`](https://github.com/Byron/gitoxide/commit/b0fc08b940dbca8d6b78f29f5d4e391c8b535121)) + - Use BStr instead ([`ec2602c`](https://github.com/Byron/gitoxide/commit/ec2602cfce2867ed341a1bff5e26ca82785a4434)) + - Add key-value delimination event ([`df0da82`](https://github.com/Byron/gitoxide/commit/df0da822d23708cc488027c0830895f0274ad9ce)) + - Gitconfig writing to string ([`b59a51a`](https://github.com/Byron/gitoxide/commit/b59a51af3896ba510c30db1044a11472df7d3998)) + - Document multivar behavior better ([`e43518e`](https://github.com/Byron/gitoxide/commit/e43518ebdadd739bd4edc90cd4ef279000b4f94e)) + - Use Cow instead of strs ([`ff8ee4a`](https://github.com/Byron/gitoxide/commit/ff8ee4a0352ad7fb8a2c93c0e09f5b3b2c15d3a9)) + - Implement get_mut for gitconfig ([`1d8e58b`](https://github.com/Byron/gitoxide/commit/1d8e58b6008c36141ba38fd37bfbbbdb458b35ef)) + - Remove meme comment ([`897450c`](https://github.com/Byron/gitoxide/commit/897450c2e7d89388072d806d4646e1bbac4df422)) + - Test get_raw_values ([`0ea6210`](https://github.com/Byron/gitoxide/commit/0ea62105abd84b3b73996f8809772b6856fab6fa)) + - Finish raw value queries for gitconfig ([`4b7f218`](https://github.com/Byron/gitoxide/commit/4b7f21875ee12a86c56c2942f2981a79993a3a2a)) + - Handle empty git-config file for parser ([`a516885`](https://github.com/Byron/gitoxide/commit/a5168857071db604f3b2e7191ed749bcdb0354af)) + - Fully document parser ([`f66e0be`](https://github.com/Byron/gitoxide/commit/f66e0bedff4c7ba9d455bd7e4d24d299b21fb109)) + - Completely refactor config ([`b820d6c`](https://github.com/Byron/gitoxide/commit/b820d6c987263251575844547298e69ddb52d8c4)) + - Booleans now retain original value ([`8ea467e`](https://github.com/Byron/gitoxide/commit/8ea467e01e7e231827041ada0c531f7a3e66715f)) + - Parser is now perfect ([`3f708ec`](https://github.com/Byron/gitoxide/commit/3f708ecadee910aebc007fdba1aae004e3344104)) + - More work on parser ([`2691756`](https://github.com/Byron/gitoxide/commit/26917564cf007949ee7f71ee48e10be1efb6f6a0)) + - More work ([`eb07890`](https://github.com/Byron/gitoxide/commit/eb07890347fda7c24721ea91fca5a2eaa519d5b3)) + - Complete initial parser ([`d721625`](https://github.com/Byron/gitoxide/commit/d72162555cc677c4ff143d01f338fc4508a7b11e)) + - Deny rust-2018-idioms ([`a4d2a4b`](https://github.com/Byron/gitoxide/commit/a4d2a4b248f0f22ffd6c8c567d780a152831347a)) + - Add remaining docs for all types in 'git-config' crate ([`b7790b4`](https://github.com/Byron/gitoxide/commit/b7790b4ce3884daaff198890f4a8fb36c38f2230)) + - More planning for config parser implementation ([`9676db9`](https://github.com/Byron/gitoxide/commit/9676db9f58b5776986cfd7185a0ade93f89cb080)) + - Add missing '.' at end of doc comments ([`7136854`](https://github.com/Byron/gitoxide/commit/71368544f97369a4d371d43513607c4805bd0fd0)) + - Signal the compiler that configuration edits must be used ([`14b17e4`](https://github.com/Byron/gitoxide/commit/14b17e4d202fae2eeabfe46552a1c0b17e30ac9c)) + - Better docs for git-config; name method for Entry ([`5ab4bdb`](https://github.com/Byron/gitoxide/commit/5ab4bdbc3f1760bcb667d9a1b26eb069084581eb)) + - Refactor; more comments ([`8d933cb`](https://github.com/Byron/gitoxide/commit/8d933cbd08977ad0dc70ed18b37e7e06ab24c4fb)) + - Time-constrained write-down of some high-level concepts of git-config structures ([`157fa2a`](https://github.com/Byron/gitoxide/commit/157fa2a31e0382ee2c8524ff7862873787f5f648)) + - Cargo clippy Rust 1.48 ([`475a68c`](https://github.com/Byron/gitoxide/commit/475a68ce33b895de911939c51afa159df534f7b8)) + - A path towards making config Files editable ([`bc008c3`](https://github.com/Byron/gitoxide/commit/bc008c32a16849a212eced783aa14727765004c3)) + - Additional setters for more fluid edits ([`5a54dae`](https://github.com/Byron/gitoxide/commit/5a54dae6470c5dcf48bf96c16c5bbe2a8951be6a)) + - Sketch out editing lossless of Files ([`8f00063`](https://github.com/Byron/gitoxide/commit/8f00063bc9b6a63ffe44e58945be55acca40a714)) + - Skip comments as well ([`32cc684`](https://github.com/Byron/gitoxide/commit/32cc6849444c16a3d2917c6de62e47597c9979da)) + - Stop entry iteration when next section is encountered ([`83a1b83`](https://github.com/Byron/gitoxide/commit/83a1b83a1f7a0ff22850efc7b5b460f0c1ed8230)) + - Sketch of iteration over sections and entries ([`acb8947`](https://github.com/Byron/gitoxide/commit/acb894762b38f77d21e6d70936727cf0daeaff6f)) + - Sketch out section and entries access ([`06679d9`](https://github.com/Byron/gitoxide/commit/06679d9b69575183231ddb22edd89ab29357632d)) + - Refactor ([`b5fa727`](https://github.com/Byron/gitoxide/commit/b5fa727403a78e5f9238dd36d8b071eec425d731)) + - Turn off 'unused' warnings for experimental git-config crate ([`0b52eb0`](https://github.com/Byron/gitoxide/commit/0b52eb0e75a268c5c7b6475677fd20acace3435b)) + - Revert "remove git-config from workspace while it's so fresh…" ([`99214f4`](https://github.com/Byron/gitoxide/commit/99214f4c1097fa8da8f14f1279caf00db78fa822)) + - Remove git-config from workspace while it's so fresh… ([`84e0d19`](https://github.com/Byron/gitoxide/commit/84e0d19ab2285916cb6a6b941ec2206aef485d56)) + - Plan how to deal with whitespace and comments to be lossless ([`eb5a534`](https://github.com/Byron/gitoxide/commit/eb5a534340396429d7c2c95e71b0a23457d954f4)) + - Refactor ([`3846bab`](https://github.com/Byron/gitoxide/commit/3846bab8c7ae53e5528388522bf4571260ec4ae6)) + - Very first sketch of types for read-only git config ([`e2a39c9`](https://github.com/Byron/gitoxide/commit/e2a39c96a96b3ec9de519c685fe9caddeb89342c)) + - Stub for git-config crate ([`3539531`](https://github.com/Byron/gitoxide/commit/3539531adb06e8f59609f0a83e8ed94d0864c0a1)) +</details> + +## 0.16.0 (2023-02-09) + +### Documentation + + - <csr-id-39ed9eda62b7718d5109135e5ad406fb1fe2978c/> fix typos + +### New Features (BREAKING) + + - <csr-id-2faad43d11283ff06381c51d2466307cfb8736ff/> transfer knowledge about configuration and its usage into the type system. + That way it's possible to use configuration overrides, even though ultimately being strings, + in a type-safe manner and leverage code-completion while at it. + + In that process, we also change `Repository::(committer|Author)()` to return + `Option<Result<...>>` to be able to account for date parse errors. + +## 0.15.1 (2023-01-10) + +A maintenance release without user-facing changes. + +## 0.15.0 (2023-01-09) + +A maintenance release without user-facing changes. + +## 0.14.0 (2022-12-30) + +A maintenance release without user-facing changes. + +## 0.13.0 (2022-12-19) + +### New Features + + - <csr-id-5b9bffe8a5eec738e892224a7e18f98c8430d8a4/> `SectionMut::push_with_comment(key, comment)` to add a new variable with a comment. + This is useful for providing more information about a value at hand, especially if it was + added programmatically and then shows up in the configuration. + - <csr-id-e4bf8f0072e60a7a2df94690c8d0b13b1f3038bb/> Add the `Source::EnvOverride` to have a place for 'terminal' overrides. + That way environment variables represented via git-configuration + can be integrated into git configuration, making clearer what's + going to happen even when looking at the configuration via + `gix config`. + + The implementation has to be careful though about assuring there + is no more specific configuration key, like `http.<URL>.proxy` that + would override the one from the environment, which always has + the final word. + - <csr-id-5fa95460db843f7dcfe68002b303b8b7649846dd/> comfort API like `string_by_key(key)` takes a key like `"remote.origin.url"`, add `section_by_key("remote.origin")` as well. + That way it's the most comfortable way to query values and very + similar to how git does it, too. + + Additionally, sections can be obtained by section key, both mutably and immutably for completeness. + +### New Features (BREAKING) + + - <csr-id-2b36d99eaf3ed24ce4cb736a3dd48440dc0c73b7/> `File::new_section()` and related now returns their `id` as well. + That way it's possible to more easily interact with it later, for instance + when one wants to delete it. + +### Bug Fixes (BREAKING) + + - <csr-id-0c98ec8fc7d8cc3195472a04fde4a681f620725f/> subsections are identified as `&BStr` in entire API. + Technically they can be any value (except for newlines and unescaped double quotes), + and these values might be paths and everything that comes with it, like + illformed UTF8. In order to be able to represent everything that + git can represent, we don't enforce UTF8 anymore for subsection names. + + Note that section names and key names are required to be valid UTF8 + (and even alphanumeric ascii), which makes illformed UTF8 very unlikely + there. + +## 0.12.0 (2022-11-21) + +### New Features + + - <csr-id-7d7bd02d4e0678565f58c5da83fd1ad88c60e911/> read worktree specific configuration of main worktrees. + Supporting this is useful when interacting with worktrees that have been + created with sparse worktree support, which moves some configuration + values into those for the worktree at hand. + + Note that linked worktrees are not supported - for that use + `gix` instead. + +### New Features (BREAKING) + + - <csr-id-3d8fa8fef9800b1576beab8a5bc39b821157a5ed/> upgrade edition to 2021 in most crates. + MSRV for this is 1.56, and we are now at 1.60 so should be compatible. + This isn't more than a patch release as it should break nobody + who is adhering to the MSRV, but let's be careful and mark it + breaking. + + Note that `gix-features` and `gix-pack` are still on edition 2018 + as they make use of a workaround to support (safe) mutable access + to non-overlapping entries in a slice which doesn't work anymore + in edition 2021. + +## 0.11.0 (2022-11-17) + +A maintenance release without user-facing changes. + +## 0.10.0 (2022-11-06) + +### Bug Fixes + + - <csr-id-839f776454d7a8522c0f2887c90fc41b20576bd9/> `File::boolean()` now correctly handles implicit bools across sections. + This means it will return the last implicit value as true, instead of + ignoring it to fall back onto an explicit boolean value that might be + false. + - <csr-id-7d92c61abc2c54f4a64c35b37bcd1843a8d1da9c/> `file::SectionMut::push()` now properly creates empty values. + Previously, when calling `.push("key", None)`, the resulting internal + state would be slightly wrong causing certain invariants and expectations + to be unfulfilled even though it might have looked OK to most users. + + Now it will uphold the invariant for empty values, those without an + `=` character. + +## 0.9.0 (2022-10-10) + +### New Features + + - <csr-id-aa5d66f60bd6c9ef404ebc120b613e0cf055b2c9/> add `parse::section::header::is_valid_subsection()` function. + It can be useful to validate subsection names without having to + construct an entire `Header` (which also includes a name). + - <csr-id-5df2a2a5a9addbda7dcc68b2f8f7f4a48d9720c6/> Add various methods to iterate sections along with their id, and mutate them. + As section names are not unique, it was previously not possible to + iterate sections and then mutate them as one wouldn't be able to refer + to the exact section that was just traversed, after all, there can be + many sections named `remote "origin"`. + + With the new methods it's possible to uniquely refer to each section + for mutation and removal. + +### Bug Fixes + + - <csr-id-9c1e639979a9615fd8334ce0e3a809df137776f6/> greatly improve whitespace handling when removing values. + Previously, newlines would remain past a value, and whitespace could + remain before one. + + Now both are removed to simulate removing an actual line. + - <csr-id-e533993e8f861ba7a6600aab114ddfecc8a85ee2/> `File::remove_section()` was fixed to allow re-adding a similarly named section. + We also add `File::remove_section_by_id()` to make it possible to remove + specific sections. + +## 0.8.0 (2022-09-20) + +### Changed + + - <csr-id-5ad296577d837b0699b4718fa2be3d0978c4e342/> `gix-config` now uses `gix-config-value`. + +### Changed (BREAKING) + + - <csr-id-27fb1ce27d2985eb1ee8bee5fffaf759902571fb/> Add `Kind::GitInstallation` for a way to obtain special git-installation configuration paths. + Note that these are lazily cached as they call the `git` binary. + - <csr-id-99905bacace8aed42b16d43f0f04cae996cb971c/> upgrade `bstr` to `1.0.1` + +## 0.7.1 (2022-08-28) + +Maintenance release without user-facing changes. + +## 0.7.0 (2022-08-24) + +<csr-id-f7f136dbe4f86e7dee1d54835c420ec07c96cd78/> +<csr-id-69ec5940d3f37eb4dace8f1ed7616b5988984d15/> + +### Chore + + - <csr-id-f7f136dbe4f86e7dee1d54835c420ec07c96cd78/> uniformize deny attributes + +### New Features + + - <csr-id-9937d0e00df3a523484c7ae2850be2712a1a4c9a/> `File::set_raw_value_filter()` to set values only in sections passing a filter. + - <csr-id-17455c9d93ad38bfee2560f5a4e60324dee3b4e5/> `File::section_mut_or_create_new_filter()` to allow choosing which sections to add values to. + - <csr-id-5902f54b93101a6290fcf89f9f13fdbea3678e00/> `File::section_mut_or_create_new(…)` to obtain an existing or new section for mutation. + - <csr-id-b1c40b0364ef092cd52d03b34f491b254816b18d/> use docsrs feature in code to show what is feature-gated automatically on docs.rs + - <csr-id-517677147f1c17304c62cf97a1dd09f232ebf5db/> pass --cfg docsrs when compiling for https://docs.rs + +### Bug Fixes + + - <csr-id-08c50a47fa901457194539c7db74ad47ab2f8b60/> Properly handle boolean values such that `a` is true but `a=` is false. + This is even consistent when no booleans are used, such that `a` has no + value as if it is not present, it's only available for booleans which + must be specified. + - <csr-id-7c585162454c476fe93f032c8a2329cffd7c054f/> Keep track of a severe limitation and prepare tests for fixing it. + This also changes behaviour, but merely removes a hack in `Boolean` + which considered empty strings true, even though they are supposed to be + false. + +### Changed (BREAKING) + + - <csr-id-2b2357e9cc54539e0dbe7c0e22802f2b884160d8/> Add `File::set_raw_value()` to unconditionally set single values, and make the value itself easier to provide. + +### New Features (BREAKING) + + - <csr-id-b6cd6ace412b0c0df4bacbe7ed7ef6608f27909c/> `file::SectionMut::push()` now supports values without key-value separator. + These make a difference as those without `=` are considered boolean + true. + Currently pushing onto a section is the only way to write them. + +### Other (BREAKING) + + - <csr-id-69ec5940d3f37eb4dace8f1ed7616b5988984d15/> `File::set_raw_[multi_]value()` to `::set_existing_raw_[multi_]value`. + This makes clear that the method will fail if the value doesn't yet + exist. + +## 0.6.1 (2022-08-17) + +A maintenance release without user facing changes. + +## 0.6.0 (2022-07-22) + +<csr-id-32d5b3c695d868ba93755123a25b276bfbe55e0a/> +<csr-id-9cd99337333f5ef4b30e0ec9461fc087699576e6/> +<csr-id-0076dcf9b37f1d633bdad5573b40d34a9fbaba90/> +<csr-id-a8604a237782f8d60a185d4730db57bad81424a6/> + +### New Features + + - <csr-id-1bc96bf378d198b012efce9ec9e5b244a91f62bc/> following includes is now non-fatal by default + Otherwise it would be relatively easy to fail gitoxide startup, + and we want to be closer to the behaviour in git which ignores + most of the errors. + - <csr-id-f9ce1b5411f1ac788f71060ecf785dda9dfd87bf/> `File::from_git_dir()` as comfortable way to instantiate most complete git configuration. + - <csr-id-14a68a6a78a09f8ae56e30e3b7501de66ef31fdc/> `File` now compares actual content, ignoring whitespace and comments. + - <csr-id-7dadfd82494d47e36d3f570988eaf3c6b628977f/> `File::new_environment_overrides()` to easily instantiate overrides from the environment. + - <csr-id-146eeb064822839bc46fd37a247a1b9a84f64e40/> `File::new_globals()` can instantiate non-local configuration with zero-configuration. + - <csr-id-e701e053fd05850973930be0cefe73e8f3604d40/> `Source::storage_location()` to know where files should be located. + - <csr-id-fff088485dd5067976cc93d525903b39aafea76a/> `file::ValueMut::(section|into_section_mut)()` to go from value to the owning section. + This can be useful if the value was obtained using `raw_value_mut()`. + - <csr-id-f5f2d9b3fef98d9100d713f9291510fa4aa27867/> `Source::is_in_repository()` to find out if a source is in the repository. + - <csr-id-91e718f0e116052b64ca436d7c74cea79529e696/> `parse::key` to parse a `remote.origin.url`-like key to identify a value + - <csr-id-26147a7a61a695eda680808ee4aab44a890b2964/> Add `File::detect_newline_style()`, which does at it says. + - <csr-id-0ad1c9a5280cc172432b5258e0f79898721bac68/> `File::frontmatter()` and `File::sections_and_postmatter()`. + - <csr-id-fc7e311b423c5fffb8240d9d0f917ae7139a6133/> `parse::Event::to_bstr_lossy()` to get a glimpse at event content. + - <csr-id-09966a8ea4eaa3e0805e04188de86dd1bac9f388/> `File::append()` can append one file to another rather losslessly. + The loss happens as we, maybe for the wrong reasons, automatically + insert newlines where needed which can only be done while we still know + the file boundaries. + - <csr-id-56ae5744e8957e617f3a0ebc4d725846b18d93f8/> `file::Section::meta()` to access a section's metadata. + - <csr-id-6f97bf0c3e7164855cf5aa53462dbc39c430e03f/> `File::sections()` to obtain an iterator over all sections, in order. + - <csr-id-5418bc70e67476f8778656f2d577f1f9aa65ffbe/> place spaces around `key = value` pairs, or whatever is used in the source configuration. + - <csr-id-8118644625dc25b616e5f33c85f5100d600766e4/> proper escaping of value bytes to allow round-tripping after mutation + - <csr-id-9f59356b4f6a1f5f7f35a62c9fbe4859bf8e8e5f/> whitespace in newly pushed keys is derived from first section value. + That way, newly added key-value pairs look like they should assuming + all keys have the same indentation as the first key in the section. + + If there is no key, then the default whitespace will be double-tabs + like what's common in git. + - <csr-id-db1f34dfb855058ac08e97d4715876b5db712f61/> `File::from_str()` implementation, to support `let config: File = "[core]".parse()?` + - <csr-id-9157717c2fb143b5decbdf60d18cc2bd99dde775/> whitespace in mutable sections can be finely controlled, and is derived from existing sections + - <csr-id-ae3895c7882e0a543a44693faee5f760b49b54d7/> `parse::Header::new(…)` with sub-section name validation + - <csr-id-d087f12eec73626eb327eaacef8ebb3836b02381/> Add `parse::(Event|section::Header|Comment)::write_to(…)`. + Now it's possible to serialize these types in a streaming fashion and + without arbitrarily enforcing UTF-8 on it + - <csr-id-5a8f242ee98793e2467e7bc9806f8780b9d320ce/> `serde1` feature to add limited serde support + +### Bug Fixes + + - <csr-id-6c1588fd1a2fa80fd866787cbf4bcc6e5b51abe6/> maintain insertion order of includes on per-section basis at least. + Note that git inserts values right after the include directive, + 'splitting' the section, but we don't do that and insert new values + after the section. Probably no issue in practice while keeping + our implementation simple. + - <csr-id-f7bd2caceb87a179288030e0771da2e4ed6bd1e4/> maintain newline format depending on what's present or use platform default. + Previously implicit newlines when adding new sections or keys to + sections was always `\n` which isn't correct on windows. + + Now the newline style is detected and used according to what's present, + or in the lack of content, defaults to what's correct for the platform. + - <csr-id-0d07ef1aa4a9e238c20249d4ae2ed19e6740308a/> validate incoming configuration keys when interpreting environment variables. + - <csr-id-6b901843cb18b3d31f8b0b84bb9ebbae279aff19/> `Boolean` can use numbers to indicate true or false, drops support for `one` and `zero`. + - <csr-id-94dde44e8dd1a0b8d4e11f2627a3f6b345a15989/> `file::MutableSection::remove()` now actually removes keys _and_ values. + - <csr-id-048b92531eb877a5a128e702504891bf1e31becf/> `file::MutableMultiValue` escapes input values and maintains key separator specific whitespace. + - <csr-id-f911707b455ba6f3800b85f667f91e4d56027b91/> value normalization (via `value::normalize()` handles escape sequences. + The latter ones are `\n`, `\t` and `\b` which are the only supported + ones in values of gix-config files. + - <csr-id-44dfec07480cc2ac6fd01674b748cc03af51fed1/> stable sort order for `File::sections_by_name_with_header()` + - <csr-id-1ea919d5ff81ab7b01b8201386ef63c7e081b537/> count newlines (for error display) in multi-line values as well + - <csr-id-1e71e71c984289f0d7e0a39379ee6728918b7dc5/> auto-normalize string values to support quote removal in case of strings. + Related to https://github.com/starship/starship/pull/3883 . + +### Other + + - <csr-id-32d5b3c695d868ba93755123a25b276bfbe55e0a/> :Events::from_bytes()` with `filter` support. + +### Changed (BREAKING) + + - <csr-id-17c83d55f8942788aac5eb1bea22a48daa045bf4/> add `File::resolve_includes()` and move its error type to `file::includes`. + - <csr-id-5221676e28f2b6cc1a7ef1bdd5654b880965f38c/> add `File::from_bytes_owned()` and remove `File::from_path_with_buf()` + - <csr-id-98d45c2f59863fdee033b38e757cec09593f6892/> remove `File::from_env_paths()`. + It's replaced by its more comfortable `new_globals()`. + - <csr-id-230a523593afcfb8720db965ff56265aaceea772/> untangle `file::init::…` `Option` and `Error` types. + This moves types to where they belong which is more specific instead + of having a catch-all `Error` and `Options` type. + - <csr-id-3f3ff11a6ebe9775ee5ae7fc0ec18a94b5b46d61/> rename `parse::Comment::(comment_tag|comment)` to `::tag|text` and `parse::Section::section_header` to `::header`. + - <csr-id-6f4eea936d64fb9827277c160f989168e7b1dba2/> Associate `file::Metadata` with each `File`. + This is the first step towards knowing more about the source of each + value to filter them based on some properties. + + This breaks various methods handling the instantiation of configuration + files as `file::Metadata` typically has to be provided by the caller + now or be associated with each path to read configuration from. + - <csr-id-b672ed7667a334be3d45c59f4727f12797b340da/> rename `file::SectionBody` to `file::section::Body`. + - <csr-id-3bea26d7d2a9b5751c6c15e1fa9a924b67e0159e/> Remove `File::sections_by_name_with_header()` as `::sections_by_name()` now returns entire sections. + - <csr-id-41b3e622ee71943c285eadc518150fc7b6c92361/> create `resolve_includes` options to make space for more options when loading paths. + - <csr-id-cabc8ef0e31c954642525e7693009a7fe4b4c465/> rename `path::Options` into `path::Context`. + It's not an option if it's required context to perform a certain + operation. + - <csr-id-3de0cfd81523e4ba7cc362d8625f85ebf8fd9172/> All accessors in `File` are now using `impl AsRef<str>` where possible for added comfort. + - <csr-id-3d25fe6c7a52529488fab19c927d64a1bc75838f/> Much more comfortable API `file::*Mut` types thanks to `impl Into/AsRef`. + - <csr-id-393b392d515661e5c3e60629319fdab771c3d3f0/> Rename `Mutable*` into `$1Mut` for consistency. + - <csr-id-0a7391a6575f4035c51a46d34fa20c69e9d078e9/> conform APIs of `file::MutableValue` and `file::MutableMultiValue`. + There are more renames and removals than worth mentioning here given the + current adoption of the crate. + - <csr-id-83a0922f06081312b79908835dac2b7f4e849bb3/> rename `file::MutableSection::set_leading_space()` to `set_leading_whitespace()`. + The corresponding getter was renamed as well to `leading_whitespace()`. + - <csr-id-219cf7ae0b35b3ac92f97974be52cd022698e01f/> Enforce `parse::section::Header::new()` by making its fields private. + - <csr-id-4f6cd8cf65c2d8698bffe327a19031c342b229a6/> Add `File::write_to()` and `File::to_bstring()`; remove some `TryFrom` impls. + Now `File` can be serialized in a streaming fashion and without the + possibility for UTF8 conversion issues. + + Note that `Display` is still implemented with the usual caveats. + - <csr-id-0e392f81e99c8c0ff29f41b9b86afd57cd99c245/> remove `Integer::to_bstring()` as well as some `TryFrom` impls. + Note that it can still display itself like before via + `std::fmt::Display`. + - <csr-id-b22732a2ab17213c4a1020859ec41f25ccabfbfc/> remove `Boolean::to_bstring()` along with a few `From` impls. + These were superfluous and aren't useful in practice. + Note that serialization is still implemented via `Display`. + - <csr-id-65c520c4de8187884f87059adf5cef9cbdcd90a2/> allocation free `File::sections_by_name()` and `File::sections_by_name_with_header()`. + - <csr-id-ac57c4479e7b6867e8b8e71f7cf76de759dc64a2/> `Path::interpolate()` now takes `path::interpolate::Options` instead of three parameters. + - <csr-id-0915051798dd782b40617a1aa16abd71f6db1175/> remove `String` type in favor of referring to the `File::string()` method. + The wrapper had no effect whatsoever except for adding complexity. + - <csr-id-9cadc6f0cbaad0ac23f5469db2f040aecfbfb82c/> Simplify `Boolean` to be a wrapper around `bool`. + Previously it tried hard not to degenerate information, making it a + complicated type. + + However, in practice nobody cares about the exact makeup of the boolean, + and there is no need to serialize a boolean faithfully either. + + Instead, those who want to set a value just set any value as a string, + no need for type safety there, and we take care of escaping values + properly on write. + - <csr-id-703922dd4e1e5b27835298217ff4eb8ef1dc57ce/> Use bitflags for `color::Attribute` instead of `Vec` of enums. + This is less wasteful and sufficient for git, so it should be sufficient + for us, especially since attributes are indeed a set and declaring + one twice has no effect. + - <csr-id-3fc4ac04f46f869c6e3a94ce4bb8a5737aa0c524/> simplify `Color` API. + For now we only parse and serialize for display, but more uses are + enabled when needed and trivially. + - <csr-id-14149eea54e2e8a25ac0ccdb2f6efe624f6eaa22/> remove `parse::Events::from_path` and `File::at` + The latter has been replaced with `File::from_path_with_buf(…)` and + is a low-level way to load just a single config file, purposefully + uncomfortable as it will not resolve includes. + + The initialization API will need some time to stabilize. + - <csr-id-73adceeae12270c0d470d4b7271c1fd6089d5c2d/> Slim down API surface of `parse::Events`. + It's more of a 'dumb' structure now than before, merely present + to facilitate typical parsing than something special on its own. + - <csr-id-2e47167e4a963743494b2df6b0c15800cb876dd0/> remove `File::new()` method in favor of `File::default()`. + - <csr-id-ea6765093b5475912ba1aa81d4440cbf5dd49fb6/> rename `parse::event::List` to `parse::Events` + - <csr-id-89f5fca843d999c5bea35fb3fe2a03dc3588f74e/> rename `parse::State` to `parse::event::List` + - <csr-id-3cdb0890b71e62cfa92b1ed1760c88cb547ec729/> move `value::*` into the crate root, except for `Error` and `normalize_*()`. + - <csr-id-748d921efd7469d5c19e40ddcb9099e2462e3bbc/> rename `value::parse::Error` to `value::Error`. + - <csr-id-7e8a22590297f2f4aab76b53be512353637fb651/> rename `value::TrueVariant` to `value::boolean::True` + - <csr-id-8bcaec0599cf085a73b344f4f53fc023f6e31430/> rename `IntegerSuffix` to `integer::Suffix` + - <csr-id-d085037ad9c067af7ce3ba3ab6e5d5ddb45b4057/> rename `value::Color(Attribute|Value)` to `value::color::Attribute` and `value::color::Name`. + - <csr-id-a0f6252343a62b0b55eef02888ac00c09100687a/> Turn `parse::ParseOrIoError` into `parse::state::from_path::Error` + - <csr-id-b6b31e9c8dd8b3dc4860431069bb1cf5eacd1702/> rename `parse::ParsedComment` into `parse::Comment` + - <csr-id-239cbfb450a8cddfc5bec1de21f3dc54fab914ce/> rename `parse::Section*` related types. + These are now located in `section::*`. + - <csr-id-60af4c9ecb1b99f21df0e8facc33e5f6fc70c424/> rename `parse::Parser` to `parse::State`. + Furthermore, make `State` the entry point for all parsing, removing + all free-standing functions that returned a `State`. + - <csr-id-3724850e0411f1f76e52c6c767fd8cebe8aea0f6/> rename `parser` module to `parse` + - <csr-id-58b22152a0295998935abb43563e9096589ef53e/> rename `normalize_cow()` to `normalize()` and move all `normalize*` functions from `values` to the `value` module + - <csr-id-767bedccdae1f3e6faf853d59ecf884a06cc3827/> move `Path` from `values` to `value` module + - <csr-id-6033f3f93d2356399a661567353a83a044662699/> Move `Boolean` and `String` from `values` into `value` module + - <csr-id-d4444e18042891b0fe5b9c6e6813fed26df6c560/> move `values::Integer` into `value` module + - <csr-id-38f31174e8c117af675cdfbc21926133b821ec38/> move `Color` to own `value` module + - <csr-id-aa630ad6ec2c6306d3307d5c77e272cb24b00ddd/> remove `values::Bytes` - use `values::String` instead. + Note that these values are always normalized and it's only possible + to get a raw values using the `raw_value()` API. + +### New Features (BREAKING) + + - <csr-id-d003c0f139d61e3bd998a0283a9c7af25a60db02/> Support for `lossy` load mode. + There is a lot of breaking changes as `file::from_paths::Options` now + became `file::init::Options`, and the same goes for the error type. + - <csr-id-1ea26d80f392114349d25ebf88a7b260ee822aa1/> add `_filter()` versions to most access methods. + That way it's possible to filter values by their origin. + + Note that the `remove_section()` methods now return the entire + removed section, not just the body, which yields more information + than before including section metadata. + - <csr-id-cfd974f46d2cbb99e7784a05f5e358fed0d4bcab/> section names are now validated. + - <csr-id-6ba2f8060768978ad7204e162fb2253ca8843879/> filtering support for `parse::Events`. + That way it's possible to construct Files which are not destined to be + written back as they only keep events necessary for value access, + greatly reducing allocations. + - <csr-id-311d4b447daf8d4364670382a20901468748d34d/> change mostly internal uses of [u8] to BString/BStr + - <csr-id-edd226719cd04a480274cb7d983b6d5d8bfdbb13/> Path-interpolation makes `home-dir` configurable. + That way the caller has full control over how the environment is used, + which also allows more fine-grained control over which config files + can be included. + +### Bug Fixes (BREAKING) + + - <csr-id-a93a156655d640ae63ff7c35b0a1f5d67a5ca20f/> Simplify specifying keys when mutating config values. + - <csr-id-895ce40aabbe6d6af5b681a0d0942303fd6549a2/> `File::rename_section()` with validation of input arguments. + - <csr-id-4a01d983f54a7713dea523f6032cbf5bb2b9dde8/> improve normalization; assure no extra copies are made on query. + We now return our own content, rather than the originals with their + lifetimes, meaning we bind lifetimes of returned values to our own + `File` instance. This allows them to be referenced more often, and + smarter normalization assures we don't copy in the simple cases + either. + + More tests were added as well. + This is breaking as lifetime changes can cause disruptions, and + `values?_as()` was removed as well as it's somewhat duplicate + to higher-level APIs and it wasn't tested at all. + - <csr-id-c9933c0b0f51d21dc8244b2acc33d7dc8a33f6ce/> Remove `gix-config` test utilities from `gix-path`. + +### Other (BREAKING) + + - <csr-id-9cd99337333f5ef4b30e0ec9461fc087699576e6/> `File::raw_multi_value()` to `File::raw_values()` + - <csr-id-0076dcf9b37f1d633bdad5573b40d34a9fbaba90/> `File::raw_multi_value_mut()` to `File::raw_values_mut()` + - <csr-id-a8604a237782f8d60a185d4730db57bad81424a6/> `File::multi_value()` to `File::values()`. + The latter is better in line with `string()/strings()` + +## 0.5.0 (2022-06-13) + +### New Features (BREAKING) + + - <csr-id-266d4379e9132fd7dd21e6c8fccb36e125069d6e/> Make `realpath()` easier to use by introducing `realpath_opt()`. + That way there is consistency about how many symlinks to follow. + +## 0.4.0 (2022-05-21) + +### Changed (BREAKING) + + - <csr-id-553f87225363903e6acdb3e7eaa8cc66a91110f1/> `File::len()` -> `File::num_values()` + The same is true for `Section::len()` which now is + `Section::num_values()`. + +## 0.3.0 (2022-05-18) + +### Bug Fixes + + - <csr-id-36e2fc0aff4e6aaa35335da90108918882d4cd16/> Use `std::env::var_os()` to avoid potential decode errors + +### Changed + + - <csr-id-b04a3465ed20d8f3088e5d3faf11e98e5595f219/> `GitConfig::from_paths(<paths>, …)` accepts more inputs + `<paths>` is more flexible and is easier to use. + +### New Features + + - <csr-id-7c75eac149c6ecb99c3dd7355d76d8d3e8b59cd0/> `GitConfig::path()` for direct access to paths. + Very similar to `string()`, but as path, whose query can never fail. + - <csr-id-031bd2f401199a05d6465c0260ceed3cc849c7ac/> add support for android + Do not interpolate `~user/` on Android (Termux). + There is no meaning of it. It is single user system. + - <csr-id-dc3dc3b41b5de3ec17429769747bf99bb2bdd03d/> support for `try_value()`, `boolean()` and `string()` access`. + Support for a convenient way of knowing if a value does or doesn't exist + via `try_value()`, which can only fail if the conversion fails. + + Lastly, `string()` is a special case which doesn't fail as there is + no conversion, and `boolean()` allows to obtain a plain boolean value + if it was a valid boolean representation. + - <csr-id-13554f8d21beb241e0fbdeb56b8414957cbee28a/> new hierarchical errors for value lookup + - <csr-id-4726bb524c1b0935d35770c907d40a0a16dbb8b5/> `GitConfig::integers()` + Get multiple fully validated integer values, with their suffix + interpreted and checked for overflow. + - <csr-id-ae22a4de486676f11469cec84be403903758b48b/> add `GitConfig::integer()` + A way to quickly obtain a valid integer with suffixes resolved + and overflow checked. + - <csr-id-bfc263797226d027e04daaf6426e57183773d7c3/> `GitConfig::strings()` for multi-value strings. + +### Changed (BREAKING) + + - <csr-id-38dfdcf80f9b7368ccaa10f4b78b2129849848d0/> remove `values::*Error` in favor of `value::parse::Error`. + This makes it easier to work with errors in practice, we are either + interested in the value that failed to parse to try something else + or want a nice user message. + + Having one decode error type facilitates that. + - <csr-id-a98a7a7af69482e9ef63f106184049049939459d/> switch from quickerror to thiserror. + This allows for generic types for sources of errors and allows to + workaround a limitation with associated type constraints in the MSRV + of 1.54. + + Using thiserror makes this work and brings the crate more closely + to the rest of the gitoxide crates (which now prefer thiserror over + quickerror). + - <csr-id-a86b2541561674df5dbef4120d3e03483cb80117/> remove all `get_` prefixes from methods + That way the API is more idiomatic and fits better into the + existing `gitoxide` crates. + - <csr-id-f9aaac11f0734afbd791132369eb5601bfc7efe9/> use `lookup::Error` and `lookup::existing::Error` + Use the newly introduced structured error to replace the 'catch-all' + `GitConfigError` while getting closer to naming conventions in other + `gitoxide` crates. + - <csr-id-c7fcb5e1db225aefc3eeab4f29f3fb85c670894a/> `GitConfig::from_paths(…, <option>)` is now owned. + The type is `Copy`, so no need to pass it by reference. + +## 0.2.1 (2022-04-05) + +### Features + +- New `values::String` data type which makes it easier to obtain string values to work with as + binary string. + +## 0.2.0 (2022-04-02) + +<csr-id-55c00d880535a1f8c37cb7d4405d39ff5a7654a0/> + +### New Features + + - <csr-id-e4d6685064ad2b433f8acd3a74b320bf0169a994/> Add `gix_config::values::Path` for a typesafe git path + Add a `Path` type to the `gix_config::values` which + can be interpolated according to gits own path interpolation + rules. + - <csr-id-61af06b905926849abce19677ff4b9ac05d625a3/> compatibility with Rust <1.53 + - Respect `include.path` when reading configuration files + - Support for path interpolation + +### Refactor + + - <csr-id-55c00d880535a1f8c37cb7d4405d39ff5a7654a0/> remove `gix_config::values::Value`; use `Bytes` in its place. + +## 0.1.11 (2022-01-31) + +### New Features + + - <csr-id-e822f566dcff3f6c784c206dff2fbc5f82d543be/> subsection iteration. + + introduce method `sections_by_name_with_header` to allow iterating over tuples of + section header and section body. + +### Bug Fixes + + - <csr-id-469406dc0d9fece4a06230ef0d8018846202f0ad/> fix usage example in README.md + +## 0.1.10 (2022-01-23) + +### New Features + + - <csr-id-61af06b905926849abce19677ff4b9ac05d625a3/> compatibility with Rust <1.53 + +## 0.1.9 (2021-11-29) + +A maintenance release. + +## 0.1.8 (2021-11-16) + +A maintenance release triggered by changes to gix-pack and changelog rewrites. + +## v0.1.7 (2021-10-15) + +This is a maintenance release without functional changes. + +## v0.1.6 (2021-09-07) + +## v0.1.5 (2021-08-29) + +- maintenance release + +## v0.1.4 (2021-08-17) + +## v0.1.3 (2021-08-15) + +## v0.1.2 (2021-08-06) + +### Added + + - Added the following methods to `GitConfig`: + - `is_empty` + - `len` + - `from_env` + - `open` +- `len` +- `from_env` +- `open` + +### Changed + + - `parse_from_path` now accepts a `AsRef<Path>` instead of a `&Path`. + - `parse_from_path` now returns an `ParserOrIoError<'static>` instead, from + `ParserFromIoError` + +### Fixed + + - _None._ + +<csr-unknown> +lenfrom_envopenlenfrom_envopenlenfrom_envopenlenfrom_envopenlenfrom_envopenlenfrom_envopenlenfrom_envopenlenfrom_envopenlenfrom_envopenlenfrom_envopenlenfrom_envopenlenfrom_envopenlenfrom_envopen<csr-unknown/> +<csr-unknown/> + +## v0.1.1 (2021-05-09) + +## v0.1.0 (2021-03-12) + +<csr-id-949622e461eb2116393ec6f4633ec0cb2e1695b5/> +<csr-id-bcacfc9bcf19a0339541b24e84de68d95291c62b/> +<csr-id-41f118d2aa560188fd3399d2390aa43794b0af75/> + +### Other + + - <csr-id-949622e461eb2116393ec6f4633ec0cb2e1695b5/> Include benches in crate to allow publishing to work + - <csr-id-bcacfc9bcf19a0339541b24e84de68d95291c62b/> remove clippy-cargo lint until there are no warnings + Please feel free to re-add once all other git-* crates have been + adjusted. It's interesting to see how a crate lint spills into the + workspace. + + Personally I am surprised that the keywords value is supposed + to repeat the crate name as it seems redundant. + - <csr-id-41f118d2aa560188fd3399d2390aa43794b0af75/> remove redundant lines from git-ignore file + +## v0.0.0 (2020-10-05) + diff --git a/vendor/gix-config/Cargo.toml b/vendor/gix-config/Cargo.toml new file mode 100644 index 000000000..a688807f8 --- /dev/null +++ b/vendor/gix-config/Cargo.toml @@ -0,0 +1,117 @@ +# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO +# +# When uploading crates to the registry Cargo will automatically +# "normalize" Cargo.toml files for maximal compatibility +# with all versions of Cargo and also rewrite `path` dependencies +# to registry (e.g., crates.io) dependencies. +# +# If you are reading this file be aware that the original Cargo.toml +# will likely look very different (and much more reasonable). +# See Cargo.toml.orig for the original contents. + +[package] +edition = "2021" +rust-version = "1.64" +name = "gix-config" +version = "0.18.0" +authors = ["Edward Shen <code@eddie.sh>"] +include = [ + "src/**/*", + "LICENSE-*", + "README.md", + "CHANGELOG.md", +] +autotests = false +description = "A gix-config file parser and editor from the gitoxide project" +readme = "README.md" +keywords = [ + "gix-config", + "git", + "config", + "gitoxide", +] +categories = [ + "config", + "parser-implementations", +] +license = "MIT OR Apache-2.0" +repository = "https://github.com/Byron/gitoxide" + +[package.metadata.docs.rs] +all-features = true +features = ["document-features"] +rustdoc-args = [ + "--cfg", + "docsrs", +] + +[[bench]] +name = "large_config_file" +path = "./benches/large_config_file.rs" +harness = false + +[dependencies.bstr] +version = "1.3.0" +features = ["std"] +default-features = false + +[dependencies.document-features] +version = "0.2.0" +optional = true + +[dependencies.gix-config-value] +version = "^0.10.1" + +[dependencies.gix-features] +version = "^0.28.0" + +[dependencies.gix-glob] +version = "^0.5.5" + +[dependencies.gix-path] +version = "^0.7.2" + +[dependencies.gix-ref] +version = "^0.26.0" + +[dependencies.gix-sec] +version = "^0.6.2" + +[dependencies.memchr] +version = "2" + +[dependencies.nom] +version = "7" +features = ["std"] +default_features = false + +[dependencies.once_cell] +version = "1.14.0" + +[dependencies.serde] +version = "1.0.114" +features = ["derive"] +optional = true +default-features = false + +[dependencies.smallvec] +version = "1.9.0" + +[dependencies.thiserror] +version = "1.0.26" + +[dependencies.unicode-bom] +version = "1.1.4" + +[dev-dependencies.criterion] +version = "0.4.0" + +[features] +serde1 = [ + "serde", + "bstr/serde", + "gix-sec/serde1", + "gix-ref/serde1", + "gix-glob/serde1", + "gix-config-value/serde1", +] diff --git a/vendor/gix-config/LICENSE-APACHE b/vendor/gix-config/LICENSE-APACHE new file mode 100644 index 000000000..a51f59a06 --- /dev/null +++ b/vendor/gix-config/LICENSE-APACHE @@ -0,0 +1,191 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + Copyright 2018-2021 Sebastian Thiel, and [contributors](https://github.com/byron/gitoxide/contributors) + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/vendor/gix-config/LICENSE-MIT b/vendor/gix-config/LICENSE-MIT new file mode 100644 index 000000000..b58e818f1 --- /dev/null +++ b/vendor/gix-config/LICENSE-MIT @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2018-2021 Sebastian Thiel, and [contributors](https://github.com/byron/gitoxide/contributors). + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/vendor/gix-config/README.md b/vendor/gix-config/README.md new file mode 100644 index 000000000..8118d83c9 --- /dev/null +++ b/vendor/gix-config/README.md @@ -0,0 +1,54 @@ +# gix-config + +**gix-config is a library for interacting with `gix-config` files.** + +This crate intents to be a performant Rust implementation for reading and +writing `gix-config` files. It exposes tiers of abstractions, from simple +config value wrappers to a high level reader and writer. + +The highlight of this crate is the zero-copy parser. We employ techniques to +avoid copying where necessary, and reads that do not need normalization are +guaranteed to be zero-copy. Higher level abstractions maintain this guarantee, +and utilizes acceleration structures for increased performance. + +Currently, this is _not_ a binary. While we do intent to have a drop-in +replacement for the `git config` sub-command, we're currently missing +system-level abstractions to do so. + +## Contributing + +Contributions are always welcome! + +### Code quality + +This repository enables pedantic, cargo, and nursery `clippy` lints. Make sure +to run `cargo clean && cargo clippy` (the clean stage is very important!) to +ensure your code is linted. + +### Testing + +Since this is a performance oriented crate, in addition to well tested code via +`cargo test`, we also perform benchmarks to measure notable gains or losses in +performance. We use [`criterion`] so benches can be run via `cargo bench` after +installing it via `cargo install cargo-criterion`. + +Changes to parsing code may include a request to fuzz to ensure that it cannot +panic on inputs. This can be done by executing `cargo +nightly fuzz run parse` after +installing the `fuzz` sub-command via `cargo install cargo-fuzz`. + +#### License + +<sup> +Licensed under either of <a href="LICENSE-APACHE">Apache License, Version +2.0</a> or <a href="LICENSE-MIT">MIT license</a> at your option. +</sup> + +<br> + +<sub> +Unless you explicitly state otherwise, any contribution intentionally submitted +for inclusion in gix-config by you, as defined in the Apache-2.0 license, shall +be dual licensed as above, without any additional terms or conditions. +</sub> + +[`criterion`]: https://github.com/bheisler/criterion.rs diff --git a/vendor/gix-config/src/file/access/comfort.rs b/vendor/gix-config/src/file/access/comfort.rs new file mode 100644 index 000000000..b4953c597 --- /dev/null +++ b/vendor/gix-config/src/file/access/comfort.rs @@ -0,0 +1,274 @@ +use std::{borrow::Cow, convert::TryFrom}; + +use bstr::BStr; + +use crate::{file::MetadataFilter, value, File}; + +/// Comfortable API for accessing values +impl<'event> File<'event> { + /// Like [`value()`][File::value()], but returning `None` if the string wasn't found. + /// + /// As strings perform no conversions, this will never fail. + pub fn string( + &self, + section_name: impl AsRef<str>, + subsection_name: Option<&BStr>, + key: impl AsRef<str>, + ) -> Option<Cow<'_, BStr>> { + self.string_filter(section_name, subsection_name, key, &mut |_| true) + } + + /// Like [`string()`][File::string()], but suitable for statically known `key`s like `remote.origin.url`. + pub fn string_by_key<'a>(&self, key: impl Into<&'a BStr>) -> Option<Cow<'_, BStr>> { + self.string_filter_by_key(key, &mut |_| true) + } + + /// Like [`string()`][File::string()], but the section containing the returned value must pass `filter` as well. + pub fn string_filter( + &self, + section_name: impl AsRef<str>, + subsection_name: Option<&BStr>, + key: impl AsRef<str>, + filter: &mut MetadataFilter, + ) -> Option<Cow<'_, BStr>> { + self.raw_value_filter(section_name, subsection_name, key, filter).ok() + } + + /// Like [`string_filter()`][File::string_filter()], but suitable for statically known `key`s like `remote.origin.url`. + pub fn string_filter_by_key<'a>( + &self, + key: impl Into<&'a BStr>, + filter: &mut MetadataFilter, + ) -> Option<Cow<'_, BStr>> { + let key = crate::parse::key(key)?; + self.raw_value_filter(key.section_name, key.subsection_name, key.value_name, filter) + .ok() + } + + /// Like [`value()`][File::value()], but returning `None` if the path wasn't found. + /// + /// Note that this path is not vetted and should only point to resources which can't be used + /// to pose a security risk. Prefer using [`path_filter()`][File::path_filter()] instead. + /// + /// As paths perform no conversions, this will never fail. + pub fn path( + &self, + section_name: impl AsRef<str>, + subsection_name: Option<&BStr>, + key: impl AsRef<str>, + ) -> Option<crate::Path<'_>> { + self.path_filter(section_name, subsection_name, key, &mut |_| true) + } + + /// Like [`path()`][File::path()], but suitable for statically known `key`s like `remote.origin.url`. + pub fn path_by_key<'a>(&self, key: impl Into<&'a BStr>) -> Option<crate::Path<'_>> { + self.path_filter_by_key(key, &mut |_| true) + } + + /// Like [`path()`][File::path()], but the section containing the returned value must pass `filter` as well. + /// + /// This should be the preferred way of accessing paths as those from untrusted + /// locations can be + /// + /// As paths perform no conversions, this will never fail. + pub fn path_filter( + &self, + section_name: impl AsRef<str>, + subsection_name: Option<&BStr>, + key: impl AsRef<str>, + filter: &mut MetadataFilter, + ) -> Option<crate::Path<'_>> { + self.raw_value_filter(section_name, subsection_name, key, filter) + .ok() + .map(crate::Path::from) + } + + /// Like [`path_filter()`][File::path_filter()], but suitable for statically known `key`s like `remote.origin.url`. + pub fn path_filter_by_key<'a>( + &self, + key: impl Into<&'a BStr>, + filter: &mut MetadataFilter, + ) -> Option<crate::Path<'_>> { + let key = crate::parse::key(key)?; + self.path_filter(key.section_name, key.subsection_name, key.value_name, filter) + } + + /// Like [`value()`][File::value()], but returning `None` if the boolean value wasn't found. + pub fn boolean( + &self, + section_name: impl AsRef<str>, + subsection_name: Option<&BStr>, + key: impl AsRef<str>, + ) -> Option<Result<bool, value::Error>> { + self.boolean_filter(section_name, subsection_name, key, &mut |_| true) + } + + /// Like [`boolean()`][File::boolean()], but suitable for statically known `key`s like `remote.origin.url`. + pub fn boolean_by_key<'a>(&self, key: impl Into<&'a BStr>) -> Option<Result<bool, value::Error>> { + self.boolean_filter_by_key(key, &mut |_| true) + } + + /// Like [`boolean()`][File::boolean()], but the section containing the returned value must pass `filter` as well. + pub fn boolean_filter( + &self, + section_name: impl AsRef<str>, + subsection_name: Option<&BStr>, + key: impl AsRef<str>, + filter: &mut MetadataFilter, + ) -> Option<Result<bool, value::Error>> { + let section_name = section_name.as_ref(); + let section_ids = self + .section_ids_by_name_and_subname(section_name, subsection_name) + .ok()?; + let key = key.as_ref(); + for section_id in section_ids.rev() { + let section = self.sections.get(§ion_id).expect("known section id"); + if !filter(section.meta()) { + continue; + } + match section.value_implicit(key) { + Some(Some(v)) => return Some(crate::Boolean::try_from(v).map(|b| b.into())), + Some(None) => return Some(Ok(true)), + None => continue, + } + } + None + } + + /// Like [`boolean_filter()`][File::boolean_filter()], but suitable for statically known `key`s like `remote.origin.url`. + pub fn boolean_filter_by_key<'a>( + &self, + key: impl Into<&'a BStr>, + filter: &mut MetadataFilter, + ) -> Option<Result<bool, value::Error>> { + let key = crate::parse::key(key)?; + self.boolean_filter(key.section_name, key.subsection_name, key.value_name, filter) + } + + /// Like [`value()`][File::value()], but returning an `Option` if the integer wasn't found. + pub fn integer( + &self, + section_name: impl AsRef<str>, + subsection_name: Option<&BStr>, + key: impl AsRef<str>, + ) -> Option<Result<i64, value::Error>> { + self.integer_filter(section_name, subsection_name, key, &mut |_| true) + } + + /// Like [`integer()`][File::integer()], but suitable for statically known `key`s like `remote.origin.url`. + pub fn integer_by_key<'a>(&self, key: impl Into<&'a BStr>) -> Option<Result<i64, value::Error>> { + self.integer_filter_by_key(key, &mut |_| true) + } + + /// Like [`integer()`][File::integer()], but the section containing the returned value must pass `filter` as well. + pub fn integer_filter( + &self, + section_name: impl AsRef<str>, + subsection_name: Option<&BStr>, + key: impl AsRef<str>, + filter: &mut MetadataFilter, + ) -> Option<Result<i64, value::Error>> { + let int = self.raw_value_filter(section_name, subsection_name, key, filter).ok()?; + Some(crate::Integer::try_from(int.as_ref()).and_then(|b| { + b.to_decimal() + .ok_or_else(|| value::Error::new("Integer overflow", int.into_owned())) + })) + } + + /// Like [`integer_filter()`][File::integer_filter()], but suitable for statically known `key`s like `remote.origin.url`. + pub fn integer_filter_by_key<'a>( + &self, + key: impl Into<&'a BStr>, + filter: &mut MetadataFilter, + ) -> Option<Result<i64, value::Error>> { + let key = crate::parse::key(key)?; + self.integer_filter(key.section_name, key.subsection_name, key.value_name, filter) + } + + /// Similar to [`values(…)`][File::values()] but returning strings if at least one of them was found. + pub fn strings( + &self, + section_name: impl AsRef<str>, + subsection_name: Option<&BStr>, + key: impl AsRef<str>, + ) -> Option<Vec<Cow<'_, BStr>>> { + self.raw_values(section_name, subsection_name, key).ok() + } + + /// Like [`strings()`][File::strings()], but suitable for statically known `key`s like `remote.origin.url`. + pub fn strings_by_key<'a>(&self, key: impl Into<&'a BStr>) -> Option<Vec<Cow<'_, BStr>>> { + let key = crate::parse::key(key)?; + self.strings(key.section_name, key.subsection_name, key.value_name) + } + + /// Similar to [`strings(…)`][File::strings()], but all values are in sections that passed `filter`. + pub fn strings_filter( + &self, + section_name: impl AsRef<str>, + subsection_name: Option<&BStr>, + key: impl AsRef<str>, + filter: &mut MetadataFilter, + ) -> Option<Vec<Cow<'_, BStr>>> { + self.raw_values_filter(section_name, subsection_name, key, filter).ok() + } + + /// Like [`strings_filter()`][File::strings_filter()], but suitable for statically known `key`s like `remote.origin.url`. + pub fn strings_filter_by_key<'a>( + &self, + key: impl Into<&'a BStr>, + filter: &mut MetadataFilter, + ) -> Option<Vec<Cow<'_, BStr>>> { + let key = crate::parse::key(key)?; + self.strings_filter(key.section_name, key.subsection_name, key.value_name, filter) + } + + /// Similar to [`values(…)`][File::values()] but returning integers if at least one of them was found + /// and if none of them overflows. + pub fn integers( + &self, + section_name: impl AsRef<str>, + subsection_name: Option<&BStr>, + key: impl AsRef<str>, + ) -> Option<Result<Vec<i64>, value::Error>> { + self.integers_filter(section_name, subsection_name, key, &mut |_| true) + } + + /// Like [`integers()`][File::integers()], but suitable for statically known `key`s like `remote.origin.url`. + pub fn integers_by_key<'a>(&self, key: impl Into<&'a BStr>) -> Option<Result<Vec<i64>, value::Error>> { + self.integers_filter_by_key(key, &mut |_| true) + } + + /// Similar to [`integers(…)`][File::integers()] but all integers are in sections that passed `filter` + /// and that are not overflowing. + pub fn integers_filter( + &self, + section_name: impl AsRef<str>, + subsection_name: Option<&BStr>, + key: impl AsRef<str>, + filter: &mut MetadataFilter, + ) -> Option<Result<Vec<i64>, value::Error>> { + self.raw_values_filter(section_name, subsection_name, key, filter) + .ok() + .map(|values| { + values + .into_iter() + .map(|v| { + crate::Integer::try_from(v.as_ref()).and_then(|int| { + int.to_decimal() + .ok_or_else(|| value::Error::new("Integer overflow", v.into_owned())) + }) + }) + .collect() + }) + } + + /// Like [`integers_filter()`][File::integers_filter()], but suitable for statically known `key`s like `remote.origin.url`. + pub fn integers_filter_by_key<'a>( + &self, + key: impl Into<&'a BStr>, + filter: &mut MetadataFilter, + ) -> Option<Result<Vec<i64>, value::Error>> { + let key = crate::parse::key(key)?; + self.integers_filter(key.section_name, key.subsection_name, key.value_name, filter) + } +} diff --git a/vendor/gix-config/src/file/access/mod.rs b/vendor/gix-config/src/file/access/mod.rs new file mode 100644 index 000000000..d602b5f8b --- /dev/null +++ b/vendor/gix-config/src/file/access/mod.rs @@ -0,0 +1,4 @@ +mod comfort; +mod mutate; +mod raw; +mod read_only; diff --git a/vendor/gix-config/src/file/access/mutate.rs b/vendor/gix-config/src/file/access/mutate.rs new file mode 100644 index 000000000..e1cfc6e1c --- /dev/null +++ b/vendor/gix-config/src/file/access/mutate.rs @@ -0,0 +1,387 @@ +use std::borrow::Cow; + +use bstr::BStr; +use gix_features::threading::OwnShared; + +use crate::{ + file::{self, rename_section, write::ends_with_newline, MetadataFilter, SectionBodyIdsLut, SectionId, SectionMut}, + lookup, + parse::{section, Event, FrontMatterEvents}, + File, +}; + +/// Mutating low-level access methods. +impl<'event> File<'event> { + /// Returns the last mutable section with a given `name` and optional `subsection_name`, _if it exists_. + pub fn section_mut<'a>( + &'a mut self, + name: impl AsRef<str>, + subsection_name: Option<&BStr>, + ) -> Result<SectionMut<'a, 'event>, lookup::existing::Error> { + let id = self + .section_ids_by_name_and_subname(name.as_ref(), subsection_name)? + .rev() + .next() + .expect("BUG: Section lookup vec was empty"); + let nl = self.detect_newline_style_smallvec(); + Ok(self + .sections + .get_mut(&id) + .expect("BUG: Section did not have id from lookup") + .to_mut(nl)) + } + + /// Returns the last found mutable section with a given `key`, identifying the name and subsection name like `core` or `remote.origin`. + pub fn section_mut_by_key<'a, 'b>( + &'a mut self, + key: impl Into<&'b BStr>, + ) -> Result<SectionMut<'a, 'event>, lookup::existing::Error> { + let key = section::unvalidated::Key::parse(key).ok_or(lookup::existing::Error::KeyMissing)?; + self.section_mut(key.section_name, key.subsection_name) + } + + /// Return the mutable section identified by `id`, or `None` if it didn't exist. + /// + /// Note that `id` is stable across deletions and insertions. + pub fn section_mut_by_id<'a>(&'a mut self, id: SectionId) -> Option<SectionMut<'a, 'event>> { + let nl = self.detect_newline_style_smallvec(); + self.sections.get_mut(&id).map(|s| s.to_mut(nl)) + } + + /// Returns the last mutable section with a given `name` and optional `subsection_name`, _if it exists_, or create a new section. + pub fn section_mut_or_create_new<'a>( + &'a mut self, + name: impl AsRef<str>, + subsection_name: Option<&BStr>, + ) -> Result<SectionMut<'a, 'event>, section::header::Error> { + self.section_mut_or_create_new_filter(name, subsection_name, &mut |_| true) + } + + /// Returns an mutable section with a given `name` and optional `subsection_name`, _if it exists_ **and** passes `filter`, or create + /// a new section. + pub fn section_mut_or_create_new_filter<'a>( + &'a mut self, + name: impl AsRef<str>, + subsection_name: Option<&BStr>, + filter: &mut MetadataFilter, + ) -> Result<SectionMut<'a, 'event>, section::header::Error> { + let name = name.as_ref(); + match self + .section_ids_by_name_and_subname(name.as_ref(), subsection_name) + .ok() + .and_then(|it| { + it.rev().find(|id| { + let s = &self.sections[id]; + filter(s.meta()) + }) + }) { + Some(id) => { + let nl = self.detect_newline_style_smallvec(); + Ok(self + .sections + .get_mut(&id) + .expect("BUG: Section did not have id from lookup") + .to_mut(nl)) + } + None => self.new_section(name.to_owned(), subsection_name.map(|n| Cow::Owned(n.to_owned()))), + } + } + + /// Returns the last found mutable section with a given `name` and optional `subsection_name`, that matches `filter`, _if it exists_. + /// + /// If there are sections matching `section_name` and `subsection_name` but the `filter` rejects all of them, `Ok(None)` + /// is returned. + pub fn section_mut_filter<'a>( + &'a mut self, + name: impl AsRef<str>, + subsection_name: Option<&BStr>, + filter: &mut MetadataFilter, + ) -> Result<Option<file::SectionMut<'a, 'event>>, lookup::existing::Error> { + let id = self + .section_ids_by_name_and_subname(name.as_ref(), subsection_name)? + .rev() + .find(|id| { + let s = &self.sections[id]; + filter(s.meta()) + }); + let nl = self.detect_newline_style_smallvec(); + Ok(id.and_then(move |id| self.sections.get_mut(&id).map(move |s| s.to_mut(nl)))) + } + + /// Like [`section_mut_filter()`][File::section_mut_filter()], but identifies the with a given `key`, + /// like `core` or `remote.origin`. + pub fn section_mut_filter_by_key<'a, 'b>( + &'a mut self, + key: impl Into<&'b BStr>, + filter: &mut MetadataFilter, + ) -> Result<Option<file::SectionMut<'a, 'event>>, lookup::existing::Error> { + let key = section::unvalidated::Key::parse(key).ok_or(lookup::existing::Error::KeyMissing)?; + self.section_mut_filter(key.section_name, key.subsection_name, filter) + } + + /// Adds a new section. If a subsection name was provided, then + /// the generated header will use the modern subsection syntax. + /// Returns a reference to the new section for immediate editing. + /// + /// # Examples + /// + /// Creating a new empty section: + /// + /// ``` + /// # use std::borrow::Cow; + /// # use gix_config::File; + /// # use std::convert::TryFrom; + /// let mut gix_config = gix_config::File::default(); + /// let section = gix_config.new_section("hello", Some(Cow::Borrowed("world".into())))?; + /// let nl = section.newline().to_owned(); + /// assert_eq!(gix_config.to_string(), format!("[hello \"world\"]{nl}")); + /// # Ok::<(), Box<dyn std::error::Error>>(()) + /// ``` + /// + /// Creating a new empty section and adding values to it: + /// + /// ``` + /// # use gix_config::File; + /// # use std::borrow::Cow; + /// # use std::convert::TryFrom; + /// # use bstr::ByteSlice; + /// # use gix_config::parse::section; + /// let mut gix_config = gix_config::File::default(); + /// let mut section = gix_config.new_section("hello", Some(Cow::Borrowed("world".into())))?; + /// section.push(section::Key::try_from("a")?, Some("b".into())); + /// let nl = section.newline().to_owned(); + /// assert_eq!(gix_config.to_string(), format!("[hello \"world\"]{nl}\ta = b{nl}")); + /// let _section = gix_config.new_section("core", None); + /// assert_eq!(gix_config.to_string(), format!("[hello \"world\"]{nl}\ta = b{nl}[core]{nl}")); + /// # Ok::<(), Box<dyn std::error::Error>>(()) + /// ``` + pub fn new_section( + &mut self, + name: impl Into<Cow<'event, str>>, + subsection: impl Into<Option<Cow<'event, BStr>>>, + ) -> Result<SectionMut<'_, 'event>, section::header::Error> { + let id = self.push_section_internal(file::Section::new(name, subsection, OwnShared::clone(&self.meta))?); + let nl = self.detect_newline_style_smallvec(); + let mut section = self.sections.get_mut(&id).expect("each id yields a section").to_mut(nl); + section.push_newline(); + Ok(section) + } + + /// Removes the section with `name` and `subsection_name` , returning it if there was a matching section. + /// If multiple sections have the same name, then the last one is returned. Note that + /// later sections with the same name have precedent over earlier ones. + /// + /// # Examples + /// + /// Creating and removing a section: + /// + /// ``` + /// # use gix_config::File; + /// # use std::convert::TryFrom; + /// let mut gix_config = gix_config::File::try_from( + /// r#"[hello "world"] + /// some-value = 4 + /// "#)?; + /// + /// let section = gix_config.remove_section("hello", Some("world".into())); + /// assert_eq!(gix_config.to_string(), ""); + /// # Ok::<(), Box<dyn std::error::Error>>(()) + /// ``` + /// + /// Precedence example for removing sections with the same name: + /// + /// ``` + /// # use gix_config::File; + /// # use std::convert::TryFrom; + /// let mut gix_config = gix_config::File::try_from( + /// r#"[hello "world"] + /// some-value = 4 + /// [hello "world"] + /// some-value = 5 + /// "#)?; + /// + /// let section = gix_config.remove_section("hello", Some("world".into())); + /// assert_eq!(gix_config.to_string(), "[hello \"world\"]\n some-value = 4\n"); + /// # Ok::<(), Box<dyn std::error::Error>>(()) + /// ``` + pub fn remove_section<'a>( + &mut self, + name: &str, + subsection_name: impl Into<Option<&'a BStr>>, + ) -> Option<file::Section<'event>> { + let id = self + .section_ids_by_name_and_subname(name, subsection_name.into()) + .ok()? + .rev() + .next()?; + self.remove_section_by_id(id) + } + + /// Remove the section identified by `id` if it exists and return it, or return `None` if no such section was present. + /// + /// Note that section ids are unambiguous even in the face of removals and additions of sections. + pub fn remove_section_by_id(&mut self, id: SectionId) -> Option<file::Section<'event>> { + self.section_order + .remove(self.section_order.iter().position(|v| *v == id)?); + let section = self.sections.remove(&id)?; + let lut = self + .section_lookup_tree + .get_mut(§ion.header.name) + .expect("lookup cache still has name to be deleted"); + // NOTE: this leaves empty lists in the data structure which our code now has to deal with. + for entry in lut { + match section.header.subsection_name.as_deref() { + Some(subsection_name) => { + if let SectionBodyIdsLut::NonTerminal(map) = entry { + if let Some(ids) = map.get_mut(subsection_name) { + ids.remove(ids.iter().position(|v| *v == id).expect("present")); + break; + } + } + } + None => { + if let SectionBodyIdsLut::Terminal(ids) = entry { + ids.remove(ids.iter().position(|v| *v == id).expect("present")); + break; + } + } + } + } + Some(section) + } + + /// Removes the section with `name` and `subsection_name` that passed `filter`, returning the removed section + /// if at least one section matched the `filter`. + /// If multiple sections have the same name, then the last one is returned. Note that + /// later sections with the same name have precedent over earlier ones. + pub fn remove_section_filter<'a>( + &mut self, + name: &str, + subsection_name: impl Into<Option<&'a BStr>>, + filter: &mut MetadataFilter, + ) -> Option<file::Section<'event>> { + let id = self + .section_ids_by_name_and_subname(name, subsection_name.into()) + .ok()? + .rev() + .find(|id| filter(self.sections.get(id).expect("each id has a section").meta()))?; + self.section_order.remove( + self.section_order + .iter() + .position(|v| *v == id) + .expect("known section id"), + ); + self.sections.remove(&id) + } + + /// Adds the provided section to the config, returning a mutable reference + /// to it for immediate editing. + /// Note that its meta-data will remain as is. + pub fn push_section( + &mut self, + section: file::Section<'event>, + ) -> Result<SectionMut<'_, 'event>, section::header::Error> { + let id = self.push_section_internal(section); + let nl = self.detect_newline_style_smallvec(); + let section = self.sections.get_mut(&id).expect("each id yields a section").to_mut(nl); + Ok(section) + } + + /// Renames the section with `name` and `subsection_name`, modifying the last matching section + /// to use `new_name` and `new_subsection_name`. + pub fn rename_section<'a>( + &mut self, + name: impl AsRef<str>, + subsection_name: impl Into<Option<&'a BStr>>, + new_name: impl Into<Cow<'event, str>>, + new_subsection_name: impl Into<Option<Cow<'event, BStr>>>, + ) -> Result<(), rename_section::Error> { + let id = self + .section_ids_by_name_and_subname(name.as_ref(), subsection_name.into())? + .rev() + .next() + .expect("list of sections were empty, which violates invariant"); + let section = self.sections.get_mut(&id).expect("known section-id"); + section.header = section::Header::new(new_name, new_subsection_name)?; + Ok(()) + } + + /// Renames the section with `name` and `subsection_name`, modifying the last matching section + /// that also passes `filter` to use `new_name` and `new_subsection_name`. + /// + /// Note that the otherwise unused [`lookup::existing::Error::KeyMissing`] variant is used to indicate + /// that the `filter` rejected all candidates, leading to no section being renamed after all. + pub fn rename_section_filter<'a>( + &mut self, + name: impl AsRef<str>, + subsection_name: impl Into<Option<&'a BStr>>, + new_name: impl Into<Cow<'event, str>>, + new_subsection_name: impl Into<Option<Cow<'event, BStr>>>, + filter: &mut MetadataFilter, + ) -> Result<(), rename_section::Error> { + let id = self + .section_ids_by_name_and_subname(name.as_ref(), subsection_name.into())? + .rev() + .find(|id| filter(self.sections.get(id).expect("each id has a section").meta())) + .ok_or(rename_section::Error::Lookup(lookup::existing::Error::KeyMissing))?; + let section = self.sections.get_mut(&id).expect("known section-id"); + section.header = section::Header::new(new_name, new_subsection_name)?; + Ok(()) + } + + /// Append another File to the end of ourselves, without losing any information. + pub fn append(&mut self, other: Self) -> &mut Self { + self.append_or_insert(other, None) + } + + /// Append another File to the end of ourselves, without losing any information. + pub(crate) fn append_or_insert(&mut self, mut other: Self, mut insert_after: Option<SectionId>) -> &mut Self { + let nl = self.detect_newline_style_smallvec(); + fn extend_and_assure_newline<'a>( + lhs: &mut FrontMatterEvents<'a>, + rhs: FrontMatterEvents<'a>, + nl: &impl AsRef<[u8]>, + ) { + if !ends_with_newline(lhs.as_ref(), nl, true) + && !rhs.first().map_or(true, |e| e.to_bstr_lossy().starts_with(nl.as_ref())) + { + lhs.push(Event::Newline(Cow::Owned(nl.as_ref().into()))) + } + lhs.extend(rhs); + } + #[allow(clippy::unnecessary_lazy_evaluations)] + let our_last_section_before_append = + insert_after.or_else(|| (self.section_id_counter != 0).then(|| SectionId(self.section_id_counter - 1))); + + for id in std::mem::take(&mut other.section_order) { + let section = other.sections.remove(&id).expect("present"); + + let new_id = match insert_after { + Some(id) => { + let new_id = self.insert_section_after(section, id); + insert_after = Some(new_id); + new_id + } + None => self.push_section_internal(section), + }; + + if let Some(post_matter) = other.frontmatter_post_section.remove(&id) { + self.frontmatter_post_section.insert(new_id, post_matter); + } + } + + if other.frontmatter_events.is_empty() { + return self; + } + + match our_last_section_before_append { + Some(last_id) => extend_and_assure_newline( + self.frontmatter_post_section.entry(last_id).or_default(), + other.frontmatter_events, + &nl, + ), + None => extend_and_assure_newline(&mut self.frontmatter_events, other.frontmatter_events, &nl), + } + self + } +} diff --git a/vendor/gix-config/src/file/access/raw.rs b/vendor/gix-config/src/file/access/raw.rs new file mode 100644 index 000000000..46f1fb006 --- /dev/null +++ b/vendor/gix-config/src/file/access/raw.rs @@ -0,0 +1,536 @@ +use std::{borrow::Cow, collections::HashMap, convert::TryInto}; + +use bstr::BStr; +use smallvec::ToSmallVec; + +use crate::{ + file::{mutable::multi_value::EntryData, Index, MetadataFilter, MultiValueMut, Size, ValueMut}, + lookup, + parse::{section, Event}, + File, +}; + +/// # Raw value API +/// +/// These functions are the raw value API, returning normalized byte strings. +impl<'event> File<'event> { + /// Returns an uninterpreted value given a section, an optional subsection + /// and key. + /// + /// Consider [`Self::raw_values()`] if you want to get all values of + /// a multivar instead. + pub fn raw_value( + &self, + section_name: impl AsRef<str>, + subsection_name: Option<&BStr>, + key: impl AsRef<str>, + ) -> Result<Cow<'_, BStr>, lookup::existing::Error> { + self.raw_value_filter(section_name, subsection_name, key, &mut |_| true) + } + + /// Returns an uninterpreted value given a section, an optional subsection + /// and key, if it passes the `filter`. + /// + /// Consider [`Self::raw_values()`] if you want to get all values of + /// a multivar instead. + pub fn raw_value_filter( + &self, + section_name: impl AsRef<str>, + subsection_name: Option<&BStr>, + key: impl AsRef<str>, + filter: &mut MetadataFilter, + ) -> Result<Cow<'_, BStr>, lookup::existing::Error> { + let section_ids = self.section_ids_by_name_and_subname(section_name.as_ref(), subsection_name)?; + let key = key.as_ref(); + for section_id in section_ids.rev() { + let section = self.sections.get(§ion_id).expect("known section id"); + if !filter(section.meta()) { + continue; + } + if let Some(v) = section.value(key) { + return Ok(v); + } + } + + Err(lookup::existing::Error::KeyMissing) + } + + /// Returns a mutable reference to an uninterpreted value given a section, + /// an optional subsection and key. + /// + /// Consider [`Self::raw_values_mut`] if you want to get mutable + /// references to all values of a multivar instead. + pub fn raw_value_mut<'lookup>( + &mut self, + section_name: impl AsRef<str>, + subsection_name: Option<&'lookup BStr>, + key: &'lookup str, + ) -> Result<ValueMut<'_, 'lookup, 'event>, lookup::existing::Error> { + self.raw_value_mut_filter(section_name, subsection_name, key, &mut |_| true) + } + + /// Returns a mutable reference to an uninterpreted value given a section, + /// an optional subsection and key, and if it passes `filter`. + /// + /// Consider [`Self::raw_values_mut`] if you want to get mutable + /// references to all values of a multivar instead. + pub fn raw_value_mut_filter<'lookup>( + &mut self, + section_name: impl AsRef<str>, + subsection_name: Option<&'lookup BStr>, + key: &'lookup str, + filter: &mut MetadataFilter, + ) -> Result<ValueMut<'_, 'lookup, 'event>, lookup::existing::Error> { + let mut section_ids = self + .section_ids_by_name_and_subname(section_name.as_ref(), subsection_name)? + .rev(); + let key = section::Key(Cow::<BStr>::Borrowed(key.into())); + + while let Some(section_id) = section_ids.next() { + let mut index = 0; + let mut size = 0; + let mut found_key = false; + let section = self.sections.get(§ion_id).expect("known section id"); + if !filter(section.meta()) { + continue; + } + for (i, event) in section.as_ref().iter().enumerate() { + match event { + Event::SectionKey(event_key) if *event_key == key => { + found_key = true; + index = i; + size = 1; + } + Event::Newline(_) | Event::Whitespace(_) | Event::ValueNotDone(_) if found_key => { + size += 1; + } + Event::ValueDone(_) | Event::Value(_) if found_key => { + found_key = false; + size += 1; + } + Event::KeyValueSeparator if found_key => { + size += 1; + } + _ => {} + } + } + + if size == 0 { + continue; + } + + drop(section_ids); + let nl = self.detect_newline_style().to_smallvec(); + return Ok(ValueMut { + section: self.sections.get_mut(§ion_id).expect("known section-id").to_mut(nl), + key, + index: Index(index), + size: Size(size), + }); + } + + Err(lookup::existing::Error::KeyMissing) + } + + /// Returns all uninterpreted values given a section, an optional subsection + /// ain order of occurrence. + /// + /// The ordering means that the last of the returned values is the one that would be the + /// value used in the single-value case.nd key. + /// + /// # Examples + /// + /// If you have the following config: + /// + /// ```text + /// [core] + /// a = b + /// [core] + /// a = c + /// a = d + /// ``` + /// + /// Attempting to get all values of `a` yields the following: + /// + /// ``` + /// # use gix_config::File; + /// # use std::borrow::Cow; + /// # use std::convert::TryFrom; + /// # use bstr::BStr; + /// # let gix_config = gix_config::File::try_from("[core]a=b\n[core]\na=c\na=d").unwrap(); + /// assert_eq!( + /// gix_config.raw_values("core", None, "a").unwrap(), + /// vec![ + /// Cow::<BStr>::Borrowed("b".into()), + /// Cow::<BStr>::Borrowed("c".into()), + /// Cow::<BStr>::Borrowed("d".into()), + /// ], + /// ); + /// ``` + /// + /// Consider [`Self::raw_value`] if you want to get the resolved single + /// value for a given key, if your key does not support multi-valued values. + pub fn raw_values( + &self, + section_name: impl AsRef<str>, + subsection_name: Option<&BStr>, + key: impl AsRef<str>, + ) -> Result<Vec<Cow<'_, BStr>>, lookup::existing::Error> { + self.raw_values_filter(section_name, subsection_name, key, &mut |_| true) + } + + /// Returns all uninterpreted values given a section, an optional subsection + /// and key, if the value passes `filter`, in order of occurrence. + /// + /// The ordering means that the last of the returned values is the one that would be the + /// value used in the single-value case. + pub fn raw_values_filter( + &self, + section_name: impl AsRef<str>, + subsection_name: Option<&BStr>, + key: impl AsRef<str>, + filter: &mut MetadataFilter, + ) -> Result<Vec<Cow<'_, BStr>>, lookup::existing::Error> { + let mut values = Vec::new(); + let section_ids = self.section_ids_by_name_and_subname(section_name.as_ref(), subsection_name)?; + let key = key.as_ref(); + for section_id in section_ids { + let section = self.sections.get(§ion_id).expect("known section id"); + if !filter(section.meta()) { + continue; + } + values.extend(section.values(key)); + } + + if values.is_empty() { + Err(lookup::existing::Error::KeyMissing) + } else { + Ok(values) + } + } + + /// Returns mutable references to all uninterpreted values given a section, + /// an optional subsection and key. + /// + /// # Examples + /// + /// If you have the following config: + /// + /// ```text + /// [core] + /// a = b + /// [core] + /// a = c + /// a = d + /// ``` + /// + /// Attempting to get all values of `a` yields the following: + /// + /// ``` + /// # use gix_config::File; + /// # use std::borrow::Cow; + /// # use std::convert::TryFrom; + /// # use bstr::BStr; + /// # let mut gix_config = gix_config::File::try_from("[core]a=b\n[core]\na=c\na=d").unwrap(); + /// assert_eq!( + /// gix_config.raw_values("core", None, "a")?, + /// vec![ + /// Cow::<BStr>::Borrowed("b".into()), + /// Cow::<BStr>::Borrowed("c".into()), + /// Cow::<BStr>::Borrowed("d".into()) + /// ] + /// ); + /// + /// gix_config.raw_values_mut("core", None, "a")?.set_all("g"); + /// + /// assert_eq!( + /// gix_config.raw_values("core", None, "a")?, + /// vec![ + /// Cow::<BStr>::Borrowed("g".into()), + /// Cow::<BStr>::Borrowed("g".into()), + /// Cow::<BStr>::Borrowed("g".into()) + /// ], + /// ); + /// # Ok::<(), gix_config::lookup::existing::Error>(()) + /// ``` + /// + /// Consider [`Self::raw_value`] if you want to get the resolved single + /// value for a given key, if your key does not support multi-valued values. + /// + /// Note that this operation is relatively expensive, requiring a full + /// traversal of the config. + pub fn raw_values_mut<'lookup>( + &mut self, + section_name: impl AsRef<str>, + subsection_name: Option<&'lookup BStr>, + key: &'lookup str, + ) -> Result<MultiValueMut<'_, 'lookup, 'event>, lookup::existing::Error> { + self.raw_values_mut_filter(section_name, subsection_name, key, &mut |_| true) + } + + /// Returns mutable references to all uninterpreted values given a section, + /// an optional subsection and key, if their sections pass `filter`. + pub fn raw_values_mut_filter<'lookup>( + &mut self, + section_name: impl AsRef<str>, + subsection_name: Option<&'lookup BStr>, + key: &'lookup str, + filter: &mut MetadataFilter, + ) -> Result<MultiValueMut<'_, 'lookup, 'event>, lookup::existing::Error> { + let section_ids = self.section_ids_by_name_and_subname(section_name.as_ref(), subsection_name)?; + let key = section::Key(Cow::<BStr>::Borrowed(key.into())); + + let mut offsets = HashMap::new(); + let mut entries = Vec::new(); + for section_id in section_ids.rev() { + let mut last_boundary = 0; + let mut expect_value = false; + let mut offset_list = Vec::new(); + let mut offset_index = 0; + let section = self.sections.get(§ion_id).expect("known section-id"); + if !filter(section.meta()) { + continue; + } + for (i, event) in section.as_ref().iter().enumerate() { + match event { + Event::SectionKey(event_key) if *event_key == key => { + expect_value = true; + offset_list.push(i - last_boundary); + offset_index += 1; + last_boundary = i; + } + Event::Value(_) | Event::ValueDone(_) if expect_value => { + expect_value = false; + entries.push(EntryData { + section_id, + offset_index, + }); + offset_list.push(i - last_boundary + 1); + offset_index += 1; + last_boundary = i + 1; + } + _ => (), + } + } + offsets.insert(section_id, offset_list); + } + + entries.sort(); + + if entries.is_empty() { + Err(lookup::existing::Error::KeyMissing) + } else { + Ok(MultiValueMut { + section: &mut self.sections, + key, + indices_and_sizes: entries, + offsets, + }) + } + } + + /// Sets a value in a given `section_name`, optional `subsection_name`, and `key`. + /// Note sections named `section_name` and `subsection_name` (if not `None`) + /// must exist for this method to work. + /// + /// # Examples + /// + /// Given the config, + /// + /// ```text + /// [core] + /// a = b + /// [core] + /// a = c + /// a = d + /// ``` + /// + /// Setting a new value to the key `core.a` will yield the following: + /// + /// ``` + /// # use gix_config::File; + /// # use std::borrow::Cow; + /// # use bstr::BStr; + /// # use std::convert::TryFrom; + /// # let mut gix_config = gix_config::File::try_from("[core]a=b\n[core]\na=c\na=d").unwrap(); + /// gix_config.set_existing_raw_value("core", None, "a", "e")?; + /// assert_eq!(gix_config.raw_value("core", None, "a")?, Cow::<BStr>::Borrowed("e".into())); + /// assert_eq!( + /// gix_config.raw_values("core", None, "a")?, + /// vec![ + /// Cow::<BStr>::Borrowed("b".into()), + /// Cow::<BStr>::Borrowed("c".into()), + /// Cow::<BStr>::Borrowed("e".into()) + /// ], + /// ); + /// # Ok::<(), Box<dyn std::error::Error>>(()) + /// ``` + pub fn set_existing_raw_value<'b>( + &mut self, + section_name: impl AsRef<str>, + subsection_name: Option<&BStr>, + key: impl AsRef<str>, + new_value: impl Into<&'b BStr>, + ) -> Result<(), lookup::existing::Error> { + self.raw_value_mut(section_name, subsection_name, key.as_ref()) + .map(|mut entry| entry.set(new_value)) + } + + /// Sets a value in a given `section_name`, optional `subsection_name`, and `key`. + /// Creates the section if necessary and the key as well, or overwrites the last existing value otherwise. + /// + /// # Examples + /// + /// Given the config, + /// + /// ```text + /// [core] + /// a = b + /// ``` + /// + /// Setting a new value to the key `core.a` will yield the following: + /// + /// ``` + /// # use gix_config::File; + /// # use std::borrow::Cow; + /// # use bstr::BStr; + /// # use std::convert::TryFrom; + /// # let mut gix_config = gix_config::File::try_from("[core]a=b").unwrap(); + /// let prev = gix_config.set_raw_value("core", None, "a", "e")?; + /// gix_config.set_raw_value("core", None, "b", "f")?; + /// assert_eq!(prev.expect("present").as_ref(), "b"); + /// assert_eq!(gix_config.raw_value("core", None, "a")?, Cow::<BStr>::Borrowed("e".into())); + /// assert_eq!(gix_config.raw_value("core", None, "b")?, Cow::<BStr>::Borrowed("f".into())); + /// # Ok::<(), Box<dyn std::error::Error>>(()) + /// ``` + pub fn set_raw_value<'b, Key, E>( + &mut self, + section_name: impl AsRef<str>, + subsection_name: Option<&BStr>, + key: Key, + new_value: impl Into<&'b BStr>, + ) -> Result<Option<Cow<'event, BStr>>, crate::file::set_raw_value::Error> + where + Key: TryInto<section::Key<'event>, Error = E>, + section::key::Error: From<E>, + { + self.set_raw_value_filter(section_name, subsection_name, key, new_value, &mut |_| true) + } + + /// Similar to [`set_raw_value()`][Self::set_raw_value()], but only sets existing values in sections matching + /// `filter`, creating a new section otherwise. + pub fn set_raw_value_filter<'b, Key, E>( + &mut self, + section_name: impl AsRef<str>, + subsection_name: Option<&BStr>, + key: Key, + new_value: impl Into<&'b BStr>, + filter: &mut MetadataFilter, + ) -> Result<Option<Cow<'event, BStr>>, crate::file::set_raw_value::Error> + where + Key: TryInto<section::Key<'event>, Error = E>, + section::key::Error: From<E>, + { + let mut section = self.section_mut_or_create_new_filter(section_name, subsection_name, filter)?; + Ok(section.set(key.try_into().map_err(section::key::Error::from)?, new_value)) + } + + /// Sets a multivar in a given section, optional subsection, and key value. + /// + /// This internally zips together the new values and the existing values. + /// As a result, if more new values are provided than the current amount of + /// multivars, then the latter values are not applied. If there are less + /// new values than old ones then the remaining old values are unmodified. + /// + /// **Note**: Mutation order is _not_ guaranteed and is non-deterministic. + /// If you need finer control over which values of the multivar are set, + /// consider using [`raw_values_mut()`][Self::raw_values_mut()], which will let you iterate + /// and check over the values instead. This is best used as a convenience + /// function for setting multivars whose values should be treated as an + /// unordered set. + /// + /// # Examples + /// + /// Let us use the follow config for all examples: + /// + /// ```text + /// [core] + /// a = b + /// [core] + /// a = c + /// a = d + /// ``` + /// + /// Setting an equal number of values: + /// + /// ``` + /// # use gix_config::File; + /// # use std::borrow::Cow; + /// # use std::convert::TryFrom; + /// # use bstr::BStr; + /// # let mut gix_config = gix_config::File::try_from("[core]a=b\n[core]\na=c\na=d").unwrap(); + /// let new_values = vec![ + /// "x", + /// "y", + /// "z", + /// ]; + /// gix_config.set_existing_raw_multi_value("core", None, "a", new_values.into_iter())?; + /// let fetched_config = gix_config.raw_values("core", None, "a")?; + /// assert!(fetched_config.contains(&Cow::<BStr>::Borrowed("x".into()))); + /// assert!(fetched_config.contains(&Cow::<BStr>::Borrowed("y".into()))); + /// assert!(fetched_config.contains(&Cow::<BStr>::Borrowed("z".into()))); + /// # Ok::<(), gix_config::lookup::existing::Error>(()) + /// ``` + /// + /// Setting less than the number of present values sets the first ones found: + /// + /// ``` + /// # use gix_config::File; + /// # use std::borrow::Cow; + /// # use std::convert::TryFrom; + /// # use bstr::BStr; + /// # let mut gix_config = gix_config::File::try_from("[core]a=b\n[core]\na=c\na=d").unwrap(); + /// let new_values = vec![ + /// "x", + /// "y", + /// ]; + /// gix_config.set_existing_raw_multi_value("core", None, "a", new_values.into_iter())?; + /// let fetched_config = gix_config.raw_values("core", None, "a")?; + /// assert!(fetched_config.contains(&Cow::<BStr>::Borrowed("x".into()))); + /// assert!(fetched_config.contains(&Cow::<BStr>::Borrowed("y".into()))); + /// # Ok::<(), gix_config::lookup::existing::Error>(()) + /// ``` + /// + /// Setting more than the number of present values discards the rest: + /// + /// ``` + /// # use gix_config::File; + /// # use std::borrow::Cow; + /// # use std::convert::TryFrom; + /// # use bstr::BStr; + /// # let mut gix_config = gix_config::File::try_from("[core]a=b\n[core]\na=c\na=d").unwrap(); + /// let new_values = vec![ + /// "x", + /// "y", + /// "z", + /// "discarded", + /// ]; + /// gix_config.set_existing_raw_multi_value("core", None, "a", new_values)?; + /// assert!(!gix_config.raw_values("core", None, "a")?.contains(&Cow::<BStr>::Borrowed("discarded".into()))); + /// # Ok::<(), gix_config::lookup::existing::Error>(()) + /// ``` + pub fn set_existing_raw_multi_value<'a, Iter, Item>( + &mut self, + section_name: impl AsRef<str>, + subsection_name: Option<&BStr>, + key: impl AsRef<str>, + new_values: Iter, + ) -> Result<(), lookup::existing::Error> + where + Iter: IntoIterator<Item = Item>, + Item: Into<&'a BStr>, + { + self.raw_values_mut(section_name, subsection_name, key.as_ref()) + .map(|mut v| v.set_values(new_values)) + } +} diff --git a/vendor/gix-config/src/file/access/read_only.rs b/vendor/gix-config/src/file/access/read_only.rs new file mode 100644 index 000000000..5520c6566 --- /dev/null +++ b/vendor/gix-config/src/file/access/read_only.rs @@ -0,0 +1,353 @@ +use std::{borrow::Cow, convert::TryFrom}; + +use bstr::{BStr, ByteSlice}; +use gix_features::threading::OwnShared; +use smallvec::SmallVec; + +use crate::{ + file, + file::{ + write::{extract_newline, platform_newline}, + Metadata, MetadataFilter, SectionId, + }, + lookup, + parse::Event, + File, +}; + +/// Read-only low-level access methods, as it requires generics for converting into +/// custom values defined in this crate like [`Integer`][crate::Integer] and +/// [`Color`][crate::Color]. +impl<'event> File<'event> { + /// Returns an interpreted value given a section, an optional subsection and + /// key. + /// + /// It's recommended to use one of the value types provide dby this crate + /// as they implement the conversion, but this function is flexible and + /// will accept any type that implements [`TryFrom<&BStr>`][std::convert::TryFrom]. + /// + /// Consider [`Self::values`] if you want to get all values of a multivar instead. + /// + /// If a `string` is desired, use the [`string()`][Self::string()] method instead. + /// + /// # Examples + /// + /// ``` + /// # use gix_config::File; + /// # use gix_config::{Integer, Boolean}; + /// # use std::borrow::Cow; + /// # use std::convert::TryFrom; + /// let config = r#" + /// [core] + /// a = 10k + /// c = false + /// "#; + /// let gix_config = gix_config::File::try_from(config)?; + /// // You can either use the turbofish to determine the type... + /// let a_value = gix_config.value::<Integer>("core", None, "a")?; + /// // ... or explicitly declare the type to avoid the turbofish + /// let c_value: Boolean = gix_config.value("core", None, "c")?; + /// # Ok::<(), Box<dyn std::error::Error>>(()) + /// ``` + pub fn value<'a, T: TryFrom<Cow<'a, BStr>>>( + &'a self, + section_name: &str, + subsection_name: Option<&BStr>, + key: &str, + ) -> Result<T, lookup::Error<T::Error>> { + T::try_from(self.raw_value(section_name, subsection_name, key)?).map_err(lookup::Error::FailedConversion) + } + + /// Like [`value()`][File::value()], but returning an `None` if the value wasn't found at `section[.subsection].key` + pub fn try_value<'a, T: TryFrom<Cow<'a, BStr>>>( + &'a self, + section_name: &str, + subsection_name: Option<&BStr>, + key: &str, + ) -> Option<Result<T, T::Error>> { + self.raw_value(section_name, subsection_name, key).ok().map(T::try_from) + } + + /// Returns all interpreted values given a section, an optional subsection + /// and key. + /// + /// It's recommended to use one of the value types provide dby this crate + /// as they implement the conversion, but this function is flexible and + /// will accept any type that implements [`TryFrom<&BStr>`][std::convert::TryFrom]. + /// + /// Consider [`Self::value`] if you want to get a single value + /// (following last-one-wins resolution) instead. + /// + /// To access plain strings, use the [`strings()`][Self::strings()] method instead. + /// + /// # Examples + /// + /// ``` + /// # use gix_config::File; + /// # use gix_config::{Integer, Boolean}; + /// # use std::borrow::Cow; + /// # use std::convert::TryFrom; + /// # use bstr::ByteSlice; + /// let config = r#" + /// [core] + /// a = true + /// c + /// [core] + /// a + /// a = false + /// "#; + /// let gix_config = gix_config::File::try_from(config).unwrap(); + /// // You can either use the turbofish to determine the type... + /// let a_value = gix_config.values::<Boolean>("core", None, "a")?; + /// assert_eq!( + /// a_value, + /// vec![ + /// Boolean(true), + /// Boolean(false), + /// Boolean(false), + /// ] + /// ); + /// // ... or explicitly declare the type to avoid the turbofish + /// let c_value: Vec<Boolean> = gix_config.values("core", None, "c").unwrap(); + /// assert_eq!(c_value, vec![Boolean(false)]); + /// # Ok::<(), Box<dyn std::error::Error>>(()) + /// ``` + /// + /// [`value`]: crate::value + /// [`TryFrom`]: std::convert::TryFrom + pub fn values<'a, T: TryFrom<Cow<'a, BStr>>>( + &'a self, + section_name: &str, + subsection_name: Option<&BStr>, + key: &str, + ) -> Result<Vec<T>, lookup::Error<T::Error>> { + self.raw_values(section_name, subsection_name, key)? + .into_iter() + .map(T::try_from) + .collect::<Result<Vec<_>, _>>() + .map_err(lookup::Error::FailedConversion) + } + + /// Returns the last found immutable section with a given `name` and optional `subsection_name`. + pub fn section( + &self, + name: impl AsRef<str>, + subsection_name: Option<&BStr>, + ) -> Result<&file::Section<'event>, lookup::existing::Error> { + self.section_filter(name, subsection_name, &mut |_| true)? + .ok_or(lookup::existing::Error::SectionMissing) + } + + /// Returns the last found immutable section with a given `key`, identifying the name and subsection name like `core` + /// or `remote.origin`. + pub fn section_by_key<'a>( + &self, + key: impl Into<&'a BStr>, + ) -> Result<&file::Section<'event>, lookup::existing::Error> { + let key = crate::parse::section::unvalidated::Key::parse(key).ok_or(lookup::existing::Error::KeyMissing)?; + self.section(key.section_name, key.subsection_name) + } + + /// Returns the last found immutable section with a given `name` and optional `subsection_name`, that matches `filter`. + /// + /// If there are sections matching `section_name` and `subsection_name` but the `filter` rejects all of them, `Ok(None)` + /// is returned. + pub fn section_filter<'a>( + &'a self, + name: impl AsRef<str>, + subsection_name: Option<&BStr>, + filter: &mut MetadataFilter, + ) -> Result<Option<&'a file::Section<'event>>, lookup::existing::Error> { + Ok(self + .section_ids_by_name_and_subname(name.as_ref(), subsection_name)? + .rev() + .find_map({ + let sections = &self.sections; + move |id| { + let s = §ions[&id]; + filter(s.meta()).then_some(s) + } + })) + } + + /// Like [`section_filter()`][File::section_filter()], but identifies the section with `key` like `core` or `remote.origin`. + pub fn section_filter_by_key<'a, 'b>( + &'a self, + key: impl Into<&'b BStr>, + filter: &mut MetadataFilter, + ) -> Result<Option<&'a file::Section<'event>>, lookup::existing::Error> { + let key = crate::parse::section::unvalidated::Key::parse(key).ok_or(lookup::existing::Error::KeyMissing)?; + self.section_filter(key.section_name, key.subsection_name, filter) + } + + /// Gets all sections that match the provided `name`, ignoring any subsections. + /// + /// # Examples + /// + /// Provided the following config: + /// + /// ```text + /// [core] + /// a = b + /// [core ""] + /// c = d + /// [core "apple"] + /// e = f + /// ``` + /// + /// Calling this method will yield all sections: + /// + /// ``` + /// # use gix_config::File; + /// # use gix_config::{Integer, Boolean}; + /// # use std::borrow::Cow; + /// # use std::convert::TryFrom; + /// let config = r#" + /// [core] + /// a = b + /// [core ""] + /// c = d + /// [core "apple"] + /// e = f + /// "#; + /// let gix_config = gix_config::File::try_from(config)?; + /// assert_eq!(gix_config.sections_by_name("core").map_or(0, |s|s.count()), 3); + /// # Ok::<(), Box<dyn std::error::Error>>(()) + /// ``` + #[must_use] + pub fn sections_by_name<'a>(&'a self, name: &'a str) -> Option<impl Iterator<Item = &file::Section<'event>> + '_> { + self.section_ids_by_name(name).ok().map(move |ids| { + ids.map(move |id| { + self.sections + .get(&id) + .expect("section doesn't have id from from lookup") + }) + }) + } + + /// Similar to [`sections_by_name()`][Self::sections_by_name()], but returns an identifier for this section as well to allow + /// referring to it unambiguously even in the light of deletions. + #[must_use] + pub fn sections_and_ids_by_name<'a>( + &'a self, + name: &'a str, + ) -> Option<impl Iterator<Item = (&file::Section<'event>, SectionId)> + '_> { + self.section_ids_by_name(name).ok().map(move |ids| { + ids.map(move |id| { + ( + self.sections + .get(&id) + .expect("section doesn't have id from from lookup"), + id, + ) + }) + }) + } + + /// Gets all sections that match the provided `name`, ignoring any subsections, and pass the `filter`. + #[must_use] + pub fn sections_by_name_and_filter<'a>( + &'a self, + name: &'a str, + filter: &'a mut MetadataFilter, + ) -> Option<impl Iterator<Item = &file::Section<'event>> + '_> { + self.section_ids_by_name(name).ok().map(move |ids| { + ids.filter_map(move |id| { + let s = self + .sections + .get(&id) + .expect("section doesn't have id from from lookup"); + filter(s.meta()).then_some(s) + }) + }) + } + + /// Returns the number of values in the config, no matter in which section. + /// + /// For example, a config with multiple empty sections will return 0. + /// This ignores any comments. + #[must_use] + pub fn num_values(&self) -> usize { + self.sections.values().map(|section| section.num_values()).sum() + } + + /// Returns if there are no entries in the config. This will return true + /// if there are only empty sections, with whitespace and comments not being considered + /// void. + #[must_use] + pub fn is_void(&self) -> bool { + self.sections.values().all(|s| s.body.is_void()) + } + + /// Return this file's metadata, typically set when it was first created to indicate its origins. + /// + /// It will be used in all newly created sections to identify them. + /// Change it with [`File::set_meta()`]. + pub fn meta(&self) -> &Metadata { + &self.meta + } + + /// Change the origin of this instance to be the given `meta`data. + /// + /// This is useful to control what origin about-to-be-added sections receive. + pub fn set_meta(&mut self, meta: impl Into<OwnShared<Metadata>>) -> &mut Self { + self.meta = meta.into(); + self + } + + /// Similar to [`meta()`][File::meta()], but with shared ownership. + pub fn meta_owned(&self) -> OwnShared<Metadata> { + OwnShared::clone(&self.meta) + } + + /// Return an iterator over all sections, in order of occurrence in the file itself. + pub fn sections(&self) -> impl Iterator<Item = &file::Section<'event>> + '_ { + self.section_order.iter().map(move |id| &self.sections[id]) + } + + /// Return an iterator over all sections and their ids, in order of occurrence in the file itself. + pub fn sections_and_ids(&self) -> impl Iterator<Item = (&file::Section<'event>, SectionId)> + '_ { + self.section_order.iter().map(move |id| (&self.sections[id], *id)) + } + + /// Return an iterator over all sections along with non-section events that are placed right after them, + /// in order of occurrence in the file itself. + /// + /// This allows to reproduce the look of sections perfectly when serializing them with + /// [`write_to()`][file::Section::write_to()]. + pub fn sections_and_postmatter(&self) -> impl Iterator<Item = (&file::Section<'event>, Vec<&Event<'event>>)> { + self.section_order.iter().map(move |id| { + let s = &self.sections[id]; + let pm: Vec<_> = self + .frontmatter_post_section + .get(id) + .map(|events| events.iter().collect()) + .unwrap_or_default(); + (s, pm) + }) + } + + /// Return all events which are in front of the first of our sections, or `None` if there are none. + pub fn frontmatter(&self) -> Option<impl Iterator<Item = &Event<'event>>> { + (!self.frontmatter_events.is_empty()).then(|| self.frontmatter_events.iter()) + } + + /// Return the newline characters that have been detected in this config file or the default ones + /// for the current platform. + /// + /// Note that the first found newline is the one we use in the assumption of consistency. + pub fn detect_newline_style(&self) -> &BStr { + self.frontmatter_events + .iter() + .find_map(extract_newline) + .or_else(|| { + self.sections() + .find_map(|s| s.body.as_ref().iter().find_map(extract_newline)) + }) + .unwrap_or_else(|| platform_newline()) + } + + pub(crate) fn detect_newline_style_smallvec(&self) -> SmallVec<[u8; 2]> { + self.detect_newline_style().as_bytes().into() + } +} diff --git a/vendor/gix-config/src/file/impls.rs b/vendor/gix-config/src/file/impls.rs new file mode 100644 index 000000000..c26df5fb8 --- /dev/null +++ b/vendor/gix-config/src/file/impls.rs @@ -0,0 +1,111 @@ +use std::{borrow::Cow, convert::TryFrom, fmt::Display, str::FromStr}; + +use bstr::{BStr, BString, ByteVec}; + +use crate::{ + file::Metadata, + parse, + parse::{section, Event}, + value::normalize, + File, +}; + +impl FromStr for File<'static> { + type Err = parse::Error; + + fn from_str(s: &str) -> Result<Self, Self::Err> { + parse::Events::from_bytes_owned(s.as_bytes(), None) + .map(|events| File::from_parse_events_no_includes(events, Metadata::api())) + } +} + +impl<'a> TryFrom<&'a str> for File<'a> { + type Error = parse::Error; + + /// Convenience constructor. Attempts to parse the provided string into a + /// [`File`]. See [`Events::from_str()`][crate::parse::Events::from_str()] for more information. + fn try_from(s: &'a str) -> Result<File<'a>, Self::Error> { + parse::Events::from_str(s).map(|events| Self::from_parse_events_no_includes(events, Metadata::api())) + } +} + +impl<'a> TryFrom<&'a BStr> for File<'a> { + type Error = parse::Error; + + /// Convenience constructor. Attempts to parse the provided byte string into + /// a [`File`]. See [`Events::from_bytes()`][parse::Events::from_bytes()] for more information. + fn try_from(value: &'a BStr) -> Result<File<'a>, Self::Error> { + parse::Events::from_bytes(value, None) + .map(|events| Self::from_parse_events_no_includes(events, Metadata::api())) + } +} + +impl From<File<'_>> for BString { + fn from(c: File<'_>) -> Self { + c.into() + } +} + +impl Display for File<'_> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + Display::fmt(&self.to_bstring(), f) + } +} + +impl PartialEq for File<'_> { + fn eq(&self, other: &Self) -> bool { + fn find_key<'a>(mut it: impl Iterator<Item = &'a Event<'a>>) -> Option<&'a section::Key<'a>> { + it.find_map(|e| match e { + Event::SectionKey(k) => Some(k), + _ => None, + }) + } + fn collect_value<'a>(it: impl Iterator<Item = &'a Event<'a>>) -> Cow<'a, BStr> { + let mut partial_value = BString::default(); + let mut value = None; + + for event in it { + match event { + Event::SectionKey(_) => break, + Event::Value(v) => { + value = v.clone().into(); + break; + } + Event::ValueNotDone(v) => partial_value.push_str(v.as_ref()), + Event::ValueDone(v) => { + partial_value.push_str(v.as_ref()); + value = Some(partial_value.into()); + break; + } + _ => (), + } + } + value.map(normalize).unwrap_or_default() + } + if self.section_order.len() != other.section_order.len() { + return false; + } + + for (lhs, rhs) in self + .section_order + .iter() + .zip(&other.section_order) + .map(|(lhs, rhs)| (&self.sections[lhs], &other.sections[rhs])) + { + if !(lhs.header.name == rhs.header.name && lhs.header.subsection_name == rhs.header.subsection_name) { + return false; + } + + let (mut lhs, mut rhs) = (lhs.body.0.iter(), rhs.body.0.iter()); + while let (Some(lhs_key), Some(rhs_key)) = (find_key(&mut lhs), find_key(&mut rhs)) { + if lhs_key != rhs_key { + return false; + } + if collect_value(&mut lhs) != collect_value(&mut rhs) { + return false; + } + } + } + true + } +} diff --git a/vendor/gix-config/src/file/includes/mod.rs b/vendor/gix-config/src/file/includes/mod.rs new file mode 100644 index 000000000..8fd92725f --- /dev/null +++ b/vendor/gix-config/src/file/includes/mod.rs @@ -0,0 +1,319 @@ +use std::{ + borrow::Cow, + path::{Path, PathBuf}, +}; + +use bstr::{BStr, BString, ByteSlice, ByteVec}; +use gix_features::threading::OwnShared; +use gix_ref::Category; + +use crate::{ + file, + file::{includes, init, Metadata, SectionId}, + path, File, +}; + +impl File<'static> { + /// Traverse all `include` and `includeIf` directives found in this instance and follow them, loading the + /// referenced files from their location and adding their content right past the value that included them. + /// + /// # Limitations + /// + /// - Note that this method is _not idempotent_ and calling it multiple times will resolve includes multiple + /// times. It's recommended use is as part of a multi-step bootstrapping which needs fine-grained control, + /// and unless that's given one should prefer one of the other ways of initialization that resolve includes + /// at the right time. + /// - included values are added after the _section_ that included them, not directly after the value. This is + /// a deviation from how git does it, as it technically adds new value right after the include path itself, + /// technically 'splitting' the section. This can only make a difference if the `include` section also has values + /// which later overwrite portions of the included file, which seems unusual as these would be related to `includes`. + /// We can fix this by 'splitting' the include section if needed so the included sections are put into the right place. + pub fn resolve_includes(&mut self, options: init::Options<'_>) -> Result<(), Error> { + if options.includes.max_depth == 0 { + return Ok(()); + } + let mut buf = Vec::new(); + resolve(self, &mut buf, options) + } +} + +pub(crate) fn resolve(config: &mut File<'static>, buf: &mut Vec<u8>, options: init::Options<'_>) -> Result<(), Error> { + resolve_includes_recursive(config, 0, buf, options) +} + +fn resolve_includes_recursive( + target_config: &mut File<'static>, + depth: u8, + buf: &mut Vec<u8>, + options: init::Options<'_>, +) -> Result<(), Error> { + if depth == options.includes.max_depth { + return if options.includes.err_on_max_depth_exceeded { + Err(Error::IncludeDepthExceeded { + max_depth: options.includes.max_depth, + }) + } else { + Ok(()) + }; + } + + let mut section_ids_and_include_paths = Vec::new(); + for (id, section) in target_config + .section_order + .iter() + .map(|id| (*id, &target_config.sections[id])) + { + let header = §ion.header; + let header_name = header.name.as_ref(); + if header_name == "include" && header.subsection_name.is_none() { + detach_include_paths(&mut section_ids_and_include_paths, section, id) + } else if header_name == "includeIf" { + if let Some(condition) = &header.subsection_name { + let target_config_path = section.meta.path.as_deref(); + if include_condition_match(condition.as_ref(), target_config_path, options.includes)? { + detach_include_paths(&mut section_ids_and_include_paths, section, id) + } + } + } + } + + append_followed_includes_recursively(section_ids_and_include_paths, target_config, depth, options, buf) +} + +fn append_followed_includes_recursively( + section_ids_and_include_paths: Vec<(SectionId, crate::Path<'_>)>, + target_config: &mut File<'static>, + depth: u8, + options: init::Options<'_>, + buf: &mut Vec<u8>, +) -> Result<(), Error> { + for (section_id, config_path) in section_ids_and_include_paths { + let meta = OwnShared::clone(&target_config.sections[§ion_id].meta); + let target_config_path = meta.path.as_deref(); + let config_path = match resolve_path(config_path, target_config_path, options.includes)? { + Some(p) => p, + None => continue, + }; + if !config_path.is_file() { + continue; + } + + buf.clear(); + std::io::copy(&mut std::fs::File::open(&config_path)?, buf)?; + let config_meta = Metadata { + path: Some(config_path), + trust: meta.trust, + level: meta.level + 1, + source: meta.source, + }; + let no_follow_options = init::Options { + includes: includes::Options::no_follow(), + ..options + }; + + let mut include_config = + File::from_bytes_owned(buf, config_meta, no_follow_options).map_err(|err| match err { + init::Error::Parse(err) => Error::Parse(err), + init::Error::Interpolate(err) => Error::Interpolate(err), + init::Error::Includes(_) => unreachable!("BUG: {:?} not possible due to no-follow options", err), + })?; + resolve_includes_recursive(&mut include_config, depth + 1, buf, options)?; + + target_config.append_or_insert(include_config, Some(section_id)); + } + Ok(()) +} + +fn detach_include_paths( + include_paths: &mut Vec<(SectionId, crate::Path<'static>)>, + section: &file::Section<'_>, + id: SectionId, +) { + include_paths.extend( + section + .body + .values("path") + .into_iter() + .map(|path| (id, crate::Path::from(Cow::Owned(path.into_owned())))), + ) +} + +fn include_condition_match( + condition: &BStr, + target_config_path: Option<&Path>, + options: Options<'_>, +) -> Result<bool, Error> { + let mut tokens = condition.splitn(2, |b| *b == b':'); + let (prefix, condition) = match (tokens.next(), tokens.next()) { + (Some(a), Some(b)) => (a, b), + _ => return Ok(false), + }; + let condition = condition.as_bstr(); + match prefix { + b"gitdir" => gitdir_matches( + condition, + target_config_path, + options, + gix_glob::wildmatch::Mode::empty(), + ), + b"gitdir/i" => gitdir_matches( + condition, + target_config_path, + options, + gix_glob::wildmatch::Mode::IGNORE_CASE, + ), + b"onbranch" => Ok(onbranch_matches(condition, options.conditional).is_some()), + _ => Ok(false), + } +} + +fn onbranch_matches( + condition: &BStr, + conditional::Context { branch_name, .. }: conditional::Context<'_>, +) -> Option<()> { + let branch_name = branch_name?; + let (_, branch_name) = branch_name + .category_and_short_name() + .filter(|(cat, _)| *cat == Category::LocalBranch)?; + + let condition = if condition.ends_with(b"/") { + let mut condition: BString = condition.into(); + condition.push_str("**"); + Cow::Owned(condition) + } else { + condition.into() + }; + + gix_glob::wildmatch( + condition.as_ref(), + branch_name, + gix_glob::wildmatch::Mode::NO_MATCH_SLASH_LITERAL, + ) + .then_some(()) +} + +fn gitdir_matches( + condition_path: &BStr, + target_config_path: Option<&Path>, + Options { + conditional: conditional::Context { git_dir, .. }, + interpolate: context, + err_on_interpolation_failure, + err_on_missing_config_path, + .. + }: Options<'_>, + wildmatch_mode: gix_glob::wildmatch::Mode, +) -> Result<bool, Error> { + if !err_on_interpolation_failure && git_dir.is_none() { + return Ok(false); + } + let git_dir = gix_path::to_unix_separators_on_windows(gix_path::into_bstr(git_dir.ok_or(Error::MissingGitDir)?)); + + let mut pattern_path: Cow<'_, _> = { + let path = match check_interpolation_result( + err_on_interpolation_failure, + crate::Path::from(Cow::Borrowed(condition_path)).interpolate(context), + )? { + Some(p) => p, + None => return Ok(false), + }; + gix_path::into_bstr(path).into_owned().into() + }; + // NOTE: yes, only if we do path interpolation will the slashes be forced to unix separators on windows + if pattern_path != condition_path { + pattern_path = gix_path::to_unix_separators_on_windows(pattern_path); + } + + if let Some(relative_pattern_path) = pattern_path.strip_prefix(b"./") { + if !err_on_missing_config_path && target_config_path.is_none() { + return Ok(false); + } + let parent_dir = target_config_path + .ok_or(Error::MissingConfigPath)? + .parent() + .expect("config path can never be /"); + let mut joined_path = gix_path::to_unix_separators_on_windows(gix_path::into_bstr(parent_dir)).into_owned(); + joined_path.push(b'/'); + joined_path.extend_from_slice(relative_pattern_path); + pattern_path = joined_path.into(); + } + + // NOTE: this special handling of leading backslash is needed to do it like git does + if pattern_path.iter().next() != Some(&(std::path::MAIN_SEPARATOR as u8)) + && !gix_path::from_bstr(pattern_path.clone()).is_absolute() + { + let mut prefixed = pattern_path.into_owned(); + prefixed.insert_str(0, "**/"); + pattern_path = prefixed.into() + } + if pattern_path.ends_with(b"/") { + let mut suffixed = pattern_path.into_owned(); + suffixed.push_str("**"); + pattern_path = suffixed.into(); + } + + let match_mode = gix_glob::wildmatch::Mode::NO_MATCH_SLASH_LITERAL | wildmatch_mode; + let is_match = gix_glob::wildmatch(pattern_path.as_bstr(), git_dir.as_bstr(), match_mode); + if is_match { + return Ok(true); + } + + let expanded_git_dir = gix_path::into_bstr(gix_path::realpath(gix_path::from_byte_slice(&git_dir))?); + Ok(gix_glob::wildmatch( + pattern_path.as_bstr(), + expanded_git_dir.as_bstr(), + match_mode, + )) +} + +fn check_interpolation_result( + disable: bool, + res: Result<Cow<'_, std::path::Path>, path::interpolate::Error>, +) -> Result<Option<Cow<'_, std::path::Path>>, path::interpolate::Error> { + if disable { + return res.map(Some); + } + match res { + Ok(good) => Ok(good.into()), + Err(err) => match err { + path::interpolate::Error::Missing { .. } | path::interpolate::Error::UserInterpolationUnsupported => { + Ok(None) + } + path::interpolate::Error::UsernameConversion(_) | path::interpolate::Error::Utf8Conversion { .. } => { + Err(err) + } + }, + } +} + +fn resolve_path( + path: crate::Path<'_>, + target_config_path: Option<&Path>, + includes::Options { + interpolate: context, + err_on_interpolation_failure, + err_on_missing_config_path, + .. + }: includes::Options<'_>, +) -> Result<Option<PathBuf>, Error> { + let path = match check_interpolation_result(err_on_interpolation_failure, path.interpolate(context))? { + Some(p) => p, + None => return Ok(None), + }; + let path: PathBuf = if path.is_relative() { + if !err_on_missing_config_path && target_config_path.is_none() { + return Ok(None); + } + target_config_path + .ok_or(Error::MissingConfigPath)? + .parent() + .expect("path is a config file which naturally lives in a directory") + .join(path) + } else { + path.into() + }; + Ok(Some(path)) +} + +mod types; +pub use types::{conditional, Error, Options}; diff --git a/vendor/gix-config/src/file/includes/types.rs b/vendor/gix-config/src/file/includes/types.rs new file mode 100644 index 000000000..64306bd9c --- /dev/null +++ b/vendor/gix-config/src/file/includes/types.rs @@ -0,0 +1,131 @@ +use crate::{parse, path::interpolate}; + +/// The error returned when following includes. +#[derive(Debug, thiserror::Error)] +#[allow(missing_docs)] +pub enum Error { + #[error(transparent)] + Io(#[from] std::io::Error), + #[error(transparent)] + Parse(#[from] parse::Error), + #[error(transparent)] + Interpolate(#[from] interpolate::Error), + #[error("The maximum allowed length {} of the file include chain built by following nested resolve_includes is exceeded", .max_depth)] + IncludeDepthExceeded { max_depth: u8 }, + #[error("Include paths from environment variables must not be relative as no config file paths exists as root")] + MissingConfigPath, + #[error("The git directory must be provided to support `gitdir:` conditional includes")] + MissingGitDir, + #[error(transparent)] + Realpath(#[from] gix_path::realpath::Error), +} + +/// Options to handle includes, like `include.path` or `includeIf.<condition>.path`, +#[derive(Clone, Copy)] +pub struct Options<'a> { + /// The maximum allowed length of the file include chain built by following nested resolve_includes where base level is depth = 0. + pub max_depth: u8, + /// When max depth is exceeded while following nested includes, + /// return an error if true or silently stop following resolve_includes. + /// + /// Setting this value to false allows to read configuration with cycles, + /// which otherwise always results in an error. + pub err_on_max_depth_exceeded: bool, + /// If true, default false, failing to interpolate paths will result in an error. + /// + /// Interpolation also happens if paths in conditional includes can't be interpolated. + pub err_on_interpolation_failure: bool, + /// If true, default true, configuration not originating from a path will cause errors when trying to resolve + /// relative include paths (which would require the including configuration's path). + pub err_on_missing_config_path: bool, + /// Used during path interpolation, both for include paths before trying to read the file, and for + /// paths used in conditional `gitdir` includes. + pub interpolate: interpolate::Context<'a>, + + /// Additional context for conditional includes to work. + pub conditional: conditional::Context<'a>, +} + +impl<'a> Options<'a> { + /// Provide options to never follow include directives at all. + pub fn no_follow() -> Self { + Options { + max_depth: 0, + err_on_max_depth_exceeded: false, + err_on_interpolation_failure: false, + err_on_missing_config_path: false, + interpolate: Default::default(), + conditional: Default::default(), + } + } + /// Provide options to follow includes like git does, provided the required `conditional` and `interpolate` contexts + /// to support `gitdir` and `onbranch` based `includeIf` directives as well as standard `include.path` resolution. + /// Note that the follow-mode is `git`-style, following at most 10 indirections while + /// producing an error if the depth is exceeded. + pub fn follow(interpolate: interpolate::Context<'a>, conditional: conditional::Context<'a>) -> Self { + Options { + max_depth: 10, + err_on_max_depth_exceeded: true, + err_on_interpolation_failure: false, + err_on_missing_config_path: true, + interpolate, + conditional, + } + } + + /// For use with `follow` type options, cause failure if an include path couldn't be interpolated or the depth limit is exceeded. + pub fn strict(mut self) -> Self { + self.err_on_interpolation_failure = true; + self.err_on_max_depth_exceeded = true; + self.err_on_missing_config_path = true; + self + } + + /// Like [`follow`][Options::follow()], but without information to resolve `includeIf` directories as well as default + /// configuration to allow resolving `~username/` path. `home_dir` is required to resolve `~/` paths if set. + /// Note that `%(prefix)` paths cannot be interpolated with this configuration, use [`follow()`][Options::follow()] + /// instead for complete control. + pub fn follow_without_conditional(home_dir: Option<&'a std::path::Path>) -> Self { + Options { + max_depth: 10, + err_on_max_depth_exceeded: true, + err_on_interpolation_failure: false, + err_on_missing_config_path: true, + interpolate: interpolate::Context { + git_install_dir: None, + home_dir, + home_for_user: Some(interpolate::home_for_user), + }, + conditional: Default::default(), + } + } + + /// Set the context used for interpolation when interpolating paths to include as well as the paths + /// in `gitdir` conditional includes. + pub fn interpolate_with(mut self, context: interpolate::Context<'a>) -> Self { + self.interpolate = context; + self + } +} + +impl Default for Options<'_> { + fn default() -> Self { + Self::no_follow() + } +} + +/// +pub mod conditional { + /// Options to handle conditional includes like `includeIf.<condition>.path`. + #[derive(Clone, Copy, Default)] + pub struct Context<'a> { + /// The location of the .git directory. If `None`, `gitdir` conditions cause an error. + /// + /// Used for conditional includes, e.g. `includeIf.gitdir:…` or `includeIf:gitdir/i…`. + pub git_dir: Option<&'a std::path::Path>, + /// The name of the branch that is currently checked out. If `None`, `onbranch` conditions cause an error. + /// + /// Used for conditional includes, e.g. `includeIf.onbranch:main.…` + pub branch_name: Option<&'a gix_ref::FullNameRef>, + } +} diff --git a/vendor/gix-config/src/file/init/comfort.rs b/vendor/gix-config/src/file/init/comfort.rs new file mode 100644 index 000000000..ffe859a1a --- /dev/null +++ b/vendor/gix-config/src/file/init/comfort.rs @@ -0,0 +1,159 @@ +use std::path::PathBuf; + +use crate::{ + file::{init, Metadata}, + path, source, File, Source, +}; + +/// Easy-instantiation of typical non-repository git configuration files with all configuration defaulting to typical values. +/// +/// ### Limitations +/// +/// Note that `includeIf` conditions in global files will cause failure as the required information +/// to resolve them isn't present without a repository. +/// +/// Also note that relevant information to interpolate paths will be obtained from the environment or other +/// source on unix. +impl File<'static> { + /// Open all global configuration files which involves the following sources: + /// + /// * [system][crate::Source::System] + /// * [git][crate::Source::Git] + /// * [user][crate::Source::User] + /// + /// which excludes repository local configuration, as well as override-configuration from environment variables. + /// + /// Note that the file might [be empty][File::is_void()] in case no configuration file was found. + pub fn from_globals() -> Result<File<'static>, init::from_paths::Error> { + let metas = [source::Kind::System, source::Kind::Global] + .iter() + .flat_map(|kind| kind.sources()) + .filter_map(|source| { + let path = source + .storage_location(&mut |name| std::env::var_os(name)) + .and_then(|p| p.is_file().then_some(p)) + .map(|p| p.into_owned()); + + Metadata { + path, + source: *source, + level: 0, + trust: gix_sec::Trust::Full, + } + .into() + }); + + let home = std::env::var("HOME").ok().map(PathBuf::from); + let options = init::Options { + includes: init::includes::Options::follow_without_conditional(home.as_deref()), + ..Default::default() + }; + File::from_paths_metadata(metas, options).map(Option::unwrap_or_default) + } + + /// Generates a config from `GIT_CONFIG_*` environment variables and return a possibly empty `File`. + /// A typical use of this is to [`append`][File::append()] this configuration to another one with lower + /// precedence to obtain overrides. + /// + /// See [`gix-config`'s documentation] for more information on the environment variables in question. + /// + /// [`gix-config`'s documentation]: https://git-scm.com/docs/gix-config#Documentation/gix-config.txt-GITCONFIGCOUNT + pub fn from_environment_overrides() -> Result<File<'static>, init::from_env::Error> { + let home = std::env::var("HOME").ok().map(PathBuf::from); + let options = init::Options { + includes: init::includes::Options::follow_without_conditional(home.as_deref()), + ..Default::default() + }; + + File::from_env(options).map(Option::unwrap_or_default) + } +} + +/// An easy way to provide complete configuration for a repository. +impl File<'static> { + /// This configuration type includes the following sources, in order of precedence: + /// + /// - globals + /// - repository-local by loading `dir`/config + /// - worktree by loading `dir`/config.worktree + /// - environment + /// + /// Note that `dir` is the `.git` dir to load the configuration from, not the configuration file. + /// + /// Includes will be resolved within limits as some information like the git installation directory is missing to interpolate + /// paths with as well as git repository information like the branch name. + pub fn from_git_dir(dir: impl Into<std::path::PathBuf>) -> Result<File<'static>, from_git_dir::Error> { + let (mut local, git_dir) = { + let source = Source::Local; + let mut path = dir.into(); + path.push( + source + .storage_location(&mut |n| std::env::var_os(n)) + .expect("location available for local"), + ); + let local = Self::from_path_no_includes(&path, source)?; + path.pop(); + (local, path) + }; + + let worktree = match local.boolean("extensions", None, "worktreeConfig") { + Some(Ok(worktree_config)) => worktree_config.then(|| { + let source = Source::Worktree; + let path = git_dir.join( + source + .storage_location(&mut |n| std::env::var_os(n)) + .expect("location available for worktree"), + ); + Self::from_path_no_includes(path, source) + }), + _ => None, + } + .transpose()?; + + let home = std::env::var("HOME").ok().map(PathBuf::from); + let options = init::Options { + includes: init::includes::Options::follow( + path::interpolate::Context { + home_dir: home.as_deref(), + ..Default::default() + }, + init::includes::conditional::Context { + git_dir: Some(git_dir.as_ref()), + branch_name: None, + }, + ), + lossy: false, + }; + + let mut globals = Self::from_globals()?; + globals.resolve_includes(options)?; + local.resolve_includes(options)?; + + globals.append(local); + if let Some(mut worktree) = worktree { + worktree.resolve_includes(options)?; + globals.append(worktree); + } + globals.append(Self::from_environment_overrides()?); + + Ok(globals) + } +} + +/// +pub mod from_git_dir { + use crate::file::init; + + /// The error returned by [`File::from_git_dir()`][crate::File::from_git_dir()]. + #[derive(Debug, thiserror::Error)] + pub enum Error { + #[error(transparent)] + FromPaths(#[from] init::from_paths::Error), + #[error(transparent)] + FromEnv(#[from] init::from_env::Error), + #[error(transparent)] + Init(#[from] init::Error), + #[error(transparent)] + Includes(#[from] init::includes::Error), + } +} diff --git a/vendor/gix-config/src/file/init/from_env.rs b/vendor/gix-config/src/file/init/from_env.rs new file mode 100644 index 000000000..167d37399 --- /dev/null +++ b/vendor/gix-config/src/file/init/from_env.rs @@ -0,0 +1,88 @@ +use std::convert::TryFrom; + +use bstr::{BStr, ByteSlice}; + +use crate::{file, file::init, parse, parse::section, path::interpolate, File}; + +/// Represents the errors that may occur when calling [`File::from_env()`]. +#[derive(Debug, thiserror::Error)] +#[allow(missing_docs)] +pub enum Error { + #[error("Configuration {kind} at index {index} contained illformed UTF-8")] + IllformedUtf8 { index: usize, kind: &'static str }, + #[error("GIT_CONFIG_COUNT was not a positive integer: {}", .input)] + InvalidConfigCount { input: String }, + #[error("GIT_CONFIG_KEY_{} was not set", .key_id)] + InvalidKeyId { key_id: usize }, + #[error("GIT_CONFIG_KEY_{} was set to an invalid value: {}", .key_id, .key_val)] + InvalidKeyValue { key_id: usize, key_val: String }, + #[error("GIT_CONFIG_VALUE_{} was not set", .value_id)] + InvalidValueId { value_id: usize }, + #[error(transparent)] + PathInterpolationError(#[from] interpolate::Error), + #[error(transparent)] + Includes(#[from] init::includes::Error), + #[error(transparent)] + Section(#[from] section::header::Error), + #[error(transparent)] + Key(#[from] section::key::Error), +} + +/// Instantiation from environment variables +impl File<'static> { + /// Generates a config from `GIT_CONFIG_*` environment variables or returns `Ok(None)` if no configuration was found. + /// See [`gix-config`'s documentation] for more information on the environment variables in question. + /// + /// With `options` configured, it's possible to resolve `include.path` or `includeIf.<condition>.path` directives as well. + /// + /// [`gix-config`'s documentation]: https://git-scm.com/docs/gix-config#Documentation/gix-config.txt-GITCONFIGCOUNT + pub fn from_env(options: init::Options<'_>) -> Result<Option<File<'static>>, Error> { + use std::env; + let count: usize = match env::var("GIT_CONFIG_COUNT") { + Ok(v) => v.parse().map_err(|_| Error::InvalidConfigCount { input: v })?, + Err(_) => return Ok(None), + }; + + if count == 0 { + return Ok(None); + } + + let meta = file::Metadata { + path: None, + source: crate::Source::Env, + level: 0, + trust: gix_sec::Trust::Full, + }; + let mut config = File::new(meta); + for i in 0..count { + let key = gix_path::os_string_into_bstring( + env::var_os(format!("GIT_CONFIG_KEY_{i}")).ok_or(Error::InvalidKeyId { key_id: i })?, + ) + .map_err(|_| Error::IllformedUtf8 { index: i, kind: "key" })?; + let value = env::var_os(format!("GIT_CONFIG_VALUE_{i}")).ok_or(Error::InvalidValueId { value_id: i })?; + let key = parse::key(<_ as AsRef<BStr>>::as_ref(&key)).ok_or_else(|| Error::InvalidKeyValue { + key_id: i, + key_val: key.to_string(), + })?; + + config + .section_mut_or_create_new(key.section_name, key.subsection_name)? + .push( + section::Key::try_from(key.value_name.to_owned())?, + Some( + gix_path::os_str_into_bstr(&value) + .map_err(|_| Error::IllformedUtf8 { + index: i, + kind: "value", + })? + .as_bytes() + .into(), + ), + ); + } + + let mut buf = Vec::new(); + init::includes::resolve(&mut config, &mut buf, options)?; + Ok(Some(config)) + } +} diff --git a/vendor/gix-config/src/file/init/from_paths.rs b/vendor/gix-config/src/file/init/from_paths.rs new file mode 100644 index 000000000..5d671b69e --- /dev/null +++ b/vendor/gix-config/src/file/init/from_paths.rs @@ -0,0 +1,94 @@ +use std::collections::BTreeSet; + +use crate::{ + file::{init, init::Options, Metadata}, + File, +}; + +/// The error returned by [`File::from_paths_metadata()`] and [`File::from_path_no_includes()`]. +#[derive(Debug, thiserror::Error)] +#[allow(missing_docs)] +pub enum Error { + #[error(transparent)] + Io(#[from] std::io::Error), + #[error(transparent)] + Init(#[from] init::Error), +} + +/// Instantiation from one or more paths +impl File<'static> { + /// Load the single file at `path` with `source` without following include directives. + /// + /// Note that the path will be checked for ownership to derive trust. + pub fn from_path_no_includes(path: impl Into<std::path::PathBuf>, source: crate::Source) -> Result<Self, Error> { + let path = path.into(); + let trust = gix_sec::Trust::from_path_ownership(&path)?; + + let mut buf = Vec::new(); + std::io::copy(&mut std::fs::File::open(&path)?, &mut buf)?; + + Ok(File::from_bytes_owned( + &mut buf, + Metadata::from(source).at(path).with(trust), + Default::default(), + )?) + } + + /// Constructs a `gix-config` file from the provided metadata, which must include a path to read from or be ignored. + /// Returns `Ok(None)` if there was not a single input path provided, which is a possibility due to + /// [`Metadata::path`] being an `Option`. + /// If an input path doesn't exist, the entire operation will abort. See [`from_paths_metadata_buf()`][Self::from_paths_metadata_buf()] + /// for a more powerful version of this method. + pub fn from_paths_metadata( + path_meta: impl IntoIterator<Item = impl Into<Metadata>>, + options: Options<'_>, + ) -> Result<Option<Self>, Error> { + let mut buf = Vec::with_capacity(512); + let err_on_nonexisting_paths = true; + Self::from_paths_metadata_buf(path_meta, &mut buf, err_on_nonexisting_paths, options) + } + + /// Like [from_paths_metadata()][Self::from_paths_metadata()], but will use `buf` to temporarily store the config file + /// contents for parsing instead of allocating an own buffer. + /// + /// If `err_on_nonexisting_paths` is false, instead of aborting with error, we will continue to the next path instead. + pub fn from_paths_metadata_buf( + path_meta: impl IntoIterator<Item = impl Into<Metadata>>, + buf: &mut Vec<u8>, + err_on_non_existing_paths: bool, + options: Options<'_>, + ) -> Result<Option<Self>, Error> { + let mut target = None; + let mut seen = BTreeSet::default(); + for (path, mut meta) in path_meta.into_iter().filter_map(|meta| { + let mut meta = meta.into(); + meta.path.take().map(|p| (p, meta)) + }) { + if !seen.insert(path.clone()) { + continue; + } + + buf.clear(); + std::io::copy( + &mut match std::fs::File::open(&path) { + Ok(f) => f, + Err(err) if !err_on_non_existing_paths && err.kind() == std::io::ErrorKind::NotFound => continue, + Err(err) => return Err(err.into()), + }, + buf, + )?; + meta.path = Some(path); + + let config = Self::from_bytes_owned(buf, meta, options)?; + match &mut target { + None => { + target = Some(config); + } + Some(target) => { + target.append(config); + } + } + } + Ok(target) + } +} diff --git a/vendor/gix-config/src/file/init/mod.rs b/vendor/gix-config/src/file/init/mod.rs new file mode 100644 index 000000000..5b4030241 --- /dev/null +++ b/vendor/gix-config/src/file/init/mod.rs @@ -0,0 +1,86 @@ +use gix_features::threading::OwnShared; + +use crate::{ + file::{includes, section, Metadata}, + parse, File, +}; + +mod types; +pub use types::{Error, Options}; + +mod comfort; +/// +pub mod from_env; +/// +pub mod from_paths; + +impl<'a> File<'a> { + /// Return an empty `File` with the given `meta`-data to be attached to all new sections. + pub fn new(meta: impl Into<OwnShared<Metadata>>) -> Self { + Self { + frontmatter_events: Default::default(), + frontmatter_post_section: Default::default(), + section_lookup_tree: Default::default(), + sections: Default::default(), + section_id_counter: 0, + section_order: Default::default(), + meta: meta.into(), + } + } + + /// Instantiate a new `File` from given `input`, associating each section and their values with + /// `meta`-data, while respecting `options`. + pub fn from_bytes_no_includes( + input: &'a [u8], + meta: impl Into<OwnShared<Metadata>>, + options: Options<'_>, + ) -> Result<Self, Error> { + let meta = meta.into(); + Ok(Self::from_parse_events_no_includes( + parse::Events::from_bytes(input, options.to_event_filter())?, + meta, + )) + } + + /// Instantiate a new `File` from given `events`, associating each section and their values with + /// `meta`-data. + pub fn from_parse_events_no_includes( + parse::Events { frontmatter, sections }: parse::Events<'a>, + meta: impl Into<OwnShared<Metadata>>, + ) -> Self { + let meta = meta.into(); + let mut this = File::new(OwnShared::clone(&meta)); + + this.frontmatter_events = frontmatter; + + for section in sections { + this.push_section_internal(crate::file::Section { + header: section.header, + body: section::Body(section.events), + meta: OwnShared::clone(&meta), + id: Default::default(), + }); + } + + this + } +} + +impl File<'static> { + /// Instantiate a new fully-owned `File` from given `input` (later reused as buffer when resolving includes), + /// associating each section and their values with `meta`-data, while respecting `options`, and + /// following includes as configured there. + pub fn from_bytes_owned( + input_and_buf: &mut Vec<u8>, + meta: impl Into<OwnShared<Metadata>>, + options: Options<'_>, + ) -> Result<Self, Error> { + let mut config = Self::from_parse_events_no_includes( + parse::Events::from_bytes_owned(input_and_buf, options.to_event_filter()).map_err(Error::from)?, + meta, + ); + + includes::resolve(&mut config, input_and_buf, options).map_err(Error::from)?; + Ok(config) + } +} diff --git a/vendor/gix-config/src/file/init/types.rs b/vendor/gix-config/src/file/init/types.rs new file mode 100644 index 000000000..fcb17c0ca --- /dev/null +++ b/vendor/gix-config/src/file/init/types.rs @@ -0,0 +1,47 @@ +use crate::{file::init, parse, parse::Event, path::interpolate}; + +/// The error returned by [`File::from_bytes_no_includes()`][crate::File::from_bytes_no_includes()]. +#[derive(Debug, thiserror::Error)] +#[allow(missing_docs)] +pub enum Error { + #[error(transparent)] + Parse(#[from] parse::Error), + #[error(transparent)] + Interpolate(#[from] interpolate::Error), + #[error(transparent)] + Includes(#[from] init::includes::Error), +} + +/// Options when loading git config using [`File::from_paths_metadata()`][crate::File::from_paths_metadata()]. +#[derive(Clone, Copy, Default)] +pub struct Options<'a> { + /// Configure how to follow includes while handling paths. + pub includes: init::includes::Options<'a>, + /// If true, only value-bearing parse events will be kept to reduce memory usage and increase performance. + /// + /// Note that doing so will degenerate [`write_to()`][crate::File::write_to()] and strip it off its comments + /// and additional whitespace entirely, but will otherwise be a valid configuration file. + pub lossy: bool, +} + +impl Options<'_> { + pub(crate) fn to_event_filter(self) -> Option<fn(&Event<'_>) -> bool> { + if self.lossy { + Some(discard_nonessential_events) + } else { + None + } + } +} + +fn discard_nonessential_events(e: &Event<'_>) -> bool { + match e { + Event::Whitespace(_) | Event::Comment(_) | Event::Newline(_) => false, + Event::SectionHeader(_) + | Event::SectionKey(_) + | Event::KeyValueSeparator + | Event::Value(_) + | Event::ValueNotDone(_) + | Event::ValueDone(_) => true, + } +} diff --git a/vendor/gix-config/src/file/meta.rs b/vendor/gix-config/src/file/meta.rs new file mode 100644 index 000000000..b5229bd7a --- /dev/null +++ b/vendor/gix-config/src/file/meta.rs @@ -0,0 +1,59 @@ +use std::path::PathBuf; + +use crate::{file, file::Metadata, Source}; + +/// Instantiation +impl Metadata { + /// Return metadata indicating the source of a [`File`][crate::File] is from an API user. + pub fn api() -> Self { + file::Metadata { + path: None, + source: Source::Api, + level: 0, + trust: gix_sec::Trust::Full, + } + } + + /// Return metadata as derived from the given `path` at `source`, which will also be used to derive the trust level + /// by checking its ownership. + pub fn try_from_path(path: impl Into<PathBuf>, source: Source) -> std::io::Result<Self> { + let path = path.into(); + gix_sec::Trust::from_path_ownership(&path).map(|trust| Metadata { + path: path.into(), + source, + level: 0, + trust, + }) + } + + /// Set the trust level of this instance to the given `trust` and return it. + /// + /// Useful in conjunction with `Metadata::from(source)`. + pub fn with(mut self, trust: gix_sec::Trust) -> Self { + self.trust = trust; + self + } + + /// Set the metadata to be located at the given `path`. + pub fn at(mut self, path: impl Into<PathBuf>) -> Self { + self.path = Some(path.into()); + self + } +} + +impl Default for Metadata { + fn default() -> Self { + Metadata::api() + } +} + +impl From<Source> for Metadata { + fn from(source: Source) -> Self { + file::Metadata { + path: None, + source, + level: 0, + trust: gix_sec::Trust::Full, + } + } +} diff --git a/vendor/gix-config/src/file/mod.rs b/vendor/gix-config/src/file/mod.rs new file mode 100644 index 000000000..2dd8c88fe --- /dev/null +++ b/vendor/gix-config/src/file/mod.rs @@ -0,0 +1,136 @@ +//! A high level wrapper around a single or multiple `gix-config` file, for reading and mutation. +use std::{ + borrow::Cow, + collections::HashMap, + ops::{Add, AddAssign}, + path::PathBuf, +}; + +use bstr::BStr; +use gix_features::threading::OwnShared; + +mod mutable; +pub use mutable::{multi_value::MultiValueMut, section::SectionMut, value::ValueMut}; + +/// +pub mod init; + +mod access; +mod impls; +/// +pub mod includes; +mod meta; +mod util; + +/// +pub mod section; + +/// +pub mod rename_section { + /// The error returned by [`File::rename_section(…)`][crate::File::rename_section()]. + #[derive(Debug, thiserror::Error)] + #[allow(missing_docs)] + pub enum Error { + #[error(transparent)] + Lookup(#[from] crate::lookup::existing::Error), + #[error(transparent)] + Section(#[from] crate::parse::section::header::Error), + } +} + +/// +pub mod set_raw_value { + /// The error returned by [`File::set_raw_value(…)`][crate::File::set_raw_value()]. + #[derive(Debug, thiserror::Error)] + #[allow(missing_docs)] + pub enum Error { + #[error(transparent)] + Header(#[from] crate::parse::section::header::Error), + #[error(transparent)] + Key(#[from] crate::parse::section::key::Error), + } +} + +/// Additional information about a section. +#[derive(Clone, Debug, PartialOrd, PartialEq, Ord, Eq, Hash)] +pub struct Metadata { + /// The file path of the source, if known. + pub path: Option<PathBuf>, + /// Where the section is coming from. + pub source: crate::Source, + /// The levels of indirection of the file, with 0 being a section + /// that was directly loaded, and 1 being an `include.path` of a + /// level 0 file. + pub level: u8, + /// The trust-level for the section this meta-data is associated with. + pub trust: gix_sec::Trust, +} + +/// A section in a gix-config file, like `[core]` or `[remote "origin"]`, along with all of its keys. +#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)] +pub struct Section<'a> { + header: crate::parse::section::Header<'a>, + body: section::Body<'a>, + meta: OwnShared<Metadata>, + id: SectionId, +} + +/// A function to filter metadata, returning `true` if the corresponding but omitted value can be used. +pub type MetadataFilter = dyn FnMut(&'_ Metadata) -> bool; + +/// A strongly typed index into some range. +#[derive(PartialEq, Eq, Hash, PartialOrd, Ord, Debug, Clone, Copy)] +pub(crate) struct Index(pub(crate) usize); + +impl Add<Size> for Index { + type Output = Self; + + fn add(self, rhs: Size) -> Self::Output { + Self(self.0 + rhs.0) + } +} + +/// A strongly typed a size. +#[derive(PartialEq, Eq, Hash, PartialOrd, Ord, Debug, Clone, Copy)] +pub(crate) struct Size(pub(crate) usize); + +impl AddAssign<usize> for Size { + fn add_assign(&mut self, rhs: usize) { + self.0 += rhs; + } +} + +/// The section ID is a monotonically increasing ID used to refer to section bodies. +/// This value does not imply any ordering between sections, as new sections +/// with higher section IDs may be in between lower ID sections after `File` mutation. +/// +/// We need to use a section id because `gix-config` permits sections with +/// identical names, making it ambiguous when used in maps, for instance. +/// +/// This id guaranteed to be unique, but not guaranteed to be compact. In other +/// words, it's possible that a section may have an ID of 3 but the next section +/// has an ID of 5 as 4 was deleted. +#[derive(PartialEq, Eq, Hash, Copy, Clone, PartialOrd, Ord, Debug)] +pub struct SectionId(pub(crate) usize); + +impl Default for SectionId { + fn default() -> Self { + SectionId(usize::MAX) + } +} + +/// All section body ids referred to by a section name. +/// +/// Note that order in Vec matters as it represents the order +/// of section ids with the matched section and name, and is used for precedence +/// management. +#[derive(PartialEq, Eq, Clone, Debug)] +pub(crate) enum SectionBodyIdsLut<'a> { + /// The list of section ids to use for obtaining the section body. + Terminal(Vec<SectionId>), + /// A hashmap from sub-section names to section ids. + NonTerminal(HashMap<Cow<'a, BStr>, Vec<SectionId>>), +} +#[cfg(test)] +mod tests; +mod write; diff --git a/vendor/gix-config/src/file/mutable/mod.rs b/vendor/gix-config/src/file/mutable/mod.rs new file mode 100644 index 000000000..ad99e09b9 --- /dev/null +++ b/vendor/gix-config/src/file/mutable/mod.rs @@ -0,0 +1,107 @@ +use std::borrow::Cow; + +use bstr::{BStr, BString, ByteSlice, ByteVec}; + +use crate::{file, parse::Event}; + +pub(crate) mod multi_value; +pub(crate) mod section; +pub(crate) mod value; + +fn escape_value(value: &BStr) -> BString { + let starts_with_whitespace = value.first().map_or(false, |b| b.is_ascii_whitespace()); + let ends_with_whitespace = value + .get(value.len().saturating_sub(1)) + .map_or(false, |b| b.is_ascii_whitespace()); + let contains_comment_indicators = value.find_byteset(b";#").is_some(); + let quote = starts_with_whitespace || ends_with_whitespace || contains_comment_indicators; + + let mut buf: BString = Vec::with_capacity(value.len()).into(); + if quote { + buf.push(b'"'); + } + + for b in value.iter().copied() { + match b { + b'\n' => buf.push_str("\\n"), + b'\t' => buf.push_str("\\t"), + b'"' => buf.push_str("\\\""), + b'\\' => buf.push_str("\\\\"), + _ => buf.push(b), + } + } + + if quote { + buf.push(b'"'); + } + buf +} + +#[derive(PartialEq, Eq, Hash, PartialOrd, Ord, Debug)] +struct Whitespace<'a> { + pre_key: Option<Cow<'a, BStr>>, + pre_sep: Option<Cow<'a, BStr>>, + post_sep: Option<Cow<'a, BStr>>, +} + +impl Default for Whitespace<'_> { + fn default() -> Self { + Whitespace { + pre_key: Some(b"\t".as_bstr().into()), + pre_sep: Some(b" ".as_bstr().into()), + post_sep: Some(b" ".as_bstr().into()), + } + } +} + +impl<'a> Whitespace<'a> { + fn key_value_separators(&self) -> Vec<Event<'a>> { + let mut out = Vec::with_capacity(3); + if let Some(ws) = &self.pre_sep { + out.push(Event::Whitespace(ws.clone())); + } + out.push(Event::KeyValueSeparator); + if let Some(ws) = &self.post_sep { + out.push(Event::Whitespace(ws.clone())); + } + out + } + + fn from_body(s: &file::section::Body<'a>) -> Self { + let key_pos = + s.0.iter() + .enumerate() + .find_map(|(idx, e)| matches!(e, Event::SectionKey(_)).then(|| idx)); + key_pos + .map(|key_pos| { + let pre_key = s.0[..key_pos].iter().rev().next().and_then(|e| match e { + Event::Whitespace(s) => Some(s.clone()), + _ => None, + }); + let from_key = &s.0[key_pos..]; + let (pre_sep, post_sep) = from_key + .iter() + .enumerate() + .find_map(|(idx, e)| matches!(e, Event::KeyValueSeparator).then(|| idx)) + .map(|sep_pos| { + ( + from_key.get(sep_pos - 1).and_then(|e| match e { + Event::Whitespace(ws) => Some(ws.clone()), + _ => None, + }), + from_key.get(sep_pos + 1).and_then(|e| match e { + Event::Whitespace(ws) => Some(ws.clone()), + _ => None, + }), + ) + }) + .unwrap_or_default(); + Whitespace { + pre_key, + pre_sep, + post_sep, + } + }) + .unwrap_or_default() + } +} diff --git a/vendor/gix-config/src/file/mutable/multi_value.rs b/vendor/gix-config/src/file/mutable/multi_value.rs new file mode 100644 index 000000000..396b49b6a --- /dev/null +++ b/vendor/gix-config/src/file/mutable/multi_value.rs @@ -0,0 +1,266 @@ +use std::{borrow::Cow, collections::HashMap, ops::DerefMut}; + +use bstr::{BStr, BString, ByteVec}; + +use crate::{ + file::{ + self, + mutable::{escape_value, Whitespace}, + Section, SectionId, + }, + lookup, + parse::{section, Event}, + value::{normalize_bstr, normalize_bstring}, +}; + +/// Internal data structure for [`MutableMultiValue`] +#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)] +pub(crate) struct EntryData { + pub(crate) section_id: SectionId, + pub(crate) offset_index: usize, +} + +/// An intermediate representation of a mutable multivar obtained from a [`File`][crate::File]. +#[derive(PartialEq, Eq, Debug)] +pub struct MultiValueMut<'borrow, 'lookup, 'event> { + pub(crate) section: &'borrow mut HashMap<SectionId, Section<'event>>, + pub(crate) key: section::Key<'lookup>, + /// Each entry data struct provides sufficient information to index into + /// [`Self::offsets`]. This layer of indirection is used for users to index + /// into the offsets rather than leaking the internal data structures. + pub(crate) indices_and_sizes: Vec<EntryData>, + /// Each offset represents the size of a event slice and whether or not the + /// event slice is significant or not. This is used to index into the + /// actual section. + pub(crate) offsets: HashMap<SectionId, Vec<usize>>, +} + +impl<'borrow, 'lookup, 'event> MultiValueMut<'borrow, 'lookup, 'event> { + /// Returns the actual values. + pub fn get(&self) -> Result<Vec<Cow<'_, BStr>>, lookup::existing::Error> { + let mut expect_value = false; + let mut values = Vec::new(); + let mut concatenated_value = BString::default(); + + for EntryData { + section_id, + offset_index, + } in &self.indices_and_sizes + { + let (offset, size) = MultiValueMut::index_and_size(&self.offsets, *section_id, *offset_index); + for event in &self.section.get(section_id).expect("known section id").as_ref()[offset..offset + size] { + match event { + Event::SectionKey(section_key) if *section_key == self.key => expect_value = true, + Event::Value(v) if expect_value => { + expect_value = false; + values.push(normalize_bstr(v.as_ref())); + } + Event::ValueNotDone(v) if expect_value => concatenated_value.push_str(v.as_ref()), + Event::ValueDone(v) if expect_value => { + expect_value = false; + concatenated_value.push_str(v.as_ref()); + values.push(normalize_bstring(std::mem::take(&mut concatenated_value))); + } + _ => (), + } + } + } + + if values.is_empty() { + return Err(lookup::existing::Error::KeyMissing); + } + + Ok(values) + } + + /// Returns the amount of values within this multivar. + #[must_use] + pub fn len(&self) -> usize { + self.indices_and_sizes.len() + } + + /// Returns true if the multivar does not have any values. + /// This might occur if the value was deleted but wasn't yet set with a new value. + #[must_use] + pub fn is_empty(&self) -> bool { + self.indices_and_sizes.is_empty() + } + + /// Sets the value at the given index. + /// + /// # Safety + /// + /// This will panic if the index is out of range. + pub fn set_string_at(&mut self, index: usize, value: impl AsRef<str>) { + self.set_at(index, value.as_ref()); + } + + /// Sets the value at the given index. + /// + /// # Safety + /// + /// This will panic if the index is out of range. + pub fn set_at<'a>(&mut self, index: usize, value: impl Into<&'a BStr>) { + let EntryData { + section_id, + offset_index, + } = self.indices_and_sizes[index]; + MultiValueMut::set_value_inner( + &self.key, + &mut self.offsets, + &mut self.section.get_mut(§ion_id).expect("known section id").body, + section_id, + offset_index, + value.into(), + ); + } + + /// Sets all values to the provided ones. Note that this follows [`zip`] + /// logic: if the number of values in the input is less than the number of + /// values currently existing, then only the first `n` values are modified. + /// If more values are provided than there currently are, then the + /// remaining values are ignored. + /// + /// [`zip`]: std::iter::Iterator::zip + pub fn set_values<'a, Iter, Item>(&mut self, values: Iter) + where + Iter: IntoIterator<Item = Item>, + Item: Into<&'a BStr>, + { + for ( + EntryData { + section_id, + offset_index, + }, + value, + ) in self.indices_and_sizes.iter().zip(values) + { + Self::set_value_inner( + &self.key, + &mut self.offsets, + &mut self.section.get_mut(section_id).expect("known section id").body, + *section_id, + *offset_index, + value.into(), + ); + } + } + + /// Sets all values in this multivar to the provided one without owning the + /// provided input. + pub fn set_all<'a>(&mut self, input: impl Into<&'a BStr>) { + let input = input.into(); + for EntryData { + section_id, + offset_index, + } in &self.indices_and_sizes + { + Self::set_value_inner( + &self.key, + &mut self.offsets, + &mut self.section.get_mut(section_id).expect("known section id").body, + *section_id, + *offset_index, + input, + ); + } + } + + fn set_value_inner<'a: 'event>( + key: §ion::Key<'lookup>, + offsets: &mut HashMap<SectionId, Vec<usize>>, + section: &mut file::section::Body<'event>, + section_id: SectionId, + offset_index: usize, + value: &BStr, + ) { + let (offset, size) = MultiValueMut::index_and_size(offsets, section_id, offset_index); + let whitespace = Whitespace::from_body(section); + let section = section.as_mut(); + section.drain(offset..offset + size); + + let key_sep_events = whitespace.key_value_separators(); + MultiValueMut::set_offset(offsets, section_id, offset_index, 2 + key_sep_events.len()); + section.insert(offset, Event::Value(escape_value(value).into())); + section.insert_many(offset, key_sep_events.into_iter().rev()); + section.insert(offset, Event::SectionKey(key.to_owned())); + } + + /// Removes the value at the given index. Does nothing when called multiple + /// times in succession. + /// + /// # Safety + /// + /// This will panic if the index is out of range. + pub fn delete(&mut self, index: usize) { + let EntryData { + section_id, + offset_index, + } = &self.indices_and_sizes[index]; + let (offset, size) = MultiValueMut::index_and_size(&self.offsets, *section_id, *offset_index); + if size == 0 { + return; + } + self.section + .get_mut(section_id) + .expect("known section id") + .body + .as_mut() + .drain(offset..offset + size); + + Self::set_offset(&mut self.offsets, *section_id, *offset_index, 0); + self.indices_and_sizes.remove(index); + } + + /// Removes all values. Does nothing when called multiple times in + /// succession. + pub fn delete_all(&mut self) { + for EntryData { + section_id, + offset_index, + } in &self.indices_and_sizes + { + let (offset, size) = MultiValueMut::index_and_size(&self.offsets, *section_id, *offset_index); + if size == 0 { + continue; + } + self.section + .get_mut(section_id) + .expect("known section id") + .body + .as_mut() + .drain(offset..offset + size); + Self::set_offset(&mut self.offsets, *section_id, *offset_index, 0); + } + self.indices_and_sizes.clear(); + } + + fn index_and_size( + offsets: &'lookup HashMap<SectionId, Vec<usize>>, + section_id: SectionId, + offset_index: usize, + ) -> (usize, usize) { + offsets + .get(§ion_id) + .expect("known section id") + .iter() + .take(offset_index + 1) + .fold((0, 0), |(total_ofs, ofs), size| (total_ofs + ofs, *size)) + } + + // This must be an associated function rather than a method to allow Rust + // to split mutable borrows. + fn set_offset( + offsets: &mut HashMap<SectionId, Vec<usize>>, + section_id: SectionId, + offset_index: usize, + value: usize, + ) { + *offsets + .get_mut(§ion_id) + .expect("known section id") + .get_mut(offset_index) + .unwrap() + .deref_mut() = value; + } +} diff --git a/vendor/gix-config/src/file/mutable/section.rs b/vendor/gix-config/src/file/mutable/section.rs new file mode 100644 index 000000000..def68ac60 --- /dev/null +++ b/vendor/gix-config/src/file/mutable/section.rs @@ -0,0 +1,316 @@ +use std::{ + borrow::Cow, + ops::{Deref, Range}, +}; + +use bstr::{BStr, BString, ByteSlice, ByteVec}; +use smallvec::SmallVec; + +use crate::{ + file::{ + self, + mutable::{escape_value, Whitespace}, + Index, Section, Size, + }, + lookup, parse, + parse::{section::Key, Event}, + value::{normalize, normalize_bstr, normalize_bstring}, +}; + +/// A opaque type that represents a mutable reference to a section. +#[derive(PartialEq, Eq, Hash, PartialOrd, Ord, Debug)] +pub struct SectionMut<'a, 'event> { + section: &'a mut Section<'event>, + implicit_newline: bool, + whitespace: Whitespace<'event>, + newline: SmallVec<[u8; 2]>, +} + +/// Mutating methods. +impl<'a, 'event> SectionMut<'a, 'event> { + /// Adds an entry to the end of this section name `key` and `value`. If `value` is `None`, no equal sign will be written leaving + /// just the key. This is useful for boolean values which are true if merely the key exists. + pub fn push<'b>(&mut self, key: Key<'event>, value: Option<&'b BStr>) -> &mut Self { + self.push_with_comment_inner(key, value, None); + self + } + + /// Adds an entry to the end of this section name `key` and `value`. If `value` is `None`, no equal sign will be written leaving + /// just the key. This is useful for boolean values which are true if merely the key exists. + /// `comment` has to be the text to put right after the value and behind a `#` character. Note that newlines are silently transformed + /// into spaces. + pub fn push_with_comment<'b, 'c>( + &mut self, + key: Key<'event>, + value: Option<&'b BStr>, + comment: impl Into<&'c BStr>, + ) -> &mut Self { + self.push_with_comment_inner(key, value, comment.into().into()); + self + } + + fn push_with_comment_inner(&mut self, key: Key<'event>, value: Option<&BStr>, comment: Option<&BStr>) { + let body = &mut self.section.body.0; + if let Some(ws) = &self.whitespace.pre_key { + body.push(Event::Whitespace(ws.clone())); + } + + body.push(Event::SectionKey(key)); + match value { + Some(value) => { + body.extend(self.whitespace.key_value_separators()); + body.push(Event::Value(escape_value(value).into())); + } + None => body.push(Event::Value(Cow::Borrowed("".into()))), + } + if let Some(comment) = comment { + body.push(Event::Whitespace(Cow::Borrowed(" ".into()))); + body.push(Event::Comment(parse::Comment { + tag: b'#', + text: Cow::Owned({ + let mut c = Vec::with_capacity(comment.len()); + let mut bytes = comment.iter().peekable(); + if !bytes.peek().map_or(true, |b| b.is_ascii_whitespace()) { + c.insert(0, b' '); + } + c.extend(bytes.map(|b| if *b == b'\n' { b' ' } else { *b })); + c.into() + }), + })); + } + if self.implicit_newline { + body.push(Event::Newline(BString::from(self.newline.to_vec()).into())); + } + } + + /// Removes all events until a key value pair is removed. This will also + /// remove the whitespace preceding the key value pair, if any is found. + pub fn pop(&mut self) -> Option<(Key<'_>, Cow<'event, BStr>)> { + let mut values = Vec::new(); + // events are popped in reverse order + let body = &mut self.section.body.0; + while let Some(e) = body.pop() { + match e { + Event::SectionKey(k) => { + // pop leading whitespace + if let Some(Event::Whitespace(_)) = body.last() { + body.pop(); + } + + if values.len() == 1 { + let value = values.pop().expect("vec is non-empty but popped to empty value"); + return Some((k, normalize(value))); + } + + return Some(( + k, + normalize_bstring({ + let mut s = BString::default(); + for value in values.into_iter().rev() { + s.push_str(value.as_ref()); + } + s + }), + )); + } + Event::Value(v) | Event::ValueNotDone(v) | Event::ValueDone(v) => values.push(v), + _ => (), + } + } + None + } + + /// Sets the last key value pair if it exists, or adds the new value. + /// Returns the previous value if it replaced a value, or None if it adds + /// the value. + pub fn set<'b>(&mut self, key: Key<'event>, value: impl Into<&'b BStr>) -> Option<Cow<'event, BStr>> { + match self.key_and_value_range_by(&key) { + None => { + self.push(key, Some(value.into())); + None + } + Some((key_range, value_range)) => { + let value_range = value_range.unwrap_or(key_range.end - 1..key_range.end); + let range_start = value_range.start; + let ret = self.remove_internal(value_range, false); + self.section + .body + .0 + .insert(range_start, Event::Value(escape_value(value.into()).into())); + Some(ret) + } + } + } + + /// Removes the latest value by key and returns it, if it exists. + pub fn remove(&mut self, key: impl AsRef<str>) -> Option<Cow<'event, BStr>> { + let key = Key::from_str_unchecked(key.as_ref()); + let (key_range, _value_range) = self.key_and_value_range_by(&key)?; + Some(self.remove_internal(key_range, true)) + } + + /// Adds a new line event. Note that you don't need to call this unless + /// you've disabled implicit newlines. + pub fn push_newline(&mut self) -> &mut Self { + self.section + .body + .0 + .push(Event::Newline(Cow::Owned(BString::from(self.newline.to_vec())))); + self + } + + /// Return the newline used when calling [`push_newline()`][Self::push_newline()]. + pub fn newline(&self) -> &BStr { + self.newline.as_slice().as_bstr() + } + + /// Enables or disables automatically adding newline events after adding + /// a value. This is _enabled by default_. + pub fn set_implicit_newline(&mut self, on: bool) -> &mut Self { + self.implicit_newline = on; + self + } + + /// Sets the exact whitespace to use before each newly created key-value pair, + /// with only whitespace characters being permissible. + /// + /// The default is 2 tabs. + /// Set to `None` to disable adding whitespace before a key value. + /// + /// # Panics + /// + /// If non-whitespace characters are used. This makes the method only suitable for validated + /// or known input. + pub fn set_leading_whitespace(&mut self, whitespace: Option<Cow<'event, BStr>>) -> &mut Self { + assert!( + whitespace + .as_deref() + .map_or(true, |ws| ws.iter().all(|b| b.is_ascii_whitespace())), + "input whitespace must only contain whitespace characters." + ); + self.whitespace.pre_key = whitespace; + self + } + + /// Returns the whitespace this section will insert before the + /// beginning of a key, if any. + #[must_use] + pub fn leading_whitespace(&self) -> Option<&BStr> { + self.whitespace.pre_key.as_deref() + } + + /// Returns the whitespace to be used before and after the `=` between the key + /// and the value. + /// + /// For example, `k = v` will have `(Some(" "), Some(" "))`, whereas `k=\tv` will + /// have `(None, Some("\t"))`. + #[must_use] + pub fn separator_whitespace(&self) -> (Option<&BStr>, Option<&BStr>) { + (self.whitespace.pre_sep.as_deref(), self.whitespace.post_sep.as_deref()) + } +} + +// Internal methods that may require exact indices for faster operations. +impl<'a, 'event> SectionMut<'a, 'event> { + pub(crate) fn new(section: &'a mut Section<'event>, newline: SmallVec<[u8; 2]>) -> Self { + let whitespace = Whitespace::from_body(§ion.body); + Self { + section, + implicit_newline: true, + whitespace, + newline, + } + } + + pub(crate) fn get( + &self, + key: &Key<'_>, + start: Index, + end: Index, + ) -> Result<Cow<'_, BStr>, lookup::existing::Error> { + let mut expect_value = false; + let mut concatenated_value = BString::default(); + + for event in &self.section.0[start.0..end.0] { + match event { + Event::SectionKey(event_key) if event_key == key => expect_value = true, + Event::Value(v) if expect_value => return Ok(normalize_bstr(v.as_ref())), + Event::ValueNotDone(v) if expect_value => { + concatenated_value.push_str(v.as_ref()); + } + Event::ValueDone(v) if expect_value => { + concatenated_value.push_str(v.as_ref()); + return Ok(normalize_bstring(concatenated_value)); + } + _ => (), + } + } + + Err(lookup::existing::Error::KeyMissing) + } + + pub(crate) fn delete(&mut self, start: Index, end: Index) { + self.section.body.0.drain(start.0..end.0); + } + + pub(crate) fn set_internal(&mut self, index: Index, key: Key<'event>, value: &BStr) -> Size { + let mut size = 0; + + let body = &mut self.section.body.0; + body.insert(index.0, Event::Value(escape_value(value).into())); + size += 1; + + let sep_events = self.whitespace.key_value_separators(); + size += sep_events.len(); + body.insert_many(index.0, sep_events.into_iter().rev()); + + body.insert(index.0, Event::SectionKey(key)); + size += 1; + + Size(size) + } + + /// Performs the removal, assuming the range is valid. + fn remove_internal(&mut self, range: Range<usize>, fix_whitespace: bool) -> Cow<'event, BStr> { + let events = &mut self.section.body.0; + if fix_whitespace + && events + .get(range.end) + .map_or(false, |ev| matches!(ev, Event::Newline(_))) + { + events.remove(range.end); + } + let value = events + .drain(range.clone()) + .fold(Cow::Owned(BString::default()), |mut acc: Cow<'_, BStr>, e| { + if let Event::Value(v) | Event::ValueNotDone(v) | Event::ValueDone(v) = e { + acc.to_mut().extend(&**v); + } + acc + }); + if fix_whitespace + && range + .start + .checked_sub(1) + .and_then(|pos| events.get(pos)) + .map_or(false, |ev| matches!(ev, Event::Whitespace(_))) + { + events.remove(range.start - 1); + } + value + } +} + +impl<'event> Deref for SectionMut<'_, 'event> { + type Target = file::Section<'event>; + + fn deref(&self) -> &Self::Target { + self.section + } +} + +impl<'event> file::section::Body<'event> { + pub(crate) fn as_mut(&mut self) -> &mut parse::section::Events<'event> { + &mut self.0 + } +} diff --git a/vendor/gix-config/src/file/mutable/value.rs b/vendor/gix-config/src/file/mutable/value.rs new file mode 100644 index 000000000..2bccfd32a --- /dev/null +++ b/vendor/gix-config/src/file/mutable/value.rs @@ -0,0 +1,63 @@ +use std::borrow::Cow; + +use bstr::BStr; + +use crate::{ + file, + file::{mutable::section::SectionMut, Index, Size}, + lookup, + parse::section, +}; + +/// An intermediate representation of a mutable value obtained from a [`File`][crate::File]. +#[derive(PartialEq, Eq, Hash, PartialOrd, Ord, Debug)] +pub struct ValueMut<'borrow, 'lookup, 'event> { + pub(crate) section: SectionMut<'borrow, 'event>, + pub(crate) key: section::Key<'lookup>, + pub(crate) index: Index, + pub(crate) size: Size, +} + +impl<'borrow, 'lookup, 'event> ValueMut<'borrow, 'lookup, 'event> { + /// Returns the actual value. This is computed each time this is called + /// requiring an allocation for multi-line values. + pub fn get(&self) -> Result<Cow<'_, BStr>, lookup::existing::Error> { + self.section.get(&self.key, self.index, self.index + self.size) + } + + /// Update the value to the provided one. This modifies the value such that + /// the Value event(s) are replaced with a single new event containing the + /// new value. + pub fn set_string(&mut self, input: impl AsRef<str>) { + self.set(input.as_ref()); + } + + /// Update the value to the provided one. This modifies the value such that + /// the Value event(s) are replaced with a single new event containing the + /// new value. + pub fn set<'a>(&mut self, input: impl Into<&'a BStr>) { + if self.size.0 > 0 { + self.section.delete(self.index, self.index + self.size); + } + self.size = self.section.set_internal(self.index, self.key.to_owned(), input.into()); + } + + /// Removes the value. Does nothing when called multiple times in + /// succession. + pub fn delete(&mut self) { + if self.size.0 > 0 { + self.section.delete(self.index, self.index + self.size); + self.size = Size(0); + } + } + + /// Return the section containing the value. + pub fn section(&self) -> &file::Section<'event> { + &self.section + } + + /// Convert this value into its owning mutable section. + pub fn into_section_mut(self) -> file::SectionMut<'borrow, 'event> { + self.section + } +} diff --git a/vendor/gix-config/src/file/section/body.rs b/vendor/gix-config/src/file/section/body.rs new file mode 100644 index 000000000..e1a53efd9 --- /dev/null +++ b/vendor/gix-config/src/file/section/body.rs @@ -0,0 +1,206 @@ +use std::{borrow::Cow, iter::FusedIterator, ops::Range}; + +use bstr::{BStr, BString, ByteVec}; + +use crate::{ + parse::{section::Key, Event}, + value::{normalize, normalize_bstr, normalize_bstring}, +}; + +/// A opaque type that represents a section body. +#[derive(PartialEq, Eq, Hash, PartialOrd, Ord, Clone, Debug, Default)] +pub struct Body<'event>(pub(crate) crate::parse::section::Events<'event>); + +/// Access +impl<'event> Body<'event> { + /// Retrieves the last matching value in a section with the given key, if present. + /// + /// Note that we consider values without key separator `=` non-existing. + #[must_use] + pub fn value(&self, key: impl AsRef<str>) -> Option<Cow<'_, BStr>> { + self.value_implicit(key).flatten() + } + + /// Retrieves the last matching value in a section with the given key, if present, and indicates an implicit value with `Some(None)`, + /// and a non-existing one as `None` + #[must_use] + pub fn value_implicit(&self, key: impl AsRef<str>) -> Option<Option<Cow<'_, BStr>>> { + let key = Key::from_str_unchecked(key.as_ref()); + let (_key_range, range) = self.key_and_value_range_by(&key)?; + let range = match range { + None => return Some(None), + Some(range) => range, + }; + let mut concatenated = BString::default(); + + for event in &self.0[range] { + match event { + Event::Value(v) => { + return Some(Some(normalize_bstr(v.as_ref()))); + } + Event::ValueNotDone(v) => { + concatenated.push_str(v.as_ref()); + } + Event::ValueDone(v) => { + concatenated.push_str(v.as_ref()); + return Some(Some(normalize_bstring(concatenated))); + } + _ => (), + } + } + None + } + + /// Retrieves all values that have the provided key name. This may return + /// an empty vec, which implies there were no values with the provided key. + #[must_use] + pub fn values(&self, key: impl AsRef<str>) -> Vec<Cow<'_, BStr>> { + let key = &Key::from_str_unchecked(key.as_ref()); + let mut values = Vec::new(); + let mut expect_value = false; + let mut concatenated_value = BString::default(); + + for event in &self.0 { + match event { + Event::SectionKey(event_key) if event_key == key => expect_value = true, + Event::Value(v) if expect_value => { + expect_value = false; + values.push(normalize_bstr(v.as_ref())); + } + Event::ValueNotDone(v) if expect_value => { + concatenated_value.push_str(v.as_ref()); + } + Event::ValueDone(v) if expect_value => { + expect_value = false; + concatenated_value.push_str(v.as_ref()); + values.push(normalize_bstring(std::mem::take(&mut concatenated_value))); + } + _ => (), + } + } + + values + } + + /// Returns an iterator visiting all keys in order. + pub fn keys(&self) -> impl Iterator<Item = &Key<'event>> { + self.0.iter().filter_map(|e| match e { + Event::SectionKey(k) => Some(k), + _ => None, + }) + } + + /// Returns true if the section contains the provided key. + #[must_use] + pub fn contains_key(&self, key: impl AsRef<str>) -> bool { + let key = &Key::from_str_unchecked(key.as_ref()); + self.0.iter().any(|e| { + matches!(e, + Event::SectionKey(k) if k == key + ) + }) + } + + /// Returns the number of values in the section. + #[must_use] + pub fn num_values(&self) -> usize { + self.0.iter().filter(|e| matches!(e, Event::SectionKey(_))).count() + } + + /// Returns if the section is empty. + /// Note that this may count whitespace, see [`num_values()`][Self::num_values()] for + /// another way to determine semantic emptiness. + #[must_use] + pub fn is_void(&self) -> bool { + self.0.is_empty() + } +} + +impl<'event> Body<'event> { + pub(crate) fn as_ref(&self) -> &[Event<'_>] { + &self.0 + } + + /// Returns the the range containing the value events for the `key`, with value range being `None` if there is no key-value separator + /// and only a 'fake' Value event with an empty string in side. + /// If the value is not found, `None` is returned. + pub(crate) fn key_and_value_range_by(&self, key: &Key<'_>) -> Option<(Range<usize>, Option<Range<usize>>)> { + let mut value_range = Range::default(); + let mut key_start = None; + for (i, e) in self.0.iter().enumerate().rev() { + match e { + Event::SectionKey(k) => { + if k == key { + key_start = Some(i); + break; + } + value_range = Range::default(); + } + Event::Value(_) => { + (value_range.start, value_range.end) = (i, i); + } + Event::ValueNotDone(_) | Event::ValueDone(_) => { + if value_range.end == 0 { + value_range.end = i + } else { + value_range.start = i + }; + } + _ => (), + } + } + key_start.map(|key_start| { + // value end needs to be offset by one so that the last value's index + // is included in the range + let value_range = value_range.start..value_range.end + 1; + let key_range = key_start..value_range.end; + (key_range, (value_range.start != key_start + 1).then_some(value_range)) + }) + } +} + +/// An owning iterator of a section body. Created by [`Body::into_iter`], yielding +/// un-normalized (`key`, `value`) pairs. +// TODO: tests +pub struct BodyIter<'event>(smallvec::IntoIter<[Event<'event>; 64]>); + +impl<'event> IntoIterator for Body<'event> { + type Item = (Key<'event>, Cow<'event, BStr>); + + type IntoIter = BodyIter<'event>; + + fn into_iter(self) -> Self::IntoIter { + BodyIter(self.0.into_iter()) + } +} + +impl<'event> Iterator for BodyIter<'event> { + type Item = (Key<'event>, Cow<'event, BStr>); + + fn next(&mut self) -> Option<Self::Item> { + let mut key = None; + let mut partial_value = BString::default(); + let mut value = None; + + for event in self.0.by_ref() { + match event { + Event::SectionKey(k) => key = Some(k), + Event::Value(v) => { + value = Some(v); + break; + } + Event::ValueNotDone(v) => partial_value.push_str(v.as_ref()), + Event::ValueDone(v) => { + partial_value.push_str(v.as_ref()); + value = Some(partial_value.into()); + break; + } + _ => (), + } + } + + key.zip(value.map(normalize)) + } +} + +impl FusedIterator for BodyIter<'_> {} diff --git a/vendor/gix-config/src/file/section/mod.rs b/vendor/gix-config/src/file/section/mod.rs new file mode 100644 index 000000000..e8e331084 --- /dev/null +++ b/vendor/gix-config/src/file/section/mod.rs @@ -0,0 +1,145 @@ +use std::{borrow::Cow, ops::Deref}; + +use bstr::{BStr, BString, ByteSlice}; +use smallvec::SmallVec; + +use crate::{ + file, + file::{Metadata, Section, SectionMut}, + parse, + parse::{section, Event}, +}; + +pub(crate) mod body; +pub use body::{Body, BodyIter}; +use gix_features::threading::OwnShared; + +use crate::file::{ + write::{extract_newline, platform_newline}, + SectionId, +}; + +impl<'a> Deref for Section<'a> { + type Target = Body<'a>; + + fn deref(&self) -> &Self::Target { + &self.body + } +} + +/// Instantiation and conversion +impl<'a> Section<'a> { + /// Create a new section with the given `name` and optional, `subsection`, `meta`-data and an empty body. + pub fn new( + name: impl Into<Cow<'a, str>>, + subsection: impl Into<Option<Cow<'a, BStr>>>, + meta: impl Into<OwnShared<file::Metadata>>, + ) -> Result<Self, parse::section::header::Error> { + Ok(Section { + header: parse::section::Header::new(name, subsection)?, + body: Default::default(), + meta: meta.into(), + id: SectionId::default(), + }) + } +} + +/// Access +impl<'a> Section<'a> { + /// Return our header. + pub fn header(&self) -> §ion::Header<'a> { + &self.header + } + + /// Return the unique `id` of the section, for use with the `*_by_id()` family of methods + /// in [gix_config::File][crate::File]. + pub fn id(&self) -> SectionId { + self.id + } + + /// Return our body, containing all keys and values. + pub fn body(&self) -> &Body<'a> { + &self.body + } + + /// Serialize this type into a `BString` for convenience. + /// + /// Note that `to_string()` can also be used, but might not be lossless. + #[must_use] + pub fn to_bstring(&self) -> BString { + let mut buf = Vec::new(); + self.write_to(&mut buf).expect("io error impossible"); + buf.into() + } + + /// Stream ourselves to the given `out`, in order to reproduce this section mostly losslessly + /// as it was parsed. + pub fn write_to(&self, mut out: impl std::io::Write) -> std::io::Result<()> { + self.header.write_to(&mut out)?; + + if self.body.0.is_empty() { + return Ok(()); + } + + let nl = self + .body + .as_ref() + .iter() + .find_map(extract_newline) + .unwrap_or_else(|| platform_newline()); + + if !self + .body + .as_ref() + .iter() + .take_while(|e| !matches!(e, Event::SectionKey(_))) + .any(|e| e.to_bstr_lossy().contains_str(nl)) + { + out.write_all(nl)?; + } + + let mut saw_newline_after_value = true; + let mut in_key_value_pair = false; + for (idx, event) in self.body.as_ref().iter().enumerate() { + match event { + Event::SectionKey(_) => { + if !saw_newline_after_value { + out.write_all(nl)?; + } + saw_newline_after_value = false; + in_key_value_pair = true; + } + Event::Newline(_) if !in_key_value_pair => { + saw_newline_after_value = true; + } + Event::Value(_) | Event::ValueDone(_) => { + in_key_value_pair = false; + } + _ => {} + } + event.write_to(&mut out)?; + if let Event::ValueNotDone(_) = event { + if self + .body + .0 + .get(idx + 1) + .filter(|e| matches!(e, Event::Newline(_))) + .is_none() + { + out.write_all(nl)?; + } + } + } + Ok(()) + } + + /// Return additional information about this sections origin. + pub fn meta(&self) -> &Metadata { + &self.meta + } + + /// Returns a mutable version of this section for adjustment of values. + pub fn to_mut(&mut self, newline: SmallVec<[u8; 2]>) -> SectionMut<'_, 'a> { + SectionMut::new(self, newline) + } +} diff --git a/vendor/gix-config/src/file/tests.rs b/vendor/gix-config/src/file/tests.rs new file mode 100644 index 000000000..c218dbaac --- /dev/null +++ b/vendor/gix-config/src/file/tests.rs @@ -0,0 +1,228 @@ +use std::collections::HashMap; + +use crate::{ + file::{self, Section, SectionId}, + parse::section, +}; + +mod try_from { + use std::{borrow::Cow, collections::HashMap, convert::TryFrom}; + + use super::{bodies, headers}; + use crate::{ + file::{self, SectionBodyIdsLut, SectionId}, + parse::{ + section, + tests::util::{name_event, newline_event, section_header, value_event}, + Event, + }, + File, + }; + + #[test] + fn empty() { + let config = File::try_from("").unwrap(); + assert_eq!(config.section_id_counter, 0); + assert!(config.section_lookup_tree.is_empty()); + assert!(config.sections.is_empty()); + assert!(config.section_order.is_empty()); + } + + #[test] + fn single_section() { + let mut config = File::try_from("[core]\na=b\nc=d").unwrap(); + let expected_separators = { + let mut map = HashMap::new(); + map.insert(SectionId(0), section_header("core", None)); + map + }; + assert_eq!(headers(&config.sections), expected_separators); + assert_eq!(config.section_id_counter, 1); + let expected_lookup_tree = { + let mut tree = HashMap::new(); + tree.insert( + section::Name(Cow::Borrowed("core".into())), + vec![SectionBodyIdsLut::Terminal(vec![SectionId(0)])], + ); + tree + }; + assert_eq!(config.section_lookup_tree, expected_lookup_tree); + let expected_sections = { + let mut sections = HashMap::new(); + sections.insert( + SectionId(0), + file::section::Body( + vec![ + newline_event(), + name_event("a"), + Event::KeyValueSeparator, + value_event("b"), + newline_event(), + name_event("c"), + Event::KeyValueSeparator, + value_event("d"), + ] + .into(), + ), + ); + sections + }; + assert_eq!(bodies(&config.sections), expected_sections); + assert_eq!(config.section_order.make_contiguous(), &[SectionId(0)]); + } + + #[test] + fn single_subsection() { + let mut config = File::try_from("[core.sub]\na=b\nc=d").unwrap(); + let expected_separators = { + let mut map = HashMap::new(); + map.insert(SectionId(0), section_header("core", (".", "sub"))); + map + }; + assert_eq!(headers(&config.sections), expected_separators); + assert_eq!(config.section_id_counter, 1); + let expected_lookup_tree = { + let mut tree = HashMap::new(); + let mut inner_tree = HashMap::new(); + inner_tree.insert(Cow::Borrowed("sub".into()), vec![SectionId(0)]); + tree.insert( + section::Name(Cow::Borrowed("core".into())), + vec![SectionBodyIdsLut::NonTerminal(inner_tree)], + ); + tree + }; + assert_eq!(config.section_lookup_tree, expected_lookup_tree); + let expected_sections = { + let mut sections = HashMap::new(); + sections.insert( + SectionId(0), + file::section::Body( + vec![ + newline_event(), + name_event("a"), + Event::KeyValueSeparator, + value_event("b"), + newline_event(), + name_event("c"), + Event::KeyValueSeparator, + value_event("d"), + ] + .into(), + ), + ); + sections + }; + assert_eq!(bodies(&config.sections), expected_sections); + assert_eq!(config.section_order.make_contiguous(), &[SectionId(0)]); + } + + #[test] + fn multiple_sections() { + let mut config = File::try_from("[core]\na=b\nc=d\n[other]e=f").unwrap(); + let expected_separators = { + let mut map = HashMap::new(); + map.insert(SectionId(0), section_header("core", None)); + map.insert(SectionId(1), section_header("other", None)); + map + }; + assert_eq!(headers(&config.sections), expected_separators); + assert_eq!(config.section_id_counter, 2); + let expected_lookup_tree = { + let mut tree = HashMap::new(); + tree.insert( + section::Name(Cow::Borrowed("core".into())), + vec![SectionBodyIdsLut::Terminal(vec![SectionId(0)])], + ); + tree.insert( + section::Name(Cow::Borrowed("other".into())), + vec![SectionBodyIdsLut::Terminal(vec![SectionId(1)])], + ); + tree + }; + assert_eq!(config.section_lookup_tree, expected_lookup_tree); + let expected_sections = { + let mut sections = HashMap::new(); + sections.insert( + SectionId(0), + file::section::Body( + vec![ + newline_event(), + name_event("a"), + Event::KeyValueSeparator, + value_event("b"), + newline_event(), + name_event("c"), + Event::KeyValueSeparator, + value_event("d"), + newline_event(), + ] + .into(), + ), + ); + sections.insert( + SectionId(1), + file::section::Body(vec![name_event("e"), Event::KeyValueSeparator, value_event("f")].into()), + ); + sections + }; + assert_eq!(bodies(&config.sections), expected_sections); + assert_eq!(config.section_order.make_contiguous(), &[SectionId(0), SectionId(1)]); + } + + #[test] + fn multiple_duplicate_sections() { + let mut config = File::try_from("[core]\na=b\nc=d\n[core]e=f").unwrap(); + let expected_separators = { + let mut map = HashMap::new(); + map.insert(SectionId(0), section_header("core", None)); + map.insert(SectionId(1), section_header("core", None)); + map + }; + assert_eq!(headers(&config.sections), expected_separators); + assert_eq!(config.section_id_counter, 2); + let expected_lookup_tree = { + let mut tree = HashMap::new(); + tree.insert( + section::Name(Cow::Borrowed("core".into())), + vec![SectionBodyIdsLut::Terminal(vec![SectionId(0), SectionId(1)])], + ); + tree + }; + assert_eq!(config.section_lookup_tree, expected_lookup_tree); + let expected_sections = { + let mut sections = HashMap::new(); + sections.insert( + SectionId(0), + file::section::Body( + vec![ + newline_event(), + name_event("a"), + Event::KeyValueSeparator, + value_event("b"), + newline_event(), + name_event("c"), + Event::KeyValueSeparator, + value_event("d"), + newline_event(), + ] + .into(), + ), + ); + sections.insert( + SectionId(1), + file::section::Body(vec![name_event("e"), Event::KeyValueSeparator, value_event("f")].into()), + ); + sections + }; + assert_eq!(bodies(&config.sections), expected_sections); + assert_eq!(config.section_order.make_contiguous(), &[SectionId(0), SectionId(1)]); + } +} + +fn headers<'a>(sections: &HashMap<SectionId, Section<'a>>) -> HashMap<SectionId, section::Header<'a>> { + sections.iter().map(|(k, v)| (*k, v.header.clone())).collect() +} + +fn bodies<'a>(sections: &HashMap<SectionId, Section<'a>>) -> HashMap<SectionId, file::section::Body<'a>> { + sections.iter().map(|(k, v)| (*k, v.body.clone())).collect() +} diff --git a/vendor/gix-config/src/file/util.rs b/vendor/gix-config/src/file/util.rs new file mode 100644 index 000000000..5c60f1fd5 --- /dev/null +++ b/vendor/gix-config/src/file/util.rs @@ -0,0 +1,190 @@ +use std::{cmp::Ordering, collections::HashMap}; + +use bstr::BStr; + +use crate::{ + file::{self, SectionBodyIdsLut, SectionId}, + lookup, + parse::section, + File, +}; + +/// Private helper functions +impl<'event> File<'event> { + /// Adds a new section to the config file, returning the section id of the newly added section. + pub(crate) fn push_section_internal(&mut self, mut section: file::Section<'event>) -> SectionId { + let new_section_id = SectionId(self.section_id_counter); + section.id = new_section_id; + self.sections.insert(new_section_id, section); + let header = &self.sections[&new_section_id].header; + let lookup = self.section_lookup_tree.entry(header.name.clone()).or_default(); + + let mut found_node = false; + if let Some(subsection_name) = header.subsection_name.clone() { + for node in lookup.iter_mut() { + if let SectionBodyIdsLut::NonTerminal(subsections) = node { + found_node = true; + subsections + .entry(subsection_name.clone()) + .or_default() + .push(new_section_id); + break; + } + } + if !found_node { + let mut map = HashMap::new(); + map.insert(subsection_name, vec![new_section_id]); + lookup.push(SectionBodyIdsLut::NonTerminal(map)); + } + } else { + for node in lookup.iter_mut() { + if let SectionBodyIdsLut::Terminal(vec) = node { + found_node = true; + vec.push(new_section_id); + break; + } + } + if !found_node { + lookup.push(SectionBodyIdsLut::Terminal(vec![new_section_id])); + } + } + self.section_order.push_back(new_section_id); + self.section_id_counter += 1; + new_section_id + } + + /// Inserts `section` after the section that comes `before` it, and maintains correct ordering in all of our lookup structures. + pub(crate) fn insert_section_after(&mut self, mut section: file::Section<'event>, before: SectionId) -> SectionId { + let lookup_section_order = { + let section_order = &self.section_order; + move |section_id| { + section_order + .iter() + .enumerate() + .find_map(|(idx, id)| (*id == section_id).then_some(idx)) + .expect("before-section exists") + } + }; + + let before_order = lookup_section_order(before); + let new_section_id = SectionId(self.section_id_counter); + section.id = new_section_id; + self.sections.insert(new_section_id, section); + let header = &self.sections[&new_section_id].header; + let lookup = self.section_lookup_tree.entry(header.name.clone()).or_default(); + + let mut found_node = false; + if let Some(subsection_name) = header.subsection_name.clone() { + for node in lookup.iter_mut() { + if let SectionBodyIdsLut::NonTerminal(subsections) = node { + found_node = true; + let sections_with_name_and_subsection_name = + subsections.entry(subsection_name.clone()).or_default(); + let insert_pos = find_insert_pos_by_order( + sections_with_name_and_subsection_name, + before_order, + lookup_section_order, + ); + sections_with_name_and_subsection_name.insert(insert_pos, new_section_id); + break; + } + } + if !found_node { + let mut map = HashMap::new(); + map.insert(subsection_name, vec![new_section_id]); + lookup.push(SectionBodyIdsLut::NonTerminal(map)); + } + } else { + for node in lookup.iter_mut() { + if let SectionBodyIdsLut::Terminal(sections_with_name) = node { + found_node = true; + let insert_pos = find_insert_pos_by_order(sections_with_name, before_order, lookup_section_order); + sections_with_name.insert(insert_pos, new_section_id); + break; + } + } + if !found_node { + lookup.push(SectionBodyIdsLut::Terminal(vec![new_section_id])); + } + } + + self.section_order.insert(before_order + 1, new_section_id); + self.section_id_counter += 1; + new_section_id + } + + /// Returns the mapping between section and subsection name to section ids. + pub(crate) fn section_ids_by_name_and_subname<'a>( + &'a self, + section_name: &'a str, + subsection_name: Option<&BStr>, + ) -> Result<impl Iterator<Item = SectionId> + ExactSizeIterator + DoubleEndedIterator + '_, lookup::existing::Error> + { + let section_name = section::Name::from_str_unchecked(section_name); + let section_ids = self + .section_lookup_tree + .get(§ion_name) + .ok_or(lookup::existing::Error::SectionMissing)?; + let mut maybe_ids = None; + if let Some(subsection_name) = subsection_name { + for node in section_ids { + if let SectionBodyIdsLut::NonTerminal(subsection_lookup) = node { + maybe_ids = subsection_lookup.get(subsection_name).map(|v| v.iter().copied()); + break; + } + } + } else { + for node in section_ids { + if let SectionBodyIdsLut::Terminal(subsection_lookup) = node { + maybe_ids = Some(subsection_lookup.iter().copied()); + break; + } + } + } + maybe_ids.ok_or(lookup::existing::Error::SubSectionMissing) + } + + pub(crate) fn section_ids_by_name<'a>( + &'a self, + section_name: &'a str, + ) -> Result<impl Iterator<Item = SectionId> + '_, lookup::existing::Error> { + let section_name = section::Name::from_str_unchecked(section_name); + match self.section_lookup_tree.get(§ion_name) { + Some(lookup) => { + let mut lut = Vec::with_capacity(self.section_order.len()); + for node in lookup { + match node { + SectionBodyIdsLut::Terminal(v) => lut.extend(v.iter().copied()), + SectionBodyIdsLut::NonTerminal(v) => lut.extend(v.values().flatten().copied()), + } + } + + Ok(self.section_order.iter().filter(move |a| lut.contains(a)).copied()) + } + None => Err(lookup::existing::Error::SectionMissing), + } + } +} + +fn find_insert_pos_by_order( + sections_with_name: &[SectionId], + before_order: usize, + lookup_section_order: impl Fn(SectionId) -> usize, +) -> usize { + let mut insert_pos = sections_with_name.len(); // push back by default + for (idx, candidate_id) in sections_with_name.iter().enumerate() { + let candidate_order = lookup_section_order(*candidate_id); + match candidate_order.cmp(&before_order) { + Ordering::Less => {} + Ordering::Equal => { + insert_pos = idx + 1; // insert right after this one + break; + } + Ordering::Greater => { + insert_pos = idx; // insert before this one + break; + } + } + } + insert_pos +} diff --git a/vendor/gix-config/src/file/write.rs b/vendor/gix-config/src/file/write.rs new file mode 100644 index 000000000..29024170d --- /dev/null +++ b/vendor/gix-config/src/file/write.rs @@ -0,0 +1,93 @@ +use bstr::{BStr, BString, ByteSlice}; + +use crate::{file::Section, parse::Event, File}; + +impl File<'_> { + /// Serialize this type into a `BString` for convenience. + /// + /// Note that `to_string()` can also be used, but might not be lossless. + #[must_use] + pub fn to_bstring(&self) -> BString { + let mut buf = Vec::new(); + self.write_to(&mut buf).expect("io error impossible"); + buf.into() + } + + /// Stream ourselves to the given `out` in order to reproduce this file mostly losslessly + /// as it was parsed, while writing only sections for which `filter` returns true. + pub fn write_to_filter( + &self, + mut out: impl std::io::Write, + mut filter: impl FnMut(&Section<'_>) -> bool, + ) -> std::io::Result<()> { + let nl = self.detect_newline_style(); + + { + for event in self.frontmatter_events.as_ref() { + event.write_to(&mut out)?; + } + + if !ends_with_newline(self.frontmatter_events.as_ref(), nl, true) && self.sections.values().any(&mut filter) + { + out.write_all(nl)?; + } + } + + let mut prev_section_ended_with_newline = true; + for section_id in &self.section_order { + if !prev_section_ended_with_newline { + out.write_all(nl)?; + } + let section = self.sections.get(section_id).expect("known section-id"); + if !filter(section) { + continue; + } + section.write_to(&mut out)?; + + prev_section_ended_with_newline = ends_with_newline(section.body.0.as_ref(), nl, false); + if let Some(post_matter) = self.frontmatter_post_section.get(section_id) { + if !prev_section_ended_with_newline { + out.write_all(nl)?; + } + for event in post_matter { + event.write_to(&mut out)?; + } + prev_section_ended_with_newline = ends_with_newline(post_matter, nl, prev_section_ended_with_newline); + } + } + + if !prev_section_ended_with_newline { + out.write_all(nl)?; + } + + Ok(()) + } + + /// Stream ourselves to the given `out`, in order to reproduce this file mostly losslessly + /// as it was parsed. + pub fn write_to(&self, out: impl std::io::Write) -> std::io::Result<()> { + self.write_to_filter(out, |_| true) + } +} + +pub(crate) fn ends_with_newline(e: &[crate::parse::Event<'_>], nl: impl AsRef<[u8]>, default: bool) -> bool { + if e.is_empty() { + return default; + } + e.iter() + .rev() + .take_while(|e| e.to_bstr_lossy().iter().all(|b| b.is_ascii_whitespace())) + .find_map(|e| e.to_bstr_lossy().contains_str(nl.as_ref()).then_some(true)) + .unwrap_or(false) +} + +pub(crate) fn extract_newline<'a>(e: &'a Event<'_>) -> Option<&'a BStr> { + match e { + Event::Newline(b) => b.as_ref().into(), + _ => None, + } +} + +pub(crate) fn platform_newline() -> &'static BStr { + if cfg!(windows) { "\r\n" } else { "\n" }.into() +} diff --git a/vendor/gix-config/src/lib.rs b/vendor/gix-config/src/lib.rs new file mode 100644 index 000000000..9b2afd692 --- /dev/null +++ b/vendor/gix-config/src/lib.rs @@ -0,0 +1,52 @@ +//! # `gix_config` +//! +//! This crate is a high performance `gix-config` file reader and writer. It +//! exposes a high level API to parse, read, and write [`gix-config` files]. +//! +//! This crate has a few primary offerings and various accessory functions. The +//! table below gives a brief explanation of all offerings, loosely in order +//! from the highest to lowest abstraction. +//! +//! | Offering | Description | Zero-copy? | +//! | ------------- | --------------------------------------------------- | ----------------- | +//! | [`File`] | Accelerated wrapper for reading and writing values. | On some reads[^1] | +//! | [`parse::State`] | Syntactic events for `gix-config` files. | Yes | +//! | value wrappers | Wrappers for `gix-config` value types. | Yes | +//! +//! This crate also exposes efficient value normalization which unescapes +//! characters and removes quotes through the `normalize_*` family of functions, +//! located in the [`value`] module. +//! +//! # Known differences to the `git config` specification +//! +//! - Legacy headers like `[section.subsection]` are supposed to be turned into to lower case and compared +//! case-sensitively. We keep its case and compare case-insensitively. +//! +//! [^1]: When read values do not need normalization and it wasn't parsed in 'owned' mode. +//! +//! [`gix-config` files]: https://git-scm.com/docs/gix-config#_configuration_file +//! [`File`]: crate::File +//! [`parse::State`]: crate::parse::Events +//! [`nom`]: https://github.com/Geal/nom +//! +//! ## Feature Flags +#![cfg_attr( + feature = "document-features", + cfg_attr(doc, doc = ::document_features::document_features!()) +)] +#![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] +#![deny(missing_docs, rust_2018_idioms, unsafe_code)] + +pub mod file; + +/// +pub mod lookup; +pub mod parse; +/// +pub mod value; +pub use gix_config_value::{color, integer, path, Boolean, Color, Integer, Path}; + +mod types; +pub use types::{File, Source}; +/// +pub mod source; diff --git a/vendor/gix-config/src/lookup.rs b/vendor/gix-config/src/lookup.rs new file mode 100644 index 000000000..781497812 --- /dev/null +++ b/vendor/gix-config/src/lookup.rs @@ -0,0 +1,24 @@ +/// The error when looking up a value, for example via [`File::try_value()`][crate::File::try_value()]. +#[derive(Debug, thiserror::Error)] +#[allow(missing_docs)] +pub enum Error<E> { + #[error(transparent)] + ValueMissing(#[from] existing::Error), + #[error(transparent)] + FailedConversion(E), +} + +/// +pub mod existing { + /// The error when looking up a value that doesn't exist, for example via [`File::value()`][crate::File::value()]. + #[derive(Debug, thiserror::Error)] + #[allow(missing_docs)] + pub enum Error { + #[error("The requested section does not exist")] + SectionMissing, + #[error("The requested subsection does not exist")] + SubSectionMissing, + #[error("The key does not exist in the requested section")] + KeyMissing, + } +} diff --git a/vendor/gix-config/src/parse/comment.rs b/vendor/gix-config/src/parse/comment.rs new file mode 100644 index 000000000..6d4bb15ff --- /dev/null +++ b/vendor/gix-config/src/parse/comment.rs @@ -0,0 +1,50 @@ +use std::{borrow::Cow, fmt::Display}; + +use bstr::BString; + +use crate::parse::Comment; + +impl Comment<'_> { + /// Turn this instance into a fully owned one with `'static` lifetime. + #[must_use] + pub fn to_owned(&self) -> Comment<'static> { + Comment { + tag: self.tag, + text: Cow::Owned(self.text.as_ref().into()), + } + } + + /// Serialize this type into a `BString` for convenience. + /// + /// Note that `to_string()` can also be used, but might not be lossless. + #[must_use] + pub fn to_bstring(&self) -> BString { + let mut buf = Vec::new(); + self.write_to(&mut buf).expect("io error impossible"); + buf.into() + } + + /// Stream ourselves to the given `out`, in order to reproduce this comment losslessly. + pub fn write_to(&self, mut out: impl std::io::Write) -> std::io::Result<()> { + out.write_all(&[self.tag])?; + out.write_all(self.text.as_ref()) + } +} + +impl Display for Comment<'_> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + Display::fmt(&self.to_bstring(), f) + } +} + +impl From<Comment<'_>> for BString { + fn from(c: Comment<'_>) -> Self { + c.into() + } +} + +impl From<&Comment<'_>> for BString { + fn from(c: &Comment<'_>) -> Self { + c.to_bstring() + } +} diff --git a/vendor/gix-config/src/parse/error.rs b/vendor/gix-config/src/parse/error.rs new file mode 100644 index 000000000..1f469ee4c --- /dev/null +++ b/vendor/gix-config/src/parse/error.rs @@ -0,0 +1,64 @@ +use std::fmt::Display; + +use crate::parse::Error; + +/// A list of parsers that parsing can fail on. This is used for pretty-printing errors +#[derive(PartialEq, Debug, Clone, Copy)] +pub(crate) enum ParseNode { + SectionHeader, + Name, + Value, +} + +impl Display for ParseNode { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::SectionHeader => write!(f, "section header"), + Self::Name => write!(f, "name"), + Self::Value => write!(f, "value"), + } + } +} + +impl Error { + /// The one-indexed line number where the error occurred. This is determined + /// by the number of newlines that were successfully parsed. + #[must_use] + pub const fn line_number(&self) -> usize { + self.line_number + 1 + } + + /// The data that was left unparsed, which contains the cause of the parse error. + #[must_use] + pub fn remaining_data(&self) -> &[u8] { + &self.parsed_until + } +} + +impl Display for Error { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!( + f, + "Got an unexpected token on line {} while trying to parse a {}: ", + self.line_number + 1, + self.last_attempted_parser, + )?; + + let data_size = self.parsed_until.len(); + let data = std::str::from_utf8(&self.parsed_until); + match (data, data_size) { + (Ok(data), _) if data_size > 10 => { + write!( + f, + "'{}' ... ({} characters omitted)", + &data.chars().take(10).collect::<String>(), + data_size - 10 + ) + } + (Ok(data), _) => write!(f, "'{data}'"), + (Err(_), _) => self.parsed_until.fmt(f), + } + } +} + +impl std::error::Error for Error {} diff --git a/vendor/gix-config/src/parse/event.rs b/vendor/gix-config/src/parse/event.rs new file mode 100644 index 000000000..b7b96934d --- /dev/null +++ b/vendor/gix-config/src/parse/event.rs @@ -0,0 +1,83 @@ +use std::{borrow::Cow, fmt::Display}; + +use bstr::{BStr, BString}; + +use crate::parse::Event; + +impl Event<'_> { + /// Serialize this type into a `BString` for convenience. + /// + /// Note that `to_string()` can also be used, but might not be lossless. + #[must_use] + pub fn to_bstring(&self) -> BString { + let mut buf = Vec::new(); + self.write_to(&mut buf).expect("io error impossible"); + buf.into() + } + + /// Turn ourselves into the text we represent, lossy. + /// + /// Note that this will be partial in case of `ValueNotDone` which doesn't include the backslash, and `SectionHeader` will only + /// provide their name, lacking the sub-section name. + pub fn to_bstr_lossy(&self) -> &BStr { + match self { + Self::ValueNotDone(e) | Self::Whitespace(e) | Self::Newline(e) | Self::Value(e) | Self::ValueDone(e) => { + e.as_ref() + } + Self::KeyValueSeparator => "=".into(), + Self::SectionKey(k) => k.0.as_ref(), + Self::SectionHeader(h) => h.name.0.as_ref(), + Self::Comment(c) => c.text.as_ref(), + } + } + + /// Stream ourselves to the given `out`, in order to reproduce this event mostly losslessly + /// as it was parsed. + pub fn write_to(&self, mut out: impl std::io::Write) -> std::io::Result<()> { + match self { + Self::ValueNotDone(e) => { + out.write_all(e.as_ref())?; + out.write_all(b"\\") + } + Self::Whitespace(e) | Self::Newline(e) | Self::Value(e) | Self::ValueDone(e) => out.write_all(e.as_ref()), + Self::KeyValueSeparator => out.write_all(b"="), + Self::SectionKey(k) => out.write_all(k.0.as_ref()), + Self::SectionHeader(h) => h.write_to(&mut out), + Self::Comment(c) => c.write_to(&mut out), + } + } + + /// Turn this instance into a fully owned one with `'static` lifetime. + #[must_use] + pub fn to_owned(&self) -> Event<'static> { + match self { + Event::Comment(e) => Event::Comment(e.to_owned()), + Event::SectionHeader(e) => Event::SectionHeader(e.to_owned()), + Event::SectionKey(e) => Event::SectionKey(e.to_owned()), + Event::Value(e) => Event::Value(Cow::Owned(e.clone().into_owned())), + Event::ValueNotDone(e) => Event::ValueNotDone(Cow::Owned(e.clone().into_owned())), + Event::ValueDone(e) => Event::ValueDone(Cow::Owned(e.clone().into_owned())), + Event::Newline(e) => Event::Newline(Cow::Owned(e.clone().into_owned())), + Event::Whitespace(e) => Event::Whitespace(Cow::Owned(e.clone().into_owned())), + Event::KeyValueSeparator => Event::KeyValueSeparator, + } + } +} + +impl Display for Event<'_> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + Display::fmt(&self.to_bstring(), f) + } +} + +impl From<Event<'_>> for BString { + fn from(event: Event<'_>) -> Self { + event.into() + } +} + +impl From<&Event<'_>> for BString { + fn from(event: &Event<'_>) -> Self { + event.to_bstring() + } +} diff --git a/vendor/gix-config/src/parse/events.rs b/vendor/gix-config/src/parse/events.rs new file mode 100644 index 000000000..62f621b52 --- /dev/null +++ b/vendor/gix-config/src/parse/events.rs @@ -0,0 +1,336 @@ +use std::convert::TryFrom; + +use smallvec::SmallVec; + +use crate::{ + parse, + parse::{section, Event, Section}, +}; + +/// A type store without allocation all events that are typically preceding the first section. +pub type FrontMatterEvents<'a> = SmallVec<[Event<'a>; 8]>; + +/// A zero-copy `gix-config` file parser. +/// +/// This is parser exposes low-level syntactic events from a `gix-config` file. +/// Generally speaking, you'll want to use [`File`] as it wraps +/// around the parser to provide a higher-level abstraction to a `gix-config` +/// file, including querying, modifying, and updating values. +/// +/// This parser guarantees that the events emitted are sufficient to +/// reconstruct a `gix-config` file identical to the source `gix-config` +/// when writing it. +/// +/// # Differences between a `.ini` parser +/// +/// While the `gix-config` format closely resembles the [`.ini` file format], +/// there are subtle differences that make them incompatible. For one, the file +/// format is not well defined, and there exists no formal specification to +/// adhere to. +/// +/// For concrete examples, some notable differences are: +/// - `gix-config` sections permit subsections via either a quoted string +/// (`[some-section "subsection"]`) or via the deprecated dot notation +/// (`[some-section.subsection]`). Successful parsing these section names is not +/// well defined in typical `.ini` parsers. This parser will handle these cases +/// perfectly. +/// - Comment markers are not strictly defined either. This parser will always +/// and only handle a semicolon or octothorpe (also known as a hash or number +/// sign). +/// - Global properties may be allowed in `.ini` parsers, but is strictly +/// disallowed by this parser. +/// - Only `\t`, `\n`, `\b` `\\` are valid escape characters. +/// - Quoted and semi-quoted values will be parsed (but quotes will be included +/// in event outputs). An example of a semi-quoted value is `5"hello world"`, +/// which should be interpreted as `5hello world` after +/// [normalization][crate::value::normalize()]. +/// - Line continuations via a `\` character is supported (inside or outside of quotes) +/// - Whitespace handling similarly follows the `gix-config` specification as +/// closely as possible, where excess whitespace after a non-quoted value are +/// trimmed, and line continuations onto a new line with excess spaces are kept. +/// - Only equal signs (optionally padded by spaces) are valid name/value +/// delimiters. +/// +/// Note that that things such as case-sensitivity or duplicate sections are +/// _not_ handled. This parser is a low level _syntactic_ interpreter +/// and higher level wrappers around this parser, which may +/// or may not be zero-copy, should handle _semantic_ values. This also means +/// that string-like values are not interpreted. For example, `hello"world"` +/// would be read at a high level as `helloworld` but this parser will return +/// the former instead, with the extra quotes. This is because it is not the +/// responsibility of the parser to interpret these values, and doing so would +/// necessarily require a copy, which this parser avoids. +/// +/// # Trait Implementations +/// +/// - This struct does _not_ implement [`FromStr`] due to lifetime +/// constraints implied on the required `from_str` method. Instead, it provides +/// [`From<&'_ str>`]. +/// +/// # Idioms +/// +/// If you do want to use this parser, there are some idioms that may help you +/// with interpreting sequences of events. +/// +/// ## `Value` events do not immediately follow `Key` events +/// +/// Consider the following `gix-config` example: +/// +/// ```text +/// [core] +/// autocrlf = input +/// ``` +/// +/// Because this parser guarantees perfect reconstruction, there are many +/// non-significant events that occur in addition to the ones you may expect: +/// +/// ``` +/// # use gix_config::parse::{Event, Events, section}; +/// # use std::borrow::Cow; +/// # use std::convert::TryFrom; +/// # let section_header = section::Header::new("core", None).unwrap(); +/// # let section_data = "[core]\n autocrlf = input"; +/// # assert_eq!(Events::from_str(section_data).unwrap().into_vec(), vec![ +/// Event::SectionHeader(section_header), +/// Event::Newline(Cow::Borrowed("\n".into())), +/// Event::Whitespace(Cow::Borrowed(" ".into())), +/// Event::SectionKey(section::Key::try_from("autocrlf")?), +/// Event::Whitespace(Cow::Borrowed(" ".into())), +/// Event::KeyValueSeparator, +/// Event::Whitespace(Cow::Borrowed(" ".into())), +/// Event::Value(Cow::Borrowed("input".into())), +/// # ]); +/// # Ok::<_, Box<dyn std::error::Error>>(()) +/// ``` +/// +/// Note the two whitespace events between the key and value pair! Those two +/// events actually refer to the whitespace between the name and value and the +/// equal sign. So if the config instead had `autocrlf=input`, those whitespace +/// events would no longer be present. +/// +/// ## `KeyValueSeparator` event is not guaranteed to emit +/// +/// Consider the following `gix-config` example: +/// +/// ```text +/// [core] +/// autocrlf +/// ``` +/// +/// This is a valid config with a `autocrlf` key having an implicit `true` +/// value. This means that there is not a `=` separating the key and value, +/// which means that the corresponding event won't appear either: +/// +/// ``` +/// # use gix_config::parse::{Event, Events, section}; +/// # use std::borrow::Cow; +/// # use std::convert::TryFrom; +/// # let section_header = section::Header::new("core", None).unwrap(); +/// # let section_data = "[core]\n autocrlf"; +/// # assert_eq!(Events::from_str(section_data).unwrap().into_vec(), vec![ +/// Event::SectionHeader(section_header), +/// Event::Newline(Cow::Borrowed("\n".into())), +/// Event::Whitespace(Cow::Borrowed(" ".into())), +/// Event::SectionKey(section::Key::try_from("autocrlf")?), +/// Event::Value(Cow::Borrowed("".into())), +/// # ]); +/// # Ok::<_, Box<dyn std::error::Error>>(()) +/// ``` +/// +/// ## Quoted values are not unquoted +/// +/// Consider the following `gix-config` example: +/// +/// ```text +/// [core] +/// autocrlf=true"" +/// filemode=fa"lse" +/// ``` +/// +/// Both these events, when fully processed, should normally be `true` and +/// `false`. However, because this parser is zero-copy, we cannot process +/// partially quoted values, such as the `false` example. As a result, to +/// maintain consistency, the parser will just take all values as literals. The +/// relevant event stream emitted is thus emitted as: +/// +/// ``` +/// # use gix_config::parse::{Event, Events, section}; +/// # use std::borrow::Cow; +/// # use std::convert::TryFrom; +/// # let section_header = section::Header::new("core", None).unwrap(); +/// # let section_data = "[core]\nautocrlf=true\"\"\nfilemode=fa\"lse\""; +/// # assert_eq!(Events::from_str(section_data).unwrap().into_vec(), vec![ +/// Event::SectionHeader(section_header), +/// Event::Newline(Cow::Borrowed("\n".into())), +/// Event::SectionKey(section::Key::try_from("autocrlf")?), +/// Event::KeyValueSeparator, +/// Event::Value(Cow::Borrowed(r#"true"""#.into())), +/// Event::Newline(Cow::Borrowed("\n".into())), +/// Event::SectionKey(section::Key::try_from("filemode")?), +/// Event::KeyValueSeparator, +/// Event::Value(Cow::Borrowed(r#"fa"lse""#.into())), +/// # ]); +/// # Ok::<_, Box<dyn std::error::Error>>(()) +/// ``` +/// +/// ## Whitespace after line continuations are part of the value +/// +/// Consider the following `gix-config` example: +/// +/// ```text +/// [some-section] +/// file=a\ +/// c +/// ``` +/// +/// Because how `gix-config` treats continuations, the whitespace preceding `c` +/// are in fact part of the value of `file`. The fully interpreted key/value +/// pair is actually `file=a c`. As a result, the parser will provide this +/// split value accordingly: +/// +/// ``` +/// # use gix_config::parse::{Event, Events, section}; +/// # use std::borrow::Cow; +/// # use std::convert::TryFrom; +/// # let section_header = section::Header::new("some-section", None).unwrap(); +/// # let section_data = "[some-section]\nfile=a\\\n c"; +/// # assert_eq!(Events::from_str(section_data).unwrap().into_vec(), vec![ +/// Event::SectionHeader(section_header), +/// Event::Newline(Cow::Borrowed("\n".into())), +/// Event::SectionKey(section::Key::try_from("file")?), +/// Event::KeyValueSeparator, +/// Event::ValueNotDone(Cow::Borrowed("a".into())), +/// Event::Newline(Cow::Borrowed("\n".into())), +/// Event::ValueDone(Cow::Borrowed(" c".into())), +/// # ]); +/// # Ok::<_, Box<dyn std::error::Error>>(()) +/// ``` +/// +/// [`File`]: crate::File +/// [`.ini` file format]: https://en.wikipedia.org/wiki/INI_file +/// [`git`'s documentation]: https://git-scm.com/docs/gix-config#_configuration_file +/// [`FromStr`]: std::str::FromStr +/// [`From<&'_ str>`]: std::convert::From +#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug, Default)] +pub struct Events<'a> { + /// Events seen before the first section. + pub frontmatter: FrontMatterEvents<'a>, + /// All parsed sections. + pub sections: Vec<Section<'a>>, +} + +impl Events<'static> { + /// Parses the provided bytes, returning an [`Events`] that contains allocated + /// and owned events. This is similar to [`Events::from_bytes()`], but performance + /// is degraded as it requires allocation for every event. + /// + /// Use `filter` to only include those events for which it returns true. + pub fn from_bytes_owned<'a>( + input: &'a [u8], + filter: Option<fn(&Event<'a>) -> bool>, + ) -> Result<Events<'static>, parse::Error> { + from_bytes(input, |e| e.to_owned(), filter) + } +} + +impl<'a> Events<'a> { + /// Attempt to zero-copy parse the provided bytes. On success, returns a + /// [`Events`] that provides methods to accessing leading comments and sections + /// of a `gix-config` file and can be converted into an iterator of [`Event`] + /// for higher level processing. + /// + /// Use `filter` to only include those events for which it returns true. + pub fn from_bytes(input: &'a [u8], filter: Option<fn(&Event<'a>) -> bool>) -> Result<Events<'a>, parse::Error> { + from_bytes(input, std::convert::identity, filter) + } + + /// Attempt to zero-copy parse the provided `input` string. + /// + /// Prefer the [`from_bytes()`][Self::from_bytes()] method if UTF8 encoding + /// isn't guaranteed. + #[allow(clippy::should_implement_trait)] + pub fn from_str(input: &'a str) -> Result<Events<'a>, parse::Error> { + Self::from_bytes(input.as_bytes(), None) + } + + /// Consumes the parser to produce an iterator of all contained events. + #[must_use = "iterators are lazy and do nothing unless consumed"] + #[allow(clippy::should_implement_trait)] + pub fn into_iter(self) -> impl Iterator<Item = parse::Event<'a>> + std::iter::FusedIterator { + self.frontmatter.into_iter().chain( + self.sections + .into_iter() + .flat_map(|section| std::iter::once(parse::Event::SectionHeader(section.header)).chain(section.events)), + ) + } + + /// Place all contained events into a single `Vec`. + pub fn into_vec(self) -> Vec<parse::Event<'a>> { + self.into_iter().collect() + } +} + +impl<'a> TryFrom<&'a str> for Events<'a> { + type Error = parse::Error; + + fn try_from(value: &'a str) -> Result<Self, Self::Error> { + Self::from_str(value) + } +} + +impl<'a> TryFrom<&'a [u8]> for Events<'a> { + type Error = parse::Error; + + fn try_from(value: &'a [u8]) -> Result<Self, Self::Error> { + Events::from_bytes(value, None) + } +} + +fn from_bytes<'a, 'b>( + input: &'a [u8], + convert: impl Fn(Event<'a>) -> Event<'b>, + filter: Option<fn(&Event<'a>) -> bool>, +) -> Result<Events<'b>, parse::Error> { + let mut header = None; + let mut events = section::Events::default(); + let mut frontmatter = FrontMatterEvents::default(); + let mut sections = Vec::new(); + parse::from_bytes(input, |e: Event<'_>| match e { + Event::SectionHeader(next_header) => { + match header.take() { + None => { + frontmatter = std::mem::take(&mut events).into_iter().collect(); + } + Some(prev_header) => { + sections.push(parse::Section { + header: prev_header, + events: std::mem::take(&mut events), + }); + } + }; + header = match convert(Event::SectionHeader(next_header)) { + Event::SectionHeader(h) => h, + _ => unreachable!("BUG: convert must not change the event type, just the lifetime"), + } + .into(); + } + event => { + if filter.map_or(true, |f| f(&event)) { + events.push(convert(event)) + } + } + })?; + + match header { + None => { + frontmatter = events.into_iter().collect(); + } + Some(prev_header) => { + sections.push(parse::Section { + header: prev_header, + events: std::mem::take(&mut events), + }); + } + } + Ok(Events { frontmatter, sections }) +} diff --git a/vendor/gix-config/src/parse/key.rs b/vendor/gix-config/src/parse/key.rs new file mode 100644 index 000000000..b0e0376be --- /dev/null +++ b/vendor/gix-config/src/parse/key.rs @@ -0,0 +1,35 @@ +use bstr::{BStr, ByteSlice}; + +/// An unvalidated parse result of parsing input like `remote.origin.url` or `core.bare`. +#[derive(Debug, PartialEq, Ord, PartialOrd, Eq, Hash, Clone, Copy)] +pub struct Key<'a> { + /// The name of the section, like `core` in `core.bare`. + pub section_name: &'a str, + /// The name of the sub-section, like `origin` in `remote.origin.url`. + pub subsection_name: Option<&'a BStr>, + /// The name of the section key, like `url` in `remote.origin.url`. + pub value_name: &'a str, +} + +/// Parse `input` like `core.bare` or `remote.origin.url` as a `Key` to make its fields available, +/// or `None` if there were not at least 2 tokens separated by `.`. +/// Note that `input` isn't validated, and is `str` as ascii is a subset of UTF-8 which is required for any valid keys. +pub fn parse_unvalidated<'a>(input: impl Into<&'a BStr>) -> Option<Key<'a>> { + let input = input.into(); + let mut tokens = input.splitn(2, |b| *b == b'.'); + let section_name = tokens.next()?; + let subsection_or_key = tokens.next()?; + let mut tokens = subsection_or_key.rsplitn(2, |b| *b == b'.'); + let (subsection_name, value_name) = match (tokens.next(), tokens.next()) { + (Some(key), Some(subsection)) => (Some(subsection.into()), key), + (Some(key), None) => (None, key), + (None, Some(_)) => unreachable!("iterator can't restart producing items"), + (None, None) => return None, + }; + + Some(Key { + section_name: section_name.to_str().ok()?, + subsection_name, + value_name: value_name.to_str().ok()?, + }) +} diff --git a/vendor/gix-config/src/parse/mod.rs b/vendor/gix-config/src/parse/mod.rs new file mode 100644 index 000000000..50363873c --- /dev/null +++ b/vendor/gix-config/src/parse/mod.rs @@ -0,0 +1,116 @@ +//! This module handles parsing a `gix-config` file. Generally speaking, you +//! want to use a higher abstraction such as [`File`] unless you have some +//! explicit reason to work with events instead. +//! +//! The workflow for interacting with this is to use +//! [`from_bytes()`] to obtain all parse events or tokens of the given input. +//! +//! On a higher level, one can use [`Events`] to parse all events into a set +//! of easily interpretable data type, similar to what [`File`] does. +//! +//! [`File`]: crate::File + +use std::{borrow::Cow, hash::Hash}; + +use bstr::BStr; + +mod nom; +pub use self::nom::from_bytes; +mod event; +#[path = "events.rs"] +mod events_type; +pub use events_type::{Events, FrontMatterEvents}; +mod comment; +mod error; +/// +pub mod section; + +/// +mod key; +pub use key::{parse_unvalidated as key, Key}; + +#[cfg(test)] +pub(crate) mod tests; + +/// Syntactic events that occurs in the config. Despite all these variants +/// holding a [`Cow`] instead over a simple reference, the parser will only emit +/// borrowed `Cow` variants. +/// +/// The `Cow` is used here for ease of inserting new, typically owned events as used +/// in the [`File`] struct when adding values, allowing a mix of owned and borrowed +/// values. +/// +/// [`Cow`]: std::borrow::Cow +/// [`File`]: crate::File +#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)] +pub enum Event<'a> { + /// A comment with a comment tag and the comment itself. Note that the + /// comment itself may contain additional whitespace and comment markers + /// at the beginning, like `# comment` or `; comment`. + Comment(Comment<'a>), + /// A section header containing the section name and a subsection, if it + /// exists. For instance, `remote "origin"` is parsed to `remote` as section + /// name and `origin` as subsection name. + SectionHeader(section::Header<'a>), + /// A name to a value in a section, like `url` in `remote.origin.url`. + SectionKey(section::Key<'a>), + /// A completed value. This may be any single-line string, including the empty string + /// if an implicit boolean value is used. + /// Note that these values may contain spaces and any special character. This value is + /// also unprocessed, so it it may contain double quotes that should be + /// [normalized][crate::value::normalize()] before interpretation. + Value(Cow<'a, BStr>), + /// Represents any token used to signify a newline character. On Unix + /// platforms, this is typically just `\n`, but can be any valid newline + /// sequence. Multiple newlines (such as `\n\n`) will be merged as a single + /// newline event containing a string of multiple newline characters. + Newline(Cow<'a, BStr>), + /// Any value that isn't completed. This occurs when the value is continued + /// onto the next line by ending it with a backslash. + /// A [`Newline`][Self::Newline] event is guaranteed after, followed by + /// either a ValueDone, a Whitespace, or another ValueNotDone. + ValueNotDone(Cow<'a, BStr>), + /// The last line of a value which was continued onto another line. + /// With this it's possible to obtain the complete value by concatenating + /// the prior [`ValueNotDone`][Self::ValueNotDone] events. + ValueDone(Cow<'a, BStr>), + /// A continuous section of insignificant whitespace. + /// + /// Note that values with internal whitespace will not be separated by this event, + /// hence interior whitespace there is always part of the value. + Whitespace(Cow<'a, BStr>), + /// This event is emitted when the parser counters a valid `=` character + /// separating the key and value. + /// This event is necessary as it eliminates the ambiguity for whitespace + /// events between a key and value event. + KeyValueSeparator, +} + +/// A parsed section containing the header and the section events, typically +/// comprising the keys and their values. +#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)] +pub struct Section<'a> { + /// The section name and subsection name, if any. + pub header: section::Header<'a>, + /// The syntactic events found in this section. + pub events: section::Events<'a>, +} + +/// A parsed comment containing the comment marker and comment. +#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug, Default)] +pub struct Comment<'a> { + /// The comment marker used. This is either a semicolon or octothorpe/hash. + pub tag: u8, + /// The parsed comment. + pub text: Cow<'a, BStr>, +} + +/// A parser error reports the one-indexed line number where the parsing error +/// occurred, as well as the last parser node and the remaining data to be +/// parsed. +#[derive(PartialEq, Debug)] +pub struct Error { + line_number: usize, + last_attempted_parser: error::ParseNode, + parsed_until: bstr::BString, +} diff --git a/vendor/gix-config/src/parse/nom/mod.rs b/vendor/gix-config/src/parse/nom/mod.rs new file mode 100644 index 000000000..11d1dea6b --- /dev/null +++ b/vendor/gix-config/src/parse/nom/mod.rs @@ -0,0 +1,460 @@ +use std::borrow::Cow; + +use bstr::{BStr, BString, ByteSlice, ByteVec}; +use nom::{ + branch::alt, + bytes::complete::{tag, take_till, take_while}, + character::{ + complete::{char, one_of}, + is_space, + }, + combinator::{map, opt}, + error::{Error as NomError, ErrorKind}, + multi::{fold_many0, fold_many1}, + sequence::delimited, + IResult, +}; + +use crate::parse::{error::ParseNode, section, Comment, Error, Event}; + +/// Attempt to zero-copy parse the provided bytes, passing results to `dispatch`. +pub fn from_bytes<'a>(input: &'a [u8], mut dispatch: impl FnMut(Event<'a>)) -> Result<(), Error> { + let bom = unicode_bom::Bom::from(input); + let mut newlines = 0; + let (i, _) = fold_many0( + alt(( + map(comment, Event::Comment), + map(take_spaces, |whitespace| Event::Whitespace(Cow::Borrowed(whitespace))), + map(take_newlines, |(newline, counter)| { + newlines += counter; + Event::Newline(Cow::Borrowed(newline)) + }), + )), + || (), + |_acc, event| dispatch(event), + )(&input[bom.len()..]) + // I don't think this can panic. many0 errors if the child parser returns + // a success where the input was not consumed, but alt will only return Ok + // if one of its children succeed. However, all of it's children are + // guaranteed to consume something if they succeed, so the Ok(i) == i case + // can never occur. + .expect("many0(alt(...)) panicked. Likely a bug in one of the children parsers."); + + if i.is_empty() { + return Ok(()); + } + + let mut node = ParseNode::SectionHeader; + + let res = fold_many1( + |i| section(i, &mut node, &mut dispatch), + || (), + |_acc, additional_newlines| { + newlines += additional_newlines; + }, + )(i); + let (i, _) = res.map_err(|_| Error { + line_number: newlines, + last_attempted_parser: node, + parsed_until: i.as_bstr().into(), + })?; + + // This needs to happen after we collect sections, otherwise the line number + // will be off. + if !i.is_empty() { + return Err(Error { + line_number: newlines, + last_attempted_parser: node, + parsed_until: i.as_bstr().into(), + }); + } + + Ok(()) +} + +fn comment(i: &[u8]) -> IResult<&[u8], Comment<'_>> { + let (i, comment_tag) = one_of(";#")(i)?; + let (i, comment) = take_till(|c| c == b'\n')(i)?; + Ok(( + i, + Comment { + tag: comment_tag as u8, + text: Cow::Borrowed(comment.as_bstr()), + }, + )) +} + +#[cfg(test)] +mod tests; + +fn section<'a>(i: &'a [u8], node: &mut ParseNode, dispatch: &mut impl FnMut(Event<'a>)) -> IResult<&'a [u8], usize> { + let (mut i, header) = section_header(i)?; + dispatch(Event::SectionHeader(header)); + + let mut newlines = 0; + + // This would usually be a many0(alt(...)), the manual loop allows us to + // optimize vec insertions + loop { + let old_i = i; + + if let Ok((new_i, v)) = take_spaces(i) { + if old_i != new_i { + i = new_i; + dispatch(Event::Whitespace(Cow::Borrowed(v.as_bstr()))); + } + } + + if let Ok((new_i, (v, new_newlines))) = take_newlines(i) { + if old_i != new_i { + i = new_i; + newlines += new_newlines; + dispatch(Event::Newline(Cow::Borrowed(v.as_bstr()))); + } + } + + if let Ok((new_i, new_newlines)) = key_value_pair(i, node, dispatch) { + if old_i != new_i { + i = new_i; + newlines += new_newlines; + } + } + + if let Ok((new_i, comment)) = comment(i) { + if old_i != new_i { + i = new_i; + dispatch(Event::Comment(comment)); + } + } + + if old_i == i { + break; + } + } + + Ok((i, newlines)) +} + +fn section_header(i: &[u8]) -> IResult<&[u8], section::Header<'_>> { + let (i, _) = char('[')(i)?; + // No spaces must be between section name and section start + let (i, name) = take_while(|c: u8| c.is_ascii_alphanumeric() || c == b'-' || c == b'.')(i)?; + + let name = name.as_bstr(); + if let Ok((i, _)) = char::<_, NomError<&[u8]>>(']')(i) { + // Either section does not have a subsection or using deprecated + // subsection syntax at this point. + let header = match memchr::memrchr(b'.', name.as_bytes()) { + Some(index) => section::Header { + name: section::Name(Cow::Borrowed(name[..index].as_bstr())), + separator: name.get(index..=index).map(|s| Cow::Borrowed(s.as_bstr())), + subsection_name: name.get(index + 1..).map(|s| Cow::Borrowed(s.as_bstr())), + }, + None => section::Header { + name: section::Name(Cow::Borrowed(name.as_bstr())), + separator: None, + subsection_name: None, + }, + }; + + if header.name.is_empty() { + return Err(nom::Err::Error(NomError { + input: i, + code: ErrorKind::NoneOf, + })); + } + return Ok((i, header)); + } + + // Section header must be using modern subsection syntax at this point. + + let (i, whitespace) = take_spaces(i)?; + let (i, subsection_name) = delimited(char('"'), opt(sub_section), tag("\"]"))(i)?; + + Ok(( + i, + section::Header { + name: section::Name(Cow::Borrowed(name)), + separator: Some(Cow::Borrowed(whitespace)), + subsection_name, + }, + )) +} + +fn sub_section(i: &[u8]) -> IResult<&[u8], Cow<'_, BStr>> { + let (rest, (found_escape, consumed)) = sub_section_delegate(i, &mut |_| ())?; + if found_escape { + let mut buf = BString::default(); + sub_section_delegate(i, &mut |b| buf.push_byte(b)).map(|(i, _)| (i, buf.into())) + } else { + Ok((rest, i[..consumed].as_bstr().into())) + } +} + +fn sub_section_delegate<'a>(i: &'a [u8], push_byte: &mut dyn FnMut(u8)) -> IResult<&'a [u8], (bool, usize)> { + let mut cursor = 0; + let mut bytes = i.iter().copied(); + let mut found_terminator = false; + let mut found_escape = false; + while let Some(mut b) = bytes.next() { + cursor += 1; + if b == b'\n' || b == 0 { + return Err(nom::Err::Error(NomError { + input: &i[cursor..], + code: ErrorKind::NonEmpty, + })); + } + if b == b'"' { + found_terminator = true; + break; + } + if b == b'\\' { + b = bytes.next().ok_or_else(|| { + nom::Err::Error(NomError { + input: &i[cursor..], + code: ErrorKind::NonEmpty, + }) + })?; + found_escape = true; + cursor += 1; + if b == b'\n' { + return Err(nom::Err::Error(NomError { + input: &i[cursor..], + code: ErrorKind::NonEmpty, + })); + } + } + push_byte(b); + } + + if !found_terminator { + return Err(nom::Err::Error(NomError { + input: &i[cursor..], + code: ErrorKind::NonEmpty, + })); + } + + Ok((&i[cursor - 1..], (found_escape, cursor - 1))) +} + +fn key_value_pair<'a>( + i: &'a [u8], + node: &mut ParseNode, + dispatch: &mut impl FnMut(Event<'a>), +) -> IResult<&'a [u8], usize> { + *node = ParseNode::Name; + let (i, name) = config_name(i)?; + + dispatch(Event::SectionKey(section::Key(Cow::Borrowed(name)))); + + let (i, whitespace) = opt(take_spaces)(i)?; + if let Some(whitespace) = whitespace { + dispatch(Event::Whitespace(Cow::Borrowed(whitespace))); + } + + *node = ParseNode::Value; + let (i, newlines) = config_value(i, dispatch)?; + Ok((i, newlines)) +} + +/// Parses the config name of a config pair. Assumes the input has already been +/// trimmed of any leading whitespace. +fn config_name(i: &[u8]) -> IResult<&[u8], &BStr> { + if i.is_empty() { + return Err(nom::Err::Error(NomError { + input: i, + code: ErrorKind::NonEmpty, + })); + } + + if !i[0].is_ascii_alphabetic() { + return Err(nom::Err::Error(NomError { + input: i, + code: ErrorKind::Alpha, + })); + } + + let (i, name) = take_while(|c: u8| c.is_ascii_alphanumeric() || c == b'-')(i)?; + Ok((i, name.as_bstr())) +} + +fn config_value<'a>(i: &'a [u8], dispatch: &mut impl FnMut(Event<'a>)) -> IResult<&'a [u8], usize> { + if let (i, Some(_)) = opt(char('='))(i)? { + dispatch(Event::KeyValueSeparator); + let (i, whitespace) = opt(take_spaces)(i)?; + if let Some(whitespace) = whitespace { + dispatch(Event::Whitespace(Cow::Borrowed(whitespace))); + } + let (i, newlines) = value_impl(i, dispatch)?; + Ok((i, newlines)) + } else { + // This is a special way of denoting 'empty' values which a lot of code depends on. + // Hence, rather to fix this everywhere else, leave it here and fix it where it matters, namely + // when it's about differentiating between a missing key-value separator, and one followed by emptiness. + dispatch(Event::Value(Cow::Borrowed("".into()))); + Ok((i, 0)) + } +} + +/// Handles parsing of known-to-be values. This function handles both single +/// line values as well as values that are continuations. +fn value_impl<'a>(i: &'a [u8], dispatch: &mut impl FnMut(Event<'a>)) -> IResult<&'a [u8], usize> { + let (i, value_end, newlines, mut dispatch) = { + let new_err = |code| nom::Err::Error(NomError { input: i, code }); + let mut value_end = None::<usize>; + let mut value_start: usize = 0; + let mut newlines = 0; + + let mut prev_char_was_backslash = false; + // This is required to ignore comment markers if they're in a quote. + let mut is_in_quotes = false; + // Used to determine if we return a Value or Value{Not,}Done + let mut partial_value_found = false; + let mut last_value_index: usize = 0; + + let mut bytes = i.iter(); + while let Some(mut c) = bytes.next() { + if prev_char_was_backslash { + prev_char_was_backslash = false; + let mut consumed = 1; + if *c == b'\r' { + c = bytes.next().ok_or_else(|| new_err(ErrorKind::Escaped))?; + if *c != b'\n' { + return Err(new_err(ErrorKind::Tag)); + } + consumed += 1; + } + + match c { + b'\n' => { + partial_value_found = true; + let backslash = 1; + dispatch(Event::ValueNotDone(Cow::Borrowed( + i[value_start..last_value_index - backslash].as_bstr(), + ))); + let nl_end = last_value_index + consumed; + dispatch(Event::Newline(Cow::Borrowed(i[last_value_index..nl_end].as_bstr()))); + value_start = nl_end; + value_end = None; + newlines += 1; + + last_value_index += consumed; + } + b'n' | b't' | b'\\' | b'b' | b'"' => { + last_value_index += 1; + } + _ => { + return Err(new_err(ErrorKind::Escaped)); + } + } + } else { + match c { + b'\n' => { + value_end = last_value_index.into(); + break; + } + b';' | b'#' if !is_in_quotes => { + value_end = last_value_index.into(); + break; + } + b'\\' => prev_char_was_backslash = true, + b'"' => is_in_quotes = !is_in_quotes, + _ => {} + } + last_value_index += 1; + } + } + + if prev_char_was_backslash { + return Err(new_err(ErrorKind::Escaped)); + } + + if is_in_quotes { + return Err(new_err(ErrorKind::Tag)); + } + + let value_end = match value_end { + None => { + if last_value_index == 0 { + dispatch(Event::Value(Cow::Borrowed("".into()))); + return Ok((&i[0..], newlines)); + } else { + i.len() + } + } + Some(idx) => idx, + }; + + let dispatch = move |value: &'a [u8]| { + if partial_value_found { + dispatch(Event::ValueDone(Cow::Borrowed(value.as_bstr()))); + } else { + dispatch(Event::Value(Cow::Borrowed(value.as_bstr()))); + } + }; + (&i[value_start..], value_end - value_start, newlines, dispatch) + }; + + let (i, remainder_value) = { + let value_end_no_trailing_whitespace = i[..value_end] + .iter() + .enumerate() + .rev() + .find_map(|(idx, b)| (!b.is_ascii_whitespace()).then_some(idx + 1)) + .unwrap_or(0); + ( + &i[value_end_no_trailing_whitespace..], + &i[..value_end_no_trailing_whitespace], + ) + }; + + dispatch(remainder_value); + + Ok((i, newlines)) +} + +fn take_spaces(i: &[u8]) -> IResult<&[u8], &BStr> { + let (i, v) = take_while(|c: u8| c.is_ascii() && is_space(c))(i)?; + if v.is_empty() { + Err(nom::Err::Error(NomError { + input: i, + code: ErrorKind::Eof, + })) + } else { + Ok((i, v.as_bstr())) + } +} + +fn take_newlines(i: &[u8]) -> IResult<&[u8], (&BStr, usize)> { + let mut counter = 0; + let mut consumed_bytes = 0; + let mut next_must_be_newline = false; + for b in i.iter().copied() { + if !b.is_ascii() { + break; + }; + if b == b'\r' { + if next_must_be_newline { + break; + } + next_must_be_newline = true; + continue; + }; + if b == b'\n' { + counter += 1; + consumed_bytes += if next_must_be_newline { 2 } else { 1 }; + next_must_be_newline = false; + } else { + break; + } + } + let (v, i) = i.split_at(consumed_bytes); + if v.is_empty() { + Err(nom::Err::Error(NomError { + input: i, + code: ErrorKind::Eof, + })) + } else { + Ok((i, (v.as_bstr(), counter))) + } +} diff --git a/vendor/gix-config/src/parse/nom/tests.rs b/vendor/gix-config/src/parse/nom/tests.rs new file mode 100644 index 000000000..f6e8c3d92 --- /dev/null +++ b/vendor/gix-config/src/parse/nom/tests.rs @@ -0,0 +1,924 @@ +use super::*; + +mod section_headers { + use super::section_header; + use crate::parse::tests::util::{fully_consumed, section_header as parsed_section_header}; + + #[test] + fn no_subsection() { + assert_eq!( + section_header(b"[hello]").unwrap(), + fully_consumed(parsed_section_header("hello", None)), + ); + } + + #[test] + fn modern_subsection() { + assert_eq!( + section_header(br#"[hello "world"]"#).unwrap(), + fully_consumed(parsed_section_header("hello", (" ", "world"))), + ); + } + + #[test] + fn escaped_subsection() { + assert_eq!( + section_header(br#"[hello "foo\\bar\""]"#).unwrap(), + fully_consumed(parsed_section_header("hello", (" ", r#"foo\bar""#))), + ); + } + + #[test] + fn deprecated_subsection() { + assert_eq!( + section_header(br#"[hello.world]"#).unwrap(), + fully_consumed(parsed_section_header("hello", (".", "world"))) + ); + assert_eq!( + section_header(br#"[Hello.World]"#).unwrap(), + fully_consumed(parsed_section_header("Hello", (".", "World"))) + ); + } + + #[test] + fn empty_legacy_subsection_name() { + assert_eq!( + section_header(br#"[hello-world.]"#).unwrap(), + fully_consumed(parsed_section_header("hello-world", (".", ""))) + ); + } + + #[test] + fn empty_modern_subsection_name() { + assert_eq!( + section_header(br#"[hello ""]"#).unwrap(), + fully_consumed(parsed_section_header("hello", (" ", ""))) + ); + } + + #[test] + fn backslashes_in_subsections_do_not_escape_newlines_or_tabs() { + assert_eq!( + section_header(br#"[hello "single \ \\ \t \n \0"]"#).unwrap(), + fully_consumed(parsed_section_header("hello", (" ", r#"single \ t n 0"#))) + ); + } + + #[test] + fn newline_in_header() { + assert!(section_header(b"[hello\n]").is_err()); + } + + #[test] + fn newline_in_sub_section() { + assert!(section_header(b"[hello \"hello\n\"]").is_err()); + } + + #[test] + fn null_byt_in_sub_section() { + assert!(section_header(b"[hello \"hello\0\"]").is_err()); + } + + #[test] + fn escaped_newline_in_sub_section() { + assert!(section_header(b"[hello \"hello\\\n\"]").is_err()); + } + + #[test] + fn eof_after_escape_in_sub_section() { + assert!(section_header(b"[hello \"hello\\").is_err()); + } + + #[test] + fn null_byte_in_header() { + assert!(section_header(b"[hello\0]").is_err()); + } + + #[test] + fn invalid_characters_in_section() { + assert!(section_header(b"[$]").is_err()); + } + #[test] + fn invalid_characters_in_legacy_sub_section() { + assert!(section_header(b"[hello.$]").is_err()); + assert!(section_header(b"[hello. world]").is_err()); + } + + #[test] + fn right_brace_in_subsection_name() { + assert_eq!( + section_header(br#"[hello "]"]"#).unwrap(), + fully_consumed(parsed_section_header("hello", (" ", "]"))) + ); + } +} + +mod sub_section { + use std::borrow::Cow; + + use super::sub_section; + + #[test] + fn zero_copy_simple() { + let actual = sub_section(b"name\"").unwrap().1; + assert_eq!(actual.as_ref(), "name"); + assert!(matches!(actual, Cow::Borrowed(_))); + } + + #[test] + fn escapes_need_allocation() { + let actual = sub_section(br#"\x\t\n\0\\\"""#).unwrap().1; + assert_eq!(actual.as_ref(), r#"xtn0\""#); + assert!(matches!(actual, Cow::Owned(_))); + } +} + +mod config_name { + use nom::combinator::all_consuming; + + use super::config_name; + use crate::parse::tests::util::fully_consumed; + + #[test] + fn just_name() { + assert_eq!(config_name(b"name").unwrap(), fully_consumed("name".into())); + } + + #[test] + fn must_start_with_alphabetic() { + assert!(config_name(b"4aaa").is_err()); + assert!(config_name(b"-aaa").is_err()); + } + + #[test] + fn only_a_subset_of_characters_is_allowed() { + assert!(all_consuming(config_name)(b"Name$_").is_err()); + assert!(all_consuming(config_name)(b"other#").is_err()); + } + + #[test] + fn cannot_be_empty() { + assert!(config_name(b"").is_err()); + } +} + +mod section { + use crate::parse::{ + error::ParseNode, + section, + tests::util::{ + comment_event, fully_consumed, name_event, newline_custom_event, newline_event, + section_header as parsed_section_header, value_done_event, value_event, value_not_done_event, + whitespace_event, + }, + Event, Section, + }; + + fn section<'a>(i: &'a [u8], node: &mut ParseNode) -> nom::IResult<&'a [u8], (Section<'a>, usize)> { + let mut header = None; + let mut events = section::Events::default(); + super::section(i, node, &mut |e| match &header { + None => { + header = Some(e); + } + Some(_) => events.push(e), + }) + .map(|(i, o)| { + ( + i, + ( + Section { + header: match header.expect("header set") { + Event::SectionHeader(header) => header, + _ => unreachable!("unexpected"), + }, + events, + }, + o, + ), + ) + }) + } + + #[test] + fn empty_value_with_windows_newlines() { + let mut node = ParseNode::SectionHeader; + assert_eq!( + section(b"[a] k = \r\n", &mut node).unwrap(), + fully_consumed(( + Section { + header: parsed_section_header("a", None), + events: vec![ + whitespace_event(" "), + name_event("k"), + whitespace_event(" "), + Event::KeyValueSeparator, + whitespace_event(" "), + value_event(""), + newline_custom_event("\r\n") + ] + .into(), + }, + 1 + )), + ); + } + + #[test] + fn simple_value_with_windows_newlines() { + let mut node = ParseNode::SectionHeader; + assert_eq!( + section(b"[a] k = v\r\n", &mut node).unwrap(), + fully_consumed(( + Section { + header: parsed_section_header("a", None), + events: vec![ + whitespace_event(" "), + name_event("k"), + whitespace_event(" "), + Event::KeyValueSeparator, + whitespace_event(" "), + value_event("v"), + newline_custom_event("\r\n") + ] + .into(), + }, + 1 + )), + ); + assert_eq!( + section(b"[a] k = \r\n", &mut node).unwrap(), + fully_consumed(( + Section { + header: parsed_section_header("a", None), + events: vec![ + whitespace_event(" "), + name_event("k"), + whitespace_event(" "), + Event::KeyValueSeparator, + whitespace_event(" "), + value_event(""), + newline_custom_event("\r\n") + ] + .into(), + }, + 1 + )), + ); + } + + #[test] + fn empty_section() { + let mut node = ParseNode::SectionHeader; + assert_eq!( + section(b"[test]", &mut node).unwrap(), + fully_consumed(( + Section { + header: parsed_section_header("test", None), + events: Default::default() + }, + 0 + )), + ); + } + + #[test] + fn simple_section() { + let mut node = ParseNode::SectionHeader; + let section_data = br#"[hello] + a = b + c + d = "lol""#; + assert_eq!( + section(section_data, &mut node).unwrap(), + fully_consumed(( + Section { + header: parsed_section_header("hello", None), + events: vec![ + newline_event(), + whitespace_event(" "), + name_event("a"), + whitespace_event(" "), + Event::KeyValueSeparator, + whitespace_event(" "), + value_event("b"), + newline_event(), + whitespace_event(" "), + name_event("c"), + value_event(""), + newline_event(), + whitespace_event(" "), + name_event("d"), + whitespace_event(" "), + Event::KeyValueSeparator, + whitespace_event(" "), + value_event("\"lol\"") + ] + .into() + }, + 3 + )) + ); + } + + #[test] + fn section_with_empty_value_simplified() { + let mut node = ParseNode::SectionHeader; + let section_data = b"[a] k="; + assert_eq!( + section(section_data, &mut node).unwrap(), + fully_consumed(( + Section { + header: parsed_section_header("a", None), + events: vec![ + whitespace_event(" "), + name_event("k"), + Event::KeyValueSeparator, + value_event(""), + ] + .into() + }, + 0 + )) + ); + + let section_data = b"[a] k=\n"; + assert_eq!( + section(section_data, &mut node).unwrap(), + fully_consumed(( + Section { + header: parsed_section_header("a", None), + events: vec![ + whitespace_event(" "), + name_event("k"), + Event::KeyValueSeparator, + value_event(""), + newline_event(), + ] + .into() + }, + 1 + )) + ); + } + + #[test] + fn section_with_empty_value() { + let mut node = ParseNode::SectionHeader; + let section_data = br#"[hello] + a = b + c= + d = "lol""#; + assert_eq!( + section(section_data, &mut node).unwrap(), + fully_consumed(( + Section { + header: parsed_section_header("hello", None), + events: vec![ + newline_event(), + whitespace_event(" "), + name_event("a"), + whitespace_event(" "), + Event::KeyValueSeparator, + whitespace_event(" "), + value_event("b"), + newline_event(), + whitespace_event(" "), + name_event("c"), + Event::KeyValueSeparator, + value_event(""), + newline_event(), + whitespace_event(" "), + name_event("d"), + whitespace_event(" "), + Event::KeyValueSeparator, + whitespace_event(" "), + value_event("\"lol\"") + ] + .into() + }, + 3 + )) + ); + } + + #[test] + fn section_implicit_value() { + let mut node = ParseNode::SectionHeader; + assert_eq!( + section(b"[hello] c", &mut node).unwrap(), + fully_consumed(( + Section { + header: parsed_section_header("hello", None), + events: vec![whitespace_event(" "), name_event("c"), value_event("")].into() + }, + 0 + )) + ); + + assert_eq!( + section(b"[hello] c\nd", &mut node).unwrap(), + fully_consumed(( + Section { + header: parsed_section_header("hello", None), + events: vec![ + whitespace_event(" "), + name_event("c"), + value_event(""), + newline_event(), + name_event("d"), + value_event("") + ] + .into() + }, + 1 + )) + ); + } + + #[test] + fn section_very_commented() { + let mut node = ParseNode::SectionHeader; + let section_data = br#"[hello] ; commentA + a = b # commentB + ; commentC + ; commentD + c = d"#; + assert_eq!( + section(section_data, &mut node).unwrap(), + fully_consumed(( + Section { + header: parsed_section_header("hello", None), + events: vec![ + whitespace_event(" "), + comment_event(';', " commentA"), + newline_event(), + whitespace_event(" "), + name_event("a"), + whitespace_event(" "), + Event::KeyValueSeparator, + whitespace_event(" "), + value_event("b"), + whitespace_event(" "), + comment_event('#', " commentB"), + newline_event(), + whitespace_event(" "), + comment_event(';', " commentC"), + newline_event(), + whitespace_event(" "), + comment_event(';', " commentD"), + newline_event(), + whitespace_event(" "), + name_event("c"), + whitespace_event(" "), + Event::KeyValueSeparator, + whitespace_event(" "), + value_event("d"), + ] + .into() + }, + 4 + )) + ); + } + + #[test] + fn complex_continuation() { + let mut node = ParseNode::SectionHeader; + // This test is absolute hell. Good luck if this fails. + assert_eq!( + section(b"[section] a = 1 \"\\\"\\\na ; e \"\\\"\\\nd # \"b\t ; c", &mut node).unwrap(), + fully_consumed(( + Section { + header: parsed_section_header("section", None), + events: vec![ + whitespace_event(" "), + name_event("a"), + whitespace_event(" "), + Event::KeyValueSeparator, + whitespace_event(" "), + value_not_done_event(r#"1 "\""#), + newline_event(), + value_not_done_event(r#"a ; e "\""#), + newline_event(), + value_done_event("d"), + whitespace_event(" "), + comment_event('#', " \"b\t ; c"), + ] + .into() + }, + 2 + )) + ); + } + + #[test] + fn quote_split_over_two_lines() { + let mut node = ParseNode::SectionHeader; + assert_eq!( + section(b"[section \"a\"] b =\"\\\n;\";a", &mut node).unwrap(), + fully_consumed(( + Section { + header: parsed_section_header("section", (" ", "a")), + events: vec![ + whitespace_event(" "), + name_event("b"), + whitespace_event(" "), + Event::KeyValueSeparator, + value_not_done_event("\""), + newline_event(), + value_done_event(";\""), + comment_event(';', "a"), + ] + .into() + }, + 1 + )) + ); + } + + #[test] + fn section_handles_extraneous_whitespace_before_comment() { + let mut node = ParseNode::SectionHeader; + assert_eq!( + section(b"[s]hello #world", &mut node).unwrap(), + fully_consumed(( + Section { + header: parsed_section_header("s", None), + events: vec![ + name_event("hello"), + whitespace_event(" "), + value_event(""), + comment_event('#', "world"), + ] + .into() + }, + 0 + )) + ); + } +} + +mod value_continuation { + use bstr::ByteSlice; + + use crate::parse::{ + section, + tests::util::{into_events, newline_custom_event, newline_event, value_done_event, value_not_done_event}, + }; + + pub fn value_impl<'a>(i: &'a [u8], events: &mut section::Events<'a>) -> nom::IResult<&'a [u8], ()> { + super::value_impl(i, &mut |e| events.push(e)).map(|t| (t.0, ())) + } + + #[test] + fn simple_continuation() { + let mut events = section::Events::default(); + assert_eq!(value_impl(b"hello\\\nworld", &mut events).unwrap().0, b""); + assert_eq!( + events, + into_events(vec![ + value_not_done_event("hello"), + newline_event(), + value_done_event("world") + ]) + ); + } + + #[test] + fn continuation_with_whitespace() { + let mut events = section::Events::default(); + assert_eq!(value_impl(b"hello\\\n world", &mut events).unwrap().0, b""); + assert_eq!( + events, + into_events(vec![ + value_not_done_event("hello"), + newline_event(), + value_done_event(" world") + ]) + ); + + let mut events = section::Events::default(); + assert_eq!(value_impl(b"hello\\\r\n world", &mut events).unwrap().0, b""); + assert_eq!( + events, + into_events(vec![ + value_not_done_event("hello"), + newline_custom_event("\r\n"), + value_done_event(" world") + ]) + ); + + let mut events = section::Events::default(); + assert!( + value_impl(b"hello\\\r\r\n world", &mut events).is_err(), + "\\r must be followed by \\n" + ); + } + + #[test] + fn complex_continuation_with_leftover_comment() { + let mut events = section::Events::default(); + assert_eq!( + value_impl(b"1 \"\\\"\\\na ; e \"\\\"\\\nd # \"b\t ; c", &mut events) + .unwrap() + .0, + b" # \"b\t ; c" + ); + assert_eq!( + events, + into_events(vec![ + value_not_done_event(r#"1 "\""#), + newline_event(), + value_not_done_event(r#"a ; e "\""#), + newline_event(), + value_done_event("d") + ]) + ); + } + + #[test] + fn quote_split_over_two_lines_with_leftover_comment() { + let mut events = section::Events::default(); + assert_eq!(value_impl(b"\"\\\n;\";a", &mut events).unwrap().0, b";a"); + assert_eq!( + events, + into_events(vec![ + value_not_done_event("\""), + newline_event(), + value_done_event(";\"") + ]) + ); + + let mut events = section::Events::default(); + assert_eq!(value_impl(b"\"a\\\r\nb;\";c", &mut events).unwrap().0, b";c"); + assert_eq!( + events, + into_events(vec![ + value_not_done_event("\"a"), + newline_custom_event("\r\n"), + value_done_event("b;\"") + ]) + ); + } + + #[test] + fn quote_split_over_multiple_lines_without_surrounding_quotes_but_inner_quotes() { + let mut events = section::Events::default(); + assert_eq!( + value_impl( + br#"1\ +"2" a\ +\"3 b\"\ +4 ; comment "#, + &mut events + ) + .unwrap() + .0 + .as_bstr(), + b" ; comment ".as_bstr() + ); + assert_eq!( + events, + into_events(vec![ + value_not_done_event("1"), + newline_event(), + value_not_done_event("\"2\" a"), + newline_event(), + value_not_done_event("\\\"3 b\\\""), + newline_event(), + value_done_event("4") + ]) + ); + } + + #[test] + fn quote_split_over_multiple_lines_with_surrounding_quotes() { + let mut events = section::Events::default(); + assert_eq!( + value_impl( + br#""1\ +"2" a\ +\"3 b\"\ +4 " ; comment "#, + &mut events + ) + .unwrap() + .0 + .as_bstr(), + b" ; comment ".as_bstr() + ); + assert_eq!( + events, + into_events(vec![ + value_not_done_event("\"1"), + newline_event(), + value_not_done_event("\"2\" a"), + newline_event(), + value_not_done_event("\\\"3 b\\\""), + newline_event(), + value_done_event("4 \"") + ]) + ); + } +} + +mod value_no_continuation { + use super::value_continuation::value_impl; + use crate::parse::{ + section, + tests::util::{into_events, value_event}, + }; + + #[test] + fn no_comment() { + let mut events = section::Events::default(); + assert_eq!(value_impl(b"hello", &mut events).unwrap().0, b""); + assert_eq!(events, into_events(vec![value_event("hello")])); + } + + #[test] + fn windows_newline() { + let mut events = section::Events::default(); + assert_eq!(value_impl(b"hi\r\nrest", &mut events).unwrap().0, b"\r\nrest"); + assert_eq!(events, into_events(vec![value_event("hi")])); + + events.clear(); + assert_eq!(value_impl(b"hi\r\r\r\nrest", &mut events).unwrap().0, b"\r\r\r\nrest"); + assert_eq!(events, into_events(vec![value_event("hi")])); + } + + #[test] + fn no_comment_newline() { + let mut events = section::Events::default(); + assert_eq!(value_impl(b"hello\na", &mut events).unwrap().0, b"\na"); + assert_eq!(events, into_events(vec![value_event("hello")])); + } + + #[test] + fn semicolon_comment_not_consumed() { + let mut events = section::Events::default(); + assert_eq!(value_impl(b"hello;world", &mut events).unwrap().0, b";world"); + assert_eq!(events, into_events(vec![value_event("hello")])); + } + + #[test] + fn octothorpe_comment_not_consumed() { + let mut events = section::Events::default(); + assert_eq!(value_impl(b"hello#world", &mut events).unwrap().0, b"#world"); + assert_eq!(events, into_events(vec![value_event("hello")])); + } + + #[test] + fn values_with_extraneous_whitespace_without_comment() { + let mut events = section::Events::default(); + assert_eq!( + value_impl(b"hello ", &mut events).unwrap().0, + b" " + ); + assert_eq!(events, into_events(vec![value_event("hello")])); + } + + #[test] + fn values_with_extraneous_whitespace_before_comment() { + let mut events = section::Events::default(); + assert_eq!( + value_impl(b"hello #world", &mut events).unwrap().0, + b" #world" + ); + assert_eq!(events, into_events(vec![value_event("hello")])); + + let mut events = section::Events::default(); + assert_eq!( + value_impl(b"hello ;world", &mut events).unwrap().0, + b" ;world" + ); + assert_eq!(events, into_events(vec![value_event("hello")])); + } + + #[test] + fn trans_escaped_comment_marker_not_consumed() { + let mut events = section::Events::default(); + assert_eq!(value_impl(br##"hello"#"world; a"##, &mut events).unwrap().0, b"; a"); + assert_eq!(events, into_events(vec![value_event(r##"hello"#"world"##)])); + } + + #[test] + fn complex_test() { + let mut events = section::Events::default(); + assert_eq!(value_impl(br#"value";";ahhhh"#, &mut events).unwrap().0, b";ahhhh"); + assert_eq!(events, into_events(vec![value_event(r#"value";""#)])); + } + + #[test] + fn garbage_after_continuation_is_err() { + assert!(value_impl(b"hello \\afwjdls", &mut Default::default()).is_err()); + } + + #[test] + fn invalid_escape() { + assert!(value_impl(br#"\x"#, &mut Default::default()).is_err()); + } + + #[test] + fn incomplete_quote() { + assert!(value_impl(br#"hello "world"#, &mut Default::default()).is_err()); + } + + #[test] + fn incomplete_escape() { + assert!(value_impl(br#"hello world\"#, &mut Default::default()).is_err()); + } +} + +mod key_value_pair { + use crate::parse::{ + error::ParseNode, + section, + tests::util::{into_events, name_event, value_event, whitespace_event}, + Event, + }; + + fn key_value<'a>( + i: &'a [u8], + node: &mut ParseNode, + events: &mut section::Events<'a>, + ) -> nom::IResult<&'a [u8], ()> { + super::key_value_pair(i, node, &mut |e| events.push(e)).map(|t| (t.0, ())) + } + + #[test] + fn nonascii_is_allowed_for_values_but_not_for_keys() { + let mut node = ParseNode::SectionHeader; + let mut vec = Default::default(); + assert!(key_value("你好".as_bytes(), &mut node, &mut vec).is_err()); + assert!(key_value("a = 你好 ".as_bytes(), &mut node, &mut vec).is_ok()); + assert_eq!( + vec, + into_events(vec![ + name_event("a"), + whitespace_event(" "), + Event::KeyValueSeparator, + whitespace_event(" "), + value_event("你好") + ]) + ); + } + + #[test] + fn whitespace_is_not_ambiguous() { + let mut node = ParseNode::SectionHeader; + let mut vec = Default::default(); + assert!(key_value(b"a =b", &mut node, &mut vec).is_ok()); + assert_eq!( + vec, + into_events(vec![ + name_event("a"), + whitespace_event(" "), + Event::KeyValueSeparator, + value_event("b") + ]) + ); + + let mut vec = Default::default(); + assert!(key_value(b"a= b", &mut node, &mut vec).is_ok()); + assert_eq!( + vec, + into_events(vec![ + name_event("a"), + Event::KeyValueSeparator, + whitespace_event(" "), + value_event("b") + ]) + ); + } +} + +mod comment { + use super::comment; + use crate::parse::tests::util::{comment as parsed_comment, fully_consumed}; + + #[test] + fn semicolon() { + assert_eq!( + comment(b"; this is a semicolon comment").unwrap(), + fully_consumed(parsed_comment(';', " this is a semicolon comment")), + ); + } + + #[test] + fn octothorpe() { + assert_eq!( + comment(b"# this is an octothorpe comment").unwrap(), + fully_consumed(parsed_comment('#', " this is an octothorpe comment")), + ); + } + + #[test] + fn multiple_markers() { + assert_eq!( + comment(b"###### this is an octothorpe comment").unwrap(), + fully_consumed(parsed_comment('#', "##### this is an octothorpe comment")), + ); + } +} diff --git a/vendor/gix-config/src/parse/section/header.rs b/vendor/gix-config/src/parse/section/header.rs new file mode 100644 index 000000000..341edcdd5 --- /dev/null +++ b/vendor/gix-config/src/parse/section/header.rs @@ -0,0 +1,180 @@ +use std::{borrow::Cow, fmt::Display}; + +use bstr::{BStr, BString, ByteSlice, ByteVec}; + +use crate::parse::{ + section::{into_cow_bstr, Header, Name}, + Event, +}; + +/// The error returned by [`Header::new(…)`][super::Header::new()]. +#[derive(Debug, PartialOrd, PartialEq, Eq, thiserror::Error)] +#[allow(missing_docs)] +pub enum Error { + #[error("section names can only be ascii, '-'")] + InvalidName, + #[error("sub-section names must not contain newlines or null bytes")] + InvalidSubSection, +} + +impl<'a> Header<'a> { + /// Instantiate a new header either with a section `name`, e.g. "core" serializing to `["core"]` + /// or `[remote "origin"]` for `subsection` being "origin" and `name` being "remote". + pub fn new( + name: impl Into<Cow<'a, str>>, + subsection: impl Into<Option<Cow<'a, BStr>>>, + ) -> Result<Header<'a>, Error> { + let name = Name(validated_name(into_cow_bstr(name.into()))?); + if let Some(subsection_name) = subsection.into() { + Ok(Header { + name, + separator: Some(Cow::Borrowed(" ".into())), + subsection_name: Some(validated_subsection(subsection_name)?), + }) + } else { + Ok(Header { + name, + separator: None, + subsection_name: None, + }) + } + } +} + +/// Return true if `name` is valid as subsection name, like `origin` in `[remote "origin"]`. +pub fn is_valid_subsection(name: &BStr) -> bool { + name.find_byteset(b"\n\0").is_none() +} + +fn validated_subsection(name: Cow<'_, BStr>) -> Result<Cow<'_, BStr>, Error> { + is_valid_subsection(name.as_ref()) + .then_some(name) + .ok_or(Error::InvalidSubSection) +} + +fn validated_name(name: Cow<'_, BStr>) -> Result<Cow<'_, BStr>, Error> { + name.iter() + .all(|b| b.is_ascii_alphanumeric() || *b == b'-') + .then_some(name) + .ok_or(Error::InvalidName) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn empty_header_names_are_legal() { + assert!(Header::new("", None).is_ok(), "yes, git allows this, so do we"); + } + + #[test] + fn empty_header_sub_names_are_legal() { + assert!( + Header::new("remote", Some(Cow::Borrowed("".into()))).is_ok(), + "yes, git allows this, so do we" + ); + } +} + +impl Header<'_> { + ///Return true if this is a header like `[legacy.subsection]`, or false otherwise. + pub fn is_legacy(&self) -> bool { + self.separator.as_deref().map_or(false, |n| n == ".") + } + + /// Return the subsection name, if present, i.e. "origin" in `[remote "origin"]`. + /// + /// It is parsed without quotes, and with escapes folded + /// into their resulting characters. + /// Thus during serialization, escapes and quotes must be re-added. + /// This makes it possible to use [`Event`] data for lookups directly. + pub fn subsection_name(&self) -> Option<&BStr> { + self.subsection_name.as_deref() + } + + /// Return the name of the header, like "remote" in `[remote "origin"]`. + pub fn name(&self) -> &BStr { + &self.name + } + + /// Serialize this type into a `BString` for convenience. + /// + /// Note that `to_string()` can also be used, but might not be lossless. + #[must_use] + pub fn to_bstring(&self) -> BString { + let mut buf = Vec::new(); + self.write_to(&mut buf).expect("io error impossible"); + buf.into() + } + + /// Stream ourselves to the given `out`, in order to reproduce this header mostly losslessly + /// as it was parsed. + pub fn write_to(&self, mut out: impl std::io::Write) -> std::io::Result<()> { + out.write_all(b"[")?; + out.write_all(&self.name)?; + + if let (Some(sep), Some(subsection)) = (&self.separator, &self.subsection_name) { + let sep = sep.as_ref(); + out.write_all(sep)?; + if sep == "." { + out.write_all(subsection.as_ref())?; + } else { + out.write_all(b"\"")?; + out.write_all(escape_subsection(subsection.as_ref()).as_ref())?; + out.write_all(b"\"")?; + } + } + + out.write_all(b"]") + } + + /// Turn this instance into a fully owned one with `'static` lifetime. + #[must_use] + pub fn to_owned(&self) -> Header<'static> { + Header { + name: self.name.to_owned(), + separator: self.separator.clone().map(|v| Cow::Owned(v.into_owned())), + subsection_name: self.subsection_name.clone().map(|v| Cow::Owned(v.into_owned())), + } + } +} + +fn escape_subsection(name: &BStr) -> Cow<'_, BStr> { + if name.find_byteset(b"\\\"").is_none() { + return name.into(); + } + let mut buf = Vec::with_capacity(name.len()); + for b in name.iter().copied() { + match b { + b'\\' => buf.push_str(br#"\\"#), + b'"' => buf.push_str(br#"\""#), + _ => buf.push(b), + } + } + BString::from(buf).into() +} + +impl Display for Header<'_> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + Display::fmt(&self.to_bstring(), f) + } +} + +impl From<Header<'_>> for BString { + fn from(header: Header<'_>) -> Self { + header.into() + } +} + +impl From<&Header<'_>> for BString { + fn from(header: &Header<'_>) -> Self { + header.to_bstring() + } +} + +impl<'a> From<Header<'a>> for Event<'a> { + fn from(header: Header<'_>) -> Event<'_> { + Event::SectionHeader(header) + } +} diff --git a/vendor/gix-config/src/parse/section/mod.rs b/vendor/gix-config/src/parse/section/mod.rs new file mode 100644 index 000000000..7ba08b87d --- /dev/null +++ b/vendor/gix-config/src/parse/section/mod.rs @@ -0,0 +1,187 @@ +use std::{borrow::Cow, fmt::Display}; + +use bstr::BStr; +use smallvec::SmallVec; + +use crate::parse::{Event, Section}; + +/// +pub mod header; + +pub(crate) mod unvalidated; + +/// A container for events, avoiding heap allocations in typical files. +pub type Events<'a> = SmallVec<[Event<'a>; 64]>; + +/// A parsed section header, containing a name and optionally a subsection name. +#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)] +pub struct Header<'a> { + /// The name of the header. + pub(crate) name: Name<'a>, + /// The separator used to determine if the section contains a subsection. + /// This is either a period `.` or a string of whitespace. Note that + /// reconstruction of subsection format is dependent on this value. If this + /// is all whitespace, then the subsection name needs to be surrounded by + /// quotes to have perfect reconstruction. + pub(crate) separator: Option<Cow<'a, BStr>>, + pub(crate) subsection_name: Option<Cow<'a, BStr>>, +} + +impl Section<'_> { + /// Turn this instance into a fully owned one with `'static` lifetime. + #[must_use] + pub fn to_owned(&self) -> Section<'static> { + Section { + header: self.header.to_owned(), + events: self.events.iter().map(Event::to_owned).collect(), + } + } +} + +impl Display for Section<'_> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.header)?; + for event in &self.events { + event.fmt(f)?; + } + Ok(()) + } +} + +mod types { + macro_rules! generate_case_insensitive { + ($name:ident, $module:ident, $err_doc:literal, $validate:ident, $cow_inner_type:ty, $comment:literal) => { + /// + pub mod $module { + /// The error returned when `TryFrom` is invoked to create an instance. + #[derive(Debug, thiserror::Error, Copy, Clone)] + #[error($err_doc)] + pub struct Error; + } + + #[doc = $comment] + #[derive(Clone, Eq, Debug, Default)] + pub struct $name<'a>(pub(crate) std::borrow::Cow<'a, $cow_inner_type>); + + impl<'a> $name<'a> { + pub(crate) fn from_str_unchecked(s: &'a str) -> Self { + $name(std::borrow::Cow::Borrowed(s.into())) + } + /// Turn this instance into a fully owned one with `'static` lifetime. + #[must_use] + pub fn to_owned(&self) -> $name<'static> { + $name(std::borrow::Cow::Owned(self.0.clone().into_owned())) + } + } + + impl PartialEq for $name<'_> { + fn eq(&self, other: &Self) -> bool { + self.0.eq_ignore_ascii_case(&other.0) + } + } + + impl std::fmt::Display for $name<'_> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + self.0.fmt(f) + } + } + + impl PartialOrd for $name<'_> { + fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> { + self.cmp(other).into() + } + } + + impl Ord for $name<'_> { + fn cmp(&self, other: &Self) -> std::cmp::Ordering { + let a = self.0.iter().map(|c| c.to_ascii_lowercase()); + let b = other.0.iter().map(|c| c.to_ascii_lowercase()); + a.cmp(b) + } + } + + impl std::hash::Hash for $name<'_> { + fn hash<H: std::hash::Hasher>(&self, state: &mut H) { + for b in self.0.iter() { + b.to_ascii_lowercase().hash(state); + } + } + } + + impl<'a> std::convert::TryFrom<&'a str> for $name<'a> { + type Error = $module::Error; + + fn try_from(s: &'a str) -> Result<Self, Self::Error> { + Self::try_from(std::borrow::Cow::Borrowed(bstr::ByteSlice::as_bstr(s.as_bytes()))) + } + } + + impl<'a> std::convert::TryFrom<String> for $name<'a> { + type Error = $module::Error; + + fn try_from(s: String) -> Result<Self, Self::Error> { + Self::try_from(std::borrow::Cow::Owned(bstr::BString::from(s))) + } + } + + impl<'a> std::convert::TryFrom<std::borrow::Cow<'a, bstr::BStr>> for $name<'a> { + type Error = $module::Error; + + fn try_from(s: std::borrow::Cow<'a, bstr::BStr>) -> Result<Self, Self::Error> { + if $validate(s.as_ref()) { + Ok(Self(s)) + } else { + Err($module::Error) + } + } + } + + impl<'a> std::ops::Deref for $name<'a> { + type Target = $cow_inner_type; + + fn deref(&self) -> &Self::Target { + &self.0 + } + } + + impl<'a> std::convert::AsRef<str> for $name<'a> { + fn as_ref(&self) -> &str { + std::str::from_utf8(self.0.as_ref()).expect("only valid UTF8 makes it through our validation") + } + } + }; + } + + fn is_valid_name(n: &bstr::BStr) -> bool { + !n.is_empty() && n.iter().all(|b| b.is_ascii_alphanumeric() || *b == b'-') + } + fn is_valid_key(n: &bstr::BStr) -> bool { + is_valid_name(n) && n[0].is_ascii_alphabetic() + } + + generate_case_insensitive!( + Name, + name, + "Valid names consist of alphanumeric characters or dashes.", + is_valid_name, + bstr::BStr, + "Wrapper struct for section header names, like `remote`, since these are case-insensitive." + ); + + generate_case_insensitive!( + Key, + key, + "Valid keys consist alphanumeric characters or dashes, starting with an alphabetic character.", + is_valid_key, + bstr::BStr, + "Wrapper struct for key names, like `path` in `include.path`, since keys are case-insensitive." + ); +} +pub use types::{key, name, Key, Name}; + +pub(crate) fn into_cow_bstr(c: Cow<'_, str>) -> Cow<'_, BStr> { + match c { + Cow::Borrowed(s) => Cow::Borrowed(s.into()), + Cow::Owned(s) => Cow::Owned(s.into()), + } +} diff --git a/vendor/gix-config/src/parse/section/unvalidated.rs b/vendor/gix-config/src/parse/section/unvalidated.rs new file mode 100644 index 000000000..1710837fe --- /dev/null +++ b/vendor/gix-config/src/parse/section/unvalidated.rs @@ -0,0 +1,25 @@ +use bstr::{BStr, ByteSlice}; + +/// An unvalidated parse result of a key for a section, parsing input like `remote.origin` or `core`. +#[derive(Debug, PartialEq, Ord, PartialOrd, Eq, Hash, Clone, Copy)] +pub struct Key<'a> { + /// The name of the section, like `remote` in `remote.origin`. + pub section_name: &'a str, + /// The name of the sub-section, like `origin` in `remote.origin`. + pub subsection_name: Option<&'a BStr>, +} + +impl<'a> Key<'a> { + /// Parse `input` like `remote.origin` or `core` as a `Key` to make its section specific fields available, + /// or `None` if there were not one or two tokens separated by `.`. + /// Note that `input` isn't validated, and is `str` as ascii is a subset of UTF-8 which is required for any valid keys. + pub fn parse(input: impl Into<&'a BStr>) -> Option<Self> { + let input = input.into(); + let mut tokens = input.splitn(2, |b| *b == b'.'); + + Some(Key { + section_name: tokens.next()?.to_str().ok()?, + subsection_name: tokens.next().map(Into::into), + }) + } +} diff --git a/vendor/gix-config/src/parse/tests.rs b/vendor/gix-config/src/parse/tests.rs new file mode 100644 index 000000000..2a2853c4c --- /dev/null +++ b/vendor/gix-config/src/parse/tests.rs @@ -0,0 +1,162 @@ +mod section { + + mod header { + mod unvalidated { + use crate::parse::section::unvalidated::Key; + + #[test] + fn section_name_only() { + assert_eq!( + Key::parse("core").unwrap(), + Key { + section_name: "core", + subsection_name: None + } + ); + } + + #[test] + fn section_name_and_subsection() { + assert_eq!( + Key::parse("core.bare").unwrap(), + Key { + section_name: "core", + subsection_name: Some("bare".into()) + } + ); + } + + #[test] + fn section_name_and_subsection_with_separators() { + assert_eq!( + Key::parse("remote.https:///home/user.git").unwrap(), + Key { + section_name: "remote", + subsection_name: Some("https:///home/user.git".into()) + } + ); + } + } + + mod write_to { + use std::borrow::Cow; + + use crate::parse::section; + + fn header(name: &str, subsection: impl Into<Option<(&'static str, &'static str)>>) -> section::Header<'_> { + let name = section::Name(Cow::Borrowed(name.into())); + if let Some((separator, subsection_name)) = subsection.into() { + section::Header { + name, + separator: Some(Cow::Borrowed(separator.into())), + subsection_name: Some(Cow::Borrowed(subsection_name.into())), + } + } else { + section::Header { + name, + separator: None, + subsection_name: None, + } + } + } + + #[test] + fn legacy_subsection_format_does_not_use_escapes() { + let invalid = header("invalid", Some((".", "\\ \""))); + assert_eq!( + invalid.to_bstring(), + "[invalid.\\ \"]", + "no escaping happens for legacy subsections" + ); + assert!(invalid.is_legacy()); + } + + #[test] + fn subsections_escape_two_characters_only() { + let invalid = header("invalid", Some((" ", "\\ \"\npost newline"))); + assert_eq!( + invalid.to_bstring(), + "[invalid \"\\\\ \\\"\npost newline\"]", + "newlines are actually invalid in subsection, but they are possible due to unvalidated instance creation" + ); + assert!(!invalid.is_legacy()); + } + } + } +} + +pub(crate) mod util { + //! This module is only included for tests, and contains common unit test helper + //! functions. + + use std::{borrow::Cow, convert::TryFrom}; + + use crate::parse::{section, Comment, Event}; + + pub fn into_events(events: Vec<Event<'_>>) -> section::Events<'_> { + events.into() + } + + pub fn section_header( + name: &str, + subsection: impl Into<Option<(&'static str, &'static str)>>, + ) -> section::Header<'_> { + let name = section::Name::try_from(name).unwrap(); + if let Some((separator, subsection_name)) = subsection.into() { + section::Header { + name, + separator: Some(Cow::Borrowed(separator.into())), + subsection_name: Some(Cow::Borrowed(subsection_name.into())), + } + } else { + section::Header { + name, + separator: None, + subsection_name: None, + } + } + } + + pub(crate) fn name_event(name: &'static str) -> Event<'static> { + Event::SectionKey(section::Key(Cow::Borrowed(name.into()))) + } + + pub(crate) fn value_event(value: &'static str) -> Event<'static> { + Event::Value(Cow::Borrowed(value.into())) + } + + pub(crate) fn value_not_done_event(value: &'static str) -> Event<'static> { + Event::ValueNotDone(Cow::Borrowed(value.into())) + } + + pub(crate) fn value_done_event(value: &'static str) -> Event<'static> { + Event::ValueDone(Cow::Borrowed(value.into())) + } + + pub(crate) fn newline_event() -> Event<'static> { + newline_custom_event("\n") + } + + pub(crate) fn newline_custom_event(value: &'static str) -> Event<'static> { + Event::Newline(Cow::Borrowed(value.into())) + } + + pub(crate) fn whitespace_event(value: &'static str) -> Event<'static> { + Event::Whitespace(Cow::Borrowed(value.into())) + } + + pub(crate) fn comment_event(tag: char, msg: &'static str) -> Event<'static> { + Event::Comment(comment(tag, msg)) + } + + pub(crate) fn comment(comment_tag: char, comment: &'static str) -> Comment<'static> { + Comment { + tag: comment_tag as u8, + text: Cow::Borrowed(comment.into()), + } + } + + pub(crate) const fn fully_consumed<T>(t: T) -> (&'static [u8], T) { + (&[], t) + } +} diff --git a/vendor/gix-config/src/source.rs b/vendor/gix-config/src/source.rs new file mode 100644 index 000000000..b1991e6b4 --- /dev/null +++ b/vendor/gix-config/src/source.rs @@ -0,0 +1,163 @@ +use std::{ + borrow::Cow, + ffi::OsString, + path::{Path, PathBuf}, +}; + +use crate::Source; + +/// The category of a [`Source`], in order of ascending precedence. +#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)] +pub enum Kind { + /// A special configuration file that ships with the git installation, and is thus tied to the used git binary. + GitInstallation, + /// A source shared for the entire system. + System, + /// Application specific configuration unique for each user of the `System`. + Global, + /// Configuration relevant only to the repository, possibly including the worktree. + Repository, + /// Configuration specified after all other configuration was loaded for the purpose of overrides. + Override, +} + +impl Kind { + /// Return a list of sources associated with this `Kind` of source, in order of ascending precedence. + pub fn sources(self) -> &'static [Source] { + let src = match self { + Kind::GitInstallation => &[Source::GitInstallation] as &[_], + Kind::System => &[Source::System], + Kind::Global => &[Source::Git, Source::User], + Kind::Repository => &[Source::Local, Source::Worktree], + Kind::Override => &[Source::Env, Source::Cli, Source::Api], + }; + debug_assert!( + src.iter().all(|src| src.kind() == self), + "BUG: classification of source has to match the ordering here, see `Source::kind()`" + ); + src + } +} + +impl Source { + /// Return true if the source indicates a location within a file of a repository. + pub const fn kind(self) -> Kind { + use Source::*; + match self { + GitInstallation => Kind::GitInstallation, + System => Kind::System, + Git | User => Kind::Global, + Local | Worktree => Kind::Repository, + Env | Cli | Api | EnvOverride => Kind::Override, + } + } + + /// Returns the location at which a file of this type would be stored, or `None` if + /// there is no notion of persistent storage for this source, with `env_var` to obtain environment variables. + /// Note that the location can be relative for repository-local sources like `Local` and `Worktree`, + /// and the caller has to known which base it it relative to, namely the `common_dir` in the `Local` case + /// and the `git_dir` in the `Worktree` case. + /// Be aware that depending on environment overrides, multiple scopes might return the same path, which should + /// only be loaded once nonetheless. + /// + /// With `env_var` it becomes possible to prevent accessing environment variables entirely to comply with `gix-sec` + /// permissions for example. + pub fn storage_location(self, env_var: &mut dyn FnMut(&str) -> Option<OsString>) -> Option<Cow<'static, Path>> { + use Source::*; + match self { + GitInstallation => git::install_config_path().map(gix_path::from_bstr), + System => env_var("GIT_CONFIG_NO_SYSTEM") + .is_none() + .then(|| PathBuf::from(env_var("GIT_CONFIG_SYSTEM").unwrap_or_else(|| "/etc/gitconfig".into())).into()), + Git => match env_var("GIT_CONFIG_GLOBAL") { + Some(global_override) => Some(PathBuf::from(global_override).into()), + None => env_var("XDG_CONFIG_HOME") + .map(|home| { + let mut p = PathBuf::from(home); + p.push("git"); + p.push("config"); + p + }) + .or_else(|| { + env_var("HOME").map(|home| { + let mut p = PathBuf::from(home); + p.push(".config"); + p.push("git"); + p.push("config"); + p + }) + }) + .map(Cow::Owned), + }, + User => env_var("GIT_CONFIG_GLOBAL") + .map(|global_override| PathBuf::from(global_override).into()) + .or_else(|| { + env_var("HOME").map(|home| { + let mut p = PathBuf::from(home); + p.push(".gitconfig"); + p.into() + }) + }), + Local => Some(Path::new("config").into()), + Worktree => Some(Path::new("config.worktree").into()), + Env | Cli | Api | EnvOverride => None, + } + } +} + +/// Environment information involving the `git` program itself. +mod git { + use std::process::{Command, Stdio}; + + use bstr::{BStr, BString, ByteSlice}; + + /// Returns the file that contains git configuration coming with the installation of the `git` file in the current `PATH`, or `None` + /// if no `git` executable was found or there were other errors during execution. + pub fn install_config_path() -> Option<&'static BStr> { + static PATH: once_cell::sync::Lazy<Option<BString>> = once_cell::sync::Lazy::new(|| { + let mut cmd = Command::new(if cfg!(windows) { "git.exe" } else { "git" }); + cmd.args(["config", "-l", "--show-origin"]) + .stdin(Stdio::null()) + .stderr(Stdio::null()); + first_file_from_config_with_origin(cmd.output().ok()?.stdout.as_slice().into()).map(ToOwned::to_owned) + }); + PATH.as_ref().map(|b| b.as_ref()) + } + + fn first_file_from_config_with_origin(source: &BStr) -> Option<&BStr> { + let file = source.strip_prefix(b"file:")?; + let end_pos = file.find_byte(b'\t')?; + file[..end_pos].as_bstr().into() + } + + #[cfg(test)] + mod tests { + #[test] + fn first_file_from_config_with_origin() { + let macos = "file:/Applications/Xcode.app/Contents/Developer/usr/share/git-core/gitconfig credential.helper=osxkeychain\nfile:/Users/byron/.gitconfig push.default=simple\n"; + let win_msys = + "file:C:/git-sdk-64/etc/gitconfig core.symlinks=false\r\nfile:C:/git-sdk-64/etc/gitconfig core.autocrlf=true"; + let win_cmd = "file:C:/Program Files/Git/etc/gitconfig diff.astextplain.textconv=astextplain\r\nfile:C:/Program Files/Git/etc/gitconfig filter.lfs.clean=gix-lfs clean -- %f\r\n"; + let linux = "file:/home/parallels/.gitconfig core.excludesfile=~/.gitignore\n"; + let bogus = "something unexpected"; + let empty = ""; + + for (source, expected) in [ + ( + macos, + Some("/Applications/Xcode.app/Contents/Developer/usr/share/git-core/gitconfig"), + ), + (win_msys, Some("C:/git-sdk-64/etc/gitconfig")), + (win_cmd, Some("C:/Program Files/Git/etc/gitconfig")), + (linux, Some("/home/parallels/.gitconfig")), + (bogus, None), + (empty, None), + ] { + assert_eq!( + super::first_file_from_config_with_origin(source.into()), + expected.map(Into::into) + ); + } + } + } +} diff --git a/vendor/gix-config/src/types.rs b/vendor/gix-config/src/types.rs new file mode 100644 index 000000000..7110906b8 --- /dev/null +++ b/vendor/gix-config/src/types.rs @@ -0,0 +1,124 @@ +use std::collections::{HashMap, VecDeque}; + +use gix_features::threading::OwnShared; + +use crate::{ + file, + file::{Metadata, SectionBodyIdsLut, SectionId}, + parse::section, +}; + +/// A list of known sources for git configuration in order of ascending precedence. +/// +/// This means values from the first one will be overridden by values in the second one, and so forth. +/// Note that included files via `include.path` and `includeIf.<condition>.path` inherit +/// their source. +#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)] +pub enum Source { + /// A special configuration file that ships with the git installation, and is thus tied to the used git binary. + GitInstallation, + /// System-wide configuration path. This is defined as + /// `$(prefix)/etc/gitconfig` (where prefix is the git-installation directory). + System, + /// A platform defined location for where a user's git application configuration should be located. + /// If `$XDG_CONFIG_HOME` is not set or empty, `$HOME/.config/git/config` will be used + /// on unix. + Git, + /// This is usually `~/.gitconfig` on unix. + User, + /// The configuration of the repository itself, located in `.git/config`. + Local, + /// Configuration specific to a worktree as created with `git worktree` and + /// typically located in `$GIT_DIR/config.worktree` if `extensions.worktreeConfig` + /// is enabled. + Worktree, + /// Values parsed from the environment using `GIT_CONFIG_COUNT`, + /// `GIT_CONFIG_KEY_N` and `GIT_CONFIG_VALUE_N` where `N` is incremented from 0 up to the + /// value of `GIT_CONFIG_COUNT`. + Env, + /// Values set from the command-line, typically controlled by the user running a program. + Cli, + /// Entirely internal from a programmatic source, and can be used to have (near final) say in configuration values. + Api, + /// Values obtained from specific environment variables that override values in the git configuration. + /// + /// For example, `HTTP_PROXY` overrides `http.proxy`, no matter where it is specified, and thus + /// controls the value similar to how it's done in `git`. + EnvOverride, +} + +/// High level `gix-config` reader and writer. +/// +/// This is the full-featured implementation that can deserialize, serialize, +/// and edit `gix-config` files without loss of whitespace or comments. +/// +/// # 'multivar' behavior +/// +/// `git` is flexible enough to allow users to set a key multiple times in +/// any number of identically named sections. When this is the case, the key +/// is known as a _"multivar"_. In this case, [`raw_value()`] follows the +/// "last one wins". +/// +/// Concretely, the following config has a multivar, `a`, with the values +/// of `b`, `c`, and `d`, while `e` is a single variable with the value +/// `f g h`. +/// +/// ```text +/// [core] +/// a = b +/// a = c +/// [core] +/// a = d +/// e = f g h +/// ``` +/// +/// Calling methods that fetch or set only one value (such as [`raw_value()`]) +/// key `a` with the above config will fetch `d` or replace `d`, since the last +/// valid config key/value pair is `a = d`: +/// +/// # Filtering +/// +/// All methods exist in a `*_filter(…, filter)` version to allow skipping sections by +/// their metadata. That way it's possible to select values based on their `gix_sec::Trust` +/// for example, or by their location. +/// +/// Note that the filter may be executed even on sections that don't contain the key in question, +/// even though the section will have matched the `name` and `subsection_name` respectively. +/// +/// ``` +/// # use std::borrow::Cow; +/// # use std::convert::TryFrom; +/// # let gix_config = gix_config::File::try_from("[core]a=b\n[core]\na=c\na=d").unwrap(); +/// assert_eq!(gix_config.raw_value("core", None, "a").unwrap().as_ref(), "d"); +/// ``` +/// +/// Consider the `multi` variants of the methods instead, if you want to work +/// with all values. +/// +/// # Equality +/// +/// In order to make it useful, equality will ignore all non-value bearing information, hence compare +/// only sections and their names, as well as all of their values. The ordering matters, of course. +/// +/// [`raw_value()`]: Self::raw_value +#[derive(Eq, Clone, Debug, Default)] +pub struct File<'event> { + /// The list of events that occur before any section. Since a + /// `gix-config` file prohibits global values, this vec is limited to only + /// comment, newline, and whitespace events. + pub(crate) frontmatter_events: crate::parse::FrontMatterEvents<'event>, + /// Frontmatter events to be placed after the given section. + pub(crate) frontmatter_post_section: HashMap<SectionId, crate::parse::FrontMatterEvents<'event>>, + /// Section name to section id lookup tree, with section bodies for subsections being in a non-terminal + /// variant of `SectionBodyIds`. + pub(crate) section_lookup_tree: HashMap<section::Name<'event>, Vec<SectionBodyIdsLut<'event>>>, + /// This indirection with the SectionId as the key is critical to flexibly + /// supporting `gix-config` sections, as duplicated keys are permitted. + pub(crate) sections: HashMap<SectionId, file::Section<'event>>, + /// Internal monotonically increasing counter for section ids. + pub(crate) section_id_counter: usize, + /// Section order for output ordering. + pub(crate) section_order: VecDeque<SectionId>, + /// The source of the File itself, which is attached to new sections automatically. + pub(crate) meta: OwnShared<Metadata>, +} diff --git a/vendor/gix-config/src/value/mod.rs b/vendor/gix-config/src/value/mod.rs new file mode 100644 index 000000000..94f2f9758 --- /dev/null +++ b/vendor/gix-config/src/value/mod.rs @@ -0,0 +1,4 @@ +pub use gix_config_value::Error; + +mod normalize; +pub use normalize::{normalize, normalize_bstr, normalize_bstring}; diff --git a/vendor/gix-config/src/value/normalize.rs b/vendor/gix-config/src/value/normalize.rs new file mode 100644 index 000000000..1f16b05d8 --- /dev/null +++ b/vendor/gix-config/src/value/normalize.rs @@ -0,0 +1,110 @@ +use std::borrow::Cow; + +use bstr::{BStr, BString, ByteSlice}; + +/// Removes quotes, if any, from the provided inputs, and transforms +/// the 3 escape sequences `\n`, `\t` and `\b` into newline and tab +/// respectively, while `\b` will remove the previous character. +/// +/// It assumes the input contains a even number of unescaped quotes, +/// and will unescape escaped quotes and everything else (even though the latter +/// would have been rejected in the parsing stage). +/// +/// The return values should be safe for value interpretation. +/// +/// This has optimizations for fully-quoted values, where the returned value +/// will be a borrowed reference if the only mutation necessary is to unquote +/// the value. +/// +/// This is the function used to normalize raw values from higher level +/// abstractions. Generally speaking these +/// high level abstractions will handle normalization for you, and you do not +/// need to call this yourself. However, if you're directly handling events +/// from the parser, you may want to use this to help with value interpretation. +/// +/// Generally speaking, you'll want to use one of the variants of this function, +/// such as [`normalize_bstr`] or [`normalize_bstring`]. +/// +/// # Examples +/// +/// Values don't need modification are returned borrowed, without allocation. +/// +/// ``` +/// # use std::borrow::Cow; +/// # use bstr::ByteSlice; +/// # use gix_config::value::normalize_bstr; +/// assert!(matches!(normalize_bstr("hello world"), Cow::Borrowed(_))); +/// ``` +/// +/// Internally quoted values are turned into owned variant with quotes removed. +/// +/// ``` +/// # use std::borrow::Cow; +/// # use bstr::{BStr, BString}; +/// # use gix_config::value::{normalize_bstr}; +/// assert_eq!(normalize_bstr("hello \"world\""), Cow::<BStr>::Owned(BString::from("hello world"))); +/// ``` +/// +/// Escaped quotes are unescaped. +/// +/// ``` +/// # use std::borrow::Cow; +/// # use bstr::{BStr, BString}; +/// # use gix_config::value::normalize_bstr; +/// assert_eq!(normalize_bstr(r#"hello "world\"""#), Cow::<BStr>::Owned(BString::from(r#"hello world""#))); +/// ``` +#[must_use] +pub fn normalize(input: Cow<'_, BStr>) -> Cow<'_, BStr> { + if input.as_ref() == "\"\"" { + return Cow::Borrowed("".into()); + } + + let size = input.len(); + if size >= 3 && input[0] == b'"' && input[size - 1] == b'"' && input[size - 2] != b'\\' { + match input { + Cow::Borrowed(input) => return normalize_bstr(&input[1..size - 1]), + Cow::Owned(mut input) => { + input.pop(); + input.remove(0); + return normalize_bstring(input); + } + } + } + + if input.find_byteset(b"\\\"").is_none() { + return input; + } + + let mut out: BString = Vec::with_capacity(input.len()).into(); + let mut bytes = input.iter().copied(); + while let Some(c) = bytes.next() { + match c { + b'\\' => match bytes.next() { + Some(b'n') => out.push(b'\n'), + Some(b't') => out.push(b'\t'), + Some(b'b') => { + out.pop(); + } + Some(c) => { + out.push(c); + } + None => break, + }, + b'"' => {} + _ => out.push(c), + } + } + Cow::Owned(out) +} + +/// `&[u8]` variant of [`normalize`]. +#[must_use] +pub fn normalize_bstr<'a>(input: impl Into<&'a BStr>) -> Cow<'a, BStr> { + normalize(Cow::Borrowed(input.into())) +} + +/// `Vec[u8]` variant of [`normalize`]. +#[must_use] +pub fn normalize_bstring(input: impl Into<BString>) -> Cow<'static, BStr> { + normalize(Cow::Owned(input.into())) +} |