Skip to content

Commit

Permalink
Allow delegates to refuse spec kind changes (#427)
Browse files Browse the repository at this point in the history
This will be useful for all methods/functions that want to take a
single spec only.

Whenever a range is set, even if no other actual revision is provided,
it indicates the input has different intentions than what we require.
  • Loading branch information
Byron committed May 29, 2022
1 parent c280b8a commit 4c5570a
Show file tree
Hide file tree
Showing 2 changed files with 41 additions and 8 deletions.
7 changes: 4 additions & 3 deletions git-revision/src/spec.rs
Expand Up @@ -42,11 +42,12 @@ pub mod parse {

/// Set the kind of the specification, which happens only once if it happens at all.
/// In case this method isn't called, assume `Single`.
/// Reject a kind by returning `None` to stop the parsing.
///
/// Note that ranges don't necessarily assure that a second specification will be parsed.
/// If `^rev` is given, this method is called with [`spec::Kind::Range`][crate::spec::Kind::Range]
/// and no second specification is provided.
fn kind(&mut self, kind: crate::spec::Kind);
fn kind(&mut self, kind: crate::spec::Kind) -> Option<()>;
}

pub(crate) mod function {
Expand All @@ -65,13 +66,13 @@ pub mod parse {
pub fn parse(mut input: &BStr, delegate: &mut impl Delegate) -> Result<(), Error> {
if let Some(b'^') = input.get(0) {
input = next(input).1;
delegate.kind(spec::Kind::Range);
delegate.kind(spec::Kind::Range).ok_or(Error::Delegate)?;
}

input = revision(input, delegate)?;
if let Some((rest, kind)) = try_range(input) {
// TODO: protect against double-kind calls, invalid for git
delegate.kind(kind);
delegate.kind(kind).ok_or(Error::Delegate)?;
input = rest.as_bstr();
}
input = revision(input, delegate)?;
Expand Down
42 changes: 37 additions & 5 deletions git-revision/tests/spec/parse.rs
@@ -1,12 +1,27 @@
use git_object::bstr::{BStr, BString};
use git_revision::spec;

#[derive(Default, Debug)]
struct Options {
reject_kind: bool,
}

#[derive(Default, Debug)]
struct Recorder {
resolve_ref_input: Option<BString>,
resolve_ref_input2: Option<BString>,
kind: Option<spec::Kind>,
calls: usize,
opts: Options,
}

impl Recorder {
fn with(options: Options) -> Self {
Recorder {
opts: options,
..Default::default()
}
}
}

impl spec::parse::Delegate for Recorder {
Expand Down Expand Up @@ -34,16 +49,24 @@ impl spec::parse::Delegate for Recorder {
todo!()
}

fn kind(&mut self, kind: spec::Kind) {
fn kind(&mut self, kind: spec::Kind) -> Option<()> {
if self.opts.reject_kind {
return None;
}
self.calls += 1;
self.kind = Some(kind);
Some(())
}
}

fn parse(spec: &str) -> Recorder {
let mut rec = Recorder::default();
spec::parse(spec.into(), &mut rec).unwrap();
rec
try_parse_opts(spec, Options::default()).unwrap()
}

fn try_parse_opts(spec: &str, options: Options) -> Result<Recorder, spec::parse::Error> {
let mut rec = Recorder::with(options);
spec::parse(spec.into(), &mut rec)?;
Ok(rec)
}

#[test]
Expand Down Expand Up @@ -85,9 +108,18 @@ mod revision {
}

mod range {
use crate::spec::parse::parse;
use crate::spec::parse::{parse, try_parse_opts, Options};
use git_revision::spec;

#[test]
fn delegate_can_refuse_spec_kinds() {
let err = try_parse_opts("^HEAD", Options { reject_kind: true }).unwrap_err();
assert!(
matches!(err, spec::parse::Error::Delegate),
"Delegates can refuse spec kind changes to abort parsing early"
);
}

#[test]
fn leading_caret_is_range_kind() {
let rec = parse("^HEAD");
Expand Down

0 comments on commit 4c5570a

Please sign in to comment.