diff --git a/Cargo.lock b/Cargo.lock index 02a5e6fa1081e8893e88c9b2cd9eac17784287ea..001dc23ce5e3373b0c42ca868b892ca29f989a1d 100644 --- a/Cargo.lock +++ b/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", diff --git a/assets/keymaps/default-linux.json b/assets/keymaps/default-linux.json index e569b4b5c9f1f71624942b547d2e719cc52fe86a..f3247e936f2b6d2d5ee5275304ea445729046afa 100644 --- a/assets/keymaps/default-linux.json +++ b/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", diff --git a/assets/keymaps/default-macos.json b/assets/keymaps/default-macos.json index 5a478542d6b0ccd5cb358b0ebb684c1c5b0c794c..77e01368462cdfcce24cf1cba39d6a2a11cdcce0 100644 --- a/assets/keymaps/default-macos.json +++ b/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", diff --git a/assets/keymaps/default-windows.json b/assets/keymaps/default-windows.json index 52dfce74efd2a0240734c44472bec19b81978f44..51b221c8389d1588d80a8186ddceb68e8cb025c7 100644 --- a/assets/keymaps/default-windows.json +++ b/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", diff --git a/crates/acp_thread/src/mention.rs b/crates/acp_thread/src/mention.rs index 6550e6f4ade781c165fe6671c9150004149f60da..5769d13860f2466f95fe7dd67c1f908812e40c2d 100644 --- a/crates/acp_thread/src/mention.rs +++ b/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::() .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 + } } } } diff --git a/crates/agent/src/thread.rs b/crates/agent/src/thread.rs index 9323eb6f70ec75fbaf17b81d42071cf3ad9c331a..3d68d39d5fb6f61ab188f632c203f9da8a43d1e5 100644 --- a/crates/agent/src/thread.rs +++ b/crates/agent/src/thread.rs @@ -216,6 +216,7 @@ impl UserMessage { const OPEN_RULES_TAG: &str = "\nThe user has specified the following rules that should be applied:\n"; const OPEN_DIAGNOSTICS_TAG: &str = ""; + const OPEN_DIFFS_TAG: &str = ""; 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("\n"); + message + .content + .push(language_model::MessageContent::Text(diffs_context)); + } + if thread_context.len() > OPEN_THREADS_TAG.len() { thread_context.push_str("\n"); message diff --git a/crates/agent_ui/src/acp/thread_view.rs b/crates/agent_ui/src/acp/thread_view.rs index 0393205c3bb00d2a955e3a76fc037ade842e3358..117f457ec0598fbe3a68b57515c2ed2c8578390b 100644 --- a/crates/agent_ui/src/acp/thread_view.rs +++ b/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, resume_thread: Option, - initial_content: Option, + initial_content: Option, workspace: WeakEntity, project: Entity, thread_store: Option>, @@ -408,7 +408,7 @@ impl AcpServerView { agent: Rc, resume_thread: Option, project: Entity, - initial_content: Option, + initial_content: Option, window: &mut Window, cx: &mut Context, ) -> ServerState { @@ -625,7 +625,7 @@ impl AcpServerView { thread: Entity, resumed_without_history: bool, resume_thread: Option, - initial_content: Option, + initial_content: Option, window: &mut Window, cx: &mut Context, ) -> Entity { diff --git a/crates/agent_ui/src/acp/thread_view/active_thread.rs b/crates/agent_ui/src/acp/thread_view/active_thread.rs index 7a399ce61b13b504c6408b5b78b51af97b0d8145..7dd0101f624f92f698b92df0a6842acd2fe48f4b 100644 --- a/crates/agent_ui/src/acp/thread_view/active_thread.rs +++ b/crates/agent_ui/src/acp/thread_view/active_thread.rs @@ -300,7 +300,7 @@ impl AcpThreadView { thread_store: Option>, history: Entity, prompt_store: Option>, - initial_content: Option, + initial_content: Option, mut subscriptions: Vec, window: &mut Window, cx: &mut Context, @@ -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); diff --git a/crates/agent_ui/src/agent_panel.rs b/crates/agent_ui/src/agent_panel.rs index ddef58888c0f172d17b0b5f759527180cb42903f..b7b8605fffa68b207327d9a91b3fae2b6a00de7c 100644 --- a/crates/agent_ui/src/agent_panel.rs +++ b/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, resume_thread: Option, - initial_content: Option, + initial_content: Option, window: &mut Window, cx: &mut Context, ) { @@ -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, resume_thread: Option, - initial_content: Option, + initial_content: Option, workspace: WeakEntity, project: Entity, ext_agent: ExternalAgent, @@ -3389,6 +3391,34 @@ impl AgentPanelDelegate for ConcreteAssistantPanelDelegate { }); }); } + + fn new_thread_with_content( + &self, + workspace: &mut Workspace, + blocks: Vec, + auto_submit: bool, + window: &mut Window, + cx: &mut Context, + ) { + let Some(panel) = workspace.panel::(cx) else { + return; + }; + + workspace.focus_panel::(window, cx); + + panel.update(cx, |panel, cx| { + panel.external_thread( + None, + None, + Some(AgentInitialContent::ContentBlock { + blocks, + auto_submit, + }), + window, + cx, + ); + }); + } } struct OnboardingUpsell; diff --git a/crates/agent_ui/src/agent_ui.rs b/crates/agent_ui/src/agent_ui.rs index 9c06ba95ba2117b01d0851b88339b2b7db5e358b..4d7f273b9011995ae683bce8a2f4c42841a4fcc2 100644 --- a/crates/agent_ui/src/agent_ui.rs +++ b/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, + auto_submit: bool, + }, } /// Opens the profile management interface for configuring agent tools and settings. diff --git a/crates/agent_ui/src/mention_set.rs b/crates/agent_ui/src/mention_set.rs index 7d752eadae558cad5ae36b0d7924e5e96f3e54ff..b1dab681ade325d0d47fa9f9310cb3e98bf72974 100644 --- a/crates/agent_ui/src/mention_set.rs +++ b/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())) diff --git a/crates/agent_ui/src/text_thread_editor.rs b/crates/agent_ui/src/text_thread_editor.rs index 1780e03b9fbfbc70b97ca445735b3ff3be03ec67..8c837e7812ded7f84018b69990e0fa056e0992b7 100644 --- a/crates/agent_ui/src/text_thread_editor.rs +++ b/crates/agent_ui/src/text_thread_editor.rs @@ -167,6 +167,15 @@ pub trait AgentPanelDelegate { window: &mut Window, cx: &mut Context, ); + + fn new_thread_with_content( + &self, + workspace: &mut Workspace, + blocks: Vec, + auto_submit: bool, + window: &mut Window, + cx: &mut Context, + ); } impl dyn AgentPanelDelegate { diff --git a/crates/git/src/repository.rs b/crates/git/src/repository.rs index d57b5b583b9228b1bbaba439928a07c5f1a86768..7d50e25780b4541a703c4524e24f73569a29926a 100644 --- a/crates/git/src/repository.rs +++ b/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{}", diff --git a/crates/git_ui/Cargo.toml b/crates/git_ui/Cargo.toml index f779570be471fd1a097e350d59ef2fb1d4003d2b..b91868ffe69b178b07946a463ac5108c8094c1e1 100644 --- a/crates/git_ui/Cargo.toml +++ b/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 diff --git a/crates/git_ui/src/project_diff.rs b/crates/git_ui/src/project_diff.rs index 559ae80077f829eea6c95ee6023849d142c2ee0f..7c07931ff292568f01781d56260281a9413e7ba4 100644 --- a/crates/git_ui/src/project_diff.rs +++ b/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.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, + ) { + let Some(project_diff) = workspace + .items_of_type::(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) = ::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, @@ -1511,7 +1591,7 @@ pub struct BranchDiffToolbar { } impl BranchDiffToolbar { - pub fn new(_: &mut Context) -> Self { + pub fn new(_cx: &mut Context) -> 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) + && ::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) diff --git a/crates/project/src/git_store.rs b/crates/project/src/git_store.rs index 2872d446e9cbaae26a8f7dba0e3703f4d52e3eff..92b9876f9919a1e016ebe578f8e43c2a7e861b20 100644 --- a/crates/project/src/git_store.rs +++ b/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?; diff --git a/crates/proto/proto/git.proto b/crates/proto/proto/git.proto index eda4fa9b5dc28dcdad9121cf7e0124ffe4e9ac03..994d319913c6d84c2e639ccd78bade4547449a7a 100644 --- a/crates/proto/proto/git.proto +++ b/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; } }