From 94e59ce2f83a3aaaf80efa2fd2c6a033994b0b34 Mon Sep 17 00:00:00 2001
From: Shuhei Kadowaki <40514306+aviatesk@users.noreply.github.com>
Date: Fri, 17 Jul 2026 17:43:11 +0900
Subject: [PATCH] git_graph: Fix long commit message overflow in detail panel
(#61163)
Follow-up to zed-industries/zed#60721.
The Taffy 0.12 upgrade exposed that the message height limit was applied
to its grid parent while percentage-sized children retained the Markdown
content's intrinsic height. Long messages therefore painted over the
changed-files summary instead of scrolling.
Move the height limit to the scroll container itself, matching the
commit view. Add a GPUI regression test that verifies the viewport stays
bounded, remains scrollable, and does not overlap the changed-files
list.
> On `main`:
---
Release Notes:
- N/A
---
crates/git_ui/src/git_graph.rs | 132 ++++++++++++++++++++++++++++++++-
1 file changed, 131 insertions(+), 1 deletion(-)
diff --git a/crates/git_ui/src/git_graph.rs b/crates/git_ui/src/git_graph.rs
index 4399545790..f0e4151d8e 100644
--- a/crates/git_ui/src/git_graph.rs
+++ b/crates/git_ui/src/git_graph.rs
@@ -3725,7 +3725,6 @@ impl GitGraph {
// grid, on the other hand, doesn't appear to give taffy the same
// problems.
.w_full()
- .max_h(line_height * 12.)
.py_2()
.pl_2()
.grid()
@@ -3740,6 +3739,7 @@ impl GitGraph {
.id("commit-message")
.text_sm()
.size_full()
+ .max_h(line_height * 12.)
.overflow_y_scroll()
.track_scroll(scroll_handle)
.child(MarkdownElement::new(message.clone(), message_style)),
@@ -7381,6 +7381,136 @@ mod tests {
});
}
+ #[gpui::test]
+ async fn test_long_commit_message_is_constrained_to_scroll_viewport(cx: &mut TestAppContext) {
+ init_test(cx);
+
+ let fs = FakeFs::new(cx.executor());
+ fs.insert_tree(
+ Path::new("/project"),
+ json!({ ".git": {}, "file.txt": "content" }),
+ )
+ .await;
+
+ let commit_sha = Oid::from_bytes(&[1; 20]).expect("commit SHA should be valid");
+ let commits = vec![Arc::new(InitialGraphCommitData {
+ sha: commit_sha,
+ parents: smallvec![],
+ ref_names: vec!["HEAD -> main".into()],
+ })];
+ fs.set_graph_commits(Path::new("/project/.git"), commits);
+
+ let message = (0..40)
+ .map(|line_number| {
+ format!(
+ "Line {line_number}: This commit message is long enough to require scrolling."
+ )
+ })
+ .collect::>()
+ .join("\n\n");
+ fs.set_commit_data(
+ Path::new("/project/.git"),
+ [(
+ CommitData {
+ sha: commit_sha,
+ parents: smallvec![],
+ author_name: "Author".into(),
+ author_email: "author@example.com".into(),
+ commit_timestamp: 1_700_000_000,
+ subject: "Long commit message".into(),
+ message: message.into(),
+ },
+ false,
+ )],
+ );
+
+ let project = Project::test(fs.clone(), [Path::new("/project")], cx).await;
+ cx.run_until_parked();
+
+ let repository = project.read_with(cx, |project, cx| {
+ project
+ .active_repository(cx)
+ .expect("should have a repository")
+ });
+ let (multi_workspace, cx) = cx.add_window_view(|window, cx| {
+ workspace::MultiWorkspace::test_new(project.clone(), window, cx)
+ });
+ let workspace_weak =
+ multi_workspace.read_with(&*cx, |multi, _| multi.workspace().downgrade());
+ let git_graph = cx.new_window_entity(|window, cx| {
+ GitGraph::new(
+ repository.read(cx).id,
+ project.read(cx).git_store().clone(),
+ workspace_weak,
+ None,
+ window,
+ cx,
+ )
+ });
+ cx.run_until_parked();
+
+ git_graph.update_in(cx, |graph, window, cx| {
+ graph.select_first(&menu::SelectFirst, window, cx);
+ });
+ cx.run_until_parked();
+
+ git_graph.update(cx, |graph, cx| {
+ graph.selected_commit_diff = Some(CommitDiff {
+ files: vec![CommitFile {
+ path: RepoPath::new("file.txt").expect("repository path should be valid"),
+ old_text: Some("content".into()),
+ new_text: Some("updated content".into()),
+ is_binary: false,
+ }],
+ });
+ graph.selected_commit_diff_stats = Some((1, 1));
+ cx.notify();
+ });
+
+ cx.draw(
+ point(px(0.), px(0.)),
+ gpui::size(px(1200.), px(800.)),
+ |_, _| git_graph.clone().into_any_element(),
+ );
+ cx.run_until_parked();
+
+ let (message_scroll_handle, changed_files_scroll_handle) =
+ git_graph.read_with(&*cx, |graph, _| {
+ (
+ graph
+ .selected_commit_message
+ .as_ref()
+ .expect("selected commit message should be loaded")
+ .scroll_handle
+ .clone(),
+ graph.changed_files_scroll_handle.clone(),
+ )
+ });
+ let maximum_message_height = git_graph.update_in(cx, |_, window, cx| {
+ editor::hover_markdown_style(window, cx)
+ .base_text_style
+ .line_height_in_pixels(window.rem_size())
+ * 12.
+ });
+ let message_bounds = message_scroll_handle.bounds();
+ let changed_files_bounds = changed_files_scroll_handle.0.borrow().base_handle.bounds();
+
+ assert!(
+ message_bounds.size.height <= maximum_message_height,
+ "commit message viewport height ({}) should not exceed its maximum ({})",
+ message_bounds.size.height,
+ maximum_message_height,
+ );
+ assert!(
+ message_scroll_handle.max_offset().y > px(0.),
+ "long commit message should be scrollable"
+ );
+ assert!(
+ message_bounds.bottom() <= changed_files_bounds.top(),
+ "commit message viewport {message_bounds:?} should not overlap changed files {changed_files_bounds:?}"
+ );
+ }
+
#[gpui::test]
async fn test_commit_message_not_reloaded_for_same_sha(cx: &mut TestAppContext) {
init_test(cx);