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

@ -9,10 +9,7 @@ use std::{
use anyhow::{anyhow, Context as _};
use collections::{BTreeMap, HashMap};
use feature_flags::FeatureFlagAppExt;
use git::{
diff::{BufferDiff, DiffHunk},
repository::GitFileStatus,
};
use git::diff::{BufferDiff, DiffHunk};
use gpui::{
actions, AnyElement, AnyView, AppContext, EventEmitter, FocusHandle, FocusableView,
InteractiveElement, Model, Render, Subscription, Task, View, WeakView,
@ -54,7 +51,6 @@ struct ProjectDiffEditor {
#[derive(Debug)]
struct Changes {
_status: GitFileStatus,
buffer: Model<Buffer>,
hunks: Vec<DiffHunk>,
}
@ -199,14 +195,13 @@ impl ProjectDiffEditor {
.repositories()
.iter()
.flat_map(|entry| {
entry.status().map(|git_entry| {
(git_entry.combined_status(), entry.join(git_entry.repo_path))
})
entry
.status()
.map(|git_entry| entry.join(git_entry.repo_path))
})
.filter_map(|(status, path)| {
.filter_map(|path| {
let id = snapshot.entry_for_path(&path)?.id;
Some((
status,
id,
ProjectPath {
worktree_id: snapshot.id(),
@ -218,9 +213,9 @@ impl ProjectDiffEditor {
Some(
applicable_entries
.into_iter()
.map(|(status, entry_id, entry_path)| {
.map(|(entry_id, entry_path)| {
let open_task = project.open_path(entry_path.clone(), cx);
(status, entry_id, entry_path, open_task)
(entry_id, entry_path, open_task)
})
.collect::<Vec<_>>(),
)
@ -234,15 +229,10 @@ impl ProjectDiffEditor {
let mut new_entries = Vec::new();
let mut buffers = HashMap::<
ProjectEntryId,
(
GitFileStatus,
text::BufferSnapshot,
Model<Buffer>,
BufferDiff,
),
(text::BufferSnapshot, Model<Buffer>, BufferDiff),
>::default();
let mut change_sets = Vec::new();
for (status, entry_id, entry_path, open_task) in open_tasks {
for (entry_id, entry_path, open_task) in open_tasks {
let Some(buffer) = open_task
.await
.and_then(|(_, opened_model)| {
@ -272,7 +262,6 @@ impl ProjectDiffEditor {
buffers.insert(
entry_id,
(
status,
buffer.read(cx).text_snapshot(),
buffer,
change_set.read(cx).diff_to_buffer.clone(),
@ -295,11 +284,10 @@ impl ProjectDiffEditor {
.background_executor()
.spawn(async move {
let mut new_changes = HashMap::<ProjectEntryId, Changes>::default();
for (entry_id, (status, buffer_snapshot, buffer, buffer_diff)) in buffers {
for (entry_id, (buffer_snapshot, buffer, buffer_diff)) in buffers {
new_changes.insert(
entry_id,
Changes {
_status: status,
buffer,
hunks: buffer_diff
.hunks_in_row_range(0..BufferRow::MAX, &buffer_snapshot)
@ -1107,6 +1095,7 @@ impl Render for ProjectDiffEditor {
#[cfg(test)]
mod tests {
use git::status::{StatusCode, TrackedStatus};
use gpui::{SemanticVersion, TestAppContext, VisualTestContext};
use project::buffer_store::BufferChangeSet;
use serde_json::json;
@ -1224,7 +1213,14 @@ mod tests {
});
fs.set_status_for_repo_via_git_operation(
Path::new("/root/.git"),
&[(Path::new("file_a"), GitFileStatus::Modified)],
&[(
Path::new("file_a"),
TrackedStatus {
worktree_status: StatusCode::Modified,
index_status: StatusCode::Unmodified,
}
.into(),
)],
);
cx.executor()
.advance_clock(UPDATE_DEBOUNCE + Duration::from_millis(100));