blob: 3e37ab99e5a50929c832b1033a24150f027e31fb [file] [log] [blame]
use std::collections::HashSet;
use gix_hash::ObjectId;
use gix_revision::spec::parse::{
delegate,
delegate::{ReflogLookup, SiblingBranch},
};
use crate::{
bstr::{BStr, BString, ByteSlice},
ext::ReferenceExt,
remote,
revision::spec::parse::{Delegate, Error, RefsHint},
};
impl<'repo> delegate::Revision for Delegate<'repo> {
fn find_ref(&mut self, name: &BStr) -> Option<()> {
self.unset_disambiguate_call();
if !self.err.is_empty() && self.refs[self.idx].is_some() {
return None;
}
match self.repo.refs.find(name) {
Ok(r) => {
assert!(self.refs[self.idx].is_none(), "BUG: cannot set the same ref twice");
self.refs[self.idx] = Some(r);
Some(())
}
Err(err) => {
self.err.push(err.into());
None
}
}
}
fn disambiguate_prefix(
&mut self,
prefix: gix_hash::Prefix,
_must_be_commit: Option<delegate::PrefixHint<'_>>,
) -> Option<()> {
self.last_call_was_disambiguate_prefix[self.idx] = true;
let mut candidates = Some(HashSet::default());
self.prefix[self.idx] = Some(prefix);
let empty_tree_id = gix_hash::ObjectId::empty_tree(prefix.as_oid().kind());
let res = if prefix.as_oid() == empty_tree_id {
candidates.as_mut().expect("set").insert(empty_tree_id);
Ok(Some(Err(())))
} else {
self.repo.objects.lookup_prefix(prefix, candidates.as_mut())
};
match res {
Err(err) => {
self.err.push(err.into());
None
}
Ok(None) => {
self.err.push(Error::PrefixNotFound { prefix });
None
}
Ok(Some(Ok(_) | Err(()))) => {
assert!(self.objs[self.idx].is_none(), "BUG: cannot set the same prefix twice");
let candidates = candidates.expect("set above");
match self.opts.refs_hint {
RefsHint::PreferObjectOnFullLengthHexShaUseRefOtherwise
if prefix.hex_len() == candidates.iter().next().expect("at least one").kind().len_in_hex() =>
{
self.ambiguous_objects[self.idx] = Some(candidates.clone());
self.objs[self.idx] = Some(candidates);
Some(())
}
RefsHint::PreferObject => {
self.ambiguous_objects[self.idx] = Some(candidates.clone());
self.objs[self.idx] = Some(candidates);
Some(())
}
RefsHint::PreferRef | RefsHint::PreferObjectOnFullLengthHexShaUseRefOtherwise | RefsHint::Fail => {
match self.repo.refs.find(&prefix.to_string()) {
Ok(ref_) => {
assert!(self.refs[self.idx].is_none(), "BUG: cannot set the same ref twice");
if self.opts.refs_hint == RefsHint::Fail {
self.refs[self.idx] = Some(ref_.clone());
self.err.push(Error::AmbiguousRefAndObject {
prefix,
reference: ref_,
});
self.err.push(Error::ambiguous(candidates, prefix, self.repo));
None
} else {
self.refs[self.idx] = Some(ref_);
Some(())
}
}
Err(_) => {
self.ambiguous_objects[self.idx] = Some(candidates.clone());
self.objs[self.idx] = Some(candidates);
Some(())
}
}
}
}
}
}
}
fn reflog(&mut self, query: ReflogLookup) -> Option<()> {
self.unset_disambiguate_call();
match query {
ReflogLookup::Date(_date) => {
// TODO: actually do this - this should be possible now despite incomplete date parsing
self.err.push(Error::Planned {
dependency: "remote handling and ref-specs are fleshed out more",
});
None
}
ReflogLookup::Entry(no) => {
let r = match &mut self.refs[self.idx] {
Some(r) => r.clone().attach(self.repo),
val @ None => match self.repo.head().map(crate::Head::try_into_referent) {
Ok(Some(r)) => {
*val = Some(r.clone().detach());
r
}
Ok(None) => {
self.err.push(Error::UnbornHeadsHaveNoRefLog);
return None;
}
Err(err) => {
self.err.push(err.into());
return None;
}
},
};
let mut platform = r.log_iter();
match platform.rev().ok().flatten() {
Some(mut it) => match it.nth(no).and_then(Result::ok) {
Some(line) => {
self.objs[self.idx]
.get_or_insert_with(HashSet::default)
.insert(line.new_oid);
Some(())
}
None => {
let available = platform.rev().ok().flatten().map_or(0, Iterator::count);
self.err.push(Error::RefLogEntryOutOfRange {
reference: r.detach(),
desired: no,
available,
});
None
}
},
None => {
self.err.push(Error::MissingRefLog {
reference: r.name().as_bstr().into(),
action: "lookup entry",
});
None
}
}
}
}
}
fn nth_checked_out_branch(&mut self, branch_no: usize) -> Option<()> {
self.unset_disambiguate_call();
fn prior_checkouts_iter<'a>(
platform: &'a mut gix_ref::file::log::iter::Platform<'static, '_>,
) -> Result<impl Iterator<Item = (BString, ObjectId)> + 'a, Error> {
match platform.rev().ok().flatten() {
Some(log) => Ok(log.filter_map(Result::ok).filter_map(|line| {
line.message
.strip_prefix(b"checkout: moving from ")
.and_then(|from_to| from_to.find(" to ").map(|pos| &from_to[..pos]))
.map(|from_branch| (from_branch.into(), line.previous_oid))
})),
None => Err(Error::MissingRefLog {
reference: "HEAD".into(),
action: "search prior checked out branch",
}),
}
}
let head = match self.repo.head() {
Ok(head) => head,
Err(err) => {
self.err.push(err.into());
return None;
}
};
match prior_checkouts_iter(&mut head.log_iter()).map(|mut it| it.nth(branch_no.saturating_sub(1))) {
Ok(Some((ref_name, id))) => {
let id = match self.repo.find_reference(ref_name.as_bstr()) {
Ok(mut r) => {
let id = r.peel_to_id_in_place().map(crate::Id::detach).unwrap_or(id);
self.refs[self.idx] = Some(r.detach());
id
}
Err(_) => id,
};
self.objs[self.idx].get_or_insert_with(HashSet::default).insert(id);
Some(())
}
Ok(None) => {
self.err.push(Error::PriorCheckoutOutOfRange {
desired: branch_no,
available: prior_checkouts_iter(&mut head.log_iter())
.map(Iterator::count)
.unwrap_or(0),
});
None
}
Err(err) => {
self.err.push(err);
None
}
}
}
fn sibling_branch(&mut self, kind: SiblingBranch) -> Option<()> {
self.unset_disambiguate_call();
let reference = match &mut self.refs[self.idx] {
val @ None => match self.repo.head().map(crate::Head::try_into_referent) {
Ok(Some(r)) => {
*val = Some(r.clone().detach());
r
}
Ok(None) => {
self.err.push(Error::UnbornHeadForSibling);
return None;
}
Err(err) => {
self.err.push(err.into());
return None;
}
},
Some(r) => r.clone().attach(self.repo),
};
let direction = match kind {
SiblingBranch::Upstream => remote::Direction::Fetch,
SiblingBranch::Push => remote::Direction::Push,
};
match reference.remote_tracking_ref_name(direction) {
None => self.err.push(Error::NoTrackingBranch {
name: reference.inner.name,
direction,
}),
Some(Err(err)) => self.err.push(Error::GetTrackingBranch {
name: reference.inner.name,
direction,
source: Box::new(err),
}),
Some(Ok(name)) => match self.repo.find_reference(name.as_ref()) {
Err(err) => self.err.push(Error::GetTrackingBranch {
name: reference.inner.name,
direction,
source: Box::new(err),
}),
Ok(r) => {
self.refs[self.idx] = r.inner.into();
return Some(());
}
},
};
None
}
}