diff --git a/git-refspec/src/parse.rs b/git-refspec/src/parse.rs index 19545d5cb6..a242bff43f 100644 --- a/git-refspec/src/parse.rs +++ b/git-refspec/src/parse.rs @@ -6,6 +6,10 @@ pub enum Error { NegativeWithDestination, #[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, } pub(crate) mod function { @@ -48,12 +52,17 @@ pub(crate) mod function { Operation::Push => return Err(Error::PushToEmpty), Operation::Fetch => (Some(src), None), }, - _ => todo!("src or dst handling"), + (Some(src), Some(dst)) => (Some(src), Some(dst)), } } - None => todo!("no colon"), + None => (Some(spec), None), }; + let (src, src_had_pattern) = validated(src)?; + let (dst, dst_had_pattern) = validated(dst)?; + if src_had_pattern != dst_had_pattern { + return Err(Error::PatternUnbalanced); + } Ok(RefSpecRef { op: operation, mode, @@ -61,4 +70,17 @@ pub(crate) mod function { dst, }) } + + fn validated(spec: Option<&BStr>) -> Result<(Option<&BStr>, bool), Error> { + match spec { + Some(spec) => { + let glob_count = spec.iter().filter(|b| **b == b'*').take(2).count(); + if glob_count == 2 { + return Err(Error::PatternUnsupported { pattern: spec.into() }); + } + Ok((Some(spec), glob_count == 1)) + } + None => Ok((None, false)), + } + } } diff --git a/git-refspec/tests/parse/mod.rs b/git-refspec/tests/parse/mod.rs index 296a31fde5..84bdc38e0e 100644 --- a/git-refspec/tests/parse/mod.rs +++ b/git-refspec/tests/parse/mod.rs @@ -15,6 +15,7 @@ fn baseline() { while let Some(kind_spec) = lines.next() { count += 1; let (kind, spec) = kind_spec.split_at(kind_spec.find_byte(b' ').expect("space between kind and spec")); + let spec = &spec[1..]; let err_code: usize = lines .next() .expect("err code") @@ -32,7 +33,7 @@ fn baseline() { Ok(res) => match (res.is_ok(), err_code == 0) { (true, true) | (false, false) => {} _ => { - eprintln!("{res:?} {err_code}"); + eprintln!("{res:?} {err_code} {} {:?}", kind.as_bstr(), spec.as_bstr()); mismatch += 1; } }, @@ -74,6 +75,27 @@ mod invalid { } } + #[test] + fn complex_patterns_with_more_than_one_asterisk() { + for op in [Operation::Fetch, Operation::Push] { + for spec in ["^*/*", "a/*/c/*", "a**:**b", "+:**/"] { + assert!(matches!( + try_parse(spec, op).unwrap_err(), + Error::PatternUnsupported { .. } + )); + } + } + } + + #[test] + fn both_sides_need_pattern_if_one_uses_it() { + for op in [Operation::Fetch, Operation::Push] { + for spec in ["/*/a", ":a/*", "+:a/*", "a*:b/c", "a:b/*"] { + assert!(matches!(try_parse(spec, op).unwrap_err(), Error::PatternUnbalanced)); + } + } + } + #[test] fn push_to_empty() { assert!(matches!(