diff --git a/src/commands/add.rs b/src/commands/add.rs index 32e0b14..11e9b3d 100644 --- a/src/commands/add.rs +++ b/src/commands/add.rs @@ -49,77 +49,85 @@ fn collect_files_recursively(dir: &PathBuf) -> Result> { /// Main function for the `guts add` command /// Adds files to the staging area (index) pub fn run(args: &AddArgs) -> Result { - // Determine current directory to use - let current_dir = args - .dir - .clone() - .unwrap_or_else(|| std::env::current_dir().expect("failed to get current directory")); - - // Check if we're in a git repository - if !simple_index::is_git_repository_from(Some(¤t_dir))? { - return Err(anyhow!("fatal: not a git repository")); + // Set current directory context for TUI + let original_dir = std::env::current_dir()?; + if let Some(dir) = &args.dir { + std::env::set_current_dir(dir)?; } + + let result = || -> Result { + // Check if we're in a git repository + if !simple_index::is_git_repository()? { + return Err(anyhow!("fatal: not a git repository")); + } - let mut added_files = Vec::new(); - let mut output = String::new(); - - // Load .gutsignore matcher - let matcher = IgnoreMatcher::from_gutsignore(¤t_dir) - .unwrap_or_else(|_| IgnoreMatcher::empty()); - - // Process each requested file - for file_path in &args.files { - // Support for "." - add all files from current directory - if file_path.to_string_lossy() == "." { - let files = collect_files_recursively(¤t_dir)?; - for file in files { - if matcher.is_ignored(&file, ¤t_dir) { - continue; + let mut added_files = Vec::new(); + let mut output = String::new(); + let current_dir = std::env::current_dir()?; + + // Load .gutsignore matcher + let matcher = IgnoreMatcher::from_gutsignore(¤t_dir) + .unwrap_or_else(|_| IgnoreMatcher::empty()); + + // Process each requested file + for file_path in &args.files { + // Support for "." - add all files from current directory + if file_path.to_string_lossy() == "." { + let files = collect_files_recursively(¤t_dir)?; + for file in files { + if matcher.is_ignored(&file, ¤t_dir) { + continue; + } + simple_index::add_file_to_index(&file)?; + added_files.push(file.display().to_string()); } - simple_index::add_file_to_index_from(&file, Some(¤t_dir))?; - added_files.push(file.display().to_string()); + continue; } - continue; - } - // Basic checks - if !file_path.exists() { - return Err(anyhow!( - "pathspec '{}' did not match any files", - file_path.display() - )); - } + // Basic checks + if !file_path.exists() { + return Err(anyhow!( + "pathspec '{}' did not match any files", + file_path.display() + )); + } - if file_path.is_dir() { - // If it's a directory, add all files recursively - let files = collect_files_recursively(file_path)?; - for file in files { - if matcher.is_ignored(&file, ¤t_dir) { + if file_path.is_dir() { + // If it's a directory, add all files recursively + let files = collect_files_recursively(file_path)?; + for file in files { + if matcher.is_ignored(&file, ¤t_dir) { + continue; + } + simple_index::add_file_to_index(&file)?; + added_files.push(file.display().to_string()); + } + } else { + // Skip if ignored + if matcher.is_ignored(file_path, ¤t_dir) { continue; } - simple_index::add_file_to_index_from(&file, Some(¤t_dir))?; - added_files.push(file.display().to_string()); - } - } else { - // Skip if ignored - if matcher.is_ignored(file_path, ¤t_dir) { - continue; + // Add the file to the JSON index + simple_index::add_file_to_index(file_path)?; + added_files.push(file_path.display().to_string()); } - // Add the file to the JSON index - simple_index::add_file_to_index_from(file_path, Some(¤t_dir))?; - added_files.push(file_path.display().to_string()); } - } - // Confirmation message - if added_files.len() == 1 { - output.push_str(&format!("Added: {}", added_files[0])); - } else { - output.push_str(&format!("Added {} files:", added_files.len())); - for file in &added_files { - output.push_str(&format!("\n - {}", file)); + // Confirmation message + if added_files.len() == 1 { + output.push_str(&format!("Added: {}", added_files[0])); + } else { + output.push_str(&format!("Added {} files:", added_files.len())); + for file in &added_files { + output.push_str(&format!("\n - {}", file)); + } } - } - Ok(output) + Ok(output) + }(); + + // Restore original directory + std::env::set_current_dir(&original_dir)?; + + result } diff --git a/src/commands/commit.rs b/src/commands/commit.rs index 3769aeb..0476fd9 100644 --- a/src/commands/commit.rs +++ b/src/commands/commit.rs @@ -35,7 +35,7 @@ pub fn run(args: &CommitArgs) -> Result { fn run_commit(args: &CommitArgs) -> Result { // Check if we're in a git repository - if !simple_index::is_git_repository_from(args.dir.as_ref())? { + if !simple_index::is_git_repository()? { return Err(anyhow::anyhow!("fatal: not a git repository")); } diff --git a/src/commands/log.rs b/src/commands/log.rs index 2ae4f5f..b4ce2f8 100644 --- a/src/commands/log.rs +++ b/src/commands/log.rs @@ -15,16 +15,19 @@ pub struct LogArgs { /// Entry point for the `guts log` command /// Traverses the commit chain from HEAD to root, printing each commit's SHA and first line of message. pub fn run(args: &LogArgs) -> Result { - // Determine current directory to use - let current_dir = args - .dir - .clone() - .unwrap_or_else(|| std::env::current_dir().expect("failed to get current directory")); - - // Check if we're in a git repository - if !simple_index::is_git_repository_from(args.dir.as_ref())? { - return Err(anyhow!("fatal: not a git repository")); + // Set current directory context for TUI + let original_dir = std::env::current_dir()?; + if let Some(dir) = &args.dir { + std::env::set_current_dir(dir)?; } + + let result = || -> Result { + // Check if we're in a git repository + if !simple_index::is_git_repository()? { + return Err(anyhow!("fatal: not a git repository")); + } + + let current_dir = std::env::current_dir()?; // Use the standard .git directory let git_dir = current_dir.join(".git"); @@ -79,7 +82,13 @@ pub fn run(args: &LogArgs) -> Result { } } - Ok(output) + Ok(output) + }(); + + // Restore original directory + std::env::set_current_dir(&original_dir)?; + + result } diff --git a/src/commands/rm.rs b/src/commands/rm.rs index b474003..50701fa 100644 --- a/src/commands/rm.rs +++ b/src/commands/rm.rs @@ -48,10 +48,17 @@ fn remove_file_from_index(file_path: &PathBuf) -> Result { /// Main function for the `guts rm` command /// Removes files from working directory and index pub fn run(args: &RmArgs) -> Result { - // Check if we're in a git repository - if !simple_index::is_git_repository_from(args.dir.as_ref())? { - return Err(anyhow!("fatal: not a git repository")); + // Set current directory context for TUI + let original_dir = std::env::current_dir()?; + if let Some(dir) = &args.dir { + std::env::set_current_dir(dir)?; } + + let result = || -> Result { + // Check if we're in a git repository + if !simple_index::is_git_repository()? { + return Err(anyhow!("fatal: not a git repository")); + } let mut removed_files = Vec::new(); let mut output = String::new(); @@ -100,5 +107,11 @@ pub fn run(args: &RmArgs) -> Result { output.pop(); // Remove last newline } - Ok(output) + Ok(output) + }(); + + // Restore original directory + std::env::set_current_dir(&original_dir)?; + + result } diff --git a/src/commands/status.rs b/src/commands/status.rs index 3f62035..141b95d 100644 --- a/src/commands/status.rs +++ b/src/commands/status.rs @@ -14,20 +14,23 @@ pub struct StatusObject { /// Entry point for the `guts status` command pub fn run(args: &StatusObject) -> Result { - let current_dir = args - .dir - .clone() - .unwrap_or_else(|| std::env::current_dir().expect("failed to get current directory")); - - if !simple_index::is_git_repository_from(Some(¤t_dir))? { - return Ok("fatal: not a git repository".to_string()); + // Set current directory context for TUI + let original_dir = std::env::current_dir()?; + if let Some(dir) = &args.dir { + std::env::set_current_dir(dir)?; } + + let result = || -> Result { + if !simple_index::is_git_repository()? { + return Ok("fatal: not a git repository".to_string()); + } - let matcher = IgnoreMatcher::from_gutsignore(¤t_dir) - .unwrap_or_else(|_| IgnoreMatcher::empty()); + let current_dir = std::env::current_dir()?; + let matcher = IgnoreMatcher::from_gutsignore(¤t_dir) + .unwrap_or_else(|_| IgnoreMatcher::empty()); - let committed_files = simple_index::get_committed_files_from(Some(¤t_dir))?; - let index = simple_index::SimpleIndex::load_from(Some(¤t_dir))?; + let committed_files = simple_index::get_committed_files()?; + let index = simple_index::SimpleIndex::load()?; let work_files = list_working_dir_files(¤t_dir, &matcher)?; let current_branch = read_head::get_current_branch() @@ -121,11 +124,17 @@ pub fn run(args: &StatusObject) -> Result { output.push_str("\n"); } - if staged_changes.is_empty() && unstaged_changes.is_empty() && untracked_files.is_empty() { - output.push_str("nothing to commit, working tree clean\n"); - } + if staged_changes.is_empty() && unstaged_changes.is_empty() && untracked_files.is_empty() { + output.push_str("nothing to commit, working tree clean\n"); + } - Ok(output) + Ok(output) + }(); + + // Restore original directory + std::env::set_current_dir(&original_dir)?; + + result } /// List all working directory files, excluding ignored and .git files @@ -153,9 +162,9 @@ fn list_working_dir_files(current_dir: &PathBuf, matcher: &IgnoreMatcher) -> Res Ok(files) } -fn get_relative_path(file_path: &PathBuf, current_dir: &PathBuf) -> Result { - // Find repo root from current directory context - let repo_root = simple_index::find_repo_root_from(Some(current_dir))?; +fn get_relative_path(file_path: &PathBuf, _current_dir: &PathBuf) -> Result { + // Find repo root from current working directory + let repo_root = simple_index::find_repo_root()?; let relative = file_path .strip_prefix(&repo_root) .map_err(|_| anyhow::anyhow!("file is not in the repository"))?; diff --git a/src/commands/write_tree.rs b/src/commands/write_tree.rs index 0d00542..8ead309 100644 --- a/src/commands/write_tree.rs +++ b/src/commands/write_tree.rs @@ -12,10 +12,17 @@ pub struct WriteTreeArgs { /// New version of write-tree that uses the simple JSON index /// Instead of reading the filesystem, reads the index to create the tree pub fn run(args: &WriteTreeArgs) -> Result { - // Check if we're in a git repository - if !simple_index::is_git_repository_from(args.dir.as_ref())? { - return Err(anyhow::anyhow!("fatal: not a git repository")); + // Set current directory context for TUI + let original_dir = std::env::current_dir()?; + if let Some(dir) = &args.dir { + std::env::set_current_dir(dir)?; } + + let result = || -> Result { + // Check if we're in a git repository + if !simple_index::is_git_repository()? { + return Err(anyhow::anyhow!("fatal: not a git repository")); + } // Load the JSON index let index = simple_index::SimpleIndex::load()?; @@ -26,7 +33,13 @@ pub fn run(args: &WriteTreeArgs) -> Result { // Write the tree object and return its hash let oid = hash::write_object(&tree)?; - Ok(oid) + Ok(oid) + }(); + + // Restore original directory + std::env::set_current_dir(&original_dir)?; + + result } /// Build a Git tree object from the JSON index diff --git a/src/core/simple_index.rs b/src/core/simple_index.rs index 2b2c1d6..678a8e8 100644 --- a/src/core/simple_index.rs +++ b/src/core/simple_index.rs @@ -8,6 +8,23 @@ use std::collections::HashMap; use std::fs; use std::path::{Path, PathBuf}; +/// Helper function to execute a closure with a temporary current directory +/// This is the simple and safe way to handle directory context for TUI +pub fn with_dir(dir: Option<&PathBuf>, f: F) -> Result +where + F: FnOnce() -> Result, +{ + if let Some(dir) = dir { + let original_dir = std::env::current_dir()?; + std::env::set_current_dir(dir)?; + let result = f(); + std::env::set_current_dir(&original_dir)?; + result + } else { + f() + } +} + /// Simple structure for Git index /// Stores only "staged" files with their SHA-1 hash #[derive(Serialize, Deserialize, Default, Debug)] @@ -35,22 +52,6 @@ impl SimpleIndex { Ok(index) } - /// Load index from .git/simple_index.json from a specific directory - pub fn load_from(start_dir: Option<&PathBuf>) -> Result { - let index_path = get_simple_index_path_from(start_dir)?; - - if !index_path.exists() { - return Ok(SimpleIndex::default()); - } - - let content = fs::read_to_string(&index_path) - .with_context(|| format!("unable to read {:?}", index_path))?; - - let index: SimpleIndex = - serde_json::from_str(&content).with_context(|| "invalid JSON in index")?; - - Ok(index) - } /// Save index to .git/simple_index.json pub fn save(&self) -> Result<()> { @@ -125,11 +126,6 @@ fn get_simple_index_path() -> Result { Ok(repo_root.join(".git").join("simple_index.json")) } -/// Return path to .git/simple_index.json from a specific directory -fn get_simple_index_path_from(start_dir: Option<&PathBuf>) -> Result { - let repo_root = find_repo_root_from(start_dir)?; - Ok(repo_root.join(".git").join("simple_index.json")) -} /// Convert absolute path to relative path from repo root fn get_relative_path(file_path: &Path) -> Result { @@ -269,100 +265,3 @@ fn decompress_object(data: &[u8]) -> Result> { } } -/// Find Git repository root from a specific directory -pub fn find_repo_root_from(start_dir: Option<&PathBuf>) -> Result { - let mut current = match start_dir { - Some(dir) => dir.clone(), - None => std::env::current_dir().with_context(|| "unable to get current directory")?, - }; - - loop { - let git_dir = current.join(".git"); - if git_dir.exists() && git_dir.is_dir() { - return Ok(current); - } - - match current.parent() { - Some(parent) => current = parent.to_path_buf(), - None => return Err(anyhow!("not a git repository")), - } - } -} - -/// Check if we're in a Git repository from a specific directory -pub fn is_git_repository_from(start_dir: Option<&PathBuf>) -> Result { - match find_repo_root_from(start_dir) { - Ok(_) => Ok(true), - Err(_) => Ok(false), - } -} - -/// Add a file to the index from a specific directory context -pub fn add_file_to_index_from(file_path: &Path, start_dir: Option<&PathBuf>) -> Result<()> { - // Set current directory context if provided - let original_dir = std::env::current_dir()?; - - if let Some(dir) = start_dir { - std::env::set_current_dir(dir)?; - } - - // Use existing add_file_to_index function - let result = add_file_to_index(file_path); - - // Restore original directory - std::env::set_current_dir(&original_dir)?; - - result -} - -/// Get the files committed in the current HEAD from a specific directory -/// Returns a HashMap: relative file path -> SHA-1 hash -pub fn get_committed_files_from(start_dir: Option<&PathBuf>) -> Result> { - let repo_root = find_repo_root_from(start_dir)?; - let git_dir = repo_root.join(".git"); - - // Read HEAD to get current commit - let head_path = git_dir.join("HEAD"); - if !head_path.exists() { - // No commits yet - return Ok(HashMap::new()); - } - - let head_content = fs::read_to_string(&head_path)?; - let head_content = head_content.trim(); - - // Get the commit hash - let commit_hash = if head_content.starts_with("ref: ") { - // HEAD points to a branch - let ref_path = head_content.strip_prefix("ref: ").unwrap(); - let ref_file = git_dir.join(ref_path); - - if !ref_file.exists() { - // Branch exists but no commits yet - return Ok(HashMap::new()); - } - - fs::read_to_string(ref_file)?.trim().to_string() - } else { - // Detached HEAD, direct commit hash - head_content.to_string() - }; - - // Read the commit object to get the tree hash - let commit_obj_path = cat::get_object_path(&git_dir, &commit_hash); - if !commit_obj_path.exists() { - return Ok(HashMap::new()); - } - - let commit_data = fs::read(&commit_obj_path)?; - let decompressed = decompress_object(&commit_data)?; - let parsed = cat::parse_object(&decompressed)?; - - let tree_hash = match parsed { - cat::ParsedObject::Commit(commit) => commit.tree, - _ => return Err(anyhow!("HEAD does not point to a commit object")), - }; - - // Read the tree object to get the files - get_files_from_tree(&git_dir, &tree_hash, "") -}