fix(sync): S-01 — excluded paths no longer deleted by --delete

This commit is contained in:
2026-09-19 00:12:14 +02:00
parent fba7f305e3
commit 374150e5b3
3 changed files with 266 additions and 1 deletions
+8
View File
@@ -226,6 +226,10 @@ enum Commands {
#[arg(long, default_value_t = false)]
delete: bool,
/// Löscht auch ausgeschlossene Dateien im Ziel bei aktiver --delete Spiegelung
#[arg(long, default_value_t = false)]
delete_excluded: bool,
/// Führt eine Simulation aus: Zeigt Änderungen an, ohne Dateien zu schreiben oder zu löschen
#[arg(short = 'n', long, default_value_t = false)]
dry_run: bool,
@@ -1216,6 +1220,7 @@ fn handle_sync(
target: &str,
pull: bool,
delete: bool,
delete_excluded: bool,
dry_run: bool,
checksum: bool,
exclude: Vec<String>,
@@ -1271,6 +1276,7 @@ fn handle_sync(
let options = sanctum::sync::SyncOptions {
direction,
delete,
delete_excluded,
dry_run,
checksum,
exclude_patterns: exclude,
@@ -1520,6 +1526,7 @@ async fn run() -> Result<()> {
target,
pull,
delete,
delete_excluded,
dry_run,
checksum,
exclude,
@@ -1532,6 +1539,7 @@ async fn run() -> Result<()> {
&target,
pull,
delete,
delete_excluded,
dry_run,
checksum,
exclude,
+65 -1
View File
@@ -77,6 +77,7 @@ pub enum SyncDirection {
pub struct SyncOptions {
pub direction: SyncDirection,
pub delete: bool,
pub delete_excluded: bool,
pub dry_run: bool,
pub checksum: bool,
pub exclude_patterns: Vec<String>,
@@ -88,6 +89,7 @@ impl Default for SyncOptions {
Self {
direction: SyncDirection::Push,
delete: false,
delete_excluded: false,
dry_run: false,
checksum: false,
exclude_patterns: Vec::new(),
@@ -537,6 +539,7 @@ fn sync_push(
} else if local_source.is_dir() {
let parent_node = ensure_vault_dir_tree(db, vault_id, dek, target_vault_dir)?;
let mut local_relative_paths = HashSet::new();
let mut excluded_relative_paths = HashSet::new();
// Rekursiv alle lokalen Dateien und Ordner erfassen
collect_and_push_dir(
@@ -550,6 +553,7 @@ fn sync_push(
options,
stats,
&mut local_relative_paths,
&mut excluded_relative_paths,
)?;
// Spiegelung mit --delete: Im Tresor verwaiste Dateien entfernen
@@ -561,6 +565,9 @@ fn sync_push(
parent_node.id,
"",
&local_relative_paths,
&excluded_relative_paths,
&options.exclude_patterns,
options.delete_excluded,
options.dry_run,
options.quiet,
stats,
@@ -582,6 +589,7 @@ fn collect_and_push_dir(
options: &SyncOptions,
stats: &mut SyncStats,
local_relative_paths: &mut HashSet<String>,
excluded_relative_paths: &mut HashSet<String>,
) -> Result<()> {
for entry in fs::read_dir(current_dir)? {
let entry = entry?;
@@ -596,6 +604,7 @@ fn collect_and_push_dir(
.replace('\\', "/");
if is_excluded(&file_name, &rel_path, &options.exclude_patterns) {
excluded_relative_paths.insert(rel_path.clone());
continue;
}
@@ -644,6 +653,7 @@ fn collect_and_push_dir(
options,
stats,
local_relative_paths,
excluded_relative_paths,
)?;
} else if path.is_file() {
stats.files_scanned += 1;
@@ -702,6 +712,9 @@ pub fn delete_orphans_in_vault(
current_vault_id: i64,
prefix_rel: &str,
local_relative_paths: &HashSet<String>,
excluded_relative_paths: &HashSet<String>,
exclude_patterns: &[String],
delete_excluded: bool,
dry_run: bool,
quiet: bool,
stats: &mut SyncStats,
@@ -724,6 +737,19 @@ pub fn delete_orphans_in_vault(
format!("{}/{}", prefix_rel, child.name)
};
// S-01: Ausgeschlossene Dateien und ganze Teilbäume vor dem Löschen schützen
if !delete_excluded {
if is_excluded(&child.name, &child_rel, exclude_patterns) {
continue;
}
if excluded_relative_paths
.iter()
.any(|ex| child_rel == *ex || child_rel.starts_with(&format!("{}/", ex)))
{
continue;
}
}
if !local_relative_paths.contains(&child_rel) {
stats.files_deleted += 1;
if dry_run {
@@ -748,6 +774,9 @@ pub fn delete_orphans_in_vault(
child.id,
&child_rel,
local_relative_paths,
excluded_relative_paths,
exclude_patterns,
delete_excluded,
dry_run,
quiet,
stats,
@@ -836,6 +865,7 @@ fn sync_pull(
}
let mut vault_relative_paths = HashSet::new();
let mut excluded_relative_paths = HashSet::new();
collect_and_pull_dir(
db,
@@ -848,6 +878,7 @@ fn sync_pull(
options,
stats,
&mut vault_relative_paths,
&mut excluded_relative_paths,
)?;
// Spiegelung mit --delete: Lokale verwaiste Dateien entfernen
@@ -856,6 +887,9 @@ fn sync_pull(
local_target_dir,
local_target_dir,
&vault_relative_paths,
&excluded_relative_paths,
&options.exclude_patterns,
options.delete_excluded,
options.dry_run,
options.quiet,
stats,
@@ -877,6 +911,7 @@ fn collect_and_pull_dir(
options: &SyncOptions,
stats: &mut SyncStats,
vault_relative_paths: &mut HashSet<String>,
excluded_relative_paths: &mut HashSet<String>,
) -> Result<()> {
let carrier_id = db.find_carrier_node_id()?.unwrap_or(0);
let children = db.list_children_in_vault(vault_node_id, vault_id, dek)?;
@@ -899,6 +934,7 @@ fn collect_and_pull_dir(
};
if is_excluded(&child.name, &child_rel, &options.exclude_patterns) {
excluded_relative_paths.insert(child_rel.clone());
continue;
}
@@ -939,6 +975,7 @@ fn collect_and_pull_dir(
options,
stats,
vault_relative_paths,
excluded_relative_paths,
)?;
} else {
stats.files_scanned += 1;
@@ -993,6 +1030,9 @@ fn delete_orphans_on_host(
base_dir: &Path,
current_dir: &Path,
vault_relative_paths: &HashSet<String>,
excluded_relative_paths: &HashSet<String>,
exclude_patterns: &[String],
delete_excluded: bool,
dry_run: bool,
quiet: bool,
stats: &mut SyncStats,
@@ -1004,12 +1044,26 @@ fn delete_orphans_on_host(
for entry in fs::read_dir(current_dir)? {
let entry = entry?;
let path = entry.path();
let file_name = entry.file_name().to_string_lossy().to_string();
let rel_path = path
.strip_prefix(base_dir)
.unwrap_or(&path)
.to_string_lossy()
.replace('\\', "/");
// S-01: Ausgeschlossene Dateien und ganze Teilbäume vor dem Löschen schützen
if !delete_excluded {
if is_excluded(&file_name, &rel_path, exclude_patterns) {
continue;
}
if excluded_relative_paths
.iter()
.any(|ex| rel_path == *ex || rel_path.starts_with(&format!("{}/", ex)))
{
continue;
}
}
if !vault_relative_paths.contains(&rel_path) {
stats.files_deleted += 1;
if path.is_dir() {
@@ -1044,7 +1098,17 @@ fn delete_orphans_on_host(
}
}
} else if path.is_dir() {
delete_orphans_on_host(base_dir, &path, vault_relative_paths, dry_run, quiet, stats)?;
delete_orphans_on_host(
base_dir,
&path,
vault_relative_paths,
excluded_relative_paths,
exclude_patterns,
delete_excluded,
dry_run,
quiet,
stats,
)?;
}
}