summaryrefslogtreecommitdiffstats
path: root/vendor/gix-refspec/src/spec.rs
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/gix-refspec/src/spec.rs')
-rw-r--r--vendor/gix-refspec/src/spec.rs257
1 files changed, 257 insertions, 0 deletions
diff --git a/vendor/gix-refspec/src/spec.rs b/vendor/gix-refspec/src/spec.rs
new file mode 100644
index 000000000..9361ae682
--- /dev/null
+++ b/vendor/gix-refspec/src/spec.rs
@@ -0,0 +1,257 @@
+use bstr::{BStr, BString, ByteSlice};
+
+use crate::{
+ instruction::{Fetch, Push},
+ parse::Operation,
+ types::Mode,
+ Instruction, RefSpec, RefSpecRef,
+};
+
+/// Conversion. Use the [RefSpecRef][RefSpec::to_ref()] type for more usage options.
+impl RefSpec {
+ /// Return ourselves as reference type.
+ pub fn to_ref(&self) -> RefSpecRef<'_> {
+ RefSpecRef {
+ mode: self.mode,
+ op: self.op,
+ src: self.src.as_ref().map(|b| b.as_ref()),
+ dst: self.dst.as_ref().map(|b| b.as_ref()),
+ }
+ }
+
+ /// Return true if the spec stats with a `+` and thus forces setting the reference.
+ pub fn allow_non_fast_forward(&self) -> bool {
+ matches!(self.mode, Mode::Force)
+ }
+}
+
+mod impls {
+ use std::{
+ cmp::Ordering,
+ hash::{Hash, Hasher},
+ };
+
+ use crate::{RefSpec, RefSpecRef};
+
+ impl From<RefSpecRef<'_>> for RefSpec {
+ fn from(v: RefSpecRef<'_>) -> Self {
+ v.to_owned()
+ }
+ }
+
+ impl Hash for RefSpec {
+ fn hash<H: Hasher>(&self, state: &mut H) {
+ self.to_ref().hash(state)
+ }
+ }
+
+ impl Hash for RefSpecRef<'_> {
+ fn hash<H: Hasher>(&self, state: &mut H) {
+ self.instruction().hash(state)
+ }
+ }
+
+ impl PartialEq for RefSpec {
+ fn eq(&self, other: &Self) -> bool {
+ self.to_ref().eq(&other.to_ref())
+ }
+ }
+
+ impl PartialEq for RefSpecRef<'_> {
+ fn eq(&self, other: &Self) -> bool {
+ self.instruction().eq(&other.instruction())
+ }
+ }
+
+ impl PartialOrd for RefSpecRef<'_> {
+ fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
+ self.instruction().partial_cmp(&other.instruction())
+ }
+ }
+
+ impl PartialOrd for RefSpec {
+ fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
+ self.to_ref().partial_cmp(&other.to_ref())
+ }
+ }
+
+ impl Ord for RefSpecRef<'_> {
+ fn cmp(&self, other: &Self) -> Ordering {
+ self.instruction().cmp(&other.instruction())
+ }
+ }
+
+ impl Ord for RefSpec {
+ fn cmp(&self, other: &Self) -> Ordering {
+ self.to_ref().cmp(&other.to_ref())
+ }
+ }
+}
+
+/// Access
+impl<'a> RefSpecRef<'a> {
+ /// Return the left-hand side of the spec, typically the source.
+ /// It takes many different forms so don't rely on this being a ref name.
+ ///
+ /// It's not present in case of deletions.
+ pub fn source(&self) -> Option<&BStr> {
+ self.src
+ }
+
+ /// Return the right-hand side of the spec, typically the destination.
+ /// It takes many different forms so don't rely on this being a ref name.
+ ///
+ /// It's not present in case of source-only specs.
+ pub fn destination(&self) -> Option<&BStr> {
+ self.dst
+ }
+
+ /// Always returns the remote side, whose actual side in the refspec depends on how it was parsed.
+ pub fn remote(&self) -> Option<&BStr> {
+ match self.op {
+ Operation::Push => self.dst,
+ Operation::Fetch => self.src,
+ }
+ }
+
+ /// Always returns the local side, whose actual side in the refspec depends on how it was parsed.
+ pub fn local(&self) -> Option<&BStr> {
+ match self.op {
+ Operation::Push => self.src,
+ Operation::Fetch => self.dst,
+ }
+ }
+
+ /// Derive the prefix from the [`source`][Self::source()] side of this spec if this is a fetch spec,
+ /// or the [`destination`][Self::destination()] side if it is a push spec, if it is possible to do so without ambiguity.
+ ///
+ /// This means it starts with `refs/`. Note that it won't contain more than two components, like `refs/heads/`
+ pub fn prefix(&self) -> Option<&BStr> {
+ if self.mode == Mode::Negative {
+ return None;
+ }
+ let source = match self.op {
+ Operation::Fetch => self.source(),
+ Operation::Push => self.destination(),
+ }?;
+ if source == "HEAD" {
+ return source.into();
+ }
+ let suffix = source.strip_prefix(b"refs/")?;
+ let slash_pos = suffix.find_byte(b'/')?;
+ let prefix = source[..="refs/".len() + slash_pos].as_bstr();
+ (!prefix.contains(&b'*')).then_some(prefix)
+ }
+
+ /// As opposed to [`prefix()`][Self::prefix], if the latter is `None` it will expand to all possible prefixes and place them in `out`.
+ ///
+ /// Note that only the `source` side is considered.
+ pub fn expand_prefixes(&self, out: &mut Vec<BString>) {
+ match self.prefix() {
+ Some(prefix) => out.push(prefix.into()),
+ None => {
+ let source = match match self.op {
+ Operation::Fetch => self.source(),
+ Operation::Push => self.destination(),
+ } {
+ Some(source) => source,
+ None => return,
+ };
+ if let Some(rest) = source.strip_prefix(b"refs/") {
+ if !rest.contains(&b'/') {
+ out.push(source.into());
+ }
+ return;
+ } else if gix_hash::ObjectId::from_hex(source).is_ok() {
+ return;
+ }
+ expand_partial_name(source, |expanded| {
+ out.push(expanded.into());
+ None::<()>
+ });
+ }
+ }
+ }
+
+ /// Transform the state of the refspec into an instruction making clear what to do with it.
+ pub fn instruction(&self) -> Instruction<'a> {
+ match self.op {
+ Operation::Fetch => match (self.mode, self.src, self.dst) {
+ (Mode::Normal | Mode::Force, Some(src), None) => Instruction::Fetch(Fetch::Only { src }),
+ (Mode::Normal | Mode::Force, Some(src), Some(dst)) => Instruction::Fetch(Fetch::AndUpdate {
+ src,
+ dst,
+ allow_non_fast_forward: matches!(self.mode, Mode::Force),
+ }),
+ (Mode::Negative, Some(src), None) => Instruction::Fetch(Fetch::Exclude { src }),
+ (mode, src, dest) => {
+ unreachable!(
+ "BUG: fetch instructions with {:?} {:?} {:?} are not possible",
+ mode, src, dest
+ )
+ }
+ },
+ Operation::Push => match (self.mode, self.src, self.dst) {
+ (Mode::Normal | Mode::Force, Some(src), None) => Instruction::Push(Push::Matching {
+ src,
+ dst: src,
+ allow_non_fast_forward: matches!(self.mode, Mode::Force),
+ }),
+ (Mode::Normal | Mode::Force, None, Some(dst)) => {
+ Instruction::Push(Push::Delete { ref_or_pattern: dst })
+ }
+ (Mode::Normal | Mode::Force, None, None) => Instruction::Push(Push::AllMatchingBranches {
+ allow_non_fast_forward: matches!(self.mode, Mode::Force),
+ }),
+ (Mode::Normal | Mode::Force, Some(src), Some(dst)) => Instruction::Push(Push::Matching {
+ src,
+ dst,
+ allow_non_fast_forward: matches!(self.mode, Mode::Force),
+ }),
+ (mode, src, dest) => {
+ unreachable!(
+ "BUG: push instructions with {:?} {:?} {:?} are not possible",
+ mode, src, dest
+ )
+ }
+ },
+ }
+ }
+}
+
+/// Conversion
+impl RefSpecRef<'_> {
+ /// Convert this ref into a standalone, owned copy.
+ pub fn to_owned(&self) -> RefSpec {
+ RefSpec {
+ mode: self.mode,
+ op: self.op,
+ src: self.src.map(ToOwned::to_owned),
+ dst: self.dst.map(ToOwned::to_owned),
+ }
+ }
+}
+
+pub(crate) fn expand_partial_name<T>(name: &BStr, mut cb: impl FnMut(&BStr) -> Option<T>) -> Option<T> {
+ use bstr::ByteVec;
+ let mut buf = BString::from(Vec::with_capacity(128));
+ for (base, append_head) in [
+ ("", false),
+ ("refs/", false),
+ ("refs/tags/", false),
+ ("refs/heads/", false),
+ ("refs/remotes/", false),
+ ("refs/remotes/", true),
+ ] {
+ buf.clear();
+ buf.push_str(base);
+ buf.push_str(name);
+ if append_head {
+ buf.push_str("/HEAD");
+ }
+ if let Some(res) = cb(buf.as_ref()) {
+ return Some(res);
+ }
+ }
+ None
+}