commit.rs

  1use crate::{Oid, status::StatusCode};
  2use anyhow::{Context as _, Result};
  3use collections::HashMap;
  4use std::path::Path;
  5
  6pub async fn get_messages(working_directory: &Path, shas: &[Oid]) -> Result<HashMap<Oid, String>> {
  7    if shas.is_empty() {
  8        return Ok(HashMap::default());
  9    }
 10
 11    const MARKER: &str = "<MARKER>";
 12
 13    let output = util::command::new_smol_command("git")
 14        .current_dir(working_directory)
 15        .arg("show")
 16        .arg("-s")
 17        .arg(format!("--format=%B{}", MARKER))
 18        .args(shas.iter().map(ToString::to_string))
 19        .output()
 20        .await
 21        .context("starting git blame process")?;
 22
 23    anyhow::ensure!(
 24        output.status.success(),
 25        "'git show' failed with error {:?}",
 26        output.status
 27    );
 28
 29    Ok(shas
 30        .iter()
 31        .cloned()
 32        .zip(
 33            String::from_utf8_lossy(&output.stdout)
 34                .trim()
 35                .split_terminator(MARKER)
 36                .map(|str| str.trim().replace("<", "&lt;").replace(">", "&gt;")),
 37        )
 38        .collect::<HashMap<Oid, String>>())
 39}
 40
 41/// Parse the output of `git diff --name-status -z`
 42pub fn parse_git_diff_name_status(content: &str) -> impl Iterator<Item = (&Path, StatusCode)> {
 43    let mut parts = content.split('\0');
 44    std::iter::from_fn(move || {
 45        loop {
 46            let status_str = parts.next()?;
 47            let path = parts.next()?;
 48            let status = match status_str {
 49                "M" => StatusCode::Modified,
 50                "A" => StatusCode::Added,
 51                "D" => StatusCode::Deleted,
 52                _ => continue,
 53            };
 54            return Some((Path::new(path), status));
 55        }
 56    })
 57}
 58
 59#[cfg(test)]
 60mod tests {
 61    use super::*;
 62
 63    #[test]
 64    fn test_parse_git_diff_name_status() {
 65        let input = concat!(
 66            "M\x00Cargo.lock\x00",
 67            "M\x00crates/project/Cargo.toml\x00",
 68            "M\x00crates/project/src/buffer_store.rs\x00",
 69            "D\x00crates/project/src/git.rs\x00",
 70            "A\x00crates/project/src/git_store.rs\x00",
 71            "A\x00crates/project/src/git_store/git_traversal.rs\x00",
 72            "M\x00crates/project/src/project.rs\x00",
 73            "M\x00crates/project/src/worktree_store.rs\x00",
 74            "M\x00crates/project_panel/src/project_panel.rs\x00",
 75        );
 76
 77        let output = parse_git_diff_name_status(input).collect::<Vec<_>>();
 78        assert_eq!(
 79            output,
 80            &[
 81                (Path::new("Cargo.lock"), StatusCode::Modified),
 82                (Path::new("crates/project/Cargo.toml"), StatusCode::Modified),
 83                (
 84                    Path::new("crates/project/src/buffer_store.rs"),
 85                    StatusCode::Modified
 86                ),
 87                (Path::new("crates/project/src/git.rs"), StatusCode::Deleted),
 88                (
 89                    Path::new("crates/project/src/git_store.rs"),
 90                    StatusCode::Added
 91                ),
 92                (
 93                    Path::new("crates/project/src/git_store/git_traversal.rs"),
 94                    StatusCode::Added,
 95                ),
 96                (
 97                    Path::new("crates/project/src/project.rs"),
 98                    StatusCode::Modified
 99                ),
100                (
101                    Path::new("crates/project/src/worktree_store.rs"),
102                    StatusCode::Modified
103                ),
104                (
105                    Path::new("crates/project_panel/src/project_panel.rs"),
106                    StatusCode::Modified
107                ),
108            ]
109        );
110    }
111}