gix_refspec/
spec.rs

1use bstr::{BStr, BString, ByteSlice};
2
3use crate::{
4    instruction::{Fetch, Push},
5    parse::Operation,
6    types::Mode,
7    Instruction, RefSpec, RefSpecRef,
8};
9
10/// Conversion. Use the [`RefSpecRef`][RefSpec::to_ref()] type for more usage options.
11impl RefSpec {
12    /// Return ourselves as reference type.
13    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    /// Return true if the spec stats with a `+` and thus forces setting the reference.
23    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.to_ref().cmp(&other.to_ref()))
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
91/// Access
92impl<'a> RefSpecRef<'a> {
93    /// Return the left-hand side of the spec, typically the source.
94    /// It takes many different forms so don't rely on this being a ref name.
95    ///
96    /// It's not present in case of deletions.
97    pub fn source(&self) -> Option<&BStr> {
98        self.src
99    }
100
101    /// Return the right-hand side of the spec, typically the destination.
102    /// It takes many different forms so don't rely on this being a ref name.
103    ///
104    /// It's not present in case of source-only specs.
105    pub fn destination(&self) -> Option<&BStr> {
106        self.dst
107    }
108
109    /// Always returns the remote side, whose actual side in the refspec depends on how it was parsed.
110    pub fn remote(&self) -> Option<&BStr> {
111        match self.op {
112            Operation::Push => self.dst,
113            Operation::Fetch => self.src,
114        }
115    }
116
117    /// Always returns the local side, whose actual side in the refspec depends on how it was parsed.
118    pub fn local(&self) -> Option<&BStr> {
119        match self.op {
120            Operation::Push => self.src,
121            Operation::Fetch => self.dst,
122        }
123    }
124
125    /// Derive the prefix from the [`source`][Self::source()] side of this spec if this is a fetch spec,
126    /// or the [`destination`][Self::destination()] side if it is a push spec, if it is possible to do so without ambiguity.
127    ///
128    /// This means it starts with `refs/`. Note that it won't contain more than two components, like `refs/heads/`
129    pub fn prefix(&self) -> Option<&BStr> {
130        if self.mode == Mode::Negative {
131            return None;
132        }
133        let source = match self.op {
134            Operation::Fetch => self.source(),
135            Operation::Push => self.destination(),
136        }?;
137        if source == "HEAD" {
138            return source.into();
139        }
140        let suffix = source.strip_prefix(b"refs/")?;
141        let slash_pos = suffix.find_byte(b'/')?;
142        let prefix = source[..="refs/".len() + slash_pos].as_bstr();
143        (!prefix.contains(&b'*')).then_some(prefix)
144    }
145
146    /// As opposed to [`prefix()`][Self::prefix], if the latter is `None` it will expand to all possible prefixes and place them in `out`.
147    ///
148    /// Note that only the `source` side is considered.
149    pub fn expand_prefixes(&self, out: &mut Vec<BString>) {
150        match self.prefix() {
151            Some(prefix) => out.push(prefix.into()),
152            None => {
153                let source = match match self.op {
154                    Operation::Fetch => self.source(),
155                    Operation::Push => self.destination(),
156                } {
157                    Some(source) => source,
158                    None => return,
159                };
160                if let Some(rest) = source.strip_prefix(b"refs/") {
161                    if !rest.contains(&b'/') {
162                        out.push(source.into());
163                    }
164                    return;
165                } else if gix_hash::ObjectId::from_hex(source).is_ok() {
166                    return;
167                }
168                expand_partial_name(source, |expanded| {
169                    out.push(expanded.into());
170                    None::<()>
171                });
172            }
173        }
174    }
175
176    /// Transform the state of the refspec into an instruction making clear what to do with it.
177    pub fn instruction(&self) -> Instruction<'a> {
178        match self.op {
179            Operation::Fetch => match (self.mode, self.src, self.dst) {
180                (Mode::Normal | Mode::Force, Some(src), None) => Instruction::Fetch(Fetch::Only { src }),
181                (Mode::Normal | Mode::Force, Some(src), Some(dst)) => Instruction::Fetch(Fetch::AndUpdate {
182                    src,
183                    dst,
184                    allow_non_fast_forward: matches!(self.mode, Mode::Force),
185                }),
186                (Mode::Negative, Some(src), None) => Instruction::Fetch(Fetch::Exclude { src }),
187                (mode, src, dest) => {
188                    unreachable!(
189                        "BUG: fetch instructions with {:?} {:?} {:?} are not possible",
190                        mode, src, dest
191                    )
192                }
193            },
194            Operation::Push => match (self.mode, self.src, self.dst) {
195                (Mode::Normal | Mode::Force, Some(src), None) => Instruction::Push(Push::Matching {
196                    src,
197                    dst: src,
198                    allow_non_fast_forward: matches!(self.mode, Mode::Force),
199                }),
200                (Mode::Normal | Mode::Force, None, Some(dst)) => {
201                    Instruction::Push(Push::Delete { ref_or_pattern: dst })
202                }
203                (Mode::Normal | Mode::Force, None, None) => Instruction::Push(Push::AllMatchingBranches {
204                    allow_non_fast_forward: matches!(self.mode, Mode::Force),
205                }),
206                (Mode::Normal | Mode::Force, Some(src), Some(dst)) => Instruction::Push(Push::Matching {
207                    src,
208                    dst,
209                    allow_non_fast_forward: matches!(self.mode, Mode::Force),
210                }),
211                (mode, src, dest) => {
212                    unreachable!(
213                        "BUG: push instructions with {:?} {:?} {:?} are not possible",
214                        mode, src, dest
215                    )
216                }
217            },
218        }
219    }
220}
221
222/// Conversion
223impl RefSpecRef<'_> {
224    /// Convert this ref into a standalone, owned copy.
225    pub fn to_owned(&self) -> RefSpec {
226        RefSpec {
227            mode: self.mode,
228            op: self.op,
229            src: self.src.map(ToOwned::to_owned),
230            dst: self.dst.map(ToOwned::to_owned),
231        }
232    }
233}
234
235pub(crate) fn expand_partial_name<T>(name: &BStr, mut cb: impl FnMut(&BStr) -> Option<T>) -> Option<T> {
236    use bstr::ByteVec;
237    let mut buf = BString::from(Vec::with_capacity(128));
238    for (base, append_head) in [
239        ("", false),
240        ("refs/", false),
241        ("refs/tags/", false),
242        ("refs/heads/", false),
243        ("refs/remotes/", false),
244        ("refs/remotes/", true),
245    ] {
246        buf.clear();
247        buf.push_str(base);
248        buf.push_str(name);
249        if append_head {
250            buf.push_str("/HEAD");
251        }
252        if let Some(res) = cb(buf.as_ref()) {
253            return Some(res);
254        }
255    }
256    None
257}