From 04de6dab7c890d815e316f2f9554f9eeeaf8ebb2 Mon Sep 17 00:00:00 2001 From: Eagl61 <17625015+Eagl61@users.noreply.github.com> Date: Mon, 6 Jul 2026 05:15:43 +0200 Subject: [PATCH] Show type-changed files in commit diffs (#60422) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit # Objective Zed ignores files marked by Git as type-changed (T), causing commits containing only these changes to show 0 Changed Files. For example, commit d7cc949e61351a6028f933abbaea39f67af9dfb2 changes crates/eval_utils/LICENSE-GPL from a regular file to a symlink, but Zed displays no changes. ## Solution Handle TypeChanged files like modified files by loading both their old and new contents. ## Testing - Added parser coverage for the T status. - Added a repository test for a regular-file-to-symlink change. - Ran cargo test -p git. - Ran ./script/clippy -p git. - Manually verified the example commit on macOS. ## Self-Review Checklist: - [x] I've reviewed my own diff for quality, security, and reliability - [x] Unsafe blocks (if any) have justifying comments - [x] The content adheres to Zed's UI standards ([UX/UI](https://github.com/zed-industries/zed/blob/main/CONTRIBUTING.md#uiux-checklist) and [icon](https://github.com/zed- industries/zed/blob/main/crates/icons/README.md) guidelines) - [x] Tests cover the new/changed behavior - [x] Performance impact has been considered and is acceptable ## Showcase Before: image After: image ——— Release Notes: - Fixed type-changed files not appearing in Git Graph and commit views. --- crates/git/src/commit.rs | 3 ++ crates/git/src/repository.rs | 59 ++++++++++++++++++++++++++++++++++-- 2 files changed, 60 insertions(+), 2 deletions(-) diff --git a/crates/git/src/commit.rs b/crates/git/src/commit.rs index 50b62fa506b..326c741ad19 100644 --- a/crates/git/src/commit.rs +++ b/crates/git/src/commit.rs @@ -109,6 +109,7 @@ pub fn parse_git_diff_name_status(content: &str) -> impl Iterator StatusCode::Modified, + "T" => StatusCode::TypeChanged, "A" => StatusCode::Added, "D" => StatusCode::Deleted, _ => continue, @@ -127,6 +128,7 @@ mod tests { fn test_parse_git_diff_name_status() { let input = concat!( "M\x00Cargo.lock\x00", + "T\x00LICENSE-GPL\x00", "M\x00crates/project/Cargo.toml\x00", "M\x00crates/project/src/buffer_store.rs\x00", "D\x00crates/project/src/git.rs\x00", @@ -142,6 +144,7 @@ mod tests { output, &[ ("Cargo.lock", StatusCode::Modified), + ("LICENSE-GPL", StatusCode::TypeChanged), ("crates/project/Cargo.toml", StatusCode::Modified), ("crates/project/src/buffer_store.rs", StatusCode::Modified), ("crates/project/src/git.rs", StatusCode::Deleted), diff --git a/crates/git/src/repository.rs b/crates/git/src/repository.rs index f09c13f54ab..2951a84b090 100644 --- a/crates/git/src/repository.rs +++ b/crates/git/src/repository.rs @@ -1445,7 +1445,7 @@ impl GitRepository for RealGitRepository { }; match status_code { - StatusCode::Modified => { + StatusCode::Modified | StatusCode::TypeChanged => { stdin.write_all(commit.as_bytes()).await?; stdin.write_all(b":").await?; stdin.write_all(path.as_bytes()).await?; @@ -1491,7 +1491,7 @@ impl GitRepository for RealGitRepository { }; match status_code { - StatusCode::Modified => { + StatusCode::Modified | StatusCode::TypeChanged => { info_line.clear(); stdout.read_line(&mut info_line).await?; let len = info_line.trim_end().parse().with_context(|| { @@ -4031,6 +4031,61 @@ mod tests { ); } + #[gpui::test] + async fn test_load_commit_with_type_changed_file(cx: &mut TestAppContext) { + disable_git_global_config(); + cx.executor().allow_parking(); + + let repo_dir = tempfile::tempdir().expect("failed to create temporary repository"); + git_init_repo(repo_dir.path()); + fs::write(repo_dir.path().join("file.txt"), "regular contents\n") + .expect("failed to write regular file"); + git_command(repo_dir.path(), ["add", "file.txt"]); + git_command(repo_dir.path(), ["commit", "-m", "initial"]); + + let repository = RealGitRepository::new( + &repo_dir.path().join(".git"), + None, + Some("git".into()), + cx.executor(), + ) + .expect("failed to open repository"); + fs::write(repo_dir.path().join("file.txt"), "target") + .expect("failed to write symlink target"); + + let symlink_blob = repository + .git_binary() + .run(&["hash-object", "-w", "file.txt"]) + .await + .expect("failed to write symlink blob"); + git_command( + repo_dir.path(), + [ + OsString::from("update-index"), + OsString::from("--cacheinfo"), + OsString::from("120000"), + OsString::from(symlink_blob), + OsString::from("file.txt"), + ], + ); + git_command(repo_dir.path(), ["commit", "-m", "type change"]); + + let commit_diff = repository + .load_commit("HEAD".to_string(), cx.to_async()) + .await + .expect("failed to load type-changed commit"); + assert_eq!(commit_diff.files.len(), 1); + + let file = commit_diff + .files + .first() + .expect("type-changed file should be present"); + assert_eq!(file.path.as_unix_str(), "file.txt"); + assert_eq!(file.old_text.as_deref(), Some("regular contents\n")); + assert_eq!(file.new_text.as_deref(), Some("target")); + assert_eq!(file.status(), CommitFileStatus::Modified); + } + #[gpui::test] async fn test_check_access(cx: &mut TestAppContext) { disable_git_global_config();