-
-
Notifications
You must be signed in to change notification settings - Fork 444
feat: filters and partial cloning: initial support #2375
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,4 +1,4 @@ | ||
| use gix::bstr::BString; | ||
| use gix::{bstr::BString, hash::ObjectId}; | ||
|
|
||
| use crate::OutputFormat; | ||
|
|
||
|
|
@@ -29,7 +29,7 @@ pub(crate) mod function { | |
| std_shapes::shapes::{Arrow, Element, ShapeKind}, | ||
| }; | ||
|
|
||
| use super::Options; | ||
| use super::{ObjectId, Options}; | ||
| use crate::OutputFormat; | ||
|
|
||
| pub fn fetch<P>( | ||
|
|
@@ -57,15 +57,28 @@ pub(crate) mod function { | |
| } | ||
|
|
||
| let mut remote = crate::repository::remote::by_name_or_url(&repo, remote.as_deref())?; | ||
| if !ref_specs.is_empty() { | ||
| remote.replace_refspecs(ref_specs.iter(), gix::remote::Direction::Fetch)?; | ||
| let mut wants = Vec::new(); | ||
| let mut fetch_refspecs = Vec::new(); | ||
| for spec in ref_specs { | ||
| if spec.len() == repo.object_hash().len_in_hex() { | ||
| if let Ok(oid) = ObjectId::from_hex(spec.as_ref()) { | ||
| wants.push(oid); | ||
| continue; | ||
| } | ||
| } | ||
| fetch_refspecs.push(spec); | ||
| } | ||
|
Comment on lines
+62
to
+70
|
||
|
|
||
| if !fetch_refspecs.is_empty() { | ||
| remote.replace_refspecs(fetch_refspecs.iter(), gix::remote::Direction::Fetch)?; | ||
| remote = remote.with_fetch_tags(gix::remote::fetch::Tags::None); | ||
| } | ||
| let res: gix::remote::fetch::Outcome = remote | ||
| .connect(gix::remote::Direction::Fetch)? | ||
| .prepare_fetch(&mut progress, Default::default())? | ||
| .with_dry_run(dry_run) | ||
| .with_shallow(shallow) | ||
| .with_additional_wants(wants) | ||
| .receive(&mut progress, &gix::interrupt::IS_INTERRUPTED)?; | ||
|
|
||
| if handshake_info { | ||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -51,6 +51,7 @@ pub async fn fetch<P, T, E>( | |||||
| shallow_file, | ||||||
| shallow, | ||||||
| tags, | ||||||
| filter, | ||||||
| reject_shallow_remote, | ||||||
| }: Options<'_>, | ||||||
| ) -> Result<Option<Outcome>, Error> | ||||||
|
|
@@ -74,6 +75,15 @@ where | |||||
| crate::fetch::Response::check_required_features(protocol_version, &fetch_features)?; | ||||||
| let sideband_all = fetch_features.iter().any(|(n, _)| *n == "sideband-all"); | ||||||
| let mut arguments = Arguments::new(protocol_version, fetch_features, trace_packetlines); | ||||||
| if let Some(filter) = filter { | ||||||
| if !arguments.can_use_filter() { | ||||||
| return Err(Error::MissingServerFeature { | ||||||
| feature: "filter", | ||||||
| description: "Partial clone filters require server support configured on the remote server", | ||||||
|
||||||
| description: "Partial clone filters require server support configured on the remote server", | |
| description: "Remote does not advertise the `filter` capability; partial clone filtering cannot be used with this remote unless it is enabled server-side", |
| Original file line number | Diff line number | Diff line change | ||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -16,13 +16,40 @@ enum WriteMode { | |||||||||||||
| Append, | ||||||||||||||
| } | ||||||||||||||
|
|
||||||||||||||
| /// Persist the provided remote into the repository's local config, optionally adding partial clone settings. | ||||||||||||||
| #[allow(clippy::result_large_err)] | ||||||||||||||
| pub fn write_remote_to_local_config_file( | ||||||||||||||
| remote: &mut crate::Remote<'_>, | ||||||||||||||
| remote_name: BString, | ||||||||||||||
| filter: Option<&str>, | ||||||||||||||
| ) -> Result<gix_config::File<'static>, Error> { | ||||||||||||||
| use gix_config::parse::section::ValueName; | ||||||||||||||
|
|
||||||||||||||
| let mut config = gix_config::File::new(local_config_meta(remote.repo)); | ||||||||||||||
| remote.save_as_to(remote_name, &mut config)?; | ||||||||||||||
| remote.save_as_to(remote_name.clone(), &mut config)?; | ||||||||||||||
|
|
||||||||||||||
| if let Some(filter_spec) = filter { | ||||||||||||||
| let subsection = remote_name.to_str().map_err(|err| Error::RemoteNameNotUtf8 { | ||||||||||||||
| remote_name: remote_name.clone(), | ||||||||||||||
| source: err, | ||||||||||||||
| })?; | ||||||||||||||
| let mut remote_section = config.section_mut_or_create_new("remote", Some(subsection.into()))?; | ||||||||||||||
|
|
||||||||||||||
| while remote_section.remove("partialclonefilter").is_some() {} | ||||||||||||||
| while remote_section.remove("promisor").is_some() {} | ||||||||||||||
|
Comment on lines
+38
to
+39
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Note to self: Create |
||||||||||||||
|
|
||||||||||||||
| let partial_clone_filter = ValueName::try_from("partialclonefilter").expect("known to be valid"); | ||||||||||||||
| remote_section.push(partial_clone_filter, Some(filter_spec.into())); | ||||||||||||||
|
|
||||||||||||||
| let promisor = ValueName::try_from("promisor").expect("known to be valid"); | ||||||||||||||
| remote_section.push(promisor, Some("true".into())); | ||||||||||||||
|
|
||||||||||||||
| let mut extensions_section = config.section_mut_or_create_new("extensions", None)?; | ||||||||||||||
| while extensions_section.remove("partialClone").is_some() {} | ||||||||||||||
|
|
||||||||||||||
| let partial_clone = ValueName::try_from("partialClone").expect("known to be valid"); | ||||||||||||||
|
Comment on lines
+48
to
+50
|
||||||||||||||
| while extensions_section.remove("partialClone").is_some() {} | |
| let partial_clone = ValueName::try_from("partialClone").expect("known to be valid"); | |
| while extensions_section.remove("partialclone").is_some() {} | |
| let partial_clone = ValueName::try_from("partialclone").expect("known to be valid"); |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -12,6 +12,7 @@ use crate::{ | |
| }, | ||
| Progress, | ||
| }; | ||
| use gix_hash::ObjectId; | ||
|
|
||
| mod error; | ||
| pub use error::Error; | ||
|
|
@@ -153,6 +154,8 @@ where | |
| reflog_message: None, | ||
| write_packed_refs: WritePackedRefs::Never, | ||
| shallow: Default::default(), | ||
| filter: None, | ||
| additional_wants: Vec::new(), | ||
| }) | ||
| } | ||
| } | ||
|
|
@@ -184,6 +187,8 @@ where | |
| reflog_message: Option<RefLogMessage>, | ||
| write_packed_refs: WritePackedRefs, | ||
| shallow: remote::fetch::Shallow, | ||
| filter: Option<remote::fetch::ObjectFilter>, | ||
| additional_wants: Vec<ObjectId>, | ||
| } | ||
|
|
||
| /// Builder | ||
|
|
@@ -225,4 +230,22 @@ where | |
| self.shallow = shallow; | ||
| self | ||
| } | ||
|
|
||
| /// Ask the server to apply `filter` when sending objects. | ||
| pub fn with_filter(mut self, filter: Option<remote::fetch::ObjectFilter>) -> Self { | ||
| self.filter = filter; | ||
| self | ||
| } | ||
|
|
||
| /// Request that the server also sends the objects identified by the given object ids. | ||
| /// | ||
| /// Objects already present locally will be ignored during negotiation. | ||
| pub fn with_additional_wants(mut self, wants: impl IntoIterator<Item = ObjectId>) -> Self { | ||
| for want in wants { | ||
| if !self.additional_wants.contains(&want) { | ||
| self.additional_wants.push(want); | ||
| } | ||
| } | ||
| self | ||
| } | ||
|
Comment on lines
+240
to
+250
|
||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The new argument split treats any token whose length matches the object hash and parses as hex as an object ID, so that token is never passed through refspec handling. This breaks valid hex-only ref names (for example a branch/tag named with 40 hex chars), because
fetchwill issue a rawwantinstead of a refspec and can fail on servers that don't allow arbitrary SHA wants. Please avoid auto-converting ambiguous tokens unless the user explicitly asks for object-ID mode or after confirming they are not valid remote refs.Useful? React with 👍 / 👎.