diff options
Diffstat (limited to 'vendor/gix-refspec/src/parse.rs')
-rw-r--r-- | vendor/gix-refspec/src/parse.rs | 255 |
1 files changed, 255 insertions, 0 deletions
diff --git a/vendor/gix-refspec/src/parse.rs b/vendor/gix-refspec/src/parse.rs new file mode 100644 index 000000000..390db13fa --- /dev/null +++ b/vendor/gix-refspec/src/parse.rs @@ -0,0 +1,255 @@ +/// The error returned by the [`parse()`][crate::parse()] function. +#[derive(Debug, thiserror::Error)] +#[allow(missing_docs)] +pub enum Error { + #[error("Empty refspecs are invalid")] + Empty, + #[error("Negative refspecs cannot have destinations as they exclude sources")] + NegativeWithDestination, + #[error("Negative specs must not be empty")] + NegativeEmpty, + #[error("Negative specs are only supported when fetching")] + NegativeUnsupported, + #[error("Negative specs must be object hashes")] + NegativeObjectHash, + #[error("Negative specs must be full ref names, starting with \"refs/\"")] + NegativePartialName, + #[error("Negative glob patterns are not allowed")] + NegativeGlobPattern, + #[error("Fetch destinations must be ref-names, like 'HEAD:refs/heads/branch'")] + InvalidFetchDestination, + #[error("Cannot push into an empty destination")] + PushToEmpty, + #[error("glob patterns may only involved a single '*' character, found {pattern:?}")] + PatternUnsupported { pattern: bstr::BString }, + #[error("Both sides of the specification need a pattern, like 'a/*:b/*'")] + PatternUnbalanced, + #[error(transparent)] + ReferenceName(#[from] gix_validate::refname::Error), + #[error(transparent)] + RevSpec(#[from] gix_revision::spec::parse::Error), +} + +/// Define how the parsed refspec should be used. +#[derive(PartialOrd, Ord, PartialEq, Eq, Copy, Clone, Hash, Debug)] +pub enum Operation { + /// The `src` side is local and the `dst` side is remote. + Push, + /// The `src` side is remote and the `dst` side is local. + Fetch, +} + +pub(crate) mod function { + use bstr::{BStr, ByteSlice}; + + use crate::{ + parse::{Error, Operation}, + types::Mode, + RefSpecRef, + }; + + /// Parse `spec` for use in `operation` and return it if it is valid. + pub fn parse(mut spec: &BStr, operation: Operation) -> Result<RefSpecRef<'_>, Error> { + fn fetch_head_only(mode: Mode) -> RefSpecRef<'static> { + RefSpecRef { + mode, + op: Operation::Fetch, + src: Some("HEAD".into()), + dst: None, + } + } + + let mode = match spec.first() { + Some(&b'^') => { + spec = &spec[1..]; + if operation == Operation::Push { + return Err(Error::NegativeUnsupported); + } + Mode::Negative + } + Some(&b'+') => { + spec = &spec[1..]; + Mode::Force + } + Some(_) => Mode::Normal, + None => { + return match operation { + Operation::Push => Err(Error::Empty), + Operation::Fetch => Ok(fetch_head_only(Mode::Normal)), + } + } + }; + + let (mut src, dst) = match spec.find_byte(b':') { + Some(pos) => { + if mode == Mode::Negative { + return Err(Error::NegativeWithDestination); + } + + let (src, dst) = spec.split_at(pos); + let dst = &dst[1..]; + let src = (!src.is_empty()).then(|| src.as_bstr()); + let dst = (!dst.is_empty()).then(|| dst.as_bstr()); + match (src, dst) { + (None, None) => match operation { + Operation::Push => (None, None), + Operation::Fetch => (Some("HEAD".into()), None), + }, + (None, Some(dst)) => match operation { + Operation::Push => (None, Some(dst)), + Operation::Fetch => (Some("HEAD".into()), Some(dst)), + }, + (Some(src), None) => match operation { + Operation::Push => return Err(Error::PushToEmpty), + Operation::Fetch => (Some(src), None), + }, + (Some(src), Some(dst)) => (Some(src), Some(dst)), + } + } + None => { + let src = (!spec.is_empty()).then_some(spec); + if Operation::Fetch == operation && mode != Mode::Negative && src.is_none() { + return Ok(fetch_head_only(mode)); + } else { + (src, None) + } + } + }; + + if let Some(spec) = src.as_mut() { + if *spec == "@" { + *spec = "HEAD".into(); + } + } + let (src, src_had_pattern) = validated(src, operation == Operation::Push && dst.is_some())?; + let (dst, dst_had_pattern) = validated(dst, false)?; + if mode != Mode::Negative && src_had_pattern != dst_had_pattern { + return Err(Error::PatternUnbalanced); + } + + if mode == Mode::Negative { + match src { + Some(spec) => { + if src_had_pattern { + return Err(Error::NegativeGlobPattern); + } else if looks_like_object_hash(spec) { + return Err(Error::NegativeObjectHash); + } else if !spec.starts_with(b"refs/") && spec != "HEAD" { + return Err(Error::NegativePartialName); + } + } + None => return Err(Error::NegativeEmpty), + } + } + + Ok(RefSpecRef { + op: operation, + mode, + src, + dst, + }) + } + + fn looks_like_object_hash(spec: &BStr) -> bool { + spec.len() >= gix_hash::Kind::shortest().len_in_hex() && spec.iter().all(|b| b.is_ascii_hexdigit()) + } + + fn validated(spec: Option<&BStr>, allow_revspecs: bool) -> Result<(Option<&BStr>, bool), Error> { + match spec { + Some(spec) => { + let glob_count = spec.iter().filter(|b| **b == b'*').take(2).count(); + if glob_count > 1 { + return Err(Error::PatternUnsupported { pattern: spec.into() }); + } + let has_globs = glob_count == 1; + if has_globs { + let mut buf = smallvec::SmallVec::<[u8; 256]>::with_capacity(spec.len()); + buf.extend_from_slice(spec); + let glob_pos = buf.find_byte(b'*').expect("glob present"); + buf[glob_pos] = b'a'; + gix_validate::reference::name_partial(buf.as_bstr())?; + } else { + gix_validate::reference::name_partial(spec) + .map_err(Error::from) + .or_else(|err| { + if allow_revspecs { + match gix_revision::spec::parse(spec, &mut super::revparse::Noop) { + Ok(_) => { + if spec.iter().any(|b| b.is_ascii_whitespace()) { + Err(err) + } else { + Ok(spec) + } + } + Err(err) => Err(err.into()), + } + } else { + Err(err) + } + })?; + } + Ok((Some(spec), has_globs)) + } + None => Ok((None, false)), + } + } +} + +mod revparse { + use bstr::BStr; + use gix_revision::spec::parse::delegate::{ + Kind, Navigate, PeelTo, PrefixHint, ReflogLookup, Revision, SiblingBranch, Traversal, + }; + + pub(crate) struct Noop; + + impl Revision for Noop { + fn find_ref(&mut self, _name: &BStr) -> Option<()> { + Some(()) + } + + fn disambiguate_prefix(&mut self, _prefix: gix_hash::Prefix, _hint: Option<PrefixHint<'_>>) -> Option<()> { + Some(()) + } + + fn reflog(&mut self, _query: ReflogLookup) -> Option<()> { + Some(()) + } + + fn nth_checked_out_branch(&mut self, _branch_no: usize) -> Option<()> { + Some(()) + } + + fn sibling_branch(&mut self, _kind: SiblingBranch) -> Option<()> { + Some(()) + } + } + + impl Navigate for Noop { + fn traverse(&mut self, _kind: Traversal) -> Option<()> { + Some(()) + } + + fn peel_until(&mut self, _kind: PeelTo<'_>) -> Option<()> { + Some(()) + } + + fn find(&mut self, _regex: &BStr, _negated: bool) -> Option<()> { + Some(()) + } + + fn index_lookup(&mut self, _path: &BStr, _stage: u8) -> Option<()> { + Some(()) + } + } + + impl Kind for Noop { + fn kind(&mut self, _kind: gix_revision::spec::Kind) -> Option<()> { + Some(()) + } + } + + impl gix_revision::spec::parse::Delegate for Noop { + fn done(&mut self) {} + } +} |