Represent git statuses more faithfully (#23082)

First, parse the output of `git status --porcelain=v1` into a
representation that can handle the full "grammar" and doesn't lose
information.

Second, as part of pushing this throughout the codebase, expand the use
of the existing `GitSummary` type to all the places where status
propagation is in play (i.e., anywhere we're dealing with a mix of files
and directories), and get rid of the previous `GitSummary ->
GitFileStatus` conversion.

- [x] Synchronize new representation over collab
  - [x] Update zed.proto
  - [x] Update DB models
- [x] Update `GitSummary` and summarization for the new `FileStatus`
- [x] Fix all tests
  - [x] worktree
  - [x] collab
- [x] Clean up `FILE_*` constants
- [x] New collab tests to exercise syncing of complex statuses
- [x] Run it locally and make sure it looks good

Release Notes:

- N/A

---------

Co-authored-by: Mikayla <mikayla@zed.dev>
Co-authored-by: Conrad <conrad@zed.dev>
This commit is contained in:
Cole Miller 2025-01-15 19:01:38 -05:00 committed by GitHub
parent 224f3d4746
commit a41d72ee81
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
24 changed files with 1015 additions and 552 deletions

View file

@ -360,6 +360,8 @@ impl Database {
update.updated_repositories.iter().flat_map(
|repository: &proto::RepositoryEntry| {
repository.updated_statuses.iter().map(|status_entry| {
let (repo_path, status_kind, first_status, second_status) =
proto_status_to_db(status_entry.clone());
worktree_repository_statuses::ActiveModel {
project_id: ActiveValue::set(project_id),
worktree_id: ActiveValue::set(worktree_id),
@ -368,8 +370,11 @@ impl Database {
),
scan_id: ActiveValue::set(update.scan_id as i64),
is_deleted: ActiveValue::set(false),
repo_path: ActiveValue::set(status_entry.repo_path.clone()),
status: ActiveValue::set(status_entry.status as i64),
repo_path: ActiveValue::set(repo_path),
status: ActiveValue::set(0),
status_kind: ActiveValue::set(status_kind),
first_status: ActiveValue::set(first_status),
second_status: ActiveValue::set(second_status),
}
})
},
@ -384,7 +389,9 @@ impl Database {
])
.update_columns([
worktree_repository_statuses::Column::ScanId,
worktree_repository_statuses::Column::Status,
worktree_repository_statuses::Column::StatusKind,
worktree_repository_statuses::Column::FirstStatus,
worktree_repository_statuses::Column::SecondStatus,
])
.to_owned(),
)
@ -759,10 +766,7 @@ impl Database {
let mut updated_statuses = Vec::new();
while let Some(status_entry) = repository_statuses.next().await {
let status_entry: worktree_repository_statuses::Model = status_entry?;
updated_statuses.push(proto::StatusEntry {
repo_path: status_entry.repo_path,
status: status_entry.status as i32,
});
updated_statuses.push(db_status_to_proto(status_entry)?);
}
worktree.repository_entries.insert(

View file

@ -732,10 +732,7 @@ impl Database {
if db_status.is_deleted {
removed_statuses.push(db_status.repo_path);
} else {
updated_statuses.push(proto::StatusEntry {
repo_path: db_status.repo_path,
status: db_status.status as i32,
});
updated_statuses.push(db_status_to_proto(db_status)?);
}
}

View file

@ -12,11 +12,26 @@ pub struct Model {
pub work_directory_id: i64,
#[sea_orm(primary_key)]
pub repo_path: String,
/// Old single-code status field, no longer used but kept here to mirror the DB schema.
pub status: i64,
pub status_kind: StatusKind,
/// For unmerged entries, this is the `first_head` status. For tracked entries, this is the `index_status`.
pub first_status: Option<i32>,
/// For unmerged entries, this is the `second_head` status. For tracked entries, this is the `worktree_status`.
pub second_status: Option<i32>,
pub scan_id: i64,
pub is_deleted: bool,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, EnumIter, DeriveActiveEnum)]
#[sea_orm(rs_type = "i32", db_type = "Integer")]
pub enum StatusKind {
Untracked = 0,
Ignored = 1,
Unmerged = 2,
Tracked = 3,
}
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
pub enum Relation {}