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 }) } }