mirror of
https://github.com/zed-industries/zed.git
synced 2026-06-01 03:14:56 +07:00
Fix the project diff sometimes missing updates (#40662)
This PR does two related things: - First, it gets rid of the undifferentiated `RepositoryEvent::Updated` in favor of three new events that have clearer definitions: `BranchChanged`, `StashEntriesChanged`, and `StatusesChanged`. An implication of this is that we no longer emit a `RepositoryEvent` unless some git state changed; previously we would emit `RepositoryUpdated` after doing a git status scan even if no statuses changed. - Second, it changes the subscription strategy of the project diff to make it update more robustly. Previously, the project diff only subscribed to the `GitStore`, so it relied on getting a `GitStoreEvent` when some buffer's diff hunks changed, even if the git status of the buffer's file didn't change (e.g. a second hunk in a file that was already modified). After this PR, it also subscribes to the individual `BufferDiff` entities for buffers that have a git status, so the `GitStore` is freed from that responsibility. This also fixes some real cases where the previous strategy was not effective in keeping the project diff up to date (captured in a test). Release Notes: - Fixed some cases where the project diff would fail to update in response to git events.
This commit is contained in:
parent
a66098b485
commit
8b6f3ec647
13 changed files with 214 additions and 159 deletions
|
|
@ -156,7 +156,7 @@ use project::{
|
|||
},
|
||||
session::{Session, SessionEvent},
|
||||
},
|
||||
git_store::{GitStoreEvent, RepositoryEvent},
|
||||
git_store::GitStoreEvent,
|
||||
lsp_store::{
|
||||
CacheInlayHints, CompletionDocumentation, FormatTrigger, LspFormatTarget,
|
||||
OpenLspBufferHandle,
|
||||
|
|
@ -1978,14 +1978,7 @@ impl Editor {
|
|||
let git_store = project.read(cx).git_store().clone();
|
||||
let project = project.clone();
|
||||
project_subscriptions.push(cx.subscribe(&git_store, move |this, _, event, cx| {
|
||||
if let GitStoreEvent::RepositoryUpdated(
|
||||
_,
|
||||
RepositoryEvent::Updated {
|
||||
new_instance: true, ..
|
||||
},
|
||||
_,
|
||||
) = event
|
||||
{
|
||||
if let GitStoreEvent::RepositoryAdded = event {
|
||||
this.load_diff_task = Some(
|
||||
update_uncommitted_diff_for_buffer(
|
||||
cx.entity(),
|
||||
|
|
|
|||
|
|
@ -12615,18 +12615,6 @@ async fn test_strip_whitespace_and_format_via_lsp(cx: &mut TestAppContext) {
|
|||
)
|
||||
.await;
|
||||
|
||||
cx.run_until_parked();
|
||||
// Set up a buffer white some trailing whitespace and no trailing newline.
|
||||
cx.set_state(
|
||||
&[
|
||||
"one ", //
|
||||
"twoˇ", //
|
||||
"three ", //
|
||||
"four", //
|
||||
]
|
||||
.join("\n"),
|
||||
);
|
||||
|
||||
// Record which buffer changes have been sent to the language server
|
||||
let buffer_changes = Arc::new(Mutex::new(Vec::new()));
|
||||
cx.lsp
|
||||
|
|
@ -12641,8 +12629,6 @@ async fn test_strip_whitespace_and_format_via_lsp(cx: &mut TestAppContext) {
|
|||
);
|
||||
}
|
||||
});
|
||||
cx.run_until_parked();
|
||||
|
||||
// Handle formatting requests to the language server.
|
||||
cx.lsp
|
||||
.set_request_handler::<lsp::request::Formatting, _, _>({
|
||||
|
|
@ -12691,6 +12677,18 @@ async fn test_strip_whitespace_and_format_via_lsp(cx: &mut TestAppContext) {
|
|||
}
|
||||
});
|
||||
|
||||
// Set up a buffer white some trailing whitespace and no trailing newline.
|
||||
cx.set_state(
|
||||
&[
|
||||
"one ", //
|
||||
"twoˇ", //
|
||||
"three ", //
|
||||
"four", //
|
||||
]
|
||||
.join("\n"),
|
||||
);
|
||||
cx.run_until_parked();
|
||||
|
||||
// Submit a format request.
|
||||
let format = cx
|
||||
.update_editor(|editor, window, cx| editor.format(&Format, window, cx))
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ use markdown::Markdown;
|
|||
use multi_buffer::{MultiBuffer, RowInfo};
|
||||
use project::{
|
||||
Project, ProjectItem as _,
|
||||
git_store::{GitStoreEvent, Repository, RepositoryEvent},
|
||||
git_store::{GitStoreEvent, Repository},
|
||||
};
|
||||
use smallvec::SmallVec;
|
||||
use std::{sync::Arc, time::Duration};
|
||||
|
|
@ -235,8 +235,8 @@ impl GitBlame {
|
|||
let git_store = project.read(cx).git_store().clone();
|
||||
let git_store_subscription =
|
||||
cx.subscribe(&git_store, move |this, _, event, cx| match event {
|
||||
GitStoreEvent::RepositoryUpdated(_, RepositoryEvent::Updated { .. }, _)
|
||||
| GitStoreEvent::RepositoryAdded(_)
|
||||
GitStoreEvent::RepositoryUpdated(_, _, _)
|
||||
| GitStoreEvent::RepositoryAdded
|
||||
| GitStoreEvent::RepositoryRemoved(_) => {
|
||||
log::debug!("Status of git repositories updated. Regenerating blame data...",);
|
||||
this.generate(cx);
|
||||
|
|
|
|||
|
|
@ -11,14 +11,20 @@ use git::{
|
|||
},
|
||||
status::{FileStatus, GitStatus, StatusCode, TrackedStatus, UnmergedStatus},
|
||||
};
|
||||
use gpui::{AsyncApp, BackgroundExecutor, SharedString, Task};
|
||||
use gpui::{AsyncApp, BackgroundExecutor, SharedString, Task, TaskLabel};
|
||||
use ignore::gitignore::GitignoreBuilder;
|
||||
use parking_lot::Mutex;
|
||||
use rope::Rope;
|
||||
use smol::future::FutureExt as _;
|
||||
use std::{path::PathBuf, sync::Arc};
|
||||
use std::{
|
||||
path::PathBuf,
|
||||
sync::{Arc, LazyLock},
|
||||
};
|
||||
use util::{paths::PathStyle, rel_path::RelPath};
|
||||
|
||||
pub static LOAD_INDEX_TEXT_TASK: LazyLock<TaskLabel> = LazyLock::new(TaskLabel::new);
|
||||
pub static LOAD_HEAD_TEXT_TASK: LazyLock<TaskLabel> = LazyLock::new(TaskLabel::new);
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct FakeGitRepository {
|
||||
pub(crate) fs: Arc<FakeFs>,
|
||||
|
|
@ -79,33 +85,29 @@ impl GitRepository for FakeGitRepository {
|
|||
fn reload_index(&self) {}
|
||||
|
||||
fn load_index_text(&self, path: RepoPath) -> BoxFuture<'_, Option<String>> {
|
||||
async {
|
||||
self.with_state_async(false, move |state| {
|
||||
state
|
||||
.index_contents
|
||||
.get(&path)
|
||||
.context("not present in index")
|
||||
.cloned()
|
||||
})
|
||||
.await
|
||||
.ok()
|
||||
}
|
||||
.boxed()
|
||||
let fut = self.with_state_async(false, move |state| {
|
||||
state
|
||||
.index_contents
|
||||
.get(&path)
|
||||
.context("not present in index")
|
||||
.cloned()
|
||||
});
|
||||
self.executor
|
||||
.spawn_labeled(*LOAD_INDEX_TEXT_TASK, async move { fut.await.ok() })
|
||||
.boxed()
|
||||
}
|
||||
|
||||
fn load_committed_text(&self, path: RepoPath) -> BoxFuture<'_, Option<String>> {
|
||||
async {
|
||||
self.with_state_async(false, move |state| {
|
||||
state
|
||||
.head_contents
|
||||
.get(&path)
|
||||
.context("not present in HEAD")
|
||||
.cloned()
|
||||
})
|
||||
.await
|
||||
.ok()
|
||||
}
|
||||
.boxed()
|
||||
let fut = self.with_state_async(false, move |state| {
|
||||
state
|
||||
.head_contents
|
||||
.get(&path)
|
||||
.context("not present in HEAD")
|
||||
.cloned()
|
||||
});
|
||||
self.executor
|
||||
.spawn_labeled(*LOAD_HEAD_TEXT_TASK, async move { fut.await.ok() })
|
||||
.boxed()
|
||||
}
|
||||
|
||||
fn load_commit(
|
||||
|
|
|
|||
|
|
@ -58,6 +58,9 @@ use smol::io::AsyncReadExt;
|
|||
#[cfg(any(test, feature = "test-support"))]
|
||||
use std::ffi::OsStr;
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
pub use fake_git_repo::{LOAD_HEAD_TEXT_TASK, LOAD_INDEX_TEXT_TASK};
|
||||
|
||||
pub trait Watcher: Send + Sync {
|
||||
fn add(&self, path: &Path) -> Result<()>;
|
||||
fn remove(&self, path: &Path) -> Result<()>;
|
||||
|
|
|
|||
|
|
@ -425,13 +425,20 @@ impl GitPanel {
|
|||
}
|
||||
GitStoreEvent::RepositoryUpdated(
|
||||
_,
|
||||
RepositoryEvent::Updated { full_scan, .. },
|
||||
RepositoryEvent::StatusesChanged { full_scan: true }
|
||||
| RepositoryEvent::BranchChanged
|
||||
| RepositoryEvent::MergeHeadsChanged,
|
||||
true,
|
||||
) => {
|
||||
this.schedule_update(*full_scan, window, cx);
|
||||
this.schedule_update(true, window, cx);
|
||||
}
|
||||
|
||||
GitStoreEvent::RepositoryAdded(_) | GitStoreEvent::RepositoryRemoved(_) => {
|
||||
GitStoreEvent::RepositoryUpdated(
|
||||
_,
|
||||
RepositoryEvent::StatusesChanged { full_scan: false },
|
||||
true,
|
||||
)
|
||||
| GitStoreEvent::RepositoryAdded
|
||||
| GitStoreEvent::RepositoryRemoved(_) => {
|
||||
this.schedule_update(false, window, cx);
|
||||
}
|
||||
GitStoreEvent::IndexWriteError(error) => {
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ use crate::{
|
|||
};
|
||||
use anyhow::Result;
|
||||
use buffer_diff::{BufferDiff, DiffHunkSecondaryStatus};
|
||||
use collections::HashSet;
|
||||
use collections::{HashMap, HashSet};
|
||||
use editor::{
|
||||
Editor, EditorEvent, SelectionEffects,
|
||||
actions::{GoToHunk, GoToPreviousHunk},
|
||||
|
|
@ -27,7 +27,7 @@ use language::{Anchor, Buffer, Capability, OffsetRangeExt};
|
|||
use multi_buffer::{MultiBuffer, PathKey};
|
||||
use project::{
|
||||
Project, ProjectPath,
|
||||
git_store::{GitStore, GitStoreEvent, Repository},
|
||||
git_store::{GitStore, GitStoreEvent, Repository, RepositoryEvent},
|
||||
};
|
||||
use settings::{Settings, SettingsStore};
|
||||
use std::any::{Any, TypeId};
|
||||
|
|
@ -57,12 +57,13 @@ pub struct ProjectDiff {
|
|||
multibuffer: Entity<MultiBuffer>,
|
||||
editor: Entity<Editor>,
|
||||
git_store: Entity<GitStore>,
|
||||
buffer_diff_subscriptions: HashMap<RepoPath, (Entity<BufferDiff>, Subscription)>,
|
||||
workspace: WeakEntity<Workspace>,
|
||||
focus_handle: FocusHandle,
|
||||
update_needed: postage::watch::Sender<()>,
|
||||
pending_scroll: Option<PathKey>,
|
||||
_task: Task<Result<()>>,
|
||||
_subscription: Subscription,
|
||||
_git_store_subscription: Subscription,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
|
|
@ -177,7 +178,11 @@ impl ProjectDiff {
|
|||
window,
|
||||
move |this, _git_store, event, _window, _cx| match event {
|
||||
GitStoreEvent::ActiveRepositoryChanged(_)
|
||||
| GitStoreEvent::RepositoryUpdated(_, _, true)
|
||||
| GitStoreEvent::RepositoryUpdated(
|
||||
_,
|
||||
RepositoryEvent::StatusesChanged { full_scan: _ },
|
||||
true,
|
||||
)
|
||||
| GitStoreEvent::ConflictsUpdated => {
|
||||
*this.update_needed.borrow_mut() = ();
|
||||
}
|
||||
|
|
@ -217,10 +222,11 @@ impl ProjectDiff {
|
|||
focus_handle,
|
||||
editor,
|
||||
multibuffer,
|
||||
buffer_diff_subscriptions: Default::default(),
|
||||
pending_scroll: None,
|
||||
update_needed: send,
|
||||
_task: worker,
|
||||
_subscription: git_store_subscription,
|
||||
_git_store_subscription: git_store_subscription,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -365,6 +371,7 @@ impl ProjectDiff {
|
|||
self.multibuffer.update(cx, |multibuffer, cx| {
|
||||
multibuffer.clear(cx);
|
||||
});
|
||||
self.buffer_diff_subscriptions.clear();
|
||||
return vec![];
|
||||
};
|
||||
|
||||
|
|
@ -407,6 +414,8 @@ impl ProjectDiff {
|
|||
});
|
||||
self.multibuffer.update(cx, |multibuffer, cx| {
|
||||
for path in previous_paths {
|
||||
self.buffer_diff_subscriptions
|
||||
.remove(&path.path.clone().into());
|
||||
multibuffer.remove_excerpts_for_path(path, cx);
|
||||
}
|
||||
});
|
||||
|
|
@ -419,9 +428,15 @@ impl ProjectDiff {
|
|||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
let path_key = diff_buffer.path_key;
|
||||
let buffer = diff_buffer.buffer;
|
||||
let diff = diff_buffer.diff;
|
||||
let path_key = diff_buffer.path_key.clone();
|
||||
let buffer = diff_buffer.buffer.clone();
|
||||
let diff = diff_buffer.diff.clone();
|
||||
|
||||
let subscription = cx.subscribe(&diff, move |this, _, _, _| {
|
||||
*this.update_needed.borrow_mut() = ();
|
||||
});
|
||||
self.buffer_diff_subscriptions
|
||||
.insert(path_key.path.clone().into(), (diff.clone(), subscription));
|
||||
|
||||
let conflict_addon = self
|
||||
.editor
|
||||
|
|
@ -440,9 +455,10 @@ impl ProjectDiff {
|
|||
.unwrap_or_default();
|
||||
let conflicts = conflicts.iter().map(|conflict| conflict.range.clone());
|
||||
|
||||
let excerpt_ranges = merge_anchor_ranges(diff_hunk_ranges, conflicts, &snapshot)
|
||||
.map(|range| range.to_point(&snapshot))
|
||||
.collect::<Vec<_>>();
|
||||
let excerpt_ranges =
|
||||
merge_anchor_ranges(diff_hunk_ranges.into_iter(), conflicts, &snapshot)
|
||||
.map(|range| range.to_point(&snapshot))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let (was_empty, is_excerpt_newly_added) = self.multibuffer.update(cx, |multibuffer, cx| {
|
||||
let was_empty = multibuffer.is_empty();
|
||||
|
|
@ -519,8 +535,7 @@ impl ProjectDiff {
|
|||
self.multibuffer
|
||||
.read(cx)
|
||||
.excerpt_paths()
|
||||
.map(|key| key.path())
|
||||
.cloned()
|
||||
.map(|key| key.path.clone())
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
|
|
@ -1621,8 +1636,8 @@ mod tests {
|
|||
cx,
|
||||
&"
|
||||
- original
|
||||
+ different
|
||||
ˇ"
|
||||
+ ˇdifferent
|
||||
"
|
||||
.unindent(),
|
||||
);
|
||||
}
|
||||
|
|
@ -1950,6 +1965,7 @@ mod tests {
|
|||
.unindent(),
|
||||
);
|
||||
|
||||
// The project diff updates its excerpts when a new hunk appears in a buffer that already has a diff.
|
||||
let buffer = project
|
||||
.update(cx, |project, cx| {
|
||||
project.open_local_buffer(path!("/project/foo.txt"), cx)
|
||||
|
|
@ -2002,4 +2018,63 @@ mod tests {
|
|||
.unindent(),
|
||||
);
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_update_on_uncommit(cx: &mut TestAppContext) {
|
||||
init_test(cx);
|
||||
|
||||
let fs = FakeFs::new(cx.executor());
|
||||
fs.insert_tree(
|
||||
path!("/project"),
|
||||
json!({
|
||||
".git": {},
|
||||
"README.md": "# My cool project\n".to_owned()
|
||||
}),
|
||||
)
|
||||
.await;
|
||||
fs.set_head_and_index_for_repo(
|
||||
Path::new(path!("/project/.git")),
|
||||
&[("README.md", "# My cool project\n".to_owned())],
|
||||
);
|
||||
let project = Project::test(fs.clone(), [Path::new(path!("/project"))], cx).await;
|
||||
let worktree_id = project.read_with(cx, |project, cx| {
|
||||
project.worktrees(cx).next().unwrap().read(cx).id()
|
||||
});
|
||||
let (workspace, cx) =
|
||||
cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
|
||||
cx.run_until_parked();
|
||||
|
||||
let _editor = workspace
|
||||
.update_in(cx, |workspace, window, cx| {
|
||||
workspace.open_path((worktree_id, rel_path("README.md")), None, true, window, cx)
|
||||
})
|
||||
.await
|
||||
.unwrap()
|
||||
.downcast::<Editor>()
|
||||
.unwrap();
|
||||
|
||||
cx.focus(&workspace);
|
||||
cx.update(|window, cx| {
|
||||
window.dispatch_action(project_diff::Diff.boxed_clone(), cx);
|
||||
});
|
||||
cx.run_until_parked();
|
||||
let item = workspace.update(cx, |workspace, cx| {
|
||||
workspace.active_item_as::<ProjectDiff>(cx).unwrap()
|
||||
});
|
||||
cx.focus(&item);
|
||||
let editor = item.read_with(cx, |item, _| item.editor.clone());
|
||||
|
||||
fs.set_head_and_index_for_repo(
|
||||
Path::new(path!("/project/.git")),
|
||||
&[(
|
||||
"README.md",
|
||||
"# My cool project\nDetails to come.\n".to_owned(),
|
||||
)],
|
||||
);
|
||||
cx.run_until_parked();
|
||||
|
||||
let mut cx = EditorTestContext::for_editor_in(editor, cx).await;
|
||||
|
||||
cx.assert_excerpts_with_selections("[EXCERPT]\nˇ# My cool project\nDetails to come.\n");
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -72,7 +72,7 @@ impl StashList {
|
|||
if let Some(repo) = repository.clone() {
|
||||
_subscriptions.push(
|
||||
cx.subscribe_in(&repo, window, |this, _, event, window, cx| {
|
||||
if matches!(event, RepositoryEvent::Updated { .. }) {
|
||||
if matches!(event, RepositoryEvent::StashEntriesChanged) {
|
||||
let stash_entries = this.picker.read_with(cx, |picker, cx| {
|
||||
picker
|
||||
.delegate
|
||||
|
|
|
|||
|
|
@ -166,8 +166,8 @@ impl MultiBufferDiffHunk {
|
|||
#[derive(PartialEq, Eq, Ord, PartialOrd, Clone, Hash, Debug)]
|
||||
pub struct PathKey {
|
||||
// Used by the derived PartialOrd & Ord
|
||||
sort_prefix: Option<u64>,
|
||||
path: Arc<RelPath>,
|
||||
pub sort_prefix: Option<u64>,
|
||||
pub path: Arc<RelPath>,
|
||||
}
|
||||
|
||||
impl PathKey {
|
||||
|
|
@ -190,11 +190,6 @@ impl PathKey {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
pub fn path(&self) -> &Arc<RelPath> {
|
||||
&self.path
|
||||
}
|
||||
}
|
||||
|
||||
pub type MultiBufferPoint = Point;
|
||||
|
|
|
|||
|
|
@ -301,9 +301,13 @@ pub enum RepositoryState {
|
|||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub enum RepositoryEvent {
|
||||
Updated { full_scan: bool, new_instance: bool },
|
||||
StatusesChanged {
|
||||
// TODO could report which statuses changed here
|
||||
full_scan: bool,
|
||||
},
|
||||
MergeHeadsChanged,
|
||||
PathsChanged,
|
||||
BranchChanged,
|
||||
StashEntriesChanged,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
|
|
@ -313,7 +317,7 @@ pub struct JobsUpdated;
|
|||
pub enum GitStoreEvent {
|
||||
ActiveRepositoryChanged(Option<RepositoryId>),
|
||||
RepositoryUpdated(RepositoryId, RepositoryEvent, bool),
|
||||
RepositoryAdded(RepositoryId),
|
||||
RepositoryAdded,
|
||||
RepositoryRemoved(RepositoryId),
|
||||
IndexWriteError(anyhow::Error),
|
||||
JobsUpdated,
|
||||
|
|
@ -1218,7 +1222,7 @@ impl GitStore {
|
|||
self._subscriptions
|
||||
.push(cx.subscribe(&repo, Self::on_jobs_updated));
|
||||
self.repositories.insert(id, repo);
|
||||
cx.emit(GitStoreEvent::RepositoryAdded(id));
|
||||
cx.emit(GitStoreEvent::RepositoryAdded);
|
||||
self.active_repo_id.get_or_insert_with(|| {
|
||||
cx.emit(GitStoreEvent::ActiveRepositoryChanged(Some(id)));
|
||||
id
|
||||
|
|
@ -1485,11 +1489,10 @@ impl GitStore {
|
|||
let id = RepositoryId::from_proto(update.id);
|
||||
let client = this.upstream_client().context("no upstream client")?;
|
||||
|
||||
let mut is_new = false;
|
||||
let mut repo_subscription = None;
|
||||
let repo = this.repositories.entry(id).or_insert_with(|| {
|
||||
is_new = true;
|
||||
let git_store = cx.weak_entity();
|
||||
cx.new(|cx| {
|
||||
let repo = cx.new(|cx| {
|
||||
Repository::remote(
|
||||
id,
|
||||
Path::new(&update.abs_path).into(),
|
||||
|
|
@ -1499,16 +1502,16 @@ impl GitStore {
|
|||
git_store,
|
||||
cx,
|
||||
)
|
||||
})
|
||||
});
|
||||
repo_subscription = Some(cx.subscribe(&repo, Self::on_repository_event));
|
||||
cx.emit(GitStoreEvent::RepositoryAdded);
|
||||
repo
|
||||
});
|
||||
if is_new {
|
||||
this._subscriptions
|
||||
.push(cx.subscribe(repo, Self::on_repository_event))
|
||||
}
|
||||
this._subscriptions.extend(repo_subscription);
|
||||
|
||||
repo.update(cx, {
|
||||
let update = update.clone();
|
||||
|repo, cx| repo.apply_remote_update(update, is_new, cx)
|
||||
|repo, cx| repo.apply_remote_update(update, cx)
|
||||
})?;
|
||||
|
||||
this.active_repo_id.get_or_insert_with(|| {
|
||||
|
|
@ -3877,18 +3880,15 @@ impl Repository {
|
|||
environment,
|
||||
..
|
||||
} => {
|
||||
// TODO would be nice to not have to do this manually
|
||||
let result = backend.stash_drop(index, environment).await;
|
||||
if result.is_ok()
|
||||
&& let Ok(stash_entries) = backend.stash_entries().await
|
||||
{
|
||||
let snapshot = this.update(&mut cx, |this, cx| {
|
||||
this.snapshot.stash_entries = stash_entries;
|
||||
let snapshot = this.snapshot.clone();
|
||||
cx.emit(RepositoryEvent::Updated {
|
||||
full_scan: false,
|
||||
new_instance: false,
|
||||
});
|
||||
snapshot
|
||||
cx.emit(RepositoryEvent::StashEntriesChanged);
|
||||
this.snapshot.clone()
|
||||
})?;
|
||||
if let Some(updates_tx) = updates_tx {
|
||||
updates_tx
|
||||
|
|
@ -4048,18 +4048,15 @@ impl Repository {
|
|||
cx.clone(),
|
||||
)
|
||||
.await;
|
||||
// TODO would be nice to not have to do this manually
|
||||
if result.is_ok() {
|
||||
let branches = backend.branches().await?;
|
||||
let branch = branches.into_iter().find(|branch| branch.is_head);
|
||||
log::info!("head branch after scan is {branch:?}");
|
||||
let snapshot = this.update(&mut cx, |this, cx| {
|
||||
this.snapshot.branch = branch;
|
||||
let snapshot = this.snapshot.clone();
|
||||
cx.emit(RepositoryEvent::Updated {
|
||||
full_scan: false,
|
||||
new_instance: false,
|
||||
});
|
||||
snapshot
|
||||
cx.emit(RepositoryEvent::BranchChanged);
|
||||
this.snapshot.clone()
|
||||
})?;
|
||||
if let Some(updates_tx) = updates_tx {
|
||||
updates_tx
|
||||
|
|
@ -4458,7 +4455,6 @@ impl Repository {
|
|||
pub(crate) fn apply_remote_update(
|
||||
&mut self,
|
||||
update: proto::UpdateRepository,
|
||||
is_new: bool,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Result<()> {
|
||||
let conflicted_paths = TreeSet::from_ordered_entries(
|
||||
|
|
@ -4467,21 +4463,30 @@ impl Repository {
|
|||
.into_iter()
|
||||
.filter_map(|path| RepoPath::from_proto(&path).log_err()),
|
||||
);
|
||||
self.snapshot.branch = update.branch_summary.as_ref().map(proto_to_branch);
|
||||
self.snapshot.head_commit = update
|
||||
let new_branch = update.branch_summary.as_ref().map(proto_to_branch);
|
||||
let new_head_commit = update
|
||||
.head_commit_details
|
||||
.as_ref()
|
||||
.map(proto_to_commit_details);
|
||||
if self.snapshot.branch != new_branch || self.snapshot.head_commit != new_head_commit {
|
||||
cx.emit(RepositoryEvent::BranchChanged)
|
||||
}
|
||||
self.snapshot.branch = new_branch;
|
||||
self.snapshot.head_commit = new_head_commit;
|
||||
|
||||
self.snapshot.merge.conflicted_paths = conflicted_paths;
|
||||
self.snapshot.merge.message = update.merge_message.map(SharedString::from);
|
||||
self.snapshot.stash_entries = GitStash {
|
||||
let new_stash_entries = GitStash {
|
||||
entries: update
|
||||
.stash_entries
|
||||
.iter()
|
||||
.filter_map(|entry| proto_to_stash(entry).ok())
|
||||
.collect(),
|
||||
};
|
||||
if self.snapshot.stash_entries != new_stash_entries {
|
||||
cx.emit(RepositoryEvent::StashEntriesChanged)
|
||||
}
|
||||
self.snapshot.stash_entries = new_stash_entries;
|
||||
|
||||
let edits = update
|
||||
.removed_statuses
|
||||
|
|
@ -4500,14 +4505,13 @@ impl Repository {
|
|||
}),
|
||||
)
|
||||
.collect::<Vec<_>>();
|
||||
if !edits.is_empty() {
|
||||
cx.emit(RepositoryEvent::StatusesChanged { full_scan: true });
|
||||
}
|
||||
self.snapshot.statuses_by_path.edit(edits, ());
|
||||
if update.is_last_update {
|
||||
self.snapshot.scan_id = update.scan_id;
|
||||
}
|
||||
cx.emit(RepositoryEvent::Updated {
|
||||
full_scan: true,
|
||||
new_instance: is_new,
|
||||
});
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
|
@ -4830,23 +4834,19 @@ impl Repository {
|
|||
.await;
|
||||
|
||||
this.update(&mut cx, |this, cx| {
|
||||
let needs_update = !changed_path_statuses.is_empty()
|
||||
|| this.snapshot.stash_entries != stash_entries;
|
||||
this.snapshot.stash_entries = stash_entries;
|
||||
if this.snapshot.stash_entries != stash_entries {
|
||||
cx.emit(RepositoryEvent::StashEntriesChanged);
|
||||
this.snapshot.stash_entries = stash_entries;
|
||||
}
|
||||
|
||||
if !changed_path_statuses.is_empty() {
|
||||
cx.emit(RepositoryEvent::StatusesChanged { full_scan: false });
|
||||
this.snapshot
|
||||
.statuses_by_path
|
||||
.edit(changed_path_statuses, ());
|
||||
this.snapshot.scan_id += 1;
|
||||
}
|
||||
|
||||
if needs_update {
|
||||
cx.emit(RepositoryEvent::Updated {
|
||||
full_scan: false,
|
||||
new_instance: false,
|
||||
});
|
||||
}
|
||||
|
||||
if let Some(updates_tx) = updates_tx {
|
||||
updates_tx
|
||||
.unbounded_send(DownstreamUpdate::UpdateRepository(
|
||||
|
|
@ -4854,7 +4854,6 @@ impl Repository {
|
|||
))
|
||||
.ok();
|
||||
}
|
||||
cx.emit(RepositoryEvent::PathsChanged);
|
||||
})
|
||||
},
|
||||
);
|
||||
|
|
@ -5117,28 +5116,24 @@ async fn compute_snapshot(
|
|||
MergeDetails::load(&backend, &statuses_by_path, &prev_snapshot).await?;
|
||||
log::debug!("new merge details (changed={merge_heads_changed:?}): {merge_details:?}");
|
||||
|
||||
if merge_heads_changed
|
||||
|| branch != prev_snapshot.branch
|
||||
|| statuses_by_path != prev_snapshot.statuses_by_path
|
||||
{
|
||||
events.push(RepositoryEvent::Updated {
|
||||
full_scan: true,
|
||||
new_instance: false,
|
||||
});
|
||||
}
|
||||
|
||||
// Cache merge conflict paths so they don't change from staging/unstaging,
|
||||
// until the merge heads change (at commit time, etc.).
|
||||
if merge_heads_changed {
|
||||
events.push(RepositoryEvent::MergeHeadsChanged);
|
||||
}
|
||||
|
||||
if statuses_by_path != prev_snapshot.statuses_by_path {
|
||||
events.push(RepositoryEvent::StatusesChanged { full_scan: true })
|
||||
}
|
||||
|
||||
// Useful when branch is None in detached head state
|
||||
let head_commit = match backend.head_sha().await {
|
||||
Some(head_sha) => backend.show(head_sha).await.log_err(),
|
||||
None => None,
|
||||
};
|
||||
|
||||
if branch != prev_snapshot.branch || head_commit != prev_snapshot.head_commit {
|
||||
events.push(RepositoryEvent::BranchChanged);
|
||||
}
|
||||
|
||||
// Used by edit prediction data collection
|
||||
let remote_origin_url = backend.remote_url("origin");
|
||||
let remote_upstream_url = backend.remote_url("upstream");
|
||||
|
|
|
|||
|
|
@ -8936,10 +8936,7 @@ async fn test_ignored_dirs_events(cx: &mut gpui::TestAppContext) {
|
|||
assert_eq!(
|
||||
repository_updates.lock().drain(..).collect::<Vec<_>>(),
|
||||
vec![
|
||||
RepositoryEvent::Updated {
|
||||
full_scan: true,
|
||||
new_instance: false,
|
||||
},
|
||||
RepositoryEvent::StatusesChanged { full_scan: true },
|
||||
RepositoryEvent::MergeHeadsChanged,
|
||||
],
|
||||
"Initial worktree scan should produce a repo update event"
|
||||
|
|
@ -9000,7 +8997,6 @@ async fn test_ignored_dirs_events(cx: &mut gpui::TestAppContext) {
|
|||
repository_updates
|
||||
.lock()
|
||||
.iter()
|
||||
.filter(|update| !matches!(update, RepositoryEvent::PathsChanged))
|
||||
.cloned()
|
||||
.collect::<Vec<_>>(),
|
||||
Vec::new(),
|
||||
|
|
@ -9104,17 +9100,10 @@ async fn test_odd_events_for_ignored_dirs(
|
|||
});
|
||||
|
||||
assert_eq!(
|
||||
repository_updates
|
||||
.lock()
|
||||
.drain(..)
|
||||
.filter(|update| !matches!(update, RepositoryEvent::PathsChanged))
|
||||
.collect::<Vec<_>>(),
|
||||
repository_updates.lock().drain(..).collect::<Vec<_>>(),
|
||||
vec![
|
||||
RepositoryEvent::Updated {
|
||||
full_scan: true,
|
||||
new_instance: false,
|
||||
},
|
||||
RepositoryEvent::MergeHeadsChanged,
|
||||
RepositoryEvent::BranchChanged
|
||||
],
|
||||
"Initial worktree scan should produce a repo update event"
|
||||
);
|
||||
|
|
@ -9142,7 +9131,6 @@ async fn test_odd_events_for_ignored_dirs(
|
|||
repository_updates
|
||||
.lock()
|
||||
.iter()
|
||||
.filter(|update| !matches!(update, RepositoryEvent::PathsChanged))
|
||||
.cloned()
|
||||
.collect::<Vec<_>>(),
|
||||
Vec::new(),
|
||||
|
|
|
|||
|
|
@ -496,8 +496,12 @@ impl ProjectPanel {
|
|||
&git_store,
|
||||
window,
|
||||
|this, _, event, window, cx| match event {
|
||||
GitStoreEvent::RepositoryUpdated(_, RepositoryEvent::Updated { .. }, _)
|
||||
| GitStoreEvent::RepositoryAdded(_)
|
||||
GitStoreEvent::RepositoryUpdated(
|
||||
_,
|
||||
RepositoryEvent::StatusesChanged { full_scan: _ },
|
||||
_,
|
||||
)
|
||||
| GitStoreEvent::RepositoryAdded
|
||||
| GitStoreEvent::RepositoryRemoved(_) => {
|
||||
this.update_visible_entries(None, false, false, window, cx);
|
||||
cx.notify();
|
||||
|
|
|
|||
|
|
@ -30,10 +30,7 @@ use gpui::{
|
|||
Subscription, WeakEntity, Window, actions, div,
|
||||
};
|
||||
use onboarding_banner::OnboardingBanner;
|
||||
use project::{
|
||||
Project, WorktreeSettings,
|
||||
git_store::{GitStoreEvent, RepositoryEvent},
|
||||
};
|
||||
use project::{Project, WorktreeSettings, git_store::GitStoreEvent};
|
||||
use remote::RemoteConnectionOptions;
|
||||
use settings::{Settings, SettingsLocation};
|
||||
use std::sync::Arc;
|
||||
|
|
@ -287,9 +284,7 @@ impl TitleBar {
|
|||
subscriptions.push(
|
||||
cx.subscribe(&git_store, move |_, _, event, cx| match event {
|
||||
GitStoreEvent::ActiveRepositoryChanged(_)
|
||||
| GitStoreEvent::RepositoryUpdated(_, RepositoryEvent::Updated { .. }, _)
|
||||
| GitStoreEvent::RepositoryAdded(_)
|
||||
| GitStoreEvent::RepositoryRemoved(_) => {
|
||||
| GitStoreEvent::RepositoryUpdated(_, _, true) => {
|
||||
cx.notify();
|
||||
}
|
||||
_ => {}
|
||||
|
|
|
|||
Loading…
Reference in a new issue