Skip to content

Conversation

@staehle
Copy link

@staehle staehle commented Jan 13, 2026

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 gitoxide version to 0.49 and had to refactor this code myself for all the changes since 0.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 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, as well as a full round-trip test creating a blobless bare clone using gix, creating (using real git) a worktree from that, and then using gix verify that --filter=blob:none is 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 GitoxideLabs group!

Let me know what you think! Thanks!

Credit:

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!

[Upstream-Status: Appropriate for OSS Release]
Copilot AI review requested due to automatic review settings January 13, 2026 17:41
@staehle staehle force-pushed the user/jstaehle/partial-clone-filters-oss branch from 64d4362 to 3f30421 Compare January 13, 2026 17:49
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.

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]
@staehle staehle force-pushed the user/jstaehle/partial-clone-filters-oss branch from 3f30421 to ddd9fd1 Compare January 13, 2026 18:26
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

self.shallow,
negotiate::make_refmapping_ignore_predicate(self.tags, self.ref_map),
)
if self.additional_wants.is_empty() {
Copy link

Copilot AI Jan 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Suggested change
if self.additional_wants.is_empty() {
if !self.additional_wants.is_empty() {

Copilot uses AI. Check for mistakes.
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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() forces MustNegotiate when 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.

Comment on lines +104 to +105
.map(crate::remote::fetch::ObjectFilter::to_argument_string)
.filter(|_| self.additional_wants.is_empty());
Copy link

Copilot AI Jan 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Suggested change
.map(crate::remote::fetch::ObjectFilter::to_argument_string)
.filter(|_| self.additional_wants.is_empty());
.map(crate::remote::fetch::ObjectFilter::to_argument_string);

Copilot uses AI. Check for mistakes.
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hard disagree. This is intentional -- additional_wants SHOULD ignore blob filters, since if you specifically want to checkout a commit, you NEED those blobs.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Minimal implementation of partial clones & promisor objects

1 participant