Tail-only resume model. When the per-file STATUS_CHECK finds an existing
destination file that is shorter than the source, an append mode stops the
normal delta/full transfer. The receiver sends a new STATUS_APPEND frame with
the resume offset (the retained prefix length); the sender transmits ONLY the
tail (STATUS_APPEND_DATA). The receiver rebuilds the full file in memory
(prefix + tail) and installs it through the normal atomic store engine, so --inplace, --partial/--partial-dir, --delay-updates, --backup, --existing/--ignore-existing/--update and delete-manifest behaviour are all
unchanged. The result is always byte-identical to the source when the prefix
matches.
Prefix-verify model.--append sends the tail without content-verifying the
retained prefix (rsync parity): a wrong prefix is kept, so the result is prefix+tail and can differ from the source — this documented risk is the
plain-append safety statement. --append-verify first exchanges the source
prefix xxHash64 (STATUS_APPEND_SIG); the receiver compares it to the retained
prefix and replies STATUS_APPEND_OK (send tail) or STATUS_NEXT (mismatch →
the sender falls back to a clean full transfer), so a mismatched prefix is never silently appended to and the result is always byte-exact.
Protocol bump. New frames STATUS_APPEND / STATUS_APPEND_SIG / STATUS_APPEND_OK / STATUS_APPEND_DATA change the wire, so PROTOCOL_VERSION
was bumped 2.9.0 → 2.10.0 (src/shared/config.h; peers must match). The append/append_verify booleans already crossed the wire.
CLI / incompatibilities (verified against rsync). Both flags imply --incremental (the handshake needs it) and are rejected up front with -s (chunk serialization) and --whole-file (never a silent full transfer).
When both spellings are given --append-verify wins. --append+--inplace
works (FastSync still reconstructs+renames, which is safer than rsync's in-place
write: an interrupted resume never leaves a partial/corrupt destination).
Tests
CLI unit: acceptance (parse + imply incremental + validate), rejection of -s and --whole-file, removal from the unimplemented reject list.
Config wire round-trip for append/append_verify.
Unit: append_resume_eligible / append_tail_length pure resume math.
Integration (tests/integration/test_append.py): matching-prefix resume is
byte-identical AND tail-only (wire bytes << source size, via a byte-counting
proxy); plain --append with a wrong prefix keeps prefix+tail (parity); --append-verify with a wrong prefix falls back to a byte-exact full transfer; --append + --inplace and --append -m.
Verified: full unit suite (27/27), full integration suite (250 passed, 10
skipped, 1 xpassed; no regressions), CI-identical clang-format (v9 image) and
cppcheck (exit 0).
Docs
RSYNC_COMPAT.md rows 86–87 (--append, --append-verify) flipped to
Implemented with precise Notes plus a Phase-3 append-wave notes block. The Summary count line is deliberately untouched.
## Design
**Tail-only resume model.** When the per-file `STATUS_CHECK` finds an existing
destination file that is **shorter** than the source, an append mode stops the
normal delta/full transfer. The receiver sends a new `STATUS_APPEND` frame with
the resume offset (the retained prefix length); the sender transmits ONLY the
tail (`STATUS_APPEND_DATA`). The receiver rebuilds the full file in memory
(prefix + tail) and installs it through the **normal atomic store engine**, so
`--inplace`, `--partial`/`--partial-dir`, `--delay-updates`, `--backup`,
`--existing`/`--ignore-existing`/`--update` and delete-manifest behaviour are all
unchanged. The result is always byte-identical to the source when the prefix
matches.
**Prefix-verify model.** `--append` sends the tail without content-verifying the
retained prefix (**rsync parity**): a wrong prefix is kept, so the result is
`prefix+tail` and can differ from the source — this documented risk is the
plain-append safety statement. `--append-verify` first exchanges the source
prefix xxHash64 (`STATUS_APPEND_SIG`); the receiver compares it to the retained
prefix and replies `STATUS_APPEND_OK` (send tail) or `STATUS_NEXT` (mismatch →
the sender falls back to a **clean full transfer**), so a mismatched prefix is
**never** silently appended to and the result is always byte-exact.
**Protocol bump.** New frames `STATUS_APPEND / STATUS_APPEND_SIG /
STATUS_APPEND_OK / STATUS_APPEND_DATA` change the wire, so `PROTOCOL_VERSION`
was bumped **2.9.0 → 2.10.0** (src/shared/config.h; peers must match). The
`append`/`append_verify` booleans already crossed the wire.
**CLI / incompatibilities (verified against rsync).** Both flags imply
`--incremental` (the handshake needs it) and are rejected up front with
`-s` (chunk serialization) and `--whole-file` (never a silent full transfer).
When both spellings are given `--append-verify` wins. `--append`+`--inplace`
works (FastSync still reconstructs+renames, which is safer than rsync's in-place
write: an interrupted resume never leaves a partial/corrupt destination).
## Tests
- **CLI unit**: acceptance (parse + imply incremental + validate), rejection of
`-s` and `--whole-file`, removal from the unimplemented reject list.
- **Config wire round-trip** for `append`/`append_verify`.
- **Unit**: `append_resume_eligible` / `append_tail_length` pure resume math.
- **Integration** (`tests/integration/test_append.py`): matching-prefix resume is
byte-identical AND tail-only (wire bytes << source size, via a byte-counting
proxy); plain `--append` with a wrong prefix keeps `prefix+tail` (parity);
`--append-verify` with a wrong prefix falls back to a byte-exact full transfer;
`--append` + `--inplace` and `--append -m`.
Verified: full unit suite (27/27), full integration suite (250 passed, 10
skipped, 1 xpassed; no regressions), CI-identical clang-format (v9 image) and
cppcheck (exit 0).
## Docs
`RSYNC_COMPAT.md` rows 86–87 (`--append`, `--append-verify`) flipped to
Implemented with precise Notes plus a Phase-3 append-wave notes block. The
**Summary** count line is deliberately untouched.
Implement rsync's append modes: when an existing destination file is SHORTER
than the source, the receiver negotiates a resume offset and only the tail is
transferred; the full file (retained prefix + tail) is rebuilt and installed
through the normal atomic store path, so the result is byte-identical to the
source whenever the prefix matches.
- --append: sends the tail without content-verifying the retained prefix
(rsync parity; the documented prefix-trust risk).
- --append-verify: verifies the retained prefix against the source's prefix
xxHash64 before appending and, on a mismatch, falls back to a clean full
transfer (never a corrupt prefix+tail blend).
New wire frames STATUS_APPEND / STATUS_APPEND_SIG / STATUS_APPEND_OK /
STATUS_APPEND_DATA; PROTOCOL_VERSION bumped 2.9.0 -> 2.10.0 (peers must match).
Both flags imply --incremental and are incompatible with -s (chunk
serialization) and --whole-file (rejected up front). Respects --inplace,
--partial/--partial-dir and --delay-updates via the shared store engine.
- CLI: --append/--append-verify acceptance (parse + imply --incremental,
validate) and incompatibility rejection with -s and --whole-file; both
removed from the unimplemented reject list.
- Config: on-the-wire append/append_verify round-trip.
- Unit: append_resume_eligible / append_tail_length pure resume math.
- Integration (test_append.py): matching-prefix resume is byte-identical and
tail-only (wire bytes << source size); --append with a wrong prefix keeps
prefix+tail (rsync parity) while --append-verify detects the mismatch and
falls back to a byte-exact full transfer; --append with --inplace and -m.
Flip both rows to Implemented with precise Notes covering the tail-resume
model, the prefix-verify semantics (and the plain-append rsync-parity safety
statement), the new wire frames, and the PROTOCOL_VERSION 2.9.0 -> 2.10.0 bump.
Add a Phase-3 append-wave implementation-notes block. The Summary count line
is deliberately left untouched.
Merged into dev via local merge (2FA blocks server-side merge). missing-args 681d7a8, append f99e6e1, checksum-choice 4c98744, recount 829e760. Independent c-reviews: missing-args REQUEST CHANGES (absent-parent no-op blocker fixed); append REQUEST CHANGES (blocker verified already-handled + tested, sentinel guard for streamed/-f sources); checksum-choice APPROVE WITH NITS (xxh3 alias removed, digest length pinned). dev CI run #494: all jobs success. Closing without server merge.
Merged into dev via local merge (2FA blocks server-side merge). missing-args 681d7a8, append f99e6e1, checksum-choice 4c98744, recount 829e760. Independent c-reviews: missing-args REQUEST CHANGES (absent-parent no-op blocker fixed); append REQUEST CHANGES (blocker verified already-handled + tested, sentinel guard for streamed/-f sources); checksum-choice APPROVE WITH NITS (xxh3 alias removed, digest length pinned). dev CI run #494: all jobs success. Closing without server merge.
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Design
Tail-only resume model. When the per-file
STATUS_CHECKfinds an existingdestination file that is shorter than the source, an append mode stops the
normal delta/full transfer. The receiver sends a new
STATUS_APPENDframe withthe resume offset (the retained prefix length); the sender transmits ONLY the
tail (
STATUS_APPEND_DATA). The receiver rebuilds the full file in memory(prefix + tail) and installs it through the normal atomic store engine, so
--inplace,--partial/--partial-dir,--delay-updates,--backup,--existing/--ignore-existing/--updateand delete-manifest behaviour are allunchanged. The result is always byte-identical to the source when the prefix
matches.
Prefix-verify model.
--appendsends the tail without content-verifying theretained prefix (rsync parity): a wrong prefix is kept, so the result is
prefix+tailand can differ from the source — this documented risk is theplain-append safety statement.
--append-verifyfirst exchanges the sourceprefix xxHash64 (
STATUS_APPEND_SIG); the receiver compares it to the retainedprefix and replies
STATUS_APPEND_OK(send tail) orSTATUS_NEXT(mismatch →the sender falls back to a clean full transfer), so a mismatched prefix is
never silently appended to and the result is always byte-exact.
Protocol bump. New frames
STATUS_APPEND / STATUS_APPEND_SIG / STATUS_APPEND_OK / STATUS_APPEND_DATAchange the wire, soPROTOCOL_VERSIONwas bumped 2.9.0 → 2.10.0 (src/shared/config.h; peers must match). The
append/append_verifybooleans already crossed the wire.CLI / incompatibilities (verified against rsync). Both flags imply
--incremental(the handshake needs it) and are rejected up front with-s(chunk serialization) and--whole-file(never a silent full transfer).When both spellings are given
--append-verifywins.--append+--inplaceworks (FastSync still reconstructs+renames, which is safer than rsync's in-place
write: an interrupted resume never leaves a partial/corrupt destination).
Tests
-sand--whole-file, removal from the unimplemented reject list.append/append_verify.append_resume_eligible/append_tail_lengthpure resume math.tests/integration/test_append.py): matching-prefix resume isbyte-identical AND tail-only (wire bytes << source size, via a byte-counting
proxy); plain
--appendwith a wrong prefix keepsprefix+tail(parity);--append-verifywith a wrong prefix falls back to a byte-exact full transfer;--append+--inplaceand--append -m.Verified: full unit suite (27/27), full integration suite (250 passed, 10
skipped, 1 xpassed; no regressions), CI-identical clang-format (v9 image) and
cppcheck (exit 0).
Docs
RSYNC_COMPAT.mdrows 86–87 (--append,--append-verify) flipped toImplemented with precise Notes plus a Phase-3 append-wave notes block. The
Summary count line is deliberately untouched.
Merged into dev via local merge (2FA blocks server-side merge). missing-args
681d7a8, appendf99e6e1, checksum-choice4c98744, recount829e760. Independent c-reviews: missing-args REQUEST CHANGES (absent-parent no-op blocker fixed); append REQUEST CHANGES (blocker verified already-handled + tested, sentinel guard for streamed/-f sources); checksum-choice APPROVE WITH NITS (xxh3 alias removed, digest length pinned). dev CI run #494: all jobs success. Closing without server merge.Pull request closed