diff --git a/crates/collab/src/db.rs b/crates/collab/src/db.rs index cc85d4f369..e881121758 100644 --- a/crates/collab/src/db.rs +++ b/crates/collab/src/db.rs @@ -1576,6 +1576,45 @@ impl Database { } } + // Repository Status Entries + for repository in worktree.updated_repositories.iter_mut() { + let repository_status_entry_filter = + if let Some(rejoined_worktree) = rejoined_worktree { + worktree_repository_statuses::Column::ScanId + .gt(rejoined_worktree.scan_id) + } else { + worktree_repository_statuses::Column::IsDeleted.eq(false) + }; + + let mut db_repository_statuses = + worktree_repository_statuses::Entity::find() + .filter( + Condition::all() + .add( + worktree_repository_statuses::Column::WorktreeId + .eq(worktree.id), + ) + .add( + worktree_repository_statuses::Column::WorkDirectoryId + .eq(repository.work_directory_id), + ) + .add(repository_status_entry_filter), + ) + .stream(&*tx) + .await?; + + while let Some(db_status_entry) = db_repository_statuses.next().await { + let db_status_entry = db_status_entry?; + if db_status_entry.is_deleted { + repository.removed_worktree_repo_paths.push(db_status_entry.repo_path); + } else { + repository.updated_worktree_statuses.push(proto::StatusEntry { + repo_path: db_status_entry.repo_path, status: db_status_entry.status as i32 + }); + } + } + } + worktrees.push(worktree); } diff --git a/crates/collab/src/tests/randomized_integration_tests.rs b/crates/collab/src/tests/randomized_integration_tests.rs index c4326be101..d5ed47675a 100644 --- a/crates/collab/src/tests/randomized_integration_tests.rs +++ b/crates/collab/src/tests/randomized_integration_tests.rs @@ -8,7 +8,7 @@ use call::ActiveCall; use client::RECEIVE_TIMEOUT; use collections::BTreeMap; use editor::Bias; -use fs::{FakeFs, Fs as _}; +use fs::{repository::GitFileStatus, FakeFs, Fs as _}; use futures::StreamExt as _; use gpui::{executor::Deterministic, ModelHandle, Task, TestAppContext}; use language::{range_to_lsp, FakeLspAdapter, Language, LanguageConfig, PointUtf16}; @@ -32,6 +32,7 @@ use std::{ }, }; use util::ResultExt; +use pretty_assertions::assert_eq; lazy_static::lazy_static! { static ref PLAN_LOAD_PATH: Option = path_env_var("LOAD_PLAN"); @@ -763,53 +764,81 @@ async fn apply_client_operation( } } - ClientOperation::WriteGitIndex { - repo_path, - contents, - } => { - if !client.fs.directories().contains(&repo_path) { - return Err(TestError::Inapplicable); - } - - log::info!( - "{}: writing git index for repo {:?}: {:?}", - client.username, + ClientOperation::GitOperation { operation } => match operation { + GitOperation::WriteGitIndex { repo_path, - contents - ); + contents, + } => { + if !client.fs.directories().contains(&repo_path) { + return Err(TestError::Inapplicable); + } - let dot_git_dir = repo_path.join(".git"); - let contents = contents - .iter() - .map(|(path, contents)| (path.as_path(), contents.clone())) - .collect::>(); - if client.fs.metadata(&dot_git_dir).await?.is_none() { - client.fs.create_dir(&dot_git_dir).await?; + log::info!( + "{}: writing git index for repo {:?}: {:?}", + client.username, + repo_path, + contents + ); + + let dot_git_dir = repo_path.join(".git"); + let contents = contents + .iter() + .map(|(path, contents)| (path.as_path(), contents.clone())) + .collect::>(); + if client.fs.metadata(&dot_git_dir).await?.is_none() { + client.fs.create_dir(&dot_git_dir).await?; + } + client.fs.set_index_for_repo(&dot_git_dir, &contents).await; } - client.fs.set_index_for_repo(&dot_git_dir, &contents).await; - } - - ClientOperation::WriteGitBranch { - repo_path, - new_branch, - } => { - if !client.fs.directories().contains(&repo_path) { - return Err(TestError::Inapplicable); - } - - log::info!( - "{}: writing git branch for repo {:?}: {:?}", - client.username, + GitOperation::WriteGitBranch { repo_path, - new_branch - ); + new_branch, + } => { + if !client.fs.directories().contains(&repo_path) { + return Err(TestError::Inapplicable); + } - let dot_git_dir = repo_path.join(".git"); - if client.fs.metadata(&dot_git_dir).await?.is_none() { - client.fs.create_dir(&dot_git_dir).await?; + log::info!( + "{}: writing git branch for repo {:?}: {:?}", + client.username, + repo_path, + new_branch + ); + + let dot_git_dir = repo_path.join(".git"); + if client.fs.metadata(&dot_git_dir).await?.is_none() { + client.fs.create_dir(&dot_git_dir).await?; + } + client.fs.set_branch_name(&dot_git_dir, new_branch).await; } - client.fs.set_branch_name(&dot_git_dir, new_branch).await; - } + GitOperation::WriteGitStatuses { + repo_path, + statuses, + } => { + if !client.fs.directories().contains(&repo_path) { + return Err(TestError::Inapplicable); + } + + log::info!( + "{}: writing git statuses for repo {:?}: {:?}", + client.username, + repo_path, + statuses + ); + + let dot_git_dir = repo_path.join(".git"); + + let statuses = statuses.iter() + .map(|(path, val)| (path.as_path(), val.clone())) + .collect::>(); + + if client.fs.metadata(&dot_git_dir).await?.is_none() { + client.fs.create_dir(&dot_git_dir).await?; + } + + client.fs.set_status_for_repo(&dot_git_dir, statuses.as_slice()).await; + }, + }, } Ok(()) } @@ -1178,6 +1207,13 @@ enum ClientOperation { is_dir: bool, content: String, }, + GitOperation { + operation: GitOperation, + }, +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +enum GitOperation { WriteGitIndex { repo_path: PathBuf, contents: Vec<(PathBuf, String)>, @@ -1186,6 +1222,10 @@ enum ClientOperation { repo_path: PathBuf, new_branch: Option, }, + WriteGitStatuses { + repo_path: PathBuf, + statuses: Vec<(PathBuf, GitFileStatus)>, + }, } #[derive(Clone, Debug, Serialize, Deserialize)] @@ -1698,57 +1738,10 @@ impl TestPlan { } } - // Update a git index - 91..=93 => { - let repo_path = client - .fs - .directories() - .into_iter() - .choose(&mut self.rng) - .unwrap() - .clone(); - - let mut file_paths = client - .fs - .files() - .into_iter() - .filter(|path| path.starts_with(&repo_path)) - .collect::>(); - let count = self.rng.gen_range(0..=file_paths.len()); - file_paths.shuffle(&mut self.rng); - file_paths.truncate(count); - - let mut contents = Vec::new(); - for abs_child_file_path in &file_paths { - let child_file_path = abs_child_file_path - .strip_prefix(&repo_path) - .unwrap() - .to_path_buf(); - let new_base = Alphanumeric.sample_string(&mut self.rng, 16); - contents.push((child_file_path, new_base)); - } - - break ClientOperation::WriteGitIndex { - repo_path, - contents, - }; - } - - // Update a git branch - 94..=95 => { - let repo_path = client - .fs - .directories() - .choose(&mut self.rng) - .unwrap() - .clone(); - - let new_branch = (self.rng.gen_range(0..10) > 3) - .then(|| Alphanumeric.sample_string(&mut self.rng, 8)); - - break ClientOperation::WriteGitBranch { - repo_path, - new_branch, + // Update a git related action + 91..=95 => { + break ClientOperation::GitOperation { + operation: self.generate_git_operation(client), }; } @@ -1786,6 +1779,86 @@ impl TestPlan { }) } + fn generate_git_operation(&mut self, client: &TestClient) -> GitOperation { + fn generate_file_paths( + repo_path: &Path, + rng: &mut StdRng, + client: &TestClient, + ) -> Vec { + let mut paths = client + .fs + .files() + .into_iter() + .filter(|path| path.starts_with(repo_path)) + .collect::>(); + + let count = rng.gen_range(0..=paths.len()); + paths.shuffle(rng); + paths.truncate(count); + + paths + .iter() + .map(|path| path.strip_prefix(repo_path).unwrap().to_path_buf()) + .collect::>() + } + + let repo_path = client + .fs + .directories() + .choose(&mut self.rng) + .unwrap() + .clone(); + + match self.rng.gen_range(0..100_u32) { + 0..=25 => { + let file_paths = generate_file_paths(&repo_path, &mut self.rng, client); + + let contents = file_paths + .into_iter() + .map(|path| (path, Alphanumeric.sample_string(&mut self.rng, 16))) + .collect(); + + GitOperation::WriteGitIndex { + repo_path, + contents, + } + } + 26..=63 => { + let new_branch = (self.rng.gen_range(0..10) > 3) + .then(|| Alphanumeric.sample_string(&mut self.rng, 8)); + + GitOperation::WriteGitBranch { + repo_path, + new_branch, + } + } + 64..=100 => { + let file_paths = generate_file_paths(&repo_path, &mut self.rng, client); + + let statuses = file_paths + .into_iter() + .map(|paths| { + ( + paths, + match self.rng.gen_range(0..3_u32) { + 0 => GitFileStatus::Added, + 1 => GitFileStatus::Modified, + 2 => GitFileStatus::Conflict, + _ => unreachable!(), + }, + ) + }) + .collect::>(); + + GitOperation::WriteGitStatuses { + repo_path, + statuses, + } + } + _ => unreachable!(), + } + } + fn next_root_dir_name(&mut self, user_id: UserId) -> String { let user_ix = self .users diff --git a/crates/fs/src/repository.rs b/crates/fs/src/repository.rs index 90b3761677..6bf0a43230 100644 --- a/crates/fs/src/repository.rs +++ b/crates/fs/src/repository.rs @@ -1,6 +1,7 @@ use anyhow::Result; use collections::HashMap; use parking_lot::Mutex; +use serde_derive::{Serialize, Deserialize}; use std::{ ffi::OsStr, os::unix::prelude::OsStrExt, @@ -183,7 +184,7 @@ fn check_path_to_repo_path_errors(relative_file_path: &Path) -> Result<()> { } } -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub enum GitFileStatus { Added, Modified,