use git_repository as git; use crate::remote; #[cfg(feature = "blocking-network-client")]mod blocking_io { use git_object::bstr::ByteSlice; use git_ref::TargetRef; use git_repository as git; use crate::remote; #[test] fn fetch_only_with_configuration() -> crate::Result { let tmp = git_testtools::tempfile::TempDir::new()?; let called_configure_remote = std::sync::Arc::new(std::sync::atomic::AtomicBool::default()); let remote_name = "special"; let mut prepare = git::clone::PrepareFetch::new( remote::repo("base").path(), tmp.path(), git::create::Kind::Bare, Default::default(), git::open::Options::isolated().config_overrides([ "init.defaultBranch=unused-as-overridden-by-remote", "core.logAllRefUpdates", ]), )? .with_remote_name(remote_name)? .configure_remote({ let called_configure_remote = called_configure_remote.clone(); move |r| { called_configure_remote.store(true, std::sync::atomic::Ordering::Relaxed); Ok( r.with_refspec("+refs/tags/*:refs/tags/*", git::remote::Direction::Fetch) .expect("valid static spec"), ) } }); let (repo, out) = prepare.fetch_only(git::progress::Discard, &std::sync::atomic::AtomicBool::default())?; drop(prepare); assert!( called_configure_remote.load(std::sync::atomic::Ordering::Relaxed), "custom remote configuration is called" ); assert_eq!(repo.remote_names().len(), 1, "only ever one remote"); let remote = repo.find_remote(remote_name)?; assert_eq!( remote.refspecs(git::remote::Direction::Fetch).len(), 2, "our added spec was stored as well" ); assert!( git::path::from_bstr( remote .url(git::remote::Direction::Fetch) .expect("present") .path .as_ref() ) .is_absolute(), "file urls can't be relative paths" ); assert_eq!(out.ref_map.mappings.len(), 14); let packed_refs = repo .refs .cached_packed_buffer()? .expect("packed refs should be present"); assert_eq!( packed_refs.iter()?.count(), 14, "all non-symbolic refs should be stored" ); assert_eq!( repo.refs.loose_iter()?.count(), 2, "HEAD and an actual symbolic ref we received" ); match out.status { git_repository::remote::fetch::Status::Change { update_refs, .. } => { for edit in &update_refs.edits { use git_odb::Find; match edit.change.new_value().expect("always set/no deletion") { TargetRef::Symbolic(referent) => { assert!( repo.find_reference(referent).is_ok(), "if we set up a symref, the target should exist by now" ) } TargetRef::Peeled(id) => { assert!(repo.objects.contains(id), "part of the fetched pack"); } } let r = repo .find_reference(edit.name.as_ref()) .unwrap_or_else(|_| panic!("didn't find created reference: {:?}", edit)); if r.name().category().expect("known") != git_ref::Category::Tag { assert!(r .name() .category_and_short_name() .expect("computable") .1 .starts_with_str(remote_name)); let mut logs = r.log_iter(); assert_reflog(logs.all()); } } } _ => unreachable!("clones are always causing changes and dry-runs aren't possible"), } let remote_head = repo .find_reference(&format!("refs/remotes/{remote_name}/HEAD")) .expect("remote HEAD present"); assert_eq!( remote_head .target() .try_name() .expect("remote HEAD is symbolic") .as_bstr(), format!("refs/remotes/{remote_name}/main"), "it points to the local tracking branch of what the remote actually points to" ); let head = repo.head()?; { let mut logs = head.log_iter(); assert_reflog(logs.all()); } let referent = head.try_into_referent().expect("symbolic ref is present"); assert!( referent.id().object().is_ok(), "the object pointed to by HEAD was fetched as well" ); assert_eq!( referent.name().as_bstr(), remote::repo("base").head_name()?.expect("symbolic").as_bstr(), "local clone always adopts the name of the remote" ); let short_name = referent.name().shorten().to_str_lossy(); assert_eq!( repo.branch_remote_name(short_name.as_ref()) .expect("remote is set") .as_ref(), remote_name, "the remote branch information is fully configured" ); assert_eq!( repo.branch_remote_ref(short_name.as_ref()).expect("present")?.as_bstr(), "refs/heads/main" ); { let mut logs = referent.log_iter(); assert_reflog(logs.all()); } Ok(()) } fn assert_reflog(log: std::io::Result<Option<git_ref::file::log::iter::Forward<'_>>>) { let lines = log .unwrap() .expect("log present") .collect::<Result<Vec<_>, _>>() .unwrap(); assert_eq!(lines.len(), 1, "just created"); let line = &lines[0]; assert!( line.message.starts_with(b"clone: from "), "{:?} unexpected", line.message ); let path = git_path::from_bstr(line.message.rsplit(|b| *b == b' ').next().expect("path").as_bstr()); assert!(path.is_absolute(), "{:?} must be absolute", path); } #[test] fn fetch_and_checkout() -> crate::Result { let tmp = git_testtools::tempfile::TempDir::new()?; let mut prepare = git::prepare_clone(remote::repo("base").path(), tmp.path())?; let (mut checkout, _out) = prepare.fetch_then_checkout(git::progress::Discard, &std::sync::atomic::AtomicBool::default())?; let (repo, _) = checkout.main_worktree(git::progress::Discard, &std::sync::atomic::AtomicBool::default())?; let index = repo.index()?; assert_eq!(index.entries().len(), 1, "All entries are known as per HEAD tree"); let work_dir = repo.work_dir().expect("non-bare"); for entry in index.entries() { let entry_path = work_dir.join(git_path::from_bstr(entry.path(&index))); assert!(entry_path.is_file(), "{:?} not found on disk", entry_path) } Ok(()) } #[test] fn fetch_and_checkout_empty_remote_repo() -> crate::Result { let tmp = git_testtools::tempfile::TempDir::new()?; let mut prepare = git::prepare_clone( git_testtools::scripted_fixture_repo_read_only("make_empty_repo.sh")?, tmp.path(), )?; let (mut checkout, out) = prepare .fetch_then_checkout(git::progress::Discard, &std::sync::atomic::AtomicBool::default()) .unwrap(); let (repo, _) = checkout.main_worktree(git::progress::Discard, &std::sync::atomic::AtomicBool::default())?; assert!(!repo.index_path().is_file(), "newly initialized repos have no index"); let head = repo.head()?; assert!(head.is_unborn()); assert!( head.log_iter().all()?.is_none(), "no reflog for unborn heads (as it needs non-null destination hash)" ); if out .ref_map .handshake .capabilities .capability("ls-refs") .expect("has ls-refs") .supports("unborn") == Some(true) { assert_eq!( head.referent_name().expect("present").as_bstr(), "refs/heads/special", "we pick up the name as present on the server, not the one we default to" ); } else { assert_eq!( head.referent_name().expect("present").as_bstr(), "refs/heads/main", "we simply keep our own post-init HEAD which defaults to the branch name we configured locally" ); } Ok(()) } #[test] fn fetch_only_without_configuration() -> crate::Result { let tmp = git_testtools::tempfile::TempDir::new()?; let (repo, out) = git::prepare_clone_bare(remote::repo("base").path(), tmp.path())? .fetch_only(git::progress::Discard, &std::sync::atomic::AtomicBool::default())?; assert!(repo.find_remote("origin").is_ok(), "default remote name is 'origin'"); match out.status { git_repository::remote::fetch::Status::Change { write_pack_bundle, .. } => { assert!( write_pack_bundle.keep_path.is_none(), "keep files aren't kept if refs are written" ); } _ => unreachable!("a clone always carries a change"), } Ok(()) }} #[test]fn clone_and_early_persist_without_receive() -> crate::Result { let tmp = git_testtools::tempfile::TempDir::new()?; let repo = git::prepare_clone_bare(remote::repo("base").path(), tmp.path())?.persist(); assert!(repo.is_bare(), "repo is now ours and remains"); Ok(())} #[test]fn clone_and_destination_must_be_empty() -> crate::Result { let tmp = git_testtools::tempfile::TempDir::new()?; std::fs::write(tmp.path().join("file"), b"hello")?; match git::prepare_clone_bare(remote::repo("base").path(), tmp.path()) { Ok(_) => unreachable!("this should fail as the directory isn't empty"), Err(err) => assert!(err .to_string() .starts_with("Refusing to initialize the non-empty directory as ")), } Ok(())} #[test]fn clone_bare_into_empty_directory_and_early_drop() -> crate::Result { let tmp = git_testtools::tempfile::TempDir::new()?; // this breaks isolation, but shouldn't be affecting the test. If so, use isolation options for opening the repo. let prep = git::prepare_clone_bare(remote::repo("base").path(), tmp.path())?; let head = tmp.path().join("HEAD"); assert!(head.is_file(), "now a bare basic repo is present"); drop(prep); assert!(!head.is_file(), "we cleanup if the clone isn't followed through"); Ok(())} #[test]fn clone_into_empty_directory_and_early_drop() -> crate::Result { let tmp = git_testtools::tempfile::TempDir::new()?; let prep = git::prepare_clone(remote::repo("base").path(), tmp.path())?; let head = tmp.path().join(".git").join("HEAD"); assert!(head.is_file(), "now a basic repo is present"); drop(prep); assert!(!head.is_file(), "we cleanup if the clone isn't followed through"); Ok(())}
use git_repository as git;
use crate::remote;
#[cfg(feature = "blocking-network-client")]
mod blocking_io {
use git_object::bstr::ByteSlice;
use git_ref::TargetRef;
use git_repository as git;
use crate::remote;
#[test]
fn fetch_only_with_configuration() -> crate::Result {
let tmp = git_testtools::tempfile::TempDir::new()?;
let called_configure_remote = std::sync::Arc::new(std::sync::atomic::AtomicBool::default());
let remote_name = "special";
let mut prepare = git::clone::PrepareFetch::new(
remote::repo("base").path(),
tmp.path(),
git::create::Kind::Bare,
Default::default(),
git::open::Options::isolated().config_overrides([
"init.defaultBranch=unused-as-overridden-by-remote",
"core.logAllRefUpdates",
]),
)?
.with_remote_name(remote_name)?
.configure_remote({
let called_configure_remote = called_configure_remote.clone();
move |r| {
called_configure_remote.store(true, std::sync::atomic::Ordering::Relaxed);
Ok(
r.with_refspec("+refs/tags/*:refs/tags/*", git::remote::Direction::Fetch)
.expect("valid static spec"),
)
}
});
let (repo, out) = prepare.fetch_only(git::progress::Discard, &std::sync::atomic::AtomicBool::default())?;
drop(prepare);
assert!(
called_configure_remote.load(std::sync::atomic::Ordering::Relaxed),
"custom remote configuration is called"
);
assert_eq!(repo.remote_names().len(), 1, "only ever one remote");
let remote = repo.find_remote(remote_name)?;
assert_eq!(
remote.refspecs(git::remote::Direction::Fetch).len(),
2,
"our added spec was stored as well"
);
assert!(
git::path::from_bstr(
remote
.url(git::remote::Direction::Fetch)
.expect("present")
.path
.as_ref()
)
.is_absolute(),
"file urls can't be relative paths"
);
assert_eq!(out.ref_map.mappings.len(), 14);
let packed_refs = repo
.refs
.cached_packed_buffer()?
.expect("packed refs should be present");
assert_eq!(
packed_refs.iter()?.count(),
14,
"all non-symbolic refs should be stored"
);
assert_eq!(
repo.refs.loose_iter()?.count(),
2,
"HEAD and an actual symbolic ref we received"
);
match out.status {
git_repository::remote::fetch::Status::Change { update_refs, .. } => {
for edit in &update_refs.edits {
use git_odb::Find;
match edit.change.new_value().expect("always set/no deletion") {
TargetRef::Symbolic(referent) => {
assert!(
repo.find_reference(referent).is_ok(),
"if we set up a symref, the target should exist by now"
)
}
TargetRef::Peeled(id) => {
assert!(repo.objects.contains(id), "part of the fetched pack");
}
}
let r = repo
.find_reference(edit.name.as_ref())
.unwrap_or_else(|_| panic!("didn't find created reference: {:?}", edit));
if r.name().category().expect("known") != git_ref::Category::Tag {
assert!(r
.name()
.category_and_short_name()
.expect("computable")
.1
.starts_with_str(remote_name));
let mut logs = r.log_iter();
assert_reflog(logs.all());
}
}
}
_ => unreachable!("clones are always causing changes and dry-runs aren't possible"),
}
let remote_head = repo
.find_reference(&format!("refs/remotes/{remote_name}/HEAD"))
.expect("remote HEAD present");
assert_eq!(
remote_head
.target()
.try_name()
.expect("remote HEAD is symbolic")
.as_bstr(),
format!("refs/remotes/{remote_name}/main"),
"it points to the local tracking branch of what the remote actually points to"
);
let head = repo.head()?;
{
let mut logs = head.log_iter();
assert_reflog(logs.all());
}
let referent = head.try_into_referent().expect("symbolic ref is present");
assert!(
referent.id().object().is_ok(),
"the object pointed to by HEAD was fetched as well"
);
assert_eq!(
referent.name().as_bstr(),
remote::repo("base").head_name()?.expect("symbolic").as_bstr(),
"local clone always adopts the name of the remote"
);
let short_name = referent.name().shorten().to_str_lossy();
assert_eq!(
repo.branch_remote_name(short_name.as_ref())
.expect("remote is set")
.as_ref(),
remote_name,
"the remote branch information is fully configured"
);
assert_eq!(
repo.branch_remote_ref(short_name.as_ref()).expect("present")?.as_bstr(),
"refs/heads/main"
);
{
let mut logs = referent.log_iter();
assert_reflog(logs.all());
}
Ok(())
}
fn assert_reflog(log: std::io::Result<Option<git_ref::file::log::iter::Forward<'_>>>) {
let lines = log
.unwrap()
.expect("log present")
.collect::<Result<Vec<_>, _>>()
.unwrap();
assert_eq!(lines.len(), 1, "just created");
let line = &lines[0];
assert!(
line.message.starts_with(b"clone: from "),
"{:?} unexpected",
line.message
);
let path = git_path::from_bstr(line.message.rsplit(|b| *b == b' ').next().expect("path").as_bstr());
assert!(path.is_absolute(), "{:?} must be absolute", path);
}
#[test]
fn fetch_and_checkout() -> crate::Result {
let tmp = git_testtools::tempfile::TempDir::new()?;
let mut prepare = git::prepare_clone(remote::repo("base").path(), tmp.path())?;
let (mut checkout, _out) =
prepare.fetch_then_checkout(git::progress::Discard, &std::sync::atomic::AtomicBool::default())?;
let (repo, _) = checkout.main_worktree(git::progress::Discard, &std::sync::atomic::AtomicBool::default())?;
let index = repo.index()?;
assert_eq!(index.entries().len(), 1, "All entries are known as per HEAD tree");
let work_dir = repo.work_dir().expect("non-bare");
for entry in index.entries() {
let entry_path = work_dir.join(git_path::from_bstr(entry.path(&index)));
assert!(entry_path.is_file(), "{:?} not found on disk", entry_path)
}
Ok(())
}
#[test]
fn fetch_and_checkout_empty_remote_repo() -> crate::Result {
let tmp = git_testtools::tempfile::TempDir::new()?;
let mut prepare = git::prepare_clone(
git_testtools::scripted_fixture_repo_read_only("make_empty_repo.sh")?,
tmp.path(),
)?;
let (mut checkout, out) = prepare
.fetch_then_checkout(git::progress::Discard, &std::sync::atomic::AtomicBool::default())
.unwrap();
let (repo, _) = checkout.main_worktree(git::progress::Discard, &std::sync::atomic::AtomicBool::default())?;
assert!(!repo.index_path().is_file(), "newly initialized repos have no index");
let head = repo.head()?;
assert!(head.is_unborn());
assert!(
head.log_iter().all()?.is_none(),
"no reflog for unborn heads (as it needs non-null destination hash)"
);
if out
.ref_map
.handshake
.capabilities
.capability("ls-refs")
.expect("has ls-refs")
.supports("unborn")
== Some(true)
{
assert_eq!(
head.referent_name().expect("present").as_bstr(),
"refs/heads/special",
"we pick up the name as present on the server, not the one we default to"
);
} else {
assert_eq!(
head.referent_name().expect("present").as_bstr(),
"refs/heads/main",
"we simply keep our own post-init HEAD which defaults to the branch name we configured locally"
);
}
Ok(())
}
#[test]
fn fetch_only_without_configuration() -> crate::Result {
let tmp = git_testtools::tempfile::TempDir::new()?;
let (repo, out) = git::prepare_clone_bare(remote::repo("base").path(), tmp.path())?
.fetch_only(git::progress::Discard, &std::sync::atomic::AtomicBool::default())?;
assert!(repo.find_remote("origin").is_ok(), "default remote name is 'origin'");
match out.status {
git_repository::remote::fetch::Status::Change { write_pack_bundle, .. } => {
assert!(
write_pack_bundle.keep_path.is_none(),
"keep files aren't kept if refs are written"
);
}
_ => unreachable!("a clone always carries a change"),
}
Ok(())
}
}
#[test]
fn clone_and_early_persist_without_receive() -> crate::Result {
let tmp = git_testtools::tempfile::TempDir::new()?;
let repo = git::prepare_clone_bare(remote::repo("base").path(), tmp.path())?.persist();
assert!(repo.is_bare(), "repo is now ours and remains");
Ok(())
}
#[test]
fn clone_and_destination_must_be_empty() -> crate::Result {
let tmp = git_testtools::tempfile::TempDir::new()?;
std::fs::write(tmp.path().join("file"), b"hello")?;
match git::prepare_clone_bare(remote::repo("base").path(), tmp.path()) {
Ok(_) => unreachable!("this should fail as the directory isn't empty"),
Err(err) => assert!(err
.to_string()
.starts_with("Refusing to initialize the non-empty directory as ")),
}
Ok(())
}
#[test]
fn clone_bare_into_empty_directory_and_early_drop() -> crate::Result {
let tmp = git_testtools::tempfile::TempDir::new()?;
// this breaks isolation, but shouldn't be affecting the test. If so, use isolation options for opening the repo.
let prep = git::prepare_clone_bare(remote::repo("base").path(), tmp.path())?;
let head = tmp.path().join("HEAD");
assert!(head.is_file(), "now a bare basic repo is present");
drop(prep);
assert!(!head.is_file(), "we cleanup if the clone isn't followed through");
Ok(())
}
#[test]
fn clone_into_empty_directory_and_early_drop() -> crate::Result {
let tmp = git_testtools::tempfile::TempDir::new()?;
let prep = git::prepare_clone(remote::repo("base").path(), tmp.path())?;
let head = tmp.path().join(".git").join("HEAD");
assert!(head.is_file(), "now a basic repo is present");
drop(prep);
assert!(!head.is_file(), "we cleanup if the clone isn't followed through");
Ok(())
}