Skip to content

Feat/better diff viewer - #4

Merged
soyboyscout merged 9 commits into
mainfrom
feat/better-diff-viewer
Jul 10, 2026
Merged

soyboyscout merged 9 commits into
mainfrom
feat/better-diff-viewer

Conversation

@soyboyscout

@soyboyscout soyboyscout commented Jul 1, 2026 •

Copy link
Copy Markdown
Member

Objective

  • Describe the objective or issue this PR addresses.
  • If you're fixing a specific issue, use "Fixes #X" for each issue as described in the GitHub docs.

Solution

  • Describe the solution used to achieve the objective above.

Testing

  • Did you test these changes? If so, how?
  • Are there any parts that need more testing?
  • How can other people (reviewers) test your changes? Is there anything specific they need to know?
  • If relevant, what platforms did you test these changes on, and are there any important ones you can't test?

Self-Review Checklist:

  • I've reviewed my own diff for quality, security, and reliability
  • Unsafe blocks (if any) have justifying comments
  • The content adheres to Zed's UI standards (UX/UI and icon guidelines)
  • Tests cover the new/changed behavior
  • Performance impact has been considered and is acceptable

Showcase

This section is optional. If this PR does not include a visual change or does not add a new user-facing feature, you can delete this section.

  • Help others understand the result of this PR by showcasing your awesome work!
  • If this PR includes a visual change, consider adding a screenshot, GIF, or video
    • A before/after comparison is very useful for changes to existing features!

While a showcase should aim to be brief and digestible, you can use a toggleable section to save space on longer showcases:

Click to view showcase

My super cool demos here


Release Notes:

  • N/A or Added/Fixed/Improved ...

Summary by cubic

Adds an embeddable, view-only diff and a new commit diff preview, while keeping the default interactive diff unchanged. Hardens conflict handling, improves targeted git status refresh, fixes diff preview integration, and adds opt-in performance tracing.

  • New Features

    • ProjectDiffOptions with toggles for hunk controls, review button, git panel addon, index mutations, and conflict view; new deploy_at_with_options/deploy_at_project_path_with_options with ProjectDiffOptions::embedded_view_only() for safe, view-only diffs.
    • CommitDiffPreview workspace item for multi-file commit diffs with syntax highlighting and excerpted hunks; preview buffers are named by display path and the commit_diff_preview module is exported for embedders.
    • Editor API: SplittableEditor::set_delegate_stage_and_restore to delegate stage/unstage/restore actions (used to disable index mutations in embedded mode).
    • Opt-in performance tracing for CommitDiffPreview via CHERRYPICK_DIFF_PREVIEW_TRACE.
  • Bug Fixes

    • Conflict UI no longer panics if the conflict addon is missing; subscriptions are only registered when conflict view is enabled.
    • Improved git status updates with GitStore::refresh_status_for_project_paths, batching path refreshes and setting a fallback active repo when needed.
    • Fixed Zed diff preview integration.
    • Fixed embedded diff review: review button and toolbar are hidden in view-only mode; staging/restore commands are delegated and do not mutate the index; default ProjectDiff behavior remains interactive.

Written for commit 700662a. Summary will update on new commits.

Review in cubic

Summary by CodeRabbit

  • New Features
    • Added a commit diff preview workspace item to browse per-file changes in a temporary read-only view.
    • Project diff views are now configurable via options (e.g., hunk controls, review button, conflict integration, and whether index edits are allowed).
    • Status can now be refreshed for multiple project paths at once.
  • Bug Fixes
    • Improved conflict view handling to avoid crashes when conflict tracking isn’t available.
    • Split editors now keep stage/restore behavior aligned and correctly delegate when enabled.

Copilot AI review requested due to automatic review settings July 1, 2026 21:49
@matterai-app

matterai-app Bot commented Jul 1, 2026 •

Copy link
Copy Markdown

Objective

  • Enhance the diff viewer to support historical commit previews, renames, and read-only delegation modes.
  • Ensure repository context is correctly propagated and state management is streamlined.

Solution

  • Implemented event delegation in SplittableEditor and refined PreviewEntry path logic.
  • Updated ProjectDiff to propagate repo context to BranchDiff instances.
  • Refactored GitStore to use idiomatic let-else guards for local state checks.

Testing

  • Tested via unit tests in commit_diff_preview.rs and manual verification of diff rendering for renames.
  • Verified repository context propagation in ProjectDiff.

Self-Review Checklist:

  • I've reviewed my own diff for quality, security, and reliability
  • Unsafe blocks (if any) have justifying comments
  • The content adheres to Zed's UI standards
  • Tests cover the new/changed behavior
  • Performance impact has been considered and is acceptable

Showcase

N/A


Release Notes:

  • Improved historical diff preview support for renames and deletions.

Summary By MatterAI MatterAI logo

🔄 What Changed

This PR enhances the diff viewer by ensuring repository context is correctly propagated during BranchDiff initialization and streamlining state-guarding logic in GitStore. It also refines path fallback logic in commit_diff_preview.rs and integrates delegation for staging/restoring hunks.

🔍 Impact of the Change

Improves the reliability of historical diff previews and read-only views. It prevents repository context loss during item creation and simplifies internal state management, leading to more maintainable and robust git integration.

📁 Total Files Changed

Click to Expand
File ChangeLog
Path Safety crates/git_ui/src/commit_diff_preview.rs Replaced expect with unreachable! for static fallback path resolution.
Repo Propagation crates/git_ui/src/project_diff.rs Clones and sets the repo context when creating new BranchDiff tasks.
State Guarding crates/project/src/git_store.rs Refactored local state checks using idiomatic let-else syntax.

🧪 Test Added/Recommended

Added

  • preview_buffer_file_marks_deleted_historic_files: Verifies DiskState::Historic correctly reflects deletion status.
  • preview_entry_uses_metadata_path_separately_from_display_path: Ensures renames use correct metadata paths.

Recommended

  • Unit test to verify BranchDiff::set_repo is called with the correct repository instance in ProjectDiff.
  • Edge case testing for RelPath creation with OS-specific reserved names.

🔒 Security Vulnerabilities

N/A - No security vulnerabilities detected. Logic focuses on internal state propagation and path safety. 🛡️

@coderabbitai

coderabbitai Bot commented Jul 1, 2026 •

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Adds a commit diff preview workspace item, threads options through ProjectDiff construction and behavior, guards optional conflict-addon access, and adds status-refresh and split-editor delegation helpers.

Changes

Diff preview and options-driven ProjectDiff

Layer / File(s) Summary
CommitDiffPreview model and construction
crates/git_ui/src/commit_diff_preview.rs
Defines preview types, historic file metadata behavior, and a read-only multibuffer/editor setup for commit diff display.
CommitDiffPreview population and rendering
crates/git_ui/src/commit_diff_preview.rs, crates/git_ui/src/git_ui.rs
Populates preview buffers asynchronously, computes diffs, registers excerpts, implements workspace item traits, exposes the module publicly, and tests path handling.
ProjectDiffOptions and deployment
crates/git_ui/src/project_diff.rs
Adds ProjectDiffOptions and routes deployment and constructor paths through options-aware creation while preserving options on instances.
Options-gated ProjectDiff behavior
crates/git_ui/src/project_diff.rs
Uses options to gate editor setup, stage/restore handling, conflict-view logic, persistence, split cloning, embedded-view checks, and toolbar visibility.
Optional ConflictAddon guards
crates/git_ui/src/conflict_view.rs
Returns early when ConflictAddon is absent in buffer update and removal paths.
Split-editor stage and restore delegation
crates/editor/src/split.rs
Adds configurable delegation for stage and restore events.
Project-path status refresh
crates/project/src/git_store.rs
Adds local repository status refresh for resolved project paths.

