summaryrefslogtreecommitdiffstats
path: root/vendor/gix-refspec/src/match_group/util.rs
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/gix-refspec/src/match_group/util.rs')
-rw-r--r--vendor/gix-refspec/src/match_group/util.rs162
1 files changed, 162 insertions, 0 deletions
diff --git a/vendor/gix-refspec/src/match_group/util.rs b/vendor/gix-refspec/src/match_group/util.rs
new file mode 100644
index 000000000..5339aef32
--- /dev/null
+++ b/vendor/gix-refspec/src/match_group/util.rs
@@ -0,0 +1,162 @@
+use std::{borrow::Cow, ops::Range};
+
+use bstr::{BStr, BString, ByteSlice, ByteVec};
+use gix_hash::ObjectId;
+
+use crate::{match_group::Item, RefSpecRef};
+
+/// A type keeping enough information about a ref-spec to be able to efficiently match it against multiple matcher items.
+pub struct Matcher<'a> {
+ pub(crate) lhs: Option<Needle<'a>>,
+ pub(crate) rhs: Option<Needle<'a>>,
+}
+
+impl<'a> Matcher<'a> {
+ /// Match `item` against this spec and return `(true, Some<rhs>)` to gain the other side of the match as configured, or `(true, None)`
+ /// if there was no `rhs`.
+ ///
+ /// This may involve resolving a glob with an allocation, as the destination is built using the matching portion of a glob.
+ pub fn matches_lhs(&self, item: Item<'_>) -> (bool, Option<Cow<'a, BStr>>) {
+ match (self.lhs, self.rhs) {
+ (Some(lhs), None) => (lhs.matches(item).is_match(), None),
+ (Some(lhs), Some(rhs)) => lhs.matches(item).into_match_outcome(rhs, item),
+ (None, None) | (None, Some(_)) => {
+ unreachable!("For all we know, the lefthand side is never empty. Push specs might change that.")
+ }
+ }
+ }
+}
+
+#[derive(Debug, Copy, Clone)]
+pub(crate) enum Needle<'a> {
+ FullName(&'a BStr),
+ PartialName(&'a BStr),
+ Glob { name: &'a BStr, asterisk_pos: usize },
+ Object(ObjectId),
+}
+
+enum Match {
+ /// There was no match.
+ None,
+ /// No additional data is provided as part of the match.
+ Normal,
+ /// The range of text to copy from the originating item name
+ GlobRange(Range<usize>),
+}
+
+impl Match {
+ fn is_match(&self) -> bool {
+ !matches!(self, Match::None)
+ }
+ fn into_match_outcome<'a>(self, destination: Needle<'a>, item: Item<'_>) -> (bool, Option<Cow<'a, BStr>>) {
+ let arg = match self {
+ Match::None => return (false, None),
+ Match::Normal => None,
+ Match::GlobRange(range) => Some((range, item)),
+ };
+ (true, destination.to_bstr_replace(arg).into())
+ }
+}
+
+impl<'a> Needle<'a> {
+ #[inline]
+ fn matches(&self, item: Item<'_>) -> Match {
+ match self {
+ Needle::FullName(name) => {
+ if *name == item.full_ref_name {
+ Match::Normal
+ } else {
+ Match::None
+ }
+ }
+ Needle::PartialName(name) => crate::spec::expand_partial_name(name, |expanded| {
+ (expanded == item.full_ref_name).then_some(Match::Normal)
+ })
+ .unwrap_or(Match::None),
+ Needle::Glob { name, asterisk_pos } => {
+ match item.full_ref_name.get(..*asterisk_pos) {
+ Some(full_name_portion) if full_name_portion != name[..*asterisk_pos] => {
+ return Match::None;
+ }
+ None => return Match::None,
+ _ => {}
+ };
+ let tail = &name[*asterisk_pos + 1..];
+ if !item.full_ref_name.ends_with(tail) {
+ return Match::None;
+ }
+ let end = item.full_ref_name.len() - tail.len();
+ Match::GlobRange(*asterisk_pos..end)
+ }
+ Needle::Object(id) => {
+ if *id == item.target {
+ return Match::Normal;
+ }
+ match item.object {
+ Some(object) if object == *id => Match::Normal,
+ _ => Match::None,
+ }
+ }
+ }
+ }
+
+ fn to_bstr_replace(self, range: Option<(Range<usize>, Item<'_>)>) -> Cow<'a, BStr> {
+ match (self, range) {
+ (Needle::FullName(name), None) => Cow::Borrowed(name),
+ (Needle::PartialName(name), None) => Cow::Owned({
+ let mut base: BString = "refs/".into();
+ if !(name.starts_with(b"tags/") || name.starts_with(b"remotes/")) {
+ base.push_str("heads/");
+ }
+ base.push_str(name);
+ base
+ }),
+ (Needle::Glob { name, asterisk_pos }, Some((range, item))) => {
+ let mut buf = Vec::with_capacity(name.len() + range.len() - 1);
+ buf.push_str(&name[..asterisk_pos]);
+ buf.push_str(&item.full_ref_name[range]);
+ buf.push_str(&name[asterisk_pos + 1..]);
+ Cow::Owned(buf.into())
+ }
+ (Needle::Object(id), None) => {
+ let mut name = id.to_string();
+ name.insert_str(0, "refs/heads/");
+ Cow::Owned(name.into())
+ }
+ (Needle::Glob { .. }, None) => unreachable!("BUG: no range provided for glob pattern"),
+ (_, Some(_)) => {
+ unreachable!("BUG: range provided even though needle wasn't a glob. Globs are symmetric.")
+ }
+ }
+ }
+
+ pub fn to_bstr(self) -> Cow<'a, BStr> {
+ self.to_bstr_replace(None)
+ }
+}
+
+impl<'a> From<&'a BStr> for Needle<'a> {
+ fn from(v: &'a BStr) -> Self {
+ if let Some(pos) = v.find_byte(b'*') {
+ Needle::Glob {
+ name: v,
+ asterisk_pos: pos,
+ }
+ } else if v.starts_with(b"refs/") {
+ Needle::FullName(v)
+ } else if let Ok(id) = gix_hash::ObjectId::from_hex(v) {
+ Needle::Object(id)
+ } else {
+ Needle::PartialName(v)
+ }
+ }
+}
+
+impl<'a> From<RefSpecRef<'a>> for Matcher<'a> {
+ fn from(v: RefSpecRef<'a>) -> Self {
+ Matcher {
+ lhs: v.src.map(Into::into),
+ rhs: v.dst.map(Into::into),
+ }
+ }
+}