//! Commit operations for the Store. //! //! Version control through commits and name bindings. use crate::id::{NumericId, Slid}; use super::append::AppendOps; use super::{BindingKind, Store}; impl Store { /// Create a new commit pub fn commit(&mut self, message: Option<&str>) -> Result { let sort_id = self.sort_ids.commit.ok_or("Commit sort not found")?; let commit_slid = self.add_element(sort_id, message.unwrap_or("commit")); // Set parent if there's a head if let Some(head) = self.head { let parent_func = self.func_ids.commit_parent.ok_or("Commit/parent not found")?; self.define_func(parent_func, commit_slid, head)?; } // Create NameBindings for all uncommitted changes let nb_sort = self.sort_ids.name_binding.ok_or("NameBinding sort not found")?; let commit_func = self.func_ids.name_binding_commit.ok_or("NameBinding/commit not found")?; let theory_func = self.func_ids.name_binding_theory.ok_or("NameBinding/theory not found")?; let instance_func = self.func_ids.name_binding_instance.ok_or("NameBinding/instance not found")?; // Collect uncommitted to avoid borrow issues let uncommitted: Vec<_> = self.uncommitted.drain().collect(); for (name, binding) in uncommitted { let nb_slid = self.add_element(nb_sort, &format!("nb_{}_{}", name, commit_slid.index())); self.define_func(commit_func, nb_slid, commit_slid)?; match binding.kind { BindingKind::Theory => { self.define_func(theory_func, nb_slid, binding.target)?; } BindingKind::Instance => { self.define_func(instance_func, nb_slid, binding.target)?; } } } // Update head self.head = Some(commit_slid); // Auto-save self.save()?; Ok(commit_slid) } /// Get the current binding for a name (from HEAD commit or uncommitted) pub fn resolve_name(&self, name: &str) -> Option<(Slid, BindingKind)> { // Check uncommitted first if let Some(binding) = self.uncommitted.get(name) { return Some((binding.target, binding.kind)); } // Search through name bindings from HEAD backwards (if we have commits) if let (Some(head), Some(nb_sort), Some(commit_func), Some(theory_func), Some(instance_func)) = ( self.head, self.sort_ids.name_binding, self.func_ids.name_binding_commit, self.func_ids.name_binding_theory, self.func_ids.name_binding_instance, ) { let mut current = Some(head); while let Some(commit) = current { // Find all NameBindings for this commit for nb_slid in self.elements_of_sort(nb_sort) { if self.get_func(commit_func, nb_slid) == Some(commit) { // Check if this binding is for our name let nb_name = self.get_element_name(nb_slid); if nb_name.starts_with(&format!("nb_{}_", name)) { // Found it! Return the target if let Some(theory) = self.get_func(theory_func, nb_slid) { return Some((theory, BindingKind::Theory)); } if let Some(instance) = self.get_func(instance_func, nb_slid) { return Some((instance, BindingKind::Instance)); } } } } // Move to parent commit if let Some(parent_func) = self.func_ids.commit_parent { current = self.get_func(parent_func, commit); } else { break; } } } // Fallback: search directly in meta Structure for uncommitted theories/instances // This handles the case where data exists in meta.bin but no commit was made yet if let Some(theory_sort) = self.sort_ids.theory { for slid in self.elements_of_sort(theory_sort) { if self.get_element_name(slid) == name { return Some((slid, BindingKind::Theory)); } } } if let Some(instance_sort) = self.sort_ids.instance { for slid in self.elements_of_sort(instance_sort) { if self.get_element_name(slid) == name { return Some((slid, BindingKind::Instance)); } } } None } /// Get all commits in order (oldest to newest) pub fn commit_history(&self) -> Vec { let Some(head) = self.head else { return vec![]; }; let mut chain = Vec::new(); let mut current = Some(head); while let Some(commit) = current { chain.push(commit); current = self .func_ids .commit_parent .and_then(|f| self.get_func(f, commit)); } chain.reverse(); chain } /// List all committed bindings (theories and instances) /// /// Returns (name, kind, target_slid) for each binding visible from HEAD. /// Names may appear multiple times if rebound in different commits. pub fn list_bindings(&self) -> Vec<(String, BindingKind, Slid)> { let Some(head) = self.head else { return vec![]; }; let Some(nb_sort) = self.sort_ids.name_binding else { return vec![]; }; let Some(commit_func) = self.func_ids.name_binding_commit else { return vec![]; }; let Some(theory_func) = self.func_ids.name_binding_theory else { return vec![]; }; let Some(instance_func) = self.func_ids.name_binding_instance else { return vec![]; }; let mut bindings = Vec::new(); let mut seen_names = std::collections::HashSet::new(); // Walk commits from head backwards let mut current = Some(head); while let Some(commit) = current { // Find all NameBindings for this commit for nb_slid in self.elements_of_sort(nb_sort) { if self.get_func(commit_func, nb_slid) == Some(commit) { // Extract name from "nb_{name}_{commit_id}" let nb_name = self.get_element_name(nb_slid); if let Some(name) = extract_binding_name(&nb_name) { // Only include first (most recent) binding for each name if seen_names.insert(name.clone()) { if let Some(theory) = self.get_func(theory_func, nb_slid) { bindings.push((name, BindingKind::Theory, theory)); } else if let Some(instance) = self.get_func(instance_func, nb_slid) { bindings.push((name, BindingKind::Instance, instance)); } } } } } // Move to parent commit current = self .func_ids .commit_parent .and_then(|f| self.get_func(f, commit)); } bindings } } /// Extract the name from a binding element name like "nb_Graph_2" fn extract_binding_name(nb_name: &str) -> Option { // Format: "nb_{name}_{commit_id}" if !nb_name.starts_with("nb_") { return None; } let rest = &nb_name[3..]; // Skip "nb_" // Find the last underscore (before commit_id) if let Some(last_underscore) = rest.rfind('_') { // Verify the part after underscore is a number if rest[last_underscore + 1..].parse::().is_ok() { return Some(rest[..last_underscore].to_string()); } } None }