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