ZIm/crates/git/src/commit.rs
Max Brunsfeld 8546dc101d
Allow viewing past commits in Zed (#27636)
This PR adds functionality for loading the diff for an arbitrary git
commit, and displaying it in a tab. To retrieve the diff for the commit,
I'm using a single `git cat-file --batch` invocation to efficiently load
both the old and new versions of each file that was changed in the
commit.

Todo

* Features
* [x] Open the commit view when clicking the most recent commit message
in the commit panel
  * [x] Open the commit view when clicking a SHA in a git blame column
  * [x] Open the commit view when clicking a SHA in a commit tooltip
  * [x] Make it work over RPC
  * [x] Allow buffer search in commit view
* [x] Command palette action to open the commit for the current blame
line
* Styling
* [x] Add a header that shows the author, timestamp, and the full commit
message
  * [x] Remove stage/unstage buttons in commit view
  * [x] Truncate the commit message in the tab
* Bugs
  * [x] Dedup commit tabs within a pane
  * [x] Add a tooltip to the tab

Release Notes:

- Added the ability to show past commits in Zed. You can view the most
recent commit by clicking its message in the commit panel. And when
viewing a git blame, you can show any commit by clicking its sha.
2025-03-31 23:26:47 +00:00

111 lines
3.5 KiB
Rust
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

use crate::{Oid, status::StatusCode};
use anyhow::{Result, anyhow};
use collections::HashMap;
use std::path::Path;
pub async fn get_messages(working_directory: &Path, shas: &[Oid]) -> Result<HashMap<Oid, String>> {
if shas.is_empty() {
return Ok(HashMap::default());
}
const MARKER: &str = "<MARKER>";
let output = util::command::new_smol_command("git")
.current_dir(working_directory)
.arg("show")
.arg("-s")
.arg(format!("--format=%B{}", MARKER))
.args(shas.iter().map(ToString::to_string))
.output()
.await
.map_err(|e| anyhow!("Failed to start git blame process: {}", e))?;
anyhow::ensure!(
output.status.success(),
"'git show' failed with error {:?}",
output.status
);
Ok(shas
.iter()
.cloned()
.zip(
String::from_utf8_lossy(&output.stdout)
.trim()
.split_terminator(MARKER)
.map(|str| str.trim().replace("<", "&lt;").replace(">", "&gt;")),
)
.collect::<HashMap<Oid, String>>())
}
/// Parse the output of `git diff --name-status -z`
pub fn parse_git_diff_name_status(content: &str) -> impl Iterator<Item = (&Path, StatusCode)> {
let mut parts = content.split('\0');
std::iter::from_fn(move || {
loop {
let status_str = parts.next()?;
let path = parts.next()?;
let status = match status_str {
"M" => StatusCode::Modified,
"A" => StatusCode::Added,
"D" => StatusCode::Deleted,
_ => continue,
};
return Some((Path::new(path), status));
}
})
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_parse_git_diff_name_status() {
let input = concat!(
"MCargo.lock",
"Mcrates/project/Cargo.toml",
"Mcrates/project/src/buffer_store.rs",
"Dcrates/project/src/git.rs",
"Acrates/project/src/git_store.rs",
"Acrates/project/src/git_store/git_traversal.rs",
"Mcrates/project/src/project.rs",
"Mcrates/project/src/worktree_store.rs",
"Mcrates/project_panel/src/project_panel.rs",
);
let output = parse_git_diff_name_status(input).collect::<Vec<_>>();
assert_eq!(
output,
&[
(Path::new("Cargo.lock"), StatusCode::Modified),
(Path::new("crates/project/Cargo.toml"), StatusCode::Modified),
(
Path::new("crates/project/src/buffer_store.rs"),
StatusCode::Modified
),
(Path::new("crates/project/src/git.rs"), StatusCode::Deleted),
(
Path::new("crates/project/src/git_store.rs"),
StatusCode::Added
),
(
Path::new("crates/project/src/git_store/git_traversal.rs"),
StatusCode::Added,
),
(
Path::new("crates/project/src/project.rs"),
StatusCode::Modified
),
(
Path::new("crates/project/src/worktree_store.rs"),
StatusCode::Modified
),
(
Path::new("crates/project_panel/src/project_panel.rs"),
StatusCode::Modified
),
]
);
}
}