git_ui: Add "Review Branch" with agent feature (#49513)

Danilo Leal and Bennet Bo Fenner created

This PR adds a button in the `git: branch diff` view that allows to
quickly and easily send the entire diff to your last used agent for a
review. What this does is automatically submits a (pre-written and
generic) prompt to the last agents you were using in the agent panel
with the whole content of your diff.

<img width="750" height="1964" alt="Screenshot 2026-02-18 at 3  35@2x"
src="https://github.com/user-attachments/assets/493d8cf4-4815-4b01-91a0-6a39ad6219fe"
/>

Release Notes:

- Added a "Review Branch" button in the `git: branch diff` view so that
the whole diff can be quickly sent for review to an agent.

---------

Co-authored-by: Bennet Bo Fenner <bennetbo@gmx.de>

Change summary

Cargo.lock                                           |   3 
assets/keymaps/default-linux.json                    |   1 
assets/keymaps/default-macos.json                    |   1 
assets/keymaps/default-windows.json                  |   1 
crates/acp_thread/src/mention.rs                     |  14 +
crates/agent/src/thread.rs                           |  21 ++
crates/agent_ui/src/acp/thread_view.rs               |  18 +-
crates/agent_ui/src/acp/thread_view/active_thread.rs |  25 ++-
crates/agent_ui/src/agent_panel.rs                   |  46 ++++-
crates/agent_ui/src/agent_ui.rs                      |   7 
crates/agent_ui/src/mention_set.rs                   |   7 
crates/agent_ui/src/text_thread_editor.rs            |   9 +
crates/git/src/repository.rs                         |  34 +++-
crates/git_ui/Cargo.toml                             |   3 
crates/git_ui/src/project_diff.rs                    | 112 +++++++++++++
crates/project/src/git_store.rs                      |  31 ++-
crates/proto/proto/git.proto                         |   2 
17 files changed, 285 insertions(+), 50 deletions(-)

Detailed changes

Cargo.lock 🔗

@@ -7141,7 +7141,10 @@ dependencies = [
 name = "git_ui"
 version = "0.1.0"
 dependencies = [
+ "acp_thread",
+ "agent-client-protocol",
  "agent_settings",
+ "agent_ui",
  "anyhow",
  "askpass",
  "buffer_diff",

assets/keymaps/default-linux.json 🔗

@@ -128,6 +128,7 @@
       "ctrl-i": "editor::ShowSignatureHelp",
       "alt-g b": "git::Blame",
       "alt-g m": "git::OpenModifiedFiles",
+      "alt-g r": "git::ReviewDiff",
       "menu": "editor::OpenContextMenu",
       "shift-f10": "editor::OpenContextMenu",
       "ctrl-alt-shift-e": "editor::ToggleEditPrediction",

assets/keymaps/default-macos.json 🔗

@@ -151,6 +151,7 @@
       "cmd-\"": "editor::ExpandAllDiffHunks",
       "cmd-alt-g b": "git::Blame",
       "cmd-alt-g m": "git::OpenModifiedFiles",
+      "cmd-alt-g r": "git::ReviewDiff",
       "cmd-i": "editor::ShowSignatureHelp",
       "f9": "editor::ToggleBreakpoint",
       "shift-f9": "editor::EditLogBreakpoint",

assets/keymaps/default-windows.json 🔗

@@ -123,6 +123,7 @@
       "ctrl-i": "editor::ShowSignatureHelp",
       "alt-g b": "git::Blame",
       "alt-g m": "git::OpenModifiedFiles",
+      "alt-g r": "git::ReviewDiff",
       "menu": "editor::OpenContextMenu",
       "shift-f10": "editor::OpenContextMenu",
       "ctrl-alt-e": "editor::ToggleEditPrediction",

crates/acp_thread/src/mention.rs 🔗

@@ -57,6 +57,9 @@ pub enum MentionUri {
     TerminalSelection {
         line_count: u32,
     },
+    GitDiff {
+        base_ref: String,
+    },
 }
 
 impl MentionUri {
@@ -208,6 +211,10 @@ impl MentionUri {
                         .parse::<u32>()
                         .unwrap_or(0);
                     Ok(Self::TerminalSelection { line_count })
+                } else if path.starts_with("/agent/git-diff") {
+                    let base_ref =
+                        single_query_param(&url, "base")?.unwrap_or_else(|| "main".to_string());
+                    Ok(Self::GitDiff { base_ref })
                 } else {
                     bail!("invalid zed url: {:?}", input);
                 }
@@ -237,6 +244,7 @@ impl MentionUri {
                     format!("Terminal ({} lines)", line_count)
                 }
             }
+            MentionUri::GitDiff { base_ref } => format!("Branch Diff ({})", base_ref),
             MentionUri::Selection {
                 abs_path: path,
                 line_range,
@@ -262,6 +270,7 @@ impl MentionUri {
             MentionUri::TerminalSelection { .. } => IconName::Terminal.path().into(),
             MentionUri::Selection { .. } => IconName::Reader.path().into(),
             MentionUri::Fetch { .. } => IconName::ToolWeb.path().into(),
+            MentionUri::GitDiff { .. } => IconName::GitBranch.path().into(),
         }
     }
 
@@ -360,6 +369,11 @@ impl MentionUri {
                     .append_pair("lines", &line_count.to_string());
                 url
             }
+            MentionUri::GitDiff { base_ref } => {
+                let mut url = Url::parse("zed:///agent/git-diff").unwrap();
+                url.query_pairs_mut().append_pair("base", base_ref);
+                url
+            }
         }
     }
 }

