Skip to content

feat(rsync): expose --exclude/--include/--exclude-from/--include-from#938

Open
ethany-nv wants to merge 1 commit into
mainfrom
ethany/rsync-filter-flags
Open

feat(rsync): expose --exclude/--include/--exclude-from/--include-from#938
ethany-nv wants to merge 1 commit into
mainfrom
ethany/rsync-filter-flags

Conversation

@ethany-nv

@ethany-nv ethany-nv commented May 1, 2026

Copy link
Copy Markdown
Collaborator

Description

Bumps the bundled rsync_bin to the f-luo/rsync fork via a go.mod replace directive. The fork ports rsync's wildmatch algorithm and uncomments --exclude-from / --include-from, both of which were stubbed errNotYetImplemented in upstream gokrazy/rsync v0.3.1 (the wildcard matcher also panicked on common patterns like *.log). The Bazel repo name @com_github_gokrazy_rsync is preserved because the fork keeps the original Go module path, so no BUILD changes are needed.

Adds an ordered RsyncFilterSpec carried on RsyncRequest and threaded through parse_rsync_request / rsync_upload / rsync_download. Filter rules apply to both foreground transfers and the watch-based upload daemon — they survive the multiprocessing.spawn boundary as a frozen dataclass with tuple fields, and RsyncDaemonMetadata.from_dict is updated with backward-compat handling so existing PID files (with or without legacy src/dst_module keys) still load. Subprocess builders inject filter args between -av and the source/dest paths.

Exposes four new flags on both osmo workflow rsync upload and osmo workflow rsync download:

  • --exclude PATTERN (repeatable)
  • --include PATTERN (repeatable)
  • --exclude-from FILE
  • --include-from FILE

A single argparse.Action dispatches by option_string so all four flags interleave into one ordered filter_rules list — this preserves rsync's first-match-wins semantics across --exclude and --include (e.g. --include '*.py' --exclude '*' to whitelist Python files). For *-from flags, file existence is validated and paths are resolved to absolute at parse time so daemon mode fails loudly upfront rather than at every reconnect.

Cross-cutting risk: src/runtime/cmd/rsync/ resolves the same Go module, so runtime container images must roll out in lockstep with the client. Old-server binaries panic when a new client sends wildcard filter rules.

Tests: Added 10 unit tests covering CLI parse-order preservation, *-from file validation, abs-path resolution, _build_filter_args ordering, JSON roundtrip of the spec, and three RsyncDaemonMetadata.from_dict backward-compat scenarios (no filter_spec, legacy schema, full roundtrip). Manually smoke-tested rsync_bin -av --exclude='*.log' src/ dst/ end-to-end and confirmed *.log was filtered.

Issue #None

Checklist

  • I am familiar with the Contributing Guidelines.
  • New or existing tests cover these changes.
  • The documentation is up to date with these changes.

Summary by CodeRabbit

Release Notes

  • New Features

    • Added rsync filter rule support to upload and download commands. Users can now specify file inclusion and exclusion patterns. Rules are processed in the order specified for precise control over which files are transferred during synchronization.
  • Chores

    • Updated module dependencies.

Bumps the bundled rsync_bin to the f-luo/rsync fork (commit c84164e),
which adds wildmatch-based filter support that upstream gokrazy/rsync
v0.3.1 lacks (file-based filters were stubbed errNotYetImplemented and
the wildcard matcher panicked).

Plumbs an ordered RsyncFilterSpec through RsyncRequest so rules apply to
both foreground transfers and the watch-based upload daemon. CLI uses a
single argparse Action across all four flags so first-match-wins
ordering between --exclude and --include is preserved. --exclude-from /
--include-from paths are validated and resolved to absolute paths at
parse time so the daemon can re-read them on every reconnect.

Note: src/runtime/cmd/rsync/ resolves the same Go module, so runtime
container images must roll out in lockstep with the client — old-server
binaries panic on filter rules they don't understand.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@ethany-nv ethany-nv requested a review from a team as a code owner May 1, 2026 19:20
@coderabbitai

coderabbitai Bot commented May 1, 2026

Copy link
Copy Markdown
Contributor
📝 Walkthrough

Walkthrough

The changes extend the rsync library and CLI with filter rule support across multiple layers. New types (RsyncFilterRule, RsyncFilterSpec) are introduced in the library, CLI arguments (--include/--exclude/--include-from/--exclude-from) are parsed and wired into request objects, and comprehensive tests validate parsing, spec construction, serialization, and backward compatibility. A Go module dependency is updated.

Changes

Cohort / File(s) Summary
Rsync Filter Library
src/lib/rsync/rsync.py
Introduces RsyncFilterRuleKind, RsyncFilterRule, and RsyncFilterSpec types. Adds _build_filter_args() to convert filter specs to rsync CLI arguments. Extends RsyncRequest with filter_spec field, updates parse_rsync_request, rsync_upload, and rsync_download signatures to accept optional filter_spec. Updates RsyncDaemonMetadata.from_dict to deserialize filter specs and maintain backward compatibility with legacy metadata.
CLI Filter Argument Parsing
src/cli/workflow.py
Adds CLI argument parsing for --include, --exclude, --include-from, and --exclude-from flags on both rsync upload and rsync download commands. Implements _build_rsync_filter_spec() helper to construct RsyncFilterSpec from parsed arguments, including absolute path resolution and existence checks for *-from files with OSMOUserError handling.
Filter Rule Test Coverage
src/cli/tests/test_cli.py
Adds test cases for CLI argument parsing (flag preservation and first-match-wins ordering), spec construction (nonexistent file detection, path resolution), library-level filter spec conversion, empty spec handling, and round-trip serialization/deserialization. Includes backward-compatibility tests for loading legacy metadata without filter_spec.
Go Module Dependency
src/go.mod
Adds a replace directive to redirect github.com/gokrazy/rsync to github.com/f-luo/rsync at a specified commit version.

