mod reflog { mod packed { use gix_ref::file::ReferenceExt; use crate::file; #[test] fn iter() -> crate::Result { let store = file::store_with_packed_refs()?; let r = store.find("main")?; assert_eq!(r.log_iter(&store).all()?.expect("log exists").count(), 1); assert!(r.log_exists(&store), "it exists if its readable"); Ok(()) } #[test] fn iter_rev() -> crate::Result { let store = file::store_with_packed_refs()?; let r = store.find("main")?; assert_eq!(r.log_iter(&store).rev()?.expect("log exists").count(), 1); Ok(()) } } mod loose { use crate::file; #[test] fn iter() -> crate::Result { let store = file::store()?; let r = store.find_loose("HEAD")?; let mut buf = Vec::new(); assert_eq!(r.log_iter(&store, &mut buf)?.expect("log exists").count(), 1); assert!(r.log_exists(&store), "it exists if its readable"); Ok(()) } #[test] fn iter_rev() -> crate::Result { let store = file::store()?; let r = store.find_loose("HEAD")?; let mut buf = [0u8; 256]; assert_eq!(r.log_iter_rev(&store, &mut buf)?.expect("log exists").count(), 1); Ok(()) } } } mod peel { use gix_object::FindExt; use gix_ref::{Reference, file::ReferenceExt}; use crate::{ file, file::{EmptyCommit, store_with_packed_refs}, hex_to_id, }; #[test] fn one_level() -> crate::Result { let store = file::store()?; let r = store.find_loose("HEAD")?; assert_eq!(r.kind(), gix_ref::Kind::Symbolic, "there is something to peel"); let nr = Reference::from(r).follow(&store).expect("exists").expect("no failure"); assert!( matches!(nr.target.to_ref(), gix_ref::TargetRef::Object(_)), "iteration peels a single level" ); assert!(nr.follow(&store).is_none(), "end of iteration"); assert_eq!( nr.target.to_ref(), gix_ref::TargetRef::Object(&hex_to_id("134385f6d781b7e97062102c6a483440bfda2a03")), "we still have the peeled target" ); Ok(()) } #[test] fn peel_with_packed_involvement() -> crate::Result { let store = store_with_packed_refs()?; let mut head: Reference = store.find_loose("HEAD")?.into(); let expected = hex_to_id("134385f6d781b7e97062102c6a483440bfda2a03"); assert_eq!(head.peel_to_id(&store, &EmptyCommit)?, expected); assert_eq!(head.target.try_id().map(ToOwned::to_owned), Some(expected)); let mut head = store.find("dt1")?; assert_eq!(head.peel_to_id(&store, &gix_object::find::Never)?, expected); assert_eq!(head.target.into_id(), expected); Ok(()) } #[test] fn peel_one_level_with_pack() -> crate::Result { let store = store_with_packed_refs()?; let mut head = store.find("dt1")?; assert_eq!( head.target.try_id().map(ToOwned::to_owned), Some(hex_to_id("4c3f4cce493d7beb45012e478021b5f65295e5a3")) ); assert_eq!( head.kind(), gix_ref::Kind::Object, "its peeled, but does have another step to peel to…" ); let final_stop = hex_to_id("134385f6d781b7e97062102c6a483440bfda2a03"); assert_eq!(head.peeled, Some(final_stop), "…it knows its peeled object"); assert_eq!( head.follow(&store).transpose()?, None, "but following doesn't do that, only real peeling does" ); head.peel_to_id(&store, &EmptyCommit)?; assert_eq!( head.target.try_id().map(ToOwned::to_owned), Some(final_stop), "packed refs are always peeled (at least the ones we choose to read)" ); assert_eq!(head.kind(), gix_ref::Kind::Object, "it's terminally peeled now"); assert_eq!( head.follow(&store).transpose()?, None, "following doesn't change anything" ); Ok(()) } #[test] fn to_id_multi_hop() -> crate::Result { let store = file::store()?; let mut r: Reference = store.find_loose("multi-link")?.into(); assert_eq!(r.kind(), gix_ref::Kind::Symbolic, "there is something to peel"); let commit = hex_to_id("134385f6d781b7e97062102c6a483440bfda2a03"); assert_eq!(r.peel_to_id(&store, &EmptyCommit)?, commit); assert_eq!(r.name.as_bstr(), "refs/remotes/origin/multi-link-target3"); let mut r: Reference = store.find_loose("dt1")?.into(); assert_eq!( r.peel_to_id(&store, &EmptyCommit)?, hex_to_id("4c3f4cce493d7beb45012e478021b5f65295e5a3"), "points to a tag object without actual object lookup" ); let odb = crate::file::odb_at(store.git_dir().join("objects"))?; let mut r: Reference = store.find_loose("dt1")?.into(); assert_eq!(r.peel_to_id(&store, &odb)?, commit, "points to the commit with lookup"); Ok(()) } #[test] fn to_id_long_jump() -> crate::Result { for packed in [None, Some("packed")] { let store = file::store_at_with_args("make_multi_hop_ref.sh", packed)?; let odb = crate::file::odb_at(store.git_dir().join("objects"))?; let mut r: Reference = store.find("multi-hop")?; r.peel_to_id(&store, &odb)?; let commit_id = hex_to_id("134385f6d781b7e97062102c6a483440bfda2a03"); assert_eq!(r.peeled, Some(commit_id)); let mut buf = Vec::new(); let obj = odb.find(&commit_id, &mut buf)?; assert_eq!(obj.kind, gix_object::Kind::Commit, "always peeled to the first non-tag"); let mut r: Reference = store.find("multi-hop")?; let tag_id = r.follow_to_object_packed(&store, store.cached_packed_buffer()?.as_ref().map(|p| &***p))?; let obj = odb.find(&tag_id, &mut buf)?; assert_eq!(obj.kind, gix_object::Kind::Tag, "the first direct object target"); assert_eq!( obj.decode()?.into_tag().expect("tag").name, "dt2", "this is the first annotated tag, which points at dt1" ); let mut r: Reference = store.find("multi-hop2")?; let other_tag_id = r.follow_to_object_packed(&store, store.cached_packed_buffer()?.as_ref().map(|p| &***p))?; assert_eq!(other_tag_id, tag_id, "it can follow with multiple hops as well"); } Ok(()) } #[test] fn to_id_cycle() -> crate::Result { let store = file::store()?; let mut r: Reference = store.find_loose("loop-a")?.into(); assert_eq!(r.kind(), gix_ref::Kind::Symbolic, "there is something to peel"); assert_eq!(r.name.as_bstr(), "refs/loop-a"); assert!(matches!( r.peel_to_id(&store, &gix_object::find::Never).unwrap_err(), gix_ref::peel::to_id::Error::FollowToObject(gix_ref::peel::to_object::Error::Cycle { .. }) )); assert_eq!(r.name.as_bstr(), "refs/loop-a", "the ref is not changed on error"); let mut r: Reference = store.find_loose("loop-a")?.into(); let err = r .follow_to_object_packed(&store, store.cached_packed_buffer()?.as_ref().map(|p| &***p)) .unwrap_err(); assert!(matches!(err, gix_ref::peel::to_object::Error::Cycle { .. })); Ok(()) } } mod parse { mod invalid { use gix_ref::file::loose::Reference; macro_rules! mktest { ($name:ident, $input:literal, $err:literal) => { #[test] fn $name() { use std::convert::TryInto; let err = Reference::try_from_path( "HEAD".try_into().expect("this is a valid name"), $input, gix_hash::Kind::Sha1, ) .unwrap_err(); assert_eq!(err.to_string(), $err); } }; } mktest!(hex_id, b"foobar", "\"foobar\" could not be parsed"); mktest!(ref_tag, b"reff: hello", "\"reff: hello\" could not be parsed"); mktest!( sha256_sized_id_for_sha1, b"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\n", "\"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\\n\" could not be parsed" ); mktest!( trailing_garbage_after_id, b"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaextra", "\"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaextra\" could not be parsed" ); } mod valid { use gix_object::bstr::ByteSlice; use gix_ref::file::loose::Reference; use crate::sha1_hex_to_id; macro_rules! mktest { ($name:ident, $input:literal, $kind:path, $id:expr, $ref:expr) => { #[test] fn $name() { use std::convert::TryInto; let reference = Reference::try_from_path( "HEAD".try_into().expect("valid static name"), $input, gix_hash::Kind::Sha1, ) .unwrap(); assert_eq!(reference.kind(), $kind); assert_eq!(reference.target.to_ref().try_id(), $id); assert_eq!( reference.target.to_ref().try_name().map(|n| n.as_bstr()), $ref ); } }; } mktest!( peeled, b"c5241b835b93af497cda80ce0dceb8f49800df1c\n", gix_ref::Kind::Object, Some(sha1_hex_to_id("c5241b835b93af497cda80ce0dceb8f49800df1c").as_ref()), None ); mktest!( peeled_uppercase, b"C5241B835B93AF497CDA80CE0DCEB8F49800DF1C\n", gix_ref::Kind::Object, Some(sha1_hex_to_id("c5241b835b93af497cda80ce0dceb8f49800df1c").as_ref()), None ); mktest!( symbolic, b"ref: refs/heads/main\n", gix_ref::Kind::Symbolic, None, Some(b"refs/heads/main".as_bstr()) ); mktest!( symbolic_more_than_one_space, b"ref: refs/foobar\n", gix_ref::Kind::Symbolic, None, Some(b"refs/foobar".as_bstr()) ); #[test] fn symbolic_ignores_nul_suffix_like_git() { use std::convert::TryInto; let reference = Reference::try_from_path( "HEAD".try_into().expect("valid static name"), b"ref: refs/heads/main\0hidden-head-metadata", gix_hash::Kind::Sha1, ) .expect("Git ignores bytes past the first NUL in symbolic ref files, so this parses as well"); assert_eq!( reference.kind(), gix_ref::Kind::Symbolic, "the ref is still symbolic despite ignored trailing metadata" ); assert_eq!( reference.target.to_ref().try_name().map(gix_ref::FullNameRef::as_bstr), Some(b"refs/heads/main".as_bstr()), "only the target before the first NUL is used" ); } #[test] fn peeled_sha256() { use std::convert::TryInto; let input = &b"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"[..]; let reference = Reference::try_from_path( "HEAD".try_into().expect("valid static name"), input, gix_hash::Kind::Sha256, ) .unwrap(); assert_eq!(reference.kind(), gix_ref::Kind::Object); let target_id = reference.target.to_ref().try_id().expect("non-symbolic").to_owned(); assert_eq!(target_id.kind(), gix_hash::Kind::Sha256); assert_eq!(target_id, gix_hash::ObjectId::from_hex(input).unwrap()); } } }