Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions gitoxide-core/src/repository/clone.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ pub struct Options {
pub handshake_info: bool,
pub no_tags: bool,
pub shallow: gix::remote::fetch::Shallow,
pub ref_name: Option<gix::refs::PartialName>,
}

pub const PROGRESS_RANGE: std::ops::RangeInclusive<u8> = 1..=3;
Expand All @@ -31,6 +32,7 @@ pub(crate) mod function {
handshake_info,
bare,
no_tags,
ref_name,
shallow,
}: Options,
) -> anyhow::Result<()>
Expand Down Expand Up @@ -75,6 +77,7 @@ pub(crate) mod function {
}
let (mut checkout, fetch_outcome) = prepare
.with_shallow(shallow)
.with_ref_name(ref_name.as_ref())?
.fetch_then_checkout(&mut progress, &gix::interrupt::IS_INTERRUPTED)?;

let (repo, outcome) = if bare {
Expand Down
7 changes: 2 additions & 5 deletions gix-attributes/tests/search/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -307,11 +307,8 @@ mod baseline {

let mut buf = Vec::new();
let mut collection = MetadataCollection::default();
let group = gix_attributes::Search::new_globals(
&mut [base.join("user.attributes")].into_iter(),
&mut buf,
&mut collection,
)?;
let group =
gix_attributes::Search::new_globals([base.join("user.attributes")].into_iter(), &mut buf, &mut collection)?;

Ok((group, collection, base, input))
}
Expand Down
2 changes: 1 addition & 1 deletion gix-odb/tests/odb/find/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ fn can_find(db: impl gix_object::Find, hex_id: &str) {

#[test]
fn loose_object() {
can_find(&db(), "37d4e6c5c48ba0d245164c4e10d5f41140cab980");
can_find(db(), "37d4e6c5c48ba0d245164c4e10d5f41140cab980");
}

#[test]
Expand Down
2 changes: 1 addition & 1 deletion gix-odb/tests/odb/header/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ fn find_header(db: impl gix_odb::Header, hex_id: &str) -> gix_odb::find::Header

#[test]
fn loose_object() {
find_header(&db(), "37d4e6c5c48ba0d245164c4e10d5f41140cab980");
find_header(db(), "37d4e6c5c48ba0d245164c4e10d5f41140cab980");
}

#[test]
Expand Down
6 changes: 3 additions & 3 deletions gix-pack/tests/pack/index.rs
Original file line number Diff line number Diff line change
Expand Up @@ -363,8 +363,8 @@ fn pack_lookup() -> Result<(), Box<dyn std::error::Error>> {
},
),
] {
let idx = index::File::at(&fixture_path(index_path), gix_hash::Kind::Sha1)?;
let pack = pack::data::File::at(&fixture_path(pack_path), gix_hash::Kind::Sha1)?;
let idx = index::File::at(fixture_path(index_path), gix_hash::Kind::Sha1)?;
let pack = pack::data::File::at(fixture_path(pack_path), gix_hash::Kind::Sha1)?;

assert_eq!(pack.version(), pack::data::Version::V2);
assert_eq!(pack.num_objects(), idx.num_objects());
Expand Down Expand Up @@ -471,7 +471,7 @@ fn iter() -> Result<(), Box<dyn std::error::Error>> {
"0f3ea84cd1bba10c2a03d736a460635082833e59",
),
] {
let idx = index::File::at(&fixture_path(path), gix_hash::Kind::Sha1)?;
let idx = index::File::at(fixture_path(path), gix_hash::Kind::Sha1)?;
assert_eq!(idx.version(), *kind);
assert_eq!(idx.num_objects(), *num_objects);
assert_eq!(
Expand Down
15 changes: 14 additions & 1 deletion gix/src/clone/access.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ impl PrepareFetch {
///
/// It can also be used to configure additional options, like those for fetching tags. Note that
/// [`with_fetch_tags()`](crate::Remote::with_fetch_tags()) should be called here to configure the clone as desired.
/// Otherwise a clone is configured to be complete and fetches all tags, not only those reachable from all branches.
/// Otherwise, a clone is configured to be complete and fetches all tags, not only those reachable from all branches.
pub fn configure_remote(
mut self,
f: impl FnMut(crate::Remote<'_>) -> Result<crate::Remote<'_>, Box<dyn std::error::Error + Send + Sync>> + 'static,
Expand Down Expand Up @@ -42,6 +42,19 @@ impl PrepareFetch {
self.config_overrides = values.into_iter().map(Into::into).collect();
self
}

/// Set the `name` of the reference to check out, instead of the remote `HEAD`.
/// If `None`, the `HEAD` will be used, which is the default.
///
/// Note that `name` should be a partial name like `main` or `feat/one`, but can be a full ref name.
/// If a branch on the remote matches, it will automatically be retrieved even without a refspec.
pub fn with_ref_name<'a, Name, E>(mut self, name: Option<Name>) -> Result<Self, E>
where
Name: TryInto<&'a gix_ref::PartialNameRef, Error = E>,
{
self.ref_name = name.map(TryInto::try_into).transpose()?.map(ToOwned::to_owned);
Ok(self)
}
}

/// Consumption
Expand Down
25 changes: 20 additions & 5 deletions gix/src/clone/checkout.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ pub mod main_worktree {
CheckoutOptions(#[from] crate::config::checkout_options::Error),
#[error(transparent)]
IndexCheckout(#[from] gix_worktree_state::checkout::Error),
#[error(transparent)]
Peel(#[from] crate::reference::peel::Error),
#[error("Failed to reopen object database as Arc (only if thread-safety wasn't compiled in)")]
OpenArcOdb(#[from] std::io::Error),
#[error("The HEAD reference could not be located")]
Expand Down Expand Up @@ -62,7 +64,13 @@ pub mod main_worktree {
/// on thread per logical core.
///
/// Note that this is a no-op if the remote was empty, leaving this repository empty as well. This can be validated by checking
/// if the `head()` of the returned repository is not unborn.
/// if the `head()` of the returned repository is *not* unborn.
///
/// # Panics
///
/// If called after it was successful. The reason here is that it auto-deletes the contained repository,
/// and keeps track of this by means of keeping just one repository instance, which is passed to the user
/// after success.
pub fn main_worktree<P>(
&mut self,
mut progress: P,
Expand All @@ -84,19 +92,26 @@ pub mod main_worktree {
let repo = self
.repo
.as_ref()
.expect("still present as we never succeeded the worktree checkout yet");
.expect("BUG: this method may only be called until it is successful");
let workdir = repo.work_dir().ok_or_else(|| Error::BareRepository {
git_dir: repo.git_dir().to_owned(),
})?;
let root_tree = match repo.head()?.try_peel_to_id_in_place()? {

let root_tree_id = match &self.ref_name {
Some(reference_val) => Some(repo.find_reference(reference_val)?.peel_to_id_in_place()?),
None => repo.head()?.try_peel_to_id_in_place()?,
};

let root_tree = match root_tree_id {
Some(id) => id.object().expect("downloaded from remote").peel_to_tree()?.id,
None => {
return Ok((
self.repo.take().expect("still present"),
gix_worktree_state::checkout::Outcome::default(),
))
));
}
};

let index = gix_index::State::from_tree(&root_tree, &repo.objects, repo.config.protect_options()?)
.map_err(|err| Error::IndexFromTree {
id: root_tree,
Expand Down Expand Up @@ -129,7 +144,7 @@ pub mod main_worktree {
bytes.show_throughput(start);

index.write(Default::default())?;
Ok((self.repo.take().expect("still present"), outcome))
Ok((self.repo.take().expect("still present").clone(), outcome))
}
}
}
Expand Down
34 changes: 31 additions & 3 deletions gix/src/clone/fetch/mod.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
use crate::bstr::BString;
use crate::bstr::ByteSlice;
use crate::clone::PrepareFetch;

/// The error returned by [`PrepareFetch::fetch_only()`].
Expand Down Expand Up @@ -35,6 +37,13 @@ pub enum Error {
},
#[error("Failed to update HEAD with values from remote")]
HeadUpdate(#[from] crate::reference::edit::Error),
#[error("The remote didn't have any ref that matched '{}'", wanted.as_ref().as_bstr())]
RefNameMissing { wanted: gix_ref::PartialName },
#[error("The remote has {} refs for '{}', try to use a specific name: {}", candidates.len(), wanted.as_ref().as_bstr(), candidates.iter().filter_map(|n| n.to_str().ok()).collect::<Vec<_>>().join(", "))]
RefNameAmbiguous {
wanted: gix_ref::PartialName,
candidates: Vec<BString>,
},
}

/// Modification
Expand Down Expand Up @@ -117,7 +126,7 @@ impl PrepareFetch {
remote = remote.with_fetch_tags(fetch_tags);
}

// Add HEAD after the remote was written to config, we need it to know what to checkout later, and assure
// Add HEAD after the remote was written to config, we need it to know what to check out later, and assure
// the ref that HEAD points to is present no matter what.
let head_refspec = gix_refspec::parse(
format!("HEAD:refs/remotes/{remote_name}/HEAD").as_str().into(),
Expand All @@ -136,10 +145,22 @@ impl PrepareFetch {
if !opts.extra_refspecs.contains(&head_refspec) {
opts.extra_refspecs.push(head_refspec)
}
if let Some(ref_name) = &self.ref_name {
opts.extra_refspecs.push(
gix_refspec::parse(ref_name.as_ref().as_bstr(), gix_refspec::parse::Operation::Fetch)
.expect("partial names are valid refspecs")
.to_owned(),
);
}
opts
})
.await?
};

// Assure problems with custom branch names fail early, not after getting the pack or during negotiation.
if let Some(ref_name) = &self.ref_name {
util::find_custom_refname(pending_pack.ref_map(), ref_name)?;
}
if pending_pack.ref_map().object_hash != repo.object_hash() {
unimplemented!("configure repository to expect a different object hash as advertised by the server")
}
Expand All @@ -160,9 +181,10 @@ impl PrepareFetch {
util::append_config_to_repo_config(repo, config);
util::update_head(
repo,
&outcome.ref_map.remote_refs,
&outcome.ref_map,
reflog_message.as_ref(),
remote_name.as_ref(),
self.ref_name.as_ref(),
)?;

Ok((self.repo.take().expect("still present"), outcome))
Expand All @@ -180,7 +202,13 @@ impl PrepareFetch {
P::SubProgress: 'static,
{
let (repo, fetch_outcome) = self.fetch_only(progress, should_interrupt)?;
Ok((crate::clone::PrepareCheckout { repo: repo.into() }, fetch_outcome))
Ok((
crate::clone::PrepareCheckout {
repo: repo.into(),
ref_name: self.ref_name.clone(),
},
fetch_outcome,
))
}
}

Expand Down
97 changes: 75 additions & 22 deletions gix/src/clone/fetch/util.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use std::{borrow::Cow, io::Write};

use gix_ref::{
transaction::{LogChange, RefLog},
FullNameRef,
FullNameRef, PartialName,
};

use super::Error;
Expand Down Expand Up @@ -60,35 +60,40 @@ pub fn append_config_to_repo_config(repo: &mut Repository, config: gix_config::F

/// HEAD cannot be written by means of refspec by design, so we have to do it manually here. Also create the pointed-to ref
/// if we have to, as it might not have been naturally included in the ref-specs.
/// Lastly, use `ref_name` if it was provided instead, and let `HEAD` point to it.
pub fn update_head(
repo: &mut Repository,
remote_refs: &[gix_protocol::handshake::Ref],
ref_map: &crate::remote::fetch::RefMap,
reflog_message: &BStr,
remote_name: &BStr,
ref_name: Option<&PartialName>,
) -> Result<(), Error> {
use gix_ref::{
transaction::{PreviousValue, RefEdit},
Target,
};
let (head_peeled_id, head_ref) = match remote_refs.iter().find_map(|r| {
Some(match r {
gix_protocol::handshake::Ref::Symbolic {
full_ref_name,
target,
tag: _,
object,
} if full_ref_name == "HEAD" => (Some(object.as_ref()), Some(target)),
gix_protocol::handshake::Ref::Direct { full_ref_name, object } if full_ref_name == "HEAD" => {
(Some(object.as_ref()), None)
}
gix_protocol::handshake::Ref::Unborn { full_ref_name, target } if full_ref_name == "HEAD" => {
(None, Some(target))
}
_ => return None,
})
}) {
Some(t) => t,
None => return Ok(()),
let head_info = match ref_name {
Some(ref_name) => Some(find_custom_refname(ref_map, ref_name)?),
None => ref_map.remote_refs.iter().find_map(|r| {
Some(match r {
gix_protocol::handshake::Ref::Symbolic {
full_ref_name,
target,
tag: _,
object,
} if full_ref_name == "HEAD" => (Some(object.as_ref()), Some(target.as_bstr())),
gix_protocol::handshake::Ref::Direct { full_ref_name, object } if full_ref_name == "HEAD" => {
(Some(object.as_ref()), None)
}
gix_protocol::handshake::Ref::Unborn { full_ref_name, target } if full_ref_name == "HEAD" => {
(None, Some(target.as_bstr()))
}
_ => return None,
})
}),
};
let Some((head_peeled_id, head_ref)) = head_info else {
return Ok(());
};

let head: gix_ref::FullName = "HEAD".try_into().expect("valid");
Expand Down Expand Up @@ -178,7 +183,55 @@ pub fn update_head(
Ok(())
}

/// Setup the remote configuration for `branch` so that it points to itself, but on the remote, if and only if currently
pub(super) fn find_custom_refname<'a>(
ref_map: &'a crate::remote::fetch::RefMap,
ref_name: &PartialName,
) -> Result<(Option<&'a gix_hash::oid>, Option<&'a BStr>), Error> {
let group = gix_refspec::MatchGroup::from_fetch_specs(Some(
gix_refspec::parse(ref_name.as_ref().as_bstr(), gix_refspec::parse::Operation::Fetch)
.expect("partial names are valid refs"),
));
// TODO: to fix ambiguity, implement priority system
let filtered_items: Vec<_> = ref_map
.mappings
.iter()
.filter_map(|m| {
m.remote
.as_name()
.and_then(|name| m.remote.as_id().map(|id| (name, id)))
})
.map(|(full_ref_name, target)| gix_refspec::match_group::Item {
full_ref_name,
target,
object: None,
})
.collect();
let res = group.match_remotes(filtered_items.iter().copied());
match res.mappings.len() {
0 => Err(Error::RefNameMissing {
wanted: ref_name.clone(),
}),
1 => {
let item = filtered_items[res.mappings[0]
.item_index
.expect("we map by name only and have no object-id in refspec")];
Ok((Some(item.target), Some(item.full_ref_name)))
}
_ => Err(Error::RefNameAmbiguous {
wanted: ref_name.clone(),
candidates: res
.mappings
.iter()
.filter_map(|m| match m.lhs {
gix_refspec::match_group::SourceRef::FullName(name) => Some(name.to_owned()),
gix_refspec::match_group::SourceRef::ObjectId(_) => None,
})
.collect(),
}),
}
}

/// Set up the remote configuration for `branch` so that it points to itself, but on the remote, if and only if currently
/// saved refspecs are able to match it.
/// For that we reload the remote of `remote_name` and use its `ref_specs` for match.
fn setup_branch_config(
Expand Down
Loading