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
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
use std::path::{Component, Path, PathBuf};

use bstr::{BStr, BString, ByteSlice, ByteVec};

use crate::{normalize, MagicSignature, Pattern, SearchMode};

/// Access
impl Pattern {
    /// Returns `true` if this seems to be a pathspec that indicates that 'there is no pathspec'.
    ///
    /// Note that such a spec is `:`.
    pub fn is_nil(&self) -> bool {
        self.nil
    }

    /// Return the prefix-portion of the `path` of this spec, which is a *directory*.
    /// It can be empty if there is no prefix.
    ///
    /// A prefix is effectively the CWD seen as relative to the working tree, and it's assumed to
    /// match case-sensitively. This makes it useful for skipping over large portions of input by
    /// directly comparing them.
    pub fn prefix_directory(&self) -> &BStr {
        self.path[..self.prefix_len].as_bstr()
    }

    /// Return the path of this spec, typically used for matching.
    pub fn path(&self) -> &BStr {
        self.path.as_ref()
    }
}

/// Mutation
impl Pattern {
    /// Normalize the pattern's path by assuring it's relative to the root of the working tree, and contains
    /// no relative path components. Further, it assures that `/` are used as path separator.
    ///
    /// If `self.path` is a relative path, it will be put in front of the pattern path if `self.signature` isn't indicating `TOP` already.
    /// If `self.path` is an absolute path, we will use `root` to make it worktree relative if possible.
    ///
    /// `prefix` can be empty, we will still normalize this pathspec to resolve relative path components, and
    /// it is assumed not to contain any relative path components, e.g. '', 'a', 'a/b' are valid.
    /// `root` is the absolute path to the root of either the worktree or the repository's `git_dir`.
    pub fn normalize(&mut self, prefix: &Path, root: &Path) -> Result<&mut Self, normalize::Error> {
        fn prefix_components_to_subtract(path: &Path) -> usize {
            let parent_component_end_bound = path.components().enumerate().fold(None::<usize>, |acc, (idx, c)| {
                matches!(c, Component::ParentDir).then_some(idx + 1).or(acc)
            });
            let count = path
                .components()
                .take(parent_component_end_bound.unwrap_or(0))
                .map(|c| match c {
                    Component::ParentDir => 1_isize,
                    Component::Normal(_) => -1,
                    _ => 0,
                })
                .sum::<isize>();
            (count > 0).then_some(count as usize).unwrap_or_default()
        }

        let mut path = gix_path::from_bstr(self.path.as_bstr());
        let mut num_prefix_components = 0;
        let mut was_absolute = false;
        if gix_path::is_absolute(path.as_ref()) {
            was_absolute = true;
            let rela_path = match path.strip_prefix(root) {
                Ok(path) => path,
                Err(_) => {
                    return Err(normalize::Error::AbsolutePathOutsideOfWorktree {
                        path: path.into_owned(),
                        worktree_path: root.into(),
                    })
                }
            };
            path = rela_path.to_owned().into();
        } else if !prefix.as_os_str().is_empty() && !self.signature.contains(MagicSignature::TOP) {
            debug_assert_eq!(
                prefix
                    .components()
                    .filter(|c| matches!(c, Component::Normal(_)))
                    .count(),
                prefix.components().count(),
                "BUG: prefixes must not have relative path components, or calculations here will be wrong so pattern won't match"
            );
            num_prefix_components = prefix
                .components()
                .count()
                .saturating_sub(prefix_components_to_subtract(path.as_ref()));
            path = prefix.join(path).into();
        }

        let assure_path_cannot_break_out_upwards = Path::new("");
        let path = match gix_path::normalize(path.as_ref().into(), assure_path_cannot_break_out_upwards) {
            Some(path) => {
                if was_absolute {
                    num_prefix_components = path.components().count().saturating_sub(
                        if self.signature.contains(MagicSignature::MUST_BE_DIR) {
                            0
                        } else {
                            1
                        },
                    );
                }
                path
            }
            None => {
                return Err(normalize::Error::OutsideOfWorktree {
                    path: path.into_owned(),
                })
            }
        };

        self.path = if path == Path::new(".") {
            BString::from(".")
        } else {
            let cleaned = PathBuf::from_iter(path.components().filter(|c| !matches!(c, Component::CurDir)));
            let mut out = gix_path::to_unix_separators_on_windows(gix_path::into_bstr(cleaned)).into_owned();
            self.prefix_len = {
                if self.signature.contains(MagicSignature::MUST_BE_DIR) {
                    out.push(b'/');
                }
                let len = out
                    .find_iter(b"/")
                    .take(num_prefix_components)
                    .last()
                    .unwrap_or_default();
                if self.signature.contains(MagicSignature::MUST_BE_DIR) {
                    out.pop();
                }
                len
            };
            out
        };

        Ok(self)
    }
}

/// Access
impl Pattern {
    /// Return `true` if this pathspec is negated, which means it will exclude an item from the result set instead of including it.
    pub fn is_excluded(&self) -> bool {
        self.signature.contains(MagicSignature::EXCLUDE)
    }

    /// Translate ourselves to a long display format, that when parsed back will yield the same pattern.
    ///
    /// Note that the
    pub fn to_bstring(&self) -> BString {
        if self.is_nil() {
            ":".into()
        } else {
            let mut buf: BString = ":(".into();
            if self.signature.contains(MagicSignature::TOP) {
                buf.push_str("top,");
            }
            if self.signature.contains(MagicSignature::EXCLUDE) {
                buf.push_str("exclude,");
            }
            if self.signature.contains(MagicSignature::ICASE) {
                buf.push_str("icase,");
            }
            match self.search_mode {
                SearchMode::ShellGlob => {}
                SearchMode::Literal => buf.push_str("literal,"),
                SearchMode::PathAwareGlob => buf.push_str("glob,"),
            }
            if self.attributes.is_empty() {
                if buf.last() == Some(&b',') {
                    buf.pop();
                }
            } else {
                buf.push_str("attr:");
                for attr in &self.attributes {
                    let attr = attr.as_ref().to_string().replace(',', "\\,");
                    buf.push_str(&attr);
                    buf.push(b' ');
                }
                buf.pop(); // trailing ' '
            }
            buf.push(b')');
            buf.extend_from_slice(&self.path);
            if self.signature.contains(MagicSignature::MUST_BE_DIR) {
                buf.push(b'/');
            }
            buf
        }
    }
}

impl std::fmt::Display for Pattern {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        self.to_bstring().fmt(f)
    }
}