feat: filters and partial cloning: initial support#2375
feat: filters and partial cloning: initial support#2375staehle wants to merge 3 commits intoGitoxideLabs:mainfrom
Conversation
3f30421 to
ddd9fd1
Compare
| self.shallow, | ||
| negotiate::make_refmapping_ignore_predicate(self.tags, self.ref_map), | ||
| ) | ||
| if self.additional_wants.is_empty() { |
There was a problem hiding this comment.
The condition is inverted. When additional_wants is empty, the code calls negotiate::add_wants() but then still iterates over additional_wants (lines 315-318), which does nothing. When additional_wants is NOT empty, it skips the normal wants logic and manually implements it (lines 323-350). This should be !self.additional_wants.is_empty() to properly handle the case when additional wants are present.
| if self.additional_wants.is_empty() { | |
| if !self.additional_wants.is_empty() { |
There was a problem hiding this comment.
It looks confusing so I get why copilot doesn't understand, but the condition is not inverted, and the behavior matches the surrounding design.
When additional_wants is empty, add_wants() delegates to negotiate::add_wants and then iterates self.additional_wants (which is empty), so that loop is a harmless no-op. This is the "normal fetch" path.
When additional_wants is non-empty, the code intentionally avoids negotiate::add_wants and instead builds the wants list itself (including want_ref() when supported) and then appends the additional object-id wants. This aligns with the other deliberate behavior you already saw:
mark_complete_and_common_ref()forcesMustNegotiatewhen additional wants exist.one_round()returns an "empty round" and intentionally skips have exchange/negotiation rounds since we want to ask for these objects directly.
So, flipping the condition to !self.additional_wants.is_empty() would make the non-empty case call negotiate::add_wants and then fall through into the manual wants logic, which would duplicate/alter behavior and be wrong.
| .map(crate::remote::fetch::ObjectFilter::to_argument_string) | ||
| .filter(|_| self.additional_wants.is_empty()); |
There was a problem hiding this comment.
The filter is being suppressed when additional_wants is present (line 105). This means users cannot use object filters when fetching specific objects by ID. Unless there's a technical limitation preventing filters with additional wants, this restriction should be removed or clearly documented why filters and additional wants are mutually exclusive.
| .map(crate::remote::fetch::ObjectFilter::to_argument_string) | |
| .filter(|_| self.additional_wants.is_empty()); | |
| .map(crate::remote::fetch::ObjectFilter::to_argument_string); |
There was a problem hiding this comment.
Hard disagree. This is intentional -- additional_wants SHOULD ignore blob filters, since if you specifically want to checkout a commit, you NEED those blobs.
|
Thanks a lot for upstreaming this feature, it's much appreciated! I hope to get to giving it the attention it deserves this weekend. |
Introduce `gix::remote::fetch::ObjectFilter` (currently blob filters only) and plumb it through clone/fetch all the way into the fetch protocol, returning a clear error if the remote doesn’t advertise `filter` capability. Also persist the partial-clone configuration on clone (`remote.<name>.partialclonefilter`, `remote.<name>.promisor`, `extensions.partialclone`) so the repository is marked as a promisor/partial clone in the same way as Git. On the CLI/plumbing side, add `--filter <spec>` to `gix clone`, and allow fetch arguments to be either refspecs or raw object IDs (treated as additional wants). Includes tests for filter parsing and for persisting partial-clone settings during clone. Creates a blobless bare clone using `gix`, creates (using real `git`) a worktree from that, and then using `gix` verify that `--filter=blob:none` is working! Credit: - Original work by Cameron Esfahani <cesfahani@roku.com> - Rewritten for modern Gitoxide by Jake Staehle <jstaehle@roku.com> [Upstream-Status: Appropriate for OSS Release]
There was a problem hiding this comment.
I'd hope to finish the review and changes today. It should be alright, but I hope I will get the negotiation logic.
Due to the lack of a server implementation this is always tricky to test, but maybe that's what the journey tests are there for.
I also wonder what it would take to port them into the gix (crate) test-suite.
Tasks
- review no-network commit
- address all coments
| while remote_section.remove("partialclonefilter").is_some() {} | ||
| while remote_section.remove("promisor").is_some() {} |
There was a problem hiding this comment.
Note to self: Create gix-config remove all.
gix/src/clone/fetch/util.rs
Outdated
| while remote_section.remove("partialclonefilter").is_some() {} | ||
| while remote_section.remove("promisor").is_some() {} | ||
|
|
||
| let partial_clone_filter = ValueName::try_from("partialclonefilter").map_err(|err| Error::ConfigValueName { |
There was a problem hiding this comment.
Create new config-tree keys for partialCloneFilter and promisor.
| return Ok(action); | ||
| } | ||
|
|
||
| Ok(match action { |
There was a problem hiding this comment.
Note to self: try to understand this.
| } | ||
|
|
||
| Ok(( | ||
| negotiate::Round { |
There was a problem hiding this comment.
Note to self: Oh try to understand this.
| /// Errors emitted when parsing [`ObjectFilter`] values from command-line input. | ||
| #[derive(Debug, thiserror::Error)] | ||
| pub enum ParseError { | ||
| /// The provided filter specification was empty. | ||
| #[error("filter specification must not be empty")] | ||
| Empty, | ||
| /// The provided filter specification is not supported yet. | ||
| #[error("unsupported filter specification '{0}'")] | ||
| Unsupported(String), | ||
| /// The provided blob size limit could not be parsed as an integer. | ||
| #[error("invalid blob size limit '{value}'")] | ||
| InvalidBlobLimit { | ||
| /// The string that failed to parse. | ||
| value: String, | ||
| /// The underlying parse error. | ||
| source: ParseIntError, | ||
| }, | ||
| } |
There was a problem hiding this comment.
Note to self: Turn this to gix-error::ValidationError right away.
ddd9fd1 to
1fdf406
Compare
…t to avoid the network. Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
1fdf406 to
2c60401
Compare
|
Apologies, GitHub just did something stupid I didn't expect it to do (I reset this branch back to the |
f7ac294 to
2c60401
Compare
Resolves #1046
Hi @Byron! I'm Jake Staehle and I work at Roku here with @cesfahani. We've had an internal version of this for our internal tools for quite some time, but I recently updated our
gitoxideversion to0.49and had to refactor this code myself for all the changes since0.40-- I would very much rather not do that again, so it's upstreaming time :)Introduce
gix::remote::fetch::ObjectFilter(currently blob filters only) and plumb it through clone/fetch all the way into the fetch protocol, returning a clear error if the remote doesn’t advertisefiltercapability.Also persist the partial-clone configuration on clone (
remote.<name>.partialclonefilter,remote.<name>.promisor,extensions.partialclone) so the repository is marked as a promisor/partial clone in the same way as Git.On the CLI/plumbing side, add
--filter <spec>togix clone, and allow fetch arguments to be either refspecs or raw object IDs (treated as additional wants).Includes tests for filter parsing and for persisting partial-clone settings during clone, as well as a full round-trip test creating a blobless bare clone using
gix, creating (using realgit) a worktree from that, and then usinggixverify that--filter=blob:noneis working!I did create a new repo https://github.com/staehle/gitoxide-testing for that last test to work properly, with a small set of known commits, tags, and blob objects. If you think this is useful, I'd be glad to transfer the repo to you / the
GitoxideLabsgroup!Let me know what you think! Thanks!
Credit: