Skip to content

Commit

Permalink
feat: add Reference::peel_to_kind()
Browse files Browse the repository at this point in the history
Make it easy to follow a ref and peel it to a given object type.
Additional `peel_to_<kind>()` shortcuts are also provided, with
the same name as in `git2`.
  • Loading branch information
Byron committed Aug 21, 2024
1 parent d296ee8 commit cdaba84
Show file tree
Hide file tree
Showing 6 changed files with 164 additions and 13 deletions.
18 changes: 18 additions & 0 deletions gix/src/reference/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,24 @@ pub mod peel {
#[error(transparent)]
PackedRefsOpen(#[from] gix_ref::packed::buffer::open::Error),
}

///
#[allow(clippy::empty_docs)]
pub mod to_kind {
/// The error returned by [`Reference::peel_to_kind(…)`](crate::Reference::peel_to_kind()).
#[derive(Debug, thiserror::Error)]
#[allow(missing_docs)]
pub enum Error {
#[error(transparent)]
FollowToObject(#[from] gix_ref::peel::to_object::Error),
#[error(transparent)]
PackedRefsOpen(#[from] gix_ref::packed::buffer::open::Error),
#[error(transparent)]
FindObject(#[from] crate::object::find::existing::Error),
#[error(transparent)]
PeelObject(#[from] crate::object::peel::to_kind::Error),
}
}
}

///
Expand Down
77 changes: 69 additions & 8 deletions gix/src/reference/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

use gix_ref::file::ReferenceExt;

use crate::{Id, Reference};
use crate::{Blob, Commit, Id, Object, Reference, Tag, Tree};

pub mod iter;
///
Expand Down Expand Up @@ -65,19 +65,21 @@ impl<'repo> Reference<'repo> {

/// Peeling
impl<'repo> Reference<'repo> {
/// Follow all symbolic targets this reference might point to and peel the underlying object
/// to the end of the chain, and return it.
/// Follow all symbolic targets this reference might point to and peel all annotated tags
/// to their first non-tag target, and return it,
///
/// This is useful to learn where this reference is ultimately pointing to.
/// This is useful to learn where this reference is ultimately pointing to after following
/// the chain of symbolic refs and annotated tags.
pub fn peel_to_id_in_place(&mut self) -> Result<Id<'repo>, peel::Error> {
let oid = self.inner.peel_to_id_in_place(&self.repo.refs, &self.repo.objects)?;
Ok(Id::from_id(oid, self.repo))
}

/// Follow all symbolic targets this reference might point to and peel the underlying object
/// to the end of the chain, and return it, reusing the `packed` buffer if available.
/// Follow all symbolic targets this reference might point to and peel all annotated tags
/// to their first non-tag target, and return it, reusing the `packed` buffer if available.
///
/// This is useful to learn where this reference is ultimately pointing to.
/// This is useful to learn where this reference is ultimately pointing to after following
/// the chain of symbolic refs and annotated tags.
pub fn peel_to_id_in_place_packed(
&mut self,
packed: Option<&gix_ref::packed::Buffer>,
Expand All @@ -88,11 +90,69 @@ impl<'repo> Reference<'repo> {
Ok(Id::from_id(oid, self.repo))
}

/// Similar to [`peel_to_id_in_place()`][Reference::peel_to_id_in_place()], but consumes this instance.
/// Similar to [`peel_to_id_in_place()`](Reference::peel_to_id_in_place()), but consumes this instance.
pub fn into_fully_peeled_id(mut self) -> Result<Id<'repo>, peel::Error> {
self.peel_to_id_in_place()
}

/// Follow this reference's target until it points at an object directly, and peel that object until
/// its type matches the given `kind`. It's an error to try to peel to a kind that this ref doesn't point to.
///
/// Note that this ref will point to the first target object afterward, which may be a tag. This is different
/// from [`peel_to_id_in_place()`](Self::peel_to_id_in_place()) where it will point to the first non-tag object.
#[doc(alias = "peel", alias = "git2")]
pub fn peel_to_kind(&mut self, kind: gix_object::Kind) -> Result<Object<'repo>, peel::to_kind::Error> {
let packed = self.repo.refs.cached_packed_buffer().map_err(|err| {
peel::to_kind::Error::FollowToObject(gix_ref::peel::to_object::Error::Follow(
file::find::existing::Error::Find(file::find::Error::PackedOpen(err)),
))
})?;
self.peel_to_kind_packed(kind, packed.as_ref().map(|p| &***p))
}

/// Peel this ref until the first commit.
///
/// For details, see [`peel_to_kind`()](Self::peel_to_kind()).
pub fn peel_to_commit(&mut self) -> Result<Commit<'repo>, peel::to_kind::Error> {
Ok(self.peel_to_kind(gix_object::Kind::Commit)?.into_commit())
}

/// Peel this ref until the first annotated tag.
///
/// For details, see [`peel_to_kind`()](Self::peel_to_kind()).
pub fn peel_to_tag(&mut self) -> Result<Tag<'repo>, peel::to_kind::Error> {
Ok(self.peel_to_kind(gix_object::Kind::Tag)?.into_tag())
}

/// Peel this ref until the first tree.
///
/// For details, see [`peel_to_kind`()](Self::peel_to_kind()).
pub fn peel_to_tree(&mut self) -> Result<Tree<'repo>, peel::to_kind::Error> {
Ok(self.peel_to_kind(gix_object::Kind::Tree)?.into_tree())
}

/// Peel this ref until it points to a blob. Note that this is highly uncommon to happen
/// as it would require an annotated tag to point to a blob, instead of a commit.
///
/// For details, see [`peel_to_kind`()](Self::peel_to_kind()).
pub fn peel_to_blob(&mut self) -> Result<Blob<'repo>, peel::to_kind::Error> {
Ok(self.peel_to_kind(gix_object::Kind::Blob)?.into_blob())
}

