TEMP

Conrad Irwin created

Change summary

crates/assistant_tools/src/edit_file_tool.rs |   5 
crates/buffer_diff/src/buffer_diff.rs        |  20 
crates/editor/src/editor.rs                  |  64 --
crates/editor/src/element.rs                 |   1 
crates/editor/src/proposed_changes_editor.rs | 516 ----------------------
crates/git/src/repository.rs                 |  33 
crates/git_ui/src/project_diff.rs            | 125 ++++
crates/project/src/buffer_store.rs           |   9 
crates/project/src/git_store.rs              |  16 
crates/zed/src/zed.rs                        |   3 
10 files changed, 139 insertions(+), 653 deletions(-)

Detailed changes

crates/assistant_tools/src/edit_file_tool.rs 🔗

@@ -636,8 +636,11 @@ impl EditFileToolCard {
         // Create a buffer diff with the current text as the base
         let buffer_diff = cx.new(|cx| {
             let mut diff = BufferDiff::new(&text_snapshot, cx);
+            let base_text = buffer_snapshot.text();
+            let language = buffer_snapshot.language().cloned();
             let _ = diff.set_base_text(
-                buffer_snapshot.clone(),
+                Some(Arc::new(base_text)),
+                language,
                 language_registry,
                 text_snapshot,
                 cx,

crates/buffer_diff/src/buffer_diff.rs 🔗

@@ -1158,34 +1158,22 @@ impl BufferDiff {
         self.hunks_intersecting_range(start..end, buffer, cx)
     }
 
-    pub fn set_base_text_buffer(
-        &mut self,
-        base_buffer: Entity<language::Buffer>,
-        buffer: text::BufferSnapshot,
-        cx: &mut Context<Self>,
-    ) -> oneshot::Receiver<()> {
-        let base_buffer = base_buffer.read(cx);
-        let language_registry = base_buffer.language_registry();
-        let base_buffer = base_buffer.snapshot();
-        self.set_base_text(base_buffer, language_registry, buffer, cx)
-    }
-
     /// Used in cases where the change set isn't derived from git.
     pub fn set_base_text(
         &mut self,
-        base_buffer: language::BufferSnapshot,
+        base_text: Option<Arc<String>>,
+        language: Option<Arc<Language>>,
         language_registry: Option<Arc<LanguageRegistry>>,
         buffer: text::BufferSnapshot,
         cx: &mut Context<Self>,
     ) -> oneshot::Receiver<()> {
         let (tx, rx) = oneshot::channel();
         let this = cx.weak_entity();
-        let base_text = Arc::new(base_buffer.text());
 
         let snapshot = BufferDiffSnapshot::new_with_base_text(
             buffer.clone(),
-            Some(base_text),
-            base_buffer.language().cloned(),
+            base_text,
+            language,
             language_registry,
             cx,
         );

crates/editor/src/editor.rs 🔗

@@ -34,7 +34,6 @@ mod lsp_ext;
 mod mouse_context_menu;
 pub mod movement;
 mod persistence;
-mod proposed_changes_editor;
 mod rust_analyzer_ext;
 pub mod scroll;
 mod selections_collection;
@@ -70,9 +69,7 @@ pub use multi_buffer::{
     Anchor, AnchorRangeExt, ExcerptId, ExcerptRange, MultiBuffer, MultiBufferSnapshot, PathKey,
     RowInfo, ToOffset, ToPoint,
 };
-pub use proposed_changes_editor::{
-    ProposedChangeLocation, ProposedChangesEditor, ProposedChangesEditorToolbar,
-};
+
 pub use text::Bias;
 
 use ::git::{
@@ -20472,65 +20469,6 @@ impl Editor {
         self.searchable
     }
 
-    fn open_proposed_changes_editor(
-        &mut self,
-        _: &OpenProposedChangesEditor,
-        window: &mut Window,
-        cx: &mut Context<Self>,
-    ) {
-        let Some(workspace) = self.workspace() else {
-            cx.propagate();
-            return;
-        };
-
-        let selections = self.selections.all::<usize>(cx);
-        let multi_buffer = self.buffer.read(cx);
-        let multi_buffer_snapshot = multi_buffer.snapshot(cx);
-        let mut new_selections_by_buffer = HashMap::default();
-        for selection in selections {
-            for (buffer, range, _) in
-                multi_buffer_snapshot.range_to_buffer_ranges(selection.start..selection.end)
-            {
-                let mut range = range.to_point(buffer);
-                range.start.column = 0;
-                range.end.column = buffer.line_len(range.end.row);
-                new_selections_by_buffer
-                    .entry(multi_buffer.buffer(buffer.remote_id()).unwrap())
-                    .or_insert(Vec::new())
-                    .push(range)
-            }
-        }
-
-        let proposed_changes_buffers = new_selections_by_buffer
-            .into_iter()
-            .map(|(buffer, ranges)| ProposedChangeLocation { buffer, ranges })
-            .collect::<Vec<_>>();
-        let proposed_changes_editor = cx.new(|cx| {
-            ProposedChangesEditor::new(
-                "Proposed changes",
-                proposed_changes_buffers,
-                self.project.clone(),
-                window,
-                cx,
-            )
-        });
-
-        window.defer(cx, move |window, cx| {
-            workspace.update(cx, |workspace, cx| {
-                workspace.active_pane().update(cx, |pane, cx| {
-                    pane.add_item(
-                        Box::new(proposed_changes_editor),
-                        true,
-                        true,
-                        None,
-                        window,
-                        cx,
-                    );
-                });
-            });
-        });
-    }
-
     pub fn open_excerpts_in_split(
         &mut self,
         _: &OpenExcerptsSplit,

crates/editor/src/element.rs 🔗

@@ -440,7 +440,6 @@ impl EditorElement {
         register_action(editor, window, Editor::toggle_code_actions);
         register_action(editor, window, Editor::open_excerpts);
         register_action(editor, window, Editor::open_excerpts_in_split);
-        register_action(editor, window, Editor::open_proposed_changes_editor);
         register_action(editor, window, Editor::toggle_soft_wrap);
         register_action(editor, window, Editor::toggle_tab_bar);
         register_action(editor, window, Editor::toggle_line_numbers);

crates/editor/src/proposed_changes_editor.rs 🔗

@@ -1,516 +0,0 @@
-use crate::{ApplyAllDiffHunks, Editor, EditorEvent, SelectionEffects, SemanticsProvider};
-use buffer_diff::BufferDiff;
-use collections::HashSet;
-use futures::{channel::mpsc, future::join_all};
-use gpui::{App, Entity, EventEmitter, Focusable, Render, Subscription, Task};
-use language::{Buffer, BufferEvent, Capability};
-use multi_buffer::{ExcerptRange, MultiBuffer};
-use project::Project;
-use smol::stream::StreamExt;
-use std::{any::TypeId, ops::Range, rc::Rc, time::Duration};
-use text::ToOffset;
-use ui::{ButtonLike, KeyBinding, prelude::*};
-use workspace::{
-    Item, ItemHandle as _, ToolbarItemEvent, ToolbarItemLocation, ToolbarItemView, Workspace,
-    item::SaveOptions, searchable::SearchableItemHandle,
-};
-
-pub struct ProposedChangesEditor {
-    editor: Entity<Editor>,
-    multibuffer: Entity<MultiBuffer>,
-    title: SharedString,
-    buffer_entries: Vec<BufferEntry>,
-    _recalculate_diffs_task: Task<Option<()>>,
-    recalculate_diffs_tx: mpsc::UnboundedSender<RecalculateDiff>,
-}
-
-pub struct ProposedChangeLocation<T> {
-    pub buffer: Entity<Buffer>,
-    pub ranges: Vec<Range<T>>,
-}
-
-struct BufferEntry {
-    base: Entity<Buffer>,
-    branch: Entity<Buffer>,
-    _subscription: Subscription,
-}
-
-pub struct ProposedChangesEditorToolbar {
-    current_editor: Option<Entity<ProposedChangesEditor>>,
-}
-
-struct RecalculateDiff {
-    buffer: Entity<Buffer>,
-    debounce: bool,
-}
-
-/// A provider of code semantics for branch buffers.
-///
-/// Requests in edited regions will return nothing, but requests in unchanged
-/// regions will be translated into the base buffer's coordinates.
-struct BranchBufferSemanticsProvider(Rc<dyn SemanticsProvider>);
-
-impl ProposedChangesEditor {
-    pub fn new<T: Clone + ToOffset>(
-        title: impl Into<SharedString>,
-        locations: Vec<ProposedChangeLocation<T>>,
-        project: Option<Entity<Project>>,
-        window: &mut Window,
-        cx: &mut Context<Self>,
-    ) -> Self {
-        let multibuffer = cx.new(|_| MultiBuffer::new(Capability::ReadWrite));
-        let (recalculate_diffs_tx, mut recalculate_diffs_rx) = mpsc::unbounded();
-        let mut this = Self {
-            editor: cx.new(|cx| {
-                let mut editor = Editor::for_multibuffer(multibuffer.clone(), project, window, cx);
-                editor.set_expand_all_diff_hunks(cx);
-                editor.set_completion_provider(None);
-                editor.clear_code_action_providers();
-                editor.set_semantics_provider(
-                    editor
-                        .semantics_provider()
-                        .map(|provider| Rc::new(BranchBufferSemanticsProvider(provider)) as _),
-                );
-                editor
-            }),
-            multibuffer,
-            title: title.into(),
-            buffer_entries: Vec::new(),
-            recalculate_diffs_tx,
-            _recalculate_diffs_task: cx.spawn_in(window, async move |this, cx| {
-                let mut buffers_to_diff = HashSet::default();
-                while let Some(mut recalculate_diff) = recalculate_diffs_rx.next().await {
-                    buffers_to_diff.insert(recalculate_diff.buffer);
-
-                    while recalculate_diff.debounce {
-                        cx.background_executor()
-                            .timer(Duration::from_millis(50))
-                            .await;
-                        let mut had_further_changes = false;
-                        while let Ok(next_recalculate_diff) = recalculate_diffs_rx.try_next() {
-                            let next_recalculate_diff = next_recalculate_diff?;
-                            recalculate_diff.debounce &= next_recalculate_diff.debounce;
-                            buffers_to_diff.insert(next_recalculate_diff.buffer);
-                            had_further_changes = true;
-                        }
-                        if !had_further_changes {
-                            break;
-                        }
-                    }
-
-                    let recalculate_diff_futures = this
-                        .update(cx, |this, cx| {
-                            buffers_to_diff
-                                .drain()
-                                .filter_map(|buffer| {
-                                    let buffer = buffer.read(cx);
-                                    let base_buffer = buffer.base_buffer()?;
-                                    let buffer = buffer.text_snapshot();
-                                    let diff =
-                                        this.multibuffer.read(cx).diff_for(buffer.remote_id())?;
-                                    Some(diff.update(cx, |diff, cx| {
-                                        diff.set_base_text_buffer(base_buffer.clone(), buffer, cx)
-                                    }))
-                                })
-                                .collect::<Vec<_>>()
-                        })
-                        .ok()?;
-
-                    join_all(recalculate_diff_futures).await;
-                }
-                None
-            }),
-        };
-        this.reset_locations(locations, window, cx);
-        this
-    }
-
-    pub fn branch_buffer_for_base(&self, base_buffer: &Entity<Buffer>) -> Option<Entity<Buffer>> {
-        self.buffer_entries.iter().find_map(|entry| {
-            if &entry.base == base_buffer {
-                Some(entry.branch.clone())
-            } else {
-                None
-            }
-        })
-    }
-
-    pub fn set_title(&mut self, title: SharedString, cx: &mut Context<Self>) {
-        self.title = title;
-        cx.notify();
-    }
-
-    pub fn reset_locations<T: Clone + ToOffset>(
-        &mut self,
-        locations: Vec<ProposedChangeLocation<T>>,
-        window: &mut Window,
-        cx: &mut Context<Self>,
-    ) {
-        // Undo all branch changes
-        for entry in &self.buffer_entries {
-            let base_version = entry.base.read(cx).version();
-            entry.branch.update(cx, |buffer, cx| {
-                let undo_counts = buffer
-                    .operations()
-                    .iter()
-                    .filter_map(|(timestamp, _)| {
-                        if !base_version.observed(*timestamp) {
-                            Some((*timestamp, u32::MAX))
-                        } else {
-                            None
-                        }
-                    })
-                    .collect();
-                buffer.undo_operations(undo_counts, cx);
-            });
-        }
-
-        self.multibuffer.update(cx, |multibuffer, cx| {
-            multibuffer.clear(cx);
-        });
-
-        let mut buffer_entries = Vec::new();
-        let mut new_diffs = Vec::new();
-        for location in locations {
-            let branch_buffer;
-            if let Some(ix) = self
-                .buffer_entries
-                .iter()
-                .position(|entry| entry.base == location.buffer)
-            {
-                let entry = self.buffer_entries.remove(ix);
-                branch_buffer = entry.branch.clone();
-                buffer_entries.push(entry);
-            } else {
-                branch_buffer = location.buffer.update(cx, |buffer, cx| buffer.branch(cx));
-                new_diffs.push(cx.new(|cx| {
-                    let mut diff = BufferDiff::new(&branch_buffer.read(cx).snapshot(), cx);
-                    let _ = diff.set_base_text_buffer(
-                        location.buffer.clone(),
-                        branch_buffer.read(cx).text_snapshot(),
-                        cx,
-                    );
-                    diff
-                }));
-                buffer_entries.push(BufferEntry {
-                    branch: branch_buffer.clone(),
-                    base: location.buffer.clone(),
-                    _subscription: cx.subscribe(&branch_buffer, Self::on_buffer_event),
-                });
-            }
-
-            self.multibuffer.update(cx, |multibuffer, cx| {
-                multibuffer.push_excerpts(
-                    branch_buffer,
-                    location
-                        .ranges
-                        .into_iter()
-                        .map(|range| ExcerptRange::new(range)),
-                    cx,
-                );
-            });
-        }
-
-        self.buffer_entries = buffer_entries;
-        self.editor.update(cx, |editor, cx| {
-            editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
-                selections.refresh()
-            });
-            editor.buffer.update(cx, |buffer, cx| {
-                for diff in new_diffs {
-                    buffer.add_diff(diff, cx)
-                }
-            })
-        });
-    }
-
-    pub fn recalculate_all_buffer_diffs(&self) {
-        for (ix, entry) in self.buffer_entries.iter().enumerate().rev() {
-            self.recalculate_diffs_tx
-                .unbounded_send(RecalculateDiff {
-                    buffer: entry.branch.clone(),
-                    debounce: ix > 0,
-                })
-                .ok();
-        }
-    }
-
-    fn on_buffer_event(
-        &mut self,
-        buffer: Entity<Buffer>,
-        event: &BufferEvent,
-        _cx: &mut Context<Self>,
-    ) {
-        if let BufferEvent::Operation { .. } = event {
-            self.recalculate_diffs_tx
-                .unbounded_send(RecalculateDiff {
-                    buffer,
-                    debounce: true,
-                })
-                .ok();
-        }
-    }
-}
-
-impl Render for ProposedChangesEditor {
-    fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
-        div()
-            .size_full()
-            .key_context("ProposedChangesEditor")
-            .child(self.editor.clone())
-    }
-}
-
-impl Focusable for ProposedChangesEditor {
-    fn focus_handle(&self, cx: &App) -> gpui::FocusHandle {
-        self.editor.focus_handle(cx)
-    }
-}
-
-impl EventEmitter<EditorEvent> for ProposedChangesEditor {}
-
-impl Item for ProposedChangesEditor {
-    type Event = EditorEvent;
-
-    fn tab_icon(&self, _window: &Window, _cx: &App) -> Option<Icon> {
-        Some(Icon::new(IconName::Diff))
-    }
-
-    fn tab_content_text(&self, _detail: usize, _cx: &App) -> SharedString {
-        self.title.clone()
-    }
-
-    fn as_searchable(&self, _: &Entity<Self>) -> Option<Box<dyn SearchableItemHandle>> {
-        Some(Box::new(self.editor.clone()))
-    }
-
-    fn act_as_type<'a>(
-        &'a self,
-        type_id: TypeId,
-        self_handle: &'a Entity<Self>,
-        _: &'a App,
-    ) -> Option<gpui::AnyView> {
-        if type_id == TypeId::of::<Self>() {
-            Some(self_handle.to_any())
-        } else if type_id == TypeId::of::<Editor>() {
-            Some(self.editor.to_any())
-        } else {
-            None
-        }
-    }
-
-    fn added_to_workspace(
-        &mut self,
-        workspace: &mut Workspace,
-        window: &mut Window,
-        cx: &mut Context<Self>,
-    ) {
-        self.editor.update(cx, |editor, cx| {
-            Item::added_to_workspace(editor, workspace, window, cx)
-        });
-    }
-
-    fn deactivated(&mut self, window: &mut Window, cx: &mut Context<Self>) {
-        self.editor
-            .update(cx, |editor, cx| editor.deactivated(window, cx));
-    }
-
-    fn navigate(
-        &mut self,
-        data: Box<dyn std::any::Any>,
-        window: &mut Window,
-        cx: &mut Context<Self>,
-    ) -> bool {
-        self.editor
-            .update(cx, |editor, cx| Item::navigate(editor, data, window, cx))
-    }
-
-    fn set_nav_history(
-        &mut self,
-        nav_history: workspace::ItemNavHistory,
-        window: &mut Window,
-        cx: &mut Context<Self>,
-    ) {
-        self.editor.update(cx, |editor, cx| {
-            Item::set_nav_history(editor, nav_history, window, cx)
-        });
-    }
-
-    fn can_save(&self, cx: &App) -> bool {
-        self.editor.read(cx).can_save(cx)
-    }
-
-    fn save(
-        &mut self,
-        options: SaveOptions,
-        project: Entity<Project>,
-        window: &mut Window,
-        cx: &mut Context<Self>,
-    ) -> Task<anyhow::Result<()>> {
-        self.editor.update(cx, |editor, cx| {
-            Item::save(editor, options, project, window, cx)
-        })
-    }
-}
-
-impl ProposedChangesEditorToolbar {
-    pub fn new() -> Self {
-        Self {
-            current_editor: None,
-        }
-    }
-
-    fn get_toolbar_item_location(&self) -> ToolbarItemLocation {
-        if self.current_editor.is_some() {
-            ToolbarItemLocation::PrimaryRight
-        } else {
-            ToolbarItemLocation::Hidden
-        }
-    }
-}
-
-impl Render for ProposedChangesEditorToolbar {
-    fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
-        let button_like = ButtonLike::new("apply-changes").child(Label::new("Apply All"));
-
-        match &self.current_editor {
-            Some(editor) => {
-                let focus_handle = editor.focus_handle(cx);
-                let keybinding =
-                    KeyBinding::for_action_in(&ApplyAllDiffHunks, &focus_handle, window, cx)
-                        .map(|binding| binding.into_any_element());
-
-                button_like.children(keybinding).on_click({
-                    move |_event, window, cx| {
-                        focus_handle.dispatch_action(&ApplyAllDiffHunks, window, cx)
-                    }
-                })
-            }
-            None => button_like.disabled(true),
-        }
-    }
-}
-
-impl EventEmitter<ToolbarItemEvent> for ProposedChangesEditorToolbar {}
-
-impl ToolbarItemView for ProposedChangesEditorToolbar {
-    fn set_active_pane_item(
-        &mut self,
-        active_pane_item: Option<&dyn workspace::ItemHandle>,
-        _window: &mut Window,
-        _cx: &mut Context<Self>,
-    ) -> workspace::ToolbarItemLocation {
-        self.current_editor =
-            active_pane_item.and_then(|item| item.downcast::<ProposedChangesEditor>());
-        self.get_toolbar_item_location()
-    }
-}
-
-impl BranchBufferSemanticsProvider {
-    fn to_base(
-        &self,
-        buffer: &Entity<Buffer>,
-        positions: &[text::Anchor],
-        cx: &App,
-    ) -> Option<Entity<Buffer>> {
-        let base_buffer = buffer.read(cx).base_buffer()?;
-        let version = base_buffer.read(cx).version();
-        if positions
-            .iter()
-            .any(|position| !version.observed(position.timestamp))
-        {
-            return None;
-        }
-        Some(base_buffer)
-    }
-}
-
-impl SemanticsProvider for BranchBufferSemanticsProvider {
-    fn hover(
-        &self,
-        buffer: &Entity<Buffer>,
-        position: text::Anchor,
-        cx: &mut App,
-    ) -> Option<Task<Option<Vec<project::Hover>>>> {
-        let buffer = self.to_base(buffer, &[position], cx)?;
-        self.0.hover(&buffer, position, cx)
-    }
-
-    fn inlay_hints(
-        &self,
-        buffer: Entity<Buffer>,
-        range: Range<text::Anchor>,
-        cx: &mut App,
-    ) -> Option<Task<anyhow::Result<Vec<project::InlayHint>>>> {
-        let buffer = self.to_base(&buffer, &[range.start, range.end], cx)?;
-        self.0.inlay_hints(buffer, range, cx)
-    }
-
-    fn inline_values(
-        &self,
-        _: Entity<Buffer>,
-        _: Range<text::Anchor>,
-        _: &mut App,
-    ) -> Option<Task<anyhow::Result<Vec<project::InlayHint>>>> {
-        None
-    }
-
-    fn resolve_inlay_hint(
-        &self,
-        hint: project::InlayHint,
-        buffer: Entity<Buffer>,
-        server_id: lsp::LanguageServerId,
-        cx: &mut App,
-    ) -> Option<Task<anyhow::Result<project::InlayHint>>> {
-        let buffer = self.to_base(&buffer, &[], cx)?;
-        self.0.resolve_inlay_hint(hint, buffer, server_id, cx)
-    }
-
-    fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool {
-        if let Some(buffer) = self.to_base(buffer, &[], cx) {
-            self.0.supports_inlay_hints(&buffer, cx)
-        } else {
-            false
-        }
-    }
-
-    fn document_highlights(
-        &self,
-        buffer: &Entity<Buffer>,
-        position: text::Anchor,
-        cx: &mut App,
-    ) -> Option<Task<anyhow::Result<Vec<project::DocumentHighlight>>>> {
-        let buffer = self.to_base(buffer, &[position], cx)?;
-        self.0.document_highlights(&buffer, position, cx)
-    }
-
-    fn definitions(
-        &self,
-        buffer: &Entity<Buffer>,
-        position: text::Anchor,
-        kind: crate::GotoDefinitionKind,
-        cx: &mut App,
-    ) -> Option<Task<anyhow::Result<Option<Vec<project::LocationLink>>>>> {
-        let buffer = self.to_base(buffer, &[position], cx)?;
-        self.0.definitions(&buffer, position, kind, cx)
-    }
-
-    fn range_for_rename(
-        &self,
-        _: &Entity<Buffer>,
-        _: text::Anchor,
-        _: &mut App,
-    ) -> Option<Task<anyhow::Result<Option<Range<text::Anchor>>>>> {
-        None
-    }
-
-    fn perform_rename(
-        &self,
-        _: &Entity<Buffer>,
-        _: text::Anchor,
-        _: String,
-        _: &mut App,
-    ) -> Option<Task<anyhow::Result<project::ProjectTransaction>>> {
-        None
-    }
-}

crates/git/src/repository.rs 🔗

@@ -661,6 +661,7 @@ impl GitRepository for RealGitRepository {
                 .context("starting git show process")?;
 
             let diff_stdout = String::from_utf8_lossy(&diff_output.stdout);
+            dbg!(&diff_stdout);
             let changes = parse_git_diff_name_status(&diff_stdout);
 
             let mut cat_file_process = util::command::new_std_command("git")
@@ -683,7 +684,14 @@ impl GitRepository for RealGitRepository {
                     StatusCode::Modified => {
                         writeln!(&mut stdin, "{commit}:{}", path.display())?;
                     }
-                    StatusCode::Added => {}
+                    StatusCode::Added => {
+                        files.push(CommitFile {
+                            path: path.into(),
+                            old_text: None,
+                            new_text: None,
+                        });
+                        continue;
+                    }
                     StatusCode::Deleted => {
                         writeln!(&mut stdin, "{commit}:{}", path.display())?;
                     }
@@ -694,34 +702,21 @@ impl GitRepository for RealGitRepository {
                 info_line.clear();
                 stdout.read_line(&mut info_line)?;
 
+                dbg!(&info_line);
+
                 let len = info_line.trim_end().parse().with_context(|| {
                     format!("invalid object size output from cat-file {info_line}")
                 })?;
                 let mut text = vec![0; len];
                 stdout.read_exact(&mut text)?;
                 stdout.read_exact(&mut newline)?;
-                let text = String::from_utf8_lossy(&text).to_string();
 
-                let old_text = match status_code {
-                    StatusCode::Modified => {
-                        info_line.clear();
-                        stdout.read_line(&mut info_line)?;
-                        let len = info_line.trim_end().parse().with_context(|| {
-                            format!("invalid object size output from cat-file {}", info_line)
-                        })?;
-                        let mut parent_text = vec![0; len];
-                        stdout.read_exact(&mut parent_text)?;
-                        stdout.read_exact(&mut newline)?;
-                        Some(String::from_utf8_lossy(&parent_text).to_string())
-                    }
-                    StatusCode::Added => None,
-                    StatusCode::Deleted => Some(text),
-                    _ => continue,
-                };
+                let text = String::from_utf8_lossy(&text).to_string();
+                dbg!(&text);
 
                 files.push(CommitFile {
                     path: path.into(),
-                    old_text,
+                    old_text: Some(text),
                     new_text: None,
                 })
             }

crates/git_ui/src/project_diff.rs 🔗

@@ -5,7 +5,7 @@ use crate::{
     remote_button::{render_publish_button, render_push_button},
 };
 use anyhow::Result;
-use buffer_diff::{BufferDiff, DiffHunkSecondaryStatus};
+use buffer_diff::{BufferDiff, BufferDiffSnapshot, DiffHunkSecondaryStatus};
 use collections::HashSet;
 use editor::{
     Editor, EditorEvent, SelectionEffects,
@@ -17,7 +17,7 @@ use futures::StreamExt;
 use git::{
     Commit, StageAll, StageAndNext, ToggleStaged, UnstageAll, UnstageAndNext,
     repository::{Branch, Upstream, UpstreamTracking, UpstreamTrackingStatus},
-    status::FileStatus,
+    status::{FileStatus, TrackedStatus},
 };
 use gpui::{
     Action, AnyElement, AnyView, App, AppContext as _, AsyncWindowContext, Entity, EventEmitter,
@@ -210,6 +210,7 @@ impl ProjectDiff {
                     });
                 }
                 DiffBaseKind::MergeBaseOfDefaultBranch => {
+                    diff_display_editor.start_temporary_diff_override();
                     diff_display_editor.set_render_diff_hunk_controls(
                         Arc::new(|_, _, _, _, _, _, _, _| gpui::Empty.into_any_element()),
                         cx,
@@ -267,7 +268,7 @@ impl ProjectDiff {
         let (mut send, recv) = postage::watch::channel::<()>();
         let worker = window.spawn(cx, {
             let this = cx.weak_entity();
-            async |cx| Self::handle_status_updates(this, diff_base_kind, recv, cx).await
+            async move |cx| Self::handle_status_updates(this, diff_base_kind, recv, cx).await
         });
         // Kick off a refresh immediately
         *send.borrow_mut() = ();
@@ -576,30 +577,113 @@ impl ProjectDiff {
         &mut self,
         window: &mut Window,
         cx: &mut Context<Self>,
-    ) -> Task<()> {
+    ) -> Task<Result<()>> {
+        let project = self.project.clone();
+        let language_registry = project.read(cx).languages().clone();
         let Some(repo) = self.git_store.read(cx).active_repository() else {
             self.multibuffer.update(cx, |multibuffer, cx| {
                 multibuffer.clear(cx);
             });
-            return;
+            return Task::ready(Ok(()));
         };
-        let default_branch = repo.read(cx).default_branch();
-        let task = cx.spawn(async move |this, cx| {
-            let Some(default_branch) = default_branch.await else {
-                return;
+        let default_branch = repo.update(cx, |repo, _| repo.default_branch());
+        cx.spawn_in(window, async move |this, cx| {
+            let Some(default_branch) = default_branch.await?? else {
+                return Ok(());
+            };
+            let Some(merge_base) = repo
+                .update(cx, |repo, _| {
+                    repo.merge_base("HEAD".to_string(), default_branch.into())
+                })?
+                .await?
+            else {
+                return Ok(());
             };
-            let merge_base = repo
-                .update(cx, |repo, cx| {
-                    repo.merge_base("HEAD".to_string(), default_branch)
-                })
-                .await?;
             let diff = repo
-                .update(cx, |repo, cx| repo.diff_to_commit(merge_base))
-                .await?;
-        });
+                .update(cx, |repo, _| repo.diff_to_commit(merge_base))?
+                .await??;
+
+            for file in diff.files {
+                let Some(path) = repo.update(cx, |repo, cx| {
+                    repo.repo_path_to_project_path(&file.path, cx)
+                })?
+                else {
+                    continue;
+                };
+                let open_buffer = project
+                    .update(cx, |project, cx| project.open_buffer(path.clone(), cx))?
+                    .await;
+
+                let mut status = FileStatus::Tracked(TrackedStatus {
+                    index_status: git::status::StatusCode::Unmodified,
+                    worktree_status: git::status::StatusCode::Modified,
+                });
+                let buffer = match open_buffer {
+                    Ok(buffer) => buffer,
+                    Err(err) => {
+                        let exists = project.read_with(cx, |project, cx| {
+                            project.entry_for_path(&path, cx).is_some()
+                        })?;
+                        if exists {
+                            return Err(err);
+                        }
+                        status = FileStatus::Tracked(TrackedStatus {
+                            index_status: git::status::StatusCode::Unmodified,
+                            worktree_status: git::status::StatusCode::Deleted,
+                        });
+                        cx.new(|cx| Buffer::local("", cx))?
+                    }
+                };
+
+                let buffer_snapshot = buffer.read_with(cx, |buffer, _| buffer.snapshot())?;
+                let namespace = if file.old_text.is_none() {
+                    NEW_NAMESPACE
+                } else {
+                    TRACKED_NAMESPACE
+                };
+
+                let buffer_diff = cx.new(|cx| BufferDiff::new(&buffer_snapshot, cx))?;
+                buffer_diff
+                    .update(cx, |buffer_diff, cx| {
+                        buffer_diff.set_base_text(
+                            file.old_text.map(Arc::new),
+                            buffer_snapshot.language().cloned(),
+                            Some(language_registry.clone()),
+                            buffer_snapshot.text,
+                            cx,
+                        )
+                    })?
+                    .await?;
+
+                this.read_with(cx, |this, cx| {
+                    BufferDiffSnapshot::new_with_base_buffer(
+                        buffer.clone(),
+                        base_text,
+                        this.base_text().clone(),
+                        cx,
+                    )
+                })?
+                .await;
 
-        cx.foreground_executor()
-            .spawn(async move |this, cx| task.await.log_err())
+                this.update_in(cx, |this, window, cx| {
+                    this.multibuffer.update(cx, |multibuffer, cx| {
+                        multibuffer.add_diff(buffer_diff.clone(), cx);
+                    });
+                    this.register_buffer(
+                        DiffBuffer {
+                            path_key: PathKey::namespaced(namespace, file.path.0),
+                            buffer,
+                            diff: buffer_diff,
+                            file_status: status,
+                        },
+                        window,
+                        cx,
+                    );
+                })?;
+            }
+
+            Ok(())
+        })
     }
 
     pub async fn handle_status_updates(
@@ -627,7 +711,8 @@ impl ProjectDiff {
                     this.update_in(cx, |this, window, cx| {
                         this.refresh_merge_base_of_default_branch(window, cx)
                     })?
-                    .await;
+                    .await
+                    .log_err();
                 }
             };
 

crates/project/src/buffer_store.rs 🔗

@@ -1249,15 +1249,6 @@ impl BufferStore {
                         .log_err();
                 }
 
-                // TODO(max): do something
-                // client
-                //     .send(proto::UpdateStagedText {
-                //         project_id,
-                //         buffer_id: buffer_id.into(),
-                //         diff_base: buffer.diff_base().map(ToString::to_string),
-                //     })
-                //     .log_err();
-
                 client
                     .send(proto::BufferReloaded {
                         project_id,

crates/project/src/git_store.rs 🔗

@@ -91,6 +91,7 @@ struct SharedDiffs {
 struct BufferGitState {
     unstaged_diff: Option<WeakEntity<BufferDiff>>,
     uncommitted_diff: Option<WeakEntity<BufferDiff>>,
+    branch_diff: Option<WeakEntity<BufferDiff>>,
     conflict_set: Option<WeakEntity<ConflictSet>>,
     recalculate_diff_task: Option<Task<Result<()>>>,
     reparse_conflict_markers_task: Option<Task<Result<()>>>,
@@ -592,6 +593,12 @@ impl GitStore {
         cx.background_spawn(async move { task.await.map_err(|e| anyhow!("{e}")) })
     }
 
+    pub fn open_diff_from_default_branch(
+        &mut self,
+        cx: &mut Context<Self>,
+    ) -> Task<Result<Vec<(Entity<Buffer>, Entity<BufferDiff>)>>> {
+    }
+
     pub fn open_uncommitted_diff(
         &mut self,
         buffer: Entity<Buffer>,
@@ -670,11 +677,10 @@ impl GitStore {
             let text_snapshot = buffer.text_snapshot();
             this.loading_diffs.remove(&(buffer_id, kind));
 
-            let git_store = cx.weak_entity();
             let diff_state = this
                 .diffs
                 .entry(buffer_id)
-                .or_insert_with(|| cx.new(|_| BufferGitState::new(git_store)));
+                .or_insert_with(|| cx.new(|_| BufferGitState::new()));
 
             let diff = cx.new(|cx| BufferDiff::new(&text_snapshot, cx));
 
@@ -755,11 +761,10 @@ impl GitStore {
         let is_unmerged = self
             .repository_and_path_for_buffer_id(buffer_id, cx)
             .is_some_and(|(repo, path)| repo.read(cx).snapshot.has_conflict(&path));
-        let git_store = cx.weak_entity();
         let buffer_git_state = self
             .diffs
             .entry(buffer_id)
-            .or_insert_with(|| cx.new(|_| BufferGitState::new(git_store)));
+            .or_insert_with(|| cx.new(|_| BufferGitState::new()));
         let conflict_set = cx.new(|cx| ConflictSet::new(buffer_id, is_unmerged, cx));
 
         self._subscriptions
@@ -2347,10 +2352,11 @@ impl GitStore {
 }
 
 impl BufferGitState {
-    fn new(_git_store: WeakEntity<GitStore>) -> Self {
+    fn new() -> Self {
         Self {
             unstaged_diff: Default::default(),
             uncommitted_diff: Default::default(),
+            branch_diff: Default::default(),
             recalculate_diff_task: Default::default(),
             language: Default::default(),
             language_registry: Default::default(),

crates/zed/src/zed.rs 🔗

@@ -17,7 +17,6 @@ use breadcrumbs::Breadcrumbs;
 use client::zed_urls;
 use collections::VecDeque;
 use debugger_ui::debugger_panel::DebugPanel;
-use editor::ProposedChangesEditorToolbar;
 use editor::{Editor, MultiBuffer};
 use feature_flags::{FeatureFlagAppExt, PanicFeatureFlag};
 use futures::future::Either;
@@ -980,8 +979,6 @@ fn initialize_pane(
                 )
             });
             toolbar.add_item(buffer_search_bar.clone(), window, cx);
-            let proposed_change_bar = cx.new(|_| ProposedChangesEditorToolbar::new());
-            toolbar.add_item(proposed_change_bar, window, cx);
             let quick_action_bar =
                 cx.new(|cx| QuickActionBar::new(buffer_search_bar, workspace, cx));
             toolbar.add_item(quick_action_bar, window, cx);