-
-
Notifications
You must be signed in to change notification settings - Fork 500
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 | ||||
|---|---|---|---|---|---|---|
|
|
@@ -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 append_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 |
|---|---|---|
|
|
@@ -14,6 +14,7 @@ use crate::{ | |
| ref_map, | ||
| }, | ||
| }; | ||
| use gix_hash::ObjectId; | ||
|
|
||
| mod error; | ||
| pub use error::Error; | ||
|
|
@@ -173,6 +174,8 @@ where | |
| reflog_message: None, | ||
| write_packed_refs: WritePackedRefs::Never, | ||
| shallow: Default::default(), | ||
| filter: None, | ||
| additional_wants: Vec::new(), | ||
| }) | ||
| } | ||
| } | ||
|
|
@@ -224,6 +227,8 @@ where | |
| reflog_message: Option<RefLogMessage>, | ||
| write_packed_refs: WritePackedRefs, | ||
| shallow: remote::fetch::Shallow, | ||
| filter: Option<remote::fetch::ObjectFilter>, | ||
| additional_wants: Vec<ObjectId>, | ||
| } | ||
|
|
||
| /// Builder | ||
|
|
@@ -262,6 +267,14 @@ where | |
| self.inner.shallow = shallow; | ||
| 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 { | ||
| self.inner = self.inner.with_additional_wants(wants); | ||
| self | ||
| } | ||
| } | ||
|
|
||
| /// Builder | ||
|
|
@@ -288,4 +301,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
+311
to
+321
|
||
| } | ||
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.
This introduces an ambiguity: any fetch argument that is exactly the hash length and hex-decodable is treated as an object ID, even if the user intended it to be a refspec/refname (e.g., an all-hex branch name of that length). If that ambiguity is acceptable, it should be documented in the CLI help; otherwise consider adding an explicit syntax/flag for object-id wants (or only treating it as an OID when prefixed, e.g.
oid:<hex>).