use crate::{ branch_picker, conflict_view, git_panel::{GitPanel, GitPanelAddon, GitStatusEntry}, git_panel_settings::GitPanelSettings,};use agent_settings::AgentSettings;use anyhow::{Context as _, Result, anyhow};use buffer_diff::{BufferDiff, DiffHunkSecondaryStatus};use collections::HashMap;use editor::{ Addon, Editor, EditorEvent, EditorSettings, SelectionEffects, SplittableEditor, actions::{GoToHunk, GoToPreviousHunk, SendReviewToAgent}, multibuffer_context_lines, scroll::Autoscroll,};use futures_lite::future::yield_now;use git::repository::DiffType; use git::{ Commit, StageAll, StageAndNext, ToggleStaged, UnstageAll, UnstageAndNext, repository::RepoPath, status::FileStatus,};use gpui::{ Action, AnyElement, App, AppContext as _, AsyncWindowContext, Entity, EventEmitter, FocusHandle, Focusable, Render, Subscription, Task, WeakEntity, actions,};use language::{Anchor, Buffer, BufferId, Capability, OffsetRangeExt};use multi_buffer::{MultiBuffer, PathKey};use project::{ ConflictSet, Project, ProjectPath, git_store::{ Repository, branch_diff::{self, BranchDiffEvent, DiffBase}, },};use settings::{GitPanelGroupBy, GitPanelSortBy, Settings, SettingsStore};use std::any::{Any, TypeId};use std::collections::BTreeMap;use std::sync::Arc;use theme::ActiveTheme;use ui::{ CommonAnimationExt as _, DiffStat, Divider, KeyBinding, PopoverMenu, Tooltip, prelude::*, vertical_divider,};use util::{ResultExt as _, rel_path::RelPath};use workspace::{ CloseActiveItem, ItemNavHistory, SerializableItem, ToolbarItemEvent, ToolbarItemLocation, ToolbarItemView, Workspace, item::{Item, ItemEvent, ItemHandle, SaveOptions, TabContentParams}, notifications::NotifyTaskExt, searchable::SearchableItemHandle,};use zed_actions::agent::ReviewBranchDiff;use ztracing::instrument; actions!( git, [ /// Shows the diff between the working directory and the index. Diff, /// Adds files to the git staging area. Add, /// 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, /// Compare with a specific branch CompareWithBranch, ]); struct BufferSubscriptions { _diff: Entity<BufferDiff>, _diff_subscription: Subscription, _conflict_set: Entity<ConflictSet>, _conflict_set_subscription: Subscription,} pub struct ProjectDiff { project: Entity<Project>, multibuffer: Entity<MultiBuffer>, branch_diff: Entity<branch_diff::BranchDiff>, editor: Entity<SplittableEditor>, buffer_subscriptions: HashMap<Arc<RelPath>, BufferSubscriptions>, workspace: WeakEntity<Workspace>, focus_handle: FocusHandle, pending_scroll: Option<PathKey>, review_comment_count: usize, _task: Task<Result<()>>, _subscription: Subscription,} 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::compare_with_branch); workspace.register_action(|workspace, _: &Add, window, cx| { Self::deploy(workspace, &Diff, window, cx); }); workspace::register_serializable_item::<ProjectDiff>(cx); } fn deploy( workspace: &mut Workspace, _: &Diff, window: &mut Window, cx: &mut Context<Workspace>, ) { Self::deploy_at(workspace, None, window, cx) } fn deploy_branch_diff( workspace: &mut Workspace, _: &BranchDiff, window: &mut Window, cx: &mut Context<Workspace>, ) { telemetry::event!("Git Branch Diff Opened"); let project = workspace.project().clone(); let Some(intended_repo) = project.read(cx).active_repository(cx) else { let workspace = cx.entity().downgrade(); window .spawn(cx, async |_cx| { let result: Result<()> = Err(anyhow!("No active repository")); result }) .detach_and_notify_err(workspace, window, cx); return; }; let default_branch = intended_repo.update(cx, |repo, _| repo.default_branch(true)); let workspace = cx.entity(); let workspace_weak = workspace.downgrade(); window .spawn(cx, async move |cx| { let base_ref = default_branch .await?? .context("Could not determine default branch")?; workspace.update_in(cx, |workspace, window, cx| { Self::deploy_branch_diff_with_base_ref( workspace, project, intended_repo, base_ref, window, cx, ); })?; anyhow::Ok(()) }) .detach_and_notify_err(workspace_weak, window, cx); } fn compare_with_branch( workspace: &mut Workspace, _: &CompareWithBranch, window: &mut Window, cx: &mut Context<Workspace>, ) { let project = workspace.project().clone(); let Some(repository) = project.read(cx).active_repository(cx) else { let workspace = cx.entity().downgrade(); window .spawn(cx, async |_cx| { let result: Result<()> = Err(anyhow!("No active repository")); result }) .detach_and_notify_err(workspace, window, cx); return; }; let selected_branch = workspace.active_item_as::<Self>(cx).and_then(|item| { match item.read(cx).diff_base(cx) { DiffBase::Merge { base_ref } => Some(base_ref.clone()), DiffBase::Head => None, } }); let workspace_handle = workspace.weak_handle(); let on_select = Arc::new({ let repository = repository.clone(); let workspace = workspace_handle.clone(); move |branch: git::repository::Branch, window: &mut Window, cx: &mut App| { let base_ref: SharedString = branch.name().to_owned().into(); workspace .update(cx, |workspace, cx| { Self::deploy_branch_diff_with_base_ref( workspace, project.clone(), repository.clone(), base_ref, window, cx, ); }) .ok(); } }); workspace.toggle_modal(window, cx, |window, cx| { branch_picker::select_modal( workspace_handle, Some(repository), selected_branch, on_select, window, cx, ) }); } fn deploy_branch_diff_with_base_ref( workspace: &mut Workspace, project: Entity<Project>, intended_repo: Entity<Repository>, base_ref: SharedString, window: &mut Window, cx: &mut Context<Workspace>, ) { let existing = workspace.items_of_type::<Self>(cx).find(|item| { let item = item.read(cx); matches!( item.diff_base(cx), DiffBase::Merge { base_ref: existing_base_ref } if existing_base_ref == &base_ref ) }); if let Some(existing) = existing { workspace.activate_item(&existing, true, true, window, cx); let needs_switch = existing .read(cx) .branch_diff .read(cx) .repo() .map_or(true, |current| { current.read(cx).id != intended_repo.read(cx).id }); if needs_switch { existing.update(cx, |project_diff, cx| { project_diff.branch_diff.update(cx, |branch_diff, cx| { branch_diff.set_repo(Some(intended_repo), cx); }); }); } return; } let workspace = cx.entity(); let workspace_weak = workspace.downgrade(); window .spawn(cx, async move |cx| { let this = cx .update(|window, cx| { Self::new_with_branch_base( project, workspace.clone(), base_ref, intended_repo, window, cx, ) })? .await?; workspace .update_in(cx, |workspace, window, cx| { workspace.add_item_to_active_pane(Box::new(this), None, true, window, cx); }) .ok(); anyhow::Ok(()) }) .detach_and_notify_err(workspace_weak, window, cx); } fn review_diff(&mut self, _: &ReviewDiff, window: &mut Window, cx: &mut Context<Self>) { let diff_base = self.diff_base(cx).clone(); let DiffBase::Merge { base_ref } = diff_base else { return; }; let Some(repo) = self.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 = self.workspace.clone(); window .spawn(cx, { let workspace = workspace.clone(); async move |cx| { let diff_text = diff_receiver.await??; if let Some(workspace) = workspace.upgrade() { workspace.update_in(cx, |_workspace, window, cx| { window.dispatch_action( ReviewBranchDiff { diff_text: diff_text.into(), base_ref, } .boxed_clone(), cx, ); })?; } anyhow::Ok(()) } }) .detach_and_notify_err(workspace, window, cx); } pub fn deploy_at( workspace: &mut Workspace, entry: Option<GitStatusEntry>, window: &mut Window, cx: &mut Context<Workspace>, ) { telemetry::event!( "Git Diff Opened", source = if entry.is_some() { "Git Panel" } else { "Action" } ); let intended_repo = workspace.project().read(cx).active_repository(cx); let existing = workspace .items_of_type::<Self>(cx) .find(|item| matches!(item.read(cx).diff_base(cx), DiffBase::Head)); let project_diff = if let Some(existing) = existing { existing.update(cx, |project_diff, cx| { project_diff.move_to_beginning(window, cx); }); workspace.activate_item(&existing, true, true, window, cx); existing } else { let workspace_handle = cx.entity(); let project_diff = cx.new(|cx| Self::new(workspace.project().clone(), workspace_handle, window, cx)); workspace.add_item_to_active_pane( Box::new(project_diff.clone()), None, true, window, cx, ); project_diff }; if let Some(intended) = &intended_repo { let needs_switch = project_diff .read(cx) .branch_diff .read(cx) .repo() .map_or(true, |current| current.read(cx).id != intended.read(cx).id); if needs_switch { project_diff.update(cx, |project_diff, cx| { project_diff.branch_diff.update(cx, |branch_diff, cx| { branch_diff.set_repo(Some(intended.clone()), cx); }); }); } } if let Some(entry) = entry { project_diff.update(cx, |project_diff, cx| { project_diff.move_to_entry(entry, window, cx); }) } } pub fn deploy_at_project_path( workspace: &mut Workspace, project_path: ProjectPath, window: &mut Window, cx: &mut Context<Workspace>, ) { telemetry::event!("Git Diff Opened", source = "Agent Panel"); let existing = workspace .items_of_type::<Self>(cx) .find(|item| matches!(item.read(cx).diff_base(cx), DiffBase::Head)); let project_diff = if let Some(existing) = existing { workspace.activate_item(&existing, true, true, window, cx); existing } else { let workspace_handle = cx.entity(); let project_diff = cx.new(|cx| Self::new(workspace.project().clone(), workspace_handle, window, cx)); workspace.add_item_to_active_pane( Box::new(project_diff.clone()), None, true, window, cx, ); project_diff }; project_diff.update(cx, |project_diff, cx| { project_diff.move_to_project_path(&project_path, window, cx); }); } pub fn autoscroll(&self, cx: &mut Context<Self>) { self.editor.update(cx, |editor, cx| { editor.rhs_editor().update(cx, |editor, cx| { editor.request_autoscroll(Autoscroll::fit(), cx); }) }) } #[cfg(test)] #[allow(dead_code)] fn new_with_default_branch( project: Entity<Project>, workspace: Entity<Workspace>, window: &mut Window, cx: &mut App, ) -> Task<Result<Entity<Self>>> { let Some(repo) = project.read(cx).git_store().read(cx).active_repository() else { return Task::ready(Err(anyhow!("No active repository"))); }; let main_branch = repo.update(cx, |repo, _| repo.default_branch(true)); window.spawn(cx, async move |cx| { let main_branch = main_branch .await?? .context("Could not determine default branch")?; let branch_diff = cx.new_window_entity(|window, cx| { let mut branch_diff = branch_diff::BranchDiff::new( DiffBase::Merge { base_ref: main_branch, }, project.clone(), window, cx, ); branch_diff.set_repo(Some(repo.clone()), cx); branch_diff })?; cx.new_window_entity(|window, cx| { Self::new_impl(branch_diff, project, workspace, window, cx) }) }) } fn new_with_branch_base( project: Entity<Project>, workspace: Entity<Workspace>, base_ref: SharedString, repo: Entity<Repository>, window: &mut Window, cx: &mut App, ) -> Task<Result<Entity<Self>>> { window.spawn(cx, async move |cx| { let branch_diff = cx.new_window_entity(|window, cx| { let mut branch_diff = branch_diff::BranchDiff::new( DiffBase::Merge { base_ref }, project.clone(), window, cx, ); branch_diff.set_repo(Some(repo.clone()), cx); branch_diff })?; cx.new_window_entity(|window, cx| { Self::new_impl(branch_diff, project, workspace, window, cx) }) }) } fn new( project: Entity<Project>, workspace: Entity<Workspace>, window: &mut Window, cx: &mut Context<Self>, ) -> Self { let branch_diff = cx.new(|cx| branch_diff::BranchDiff::new(DiffBase::Head, project.clone(), window, cx)); Self::new_impl(branch_diff, project, workspace, window, cx) } fn new_impl( branch_diff: Entity<branch_diff::BranchDiff>, project: Entity<Project>,Lines 1–500 of 3220. Load more
use crate::{ branch_picker, conflict_view, git_panel::{GitPanel, GitPanelAddon, GitStatusEntry}, git_panel_settings::GitPanelSettings, }; use agent_settings::AgentSettings; use anyhow::{Context as _, Result, anyhow}; use buffer_diff::{BufferDiff, DiffHunkSecondaryStatus}; use collections::HashMap; use editor::{ Addon, Editor, EditorEvent, EditorSettings, SelectionEffects, SplittableEditor, actions::{GoToHunk, GoToPreviousHunk, SendReviewToAgent}, multibuffer_context_lines, scroll::Autoscroll, }; use futures_lite::future::yield_now; use git::repository::DiffType; use git::{ Commit, StageAll, StageAndNext, ToggleStaged, UnstageAll, UnstageAndNext, repository::RepoPath, status::FileStatus, }; use gpui::{ Action, AnyElement, App, AppContext as _, AsyncWindowContext, Entity, EventEmitter, FocusHandle, Focusable, Render, Subscription, Task, WeakEntity, actions, }; use language::{Anchor, Buffer, BufferId, Capability, OffsetRangeExt}; use multi_buffer::{MultiBuffer, PathKey}; use project::{ ConflictSet, Project, ProjectPath, git_store::{ Repository, branch_diff::{self, BranchDiffEvent, DiffBase}, }, }; use settings::{GitPanelGroupBy, GitPanelSortBy, Settings, SettingsStore}; use std::any::{Any, TypeId}; use std::collections::BTreeMap; use std::sync::Arc; use theme::ActiveTheme; use ui::{ CommonAnimationExt as _, DiffStat, Divider, KeyBinding, PopoverMenu, Tooltip, prelude::*, vertical_divider, }; use util::{ResultExt as _, rel_path::RelPath}; use workspace::{ CloseActiveItem, ItemNavHistory, SerializableItem, ToolbarItemEvent, ToolbarItemLocation, ToolbarItemView, Workspace, item::{Item, ItemEvent, ItemHandle, SaveOptions, TabContentParams}, notifications::NotifyTaskExt, searchable::SearchableItemHandle, }; use zed_actions::agent::ReviewBranchDiff; use ztracing::instrument; actions!( git, [ /// Shows the diff between the working directory and the index. Diff, /// Adds files to the git staging area. Add, /// 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, /// Compare with a specific branch CompareWithBranch, ] ); struct BufferSubscriptions { _diff: Entity<BufferDiff>, _diff_subscription: Subscription, _conflict_set: Entity<ConflictSet>, _conflict_set_subscription: Subscription, } pub struct ProjectDiff { project: Entity<Project>, multibuffer: Entity<MultiBuffer>, branch_diff: Entity<branch_diff::BranchDiff>, editor: Entity<SplittableEditor>, buffer_subscriptions: HashMap<Arc<RelPath>, BufferSubscriptions>, workspace: WeakEntity<Workspace>, focus_handle: FocusHandle, pending_scroll: Option<PathKey>, review_comment_count: usize, _task: Task<Result<()>>, _subscription: Subscription, } 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::compare_with_branch); workspace.register_action(|workspace, _: &Add, window, cx| { Self::deploy(workspace, &Diff, window, cx); }); workspace::register_serializable_item::<ProjectDiff>(cx); } fn deploy( workspace: &mut Workspace, _: &Diff, window: &mut Window, cx: &mut Context<Workspace>, ) { Self::deploy_at(workspace, None, window, cx) } fn deploy_branch_diff( workspace: &mut Workspace, _: &BranchDiff, window: &mut Window, cx: &mut Context<Workspace>, ) { telemetry::event!("Git Branch Diff Opened"); let project = workspace.project().clone(); let Some(intended_repo) = project.read(cx).active_repository(cx) else { let workspace = cx.entity().downgrade(); window .spawn(cx, async |_cx| { let result: Result<()> = Err(anyhow!("No active repository")); result }) .detach_and_notify_err(workspace, window, cx); return; }; let default_branch = intended_repo.update(cx, |repo, _| repo.default_branch(true)); let workspace = cx.entity(); let workspace_weak = workspace.downgrade(); window .spawn(cx, async move |cx| { let base_ref = default_branch .await?? .context("Could not determine default branch")?; workspace.update_in(cx, |workspace, window, cx| { Self::deploy_branch_diff_with_base_ref( workspace, project, intended_repo, base_ref, window, cx, ); })?; anyhow::Ok(()) }) .detach_and_notify_err(workspace_weak, window, cx); } fn compare_with_branch( workspace: &mut Workspace, _: &CompareWithBranch, window: &mut Window, cx: &mut Context<Workspace>, ) { let project = workspace.project().clone(); let Some(repository) = project.read(cx).active_repository(cx) else { let workspace = cx.entity().downgrade(); window .spawn(cx, async |_cx| { let result: Result<()> = Err(anyhow!("No active repository")); result }) .detach_and_notify_err(workspace, window, cx); return; }; let selected_branch = workspace.active_item_as::<Self>(cx).and_then(|item| { match item.read(cx).diff_base(cx) { DiffBase::Merge { base_ref } => Some(base_ref.clone()), DiffBase::Head => None, } }); let workspace_handle = workspace.weak_handle(); let on_select = Arc::new({ let repository = repository.clone(); let workspace = workspace_handle.clone(); move |branch: git::repository::Branch, window: &mut Window, cx: &mut App| { let base_ref: SharedString = branch.name().to_owned().into(); workspace .update(cx, |workspace, cx| { Self::deploy_branch_diff_with_base_ref( workspace, project.clone(), repository.clone(), base_ref, window, cx, ); }) .ok(); } }); workspace.toggle_modal(window, cx, |window, cx| { branch_picker::select_modal( workspace_handle, Some(repository), selected_branch, on_select, window, cx, ) }); } fn deploy_branch_diff_with_base_ref( workspace: &mut Workspace, project: Entity<Project>, intended_repo: Entity<Repository>, base_ref: SharedString, window: &mut Window, cx: &mut Context<Workspace>, ) { let existing = workspace.items_of_type::<Self>(cx).find(|item| { let item = item.read(cx); matches!( item.diff_base(cx), DiffBase::Merge { base_ref: existing_base_ref } if existing_base_ref == &base_ref ) }); if let Some(existing) = existing { workspace.activate_item(&existing, true, true, window, cx); let needs_switch = existing .read(cx) .branch_diff .read(cx) .repo() .map_or(true, |current| { current.read(cx).id != intended_repo.read(cx).id }); if needs_switch { existing.update(cx, |project_diff, cx| { project_diff.branch_diff.update(cx, |branch_diff, cx| { branch_diff.set_repo(Some(intended_repo), cx); }); }); } return; } let workspace = cx.entity(); let workspace_weak = workspace.downgrade(); window .spawn(cx, async move |cx| { let this = cx .update(|window, cx| { Self::new_with_branch_base( project, workspace.clone(), base_ref, intended_repo, window, cx, ) })? .await?; workspace .update_in(cx, |workspace, window, cx| { workspace.add_item_to_active_pane(Box::new(this), None, true, window, cx); }) .ok(); anyhow::Ok(()) }) .detach_and_notify_err(workspace_weak, window, cx); } fn review_diff(&mut self, _: &ReviewDiff, window: &mut Window, cx: &mut Context<Self>) { let diff_base = self.diff_base(cx).clone(); let DiffBase::Merge { base_ref } = diff_base else { return; }; let Some(repo) = self.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 = self.workspace.clone(); window .spawn(cx, { let workspace = workspace.clone(); async move |cx| { let diff_text = diff_receiver.await??; if let Some(workspace) = workspace.upgrade() { workspace.update_in(cx, |_workspace, window, cx| { window.dispatch_action( ReviewBranchDiff { diff_text: diff_text.into(), base_ref, } .boxed_clone(), cx, ); })?; } anyhow::Ok(()) } }) .detach_and_notify_err(workspace, window, cx); } pub fn deploy_at( workspace: &mut Workspace, entry: Option<GitStatusEntry>, window: &mut Window, cx: &mut Context<Workspace>, ) { telemetry::event!( "Git Diff Opened", source = if entry.is_some() { "Git Panel" } else { "Action" } ); let intended_repo = workspace.project().read(cx).active_repository(cx); let existing = workspace .items_of_type::<Self>(cx) .find(|item| matches!(item.read(cx).diff_base(cx), DiffBase::Head)); let project_diff = if let Some(existing) = existing { existing.update(cx, |project_diff, cx| { project_diff.move_to_beginning(window, cx); }); workspace.activate_item(&existing, true, true, window, cx); existing } else { let workspace_handle = cx.entity(); let project_diff = cx.new(|cx| Self::new(workspace.project().clone(), workspace_handle, window, cx)); workspace.add_item_to_active_pane( Box::new(project_diff.clone()), None, true, window, cx, ); project_diff }; if let Some(intended) = &intended_repo { let needs_switch = project_diff .read(cx) .branch_diff .read(cx) .repo() .map_or(true, |current| current.read(cx).id != intended.read(cx).id); if needs_switch { project_diff.update(cx, |project_diff, cx| { project_diff.branch_diff.update(cx, |branch_diff, cx| { branch_diff.set_repo(Some(intended.clone()), cx); }); }); } } if let Some(entry) = entry { project_diff.update(cx, |project_diff, cx| { project_diff.move_to_entry(entry, window, cx); }) } } pub fn deploy_at_project_path( workspace: &mut Workspace, project_path: ProjectPath, window: &mut Window, cx: &mut Context<Workspace>, ) { telemetry::event!("Git Diff Opened", source = "Agent Panel"); let existing = workspace .items_of_type::<Self>(cx) .find(|item| matches!(item.read(cx).diff_base(cx), DiffBase::Head)); let project_diff = if let Some(existing) = existing { workspace.activate_item(&existing, true, true, window, cx); existing } else { let workspace_handle = cx.entity(); let project_diff = cx.new(|cx| Self::new(workspace.project().clone(), workspace_handle, window, cx)); workspace.add_item_to_active_pane( Box::new(project_diff.clone()), None, true, window, cx, ); project_diff }; project_diff.update(cx, |project_diff, cx| { project_diff.move_to_project_path(&project_path, window, cx); }); } pub fn autoscroll(&self, cx: &mut Context<Self>) { self.editor.update(cx, |editor, cx| { editor.rhs_editor().update(cx, |editor, cx| { editor.request_autoscroll(Autoscroll::fit(), cx); }) }) } #[cfg(test)] #[allow(dead_code)] fn new_with_default_branch( project: Entity<Project>, workspace: Entity<Workspace>, window: &mut Window, cx: &mut App, ) -> Task<Result<Entity<Self>>> { let Some(repo) = project.read(cx).git_store().read(cx).active_repository() else { return Task::ready(Err(anyhow!("No active repository"))); }; let main_branch = repo.update(cx, |repo, _| repo.default_branch(true)); window.spawn(cx, async move |cx| { let main_branch = main_branch .await?? .context("Could not determine default branch")?; let branch_diff = cx.new_window_entity(|window, cx| { let mut branch_diff = branch_diff::BranchDiff::new( DiffBase::Merge { base_ref: main_branch, }, project.clone(), window, cx, ); branch_diff.set_repo(Some(repo.clone()), cx); branch_diff })?; cx.new_window_entity(|window, cx| { Self::new_impl(branch_diff, project, workspace, window, cx) }) }) } fn new_with_branch_base( project: Entity<Project>, workspace: Entity<Workspace>, base_ref: SharedString, repo: Entity<Repository>, window: &mut Window, cx: &mut App, ) -> Task<Result<Entity<Self>>> { window.spawn(cx, async move |cx| { let branch_diff = cx.new_window_entity(|window, cx| { let mut branch_diff = branch_diff::BranchDiff::new( DiffBase::Merge { base_ref }, project.clone(), window, cx, ); branch_diff.set_repo(Some(repo.clone()), cx); branch_diff })?; cx.new_window_entity(|window, cx| { Self::new_impl(branch_diff, project, workspace, window, cx) }) }) } fn new( project: Entity<Project>, workspace: Entity<Workspace>, window: &mut Window, cx: &mut Context<Self>, ) -> Self { let branch_diff = cx.new(|cx| branch_diff::BranchDiff::new(DiffBase::Head, project.clone(), window, cx)); Self::new_impl(branch_diff, project, workspace, window, cx) } fn new_impl( branch_diff: Entity<branch_diff::BranchDiff>, project: Entity<Project>,