Fix staging and unstaging of added and deleted files (#25631)

* When staging in a buffer whose file has been deleted, do not save the
file
* Fix logic for writing to index when file is deleted

Release Notes:

- N/A
This commit is contained in:
Max Brunsfeld 2025-02-25 23:25:31 -08:00 committed by GitHub
parent 33754f8eac
commit ebccef1aa4
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 88 additions and 48 deletions

View file

@ -184,17 +184,31 @@ impl BufferDiffSnapshot {
cx: &mut App, cx: &mut App,
) -> Option<Rope> { ) -> Option<Rope> {
let secondary_diff = self.secondary_diff()?; let secondary_diff = self.secondary_diff()?;
let index_base = if let Some(index_base) = secondary_diff.base_text() { let head_text = self.base_text().map(|text| text.as_rope().clone());
index_base.text.as_rope().clone() let index_text = secondary_diff
} else if stage { .base_text()
Rope::from("") .map(|text| text.as_rope().clone());
} else { let (index_text, head_text) = match (index_text, head_text) {
return None; (Some(index_text), Some(head_text)) => (index_text, head_text),
// file is deleted in both index and head
(None, None) => return None,
// file is deleted in index
(None, Some(head_text)) => {
return if stage {
Some(buffer.as_rope().clone())
} else {
Some(head_text)
}
}
// file exists in the index, but is deleted in head
(Some(_), None) => {
return if stage {
Some(buffer.as_rope().clone())
} else {
None
}
}
}; };
let head_base = self.base_text().map_or_else(
|| Rope::from(""),
|snapshot| snapshot.text.as_rope().clone(),
);
let mut secondary_cursor = secondary_diff.inner.hunks.cursor::<DiffHunkSummary>(buffer); let mut secondary_cursor = secondary_diff.inner.hunks.cursor::<DiffHunkSummary>(buffer);
secondary_cursor.next(buffer); secondary_cursor.next(buffer);
@ -251,7 +265,7 @@ impl BufferDiffSnapshot {
.collect::<String>() .collect::<String>()
} else { } else {
log::debug!("unstaging"); log::debug!("unstaging");
head_base head_text
.chunks_in_range(diff_base_byte_range.clone()) .chunks_in_range(diff_base_byte_range.clone())
.collect::<String>() .collect::<String>()
}; };
@ -259,7 +273,7 @@ impl BufferDiffSnapshot {
} }
let buffer = cx.new(|cx| { let buffer = cx.new(|cx| {
language::Buffer::local_normalized(index_base, text::LineEnding::default(), cx) language::Buffer::local_normalized(index_text, text::LineEnding::default(), cx)
}); });
let new_text = buffer.update(cx, |buffer, cx| { let new_text = buffer.update(cx, |buffer, cx| {
buffer.edit(edits, None, cx); buffer.edit(edits, None, cx);

View file

@ -102,9 +102,9 @@ use language::{
self, all_language_settings, language_settings, InlayHintSettings, RewrapBehavior, self, all_language_settings, language_settings, InlayHintSettings, RewrapBehavior,
}, },
point_from_lsp, text_diff_with_options, AutoindentMode, BracketMatch, BracketPair, Buffer, point_from_lsp, text_diff_with_options, AutoindentMode, BracketMatch, BracketPair, Buffer,
Capability, CharKind, CodeLabel, CursorShape, Diagnostic, DiffOptions, DiskState, Capability, CharKind, CodeLabel, CursorShape, Diagnostic, DiffOptions, EditPredictionsMode,
EditPredictionsMode, EditPreview, HighlightedText, IndentKind, IndentSize, Language, EditPreview, HighlightedText, IndentKind, IndentSize, Language, OffsetRangeExt, Point,
OffsetRangeExt, Point, Selection, SelectionGoal, TextObject, TransactionId, TreeSitterOptions, Selection, SelectionGoal, TextObject, TransactionId, TreeSitterOptions,
}; };
use language::{point_to_lsp, BufferRow, CharClassifier, Runnable, RunnableRange}; use language::{point_to_lsp, BufferRow, CharClassifier, Runnable, RunnableRange};
use linked_editing_ranges::refresh_linked_ranges; use linked_editing_ranges::refresh_linked_ranges;
@ -13483,13 +13483,16 @@ impl Editor {
buffer_id: BufferId, buffer_id: BufferId,
hunks: impl Iterator<Item = MultiBufferDiffHunk>, hunks: impl Iterator<Item = MultiBufferDiffHunk>,
snapshot: &MultiBufferSnapshot, snapshot: &MultiBufferSnapshot,
cx: &mut Context<Self>, cx: &mut App,
) { ) {
let Some(buffer) = project.read(cx).buffer_for_id(buffer_id, cx) else { let Some(buffer) = project.read(cx).buffer_for_id(buffer_id, cx) else {
log::debug!("no buffer for id"); log::debug!("no buffer for id");
return; return;
}; };
let buffer_snapshot = buffer.read(cx).snapshot(); let buffer_snapshot = buffer.read(cx).snapshot();
let file_exists = buffer_snapshot
.file()
.is_some_and(|file| file.disk_state().exists());
let Some((repo, path)) = project let Some((repo, path)) = project
.read(cx) .read(cx)
.repository_and_path_for_buffer_id(buffer_id, cx) .repository_and_path_for_buffer_id(buffer_id, cx)
@ -13502,40 +13505,33 @@ impl Editor {
return; return;
}; };
let Some(new_index_text) = diff.new_secondary_text_for_stage_or_unstage( let new_index_text = if !stage && diff.is_single_insertion || stage && !file_exists {
stage,
hunks.filter_map(|hunk| {
if stage && hunk.secondary_status == DiffHunkSecondaryStatus::None {
return None;
} else if !stage
&& hunk.secondary_status == DiffHunkSecondaryStatus::HasSecondaryHunk
{
return None;
}
Some((hunk.buffer_range.clone(), hunk.diff_base_byte_range.clone()))
}),
&buffer_snapshot,
cx,
) else {
log::debug!("missing secondary diff or index text");
return;
};
let new_index_text = if new_index_text.is_empty()
&& !stage
&& (diff.is_single_insertion
|| buffer_snapshot
.file()
.map_or(false, |file| file.disk_state() == DiskState::New))
{
log::debug!("removing from index"); log::debug!("removing from index");
None None
} else { } else {
Some(new_index_text) diff.new_secondary_text_for_stage_or_unstage(
stage,
hunks.filter_map(|hunk| {
if stage && hunk.secondary_status == DiffHunkSecondaryStatus::None {
return None;
} else if !stage
&& hunk.secondary_status == DiffHunkSecondaryStatus::HasSecondaryHunk
{
return None;
}
Some((hunk.buffer_range.clone(), hunk.diff_base_byte_range.clone()))
}),
&buffer_snapshot,
cx,
)
}; };
let buffer_store = project.read(cx).buffer_store().clone();
buffer_store if file_exists {
.update(cx, |buffer_store, cx| buffer_store.save_buffer(buffer, cx)) let buffer_store = project.read(cx).buffer_store().clone();
.detach_and_log_err(cx); buffer_store
.update(cx, |buffer_store, cx| buffer_store.save_buffer(buffer, cx))
.detach_and_log_err(cx);
}
cx.background_spawn( cx.background_spawn(
repo.read(cx) repo.read(cx)

View file

@ -16510,6 +16510,7 @@ fn assert_hunk_revert(
) { ) {
cx.set_state(not_reverted_text_with_selections); cx.set_state(not_reverted_text_with_selections);
cx.set_head_text(base_text); cx.set_head_text(base_text);
cx.clear_index_text();
cx.executor().run_until_parked(); cx.executor().run_until_parked();
let actual_hunk_statuses_before = cx.update_editor(|editor, window, cx| { let actual_hunk_statuses_before = cx.update_editor(|editor, window, cx| {

View file

@ -298,6 +298,15 @@ impl EditorTestContext {
self.cx.run_until_parked(); self.cx.run_until_parked();
} }
pub fn clear_index_text(&mut self) {
self.cx.run_until_parked();
let fs = self.update_editor(|editor, _, cx| {
editor.project.as_ref().unwrap().read(cx).fs().as_fake()
});
fs.set_index_for_repo(&Self::root_path().join(".git"), &[]);
self.cx.run_until_parked();
}
pub fn set_index_text(&mut self, diff_base: &str) { pub fn set_index_text(&mut self, diff_base: &str) {
self.cx.run_until_parked(); self.cx.run_until_parked();
let fs = self.update_editor(|editor, _, cx| { let fs = self.update_editor(|editor, _, cx| {

View file

@ -368,6 +368,14 @@ impl DiskState {
DiskState::Deleted => None, DiskState::Deleted => None,
} }
} }
pub fn exists(&self) -> bool {
match self {
DiskState::New => false,
DiskState::Present { .. } => true,
DiskState::Deleted => false,
}
}
} }
/// The file associated with a buffer, in the case where the file is on the local disk. /// The file associated with a buffer, in the case where the file is on the local disk.

View file

@ -1071,7 +1071,13 @@ impl Repository {
}; };
let project_path = (self.worktree_id, path).into(); let project_path = (self.worktree_id, path).into();
if let Some(buffer) = buffer_store.get_by_path(&project_path, cx) { if let Some(buffer) = buffer_store.get_by_path(&project_path, cx) {
save_futures.push(buffer_store.save_buffer(buffer, cx)); if buffer
.read(cx)
.file()
.map_or(false, |file| file.disk_state().exists())
{
save_futures.push(buffer_store.save_buffer(buffer, cx));
}
} }
} }
}) })
@ -1110,7 +1116,13 @@ impl Repository {
}; };
let project_path = (self.worktree_id, path).into(); let project_path = (self.worktree_id, path).into();
if let Some(buffer) = buffer_store.get_by_path(&project_path, cx) { if let Some(buffer) = buffer_store.get_by_path(&project_path, cx) {
save_futures.push(buffer_store.save_buffer(buffer, cx)); if buffer
.read(cx)
.file()
.map_or(false, |file| file.disk_state().exists())
{
save_futures.push(buffer_store.save_buffer(buffer, cx));
}
} }
} }
}) })