Sequence Diagram

sequenceDiagram
    actor User
    participant CLI as CLI Parser
    participant Workflow as Workflow
    participant Library as Rsync Library
    participant RsyncClient as Rsync Client
    
    User->>CLI: Invoke rsync upload/download<br/>with --include/--exclude flags
    CLI->>Workflow: Parse arguments
    Workflow->>Workflow: Collect filter rules<br/>in order
    Workflow->>Workflow: Build RsyncFilterSpec<br/>(resolve paths, validate files)
    Workflow->>Library: Create RsyncRequest<br/>with filter_spec
    Library->>RsyncClient: Pass filter_spec to<br/>upload/download
    RsyncClient->>RsyncClient: Build filter args<br/>via _build_filter_args()
    RsyncClient->>RsyncClient: Apply args to rsync<br/>command execution
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐰 Filter rules in rsync flow,
From CLI flags to lib below,
Include, exclude, now side-by-side,
Order preserved with rabbit pride! 🔄

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 60.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main change: exposing four rsync filter CLI flags (--exclude, --include, --exclude-from, --include-from) that are the primary feature of this PR.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch ethany/rsync-filter-flags

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 golangci-lint (2.11.4)

level=error msg="[linters_context] typechecking error: pattern ./...: directory prefix . does not contain main module or its selected dependencies"


Review rate limit: 9/10 reviews remaining, refill in 6 minutes.

Comment @coderabbitai help to get the list of available commands and usage tips.

@codecov

codecov Bot commented May 1, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 92.30769% with 4 lines in your changes missing coverage. Please review.
✅ Project coverage is 44.44%. Comparing base (2f29f42) to head (3d84e3a).

Files with missing lines Patch % Lines
src/cli/workflow.py 91.66% 1 Missing and 1 partial ⚠️
src/lib/rsync/rsync.py 92.85% 2 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #938      +/-   ##
==========================================
+ Coverage   43.88%   44.44%   +0.56%     
==========================================
  Files         211      211              
  Lines       26982    27033      +51     
  Branches     4226     4231       +5     
==========================================
+ Hits        11841    12015     +174     
+ Misses      14505    14381     -124     
- Partials      636      637       +1     
Flag Coverage Δ
backend 45.40% <92.30%> (+0.61%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
src/cli/workflow.py 33.63% <91.66%> (+17.86%) ⬆️
src/lib/rsync/rsync.py 20.15% <92.85%> (+3.20%) ⬆️
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
src/lib/rsync/rsync.py (1)

1834-1887: ⚡ Quick win

Make filter_spec non-optional on the new API surface.

None never carries distinct behavior here; every entry point immediately coerces it to RsyncFilterSpec(). Tightening these signatures to accept an empty spec directly keeps the types simpler and removes the normalization branch in three places. Please verify no external caller relies on None as a sentinel before changing the signatures. As per coding guidelines "Avoid unnecessary Optional types in Python — only use Optional[T] or T | None when None has a distinct meaning from an empty value (e.g., 'not provided' vs 'provided but empty')."

Also applies to: 1924-2034

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/lib/rsync/rsync.py` around lines 1834 - 1887, The public API should not
accept None for filter_spec because callers always coerce it to
RsyncFilterSpec(); change the function signature (the parser that returns
RsyncRequest) to make filter_spec: RsyncFilterSpec (remove Optional/None), give
it a default of RsyncFilterSpec() if you want a default, and remove the runtime
normalization branch (the "filter_spec or RsyncFilterSpec()" in the RsyncRequest
constructor). Update the other two similar APIs mentioned (the code regions
around the other occurrences of filter_spec at ~1924-2034) to the same
non-optional signature and remove any code paths that check for/filter None;
also search for external callers and adjust any call sites that pass None to
instead omit the argument or pass RsyncFilterSpec().
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/cli/workflow.py`:
- Around line 145-147: The check currently uses os.path.isfile(resolved) which
allows files that exist but are not readable; change the validation in the same
helper where the OSMOUserError is raised (the block referencing resolved and
kind.value) to also verify the file is readable (e.g., os.access(resolved,
os.R_OK) or attempt to open the file) and raise the same OSMOUserError with a
clear message (e.g., '--{kind.value} file is not readable: {value}') when the
check fails so unreadable `*-from` files are reported up front.

---

Nitpick comments:
In `@src/lib/rsync/rsync.py`:
- Around line 1834-1887: The public API should not accept None for filter_spec
because callers always coerce it to RsyncFilterSpec(); change the function
signature (the parser that returns RsyncRequest) to make filter_spec:
RsyncFilterSpec (remove Optional/None), give it a default of RsyncFilterSpec()
if you want a default, and remove the runtime normalization branch (the
"filter_spec or RsyncFilterSpec()" in the RsyncRequest constructor). Update the
other two similar APIs mentioned (the code regions around the other occurrences
of filter_spec at ~1924-2034) to the same non-optional signature and remove any
code paths that check for/filter None; also search for external callers and
adjust any call sites that pass None to instead omit the argument or pass
RsyncFilterSpec().
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: 784db0d0-f38a-4f7f-b016-20e2c0f47b66

📥 Commits

Reviewing files that changed from the base of the PR and between 2f29f42 and 3d84e3a.

⛔ Files ignored due to path filters (1)
  • src/go.sum is excluded by !**/*.sum
📒 Files selected for processing (4)
  • src/cli/tests/test_cli.py
  • src/cli/workflow.py
  • src/go.mod
  • src/lib/rsync/rsync.py

Comment thread src/cli/workflow.py
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.

1 participant