git_ui: Add "Review Branch" with agent feature (#49513)
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>
This commit is contained in:
1 parent
128d3a4f2f
commit
fc80cd4c25
17 files changed
+285
-50
No files matched your search
Generated
+3
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user