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