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:
co-authored by
Mikayla
Conrad
parent
224f3d4746
commit
a41d72ee81
@@ -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));
|
||||
|
||||
+12
-16
@@ -9,7 +9,7 @@ use anyhow::{anyhow, Context as _, Result};
|
||||
use collections::HashSet;
|
||||
use file_icons::FileIcons;
|
||||
use futures::future::try_join_all;
|
||||
use git::repository::GitFileStatus;
|
||||
use git::status::GitSummary;
|
||||
use gpui::{
|
||||
point, AnyElement, AppContext, AsyncWindowContext, Context, Entity, EntityId, EventEmitter,
|
||||
IntoElement, Model, ParentElement, Pixels, SharedString, Styled, Task, View, ViewContext,
|
||||
@@ -27,8 +27,6 @@ use project::{
|
||||
};
|
||||
use rpc::proto::{self, update_view, PeerId};
|
||||
use settings::Settings;
|
||||
use workspace::item::{Dedup, ItemSettings, SerializableItem, TabContentParams};
|
||||
|
||||
use std::{
|
||||
any::TypeId,
|
||||
borrow::Cow,
|
||||
@@ -43,6 +41,7 @@ use theme::{Theme, ThemeSettings};
|
||||
use ui::{h_flex, prelude::*, IconDecorationKind, Label};
|
||||
use util::{paths::PathExt, ResultExt, TryFutureExt};
|
||||
use workspace::item::{BreadcrumbText, FollowEvent};
|
||||
use workspace::item::{Dedup, ItemSettings, SerializableItem, TabContentParams};
|
||||
use workspace::{
|
||||
item::{FollowableItem, Item, ItemEvent, ProjectItem},
|
||||
searchable::{Direction, SearchEvent, SearchableItem, SearchableItemHandle},
|
||||
@@ -621,10 +620,10 @@ impl Item for Editor {
|
||||
.worktree_for_id(path.worktree_id, cx)?
|
||||
.read(cx)
|
||||
.snapshot()
|
||||
.status_for_file(path.path);
|
||||
.status_for_file(path.path)?;
|
||||
|
||||
Some(entry_git_aware_label_color(
|
||||
git_status,
|
||||
git_status.summary(),
|
||||
entry.is_ignored,
|
||||
params.selected,
|
||||
))
|
||||
@@ -1560,20 +1559,17 @@ pub fn entry_diagnostic_aware_icon_decoration_and_color(
|
||||
}
|
||||
}
|
||||
|
||||
pub fn entry_git_aware_label_color(
|
||||
git_status: Option<GitFileStatus>,
|
||||
ignored: bool,
|
||||
selected: bool,
|
||||
) -> Color {
|
||||
pub fn entry_git_aware_label_color(git_status: GitSummary, ignored: bool, selected: bool) -> Color {
|
||||
if ignored {
|
||||
Color::Ignored
|
||||
} else if git_status.conflict > 0 {
|
||||
Color::Conflict
|
||||
} else if git_status.modified > 0 {
|
||||
Color::Modified
|
||||
} else if git_status.added > 0 || git_status.untracked > 0 {
|
||||
Color::Created
|
||||
} else {
|
||||
match git_status {
|
||||
Some(GitFileStatus::Added) | Some(GitFileStatus::Untracked) => Color::Created,
|
||||
Some(GitFileStatus::Modified) => Color::Modified,
|
||||
Some(GitFileStatus::Conflict) => Color::Conflict,
|
||||
Some(GitFileStatus::Deleted) | None => entry_label_color(selected),
|
||||
}
|
||||
entry_label_color(selected)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user