feat: replace linked worktrees with runtime clones

This commit is contained in:
2026-09-12 01:43:21 +09:00
parent f6ce1df766
commit ec5a403ec6
21 changed files with 258 additions and 518 deletions
-2
View File
@@ -148,8 +148,6 @@ pub struct RepositoryMaterializationContext {
pub operation_id: String,
pub config_revision: u64,
pub config_projection_digest: String,
#[serde(default)]
pub cache_generation: u64,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub ssh: Option<RepositorySshMaterializationAccess>,
}
+2 -2
View File
@@ -25,7 +25,7 @@ use worker_runtime::http_server::{
WorkspaceRuntimeHttpAuth,
};
use worker_runtime::worker_backend::{ProfileRuntimeWorkerFactory, WorkerRuntimeExecutionBackend};
use worker_runtime::working_directory::RuntimeGitCacheMaterializer;
use worker_runtime::working_directory::RuntimeGitMaterializer;
use worker_runtime::workspace_issuer::{
FileWorkspaceClaimReplayProtection, FileWorkspaceRuntimeVerificationAuthority,
MAX_WORKSPACE_ISSUER_TRUST_RECORDS, RuntimeVerificationSigner, WorkspaceCapabilityVerifier,
@@ -266,7 +266,7 @@ fn build_runtime(config: &ProcessConfig) -> Result<Runtime, ProcessError> {
let backend = Arc::new(
WorkerRuntimeExecutionBackend::new(factory)
.map_err(ProcessError::WorkerAdapter)?
.with_working_directory_materializer(RuntimeGitCacheMaterializer::new(
.with_working_directory_materializer(RuntimeGitMaterializer::new(
fs_paths.workdir_target.clone(),
)),
);
+2 -5
View File
@@ -4020,7 +4020,7 @@ mod tests {
source_fingerprint: "sha256:source".to_string(),
selector: None,
},
materializer: MaterializerKind::RuntimeGitCache,
materializer: MaterializerKind::RuntimeGitClone,
backend_workdir_id: Some("working-directory-1".to_string()),
materialization: Some(RepositoryMaterializationContext {
workspace_id: "workspace-1".to_string(),
@@ -4028,7 +4028,6 @@ mod tests {
operation_id: "operation-1".to_string(),
config_revision: 1,
config_projection_digest: "sha256:projection".to_string(),
cache_generation: 0,
ssh: Some(RepositorySshMaterializationAccess {
credential_candidates: vec![RepositorySshCredentialCandidate {
credential_id: "credential-1".to_string(),
@@ -4140,7 +4139,6 @@ mod tests {
operation_id: "operation-1".to_string(),
config_revision: 1,
config_projection_digest: "sha256:projection".to_string(),
cache_generation: 0,
ssh: Some(RepositorySshMaterializationAccess {
credential_candidates: vec![
RepositorySshCredentialCandidate {
@@ -4253,7 +4251,7 @@ mod tests {
source_fingerprint: "sha256:source".to_string(),
selector: None,
},
materializer: MaterializerKind::RuntimeGitCache,
materializer: MaterializerKind::RuntimeGitClone,
backend_workdir_id: Some("working-directory-1".to_string()),
materialization: Some(RepositoryMaterializationContext {
workspace_id: "workspace-1".to_string(),
@@ -4261,7 +4259,6 @@ mod tests {
operation_id: "operation-create".to_string(),
config_revision: 1,
config_projection_digest: "sha256:projection".to_string(),
cache_generation: 0,
ssh: Some(RepositorySshMaterializationAccess {
credential_candidates: vec![
RepositorySshCredentialCandidate {
+14 -22
View File
@@ -2248,7 +2248,7 @@ mod tests {
use crate::identity::WorkerRef;
use crate::management::RuntimeOptions;
use crate::observation::WorkerObservationCursor;
use crate::working_directory::RuntimeGitCacheMaterializer;
use crate::working_directory::RuntimeGitMaterializer;
use agen::Engine;
use agen::llm_client::event::{Event as LlmEvent, ResponseStatus, StatusEvent};
use agen::llm_client::{ClientError, LlmClient, Request};
@@ -2992,13 +2992,13 @@ mod tests {
source_fingerprint: "sha256:test".to_string(),
selector: Some(RepositorySelector::from("HEAD")),
},
materializer: MaterializerKind::RuntimeGitCache,
materializer: MaterializerKind::RuntimeGitClone,
backend_workdir_id: None,
materialization: None,
}
}
fn materialized_worktree_root(
fn materialized_clone_root(
runtime_base: &std::path::Path,
working_directory_id: &str,
) -> PathBuf {
@@ -3734,9 +3734,7 @@ mod tests {
};
let backend = WorkerRuntimeExecutionBackend::new(factory)
.unwrap()
.with_working_directory_materializer(RuntimeGitCacheMaterializer::new(
runtime_base.path(),
));
.with_working_directory_materializer(RuntimeGitMaterializer::new(runtime_base.path()));
let runtime =
EmbeddedRuntime::with_execution_backend(RuntimeOptions::default(), Arc::new(backend))
.unwrap();
@@ -3973,9 +3971,7 @@ mod tests {
};
let backend = WorkerRuntimeExecutionBackend::new(factory)
.unwrap()
.with_working_directory_materializer(RuntimeGitCacheMaterializer::new(
runtime_base.path(),
));
.with_working_directory_materializer(RuntimeGitMaterializer::new(runtime_base.path()));
let runtime =
EmbeddedRuntime::with_execution_backend(RuntimeOptions::default(), Arc::new(backend))
.unwrap();
@@ -3990,13 +3986,13 @@ mod tests {
.summary
.working_directory_id
.clone();
let worktree_root = materialized_worktree_root(runtime_base.path(), &workdir_id);
assert!(worktree_root.join("README.md").exists());
let clone_root = materialized_clone_root(runtime_base.path(), &workdir_id);
assert!(clone_root.join("README.md").exists());
runtime.stop_worker(&detail.worker_ref, None).unwrap();
runtime.delete_worker(&detail.worker_ref).unwrap();
assert!(worktree_root.join("README.md").exists());
assert!(clone_root.join("README.md").exists());
let status = runtime.working_directory(&workdir_id).unwrap();
assert_eq!(
status.summary.status,
@@ -4012,9 +4008,7 @@ mod tests {
let repo = create_clean_repo();
let backend = WorkerRuntimeExecutionBackend::new(FailingFactory)
.unwrap()
.with_working_directory_materializer(RuntimeGitCacheMaterializer::new(
runtime_base.path(),
));
.with_working_directory_materializer(RuntimeGitMaterializer::new(runtime_base.path()));
let runtime =
EmbeddedRuntime::with_execution_backend(RuntimeOptions::default(), Arc::new(backend))
.unwrap();
@@ -4023,8 +4017,8 @@ mod tests {
.create_working_directory(working_directory_request(repo.path()))
.unwrap();
let workdir_id = status.summary.working_directory_id.clone();
let worktree_root = materialized_worktree_root(runtime_base.path(), &workdir_id);
assert!(worktree_root.join("README.md").exists());
let clone_root = materialized_clone_root(runtime_base.path(), &workdir_id);
assert!(clone_root.join("README.md").exists());
let mut request = create_request("chat");
request.working_directory = Some(WorkingDirectoryClaim {
working_directory_id: workdir_id.clone(),
@@ -4034,7 +4028,7 @@ mod tests {
let error = runtime.create_worker(request).unwrap_err();
assert!(format!("{error:?}").contains("spawn failed"));
assert!(worktree_root.join("README.md").exists());
assert!(clone_root.join("README.md").exists());
let status = runtime.working_directory(&workdir_id).unwrap();
assert_eq!(
status.summary.status,
@@ -4048,9 +4042,7 @@ mod tests {
let repo = create_clean_repo();
let backend = WorkerRuntimeExecutionBackend::new(FailingFactory)
.unwrap()
.with_working_directory_materializer(RuntimeGitCacheMaterializer::new(
runtime_base.path(),
));
.with_working_directory_materializer(RuntimeGitMaterializer::new(runtime_base.path()));
let runtime =
EmbeddedRuntime::with_execution_backend(RuntimeOptions::default(), Arc::new(backend))
.unwrap();
@@ -4071,6 +4063,6 @@ mod tests {
})
.unwrap_or(0);
assert_eq!(remaining_workdirs, 0);
assert!(working_directories_root.join(".repository-cache").is_dir());
assert!(!working_directories_root.join(".repository-cache").exists());
}
}
+126 -409
View File
@@ -24,7 +24,6 @@ use workdir::WorkdirSessionResource;
const CHECKOUT_DIR: &str = "checkout";
const MATERIALIZATION_RECORD: &str = "materialization.json";
const REPOSITORY_CACHE_DIR: &str = ".repository-cache";
const REPOSITORY_ACCESS_DIR: &str = ".repository-access";
const REPOSITORY_COMMAND_TIMEOUT: Duration = Duration::from_secs(300);
const REPOSITORY_SSH_CONNECT_TIMEOUT_SECONDS: &str = "10";
@@ -48,10 +47,6 @@ pub struct WorkingDirectoryEvidence {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub repository_source_fingerprint: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub repository_cache_key: Option<String>,
#[serde(default)]
pub cache_generation: u64,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub operation_id: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub credential_revision: Option<u64>,
@@ -99,7 +94,6 @@ pub struct WorkingDirectoryBinding {
pub root: PathBuf,
pub cwd: PathBuf,
working_directory_root: PathBuf,
source_repository_path: PathBuf,
command_environment: BTreeMap<String, String>,
session_resources: Vec<Arc<dyn WorkdirSessionResource>>,
}
@@ -117,10 +111,6 @@ impl WorkingDirectoryBinding {
&self.working_directory_root
}
pub fn source_repository_path(&self) -> &Path {
&self.source_repository_path
}
pub fn command_environment(&self) -> BTreeMap<String, String> {
self.command_environment.clone()
}
@@ -235,10 +225,7 @@ fn binding_paths_are_available(binding: &WorkingDirectoryBinding) -> bool {
if !root.is_dir() {
return false;
}
let Ok(source_repository_path) = binding.source_repository_path.canonicalize() else {
return false;
};
source_repository_path.is_dir()
root.join(".git").is_dir()
}
fn binding_current_revision(
@@ -271,18 +258,16 @@ fn binding_cleanliness(binding: &WorkingDirectoryBinding) -> String {
}
#[derive(Clone, Debug)]
pub struct RuntimeGitCacheMaterializer {
pub struct RuntimeGitMaterializer {
runtime_root: PathBuf,
repository_access: Arc<Mutex<HashMap<String, RepositorySshMaterializationAccess>>>,
cache_locks: Arc<Mutex<HashMap<String, Arc<Mutex<()>>>>>,
}
impl RuntimeGitCacheMaterializer {
impl RuntimeGitMaterializer {
pub fn new(runtime_root: impl Into<PathBuf>) -> Self {
Self {
runtime_root: runtime_root.into(),
repository_access: Arc::new(Mutex::new(HashMap::new())),
cache_locks: Arc::new(Mutex::new(HashMap::new())),
}
}
@@ -298,33 +283,6 @@ impl RuntimeGitCacheMaterializer {
self.runtime_root.join(working_directory_id)
}
fn repository_cache_key(request: &WorkingDirectoryRequest) -> String {
let mut digest = Sha256::new();
if let Some(materialization) = &request.materialization {
digest.update(materialization.workspace_id.as_bytes());
digest.update([0]);
digest.update(materialization.cache_generation.to_be_bytes());
}
digest.update(request.repository.id.as_bytes());
digest.update([0]);
digest.update(request.repository.source.kind.as_str().as_bytes());
digest.update([0]);
digest.update(request.repository.source_revision.to_be_bytes());
digest.update([0]);
digest.update(request.repository.source_fingerprint.as_bytes());
digest
.finalize()
.iter()
.map(|byte| format!("{byte:02x}"))
.collect()
}
fn repository_cache_path(&self, request: &WorkingDirectoryRequest) -> PathBuf {
self.runtime_root
.join(REPOSITORY_CACHE_DIR)
.join(format!("{}.git", Self::repository_cache_key(request)))
}
fn corrupted_status(&self, working_directory_id: &str) -> WorkingDirectoryStatus {
WorkingDirectoryStatus {
summary: WorkingDirectorySummary {
@@ -337,9 +295,9 @@ impl RuntimeGitCacheMaterializer {
current_ref: None,
current_tree: None,
observed_at_epoch_seconds: None,
materializer_kind: MaterializerKind::RuntimeGitCache,
materializer_kind: MaterializerKind::RuntimeGitClone,
cleanup_target: Some(WorkingDirectoryCleanupTarget {
kind: "runtime_git_cache_worktree".to_string(),
kind: "runtime_git_clone".to_string(),
working_directory_id: working_directory_id.to_string(),
repository_id: "unknown".to_string(),
}),
@@ -358,7 +316,6 @@ impl RuntimeGitCacheMaterializer {
let record = WorkingDirectoryMaterializationRecord {
working_directory: binding.working_directory.clone(),
root: binding.root.clone(),
source_repository_path: binding.source_repository_path.clone(),
};
let path = binding.working_directory_root.join(MATERIALIZATION_RECORD);
let raw = serde_json::to_vec_pretty(&record).map_err(|error| {
@@ -398,13 +355,12 @@ impl RuntimeGitCacheMaterializer {
root: record.root.clone(),
cwd: record.root,
working_directory_root,
source_repository_path: record.source_repository_path,
command_environment: BTreeMap::new(),
session_resources: Vec::new(),
})
}
fn cache_repository_access(
fn store_repository_access(
&self,
working_directory_id: &str,
ssh: &RepositorySshMaterializationAccess,
@@ -490,11 +446,7 @@ impl RuntimeGitCacheMaterializer {
return Ok(binding);
};
validate_ssh_materialization_access(&access)?;
apply_worktree_access_policy(
binding.source_repository_path(),
binding.root(),
access.access,
)?;
apply_repository_access_policy(binding.root(), access.access)?;
let command_access = Arc::new(RepositoryCommandAccess::prepare_ssh(
&self.runtime_root,
&format!("attachment-{working_directory_id}"),
@@ -537,10 +489,7 @@ impl RuntimeGitCacheMaterializer {
fn validate_request(
request: &WorkingDirectoryRequest,
) -> Result<(), WorkingDirectoryDiagnostic> {
if !matches!(
request.materializer,
MaterializerKind::RuntimeGitCache | MaterializerKind::LocalGitWorktree
) {
if !matches!(request.materializer, MaterializerKind::RuntimeGitClone) {
return Err(WorkingDirectoryDiagnostic::new(
"working_directory_materializer_unsupported",
"the requested working directory materializer is unsupported",
@@ -605,120 +554,6 @@ impl RuntimeGitCacheMaterializer {
validate_selector(request.repository.selector.as_deref().unwrap_or("HEAD"))
}
fn ensure_repository_cache(
&self,
request: &WorkingDirectoryRequest,
) -> Result<PathBuf, WorkingDirectoryDiagnostic> {
Self::validate_request(request)?;
let cache_key = Self::repository_cache_key(request);
let cache_lock = self
.cache_locks
.lock()
.map_err(|_| {
WorkingDirectoryDiagnostic::new(
"working_directory_repository_cache_unavailable",
"Runtime Repository cache coordination is unavailable",
)
})?
.entry(cache_key)
.or_insert_with(|| Arc::new(Mutex::new(())))
.clone();
let _cache_guard = cache_lock.lock().map_err(|_| {
WorkingDirectoryDiagnostic::new(
"working_directory_repository_cache_unavailable",
"Runtime Repository cache coordination is unavailable",
)
})?;
let cache_path = self.repository_cache_path(request);
let cache_parent = cache_path.parent().ok_or_else(|| {
WorkingDirectoryDiagnostic::new(
"working_directory_repository_cache_invalid",
"Runtime Repository cache path is invalid",
)
})?;
fs::create_dir_all(cache_parent).map_err(|_| {
WorkingDirectoryDiagnostic::new(
"working_directory_repository_cache_create_failed",
"Runtime Repository cache could not be created; backend-private path details were omitted",
)
})?;
let access = RepositoryCommandAccess::prepare(&self.runtime_root, request)?;
if cache_path.exists() {
if git_dir_stdout(&cache_path, ["rev-parse", "--is-bare-repository"])? != "true"
|| git_dir_stdout(&cache_path, ["remote", "get-url", "origin"])?
!= request.repository.source.uri
{
return Err(WorkingDirectoryDiagnostic::new(
"working_directory_repository_cache_identity_mismatch",
"Runtime Repository cache identity does not match the requested source",
));
}
fetch_repository_cache(request, access.as_ref(), &cache_path)?;
} else {
let staging = cache_path.with_extension(format!(
"staging-{}",
next_working_directory_id(&request.repository.id)
));
if staging.exists() {
let _ = fs::remove_dir_all(&staging);
}
fs::create_dir_all(&staging).map_err(|_| {
WorkingDirectoryDiagnostic::new(
"working_directory_repository_cache_create_failed",
"Runtime Repository cache could not be created; backend-private path details were omitted",
)
})?;
let mut init = isolated_git_command();
init.args(["init", "--bare"]).arg(&staging);
let mut add_origin = repository_git_command(request, access.as_ref());
add_origin
.arg("--git-dir")
.arg(&staging)
.args(["remote", "add", "origin"])
.arg(&request.repository.source.uri);
let mut configure_fetch = isolated_git_command();
configure_fetch.arg("--git-dir").arg(&staging).args([
"config",
"remote.origin.fetch",
"+refs/heads/*:refs/remotes/origin/*",
]);
let initialized =
run_repository_git(init, "working_directory_repository_cache_create_failed")
.and_then(|_| {
run_repository_git(
add_origin,
"working_directory_repository_cache_create_failed",
)
})
.and_then(|_| {
run_repository_git(
configure_fetch,
"working_directory_repository_cache_create_failed",
)
})
.and_then(|_| fetch_repository_cache(request, access.as_ref(), &staging));
if let Err(error) = initialized {
let _ = fs::remove_dir_all(&staging);
return Err(error);
}
if let Err(error) = fs::rename(&staging, &cache_path) {
let _ = fs::remove_dir_all(&staging);
if !cache_path.exists() {
return Err(WorkingDirectoryDiagnostic::new(
"working_directory_repository_cache_publish_failed",
format!(
"Runtime Repository cache could not be published: {}",
error.kind()
),
));
}
}
}
validate_repository_cache_limits(&cache_path)?;
Ok(cache_path)
}
fn request_with_authorized_repository_access(
&self,
working_directory_id: &str,
@@ -789,17 +624,11 @@ impl RuntimeGitCacheMaterializer {
validate_working_directory_id(&working_directory_id)?;
let request =
self.request_with_authorized_repository_access(&working_directory_id, request)?;
let repository_cache = self.ensure_repository_cache(&request)?;
Self::validate_request(&request)?;
let selector = request.repository.selector.as_deref().unwrap_or("HEAD");
let resolved_commit = resolve_cached_commit(&repository_cache, selector)?;
let tree_spec = format!("{resolved_commit}^{{tree}}");
let resolved_tree = git_dir_stdout(&repository_cache, ["rev-parse", tree_spec.as_str()])
.ok()
.filter(|value| !value.is_empty());
let working_directory_root = self.working_directory_root(&working_directory_id);
let worktree_root = working_directory_root.join(CHECKOUT_DIR);
if worktree_root.exists() {
let checkout_root = working_directory_root.join(CHECKOUT_DIR);
if checkout_root.exists() {
return Err(WorkingDirectoryDiagnostic::new(
"working_directory_exists",
"working directory target already exists; cleanup or choose a new working_directory",
@@ -811,14 +640,47 @@ impl RuntimeGitCacheMaterializer {
"failed to create working directory; backend-private path details were omitted",
)
})?;
let mut command = isolated_git_command();
command
.arg("--git-dir")
.arg(&repository_cache)
.args(["worktree", "add", "--detach"])
.arg(&worktree_root)
.arg(&resolved_commit);
if let Err(error) = run_repository_git(command, "working_directory_git_failed") {
let access = RepositoryCommandAccess::prepare(&self.runtime_root, &request)?;
let mut clone = repository_git_command(&request, access.as_ref());
clone
.args([
"clone",
"--no-checkout",
"--no-hardlinks",
"--origin",
"origin",
"--",
])
.arg(&request.repository.source.uri)
.arg(&checkout_root);
if let Err(error) = run_repository_git(clone, "working_directory_repository_clone_failed") {
let _ = fs::remove_dir_all(&working_directory_root);
return Err(error);
}
drop(access);
if let Err(error) = validate_repository_limits(&checkout_root) {
let _ = fs::remove_dir_all(&working_directory_root);
return Err(error);
}
let resolved_commit = match resolve_cloned_commit(&checkout_root, selector) {
Ok(commit) => commit,
Err(error) => {
let _ = fs::remove_dir_all(&working_directory_root);
return Err(error);
}
};
let mut checkout = isolated_git_command();
checkout
.arg("-C")
.arg(&checkout_root)
.args(["checkout", "--detach"])
.arg(&resolved_commit)
.arg("--");
if let Err(error) =
run_repository_git(checkout, "working_directory_repository_checkout_failed")
{
let _ = fs::remove_dir_all(&working_directory_root);
return Err(error);
}
@@ -826,19 +688,21 @@ impl RuntimeGitCacheMaterializer {
.materialization
.as_ref()
.and_then(|materialization| materialization.ssh.as_ref())
&& let Err(error) =
apply_worktree_access_policy(&repository_cache, &worktree_root, ssh.access)
&& let Err(error) = apply_repository_access_policy(&checkout_root, ssh.access)
{
remove_cached_worktree(&repository_cache, &worktree_root);
let _ = fs::remove_dir_all(&working_directory_root);
return Err(error);
}
let tree_spec = format!("{resolved_commit}^{{tree}}");
let resolved_tree = git_stdout(&checkout_root, ["rev-parse", tree_spec.as_str()])
.ok()
.filter(|value| !value.is_empty());
let context = request.materialization.as_ref();
let working_directory = WorkingDirectory {
id: working_directory_id.clone(),
repository_id: request.repository.id.clone(),
materializer_kind: MaterializerKind::RuntimeGitCache,
materializer_kind: MaterializerKind::RuntimeGitClone,
evidence: WorkingDirectoryEvidence {
repository_id: request.repository.id.clone(),
requested_selector: request
@@ -848,13 +712,9 @@ impl RuntimeGitCacheMaterializer {
.map(|selector| selector.as_ref().to_string()),
resolved_commit,
resolved_tree,
materializer_kind: MaterializerKind::RuntimeGitCache,
materializer_kind: MaterializerKind::RuntimeGitClone,
repository_source_revision: Some(request.repository.source_revision),
repository_source_fingerprint: Some(request.repository.source_fingerprint.clone()),
repository_cache_key: Some(Self::repository_cache_key(&request)),
cache_generation: context
.map(|value| value.cache_generation)
.unwrap_or_default(),
operation_id: context.map(|value| value.operation_id.clone()),
credential_revision: context
.and_then(|value| value.ssh.as_ref())
@@ -867,7 +727,7 @@ impl RuntimeGitCacheMaterializer {
.map(str::to_string),
},
cleanup_target: WorkingDirectoryCleanupTarget {
kind: "runtime_git_cache_worktree".to_string(),
kind: "runtime_git_clone".to_string(),
working_directory_id: working_directory_id.clone(),
repository_id: request.repository.id.clone(),
},
@@ -875,15 +735,13 @@ impl RuntimeGitCacheMaterializer {
};
let binding = WorkingDirectoryBinding {
working_directory,
root: worktree_root.clone(),
cwd: worktree_root.clone(),
root: checkout_root.clone(),
cwd: checkout_root.clone(),
working_directory_root: working_directory_root.clone(),
source_repository_path: repository_cache.clone(),
command_environment: BTreeMap::new(),
session_resources: Vec::new(),
};
if let Err(error) = self.write_record(&binding) {
remove_cached_worktree(&repository_cache, &worktree_root);
let _ = fs::remove_dir_all(&working_directory_root);
return Err(error);
}
@@ -891,9 +749,8 @@ impl RuntimeGitCacheMaterializer {
.materialization
.as_ref()
.and_then(|materialization| materialization.ssh.as_ref())
&& let Err(error) = self.cache_repository_access(&working_directory_id, ssh)
&& let Err(error) = self.store_repository_access(&working_directory_id, ssh)
{
remove_cached_worktree(&repository_cache, &worktree_root);
let _ = fs::remove_dir_all(&working_directory_root);
return Err(error);
}
@@ -901,7 +758,7 @@ impl RuntimeGitCacheMaterializer {
}
}
impl WorkingDirectoryMaterializer for RuntimeGitCacheMaterializer {
impl WorkingDirectoryMaterializer for RuntimeGitMaterializer {
fn materialize(
&self,
worker_ref: &WorkerRef,
@@ -937,7 +794,7 @@ impl WorkingDirectoryMaterializer for RuntimeGitCacheMaterializer {
let mut binding = match self.read_binding(&request.working_directory_id) {
Ok(binding) => binding,
Err(error) if error.code == "working_directory_not_found" => {
return self.cache_repository_access(&request.working_directory_id, ssh);
return self.store_repository_access(&request.working_directory_id, ssh);
}
Err(error) => return Err(error),
};
@@ -973,7 +830,7 @@ impl WorkingDirectoryMaterializer for RuntimeGitCacheMaterializer {
);
binding.working_directory.evidence.host_trust_revision = Some(ssh.host_trust_revision);
self.write_record(&binding)?;
self.cache_repository_access(&request.working_directory_id, ssh)
self.store_repository_access(&request.working_directory_id, ssh)
}
fn observe_repository_ref(
@@ -985,7 +842,7 @@ impl WorkingDirectoryMaterializer for RuntimeGitCacheMaterializer {
let working_request = WorkingDirectoryRequest {
repository: request.repository.clone(),
materializer: MaterializerKind::RuntimeGitCache,
materializer: MaterializerKind::RuntimeGitClone,
backend_workdir_id: None,
materialization: request.materialization.clone(),
};
@@ -1175,32 +1032,11 @@ impl WorkingDirectoryMaterializer for RuntimeGitCacheMaterializer {
"working directory cleanup target is outside the working directory root",
));
}
let workspace_worktree_root_arg = path_str(&root)?;
let mut remove_command = isolated_git_command();
remove_command
.arg("--git-dir")
.arg(binding.source_repository_path())
.args([
"worktree",
"remove",
"--force",
workspace_worktree_root_arg.as_str(),
]);
let remove_result = run_repository_git(
remove_command,
"working_directory_cleanup_failed",
)
.or_else(|_| {
if root.exists() {
fs::remove_dir_all(&root).map_err(|_| {
WorkingDirectoryDiagnostic::new(
"working_directory_cleanup_failed",
"failed to remove working directory; backend-private path details were omitted",
)
})
} else {
Ok(())
}
let remove_result = fs::remove_dir_all(&working_directory_root).map_err(|_| {
WorkingDirectoryDiagnostic::new(
"working_directory_cleanup_failed",
"failed to remove working directory; backend-private path details were omitted",
)
});
if remove_result.is_err() {
working_directory.status = WorkingDirectoryStatusKind::CleanupPending;
@@ -1209,7 +1045,6 @@ impl WorkingDirectoryMaterializer for RuntimeGitCacheMaterializer {
root: binding.root.clone(),
cwd: binding.cwd.clone(),
working_directory_root: binding.working_directory_root.clone(),
source_repository_path: binding.source_repository_path.clone(),
command_environment: BTreeMap::new(),
session_resources: Vec::new(),
};
@@ -2589,13 +2424,13 @@ fn repository_git_failure_diagnostic(
WorkingDirectoryDiagnostic::new(code, message)
}
fn resolve_cached_commit(
repository_cache: &Path,
fn resolve_cloned_commit(
repository_root: &Path,
selector: &str,
) -> Result<String, WorkingDirectoryDiagnostic> {
let mut candidates = Vec::new();
if selector == "HEAD" {
candidates.push("FETCH_HEAD".to_string());
candidates.push("HEAD".to_string());
} else if let Some(branch) = selector.strip_prefix("refs/heads/") {
candidates.push(format!("refs/remotes/origin/{branch}"));
} else if selector.starts_with("refs/") || selector.bytes().all(|byte| byte.is_ascii_hexdigit())
@@ -2607,7 +2442,7 @@ fn resolve_cached_commit(
}
for candidate in candidates {
let spec = format!("{candidate}^{{commit}}");
if let Ok(commit) = git_dir_stdout(repository_cache, ["rev-parse", spec.as_str()])
if let Ok(commit) = git_stdout(repository_root, ["rev-parse", spec.as_str()])
&& !commit.is_empty()
{
return Ok(commit);
@@ -2619,65 +2454,34 @@ fn resolve_cached_commit(
))
}
fn fetch_repository_cache(
request: &WorkingDirectoryRequest,
access: Option<&RepositoryCommandAccess>,
repository_cache: &Path,
) -> Result<(), WorkingDirectoryDiagnostic> {
let mut refs = repository_git_command(request, access);
refs.arg("--git-dir").arg(repository_cache).args([
"fetch",
"--prune",
"--tags",
"origin",
"+refs/heads/*:refs/remotes/origin/*",
]);
let mut head = repository_git_command(request, access);
head.arg("--git-dir")
.arg(repository_cache)
.args(["fetch", "--no-tags", "origin", "HEAD"]);
run_repository_git(refs, "working_directory_repository_fetch_failed")?;
run_repository_git(head, "working_directory_repository_fetch_failed")
}
fn apply_worktree_access_policy(
repository_cache: &Path,
worktree_root: &Path,
fn apply_repository_access_policy(
repository_root: &Path,
access: workspace_api::RepositoryAccessMode,
) -> Result<(), WorkingDirectoryDiagnostic> {
let mut enable_worktree_config = isolated_git_command();
enable_worktree_config
.arg("--git-dir")
.arg(repository_cache)
.args(["config", "extensions.worktreeConfig", "true"]);
run_repository_git(
enable_worktree_config,
"working_directory_repository_policy_failed",
)?;
match access {
workspace_api::RepositoryAccessMode::ReadOnly => {
let mut disable_push = isolated_git_command();
disable_push.arg("-C").arg(worktree_root).args([
"config",
"--worktree",
"remote.origin.pushurl",
disable_push.arg("-C").arg(repository_root).args([
"remote",
"set-url",
"--push",
"origin",
"yoi-read-only://repository-push-disabled",
]);
run_repository_git(disable_push, "working_directory_repository_policy_failed")
}
workspace_api::RepositoryAccessMode::ReadWrite => {
if git_stdout(
worktree_root,
["config", "--worktree", "--get-all", "remote.origin.pushurl"],
repository_root,
["config", "--get-all", "remote.origin.pushurl"],
)
.is_err()
{
return Ok(());
}
let mut enable_push = isolated_git_command();
enable_push.arg("-C").arg(worktree_root).args([
enable_push.arg("-C").arg(repository_root).args([
"config",
"--worktree",
"--unset-all",
"remote.origin.pushurl",
]);
@@ -2686,10 +2490,8 @@ fn apply_worktree_access_policy(
}
}
fn validate_repository_cache_limits(
repository_cache: &Path,
) -> Result<(), WorkingDirectoryDiagnostic> {
let report = git_dir_stdout(repository_cache, ["count-objects", "-v"])?;
fn validate_repository_limits(repository_root: &Path) -> Result<(), WorkingDirectoryDiagnostic> {
let report = git_stdout(repository_root, ["count-objects", "-v"])?;
let mut objects = 0u64;
let mut kibibytes = 0u64;
for line in report.lines() {
@@ -2712,46 +2514,6 @@ fn validate_repository_cache_limits(
Ok(())
}
fn remove_cached_worktree(repository_cache: &Path, worktree_root: &Path) {
let mut command = isolated_git_command();
command
.arg("--git-dir")
.arg(repository_cache)
.args(["worktree", "remove", "--force"])
.arg(worktree_root);
let _ = run_repository_git(command, "working_directory_cleanup_failed");
}
fn git_dir_stdout<'a, I>(
repository_path: &Path,
args: I,
) -> Result<String, WorkingDirectoryDiagnostic>
where
I: IntoIterator<Item = &'a str>,
{
let mut command = isolated_git_command();
let output = command
.arg("--git-dir")
.arg(repository_path)
.args(args)
.stdin(Stdio::null())
.stderr(Stdio::null())
.output()
.map_err(|_| {
WorkingDirectoryDiagnostic::new(
"working_directory_git_unavailable",
"Git command could not be executed; backend-private path details were omitted",
)
})?;
if !output.status.success() || output.stdout.len() > 4096 {
return Err(WorkingDirectoryDiagnostic::new(
"working_directory_repository_selector_unresolved",
"configured Repository selector could not be resolved to a commit",
));
}
Ok(String::from_utf8_lossy(&output.stdout).trim().to_string())
}
fn write_owner_only(path: &Path, content: &[u8]) -> Result<(), WorkingDirectoryDiagnostic> {
let mut options = fs::OpenOptions::new();
options.create_new(true).write(true);
@@ -2814,7 +2576,6 @@ fn shell_quote_path(path: &Path) -> Result<String, WorkingDirectoryDiagnostic> {
struct WorkingDirectoryMaterializationRecord {
working_directory: WorkingDirectory,
root: PathBuf,
source_repository_path: PathBuf,
}
fn git_stdout<'a, I>(repository_path: &Path, args: I) -> Result<String, WorkingDirectoryDiagnostic>
@@ -3027,7 +2788,7 @@ mod tests {
source_fingerprint: "sha256:test".to_string(),
selector: Some(RepositorySelector::from("HEAD")),
},
materializer: MaterializerKind::RuntimeGitCache,
materializer: MaterializerKind::RuntimeGitClone,
backend_workdir_id: None,
materialization: None,
}
@@ -3042,7 +2803,7 @@ mod tests {
let repo = create_clean_repo();
git(repo.path(), &["branch", "published"]);
let runtime_root = tempfile::tempdir().unwrap();
let materializer = RuntimeGitCacheMaterializer::new(runtime_root.path());
let materializer = RuntimeGitMaterializer::new(runtime_root.path());
let repository = request(repo.path()).repository;
let observation_request = RepositoryRefObservationRequest {
repository,
@@ -3108,7 +2869,7 @@ mod tests {
git(&workdir, &["commit", "-m", "source first"]);
let runtime_root = tempfile::tempdir().unwrap();
let materializer = RuntimeGitCacheMaterializer::new(runtime_root.path());
let materializer = RuntimeGitMaterializer::new(runtime_root.path());
let repository = request(&provider).repository;
let observation_request = RepositoryRefObservationRequest {
repository,
@@ -3159,7 +2920,7 @@ mod tests {
fn repository_ref_observation_rejects_missing_and_non_branch_selectors() {
let repo = create_clean_repo();
let runtime_root = tempfile::tempdir().unwrap();
let materializer = RuntimeGitCacheMaterializer::new(runtime_root.path());
let materializer = RuntimeGitMaterializer::new(runtime_root.path());
let repository = request(repo.path()).repository;
let missing = materializer
@@ -3189,10 +2950,10 @@ mod tests {
}
#[test]
fn local_git_repo_materializes_detached_worktree_under_runtime_root() {
fn local_git_repo_materializes_self_contained_clone_under_runtime_root() {
let repo = create_clean_repo();
let runtime_root = tempfile::tempdir().unwrap();
let materializer = RuntimeGitCacheMaterializer::new(runtime_root.path());
let materializer = RuntimeGitMaterializer::new(runtime_root.path());
let binding = materializer
.materialize(&worker_ref(1), &request(repo.path()))
.unwrap();
@@ -3210,14 +2971,14 @@ mod tests {
binding.working_directory_root().join(CHECKOUT_DIR)
);
assert!(binding.root.join("README.md").exists());
assert!(binding.root.join(".git").is_dir());
assert!(!binding.root.join(".git/objects/info/alternates").exists());
assert!(git_stdout(binding.root(), ["status", "--porcelain"]).is_ok());
let branch = git_stdout(binding.root(), ["branch", "--show-current"]).unwrap();
assert!(
branch.is_empty(),
"worktree should be detached, got {branch}"
);
assert!(branch.is_empty(), "clone should be detached, got {branch}");
assert_eq!(
binding.working_directory.materializer_kind,
MaterializerKind::RuntimeGitCache
MaterializerKind::RuntimeGitClone
);
assert!(
binding
@@ -3231,7 +2992,7 @@ mod tests {
fn multiple_workers_materialize_distinct_paths_for_same_source_repo() {
let repo = create_clean_repo();
let runtime_root = tempfile::tempdir().unwrap();
let materializer = RuntimeGitCacheMaterializer::new(runtime_root.path());
let materializer = RuntimeGitMaterializer::new(runtime_root.path());
let first = materializer
.materialize(&worker_ref(1), &request(repo.path()))
.unwrap();
@@ -3251,7 +3012,7 @@ mod tests {
let repo = create_clean_repo();
fs::write(repo.path().join("dirty.txt"), "dirty\n").unwrap();
let runtime_root = tempfile::tempdir().unwrap();
let materializer = RuntimeGitCacheMaterializer::new(runtime_root.path());
let materializer = RuntimeGitMaterializer::new(runtime_root.path());
let binding = materializer
.materialize(&worker_ref(1), &request(repo.path()))
@@ -3267,7 +3028,7 @@ mod tests {
git(repo.path(), &["branch", "pinned"]);
fs::write(repo.path().join("dirty.txt"), "dirty\n").unwrap();
let runtime_root = tempfile::tempdir().unwrap();
let materializer = RuntimeGitCacheMaterializer::new(runtime_root.path());
let materializer = RuntimeGitMaterializer::new(runtime_root.path());
let mut request = request(repo.path());
request.repository.selector = Some(RepositorySelector::from("pinned"));
@@ -3286,10 +3047,10 @@ mod tests {
}
#[test]
fn file_and_local_sources_share_the_runtime_cache_pipeline() {
fn file_and_local_sources_create_independent_clones() {
let repo = create_clean_repo();
let runtime_root = tempfile::tempdir().unwrap();
let materializer = RuntimeGitCacheMaterializer::new(runtime_root.path());
let materializer = RuntimeGitMaterializer::new(runtime_root.path());
let local = materializer
.materialize(&worker_ref(1), &request(repo.path()))
.unwrap();
@@ -3297,31 +3058,10 @@ mod tests {
.materialize(&worker_ref(2), &request(repo.path()))
.unwrap();
assert_eq!(
local.working_directory.evidence.repository_cache_key,
second.working_directory.evidence.repository_cache_key
);
assert_eq!(
git_dir_stdout(
local.source_repository_path(),
["config", "--get-all", "remote.origin.fetch"],
)
.unwrap(),
"+refs/heads/*:refs/remotes/origin/*"
);
assert!(
git_dir_stdout(
local.source_repository_path(),
["config", "--get", "remote.origin.mirror"],
)
.is_err()
);
assert_eq!(
fs::read_dir(runtime_root.path().join(REPOSITORY_CACHE_DIR))
.unwrap()
.count(),
1
);
assert!(local.root.join(".git").is_dir());
assert!(second.root.join(".git").is_dir());
assert_ne!(local.root.join(".git"), second.root.join(".git"));
assert!(!runtime_root.path().join(".repository-cache").exists());
let mut file_request = request(repo.path());
file_request.repository.source.kind = workspace_api::RepositorySourceKind::File;
@@ -3332,17 +3072,15 @@ mod tests {
.materialize(&worker_ref(3), &file_request)
.unwrap();
assert!(file.root.join("README.md").exists());
assert_ne!(
local.working_directory.evidence.repository_cache_key,
file.working_directory.evidence.repository_cache_key
);
assert!(file.root.join(".git").is_dir());
assert!(git_stdout(&file.root, ["status", "--porcelain"]).is_ok());
}
#[test]
fn materialization_context_is_audited_without_secret_values() {
let repo = create_clean_repo();
let runtime_root = tempfile::tempdir().unwrap();
let materializer = RuntimeGitCacheMaterializer::new(runtime_root.path());
let materializer = RuntimeGitMaterializer::new(runtime_root.path());
let mut request = request(repo.path());
request.materialization = Some(crate::catalog::RepositoryMaterializationContext {
workspace_id: "workspace-1".to_string(),
@@ -3350,7 +3088,6 @@ mod tests {
operation_id: "operation-1".to_string(),
config_revision: 7,
config_projection_digest: "sha256:projection".to_string(),
cache_generation: 3,
ssh: None,
});
@@ -3359,7 +3096,6 @@ mod tests {
binding.working_directory.evidence.operation_id.as_deref(),
Some("operation-1")
);
assert_eq!(binding.working_directory.evidence.cache_generation, 3);
let record = fs::read_to_string(
binding
.working_directory_root()
@@ -3430,7 +3166,7 @@ mod tests {
.unwrap();
assert!(status.success());
}
let materializer = RuntimeGitCacheMaterializer::new(runtime_root.path());
let materializer = RuntimeGitMaterializer::new(runtime_root.path());
let mut request = request(repo.path());
request.materialization = Some(crate::catalog::RepositoryMaterializationContext {
workspace_id: "workspace-1".to_string(),
@@ -3438,7 +3174,6 @@ mod tests {
operation_id: "operation-agent".to_string(),
config_revision: 2,
config_projection_digest: "sha256:projection".to_string(),
cache_generation: 0,
ssh: Some(crate::catalog::RepositorySshMaterializationAccess {
credential_candidates: vec![
crate::catalog::RepositorySshCredentialCandidate {
@@ -3754,11 +3489,7 @@ mod tests {
&read_only_command_policy,
));
assert_eq!(
git_stdout(
rebound.root(),
["config", "--worktree", "--get", "remote.origin.pushurl"],
)
.unwrap(),
git_stdout(rebound.root(), ["config", "--get", "remote.origin.pushurl"],).unwrap(),
"yoi-read-only://repository-push-disabled"
);
drop(rebound);
@@ -3796,13 +3527,7 @@ mod tests {
],
&read_write_command_policy,
));
assert!(
git_stdout(
rebound.root(),
["config", "--worktree", "--get", "remote.origin.pushurl"],
)
.is_err()
);
assert!(git_stdout(rebound.root(), ["config", "--get", "remote.origin.pushurl"],).is_err());
drop(rebound);
let mut expired = initial_materialization;
@@ -3817,7 +3542,7 @@ mod tests {
.code,
"working_directory_repository_access_expired"
);
let restored = RuntimeGitCacheMaterializer::new(runtime_root.path());
let restored = RuntimeGitMaterializer::new(runtime_root.path());
assert_eq!(
restored.bind_working_directory(&id, None).unwrap_err().code,
"working_directory_remote_repository_access_required"
@@ -3828,7 +3553,7 @@ mod tests {
fn read_only_repository_access_disables_default_push_target() {
let repo = create_clean_repo();
let runtime_root = tempfile::tempdir().unwrap();
let materializer = RuntimeGitCacheMaterializer::new(runtime_root.path());
let materializer = RuntimeGitMaterializer::new(runtime_root.path());
let mut request = request(repo.path());
request.materialization = Some(crate::catalog::RepositoryMaterializationContext {
workspace_id: "workspace-1".to_string(),
@@ -3836,7 +3561,6 @@ mod tests {
operation_id: "operation-read-only".to_string(),
config_revision: 2,
config_projection_digest: "sha256:projection".to_string(),
cache_generation: 0,
ssh: Some(crate::catalog::RepositorySshMaterializationAccess {
credential_candidates: vec![crate::catalog::RepositorySshCredentialCandidate {
credential_id: "credential-1".to_string(),
@@ -3859,11 +3583,7 @@ mod tests {
let binding = materializer.create(&request).unwrap();
assert_eq!(
git_stdout(
binding.root(),
["config", "--worktree", "--get", "remote.origin.pushurl"],
)
.unwrap(),
git_stdout(binding.root(), ["config", "--get", "remote.origin.pushurl"],).unwrap(),
"yoi-read-only://repository-push-disabled"
);
}
@@ -3872,7 +3592,7 @@ mod tests {
fn selector_is_not_accepted_as_a_git_option_or_refspec() {
let repo = create_clean_repo();
let runtime_root = tempfile::tempdir().unwrap();
let materializer = RuntimeGitCacheMaterializer::new(runtime_root.path());
let materializer = RuntimeGitMaterializer::new(runtime_root.path());
for selector in ["--upload-pack=evil", "refs/heads/main:evil", "main@{1}"] {
let mut request = request(repo.path());
request.repository.selector = Some(RepositorySelector::from(selector));
@@ -4011,7 +3731,6 @@ mod tests {
operation_id: "operation-1".to_string(),
config_revision: 1,
config_projection_digest: "sha256:projection".to_string(),
cache_generation: 0,
ssh: Some(access),
});
validate_remote_source_uri(&request).unwrap();
@@ -4034,14 +3753,13 @@ mod tests {
fn remote_source_rejects_uri_credentials_and_mismatched_host_trust() {
let repo = create_clean_repo();
let runtime_root = tempfile::tempdir().unwrap();
let materializer = RuntimeGitCacheMaterializer::new(runtime_root.path());
let materializer = RuntimeGitMaterializer::new(runtime_root.path());
let context = |ssh| crate::catalog::RepositoryMaterializationContext {
workspace_id: "workspace-1".to_string(),
runtime_id: "runtime-1".to_string(),
operation_id: "operation-1".to_string(),
config_revision: 1,
config_projection_digest: "sha256:projection".to_string(),
cache_generation: 0,
ssh,
};
@@ -4065,7 +3783,7 @@ mod tests {
uri: "http://example.test/repo.git".to_string(),
};
http.materialization = Some(context(None));
RuntimeGitCacheMaterializer::validate_request(&http).unwrap();
RuntimeGitMaterializer::validate_request(&http).unwrap();
assert_eq!(
repository_transport_warning(http.repository.source.kind),
Some("plain_http_transport")
@@ -4108,7 +3826,7 @@ mod tests {
#[test]
fn unsupported_remote_and_non_git_provider_return_typed_diagnostics() {
let runtime_root = tempfile::tempdir().unwrap();
let materializer = RuntimeGitCacheMaterializer::new(runtime_root.path());
let materializer = RuntimeGitMaterializer::new(runtime_root.path());
let mut remote = request(Path::new("."));
remote.repository.source = workspace_api::RepositorySource {
kind: workspace_api::RepositorySourceKind::Ssh,
@@ -4120,7 +3838,6 @@ mod tests {
operation_id: "operation-1".to_string(),
config_revision: 1,
config_projection_digest: "sha256:projection".to_string(),
cache_generation: 0,
ssh: None,
});
let error = materializer
@@ -4151,7 +3868,7 @@ mod tests {
git(repo.path(), &["add", "crates/yoi/lib.rs"]);
git(repo.path(), &["commit", "-m", "add crate"]);
let runtime_root = tempfile::tempdir().unwrap();
let materializer = RuntimeGitCacheMaterializer::new(runtime_root.path());
let materializer = RuntimeGitMaterializer::new(runtime_root.path());
let working_directory = materializer.create(&request(repo.path())).unwrap();
let bound = materializer
@@ -4176,7 +3893,7 @@ mod tests {
fn working_directory_observes_current_selector_and_ref_without_changing_creation_evidence() {
let repo = create_clean_repo();
let runtime_root = tempfile::tempdir().unwrap();
let materializer = RuntimeGitCacheMaterializer::new(runtime_root.path());
let materializer = RuntimeGitMaterializer::new(runtime_root.path());
let working_directory = materializer.create(&request(repo.path())).unwrap();
let bound = materializer
.bind_working_directory(&working_directory.working_directory.id, None)
@@ -4220,7 +3937,7 @@ mod tests {
git(repo.path(), &["add", "inside/file.txt"]);
git(repo.path(), &["commit", "-m", "add inside"]);
let runtime_root = tempfile::tempdir().unwrap();
let materializer = RuntimeGitCacheMaterializer::new(runtime_root.path());
let materializer = RuntimeGitMaterializer::new(runtime_root.path());
let working_directory = materializer.create(&request(repo.path())).unwrap();
assert_eq!(
@@ -4269,10 +3986,10 @@ mod tests {
}
#[test]
fn cleanup_working_directory_removes_worktree_and_record() {
fn cleanup_working_directory_removes_clone_and_record() {
let repo = create_clean_repo();
let runtime_root = tempfile::tempdir().unwrap();
let materializer = RuntimeGitCacheMaterializer::new(runtime_root.path());
let materializer = RuntimeGitMaterializer::new(runtime_root.path());
let binding = materializer
.materialize(&worker_ref(1), &request(repo.path()))
.unwrap();