diff --git a/crates/collab/migrations.sqlite/20221109000000_test_schema.sql b/crates/collab/migrations.sqlite/20221109000000_test_schema.sql index 89b924087ef987c89ec58e65f2b165a7d11b4afa..c77d13c898aa237987ecf6ac799a253b3c0da8b1 100644 --- a/crates/collab/migrations.sqlite/20221109000000_test_schema.sql +++ b/crates/collab/migrations.sqlite/20221109000000_test_schema.sql @@ -82,6 +82,21 @@ CREATE TABLE "worktree_entries" ( CREATE INDEX "index_worktree_entries_on_project_id" ON "worktree_entries" ("project_id"); CREATE INDEX "index_worktree_entries_on_project_id_and_worktree_id" ON "worktree_entries" ("project_id", "worktree_id"); +CREATE TABLE "worktree_repositories" ( + "project_id" INTEGER NOT NULL, + "worktree_id" INTEGER NOT NULL, + "dot_git_entry_id" INTEGER NOT NULL, + "scan_id" INTEGER NOT NULL, + "branch" VARCHAR, + "work_directory_path" VARCHAR NOT NULL, + "is_deleted" BOOL NOT NULL, + PRIMARY KEY(project_id, worktree_id, dot_git_entry_id), + FOREIGN KEY(project_id, worktree_id) REFERENCES worktrees (project_id, id) ON DELETE CASCADE, + FOREIGN KEY(project_id, worktree_id, dot_git_entry_id) REFERENCES worktree_entries (project_id, worktree_id, id) ON DELETE CASCADE +); +CREATE INDEX "index_worktree_repositories_on_project_id" ON "worktree_repositories" ("project_id"); +CREATE INDEX "index_worktree_repositories_on_project_id_and_worktree_id" ON "worktree_repositories" ("project_id", "worktree_id"); + CREATE TABLE "worktree_diagnostic_summaries" ( "project_id" INTEGER NOT NULL, "worktree_id" INTEGER NOT NULL, @@ -153,7 +168,7 @@ CREATE TABLE "followers" ( "follower_connection_server_id" INTEGER NOT NULL REFERENCES servers (id) ON DELETE CASCADE, "follower_connection_id" INTEGER NOT NULL ); -CREATE UNIQUE INDEX +CREATE UNIQUE INDEX "index_followers_on_project_id_and_leader_connection_server_id_and_leader_connection_id_and_follower_connection_server_id_and_follower_connection_id" ON "followers" ("project_id", "leader_connection_server_id", "leader_connection_id", "follower_connection_server_id", "follower_connection_id"); CREATE INDEX "index_followers_on_room_id" ON "followers" ("room_id"); diff --git a/crates/collab/src/db.rs b/crates/collab/src/db.rs index f441bbfb000504d959b9e54f333b5a33cc31273d..9ac7e72ffedb0de4061dbed6a98a5a6bd015648c 100644 --- a/crates/collab/src/db.rs +++ b/crates/collab/src/db.rs @@ -14,6 +14,7 @@ mod user; mod worktree; mod worktree_diagnostic_summary; mod worktree_entry; +mod worktree_repository; use crate::executor::Executor; use crate::{Error, Result}; @@ -2330,6 +2331,55 @@ impl Database { .await?; } + if !update.updated_repositories.is_empty() { + worktree_repository::Entity::insert_many(update.updated_repositories.iter().map( + |repository| worktree_repository::ActiveModel { + project_id: ActiveValue::set(project_id), + worktree_id: ActiveValue::set(worktree_id), + dot_git_entry_id: ActiveValue::set(repository.dot_git_entry_id as i64), + work_directory_path: ActiveValue::set(repository.work_directory.clone()), + scan_id: ActiveValue::set(update.scan_id as i64), + branch: ActiveValue::set(repository.branch.clone()), + is_deleted: ActiveValue::set(false), + }, + )) + .on_conflict( + OnConflict::columns([ + worktree_repository::Column::ProjectId, + worktree_repository::Column::WorktreeId, + worktree_repository::Column::DotGitEntryId, + ]) + .update_columns([ + worktree_repository::Column::ScanId, + worktree_repository::Column::WorkDirectoryPath, + worktree_repository::Column::Branch, + ]) + .to_owned(), + ) + .exec(&*tx) + .await?; + } + + if !update.removed_repositories.is_empty() { + worktree_repository::Entity::update_many() + .filter( + worktree_repository::Column::ProjectId + .eq(project_id) + .and(worktree_repository::Column::WorktreeId.eq(worktree_id)) + .and( + worktree_repository::Column::DotGitEntryId + .is_in(update.removed_repositories.iter().map(|id| *id as i64)), + ), + ) + .set(worktree_repository::ActiveModel { + is_deleted: ActiveValue::Set(true), + scan_id: ActiveValue::Set(update.scan_id as i64), + ..Default::default() + }) + .exec(&*tx) + .await?; + } + let connection_ids = self.project_guest_connection_ids(project_id, &tx).await?; Ok(connection_ids) }) diff --git a/crates/collab/src/db/worktree_repository.rs b/crates/collab/src/db/worktree_repository.rs new file mode 100644 index 0000000000000000000000000000000000000000..b281f2047aafd15ef8b45d9cce9235851bf35d2e --- /dev/null +++ b/crates/collab/src/db/worktree_repository.rs @@ -0,0 +1,22 @@ +use super::ProjectId; +use sea_orm::entity::prelude::*; + +#[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)] +#[sea_orm(table_name = "worktree_repositories")] +pub struct Model { + #[sea_orm(primary_key)] + pub project_id: ProjectId, + #[sea_orm(primary_key)] + pub worktree_id: i64, + #[sea_orm(primary_key)] + pub dot_git_entry_id: i64, + pub scan_id: i64, + pub branch: Option, + pub work_directory_path: String, + pub is_deleted: bool, +} + +#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] +pub enum Relation {} + +impl ActiveModelBehavior for ActiveModel {} diff --git a/crates/collab/src/rpc.rs b/crates/collab/src/rpc.rs index 05c7d10982a3a705a08b7037cb31c6eb6a75ef55..0d57582d7e641579ba9c39d654ae7c1ae26141b1 100644 --- a/crates/collab/src/rpc.rs +++ b/crates/collab/src/rpc.rs @@ -1065,6 +1065,7 @@ async fn rejoin_room( is_last_update: worktree.completed_scan_id == worktree.scan_id, //TODO repo updated_repositories: vec![], + removed_repositories: vec![], }; for update in proto::split_worktree_update(message, MAX_CHUNK_SIZE) { session.peer.send(session.connection_id, update.clone())?; @@ -1387,6 +1388,7 @@ async fn join_project( is_last_update: worktree.scan_id == worktree.completed_scan_id, // TODO repo updated_repositories: vec![], + removed_repositories: vec![], }; for update in proto::split_worktree_update(message, MAX_CHUNK_SIZE) { session.peer.send(session.connection_id, update.clone())?; diff --git a/crates/collab/src/tests/integration_tests.rs b/crates/collab/src/tests/integration_tests.rs index 4c1c2534710a64d46617229f8c700caeb3f269dd..9f04642e30855a79aa6ac91e674c62e3c23c4adc 100644 --- a/crates/collab/src/tests/integration_tests.rs +++ b/crates/collab/src/tests/integration_tests.rs @@ -2609,13 +2609,15 @@ async fn test_git_branch_name( deterministic: Arc, cx_a: &mut TestAppContext, cx_b: &mut TestAppContext, + cx_c: &mut TestAppContext, ) { deterministic.forbid_parking(); let mut server = TestServer::start(&deterministic).await; let client_a = server.create_client(cx_a, "user_a").await; let client_b = server.create_client(cx_b, "user_b").await; + let client_c = server.create_client(cx_c, "user_c").await; server - .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)]) + .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b), (&client_c, cx_c)]) .await; let active_call_a = cx_a.read(ActiveCall::global); @@ -2681,6 +2683,11 @@ async fn test_git_branch_name( project_remote.read_with(cx_b, |project, cx| { assert_branch(Some("branch-2"), project, cx) }); + + let project_remote_c = client_c.build_remote_project(project_id, cx_c).await; + project_remote_c.read_with(cx_c, |project, cx| { + assert_branch(Some("branch-2"), project, cx) + }); } #[gpui::test(iterations = 10)] diff --git a/crates/project/src/worktree.rs b/crates/project/src/worktree.rs index 3adb956be22484c439f4eb0b84349c3486f436fe..3c550aa6bbf26ae252032c717c4d08fcdbb7edf2 100644 --- a/crates/project/src/worktree.rs +++ b/crates/project/src/worktree.rs @@ -43,7 +43,6 @@ use std::{ future::Future, mem, ops::{Deref, DerefMut}, - os::unix::prelude::OsStrExt, path::{Path, PathBuf}, pin::Pin, sync::{ @@ -120,21 +119,15 @@ pub struct Snapshot { #[derive(Clone, Debug)] pub struct RepositoryEntry { - // Path to the actual .git folder. - // Note: if .git is a file, this points to the folder indicated by the .git file - pub(crate) git_dir_path: Arc, - pub(crate) git_dir_entry_id: ProjectEntryId, - pub(crate) work_directory: RepositoryWorkDirectory, pub(crate) scan_id: usize, + pub(crate) dot_git_entry_id: ProjectEntryId, + /// Relative to the worktree, the repository for the root will have + /// a work directory equal to: "" + pub(crate) work_directory: RepositoryWorkDirectory, pub(crate) branch: Option>, } impl RepositoryEntry { - // Note that this path should be relative to the worktree root. - pub(crate) fn in_dot_git(&self, path: &Path) -> bool { - path.starts_with(self.git_dir_path.as_ref()) - } - pub fn branch(&self) -> Option> { self.branch.clone() } @@ -147,10 +140,9 @@ impl RepositoryEntry { impl From<&RepositoryEntry> for proto::RepositoryEntry { fn from(value: &RepositoryEntry) -> Self { proto::RepositoryEntry { - git_dir_entry_id: value.git_dir_entry_id.to_proto(), + dot_git_entry_id: value.dot_git_entry_id.to_proto(), scan_id: value.scan_id as u64, - git_dir_path: value.git_dir_path.as_os_str().as_bytes().to_vec(), - work_directory: value.work_directory.0.as_os_str().as_bytes().to_vec(), + work_directory: value.work_directory.to_string_lossy().to_string(), branch: value.branch.as_ref().map(|str| str.to_string()), } } @@ -187,6 +179,12 @@ impl<'a> From<&'a str> for RepositoryWorkDirectory { } } +impl Default for RepositoryWorkDirectory { + fn default() -> Self { + RepositoryWorkDirectory(Arc::from(Path::new(""))) + } +} + #[derive(Clone, Debug, Ord, PartialOrd, Eq, PartialEq)] pub struct RepoPath(PathBuf); @@ -210,22 +208,31 @@ impl AsRef for RepositoryWorkDirectory { } } -impl Default for RepositoryWorkDirectory { - fn default() -> Self { - RepositoryWorkDirectory(Arc::from(Path::new(""))) - } -} - #[derive(Debug, Clone)] pub struct LocalSnapshot { ignores_by_parent_abs_path: HashMap, (Arc, usize)>, // The ProjectEntryId corresponds to the entry for the .git dir - git_repositories: TreeMap>>, + git_repositories: TreeMap, removed_entry_ids: HashMap, next_entry_id: Arc, snapshot: Snapshot, } +#[derive(Debug, Clone)] +pub struct LocalRepositoryEntry { + pub(crate) repo_ptr: Arc>, + /// Path to the actual .git folder. + /// Note: if .git is a file, this points to the folder indicated by the .git file + pub(crate) git_dir_path: Arc, +} + +impl LocalRepositoryEntry { + // Note that this path should be relative to the worktree root. + pub(crate) fn in_dot_git(&self, path: &Path) -> bool { + path.starts_with(self.git_dir_path.as_ref()) + } +} + impl Deref for LocalSnapshot { type Target = Snapshot; @@ -690,20 +697,21 @@ impl LocalWorktree { fn diff<'a>( a: impl Iterator, mut b: impl Iterator, - updated: &mut HashMap<&'a Path, RepositoryEntry>, + updated: &mut HashMap, ) { for a_repo in a { let matched = b.find(|b_repo| { - a_repo.git_dir_path == b_repo.git_dir_path && a_repo.scan_id == b_repo.scan_id + a_repo.dot_git_entry_id == b_repo.dot_git_entry_id + && a_repo.scan_id == b_repo.scan_id }); if matched.is_none() { - updated.insert(a_repo.git_dir_path.as_ref(), a_repo.clone()); + updated.insert(a_repo.dot_git_entry_id, a_repo.clone()); } } } - let mut updated = HashMap::<&Path, RepositoryEntry>::default(); + let mut updated = HashMap::::default(); diff(old_repos.values(), new_repos.values(), &mut updated); diff(new_repos.values(), old_repos.values(), &mut updated); @@ -753,8 +761,8 @@ impl LocalWorktree { if let Some(repo) = snapshot.repo_for(&path) { let repo_path = repo.work_directory.relativize(&path).unwrap(); - if let Some(repo) = self.git_repositories.get(&repo.git_dir_entry_id) { - let repo = repo.to_owned(); + if let Some(repo) = self.git_repositories.get(&repo.dot_git_entry_id) { + let repo = repo.repo_ptr.to_owned(); index_task = Some( cx.background() .spawn(async move { repo.lock().load_index_text(&repo_path) }), @@ -1150,9 +1158,11 @@ impl LocalWorktree { repo_path: RepoPath, cx: &mut ModelContext, ) -> Task> { - let Some(git_ptr) = self.git_repositories.get(&repo.git_dir_entry_id).map(|git_ptr| git_ptr.to_owned()) else { + let Some(git_ptr) = self.git_repositories.get(&repo.dot_git_entry_id).map(|git_ptr| git_ptr.to_owned()) else { return Task::Ready(Some(None)) }; + let git_ptr = git_ptr.repo_ptr; + cx.background() .spawn(async move { git_ptr.lock().load_index_text(&repo_path) }) } @@ -1352,7 +1362,7 @@ impl Snapshot { Some(removed_entry.path) } - pub(crate) fn apply_remote_update(&mut self, update: proto::UpdateWorktree) -> Result<()> { + pub(crate) fn apply_remote_update(&mut self, mut update: proto::UpdateWorktree) -> Result<()> { let mut entries_by_path_edits = Vec::new(); let mut entries_by_id_edits = Vec::new(); for entry_id in update.removed_entries { @@ -1378,6 +1388,32 @@ impl Snapshot { self.entries_by_path.edit(entries_by_path_edits, &()); self.entries_by_id.edit(entries_by_id_edits, &()); + + update.removed_repositories.sort_unstable(); + self.repository_entries.retain(|_, entry| { + if let Ok(_) = update + .removed_repositories + .binary_search(&entry.dot_git_entry_id.to_proto()) + { + false + } else { + true + } + }); + + for repository in update.updated_repositories { + let repository = RepositoryEntry { + dot_git_entry_id: ProjectEntryId::from_proto(repository.dot_git_entry_id), + work_directory: RepositoryWorkDirectory( + Path::new(&repository.work_directory).into(), + ), + scan_id: repository.scan_id as usize, + branch: repository.branch.map(Into::into), + }; + self.repository_entries + .insert(repository.work_directory.clone(), repository) + } + self.scan_id = update.scan_id as usize; if update.is_last_update { self.completed_scan_id = update.scan_id as usize; @@ -1529,18 +1565,19 @@ impl LocalSnapshot { &self, path: &Path, ) -> Option<(RepositoryWorkDirectory, Arc>)> { - self.repository_entries + let (entry_id, local_repo) = self + .git_repositories .iter() - .find(|(_, repo)| repo.in_dot_git(path)) - .map(|(work_directory, entry)| { - ( - work_directory.to_owned(), - self.git_repositories - .get(&entry.git_dir_entry_id) - .expect("These two data structures should be in sync") - .to_owned(), - ) - }) + .find(|(_, repo)| repo.in_dot_git(path))?; + + let work_dir = self + .snapshot + .repository_entries + .iter() + .find(|(_, entry)| entry.dot_git_entry_id == *entry_id) + .map(|(_, entry)| entry.work_directory.to_owned())?; + + Some((work_dir, local_repo.repo_ptr.to_owned())) } #[cfg(test)] @@ -1556,6 +1593,7 @@ impl LocalSnapshot { scan_id: self.scan_id as u64, is_last_update: true, updated_repositories: self.repository_entries.values().map(Into::into).collect(), + removed_repositories: Default::default(), } } @@ -1615,6 +1653,44 @@ impl LocalSnapshot { } } + let mut updated_repositories: Vec = Vec::new(); + let mut removed_repositories = Vec::new(); + let mut self_repos = self.snapshot.repository_entries.values().peekable(); + let mut other_repos = other.snapshot.repository_entries.values().peekable(); + loop { + match (self_repos.peek(), other_repos.peek()) { + (Some(self_repo), Some(other_repo)) => { + match Ord::cmp(&self_repo.work_directory, &other_repo.work_directory) { + Ordering::Less => { + updated_repositories.push((*self_repo).into()); + self_repos.next(); + } + Ordering::Equal => { + if self_repo.scan_id != other_repo.scan_id { + updated_repositories.push((*self_repo).into()); + } + + self_repos.next(); + other_repos.next(); + } + Ordering::Greater => { + removed_repositories.push(other_repo.dot_git_entry_id.to_proto()); + other_repos.next(); + } + } + } + (Some(self_repo), None) => { + updated_repositories.push((*self_repo).into()); + self_repos.next(); + } + (None, Some(other_repo)) => { + removed_repositories.push(other_repo.dot_git_entry_id.to_proto()); + other_repos.next(); + } + (None, None) => break, + } + } + proto::UpdateWorktree { project_id, worktree_id, @@ -1624,8 +1700,8 @@ impl LocalSnapshot { removed_entries, scan_id: self.scan_id as u64, is_last_update: self.completed_scan_id == self.scan_id, - // TODO repo - updated_repositories: vec![], + updated_repositories, + removed_repositories, } } @@ -1724,8 +1800,7 @@ impl LocalSnapshot { self.repository_entries.insert( key.clone(), RepositoryEntry { - git_dir_path: parent_path.clone(), - git_dir_entry_id: parent_entry.id, + dot_git_entry_id: parent_entry.id, work_directory: key, scan_id: 0, branch: repo_lock.branch_name().map(Into::into), @@ -1733,7 +1808,13 @@ impl LocalSnapshot { ); drop(repo_lock); - self.git_repositories.insert(parent_entry.id, repo) + self.git_repositories.insert( + parent_entry.id, + LocalRepositoryEntry { + repo_ptr: repo, + git_dir_path: parent_path.clone(), + }, + ) } } } @@ -2424,11 +2505,15 @@ impl BackgroundScanner { let mut snapshot = self.snapshot.lock(); let mut git_repositories = mem::take(&mut snapshot.git_repositories); - git_repositories.retain(|project_entry_id, _| snapshot.contains_entry(*project_entry_id)); + git_repositories.retain(|project_entry_id, _| { + snapshot + .entry_for_id(*project_entry_id) + .map_or(false, |entry| entry.path.file_name() == Some(&DOT_GIT)) + }); snapshot.git_repositories = git_repositories; let mut git_repository_entries = mem::take(&mut snapshot.snapshot.repository_entries); - git_repository_entries.retain(|_, entry| snapshot.contains_entry(entry.git_dir_entry_id)); + git_repository_entries.retain(|_, entry| snapshot.contains_entry(entry.dot_git_entry_id)); snapshot.snapshot.repository_entries = git_repository_entries; snapshot.removed_entry_ids.clear(); @@ -3509,12 +3594,21 @@ mod tests { let entry = tree.repo_for("dir1/src/b.txt".as_ref()).unwrap(); assert_eq!(entry.work_directory.0.as_ref(), Path::new("dir1")); - assert_eq!(entry.git_dir_path.as_ref(), Path::new("dir1/.git")); + assert_eq!( + tree.entry_for_id(entry.dot_git_entry_id) + .unwrap() + .path + .as_ref(), + Path::new("dir1/.git") + ); let entry = tree.repo_for("dir1/deps/dep1/src/a.txt".as_ref()).unwrap(); assert_eq!(entry.work_directory.deref(), Path::new("dir1/deps/dep1")); assert_eq!( - entry.git_dir_path.as_ref(), + tree.entry_for_id(entry.dot_git_entry_id) + .unwrap() + .path + .as_ref(), Path::new("dir1/deps/dep1/.git"), ); }); @@ -3552,11 +3646,10 @@ mod tests { #[test] fn test_changed_repos() { - fn fake_entry(git_dir_path: impl AsRef, scan_id: usize) -> RepositoryEntry { + fn fake_entry(dot_git_id: usize, scan_id: usize) -> RepositoryEntry { RepositoryEntry { scan_id, - git_dir_path: git_dir_path.as_ref().into(), - git_dir_entry_id: ProjectEntryId(0), + dot_git_entry_id: ProjectEntryId(dot_git_id), work_directory: RepositoryWorkDirectory( Path::new(&format!("don't-care-{}", scan_id)).into(), ), @@ -3567,29 +3660,29 @@ mod tests { let mut prev_repos = TreeMap::::default(); prev_repos.insert( RepositoryWorkDirectory(Path::new("don't-care-1").into()), - fake_entry("/.git", 0), + fake_entry(1, 0), ); prev_repos.insert( RepositoryWorkDirectory(Path::new("don't-care-2").into()), - fake_entry("/a/.git", 0), + fake_entry(2, 0), ); prev_repos.insert( RepositoryWorkDirectory(Path::new("don't-care-3").into()), - fake_entry("/a/b/.git", 0), + fake_entry(3, 0), ); let mut new_repos = TreeMap::::default(); new_repos.insert( RepositoryWorkDirectory(Path::new("don't-care-4").into()), - fake_entry("/a/.git", 1), + fake_entry(2, 1), ); new_repos.insert( RepositoryWorkDirectory(Path::new("don't-care-5").into()), - fake_entry("/a/b/.git", 0), + fake_entry(3, 0), ); new_repos.insert( RepositoryWorkDirectory(Path::new("don't-care-6").into()), - fake_entry("/a/c/.git", 0), + fake_entry(4, 0), ); let res = LocalWorktree::changed_repos(&prev_repos, &new_repos); @@ -3597,25 +3690,25 @@ mod tests { // Deletion retained assert!(res .iter() - .find(|repo| repo.git_dir_path.as_ref() == Path::new("/.git") && repo.scan_id == 0) + .find(|repo| repo.dot_git_entry_id.0 == 1 && repo.scan_id == 0) .is_some()); // Update retained assert!(res .iter() - .find(|repo| repo.git_dir_path.as_ref() == Path::new("/a/.git") && repo.scan_id == 1) + .find(|repo| repo.dot_git_entry_id.0 == 2 && repo.scan_id == 1) .is_some()); // Addition retained assert!(res .iter() - .find(|repo| repo.git_dir_path.as_ref() == Path::new("/a/c/.git") && repo.scan_id == 0) + .find(|repo| repo.dot_git_entry_id.0 == 4 && repo.scan_id == 0) .is_some()); // Nochange, not retained assert!(res .iter() - .find(|repo| repo.git_dir_path.as_ref() == Path::new("/a/b/.git") && repo.scan_id == 0) + .find(|repo| repo.dot_git_entry_id.0 == 3 && repo.scan_id == 0) .is_none()); } diff --git a/crates/rpc/proto/zed.proto b/crates/rpc/proto/zed.proto index 179452296bd5f0b7686fed46adf5c43ef28668d0..37092dea4d20e7b7e10bc18b6f88974e139532de 100644 --- a/crates/rpc/proto/zed.proto +++ b/crates/rpc/proto/zed.proto @@ -330,9 +330,10 @@ message UpdateWorktree { repeated Entry updated_entries = 4; repeated uint64 removed_entries = 5; repeated RepositoryEntry updated_repositories = 6; - uint64 scan_id = 7; - bool is_last_update = 8; - string abs_path = 9; + repeated uint64 removed_repositories = 7; + uint64 scan_id = 8; + bool is_last_update = 9; + string abs_path = 10; } message CreateProjectEntry { @@ -981,10 +982,9 @@ message Entry { } message RepositoryEntry { - uint64 git_dir_entry_id = 1; + uint64 dot_git_entry_id = 1; uint64 scan_id = 2; - bytes git_dir_path = 3; - bytes work_directory = 4; + string work_directory = 4; optional string branch = 5; } diff --git a/crates/rpc/src/proto.rs b/crates/rpc/src/proto.rs index b410d0cb83892ec2f7c33b8155c5d2ec98f0a2b6..14ab916f6527dfe49296d9b2e72b888b060796e7 100644 --- a/crates/rpc/src/proto.rs +++ b/crates/rpc/src/proto.rs @@ -509,6 +509,13 @@ pub fn split_worktree_update( .drain(..updated_repositories_chunk_size) .collect(); + let removed_repositories_chunk_size = + cmp::min(message.removed_repositories.len(), max_chunk_size); + let removed_repositories = message + .removed_repositories + .drain(..removed_repositories_chunk_size) + .collect(); + done = message.updated_entries.is_empty() && message.removed_entries.is_empty(); Some(UpdateWorktree { project_id: message.project_id, @@ -520,6 +527,7 @@ pub fn split_worktree_update( scan_id: message.scan_id, is_last_update: done && message.is_last_update, updated_repositories, + removed_repositories, }) }) }