1use bstr::{BStr, BString, ByteSlice};
2
3use crate::{
4 instruction::{Fetch, Push},
5 parse::Operation,
6 types::Mode,
7 Instruction, RefSpec, RefSpecRef,
8};
9
10impl RefSpec {
12 pub fn to_ref(&self) -> RefSpecRef<'_> {
14 RefSpecRef {
15 mode: self.mode,
16 op: self.op,
17 src: self.src.as_ref().map(AsRef::as_ref),
18 dst: self.dst.as_ref().map(AsRef::as_ref),
19 }
20 }
21
22 pub fn allow_non_fast_forward(&self) -> bool {
24 matches!(self.mode, Mode::Force)
25 }
26}
27
28mod impls {
29 use std::{
30 cmp::Ordering,
31 hash::{Hash, Hasher},
32 };
33
34 use crate::{RefSpec, RefSpecRef};
35
36 impl From<RefSpecRef<'_>> for RefSpec {
37 fn from(v: RefSpecRef<'_>) -> Self {
38 v.to_owned()
39 }
40 }
41
42 impl Hash for RefSpec {
43 fn hash<H: Hasher>(&self, state: &mut H) {
44 self.to_ref().hash(state);
45 }
46 }
47
48 impl Hash for RefSpecRef<'_> {
49 fn hash<H: Hasher>(&self, state: &mut H) {
50 self.instruction().hash(state);
51 }
52 }
53
54 impl PartialEq for RefSpec {
55 fn eq(&self, other: &Self) -> bool {
56 self.to_ref().eq(&other.to_ref())
57 }
58 }
59
60 impl PartialEq for RefSpecRef<'_> {
61 fn eq(&self, other: &Self) -> bool {
62 self.instruction().eq(&other.instruction())
63 }
64 }
65
66 impl PartialOrd for RefSpecRef<'_> {
67 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
68 Some(self.cmp(other))
69 }
70 }
71
72 #[allow(clippy::non_canonical_partial_ord_impl)]
73 impl PartialOrd for RefSpec {
74 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
75 Some(self.to_ref().cmp(&other.to_ref()))
76 }
77 }
78
79 impl Ord for RefSpecRef<'_> {
80 fn cmp(&self, other: &Self) -> Ordering {
81 self.instruction().cmp(&other.instruction())
82 }
83 }
84
85 impl Ord for RefSpec {
86 fn cmp(&self, other: &Self) -> Ordering {
87 self.to_ref().cmp(&other.to_ref())
88 }
89 }
90}
91
92impl<'a> RefSpecRef<'a> {
94 pub fn source(&self) -> Option<&BStr> {
99 self.src
100 }
101
102 pub fn destination(&self) -> Option<&BStr> {
107 self.dst
108 }
109
110 pub fn remote(&self) -> Option<&BStr> {
112 match self.op {
113 Operation::Push => self.dst,
114 Operation::Fetch => self.src,
115 }
116 }
117
118 pub fn local(&self) -> Option<&BStr> {
120 match self.op {
121 Operation::Push => self.src,
122 Operation::Fetch => self.dst,
123 }
124 }
125
126 pub fn prefix(&self) -> Option<&BStr> {
135 if self.mode == Mode::Negative {
136 return None;
137 }
138 let source = match self.op {
139 Operation::Fetch => self.source(),
140 Operation::Push => self.destination(),
141 }?;
142 if source == "HEAD" {
143 return source.into();
144 }
145
146 let sans_refs_prefix = source.strip_prefix(b"refs/")?;
147 if let Some(star_pos) = sans_refs_prefix.find_byte(b'*') {
148 if star_pos == 0
149 || sans_refs_prefix[star_pos + 1..].contains(&b'*')
150 || sans_refs_prefix.find_byteset(b"?[]\\").is_some()
151 {
152 return None;
153 }
154 let prefix = &source[.."refs/".len() + star_pos];
155 return (!prefix.is_empty()).then_some(prefix.as_bstr());
156 }
157 Some(source)
158 }
159
160 pub fn expand_prefixes(&self, out: &mut Vec<BString>) {
164 match self.prefix() {
165 Some(prefix) => out.push(prefix.into()),
166 None => {
167 let source = match match self.op {
168 Operation::Fetch => self.source(),
169 Operation::Push => self.destination(),
170 } {
171 Some(source) => source,
172 None => return,
173 };
174 if let Some(rest) = source.strip_prefix(b"refs/") {
175 if !rest.contains(&b'/') {
176 out.push(source.into());
177 }
178 return;
179 } else if gix_hash::ObjectId::from_hex(source).is_ok() {
180 return;
181 }
182 expand_partial_name(source, |expanded| {
183 out.push(expanded.into());
184 None::<()>
185 });
186 }
187 }
188 }
189
190 pub fn instruction(&self) -> Instruction<'a> {
192 match self.op {
193 Operation::Fetch => match (self.mode, self.src, self.dst) {
194 (Mode::Normal | Mode::Force, Some(src), None) => Instruction::Fetch(Fetch::Only { src }),
195 (Mode::Normal | Mode::Force, Some(src), Some(dst)) => Instruction::Fetch(Fetch::AndUpdate {
196 src,
197 dst,
198 allow_non_fast_forward: matches!(self.mode, Mode::Force),
199 }),
200 (Mode::Negative, Some(src), None) => Instruction::Fetch(Fetch::Exclude { src }),
201 (mode, src, dest) => {
202 unreachable!(
203 "BUG: fetch instructions with {:?} {:?} {:?} are not possible",
204 mode, src, dest
205 )
206 }
207 },
208 Operation::Push => match (self.mode, self.src, self.dst) {
209 (Mode::Normal | Mode::Force, Some(src), None) => Instruction::Push(Push::Matching {
210 src,
211 dst: src,
212 allow_non_fast_forward: matches!(self.mode, Mode::Force),
213 }),
214 (Mode::Normal | Mode::Force, None, Some(dst)) => {
215 Instruction::Push(Push::Delete { ref_or_pattern: dst })
216 }
217 (Mode::Normal | Mode::Force, None, None) => Instruction::Push(Push::AllMatchingBranches {
218 allow_non_fast_forward: matches!(self.mode, Mode::Force),
219 }),
220 (Mode::Normal | Mode::Force, Some(src), Some(dst)) => Instruction::Push(Push::Matching {
221 src,
222 dst,
223 allow_non_fast_forward: matches!(self.mode, Mode::Force),
224 }),
225 (Mode::Negative, Some(src), None) => Instruction::Push(Push::Exclude { src }),
226 (mode, src, dest) => {
227 unreachable!(
228 "BUG: push instructions with {:?} {:?} {:?} are not possible",
229 mode, src, dest
230 )
231 }
232 },
233 }
234 }
235}
236
237impl RefSpecRef<'_> {
239 pub fn to_owned(&self) -> RefSpec {
241 RefSpec {
242 mode: self.mode,
243 op: self.op,
244 src: self.src.map(ToOwned::to_owned),
245 dst: self.dst.map(ToOwned::to_owned),
246 }
247 }
248}
249
250pub(crate) fn expand_partial_name<T>(name: &BStr, mut cb: impl FnMut(&BStr) -> Option<T>) -> Option<T> {
251 use bstr::ByteVec;
252 let mut buf = BString::from(Vec::with_capacity(128));
253 for (base, append_head) in [
254 ("", false),
255 ("refs/", false),
256 ("refs/tags/", false),
257 ("refs/heads/", false),
258 ("refs/remotes/", false),
259 ("refs/remotes/", true),
260 ] {
261 buf.clear();
262 buf.push_str(base);
263 buf.push_str(name);
264 if append_head {
265 buf.push_str("/HEAD");
266 }
267 if let Some(res) = cb(buf.as_ref()) {
268 return Some(res);
269 }
270 }
271 None
272}