mirror of
https://github.com/Byron/gitoxide
synced 2025-10-06 01:52:40 +02:00
794 lines
30 KiB
Rust
794 lines
30 KiB
Rust
use crate::{remote, util::restricted};
|
|
|
|
#[cfg(all(feature = "worktree-mutation", feature = "blocking-network-client"))]
|
|
mod blocking_io {
|
|
use std::path::Path;
|
|
use std::{borrow::Cow, sync::atomic::AtomicBool};
|
|
|
|
use gix::{
|
|
bstr::BString,
|
|
config::tree::{Clone, Core, Init, Key},
|
|
remote::{
|
|
fetch::{refmap::SpecIndex, Shallow},
|
|
Direction,
|
|
},
|
|
};
|
|
use gix_object::bstr::ByteSlice;
|
|
use gix_ref::TargetRef;
|
|
|
|
use crate::{
|
|
remote,
|
|
util::{hex_to_id, restricted},
|
|
};
|
|
|
|
#[test]
|
|
fn fetch_shallow_no_checkout_then_unshallow() -> crate::Result {
|
|
let tmp = gix_testtools::tempfile::TempDir::new()?;
|
|
let called_configure_remote = std::sync::Arc::new(std::sync::atomic::AtomicBool::default());
|
|
let remote_name = "special";
|
|
let desired_fetch_tags = gix::remote::fetch::Tags::Included;
|
|
let mut prepare = gix::prepare_clone_bare(remote::repo("base").path(), tmp.path())?
|
|
.with_remote_name(remote_name)?
|
|
.configure_remote({
|
|
move |r| {
|
|
called_configure_remote.store(true, std::sync::atomic::Ordering::Relaxed);
|
|
let mut r = r.with_fetch_tags(desired_fetch_tags);
|
|
r.replace_refspecs(
|
|
[
|
|
BString::from(format!("refs/heads/main:refs/remotes/{remote_name}/main")),
|
|
"+refs/tags/b-tag:refs/tags/b-tag".to_owned().into(),
|
|
],
|
|
Direction::Fetch,
|
|
)?;
|
|
Ok(r)
|
|
}
|
|
})
|
|
.with_shallow(Shallow::DepthAtRemote(2.try_into().expect("non-zero")));
|
|
let (repo, _out) = prepare.fetch_only(gix::progress::Discard, &std::sync::atomic::AtomicBool::default())?;
|
|
drop(prepare);
|
|
|
|
assert_eq!(
|
|
repo.shallow_commits()?.expect("shallow").as_slice(),
|
|
[
|
|
hex_to_id("27e71576a6335294aa6073ab767f8b36bdba81d0"),
|
|
hex_to_id("2d9d136fb0765f2e24c44a0f91984318d580d03b"),
|
|
hex_to_id("82024b2ef7858273337471cbd1ca1cedbdfd5616"),
|
|
hex_to_id("b5152869aedeb21e55696bb81de71ea1bb880c85")
|
|
],
|
|
"shallow information is written"
|
|
);
|
|
|
|
let shallow_commit_count = repo.head_id()?.ancestors().all()?.count();
|
|
let remote = repo.head()?.into_remote(Direction::Fetch).expect("present")?;
|
|
|
|
remote
|
|
.connect(Direction::Fetch)?
|
|
.prepare_fetch(gix::progress::Discard, Default::default())?
|
|
.with_shallow(Shallow::undo())
|
|
.receive(gix::progress::Discard, &AtomicBool::default())?;
|
|
|
|
assert!(repo.shallow_commits()?.is_none(), "the repo isn't shallow anymore");
|
|
assert!(
|
|
!repo.is_shallow(),
|
|
"both methods agree - if there are no shallow commits, it shouldn't think the repo is shallow"
|
|
);
|
|
assert!(
|
|
!repo.shallow_file().exists(),
|
|
"when the repo is not shallow anymore, there is no need for a shallow file"
|
|
);
|
|
assert!(
|
|
repo.head_id()?.ancestors().all()?.count() > shallow_commit_count,
|
|
"there are more commits now as the history is complete"
|
|
);
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[test]
|
|
fn from_shallow_prohibited_with_option() -> crate::Result {
|
|
let tmp = gix_testtools::tempfile::TempDir::new()?;
|
|
let err = gix::clone::PrepareFetch::new(
|
|
remote::repo("base.shallow").path(),
|
|
tmp.path(),
|
|
gix::create::Kind::Bare,
|
|
Default::default(),
|
|
gix::open::Options::isolated().config_overrides([Clone::REJECT_SHALLOW.validated_assignment_fmt(&true)?]),
|
|
)?
|
|
.fetch_only(gix::progress::Discard, &std::sync::atomic::AtomicBool::default())
|
|
.unwrap_err();
|
|
assert!(
|
|
matches!(
|
|
err,
|
|
gix::clone::fetch::Error::Fetch(gix::remote::fetch::Error::Fetch(
|
|
gix_protocol::fetch::Error::RejectShallowRemote
|
|
))
|
|
),
|
|
"we can avoid fetching from remotes with this setting"
|
|
);
|
|
Ok(())
|
|
}
|
|
|
|
#[test]
|
|
fn from_shallow_allowed_by_default() -> crate::Result {
|
|
let tmp = gix_testtools::tempfile::TempDir::new()?;
|
|
let (repo, _change) = gix::prepare_clone_bare(remote::repo("base.shallow").path(), tmp.path())?
|
|
.with_in_memory_config_overrides(Some("my.marker=1"))
|
|
.fetch_only(gix::progress::Discard, &std::sync::atomic::AtomicBool::default())?;
|
|
assert_eq!(
|
|
repo.shallow_commits()?.expect("present").as_slice(),
|
|
vec![
|
|
hex_to_id("2d9d136fb0765f2e24c44a0f91984318d580d03b"),
|
|
hex_to_id("dfd0954dabef3b64f458321ef15571cc1a46d552"),
|
|
hex_to_id("dfd0954dabef3b64f458321ef15571cc1a46d552"),
|
|
]
|
|
);
|
|
assert_eq!(
|
|
repo.config_snapshot().boolean("my.marker"),
|
|
Some(true),
|
|
"configuration overrides are set in time"
|
|
);
|
|
assert_eq!(
|
|
gix::open_opts(repo.git_dir(), gix::open::Options::isolated())?
|
|
.config_snapshot()
|
|
.boolean("my.marker"),
|
|
None,
|
|
"these options are not persisted"
|
|
);
|
|
Ok(())
|
|
}
|
|
|
|
#[test]
|
|
fn from_non_shallow_then_deepen_then_deepen_since_to_unshallow() -> crate::Result {
|
|
let tmp = gix_testtools::tempfile::TempDir::new()?;
|
|
let (repo, _change) = gix::prepare_clone_bare(remote::repo("base").path(), tmp.path())?
|
|
.with_shallow(Shallow::DepthAtRemote(2.try_into()?))
|
|
.configure_remote(|mut r| {
|
|
r.replace_refspecs(Some("refs/heads/main:refs/remotes/origin/main"), Direction::Fetch)?;
|
|
Ok(r)
|
|
})
|
|
.fetch_only(gix::progress::Discard, &std::sync::atomic::AtomicBool::default())?;
|
|
|
|
assert!(repo.is_shallow());
|
|
assert_eq!(
|
|
repo.shallow_commits()?.expect("present").as_slice(),
|
|
vec![
|
|
hex_to_id("2d9d136fb0765f2e24c44a0f91984318d580d03b"),
|
|
hex_to_id("dfd0954dabef3b64f458321ef15571cc1a46d552"),
|
|
]
|
|
);
|
|
|
|
let shallow_commit_count = repo.head_id()?.ancestors().all()?.count();
|
|
|
|
let remote = repo.head()?.into_remote(Direction::Fetch).expect("present")?;
|
|
remote
|
|
.connect(Direction::Fetch)?
|
|
.prepare_fetch(gix::progress::Discard, Default::default())?
|
|
.with_shallow(Shallow::Deepen(1))
|
|
.receive(gix::progress::Discard, &AtomicBool::default())?;
|
|
|
|
assert_eq!(
|
|
repo.shallow_commits()?.expect("present").as_slice(),
|
|
vec![
|
|
hex_to_id("27e71576a6335294aa6073ab767f8b36bdba81d0"),
|
|
hex_to_id("82024b2ef7858273337471cbd1ca1cedbdfd5616"),
|
|
hex_to_id("b5152869aedeb21e55696bb81de71ea1bb880c85"),
|
|
],
|
|
"the shallow boundary was changed"
|
|
);
|
|
assert!(
|
|
repo.head_id()?.ancestors().all()?.count() > shallow_commit_count,
|
|
"there are more commits now as the history was deepened"
|
|
);
|
|
|
|
let shallow_commit_count = repo.head_id()?.ancestors().all()?.count();
|
|
remote
|
|
.connect(Direction::Fetch)?
|
|
.prepare_fetch(gix::progress::Discard, Default::default())?
|
|
.with_shallow(Shallow::Since {
|
|
cutoff: gix::date::Time::new(1112354053, 0),
|
|
})
|
|
.receive(gix::progress::Discard, &AtomicBool::default())?;
|
|
|
|
assert!(
|
|
!repo.is_shallow(),
|
|
"the cutoff date is before the first commit, effectively unshallowing"
|
|
);
|
|
assert!(
|
|
repo.head_id()?.ancestors().all()?.count() > shallow_commit_count,
|
|
"there is even more commits than previously"
|
|
);
|
|
Ok(())
|
|
}
|
|
|
|
#[test]
|
|
fn from_non_shallow_by_deepen_exclude_then_deepen_to_unshallow() -> crate::Result {
|
|
let tmp = gix_testtools::tempfile::TempDir::new()?;
|
|
let excluded_leaf_refs = ["g", "h", "j"];
|
|
let (repo, _change) = gix::prepare_clone_bare(remote::repo("base").path(), tmp.path())?
|
|
.with_shallow(Shallow::Exclude {
|
|
remote_refs: excluded_leaf_refs
|
|
.into_iter()
|
|
.map(|n| n.try_into().expect("valid"))
|
|
.collect(),
|
|
since_cutoff: None,
|
|
})
|
|
.fetch_only(gix::progress::Discard, &std::sync::atomic::AtomicBool::default())?;
|
|
|
|
assert!(repo.is_shallow());
|
|
assert_eq!(
|
|
repo.shallow_commits()?.expect("present").as_slice(),
|
|
vec![
|
|
hex_to_id("27e71576a6335294aa6073ab767f8b36bdba81d0"),
|
|
hex_to_id("82024b2ef7858273337471cbd1ca1cedbdfd5616"),
|
|
]
|
|
);
|
|
|
|
let remote = repo.head()?.into_remote(Direction::Fetch).expect("present")?;
|
|
remote
|
|
.connect(Direction::Fetch)?
|
|
.prepare_fetch(gix::progress::Discard, Default::default())?
|
|
.with_shallow(Shallow::Deepen(2))
|
|
.receive(gix::progress::Discard, &AtomicBool::default())?;
|
|
|
|
assert!(!repo.is_shallow(), "one is just enough to unshallow it");
|
|
Ok(())
|
|
}
|
|
|
|
#[test]
|
|
fn fetch_only_with_configuration() -> crate::Result {
|
|
let tmp = gix_testtools::tempfile::TempDir::new()?;
|
|
let called_configure_remote = std::sync::Arc::new(std::sync::atomic::AtomicBool::default());
|
|
let remote_name = "special";
|
|
let desired_fetch_tags = gix::remote::fetch::Tags::Included;
|
|
let mut prepare = gix::clone::PrepareFetch::new(
|
|
remote::repo("base").path(),
|
|
tmp.path(),
|
|
gix::create::Kind::Bare,
|
|
Default::default(),
|
|
gix::open::Options::isolated().config_overrides([
|
|
Init::DEFAULT_BRANCH.validated_assignment_fmt(&"unused-as-overridden-by-remote")?,
|
|
Core::LOG_ALL_REF_UPDATES.logical_name().into(),
|
|
// missing user and email is acceptable in this special case, i.e. `git` also doesn't mind filling it in.
|
|
]),
|
|
)?
|
|
.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);
|
|
let r = r
|
|
.with_refspecs(Some("+refs/tags/b-tag:refs/tags/b-tag"), gix::remote::Direction::Fetch)?
|
|
.with_fetch_tags(desired_fetch_tags);
|
|
Ok(r)
|
|
}
|
|
});
|
|
let (repo, out) = prepare.fetch_only(gix::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)?;
|
|
let num_refspecs = remote.refspecs(gix::remote::Direction::Fetch).len();
|
|
assert_eq!(
|
|
num_refspecs, 2,
|
|
"our added spec was stored as well, but no implied specs due to the `Tags::All` setting"
|
|
);
|
|
assert_eq!(
|
|
remote.fetch_tags(),
|
|
desired_fetch_tags,
|
|
"fetch-tags are persisted via the 'tagOpt` key"
|
|
);
|
|
assert!(
|
|
gix::path::from_bstr(Cow::Borrowed(
|
|
remote
|
|
.url(gix::remote::Direction::Fetch)
|
|
.expect("present")
|
|
.path
|
|
.as_ref()
|
|
))
|
|
.is_absolute(),
|
|
"file urls can't be relative paths"
|
|
);
|
|
|
|
let (explicit_max_idx, implicit_max_index) =
|
|
out.ref_map
|
|
.mappings
|
|
.iter()
|
|
.map(|m| m.spec_index)
|
|
.fold((0, 0), |(a, b), i| match i {
|
|
SpecIndex::ExplicitInRemote(idx) => (idx.max(a), b),
|
|
SpecIndex::Implicit(idx) => (a, idx.max(b)),
|
|
});
|
|
assert_eq!(
|
|
explicit_max_idx,
|
|
num_refspecs - 1,
|
|
"mappings don't refer to non-existing explicit refspecs"
|
|
);
|
|
assert_eq!(
|
|
implicit_max_index,
|
|
&out.ref_map.extra_refspecs.len() - 1,
|
|
"mappings don't refer to non-existing implicit refspecs"
|
|
);
|
|
let packed_refs = repo
|
|
.refs
|
|
.cached_packed_buffer()?
|
|
.expect("packed refs should be present");
|
|
assert_eq!(
|
|
repo.refs.loose_iter()?.count(),
|
|
2,
|
|
"HEAD and an actual symbolic ref we received"
|
|
);
|
|
assert_eq!(
|
|
packed_refs.iter()?.count(),
|
|
14,
|
|
"all non-symbolic refs should be stored, if reachable from our refs"
|
|
);
|
|
let sig = repo
|
|
.head()?
|
|
.log_iter()
|
|
.all()?
|
|
.expect("present")
|
|
.next()
|
|
.expect("one line")?
|
|
.signature
|
|
.to_owned()?;
|
|
assert_eq!(sig.name, "no name configured during clone");
|
|
assert_eq!(sig.email, "noEmailAvailable@example.com");
|
|
|
|
match out.status {
|
|
gix::remote::fetch::Status::Change { update_refs, .. } => {
|
|
for edit in &update_refs.edits {
|
|
use gix_object::Exists;
|
|
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::Object(id) => {
|
|
assert!(repo.objects.exists(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") != gix_ref::Category::Tag {
|
|
assert!(r
|
|
.name()
|
|
.category_and_short_name()
|
|
.expect("computable")
|
|
.1
|
|
.starts_with_str(remote_name));
|
|
match r.target() {
|
|
TargetRef::Object(_) => {
|
|
let mut logs = r.log_iter();
|
|
assert_reflog(logs.all());
|
|
}
|
|
TargetRef::Symbolic(_) => {
|
|
// TODO: it *should* be possible to set the reflog here based on the referent if deref = true
|
|
// when setting up the edits. But it doesn't seem to work. Also, some tests are
|
|
// missing for `leaf_referent_previous_oid`.
|
|
assert!(
|
|
!r.log_exists(),
|
|
"symbolic refs don't have object ids, so they can't get \
|
|
into the reflog as these need previous and new oid"
|
|
);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
let mut out_of_graph_tags = Vec::new();
|
|
for mapping in update_refs
|
|
.updates
|
|
.iter()
|
|
.enumerate()
|
|
.filter(|(_, update)| {
|
|
matches!(
|
|
update.mode,
|
|
gix::remote::fetch::refs::update::Mode::ImplicitTagNotSentByRemote
|
|
)
|
|
})
|
|
.map(|(idx, _)| &out.ref_map.mappings[idx])
|
|
{
|
|
out_of_graph_tags.push(
|
|
mapping
|
|
.remote
|
|
.as_name()
|
|
.expect("tag always has a path")
|
|
.to_str()
|
|
.expect("valid UTF8"),
|
|
);
|
|
}
|
|
assert_eq!(
|
|
out_of_graph_tags,
|
|
&[
|
|
"refs/tags/annotated-detached-tag",
|
|
"refs/tags/annotated-future-tag",
|
|
"refs/tags/detached-tag",
|
|
"refs/tags/future-tag"
|
|
]
|
|
);
|
|
}
|
|
_ => 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 ref_name = referent.name();
|
|
assert_eq!(
|
|
referent
|
|
.remote_name(gix::remote::Direction::Fetch)
|
|
.expect("remote is set")
|
|
.as_ref(),
|
|
remote_name,
|
|
"the remote branch information is fully configured"
|
|
);
|
|
assert_eq!(
|
|
repo.branch_remote_ref_name(ref_name, gix::remote::Direction::Fetch)
|
|
.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<gix_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 = gix_path::from_bstr(line.message.rsplit(|b| *b == b' ').next().expect("path").as_bstr());
|
|
assert!(path.is_absolute(), "{path:?} must be absolute");
|
|
}
|
|
|
|
#[test]
|
|
fn fetch_and_checkout() -> crate::Result {
|
|
let tmp = gix_testtools::tempfile::TempDir::new()?;
|
|
let mut prepare = gix::clone::PrepareFetch::new(
|
|
remote::repo("base").path(),
|
|
tmp.path(),
|
|
gix::create::Kind::WithWorktree,
|
|
Default::default(),
|
|
restricted(),
|
|
)?;
|
|
let (mut checkout, _out) =
|
|
prepare.fetch_then_checkout(gix::progress::Discard, &std::sync::atomic::AtomicBool::default())?;
|
|
let (repo, _) = checkout.main_worktree(gix::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");
|
|
|
|
assure_index_entries_on_disk(&index, repo.workdir().expect("non-bare"));
|
|
Ok(())
|
|
}
|
|
#[test]
|
|
fn fetch_and_checkout_specific_ref() -> crate::Result {
|
|
let tmp = gix_testtools::tempfile::TempDir::new()?;
|
|
let remote_repo = remote::repo("base");
|
|
let ref_to_checkout = "a";
|
|
let mut prepare = gix::clone::PrepareFetch::new(
|
|
remote_repo.path(),
|
|
tmp.path(),
|
|
gix::create::Kind::WithWorktree,
|
|
Default::default(),
|
|
restricted(),
|
|
)?
|
|
.with_ref_name(Some(ref_to_checkout))?;
|
|
let (mut checkout, _out) =
|
|
prepare.fetch_then_checkout(gix::progress::Discard, &std::sync::atomic::AtomicBool::default())?;
|
|
|
|
let (repo, _) = checkout.main_worktree(gix::progress::Discard, &std::sync::atomic::AtomicBool::default())?;
|
|
|
|
assert_eq!(
|
|
repo.references()?.all()?.count() - 2,
|
|
remote_repo.references()?.all()?.count(),
|
|
"all references have been cloned, + remote HEAD + remote main (not listed in remote_repo)"
|
|
);
|
|
let checked_out_ref = repo.head_ref()?.expect("head points to ref");
|
|
let remote_ref_name = format!("refs/heads/{ref_to_checkout}");
|
|
assert_eq!(
|
|
checked_out_ref.name().as_bstr(),
|
|
remote_ref_name,
|
|
"it's possible to checkout anything with that name, but here we have an ordinary branch"
|
|
);
|
|
|
|
assert_eq!(
|
|
checked_out_ref
|
|
.remote_ref_name(gix::remote::Direction::Fetch)
|
|
.transpose()?
|
|
.unwrap()
|
|
.as_bstr(),
|
|
remote_ref_name,
|
|
"the merge configuration is using the given name"
|
|
);
|
|
|
|
let index = repo.index()?;
|
|
assert_eq!(index.entries().len(), 1, "All entries are known as per HEAD tree");
|
|
|
|
assure_index_entries_on_disk(&index, repo.workdir().expect("non-bare"));
|
|
Ok(())
|
|
}
|
|
|
|
#[test]
|
|
fn fetch_and_checkout_specific_non_existing() -> crate::Result {
|
|
let tmp = gix_testtools::tempfile::TempDir::new()?;
|
|
let remote_repo = remote::repo("base");
|
|
let ref_to_checkout = "does-not-exist";
|
|
let mut prepare = gix::clone::PrepareFetch::new(
|
|
remote_repo.path(),
|
|
tmp.path(),
|
|
gix::create::Kind::WithWorktree,
|
|
Default::default(),
|
|
restricted(),
|
|
)?
|
|
.with_ref_name(Some(ref_to_checkout))?;
|
|
|
|
let err = prepare
|
|
.fetch_then_checkout(gix::progress::Discard, &std::sync::atomic::AtomicBool::default())
|
|
.unwrap_err();
|
|
assert_eq!(
|
|
err.to_string(),
|
|
"The remote didn't have any ref that matched 'does-not-exist'",
|
|
"we don't test this, but it's important that it determines this before receiving a pack"
|
|
);
|
|
Ok(())
|
|
}
|
|
|
|
#[test]
|
|
fn fetch_succeeds_despite_remote_head_ref() -> crate::Result {
|
|
let tmp = gix_testtools::tempfile::TempDir::new()?;
|
|
let remote_repo = remote::repo("head-ref");
|
|
let mut prepare = gix::clone::PrepareFetch::new(
|
|
remote_repo.path(),
|
|
tmp.path(),
|
|
gix::create::Kind::WithWorktree,
|
|
Default::default(),
|
|
restricted(),
|
|
)?;
|
|
|
|
let (mut checkout, _out) = prepare.fetch_then_checkout(gix::progress::Discard, &AtomicBool::default())?;
|
|
let (repo, _) = checkout.main_worktree(gix::progress::Discard, &AtomicBool::default())?;
|
|
assert!(repo.head().is_ok(), "we could handle the HEAD normaller");
|
|
Ok(())
|
|
}
|
|
|
|
#[test]
|
|
fn fetch_and_checkout_specific_annotated_tag() -> crate::Result {
|
|
let tmp = gix_testtools::tempfile::TempDir::new()?;
|
|
let remote_repo = remote::repo("base");
|
|
let ref_to_checkout = "annotated-detached-tag";
|
|
let mut prepare = gix::clone::PrepareFetch::new(
|
|
remote_repo.path(),
|
|
tmp.path(),
|
|
gix::create::Kind::WithWorktree,
|
|
Default::default(),
|
|
restricted(),
|
|
)?
|
|
.with_ref_name(Some(ref_to_checkout))?;
|
|
let (mut checkout, _out) =
|
|
prepare.fetch_then_checkout(gix::progress::Discard, &std::sync::atomic::AtomicBool::default())?;
|
|
|
|
let (repo, _) = checkout.main_worktree(gix::progress::Discard, &std::sync::atomic::AtomicBool::default())?;
|
|
|
|
assert_eq!(
|
|
repo.references()?.all()?.count() - 1,
|
|
remote_repo.references()?.all()?.count(),
|
|
"all references have been cloned, + remote HEAD (not listed in remote_repo)"
|
|
);
|
|
let checked_out_ref = repo.head_ref()?.expect("head points to ref");
|
|
let remote_ref_name = format!("refs/tags/{ref_to_checkout}");
|
|
assert_eq!(
|
|
checked_out_ref.name().as_bstr(),
|
|
remote_ref_name,
|
|
"it also works with tags"
|
|
);
|
|
|
|
assert_eq!(
|
|
checked_out_ref
|
|
.remote_ref_name(gix::remote::Direction::Fetch)
|
|
.transpose()?,
|
|
None,
|
|
"there is no merge configuration for tags"
|
|
);
|
|
Ok(())
|
|
}
|
|
|
|
fn assure_index_entries_on_disk(index: &gix::worktree::Index, work_dir: &Path) {
|
|
for entry in index.entries() {
|
|
let entry_path = work_dir.join(gix_path::from_bstr(entry.path(index)));
|
|
assert!(entry_path.is_file(), "{entry_path:?} not found on disk");
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn fetch_and_checkout_empty_remote_repo() -> crate::Result {
|
|
for version in [
|
|
gix::protocol::transport::Protocol::V0,
|
|
gix::protocol::transport::Protocol::V2,
|
|
] {
|
|
let tmp = gix_testtools::tempfile::TempDir::new()?;
|
|
let mut prepare = gix::clone::PrepareFetch::new(
|
|
gix_testtools::scripted_fixture_read_only("make_empty_repo.sh")?,
|
|
tmp.path(),
|
|
gix::create::Kind::WithWorktree,
|
|
Default::default(),
|
|
restricted().config_overrides(Some(format!("protocol.version={}", version as u8))),
|
|
)?;
|
|
let (mut checkout, out) =
|
|
prepare.fetch_then_checkout(gix::progress::Discard, &std::sync::atomic::AtomicBool::default())?;
|
|
let (repo, _) =
|
|
checkout.main_worktree(gix::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_eq!(repo.head_tree_id_or_empty()?, repo.empty_tree().id());
|
|
|
|
assert!(
|
|
head.log_iter().all()?.is_none(),
|
|
"no reflog for unborn heads (as it needs non-null destination hash)"
|
|
);
|
|
|
|
let supports_unborn = out
|
|
.handshake
|
|
.capabilities
|
|
.capability("ls-refs")
|
|
.is_some_and(|cap| cap.supports("unborn").unwrap_or(false));
|
|
if supports_unborn {
|
|
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 = gix_testtools::tempfile::TempDir::new()?;
|
|
let (repo, out) = gix::clone::PrepareFetch::new(
|
|
remote::repo("base").path(),
|
|
tmp.path(),
|
|
gix::create::Kind::Bare,
|
|
Default::default(),
|
|
restricted(),
|
|
)?
|
|
.fetch_only(gix::progress::Discard, &std::sync::atomic::AtomicBool::default())?;
|
|
assert!(repo.find_remote("origin").is_ok(), "default remote name is 'origin'");
|
|
match out.status {
|
|
gix::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 = gix_testtools::tempfile::TempDir::new()?;
|
|
let repo = gix::clone::PrepareFetch::new(
|
|
remote::repo("base").path(),
|
|
tmp.path(),
|
|
gix::create::Kind::Bare,
|
|
Default::default(),
|
|
restricted(),
|
|
)?
|
|
.persist();
|
|
assert!(repo.is_bare(), "repo is now ours and remains");
|
|
Ok(())
|
|
}
|
|
|
|
#[test]
|
|
fn clone_and_destination_must_be_empty() -> crate::Result {
|
|
let tmp = gix_testtools::tempfile::TempDir::new()?;
|
|
std::fs::write(tmp.path().join("file"), b"hello")?;
|
|
match gix::clone::PrepareFetch::new(
|
|
remote::repo("base").path(),
|
|
tmp.path(),
|
|
gix::create::Kind::Bare,
|
|
Default::default(),
|
|
restricted(),
|
|
) {
|
|
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 = gix_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 = gix::clone::PrepareFetch::new(
|
|
remote::repo("base").path(),
|
|
tmp.path(),
|
|
gix::create::Kind::Bare,
|
|
Default::default(),
|
|
restricted(),
|
|
)?;
|
|
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 = gix_testtools::tempfile::TempDir::new()?;
|
|
let prep = gix::clone::PrepareFetch::new(
|
|
remote::repo("base").path(),
|
|
tmp.path(),
|
|
gix::create::Kind::WithWorktree,
|
|
Default::default(),
|
|
restricted(),
|
|
)?;
|
|
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(())
|
|
}
|