diff options
Diffstat (limited to 'vendor/gix/src/revision/spec/parse/delegate/mod.rs')
-rw-r--r-- | vendor/gix/src/revision/spec/parse/delegate/mod.rs | 256 |
1 files changed, 256 insertions, 0 deletions
diff --git a/vendor/gix/src/revision/spec/parse/delegate/mod.rs b/vendor/gix/src/revision/spec/parse/delegate/mod.rs new file mode 100644 index 000000000..78e4ab9ee --- /dev/null +++ b/vendor/gix/src/revision/spec/parse/delegate/mod.rs @@ -0,0 +1,256 @@ +use std::collections::HashSet; + +use gix_hash::ObjectId; +use gix_revision::spec::{ + parse, + parse::delegate::{self}, +}; +use smallvec::SmallVec; + +use super::{Delegate, Error, ObjectKindHint}; +use crate::{ + ext::{ObjectIdExt, ReferenceExt}, + Repository, +}; + +type Replacements = SmallVec<[(ObjectId, ObjectId); 1]>; + +impl<'repo> Delegate<'repo> { + pub fn new(repo: &'repo Repository, opts: crate::revision::spec::parse::Options) -> Self { + Delegate { + refs: Default::default(), + objs: Default::default(), + ambiguous_objects: Default::default(), + idx: 0, + kind: None, + err: Vec::new(), + prefix: Default::default(), + last_call_was_disambiguate_prefix: Default::default(), + opts, + repo, + } + } + + pub fn into_err(mut self) -> Error { + let repo = self.repo; + for err in self + .ambiguous_objects + .iter_mut() + .zip(self.prefix) + .filter_map(|(a, b)| a.take().filter(|candidates| candidates.len() > 1).zip(b)) + .map(|(candidates, prefix)| Error::ambiguous(candidates, prefix, repo)) + .rev() + { + self.err.insert(0, err); + } + Error::from_errors(self.err) + } + + pub fn into_rev_spec(mut self) -> Result<crate::revision::Spec<'repo>, Error> { + fn zero_or_one_objects_or_ambiguity_err( + mut candidates: [Option<HashSet<ObjectId>>; 2], + prefix: [Option<gix_hash::Prefix>; 2], + mut errors: Vec<Error>, + repo: &Repository, + ) -> Result<[Option<ObjectId>; 2], Error> { + let mut out = [None, None]; + for ((candidates, prefix), out) in candidates.iter_mut().zip(prefix).zip(out.iter_mut()) { + let candidates = candidates.take(); + match candidates { + None => *out = None, + Some(candidates) => { + match candidates.len() { + 0 => unreachable!( + "BUG: let's avoid still being around if no candidate matched the requirements" + ), + 1 => { + *out = candidates.into_iter().next(); + } + _ => { + errors.insert( + 0, + Error::ambiguous(candidates, prefix.expect("set when obtaining candidates"), repo), + ); + return Err(Error::from_errors(errors)); + } + }; + } + }; + } + Ok(out) + } + + fn kind_to_spec( + kind: Option<gix_revision::spec::Kind>, + [first, second]: [Option<ObjectId>; 2], + ) -> Result<gix_revision::Spec, Error> { + use gix_revision::spec::Kind::*; + Ok(match kind.unwrap_or_default() { + IncludeReachable => gix_revision::Spec::Include(first.ok_or(Error::Malformed)?), + ExcludeReachable => gix_revision::Spec::Exclude(first.ok_or(Error::Malformed)?), + RangeBetween => gix_revision::Spec::Range { + from: first.ok_or(Error::Malformed)?, + to: second.ok_or(Error::Malformed)?, + }, + ReachableToMergeBase => gix_revision::Spec::Merge { + theirs: first.ok_or(Error::Malformed)?, + ours: second.ok_or(Error::Malformed)?, + }, + IncludeReachableFromParents => gix_revision::Spec::IncludeOnlyParents(first.ok_or(Error::Malformed)?), + ExcludeReachableFromParents => gix_revision::Spec::ExcludeParents(first.ok_or(Error::Malformed)?), + }) + } + + let range = zero_or_one_objects_or_ambiguity_err(self.objs, self.prefix, self.err, self.repo)?; + Ok(crate::revision::Spec { + first_ref: self.refs[0].take(), + second_ref: self.refs[1].take(), + inner: kind_to_spec(self.kind, range)?, + repo: self.repo, + }) + } +} + +impl<'repo> parse::Delegate for Delegate<'repo> { + fn done(&mut self) { + self.follow_refs_to_objects_if_needed(); + self.disambiguate_objects_by_fallback_hint( + self.kind_implies_committish() + .then_some(ObjectKindHint::Committish) + .or(self.opts.object_kind_hint), + ); + } +} + +impl<'repo> delegate::Kind for Delegate<'repo> { + fn kind(&mut self, kind: gix_revision::spec::Kind) -> Option<()> { + use gix_revision::spec::Kind::*; + self.kind = Some(kind); + + if self.kind_implies_committish() { + self.disambiguate_objects_by_fallback_hint(ObjectKindHint::Committish.into()); + } + if matches!(kind, RangeBetween | ReachableToMergeBase) { + self.idx += 1; + } + + Some(()) + } +} + +impl<'repo> Delegate<'repo> { + fn kind_implies_committish(&self) -> bool { + self.kind.unwrap_or(gix_revision::spec::Kind::IncludeReachable) != gix_revision::spec::Kind::IncludeReachable + } + fn disambiguate_objects_by_fallback_hint(&mut self, hint: Option<ObjectKindHint>) { + fn require_object_kind(repo: &Repository, obj: &gix_hash::oid, kind: gix_object::Kind) -> Result<(), Error> { + let obj = repo.find_object(obj)?; + if obj.kind == kind { + Ok(()) + } else { + Err(Error::ObjectKind { + actual: obj.kind, + expected: kind, + oid: obj.id.attach(repo).shorten_or_id(), + }) + } + } + + if self.last_call_was_disambiguate_prefix[self.idx] { + self.unset_disambiguate_call(); + + if let Some(objs) = self.objs[self.idx].as_mut() { + let repo = self.repo; + let errors: Vec<_> = match hint { + Some(kind_hint) => match kind_hint { + ObjectKindHint::Treeish | ObjectKindHint::Committish => { + let kind = match kind_hint { + ObjectKindHint::Treeish => gix_object::Kind::Tree, + ObjectKindHint::Committish => gix_object::Kind::Commit, + _ => unreachable!("BUG: we narrow possibilities above"), + }; + objs.iter() + .filter_map(|obj| peel(repo, obj, kind).err().map(|err| (*obj, err))) + .collect() + } + ObjectKindHint::Tree | ObjectKindHint::Commit | ObjectKindHint::Blob => { + let kind = match kind_hint { + ObjectKindHint::Tree => gix_object::Kind::Tree, + ObjectKindHint::Commit => gix_object::Kind::Commit, + ObjectKindHint::Blob => gix_object::Kind::Blob, + _ => unreachable!("BUG: we narrow possibilities above"), + }; + objs.iter() + .filter_map(|obj| require_object_kind(repo, obj, kind).err().map(|err| (*obj, err))) + .collect() + } + }, + None => return, + }; + + if errors.len() == objs.len() { + self.err.extend(errors.into_iter().map(|(_, err)| err)); + } else { + for (obj, err) in errors { + objs.remove(&obj); + self.err.push(err); + } + } + } + } + } + fn follow_refs_to_objects_if_needed(&mut self) -> Option<()> { + assert_eq!(self.refs.len(), self.objs.len()); + let repo = self.repo; + for (r, obj) in self.refs.iter().zip(self.objs.iter_mut()) { + if let (_ref_opt @ Some(ref_), obj_opt @ None) = (r, obj) { + if let Some(id) = ref_.target.try_id().map(ToOwned::to_owned).or_else(|| { + ref_.clone() + .attach(repo) + .peel_to_id_in_place() + .ok() + .map(|id| id.detach()) + }) { + obj_opt.get_or_insert_with(HashSet::default).insert(id); + }; + }; + } + Some(()) + } + + fn unset_disambiguate_call(&mut self) { + self.last_call_was_disambiguate_prefix[self.idx] = false; + } +} + +fn peel(repo: &Repository, obj: &gix_hash::oid, kind: gix_object::Kind) -> Result<ObjectId, Error> { + let mut obj = repo.find_object(obj)?; + obj = obj.peel_to_kind(kind)?; + debug_assert_eq!(obj.kind, kind, "bug in Object::peel_to_kind() which didn't deliver"); + Ok(obj.id) +} + +fn handle_errors_and_replacements( + destination: &mut Vec<Error>, + objs: &mut HashSet<ObjectId>, + errors: Vec<(ObjectId, Error)>, + replacements: &mut Replacements, +) -> Option<()> { + if errors.len() == objs.len() { + destination.extend(errors.into_iter().map(|(_, err)| err)); + None + } else { + for (obj, err) in errors { + objs.remove(&obj); + destination.push(err); + } + for (find, replace) in replacements { + objs.remove(find); + objs.insert(*replace); + } + Some(()) + } +} + +mod navigate; +mod revision; |