Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions extensions/s3/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,20 @@ includes CDDL, deterministic CBOR and hashing rules, the physical S3 layout,
state machines, a Smithy semantic API, and executable conformance vectors for
implementing compatible Java, Go, TypeScript, and other clients.

## Try commits and branches locally

The runnable walkthrough needs no credentials or external service. It creates
history, forks a branch, pages through a diff, detects a merge conflict,
resolves it, and reads the merged files:

```bash
cargo run --manifest-path extensions/s3/Cargo.toml \
-p prolly-s3-core --example branching_walkthrough
```

It uses an in-memory versioned object plane to focus on repository semantics.
Use the RustFS verification below to exercise the S3 transport as well.

## Local RustFS verification

The checked-in Compose file runs a versioning-capable RustFS endpoint:
Expand Down
16 changes: 16 additions & 0 deletions extensions/s3/client/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,22 @@ against your key count and memory budget. Export `performance_snapshot()` for
publication queue/wait telemetry and `s3_operation_metrics()` for SDK request
counts.

## Run the branching walkthrough

To explore the repository model without AWS credentials or a running S3
service, run the in-memory example from the workspace root:

```bash
cargo run --manifest-path extensions/s3/Cargo.toml \
-p prolly-s3-core --example branching_walkthrough
```

The example creates diverging `main` and `feature` branches, pages through a
structural diff, previews a conflict, proves that a rejected merge does not
move `main`, resolves the conflict explicitly, and prints the resulting
first-parent history. It uses the same provider-neutral repository operations
as the S3 client; use the RustFS example for a complete transport integration.

## Put and read a file

The builders intentionally resemble the AWS Rust SDK:
Expand Down
170 changes: 170 additions & 0 deletions extensions/s3/core/examples/branching_walkthrough.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
//! Runnable tour of Prolly S3 commits, branches, diffs, and merges.
//!
//! This uses the in-memory object plane, so it needs no AWS account or RustFS.
//! The repository operations are the same ones used by the S3-backed client.

use std::{collections::BTreeMap, sync::Arc};

use prolly_s3_core::{
CommitReceipt, ErrorCode, MemoryObjectPlane, MergePolicy, ObjectHeaders, Repository,
RepositoryOptions, Result, TraversalBudget,
};

#[tokio::main]
async fn main() -> Result<()> {
let plane = Arc::new(MemoryObjectPlane::new(true));
let repository = Repository::initialize(
plane,
RepositoryOptions {
repository_prefix: ".prolly/examples/branching".to_string(),
writer: "walkthrough".to_string(),
..RepositoryOptions::default()
},
)
.await?;

println!("1. Build a shared history on main");
put(&repository, "main", "README.md", "Welcome to the site\n").await?;
let base = put(&repository, "main", "config/theme.txt", "light\n").await?;
println!(" base commit: {}", base.id);

println!("\n2. Create feature from that exact commit");
repository.create_branch("feature", base.id).await?;
put(
&repository,
"feature",
"feature/banner.txt",
"Try the new theme!\n",
)
.await?;
let feature_head = put(&repository, "feature", "config/theme.txt", "dark\n").await?;
println!(" feature head: {}", feature_head.id);

println!("\n3. Let main diverge");
put(&repository, "main", "release.txt", "1.0\n").await?;
let main_head = put(&repository, "main", "config/theme.txt", "solarized\n").await?;
println!(" main head: {}", main_head.id);

println!("\n4. Diff base..feature one result at a time");
let mut cursor = None;
loop {
let page = repository
.diff_page_bounded(base.id, feature_head.id, cursor.as_ref(), 1)
.await?;

for change in page.changes {
println!(
" {:<20} {} -> {}",
display_key(&change.key),
display_version(change.from),
display_version(change.to),
);
}

cursor = page.continuation;
if cursor.is_none() {
break;
}
}

println!("\n5. Plan the merge before changing main");
let plan = repository
.plan_merge("main", feature_head.id, Some(base.id), MergePolicy::Fail)
.await?;
for conflict in &plan.conflicts {
println!(" conflict: {}", display_key(&conflict.key));
}
assert_eq!(plan.conflicts.len(), 1);

println!("\n6. A fail-on-conflict merge leaves main unchanged");
let error = repository
.merge(
"main",
feature_head.id,
Some(base.id),
MergePolicy::Fail,
None,
Some("merge feature".to_string()),
)
.await
.expect_err("the theme edits must conflict");
assert_eq!(error.code, ErrorCode::MergeConflict);
assert_eq!(repository.head("main").await?, main_head.id);
println!(" rejected: {}", error.message);

println!("\n7. Resolve explicitly with the feature branch's value");
let merged = repository
.merge(
"main",
feature_head.id,
Some(base.id),
MergePolicy::Theirs,
None,
Some("merge feature using its theme".to_string()),
)
.await?;
println!(" merge commit: {}", merged.id);
println!(" parents: {}", merged.parents.len());

let theme = repository.get_current("main", b"config/theme.txt").await?;
let banner = repository
.get_current("main", b"feature/banner.txt")
.await?;
let release = repository.get_current("main", b"release.txt").await?;
assert_eq!(theme.bytes, b"dark\n");
assert_eq!(banner.bytes, b"Try the new theme!\n");
assert_eq!(release.bytes, b"1.0\n");

println!(" config/theme.txt = {}", display_bytes(&theme.bytes));
println!(" feature/banner.txt = {}", display_bytes(&banner.bytes));
println!(" release.txt = {}", display_bytes(&release.bytes));

println!("\n8. Walk main's first-parent history");
let history = repository
.log_page_bounded(merged.id, None, 10, TraversalBudget::default())
.await?;
for (id, commit) in history.commits {
println!(
" {} parents={} {}",
id,
commit.parents.len(),
commit.message.as_deref().unwrap_or("(no message)"),
);
}

println!("\nWalkthrough complete.");
Ok(())
}

async fn put(
repository: &Repository<MemoryObjectPlane>,
branch: &str,
key: &str,
value: &str,
) -> Result<CommitReceipt> {
repository
.put_bytes(
branch,
key.as_bytes().to_vec(),
value.as_bytes().to_vec(),
ObjectHeaders {
content_type: Some("text/plain".to_string()),
..ObjectHeaders::default()
},
BTreeMap::new(),
None,
)
.await
}

fn display_key(key: &[u8]) -> String {
String::from_utf8_lossy(key).into_owned()
}

fn display_version(version: Option<prolly_s3_core::ObjectVersionId>) -> String {
version.map_or_else(|| "(absent)".to_string(), |version| version.to_string())
}

fn display_bytes(bytes: &[u8]) -> String {
String::from_utf8_lossy(bytes).trim_end().to_string()
}
Loading