diff --git a/git-repository/src/revision/spec/parse/delegate.rs b/git-repository/src/revision/spec/parse/delegate.rs index a4d7a876ad..e0ab65a479 100644 --- a/git-repository/src/revision/spec/parse/delegate.rs +++ b/git-repository/src/revision/spec/parse/delegate.rs @@ -286,9 +286,60 @@ impl<'repo> delegate::Revision for Delegate<'repo> { } impl<'repo> delegate::Navigate for Delegate<'repo> { - fn traverse(&mut self, _kind: Traversal) -> Option<()> { + fn traverse(&mut self, kind: Traversal) -> Option<()> { self.unset_disambiguate_call(); - todo!() + self.follow_refs_to_objects_if_needed()?; + + let mut replacements = SmallVec::<[(ObjectId, ObjectId); 1]>::default(); + let mut errors = Vec::new(); + let objs = self.objs[self.idx].as_mut()?; + let repo = self.repo; + + for obj in objs.iter() { + match kind { + Traversal::NthParent(num) => { + match self.repo.find_object(*obj).map_err(Error::from).and_then(|obj| { + obj.try_into_commit().map_err(|err| { + let object::try_into::Error { actual, expected, id } = err; + Error::ObjectKind { + oid: id.attach(repo).shorten_or_id(), + actual, + expected, + } + }) + }) { + Ok(commit) => match commit.parent_ids().skip(num.saturating_sub(1)).next() { + Some(id) => replacements.push((commit.id, id.detach())), + None => errors.push(( + commit.id, + Error::ParentOutOfRange { + oid: commit.id().shorten_or_id(), + desired: num, + available: commit.parent_ids().count(), + }, + )), + }, + Err(err) => errors.push((*obj, err)), + } + } + Traversal::NthAncestor(_num) => todo!("ancestor"), + } + } + + if errors.len() == objs.len() { + self.err.extend(errors.into_iter().map(|(_, err)| err)); + None + } else { + for (obj, err) in errors { + objs.remove(&obj); + self.err.push(err); + } + for (find, replace) in replacements { + objs.remove(&find); + objs.insert(replace); + } + Some(()) + } } fn peel_until(&mut self, kind: PeelTo<'_>) -> Option<()> { diff --git a/git-repository/src/revision/spec/parse/types.rs b/git-repository/src/revision/spec/parse/types.rs index 28c181bfb5..70b4871b85 100644 --- a/git-repository/src/revision/spec/parse/types.rs +++ b/git-repository/src/revision/spec/parse/types.rs @@ -61,6 +61,12 @@ pub struct Options { #[derive(Debug, thiserror::Error)] #[allow(missing_docs)] pub enum Error { + #[error("Commit {oid} has {available} parents and parent number {desired} is out of range")] + ParentOutOfRange { + oid: git_hash::Prefix, + desired: usize, + available: usize, + }, #[error("Path {desired_path:?} did not exist in index at stage {desired_stage}{}{}", stage_hint.map(|actual|format!(". It does exist at stage {actual}")).unwrap_or_default(), exists.then(|| ". It exists on disk").unwrap_or(". It does not exist on disk"))] IndexLookup { desired_path: BString, diff --git a/git-repository/tests/fixtures/generated-archives/make_rev_spec_parse_repos.tar.xz b/git-repository/tests/fixtures/generated-archives/make_rev_spec_parse_repos.tar.xz index 4cade160c6..c1310f8e22 100644 --- a/git-repository/tests/fixtures/generated-archives/make_rev_spec_parse_repos.tar.xz +++ b/git-repository/tests/fixtures/generated-archives/make_rev_spec_parse_repos.tar.xz @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e40c952da2d14b24fd94d1a47c7c753d634674145c938853af39a922453a7c57 -size 27956 +oid sha256:a6db0e8ed85ec2e7a3457d411a65a3508683de5156f45baf551c8d019b26e2e3 +size 28012 diff --git a/git-repository/tests/fixtures/make_rev_spec_parse_repos.sh b/git-repository/tests/fixtures/make_rev_spec_parse_repos.sh index b108bdd662..46949e4574 100644 --- a/git-repository/tests/fixtures/make_rev_spec_parse_repos.sh +++ b/git-repository/tests/fixtures/make_rev_spec_parse_repos.sh @@ -336,6 +336,7 @@ git init complex_graph git merge c || : { echo g && echo h && echo i && echo j && echo d && echo e && echo f && echo b && echo c && echo a; } > file git add file && git commit -m A + git branch a baseline ":/message" # finds 'message recent' instead of 'initial message' baseline ":/!-message" # above, negated @@ -346,4 +347,9 @@ git init complex_graph baseline ":file" # index lookup, default stage 0 baseline ":1:file" # stage 1 baseline ":foo" # not found + # parents + baseline "a" + baseline "a^1" + baseline "a^0" + baseline "a^42" ) diff --git a/git-repository/tests/revision/spec/from_bytes/mod.rs b/git-repository/tests/revision/spec/from_bytes/mod.rs index 96b13f5b59..390617cee9 100644 --- a/git-repository/tests/revision/spec/from_bytes/mod.rs +++ b/git-repository/tests/revision/spec/from_bytes/mod.rs @@ -7,6 +7,28 @@ pub use util::*; mod ambiguous; mod regex; + +mod traverse { + use crate::revision::spec::from_bytes::{parse_spec, repo}; + use git_repository::prelude::ObjectIdExt; + use git_repository::revision::Spec; + use git_testtools::hex_to_id; + + #[test] + fn parent() { + let repo = repo("complex_graph").unwrap(); + assert_eq!( + parse_spec("a^1", &repo).unwrap(), + Spec::from_id(hex_to_id("5b3f9e24965d0b28780b7ce5daf2b5b7f7e0459f").attach(&repo)) + ); + assert_eq!(parse_spec("a", &repo).unwrap(), parse_spec("a^0", &repo).unwrap(),); + assert_eq!( + parse_spec("a^42", &repo).unwrap_err().to_string(), + "Commit 55e825e has 2 parents and parent number 42 is out of range" + ); + } +} + mod index { use crate::revision::spec::from_bytes::{parse_spec, repo}; use git_repository::prelude::ObjectIdExt;