
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.
111 lines
3.5 KiB
Rust
111 lines
3.5 KiB
Rust
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("<", "<").replace(">", ">")),
|
||
)
|
||
.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!(
|
||
"M |