use crate::bare::GitBare; use crate::error::GitResult; use crate::pb::*; impl GitBare { /// Find changed paths between two revisions (no diff content). pub fn find_changed_paths(&self, request: FindChangedPathsRequest) -> GitResult { crate::sanitize::validate_revision(&request.base)?; crate::sanitize::validate_revision(&request.head)?; let mut args = vec![ "--git-dir".to_string(), self.bare_dir.to_string_lossy().into_owned(), "diff-tree".to_string(), "--name-status".to_string(), "-r".to_string(), ]; if !request.paths.is_empty() { args.push("--".to_string()); for p in &request.paths { args.push(p.clone()); } } args.push(request.base.clone()); args.push(request.head.clone()); let output = std::process::Command::new("git") .args(&args) .stdout(std::process::Stdio::piped()) .stderr(std::process::Stdio::piped()) .output() .map_err(|e| crate::error::GitError::CommandFailed { status_code: None, stderr: e.to_string(), })?; let stdout = String::from_utf8_lossy(&output.stdout); let mut paths = Vec::new(); for line in stdout.lines() { let line = line.trim(); if line.is_empty() { continue; } let parts: Vec<&str> = line.split('\t').collect(); if parts.is_empty() { continue; } let status_str = parts[0]; let status_letter = status_str.chars().next().unwrap_or('M'); let (status, old_path, new_path) = match status_letter { 'A' => (changed_path::Status::ChangedPathStatusAdded as i32, String::new(), parts.get(1).cloned().unwrap_or_default().to_string()), 'D' => (changed_path::Status::ChangedPathStatusDeleted as i32, parts.get(1).cloned().unwrap_or_default().to_string(), String::new()), 'R' => (changed_path::Status::ChangedPathStatusRenamed as i32, parts.get(1).cloned().unwrap_or_default().to_string(), parts.get(2).cloned().unwrap_or_default().to_string()), 'C' => (changed_path::Status::ChangedPathStatusCopied as i32, parts.get(1).cloned().unwrap_or_default().to_string(), parts.get(2).cloned().unwrap_or_default().to_string()), 'T' => (changed_path::Status::ChangedPathStatusTypeChanged as i32, String::new(), parts.get(1).cloned().unwrap_or_default().to_string()), _ => (changed_path::Status::ChangedPathStatusModified as i32, String::new(), parts.get(1).cloned().unwrap_or_default().to_string()), }; paths.push(ChangedPath { status, old_path, new_path, additions: 0, deletions: 0, binary: false, }); } Ok(FindChangedPathsResponse { paths }) } }