fix(sync): S-01 — excluded paths no longer deleted by --delete
This commit is contained in:
@@ -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
@@ -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,
|
||||
)?;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user