Estimated code review effort: 4 (Complex) | ~60 minutes

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 12.86% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The title is relevant, but 'better' is too vague to clearly summarize the main change. Rename it to a specific summary like 'Add embeddable commit diff preview and project diff options'.
✅ Passed checks (3 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description check ✅ Passed The description follows the template and includes a substantive objective/solution summary; only testing details are sparse.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/better-diff-viewer

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@qodo-code-review

Copy link
Copy Markdown

PR Summary by Qodo

Add embedded, view-only diff previews for project and commit diffs

✨ Enhancement 🐞 Bug fix 🧪 Tests 🕐 40+ Minutes

Grey Divider

AI Description

• Add configurable ProjectDiff options to support embedded, view-only diff experiences.
• Introduce a CommitDiffPreview item for read-only commit/file diffs in a single editor.
• Harden embedded diff paths (conflict addon safety, git status refresh) to avoid stale UI.
Diagram

graph TD
  A["Embedder UI"] --> B["ProjectDiff (options)"] --> C["SplittableEditor"] --> D["Editor"]
  B --> E["GitStore"] --> F[("Repository status")]
  A --> G["CommitDiffPreview"] --> H["MultiBuffer + BufferDiff"] --> D

  subgraph Legend
    direction LR
    _ui["UI/Item"] ~~~ _svc["Component"] ~~~ _db[("State/Repo")]
  end
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Reuse ProjectDiff in embedded mode for commit previews
  • ➕ Reduces duplicated diff rendering logic and UI behaviors
  • ➕ Keeps diff UX consistent across project/commit contexts
  • ➖ ProjectDiff is tied to BranchDiff/working-copy concepts and workspace add-ons
  • ➖ Harder to feed arbitrary old/new file contents (e.g., commit blobs, truncated/binary placeholders)
2. Add an Editor-native 'diff preview mode' helper API
  • ➕ Centralizes read-only diff configuration (controls, staging delegation, diagnostics)
  • ➕ Makes future embedded diff surfaces cheaper to implement
  • ➖ Larger refactor and API surface expansion
  • ➖ May require broader coordination across crates and consumers

Recommendation: The PR’s approach (explicit ProjectDiffOptions for embedded/view-only + a dedicated CommitDiffPreview item) is a pragmatic separation: ProjectDiff becomes safely embeddable without index mutations, while CommitDiffPreview cleanly supports commit/file blob inputs and placeholders (binary/truncated/errors). Consider a follow-up to factor common 'read-only diff editor configuration' into a shared helper to reduce repetition and future drift.

Files changed (6) +812 / -71

Enhancement (5) +790 / -46
split.rsExpose staged/restore delegation control on SplittableEditor +6/-0

Expose staged/restore delegation control on SplittableEditor

• Adds a SplittableEditor method to propagate delegate stage/restore behavior to underlying editors. This supports embedding scenarios where stage/restore actions must be intercepted instead of mutating the index/worktree.

crates/editor/src/split.rs

commit_diff_preview.rsAdd read-only CommitDiffPreview item backed by MultiBuffer diffs +548/-0

Add read-only CommitDiffPreview item backed by MultiBuffer diffs

• Introduces a new workspace Item that renders multi-file commit diffs using per-file Buffers and BufferDiff snapshots, with configurable title/context lines and optional hunk expansion. Includes placeholder rendering for load errors, binary files, and truncated content, and adds unit tests for path metadata/key ordering.

crates/git_ui/src/commit_diff_preview.rs

git_ui.rsExport commit_diff_preview module +1/-0

Export commit_diff_preview module

• Registers the new commit_diff_preview module in git_ui so it can be used by other crate entry points.

crates/git_ui/src/git_ui.rs

project_diff.rsAdd ProjectDiffOptions for embedded view-only diffs and gate behaviors +186/-46

Add ProjectDiffOptions for embedded view-only diffs and gate behaviors

• Adds ProjectDiffOptions (with an embedded_view_only preset) and plumbs options through deploy/new paths, including item reuse keyed by both diff base and options. Options gate hunk controls, review button visibility, git panel addon registration, conflict view subscriptions, and index/worktree mutations by delegating stage/restore requests to the ProjectDiff handler.

crates/git_ui/src/project_diff.rs

git_store.rsAdd targeted git status refresh for project paths +49/-0

Add targeted git status refresh for project paths

• Introduces refresh_status_for_project_paths to group ProjectPaths by repository and trigger repo.paths_changed updates (and set an active repo if needed). This enables embedded diff viewers to refresh status for the specific files they display without relying on broader refresh flows.

crates/project/src/git_store.rs

Bug fix (1) +22 / -25
conflict_view.rsAvoid panics when ConflictAddon is unavailable +22/-25

Avoid panics when ConflictAddon is unavailable

• Replaces unwrap-based access to ConflictAddon with guarded early returns. This hardens buffer conflict bookkeeping for contexts where the addon is intentionally not registered (e.g., embedded/view-only diffs).

crates/git_ui/src/conflict_view.rs

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR extends Zed’s git diff UI to support an embeddable, view-only “ProjectDiff” mode (via configurable options), introduces a new multi-file commit diff preview item, and improves git status refresh batching while hardening conflict UI behavior against missing addons.

Changes:

  • Added GitStore::refresh_status_for_project_paths to batch per-repo status refreshes from a set of ProjectPaths.
  • Introduced ProjectDiffOptions and new deploy helpers to allow embedding ProjectDiff in a view-only configuration (disabling hunk controls, review button, git panel addon, index mutations, and conflict view).
  • Added CommitDiffPreview (new item) to render multi-file commit diffs in a read-only editor-backed view, and exposed it via git_ui module exports.

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
crates/project/src/git_store.rs Adds a batched status refresh API for multiple project paths.
crates/git_ui/src/project_diff.rs Adds ProjectDiffOptions and deploy helpers to support embedded/view-only diffs and safer conflict handling.
crates/git_ui/src/git_ui.rs Exposes the new commit_diff_preview module.
crates/git_ui/src/conflict_view.rs Avoids panics by handling missing ConflictAddon more defensively.
crates/git_ui/src/commit_diff_preview.rs New multi-file commit diff preview item with excerpted hunks and basic tests.
crates/editor/src/split.rs Adds a SplittableEditor helper to delegate stage/restore behavior to callers.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread crates/project/src/git_store.rs Outdated
Comment on lines +1414 to +1419
let updates_tx = match &self.state {
GitStoreState::Local { downstream, .. } => downstream
.as_ref()
.map(|downstream| downstream.updates_tx.clone()),
GitStoreState::Remote { .. } => None,
};

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces a new CommitDiffPreview component in git_ui for previewing commit diffs, adds configuration options to ProjectDiff via ProjectDiffOptions to support view-only embedded diffs, and implements refresh_status_for_project_paths in GitStore. Additionally, it refactors conflict_view.rs to safely handle the absence of ConflictAddon. Review feedback focused on adhering to the repository style guide in the new commit_diff_preview.rs file, specifically recommending importing util::ResultExt to log errors, handling the Result of the spawned task in populate_entries with .log_err(), and replacing panicking .unwrap() calls with safe fallbacks.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment on lines +21 to +22
use util::paths::PathStyle;
use util::rel_path::RelPath;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Import util::ResultExt to allow logging of errors using .log_err() on fallible operations, adhering to the repository style guide.

use util::paths::PathStyle;
use util::rel_path::RelPath;
use util::ResultExt as _;
References
  1. Never silently discard errors with let _ = on fallible operations. Always handle errors appropriately: Use .log_err() or similar when you need to ignore errors but want visibility. (link)

Comment on lines +258 to +260
let populate_task = cx.spawn(async move |_, cx| {
populate_entries(multibuffer, entries, context_lines, language_registry, cx).await
});

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The task returned by cx.spawn is stored in _populate_task to prevent cancellation, but its inner Result<()> is never awaited or handled. If populate_entries fails, the error will be silently discarded. Use .log_err() to ensure visibility of any errors, adhering to the repository style guide.

        let populate_task = cx.spawn(async move |_, cx| {
            populate_entries(multibuffer, entries, context_lines, language_registry, cx)
                .await
                .log_err();
            anyhow::Ok(())
        });
References
  1. Never silently discard errors with let _ = on fallible operations. Always handle errors appropriately: Use .log_err() or similar when you need to ignore errors but want visibility. (link)

Comment on lines +381 to +383
RelPath::new(Path::new(display_path), PathStyle::Posix)
.unwrap_or_else(|_| RelPath::new(Path::new("untitled"), PathStyle::Posix).unwrap())
.into_owned()

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Avoid using panicking functions like .unwrap() on fallible operations, even when the fallback path is statically guaranteed to succeed. Use safe fallbacks or unwrap_or_else with unreachable!() to adhere to the repository style guide.

    RelPath::new(Path::new(display_path), PathStyle::Posix)
        .unwrap_or_else(|_| {
            RelPath::new(Path::new("untitled"), PathStyle::Posix)
                .unwrap_or_else(|_| unreachable!("'untitled' is a valid relative path"))
        })
        .into_owned()
References
  1. Avoid using functions that panic like unwrap(), instead use mechanisms like ? to propagate errors. (link)

@qodo-code-review

qodo-code-review Bot commented Jul 1, 2026 •

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (0) 📘 Rule violations (0) 📎 Requirement gaps (0) 🎨 UX issues (0) 🔗 Cross-repo conflicts (0) 📜 Skill insights (0)

Grey Divider


Action required

1. Remote status refresh no-op ✓ Resolved 🐞 Bug ☼ Reliability
Description
GitStore::refresh_status_for_project_paths calls Repository::paths_changed even when GitStoreState
is Remote, but Repository::paths_changed bails unless the repository is local, so status refreshes
can silently fail in remote/collab mode.
Code

crates/project/src/git_store.rs[R1406-1451]

+    pub fn refresh_status_for_project_paths<I>(
+        &mut self,
+        project_paths: I,
+        cx: &mut Context<Self>,
+    ) -> usize
+    where
+        I: IntoIterator<Item = ProjectPath>,
+    {
+        let updates_tx = match &self.state {
+            GitStoreState::Local { downstream, .. } => downstream
+                .as_ref()
+                .map(|downstream| downstream.updates_tx.clone()),
+            GitStoreState::Remote { .. } => None,
+        };
+
+        let mut paths_by_repo: HashMap<RepositoryId, (Entity<Repository>, Vec<RepoPath>)> =
+            HashMap::default();
+        let mut fallback_active_repo_id = None;
+        for project_path in project_paths {
+            let Some((repo, repo_path)) =
+                self.repository_and_path_for_project_path(&project_path, cx)
+            else {
+                continue;
+            };
+            let repo_id = repo.read(cx).id;
+            if self.active_repo_id.is_none() && fallback_active_repo_id.is_none() {
+                fallback_active_repo_id = Some(repo_id);
+            }
+            paths_by_repo
+                .entry(repo_id)
+                .or_insert_with(|| (repo, Vec::new()))
+                .1
+                .push(repo_path);
+        }
+
+        if let Some(repo_id) = fallback_active_repo_id {
+            self.set_active_repo_id(repo_id, cx);
+        }
+
+        let mut refreshed = 0;
+        for (_, (repo, paths)) in paths_by_repo {
+            refreshed += paths.len();
+            repo.update(cx, |repo, cx| {
+                repo.paths_changed(paths, updates_tx.clone(), cx);
+            });
+        }
Evidence
The new helper explicitly handles GitStoreState::Remote by setting updates_tx to None but
still calls repo.paths_changed(...). The underlying paths_changed implementation bails when the
repository state is not local, so in remote/collab mode the refresh job will not run successfully.

crates/project/src/git_store.rs[1406-1451]
crates/project/src/git_store.rs[8650-8674]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`GitStore::refresh_status_for_project_paths` invokes `repo.paths_changed(...)` regardless of `GitStoreState`. For remote stores, `Repository::paths_changed` is implemented to only work for `RepositoryState::Local` and will bail, meaning the refresh won’t happen.
## Issue Context
This method is public and likely to be used by UI code to force-refresh git status for a set of files. In remote/collab contexts the store is `GitStoreState::Remote`, and calling into `paths_changed` is not supported.
## Fix Focus Areas
- crates/project/src/git_store.rs[1406-1453]
- crates/project/src/git_store.rs[8650-8674]
## Suggested fix
- Add an early return for remote stores (e.g. `if !self.is_local() { return 0; }`) **before** building `paths_by_repo`.
- Alternatively, gate the `repo.paths_changed(...)` call behind a check that the underlying repository state is local, and skip (or route through a remote-appropriate mechanism) otherwise.
- Ensure the function’s return value reflects work performed (e.g. return 0 when remote).

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Remediation recommended

2. Deleted files not marked ✓ Resolved 🐞 Bug ≡ Correctness
Description
CommitDiffPreview’s PreviewBufferFile hardcodes DiskState::Historic { was_deleted: false } and
to_proto().is_deleted = false, so deleted-file previews won’t be treated as deleted by UI logic
that relies on DiskState::is_deleted.
Code

crates/git_ui/src/commit_diff_preview.rs[R108-140]

+    fn disk_state(&self) -> DiskState {
+        DiskState::Historic { was_deleted: false }
+    }
+
+    fn path(&self) -> &Arc<RelPath> {
+        &self.path
+    }
+
+    fn full_path(&self, _: &App) -> PathBuf {
+        self.full_path.clone()
+    }
+
+    fn path_style(&self, _: &App) -> PathStyle {
+        PathStyle::Posix
+    }
+
+    fn file_name<'a>(&'a self, _: &'a App) -> &'a str {
+        &self.file_name
+    }
+
+    fn worktree_id(&self, _: &App) -> WorktreeId {
+        WorktreeId::from_usize(0)
+    }
+
+    fn to_proto(&self, cx: &App) -> language::proto::File {
+        language::proto::File {
+            worktree_id: self.worktree_id(cx).to_proto(),
+            entry_id: None,
+            path: self.path.as_ref().to_proto(),
+            mtime: None,
+            is_deleted: false,
+            is_historic: true,
+        }
Evidence
PreviewBufferFile hardcodes deleted=false in both DiskState and proto metadata. Other parts of the
codebase explicitly use DiskState::is_deleted to render deleted files (e.g. strike-through in tab
titles), and a similar historic file implementation (GitBlob) propagates was_deleted correctly.

crates/git_ui/src/commit_diff_preview.rs[103-140]
crates/editor/src/items.rs[775-803]
crates/git_ui/src/commit_view.rs[915-924]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`PreviewBufferFile` always reports `was_deleted: false` and `is_deleted: false` even when the diff entry represents a deleted file. UI code (e.g. tab/title styling) checks `file.disk_state().is_deleted()` to render deleted files appropriately, so deleted-file previews will be misrepresented.
## Issue Context
`CommitDiffPreviewStatus` includes `Deleted`, but `PreviewEntry`/`PreviewBufferFile` drops that information and can’t reflect deletion in the `LanguageFile` metadata.
## Fix Focus Areas
- crates/git_ui/src/commit_diff_preview.rs[83-150]
## Suggested fix
- Add a `was_deleted: bool` field to `PreviewEntry` (set it in `CommitDiffPreviewFile::into_entry` based on `CommitDiffPreviewStatus::Deleted`).
- Add a `was_deleted: bool` field to `PreviewBufferFile` and pass it via `PreviewBufferFile::new(display_path, was_deleted)`.
- Return `DiskState::Historic { was_deleted }` and set `to_proto().is_deleted = was_deleted`.
- (Optional) Add/adjust a unit test to cover the deleted-file case.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Qodo Logo

Comment thread crates/project/src/git_store.rs Resolved

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
crates/git_ui/src/project_diff.rs (1)

1017-1033: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Fall back to diff hunk ranges when conflict view is disabled.

excerpt_ranges still prefers conflict_set ranges whenever conflicts exist, but this block skips conflict UI updates when enable_conflict_view is false. Embedded/view-only diffs can then show only conflict regions and omit other changed hunks in the file.

Proposed fix
-        let excerpt_ranges = {
+        let enable_conflict_view = self.options.enable_conflict_view;
+        let excerpt_ranges = {
             let diff_hunk_ranges = diff_snapshot
                 .hunks_intersecting_range(
                     Anchor::min_max_range_for_buffer(snapshot.remote_id()),
                     &snapshot,
                 )
                 .map(|diff_hunk| diff_hunk.buffer_range.to_point(&snapshot));
-            let conflicts = conflict_set.read(cx).snapshot();
-            let mut conflicts = conflicts
-                .conflicts
-                .iter()
-                .map(|conflict| conflict.range.to_point(&snapshot))
-                .peekable();
-
-            if conflicts.peek().is_some() {
-                conflicts.collect::<Vec<_>>()
+
+            if enable_conflict_view {
+                let conflicts = conflict_set.read(cx).snapshot();
+                let mut conflicts = conflicts
+                    .conflicts
+                    .iter()
+                    .map(|conflict| conflict.range.to_point(&snapshot))
+                    .peekable();
+
+                if conflicts.peek().is_some() {
+                    conflicts.collect::<Vec<_>>()
+                } else {
+                    diff_hunk_ranges.collect()
+                }
             } else {
                 diff_hunk_ranges.collect()
             }
         };
 
         let mut needs_fold = None;
-        let enable_conflict_view = self.options.enable_conflict_view;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/git_ui/src/project_diff.rs` around lines 1017 - 1033, The excerpt
selection in update logic still prioritizes conflict_set ranges even when
enable_conflict_view is off, so embedded/view-only diffs can miss non-conflict
hunks. Update the range selection around editor.update_excerpts_for_path in
project_diff.rs to fall back to normal diff hunk ranges whenever conflict view
is disabled, while keeping conflict_view::buffer_ranges_updated limited to the
enabled case. Make sure the excerpt_ranges passed into update_excerpts_for_path
reflects all changed hunks when self.options.enable_conflict_view is false.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@crates/git_ui/src/commit_diff_preview.rs`:
- Around line 76-87: Preview registration is losing the real metadata for
renamed/copied and deleted files because PreviewEntry only keeps a display path
and PreviewBufferFile is built without the original paths or deletion flags.
Update PreviewEntry and PreviewBufferFile to carry a metadata path plus
was_deleted (and preserve old_path/new_path where needed), then thread those
values through the PreviewEntry creation and PreviewBufferFile construction in
commit_diff_preview so language/metadata lookup uses the real file path and
deleted entries are reported correctly.

In `@crates/git_ui/src/project_diff.rs`:
- Around line 636-638: Split-pane stage/restore is still being handled in
SplittableEditor before ProjectDiff can block it, so left-pane keybindings can
mutate the index/worktree even when allow_index_mutations is false. Add a gate
in SplittableEditor for the left-pane stage/restore path, or stop translating
those events directly into do_stage_or_unstage and restore_diff_hunks when
mutations are disallowed, so ProjectDiff’s set_delegate_stage_and_restore
control is respected everywhere.

---

Outside diff comments:
In `@crates/git_ui/src/project_diff.rs`:
- Around line 1017-1033: The excerpt selection in update logic still prioritizes
conflict_set ranges even when enable_conflict_view is off, so embedded/view-only
diffs can miss non-conflict hunks. Update the range selection around
editor.update_excerpts_for_path in project_diff.rs to fall back to normal diff
hunk ranges whenever conflict view is disabled, while keeping
conflict_view::buffer_ranges_updated limited to the enabled case. Make sure the
excerpt_ranges passed into update_excerpts_for_path reflects all changed hunks
when self.options.enable_conflict_view is false.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 7c71c91a-6629-46ae-8ae0-23a0a7d127e3

📥 Commits

Reviewing files that changed from the base of the PR and between 76f7ba3 and d3cbd4e.

📒 Files selected for processing (6)
  • crates/editor/src/split.rs
  • crates/git_ui/src/commit_diff_preview.rs
  • crates/git_ui/src/conflict_view.rs
  • crates/git_ui/src/git_ui.rs
  • crates/git_ui/src/project_diff.rs
  • crates/project/src/git_store.rs

Comment on lines +636 to 638
if !options.allow_index_mutations {
diff_display_editor.set_delegate_stage_and_restore(true, cx);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

Block split-pane stage/restore before it reaches the RHS editor.

ProjectDiff swallows delegated stage/restore events here, but split-mode left-pane events are handled inside SplittableEditor and translated directly into RHS do_stage_or_unstage / restore_diff_hunks calls before ProjectDiff sees them. Embedded view-only diffs can still mutate the index/worktree via left-pane keybindings. Add a SplittableEditor-level gate, or emit those left-pane events upward instead of translating when allow_index_mutations is false.

Also applies to: 909-915

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/git_ui/src/project_diff.rs` around lines 636 - 638, Split-pane
stage/restore is still being handled in SplittableEditor before ProjectDiff can
block it, so left-pane keybindings can mutate the index/worktree even when
allow_index_mutations is false. Add a gate in SplittableEditor for the left-pane
stage/restore path, or stop translating those events directly into
do_stage_or_unstage and restore_diff_hunks when mutations are disallowed, so
ProjectDiff’s set_delegate_stage_and_restore control is respected everywhere.

@cubic-dev-ai cubic-dev-ai Bot left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

7 issues found across 6 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="crates/git_ui/src/git_ui.rs">

<violation number="1" location="crates/git_ui/src/git_ui.rs:34">
P3: The new `pub mod commit_diff_preview;` module (548 lines) is declared but has zero references across the codebase — no imports, no re-exports, no symbol usage anywhere. If this module is intended to be used in the same PR, wire it up before merging. If it's a public API for future consumers, consider adding a TODO or an explicit re-export path so the intent is clear. Otherwise it's dead code that adds compile-time overhead without providing value.</violation>
</file>

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread crates/git_ui/src/project_diff.rs Resolved
Comment thread crates/git_ui/src/project_diff.rs Resolved
Comment thread crates/project/src/git_store.rs Resolved

mod askpass_modal;
pub mod branch_picker;
pub mod commit_diff_preview;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3: The new pub mod commit_diff_preview; module (548 lines) is declared but has zero references across the codebase — no imports, no re-exports, no symbol usage anywhere. If this module is intended to be used in the same PR, wire it up before merging. If it's a public API for future consumers, consider adding a TODO or an explicit re-export path so the intent is clear. Otherwise it's dead code that adds compile-time overhead without providing value.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At crates/git_ui/src/git_ui.rs, line 34:

<comment>The new `pub mod commit_diff_preview;` module (548 lines) is declared but has zero references across the codebase — no imports, no re-exports, no symbol usage anywhere. If this module is intended to be used in the same PR, wire it up before merging. If it's a public API for future consumers, consider adding a TODO or an explicit re-export path so the intent is clear. Otherwise it's dead code that adds compile-time overhead without providing value.</comment>

<file context>
@@ -31,6 +31,7 @@ use crate::{commit_view::CommitView, git_panel::GitPanel, text_diff_view::TextDi
 
 mod askpass_modal;
 pub mod branch_picker;
+pub mod commit_diff_preview;
 mod commit_modal;
 pub mod commit_tooltip;
</file context>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
crates/project/src/git_store.rs (1)

1414-1423: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Redundant double-match on self.state.

The remote check at Line 1414-1416 and the second match &self.state at Line 1418-1423 both inspect the same unmutated state. Collapsing into a single let-else avoids the unreachable!() branch entirely.

♻️ Suggested consolidation
-        if matches!(&self.state, GitStoreState::Remote { .. }) {
-            return 0;
-        }
-
-        let updates_tx = match &self.state {
-            GitStoreState::Local { downstream, .. } => downstream
-                .as_ref()
-                .map(|downstream| downstream.updates_tx.clone()),
-            GitStoreState::Remote { .. } => unreachable!("remote git store returned above"),
-        };
+        let GitStoreState::Local { downstream, .. } = &self.state else {
+            return 0;
+        };
+        let updates_tx = downstream.as_ref().map(|downstream| downstream.updates_tx.clone());
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/project/src/git_store.rs` around lines 1414 - 1423, The `self.state`
logic in the `updates_tx` path is doing a redundant second match after already
returning for `GitStoreState::Remote`; collapse this into a single `let-else` or
equivalent in the same function so `unreachable!()` is no longer needed. Update
the `updates_tx` extraction to handle only the `GitStoreState::Local {
downstream, .. }` case directly, keeping the existing remote early return
behavior intact.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@crates/project/src/git_store.rs`:
- Around line 1414-1423: The `self.state` logic in the `updates_tx` path is
doing a redundant second match after already returning for
`GitStoreState::Remote`; collapse this into a single `let-else` or equivalent in
the same function so `unreachable!()` is no longer needed. Update the
`updates_tx` extraction to handle only the `GitStoreState::Local { downstream,
.. }` case directly, keeping the existing remote early return behavior intact.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 2aaa9091-07a6-44c2-8efd-bcb53867e41e

📥 Commits

Reviewing files that changed from the base of the PR and between d3cbd4e and 306b593.

📒 Files selected for processing (5)
  • crates/editor/src/split.rs
  • crates/git_ui/src/commit_diff_preview.rs
  • crates/git_ui/src/git_ui.rs
  • crates/git_ui/src/project_diff.rs
  • crates/project/src/git_store.rs
🚧 Files skipped from review as they are similar to previous changes (3)
  • crates/git_ui/src/git_ui.rs
  • crates/git_ui/src/commit_diff_preview.rs
  • crates/git_ui/src/project_diff.rs

@cubic-dev-ai cubic-dev-ai Bot left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1 issue found across 5 files (changes from recent commits).

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="crates/git_ui/src/project_diff.rs">

<violation number="1" location="crates/git_ui/src/project_diff.rs:1375">
P2: Splitting a ProjectDiff can show the wrong repository when the original diff was pinned to a non-active repo, because the cloned BranchDiff is recreated from DiffBase only and falls back to active_repository(). Preserving self.branch_diff.read(cx).repo() and applying set_repo in the new BranchDiff would keep split panes on the same repo.</violation>
</file>

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread crates/git_ui/src/project_diff.rs Resolved
@soyboyscout
soyboyscout merged commit 84bf4a8 into main Jul 10, 2026
40 of 42 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants