1use std::path::{Component, Path, PathBuf};
2
3use bstr::{BStr, BString, ByteSlice, ByteVec};
4
5use crate::{normalize, MagicSignature, Pattern, SearchMode};
6
7impl Pattern {
9 pub fn is_nil(&self) -> bool {
13 self.nil
14 }
15
16 pub fn prefix_directory(&self) -> &BStr {
23 self.path[..self.prefix_len].as_bstr()
24 }
25
26 pub fn path(&self) -> &BStr {
28 self.path.as_ref()
29 }
30}
31
32impl Pattern {
34 pub fn normalize(&mut self, prefix: &Path, root: &Path) -> Result<&mut Self, normalize::Error> {
44 fn prefix_components_to_subtract(path: &Path) -> usize {
45 let parent_component_end_bound = path.components().enumerate().fold(None::<usize>, |acc, (idx, c)| {
46 matches!(c, Component::ParentDir).then_some(idx + 1).or(acc)
47 });
48 let count = path
49 .components()
50 .take(parent_component_end_bound.unwrap_or(0))
51 .map(|c| match c {
52 Component::ParentDir => 1_isize,
53 Component::Normal(_) => -1,
54 _ => 0,
55 })
56 .sum::<isize>();
57 (count > 0).then_some(count as usize).unwrap_or_default()
58 }
59
60 let mut path = gix_path::from_bstr(self.path.as_bstr());
61 let mut num_prefix_components = 0;
62 let mut was_absolute = false;
63 if gix_path::is_absolute(path.as_ref()) {
64 was_absolute = true;
65 let rela_path = match path.strip_prefix(root) {
66 Ok(path) => path,
67 Err(_) => {
68 return Err(normalize::Error::AbsolutePathOutsideOfWorktree {
69 path: path.into_owned(),
70 worktree_path: root.into(),
71 })
72 }
73 };
74 path = rela_path.to_owned().into();
75 } else if !prefix.as_os_str().is_empty() && !self.signature.contains(MagicSignature::TOP) {
76 debug_assert_eq!(
77 prefix
78 .components()
79 .filter(|c| matches!(c, Component::Normal(_)))
80 .count(),
81 prefix.components().count(),
82 "BUG: prefixes must not have relative path components, or calculations here will be wrong so pattern won't match"
83 );
84 num_prefix_components = prefix
85 .components()
86 .count()
87 .saturating_sub(prefix_components_to_subtract(path.as_ref()));
88 path = prefix.join(path).into();
89 }
90
91 let assure_path_cannot_break_out_upwards = Path::new("");
92 let path = match gix_path::normalize(path.as_ref().into(), assure_path_cannot_break_out_upwards) {
93 Some(path) => {
94 if was_absolute {
95 num_prefix_components = path.components().count().saturating_sub(
96 if self.signature.contains(MagicSignature::MUST_BE_DIR) {
97 0
98 } else {
99 1
100 },
101 );
102 }
103 path
104 }
105 None => {
106 return Err(normalize::Error::OutsideOfWorktree {
107 path: path.into_owned(),
108 })
109 }
110 };
111
112 self.path = if path == Path::new(".") {
113 self.nil = true;
114 BString::from(".")
115 } else {
116 let cleaned = PathBuf::from_iter(path.components().filter(|c| !matches!(c, Component::CurDir)));
117 let mut out = gix_path::to_unix_separators_on_windows(gix_path::into_bstr(cleaned)).into_owned();
118 self.prefix_len = {
119 if self.signature.contains(MagicSignature::MUST_BE_DIR) {
120 out.push(b'/');
121 }
122 let len = out
123 .find_iter(b"/")
124 .take(num_prefix_components)
125 .last()
126 .unwrap_or_default();
127 if self.signature.contains(MagicSignature::MUST_BE_DIR) {
128 out.pop();
129 }
130 len
131 };
132 out
133 };
134
135 Ok(self)
136 }
137}
138
139impl Pattern {
141 pub fn is_excluded(&self) -> bool {
143 self.signature.contains(MagicSignature::EXCLUDE)
144 }
145
146 pub fn always_matches(&self) -> bool {
149 self.is_nil() || self.path.is_empty()
150 }
151
152 pub fn to_bstring(&self) -> BString {
156 if self.is_nil() {
157 ":".into()
158 } else {
159 let mut buf: BString = ":(".into();
160 if self.signature.contains(MagicSignature::TOP) {
161 buf.push_str("top,");
162 }
163 if self.signature.contains(MagicSignature::EXCLUDE) {
164 buf.push_str("exclude,");
165 }
166 if self.signature.contains(MagicSignature::ICASE) {
167 buf.push_str("icase,");
168 }
169 match self.search_mode {
170 SearchMode::ShellGlob => {}
171 SearchMode::Literal => buf.push_str("literal,"),
172 SearchMode::PathAwareGlob => buf.push_str("glob,"),
173 }
174 if self.attributes.is_empty() {
175 if buf.last() == Some(&b',') {
176 buf.pop();
177 }
178 } else {
179 buf.push_str("attr:");
180 for attr in &self.attributes {
181 let attr = attr.as_ref().to_string().replace(',', r"\,");
182 buf.push_str(&attr);
183 buf.push(b' ');
184 }
185 buf.pop(); }
187 buf.push(b')');
188 buf.extend_from_slice(&self.path);
189 if self.signature.contains(MagicSignature::MUST_BE_DIR) {
190 buf.push(b'/');
191 }
192 buf
193 }
194 }
195}
196
197impl std::fmt::Display for Pattern {
198 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
199 self.to_bstring().fmt(f)
200 }
201}