/// Like [`peel_to_kind()`](Self::peel_to_kind), but allows to provide `packed` for best possible performance
/// when peeling many refs.
pub fn peel_to_kind_packed(
&mut self,
kind: gix_object::Kind,
packed: Option<&gix_ref::packed::Buffer>,
) -> Result<Object<'repo>, peel::to_kind::Error> {
let target = self
.inner
.follow_to_object_in_place_packed(&self.repo.refs, packed)?
.attach(self.repo);
Ok(target.object()?.peel_to_kind(kind)?)
}

/// Follow this symbolic reference one level and return the ref it refers to.
///
/// Returns `None` if this is not a symbolic reference, hence the leaf of the chain.
Expand All @@ -108,3 +168,4 @@ impl<'repo> Reference<'repo> {

mod edits;
pub use edits::{delete, set_target_id};
use gix_ref::file;
Binary file not shown.
2 changes: 2 additions & 0 deletions gix/tests/fixtures/make_references_repo.sh
Original file line number Diff line number Diff line change
Expand Up @@ -27,5 +27,7 @@ echo "ref: refs/loop-a" > .git/refs/loop-b

git tag t1
git tag -m "tag object" dt1
git tag -m "tag object indirect" dt2 dt1
echo "ref: refs/tags/dt2" > .git/refs/tags/dt3

git pack-refs --all --prune
74 changes: 71 additions & 3 deletions gix/tests/reference/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,7 @@ fn remote_name() -> crate::Result {
}

mod find {
use gix_ref as refs;
use gix_ref::{FullName, FullNameRef, Target};
use gix_ref::{FullName, FullNameRef, Target, TargetRef};

use crate::util::hex_to_id;

Expand All @@ -63,7 +62,7 @@ mod find {

assert_eq!(
packed_tag_ref.inner.target,
refs::Target::Object(hex_to_id("4c3f4cce493d7beb45012e478021b5f65295e5a3")),
Target::Object(hex_to_id("4c3f4cce493d7beb45012e478021b5f65295e5a3")),
"it points to a tag object"
);

Expand All @@ -85,6 +84,75 @@ mod find {
let expected: &FullNameRef = "refs/remotes/origin/multi-link-target3".try_into()?;
assert_eq!(symbolic_ref.name(), expected, "it follows symbolic refs, too");
assert_eq!(symbolic_ref.into_fully_peeled_id()?, the_commit, "idempotency");

let mut tag_ref = repo.find_reference("dt3")?;
assert_eq!(
tag_ref.target(),
TargetRef::Symbolic("refs/tags/dt2".try_into()?),
"the ref points at another tag"
);
assert_eq!(tag_ref.inner.peeled, None, "it wasn't peeled yet, nothing is stored");
let obj = tag_ref.peel_to_kind(gix::object::Kind::Tag)?;
assert_eq!(tag_ref.peel_to_tag()?.id, obj.id);
assert_eq!(obj.kind, gix::object::Kind::Tag);
assert_eq!(
obj.into_tag().decode()?.name,
"dt2",
"it stop at the first direct target"
);

let first_tag_id = hex_to_id("0f35190769db39bc70f60b6fbec9156370ce2f83");
assert_eq!(
tag_ref.target().id(),
first_tag_id,
"it's now followed to the first target"
);
let target_commit_id = hex_to_id("134385f6d781b7e97062102c6a483440bfda2a03");
assert_eq!(
tag_ref.inner.peeled, Some(target_commit_id),
"It only counts as peeled as this ref is packed, and peeling in place is a way to 'make it the target' officially."
);

let err = tag_ref.peel_to_kind(gix::object::Kind::Blob).unwrap_err();
let expectd_err = "Last encountered object 4b825dc was tree while trying to peel to blob";
assert_eq!(
err.to_string(),
expectd_err,
"it's an error if the desired type isn't actually present"
);
match tag_ref.peel_to_blob() {
Ok(_) => {
unreachable!("target is a commit")
}
Err(err) => {
assert_eq!(err.to_string(), expectd_err);
}
}

let obj = tag_ref.peel_to_kind(gix::object::Kind::Tree)?;
assert!(obj.kind.is_tree());
assert_eq!(obj.id, hex_to_id("4b825dc642cb6eb9a060e54bf8d69288fbee4904"),);
assert_eq!(tag_ref.peel_to_tree()?.id, obj.id);

assert_eq!(
tag_ref.target().id(),
first_tag_id,
"nothing changed - it still points to the target"
);
assert_eq!(
tag_ref.inner.peeled,
Some(target_commit_id),
"the peeling cache wasn't changed"
);

let obj = tag_ref.peel_to_kind(gix::object::Kind::Commit)?;
assert!(obj.kind.is_commit());
assert_eq!(
obj.id, target_commit_id,
"the standard-peel peels to right after all tags"
);
assert_eq!(tag_ref.peel_to_commit()?.id, obj.id);

Ok(())
}

Expand Down
6 changes: 4 additions & 2 deletions gix/tests/repository/reference.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ mod set_namespace {
let (mut repo, _keep) = easy_repo_rw()?;
assert_eq!(
repo.references()?.all()?.count(),
15,
17,
"there are plenty of references in the default namespace"
);
assert!(repo.namespace().is_none(), "no namespace is set initially");
Expand Down Expand Up @@ -73,7 +73,7 @@ mod set_namespace {

assert_eq!(
repo.references()?.all()?.count(),
17,
19,
"it lists all references, also the ones in namespaces"
);
Ok(())
Expand Down Expand Up @@ -110,6 +110,8 @@ mod iter_references {
"refs/remotes/origin/main",
"refs/remotes/origin/multi-link-target3",
"refs/tags/dt1",
"refs/tags/dt2",
"refs/tags/dt3",
"refs/tags/multi-link-target2",
"refs/tags/t1"
]
Expand Down

0 comments on commit cdaba84

Please sign in to comment.