Skip to content
Merged
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
68 changes: 62 additions & 6 deletions engine/crates/layerfs-api/tests/operator_history.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
//! `backend.store.project.get_history`: the read model's history half, read from the
//! Store with no daemon, so the dsh project page starts no Sandbox to show a
//! project. It lists the Branches, their Commits and the Layers, holds no
//! Workspaces, and `since` its version answers `unchanged` until history
//! moves. Disposable profile.
//! `backend.store.project.get_history` and the `snapshot` behind
//! `sandbox.list_workspaces`: the read model, read from the Store with no
//! daemon, so the dsh project page starts no Sandbox to show a project. It
//! lists the Branches, their Commits and the Layers, names the Branches the
//! Workspaces are mounted on, and `since` its version answers `unchanged`
//! until what it shows moves. Disposable profile.
#[path = "support/installed_store.rs"]
#[allow(dead_code)]
mod installed;
Expand All @@ -11,7 +12,8 @@ mod installed;
mod support;

use layerfs_bridge::control::{SdkCall, SdkValue};
use layerfs_store_service::operator;
use layerfs_history::HistoryName;
use layerfs_store_service::operator::{self, WorkspaceView};
use std::fs;
use support::{write, Fixture};

Expand Down Expand Up @@ -59,3 +61,57 @@ fn history_is_read_from_the_store_alone() {
assert_eq!(moved.get("workspaces"), Some(&SdkValue::List(Vec::new())));
fixture.cleanup();
}

#[test]
fn workspaces_name_branches_beyond_the_first_page() {
let fixture = Fixture::new("operator-history-branches", |source| {
fs::write(source.join("seed.txt"), b"seed\n").unwrap();
1
});
for n in 1..=70 {
fixture.fork(&format!("b{n:02}"));
}
let history = fixture.service.store.history();
let installed = HistoryName::new("installed").unwrap();
let stack = history
.layer_stack_named(&installed)
.unwrap()
.expect("installed stack")
.id;
let branch = history
.branch_named(stack, &HistoryName::new("b70").unwrap())
.unwrap()
.expect("b70 exists");
let binding = history
.branch_snapshot(branch.id)
.unwrap()
.expect("b70 snapshot");
// Move the head after the Workspace's binding was taken, so the answer
// also proves it reads the Branch's current head and not the page's.
fixture.apply("b70", vec![write("/after-mount.txt", "moved\n")]);
let view = WorkspaceView {
workspace: "workspace-4".into(),
namespace: 4,
binding,
activity: "idle",
mounted: true,
directory: Some("/workspaces/4".into()),
dirty: None,
pending_merge: false,
revision: None,
};
let args = SdkValue::Map(vec![("project".to_owned(), SdkValue::text("installed"))]);
let answer = operator::snapshot(&fixture.service.store, &args, &[view]).unwrap();
assert_eq!(answer.get("moreBranches"), Some(&SdkValue::Bool(true)));
let workspaces = match answer.get("workspaces") {
Some(SdkValue::List(items)) => items,
other => panic!("workspaces: {other:?}"),
};
assert_eq!(workspaces.len(), 1);
assert_eq!(workspaces[0].get_text("branch"), Some("b70"));
assert_eq!(
workspaces[0].get("headMovedSinceMount"),
Some(&SdkValue::Bool(true))
);
fixture.cleanup();
}
18 changes: 17 additions & 1 deletion engine/crates/layerfs-store-service/src/operator/inspect.rs
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ pub fn snapshot(store: &Arc<Store>, args: &SdkValue, views: &[WorkspaceView]) ->
.into_iter()
.filter(|branch| branch.name.as_str() != MAIN)
.collect::<Vec<_>>();
let names = branches
let mut names = branches
.iter()
.map(|branch| {
(
Expand All @@ -138,6 +138,22 @@ pub fn snapshot(store: &Arc<Store>, args: &SdkValue, views: &[WorkspaceView]) ->
)
})
.collect::<BTreeMap<_, _>>();
// The page is bounded, but a Workspace may be mounted on a Branch outside
// it. Resolve just those mounted identities, so the listing does not
// depend on how many Branches the project has. The page still reports
// `moreBranches`; the Workspace answer needs the name and current head.
for view in views {
if view.binding.branch.stack != stack.id {
continue;
}
let id = view.binding.branch.id;
if names.contains_key(&id) {
continue;
}
if let Some(branch) = store.history().branch(id).map_err(args::history)? {
names.insert(id, (branch.name.as_str().to_owned(), branch.head_commit));
}
}
let mut spent = 0usize;
let mut take = |value: SdkValue| {
spent += cost(&value);
Expand Down