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 18:03:49 UTC
(opendal-yinyang) branch xuanwo/managed-directory-sync updated: fix: compare canonical file identities across replicas
This is an automated email from the ASF dual-hosted git repository.
Xuanwo pushed a commit to branch xuanwo/managed-directory-sync
in repository https://gitbox.apache.org/repos/asf/opendal-yinyang.git
The following commit(s) were added to refs/heads/xuanwo/managed-directory-sync by this push:
new d047f8c fix: compare canonical file identities across replicas
d047f8c is described below
commit d047f8c51dd4b0c3d9f30df1a97623c9a59892e6
Author: Xuanwo <gi...@xuanwo.io>
AuthorDate: Tue Oct 27 02:03:44 2026 +0800
fix: compare canonical file identities across replicas
Co-authored-by: Chojan Shang <ps...@apache.org>
---
src/replica/directory.rs | 46 +++++++++++++++++++++++-----------------------
1 file changed, 23 insertions(+), 23 deletions(-)
diff --git a/src/replica/directory.rs b/src/replica/directory.rs
index 8039431..30d24bd 100644
--- a/src/replica/directory.rs
+++ b/src/replica/directory.rs
@@ -24,7 +24,7 @@ use std::collections::{BTreeMap, BTreeSet};
use std::io::{Seek as _, SeekFrom, Write};
use std::path::Path;
use std::sync::Arc;
-use yinyang_core::{Authority, Node, NodeId, NodeKind, Revision, Snapshot};
+use yinyang_core::{Authority, ContentId, Node, NodeId, NodeKind, Revision, Snapshot};
pub type Result<T> = std::result::Result<T, Box<dyn std::error::Error + Send + std::marker::Sync>>;
@@ -266,7 +266,7 @@ impl DirectorySync {
outcome.materialized_remote += 1;
let fresh = self.replica.scan()?;
if let Some(local) = fresh.get(path) {
- if same_side(Some(local), Some(node)) {
+ if canonical_matches(&mut self.replica, local, node).await? {
self.replica
.store_mut()
.save_baseline_path(path, node, Some(local))?;
@@ -526,7 +526,7 @@ impl<'a> Context<'a> {
(Action::Create, Action::Create) => {
let local = self.current.get(path);
let remote = self.remote.get(path);
- if same_side(local, remote) {
+ if canonical_matches(self.replica, local.unwrap(), remote.unwrap()).await? {
Ok(())
} else {
self.conflict(path, None, Some(path), "create", "create")
@@ -536,7 +536,7 @@ impl<'a> Context<'a> {
(Action::Modify, Action::Modify) => {
let local = self.current.get(path);
let remote = self.remote.get(path);
- if same_side(local, remote) {
+ if canonical_matches(self.replica, local.unwrap(), remote.unwrap()).await? {
Ok(())
} else {
self.conflict(path, Some(path), None, "modify", "modify")
@@ -899,7 +899,7 @@ impl<'a> Context<'a> {
let Some(current) = fresh.get(path) else {
return Ok(());
};
- if same_side(Some(current), Some(node)) {
+ if canonical_matches(self.replica, current, node).await? {
self.replica
.store_mut()
.save_baseline_path(path, node, Some(current))?;
@@ -945,6 +945,24 @@ fn local_equal(left: &LocalNode, right: &LocalNode) -> bool {
left.kind == right.kind && left.executable == right.executable
}
+async fn canonical_matches(
+ replica: &mut Replica,
+ local: &LocalNode,
+ remote: &Node,
+) -> Result<bool> {
+ match (local.kind, remote.kind()) {
+ (LocalKind::Directory, NodeKind::Directory { .. }) => Ok(true),
+ (LocalKind::File { .. }, NodeKind::File(file)) => {
+ let captured = replica.capture_file(&local.path, local)?;
+ let mut source =
+ tokio::fs::File::from_std(replica.open_artifact(&captured.artifact.id)?);
+ let content = ContentId::calculate(&mut source).await?;
+ Ok(local.executable == remote.executable() && content == *file.content_id())
+ }
+ _ => Ok(false),
+ }
+}
+
fn remote_equal(baseline: &crate::replica::state::BaselineNode, current: &Node) -> bool {
let digest = match current.kind() {
NodeKind::File(file) => Some(*file.content_id().digest()),
@@ -956,24 +974,6 @@ fn remote_equal(baseline: &crate::replica::state::BaselineNode, current: &Node)
&& baseline.digest == digest
}
-fn same_side(local: Option<&LocalNode>, remote: Option<&Node>) -> bool {
- match (local, remote) {
- (Some(local), Some(remote)) => {
- let digest = match local.kind {
- LocalKind::File { digest, .. } => Some(*digest.as_bytes()),
- LocalKind::Directory => None,
- };
- let remote_digest = match remote.kind() {
- NodeKind::File(file) => Some(*file.content_id().digest()),
- NodeKind::Directory { .. } => None,
- };
- local.executable == remote.executable() && digest == remote_digest
- }
- (None, None) => true,
- _ => false,
- }
-}
-
fn record_conflict(
outcome: &mut RunOutcome,
replica: &mut Replica,