use git_object::bstr::{BStr, BString}; use git_revision::{ spec, spec::parse::{delegate, Delegate}, }; #[derive(Default, Debug, Eq, PartialEq, Ord, PartialOrd)] struct Options { reject_kind: bool, reject_prefix: bool, } #[derive(Default, Debug, Eq, PartialEq, Ord, PartialOrd)] struct Recorder { // anchors find_ref: [Option; 2], prefix: [Option; 2], current_branch_reflog_entry: [Option; 2], nth_checked_out_branch: [Option; 2], sibling_branch: [Option; 2], index_lookups: Vec<(BString, u8)>, // navigation traversal: Vec, peel_to: Vec, patterns: Vec<(BString, bool)>, // range kind: Option, calls: usize, opts: Options, done: bool, } #[derive(Debug, Eq, PartialEq, Ord, PartialOrd)] pub enum PeelToOwned { ObjectKind(git_object::Kind), ExistingObject, RecursiveTagObject, Path(BString), } impl Recorder { fn with(options: Options) -> Self { Recorder { opts: options, ..Default::default() } } fn get_ref(&self, idx: usize) -> &BStr { self.find_ref[idx].as_ref().map(|b| b.as_ref()).unwrap() } } fn set_val(fn_name: &str, store: &mut [Option; 2], val: T) -> Option<()> { for entry in store.iter_mut() { if entry.is_none() { *entry = Some(val); return Some(()); } } panic!("called {}() more than twice with '{:?}'", fn_name, val); } impl delegate::Revision for Recorder { fn find_ref(&mut self, input: &BStr) -> Option<()> { self.calls += 1; set_val("find_ref", &mut self.find_ref, input.into()) } fn disambiguate_prefix(&mut self, input: git_hash::Prefix) -> Option<()> { self.calls += 1; if self.opts.reject_prefix { return None; } set_val("disambiguate_prefix", &mut self.prefix, input) } fn reflog(&mut self, entry: delegate::ReflogLookup) -> Option<()> { self.calls += 1; set_val( "current_branch_reflog", &mut self.current_branch_reflog_entry, match entry { delegate::ReflogLookup::Entry(no) => no.to_string(), delegate::ReflogLookup::Date(time) => { let mut buf = Vec::new(); time.write_to(&mut buf).unwrap(); BString::from(buf).to_string() } }, ) } fn nth_checked_out_branch(&mut self, branch: usize) -> Option<()> { assert_ne!(branch, 0); self.calls += 1; set_val("nth_checked_out_branch", &mut self.nth_checked_out_branch, branch) } fn sibling_branch(&mut self, kind: delegate::SiblingBranch) -> Option<()> { self.calls += 1; set_val("sibling_branch", &mut self.sibling_branch, format!("{:?}", kind)) } } impl delegate::Navigate for Recorder { fn traverse(&mut self, kind: delegate::Traversal) -> Option<()> { self.calls += 1; self.traversal.push(kind); Some(()) } fn peel_until(&mut self, kind: delegate::PeelTo) -> Option<()> { self.calls += 1; self.peel_to.push(match kind { delegate::PeelTo::ObjectKind(kind) => PeelToOwned::ObjectKind(kind), delegate::PeelTo::ExistingObject => PeelToOwned::ExistingObject, delegate::PeelTo::Path(path) => PeelToOwned::Path(path.into()), delegate::PeelTo::RecursiveTagObject => PeelToOwned::RecursiveTagObject, }); Some(()) } fn find(&mut self, regex: &BStr, negated: bool) -> Option<()> { self.calls += 1; self.patterns.push((regex.into(), negated)); Some(()) } fn index_lookup(&mut self, path: &BStr, stage: u8) -> Option<()> { self.calls += 1; self.index_lookups.push((path.into(), stage)); Some(()) } } impl delegate::Kind for Recorder { fn kind(&mut self, kind: spec::Kind) -> Option<()> { self.calls += 1; if self.opts.reject_kind { return None; } if self.kind.is_none() { self.kind = Some(kind); } else { panic!("called kind more than once with '{:?}'", kind); } Some(()) } } impl Delegate for Recorder { fn done(&mut self) { self.done = true; } } fn parse(spec: &str) -> Recorder { try_parse_opts(spec, Options::default()).unwrap() } fn try_parse(spec: &str) -> Result { try_parse_opts(spec, Default::default()) } fn try_parse_opts(spec: &str, options: Options) -> Result { let mut rec = Recorder::with(options); spec::parse(spec.into(), &mut rec)?; Ok(rec) } #[test] fn empty_specs_are_valid() { // they should of course be invalid for the delegate. CLIs may pre-process the input as well if they wish // but git itself doesn't do that. for spec in [" ", "\n\t"] { let rec = parse(spec); assert_eq!(rec.calls, 1); } let rec = parse(""); assert_eq!(rec.calls, 0, "but we do not bother to call the delegate with nothing"); assert!(rec.done); } #[test] fn all_characters_are_taken_verbatim_which_includes_whitespace() { let spec = " HEAD \n"; let rec = parse(spec); assert!(rec.kind.is_none()); assert_eq!(rec.get_ref(0), spec); } mod anchor; mod kind; mod navigate;