summaryrefslogtreecommitdiffstats
path: root/vendor/gix-refspec/src/parse.rs
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/gix-refspec/src/parse.rs')
-rw-r--r--vendor/gix-refspec/src/parse.rs255
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) {}
+ }
+}