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 20:12:47 UTC
(opendal-yinyang) 02/02: feat: project artifact evidence and export conflict-local offline
This is an automated email from the ASF dual-hosted git repository.
Xuanwo pushed a commit to branch xuanwo/managed-sync-cli
in repository https://gitbox.apache.org/repos/asf/opendal-yinyang.git
commit 0262d3b6d442a5cd8b7bda78352bcdc7ef0274a3
Author: Xuanwo <gi...@xuanwo.io>
AuthorDate: Tue Oct 27 04:12:39 2026 +0800
feat: project artifact evidence and export conflict-local offline
Use the domain artifact `path`/`exportable` directly instead of
deriving the quarantine layout, and route `export --conflict --side local`
through the authority-free `replica::export_conflict_local`. Only a
conflict's remote side still needs the authority. Update the Linux
Managed Sync specification to match.
Co-authored-by: Chojan Shang <ps...@apache.org>
---
specs/linux-sync.md | 12 ++++++------
src/sync_cli.rs | 48 ++++++++++++++++++++++++------------------------
2 files changed, 30 insertions(+), 30 deletions(-)
diff --git a/specs/linux-sync.md b/specs/linux-sync.md
index 4ebcc56..2ba0d14 100644
--- a/specs/linux-sync.md
+++ b/specs/linux-sync.md
@@ -77,10 +77,10 @@ decide whether to retry.
`status` and `export --artifact` work without remote connectivity. `status`
classifies the local binding first and reports remote availability truthfully,
marking it unavailable with the real connection error when the authority cannot
-be reached; it never fabricates success. `export --artifact` reads retained
-bytes from the local replica and never contacts the authority. `export
---conflict` currently connects to the authority, because the remote side reads
-a retained revision.
+be reached; it never fabricates success. `export --artifact` and `export
+--conflict --side local` read durable local bytes and never contact the
+authority. `export --conflict --side remote` reads a retained revision and
+needs the authority.
## Status document
@@ -165,5 +165,5 @@ yy --volume sync.json sync status --json
`sync` is implemented only on Linux and drives an ordinary directory; it does
not provide macOS File Provider integration, an automatic watcher or a daemon.
-Retained-artifact export is offline; conflict-side export currently needs the
-authority.
+A retained artifact's bytes and a conflict's local side are exported offline; a
+conflict's remote side needs the authority.
diff --git a/src/sync_cli.rs b/src/sync_cli.rs
index bd13be1..0b77bad 100644
--- a/src/sync_cli.rs
+++ b/src/sync_cli.rs
@@ -531,14 +531,12 @@ async fn export(
// Retained bytes are local; export must not require remote connectivity.
export_artifact_local(config, id, &mut file).await
} else {
- export_conflict(
- config,
- operator,
- conflict.as_deref().expect("conflict selector present"),
- side.expect("conflict side present").as_str(),
- &mut file,
- )
- .await
+ let id = conflict.as_deref().expect("conflict selector present");
+ match side.expect("conflict side present") {
+ // The local side is durable local bytes; no authority is needed.
+ SyncSide::Local => export_conflict_local(config, id, &mut file),
+ SyncSide::Remote => export_conflict_remote(config, operator, id, &mut file).await,
+ }
};
drop(file);
if let Err(error) = outcome {
@@ -568,13 +566,22 @@ async fn export_artifact_local(
Ok(())
}
-/// Exports one side of a conflict. The remote side reads the retained revision,
-/// so this path needs the publication authority.
-async fn export_conflict(
+/// Exports a conflict's local side through the authority-free local reader.
+fn export_conflict_local(
+ config: &VolumeConfig,
+ id: &str,
+ output: &mut dyn std::io::Write,
+) -> Result<(), Box<dyn std::error::Error>> {
+ yinyang::replica::export_conflict_local(&config.staging, id, output).map_err(fatal)?;
+ Ok(())
+}
+
+/// Exports a conflict's remote side. It reads a retained revision, so this path
+/// needs the publication authority.
+async fn export_conflict_remote(
config: &VolumeConfig,
operator: opendal::Operator,
id: &str,
- side: &str,
output: &mut dyn std::io::Write,
) -> Result<(), Box<dyn std::error::Error>> {
let authority = connect(config, operator).await?;
@@ -582,7 +589,7 @@ async fn export_conflict(
.await
.map_err(fatal)?;
directory
- .export_conflict_side(id, side, output)
+ .export_conflict_side(id, "remote", output)
.await
.map_err(fatal)?;
Ok(())
@@ -666,7 +673,7 @@ fn project(inspection: &domain::Inspection) -> Result<SyncStatus, String> {
let artifacts = inspection
.artifacts
.iter()
- .map(|artifact| project_artifact(artifact, &inspection.binding.state_directory))
+ .map(project_artifact)
.collect::<Result<Vec<_>, _>>()?;
Ok(SyncStatus {
schema: SyncStatus::SCHEMA.to_owned(),
@@ -869,7 +876,7 @@ fn conflict_side(side: &str) -> ConflictSide {
}
}
-fn project_artifact(artifact: &domain::Artifact, state: &Path) -> Result<ArtifactRecord, String> {
+fn project_artifact(artifact: &domain::Artifact) -> Result<ArtifactRecord, String> {
let kind = match artifact.kind.as_str() {
"replaced-file" => ArtifactKind::ReplacedFile,
"rollback-displaced" => ArtifactKind::RollbackDisplaced,
@@ -892,15 +899,8 @@ fn project_artifact(artifact: &domain::Artifact, state: &Path) -> Result<Artifac
origin: artifact.origin.clone(),
kind,
phase,
- // Retained bytes live under the state directory's quarantine. The
- // domain record exposes the file name; the adapter composes the public
- // path the operator and export use.
- path: state
- .join("quarantine")
- .join(&artifact.name)
- .display()
- .to_string(),
- exportable: kind != ArtifactKind::QuarantinedDirectory,
+ path: artifact.path.display().to_string(),
+ exportable: artifact.exportable,
error: artifact.error.clone(),
})
}