Skip to content

feat: reject --append with --whole-file at config build time#4049

Merged
oferchen merged 1 commit into
masterfrom
feat/reject-append-wholefile-2145
May 14, 2026
Merged

feat: reject --append with --whole-file at config build time#4049
oferchen merged 1 commit into
masterfrom
feat/reject-append-wholefile-2145

Conversation

@oferchen
Copy link
Copy Markdown
Owner

Summary

  • Upstream rsync (options.c:2382) rejects --append --whole-file at parse time with RERR_SYNTAX (exit code 1) and the message --append cannot be used with --whole-file. oc-rsync was silently accepting the combination, then producing inconsistent behavior at transfer time depending on which layer was reached first.
  • Add the mutual-exclusion check to both validation layers that gate config construction so the conflict is rejected before any work begins:
    • ClientConfigBuilder::validate (core) - the primary path the CLI uses; surfaces a ConfigConflict whose Display matches upstream wording exactly.
    • LocalCopyOptionsBuilder::validate (engine) - keeps the engine-level builder in sync so programmatic embedders also reject the combination.
  • Only an explicit --whole-file (Some(true)) conflicts; the default (None) and --no-whole-file (Some(false)) remain accepted so existing call sites that set the tri-state from auto-detection are unaffected.

Motivation

Audit follow-up tasks #2145, #2146, #2147 flagged that the --append --whole-file combination must be rejected at config build time to mirror upstream rsync's syntax check (options.c:2382, function parse_arguments). Silent acceptance produced upstream-incompatible behaviour and made transfer-time diagnostics confusing.

Test plan

  • cargo fmt --all -- --check
  • crates/engine/.../builder/tests.rs::append_and_whole_file_conflict and the two accept-path tests (append_with_no_whole_file_ok, append_with_default_whole_file_ok).
  • crates/core/.../builder/tests.rs::validate_append_with_whole_file_conflicts plus the accept-path companions; the error-string assertion locks the exact upstream wording.
  • crates/cli/tests/mutually_exclusive_options.rs::test_append_and_whole_file_rejected drives cli::run, asserts exit code 1, and confirms the diagnostic names both flags using upstream phrasing.
  • CI: fmt+clippy, nextest (stable), Windows, macOS, Linux musl.

Upstream rsync (options.c:2382) rejects the combination
`--append --whole-file` with the message
`--append cannot be used with --whole-file` and exits with
RERR_SYNTAX (1). oc-rsync was silently accepting both flags
together.

Add the conflict check to both validation layers that gate
config construction:
- `ClientConfigBuilder::validate` (core) - the primary path
  used by the CLI; surfaces a `ConfigConflict` whose `Display`
  matches upstream wording.
- `LocalCopyOptionsBuilder::validate` (engine) - keeps the
  engine-level builder in sync so programmatic embedders also
  reject the combination.

Only an explicit `--whole-file` (`Some(true)`) conflicts; the
default (`None`) and `--no-whole-file` (`Some(false)`) remain
accepted so existing call sites that set the tri-state from
auto-detection are unaffected.

Tests cover the engine builder, the core config builder
(including the exact upstream error string), and an end-to-end
CLI integration test that drives `cli::run` and asserts the
RERR_SYNTAX exit code plus the diagnostic mentions both flags.

Refs: #2145, #2146, #2147
@github-actions github-actions Bot added the enhancement New feature or request label May 14, 2026
@oferchen oferchen merged commit 6e7ed32 into master May 14, 2026
37 of 39 checks passed
@oferchen oferchen deleted the feat/reject-append-wholefile-2145 branch May 14, 2026 14:57
oferchen added a commit that referenced this pull request May 16, 2026
Convert the compatibility-flags mutual-exclusion audit from a
current-state inventory into a historical record. Each gap row (G1-G4)
now cites the PR that landed the remediation:

