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",
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>
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(-)
@@ -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",
@@ -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",
@@ -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",
@@ -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",
@@ -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
+ }
}
}
}
@@ -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
@@ -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> {
@@ -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);
@@ -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;
@@ -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.
@@ -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()))
@@ -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 {
@@ -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{}",
@@ -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
@@ -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)
@@ -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?;
@@ -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;
}
}