feat: replace linked worktrees with runtime clones
This commit is contained in:
@@ -913,7 +913,7 @@ mod tests {
|
|||||||
"working_directory": {
|
"working_directory": {
|
||||||
"working_directory_id": "wd-1",
|
"working_directory_id": "wd-1",
|
||||||
"repository_key": "main",
|
"repository_key": "main",
|
||||||
"materializer_kind": "local_git_worktree",
|
"materializer_kind": "runtime_git_clone",
|
||||||
"status": "active",
|
"status": "active",
|
||||||
"occupied_by": {
|
"occupied_by": {
|
||||||
"runtime_id": "arcadia",
|
"runtime_id": "arcadia",
|
||||||
|
|||||||
@@ -475,7 +475,7 @@ mod tests {
|
|||||||
serde_json::from_value(serde_json::json!({
|
serde_json::from_value(serde_json::json!({
|
||||||
"working_directory_id": "001a06a9f0202000000",
|
"working_directory_id": "001a06a9f0202000000",
|
||||||
"repository_key": "main",
|
"repository_key": "main",
|
||||||
"materializer_kind": "local_git_worktree",
|
"materializer_kind": "runtime_git_clone",
|
||||||
"status": "active",
|
"status": "active",
|
||||||
"cleanliness": "clean"
|
"cleanliness": "clean"
|
||||||
}))
|
}))
|
||||||
@@ -518,7 +518,7 @@ mod tests {
|
|||||||
serde_json::from_value(serde_json::json!({
|
serde_json::from_value(serde_json::json!({
|
||||||
"working_directory_id": "workdir-1",
|
"working_directory_id": "workdir-1",
|
||||||
"repository_key": "main",
|
"repository_key": "main",
|
||||||
"materializer_kind": "local_git_worktree",
|
"materializer_kind": "runtime_git_clone",
|
||||||
"status": "active"
|
"status": "active"
|
||||||
}))
|
}))
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
|
|||||||
@@ -148,8 +148,6 @@ pub struct RepositoryMaterializationContext {
|
|||||||
pub operation_id: String,
|
pub operation_id: String,
|
||||||
pub config_revision: u64,
|
pub config_revision: u64,
|
||||||
pub config_projection_digest: String,
|
pub config_projection_digest: String,
|
||||||
#[serde(default)]
|
|
||||||
pub cache_generation: u64,
|
|
||||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||||
pub ssh: Option<RepositorySshMaterializationAccess>,
|
pub ssh: Option<RepositorySshMaterializationAccess>,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ use worker_runtime::http_server::{
|
|||||||
WorkspaceRuntimeHttpAuth,
|
WorkspaceRuntimeHttpAuth,
|
||||||
};
|
};
|
||||||
use worker_runtime::worker_backend::{ProfileRuntimeWorkerFactory, WorkerRuntimeExecutionBackend};
|
use worker_runtime::worker_backend::{ProfileRuntimeWorkerFactory, WorkerRuntimeExecutionBackend};
|
||||||
use worker_runtime::working_directory::RuntimeGitCacheMaterializer;
|
use worker_runtime::working_directory::RuntimeGitMaterializer;
|
||||||
use worker_runtime::workspace_issuer::{
|
use worker_runtime::workspace_issuer::{
|
||||||
FileWorkspaceClaimReplayProtection, FileWorkspaceRuntimeVerificationAuthority,
|
FileWorkspaceClaimReplayProtection, FileWorkspaceRuntimeVerificationAuthority,
|
||||||
MAX_WORKSPACE_ISSUER_TRUST_RECORDS, RuntimeVerificationSigner, WorkspaceCapabilityVerifier,
|
MAX_WORKSPACE_ISSUER_TRUST_RECORDS, RuntimeVerificationSigner, WorkspaceCapabilityVerifier,
|
||||||
@@ -266,7 +266,7 @@ fn build_runtime(config: &ProcessConfig) -> Result<Runtime, ProcessError> {
|
|||||||
let backend = Arc::new(
|
let backend = Arc::new(
|
||||||
WorkerRuntimeExecutionBackend::new(factory)
|
WorkerRuntimeExecutionBackend::new(factory)
|
||||||
.map_err(ProcessError::WorkerAdapter)?
|
.map_err(ProcessError::WorkerAdapter)?
|
||||||
.with_working_directory_materializer(RuntimeGitCacheMaterializer::new(
|
.with_working_directory_materializer(RuntimeGitMaterializer::new(
|
||||||
fs_paths.workdir_target.clone(),
|
fs_paths.workdir_target.clone(),
|
||||||
)),
|
)),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -4020,7 +4020,7 @@ mod tests {
|
|||||||
source_fingerprint: "sha256:source".to_string(),
|
source_fingerprint: "sha256:source".to_string(),
|
||||||
selector: None,
|
selector: None,
|
||||||
},
|
},
|
||||||
materializer: MaterializerKind::RuntimeGitCache,
|
materializer: MaterializerKind::RuntimeGitClone,
|
||||||
backend_workdir_id: Some("working-directory-1".to_string()),
|
backend_workdir_id: Some("working-directory-1".to_string()),
|
||||||
materialization: Some(RepositoryMaterializationContext {
|
materialization: Some(RepositoryMaterializationContext {
|
||||||
workspace_id: "workspace-1".to_string(),
|
workspace_id: "workspace-1".to_string(),
|
||||||
@@ -4028,7 +4028,6 @@ mod tests {
|
|||||||
operation_id: "operation-1".to_string(),
|
operation_id: "operation-1".to_string(),
|
||||||
config_revision: 1,
|
config_revision: 1,
|
||||||
config_projection_digest: "sha256:projection".to_string(),
|
config_projection_digest: "sha256:projection".to_string(),
|
||||||
cache_generation: 0,
|
|
||||||
ssh: Some(RepositorySshMaterializationAccess {
|
ssh: Some(RepositorySshMaterializationAccess {
|
||||||
credential_candidates: vec![RepositorySshCredentialCandidate {
|
credential_candidates: vec![RepositorySshCredentialCandidate {
|
||||||
credential_id: "credential-1".to_string(),
|
credential_id: "credential-1".to_string(),
|
||||||
@@ -4140,7 +4139,6 @@ mod tests {
|
|||||||
operation_id: "operation-1".to_string(),
|
operation_id: "operation-1".to_string(),
|
||||||
config_revision: 1,
|
config_revision: 1,
|
||||||
config_projection_digest: "sha256:projection".to_string(),
|
config_projection_digest: "sha256:projection".to_string(),
|
||||||
cache_generation: 0,
|
|
||||||
ssh: Some(RepositorySshMaterializationAccess {
|
ssh: Some(RepositorySshMaterializationAccess {
|
||||||
credential_candidates: vec![
|
credential_candidates: vec![
|
||||||
RepositorySshCredentialCandidate {
|
RepositorySshCredentialCandidate {
|
||||||
@@ -4253,7 +4251,7 @@ mod tests {
|
|||||||
source_fingerprint: "sha256:source".to_string(),
|
source_fingerprint: "sha256:source".to_string(),
|
||||||
selector: None,
|
selector: None,
|
||||||
},
|
},
|
||||||
materializer: MaterializerKind::RuntimeGitCache,
|
materializer: MaterializerKind::RuntimeGitClone,
|
||||||
backend_workdir_id: Some("working-directory-1".to_string()),
|
backend_workdir_id: Some("working-directory-1".to_string()),
|
||||||
materialization: Some(RepositoryMaterializationContext {
|
materialization: Some(RepositoryMaterializationContext {
|
||||||
workspace_id: "workspace-1".to_string(),
|
workspace_id: "workspace-1".to_string(),
|
||||||
@@ -4261,7 +4259,6 @@ mod tests {
|
|||||||
operation_id: "operation-create".to_string(),
|
operation_id: "operation-create".to_string(),
|
||||||
config_revision: 1,
|
config_revision: 1,
|
||||||
config_projection_digest: "sha256:projection".to_string(),
|
config_projection_digest: "sha256:projection".to_string(),
|
||||||
cache_generation: 0,
|
|
||||||
ssh: Some(RepositorySshMaterializationAccess {
|
ssh: Some(RepositorySshMaterializationAccess {
|
||||||
credential_candidates: vec![
|
credential_candidates: vec![
|
||||||
RepositorySshCredentialCandidate {
|
RepositorySshCredentialCandidate {
|
||||||
|
|||||||
@@ -2248,7 +2248,7 @@ mod tests {
|
|||||||
use crate::identity::WorkerRef;
|
use crate::identity::WorkerRef;
|
||||||
use crate::management::RuntimeOptions;
|
use crate::management::RuntimeOptions;
|
||||||
use crate::observation::WorkerObservationCursor;
|
use crate::observation::WorkerObservationCursor;
|
||||||
use crate::working_directory::RuntimeGitCacheMaterializer;
|
use crate::working_directory::RuntimeGitMaterializer;
|
||||||
use agen::Engine;
|
use agen::Engine;
|
||||||
use agen::llm_client::event::{Event as LlmEvent, ResponseStatus, StatusEvent};
|
use agen::llm_client::event::{Event as LlmEvent, ResponseStatus, StatusEvent};
|
||||||
use agen::llm_client::{ClientError, LlmClient, Request};
|
use agen::llm_client::{ClientError, LlmClient, Request};
|
||||||
@@ -2992,13 +2992,13 @@ mod tests {
|
|||||||
source_fingerprint: "sha256:test".to_string(),
|
source_fingerprint: "sha256:test".to_string(),
|
||||||
selector: Some(RepositorySelector::from("HEAD")),
|
selector: Some(RepositorySelector::from("HEAD")),
|
||||||
},
|
},
|
||||||
materializer: MaterializerKind::RuntimeGitCache,
|
materializer: MaterializerKind::RuntimeGitClone,
|
||||||
backend_workdir_id: None,
|
backend_workdir_id: None,
|
||||||
materialization: None,
|
materialization: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn materialized_worktree_root(
|
fn materialized_clone_root(
|
||||||
runtime_base: &std::path::Path,
|
runtime_base: &std::path::Path,
|
||||||
working_directory_id: &str,
|
working_directory_id: &str,
|
||||||
) -> PathBuf {
|
) -> PathBuf {
|
||||||
@@ -3734,9 +3734,7 @@ mod tests {
|
|||||||
};
|
};
|
||||||
let backend = WorkerRuntimeExecutionBackend::new(factory)
|
let backend = WorkerRuntimeExecutionBackend::new(factory)
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.with_working_directory_materializer(RuntimeGitCacheMaterializer::new(
|
.with_working_directory_materializer(RuntimeGitMaterializer::new(runtime_base.path()));
|
||||||
runtime_base.path(),
|
|
||||||
));
|
|
||||||
let runtime =
|
let runtime =
|
||||||
EmbeddedRuntime::with_execution_backend(RuntimeOptions::default(), Arc::new(backend))
|
EmbeddedRuntime::with_execution_backend(RuntimeOptions::default(), Arc::new(backend))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
@@ -3973,9 +3971,7 @@ mod tests {
|
|||||||
};
|
};
|
||||||
let backend = WorkerRuntimeExecutionBackend::new(factory)
|
let backend = WorkerRuntimeExecutionBackend::new(factory)
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.with_working_directory_materializer(RuntimeGitCacheMaterializer::new(
|
.with_working_directory_materializer(RuntimeGitMaterializer::new(runtime_base.path()));
|
||||||
runtime_base.path(),
|
|
||||||
));
|
|
||||||
let runtime =
|
let runtime =
|
||||||
EmbeddedRuntime::with_execution_backend(RuntimeOptions::default(), Arc::new(backend))
|
EmbeddedRuntime::with_execution_backend(RuntimeOptions::default(), Arc::new(backend))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
@@ -3990,13 +3986,13 @@ mod tests {
|
|||||||
.summary
|
.summary
|
||||||
.working_directory_id
|
.working_directory_id
|
||||||
.clone();
|
.clone();
|
||||||
let worktree_root = materialized_worktree_root(runtime_base.path(), &workdir_id);
|
let clone_root = materialized_clone_root(runtime_base.path(), &workdir_id);
|
||||||
assert!(worktree_root.join("README.md").exists());
|
assert!(clone_root.join("README.md").exists());
|
||||||
|
|
||||||
runtime.stop_worker(&detail.worker_ref, None).unwrap();
|
runtime.stop_worker(&detail.worker_ref, None).unwrap();
|
||||||
runtime.delete_worker(&detail.worker_ref).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();
|
let status = runtime.working_directory(&workdir_id).unwrap();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
status.summary.status,
|
status.summary.status,
|
||||||
@@ -4012,9 +4008,7 @@ mod tests {
|
|||||||
let repo = create_clean_repo();
|
let repo = create_clean_repo();
|
||||||
let backend = WorkerRuntimeExecutionBackend::new(FailingFactory)
|
let backend = WorkerRuntimeExecutionBackend::new(FailingFactory)
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.with_working_directory_materializer(RuntimeGitCacheMaterializer::new(
|
.with_working_directory_materializer(RuntimeGitMaterializer::new(runtime_base.path()));
|
||||||
runtime_base.path(),
|
|
||||||
));
|
|
||||||
let runtime =
|
let runtime =
|
||||||
EmbeddedRuntime::with_execution_backend(RuntimeOptions::default(), Arc::new(backend))
|
EmbeddedRuntime::with_execution_backend(RuntimeOptions::default(), Arc::new(backend))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
@@ -4023,8 +4017,8 @@ mod tests {
|
|||||||
.create_working_directory(working_directory_request(repo.path()))
|
.create_working_directory(working_directory_request(repo.path()))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let workdir_id = status.summary.working_directory_id.clone();
|
let workdir_id = status.summary.working_directory_id.clone();
|
||||||
let worktree_root = materialized_worktree_root(runtime_base.path(), &workdir_id);
|
let clone_root = materialized_clone_root(runtime_base.path(), &workdir_id);
|
||||||
assert!(worktree_root.join("README.md").exists());
|
assert!(clone_root.join("README.md").exists());
|
||||||
let mut request = create_request("chat");
|
let mut request = create_request("chat");
|
||||||
request.working_directory = Some(WorkingDirectoryClaim {
|
request.working_directory = Some(WorkingDirectoryClaim {
|
||||||
working_directory_id: workdir_id.clone(),
|
working_directory_id: workdir_id.clone(),
|
||||||
@@ -4034,7 +4028,7 @@ mod tests {
|
|||||||
let error = runtime.create_worker(request).unwrap_err();
|
let error = runtime.create_worker(request).unwrap_err();
|
||||||
|
|
||||||
assert!(format!("{error:?}").contains("spawn failed"));
|
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();
|
let status = runtime.working_directory(&workdir_id).unwrap();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
status.summary.status,
|
status.summary.status,
|
||||||
@@ -4048,9 +4042,7 @@ mod tests {
|
|||||||
let repo = create_clean_repo();
|
let repo = create_clean_repo();
|
||||||
let backend = WorkerRuntimeExecutionBackend::new(FailingFactory)
|
let backend = WorkerRuntimeExecutionBackend::new(FailingFactory)
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.with_working_directory_materializer(RuntimeGitCacheMaterializer::new(
|
.with_working_directory_materializer(RuntimeGitMaterializer::new(runtime_base.path()));
|
||||||
runtime_base.path(),
|
|
||||||
));
|
|
||||||
let runtime =
|
let runtime =
|
||||||
EmbeddedRuntime::with_execution_backend(RuntimeOptions::default(), Arc::new(backend))
|
EmbeddedRuntime::with_execution_backend(RuntimeOptions::default(), Arc::new(backend))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
@@ -4071,6 +4063,6 @@ mod tests {
|
|||||||
})
|
})
|
||||||
.unwrap_or(0);
|
.unwrap_or(0);
|
||||||
assert_eq!(remaining_workdirs, 0);
|
assert_eq!(remaining_workdirs, 0);
|
||||||
assert!(working_directories_root.join(".repository-cache").is_dir());
|
assert!(!working_directories_root.join(".repository-cache").exists());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,7 +24,6 @@ use workdir::WorkdirSessionResource;
|
|||||||
|
|
||||||
const CHECKOUT_DIR: &str = "checkout";
|
const CHECKOUT_DIR: &str = "checkout";
|
||||||
const MATERIALIZATION_RECORD: &str = "materialization.json";
|
const MATERIALIZATION_RECORD: &str = "materialization.json";
|
||||||
const REPOSITORY_CACHE_DIR: &str = ".repository-cache";
|
|
||||||
const REPOSITORY_ACCESS_DIR: &str = ".repository-access";
|
const REPOSITORY_ACCESS_DIR: &str = ".repository-access";
|
||||||
const REPOSITORY_COMMAND_TIMEOUT: Duration = Duration::from_secs(300);
|
const REPOSITORY_COMMAND_TIMEOUT: Duration = Duration::from_secs(300);
|
||||||
const REPOSITORY_SSH_CONNECT_TIMEOUT_SECONDS: &str = "10";
|
const REPOSITORY_SSH_CONNECT_TIMEOUT_SECONDS: &str = "10";
|
||||||
@@ -48,10 +47,6 @@ pub struct WorkingDirectoryEvidence {
|
|||||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||||
pub repository_source_fingerprint: Option<String>,
|
pub repository_source_fingerprint: Option<String>,
|
||||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
#[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>,
|
pub operation_id: Option<String>,
|
||||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||||
pub credential_revision: Option<u64>,
|
pub credential_revision: Option<u64>,
|
||||||
@@ -99,7 +94,6 @@ pub struct WorkingDirectoryBinding {
|
|||||||
pub root: PathBuf,
|
pub root: PathBuf,
|
||||||
pub cwd: PathBuf,
|
pub cwd: PathBuf,
|
||||||
working_directory_root: PathBuf,
|
working_directory_root: PathBuf,
|
||||||
source_repository_path: PathBuf,
|
|
||||||
command_environment: BTreeMap<String, String>,
|
command_environment: BTreeMap<String, String>,
|
||||||
session_resources: Vec<Arc<dyn WorkdirSessionResource>>,
|
session_resources: Vec<Arc<dyn WorkdirSessionResource>>,
|
||||||
}
|
}
|
||||||
@@ -117,10 +111,6 @@ impl WorkingDirectoryBinding {
|
|||||||
&self.working_directory_root
|
&self.working_directory_root
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn source_repository_path(&self) -> &Path {
|
|
||||||
&self.source_repository_path
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn command_environment(&self) -> BTreeMap<String, String> {
|
pub fn command_environment(&self) -> BTreeMap<String, String> {
|
||||||
self.command_environment.clone()
|
self.command_environment.clone()
|
||||||
}
|
}
|
||||||
@@ -235,10 +225,7 @@ fn binding_paths_are_available(binding: &WorkingDirectoryBinding) -> bool {
|
|||||||
if !root.is_dir() {
|
if !root.is_dir() {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
let Ok(source_repository_path) = binding.source_repository_path.canonicalize() else {
|
root.join(".git").is_dir()
|
||||||
return false;
|
|
||||||
};
|
|
||||||
source_repository_path.is_dir()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn binding_current_revision(
|
fn binding_current_revision(
|
||||||
@@ -271,18 +258,16 @@ fn binding_cleanliness(binding: &WorkingDirectoryBinding) -> String {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct RuntimeGitCacheMaterializer {
|
pub struct RuntimeGitMaterializer {
|
||||||
runtime_root: PathBuf,
|
runtime_root: PathBuf,
|
||||||
repository_access: Arc<Mutex<HashMap<String, RepositorySshMaterializationAccess>>>,
|
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 {
|
pub fn new(runtime_root: impl Into<PathBuf>) -> Self {
|
||||||
Self {
|
Self {
|
||||||
runtime_root: runtime_root.into(),
|
runtime_root: runtime_root.into(),
|
||||||
repository_access: Arc::new(Mutex::new(HashMap::new())),
|
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)
|
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 {
|
fn corrupted_status(&self, working_directory_id: &str) -> WorkingDirectoryStatus {
|
||||||
WorkingDirectoryStatus {
|
WorkingDirectoryStatus {
|
||||||
summary: WorkingDirectorySummary {
|
summary: WorkingDirectorySummary {
|
||||||
@@ -337,9 +295,9 @@ impl RuntimeGitCacheMaterializer {
|
|||||||
current_ref: None,
|
current_ref: None,
|
||||||
current_tree: None,
|
current_tree: None,
|
||||||
observed_at_epoch_seconds: None,
|
observed_at_epoch_seconds: None,
|
||||||
materializer_kind: MaterializerKind::RuntimeGitCache,
|
materializer_kind: MaterializerKind::RuntimeGitClone,
|
||||||
cleanup_target: Some(WorkingDirectoryCleanupTarget {
|
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(),
|
working_directory_id: working_directory_id.to_string(),
|
||||||
repository_id: "unknown".to_string(),
|
repository_id: "unknown".to_string(),
|
||||||
}),
|
}),
|
||||||
@@ -358,7 +316,6 @@ impl RuntimeGitCacheMaterializer {
|
|||||||
let record = WorkingDirectoryMaterializationRecord {
|
let record = WorkingDirectoryMaterializationRecord {
|
||||||
working_directory: binding.working_directory.clone(),
|
working_directory: binding.working_directory.clone(),
|
||||||
root: binding.root.clone(),
|
root: binding.root.clone(),
|
||||||
source_repository_path: binding.source_repository_path.clone(),
|
|
||||||
};
|
};
|
||||||
let path = binding.working_directory_root.join(MATERIALIZATION_RECORD);
|
let path = binding.working_directory_root.join(MATERIALIZATION_RECORD);
|
||||||
let raw = serde_json::to_vec_pretty(&record).map_err(|error| {
|
let raw = serde_json::to_vec_pretty(&record).map_err(|error| {
|
||||||
@@ -398,13 +355,12 @@ impl RuntimeGitCacheMaterializer {
|
|||||||
root: record.root.clone(),
|
root: record.root.clone(),
|
||||||
cwd: record.root,
|
cwd: record.root,
|
||||||
working_directory_root,
|
working_directory_root,
|
||||||
source_repository_path: record.source_repository_path,
|
|
||||||
command_environment: BTreeMap::new(),
|
command_environment: BTreeMap::new(),
|
||||||
session_resources: Vec::new(),
|
session_resources: Vec::new(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn cache_repository_access(
|
fn store_repository_access(
|
||||||
&self,
|
&self,
|
||||||
working_directory_id: &str,
|
working_directory_id: &str,
|
||||||
ssh: &RepositorySshMaterializationAccess,
|
ssh: &RepositorySshMaterializationAccess,
|
||||||
@@ -490,11 +446,7 @@ impl RuntimeGitCacheMaterializer {
|
|||||||
return Ok(binding);
|
return Ok(binding);
|
||||||
};
|
};
|
||||||
validate_ssh_materialization_access(&access)?;
|
validate_ssh_materialization_access(&access)?;
|
||||||
apply_worktree_access_policy(
|
apply_repository_access_policy(binding.root(), access.access)?;
|
||||||
binding.source_repository_path(),
|
|
||||||
binding.root(),
|
|
||||||
access.access,
|
|
||||||
)?;
|
|
||||||
let command_access = Arc::new(RepositoryCommandAccess::prepare_ssh(
|
let command_access = Arc::new(RepositoryCommandAccess::prepare_ssh(
|
||||||
&self.runtime_root,
|
&self.runtime_root,
|
||||||
&format!("attachment-{working_directory_id}"),
|
&format!("attachment-{working_directory_id}"),
|
||||||
@@ -537,10 +489,7 @@ impl RuntimeGitCacheMaterializer {
|
|||||||
fn validate_request(
|
fn validate_request(
|
||||||
request: &WorkingDirectoryRequest,
|
request: &WorkingDirectoryRequest,
|
||||||
) -> Result<(), WorkingDirectoryDiagnostic> {
|
) -> Result<(), WorkingDirectoryDiagnostic> {
|
||||||
if !matches!(
|
if !matches!(request.materializer, MaterializerKind::RuntimeGitClone) {
|
||||||
request.materializer,
|
|
||||||
MaterializerKind::RuntimeGitCache | MaterializerKind::LocalGitWorktree
|
|
||||||
) {
|
|
||||||
return Err(WorkingDirectoryDiagnostic::new(
|
return Err(WorkingDirectoryDiagnostic::new(
|
||||||
"working_directory_materializer_unsupported",
|
"working_directory_materializer_unsupported",
|
||||||
"the requested working directory materializer is unsupported",
|
"the requested working directory materializer is unsupported",
|
||||||
@@ -605,120 +554,6 @@ impl RuntimeGitCacheMaterializer {
|
|||||||
validate_selector(request.repository.selector.as_deref().unwrap_or("HEAD"))
|
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(
|
fn request_with_authorized_repository_access(
|
||||||
&self,
|
&self,
|
||||||
working_directory_id: &str,
|
working_directory_id: &str,
|
||||||
@@ -789,17 +624,11 @@ impl RuntimeGitCacheMaterializer {
|
|||||||
validate_working_directory_id(&working_directory_id)?;
|
validate_working_directory_id(&working_directory_id)?;
|
||||||
let request =
|
let request =
|
||||||
self.request_with_authorized_repository_access(&working_directory_id, 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 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 working_directory_root = self.working_directory_root(&working_directory_id);
|
||||||
let worktree_root = working_directory_root.join(CHECKOUT_DIR);
|
let checkout_root = working_directory_root.join(CHECKOUT_DIR);
|
||||||
if worktree_root.exists() {
|
if checkout_root.exists() {
|
||||||
return Err(WorkingDirectoryDiagnostic::new(
|
return Err(WorkingDirectoryDiagnostic::new(
|
||||||
"working_directory_exists",
|
"working_directory_exists",
|
||||||
"working directory target already exists; cleanup or choose a new working_directory",
|
"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",
|
"failed to create working directory; backend-private path details were omitted",
|
||||||
)
|
)
|
||||||
})?;
|
})?;
|
||||||
let mut command = isolated_git_command();
|
|
||||||
command
|
let access = RepositoryCommandAccess::prepare(&self.runtime_root, &request)?;
|
||||||
.arg("--git-dir")
|
let mut clone = repository_git_command(&request, access.as_ref());
|
||||||
.arg(&repository_cache)
|
clone
|
||||||
.args(["worktree", "add", "--detach"])
|
.args([
|
||||||
.arg(&worktree_root)
|
"clone",
|
||||||
.arg(&resolved_commit);
|
"--no-checkout",
|
||||||
if let Err(error) = run_repository_git(command, "working_directory_git_failed") {
|
"--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);
|
let _ = fs::remove_dir_all(&working_directory_root);
|
||||||
return Err(error);
|
return Err(error);
|
||||||
}
|
}
|
||||||
@@ -826,19 +688,21 @@ impl RuntimeGitCacheMaterializer {
|
|||||||
.materialization
|
.materialization
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.and_then(|materialization| materialization.ssh.as_ref())
|
.and_then(|materialization| materialization.ssh.as_ref())
|
||||||
&& let Err(error) =
|
&& let Err(error) = apply_repository_access_policy(&checkout_root, ssh.access)
|
||||||
apply_worktree_access_policy(&repository_cache, &worktree_root, ssh.access)
|
|
||||||
{
|
{
|
||||||
remove_cached_worktree(&repository_cache, &worktree_root);
|
|
||||||
let _ = fs::remove_dir_all(&working_directory_root);
|
let _ = fs::remove_dir_all(&working_directory_root);
|
||||||
return Err(error);
|
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 context = request.materialization.as_ref();
|
||||||
let working_directory = WorkingDirectory {
|
let working_directory = WorkingDirectory {
|
||||||
id: working_directory_id.clone(),
|
id: working_directory_id.clone(),
|
||||||
repository_id: request.repository.id.clone(),
|
repository_id: request.repository.id.clone(),
|
||||||
materializer_kind: MaterializerKind::RuntimeGitCache,
|
materializer_kind: MaterializerKind::RuntimeGitClone,
|
||||||
evidence: WorkingDirectoryEvidence {
|
evidence: WorkingDirectoryEvidence {
|
||||||
repository_id: request.repository.id.clone(),
|
repository_id: request.repository.id.clone(),
|
||||||
requested_selector: request
|
requested_selector: request
|
||||||
@@ -848,13 +712,9 @@ impl RuntimeGitCacheMaterializer {
|
|||||||
.map(|selector| selector.as_ref().to_string()),
|
.map(|selector| selector.as_ref().to_string()),
|
||||||
resolved_commit,
|
resolved_commit,
|
||||||
resolved_tree,
|
resolved_tree,
|
||||||
materializer_kind: MaterializerKind::RuntimeGitCache,
|
materializer_kind: MaterializerKind::RuntimeGitClone,
|
||||||
repository_source_revision: Some(request.repository.source_revision),
|
repository_source_revision: Some(request.repository.source_revision),
|
||||||
repository_source_fingerprint: Some(request.repository.source_fingerprint.clone()),
|
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()),
|
operation_id: context.map(|value| value.operation_id.clone()),
|
||||||
credential_revision: context
|
credential_revision: context
|
||||||
.and_then(|value| value.ssh.as_ref())
|
.and_then(|value| value.ssh.as_ref())
|
||||||
@@ -867,7 +727,7 @@ impl RuntimeGitCacheMaterializer {
|
|||||||
.map(str::to_string),
|
.map(str::to_string),
|
||||||
},
|
},
|
||||||
cleanup_target: WorkingDirectoryCleanupTarget {
|
cleanup_target: WorkingDirectoryCleanupTarget {
|
||||||
kind: "runtime_git_cache_worktree".to_string(),
|
kind: "runtime_git_clone".to_string(),
|
||||||
working_directory_id: working_directory_id.clone(),
|
working_directory_id: working_directory_id.clone(),
|
||||||
repository_id: request.repository.id.clone(),
|
repository_id: request.repository.id.clone(),
|
||||||
},
|
},
|
||||||
@@ -875,15 +735,13 @@ impl RuntimeGitCacheMaterializer {
|
|||||||
};
|
};
|
||||||
let binding = WorkingDirectoryBinding {
|
let binding = WorkingDirectoryBinding {
|
||||||
working_directory,
|
working_directory,
|
||||||
root: worktree_root.clone(),
|
root: checkout_root.clone(),
|
||||||
cwd: worktree_root.clone(),
|
cwd: checkout_root.clone(),
|
||||||
working_directory_root: working_directory_root.clone(),
|
working_directory_root: working_directory_root.clone(),
|
||||||
source_repository_path: repository_cache.clone(),
|
|
||||||
command_environment: BTreeMap::new(),
|
command_environment: BTreeMap::new(),
|
||||||
session_resources: Vec::new(),
|
session_resources: Vec::new(),
|
||||||
};
|
};
|
||||||
if let Err(error) = self.write_record(&binding) {
|
if let Err(error) = self.write_record(&binding) {
|
||||||
remove_cached_worktree(&repository_cache, &worktree_root);
|
|
||||||
let _ = fs::remove_dir_all(&working_directory_root);
|
let _ = fs::remove_dir_all(&working_directory_root);
|
||||||
return Err(error);
|
return Err(error);
|
||||||
}
|
}
|
||||||
@@ -891,9 +749,8 @@ impl RuntimeGitCacheMaterializer {
|
|||||||
.materialization
|
.materialization
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.and_then(|materialization| materialization.ssh.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);
|
let _ = fs::remove_dir_all(&working_directory_root);
|
||||||
return Err(error);
|
return Err(error);
|
||||||
}
|
}
|
||||||
@@ -901,7 +758,7 @@ impl RuntimeGitCacheMaterializer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl WorkingDirectoryMaterializer for RuntimeGitCacheMaterializer {
|
impl WorkingDirectoryMaterializer for RuntimeGitMaterializer {
|
||||||
fn materialize(
|
fn materialize(
|
||||||
&self,
|
&self,
|
||||||
worker_ref: &WorkerRef,
|
worker_ref: &WorkerRef,
|
||||||
@@ -937,7 +794,7 @@ impl WorkingDirectoryMaterializer for RuntimeGitCacheMaterializer {
|
|||||||
let mut binding = match self.read_binding(&request.working_directory_id) {
|
let mut binding = match self.read_binding(&request.working_directory_id) {
|
||||||
Ok(binding) => binding,
|
Ok(binding) => binding,
|
||||||
Err(error) if error.code == "working_directory_not_found" => {
|
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),
|
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);
|
binding.working_directory.evidence.host_trust_revision = Some(ssh.host_trust_revision);
|
||||||
self.write_record(&binding)?;
|
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(
|
fn observe_repository_ref(
|
||||||
@@ -985,7 +842,7 @@ impl WorkingDirectoryMaterializer for RuntimeGitCacheMaterializer {
|
|||||||
|
|
||||||
let working_request = WorkingDirectoryRequest {
|
let working_request = WorkingDirectoryRequest {
|
||||||
repository: request.repository.clone(),
|
repository: request.repository.clone(),
|
||||||
materializer: MaterializerKind::RuntimeGitCache,
|
materializer: MaterializerKind::RuntimeGitClone,
|
||||||
backend_workdir_id: None,
|
backend_workdir_id: None,
|
||||||
materialization: request.materialization.clone(),
|
materialization: request.materialization.clone(),
|
||||||
};
|
};
|
||||||
@@ -1175,32 +1032,11 @@ impl WorkingDirectoryMaterializer for RuntimeGitCacheMaterializer {
|
|||||||
"working directory cleanup target is outside the working directory root",
|
"working directory cleanup target is outside the working directory root",
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
let workspace_worktree_root_arg = path_str(&root)?;
|
let remove_result = fs::remove_dir_all(&working_directory_root).map_err(|_| {
|
||||||
let mut remove_command = isolated_git_command();
|
WorkingDirectoryDiagnostic::new(
|
||||||
remove_command
|
"working_directory_cleanup_failed",
|
||||||
.arg("--git-dir")
|
"failed to remove working directory; backend-private path details were omitted",
|
||||||
.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(())
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
if remove_result.is_err() {
|
if remove_result.is_err() {
|
||||||
working_directory.status = WorkingDirectoryStatusKind::CleanupPending;
|
working_directory.status = WorkingDirectoryStatusKind::CleanupPending;
|
||||||
@@ -1209,7 +1045,6 @@ impl WorkingDirectoryMaterializer for RuntimeGitCacheMaterializer {
|
|||||||
root: binding.root.clone(),
|
root: binding.root.clone(),
|
||||||
cwd: binding.cwd.clone(),
|
cwd: binding.cwd.clone(),
|
||||||
working_directory_root: binding.working_directory_root.clone(),
|
working_directory_root: binding.working_directory_root.clone(),
|
||||||
source_repository_path: binding.source_repository_path.clone(),
|
|
||||||
command_environment: BTreeMap::new(),
|
command_environment: BTreeMap::new(),
|
||||||
session_resources: Vec::new(),
|
session_resources: Vec::new(),
|
||||||
};
|
};
|
||||||
@@ -2589,13 +2424,13 @@ fn repository_git_failure_diagnostic(
|
|||||||
WorkingDirectoryDiagnostic::new(code, message)
|
WorkingDirectoryDiagnostic::new(code, message)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn resolve_cached_commit(
|
fn resolve_cloned_commit(
|
||||||
repository_cache: &Path,
|
repository_root: &Path,
|
||||||
selector: &str,
|
selector: &str,
|
||||||
) -> Result<String, WorkingDirectoryDiagnostic> {
|
) -> Result<String, WorkingDirectoryDiagnostic> {
|
||||||
let mut candidates = Vec::new();
|
let mut candidates = Vec::new();
|
||||||
if selector == "HEAD" {
|
if selector == "HEAD" {
|
||||||
candidates.push("FETCH_HEAD".to_string());
|
candidates.push("HEAD".to_string());
|
||||||
} else if let Some(branch) = selector.strip_prefix("refs/heads/") {
|
} else if let Some(branch) = selector.strip_prefix("refs/heads/") {
|
||||||
candidates.push(format!("refs/remotes/origin/{branch}"));
|
candidates.push(format!("refs/remotes/origin/{branch}"));
|
||||||
} else if selector.starts_with("refs/") || selector.bytes().all(|byte| byte.is_ascii_hexdigit())
|
} 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 {
|
for candidate in candidates {
|
||||||
let spec = format!("{candidate}^{{commit}}");
|
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()
|
&& !commit.is_empty()
|
||||||
{
|
{
|
||||||
return Ok(commit);
|
return Ok(commit);
|
||||||
@@ -2619,65 +2454,34 @@ fn resolve_cached_commit(
|
|||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn fetch_repository_cache(
|
fn apply_repository_access_policy(
|
||||||
request: &WorkingDirectoryRequest,
|
repository_root: &Path,
|
||||||
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,
|
|
||||||
access: workspace_api::RepositoryAccessMode,
|
access: workspace_api::RepositoryAccessMode,
|
||||||
) -> Result<(), WorkingDirectoryDiagnostic> {
|
) -> 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 {
|
match access {
|
||||||
workspace_api::RepositoryAccessMode::ReadOnly => {
|
workspace_api::RepositoryAccessMode::ReadOnly => {
|
||||||
let mut disable_push = isolated_git_command();
|
let mut disable_push = isolated_git_command();
|
||||||
disable_push.arg("-C").arg(worktree_root).args([
|
disable_push.arg("-C").arg(repository_root).args([
|
||||||
"config",
|
"remote",
|
||||||
"--worktree",
|
"set-url",
|
||||||
"remote.origin.pushurl",
|
"--push",
|
||||||
|
"origin",
|
||||||
"yoi-read-only://repository-push-disabled",
|
"yoi-read-only://repository-push-disabled",
|
||||||
]);
|
]);
|
||||||
run_repository_git(disable_push, "working_directory_repository_policy_failed")
|
run_repository_git(disable_push, "working_directory_repository_policy_failed")
|
||||||
}
|
}
|
||||||
workspace_api::RepositoryAccessMode::ReadWrite => {
|
workspace_api::RepositoryAccessMode::ReadWrite => {
|
||||||
if git_stdout(
|
if git_stdout(
|
||||||
worktree_root,
|
repository_root,
|
||||||
["config", "--worktree", "--get-all", "remote.origin.pushurl"],
|
["config", "--get-all", "remote.origin.pushurl"],
|
||||||
)
|
)
|
||||||
.is_err()
|
.is_err()
|
||||||
{
|
{
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
let mut enable_push = isolated_git_command();
|
let mut enable_push = isolated_git_command();
|
||||||
enable_push.arg("-C").arg(worktree_root).args([
|
enable_push.arg("-C").arg(repository_root).args([
|
||||||
"config",
|
"config",
|
||||||
"--worktree",
|
|
||||||
"--unset-all",
|
"--unset-all",
|
||||||
"remote.origin.pushurl",
|
"remote.origin.pushurl",
|
||||||
]);
|
]);
|
||||||
@@ -2686,10 +2490,8 @@ fn apply_worktree_access_policy(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn validate_repository_cache_limits(
|
fn validate_repository_limits(repository_root: &Path) -> Result<(), WorkingDirectoryDiagnostic> {
|
||||||
repository_cache: &Path,
|
let report = git_stdout(repository_root, ["count-objects", "-v"])?;
|
||||||
) -> Result<(), WorkingDirectoryDiagnostic> {
|
|
||||||
let report = git_dir_stdout(repository_cache, ["count-objects", "-v"])?;
|
|
||||||
let mut objects = 0u64;
|
let mut objects = 0u64;
|
||||||
let mut kibibytes = 0u64;
|
let mut kibibytes = 0u64;
|
||||||
for line in report.lines() {
|
for line in report.lines() {
|
||||||
@@ -2712,46 +2514,6 @@ fn validate_repository_cache_limits(
|
|||||||
Ok(())
|
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> {
|
fn write_owner_only(path: &Path, content: &[u8]) -> Result<(), WorkingDirectoryDiagnostic> {
|
||||||
let mut options = fs::OpenOptions::new();
|
let mut options = fs::OpenOptions::new();
|
||||||
options.create_new(true).write(true);
|
options.create_new(true).write(true);
|
||||||
@@ -2814,7 +2576,6 @@ fn shell_quote_path(path: &Path) -> Result<String, WorkingDirectoryDiagnostic> {
|
|||||||
struct WorkingDirectoryMaterializationRecord {
|
struct WorkingDirectoryMaterializationRecord {
|
||||||
working_directory: WorkingDirectory,
|
working_directory: WorkingDirectory,
|
||||||
root: PathBuf,
|
root: PathBuf,
|
||||||
source_repository_path: PathBuf,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn git_stdout<'a, I>(repository_path: &Path, args: I) -> Result<String, WorkingDirectoryDiagnostic>
|
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(),
|
source_fingerprint: "sha256:test".to_string(),
|
||||||
selector: Some(RepositorySelector::from("HEAD")),
|
selector: Some(RepositorySelector::from("HEAD")),
|
||||||
},
|
},
|
||||||
materializer: MaterializerKind::RuntimeGitCache,
|
materializer: MaterializerKind::RuntimeGitClone,
|
||||||
backend_workdir_id: None,
|
backend_workdir_id: None,
|
||||||
materialization: None,
|
materialization: None,
|
||||||
}
|
}
|
||||||
@@ -3042,7 +2803,7 @@ mod tests {
|
|||||||
let repo = create_clean_repo();
|
let repo = create_clean_repo();
|
||||||
git(repo.path(), &["branch", "published"]);
|
git(repo.path(), &["branch", "published"]);
|
||||||
let runtime_root = tempfile::tempdir().unwrap();
|
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 repository = request(repo.path()).repository;
|
||||||
let observation_request = RepositoryRefObservationRequest {
|
let observation_request = RepositoryRefObservationRequest {
|
||||||
repository,
|
repository,
|
||||||
@@ -3108,7 +2869,7 @@ mod tests {
|
|||||||
git(&workdir, &["commit", "-m", "source first"]);
|
git(&workdir, &["commit", "-m", "source first"]);
|
||||||
|
|
||||||
let runtime_root = tempfile::tempdir().unwrap();
|
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 repository = request(&provider).repository;
|
||||||
let observation_request = RepositoryRefObservationRequest {
|
let observation_request = RepositoryRefObservationRequest {
|
||||||
repository,
|
repository,
|
||||||
@@ -3159,7 +2920,7 @@ mod tests {
|
|||||||
fn repository_ref_observation_rejects_missing_and_non_branch_selectors() {
|
fn repository_ref_observation_rejects_missing_and_non_branch_selectors() {
|
||||||
let repo = create_clean_repo();
|
let repo = create_clean_repo();
|
||||||
let runtime_root = tempfile::tempdir().unwrap();
|
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 repository = request(repo.path()).repository;
|
||||||
|
|
||||||
let missing = materializer
|
let missing = materializer
|
||||||
@@ -3189,10 +2950,10 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[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 repo = create_clean_repo();
|
||||||
let runtime_root = tempfile::tempdir().unwrap();
|
let runtime_root = tempfile::tempdir().unwrap();
|
||||||
let materializer = RuntimeGitCacheMaterializer::new(runtime_root.path());
|
let materializer = RuntimeGitMaterializer::new(runtime_root.path());
|
||||||
let binding = materializer
|
let binding = materializer
|
||||||
.materialize(&worker_ref(1), &request(repo.path()))
|
.materialize(&worker_ref(1), &request(repo.path()))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
@@ -3210,14 +2971,14 @@ mod tests {
|
|||||||
binding.working_directory_root().join(CHECKOUT_DIR)
|
binding.working_directory_root().join(CHECKOUT_DIR)
|
||||||
);
|
);
|
||||||
assert!(binding.root.join("README.md").exists());
|
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();
|
let branch = git_stdout(binding.root(), ["branch", "--show-current"]).unwrap();
|
||||||
assert!(
|
assert!(branch.is_empty(), "clone should be detached, got {branch}");
|
||||||
branch.is_empty(),
|
|
||||||
"worktree should be detached, got {branch}"
|
|
||||||
);
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
binding.working_directory.materializer_kind,
|
binding.working_directory.materializer_kind,
|
||||||
MaterializerKind::RuntimeGitCache
|
MaterializerKind::RuntimeGitClone
|
||||||
);
|
);
|
||||||
assert!(
|
assert!(
|
||||||
binding
|
binding
|
||||||
@@ -3231,7 +2992,7 @@ mod tests {
|
|||||||
fn multiple_workers_materialize_distinct_paths_for_same_source_repo() {
|
fn multiple_workers_materialize_distinct_paths_for_same_source_repo() {
|
||||||
let repo = create_clean_repo();
|
let repo = create_clean_repo();
|
||||||
let runtime_root = tempfile::tempdir().unwrap();
|
let runtime_root = tempfile::tempdir().unwrap();
|
||||||
let materializer = RuntimeGitCacheMaterializer::new(runtime_root.path());
|
let materializer = RuntimeGitMaterializer::new(runtime_root.path());
|
||||||
let first = materializer
|
let first = materializer
|
||||||
.materialize(&worker_ref(1), &request(repo.path()))
|
.materialize(&worker_ref(1), &request(repo.path()))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
@@ -3251,7 +3012,7 @@ mod tests {
|
|||||||
let repo = create_clean_repo();
|
let repo = create_clean_repo();
|
||||||
fs::write(repo.path().join("dirty.txt"), "dirty\n").unwrap();
|
fs::write(repo.path().join("dirty.txt"), "dirty\n").unwrap();
|
||||||
let runtime_root = tempfile::tempdir().unwrap();
|
let runtime_root = tempfile::tempdir().unwrap();
|
||||||
let materializer = RuntimeGitCacheMaterializer::new(runtime_root.path());
|
let materializer = RuntimeGitMaterializer::new(runtime_root.path());
|
||||||
|
|
||||||
let binding = materializer
|
let binding = materializer
|
||||||
.materialize(&worker_ref(1), &request(repo.path()))
|
.materialize(&worker_ref(1), &request(repo.path()))
|
||||||
@@ -3267,7 +3028,7 @@ mod tests {
|
|||||||
git(repo.path(), &["branch", "pinned"]);
|
git(repo.path(), &["branch", "pinned"]);
|
||||||
fs::write(repo.path().join("dirty.txt"), "dirty\n").unwrap();
|
fs::write(repo.path().join("dirty.txt"), "dirty\n").unwrap();
|
||||||
let runtime_root = tempfile::tempdir().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());
|
let mut request = request(repo.path());
|
||||||
request.repository.selector = Some(RepositorySelector::from("pinned"));
|
request.repository.selector = Some(RepositorySelector::from("pinned"));
|
||||||
|
|
||||||
@@ -3286,10 +3047,10 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[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 repo = create_clean_repo();
|
||||||
let runtime_root = tempfile::tempdir().unwrap();
|
let runtime_root = tempfile::tempdir().unwrap();
|
||||||
let materializer = RuntimeGitCacheMaterializer::new(runtime_root.path());
|
let materializer = RuntimeGitMaterializer::new(runtime_root.path());
|
||||||
let local = materializer
|
let local = materializer
|
||||||
.materialize(&worker_ref(1), &request(repo.path()))
|
.materialize(&worker_ref(1), &request(repo.path()))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
@@ -3297,31 +3058,10 @@ mod tests {
|
|||||||
.materialize(&worker_ref(2), &request(repo.path()))
|
.materialize(&worker_ref(2), &request(repo.path()))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
assert_eq!(
|
assert!(local.root.join(".git").is_dir());
|
||||||
local.working_directory.evidence.repository_cache_key,
|
assert!(second.root.join(".git").is_dir());
|
||||||
second.working_directory.evidence.repository_cache_key
|
assert_ne!(local.root.join(".git"), second.root.join(".git"));
|
||||||
);
|
assert!(!runtime_root.path().join(".repository-cache").exists());
|
||||||
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
|
|
||||||
);
|
|
||||||
|
|
||||||
let mut file_request = request(repo.path());
|
let mut file_request = request(repo.path());
|
||||||
file_request.repository.source.kind = workspace_api::RepositorySourceKind::File;
|
file_request.repository.source.kind = workspace_api::RepositorySourceKind::File;
|
||||||
@@ -3332,17 +3072,15 @@ mod tests {
|
|||||||
.materialize(&worker_ref(3), &file_request)
|
.materialize(&worker_ref(3), &file_request)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert!(file.root.join("README.md").exists());
|
assert!(file.root.join("README.md").exists());
|
||||||
assert_ne!(
|
assert!(file.root.join(".git").is_dir());
|
||||||
local.working_directory.evidence.repository_cache_key,
|
assert!(git_stdout(&file.root, ["status", "--porcelain"]).is_ok());
|
||||||
file.working_directory.evidence.repository_cache_key
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn materialization_context_is_audited_without_secret_values() {
|
fn materialization_context_is_audited_without_secret_values() {
|
||||||
let repo = create_clean_repo();
|
let repo = create_clean_repo();
|
||||||
let runtime_root = tempfile::tempdir().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());
|
let mut request = request(repo.path());
|
||||||
request.materialization = Some(crate::catalog::RepositoryMaterializationContext {
|
request.materialization = Some(crate::catalog::RepositoryMaterializationContext {
|
||||||
workspace_id: "workspace-1".to_string(),
|
workspace_id: "workspace-1".to_string(),
|
||||||
@@ -3350,7 +3088,6 @@ mod tests {
|
|||||||
operation_id: "operation-1".to_string(),
|
operation_id: "operation-1".to_string(),
|
||||||
config_revision: 7,
|
config_revision: 7,
|
||||||
config_projection_digest: "sha256:projection".to_string(),
|
config_projection_digest: "sha256:projection".to_string(),
|
||||||
cache_generation: 3,
|
|
||||||
ssh: None,
|
ssh: None,
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -3359,7 +3096,6 @@ mod tests {
|
|||||||
binding.working_directory.evidence.operation_id.as_deref(),
|
binding.working_directory.evidence.operation_id.as_deref(),
|
||||||
Some("operation-1")
|
Some("operation-1")
|
||||||
);
|
);
|
||||||
assert_eq!(binding.working_directory.evidence.cache_generation, 3);
|
|
||||||
let record = fs::read_to_string(
|
let record = fs::read_to_string(
|
||||||
binding
|
binding
|
||||||
.working_directory_root()
|
.working_directory_root()
|
||||||
@@ -3430,7 +3166,7 @@ mod tests {
|
|||||||
.unwrap();
|
.unwrap();
|
||||||
assert!(status.success());
|
assert!(status.success());
|
||||||
}
|
}
|
||||||
let materializer = RuntimeGitCacheMaterializer::new(runtime_root.path());
|
let materializer = RuntimeGitMaterializer::new(runtime_root.path());
|
||||||
let mut request = request(repo.path());
|
let mut request = request(repo.path());
|
||||||
request.materialization = Some(crate::catalog::RepositoryMaterializationContext {
|
request.materialization = Some(crate::catalog::RepositoryMaterializationContext {
|
||||||
workspace_id: "workspace-1".to_string(),
|
workspace_id: "workspace-1".to_string(),
|
||||||
@@ -3438,7 +3174,6 @@ mod tests {
|
|||||||
operation_id: "operation-agent".to_string(),
|
operation_id: "operation-agent".to_string(),
|
||||||
config_revision: 2,
|
config_revision: 2,
|
||||||
config_projection_digest: "sha256:projection".to_string(),
|
config_projection_digest: "sha256:projection".to_string(),
|
||||||
cache_generation: 0,
|
|
||||||
ssh: Some(crate::catalog::RepositorySshMaterializationAccess {
|
ssh: Some(crate::catalog::RepositorySshMaterializationAccess {
|
||||||
credential_candidates: vec![
|
credential_candidates: vec![
|
||||||
crate::catalog::RepositorySshCredentialCandidate {
|
crate::catalog::RepositorySshCredentialCandidate {
|
||||||
@@ -3754,11 +3489,7 @@ mod tests {
|
|||||||
&read_only_command_policy,
|
&read_only_command_policy,
|
||||||
));
|
));
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
git_stdout(
|
git_stdout(rebound.root(), ["config", "--get", "remote.origin.pushurl"],).unwrap(),
|
||||||
rebound.root(),
|
|
||||||
["config", "--worktree", "--get", "remote.origin.pushurl"],
|
|
||||||
)
|
|
||||||
.unwrap(),
|
|
||||||
"yoi-read-only://repository-push-disabled"
|
"yoi-read-only://repository-push-disabled"
|
||||||
);
|
);
|
||||||
drop(rebound);
|
drop(rebound);
|
||||||
@@ -3796,13 +3527,7 @@ mod tests {
|
|||||||
],
|
],
|
||||||
&read_write_command_policy,
|
&read_write_command_policy,
|
||||||
));
|
));
|
||||||
assert!(
|
assert!(git_stdout(rebound.root(), ["config", "--get", "remote.origin.pushurl"],).is_err());
|
||||||
git_stdout(
|
|
||||||
rebound.root(),
|
|
||||||
["config", "--worktree", "--get", "remote.origin.pushurl"],
|
|
||||||
)
|
|
||||||
.is_err()
|
|
||||||
);
|
|
||||||
drop(rebound);
|
drop(rebound);
|
||||||
|
|
||||||
let mut expired = initial_materialization;
|
let mut expired = initial_materialization;
|
||||||
@@ -3817,7 +3542,7 @@ mod tests {
|
|||||||
.code,
|
.code,
|
||||||
"working_directory_repository_access_expired"
|
"working_directory_repository_access_expired"
|
||||||
);
|
);
|
||||||
let restored = RuntimeGitCacheMaterializer::new(runtime_root.path());
|
let restored = RuntimeGitMaterializer::new(runtime_root.path());
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
restored.bind_working_directory(&id, None).unwrap_err().code,
|
restored.bind_working_directory(&id, None).unwrap_err().code,
|
||||||
"working_directory_remote_repository_access_required"
|
"working_directory_remote_repository_access_required"
|
||||||
@@ -3828,7 +3553,7 @@ mod tests {
|
|||||||
fn read_only_repository_access_disables_default_push_target() {
|
fn read_only_repository_access_disables_default_push_target() {
|
||||||
let repo = create_clean_repo();
|
let repo = create_clean_repo();
|
||||||
let runtime_root = tempfile::tempdir().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());
|
let mut request = request(repo.path());
|
||||||
request.materialization = Some(crate::catalog::RepositoryMaterializationContext {
|
request.materialization = Some(crate::catalog::RepositoryMaterializationContext {
|
||||||
workspace_id: "workspace-1".to_string(),
|
workspace_id: "workspace-1".to_string(),
|
||||||
@@ -3836,7 +3561,6 @@ mod tests {
|
|||||||
operation_id: "operation-read-only".to_string(),
|
operation_id: "operation-read-only".to_string(),
|
||||||
config_revision: 2,
|
config_revision: 2,
|
||||||
config_projection_digest: "sha256:projection".to_string(),
|
config_projection_digest: "sha256:projection".to_string(),
|
||||||
cache_generation: 0,
|
|
||||||
ssh: Some(crate::catalog::RepositorySshMaterializationAccess {
|
ssh: Some(crate::catalog::RepositorySshMaterializationAccess {
|
||||||
credential_candidates: vec![crate::catalog::RepositorySshCredentialCandidate {
|
credential_candidates: vec![crate::catalog::RepositorySshCredentialCandidate {
|
||||||
credential_id: "credential-1".to_string(),
|
credential_id: "credential-1".to_string(),
|
||||||
@@ -3859,11 +3583,7 @@ mod tests {
|
|||||||
|
|
||||||
let binding = materializer.create(&request).unwrap();
|
let binding = materializer.create(&request).unwrap();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
git_stdout(
|
git_stdout(binding.root(), ["config", "--get", "remote.origin.pushurl"],).unwrap(),
|
||||||
binding.root(),
|
|
||||||
["config", "--worktree", "--get", "remote.origin.pushurl"],
|
|
||||||
)
|
|
||||||
.unwrap(),
|
|
||||||
"yoi-read-only://repository-push-disabled"
|
"yoi-read-only://repository-push-disabled"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -3872,7 +3592,7 @@ mod tests {
|
|||||||
fn selector_is_not_accepted_as_a_git_option_or_refspec() {
|
fn selector_is_not_accepted_as_a_git_option_or_refspec() {
|
||||||
let repo = create_clean_repo();
|
let repo = create_clean_repo();
|
||||||
let runtime_root = tempfile::tempdir().unwrap();
|
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}"] {
|
for selector in ["--upload-pack=evil", "refs/heads/main:evil", "main@{1}"] {
|
||||||
let mut request = request(repo.path());
|
let mut request = request(repo.path());
|
||||||
request.repository.selector = Some(RepositorySelector::from(selector));
|
request.repository.selector = Some(RepositorySelector::from(selector));
|
||||||
@@ -4011,7 +3731,6 @@ mod tests {
|
|||||||
operation_id: "operation-1".to_string(),
|
operation_id: "operation-1".to_string(),
|
||||||
config_revision: 1,
|
config_revision: 1,
|
||||||
config_projection_digest: "sha256:projection".to_string(),
|
config_projection_digest: "sha256:projection".to_string(),
|
||||||
cache_generation: 0,
|
|
||||||
ssh: Some(access),
|
ssh: Some(access),
|
||||||
});
|
});
|
||||||
validate_remote_source_uri(&request).unwrap();
|
validate_remote_source_uri(&request).unwrap();
|
||||||
@@ -4034,14 +3753,13 @@ mod tests {
|
|||||||
fn remote_source_rejects_uri_credentials_and_mismatched_host_trust() {
|
fn remote_source_rejects_uri_credentials_and_mismatched_host_trust() {
|
||||||
let repo = create_clean_repo();
|
let repo = create_clean_repo();
|
||||||
let runtime_root = tempfile::tempdir().unwrap();
|
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 {
|
let context = |ssh| crate::catalog::RepositoryMaterializationContext {
|
||||||
workspace_id: "workspace-1".to_string(),
|
workspace_id: "workspace-1".to_string(),
|
||||||
runtime_id: "runtime-1".to_string(),
|
runtime_id: "runtime-1".to_string(),
|
||||||
operation_id: "operation-1".to_string(),
|
operation_id: "operation-1".to_string(),
|
||||||
config_revision: 1,
|
config_revision: 1,
|
||||||
config_projection_digest: "sha256:projection".to_string(),
|
config_projection_digest: "sha256:projection".to_string(),
|
||||||
cache_generation: 0,
|
|
||||||
ssh,
|
ssh,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -4065,7 +3783,7 @@ mod tests {
|
|||||||
uri: "http://example.test/repo.git".to_string(),
|
uri: "http://example.test/repo.git".to_string(),
|
||||||
};
|
};
|
||||||
http.materialization = Some(context(None));
|
http.materialization = Some(context(None));
|
||||||
RuntimeGitCacheMaterializer::validate_request(&http).unwrap();
|
RuntimeGitMaterializer::validate_request(&http).unwrap();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
repository_transport_warning(http.repository.source.kind),
|
repository_transport_warning(http.repository.source.kind),
|
||||||
Some("plain_http_transport")
|
Some("plain_http_transport")
|
||||||
@@ -4108,7 +3826,7 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn unsupported_remote_and_non_git_provider_return_typed_diagnostics() {
|
fn unsupported_remote_and_non_git_provider_return_typed_diagnostics() {
|
||||||
let runtime_root = tempfile::tempdir().unwrap();
|
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("."));
|
let mut remote = request(Path::new("."));
|
||||||
remote.repository.source = workspace_api::RepositorySource {
|
remote.repository.source = workspace_api::RepositorySource {
|
||||||
kind: workspace_api::RepositorySourceKind::Ssh,
|
kind: workspace_api::RepositorySourceKind::Ssh,
|
||||||
@@ -4120,7 +3838,6 @@ mod tests {
|
|||||||
operation_id: "operation-1".to_string(),
|
operation_id: "operation-1".to_string(),
|
||||||
config_revision: 1,
|
config_revision: 1,
|
||||||
config_projection_digest: "sha256:projection".to_string(),
|
config_projection_digest: "sha256:projection".to_string(),
|
||||||
cache_generation: 0,
|
|
||||||
ssh: None,
|
ssh: None,
|
||||||
});
|
});
|
||||||
let error = materializer
|
let error = materializer
|
||||||
@@ -4151,7 +3868,7 @@ mod tests {
|
|||||||
git(repo.path(), &["add", "crates/yoi/lib.rs"]);
|
git(repo.path(), &["add", "crates/yoi/lib.rs"]);
|
||||||
git(repo.path(), &["commit", "-m", "add crate"]);
|
git(repo.path(), &["commit", "-m", "add crate"]);
|
||||||
let runtime_root = tempfile::tempdir().unwrap();
|
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 working_directory = materializer.create(&request(repo.path())).unwrap();
|
||||||
|
|
||||||
let bound = materializer
|
let bound = materializer
|
||||||
@@ -4176,7 +3893,7 @@ mod tests {
|
|||||||
fn working_directory_observes_current_selector_and_ref_without_changing_creation_evidence() {
|
fn working_directory_observes_current_selector_and_ref_without_changing_creation_evidence() {
|
||||||
let repo = create_clean_repo();
|
let repo = create_clean_repo();
|
||||||
let runtime_root = tempfile::tempdir().unwrap();
|
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 working_directory = materializer.create(&request(repo.path())).unwrap();
|
||||||
let bound = materializer
|
let bound = materializer
|
||||||
.bind_working_directory(&working_directory.working_directory.id, None)
|
.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(), &["add", "inside/file.txt"]);
|
||||||
git(repo.path(), &["commit", "-m", "add inside"]);
|
git(repo.path(), &["commit", "-m", "add inside"]);
|
||||||
let runtime_root = tempfile::tempdir().unwrap();
|
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 working_directory = materializer.create(&request(repo.path())).unwrap();
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
@@ -4269,10 +3986,10 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn cleanup_working_directory_removes_worktree_and_record() {
|
fn cleanup_working_directory_removes_clone_and_record() {
|
||||||
let repo = create_clean_repo();
|
let repo = create_clean_repo();
|
||||||
let runtime_root = tempfile::tempdir().unwrap();
|
let runtime_root = tempfile::tempdir().unwrap();
|
||||||
let materializer = RuntimeGitCacheMaterializer::new(runtime_root.path());
|
let materializer = RuntimeGitMaterializer::new(runtime_root.path());
|
||||||
let binding = materializer
|
let binding = materializer
|
||||||
.materialize(&worker_ref(1), &request(repo.path()))
|
.materialize(&worker_ref(1), &request(repo.path()))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|||||||
@@ -863,9 +863,9 @@ mod tests {
|
|||||||
"repository_key": "main",
|
"repository_key": "main",
|
||||||
"creation_selector": "refs/heads/main",
|
"creation_selector": "refs/heads/main",
|
||||||
"creation_ref": "0123456789abcdef",
|
"creation_ref": "0123456789abcdef",
|
||||||
"materializer_kind": "local_git_worktree",
|
"materializer_kind": "runtime_git_clone",
|
||||||
"cleanup_target": {
|
"cleanup_target": {
|
||||||
"kind": "git_worktree",
|
"kind": "runtime_git_clone",
|
||||||
"working_directory_id": id,
|
"working_directory_id": id,
|
||||||
"repository_key": "main"
|
"repository_key": "main"
|
||||||
},
|
},
|
||||||
@@ -1242,16 +1242,10 @@ mod tests {
|
|||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn scoped_broker_operations_carry_no_child_context() {
|
async fn scoped_broker_operations_carry_no_child_context() {
|
||||||
let client = Arc::new(RecordingWorkspaceClient::new(vec![
|
let client = Arc::new(RecordingWorkspaceClient::new(vec![response(json!({
|
||||||
response(json!({
|
"operation": "stat",
|
||||||
"operation": "stat",
|
"result": {"path": "visible.txt", "kind": "file", "size": 8}
|
||||||
"result": {"path": "visible.txt", "kind": "file", "size": 8}
|
}))]));
|
||||||
})),
|
|
||||||
response(json!({
|
|
||||||
"operation": "stat",
|
|
||||||
"result": {"path": "visible.txt", "kind": "file", "size": 8}
|
|
||||||
})),
|
|
||||||
]));
|
|
||||||
let broker = workdir::WorkdirToolBroker::new(WorkspaceAttachedWorkdirSession::handle(
|
let broker = workdir::WorkdirToolBroker::new(WorkspaceAttachedWorkdirSession::handle(
|
||||||
client.clone(),
|
client.clone(),
|
||||||
));
|
));
|
||||||
@@ -1275,7 +1269,7 @@ mod tests {
|
|||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let requests = client.requests();
|
let requests = client.requests();
|
||||||
assert_eq!(requests.len(), 2);
|
assert_eq!(requests.len(), 1);
|
||||||
for request in requests {
|
for request in requests {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
request.path,
|
request.path,
|
||||||
|
|||||||
@@ -1248,8 +1248,7 @@ pub struct Diagnostic {
|
|||||||
#[serde(rename_all = "snake_case")]
|
#[serde(rename_all = "snake_case")]
|
||||||
pub enum WorkingDirectoryMaterializerKind {
|
pub enum WorkingDirectoryMaterializerKind {
|
||||||
#[default]
|
#[default]
|
||||||
RuntimeGitCache,
|
RuntimeGitClone,
|
||||||
LocalGitWorktree,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
|
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
|
||||||
@@ -4557,7 +4556,7 @@ mod tests {
|
|||||||
current_ref: None,
|
current_ref: None,
|
||||||
current_tree: None,
|
current_tree: None,
|
||||||
observed_at_epoch_seconds: None,
|
observed_at_epoch_seconds: None,
|
||||||
materializer_kind: WorkingDirectoryMaterializerKind::RuntimeGitCache,
|
materializer_kind: WorkingDirectoryMaterializerKind::RuntimeGitClone,
|
||||||
cleanup_target: None,
|
cleanup_target: None,
|
||||||
status: WorkingDirectoryStatusKind::Active,
|
status: WorkingDirectoryStatusKind::Active,
|
||||||
cleanliness: None,
|
cleanliness: None,
|
||||||
@@ -4594,7 +4593,7 @@ mod tests {
|
|||||||
"items": [{
|
"items": [{
|
||||||
"working_directory_id": "workdir-1",
|
"working_directory_id": "workdir-1",
|
||||||
"repository_key": "main",
|
"repository_key": "main",
|
||||||
"materializer_kind": "runtime_git_cache",
|
"materializer_kind": "runtime_git_clone",
|
||||||
"status": "active",
|
"status": "active",
|
||||||
"occupied_by": {
|
"occupied_by": {
|
||||||
"runtime_worker_id": "worker-1",
|
"runtime_worker_id": "worker-1",
|
||||||
|
|||||||
@@ -624,7 +624,7 @@ CREATE TABLE workdir_create_operations (
|
|||||||
state TEXT NOT NULL CHECK (state IN ('pending', 'succeeded', 'failed')),
|
state TEXT NOT NULL CHECK (state IN ('pending', 'succeeded', 'failed')),
|
||||||
failure TEXT,
|
failure TEXT,
|
||||||
created_at TEXT NOT NULL,
|
created_at TEXT NOT NULL,
|
||||||
updated_at TEXT NOT NULL, source_kind TEXT, source_uri TEXT, source_revision INTEGER, source_fingerprint TEXT, credential_id TEXT, credential_revision INTEGER, host_trust_id TEXT, host_trust_revision INTEGER, repository_access_mode TEXT, cache_generation INTEGER NOT NULL DEFAULT 0,
|
updated_at TEXT NOT NULL, source_kind TEXT, source_uri TEXT, source_revision INTEGER, source_fingerprint TEXT, credential_id TEXT, credential_revision INTEGER, host_trust_id TEXT, host_trust_revision INTEGER, repository_access_mode TEXT,
|
||||||
PRIMARY KEY (workspace_id, operation_id),
|
PRIMARY KEY (workspace_id, operation_id),
|
||||||
UNIQUE (workspace_id, working_directory_id)
|
UNIQUE (workspace_id, working_directory_id)
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -10910,7 +10910,6 @@ async fn create_workspace_working_directory(
|
|||||||
host_trust_id: None,
|
host_trust_id: None,
|
||||||
host_trust_revision: None,
|
host_trust_revision: None,
|
||||||
repository_access_mode: None,
|
repository_access_mode: None,
|
||||||
cache_generation: 0,
|
|
||||||
working_directory_id: next_backend_workdir_id(&request.repository_key),
|
working_directory_id: next_backend_workdir_id(&request.repository_key),
|
||||||
state: "pending".to_string(),
|
state: "pending".to_string(),
|
||||||
failure: None,
|
failure: None,
|
||||||
@@ -14216,7 +14215,7 @@ fn working_directory_request_from_repository(
|
|||||||
})
|
})
|
||||||
.or_else(|| Some(RuntimeRepositorySelector::from("HEAD"))),
|
.or_else(|| Some(RuntimeRepositorySelector::from("HEAD"))),
|
||||||
},
|
},
|
||||||
materializer: MaterializerKind::RuntimeGitCache,
|
materializer: MaterializerKind::RuntimeGitClone,
|
||||||
backend_workdir_id: None,
|
backend_workdir_id: None,
|
||||||
materialization: None,
|
materialization: None,
|
||||||
}
|
}
|
||||||
@@ -17381,9 +17380,9 @@ fn runtime_workdir_summary_from_record(
|
|||||||
current_ref: record.current_ref.clone(),
|
current_ref: record.current_ref.clone(),
|
||||||
current_tree: record.current_tree.clone(),
|
current_tree: record.current_tree.clone(),
|
||||||
observed_at_epoch_seconds: record.observed_at_epoch_seconds,
|
observed_at_epoch_seconds: record.observed_at_epoch_seconds,
|
||||||
materializer_kind: MaterializerKind::RuntimeGitCache,
|
materializer_kind: MaterializerKind::RuntimeGitClone,
|
||||||
cleanup_target: Some(worker_runtime::catalog::WorkingDirectoryCleanupTarget {
|
cleanup_target: Some(worker_runtime::catalog::WorkingDirectoryCleanupTarget {
|
||||||
kind: "runtime_git_cache_worktree".to_string(),
|
kind: "runtime_git_clone".to_string(),
|
||||||
working_directory_id: record.workdir_id.clone(),
|
working_directory_id: record.workdir_id.clone(),
|
||||||
repository_id: record.repository_id.clone(),
|
repository_id: record.repository_id.clone(),
|
||||||
}),
|
}),
|
||||||
@@ -17416,9 +17415,9 @@ fn workdir_summary_from_record(
|
|||||||
current_ref: record.current_ref.clone(),
|
current_ref: record.current_ref.clone(),
|
||||||
current_tree: record.current_tree.clone(),
|
current_tree: record.current_tree.clone(),
|
||||||
observed_at_epoch_seconds: record.observed_at_epoch_seconds,
|
observed_at_epoch_seconds: record.observed_at_epoch_seconds,
|
||||||
materializer_kind: MaterializerKind::RuntimeGitCache,
|
materializer_kind: MaterializerKind::RuntimeGitClone,
|
||||||
cleanup_target: Some(WorkingDirectoryCleanupTarget {
|
cleanup_target: Some(WorkingDirectoryCleanupTarget {
|
||||||
kind: "runtime_git_cache_worktree".to_string(),
|
kind: "runtime_git_clone".to_string(),
|
||||||
working_directory_id: record.workdir_id.clone(),
|
working_directory_id: record.workdir_id.clone(),
|
||||||
repository_key: repository_key.to_string(),
|
repository_key: repository_key.to_string(),
|
||||||
}),
|
}),
|
||||||
@@ -17647,7 +17646,6 @@ fn authorize_repository_materialization_operation(
|
|||||||
operation_id: operation.operation_id.clone(),
|
operation_id: operation.operation_id.clone(),
|
||||||
config_revision: operation.config_revision,
|
config_revision: operation.config_revision,
|
||||||
config_projection_digest: operation.config_projection_digest.clone(),
|
config_projection_digest: operation.config_projection_digest.clone(),
|
||||||
cache_generation: operation.cache_generation,
|
|
||||||
ssh: Some(RepositorySshMaterializationAccess {
|
ssh: Some(RepositorySshMaterializationAccess {
|
||||||
credential_candidates: leases
|
credential_candidates: leases
|
||||||
.into_iter()
|
.into_iter()
|
||||||
@@ -17715,7 +17713,6 @@ fn authorize_repository_materialization_operation(
|
|||||||
workspace_api::RepositoryAccessMode::ReadOnly => "read_only",
|
workspace_api::RepositoryAccessMode::ReadOnly => "read_only",
|
||||||
workspace_api::RepositoryAccessMode::ReadWrite => "read_write",
|
workspace_api::RepositoryAccessMode::ReadWrite => "read_write",
|
||||||
},
|
},
|
||||||
context.cache_generation,
|
|
||||||
&now_registry_timestamp(),
|
&now_registry_timestamp(),
|
||||||
)?;
|
)?;
|
||||||
context
|
context
|
||||||
@@ -17727,7 +17724,6 @@ fn authorize_repository_materialization_operation(
|
|||||||
operation_id: operation.operation_id.clone(),
|
operation_id: operation.operation_id.clone(),
|
||||||
config_revision: operation.config_revision,
|
config_revision: operation.config_revision,
|
||||||
config_projection_digest: operation.config_projection_digest.clone(),
|
config_projection_digest: operation.config_projection_digest.clone(),
|
||||||
cache_generation: operation.cache_generation,
|
|
||||||
ssh: None,
|
ssh: None,
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -17870,7 +17866,6 @@ fn authorize_repository_materialization(
|
|||||||
operation_id: operation_id.to_string(),
|
operation_id: operation_id.to_string(),
|
||||||
config_revision: projection.config_revision,
|
config_revision: projection.config_revision,
|
||||||
config_projection_digest: projection.projection_digest.clone(),
|
config_projection_digest: projection.projection_digest.clone(),
|
||||||
cache_generation: 0,
|
|
||||||
ssh,
|
ssh,
|
||||||
});
|
});
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -17935,7 +17930,7 @@ fn working_directory_request_for_browser(
|
|||||||
source_fingerprint: repository.source_fingerprint.clone(),
|
source_fingerprint: repository.source_fingerprint.clone(),
|
||||||
selector: selector.map(RuntimeRepositorySelector),
|
selector: selector.map(RuntimeRepositorySelector),
|
||||||
},
|
},
|
||||||
materializer: MaterializerKind::RuntimeGitCache,
|
materializer: MaterializerKind::RuntimeGitClone,
|
||||||
backend_workdir_id: None,
|
backend_workdir_id: None,
|
||||||
materialization: None,
|
materialization: None,
|
||||||
})
|
})
|
||||||
@@ -18707,7 +18702,7 @@ mod tests {
|
|||||||
current_ref: Some("abc123".to_string()),
|
current_ref: Some("abc123".to_string()),
|
||||||
current_tree: None,
|
current_tree: None,
|
||||||
observed_at_epoch_seconds: Some(1_767_225_600),
|
observed_at_epoch_seconds: Some(1_767_225_600),
|
||||||
materializer_kind: workspace_api::WorkingDirectoryMaterializerKind::RuntimeGitCache,
|
materializer_kind: workspace_api::WorkingDirectoryMaterializerKind::RuntimeGitClone,
|
||||||
cleanup_target: None,
|
cleanup_target: None,
|
||||||
status: worker_runtime::catalog::WorkingDirectoryStatusKind::Active,
|
status: worker_runtime::catalog::WorkingDirectoryStatusKind::Active,
|
||||||
cleanliness: Some("clean".to_string()),
|
cleanliness: Some("clean".to_string()),
|
||||||
@@ -19758,7 +19753,6 @@ mod tests {
|
|||||||
operation_id: "operation-1".to_string(),
|
operation_id: "operation-1".to_string(),
|
||||||
config_revision: 1,
|
config_revision: 1,
|
||||||
config_projection_digest: "sha256:projection".to_string(),
|
config_projection_digest: "sha256:projection".to_string(),
|
||||||
cache_generation: 0,
|
|
||||||
ssh: Some(
|
ssh: Some(
|
||||||
worker_runtime::catalog::RepositorySshMaterializationAccess {
|
worker_runtime::catalog::RepositorySshMaterializationAccess {
|
||||||
credential_candidates: vec![
|
credential_candidates: vec![
|
||||||
@@ -21096,7 +21090,7 @@ mod tests {
|
|||||||
worker_runtime::execution::WorkerExecutionContext,
|
worker_runtime::execution::WorkerExecutionContext,
|
||||||
>,
|
>,
|
||||||
>,
|
>,
|
||||||
materializer: worker_runtime::working_directory::RuntimeGitCacheMaterializer,
|
materializer: worker_runtime::working_directory::RuntimeGitMaterializer,
|
||||||
spawn_failure: std::sync::Mutex<Option<String>>,
|
spawn_failure: std::sync::Mutex<Option<String>>,
|
||||||
input_failure: std::sync::Mutex<Option<String>>,
|
input_failure: std::sync::Mutex<Option<String>>,
|
||||||
inputs: std::sync::Mutex<Vec<(worker_runtime::identity::WorkerRef, String)>>,
|
inputs: std::sync::Mutex<Vec<(worker_runtime::identity::WorkerRef, String)>>,
|
||||||
@@ -21116,7 +21110,7 @@ mod tests {
|
|||||||
);
|
);
|
||||||
Self {
|
Self {
|
||||||
contexts: std::sync::Mutex::new(std::collections::HashMap::new()),
|
contexts: std::sync::Mutex::new(std::collections::HashMap::new()),
|
||||||
materializer: worker_runtime::working_directory::RuntimeGitCacheMaterializer::new(
|
materializer: worker_runtime::working_directory::RuntimeGitMaterializer::new(
|
||||||
std::env::temp_dir().join(unique),
|
std::env::temp_dir().join(unique),
|
||||||
),
|
),
|
||||||
spawn_failure: std::sync::Mutex::new(None),
|
spawn_failure: std::sync::Mutex::new(None),
|
||||||
@@ -28115,7 +28109,6 @@ mod tests {
|
|||||||
host_trust_id: None,
|
host_trust_id: None,
|
||||||
host_trust_revision: None,
|
host_trust_revision: None,
|
||||||
repository_access_mode: None,
|
repository_access_mode: None,
|
||||||
cache_generation: 0,
|
|
||||||
working_directory_id: "workdir-provider-rejection".to_string(),
|
working_directory_id: "workdir-provider-rejection".to_string(),
|
||||||
state: "pending".to_string(),
|
state: "pending".to_string(),
|
||||||
failure: None,
|
failure: None,
|
||||||
@@ -30989,7 +30982,7 @@ VALUES ('0192f0e8-4d84-7d6e-a000-000000000001', 'ticket', 3);
|
|||||||
current_ref: Some("abc123".to_string()),
|
current_ref: Some("abc123".to_string()),
|
||||||
current_tree: Some("tree123".to_string()),
|
current_tree: Some("tree123".to_string()),
|
||||||
observed_at_epoch_seconds: Some(1_777_777_777),
|
observed_at_epoch_seconds: Some(1_777_777_777),
|
||||||
materializer_kind: MaterializerKind::LocalGitWorktree,
|
materializer_kind: MaterializerKind::RuntimeGitClone,
|
||||||
cleanup_target: None,
|
cleanup_target: None,
|
||||||
status: WorkingDirectoryStatusKind::Active,
|
status: WorkingDirectoryStatusKind::Active,
|
||||||
cleanliness: Some("clean".to_string()),
|
cleanliness: Some("clean".to_string()),
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ use crate::workspace_deletion::WorkspaceDeletionStore;
|
|||||||
use crate::{Error, Result};
|
use crate::{Error, Result};
|
||||||
|
|
||||||
const OLDEST_SCHEMA_VERSION: i64 = 50;
|
const OLDEST_SCHEMA_VERSION: i64 = 50;
|
||||||
const LATEST_SCHEMA_VERSION: i64 = 57;
|
const LATEST_SCHEMA_VERSION: i64 = 58;
|
||||||
const SCHEMA_BASELINE_NAME: &str = "workspace schema baseline";
|
const SCHEMA_BASELINE_NAME: &str = "workspace schema baseline";
|
||||||
const WORKSPACE_RUNTIME_BINDINGS_MIGRATION_NAME: &str = "workspace runtime bindings";
|
const WORKSPACE_RUNTIME_BINDINGS_MIGRATION_NAME: &str = "workspace runtime bindings";
|
||||||
const RUNTIME_BINDING_AUDIT_MIGRATION_NAME: &str = "workspace Runtime binding revision and audit";
|
const RUNTIME_BINDING_AUDIT_MIGRATION_NAME: &str = "workspace Runtime binding revision and audit";
|
||||||
@@ -30,6 +30,8 @@ const WORKSPACE_RUNTIME_VERIFICATION_MIGRATION_NAME: &str =
|
|||||||
"Workspace-signed Runtime verification evidence";
|
"Workspace-signed Runtime verification evidence";
|
||||||
const LEGACY_EXTERNAL_RUNTIME_BINDING_CUTOVER_MIGRATION_NAME: &str =
|
const LEGACY_EXTERNAL_RUNTIME_BINDING_CUTOVER_MIGRATION_NAME: &str =
|
||||||
"convert legacy Server-issued Runtime bindings to Workspace identity";
|
"convert legacy Server-issued Runtime bindings to Workspace identity";
|
||||||
|
const REMOVE_WORKDIR_CACHE_GENERATION_MIGRATION_NAME: &str =
|
||||||
|
"remove obsolete Workdir Repository cache generation";
|
||||||
|
|
||||||
const MIGRATIONS: &[Migration] = &[
|
const MIGRATIONS: &[Migration] = &[
|
||||||
Migration {
|
Migration {
|
||||||
@@ -67,6 +69,11 @@ const MIGRATIONS: &[Migration] = &[
|
|||||||
name: LEGACY_EXTERNAL_RUNTIME_BINDING_CUTOVER_MIGRATION_NAME,
|
name: LEGACY_EXTERNAL_RUNTIME_BINDING_CUTOVER_MIGRATION_NAME,
|
||||||
apply: migrate_legacy_external_runtime_bindings_v56_to_v57,
|
apply: migrate_legacy_external_runtime_bindings_v56_to_v57,
|
||||||
},
|
},
|
||||||
|
Migration {
|
||||||
|
version: 58,
|
||||||
|
name: REMOVE_WORKDIR_CACHE_GENERATION_MIGRATION_NAME,
|
||||||
|
apply: migrate_workdir_cache_generation_v57_to_v58,
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
#[derive(Clone, Copy)]
|
#[derive(Clone, Copy)]
|
||||||
@@ -566,7 +573,6 @@ pub struct WorkdirCreateOperationRecord {
|
|||||||
pub host_trust_id: Option<String>,
|
pub host_trust_id: Option<String>,
|
||||||
pub host_trust_revision: Option<u64>,
|
pub host_trust_revision: Option<u64>,
|
||||||
pub repository_access_mode: Option<String>,
|
pub repository_access_mode: Option<String>,
|
||||||
pub cache_generation: u64,
|
|
||||||
pub working_directory_id: String,
|
pub working_directory_id: String,
|
||||||
pub state: String,
|
pub state: String,
|
||||||
pub failure: Option<String>,
|
pub failure: Option<String>,
|
||||||
@@ -8534,6 +8540,33 @@ fn migrate_legacy_external_runtime_bindings_v56_to_v57(conn: &Connection) -> Res
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn migrate_workdir_cache_generation_v57_to_v58(conn: &Connection) -> Result<()> {
|
||||||
|
let current = current_schema_version(conn)?;
|
||||||
|
if current != 57 {
|
||||||
|
return Err(Error::Store(format!(
|
||||||
|
"expected schema version 57 before {REMOVE_WORKDIR_CACHE_GENERATION_MIGRATION_NAME} migration, found {current}"
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
let tx = rusqlite::Transaction::new_unchecked(conn, TransactionBehavior::Exclusive)?;
|
||||||
|
if table_columns(&tx, "workdir_create_operations")?.contains(&"cache_generation".to_string()) {
|
||||||
|
tx.execute(
|
||||||
|
"ALTER TABLE workdir_create_operations DROP COLUMN cache_generation",
|
||||||
|
[],
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
if table_columns(&tx, "workdir_create_operations")?.contains(&"cache_generation".to_string()) {
|
||||||
|
return Err(Error::Store(
|
||||||
|
"obsolete Workdir cache generation remains after schema-58 migration".to_string(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
tx.execute(
|
||||||
|
"INSERT INTO __yoi_schema_migrations (version, name) VALUES (?1, ?2)",
|
||||||
|
params![58_i64, REMOVE_WORKDIR_CACHE_GENERATION_MIGRATION_NAME],
|
||||||
|
)?;
|
||||||
|
tx.commit()?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
fn verify_workspace_runtime_verification_schema(conn: &Connection) -> Result<()> {
|
fn verify_workspace_runtime_verification_schema(conn: &Connection) -> Result<()> {
|
||||||
let actual = table_columns(conn, "workspace_runtime_verifications")?
|
let actual = table_columns(conn, "workspace_runtime_verifications")?
|
||||||
.into_iter()
|
.into_iter()
|
||||||
@@ -9676,6 +9709,10 @@ mod tests {
|
|||||||
version: 57,
|
version: 57,
|
||||||
name: LEGACY_EXTERNAL_RUNTIME_BINDING_CUTOVER_MIGRATION_NAME.to_string(),
|
name: LEGACY_EXTERNAL_RUNTIME_BINDING_CUTOVER_MIGRATION_NAME.to_string(),
|
||||||
},
|
},
|
||||||
|
WorkspaceSchemaMigrationStep {
|
||||||
|
version: 58,
|
||||||
|
name: REMOVE_WORKDIR_CACHE_GENERATION_MIGRATION_NAME.to_string(),
|
||||||
|
},
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -9710,6 +9747,10 @@ mod tests {
|
|||||||
57,
|
57,
|
||||||
LEGACY_EXTERNAL_RUNTIME_BINDING_CUTOVER_MIGRATION_NAME.to_string(),
|
LEGACY_EXTERNAL_RUNTIME_BINDING_CUTOVER_MIGRATION_NAME.to_string(),
|
||||||
),
|
),
|
||||||
|
(
|
||||||
|
58,
|
||||||
|
REMOVE_WORKDIR_CACHE_GENERATION_MIGRATION_NAME.to_string(),
|
||||||
|
),
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
assert!(!table_exists(conn, "trusted_runtime_records")?);
|
assert!(!table_exists(conn, "trusted_runtime_records")?);
|
||||||
@@ -9780,7 +9821,7 @@ mod tests {
|
|||||||
.iter()
|
.iter()
|
||||||
.map(|migration| migration.version)
|
.map(|migration| migration.version)
|
||||||
.collect::<Vec<_>>(),
|
.collect::<Vec<_>>(),
|
||||||
vec![52, 53, 54, 55, 56, 57]
|
vec![52, 53, 54, 55, 56, 57, 58]
|
||||||
);
|
);
|
||||||
SqliteWorkspaceStore::migrate_database(&path).unwrap();
|
SqliteWorkspaceStore::migrate_database(&path).unwrap();
|
||||||
let conn = Connection::open(&path).unwrap();
|
let conn = Connection::open(&path).unwrap();
|
||||||
@@ -9788,7 +9829,7 @@ mod tests {
|
|||||||
current_schema_version(&conn).unwrap(),
|
current_schema_version(&conn).unwrap(),
|
||||||
LATEST_SCHEMA_VERSION
|
LATEST_SCHEMA_VERSION
|
||||||
);
|
);
|
||||||
assert_eq!(workspace_schema_migration_history(&conn).unwrap().len(), 8);
|
assert_eq!(workspace_schema_migration_history(&conn).unwrap().len(), 9);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -10547,6 +10588,35 @@ mod tests {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn schema_v58_removes_obsolete_workdir_cache_generation() {
|
||||||
|
let temp = tempfile::tempdir().unwrap();
|
||||||
|
let path = temp.path().join("server.db");
|
||||||
|
prepare_schema_v50(&path, Some("workspace-a"));
|
||||||
|
let conn = Connection::open(&path).unwrap();
|
||||||
|
configure_sqlite(&conn).unwrap();
|
||||||
|
for migration in MIGRATIONS
|
||||||
|
.iter()
|
||||||
|
.filter(|migration| migration.version <= 57)
|
||||||
|
{
|
||||||
|
(migration.apply)(&conn).unwrap();
|
||||||
|
}
|
||||||
|
conn.execute(
|
||||||
|
"ALTER TABLE workdir_create_operations ADD COLUMN cache_generation INTEGER NOT NULL DEFAULT 0",
|
||||||
|
[],
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
migrate_workdir_cache_generation_v57_to_v58(&conn).unwrap();
|
||||||
|
|
||||||
|
assert_eq!(current_schema_version(&conn).unwrap(), 58);
|
||||||
|
assert!(
|
||||||
|
!table_columns(&conn, "workdir_create_operations")
|
||||||
|
.unwrap()
|
||||||
|
.contains(&"cache_generation".to_string())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn runtime_binding_key_mutations_are_revisioned_idempotent_and_audited() {
|
fn runtime_binding_key_mutations_are_revisioned_idempotent_and_audited() {
|
||||||
let store = SqliteWorkspaceStore::in_memory().unwrap();
|
let store = SqliteWorkspaceStore::in_memory().unwrap();
|
||||||
|
|||||||
@@ -172,7 +172,6 @@ impl SqliteWorkspaceStore {
|
|||||||
host_trust_id: &str,
|
host_trust_id: &str,
|
||||||
host_trust_revision: u64,
|
host_trust_revision: u64,
|
||||||
repository_access_mode: &str,
|
repository_access_mode: &str,
|
||||||
cache_generation: u64,
|
|
||||||
now: &str,
|
now: &str,
|
||||||
) -> Result<WorkdirCreateOperationRecord> {
|
) -> Result<WorkdirCreateOperationRecord> {
|
||||||
self.with_conn_mut(|conn| {
|
self.with_conn_mut(|conn| {
|
||||||
@@ -194,7 +193,6 @@ impl SqliteWorkspaceStore {
|
|||||||
|| operation.host_trust_revision != Some(host_trust_revision)
|
|| operation.host_trust_revision != Some(host_trust_revision)
|
||||||
|| operation.repository_access_mode.as_deref()
|
|| operation.repository_access_mode.as_deref()
|
||||||
!= Some(repository_access_mode)
|
!= Some(repository_access_mode)
|
||||||
|| operation.cache_generation != cache_generation
|
|
||||||
{
|
{
|
||||||
return Err(Error::InvalidInput(format!(
|
return Err(Error::InvalidInput(format!(
|
||||||
"Workdir create operation `{operation_id}` Repository access evidence changed"
|
"Workdir create operation `{operation_id}` Repository access evidence changed"
|
||||||
@@ -206,8 +204,7 @@ impl SqliteWorkspaceStore {
|
|||||||
r#"UPDATE workdir_create_operations
|
r#"UPDATE workdir_create_operations
|
||||||
SET credential_id = ?4, credential_revision = ?5,
|
SET credential_id = ?4, credential_revision = ?5,
|
||||||
host_trust_id = ?6, host_trust_revision = ?7,
|
host_trust_id = ?6, host_trust_revision = ?7,
|
||||||
repository_access_mode = ?8, cache_generation = ?9,
|
repository_access_mode = ?8, updated_at = ?9
|
||||||
updated_at = ?10
|
|
||||||
WHERE workspace_id = ?1 AND operation_id = ?2
|
WHERE workspace_id = ?1 AND operation_id = ?2
|
||||||
AND request_fingerprint = ?3 AND credential_id IS NULL"#,
|
AND request_fingerprint = ?3 AND credential_id IS NULL"#,
|
||||||
params![
|
params![
|
||||||
@@ -223,9 +220,6 @@ impl SqliteWorkspaceStore {
|
|||||||
"host-trust revision is out of range".to_string()
|
"host-trust revision is out of range".to_string()
|
||||||
))?,
|
))?,
|
||||||
repository_access_mode,
|
repository_access_mode,
|
||||||
i64::try_from(cache_generation).map_err(|_| Error::InvalidInput(
|
|
||||||
"cache generation is out of range".to_string()
|
|
||||||
))?,
|
|
||||||
now,
|
now,
|
||||||
],
|
],
|
||||||
)?;
|
)?;
|
||||||
@@ -294,7 +288,7 @@ fn read_workdir_create_operation(
|
|||||||
config_projection_digest, source_kind, source_uri, source_revision,
|
config_projection_digest, source_kind, source_uri, source_revision,
|
||||||
source_fingerprint, credential_id, credential_revision,
|
source_fingerprint, credential_id, credential_revision,
|
||||||
host_trust_id, host_trust_revision, repository_access_mode,
|
host_trust_id, host_trust_revision, repository_access_mode,
|
||||||
cache_generation, working_directory_id, state, failure,
|
working_directory_id, state, failure,
|
||||||
created_at, updated_at
|
created_at, updated_at
|
||||||
FROM workdir_create_operations
|
FROM workdir_create_operations
|
||||||
WHERE workspace_id = ?1 AND operation_id = ?2"#,
|
WHERE workspace_id = ?1 AND operation_id = ?2"#,
|
||||||
@@ -319,12 +313,11 @@ fn read_workdir_create_operation(
|
|||||||
host_trust_id: row.get(15)?,
|
host_trust_id: row.get(15)?,
|
||||||
host_trust_revision: row.get::<_, Option<i64>>(16)?.map(|value| value as u64),
|
host_trust_revision: row.get::<_, Option<i64>>(16)?.map(|value| value as u64),
|
||||||
repository_access_mode: row.get(17)?,
|
repository_access_mode: row.get(17)?,
|
||||||
cache_generation: row.get::<_, i64>(18)? as u64,
|
working_directory_id: row.get(18)?,
|
||||||
working_directory_id: row.get(19)?,
|
state: row.get(19)?,
|
||||||
state: row.get(20)?,
|
failure: row.get(20)?,
|
||||||
failure: row.get(21)?,
|
created_at: row.get(21)?,
|
||||||
created_at: row.get(22)?,
|
updated_at: row.get(22)?,
|
||||||
updated_at: row.get(23)?,
|
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
@@ -410,7 +403,6 @@ mod tests {
|
|||||||
host_trust_id: None,
|
host_trust_id: None,
|
||||||
host_trust_revision: None,
|
host_trust_revision: None,
|
||||||
repository_access_mode: None,
|
repository_access_mode: None,
|
||||||
cache_generation: 0,
|
|
||||||
working_directory_id: "wd-1".to_string(),
|
working_directory_id: "wd-1".to_string(),
|
||||||
state: "pending".to_string(),
|
state: "pending".to_string(),
|
||||||
failure: None,
|
failure: None,
|
||||||
@@ -431,14 +423,12 @@ mod tests {
|
|||||||
"trust-1",
|
"trust-1",
|
||||||
5,
|
5,
|
||||||
"read_only",
|
"read_only",
|
||||||
2,
|
|
||||||
"2026-08-24T00:00:01Z",
|
"2026-08-24T00:00:01Z",
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert_eq!(bound.credential_id.as_deref(), Some("credential-1"));
|
assert_eq!(bound.credential_id.as_deref(), Some("credential-1"));
|
||||||
assert_eq!(bound.credential_revision, Some(3));
|
assert_eq!(bound.credential_revision, Some(3));
|
||||||
assert_eq!(bound.host_trust_revision, Some(5));
|
assert_eq!(bound.host_trust_revision, Some(5));
|
||||||
assert_eq!(bound.cache_generation, 2);
|
|
||||||
assert!(
|
assert!(
|
||||||
store
|
store
|
||||||
.bind_workdir_create_repository_access(
|
.bind_workdir_create_repository_access(
|
||||||
@@ -450,7 +440,6 @@ mod tests {
|
|||||||
"trust-1",
|
"trust-1",
|
||||||
5,
|
5,
|
||||||
"read_only",
|
"read_only",
|
||||||
2,
|
|
||||||
"2026-08-24T00:00:02Z",
|
"2026-08-24T00:00:02Z",
|
||||||
)
|
)
|
||||||
.is_err()
|
.is_err()
|
||||||
|
|||||||
@@ -1096,7 +1096,6 @@ mod tests {
|
|||||||
host_trust_id: None,
|
host_trust_id: None,
|
||||||
host_trust_revision: None,
|
host_trust_revision: None,
|
||||||
repository_access_mode: None,
|
repository_access_mode: None,
|
||||||
cache_generation: 0,
|
|
||||||
working_directory_id: "workdir-a".to_string(),
|
working_directory_id: "workdir-a".to_string(),
|
||||||
state: "pending".to_string(),
|
state: "pending".to_string(),
|
||||||
failure: None,
|
failure: None,
|
||||||
|
|||||||
@@ -9,9 +9,7 @@ export type Diagnostic = {
|
|||||||
message: string;
|
message: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type WorkingDirectoryMaterializerKind =
|
export type WorkingDirectoryMaterializerKind = "runtime_git_clone";
|
||||||
| "runtime_git_cache"
|
|
||||||
| "local_git_worktree";
|
|
||||||
|
|
||||||
export type WorkingDirectoryStatusKind =
|
export type WorkingDirectoryStatusKind =
|
||||||
| "active"
|
| "active"
|
||||||
|
|||||||
@@ -11,9 +11,7 @@ export type Diagnostic = {
|
|||||||
message: string;
|
message: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type WorkingDirectoryMaterializerKind =
|
export type WorkingDirectoryMaterializerKind = "runtime_git_clone";
|
||||||
| "runtime_git_cache"
|
|
||||||
| "local_git_worktree";
|
|
||||||
|
|
||||||
export type WorkingDirectoryStatusKind =
|
export type WorkingDirectoryStatusKind =
|
||||||
| "active"
|
| "active"
|
||||||
|
|||||||
@@ -114,8 +114,7 @@ export function parseWorkingDirectorySummary(
|
|||||||
working_directory_id: stringField(record, "working_directory_id"),
|
working_directory_id: stringField(record, "working_directory_id"),
|
||||||
repository_key: stringField(record, "repository_key"),
|
repository_key: stringField(record, "repository_key"),
|
||||||
materializer_kind: enumField(record, "materializer_kind", [
|
materializer_kind: enumField(record, "materializer_kind", [
|
||||||
"runtime_git_cache",
|
"runtime_git_clone",
|
||||||
"local_git_worktree",
|
|
||||||
]),
|
]),
|
||||||
status: enumField(record, "status", [
|
status: enumField(record, "status", [
|
||||||
"active",
|
"active",
|
||||||
|
|||||||
@@ -282,10 +282,7 @@ function runtimeWorkingDirectory(
|
|||||||
item.materializer_kind,
|
item.materializer_kind,
|
||||||
`${label}.materializer_kind`,
|
`${label}.materializer_kind`,
|
||||||
);
|
);
|
||||||
if (
|
if (materializerKind !== "runtime_git_clone") {
|
||||||
materializerKind !== "runtime_git_cache" &&
|
|
||||||
materializerKind !== "local_git_worktree"
|
|
||||||
) {
|
|
||||||
throw new Error(`${label}.materializer_kind is invalid`);
|
throw new Error(`${label}.materializer_kind is invalid`);
|
||||||
}
|
}
|
||||||
const status = string(item.status, `${label}.status`);
|
const status = string(item.status, `${label}.status`);
|
||||||
|
|||||||
@@ -20,10 +20,10 @@ function workdir(
|
|||||||
repository_key: "repository-1",
|
repository_key: "repository-1",
|
||||||
current_selector,
|
current_selector,
|
||||||
current_ref,
|
current_ref,
|
||||||
materializer_kind: "local_git_worktree",
|
materializer_kind: "runtime_git_clone",
|
||||||
status: "active",
|
status: "active",
|
||||||
cleanup_target: {
|
cleanup_target: {
|
||||||
kind: "local_git_worktree",
|
kind: "runtime_git_clone",
|
||||||
working_directory_id: "workdir-1",
|
working_directory_id: "workdir-1",
|
||||||
repository_key: "repository-1",
|
repository_key: "repository-1",
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -54,12 +54,12 @@ const options: WorkerLaunchOptionsResponse = {
|
|||||||
creation_ref: "0123456789abcdef",
|
creation_ref: "0123456789abcdef",
|
||||||
current_selector: null,
|
current_selector: null,
|
||||||
current_ref: "0123456789abcdef",
|
current_ref: "0123456789abcdef",
|
||||||
materializer_kind: "local_git_worktree",
|
materializer_kind: "runtime_git_clone",
|
||||||
status: "active",
|
status: "active",
|
||||||
cleanliness: "clean",
|
cleanliness: "clean",
|
||||||
primary_worker_id: null,
|
primary_worker_id: null,
|
||||||
cleanup_target: {
|
cleanup_target: {
|
||||||
kind: "git_worktree",
|
kind: "runtime_git_clone",
|
||||||
working_directory_id: "wd-1-repo",
|
working_directory_id: "wd-1-repo",
|
||||||
repository_key: "repo",
|
repository_key: "repo",
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import {
|
|||||||
const summary = {
|
const summary = {
|
||||||
working_directory_id: "workdir-1",
|
working_directory_id: "workdir-1",
|
||||||
repository_key: "main",
|
repository_key: "main",
|
||||||
materializer_kind: "runtime_git_cache",
|
materializer_kind: "runtime_git_clone",
|
||||||
status: "active",
|
status: "active",
|
||||||
occupied_by: {
|
occupied_by: {
|
||||||
runtime_id: "arcadia",
|
runtime_id: "arcadia",
|
||||||
|
|||||||
Reference in New Issue
Block a user