- G1: --checksum-choice=none promotes whole_file=1 (PR #4066)
- G2: --inplace --partial-dir gated on CF_INPLACE_PARTIAL_DIR (PR #4064)
- G3: --append --whole-file rejected at config build time (PR #4049)
- G4: --delay-updates partial_dir promotion centralised (PR #4050)

Section 5 is replaced with a resolution-summary table. Footnotes [g]
and [j] are updated to describe the current behaviour and reference
the resolving PRs. The divergence descriptions are preserved so the
audit remains a record of the upstream-vs-oc-rsync conflict matrix
prior to remediation.

Refs: #2151, #2106
oferchen added a commit that referenced this pull request May 16, 2026
)

Convert the compatibility-flags mutual-exclusion audit from a
current-state inventory into a historical record. Each gap row (G1-G4)
now cites the PR that landed the remediation:

- G1: --checksum-choice=none promotes whole_file=1 (PR #4066)
- G2: --inplace --partial-dir gated on CF_INPLACE_PARTIAL_DIR (PR #4064)
- G3: --append --whole-file rejected at config build time (PR #4049)
- G4: --delay-updates partial_dir promotion centralised (PR #4050)

Section 5 is replaced with a resolution-summary table. Footnotes [g]
and [j] are updated to describe the current behaviour and reference
the resolving PRs. The divergence descriptions are preserved so the
audit remains a record of the upstream-vs-oc-rsync conflict matrix
prior to remediation.

Refs: #2151, #2106
oferchen added a commit that referenced this pull request May 18, 2026
Upstream rsync (options.c:2382) rejects the combination
`--append --whole-file` with the message
`--append cannot be used with --whole-file` and exits with
RERR_SYNTAX (1). oc-rsync was silently accepting both flags
together.

Add the conflict check to both validation layers that gate
config construction:
- `ClientConfigBuilder::validate` (core) - the primary path
  used by the CLI; surfaces a `ConfigConflict` whose `Display`
  matches upstream wording.
- `LocalCopyOptionsBuilder::validate` (engine) - keeps the
  engine-level builder in sync so programmatic embedders also
  reject the combination.

Only an explicit `--whole-file` (`Some(true)`) conflicts; the
default (`None`) and `--no-whole-file` (`Some(false)`) remain
accepted so existing call sites that set the tri-state from
auto-detection are unaffected.

Tests cover the engine builder, the core config builder
(including the exact upstream error string), and an end-to-end
CLI integration test that drives `cli::run` and asserts the
RERR_SYNTAX exit code plus the diagnostic mentions both flags.

Refs: #2145, #2146, #2147
oferchen added a commit that referenced this pull request May 18, 2026
)

Convert the compatibility-flags mutual-exclusion audit from a
current-state inventory into a historical record. Each gap row (G1-G4)
now cites the PR that landed the remediation:

- G1: --checksum-choice=none promotes whole_file=1 (PR #4066)
- G2: --inplace --partial-dir gated on CF_INPLACE_PARTIAL_DIR (PR #4064)
- G3: --append --whole-file rejected at config build time (PR #4049)
- G4: --delay-updates partial_dir promotion centralised (PR #4050)

Section 5 is replaced with a resolution-summary table. Footnotes [g]
and [j] are updated to describe the current behaviour and reference
the resolving PRs. The divergence descriptions are preserved so the
audit remains a record of the upstream-vs-oc-rsync conflict matrix
prior to remediation.

Refs: #2151, #2106
oferchen added a commit that referenced this pull request May 18, 2026
Upstream rsync (options.c:2382) rejects the combination
`--append --whole-file` with the message
`--append cannot be used with --whole-file` and exits with
RERR_SYNTAX (1). oc-rsync was silently accepting both flags
together.

Add the conflict check to both validation layers that gate
config construction:
- `ClientConfigBuilder::validate` (core) - the primary path
  used by the CLI; surfaces a `ConfigConflict` whose `Display`
  matches upstream wording.
- `LocalCopyOptionsBuilder::validate` (engine) - keeps the
  engine-level builder in sync so programmatic embedders also
  reject the combination.

Only an explicit `--whole-file` (`Some(true)`) conflicts; the
default (`None`) and `--no-whole-file` (`Some(false)`) remain
accepted so existing call sites that set the tri-state from
auto-detection are unaffected.

Tests cover the engine builder, the core config builder
(including the exact upstream error string), and an end-to-end
CLI integration test that drives `cli::run` and asserts the
RERR_SYNTAX exit code plus the diagnostic mentions both flags.

Refs: #2145, #2146, #2147
oferchen added a commit that referenced this pull request May 18, 2026
)

Convert the compatibility-flags mutual-exclusion audit from a
current-state inventory into a historical record. Each gap row (G1-G4)
now cites the PR that landed the remediation:

- G1: --checksum-choice=none promotes whole_file=1 (PR #4066)
- G2: --inplace --partial-dir gated on CF_INPLACE_PARTIAL_DIR (PR #4064)
- G3: --append --whole-file rejected at config build time (PR #4049)
- G4: --delay-updates partial_dir promotion centralised (PR #4050)

Section 5 is replaced with a resolution-summary table. Footnotes [g]
and [j] are updated to describe the current behaviour and reference
the resolving PRs. The divergence descriptions are preserved so the
audit remains a record of the upstream-vs-oc-rsync conflict matrix
prior to remediation.

Refs: #2151, #2106
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant