You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@opendal.apache.org by xu...@apache.org on 2026/10/26 19:26:17 UTC
(opendal-yinyang) branch xuanwo/managed-sync-reconciliation updated: fix: keep per-entry predicates and advance only changed parents
This is an automated email from the ASF dual-hosted git repository.
Xuanwo pushed a commit to branch xuanwo/managed-sync-reconciliation
in repository https://gitbox.apache.org/repos/asf/opendal-yinyang.git
The following commit(s) were added to refs/heads/xuanwo/managed-sync-reconciliation by this push:
new 92855e5 fix: keep per-entry predicates and advance only changed parents
92855e5 is described below
commit 92855e553a934a3a503c0d04e2ace0959db0e3a2
Author: Xuanwo <gi...@xuanwo.io>
AuthorDate: Tue Oct 27 03:26:12 2026 +0800
fix: keep per-entry predicates and advance only changed parents
Restore per-path accepted revisions for staging and advance the predicate only
for paths whose namespace we changed earlier in the run (child removals,
parent entry-list changes), preventing an unrelated path from being rebased
onto a newer global revision.
Co-authored-by: Chojan Shang <ps...@apache.org>
---
src/replica/directory.rs | 33 ++++++++++++++++++++++++++++-----
1 file changed, 28 insertions(+), 5 deletions(-)
diff --git a/src/replica/directory.rs b/src/replica/directory.rs
index df73c38..4cdeecb 100644
--- a/src/replica/directory.rs
+++ b/src/replica/directory.rs
@@ -705,6 +705,7 @@ impl DirectorySync {
revision: observed.revision(),
nodes: BTreeMap::new(),
revisions: BTreeMap::new(),
+ dirty: BTreeSet::new(),
};
ctx.reconcile().await?;
}
@@ -1298,6 +1299,9 @@ struct Context<'a> {
revision: Revision,
nodes: BTreeMap<String, NodeId>,
revisions: BTreeMap<String, Revision>,
+ /// Paths whose remote namespace we changed earlier in this run; their
+ /// staging predicate must reflect those receipts.
+ dirty: BTreeSet<String>,
}
impl<'a> Context<'a> {
@@ -1789,6 +1793,7 @@ impl<'a> Context<'a> {
let node_id = self.namespace_node(edit).await?;
self.nodes.insert(path.to_owned(), node_id);
self.revisions.insert(path.to_owned(), revision);
+ self.mark_parent_dirty(path);
if let Some(local) = self.current.get(path) {
self.accept_local(path, local, node_id, revision).await?;
}
@@ -1836,6 +1841,7 @@ impl<'a> Context<'a> {
let node_id = self.namespace_node(edit).await?;
self.nodes.insert(path.to_owned(), node_id);
self.revisions.insert(path.to_owned(), revision);
+ self.mark_parent_dirty(path);
self.accept_local(path, node, node_id, revision).await?;
self.replica
.store_mut()
@@ -1900,6 +1906,7 @@ impl<'a> Context<'a> {
self.revision = revision;
self.nodes.remove(path);
self.revisions.remove(path);
+ self.mark_parent_dirty(path);
self.replica.store_mut().remove_baseline_path(path)?;
self.replica
.store_mut()
@@ -1940,6 +1947,8 @@ impl<'a> Context<'a> {
self.revision = revision;
// The rename receipt proves the namespace move, never a concurrent
// content edit. Preserve the previously accepted fingerprint for `to`.
+ self.mark_parent_dirty(from);
+ self.mark_parent_dirty(to);
self.migrate_baseline_path(from, to, revision).await?;
self.replica
.store_mut()
@@ -2067,11 +2076,25 @@ impl<'a> Context<'a> {
}
}
- fn entry_revision(&self, _path: &str) -> Revision {
- // Stage against the coherent state observed (or produced) earlier in
- // this run. Sequential namespace work such as a subtree removal needs
- // its parent predicate to reflect the already-applied child removals.
- self.revision
+ fn entry_revision(&self, path: &str) -> Revision {
+ // Preserve each path's own accepted revision, except where our earlier
+ // namespace receipts in this run changed that path; then stage against
+ // the running revision. This keeps unrelated paths from being rebased
+ // onto a newer global revision (which could hide an external edit).
+ if self.dirty.contains(path) {
+ return self.revision;
+ }
+ self.revisions
+ .get(path)
+ .copied()
+ .or_else(|| self.baseline.remote.get(path).map(|entry| entry.revision))
+ .unwrap_or(self.revision)
+ }
+
+ /// A path whose parent directory entry list we changed this run.
+ fn mark_parent_dirty(&mut self, path: &str) {
+ let (parent, _) = split(path);
+ self.dirty.insert(parent.to_owned());
}
#[allow(clippy::too_many_arguments)]