gix_pathspec/lib.rs
1//! Parse [path specifications](https://git-scm.com/docs/gitglossary#Documentation/gitglossary.txt-aiddefpathspecapathspec) and
2//! see if a path matches.
3//!
4//! ## Examples
5//!
6//! ```
7//! # fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
8//! use std::path::Path;
9//!
10//! fn no_attrs(
11//! _path: &bstr::BStr,
12//! _case: gix_pathspec::attributes::glob::pattern::Case,
13//! _is_dir: bool,
14//! _out: &mut gix_pathspec::attributes::search::Outcome,
15//! ) -> bool {
16//! false
17//! }
18//!
19//! let specs = ["src/**", ":!src/generated/**"]
20//! .into_iter()
21//! .map(|spec| gix_pathspec::parse(spec.as_bytes(), Default::default()).unwrap());
22//! let mut search = gix_pathspec::Search::from_specs(specs, None, Path::new(""))?;
23//!
24//! assert!(search.can_match_relative_path("src".into(), Some(true)));
25//!
26//! let matched = search
27//! .pattern_matching_relative_path("src/lib.rs".into(), Some(false), &mut no_attrs)
28//! .unwrap();
29//! assert_eq!(matched.pattern.path(), "src/**");
30//! assert!(!matched.pattern.is_excluded());
31//!
32//! let excluded = search
33//! .pattern_matching_relative_path("src/generated/lib.rs".into(), Some(false), &mut no_attrs)
34//! .unwrap();
35//! assert_eq!(excluded.pattern.to_bstring(), ":(exclude)src/generated/**");
36//! assert!(excluded.pattern.is_excluded());
37//! # Ok(()) }
38//! ```
39#![deny(missing_docs)]
40#![forbid(unsafe_code)]
41
42use gix_error::Result;
43use std::path::PathBuf;
44
45use bitflags::bitflags;
46use bstr::BString;
47/// `gix-glob` types are available through [`attributes::glob`].
48pub use gix_attributes as attributes;
49
50mod pattern;
51
52///
53pub mod search;
54
55///
56pub mod parse;
57
58/// Default settings for some fields of a [`Pattern`].
59///
60/// These can be used to represent `GIT_*_PATHSPECS` environment variables, for example.
61#[derive(Debug, Default, Copy, Clone, Ord, PartialOrd, Eq, PartialEq)]
62pub struct Defaults {
63 /// The default signature.
64 pub signature: MagicSignature,
65 /// The default search-mode.
66 ///
67 /// Note that even if it's [`SearchMode::Literal`], the pathspecs will be parsed as usual, but matched verbatim afterwards.
68 ///
69 /// Note that pathspecs can override this the [`SearchMode::Literal`] variant with an explicit `:(glob)` prefix.
70 pub search_mode: SearchMode,
71 /// If set, the pathspec will not be parsed but used verbatim. Implies [`SearchMode::Literal`] for `search_mode`.
72 pub literal: bool,
73}
74
75///
76pub mod defaults;
77
78/// A lists of pathspec patterns, possibly from a file.
79///
80/// Pathspecs are generally relative to the root of the repository.
81#[derive(Debug, Clone)]
82pub struct Search {
83 /// Patterns and their associated data in the order they were loaded in or specified,
84 /// the line number in its source file or its sequence number (_`(pattern, value, line_number)`_).
85 ///
86 /// During matching, this order is reversed.
87 patterns: Vec<gix_glob::search::pattern::Mapping<search::Spec>>,
88
89 /// The synthetic pattern returned when an empty search matches everything.
90 /// Can't be static as it's not (always) `Send` due to `gix-attributes/?parallel`.
91 match_all: Pattern,
92
93 /// The path from which the patterns were read, or `None` if the patterns
94 /// don't originate in a file on disk.
95 pub source: Option<PathBuf>,
96
97 /// If `true`, this means all `patterns` are exclude patterns. This means that if there is no match
98 /// (which would exclude an item), we would actually match it for lack of exclusion.
99 all_patterns_are_excluded: bool,
100 /// The amount of bytes that are in common among all `patterns` and that aren't matched case-insensitively
101 common_prefix_len: usize,
102}
103
104/// The output of a pathspec [parsing][parse()] operation. It can be used to match against a one or more paths.
105#[derive(Default, PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone)]
106pub struct Pattern {
107 /// The path part of a pathspec, which is typically a path possibly mixed with glob patterns.
108 /// Note that it might be an empty string as well.
109 ///
110 /// For example, `:(top,literal,icase,attr,exclude)some/path` would yield `some/path`.
111 path: BString,
112 /// All magic signatures that were included in the pathspec.
113 pub signature: MagicSignature,
114 /// The search mode of the pathspec.
115 pub search_mode: SearchMode,
116 /// All attributes that were included in the `ATTR` part of the pathspec, if present.
117 ///
118 /// `:(attr:a=one b=):path` would yield attribute `a` and `b`.
119 pub attributes: Vec<gix_attributes::Assignment>,
120 /// If `true`, we are a special Nil pattern and always match.
121 nil: bool,
122 /// The length of bytes in `path` that belong to the prefix, which will always be matched case-sensitively
123 /// on case-sensitive filesystems.
124 ///
125 /// That way, even though pathspecs are applied from the top, we can emulate having changed directory into
126 /// a specific sub-directory in a case-sensitive file-system, even if the rest of the pathspec can be set to
127 /// match case-insensitively.
128 /// Is set by [Pattern::normalize()].
129 prefix_len: usize,
130}
131
132bitflags! {
133 /// Flags to represent 'magic signatures' which are parsed behind colons, like `:top:`.
134 #[derive(Default, PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone, Copy)]
135 pub struct MagicSignature: u32 {
136 /// Matches patterns from the root of the repository
137 const TOP = 1 << 0;
138 /// Matches patterns in case insensitive mode
139 const ICASE = 1 << 1;
140 /// Excludes the matching patterns from the previous results
141 const EXCLUDE = 1 << 2;
142 /// The pattern must match a directory, and not a file.
143 /// This is equivalent to how it's handled in `gix-glob`
144 const MUST_BE_DIR = 1 << 3;
145 }
146}
147
148/// Parts of [magic signatures][MagicSignature] which don't stack as they all configure
149/// the way path specs are matched.
150#[derive(Default, PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone, Copy)]
151pub enum SearchMode {
152 /// Expand special characters like `*` similar to how the shell would do it.
153 ///
154 /// See [`PathAwareGlob`](SearchMode::PathAwareGlob) for the alternative.
155 #[default]
156 ShellGlob,
157 /// Special characters in the pattern, like `*` or `?`, are treated literally, effectively turning off globbing.
158 Literal,
159 /// A single `*` will not match a `/` in the pattern, but a `**` will
160 PathAwareGlob,
161}
162
163/// Parse a git-style pathspec into a [`Pattern`],
164/// setting the given `default` values in case these aren't specified in `input`.
165///
166/// Note that empty [paths](Pattern::path) are allowed here, and generally some processing has to be performed.
167pub fn parse(input: &[u8], default: Defaults) -> Result<Pattern> {
168 Pattern::from_bytes(input, default)
169}