fix(parity): empty-dir delete, per-dir filter errors, -R protect, stats parser
Blockers addressed together (shared scanner/delete-plan plumbing): * #10: an empty in-scope source directory produced no plan keep entry, so the receiver deleted the destination directory itself. The scanner now records every traversed directory into a delete-plan sink, the plan sender keeps them, and any directory whose plan the data stream never triggered is emitted after the data so its extras are still removed. Differential tests cover --delete-during and --delete-delay. * #8: an invalid per-directory filter file was silently ignored when an earlier merge file in the same directory existed; key the failure off the error text (both sequential and parallel scanners) and fail the scan. * #9: -R + --files-from receiver-protect rules recorded the source-relative path; record the bare relative wire path in both scanners so the protected destination mirror survives --delete. * #5: the STATUS_STATS would-delete parser now validates each retained path and enforces the shared MAX_MANIFEST_BYTES budget, and the --out-format dry-run delete line is escaped like the itemize line. * #11: drop the unused DELETE_PLAN_MAX_NAMES macro, log the delete-limit warning once per session, roll back dir-merge names from a per-directory file that fails to parse, and guard every filter error snprintf against err==NULL. #10 leaves the empty directory itself kept and its extras removed, matching rsync's final state on both per-directory timings.
This commit is contained in:
@@ -20,8 +20,6 @@
|
||||
* the number of entries one deletion commit may remove. A client
|
||||
* --max-delete=NUM smaller than this replaces it for the run. */
|
||||
#define DELETE_PLAN_SERVER_LIMIT 100000U
|
||||
/* Per-frame entry cap for the name sections (the dir/file child lists). */
|
||||
#define DELETE_PLAN_MAX_NAMES MAX_MANIFEST_ENTRIES
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* Sender: plan builder */
|
||||
@@ -387,6 +385,17 @@ int delete_plan_send_for_path(int fd, DeletePlanSender* sender, const char* path
|
||||
return rc;
|
||||
}
|
||||
|
||||
int delete_plan_send_remaining(int fd, DeletePlanSender* sender, const ArrayList* dirs) {
|
||||
if (!sender || !dirs)
|
||||
return 0;
|
||||
for (int i = 0; i < dirs->size; i++) {
|
||||
const char* dir = (const char*)dirs->items[i];
|
||||
if (delete_plan_send_for_path(fd, sender, dir, true) != 0)
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* Receiver: delete session */
|
||||
/* ------------------------------------------------------------------ */
|
||||
@@ -398,6 +407,7 @@ struct DeletePlanSession {
|
||||
size_t deleted;
|
||||
size_t skipped;
|
||||
bool limit_hit;
|
||||
bool limit_logged;
|
||||
bool config_seen;
|
||||
bool missing_applied;
|
||||
ArrayList* protected_prefixes;
|
||||
@@ -811,9 +821,11 @@ int delete_plan_session_receive(DeletePlanSession* session, const Config* config
|
||||
send_status(fd, STATUS_ERROR);
|
||||
return -1;
|
||||
}
|
||||
if (session->limit_hit)
|
||||
if (session->limit_hit && !session->limit_logged) {
|
||||
session->limit_logged = true;
|
||||
log_message(LOG_LEVEL_WARNING, "Deletions stopped due to the delete limit (%zu skipped)",
|
||||
session->skipped);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user