Fix race conditions in updating buffer diffs on git changes (#26409)
Release Notes: - N/A --------- Co-authored-by: Cole Miller <m@cole-miller.net>
This commit is contained in:
parent
c747cccde3
commit
1a3597d726
2 changed files with 116 additions and 100 deletions
|
@ -837,8 +837,8 @@ impl BufferDiff {
|
|||
language: Option<Arc<Language>>,
|
||||
language_registry: Option<Arc<LanguageRegistry>>,
|
||||
cx: &mut AsyncApp,
|
||||
) -> anyhow::Result<Option<Range<Anchor>>> {
|
||||
let snapshot = if base_text_changed || language_changed {
|
||||
) -> anyhow::Result<BufferDiffSnapshot> {
|
||||
let inner = if base_text_changed || language_changed {
|
||||
cx.update(|cx| {
|
||||
Self::build(
|
||||
buffer.clone(),
|
||||
|
@ -860,18 +860,45 @@ impl BufferDiff {
|
|||
})?
|
||||
.await
|
||||
};
|
||||
|
||||
this.update(cx, |this, _| this.set_state(snapshot, &buffer))
|
||||
Ok(BufferDiffSnapshot {
|
||||
inner,
|
||||
secondary_diff: None,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn update_diff_from(
|
||||
pub fn set_snapshot(
|
||||
&mut self,
|
||||
buffer: &text::BufferSnapshot,
|
||||
other: &Entity<Self>,
|
||||
new_snapshot: BufferDiffSnapshot,
|
||||
language_changed: bool,
|
||||
secondary_changed_range: Option<Range<Anchor>>,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Option<Range<Anchor>> {
|
||||
let other = other.read(cx).inner.clone();
|
||||
self.set_state(other, buffer)
|
||||
let changed_range = self.set_state(new_snapshot.inner, buffer);
|
||||
if language_changed {
|
||||
cx.emit(BufferDiffEvent::LanguageChanged);
|
||||
}
|
||||
|
||||
let changed_range = match (secondary_changed_range, changed_range) {
|
||||
(None, None) => None,
|
||||
(Some(unstaged_range), None) => self.range_to_hunk_range(unstaged_range, &buffer, cx),
|
||||
(None, Some(uncommitted_range)) => Some(uncommitted_range),
|
||||
(Some(unstaged_range), Some(uncommitted_range)) => {
|
||||
let mut start = uncommitted_range.start;
|
||||
let mut end = uncommitted_range.end;
|
||||
if let Some(unstaged_range) = self.range_to_hunk_range(unstaged_range, &buffer, cx)
|
||||
{
|
||||
start = unstaged_range.start.min(&uncommitted_range.start, &buffer);
|
||||
end = unstaged_range.end.max(&uncommitted_range.end, &buffer);
|
||||
}
|
||||
Some(start..end)
|
||||
}
|
||||
};
|
||||
|
||||
cx.emit(BufferDiffEvent::DiffChanged {
|
||||
changed_range: changed_range.clone(),
|
||||
});
|
||||
changed_range
|
||||
}
|
||||
|
||||
fn set_state(
|
||||
|
|
|
@ -6,7 +6,7 @@ use crate::{
|
|||
};
|
||||
use ::git::{parse_git_remote_url, BuildPermalinkParams, GitHostingProviderRegistry};
|
||||
use anyhow::{anyhow, bail, Context as _, Result};
|
||||
use buffer_diff::{BufferDiff, BufferDiffEvent};
|
||||
use buffer_diff::BufferDiff;
|
||||
use client::Client;
|
||||
use collections::{hash_map, HashMap, HashSet};
|
||||
use fs::Fs;
|
||||
|
@ -217,39 +217,29 @@ impl BufferDiffState {
|
|||
_ => false,
|
||||
};
|
||||
self.recalculate_diff_task = Some(cx.spawn(|this, mut cx| async move {
|
||||
let mut unstaged_changed_range = None;
|
||||
let mut new_unstaged_diff = None;
|
||||
if let Some(unstaged_diff) = &unstaged_diff {
|
||||
unstaged_changed_range = BufferDiff::update_diff(
|
||||
unstaged_diff.clone(),
|
||||
buffer.clone(),
|
||||
index,
|
||||
index_changed,
|
||||
language_changed,
|
||||
language.clone(),
|
||||
language_registry.clone(),
|
||||
&mut cx,
|
||||
)
|
||||
.await?;
|
||||
|
||||
unstaged_diff.update(&mut cx, |_, cx| {
|
||||
if language_changed {
|
||||
cx.emit(BufferDiffEvent::LanguageChanged);
|
||||
}
|
||||
if let Some(changed_range) = unstaged_changed_range.clone() {
|
||||
cx.emit(BufferDiffEvent::DiffChanged {
|
||||
changed_range: Some(changed_range),
|
||||
})
|
||||
}
|
||||
})?;
|
||||
new_unstaged_diff = Some(
|
||||
BufferDiff::update_diff(
|
||||
unstaged_diff.clone(),
|
||||
buffer.clone(),
|
||||
index,
|
||||
index_changed,
|
||||
language_changed,
|
||||
language.clone(),
|
||||
language_registry.clone(),
|
||||
&mut cx,
|
||||
)
|
||||
.await?,
|
||||
);
|
||||
}
|
||||
|
||||
let mut new_uncommitted_diff = None;
|
||||
if let Some(uncommitted_diff) = &uncommitted_diff {
|
||||
let uncommitted_changed_range =
|
||||
if let (Some(unstaged_diff), true) = (&unstaged_diff, index_matches_head) {
|
||||
uncommitted_diff.update(&mut cx, |uncommitted_diff, cx| {
|
||||
uncommitted_diff.update_diff_from(&buffer, unstaged_diff, cx)
|
||||
})?
|
||||
} else {
|
||||
new_uncommitted_diff = if index_matches_head {
|
||||
new_unstaged_diff.clone()
|
||||
} else {
|
||||
Some(
|
||||
BufferDiff::update_diff(
|
||||
uncommitted_diff.clone(),
|
||||
buffer.clone(),
|
||||
|
@ -260,32 +250,32 @@ impl BufferDiffState {
|
|||
language_registry.clone(),
|
||||
&mut cx,
|
||||
)
|
||||
.await?
|
||||
};
|
||||
.await?,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
let unstaged_changed_range = if let Some((unstaged_diff, new_unstaged_diff)) =
|
||||
unstaged_diff.as_ref().zip(new_unstaged_diff.clone())
|
||||
{
|
||||
unstaged_diff.update(&mut cx, |diff, cx| {
|
||||
diff.set_snapshot(&buffer, new_unstaged_diff, language_changed, None, cx)
|
||||
})?
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
if let Some((uncommitted_diff, new_uncommitted_diff)) =
|
||||
uncommitted_diff.as_ref().zip(new_uncommitted_diff.clone())
|
||||
{
|
||||
uncommitted_diff.update(&mut cx, |uncommitted_diff, cx| {
|
||||
if language_changed {
|
||||
cx.emit(BufferDiffEvent::LanguageChanged);
|
||||
}
|
||||
let changed_range = match (unstaged_changed_range, uncommitted_changed_range) {
|
||||
(None, None) => None,
|
||||
(Some(unstaged_range), None) => {
|
||||
uncommitted_diff.range_to_hunk_range(unstaged_range, &buffer, cx)
|
||||
}
|
||||
(None, Some(uncommitted_range)) => Some(uncommitted_range),
|
||||
(Some(unstaged_range), Some(uncommitted_range)) => {
|
||||
let mut start = uncommitted_range.start;
|
||||
let mut end = uncommitted_range.end;
|
||||
if let Some(unstaged_range) =
|
||||
uncommitted_diff.range_to_hunk_range(unstaged_range, &buffer, cx)
|
||||
{
|
||||
start = unstaged_range.start.min(&uncommitted_range.start, &buffer);
|
||||
end = unstaged_range.end.max(&uncommitted_range.end, &buffer);
|
||||
}
|
||||
Some(start..end)
|
||||
}
|
||||
};
|
||||
cx.emit(BufferDiffEvent::DiffChanged { changed_range });
|
||||
uncommitted_diff.set_snapshot(
|
||||
&buffer,
|
||||
new_uncommitted_diff,
|
||||
language_changed,
|
||||
unstaged_changed_range,
|
||||
cx,
|
||||
);
|
||||
})?;
|
||||
}
|
||||
|
||||
|
@ -813,8 +803,7 @@ impl LocalBufferStore {
|
|||
let Some(buffer) = buffer.upgrade() else {
|
||||
continue;
|
||||
};
|
||||
let buffer = buffer.read(cx);
|
||||
let Some(file) = File::from_dyn(buffer.file()) else {
|
||||
let Some(file) = File::from_dyn(buffer.read(cx).file()) else {
|
||||
continue;
|
||||
};
|
||||
if file.worktree != worktree_handle {
|
||||
|
@ -825,7 +814,6 @@ impl LocalBufferStore {
|
|||
.iter()
|
||||
.any(|(work_dir, _)| file.path.starts_with(work_dir))
|
||||
{
|
||||
let snapshot = buffer.text_snapshot();
|
||||
let has_unstaged_diff = diff_state
|
||||
.unstaged_diff
|
||||
.as_ref()
|
||||
|
@ -835,7 +823,7 @@ impl LocalBufferStore {
|
|||
.as_ref()
|
||||
.is_some_and(|set| set.is_upgradable());
|
||||
diff_state_updates.push((
|
||||
snapshot.clone(),
|
||||
buffer,
|
||||
file.path.clone(),
|
||||
has_unstaged_diff.then(|| diff_state.index_text.clone()),
|
||||
has_uncommitted_diff.then(|| diff_state.head_text.clone()),
|
||||
|
@ -854,36 +842,33 @@ impl LocalBufferStore {
|
|||
.background_spawn(async move {
|
||||
diff_state_updates
|
||||
.into_iter()
|
||||
.filter_map(
|
||||
|(buffer_snapshot, path, current_index_text, current_head_text)| {
|
||||
let local_repo = snapshot.local_repo_for_path(&path)?;
|
||||
let relative_path = local_repo.relativize(&path).ok()?;
|
||||
let index_text = if current_index_text.is_some() {
|
||||
local_repo.repo().load_index_text(&relative_path)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let head_text = if current_head_text.is_some() {
|
||||
local_repo.repo().load_committed_text(&relative_path)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
.filter_map(|(buffer, path, current_index_text, current_head_text)| {
|
||||
let local_repo = snapshot.local_repo_for_path(&path)?;
|
||||
let relative_path = local_repo.relativize(&path).ok()?;
|
||||
let index_text = if current_index_text.is_some() {
|
||||
local_repo.repo().load_index_text(&relative_path)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let head_text = if current_head_text.is_some() {
|
||||
local_repo.repo().load_committed_text(&relative_path)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
// Avoid triggering a diff update if the base text has not changed.
|
||||
if let Some((current_index, current_head)) =
|
||||
current_index_text.as_ref().zip(current_head_text.as_ref())
|
||||
// Avoid triggering a diff update if the base text has not changed.
|
||||
if let Some((current_index, current_head)) =
|
||||
current_index_text.as_ref().zip(current_head_text.as_ref())
|
||||
{
|
||||
if current_index.as_deref() == index_text.as_ref()
|
||||
&& current_head.as_deref() == head_text.as_ref()
|
||||
{
|
||||
if current_index.as_deref() == index_text.as_ref()
|
||||
&& current_head.as_deref() == head_text.as_ref()
|
||||
{
|
||||
return None;
|
||||
}
|
||||
return None;
|
||||
}
|
||||
}
|
||||
|
||||
let diff_bases_change = match (
|
||||
current_index_text.is_some(),
|
||||
current_head_text.is_some(),
|
||||
) {
|
||||
let diff_bases_change =
|
||||
match (current_index_text.is_some(), current_head_text.is_some()) {
|
||||
(true, true) => Some(if index_text == head_text {
|
||||
DiffBasesChange::SetBoth(head_text)
|
||||
} else {
|
||||
|
@ -896,17 +881,17 @@ impl LocalBufferStore {
|
|||
(false, true) => Some(DiffBasesChange::SetHead(head_text)),
|
||||
(false, false) => None,
|
||||
};
|
||||
Some((buffer_snapshot, diff_bases_change))
|
||||
},
|
||||
)
|
||||
|
||||
Some((buffer, diff_bases_change))
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
})
|
||||
.await;
|
||||
|
||||
this.update(&mut cx, |this, cx| {
|
||||
for (buffer_snapshot, diff_bases_change) in diff_bases_changes_by_buffer {
|
||||
for (buffer, diff_bases_change) in diff_bases_changes_by_buffer {
|
||||
let Some(OpenBuffer::Complete { diff_state, .. }) =
|
||||
this.opened_buffers.get_mut(&buffer_snapshot.remote_id())
|
||||
this.opened_buffers.get_mut(&buffer.read(cx).remote_id())
|
||||
else {
|
||||
continue;
|
||||
};
|
||||
|
@ -917,8 +902,9 @@ impl LocalBufferStore {
|
|||
diff_state.update(cx, |diff_state, cx| {
|
||||
use proto::update_diff_bases::Mode;
|
||||
|
||||
let buffer = buffer.read(cx);
|
||||
if let Some((client, project_id)) = this.downstream_client.as_ref() {
|
||||
let buffer_id = buffer_snapshot.remote_id().to_proto();
|
||||
let buffer_id = buffer.remote_id().to_proto();
|
||||
let (staged_text, committed_text, mode) = match diff_bases_change
|
||||
.clone()
|
||||
{
|
||||
|
@ -942,8 +928,11 @@ impl LocalBufferStore {
|
|||
client.send(message).log_err();
|
||||
}
|
||||
|
||||
let _ =
|
||||
diff_state.diff_bases_changed(buffer_snapshot, diff_bases_change, cx);
|
||||
let _ = diff_state.diff_bases_changed(
|
||||
buffer.text_snapshot(),
|
||||
diff_bases_change,
|
||||
cx,
|
||||
);
|
||||
});
|
||||
}
|
||||
})
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue