1use bstr::{BStr, BString, ByteSlice};
2
3use crate::{
4 Instruction, RefSpec, RefSpecRef,
5 instruction::{Fetch, Push},
6 parse::Operation,
7 types::Mode,
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 impl PartialOrd for RefSpec {
73 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
74 Some(self.cmp(other))
75 }
76 }
77
78 impl Ord for RefSpecRef<'_> {
79 fn cmp(&self, other: &Self) -> Ordering {
80 self.instruction().cmp(&other.instruction())
81 }
82 }
83
84 impl Ord for RefSpec {
85 fn cmp(&self, other: &Self) -> Ordering {
86 self.to_ref().cmp(&other.to_ref())
87 }
88 }
89}
90
91impl<'a> RefSpecRef<'a> {
93 pub fn source(&self) -> Option<&BStr> {
98 self.src
99 }
100
101 pub fn destination(&self) -> Option<&BStr> {
105 self.dst
106 }
107
108 pub fn remote(&self) -> Option<&BStr> {
113 match self.op {
114 Operation::Push => self.dst,
115 Operation::Fetch => self.src,
116 }
117 }
118
119 pub fn local(&self) -> Option<&BStr> {
121 match self.op {
122 Operation::Push => self.src,
123 Operation::Fetch => self.dst,
124 }
125 }
126
127 pub fn prefix(&self) -> Option<&BStr> {
136 if self.mode == Mode::Negative {
137 return None;
138 }
139 let source = match self.op {
140 Operation::Fetch => self.source(),
141 Operation::Push => self.destination(),
142 }?;
143 if source == "HEAD" {
144 return source.into();
145 }
146
147 let sans_refs_prefix = source.strip_prefix(b"refs/")?;
148 if let Some(star_pos) = sans_refs_prefix.find_byte(b'*') {
149 if star_pos == 0
150 || sans_refs_prefix[star_pos + 1..].contains(&b'*')
151 || sans_refs_prefix.find_byteset(b"?[]\\").is_some()
152 {
153 return None;
154 }
155 let prefix = &source[.."refs/".len() + star_pos];
156 return (!prefix.is_empty()).then_some(prefix.as_bstr());
157 }
158 Some(source)
159 }
160
161 pub fn expand_prefixes(&self, out: &mut Vec<BString>) {
169 if self.mode == Mode::Negative {
170 return;
171 }
172 match self.prefix() {
173 Some(prefix) => out.push(prefix.into()),
174 None => {
175 let source = match match self.op {
176 Operation::Fetch => self.source(),
177 Operation::Push => self.destination(),
178 } {
179 Some(source) => source,
180 None => return,
181 };
182 if let Some(rest) = source.strip_prefix(b"refs/") {
183 if !rest.contains(&b'/') {
184 out.push(source.into());
185 }
186 return;
187 } else if gix_hash::ObjectId::from_hex(source).is_ok() {
188 return;
189 }
190 expand_partial_name(source, |expanded| {
191 out.push(expanded.into());
192 None::<()>
193 });
194 }
195 }
196 }
197
198 pub fn instruction(&self) -> Instruction<'a> {
200 match self.op {
201 Operation::Fetch => match (self.mode, self.src, self.dst) {
202 (Mode::Normal | Mode::Force, Some(src), None) => Instruction::Fetch(Fetch::Only { src }),
203 (Mode::Normal | Mode::Force, Some(src), Some(dst)) => Instruction::Fetch(Fetch::AndUpdate {
204 src,
205 dst,
206 allow_non_fast_forward: matches!(self.mode, Mode::Force),
207 }),
208 (Mode::Negative, Some(src), None) => Instruction::Fetch(Fetch::Exclude { src }),
209 (mode, src, dest) => {
210 unreachable!(
211 "BUG: fetch instructions with {:?} {:?} {:?} are not possible",
212 mode, src, dest
213 )
214 }
215 },
216 Operation::Push => match (self.mode, self.src, self.dst) {
217 (Mode::Normal | Mode::Force, Some(src), None) => Instruction::Push(Push::Matching {
218 src,
219 dst: src,
220 allow_non_fast_forward: matches!(self.mode, Mode::Force),
221 }),
222 (Mode::Normal | Mode::Force, None, Some(dst)) => {
223 Instruction::Push(Push::Delete { ref_or_pattern: dst })
224 }
225 (Mode::Normal | Mode::Force, None, None) => Instruction::Push(Push::AllMatchingBranches {
226 allow_non_fast_forward: matches!(self.mode, Mode::Force),
227 }),
228 (Mode::Normal | Mode::Force, Some(src), Some(dst)) => Instruction::Push(Push::Matching {
229 src,
230 dst,
231 allow_non_fast_forward: matches!(self.mode, Mode::Force),
232 }),
233 (Mode::Negative, Some(src), None) => Instruction::Push(Push::Exclude { src }),
234 (mode, src, dest) => {
235 unreachable!(
236 "BUG: push instructions with {:?} {:?} {:?} are not possible",
237 mode, src, dest
238 )
239 }
240 },
241 }
242 }
243}
244
245impl RefSpecRef<'_> {
247 pub fn to_owned(&self) -> RefSpec {
249 RefSpec {
250 mode: self.mode,
251 op: self.op,
252 src: self.src.map(ToOwned::to_owned),
253 dst: self.dst.map(ToOwned::to_owned),
254 }
255 }
256}
257
258pub(crate) fn expand_partial_name<T>(name: &BStr, mut cb: impl FnMut(&BStr) -> Option<T>) -> Option<T> {
259 use bstr::ByteVec;
260 let mut buf = BString::from(Vec::with_capacity(128));
261 for (base, append_head) in [
262 ("", false),
263 ("refs/", false),
264 ("refs/tags/", false),
265 ("refs/heads/", false),
266 ("refs/remotes/", false),
267 ("refs/remotes/", true),
268 ] {
269 buf.clear();
270 buf.push_str(base);
271 buf.push_str(name);
272 if append_head {
273 buf.push_str("/HEAD");
274 }
275 if let Some(res) = cb(buf.as_ref()) {
276 return Some(res);
277 }
278 }
279 None
280}