crates/agent/src/thread.rs 🔗

@@ -216,6 +216,7 @@ impl UserMessage {
         const OPEN_RULES_TAG: &str =
             "<rules>\nThe user has specified the following rules that should be applied:\n";
         const OPEN_DIAGNOSTICS_TAG: &str = "<diagnostics>";
+        const OPEN_DIFFS_TAG: &str = "<diffs>";
 
         let mut file_context = OPEN_FILES_TAG.to_string();
         let mut directory_context = OPEN_DIRECTORIES_TAG.to_string();
@@ -225,6 +226,7 @@ impl UserMessage {
         let mut fetch_context = OPEN_FETCH_TAG.to_string();
         let mut rules_context = OPEN_RULES_TAG.to_string();
         let mut diagnostics_context = OPEN_DIAGNOSTICS_TAG.to_string();
+        let mut diffs_context = OPEN_DIFFS_TAG.to_string();
 
         for chunk in &self.content {
             let chunk = match chunk {
@@ -320,6 +322,18 @@ impl UserMessage {
                             )
                             .ok();
                         }
+                        MentionUri::GitDiff { base_ref } => {
+                            write!(
+                                &mut diffs_context,
+                                "\nBranch diff against {}:\n{}",
+                                base_ref,
+                                MarkdownCodeBlock {
+                                    tag: "diff",
+                                    text: content
+                                }
+                            )
+                            .ok();
+                        }
                     }
 
                     language_model::MessageContent::Text(uri.as_link().to_string())
@@ -359,6 +373,13 @@ impl UserMessage {
                 .push(language_model::MessageContent::Text(selection_context));
         }
 
+        if diffs_context.len() > OPEN_DIFFS_TAG.len() {
+            diffs_context.push_str("</diffs>\n");
+            message
+                .content
+                .push(language_model::MessageContent::Text(diffs_context));
+        }
+
         if thread_context.len() > OPEN_THREADS_TAG.len() {
             thread_context.push_str("</threads>\n");
             message

crates/agent_ui/src/acp/thread_view.rs 🔗

@@ -71,12 +71,12 @@ use crate::agent_diff::AgentDiff;
 use crate::profile_selector::{ProfileProvider, ProfileSelector};
 use crate::ui::{AgentNotification, AgentNotificationEvent};
 use crate::{
-    AgentDiffPane, AgentPanel, AllowAlways, AllowOnce, AuthorizeToolCall, ClearMessageQueue,
-    CycleFavoriteModels, CycleModeSelector, CycleThinkingEffort, EditFirstQueuedMessage,
-    ExpandMessageEditor, ExternalAgentInitialContent, Follow, KeepAll, NewThread,
-    OpenAddContextMenu, OpenAgentDiff, OpenHistory, RejectAll, RejectOnce,
-    RemoveFirstQueuedMessage, SelectPermissionGranularity, SendImmediately, SendNextQueuedMessage,
-    ToggleProfileSelector, ToggleThinkingEffortMenu, ToggleThinkingMode, UndoLastReject,
+    AgentDiffPane, AgentInitialContent, AgentPanel, AllowAlways, AllowOnce, AuthorizeToolCall,
+    ClearMessageQueue, CycleFavoriteModels, CycleModeSelector, CycleThinkingEffort,
+    EditFirstQueuedMessage, ExpandMessageEditor, Follow, KeepAll, NewThread, OpenAddContextMenu,
+    OpenAgentDiff, OpenHistory, RejectAll, RejectOnce, RemoveFirstQueuedMessage,
+    SelectPermissionGranularity, SendImmediately, SendNextQueuedMessage, ToggleProfileSelector,
+    ToggleThinkingEffortMenu, ToggleThinkingMode, UndoLastReject,
 };
 
 const STOPWATCH_THRESHOLD: Duration = Duration::from_secs(30);
@@ -307,7 +307,7 @@ impl AcpServerView {
     pub fn new(
         agent: Rc<dyn AgentServer>,
         resume_thread: Option<AgentSessionInfo>,
-        initial_content: Option<ExternalAgentInitialContent>,
+        initial_content: Option<AgentInitialContent>,
         workspace: WeakEntity<Workspace>,
         project: Entity<Project>,
         thread_store: Option<Entity<ThreadStore>>,
@@ -408,7 +408,7 @@ impl AcpServerView {
         agent: Rc<dyn AgentServer>,
         resume_thread: Option<AgentSessionInfo>,
         project: Entity<Project>,
-        initial_content: Option<ExternalAgentInitialContent>,
+        initial_content: Option<AgentInitialContent>,
         window: &mut Window,
         cx: &mut Context<Self>,
     ) -> ServerState {
@@ -625,7 +625,7 @@ impl AcpServerView {
         thread: Entity<AcpThread>,
         resumed_without_history: bool,
         resume_thread: Option<AgentSessionInfo>,
-        initial_content: Option<ExternalAgentInitialContent>,
+        initial_content: Option<AgentInitialContent>,
         window: &mut Window,
         cx: &mut Context<Self>,
     ) -> Entity<AcpThreadView> {

crates/agent_ui/src/acp/thread_view/active_thread.rs 🔗

@@ -300,7 +300,7 @@ impl AcpThreadView {
         thread_store: Option<Entity<ThreadStore>>,
         history: Entity<AcpThreadHistory>,
         prompt_store: Option<Entity<PromptStore>>,
-        initial_content: Option<ExternalAgentInitialContent>,
+        initial_content: Option<AgentInitialContent>,
         mut subscriptions: Vec<Subscription>,
         window: &mut Window,
         cx: &mut Context<Self>,
@@ -313,6 +313,8 @@ impl AcpThreadView {
             this.update_recent_history_from_cache(&history, cx);
         });
 
+        let mut should_auto_submit = false;
+
         let message_editor = cx.new(|cx| {
             let mut editor = MessageEditor::new(
                 workspace.clone(),
@@ -333,15 +335,15 @@ impl AcpThreadView {
             );
             if let Some(content) = initial_content {
                 match content {
-                    ExternalAgentInitialContent::ThreadSummary(entry) => {
+                    AgentInitialContent::ThreadSummary(entry) => {
                         editor.insert_thread_summary(entry, window, cx);
                     }
-                    ExternalAgentInitialContent::Text(prompt) => {
-                        editor.set_message(
-                            vec![acp::ContentBlock::Text(acp::TextContent::new(prompt))],
-                            window,
-                            cx,
-                        );
+                    AgentInitialContent::ContentBlock {
+                        blocks,
+                        auto_submit,
+                    } => {
+                        should_auto_submit = auto_submit;
+                        editor.set_message(blocks, window, cx);
                     }
                 }
             }
@@ -378,7 +380,7 @@ impl AcpThreadView {
 
         let recent_history_entries = history.read(cx).get_recent_sessions(3);
 
-        Self {
+        let mut this = Self {
             id,
             parent_id,
             focus_handle: cx.focus_handle(),
@@ -442,7 +444,11 @@ impl AcpThreadView {
             history,
             _history_subscription: history_subscription,
             show_codex_windows_warning,
+        };
+        if should_auto_submit {
+            this.send(window, cx);
         }
+        this
     }
 
     pub fn handle_message_editor_event(
@@ -7607,6 +7613,7 @@ pub(crate) fn open_link(
             }
             MentionUri::Diagnostics { .. } => {}
             MentionUri::TerminalSelection { .. } => {}
+            MentionUri::GitDiff { .. } => {}
         })
     } else {
         cx.open_url(&url);

crates/agent_ui/src/agent_panel.rs 🔗

@@ -26,15 +26,14 @@ use crate::{
     text_thread_editor::{AgentPanelDelegate, TextThreadEditor, make_lsp_adapter_delegate},
     ui::EndTrialUpsell,
 };
+use crate::{
+    AgentInitialContent, ExternalAgent, NewExternalAgentThread, NewNativeAgentThreadFromSummary,
+};
 use crate::{
     ExpandMessageEditor,
     acp::{AcpThreadHistory, ThreadHistoryEvent},
     text_thread_history::{TextThreadHistory, TextThreadHistoryEvent},
 };
-use crate::{
-    ExternalAgent, ExternalAgentInitialContent, NewExternalAgentThread,
-    NewNativeAgentThreadFromSummary,
-};
 use crate::{ManageProfiles, acp::thread_view::AcpThreadView};
 use agent_settings::AgentSettings;
 use ai_onboarding::AgentPanelOnboarding;
@@ -879,7 +878,7 @@ impl AgentPanel {
         self.external_thread(
             Some(ExternalAgent::NativeAgent),
             None,
-            Some(ExternalAgentInitialContent::ThreadSummary(thread)),
+            Some(AgentInitialContent::ThreadSummary(thread)),
             window,
             cx,
         );
@@ -932,7 +931,7 @@ impl AgentPanel {
         &mut self,
         agent_choice: Option<crate::ExternalAgent>,
         resume_thread: Option<AgentSessionInfo>,
-        initial_content: Option<ExternalAgentInitialContent>,
+        initial_content: Option<AgentInitialContent>,
         window: &mut Window,
         cx: &mut Context<Self>,
     ) {
@@ -1765,7 +1764,10 @@ impl AgentPanel {
         self.external_thread(
             None,
             None,
-            initial_text.map(ExternalAgentInitialContent::Text),
+            initial_text.map(|text| AgentInitialContent::ContentBlock {
+                blocks: vec![acp::ContentBlock::Text(acp::TextContent::new(text))],
+                auto_submit: false,
+            }),
             window,
             cx,
         );
@@ -1833,7 +1835,7 @@ impl AgentPanel {
         &mut self,
         server: Rc<dyn AgentServer>,
         resume_thread: Option<AgentSessionInfo>,
-        initial_content: Option<ExternalAgentInitialContent>,
+        initial_content: Option<AgentInitialContent>,
         workspace: WeakEntity<Workspace>,
         project: Entity<Project>,
         ext_agent: ExternalAgent,
@@ -3389,6 +3391,34 @@ impl AgentPanelDelegate for ConcreteAssistantPanelDelegate {
             });
         });
     }
+
+    fn new_thread_with_content(
+        &self,
+        workspace: &mut Workspace,
+        blocks: Vec<acp::ContentBlock>,
+        auto_submit: bool,
+        window: &mut Window,
+        cx: &mut Context<Workspace>,
+    ) {
+        let Some(panel) = workspace.panel::<AgentPanel>(cx) else {
+            return;
+        };
+
+        workspace.focus_panel::<AgentPanel>(window, cx);
+
+        panel.update(cx, |panel, cx| {
+            panel.external_thread(
+                None,
+                None,
+                Some(AgentInitialContent::ContentBlock {
+                    blocks,
+                    auto_submit,
+                }),
+                window,
+                cx,
+            );
+        });
+    }
 }
 
 struct OnboardingUpsell;

crates/agent_ui/src/agent_ui.rs 🔗

@@ -228,9 +228,12 @@ impl ExternalAgent {
 }
 
 /// Content to initialize new external agent with.
-pub enum ExternalAgentInitialContent {
+pub enum AgentInitialContent {
     ThreadSummary(acp_thread::AgentSessionInfo),
-    Text(String),
+    ContentBlock {
+        blocks: Vec<agent_client_protocol::ContentBlock>,
+        auto_submit: bool,
+    },
 }
 
 /// Opens the profile management interface for configuring agent tools and settings.

crates/agent_ui/src/mention_set.rs 🔗

@@ -149,7 +149,8 @@ impl MentionSet {
             } => self.confirm_mention_for_diagnostics(include_errors, include_warnings, cx),
             MentionUri::PastedImage
             | MentionUri::Selection { .. }
-            | MentionUri::TerminalSelection { .. } => {
+            | MentionUri::TerminalSelection { .. }
+            | MentionUri::GitDiff { .. } => {
                 Task::ready(Err(anyhow!("Unsupported mention URI type for paste")))
             }
         }
@@ -290,6 +291,10 @@ impl MentionSet {
                 debug_panic!("unexpected terminal URI");
                 Task::ready(Err(anyhow!("unexpected terminal URI")))
             }
+            MentionUri::GitDiff { .. } => {
+                debug_panic!("unexpected git diff URI");
+                Task::ready(Err(anyhow!("unexpected git diff URI")))
+            }
         };
         let task = cx
             .spawn(async move |_, _| task.await.map_err(|e| e.to_string()))

crates/agent_ui/src/text_thread_editor.rs 🔗

@@ -167,6 +167,15 @@ pub trait AgentPanelDelegate {
         window: &mut Window,
         cx: &mut Context<Workspace>,
     );
+
+    fn new_thread_with_content(
+        &self,
+        workspace: &mut Workspace,
+        blocks: Vec<agent_client_protocol::ContentBlock>,
+        auto_submit: bool,
+        window: &mut Window,
+        cx: &mut Context<Workspace>,
+    );
 }
 
 impl dyn AgentPanelDelegate {

crates/git/src/repository.rs 🔗

@@ -826,6 +826,7 @@ pub trait GitRepository: Send + Sync {
 pub enum DiffType {
     HeadToIndex,
     HeadToWorktree,
+    MergeBase { base_ref: SharedString },
 }
 
 #[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize, JsonSchema)]
@@ -1881,18 +1882,31 @@ impl GitRepository for RealGitRepository {
         let git_binary_path = self.any_git_binary_path.clone();
         self.executor
             .spawn(async move {
-                let args = match diff {
-                    DiffType::HeadToIndex => Some("--staged"),
-                    DiffType::HeadToWorktree => None,
+                let working_directory = working_directory?;
+                let output = match diff {
+                    DiffType::HeadToIndex => {
+                        new_command(&git_binary_path)
+                            .current_dir(&working_directory)
+                            .args(["diff", "--staged"])
+                            .output()
+                            .await?
+                    }
+                    DiffType::HeadToWorktree => {
+                        new_command(&git_binary_path)
+                            .current_dir(&working_directory)
+                            .args(["diff"])
+                            .output()
+                            .await?
+                    }
+                    DiffType::MergeBase { base_ref } => {
+                        new_command(&git_binary_path)
+                            .current_dir(&working_directory)
+                            .args(["diff", "--merge-base", base_ref.as_ref(), "HEAD"])
+                            .output()
+                            .await?
+                    }
                 };
 
-                let output = new_command(&git_binary_path)
-                    .current_dir(&working_directory?)
-                    .args(["diff"])
-                    .args(args)
-                    .output()
-                    .await?;
-
                 anyhow::ensure!(
                     output.status.success(),
                     "Failed to run git diff:\n{}",

crates/git_ui/Cargo.toml 🔗

@@ -16,7 +16,10 @@ path = "src/git_ui.rs"
 test-support = ["multi_buffer/test-support", "remote_connection/test-support"]
 
 [dependencies]
+acp_thread.workspace = true
+agent-client-protocol.workspace = true
 agent_settings.workspace = true
+agent_ui.workspace = true
 anyhow.workspace = true
 askpass.workspace = true
 buffer_diff.workspace = true

crates/git_ui/src/project_diff.rs 🔗

@@ -5,6 +5,10 @@ use crate::{
     remote_button::{render_publish_button, render_push_button},
     resolve_active_repository,
 };
+use acp_thread::MentionUri;
+use agent_client_protocol as acp;
+use agent_settings::AgentSettings;
+use agent_ui::AgentPanelDelegate;
 use anyhow::{Context as _, Result, anyhow};
 use buffer_diff::{BufferDiff, DiffHunkSecondaryStatus};
 use collections::{HashMap, HashSet};
@@ -14,6 +18,7 @@ use editor::{
     multibuffer_context_lines,
     scroll::Autoscroll,
 };
+use git::repository::DiffType;
 
 use git::{
     Commit, StageAll, StageAndNext, ToggleStaged, UnstageAll, UnstageAndNext,
@@ -59,6 +64,8 @@ actions!(
         /// Shows the diff between the working directory and your default
         /// branch (typically main or master).
         BranchDiff,
+        /// Opens a new agent thread with the branch diff for review.
+        ReviewDiff,
         LeaderAndFollower,
     ]
 );
@@ -92,6 +99,7 @@ impl ProjectDiff {
     pub(crate) fn register(workspace: &mut Workspace, cx: &mut Context<Workspace>) {
         workspace.register_action(Self::deploy);
         workspace.register_action(Self::deploy_branch_diff);
+        workspace.register_action(Self::deploy_review_diff);
         workspace.register_action(|workspace, _: &Add, window, cx| {
             Self::deploy(workspace, &Diff, window, cx);
         });
@@ -142,6 +150,78 @@ impl ProjectDiff {
             .detach_and_notify_err(workspace_weak, window, cx);
     }
 
+    fn deploy_review_diff(
+        workspace: &mut Workspace,
+        _: &ReviewDiff,
+        window: &mut Window,
+        cx: &mut Context<Workspace>,
+    ) {
+        let Some(project_diff) = workspace
+            .items_of_type::<Self>(cx)
+            .find(|item| matches!(item.read(cx).diff_base(cx), DiffBase::Merge { .. }))
+        else {
+            return;
+        };
+
+        let diff_base = project_diff.read(cx).diff_base(cx).clone();
+        let DiffBase::Merge { base_ref } = diff_base else {
+            return;
+        };
+
+        let Some(repo) = project_diff.read(cx).branch_diff.read(cx).repo().cloned() else {
+            return;
+        };
+
+        let diff_receiver = repo.update(cx, |repo, cx| {
+            repo.diff(
+                DiffType::MergeBase {
+                    base_ref: base_ref.clone(),
+                },
+                cx,
+            )
+        });
+
+        let workspace_handle = cx.entity();
+        let workspace_weak = workspace_handle.downgrade();
+        window
+            .spawn(cx, async move |cx| {
+                let diff_text = diff_receiver.await??;
+
+                let mention_uri = MentionUri::GitDiff {
+                    base_ref: base_ref.into(),
+                };
+                let diff_uri = mention_uri.to_uri().to_string();
+
+                let content_blocks = vec![
+                    acp::ContentBlock::Text(acp::TextContent::new(
+                        "Please review this branch diff carefully. Point out any issues, potential bugs, \
+                         or improvement opportunities you find.\n\n"
+                            .to_string(),
+                    )),
+                    acp::ContentBlock::Resource(acp::EmbeddedResource::new(
+                        acp::EmbeddedResourceResource::TextResourceContents(
+                            acp::TextResourceContents::new(diff_text, diff_uri),
+                        ),
+                    )),
+                ];
+
+                workspace_handle.update_in(cx, |workspace, window, cx| {
+                    if let Some(delegate) = <dyn AgentPanelDelegate>::try_global(cx) {
+                        delegate.new_thread_with_content(
+                            workspace,
+                            content_blocks,
+                            true,
+                            window,
+                            cx,
+                        );
+                    }
+                })?;
+
+                anyhow::Ok(())
+            })
+            .detach_and_notify_err(workspace_weak, window, cx);
+    }
+
     pub fn deploy_at(
         workspace: &mut Workspace,
         entry: Option<GitStatusEntry>,
@@ -1511,7 +1591,7 @@ pub struct BranchDiffToolbar {
 }
 
 impl BranchDiffToolbar {
-    pub fn new(_: &mut Context<Self>) -> Self {
+    pub fn new(_cx: &mut Context<Self>) -> Self {
         Self { project_diff: None }
     }
 
@@ -1567,14 +1647,40 @@ impl Render for BranchDiffToolbar {
         let focus_handle = project_diff.focus_handle(cx);
         let review_count = project_diff.read(cx).total_review_comment_count();
 
+        let show_review_button = AgentSettings::get_global(cx).enabled(cx)
+            && <dyn AgentPanelDelegate>::try_global(cx).is_some();
+
         h_group_xl()
             .my_neg_1()
             .py_1()
             .items_center()
             .flex_wrap()
             .justify_end()
-            .when(review_count > 0, |el| {
-                el.child(
+            .when(show_review_button, |this| {
+                let focus_handle = focus_handle.clone();
+                this.child(
+                    Button::new("review-diff", "Review Diff")
+                        .icon(IconName::ZedAssistant)
+                        .icon_position(IconPosition::Start)
+                        .icon_size(IconSize::Small)
+                        .icon_color(Color::Muted)
+                        .key_binding(KeyBinding::for_action_in(&ReviewDiff, &focus_handle, cx))
+                        .tooltip(move |_, cx| {
+                            Tooltip::with_meta_in(
+                                "Review Diff",
+                                Some(&ReviewDiff),
+                                "Send this diff for your last agent to review.",
+                                &focus_handle,
+                                cx,
+                            )
+                        })
+                        .on_click(cx.listener(|this, _, window, cx| {
+                            this.dispatch_action(&ReviewDiff, window, cx);
+                        })),
+                )
+            })
+            .when(review_count > 0, |this| {
+                this.child(vertical_divider()).child(
                     render_send_review_to_agent_button(review_count, &focus_handle).on_click(
                         cx.listener(|this, _, window, cx| {
                             this.dispatch_action(&SendReviewToAgent, window, cx)

crates/project/src/git_store.rs 🔗

@@ -2661,6 +2661,15 @@ impl GitStore {
         let diff_type = match envelope.payload.diff_type() {
             proto::git_diff::DiffType::HeadToIndex => DiffType::HeadToIndex,
             proto::git_diff::DiffType::HeadToWorktree => DiffType::HeadToWorktree,
+            proto::git_diff::DiffType::MergeBase => {
+                let base_ref = envelope
+                    .payload
+                    .merge_base_ref
+                    .ok_or_else(|| anyhow!("merge_base_ref is required for MergeBase diff type"))?;
+                DiffType::MergeBase {
+                    base_ref: base_ref.into(),
+                }
+            }
         };
 
         let mut diff = repository_handle
@@ -5653,18 +5662,24 @@ impl Repository {
                     backend.diff(diff_type).await
                 }
                 RepositoryState::Remote(RemoteRepositoryState { project_id, client }) => {
+                    let (proto_diff_type, merge_base_ref) = match &diff_type {
+                        DiffType::HeadToIndex => {
+                            (proto::git_diff::DiffType::HeadToIndex.into(), None)
+                        }
+                        DiffType::HeadToWorktree => {
+                            (proto::git_diff::DiffType::HeadToWorktree.into(), None)
+                        }
+                        DiffType::MergeBase { base_ref } => (
+                            proto::git_diff::DiffType::MergeBase.into(),
+                            Some(base_ref.to_string()),
+                        ),
+                    };
                     let response = client
                         .request(proto::GitDiff {
                             project_id: project_id.0,
                             repository_id: id.to_proto(),
-                            diff_type: match diff_type {
-                                DiffType::HeadToIndex => {
-                                    proto::git_diff::DiffType::HeadToIndex.into()
-                                }
-                                DiffType::HeadToWorktree => {
-                                    proto::git_diff::DiffType::HeadToWorktree.into()
-                                }
-                            },
+                            diff_type: proto_diff_type,
+                            merge_base_ref,
                         })
                         .await?;
 

crates/proto/proto/git.proto 🔗

@@ -216,10 +216,12 @@ message GitDiff {
     reserved 2;
     uint64 repository_id = 3;
     DiffType diff_type = 4;
+    optional string merge_base_ref = 5;
 
     enum DiffType {
         HEAD_TO_WORKTREE = 0;
         HEAD_TO_INDEX = 1;
+        MERGE_BASE = 2;
     }
 }