1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
//! Parse [path specifications](https://git-scm.com/docs/gitglossary#Documentation/gitglossary.txt-aiddefpathspecapathspec) and
//! see if a path matches.
#![deny(missing_docs, rust_2018_idioms)]
#![forbid(unsafe_code)]

use std::path::PathBuf;

use bitflags::bitflags;
use bstr::BString;
/// `gix-glob` types are available through [`attributes::glob`].
pub use gix_attributes as attributes;

///
pub mod normalize {
    use std::path::PathBuf;

    /// The error returned by [Pattern::normalize()](super::Pattern::normalize()).
    #[derive(Debug, thiserror::Error)]
    #[allow(missing_docs)]
    pub enum Error {
        #[error("The path '{}' is not inside of the worktree '{}'", path.display(), worktree_path.display())]
        AbsolutePathOutsideOfWorktree { path: PathBuf, worktree_path: PathBuf },
        #[error("The path '{}' leaves the repository", path.display())]
        OutsideOfWorktree { path: PathBuf },
    }
}

mod pattern;

///
pub mod search;

///
pub mod parse;

/// Default settings for some fields of a [`Pattern`].
///
/// These can be used to represent `GIT_*_PATHSPECS` environment variables, for example.
#[derive(Debug, Default, Copy, Clone, Ord, PartialOrd, Eq, PartialEq)]
pub struct Defaults {
    /// The default signature.
    pub signature: MagicSignature,
    /// The default search-mode.
    ///
    /// Note that even if it's [`SearchMode::Literal`], the pathspecs will be parsed as usual, but matched verbatim afterwards.
    ///
    /// Note that pathspecs can override this the [`SearchMode::Literal`] variant with an explicit `:(glob)` prefix.
    pub search_mode: SearchMode,
    /// If set, the pathspec will not be parsed but used verbatim. Implies [`SearchMode::Literal`] for `search_mode`.
    pub literal: bool,
}

///
pub mod defaults;

/// A lists of pathspec patterns, possibly from a file.
///
/// Pathspecs are generally relative to the root of the repository.
#[derive(Debug, Clone)]
pub struct Search {
    /// 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)`_).
    ///
    /// During matching, this order is reversed.
    patterns: Vec<gix_glob::search::pattern::Mapping<search::Spec>>,

    /// The path from which the patterns were read, or `None` if the patterns
    /// don't originate in a file on disk.
    pub source: Option<PathBuf>,

    /// If `true`, this means all `patterns` are exclude patterns. This means that if there is no match
    /// (which would exclude an item), we would actually match it for lack of exclusion.
    all_patterns_are_excluded: bool,
    /// The amount of bytes that are in common among all `patterns` and that aren't matched case-insensitively
    common_prefix_len: usize,
}

/// The output of a pathspec [parsing][parse()] operation. It can be used to match against a one or more paths.
#[derive(Default, PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone)]
pub struct Pattern {
    /// The path part of a pathspec, which is typically a path possibly mixed with glob patterns.
    /// Note that it might be an empty string as well.
    ///
    /// For example, `:(top,literal,icase,attr,exclude)some/path` would yield `some/path`.
    path: BString,
    /// All magic signatures that were included in the pathspec.
    pub signature: MagicSignature,
    /// The search mode of the pathspec.
    pub search_mode: SearchMode,
    /// All attributes that were included in the `ATTR` part of the pathspec, if present.
    ///
    /// `:(attr:a=one b=):path` would yield attribute `a` and `b`.
    pub attributes: Vec<gix_attributes::Assignment>,
    /// If `true`, we are a special Nil pattern and always match.
    nil: bool,
    /// The length of bytes in `path` that belong to the prefix, which will always be matched case-sensitively
    /// on case-sensitive filesystems.
    ///
    /// That way, even though pathspecs are applied from the top, we can emulate having changed directory into
    /// a specific sub-directory in a case-sensitive file-system, even if the rest of the pathspec can be set to
    /// match case-insensitively.
    /// Is set by [Pattern::normalize()].
    prefix_len: usize,
}

bitflags! {
    /// Flags to represent 'magic signatures' which are parsed behind colons, like `:top:`.
    #[derive(Default, PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone, Copy)]
    pub struct MagicSignature: u32 {
        /// Matches patterns from the root of the repository
        const TOP = 1 << 0;
        /// Matches patterns in case insensitive mode
        const ICASE = 1 << 1;
        /// Excludes the matching patterns from the previous results
        const EXCLUDE = 1 << 2;
        /// The pattern must match a directory, and not a file.
        /// This is equivalent to how it's handled in `gix-glob`
        const MUST_BE_DIR = 1 << 3;
    }
}

/// Parts of [magic signatures][MagicSignature] which don't stack as they all configure
/// the way path specs are matched.
#[derive(Default, PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone, Copy)]
pub enum SearchMode {
    /// Expand special characters like `*` similar to how the shell would do it.
    ///
    /// See [`PathAwareGlob`](SearchMode::PathAwareGlob) for the alternative.
    #[default]
    ShellGlob,
    /// Special characters in the pattern, like `*` or `?`, are treated literally, effectively turning off globbing.
    Literal,
    /// A single `*` will not match a `/` in the pattern, but a `**` will
    PathAwareGlob,
}

/// Parse a git-style pathspec into a [`Pattern`],
/// setting the given `default` values in case these aren't specified in `input`.
///
/// Note that empty [paths](Pattern::path) are allowed here, and generally some processing has to be performed.
pub fn parse(input: &[u8], default: Defaults) -> Result<Pattern, parse::Error> {
    Pattern::from_bytes(input, default)
}