mirror of
https://github.com/narrrl/proton-drive-linux.git
synced 2026-11-04 16:56:59 +01:00
fix(fuse,core): key conflict detection on revision id, stop transient-name seals (B69, B70)
B69 — spurious `(sync-conflict)` copies came from `(mtime, size)` being used as a revision identity. The server re-stamps a sealed revision with its own commit time, so the drain diverted a write into a conflict copy of its own base; and `plan_file` classified a no-baseline (local, remote) pair as `Conflict`, forking every pre-existing file on first reconcile. - `Baseline` carries `revision_id` (from the SDK's new `active_revision_id`), captured at write-open and preserved across supersede/rebaseline. `revision_changed` trusts the id and only falls back to `(mtime, size)` for old sidecars. - Planner gains `FilePlan::AdoptBaseline`: equal-size no-baseline pairs are recorded, not conflicted. Divergent sizes stay a real conflict. - New `sweep.rs` (`pdfs-conflict-sweep`, 5-min cadence) removes conflict copies proven identical by size + `content_sha1` and surfaces divergent ones once as an `ActivityKind::Conflict` entry. Never removes what it cannot prove. B70 — browsers downloading into an on-demand mount had their in-flight temps sealed as canonical revisions, exiling the finished file to a conflict copy. `syncignore::is_transient_name` now recognises download/scratch temps, and the on-demand `release`/`rename` paths hold them (`set_create_hold`) instead of sealing until the name is finalized. Workspace deps bumped to proton-sdk/proton-drive-rs 0.2.2 (registry, no patch). Live validation against a real account still pending for both. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01V8P42asymU158CPnhjRwri
This commit is contained in:
1 parent
6df757006a
commit
bca0fe34bc
19 files changed
+978
-115
No files matched your search
@@ -200,6 +200,12 @@ pub(super) enum FilePlan {
|
||||
DeleteLocal,
|
||||
DeleteRemote,
|
||||
ForgetBaseline,
|
||||
/// First sync of a file that already exists on both sides with identical
|
||||
/// content: record the baseline without transferring anything. Without this,
|
||||
/// a folder synced for the first time cuts a `(sync-conflict)` copy of every
|
||||
/// pre-existing file, because a missing baseline makes both sides read as
|
||||
/// "changed" (see the mass first-sync conflict storm in docs/BUGS.md).
|
||||
AdoptBaseline,
|
||||
}
|
||||
|
||||
pub(super) fn plan_file(
|
||||
@@ -219,10 +225,16 @@ pub(super) fn plan_file(
|
||||
});
|
||||
|
||||
match (local, remote) {
|
||||
(Some(_), Some(_)) => match (local_changed, remote_changed) {
|
||||
(Some(l), Some(r)) => match (local_changed, remote_changed) {
|
||||
(false, false) => FilePlan::Unchanged,
|
||||
(true, false) => FilePlan::UploadRevision,
|
||||
(false, true) => FilePlan::Download,
|
||||
// With a baseline, both-changed is a genuine concurrent edit and must
|
||||
// be preserved as a conflict. Without one, "both changed" only means
|
||||
// this is the first sync and there was nothing to compare to: adopt
|
||||
// the baseline when the sizes already match rather than conflict-copy
|
||||
// an already-identical file. Divergent sizes stay a real conflict.
|
||||
(true, true) if baseline.is_none() && l.size == r.size => FilePlan::AdoptBaseline,
|
||||
(true, true) => FilePlan::Conflict,
|
||||
},
|
||||
(Some(_), None) if baseline.is_none() || local_changed => FilePlan::UploadNew,
|
||||
@@ -350,6 +362,31 @@ mod file_plan_tests {
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn first_sync_of_an_identical_file_adopts_the_baseline_not_a_conflict() {
|
||||
// No baseline yet (first sync). Same size on both sides is a pre-existing
|
||||
// identical file, not a concurrent edit: adopt it rather than cut a
|
||||
// conflict copy of every file in the folder.
|
||||
let local = local(10, 40);
|
||||
let remote = remote(30, 40);
|
||||
assert_eq!(
|
||||
plan_file(Some(&local), Some(&remote), None),
|
||||
FilePlan::AdoptBaseline
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn first_sync_of_differing_sizes_is_still_a_conflict() {
|
||||
// No baseline and the sizes disagree: a genuine both-sides divergence,
|
||||
// preserved non-destructively as a conflict.
|
||||
let local = local(10, 20);
|
||||
let remote = remote(30, 40);
|
||||
assert_eq!(
|
||||
plan_file(Some(&local), Some(&remote), None),
|
||||
FilePlan::Conflict
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_missing_remote_signature_is_treated_as_changed() {
|
||||
let local = local(10, 20);
|
||||
|
||||
Reference in new issue
Block a user