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

@ -1,4 +1,4 @@
use crate::status::GitStatusPair;
use crate::status::FileStatus;
use crate::GitHostingProviderRegistry;
use crate::{blame::Blame, status::GitStatus};
use anyhow::{anyhow, Context, Result};
@ -7,7 +7,6 @@ use git2::BranchType;
use gpui::SharedString;
use parking_lot::Mutex;
use rope::Rope;
use serde::{Deserialize, Serialize};
use std::borrow::Borrow;
use std::sync::LazyLock;
use std::{
@ -294,7 +293,7 @@ pub struct FakeGitRepositoryState {
pub event_emitter: smol::channel::Sender<PathBuf>,
pub index_contents: HashMap<PathBuf, String>,
pub blames: HashMap<PathBuf, Blame>,
pub worktree_statuses: HashMap<RepoPath, GitFileStatus>,
pub statuses: HashMap<RepoPath, FileStatus>,
pub current_branch_name: Option<String>,
pub branches: HashSet<String>,
}
@ -312,7 +311,7 @@ impl FakeGitRepositoryState {
event_emitter,
index_contents: Default::default(),
blames: Default::default(),
worktree_statuses: Default::default(),
statuses: Default::default(),
current_branch_name: Default::default(),
branches: Default::default(),
}
@ -349,20 +348,14 @@ impl GitRepository for FakeGitRepository {
let state = self.state.lock();
let mut entries = state
.worktree_statuses
.statuses
.iter()
.filter_map(|(repo_path, status_worktree)| {
.filter_map(|(repo_path, status)| {
if path_prefixes
.iter()
.any(|path_prefix| repo_path.0.starts_with(path_prefix))
{
Some((
repo_path.to_owned(),
GitStatusPair {
index_status: None,
worktree_status: Some(*status_worktree),
},
))
Some((repo_path.to_owned(), *status))
} else {
None
}
@ -461,51 +454,6 @@ fn check_path_to_repo_path_errors(relative_file_path: &Path) -> Result<()> {
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum GitFileStatus {
Added,
Modified,
// TODO conflicts should be represented by the GitStatusPair
Conflict,
Deleted,
Untracked,
}
impl GitFileStatus {
pub fn merge(
this: Option<GitFileStatus>,
other: Option<GitFileStatus>,
prefer_other: bool,
) -> Option<GitFileStatus> {
if prefer_other {
return other;
}
match (this, other) {
(Some(GitFileStatus::Conflict), _) | (_, Some(GitFileStatus::Conflict)) => {
Some(GitFileStatus::Conflict)
}
(Some(GitFileStatus::Modified), _) | (_, Some(GitFileStatus::Modified)) => {
Some(GitFileStatus::Modified)
}
(Some(GitFileStatus::Added), _) | (_, Some(GitFileStatus::Added)) => {
Some(GitFileStatus::Added)
}
_ => None,
}
}
pub fn from_byte(byte: u8) -> Option<Self> {
match byte {
b'M' => Some(GitFileStatus::Modified),
b'A' => Some(GitFileStatus::Added),
b'D' => Some(GitFileStatus::Deleted),
b'?' => Some(GitFileStatus::Untracked),
_ => None,
}
}
}
pub static WORK_DIRECTORY_REPO_PATH: LazyLock<RepoPath> =
LazyLock::new(|| RepoPath(Path::new("").into()));