From 837b550238aa671a591ccf282dddeab29cadb206 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sat, 18 May 2024 04:49:42 +0200 Subject: Merging upstream version 1.71.1+dfsg1. Signed-off-by: Daniel Baumann --- vendor/gix-attributes/.cargo-checksum.json | 2 +- vendor/gix-attributes/CHANGELOG.md | 97 ++++++- vendor/gix-attributes/Cargo.toml | 29 +- vendor/gix-attributes/src/assignment.rs | 23 ++ vendor/gix-attributes/src/lib.rs | 93 +++---- vendor/gix-attributes/src/match_group.rs | 354 ------------------------ vendor/gix-attributes/src/name.rs | 26 +- vendor/gix-attributes/src/parse.rs | 169 ++++++++++++ vendor/gix-attributes/src/parse/attribute.rs | 172 ------------ vendor/gix-attributes/src/parse/ignore.rs | 36 --- vendor/gix-attributes/src/parse/mod.rs | 10 - vendor/gix-attributes/src/search/attributes.rs | 217 +++++++++++++++ vendor/gix-attributes/src/search/mod.rs | 150 ++++++++++ vendor/gix-attributes/src/search/outcome.rs | 365 +++++++++++++++++++++++++ vendor/gix-attributes/src/search/refmap.rs | 52 ++++ vendor/gix-attributes/src/source.rs | 27 ++ vendor/gix-attributes/src/state.rs | 84 +++++- 17 files changed, 1263 insertions(+), 643 deletions(-) delete mode 100644 vendor/gix-attributes/src/match_group.rs create mode 100644 vendor/gix-attributes/src/parse.rs delete mode 100644 vendor/gix-attributes/src/parse/attribute.rs delete mode 100644 vendor/gix-attributes/src/parse/ignore.rs delete mode 100644 vendor/gix-attributes/src/parse/mod.rs create mode 100644 vendor/gix-attributes/src/search/attributes.rs create mode 100644 vendor/gix-attributes/src/search/mod.rs create mode 100644 vendor/gix-attributes/src/search/outcome.rs create mode 100644 vendor/gix-attributes/src/search/refmap.rs create mode 100644 vendor/gix-attributes/src/source.rs (limited to 'vendor/gix-attributes') diff --git a/vendor/gix-attributes/.cargo-checksum.json b/vendor/gix-attributes/.cargo-checksum.json index 92af48df6..fa678f301 100644 --- a/vendor/gix-attributes/.cargo-checksum.json +++ b/vendor/gix-attributes/.cargo-checksum.json @@ -1 +1 @@ -{"files":{"CHANGELOG.md":"486353ee7fa95059993a76a9702a018a4db46dc1be33cd87ba6a5d3c92a19e07","Cargo.toml":"e4615386ba458e9339ee36a5f732a768d65380265c564050b8322e97083c9815","src/assignment.rs":"43a4eceb7851d102f7d85617481ace720c5ee62149f76607c3a441f688107319","src/lib.rs":"516ffb048d29fb9688df9ac49358f7f809f0a4f896ebde85eb1964bbe58e4271","src/match_group.rs":"73e41139695b6cc10b1116301feb56617bef0cfc4167bd7c570c3b639c278e9c","src/name.rs":"d3d983fcdbb1ab2fa70be22df165cd3dbf4200e7b85721cfd805b38f591317c9","src/parse/attribute.rs":"1b2dbcb8cfcb2d30ec080e5ea2525547ed44b12b68d880153df055395f01ee21","src/parse/ignore.rs":"c6ff8cf3978a484cf1100bc502cd8c7440e62b33cce34df7d28ac614c9d63d44","src/parse/mod.rs":"6705e0a2197d203bbd683008c0d28a1c61cd717cb157aaa7cfa69782bd59f312","src/state.rs":"142db35ff1eceaad25f5a75a2931f1f1d9af2a6cf9d7849e7b3cee914af8f2c9"},"package":"2231a25934a240d0a4b6f4478401c73ee81d8be52de0293eedbc172334abf3e1"} \ No newline at end of file +{"files":{"CHANGELOG.md":"2460284290ae51f34a8858a81e293fc4190a9ebfd128589b96438fc60120e94c","Cargo.toml":"37e4aa70988b36c4d5b7e295eb49686071b2a727ef32d8acfe7590aa931e711c","src/assignment.rs":"6270dd7e7bcd0c968a04b7e2cd84e641bd51d59ad97d693f324b5284308e1fb9","src/lib.rs":"d0abd5cf5f07b08e8303aef14f436ce767b540dfcfb5b68704e42dc80593bd93","src/name.rs":"6020a168779414bda21227d5486781a0238b379c4b05d19da8ac3ae27c671887","src/parse.rs":"ace256f34d27e9068c3e1a1572cb75afac54c4bdfd9deaf28e6e79fcffdacbe7","src/search/attributes.rs":"4dd191edf1940a6b5920d5d0cd14f79c28ad0e627b06e4f81d52eb34873a29e6","src/search/mod.rs":"664c13e8251178336b0b5c28f5a306b6d7ff53b02fc1f67e3aa156a0f2fac9f7","src/search/outcome.rs":"fccd479276a59b6579a98e9aeae5b80f2ea7298536996cc5377ccfa39f93ed93","src/search/refmap.rs":"5ead4ed7a36f02f356ac83df116d3b79a37dee6fcd68a3cf7d8819e8730113fa","src/source.rs":"81092fff85f1f31c8daa1bac313ffd44511c64617e272d4f4225ed714bf89464","src/state.rs":"5510b197454341ce6a3e12d80cbcac31d53b80e85b431bc83f3bb09b203549d8"},"package":"3015baa01ad2122fbcaab7863c857a603eb7b7ec12ac8141207c42c6439805e2"} \ No newline at end of file diff --git a/vendor/gix-attributes/CHANGELOG.md b/vendor/gix-attributes/CHANGELOG.md index cb947f104..6af43478f 100644 --- a/vendor/gix-attributes/CHANGELOG.md +++ b/vendor/gix-attributes/CHANGELOG.md @@ -5,6 +5,100 @@ 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.12.0 (2023-04-27) + +A maintenance release without user-facing changes. + +### New Features + + - `std::fmt::Display` for `AssignmentRef`. + It displays exactly as it would in a `.gitattributes` file. + +### Commit Statistics + + + + - 3 commits contributed to the release. + - 1 commit was understood as [conventional](https://www.conventionalcommits.org). + - 0 issues like '(#ID)' were seen in commit messages + +### Commit Details + + + +
view details + + * **Uncategorized** + - Prepare changelogs prior to release ([`0135158`](https://github.com/Byron/gitoxide/commit/013515897215400539bfd53c25548bd054186ba6)) + - Bump gix-path v0.8.0, safety bump 20 crates (gix set to 0.44.1 manually) ([`43ebaf2`](https://github.com/Byron/gitoxide/commit/43ebaf267557218865862538ffc7bdf00558492f)) + - `std::fmt::Display` for `AssignmentRef`. ([`c18482b`](https://github.com/Byron/gitoxide/commit/c18482bd388c684c553d839f6360f085e68d754b)) +
+ +## 0.11.0 (2023-04-26) + +### New Features + + - add `Search::pop_pattern_list()`. + Also, improve method documentation. + +### New Features (BREAKING) + + - Rename `serde1` cargo feature to `serde` and use the weak-deps cargo capability. + With it it's possible to not automatically declare all optional dependencies externally visible + features, and thus re-use feature names that oterwise are also a crate name. + + Previously I thought that `serde1` is for future-proofing and supporting multiple serde versions + at the same time. However, it's most definitely a burden I wouldn't want anyway, so using + `serde` seems to be the way to go into the future. + - an API for matching attributes. + An implementation for `Search` along with various + breaking modifications of the original module and type layout. + +### Bug Fixes (BREAKING) + + - Avoid lifetime in `Outcome`. + Otherwise it's not possible to keep allocations for matches, + while also adjusting pattern lists due to stack changes. + + On the bright side, this adds some more deduplication to the implementation, + at the cost of dealing with some hashmaps that are updated when matching. + +### Commit Statistics + + + + - 17 commits contributed to the release over the course of 23 calendar days. + - 53 days passed between releases. + - 4 commits were understood as [conventional](https://www.conventionalcommits.org). + - 1 unique issue was worked on: [#814](https://github.com/Byron/gitoxide/issues/814) + +### Commit Details + + + +
view details + + * **[#814](https://github.com/Byron/gitoxide/issues/814)** + - Rename `serde1` cargo feature to `serde` and use the weak-deps cargo capability. ([`b83ee36`](https://github.com/Byron/gitoxide/commit/b83ee366a3c65c717beb587ad809268f1c54b8ad)) + * **Uncategorized** + - Release gix-hash v0.11.1, gix-path v0.7.4, gix-glob v0.6.0, gix-attributes v0.11.0, gix-config-value v0.11.0, gix-fs v0.1.1, gix-tempfile v5.0.3, gix-utils v0.1.1, gix-lock v5.0.1, gix-object v0.29.1, gix-ref v0.28.0, gix-sec v0.7.0, gix-config v0.21.0, gix-prompt v0.4.0, gix-url v0.17.0, gix-credentials v0.13.0, gix-diff v0.29.0, gix-discover v0.17.0, gix-hashtable v0.2.0, gix-ignore v0.1.0, gix-bitmap v0.2.3, gix-traverse v0.25.0, gix-index v0.16.0, gix-mailmap v0.12.0, gix-pack v0.34.0, gix-odb v0.44.0, gix-packetline v0.16.0, gix-transport v0.30.0, gix-protocol v0.31.0, gix-revision v0.13.0, gix-refspec v0.10.0, gix-worktree v0.16.0, gix v0.44.0, safety bump 7 crates ([`91134a1`](https://github.com/Byron/gitoxide/commit/91134a11c8ba0e942f692488ec9bce9fa1086324)) + - Prepare changelogs prior to release ([`30a1a71`](https://github.com/Byron/gitoxide/commit/30a1a71f36f24faac0e0b362ffdfedea7f9cdbf1)) + - Merge branch 'attributes-cache' ([`3456c84`](https://github.com/Byron/gitoxide/commit/3456c845dfeedd2fa96b4313b1a84c8cbe9433c5)) + - Avoid lifetime in `Outcome`. ([`1d15173`](https://github.com/Byron/gitoxide/commit/1d15173a5f492bd695f6456774c65261cddb5fe4)) + - Add `Search::pop_pattern_list()`. ([`1eac372`](https://github.com/Byron/gitoxide/commit/1eac372584e94689fd8b08d20d38d240dcfda26a)) + - Release gix-utils v0.1.0, gix-hash v0.11.0, gix-date v0.5.0, gix-features v0.29.0, gix-actor v0.20.0, gix-object v0.29.0, gix-archive v0.1.0, gix-fs v0.1.0, safety bump 25 crates ([`8dbd0a6`](https://github.com/Byron/gitoxide/commit/8dbd0a60557a85acfa231800a058cbac0271a8cf)) + - Make fmt ([`5d2b5d0`](https://github.com/Byron/gitoxide/commit/5d2b5d02c3869e07dc2507a8f2519ee1df633df7)) + - Merge branch 'main' into dev ([`cdef398`](https://github.com/Byron/gitoxide/commit/cdef398c4a3bd01baf0be2c27a3f77a400172b0d)) + - Rename the serde1 feature to serde ([`19338d9`](https://github.com/Byron/gitoxide/commit/19338d934b6712b7d6bd3fa3b2e4189bf7e6c8a1)) + - Create new `gix-fs` crate to consolidate all filesystem utilities ([`f8cc33c`](https://github.com/Byron/gitoxide/commit/f8cc33cb372dd2b4bbe4a09cf4f64916681ab1dd)) + - Merge branch 'main' into dev ([`23ee47f`](https://github.com/Byron/gitoxide/commit/23ee47fb24c197f8437bd426544b2aa74e005bdc)) + - Merge branch 'worktree-stack' ([`3d47919`](https://github.com/Byron/gitoxide/commit/3d47919c1a2f83fc7c1fd7ae590d098057a22626)) + - An API for matching attributes. ([`424ad62`](https://github.com/Byron/gitoxide/commit/424ad62c852112b1104fe853b71de4dfe3a9f915)) + - Refactor ([`0677406`](https://github.com/Byron/gitoxide/commit/067740636b3ca24ce90db91923dfd4ee592fa7f6)) + - Merge branch 'patch-1' ([`d0052c1`](https://github.com/Byron/gitoxide/commit/d0052c13cabcde8058177d2439053b50ea5adbfc)) + - Upgrade various dependencies ([`f9ad837`](https://github.com/Byron/gitoxide/commit/f9ad83712deb53e0f8ac2be3cd35df8edcc5253c)) +
+ ## 0.10.0 (2023-03-04) A maintenance release without user-facing changes. @@ -13,7 +107,7 @@ A maintenance release without user-facing changes. - - 2 commits contributed to the release. + - 3 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 @@ -25,6 +119,7 @@ A maintenance release without user-facing changes.
view details * **Uncategorized** + - Release gix-attributes v0.10.0, gix-ref v0.26.0, gix-config v0.18.0, gix-url v0.15.0, gix-credentials v0.11.0, gix-discover v0.15.0, gix-index v0.14.0, gix-mailmap v0.11.0, gix-odb v0.42.0, gix-transport v0.27.0, gix-protocol v0.28.0, gix-revision v0.12.0, gix-refspec v0.9.0, gix-worktree v0.14.0, gix v0.39.0 ([`93e75fe`](https://github.com/Byron/gitoxide/commit/93e75fed454ed8b342231bde4638db90e407ce52)) - 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))
diff --git a/vendor/gix-attributes/Cargo.toml b/vendor/gix-attributes/Cargo.toml index a40f3e9a5..b9b3d5251 100644 --- a/vendor/gix-attributes/Cargo.toml +++ b/vendor/gix-attributes/Cargo.toml @@ -13,7 +13,7 @@ edition = "2021" rust-version = "1.64" name = "gix-attributes" -version = "0.10.0" +version = "0.12.0" authors = ["Sebastian Thiel "] include = [ "src/**/*", @@ -25,11 +25,11 @@ repository = "https://github.com/Byron/gitoxide" [package.metadata.docs.rs] all-features = true +features = ["document-features"] rustdoc-args = [ "--cfg", "docsrs", ] -features = ["document-features"] [lib] doctest = false @@ -46,35 +46,42 @@ default-features = false version = "0.2.1" optional = true -[dependencies.gix-features] -version = "^0.28.0" - [dependencies.gix-glob] -version = "^0.5.5" +version = "^0.7.0" [dependencies.gix-path] -version = "^0.7.2" +version = "^0.8.0" [dependencies.gix-quote] version = "^0.4.3" +[dependencies.kstring] +version = "2.0.0" + +[dependencies.log] +version = "0.4.17" + [dependencies.serde] version = "1.0.114" features = ["derive"] optional = true default-features = false +[dependencies.smallvec] +version = "1.10.0" + [dependencies.thiserror] version = "1.0.26" [dependencies.unicode-bom] -version = "1.1.4" +version = "2.0.2" [dev-dependencies] [features] -serde1 = [ - "serde", +serde = [ + "dep:serde", "bstr/serde", - "gix-glob/serde1", + "gix-glob/serde", + "kstring/serde", ] diff --git a/vendor/gix-attributes/src/assignment.rs b/vendor/gix-attributes/src/assignment.rs index e1d7263f7..87689d443 100644 --- a/vendor/gix-attributes/src/assignment.rs +++ b/vendor/gix-attributes/src/assignment.rs @@ -1,4 +1,6 @@ use crate::{Assignment, AssignmentRef, NameRef, StateRef}; +use bstr::ByteSlice; +use std::fmt::Write; impl<'a> AssignmentRef<'a> { pub(crate) fn new(name: NameRef<'a>, state: StateRef<'a>) -> AssignmentRef<'a> { @@ -26,3 +28,24 @@ impl<'a> Assignment { AssignmentRef::new(self.name.as_ref(), self.state.as_ref()) } } + +impl std::fmt::Display for AssignmentRef<'_> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self.state { + StateRef::Set => f.write_str(self.name.as_str()), + StateRef::Unset => { + f.write_char('-')?; + f.write_str(self.name.as_str()) + } + StateRef::Value(v) => { + f.write_str(self.name.as_str())?; + f.write_char('=')?; + f.write_str(v.as_bstr().to_str_lossy().as_ref()) + } + StateRef::Unspecified => { + f.write_char('!')?; + f.write_str(self.name.as_str()) + } + } + } +} diff --git a/vendor/gix-attributes/src/lib.rs b/vendor/gix-attributes/src/lib.rs index 7d95c022f..7eaac4282 100644 --- a/vendor/gix-attributes/src/lib.rs +++ b/vendor/gix-attributes/src/lib.rs @@ -1,4 +1,4 @@ -//! Parse `.gitattribute` and `.gitignore` files and provide utilities to match against them. +//! Parse `.gitattribute` files and provide utilities to match against them. //! //! ## Feature Flags #![cfg_attr( @@ -6,25 +6,26 @@ cfg_attr(doc, doc = ::document_features::document_features!()) )] #![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] -#![deny(missing_docs, rust_2018_idioms)] -#![forbid(unsafe_code)] +#![deny(missing_docs, rust_2018_idioms, unsafe_code)] -use std::path::PathBuf; - -use bstr::{BStr, BString}; pub use gix_glob as glob; +use kstring::{KString, KStringRef}; mod assignment; /// pub mod name; -mod state; +/// +pub mod state; -mod match_group; -pub use match_group::{Attributes, Ignore, Match, Pattern}; +/// +pub mod search; /// pub mod parse; -/// Parse attribute assignments line by line from `bytes`. + +/// Parse attribute assignments line by line from `bytes`, and fail the operation on error. +/// +/// For leniency, ignore errors using `filter_map(Result::ok)` for example. pub fn parse(bytes: &[u8]) -> parse::Lines<'_> { parse::Lines::new(bytes) } @@ -33,7 +34,7 @@ pub fn parse(bytes: &[u8]) -> parse::Lines<'_> { /// /// Note that this doesn't contain the name. #[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone, Copy)] -#[cfg_attr(feature = "serde1", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub enum StateRef<'a> { /// The attribute is listed, or has the special value 'true' Set, @@ -41,8 +42,8 @@ pub enum StateRef<'a> { Unset, /// The attribute is set to the given value, which followed the `=` sign. /// Note that values can be empty. - #[cfg_attr(feature = "serde1", serde(borrow))] - Value(&'a BStr), + #[cfg_attr(feature = "serde", serde(borrow))] + Value(state::ValueRef<'a>), /// The attribute isn't mentioned with a given path or is explicitly set to `Unspecified` using the `!` sign. Unspecified, } @@ -51,7 +52,7 @@ pub enum StateRef<'a> { /// /// Note that this doesn't contain the name. #[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone)] -#[cfg_attr(feature = "serde1", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub enum State { /// The attribute is listed, or has the special value 'true' Set, @@ -59,23 +60,23 @@ pub enum State { Unset, /// The attribute is set to the given value, which followed the `=` sign. /// Note that values can be empty. - Value(BString), // TODO(performance): Is there a non-utf8 compact_str/KBString crate? See https://github.com/cobalt-org/kstring/issues/37#issuecomment-1446777265 . + Value(state::Value), /// The attribute isn't mentioned with a given path or is explicitly set to `Unspecified` using the `!` sign. Unspecified, } /// Represents a validated attribute name #[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone)] -#[cfg_attr(feature = "serde1", derive(serde::Serialize, serde::Deserialize))] -pub struct Name(pub(crate) String); // TODO(performance): See if `KBString` or `compact_string` could be meaningful here. +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct Name(pub(crate) KString); /// Holds a validated attribute name as a reference -#[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd)] -pub struct NameRef<'a>(&'a str); +#[derive(Copy, Clone, PartialEq, Eq, Debug, Hash, Ord, PartialOrd)] +pub struct NameRef<'a>(KStringRef<'a>); /// Name an attribute and describe it's assigned state. #[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone)] -#[cfg_attr(feature = "serde1", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct Assignment { /// The validated name of the attribute. pub name: Name, @@ -84,7 +85,7 @@ pub struct Assignment { } /// Holds validated attribute data as a reference -#[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd)] +#[derive(Copy, Clone, PartialEq, Eq, Debug, Hash, Ord, PartialOrd)] pub struct AssignmentRef<'a> { /// The name of the attribute. pub name: NameRef<'a>, @@ -92,46 +93,34 @@ pub struct AssignmentRef<'a> { pub state: StateRef<'a>, } -/// A grouping of lists of patterns while possibly keeping associated to their base path. +/// A grouping of lists of patterns while possibly keeping associated to their base path in order to find matches. /// /// Pattern lists with base path are queryable relative to that base, otherwise they are relative to the repository root. #[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone, Default)] -pub struct MatchGroup { +pub struct Search { /// A list of pattern lists, each representing a patterns from a file or specified by hand, in the order they were /// specified in. /// - /// During matching, this order is reversed. - pub patterns: Vec>, + /// When matching, this order is reversed. + patterns: Vec>, } -/// A list of patterns which optionally know where they were loaded from and what their base is. +/// A list of known global sources for git attribute files in order of ascending precedence. /// -/// Knowing their base which is relative to a source directory, it will ignore all path to match against -/// that don't also start with said base. -#[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone, Default)] -pub struct PatternList { - /// Patterns and their associated data in the order they were loaded in or specified, - /// the line number in its source file or its sequence number (_`(pattern, value, line_number)`_). +/// This means that values from the first variant will be returned first. +#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)] +pub enum Source { + /// The attribute file that the installation itself ships with. + GitInstallation, + /// System-wide attributes file. This is typically defined as + /// `$(prefix)/etc/gitattributes` (where prefix is the git-installation directory). + System, + /// This is `/git/attributes` and is git application configuration per user. /// - /// During matching, this order is reversed. - pub patterns: Vec>, - - /// The path from which the patterns were read, or `None` if the patterns - /// don't originate in a file on disk. - pub source: Option, - - /// The parent directory of source, or `None` if the patterns are _global_ to match against the repository root. - /// It's processed to contain slashes only and to end with a trailing slash, and is relative to the repository root. - pub base: Option, + /// Note that there is no `~/.gitattributes` file. + Git, + /// The configuration of the repository itself, located in `$GIT_DIR/info/attributes`. + Local, } -/// An association of a pattern with its value, along with a sequence number providing a sort order in relation to its peers. -#[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone)] -pub struct PatternMapping { - /// The pattern itself, like `/target/*` - pub pattern: gix_glob::Pattern, - /// The value associated with the pattern. - pub value: T, - /// Typically the line number in the file the pattern was parsed from. - pub sequence_number: usize, -} +mod source; diff --git a/vendor/gix-attributes/src/match_group.rs b/vendor/gix-attributes/src/match_group.rs deleted file mode 100644 index 018bf2567..000000000 --- a/vendor/gix-attributes/src/match_group.rs +++ /dev/null @@ -1,354 +0,0 @@ -use std::{ - ffi::OsString, - io::Read, - path::{Path, PathBuf}, -}; - -use bstr::{BStr, BString, ByteSlice, ByteVec}; - -use crate::{Assignment, MatchGroup, PatternList, PatternMapping}; - -fn into_owned_assignments<'a>( - attrs: impl Iterator, crate::name::Error>>, -) -> Result, crate::name::Error> { - attrs.map(|res| res.map(|attr| attr.to_owned())).collect() -} - -/// A trait to convert bytes into patterns and their associated value. -/// -/// This is used for `gitattributes` which have a value, and `gitignore` which don't. -pub trait Pattern: Clone + PartialEq + Eq + std::fmt::Debug + std::hash::Hash + Ord + PartialOrd + Default { - /// The value associated with a pattern. - type Value: PartialEq + Eq + std::fmt::Debug + std::hash::Hash + Ord + PartialOrd + Clone; - - /// Parse all patterns in `bytes` line by line, ignoring lines with errors, and collect them. - fn bytes_to_patterns(bytes: &[u8]) -> Vec>; - - /// Returns true if the given pattern may be used for matching. - fn may_use_glob_pattern(pattern: &gix_glob::Pattern) -> bool; -} - -/// An implementation of the [`Pattern`] trait for ignore patterns. -#[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone, Default)] -pub struct Ignore; - -impl Pattern for Ignore { - type Value = (); - - fn bytes_to_patterns(bytes: &[u8]) -> Vec> { - crate::parse::ignore(bytes) - .map(|(pattern, line_number)| PatternMapping { - pattern, - value: (), - sequence_number: line_number, - }) - .collect() - } - - fn may_use_glob_pattern(_pattern: &gix_glob::Pattern) -> bool { - true - } -} - -/// A value of an attribute pattern, which is either a macro definition or -#[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone)] -pub enum Value { - MacroAttributes(Vec), - Assignments(Vec), -} - -/// An implementation of the [`Pattern`] trait for attributes. -#[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone, Default)] -pub struct Attributes; - -impl Pattern for Attributes { - type Value = Value; - - fn bytes_to_patterns(bytes: &[u8]) -> Vec> { - crate::parse(bytes) - .filter_map(Result::ok) - .filter_map(|(pattern_kind, assignments, line_number)| { - let (pattern, value) = match pattern_kind { - crate::parse::Kind::Macro(macro_name) => ( - gix_glob::Pattern { - text: macro_name.as_str().into(), - mode: gix_glob::pattern::Mode::all(), - first_wildcard_pos: None, - }, - Value::MacroAttributes(into_owned_assignments(assignments).ok()?), - ), - crate::parse::Kind::Pattern(p) => ( - (!p.is_negative()).then_some(p)?, - Value::Assignments(into_owned_assignments(assignments).ok()?), - ), - }; - PatternMapping { - pattern, - value, - sequence_number: line_number, - } - .into() - }) - .collect() - } - - fn may_use_glob_pattern(pattern: &gix_glob::Pattern) -> bool { - pattern.mode != gix_glob::pattern::Mode::all() - } -} - -/// Describes a matching value within a [`MatchGroup`]. -#[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone)] -pub struct Match<'a, T> { - /// The glob pattern itself, like `/target/*`. - pub pattern: &'a gix_glob::Pattern, - /// The value associated with the pattern. - pub value: &'a T, - /// The path to the source from which the pattern was loaded, or `None` if it was specified by other means. - pub source: Option<&'a Path>, - /// The line at which the pattern was found in its `source` file, or the occurrence in which it was provided. - pub sequence_number: usize, -} - -impl MatchGroup -where - T: Pattern, -{ - /// Match `relative_path`, a path relative to the repository containing all patterns, and return the first match if available. - // TODO: better docs - pub fn pattern_matching_relative_path<'a>( - &self, - relative_path: impl Into<&'a BStr>, - is_dir: Option, - case: gix_glob::pattern::Case, - ) -> Option> { - let relative_path = relative_path.into(); - let basename_pos = relative_path.rfind(b"/").map(|p| p + 1); - self.patterns - .iter() - .rev() - .find_map(|pl| pl.pattern_matching_relative_path(relative_path, basename_pos, is_dir, case)) - } -} - -impl MatchGroup { - /// Given `git_dir`, a `.git` repository, load ignore patterns from `info/exclude` and from `excludes_file` if it - /// is provided. - /// Note that it's not considered an error if the provided `excludes_file` does not exist. - pub fn from_git_dir( - git_dir: impl AsRef, - excludes_file: Option, - buf: &mut Vec, - ) -> std::io::Result { - let mut group = Self::default(); - - let follow_symlinks = true; - // order matters! More important ones first. - group.patterns.extend( - excludes_file - .map(|file| PatternList::::from_file(file, None, follow_symlinks, buf)) - .transpose()? - .flatten(), - ); - group.patterns.extend(PatternList::::from_file( - git_dir.as_ref().join("info").join("exclude"), - None, - follow_symlinks, - buf, - )?); - Ok(group) - } - - /// See [PatternList::::from_overrides()] for details. - pub fn from_overrides(patterns: impl IntoIterator>) -> Self { - MatchGroup { - patterns: vec![PatternList::::from_overrides(patterns)], - } - } - - /// Add the given file at `source` if it exists, otherwise do nothing. If a `root` is provided, it's not considered a global file anymore. - /// Returns true if the file was added, or false if it didn't exist. - pub fn add_patterns_file( - &mut self, - source: impl Into, - follow_symlinks: bool, - root: Option<&Path>, - buf: &mut Vec, - ) -> std::io::Result { - let previous_len = self.patterns.len(); - self.patterns.extend(PatternList::::from_file( - source.into(), - root, - follow_symlinks, - buf, - )?); - Ok(self.patterns.len() != previous_len) - } - - /// Add patterns as parsed from `bytes`, providing their `source` path and possibly their `root` path, the path they - /// are relative to. This also means that `source` is contained within `root` if `root` is provided. - pub fn add_patterns_buffer(&mut self, bytes: &[u8], source: impl Into, root: Option<&Path>) { - self.patterns - .push(PatternList::::from_bytes(bytes, source.into(), root)); - } -} - -fn read_in_full_ignore_missing(path: &Path, follow_symlinks: bool, buf: &mut Vec) -> std::io::Result { - buf.clear(); - let file = if follow_symlinks { - std::fs::File::open(path) - } else { - gix_features::fs::open_options_no_follow().read(true).open(path) - }; - Ok(match file { - Ok(mut file) => { - file.read_to_end(buf)?; - true - } - Err(err) if err.kind() == std::io::ErrorKind::NotFound => false, - Err(err) => return Err(err), - }) -} - -impl PatternList -where - T: Pattern, -{ - /// `source` is the location of the `bytes` which represent a list of patterns line by line. - pub fn from_bytes(bytes: &[u8], source: impl Into, root: Option<&Path>) -> Self { - let source = source.into(); - let patterns = T::bytes_to_patterns(bytes); - - let base = root - .and_then(|root| source.parent().expect("file").strip_prefix(root).ok()) - .and_then(|base| { - (!base.as_os_str().is_empty()).then(|| { - let mut base: BString = - gix_path::to_unix_separators_on_windows(gix_path::into_bstr(base)).into_owned(); - - base.push_byte(b'/'); - base - }) - }); - PatternList { - patterns, - source: Some(source), - base, - } - } - - /// Create a pattern list from the `source` file, which may be located underneath `root`, while optionally - /// following symlinks with `follow_symlinks`, providing `buf` to temporarily store the data contained in the file. - pub fn from_file( - source: impl Into, - root: Option<&Path>, - follow_symlinks: bool, - buf: &mut Vec, - ) -> std::io::Result> { - let source = source.into(); - Ok(read_in_full_ignore_missing(&source, follow_symlinks, buf)?.then(|| Self::from_bytes(buf, source, root))) - } -} - -impl PatternList -where - T: Pattern, -{ - /// Return a match if a pattern matches `relative_path`, providing a pre-computed `basename_pos` which is the - /// starting position of the basename of `relative_path`. `is_dir` is true if `relative_path` is a directory. - /// `case` specifies whether cases should be folded during matching or not. - pub fn pattern_matching_relative_path( - &self, - relative_path: &BStr, - basename_pos: Option, - is_dir: Option, - case: gix_glob::pattern::Case, - ) -> Option> { - let (relative_path, basename_start_pos) = - self.strip_base_handle_recompute_basename_pos(relative_path, basename_pos)?; - self.patterns - .iter() - .rev() - .filter(|pm| T::may_use_glob_pattern(&pm.pattern)) - .find_map( - |PatternMapping { - pattern, - value, - sequence_number, - }| { - pattern - .matches_repo_relative_path(relative_path, basename_start_pos, is_dir, case) - .then_some(Match { - pattern, - value, - source: self.source.as_deref(), - sequence_number: *sequence_number, - }) - }, - ) - } - - /// Like [`pattern_matching_relative_path()`][Self::pattern_matching_relative_path()], but returns an index to the pattern - /// that matched `relative_path`, instead of the match itself. - pub fn pattern_idx_matching_relative_path( - &self, - relative_path: &BStr, - basename_pos: Option, - is_dir: Option, - case: gix_glob::pattern::Case, - ) -> Option { - let (relative_path, basename_start_pos) = - self.strip_base_handle_recompute_basename_pos(relative_path, basename_pos)?; - self.patterns - .iter() - .enumerate() - .rev() - .filter(|(_, pm)| T::may_use_glob_pattern(&pm.pattern)) - .find_map(|(idx, pm)| { - pm.pattern - .matches_repo_relative_path(relative_path, basename_start_pos, is_dir, case) - .then_some(idx) - }) - } - - fn strip_base_handle_recompute_basename_pos<'a>( - &self, - relative_path: &'a BStr, - basename_pos: Option, - ) -> Option<(&'a BStr, Option)> { - match self.base.as_deref() { - Some(base) => ( - relative_path.strip_prefix(base.as_slice())?.as_bstr(), - basename_pos.and_then(|pos| { - let pos = pos - base.len(); - (pos != 0).then_some(pos) - }), - ), - None => (relative_path, basename_pos), - } - .into() - } -} - -impl PatternList { - /// Parse a list of patterns, using slashes as path separators - pub fn from_overrides(patterns: impl IntoIterator>) -> Self { - PatternList { - patterns: patterns - .into_iter() - .map(Into::into) - .enumerate() - .filter_map(|(seq_id, pattern)| { - let pattern = gix_path::try_into_bstr(PathBuf::from(pattern)).ok()?; - gix_glob::parse(pattern.as_ref()).map(|p| PatternMapping { - pattern: p, - value: (), - sequence_number: seq_id, - }) - }) - .collect(), - source: None, - base: None, - } - } -} diff --git a/vendor/gix-attributes/src/name.rs b/vendor/gix-attributes/src/name.rs index 03064dbda..40d86fd4c 100644 --- a/vendor/gix-attributes/src/name.rs +++ b/vendor/gix-attributes/src/name.rs @@ -1,4 +1,5 @@ -use bstr::BString; +use bstr::{BStr, BString, ByteSlice}; +use kstring::KStringRef; use crate::{Name, NameRef}; @@ -10,13 +11,32 @@ impl<'a> NameRef<'a> { /// Return the inner `str`. pub fn as_str(&self) -> &str { - self.0 + self.0.as_str() } } impl AsRef for NameRef<'_> { fn as_ref(&self) -> &str { - self.0 + self.0.as_ref() + } +} + +impl<'a> TryFrom<&'a BStr> for NameRef<'a> { + type Error = Error; + + fn try_from(attr: &'a BStr) -> Result { + fn attr_valid(attr: &BStr) -> bool { + if attr.first() == Some(&b'-') { + return false; + } + + attr.bytes() + .all(|b| matches!(b, b'-' | b'.' | b'_' | b'A'..=b'Z' | b'a'..=b'z' | b'0'..=b'9')) + } + + attr_valid(attr) + .then(|| NameRef(KStringRef::from_ref(attr.to_str().expect("no illformed utf8")))) + .ok_or_else(|| Error { attribute: attr.into() }) } } diff --git a/vendor/gix-attributes/src/parse.rs b/vendor/gix-attributes/src/parse.rs new file mode 100644 index 000000000..0b70cb306 --- /dev/null +++ b/vendor/gix-attributes/src/parse.rs @@ -0,0 +1,169 @@ +use std::borrow::Cow; + +use bstr::{BStr, ByteSlice}; +use kstring::KStringRef; + +use crate::{name, AssignmentRef, Name, NameRef, StateRef}; + +/// The kind of attribute that was parsed. +#[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub enum Kind { + /// A pattern to match paths against + Pattern(gix_glob::Pattern), + /// The name of the macro to define, always a valid attribute name + Macro(Name), +} + +mod error { + use bstr::BString; + /// The error returned by [`parse::Lines`][crate::parse::Lines]. + #[derive(thiserror::Error, Debug)] + #[allow(missing_docs)] + pub enum Error { + #[error("Line {line_number} has a negative pattern, for literal characters use \\!: {line}")] + PatternNegation { line_number: usize, line: BString }, + #[error("Attribute in line {line_number} has non-ascii characters or starts with '-': {attribute}")] + AttributeName { line_number: usize, attribute: BString }, + #[error("Macro in line {line_number} has non-ascii characters or starts with '-': {macro_name}")] + MacroName { line_number: usize, macro_name: BString }, + #[error("Could not unquote attributes line")] + Unquote(#[from] gix_quote::ansi_c::undo::Error), + } +} +pub use error::Error; + +/// An iterator over attribute assignments, parsed line by line. +pub struct Lines<'a> { + lines: bstr::Lines<'a>, + line_no: usize, +} + +/// An iterator over attribute assignments in a single line. +pub struct Iter<'a> { + attrs: bstr::Fields<'a>, +} + +impl<'a> Iter<'a> { + /// Create a new instance to parse attribute assignments from `input`. + pub fn new(input: &'a BStr) -> Self { + Iter { attrs: input.fields() } + } + + fn parse_attr(&self, attr: &'a [u8]) -> Result, name::Error> { + let mut tokens = attr.splitn(2, |b| *b == b'='); + let attr = tokens.next().expect("attr itself").as_bstr(); + let possibly_value = tokens.next(); + let (attr, state) = if attr.first() == Some(&b'-') { + (&attr[1..], StateRef::Unset) + } else if attr.first() == Some(&b'!') { + (&attr[1..], StateRef::Unspecified) + } else { + (attr, possibly_value.map(StateRef::from_bytes).unwrap_or(StateRef::Set)) + }; + Ok(AssignmentRef::new(check_attr(attr)?, state)) + } +} + +fn check_attr(attr: &BStr) -> Result, name::Error> { + fn attr_valid(attr: &BStr) -> bool { + if attr.first() == Some(&b'-') { + return false; + } + + attr.bytes() + .all(|b| matches!(b, b'-' | b'.' | b'_' | b'A'..=b'Z' | b'a'..=b'z' | b'0'..=b'9')) + } + + attr_valid(attr) + .then(|| NameRef(KStringRef::from_ref(attr.to_str().expect("no illformed utf8")))) + .ok_or_else(|| name::Error { attribute: attr.into() }) +} + +impl<'a> Iterator for Iter<'a> { + type Item = Result, name::Error>; + + fn next(&mut self) -> Option { + let attr = self.attrs.next().filter(|a| !a.is_empty())?; + self.parse_attr(attr).into() + } +} + +/// Instantiation +impl<'a> Lines<'a> { + /// Create a new instance to parse all attributes in all lines of the input `bytes`. + pub fn new(bytes: &'a [u8]) -> Self { + let bom = unicode_bom::Bom::from(bytes); + Lines { + lines: bytes[bom.len()..].lines(), + line_no: 0, + } + } +} + +impl<'a> Iterator for Lines<'a> { + type Item = Result<(Kind, Iter<'a>, usize), Error>; + + fn next(&mut self) -> Option { + fn skip_blanks(line: &BStr) -> &BStr { + line.find_not_byteset(BLANKS).map(|pos| &line[pos..]).unwrap_or(line) + } + for line in self.lines.by_ref() { + self.line_no += 1; + let line = skip_blanks(line.into()); + if line.first() == Some(&b'#') { + continue; + } + match parse_line(line, self.line_no) { + None => continue, + Some(res) => return Some(res), + } + } + None + } +} + +fn parse_line(line: &BStr, line_number: usize) -> Option, usize), Error>> { + if line.is_empty() { + return None; + } + + let (line, attrs): (Cow<'_, _>, _) = if line.starts_with(b"\"") { + let (unquoted, consumed) = match gix_quote::ansi_c::undo(line) { + Ok(res) => res, + Err(err) => return Some(Err(err.into())), + }; + (unquoted, &line[consumed..]) + } else { + line.find_byteset(BLANKS) + .map(|pos| (line[..pos].as_bstr().into(), line[pos..].as_bstr())) + .unwrap_or((line.into(), [].as_bstr())) + }; + + let kind_res = match line.strip_prefix(b"[attr]") { + Some(macro_name) => check_attr(macro_name.into()) + .map_err(|err| Error::MacroName { + line_number, + macro_name: err.attribute, + }) + .map(|name| Kind::Macro(name.to_owned())), + None => { + let pattern = gix_glob::Pattern::from_bytes(line.as_ref())?; + if pattern.mode.contains(gix_glob::pattern::Mode::NEGATIVE) { + Err(Error::PatternNegation { + line: line.into_owned(), + line_number, + }) + } else { + Ok(Kind::Pattern(pattern)) + } + } + }; + let kind = match kind_res { + Ok(kind) => kind, + Err(err) => return Some(Err(err)), + }; + Ok((kind, Iter::new(attrs), line_number)).into() +} + +const BLANKS: &[u8] = b" \t\r"; diff --git a/vendor/gix-attributes/src/parse/attribute.rs b/vendor/gix-attributes/src/parse/attribute.rs deleted file mode 100644 index 9e4b4c66e..000000000 --- a/vendor/gix-attributes/src/parse/attribute.rs +++ /dev/null @@ -1,172 +0,0 @@ -use std::borrow::Cow; - -use bstr::{BStr, ByteSlice}; - -use crate::{name, AssignmentRef, Name, NameRef, StateRef}; - -/// The kind of attribute that was parsed. -#[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone)] -#[cfg_attr(feature = "serde1", derive(serde::Serialize, serde::Deserialize))] -pub enum Kind { - /// A pattern to match paths against - Pattern(gix_glob::Pattern), - /// The name of the macro to define, always a valid attribute name - Macro(Name), -} - -mod error { - use bstr::BString; - /// The error returned by [`parse::Lines`][crate::parse::Lines]. - #[derive(thiserror::Error, Debug)] - #[allow(missing_docs)] - pub enum Error { - #[error("Line {line_number} has a negative pattern, for literal characters use \\!: {line}")] - PatternNegation { line_number: usize, line: BString }, - #[error("Attribute in line {line_number} has non-ascii characters or starts with '-': {attribute}")] - AttributeName { line_number: usize, attribute: BString }, - #[error("Macro in line {line_number} has non-ascii characters or starts with '-': {macro_name}")] - MacroName { line_number: usize, macro_name: BString }, - #[error("Could not unquote attributes line")] - Unquote(#[from] gix_quote::ansi_c::undo::Error), - } -} -pub use error::Error; - -/// An iterator over attribute assignments, parsed line by line. -pub struct Lines<'a> { - lines: bstr::Lines<'a>, - line_no: usize, -} - -/// An iterator over attribute assignments in a single line. -pub struct Iter<'a> { - attrs: bstr::Fields<'a>, -} - -impl<'a> Iter<'a> { - /// Create a new instance to parse attribute assignments from `input`. - pub fn new(input: &'a BStr) -> Self { - Iter { attrs: input.fields() } - } - - fn parse_attr(&self, attr: &'a [u8]) -> Result, name::Error> { - let mut tokens = attr.splitn(2, |b| *b == b'='); - let attr = tokens.next().expect("attr itself").as_bstr(); - let possibly_value = tokens.next(); - let (attr, state) = if attr.first() == Some(&b'-') { - (&attr[1..], StateRef::Unset) - } else if attr.first() == Some(&b'!') { - (&attr[1..], StateRef::Unspecified) - } else { - ( - attr, - possibly_value - .map(|v| StateRef::Value(v.as_bstr())) - .unwrap_or(StateRef::Set), - ) - }; - Ok(AssignmentRef::new(check_attr(attr)?, state)) - } -} - -fn check_attr(attr: &BStr) -> Result, name::Error> { - fn attr_valid(attr: &BStr) -> bool { - if attr.first() == Some(&b'-') { - return false; - } - - attr.bytes() - .all(|b| matches!(b, b'-' | b'.' | b'_' | b'A'..=b'Z' | b'a'..=b'z' | b'0'..=b'9')) - } - - attr_valid(attr) - .then(|| NameRef(attr.to_str().expect("no illformed utf8"))) - .ok_or_else(|| name::Error { attribute: attr.into() }) -} - -impl<'a> Iterator for Iter<'a> { - type Item = Result, name::Error>; - - fn next(&mut self) -> Option { - let attr = self.attrs.next().filter(|a| !a.is_empty())?; - self.parse_attr(attr).into() - } -} - -impl<'a> Lines<'a> { - /// Create a new instance to parse all attributes in all lines of the input `bytes`. - pub fn new(bytes: &'a [u8]) -> Self { - let bom = unicode_bom::Bom::from(bytes); - Lines { - lines: bytes[bom.len()..].lines(), - line_no: 0, - } - } -} - -impl<'a> Iterator for Lines<'a> { - type Item = Result<(Kind, Iter<'a>, usize), Error>; - - fn next(&mut self) -> Option { - fn skip_blanks(line: &BStr) -> &BStr { - line.find_not_byteset(BLANKS).map(|pos| &line[pos..]).unwrap_or(line) - } - for line in self.lines.by_ref() { - self.line_no += 1; - let line = skip_blanks(line.into()); - if line.first() == Some(&b'#') { - continue; - } - match parse_line(line, self.line_no) { - None => continue, - Some(res) => return Some(res), - } - } - None - } -} - -fn parse_line(line: &BStr, line_number: usize) -> Option, usize), Error>> { - if line.is_empty() { - return None; - } - - let (line, attrs): (Cow<'_, _>, _) = if line.starts_with(b"\"") { - let (unquoted, consumed) = match gix_quote::ansi_c::undo(line) { - Ok(res) => res, - Err(err) => return Some(Err(err.into())), - }; - (unquoted, &line[consumed..]) - } else { - line.find_byteset(BLANKS) - .map(|pos| (line[..pos].as_bstr().into(), line[pos..].as_bstr())) - .unwrap_or((line.into(), [].as_bstr())) - }; - - let kind_res = match line.strip_prefix(b"[attr]") { - Some(macro_name) => check_attr(macro_name.into()) - .map(|name| Kind::Macro(name.to_owned())) - .map_err(|err| Error::MacroName { - line_number, - macro_name: err.attribute, - }), - None => { - let pattern = gix_glob::Pattern::from_bytes(line.as_ref())?; - if pattern.mode.contains(gix_glob::pattern::Mode::NEGATIVE) { - Err(Error::PatternNegation { - line: line.into_owned(), - line_number, - }) - } else { - Ok(Kind::Pattern(pattern)) - } - } - }; - let kind = match kind_res { - Ok(kind) => kind, - Err(err) => return Some(Err(err)), - }; - Ok((kind, Iter::new(attrs), line_number)).into() -} - -const BLANKS: &[u8] = b" \t\r"; diff --git a/vendor/gix-attributes/src/parse/ignore.rs b/vendor/gix-attributes/src/parse/ignore.rs deleted file mode 100644 index a27ee0285..000000000 --- a/vendor/gix-attributes/src/parse/ignore.rs +++ /dev/null @@ -1,36 +0,0 @@ -use bstr::ByteSlice; - -/// An iterator over line-wise ignore patterns parsed from a buffer. -pub struct Lines<'a> { - lines: bstr::Lines<'a>, - line_no: usize, -} - -impl<'a> Lines<'a> { - /// Create a new instance from `buf` to parse ignore patterns from. - pub fn new(buf: &'a [u8]) -> Self { - let bom = unicode_bom::Bom::from(buf); - Lines { - lines: buf[bom.len()..].lines(), - line_no: 0, - } - } -} - -impl<'a> Iterator for Lines<'a> { - type Item = (gix_glob::Pattern, usize); - - fn next(&mut self) -> Option { - for line in self.lines.by_ref() { - self.line_no += 1; - if line.first() == Some(&b'#') { - continue; - } - match gix_glob::Pattern::from_bytes(line) { - None => continue, - Some(pattern) => return Some((pattern, self.line_no)), - } - } - None - } -} diff --git a/vendor/gix-attributes/src/parse/mod.rs b/vendor/gix-attributes/src/parse/mod.rs deleted file mode 100644 index 82cacc8ed..000000000 --- a/vendor/gix-attributes/src/parse/mod.rs +++ /dev/null @@ -1,10 +0,0 @@ -/// -pub mod ignore; - -mod attribute; -pub use attribute::{Error, Iter, Kind, Lines}; - -/// Parse git ignore patterns, line by line, from `bytes`. -pub fn ignore(bytes: &[u8]) -> ignore::Lines<'_> { - ignore::Lines::new(bytes) -} diff --git a/vendor/gix-attributes/src/search/attributes.rs b/vendor/gix-attributes/src/search/attributes.rs new file mode 100644 index 000000000..a34ae8b3e --- /dev/null +++ b/vendor/gix-attributes/src/search/attributes.rs @@ -0,0 +1,217 @@ +use std::path::{Path, PathBuf}; + +use bstr::{BStr, ByteSlice}; +use gix_glob::search::{pattern, Pattern}; + +use super::Attributes; +use crate::{ + search::{Assignments, MetadataCollection, Outcome, TrackedAssignment, Value}, + Search, +}; + +/// Instantiation and initialization. +impl Search { + /// Create a search instance preloaded with *built-ins* followed by attribute `files` from various global locations. + /// + /// See [`Source`][crate::Source] for a way to obtain these paths. + /// + /// Note that parsing is lenient and errors are logged. + /// + /// * `buf` is used to read `files` from disk which will be ignored if they do not exist. + /// * `collection` will be updated with information necessary to perform lookups later. + pub fn new_globals( + files: impl IntoIterator>, + buf: &mut Vec, + collection: &mut MetadataCollection, + ) -> std::io::Result { + let mut group = Self::default(); + group.add_patterns_buffer(b"[attr]binary -diff -merge -text", "[builtin]", None, collection); + + for path in files.into_iter() { + group.add_patterns_file(path, true, None, buf, collection)?; + } + Ok(group) + } +} + +/// Mutation +impl Search { + /// Add the given file at `source` to our patterns if it exists, otherwise do nothing. + /// Update `collection` with newly added attribute names. + /// If a `root` is provided, it's not considered a global file anymore. + /// Returns `true` if the file was added, or `false` if it didn't exist. + pub fn add_patterns_file( + &mut self, + source: impl Into, + follow_symlinks: bool, + root: Option<&Path>, + buf: &mut Vec, + collection: &mut MetadataCollection, + ) -> std::io::Result { + let was_added = gix_glob::search::add_patterns_file(&mut self.patterns, source, follow_symlinks, root, buf)?; + if was_added { + collection.update_from_list(self.patterns.last_mut().expect("just added")); + } + Ok(was_added) + } + /// Add patterns as parsed from `bytes`, providing their `source` path and possibly their `root` path, the path they + /// are relative to. This also means that `source` is contained within `root` if `root` is provided. + pub fn add_patterns_buffer( + &mut self, + bytes: &[u8], + source: impl Into, + root: Option<&Path>, + collection: &mut MetadataCollection, + ) { + self.patterns.push(pattern::List::from_bytes(bytes, source, root)); + collection.update_from_list(self.patterns.last_mut().expect("just added")); + } + + /// Pop the last attribute patterns list from our queue. + pub fn pop_pattern_list(&mut self) -> Option> { + self.patterns.pop() + } +} + +/// Access and matching +impl Search { + /// Match `relative_path`, a path relative to the repository, while respective `case`-sensitivity and write them to `out` + /// Return `true` if at least one pattern matched. + pub fn pattern_matching_relative_path<'a, 'b>( + &'a self, + relative_path: impl Into<&'b BStr>, + case: gix_glob::pattern::Case, + out: &mut Outcome, + ) -> bool { + let relative_path = relative_path.into(); + let basename_pos = relative_path.rfind(b"/").map(|p| p + 1); + let mut has_match = false; + self.patterns.iter().rev().any(|pl| { + has_match |= pattern_matching_relative_path(pl, relative_path, basename_pos, case, out); + out.is_done() + }); + has_match + } + + /// Return the amount of pattern lists contained in this instance. + pub fn num_pattern_lists(&self) -> usize { + self.patterns.len() + } +} + +impl Pattern for Attributes { + type Value = Value; + + fn bytes_to_patterns(bytes: &[u8], source: &std::path::Path) -> Vec> { + fn into_owned_assignments<'a>( + attrs: impl Iterator, crate::name::Error>>, + ) -> Option { + let res = attrs + .map(|res| { + res.map(|a| TrackedAssignment { + id: Default::default(), + inner: a.to_owned(), + }) + }) + .collect::>(); + match res { + Ok(res) => Some(res), + Err(err) => { + log::warn!("{}", err); + None + } + } + } + + crate::parse(bytes) + .filter_map(|res| match res { + Ok(pattern) => Some(pattern), + Err(err) => { + log::warn!("{}: {}", source.display(), err); + None + } + }) + .filter_map(|(pattern_kind, assignments, line_number)| { + let (pattern, value) = match pattern_kind { + crate::parse::Kind::Macro(macro_name) => ( + gix_glob::Pattern { + text: macro_name.as_str().into(), + mode: macro_mode(), + first_wildcard_pos: None, + }, + Value::MacroAssignments { + id: Default::default(), + assignments: into_owned_assignments(assignments)?, + }, + ), + crate::parse::Kind::Pattern(p) => ( + (!p.is_negative()).then_some(p)?, + Value::Assignments(into_owned_assignments(assignments)?), + ), + }; + pattern::Mapping { + pattern, + value, + sequence_number: line_number, + } + .into() + }) + .collect() + } + + fn may_use_glob_pattern(pattern: &gix_glob::Pattern) -> bool { + pattern.mode != macro_mode() + } +} + +fn macro_mode() -> gix_glob::pattern::Mode { + gix_glob::pattern::Mode::all() +} + +/// Append all matches of patterns matching `relative_path` to `out`, +/// providing a pre-computed `basename_pos` which is the starting position of the basename of `relative_path`. +/// `case` specifies whether cases should be folded during matching or not. +/// `is_dir` is true if `relative_path` is a directory. +/// Return `true` if at least one pattern matched. +#[allow(unused_variables)] +fn pattern_matching_relative_path( + list: &gix_glob::search::pattern::List, + relative_path: &BStr, + basename_pos: Option, + case: gix_glob::pattern::Case, + out: &mut Outcome, +) -> bool { + let (relative_path, basename_start_pos) = + match list.strip_base_handle_recompute_basename_pos(relative_path, basename_pos, case) { + Some(r) => r, + None => return false, + }; + let cur_len = out.remaining(); + 'outer: for pattern::Mapping { + pattern, + value, + sequence_number, + } in list + .patterns + .iter() + .rev() + .filter(|pm| Attributes::may_use_glob_pattern(&pm.pattern)) + { + let value: &Value = value; + let attrs = match value { + Value::MacroAssignments { .. } => { + unreachable!("we can't match on macros as they have no pattern") + } + Value::Assignments(attrs) => attrs, + }; + if out.has_unspecified_attributes(attrs.iter().map(|attr| attr.id)) + && pattern.matches_repo_relative_path(relative_path, basename_start_pos, None, case) + { + let all_filled = out.fill_attributes(attrs.iter(), pattern, list.source.as_ref(), *sequence_number); + if all_filled { + break 'outer; + } + } + } + cur_len != out.remaining() +} diff --git a/vendor/gix-attributes/src/search/mod.rs b/vendor/gix-attributes/src/search/mod.rs new file mode 100644 index 000000000..e70c3b8b1 --- /dev/null +++ b/vendor/gix-attributes/src/search/mod.rs @@ -0,0 +1,150 @@ +use std::collections::HashMap; + +use kstring::KString; +use smallvec::SmallVec; + +use crate::{Assignment, AssignmentRef}; + +mod attributes; +mod outcome; +mod refmap; +pub(crate) use refmap::RefMap; + +/// A typically sized list of attributes. +pub type Assignments = SmallVec<[TrackedAssignment; AVERAGE_NUM_ATTRS]>; + +/// A value of a [pattern mapping][gix_glob::search::pattern::Mapping], +/// which is either a macro definition or a set of attributes. +#[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone)] +pub enum Value { + /// A macro, whose name resolves to the contained assignments. Note that the name is the pattern of the mapping itself. + MacroAssignments { + /// The id of the macro itself, which is both an attribute as well as a set of additional attributes into which the macro + /// resolves + id: AttributeId, + /// The attributes or assignments that the macro resolves to. + assignments: Assignments, + }, + /// A set of assignments which are the attributes themselves. + Assignments(Assignments), +} + +/// A way to have an assignment (`attr=value`) but also associated it with an id that allows perfect mapping +/// to tracking information. +/// Note that the order is produced after the files are parsed as global ordering is needed that goes beyond the scope of a +/// single `Search` instance. +#[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone)] +pub struct TrackedAssignment { + /// The order of the assignment. + pub id: AttributeId, + /// The actual assignment information. + pub inner: Assignment, +} + +/// An implementation of the [`Pattern`][gix_glob::search::Pattern] trait for attributes. +#[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone, Default)] +pub struct Attributes; + +/// Describes a matching pattern with +#[derive(Clone, PartialEq, Eq, Debug, Hash, Ord, PartialOrd)] +pub struct Match<'a> { + /// The glob pattern itself, like `/target/*`. + pub pattern: &'a gix_glob::Pattern, + /// The key=value pair of the attribute that matched at the pattern. There can be multiple matches per pattern. + pub assignment: AssignmentRef<'a>, + /// Additional information about the kind of match. + pub kind: MatchKind, + /// Information about the location of the match. + pub location: MatchLocation<'a>, +} + +/// Describes in which what file and line the match was found. +#[derive(Clone, PartialEq, Eq, Debug, Hash, Ord, PartialOrd)] +pub struct MatchLocation<'a> { + /// The path to the source from which the pattern was loaded, or `None` if it was specified by other means. + pub source: Option<&'a std::path::Path>, + /// The line at which the pattern was found in its `source` file, or the occurrence in which it was provided. + pub sequence_number: usize, +} + +/// The kind of attribute within the context of a [match][Match]. +#[derive(Clone, Copy, PartialEq, Eq, Debug, Hash, Ord, PartialOrd)] +pub enum MatchKind { + /// A attribute. + Attribute { + /// The location of the macro which referred to it the list with all in-order attributes and macros, or `None` if + /// this is attribute wasn't resolved. + /// + /// Use [`Outcome::match_by_id()`] to retrieve the macro. + macro_id: Option, + }, + /// The attribute is a macro, which will resolve into one or more attributes or macros. + Macro { + /// The location of the parent macro which referred to this one in the list with all in-order attributes and macros, + /// or `None` if this is macro wasn't resolved by another one. + /// + /// Use [`Outcome::match_by_id()`] to retrieve the parent. + parent_macro_id: Option, + }, +} + +/// The result of a search, containing all matching attributes. +#[derive(Default)] +pub struct Outcome { + /// The list of all available attributes, by ascending order. Each slots index corresponds to an attribute with that order, i.e. + /// `arr[attr.id] = `. + /// + /// This list needs to be up-to-date with the search group so all possible attribute names are known. + matches_by_id: Vec, + /// A stack of attributes to use for processing attributes of matched patterns and for resolving their macros. + attrs_stack: SmallVec<[(AttributeId, Assignment, Option); 8]>, + /// A set of attributes we should limit ourselves to, or empty if we should fill in all attributes, made of + selected: SmallVec<[(KString, Option); AVERAGE_NUM_ATTRS]>, + /// storage for all patterns we have matched so far (in order to avoid referencing them, we copy them, but only once). + patterns: RefMap, + /// storage for all assignments we have matched so far (in order to avoid referencing them, we copy them, but only once). + assignments: RefMap, + /// storage for all source paths we have matched so far (in order to avoid referencing them, we copy them, but only once). + source_paths: RefMap, + /// The amount of attributes that still need to be set, or `None` if this outcome is consumed which means it + /// needs to be re-initialized. + remaining: Option, +} + +#[derive(Default, Clone)] +struct Slot { + r#match: Option, + /// A list of all assignments, being an empty list for non-macro attributes, or all assignments (with order) for macros. + /// It's used to resolve macros. + macro_attributes: Assignments, +} + +/// A type to denote an id of an attribute assignment for uniquely identifying each attribute or assignment. +#[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone, Copy)] +pub struct AttributeId(pub usize); + +impl Default for AttributeId { + fn default() -> Self { + AttributeId(usize::MAX) + } +} + +/// A utility type to collect metadata for each attribute, unified by its name. +#[derive(Clone, Debug, Default)] +pub struct MetadataCollection { + /// A mapping of an attribute or macro name to its order, that is the time when it was *first* seen. + /// + /// This is the inverse of the order attributes are searched. + name_to_meta: HashMap, +} + +/// Metadata associated with an attribute or macro name. +#[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone)] +pub struct Metadata { + /// The id to uniquely identify an attribute in the [MetadataCollection]. + pub id: AttributeId, + /// If non-zero in length, this entry belongs to a macro which resolves to these attribute names. + pub macro_attributes: Assignments, +} + +const AVERAGE_NUM_ATTRS: usize = 3; diff --git a/vendor/gix-attributes/src/search/outcome.rs b/vendor/gix-attributes/src/search/outcome.rs new file mode 100644 index 000000000..5d5a26b44 --- /dev/null +++ b/vendor/gix-attributes/src/search/outcome.rs @@ -0,0 +1,365 @@ +use bstr::{BString, ByteSlice}; +use gix_glob::Pattern; +use kstring::{KString, KStringRef}; + +use crate::search::refmap::RefMapKey; +use crate::{ + search::{ + Assignments, AttributeId, Attributes, MatchKind, Metadata, MetadataCollection, Outcome, TrackedAssignment, + Value, + }, + AssignmentRef, NameRef, StateRef, +}; + +/// Initialization +impl Outcome { + /// Initialize this instance to collect outcomes for all names in `collection`, which represents all possible attributes + /// or macros we may visit, and [`reset`][Self::reset()] it unconditionally. + /// + /// This must be called after each time `collection` changes. + pub fn initialize(&mut self, collection: &MetadataCollection) { + if self.matches_by_id.len() != collection.name_to_meta.len() { + let global_num_attrs = collection.name_to_meta.len(); + + self.matches_by_id.resize(global_num_attrs, Default::default()); + + // NOTE: This works only under the assumption that macros remain defined. + for (order, macro_attributes) in collection.iter().filter_map(|(_, meta)| { + (!meta.macro_attributes.is_empty()).then_some((meta.id.0, &meta.macro_attributes)) + }) { + self.matches_by_id[order].macro_attributes = macro_attributes.clone() + } + } + self.reset(); + } + + /// Like [`initialize()`][Self::initialize()], but limits the set of attributes to look for and fill in + /// to `attribute_names`. + /// Users of this instance should prefer to limit their search as this would allow it to finish earlier. + /// + /// Note that `attribute_names` aren't validated to be valid names here, as invalid names definitely will always be unspecified. + pub fn initialize_with_selection<'a>( + &mut self, + collection: &MetadataCollection, + attribute_names: impl IntoIterator>>, + ) { + self.initialize(collection); + + self.selected.clear(); + self.selected.extend(attribute_names.into_iter().map(|name| { + let name = name.into(); + ( + name.to_owned(), + collection.name_to_meta.get(name.as_str()).map(|meta| meta.id), + ) + })); + self.reset_remaining(); + } + + /// Prepare for a new search over the known set of attributes by resetting our state. + pub fn reset(&mut self) { + self.matches_by_id.iter_mut().for_each(|item| item.r#match = None); + self.attrs_stack.clear(); + self.reset_remaining(); + } + + fn reset_remaining(&mut self) { + self.remaining = Some(if self.selected.is_empty() { + self.matches_by_id.len() + } else { + self.selected.iter().filter(|(_name, id)| id.is_some()).count() + }); + } +} + +/// Access +impl Outcome { + /// Return an iterator over all filled attributes we were initialized with. + /// + /// ### Note + /// + /// If [`initialize_with_selection`][Self::initialize_with_selection()] was used, + /// use [`iter_selected()`][Self::iter_selected()] instead. + /// + /// ### Deviation + /// + /// It's possible that the order in which the attribute are returned (if not limited to a set of attributes) isn't exactly + /// the same as what `git` provides. + /// Ours is in order of declaration, whereas `git` seems to list macros first somehow. Since the values are the same, this + /// shouldn't be an issue. + pub fn iter(&self) -> impl Iterator> { + self.matches_by_id + .iter() + .filter_map(|item| item.r#match.as_ref().map(|m| m.to_outer(self))) + } + + /// Iterate over all matches of the attribute selection in their original order. + /// + /// This only yields values if this instance was initialized with [`Outcome::initialize_with_selection()`]. + pub fn iter_selected(&self) -> impl Iterator> { + static DUMMY: Pattern = Pattern { + text: BString::new(Vec::new()), + mode: gix_glob::pattern::Mode::empty(), + first_wildcard_pos: None, + }; + self.selected.iter().map(|(name, id)| { + id.and_then(|id| self.matches_by_id[id.0].r#match.as_ref().map(|m| m.to_outer(self))) + .unwrap_or_else(|| crate::search::Match { + pattern: &DUMMY, + assignment: AssignmentRef { + name: NameRef::try_from(name.as_bytes().as_bstr()) + .unwrap_or_else(|_| NameRef("invalid".into())), + state: StateRef::Unspecified, + }, + kind: MatchKind::Attribute { macro_id: None }, + location: crate::search::MatchLocation { + source: None, + sequence_number: 0, + }, + }) + }) + } + + /// Obtain a match by the order of its attribute, if the order exists in our initialized attribute list and there was a match. + pub fn match_by_id(&self, id: AttributeId) -> Option> { + self.matches_by_id + .get(id.0) + .and_then(|m| m.r#match.as_ref().map(|m| m.to_outer(self))) + } + + /// Return `true` if there is nothing more to be done as all attributes were filled. + pub fn is_done(&self) -> bool { + self.remaining() == 0 + } +} + +/// Mutation +impl Outcome { + /// Fill all `attrs` and resolve them recursively if they are macros. Return `true` if there is no attribute left to be resolved and + /// we are totally done. + /// `pattern` is what matched a patch and is passed for contextual information, + /// providing `sequence_number` and `source` as well. + pub(crate) fn fill_attributes<'a>( + &mut self, + attrs: impl Iterator, + pattern: &gix_glob::Pattern, + source: Option<&std::path::PathBuf>, + sequence_number: usize, + ) -> bool { + self.attrs_stack.extend(attrs.filter_map(|attr| { + self.matches_by_id[attr.id.0] + .r#match + .is_none() + .then(|| (attr.id, attr.inner.clone(), None)) + })); + while let Some((id, assignment, parent_order)) = self.attrs_stack.pop() { + let slot = &mut self.matches_by_id[id.0]; + if slot.r#match.is_some() { + continue; + } + // Let's be explicit - this is only non-empty for macros. + let is_macro = !slot.macro_attributes.is_empty(); + + slot.r#match = Some(Match { + pattern: self.patterns.insert(pattern), + assignment: self.assignments.insert_owned(assignment), + kind: if is_macro { + MatchKind::Macro { + parent_macro_id: parent_order, + } + } else { + MatchKind::Attribute { macro_id: parent_order } + }, + location: MatchLocation { + source: source.map(|path| self.source_paths.insert(path)), + sequence_number, + }, + }); + if self.reduce_and_check_if_done(id) { + return true; + } + + if is_macro { + // TODO(borrowchk): one fine day we should be able to re-borrow `slot` without having to redo the array access. + let slot = &self.matches_by_id[id.0]; + self.attrs_stack.extend(slot.macro_attributes.iter().filter_map(|attr| { + self.matches_by_id[attr.id.0] + .r#match + .is_none() + .then(|| (attr.id, attr.inner.clone(), Some(id))) + })); + } + } + false + } +} + +impl Outcome { + /// Given a list of `attrs` by order, return true if at least one of them is not set + pub(crate) fn has_unspecified_attributes(&self, mut attrs: impl Iterator) -> bool { + attrs.any(|order| self.matches_by_id[order.0].r#match.is_none()) + } + /// Return the amount of attributes haven't yet been found. + /// + /// If this number reaches 0, then the search can be stopped as there is nothing more to fill in. + pub(crate) fn remaining(&self) -> usize { + self.remaining + .expect("BUG: instance must be initialized for each search set") + } + + fn reduce_and_check_if_done(&mut self, attr: AttributeId) -> bool { + if self.selected.is_empty() + || self + .selected + .iter() + .any(|(_name, id)| id.map_or(false, |id| id == attr)) + { + *self.remaining.as_mut().expect("initialized") -= 1; + } + self.is_done() + } +} + +/// Mutation +impl MetadataCollection { + /// Assign order ids to each attribute either in macros (along with macros themselves) or attributes of patterns, and store + /// them in this collection. + /// + /// Must be called before querying matches. + pub fn update_from_list(&mut self, list: &mut gix_glob::search::pattern::List) { + for pattern in &mut list.patterns { + match &mut pattern.value { + Value::MacroAssignments { id: order, assignments } => { + *order = self.id_for_macro( + pattern + .pattern + .text + .to_str() + .expect("valid macro names are always UTF8 and this was verified"), + assignments, + ); + } + Value::Assignments(assignments) => { + self.assign_order_to_attributes(assignments); + } + } + } + } +} + +/// Access +impl MetadataCollection { + /// Return an iterator over the contents of the map in an easy-to-consume form. + pub fn iter(&self) -> impl Iterator { + self.name_to_meta.iter().map(|(k, v)| (k.as_str(), v)) + } +} + +impl MetadataCollection { + pub(crate) fn id_for_macro(&mut self, name: &str, attrs: &mut Assignments) -> AttributeId { + let order = match self.name_to_meta.get_mut(name) { + Some(meta) => meta.id, + None => { + let order = AttributeId(self.name_to_meta.len()); + self.name_to_meta.insert( + KString::from_ref(name), + Metadata { + id: order, + macro_attributes: Default::default(), + }, + ); + order + } + }; + + self.assign_order_to_attributes(attrs); + self.name_to_meta.get_mut(name).expect("just added").macro_attributes = attrs.clone(); + + order + } + pub(crate) fn id_for_attribute(&mut self, name: &str) -> AttributeId { + match self.name_to_meta.get(name) { + Some(meta) => meta.id, + None => { + let order = AttributeId(self.name_to_meta.len()); + self.name_to_meta.insert(KString::from_ref(name), order.into()); + order + } + } + } + pub(crate) fn assign_order_to_attributes(&mut self, attributes: &mut [TrackedAssignment]) { + for TrackedAssignment { + id: order, + inner: crate::Assignment { name, .. }, + } in attributes + { + *order = self.id_for_attribute(&name.0); + } + } +} + +impl From for Metadata { + fn from(order: AttributeId) -> Self { + Metadata { + id: order, + macro_attributes: Default::default(), + } + } +} + +impl MatchKind { + /// return the id of the macro that resolved us, or `None` if that didn't happen. + pub fn source_id(&self) -> Option { + match self { + MatchKind::Attribute { macro_id: id } | MatchKind::Macro { parent_macro_id: id } => *id, + } + } +} + +/// A version of `Match` without references. +#[derive(Clone, PartialEq, Eq, Debug, Hash, Ord, PartialOrd)] +pub struct Match { + /// The glob pattern itself, like `/target/*`. + pub pattern: RefMapKey, + /// The key=value pair of the attribute that matched at the pattern. There can be multiple matches per pattern. + pub assignment: RefMapKey, + /// Additional information about the kind of match. + pub kind: MatchKind, + /// Information about the location of the match. + pub location: MatchLocation, +} + +impl Match { + fn to_outer<'a>(&self, out: &'a Outcome) -> crate::search::Match<'a> { + crate::search::Match { + pattern: out.patterns.resolve(self.pattern).expect("pattern still present"), + assignment: out + .assignments + .resolve(self.assignment) + .expect("assignment present") + .as_ref(), + kind: self.kind, + location: self.location.to_outer(out), + } + } +} + +/// A version of `MatchLocation` without references. +#[derive(Clone, PartialEq, Eq, Debug, Hash, Ord, PartialOrd)] +pub struct MatchLocation { + /// The path to the source from which the pattern was loaded, or `None` if it was specified by other means. + pub source: Option, + /// The line at which the pattern was found in its `source` file, or the occurrence in which it was provided. + pub sequence_number: usize, +} + +impl MatchLocation { + fn to_outer<'a>(&self, out: &'a Outcome) -> crate::search::MatchLocation<'a> { + crate::search::MatchLocation { + source: self + .source + .and_then(|source| out.source_paths.resolve(source).map(|p| p.as_path())), + sequence_number: self.sequence_number, + } + } +} diff --git a/vendor/gix-attributes/src/search/refmap.rs b/vendor/gix-attributes/src/search/refmap.rs new file mode 100644 index 000000000..3dc51265c --- /dev/null +++ b/vendor/gix-attributes/src/search/refmap.rs @@ -0,0 +1,52 @@ +//! A utility to store objects by identity, which deduplicates them while avoiding lifetimes. +//! +//! We chose to use hashing/identity over pointers as it's possible that different objects end up in the same memory location, +//! which would create obscure bugs. The same could happen with hash collisions, but they these are designed to be less likely. +use std::collections::btree_map::Entry; +use std::collections::hash_map::DefaultHasher; +use std::collections::BTreeMap; +use std::hash::{Hash, Hasher}; + +pub(crate) type RefMapKey = u64; +pub(crate) struct RefMap(BTreeMap); + +impl Default for RefMap { + fn default() -> Self { + RefMap(Default::default()) + } +} + +impl RefMap +where + T: Hash + Clone, +{ + pub(crate) fn insert(&mut self, value: &T) -> RefMapKey { + let mut s = DefaultHasher::new(); + value.hash(&mut s); + let key = s.finish(); + match self.0.entry(key) { + Entry::Vacant(e) => { + e.insert(value.clone()); + key + } + Entry::Occupied(_) => key, + } + } + + pub(crate) fn insert_owned(&mut self, value: T) -> RefMapKey { + let mut s = DefaultHasher::new(); + value.hash(&mut s); + let key = s.finish(); + match self.0.entry(key) { + Entry::Vacant(e) => { + e.insert(value); + key + } + Entry::Occupied(_) => key, + } + } + + pub(crate) fn resolve(&self, key: RefMapKey) -> Option<&T> { + self.0.get(&key) + } +} diff --git a/vendor/gix-attributes/src/source.rs b/vendor/gix-attributes/src/source.rs new file mode 100644 index 000000000..4977c4739 --- /dev/null +++ b/vendor/gix-attributes/src/source.rs @@ -0,0 +1,27 @@ +use std::{borrow::Cow, ffi::OsString, path::Path}; + +use crate::Source; + +impl Source { + /// Produce a storage location for the this source while potentially querying environment variables using `env_var()`, + /// or `None` if the storage location could not be obtained. + /// + /// Note that local sources are returned as relative paths to be joined with the base in a separate step. + pub fn storage_location(self, env_var: &mut dyn FnMut(&str) -> Option) -> Option> { + use Source::*; + Some(match self { + GitInstallation => gix_path::env::installation_config_prefix()? + .join("gitattributes") + .into(), + System => { + if env_var("GIT_ATTR_NOSYSTEM").is_some() { + return None; + } else { + gix_path::env::system_prefix()?.join("etc/gitattributes").into() + } + } + Git => return gix_path::env::xdg_config("attributes", env_var).map(Cow::Owned), + Local => Cow::Borrowed(Path::new("info/attributes")), + }) + } +} diff --git a/vendor/gix-attributes/src/state.rs b/vendor/gix-attributes/src/state.rs index 02dc8ee0d..27ce2a247 100644 --- a/vendor/gix-attributes/src/state.rs +++ b/vendor/gix-attributes/src/state.rs @@ -1,7 +1,85 @@ -use bstr::ByteSlice; +use bstr::{BStr, ByteSlice}; +use kstring::{KString, KStringRef}; use crate::{State, StateRef}; +/// A container to encapsulate a tightly packed and typically unallocated byte value that isn't necessarily UTF8 encoded. +#[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct Value(KString); + +/// A reference container to encapsulate a tightly packed and typically unallocated byte value that isn't necessarily UTF8 encoded. +#[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone, Copy)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct ValueRef<'a>(#[cfg_attr(feature = "serde", serde(borrow))] KStringRef<'a>); + +/// Conversions +impl<'a> ValueRef<'a> { + /// Keep `input` as our value. + pub fn from_bytes(input: &'a [u8]) -> Self { + Self(KStringRef::from_ref( + // SAFETY: our API makes accessing that value as `str` impossible, so illformed UTF8 is never exposed as such. + #[allow(unsafe_code)] + unsafe { + std::str::from_utf8_unchecked(input) + }, + )) + } + + /// Access this value as byte string. + pub fn as_bstr(&self) -> &BStr { + self.0.as_bytes().as_bstr() + } + + /// Convert this instance into its owned form. + pub fn to_owned(self) -> Value { + self.into() + } +} + +impl<'a> From<&'a str> for ValueRef<'a> { + fn from(v: &'a str) -> Self { + ValueRef(v.into()) + } +} + +impl<'a> From> for Value { + fn from(v: ValueRef<'a>) -> Self { + Value(v.0.into()) + } +} + +impl From<&str> for Value { + fn from(v: &str) -> Self { + Value(KString::from_ref(v)) + } +} + +/// Access +impl Value { + /// Return ourselves as reference. + pub fn as_ref(&self) -> ValueRef<'_> { + ValueRef(self.0.as_ref()) + } +} + +/// Access +impl StateRef<'_> { + /// Return `true` if the associated attribute was set to be unspecified using the `!attr` prefix or it wasn't mentioned. + pub fn is_unspecified(&self) -> bool { + matches!(self, StateRef::Unspecified) + } +} + +/// Initialization +impl<'a> StateRef<'a> { + /// Keep `input` in one of our enums. + pub fn from_bytes(input: &'a [u8]) -> Self { + Self::Value(ValueRef::from_bytes(input)) + } +} + +/// Access impl<'a> StateRef<'a> { /// Turn ourselves into our owned counterpart. pub fn to_owned(self) -> State { @@ -13,7 +91,7 @@ impl<'a> State { /// Turn ourselves into our ref-type. pub fn as_ref(&'a self) -> StateRef<'a> { match self { - State::Value(v) => StateRef::Value(v.as_bytes().as_bstr()), + State::Value(v) => StateRef::Value(v.as_ref()), State::Set => StateRef::Set, State::Unset => StateRef::Unset, State::Unspecified => StateRef::Unspecified, @@ -24,7 +102,7 @@ impl<'a> State { impl<'a> From> for State { fn from(s: StateRef<'a>) -> Self { match s { - StateRef::Value(v) => State::Value(v.to_str().expect("no illformed unicode").into()), + StateRef::Value(v) => State::Value(v.into()), StateRef::Set => State::Set, StateRef::Unset => State::Unset, StateRef::Unspecified => State::Unspecified, -- cgit v1.2.3