diff --git a/src/commands/add.rs b/src/commands/add.rs index 5af62ae..32e0b14 100644 --- a/src/commands/add.rs +++ b/src/commands/add.rs @@ -49,20 +49,20 @@ 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()? { + if !simple_index::is_git_repository_from(Some(¤t_dir))? { return Err(anyhow!("fatal: not a git repository")); } let mut added_files = Vec::new(); let mut output = String::new(); - // Determine current directory to use - let current_dir = args - .dir - .clone() - .unwrap_or_else(|| std::env::current_dir().expect("failed to get current directory")); - // Load .gutsignore matcher let matcher = IgnoreMatcher::from_gutsignore(¤t_dir) .unwrap_or_else(|_| IgnoreMatcher::empty()); @@ -76,7 +76,7 @@ pub fn run(args: &AddArgs) -> Result { if matcher.is_ignored(&file, ¤t_dir) { continue; } - simple_index::add_file_to_index(&file)?; + simple_index::add_file_to_index_from(&file, Some(¤t_dir))?; added_files.push(file.display().to_string()); } continue; @@ -97,7 +97,7 @@ pub fn run(args: &AddArgs) -> Result { if matcher.is_ignored(&file, ¤t_dir) { continue; } - simple_index::add_file_to_index(&file)?; + simple_index::add_file_to_index_from(&file, Some(¤t_dir))?; added_files.push(file.display().to_string()); } } else { @@ -106,7 +106,7 @@ pub fn run(args: &AddArgs) -> Result { continue; } // Add the file to the JSON index - simple_index::add_file_to_index(file_path)?; + simple_index::add_file_to_index_from(file_path, Some(¤t_dir))?; added_files.push(file_path.display().to_string()); } } diff --git a/src/commands/commit.rs b/src/commands/commit.rs index 0476fd9..3769aeb 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()? { + if !simple_index::is_git_repository_from(args.dir.as_ref())? { return Err(anyhow::anyhow!("fatal: not a git repository")); } diff --git a/src/commands/log.rs b/src/commands/log.rs index ecf7e57..2ae4f5f 100644 --- a/src/commands/log.rs +++ b/src/commands/log.rs @@ -22,7 +22,7 @@ pub fn run(args: &LogArgs) -> Result { .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()? { + if !simple_index::is_git_repository_from(args.dir.as_ref())? { return Err(anyhow!("fatal: not a git repository")); } diff --git a/src/commands/rm.rs b/src/commands/rm.rs index 6af113e..b474003 100644 --- a/src/commands/rm.rs +++ b/src/commands/rm.rs @@ -49,7 +49,7 @@ fn remove_file_from_index(file_path: &PathBuf) -> Result { /// 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()? { + if !simple_index::is_git_repository_from(args.dir.as_ref())? { return Err(anyhow!("fatal: not a git repository")); } diff --git a/src/commands/status.rs b/src/commands/status.rs index de5f46e..3f62035 100644 --- a/src/commands/status.rs +++ b/src/commands/status.rs @@ -19,15 +19,15 @@ pub fn run(args: &StatusObject) -> Result { .clone() .unwrap_or_else(|| std::env::current_dir().expect("failed to get current directory")); - if !simple_index::is_git_repository()? { + if !simple_index::is_git_repository_from(Some(¤t_dir))? { return Ok("fatal: not a git repository".to_string()); } let matcher = IgnoreMatcher::from_gutsignore(¤t_dir) .unwrap_or_else(|_| IgnoreMatcher::empty()); - let committed_files = simple_index::get_committed_files()?; - let index = simple_index::SimpleIndex::load()?; + let committed_files = simple_index::get_committed_files_from(Some(¤t_dir))?; + let index = simple_index::SimpleIndex::load_from(Some(¤t_dir))?; let work_files = list_working_dir_files(¤t_dir, &matcher)?; let current_branch = read_head::get_current_branch() @@ -154,9 +154,11 @@ fn list_working_dir_files(current_dir: &PathBuf, matcher: &IgnoreMatcher) -> Res } 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))?; let relative = file_path - .strip_prefix(current_dir) - .map_err(|_| anyhow::anyhow!("file is not in the current directory"))?; + .strip_prefix(&repo_root) + .map_err(|_| anyhow::anyhow!("file is not in the repository"))?; Ok(relative.to_string_lossy().to_string()) } diff --git a/src/commands/write_tree.rs b/src/commands/write_tree.rs index 7aa12c0..0d00542 100644 --- a/src/commands/write_tree.rs +++ b/src/commands/write_tree.rs @@ -11,9 +11,9 @@ 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 { +pub fn run(args: &WriteTreeArgs) -> Result { // Check if we're in a git repository - if !simple_index::is_git_repository()? { + if !simple_index::is_git_repository_from(args.dir.as_ref())? { return Err(anyhow::anyhow!("fatal: not a git repository")); } diff --git a/src/core/simple_index.rs b/src/core/simple_index.rs index 350d54e..2b2c1d6 100644 --- a/src/core/simple_index.rs +++ b/src/core/simple_index.rs @@ -35,6 +35,23 @@ 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<()> { let index_path = get_simple_index_path()?; @@ -108,6 +125,12 @@ 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 { let repo_root = find_repo_root()?; @@ -245,3 +268,101 @@ 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, "") +} diff --git a/src/terminal/app.rs b/src/terminal/app.rs index 307e22c..83f75b5 100644 --- a/src/terminal/app.rs +++ b/src/terminal/app.rs @@ -329,7 +329,7 @@ impl App { } // Sinon, commande système via shell - let _cleaned_dir = if self.current_dir.starts_with(r"\\?\") { + let cleaned_dir = if self.current_dir.starts_with(r"\\?\") { self.current_dir.trim_start_matches(r"\\?\\").to_string() } else { self.current_dir.clone() @@ -346,7 +346,7 @@ impl App { let shell_result = std::process::Command::new("sh") .arg("-c") .arg(&command) - .current_dir(&self.current_dir) + .current_dir(&cleaned_dir) .output(); let result = match shell_result {