Add integration test for git status

Mikayla Maki created

Change summary

crates/collab/migrations.sqlite/20221109000000_test_schema.sql |  19 
crates/collab/src/db.rs                                        | 134 +++
crates/collab/src/rpc.rs                                       |   2 
crates/collab/src/tests/integration_tests.rs                   |   2 
4 files changed, 148 insertions(+), 9 deletions(-)

Detailed changes

crates/collab/migrations.sqlite/20221109000000_test_schema.sql 🔗

@@ -86,8 +86,8 @@ CREATE TABLE "worktree_repositories" (
     "project_id" INTEGER NOT NULL,
     "worktree_id" INTEGER NOT NULL,
     "work_directory_id" INTEGER NOT NULL,
-    "scan_id" INTEGER NOT NULL,
     "branch" VARCHAR,
+    "scan_id" INTEGER NOT NULL,
     "is_deleted" BOOL NOT NULL,
     PRIMARY KEY(project_id, worktree_id, work_directory_id),
     FOREIGN KEY(project_id, worktree_id) REFERENCES worktrees (project_id, id) ON DELETE CASCADE,
@@ -96,6 +96,23 @@ CREATE TABLE "worktree_repositories" (
 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_repository_statuses" (
+    "project_id" INTEGER NOT NULL,
+    "worktree_id" INTEGER NOT NULL,
+    "work_directory_id" INTEGER NOT NULL,
+    "repo_path" VARCHAR NOT NULL,
+    "status" INTEGER NOT NULL,
+    "scan_id" INTEGER NOT NULL,
+    "is_deleted" BOOL NOT NULL,
+    PRIMARY KEY(project_id, worktree_id, work_directory_id, repo_path),
+    FOREIGN KEY(project_id, worktree_id) REFERENCES worktrees (project_id, id) ON DELETE CASCADE,
+    FOREIGN KEY(project_id, worktree_id, work_directory_id) REFERENCES worktree_entries (project_id, worktree_id, id) ON DELETE CASCADE
+);
+CREATE INDEX "index_worktree_repository_statuses_on_project_id" ON "worktree_repository_statuses" ("project_id");
+CREATE INDEX "index_worktree_repository_statuses_on_project_id_and_worktree_id" ON "worktree_repository_statuses" ("project_id", "worktree_id");
+CREATE INDEX "index_worktree_repository_statuses_on_project_id_and_worktree_id_and_work_directory_id" ON "worktree_repository_statuses" ("project_id", "worktree_id", "work_directory_id");
+
+
 CREATE TABLE "worktree_diagnostic_summaries" (
     "project_id" INTEGER NOT NULL,
     "worktree_id" INTEGER NOT NULL,

crates/collab/src/db.rs 🔗

@@ -15,6 +15,7 @@ mod worktree;
 mod worktree_diagnostic_summary;
 mod worktree_entry;
 mod worktree_repository;
+mod worktree_repository_statuses;
 
 use crate::executor::Executor;
 use crate::{Error, Result};
@@ -2397,6 +2398,74 @@ impl Database {
                 )
                 .exec(&*tx)
                 .await?;
+
+                for repository in update.updated_repositories.iter() {
+                    if !repository.updated_worktree_statuses.is_empty() {
+                        worktree_repository_statuses::Entity::insert_many(
+                            repository
+                                .updated_worktree_statuses
+                                .iter()
+                                .map(|status_entry| worktree_repository_statuses::ActiveModel {
+                                    project_id: ActiveValue::set(project_id),
+                                    worktree_id: ActiveValue::set(worktree_id),
+                                    work_directory_id: ActiveValue::set(
+                                        repository.work_directory_id as i64,
+                                    ),
+                                    repo_path: ActiveValue::set(status_entry.repo_path.clone()),
+                                    status: ActiveValue::set(status_entry.status as i64),
+                                    scan_id: ActiveValue::set(update.scan_id as i64),
+                                    is_deleted: ActiveValue::set(false),
+                                }),
+                        )
+                        .on_conflict(
+                            OnConflict::columns([
+                                worktree_repository_statuses::Column::ProjectId,
+                                worktree_repository_statuses::Column::WorktreeId,
+                                worktree_repository_statuses::Column::WorkDirectoryId,
+                                worktree_repository_statuses::Column::RepoPath,
+                            ])
+                            .update_columns([
+                                worktree_repository_statuses::Column::ScanId,
+                                worktree_repository_statuses::Column::Status,
+                            ])
+                            .to_owned(),
+                        )
+                        .exec(&*tx)
+                        .await?;
+                    }
+
+                    if !repository.removed_worktree_repo_paths.is_empty() {
+                        worktree_repository_statuses::Entity::update_many()
+                            .filter(
+                                worktree_repository_statuses::Column::ProjectId
+                                    .eq(project_id)
+                                    .and(
+                                        worktree_repository_statuses::Column::WorktreeId
+                                            .eq(worktree_id),
+                                    )
+                                    .and(
+                                        worktree_repository_statuses::Column::WorkDirectoryId
+                                            .eq(repository.work_directory_id),
+                                    )
+                                    .and(
+                                        worktree_repository_statuses::Column::RepoPath.is_in(
+                                            repository
+                                                .removed_worktree_repo_paths
+                                                .iter()
+                                                .cloned()
+                                                .collect::<Vec<_>>(),
+                                        ),
+                                    ),
+                            )
+                            .set(worktree_repository_statuses::ActiveModel {
+                                is_deleted: ActiveValue::Set(true),
+                                scan_id: ActiveValue::Set(update.scan_id as i64),
+                                ..Default::default()
+                            })
+                            .exec(&*tx)
+                            .await?;
+                    }
+                }
             }
 
             if !update.removed_repositories.is_empty() {
@@ -2417,6 +2486,25 @@ impl Database {
                     })
                     .exec(&*tx)
                     .await?;
+
+                // Flip all status entries associated with a given repository_entry
+                worktree_repository_statuses::Entity::update_many()
+                    .filter(
+                        worktree_repository_statuses::Column::ProjectId
+                            .eq(project_id)
+                            .and(worktree_repository_statuses::Column::WorktreeId.eq(worktree_id))
+                            .and(
+                                worktree_repository_statuses::Column::WorkDirectoryId
+                                    .is_in(update.removed_repositories.iter().map(|id| *id as i64)),
+                            ),
+                    )
+                    .set(worktree_repository_statuses::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?;
@@ -2647,12 +2735,44 @@ impl Database {
                     if let Some(worktree) =
                         worktrees.get_mut(&(db_repository_entry.worktree_id as u64))
                     {
-                        worktree.repository_entries.push(proto::RepositoryEntry {
-                            work_directory_id: db_repository_entry.work_directory_id as u64,
-                            branch: db_repository_entry.branch,
-                            removed_worktree_repo_paths: Default::default(),
-                            updated_worktree_statuses: Default::default(),
-                        });
+                        worktree.repository_entries.insert(
+                            db_repository_entry.work_directory_id as u64,
+                            proto::RepositoryEntry {
+                                work_directory_id: db_repository_entry.work_directory_id as u64,
+                                branch: db_repository_entry.branch,
+                                removed_worktree_repo_paths: Default::default(),
+                                updated_worktree_statuses: Default::default(),
+                            },
+                        );
+                    }
+                }
+            }
+
+            {
+                let mut db_status_entries = worktree_repository_statuses::Entity::find()
+                    .filter(
+                        Condition::all()
+                            .add(worktree_repository_statuses::Column::ProjectId.eq(project_id))
+                            .add(worktree_repository_statuses::Column::IsDeleted.eq(false)),
+                    )
+                    .stream(&*tx)
+                    .await?;
+
+                while let Some(db_status_entry) = db_status_entries.next().await {
+                    let db_status_entry = db_status_entry?;
+                    if let Some(worktree) = worktrees.get_mut(&(db_status_entry.worktree_id as u64))
+                    {
+                        if let Some(repository_entry) = worktree
+                            .repository_entries
+                            .get_mut(&(db_status_entry.work_directory_id as u64))
+                        {
+                            repository_entry
+                                .updated_worktree_statuses
+                                .push(proto::StatusEntry {
+                                    repo_path: db_status_entry.repo_path,
+                                    status: db_status_entry.status as i32,
+                                });
+                        }
                     }
                 }
             }
@@ -3394,7 +3514,7 @@ pub struct Worktree {
     pub root_name: String,
     pub visible: bool,
     pub entries: Vec<proto::Entry>,
-    pub repository_entries: Vec<proto::RepositoryEntry>,
+    pub repository_entries: BTreeMap<u64, proto::RepositoryEntry>,
     pub diagnostic_summaries: Vec<proto::DiagnosticSummary>,
     pub scan_id: u64,
     pub completed_scan_id: u64,

crates/collab/src/rpc.rs 🔗

@@ -1385,7 +1385,7 @@ async fn join_project(
             removed_entries: Default::default(),
             scan_id: worktree.scan_id,
             is_last_update: worktree.scan_id == worktree.completed_scan_id,
-            updated_repositories: worktree.repository_entries,
+            updated_repositories: worktree.repository_entries.into_values().collect(),
             removed_repositories: Default::default(),
         };
         for update in proto::split_worktree_update(message, MAX_CHUNK_SIZE) {

crates/collab/src/tests/integration_tests.rs 🔗

@@ -2820,6 +2820,8 @@ async fn test_git_status_sync(
 
     // And synchronization while joining
     let project_remote_c = client_c.build_remote_project(project_id, cx_c).await;
+    deterministic.run_until_parked();
+
     project_remote_c.read_with(cx_c, |project, cx| {
         assert_status(
             &Path::new(A_TXT),