// file: crates/knowledge-git-lib/src/backend.rs // version: 4 //! Physical source configuration, generated-state layout and controlled Git process runner. /// Explicit physical configuration for one controlled Git backend instance. #[derive(Clone, Debug, Eq, PartialEq)] pub struct GitBackendConfig { git_executable: std::path::PathBuf, state_root: std::path::PathBuf, } impl crate::GitBackendConfig { /// Build a backend configuration using `git` from the process environment. pub fn new(state_root: std::path::PathBuf) -> Result { return crate::GitBackendConfig::with_git_executable(state_root, std::path::PathBuf::from("git")); } /// Build a backend configuration with an explicit Git executable path. pub fn with_git_executable(state_root: std::path::PathBuf, git_executable: std::path::PathBuf) -> Result { if state_root.as_os_str().is_empty() { return Err(crate::GitBackendError::EmptyStateRoot); } if git_executable.as_os_str().is_empty() { return Err(crate::GitBackendError::EmptyGitExecutable); } return Ok(crate::GitBackendConfig { git_executable, state_root }); } /// Return the configured Git executable path. pub fn git_executable(&self) -> &std::path::Path { return self.git_executable.as_path(); } /// Return the exclusive generated-state root owned by this backend instance. pub fn state_root(&self) -> &std::path::Path { return self.state_root.as_path(); } /// Return the empty controlled hooks directory used to disable repository hooks. pub fn hooks_path(&self) -> std::path::PathBuf { return self.state_root.join("hooks-disabled"); } /// Return the directory that contains controlled bare mirrors. pub fn repositories_path(&self) -> std::path::PathBuf { return self.state_root.join("repositories"); } /// Return the directory reserved for revision-addressed snapshots. pub fn snapshots_path(&self) -> std::path::PathBuf { return self.state_root.join("snapshots"); } /// Return the directory reserved for temporary isolated write worktrees. pub fn write_worktrees_path(&self) -> std::path::PathBuf { return self.state_root.join("write-worktrees"); } /// Return the controlled empty global Git configuration path. pub fn empty_global_config_path(&self) -> std::path::PathBuf { return self.state_root.join("empty-global.gitconfig"); } } /// Coarse physical source kind used by the backend. #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub enum GitSourceKind { /// Source is a path on the local filesystem. Local, /// Source is a Git remote location interpreted by the Git executable. Remote, } /// Physical Git source configuration associated with one logical source identifier. #[derive(Clone, Debug, Eq, PartialEq)] pub struct GitSourceConfig { location: std::ffi::OsString, source_id: knowledge_domain_lib::SourceId, source_kind: crate::GitSourceKind, } impl crate::GitSourceConfig { /// Build a local Git source configuration. pub fn local(source_id: knowledge_domain_lib::SourceId, path: std::path::PathBuf) -> Result { if path.as_os_str().is_empty() { return Err(crate::GitBackendError::EmptySourceLocation); } return Ok(crate::GitSourceConfig { location: path.into_os_string(), source_id, source_kind: crate::GitSourceKind::Local }); } /// Build a remote Git source configuration without interpreting provider-specific syntax. pub fn remote(source_id: knowledge_domain_lib::SourceId, location: &str) -> Result { if location.trim().is_empty() { return Err(crate::GitBackendError::EmptySourceLocation); } return Ok(crate::GitSourceConfig { location: std::ffi::OsString::from(location), source_id, source_kind: crate::GitSourceKind::Remote }); } /// Return the logical source identifier associated with this physical source. pub fn source_id(&self) -> &knowledge_domain_lib::SourceId { return &self.source_id; } /// Return whether the source is local or remote. pub fn source_kind(&self) -> crate::GitSourceKind { return self.source_kind; } } /// Path-safe storage key derived from a logical Knowledge source identifier. #[derive(Clone, Debug, Eq, Hash, PartialEq)] pub struct GitSourceKey(String); impl crate::GitSourceKey { /// Derive a reversible collision-free key from the UTF-8 bytes of a logical source identifier. pub fn from_source_id(source_id: &knowledge_domain_lib::SourceId) -> crate::GitSourceKey { let mut value = String::from("source-"); for byte in source_id.as_str().as_bytes() { value.push_str(format!("{:02x}", *byte).as_str()); } return Self(value); } /// Return the path-safe key text. pub fn as_str(&self) -> &str { return self.0.as_str(); } } /// Result of ensuring that a controlled mirror exists. #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub enum GitMirrorStatus { /// The mirror was created by the current call. Created, /// A valid existing mirror was reused. Existing, } /// Description of one initialized or already existing controlled mirror. #[derive(Clone, Debug, Eq, PartialEq)] pub struct GitMirror { repository_path: std::path::PathBuf, source_key: crate::GitSourceKey, status: crate::GitMirrorStatus, } impl crate::GitMirror { /// Return the controlled bare-repository path. pub fn repository_path(&self) -> &std::path::Path { return self.repository_path.as_path(); } /// Return the path-safe key associated with the logical source identifier. pub fn source_key(&self) -> &crate::GitSourceKey { return &self.source_key; } /// Return whether the mirror was newly created or already existed. pub fn status(&self) -> crate::GitMirrorStatus { return self.status; } } #[derive(Clone, Debug, Eq, PartialEq)] struct GitCommandSpec { arguments: Vec, environment: Vec<(std::ffi::OsString, std::ffi::OsString)>, executable: std::path::PathBuf, global_config_path: std::path::PathBuf, hooks_path: std::path::PathBuf, operation: crate::GitOperation, } #[derive(Clone, Debug, Eq, PartialEq)] struct GitCommandOutput { stdout: Vec, } trait GitCommandRunner { fn run(&self, specification: &GitCommandSpec) -> Result; } struct SystemGitRunner; impl GitCommandRunner for SystemGitRunner { fn run(&self, specification: &GitCommandSpec) -> Result { let mut command = std::process::Command::new(&specification.executable); command.args(&specification.arguments); command.env("GIT_TERMINAL_PROMPT", "0"); command.env_remove("GIT_ALTERNATE_OBJECT_DIRECTORIES"); command.env_remove("GIT_COMMON_DIR"); command.env_remove("GIT_CONFIG_PARAMETERS"); command.env_remove("GIT_DIR"); command.env_remove("GIT_INDEX_FILE"); command.env_remove("GIT_OBJECT_DIRECTORY"); command.env_remove("GIT_WORK_TREE"); command.env_remove("GIT_AUTHOR_NAME"); command.env_remove("GIT_AUTHOR_EMAIL"); command.env_remove("GIT_COMMITTER_NAME"); command.env_remove("GIT_COMMITTER_EMAIL"); command.env_remove("GIT_CONFIG_GLOBAL"); command.env_remove("GIT_CONFIG_NOSYSTEM"); command.env("GIT_CONFIG_GLOBAL", &specification.global_config_path); command.env("GIT_CONFIG_NOSYSTEM", "1"); command.env("GIT_CONFIG_COUNT", "1"); command.env("GIT_CONFIG_KEY_0", "core.hooksPath"); command.env("GIT_CONFIG_VALUE_0", &specification.hooks_path); for (name, value) in &specification.environment { command.env(name, value); } let output_result = command.output(); let output = match output_result { Ok(value) => value, Err(_) => { return Err(crate::GitBackendError::GitUnavailable { operation: specification.operation }); }, }; if !output.status.success() { return Err(crate::GitBackendError::GitCommandFailed { operation: specification.operation, exit_code: output.status.code() }); } return Ok(GitCommandOutput { stdout: output.stdout }); } } /// Controlled Git backend that owns configured sources and local read materialization. pub struct GitBackend { config: crate::GitBackendConfig, runner: Box, sources: Vec, } impl crate::GitBackend { /// Create a backend using the configured real Git executable and no registered source yet. pub fn new(config: crate::GitBackendConfig) -> crate::GitBackend { return crate::GitBackend { config, runner: Box::new(SystemGitRunner), sources: Vec::new() }; } #[cfg(test)] fn with_runner(config: crate::GitBackendConfig, runner: Box) -> crate::GitBackend { return crate::GitBackend { config, runner, sources: Vec::new() }; } /// Return the immutable backend configuration. pub fn config(&self) -> &crate::GitBackendConfig { return &self.config; } /// Register one physical source configuration for provider-neutral operations. pub fn register_source(&mut self, source: crate::GitSourceConfig) -> Result<(), crate::GitBackendError> { for existing in &self.sources { if existing.source_id() == source.source_id() { return Err(crate::GitBackendError::DuplicateSource); } } self.sources.push(source); return Ok(()); } /// Derive the controlled mirror path without interpolating the raw source identifier. pub fn repository_path(&self, source_id: &knowledge_domain_lib::SourceId) -> std::path::PathBuf { let source_key = crate::GitSourceKey::from_source_id(source_id); return self.config.repositories_path().join(format!("{}.git", source_key.as_str())); } /// Derive the revision-addressed snapshot path after validating the revision as a safe object identity. pub fn snapshot_path( &self, source_id: &knowledge_domain_lib::SourceId, revision: &knowledge_git_api::GitRevision, ) -> Result { if !crate::is_safe_revision(revision.as_str()) { return Err(crate::GitBackendError::InvalidRevision); } let source_key = crate::GitSourceKey::from_source_id(source_id); return Ok(self.config.snapshots_path().join(source_key.as_str()).join(revision.as_str())); } /// Inspect one mirror when it already exists without creating it. pub fn inspect_mirror(&self, source_id: &knowledge_domain_lib::SourceId) -> Result, crate::GitBackendError> { let source_key = crate::GitSourceKey::from_source_id(source_id); let repository_path = self.repository_path(source_id); if !repository_path.exists() { return Ok(None); } let inspection_result = self.inspect_bare_repository(&repository_path); match inspection_result { Ok(()) => { return Ok(Some(crate::GitMirror { repository_path, source_key, status: crate::GitMirrorStatus::Existing })); }, Err(error) => return Err(error), } } /// Ensure that one configured source has a valid controlled bare mirror. pub fn ensure_mirror(&self, source: &crate::GitSourceConfig) -> Result { let preparation_result = self.prepare_state_directories(); match preparation_result { Ok(()) => (), Err(error) => return Err(error), } let source_key = crate::GitSourceKey::from_source_id(source.source_id()); let repository_path = self.repository_path(source.source_id()); if repository_path.exists() { let inspection_result = self.inspect_bare_repository(&repository_path); match inspection_result { Ok(()) => { return Ok(crate::GitMirror { repository_path, source_key, status: crate::GitMirrorStatus::Existing }); }, Err(error) => return Err(error), } } let clone_result = self.clone_mirror(source, &repository_path); match clone_result { Ok(()) => (), Err(error) => return Err(error), } let inspection_result = self.inspect_bare_repository(&repository_path); match inspection_result { Ok(()) => { return Ok(crate::GitMirror { repository_path, source_key, status: crate::GitMirrorStatus::Created }); }, Err(error) => return Err(error), } } pub(crate) fn configured_source(&self, source_id: &knowledge_domain_lib::SourceId) -> Result<&crate::GitSourceConfig, crate::GitBackendError> { for source in &self.sources { if source.source_id() == source_id { return Ok(source); } } return Err(crate::GitBackendError::SourceNotConfigured); } pub(crate) fn prepare_state_directories(&self) -> Result<(), crate::GitBackendError> { for path in [self.config.hooks_path(), self.config.repositories_path(), self.config.snapshots_path(), self.config.write_worktrees_path()] { let create_result = std::fs::create_dir_all(path); if create_result.is_err() { return Err(crate::GitBackendError::StatePreparationFailed); } } let config_path = self.config.empty_global_config_path(); if !config_path.exists() { let write_result = std::fs::write(config_path, b""); if write_result.is_err() { return Err(crate::GitBackendError::StatePreparationFailed); } } return Ok(()); } pub(crate) fn run_controlled_git( &self, operation: crate::GitOperation, arguments: Vec, environment: Vec<(std::ffi::OsString, std::ffi::OsString)>, ) -> Result, crate::GitBackendError> { let specification = GitCommandSpec { arguments, environment, executable: self.config.git_executable().to_path_buf(), global_config_path: self.config.empty_global_config_path(), hooks_path: self.config.hooks_path(), operation, }; let result = self.runner.run(&specification); match result { Ok(output) => return Ok(output.stdout), Err(error) => return Err(error), } } fn clone_mirror(&self, source: &crate::GitSourceConfig, repository_path: &std::path::Path) -> Result<(), crate::GitBackendError> { let mut arguments = vec![std::ffi::OsString::from("clone"), std::ffi::OsString::from("--mirror")]; if source.source_kind == crate::GitSourceKind::Local { arguments.push(std::ffi::OsString::from("--no-local")); } arguments.push(std::ffi::OsString::from("--")); arguments.push(source.location.clone()); arguments.push(repository_path.as_os_str().to_os_string()); let specification = GitCommandSpec { arguments, environment: Vec::new(), executable: self.config.git_executable().to_path_buf(), global_config_path: self.config.empty_global_config_path(), hooks_path: self.config.hooks_path(), operation: crate::GitOperation::CloneMirror, }; let run_result = self.runner.run(&specification); match run_result { Ok(_) => return Ok(()), Err(error) => return Err(error), } } fn inspect_bare_repository(&self, repository_path: &std::path::Path) -> Result<(), crate::GitBackendError> { let specification = GitCommandSpec { arguments: vec![ std::ffi::OsString::from("--git-dir"), repository_path.as_os_str().to_os_string(), std::ffi::OsString::from("rev-parse"), std::ffi::OsString::from("--is-bare-repository"), ], environment: Vec::new(), executable: self.config.git_executable().to_path_buf(), global_config_path: self.config.empty_global_config_path(), hooks_path: self.config.hooks_path(), operation: crate::GitOperation::InspectMirror, }; let run_result = self.runner.run(&specification); let output = match run_result { Ok(value) => value, Err(error) => return Err(error), }; let stdout_result = std::str::from_utf8(&output.stdout); let stdout = match stdout_result { Ok(value) => value.trim(), Err(_) => { return Err(crate::GitBackendError::UnexpectedGitOutput { operation: crate::GitOperation::InspectMirror }); }, }; if stdout != "true" { return Err(crate::GitBackendError::InvalidMirror); } return Ok(()); } fn refresh_registered_source(&self, source_id: &knowledge_domain_lib::SourceId) -> Result<(), crate::GitBackendError> { let source_result = self.configured_source(source_id); let source = match source_result { Ok(value) => value, Err(error) => return Err(error), }; let mirror_result = self.ensure_mirror(source); let mirror = match mirror_result { Ok(value) => value, Err(error) => return Err(error), }; let specification = GitCommandSpec { arguments: vec![ std::ffi::OsString::from("--git-dir"), mirror.repository_path().as_os_str().to_os_string(), std::ffi::OsString::from("remote"), std::ffi::OsString::from("update"), std::ffi::OsString::from("--prune"), ], environment: Vec::new(), executable: self.config.git_executable().to_path_buf(), global_config_path: self.config.empty_global_config_path(), hooks_path: self.config.hooks_path(), operation: crate::GitOperation::RefreshMirror, }; let run_result = self.runner.run(&specification); match run_result { Ok(_) => return Ok(()), Err(error) => return Err(error), } } fn resolve_registered_revision( &self, source_id: &knowledge_domain_lib::SourceId, reference: &knowledge_git_api::GitReference, ) -> Result { let configured_result = self.configured_source(source_id); match configured_result { Ok(_) => (), Err(error) => return Err(error), } let mirror_result = self.inspect_mirror(source_id); let mirror = match mirror_result { Ok(Some(value)) => value, Ok(None) => return Err(crate::GitBackendError::MirrorMissing), Err(error) => return Err(error), }; let revision_expression = format!("{}^{{commit}}", reference.as_str()); let specification = GitCommandSpec { arguments: vec![ std::ffi::OsString::from("--git-dir"), mirror.repository_path().as_os_str().to_os_string(), std::ffi::OsString::from("rev-parse"), std::ffi::OsString::from("--verify"), std::ffi::OsString::from("--end-of-options"), std::ffi::OsString::from(revision_expression), ], environment: Vec::new(), executable: self.config.git_executable().to_path_buf(), global_config_path: self.config.empty_global_config_path(), hooks_path: self.config.hooks_path(), operation: crate::GitOperation::ResolveRevision, }; let run_result = self.runner.run(&specification); let output = match run_result { Ok(value) => value, Err(error) => return Err(error), }; let stdout_result = std::str::from_utf8(&output.stdout); let stdout = match stdout_result { Ok(value) => value.trim(), Err(_) => { return Err(crate::GitBackendError::UnexpectedGitOutput { operation: crate::GitOperation::ResolveRevision }); }, }; if !crate::is_safe_revision(stdout) { return Err(crate::GitBackendError::UnexpectedGitOutput { operation: crate::GitOperation::ResolveRevision }); } return Ok(knowledge_git_api::GitRevision::new(stdout)); } fn prepare_registered_snapshot( &self, source_id: &knowledge_domain_lib::SourceId, revision: &knowledge_git_api::GitRevision, ) -> Result { let configured_result = self.configured_source(source_id); match configured_result { Ok(_) => (), Err(error) => return Err(error), } if !crate::is_safe_revision(revision.as_str()) { return Err(crate::GitBackendError::InvalidRevision); } let mirror_result = self.inspect_mirror(source_id); let mirror = match mirror_result { Ok(Some(value)) => value, Ok(None) => return Err(crate::GitBackendError::MirrorMissing), Err(error) => return Err(error), }; let snapshot_path_result = self.snapshot_path(source_id, revision); let snapshot_path = match snapshot_path_result { Ok(value) => value, Err(error) => return Err(error), }; if snapshot_path.exists() { let validation_result = self.validate_snapshot(&snapshot_path, revision); match validation_result { Ok(()) => return Ok(knowledge_git_api::GitSnapshot::new(revision.clone(), snapshot_path)), Err(error) => return Err(error), } } let parent = match snapshot_path.parent() { Some(value) => value, None => return Err(crate::GitBackendError::StatePreparationFailed), }; let create_result = std::fs::create_dir_all(parent); if create_result.is_err() { return Err(crate::GitBackendError::StatePreparationFailed); } let specification = GitCommandSpec { arguments: vec![ std::ffi::OsString::from("--git-dir"), mirror.repository_path().as_os_str().to_os_string(), std::ffi::OsString::from("worktree"), std::ffi::OsString::from("add"), std::ffi::OsString::from("--detach"), std::ffi::OsString::from("--"), snapshot_path.as_os_str().to_os_string(), std::ffi::OsString::from(revision.as_str()), ], environment: Vec::new(), executable: self.config.git_executable().to_path_buf(), global_config_path: self.config.empty_global_config_path(), hooks_path: self.config.hooks_path(), operation: crate::GitOperation::CreateSnapshot, }; let run_result = self.runner.run(&specification); match run_result { Ok(_) => (), Err(error) => return Err(error), } let validation_result = self.validate_snapshot(&snapshot_path, revision); match validation_result { Ok(()) => return Ok(knowledge_git_api::GitSnapshot::new(revision.clone(), snapshot_path)), Err(error) => return Err(error), } } fn validate_snapshot(&self, snapshot_path: &std::path::Path, revision: &knowledge_git_api::GitRevision) -> Result<(), crate::GitBackendError> { if !snapshot_path.is_dir() { return Err(crate::GitBackendError::InvalidSnapshot); } let head_specification = GitCommandSpec { arguments: vec![ std::ffi::OsString::from("-C"), snapshot_path.as_os_str().to_os_string(), std::ffi::OsString::from("rev-parse"), std::ffi::OsString::from("--verify"), std::ffi::OsString::from("HEAD"), ], environment: Vec::new(), executable: self.config.git_executable().to_path_buf(), global_config_path: self.config.empty_global_config_path(), hooks_path: self.config.hooks_path(), operation: crate::GitOperation::InspectSnapshot, }; let head_result = self.runner.run(&head_specification); let head_output = match head_result { Ok(value) => value, Err(_) => return Err(crate::GitBackendError::InvalidSnapshot), }; let head_text_result = std::str::from_utf8(&head_output.stdout); let head_text = match head_text_result { Ok(value) => value.trim(), Err(_) => return Err(crate::GitBackendError::InvalidSnapshot), }; if head_text != revision.as_str() { return Err(crate::GitBackendError::InvalidSnapshot); } let status_specification = GitCommandSpec { arguments: vec![ std::ffi::OsString::from("-C"), snapshot_path.as_os_str().to_os_string(), std::ffi::OsString::from("status"), std::ffi::OsString::from("--porcelain=v1"), std::ffi::OsString::from("--untracked-files=all"), ], environment: Vec::new(), executable: self.config.git_executable().to_path_buf(), global_config_path: self.config.empty_global_config_path(), hooks_path: self.config.hooks_path(), operation: crate::GitOperation::InspectSnapshot, }; let status_result = self.runner.run(&status_specification); let status_output = match status_result { Ok(value) => value, Err(_) => return Err(crate::GitBackendError::InvalidSnapshot), }; if !status_output.stdout.is_empty() { return Err(crate::GitBackendError::InvalidSnapshot); } return Ok(()); } } impl knowledge_git_api::GitRepository for crate::GitBackend { type Error = crate::GitBackendError; fn refresh(&mut self, source_id: &knowledge_domain_lib::SourceId) -> Result<(), Self::Error> { return self.refresh_registered_source(source_id); } fn resolve_revision( &self, source_id: &knowledge_domain_lib::SourceId, reference: &knowledge_git_api::GitReference, ) -> Result { return self.resolve_registered_revision(source_id, reference); } fn prepare_snapshot( &self, source_id: &knowledge_domain_lib::SourceId, revision: &knowledge_git_api::GitRevision, ) -> Result { return self.prepare_registered_snapshot(source_id, revision); } } pub(crate) fn is_safe_revision(value: &str) -> bool { if value.is_empty() { return false; } for character in value.chars() { if !character.is_ascii_hexdigit() { return false; } } return true; } #[cfg(test)] #[path = "../unit_tests/backend.rs"] mod tests;