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:
parent
224f3d4746
commit
a41d72ee81
24 changed files with 1015 additions and 552 deletions
|
@ -18,11 +18,12 @@ use futures::{
|
|||
FutureExt as _, Stream, StreamExt,
|
||||
};
|
||||
use fuzzy::CharBag;
|
||||
use git::GitHostingProviderRegistry;
|
||||
use git::{
|
||||
repository::{GitFileStatus, GitRepository, RepoPath},
|
||||
status::GitStatusPair,
|
||||
COOKIES, DOT_GIT, FSMONITOR_DAEMON, GITIGNORE,
|
||||
repository::{GitRepository, RepoPath},
|
||||
status::{
|
||||
FileStatus, GitSummary, StatusCode, TrackedStatus, UnmergedStatus, UnmergedStatusCode,
|
||||
},
|
||||
GitHostingProviderRegistry, COOKIES, DOT_GIT, FSMONITOR_DAEMON, GITIGNORE,
|
||||
};
|
||||
use gpui::{
|
||||
AppContext, AsyncAppContext, BackgroundExecutor, Context, EventEmitter, Model, ModelContext,
|
||||
|
@ -239,10 +240,7 @@ impl RepositoryEntry {
|
|||
updated_statuses: self
|
||||
.statuses_by_path
|
||||
.iter()
|
||||
.map(|entry| proto::StatusEntry {
|
||||
repo_path: entry.repo_path.to_string_lossy().to_string(),
|
||||
status: status_pair_to_proto(entry.status.clone()),
|
||||
})
|
||||
.map(|entry| entry.to_proto())
|
||||
.collect(),
|
||||
removed_statuses: Default::default(),
|
||||
}
|
||||
|
@ -266,7 +264,7 @@ impl RepositoryEntry {
|
|||
current_new_entry = new_statuses.next();
|
||||
}
|
||||
Ordering::Equal => {
|
||||
if new_entry.combined_status() != old_entry.combined_status() {
|
||||
if new_entry.status != old_entry.status {
|
||||
updated_statuses.push(new_entry.to_proto());
|
||||
}
|
||||
current_old_entry = old_statuses.next();
|
||||
|
@ -2361,13 +2359,13 @@ impl Snapshot {
|
|||
Some(removed_entry.path)
|
||||
}
|
||||
|
||||
pub fn status_for_file(&self, path: impl AsRef<Path>) -> Option<GitFileStatus> {
|
||||
pub fn status_for_file(&self, path: impl AsRef<Path>) -> Option<FileStatus> {
|
||||
let path = path.as_ref();
|
||||
self.repository_for_path(path).and_then(|repo| {
|
||||
let repo_path = repo.relativize(path).unwrap();
|
||||
repo.statuses_by_path
|
||||
.get(&PathKey(repo_path.0), &())
|
||||
.map(|entry| entry.combined_status())
|
||||
.map(|entry| entry.status)
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -3633,41 +3631,41 @@ pub type UpdatedGitRepositoriesSet = Arc<[(Arc<Path>, GitRepositoryChange)]>;
|
|||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct StatusEntry {
|
||||
pub repo_path: RepoPath,
|
||||
pub status: GitStatusPair,
|
||||
pub status: FileStatus,
|
||||
}
|
||||
|
||||
impl StatusEntry {
|
||||
// TODO revisit uses of this
|
||||
pub fn combined_status(&self) -> GitFileStatus {
|
||||
self.status.combined()
|
||||
}
|
||||
|
||||
pub fn index_status(&self) -> Option<GitFileStatus> {
|
||||
self.status.index_status
|
||||
}
|
||||
|
||||
pub fn worktree_status(&self) -> Option<GitFileStatus> {
|
||||
self.status.worktree_status
|
||||
}
|
||||
|
||||
pub fn is_staged(&self) -> Option<bool> {
|
||||
self.status.is_staged()
|
||||
}
|
||||
|
||||
fn to_proto(&self) -> proto::StatusEntry {
|
||||
let simple_status = match self.status {
|
||||
FileStatus::Ignored | FileStatus::Untracked => proto::GitStatus::Added as i32,
|
||||
FileStatus::Unmerged { .. } => proto::GitStatus::Conflict as i32,
|
||||
FileStatus::Tracked(TrackedStatus {
|
||||
index_status,
|
||||
worktree_status,
|
||||
}) => tracked_status_to_proto(if worktree_status != StatusCode::Unmodified {
|
||||
worktree_status
|
||||
} else {
|
||||
index_status
|
||||
}),
|
||||
};
|
||||
proto::StatusEntry {
|
||||
repo_path: self.repo_path.to_proto(),
|
||||
status: status_pair_to_proto(self.status.clone()),
|
||||
simple_status,
|
||||
status: Some(status_to_proto(self.status)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<proto::StatusEntry> for StatusEntry {
|
||||
type Error = anyhow::Error;
|
||||
|
||||
fn try_from(value: proto::StatusEntry) -> Result<Self, Self::Error> {
|
||||
let repo_path = RepoPath(Path::new(&value.repo_path).into());
|
||||
let status = status_pair_from_proto(value.status)
|
||||
.ok_or_else(|| anyhow!("Unable to parse status value {}", value.status))?;
|
||||
let status = status_from_proto(value.simple_status, value.status)?;
|
||||
Ok(Self { repo_path, status })
|
||||
}
|
||||
}
|
||||
|
@ -3734,43 +3732,13 @@ impl sum_tree::KeyedItem for RepositoryEntry {
|
|||
}
|
||||
}
|
||||
|
||||
impl sum_tree::Summary for GitStatuses {
|
||||
type Context = ();
|
||||
|
||||
fn zero(_: &Self::Context) -> Self {
|
||||
Default::default()
|
||||
}
|
||||
|
||||
fn add_summary(&mut self, rhs: &Self, _: &Self::Context) {
|
||||
*self += *rhs;
|
||||
}
|
||||
}
|
||||
|
||||
impl sum_tree::Item for StatusEntry {
|
||||
type Summary = PathSummary<GitStatuses>;
|
||||
type Summary = PathSummary<GitSummary>;
|
||||
|
||||
fn summary(&self, _: &<Self::Summary as Summary>::Context) -> Self::Summary {
|
||||
PathSummary {
|
||||
max_path: self.repo_path.0.clone(),
|
||||
item_summary: match self.combined_status() {
|
||||
GitFileStatus::Added => GitStatuses {
|
||||
added: 1,
|
||||
..Default::default()
|
||||
},
|
||||
GitFileStatus::Modified => GitStatuses {
|
||||
modified: 1,
|
||||
..Default::default()
|
||||
},
|
||||
GitFileStatus::Conflict => GitStatuses {
|
||||
conflict: 1,
|
||||
..Default::default()
|
||||
},
|
||||
GitFileStatus::Deleted => Default::default(),
|
||||
GitFileStatus::Untracked => GitStatuses {
|
||||
untracked: 1,
|
||||
..Default::default()
|
||||
},
|
||||
},
|
||||
item_summary: self.status.summary(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -3783,69 +3751,12 @@ impl sum_tree::KeyedItem for StatusEntry {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default, Copy, PartialEq, Eq)]
|
||||
pub struct GitStatuses {
|
||||
added: usize,
|
||||
modified: usize,
|
||||
conflict: usize,
|
||||
untracked: usize,
|
||||
}
|
||||
|
||||
impl GitStatuses {
|
||||
pub fn to_status(&self) -> Option<GitFileStatus> {
|
||||
if self.conflict > 0 {
|
||||
Some(GitFileStatus::Conflict)
|
||||
} else if self.modified > 0 {
|
||||
Some(GitFileStatus::Modified)
|
||||
} else if self.added > 0 || self.untracked > 0 {
|
||||
Some(GitFileStatus::Added)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::Add<Self> for GitStatuses {
|
||||
type Output = Self;
|
||||
|
||||
fn add(self, rhs: Self) -> Self {
|
||||
GitStatuses {
|
||||
added: self.added + rhs.added,
|
||||
modified: self.modified + rhs.modified,
|
||||
conflict: self.conflict + rhs.conflict,
|
||||
untracked: self.untracked + rhs.untracked,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::AddAssign for GitStatuses {
|
||||
fn add_assign(&mut self, rhs: Self) {
|
||||
self.added += rhs.added;
|
||||
self.modified += rhs.modified;
|
||||
self.conflict += rhs.conflict;
|
||||
self.untracked += rhs.untracked;
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::Sub for GitStatuses {
|
||||
type Output = GitStatuses;
|
||||
|
||||
fn sub(self, rhs: Self) -> Self::Output {
|
||||
GitStatuses {
|
||||
added: self.added - rhs.added,
|
||||
modified: self.modified - rhs.modified,
|
||||
conflict: self.conflict - rhs.conflict,
|
||||
untracked: self.untracked - rhs.untracked,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> sum_tree::Dimension<'a, PathSummary<GitStatuses>> for GitStatuses {
|
||||
impl<'a> sum_tree::Dimension<'a, PathSummary<GitSummary>> for GitSummary {
|
||||
fn zero(_cx: &()) -> Self {
|
||||
Default::default()
|
||||
}
|
||||
|
||||
fn add_summary(&mut self, summary: &'a PathSummary<GitStatuses>, _: &()) {
|
||||
fn add_summary(&mut self, summary: &'a PathSummary<GitSummary>, _: &()) {
|
||||
*self += summary.item_summary
|
||||
}
|
||||
}
|
||||
|
@ -4851,7 +4762,7 @@ impl BackgroundScanner {
|
|||
|
||||
changed_path_statuses.push(Edit::Insert(StatusEntry {
|
||||
repo_path: repo_path.clone(),
|
||||
status: status.clone(),
|
||||
status: *status,
|
||||
}));
|
||||
}
|
||||
|
||||
|
@ -5280,7 +5191,7 @@ impl BackgroundScanner {
|
|||
new_entries_by_path.insert_or_replace(
|
||||
StatusEntry {
|
||||
repo_path: repo_path.clone(),
|
||||
status: status.clone(),
|
||||
status: *status,
|
||||
},
|
||||
&(),
|
||||
);
|
||||
|
@ -5695,14 +5606,14 @@ impl<'a> Default for TraversalProgress<'a> {
|
|||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct GitEntryRef<'a> {
|
||||
pub entry: &'a Entry,
|
||||
pub git_status: Option<GitFileStatus>,
|
||||
pub git_summary: GitSummary,
|
||||
}
|
||||
|
||||
impl<'a> GitEntryRef<'a> {
|
||||
pub fn to_owned(&self) -> GitEntry {
|
||||
GitEntry {
|
||||
entry: self.entry.clone(),
|
||||
git_status: self.git_status,
|
||||
git_summary: self.git_summary,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -5724,14 +5635,14 @@ impl<'a> AsRef<Entry> for GitEntryRef<'a> {
|
|||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct GitEntry {
|
||||
pub entry: Entry,
|
||||
pub git_status: Option<GitFileStatus>,
|
||||
pub git_summary: GitSummary,
|
||||
}
|
||||
|
||||
impl GitEntry {
|
||||
pub fn to_ref(&self) -> GitEntryRef {
|
||||
GitEntryRef {
|
||||
entry: &self.entry,
|
||||
git_status: self.git_status,
|
||||
git_summary: self.git_summary,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -5753,7 +5664,7 @@ impl AsRef<Entry> for GitEntry {
|
|||
/// Walks the worktree entries and their associated git statuses.
|
||||
pub struct GitTraversal<'a> {
|
||||
traversal: Traversal<'a>,
|
||||
current_entry_status: Option<GitFileStatus>,
|
||||
current_entry_summary: Option<GitSummary>,
|
||||
repo_location: Option<(
|
||||
&'a RepositoryEntry,
|
||||
Cursor<'a, StatusEntry, PathProgress<'a>>,
|
||||
|
@ -5762,7 +5673,7 @@ pub struct GitTraversal<'a> {
|
|||
|
||||
impl<'a> GitTraversal<'a> {
|
||||
fn synchronize_statuses(&mut self, reset: bool) {
|
||||
self.current_entry_status = None;
|
||||
self.current_entry_summary = None;
|
||||
|
||||
let Some(entry) = self.traversal.cursor.item() else {
|
||||
return;
|
||||
|
@ -5787,14 +5698,16 @@ impl<'a> GitTraversal<'a> {
|
|||
if entry.is_dir() {
|
||||
let mut statuses = statuses.clone();
|
||||
statuses.seek_forward(&PathTarget::Path(repo_path.as_ref()), Bias::Left, &());
|
||||
let summary: GitStatuses =
|
||||
let summary =
|
||||
statuses.summary(&PathTarget::Successor(repo_path.as_ref()), Bias::Left, &());
|
||||
|
||||
self.current_entry_status = summary.to_status();
|
||||
self.current_entry_summary = Some(summary);
|
||||
} else if entry.is_file() {
|
||||
// For a file entry, park the cursor on the corresponding status
|
||||
if statuses.seek_forward(&PathTarget::Path(repo_path.as_ref()), Bias::Left, &()) {
|
||||
self.current_entry_status = Some(statuses.item().unwrap().combined_status());
|
||||
self.current_entry_summary = Some(statuses.item().unwrap().status.into());
|
||||
} else {
|
||||
self.current_entry_summary = Some(GitSummary::zero(&()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -5830,10 +5743,9 @@ impl<'a> GitTraversal<'a> {
|
|||
}
|
||||
|
||||
pub fn entry(&self) -> Option<GitEntryRef<'a>> {
|
||||
Some(GitEntryRef {
|
||||
entry: self.traversal.cursor.item()?,
|
||||
git_status: self.current_entry_status,
|
||||
})
|
||||
let entry = self.traversal.cursor.item()?;
|
||||
let git_summary = self.current_entry_summary.unwrap_or_default();
|
||||
Some(GitEntryRef { entry, git_summary })
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -5884,7 +5796,7 @@ impl<'a> Traversal<'a> {
|
|||
pub fn with_git_statuses(self) -> GitTraversal<'a> {
|
||||
let mut this = GitTraversal {
|
||||
traversal: self,
|
||||
current_entry_status: None,
|
||||
current_entry_summary: None,
|
||||
repo_location: None,
|
||||
};
|
||||
this.synchronize_statuses(true);
|
||||
|
@ -6003,10 +5915,10 @@ impl<'a, 'b, S: Summary> SeekTarget<'a, PathSummary<S>, TraversalProgress<'a>> f
|
|||
}
|
||||
}
|
||||
|
||||
impl<'a, 'b> SeekTarget<'a, PathSummary<GitStatuses>, (TraversalProgress<'a>, GitStatuses)>
|
||||
impl<'a, 'b> SeekTarget<'a, PathSummary<GitSummary>, (TraversalProgress<'a>, GitSummary)>
|
||||
for PathTarget<'b>
|
||||
{
|
||||
fn cmp(&self, cursor_location: &(TraversalProgress<'a>, GitStatuses), _: &()) -> Ordering {
|
||||
fn cmp(&self, cursor_location: &(TraversalProgress<'a>, GitSummary), _: &()) -> Ordering {
|
||||
self.cmp_path(&cursor_location.0.max_path)
|
||||
}
|
||||
}
|
||||
|
@ -6159,28 +6071,135 @@ impl<'a> TryFrom<(&'a CharBag, &PathMatcher, proto::Entry)> for Entry {
|
|||
}
|
||||
}
|
||||
|
||||
// TODO pass the status pair all the way through
|
||||
fn status_pair_from_proto(proto: i32) -> Option<GitStatusPair> {
|
||||
let proto = proto::GitStatus::from_i32(proto)?;
|
||||
let worktree_status = match proto {
|
||||
proto::GitStatus::Added => GitFileStatus::Added,
|
||||
proto::GitStatus::Modified => GitFileStatus::Modified,
|
||||
proto::GitStatus::Conflict => GitFileStatus::Conflict,
|
||||
proto::GitStatus::Deleted => GitFileStatus::Deleted,
|
||||
fn status_from_proto(
|
||||
simple_status: i32,
|
||||
status: Option<proto::GitFileStatus>,
|
||||
) -> anyhow::Result<FileStatus> {
|
||||
use proto::git_file_status::Variant;
|
||||
|
||||
let Some(variant) = status.and_then(|status| status.variant) else {
|
||||
let code = proto::GitStatus::from_i32(simple_status)
|
||||
.ok_or_else(|| anyhow!("Invalid git status code: {simple_status}"))?;
|
||||
let result = match code {
|
||||
proto::GitStatus::Added => TrackedStatus {
|
||||
worktree_status: StatusCode::Added,
|
||||
index_status: StatusCode::Unmodified,
|
||||
}
|
||||
.into(),
|
||||
proto::GitStatus::Modified => TrackedStatus {
|
||||
worktree_status: StatusCode::Modified,
|
||||
index_status: StatusCode::Unmodified,
|
||||
}
|
||||
.into(),
|
||||
proto::GitStatus::Conflict => UnmergedStatus {
|
||||
first_head: UnmergedStatusCode::Updated,
|
||||
second_head: UnmergedStatusCode::Updated,
|
||||
}
|
||||
.into(),
|
||||
proto::GitStatus::Deleted => TrackedStatus {
|
||||
worktree_status: StatusCode::Deleted,
|
||||
index_status: StatusCode::Unmodified,
|
||||
}
|
||||
.into(),
|
||||
_ => return Err(anyhow!("Invalid code for simple status: {simple_status}")),
|
||||
};
|
||||
return Ok(result);
|
||||
};
|
||||
Some(GitStatusPair {
|
||||
index_status: None,
|
||||
worktree_status: Some(worktree_status),
|
||||
})
|
||||
|
||||
let result = match variant {
|
||||
Variant::Untracked(_) => FileStatus::Untracked,
|
||||
Variant::Ignored(_) => FileStatus::Ignored,
|
||||
Variant::Unmerged(unmerged) => {
|
||||
let [first_head, second_head] =
|
||||
[unmerged.first_head, unmerged.second_head].map(|head| {
|
||||
let code = proto::GitStatus::from_i32(head)
|
||||
.ok_or_else(|| anyhow!("Invalid git status code: {head}"))?;
|
||||
let result = match code {
|
||||
proto::GitStatus::Added => UnmergedStatusCode::Added,
|
||||
proto::GitStatus::Updated => UnmergedStatusCode::Updated,
|
||||
proto::GitStatus::Deleted => UnmergedStatusCode::Deleted,
|
||||
_ => return Err(anyhow!("Invalid code for unmerged status: {code:?}")),
|
||||
};
|
||||
Ok(result)
|
||||
});
|
||||
let [first_head, second_head] = [first_head?, second_head?];
|
||||
UnmergedStatus {
|
||||
first_head,
|
||||
second_head,
|
||||
}
|
||||
.into()
|
||||
}
|
||||
Variant::Tracked(tracked) => {
|
||||
let [index_status, worktree_status] = [tracked.index_status, tracked.worktree_status]
|
||||
.map(|status| {
|
||||
let code = proto::GitStatus::from_i32(status)
|
||||
.ok_or_else(|| anyhow!("Invalid git status code: {status}"))?;
|
||||
let result = match code {
|
||||
proto::GitStatus::Modified => StatusCode::Modified,
|
||||
proto::GitStatus::TypeChanged => StatusCode::TypeChanged,
|
||||
proto::GitStatus::Added => StatusCode::Added,
|
||||
proto::GitStatus::Deleted => StatusCode::Deleted,
|
||||
proto::GitStatus::Renamed => StatusCode::Renamed,
|
||||
proto::GitStatus::Copied => StatusCode::Copied,
|
||||
proto::GitStatus::Unmodified => StatusCode::Unmodified,
|
||||
_ => return Err(anyhow!("Invalid code for tracked status: {code:?}")),
|
||||
};
|
||||
Ok(result)
|
||||
});
|
||||
let [index_status, worktree_status] = [index_status?, worktree_status?];
|
||||
TrackedStatus {
|
||||
index_status,
|
||||
worktree_status,
|
||||
}
|
||||
.into()
|
||||
}
|
||||
};
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
fn status_pair_to_proto(status: GitStatusPair) -> i32 {
|
||||
match status.combined() {
|
||||
GitFileStatus::Added => proto::GitStatus::Added as i32,
|
||||
GitFileStatus::Modified => proto::GitStatus::Modified as i32,
|
||||
GitFileStatus::Conflict => proto::GitStatus::Conflict as i32,
|
||||
GitFileStatus::Deleted => proto::GitStatus::Deleted as i32,
|
||||
GitFileStatus::Untracked => proto::GitStatus::Added as i32, // TODO
|
||||
fn status_to_proto(status: FileStatus) -> proto::GitFileStatus {
|
||||
use proto::git_file_status::{Tracked, Unmerged, Variant};
|
||||
|
||||
let variant = match status {
|
||||
FileStatus::Untracked => Variant::Untracked(Default::default()),
|
||||
FileStatus::Ignored => Variant::Ignored(Default::default()),
|
||||
FileStatus::Unmerged(UnmergedStatus {
|
||||
first_head,
|
||||
second_head,
|
||||
}) => Variant::Unmerged(Unmerged {
|
||||
first_head: unmerged_status_to_proto(first_head),
|
||||
second_head: unmerged_status_to_proto(second_head),
|
||||
}),
|
||||
FileStatus::Tracked(TrackedStatus {
|
||||
index_status,
|
||||
worktree_status,
|
||||
}) => Variant::Tracked(Tracked {
|
||||
index_status: tracked_status_to_proto(index_status),
|
||||
worktree_status: tracked_status_to_proto(worktree_status),
|
||||
}),
|
||||
};
|
||||
proto::GitFileStatus {
|
||||
variant: Some(variant),
|
||||
}
|
||||
}
|
||||
|
||||
fn unmerged_status_to_proto(code: UnmergedStatusCode) -> i32 {
|
||||
match code {
|
||||
UnmergedStatusCode::Added => proto::GitStatus::Added as _,
|
||||
UnmergedStatusCode::Deleted => proto::GitStatus::Deleted as _,
|
||||
UnmergedStatusCode::Updated => proto::GitStatus::Updated as _,
|
||||
}
|
||||
}
|
||||
|
||||
fn tracked_status_to_proto(code: StatusCode) -> i32 {
|
||||
match code {
|
||||
StatusCode::Added => proto::GitStatus::Added as _,
|
||||
StatusCode::Deleted => proto::GitStatus::Deleted as _,
|
||||
StatusCode::Modified => proto::GitStatus::Modified as _,
|
||||
StatusCode::Renamed => proto::GitStatus::Renamed as _,
|
||||
StatusCode::TypeChanged => proto::GitStatus::TypeChanged as _,
|
||||
StatusCode::Copied => proto::GitStatus::Copied as _,
|
||||
StatusCode::Unmodified => proto::GitStatus::Unmodified as _,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -4,7 +4,12 @@ use crate::{
|
|||
};
|
||||
use anyhow::Result;
|
||||
use fs::{FakeFs, Fs, RealFs, RemoveOptions};
|
||||
use git::{repository::GitFileStatus, GITIGNORE};
|
||||
use git::{
|
||||
status::{
|
||||
FileStatus, GitSummary, StatusCode, TrackedStatus, UnmergedStatus, UnmergedStatusCode,
|
||||
},
|
||||
GITIGNORE,
|
||||
};
|
||||
use gpui::{BorrowAppContext, ModelContext, Task, TestAppContext};
|
||||
use parking_lot::Mutex;
|
||||
use postage::stream::Stream;
|
||||
|
@ -738,7 +743,10 @@ async fn test_rescan_with_gitignore(cx: &mut TestAppContext) {
|
|||
|
||||
fs.set_status_for_repo_via_working_copy_change(
|
||||
Path::new("/root/tree/.git"),
|
||||
&[(Path::new("tracked-dir/tracked-file2"), GitFileStatus::Added)],
|
||||
&[(
|
||||
Path::new("tracked-dir/tracked-file2"),
|
||||
FileStatus::worktree(StatusCode::Added),
|
||||
)],
|
||||
);
|
||||
|
||||
fs.create_file(
|
||||
|
@ -766,7 +774,7 @@ async fn test_rescan_with_gitignore(cx: &mut TestAppContext) {
|
|||
assert_entry_git_state(
|
||||
tree,
|
||||
"tracked-dir/tracked-file2",
|
||||
Some(GitFileStatus::Added),
|
||||
Some(StatusCode::Added),
|
||||
false,
|
||||
);
|
||||
assert_entry_git_state(tree, "tracked-dir/ancestor-ignored-file2", None, false);
|
||||
|
@ -822,14 +830,14 @@ async fn test_update_gitignore(cx: &mut TestAppContext) {
|
|||
|
||||
fs.set_status_for_repo_via_working_copy_change(
|
||||
Path::new("/root/.git"),
|
||||
&[(Path::new("b.txt"), GitFileStatus::Added)],
|
||||
&[(Path::new("b.txt"), FileStatus::worktree(StatusCode::Added))],
|
||||
);
|
||||
|
||||
cx.executor().run_until_parked();
|
||||
cx.read(|cx| {
|
||||
let tree = tree.read(cx);
|
||||
assert_entry_git_state(tree, "a.xml", None, true);
|
||||
assert_entry_git_state(tree, "b.txt", Some(GitFileStatus::Added), false);
|
||||
assert_entry_git_state(tree, "b.txt", Some(StatusCode::Added), false);
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -1492,7 +1500,10 @@ async fn test_bump_mtime_of_git_repo_workdir(cx: &mut TestAppContext) {
|
|||
// detected.
|
||||
fs.set_status_for_repo_via_git_operation(
|
||||
Path::new("/root/.git"),
|
||||
&[(Path::new("b/c.txt"), GitFileStatus::Modified)],
|
||||
&[(
|
||||
Path::new("b/c.txt"),
|
||||
FileStatus::worktree(StatusCode::Modified),
|
||||
)],
|
||||
);
|
||||
cx.executor().run_until_parked();
|
||||
|
||||
|
@ -1501,9 +1512,9 @@ async fn test_bump_mtime_of_git_repo_workdir(cx: &mut TestAppContext) {
|
|||
check_git_statuses(
|
||||
&snapshot,
|
||||
&[
|
||||
(Path::new(""), Some(GitFileStatus::Modified)),
|
||||
(Path::new("a.txt"), None),
|
||||
(Path::new("b/c.txt"), Some(GitFileStatus::Modified)),
|
||||
(Path::new(""), GitSummary::MODIFIED),
|
||||
(Path::new("a.txt"), GitSummary::UNCHANGED),
|
||||
(Path::new("b/c.txt"), GitSummary::MODIFIED),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
@ -2142,6 +2153,11 @@ fn random_filename(rng: &mut impl Rng) -> String {
|
|||
.collect()
|
||||
}
|
||||
|
||||
const CONFLICT: FileStatus = FileStatus::Unmerged(UnmergedStatus {
|
||||
first_head: UnmergedStatusCode::Updated,
|
||||
second_head: UnmergedStatusCode::Updated,
|
||||
});
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_rename_work_directory(cx: &mut TestAppContext) {
|
||||
init_test(cx);
|
||||
|
@ -2183,11 +2199,11 @@ async fn test_rename_work_directory(cx: &mut TestAppContext) {
|
|||
assert_eq!(repo.path.as_ref(), Path::new("projects/project1"));
|
||||
assert_eq!(
|
||||
tree.status_for_file(Path::new("projects/project1/a")),
|
||||
Some(GitFileStatus::Modified)
|
||||
Some(FileStatus::worktree(StatusCode::Modified)),
|
||||
);
|
||||
assert_eq!(
|
||||
tree.status_for_file(Path::new("projects/project1/b")),
|
||||
Some(GitFileStatus::Untracked)
|
||||
Some(FileStatus::Untracked),
|
||||
);
|
||||
});
|
||||
|
||||
|
@ -2204,11 +2220,11 @@ async fn test_rename_work_directory(cx: &mut TestAppContext) {
|
|||
assert_eq!(repo.path.as_ref(), Path::new("projects/project2"));
|
||||
assert_eq!(
|
||||
tree.status_for_file(Path::new("projects/project2/a")),
|
||||
Some(GitFileStatus::Modified)
|
||||
Some(FileStatus::worktree(StatusCode::Modified)),
|
||||
);
|
||||
assert_eq!(
|
||||
tree.status_for_file(Path::new("projects/project2/b")),
|
||||
Some(GitFileStatus::Untracked)
|
||||
Some(FileStatus::Untracked),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
@ -2387,11 +2403,11 @@ async fn test_file_status(cx: &mut TestAppContext) {
|
|||
|
||||
assert_eq!(
|
||||
snapshot.status_for_file(project_path.join(B_TXT)),
|
||||
Some(GitFileStatus::Untracked)
|
||||
Some(FileStatus::Untracked),
|
||||
);
|
||||
assert_eq!(
|
||||
snapshot.status_for_file(project_path.join(F_TXT)),
|
||||
Some(GitFileStatus::Untracked)
|
||||
Some(FileStatus::Untracked),
|
||||
);
|
||||
});
|
||||
|
||||
|
@ -2405,7 +2421,7 @@ async fn test_file_status(cx: &mut TestAppContext) {
|
|||
let snapshot = tree.snapshot();
|
||||
assert_eq!(
|
||||
snapshot.status_for_file(project_path.join(A_TXT)),
|
||||
Some(GitFileStatus::Modified)
|
||||
Some(FileStatus::worktree(StatusCode::Modified)),
|
||||
);
|
||||
});
|
||||
|
||||
|
@ -2421,7 +2437,7 @@ async fn test_file_status(cx: &mut TestAppContext) {
|
|||
let snapshot = tree.snapshot();
|
||||
assert_eq!(
|
||||
snapshot.status_for_file(project_path.join(F_TXT)),
|
||||
Some(GitFileStatus::Untracked)
|
||||
Some(FileStatus::Untracked),
|
||||
);
|
||||
assert_eq!(snapshot.status_for_file(project_path.join(B_TXT)), None);
|
||||
assert_eq!(snapshot.status_for_file(project_path.join(A_TXT)), None);
|
||||
|
@ -2443,11 +2459,11 @@ async fn test_file_status(cx: &mut TestAppContext) {
|
|||
assert_eq!(snapshot.status_for_file(project_path.join(A_TXT)), None);
|
||||
assert_eq!(
|
||||
snapshot.status_for_file(project_path.join(B_TXT)),
|
||||
Some(GitFileStatus::Untracked)
|
||||
Some(FileStatus::Untracked),
|
||||
);
|
||||
assert_eq!(
|
||||
snapshot.status_for_file(project_path.join(E_TXT)),
|
||||
Some(GitFileStatus::Modified)
|
||||
Some(FileStatus::worktree(StatusCode::Modified)),
|
||||
);
|
||||
});
|
||||
|
||||
|
@ -2482,7 +2498,7 @@ async fn test_file_status(cx: &mut TestAppContext) {
|
|||
let snapshot = tree.snapshot();
|
||||
assert_eq!(
|
||||
snapshot.status_for_file(project_path.join(renamed_dir_name).join(RENAMED_FILE)),
|
||||
Some(GitFileStatus::Untracked)
|
||||
Some(FileStatus::Untracked),
|
||||
);
|
||||
});
|
||||
|
||||
|
@ -2506,7 +2522,7 @@ async fn test_file_status(cx: &mut TestAppContext) {
|
|||
.join(Path::new(renamed_dir_name))
|
||||
.join(RENAMED_FILE)
|
||||
),
|
||||
Some(GitFileStatus::Untracked)
|
||||
Some(FileStatus::Untracked),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
@ -2559,11 +2575,14 @@ async fn test_git_repository_status(cx: &mut TestAppContext) {
|
|||
|
||||
assert_eq!(entries.len(), 3);
|
||||
assert_eq!(entries[0].repo_path.as_ref(), Path::new("a.txt"));
|
||||
assert_eq!(entries[0].worktree_status(), Some(GitFileStatus::Modified));
|
||||
assert_eq!(
|
||||
entries[0].status,
|
||||
FileStatus::worktree(StatusCode::Modified)
|
||||
);
|
||||
assert_eq!(entries[1].repo_path.as_ref(), Path::new("b.txt"));
|
||||
assert_eq!(entries[1].worktree_status(), Some(GitFileStatus::Untracked));
|
||||
assert_eq!(entries[1].status, FileStatus::Untracked);
|
||||
assert_eq!(entries[2].repo_path.as_ref(), Path::new("d.txt"));
|
||||
assert_eq!(entries[2].worktree_status(), Some(GitFileStatus::Deleted));
|
||||
assert_eq!(entries[2].status, FileStatus::worktree(StatusCode::Deleted));
|
||||
});
|
||||
|
||||
std::fs::write(work_dir.join("c.txt"), "some changes").unwrap();
|
||||
|
@ -2581,14 +2600,20 @@ async fn test_git_repository_status(cx: &mut TestAppContext) {
|
|||
|
||||
std::assert_eq!(entries.len(), 4, "entries: {entries:?}");
|
||||
assert_eq!(entries[0].repo_path.as_ref(), Path::new("a.txt"));
|
||||
assert_eq!(entries[0].worktree_status(), Some(GitFileStatus::Modified));
|
||||
assert_eq!(
|
||||
entries[0].status,
|
||||
FileStatus::worktree(StatusCode::Modified)
|
||||
);
|
||||
assert_eq!(entries[1].repo_path.as_ref(), Path::new("b.txt"));
|
||||
assert_eq!(entries[1].worktree_status(), Some(GitFileStatus::Untracked));
|
||||
assert_eq!(entries[1].status, FileStatus::Untracked);
|
||||
// Status updated
|
||||
assert_eq!(entries[2].repo_path.as_ref(), Path::new("c.txt"));
|
||||
assert_eq!(entries[2].worktree_status(), Some(GitFileStatus::Modified));
|
||||
assert_eq!(
|
||||
entries[2].status,
|
||||
FileStatus::worktree(StatusCode::Modified)
|
||||
);
|
||||
assert_eq!(entries[3].repo_path.as_ref(), Path::new("d.txt"));
|
||||
assert_eq!(entries[3].worktree_status(), Some(GitFileStatus::Deleted));
|
||||
assert_eq!(entries[3].status, FileStatus::worktree(StatusCode::Deleted));
|
||||
});
|
||||
|
||||
git_add("a.txt", &repo);
|
||||
|
@ -2621,7 +2646,7 @@ async fn test_git_repository_status(cx: &mut TestAppContext) {
|
|||
&entries
|
||||
);
|
||||
assert_eq!(entries[0].repo_path.as_ref(), Path::new("a.txt"));
|
||||
assert_eq!(entries[0].worktree_status(), Some(GitFileStatus::Deleted));
|
||||
assert_eq!(entries[0].status, FileStatus::worktree(StatusCode::Deleted));
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -2692,7 +2717,7 @@ async fn test_repository_subfolder_git_status(cx: &mut TestAppContext) {
|
|||
assert_eq!(snapshot.status_for_file("c.txt"), None);
|
||||
assert_eq!(
|
||||
snapshot.status_for_file("d/e.txt"),
|
||||
Some(GitFileStatus::Untracked)
|
||||
Some(FileStatus::Untracked)
|
||||
);
|
||||
});
|
||||
|
||||
|
@ -2744,17 +2769,20 @@ async fn test_traverse_with_git_status(cx: &mut TestAppContext) {
|
|||
fs.set_status_for_repo_via_git_operation(
|
||||
Path::new("/root/x/.git"),
|
||||
&[
|
||||
(Path::new("x2.txt"), GitFileStatus::Modified),
|
||||
(Path::new("z.txt"), GitFileStatus::Added),
|
||||
(
|
||||
Path::new("x2.txt"),
|
||||
FileStatus::worktree(StatusCode::Modified),
|
||||
),
|
||||
(Path::new("z.txt"), FileStatus::worktree(StatusCode::Added)),
|
||||
],
|
||||
);
|
||||
fs.set_status_for_repo_via_git_operation(
|
||||
Path::new("/root/x/y/.git"),
|
||||
&[(Path::new("y1.txt"), GitFileStatus::Conflict)],
|
||||
&[(Path::new("y1.txt"), CONFLICT)],
|
||||
);
|
||||
fs.set_status_for_repo_via_git_operation(
|
||||
Path::new("/root/z/.git"),
|
||||
&[(Path::new("z2.txt"), GitFileStatus::Added)],
|
||||
&[(Path::new("z2.txt"), FileStatus::worktree(StatusCode::Added))],
|
||||
);
|
||||
|
||||
let tree = Worktree::local(
|
||||
|
@ -2780,25 +2808,25 @@ async fn test_traverse_with_git_status(cx: &mut TestAppContext) {
|
|||
|
||||
let entry = traversal.next().unwrap();
|
||||
assert_eq!(entry.path.as_ref(), Path::new("x/x1.txt"));
|
||||
assert_eq!(entry.git_status, None);
|
||||
assert_eq!(entry.git_summary, GitSummary::UNCHANGED);
|
||||
let entry = traversal.next().unwrap();
|
||||
assert_eq!(entry.path.as_ref(), Path::new("x/x2.txt"));
|
||||
assert_eq!(entry.git_status, Some(GitFileStatus::Modified));
|
||||
assert_eq!(entry.git_summary, GitSummary::MODIFIED);
|
||||
let entry = traversal.next().unwrap();
|
||||
assert_eq!(entry.path.as_ref(), Path::new("x/y/y1.txt"));
|
||||
assert_eq!(entry.git_status, Some(GitFileStatus::Conflict));
|
||||
assert_eq!(entry.git_summary, GitSummary::CONFLICT);
|
||||
let entry = traversal.next().unwrap();
|
||||
assert_eq!(entry.path.as_ref(), Path::new("x/y/y2.txt"));
|
||||
assert_eq!(entry.git_status, None);
|
||||
assert_eq!(entry.git_summary, GitSummary::UNCHANGED);
|
||||
let entry = traversal.next().unwrap();
|
||||
assert_eq!(entry.path.as_ref(), Path::new("x/z.txt"));
|
||||
assert_eq!(entry.git_status, Some(GitFileStatus::Added));
|
||||
assert_eq!(entry.git_summary, GitSummary::ADDED);
|
||||
let entry = traversal.next().unwrap();
|
||||
assert_eq!(entry.path.as_ref(), Path::new("z/z1.txt"));
|
||||
assert_eq!(entry.git_status, None);
|
||||
assert_eq!(entry.git_summary, GitSummary::UNCHANGED);
|
||||
let entry = traversal.next().unwrap();
|
||||
assert_eq!(entry.path.as_ref(), Path::new("z/z2.txt"));
|
||||
assert_eq!(entry.git_status, Some(GitFileStatus::Added));
|
||||
assert_eq!(entry.git_summary, GitSummary::ADDED);
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
|
@ -2834,9 +2862,15 @@ async fn test_propagate_git_statuses(cx: &mut TestAppContext) {
|
|||
fs.set_status_for_repo_via_git_operation(
|
||||
Path::new("/root/.git"),
|
||||
&[
|
||||
(Path::new("a/b/c1.txt"), GitFileStatus::Added),
|
||||
(Path::new("a/d/e2.txt"), GitFileStatus::Modified),
|
||||
(Path::new("g/h2.txt"), GitFileStatus::Conflict),
|
||||
(
|
||||
Path::new("a/b/c1.txt"),
|
||||
FileStatus::worktree(StatusCode::Added),
|
||||
),
|
||||
(
|
||||
Path::new("a/d/e2.txt"),
|
||||
FileStatus::worktree(StatusCode::Modified),
|
||||
),
|
||||
(Path::new("g/h2.txt"), CONFLICT),
|
||||
],
|
||||
);
|
||||
|
||||
|
@ -2859,52 +2893,58 @@ async fn test_propagate_git_statuses(cx: &mut TestAppContext) {
|
|||
check_git_statuses(
|
||||
&snapshot,
|
||||
&[
|
||||
(Path::new(""), Some(GitFileStatus::Conflict)),
|
||||
(Path::new("g"), Some(GitFileStatus::Conflict)),
|
||||
(Path::new("g/h2.txt"), Some(GitFileStatus::Conflict)),
|
||||
(
|
||||
Path::new(""),
|
||||
GitSummary::CONFLICT + GitSummary::MODIFIED + GitSummary::ADDED,
|
||||
),
|
||||
(Path::new("g"), GitSummary::CONFLICT),
|
||||
(Path::new("g/h2.txt"), GitSummary::CONFLICT),
|
||||
],
|
||||
);
|
||||
|
||||
check_git_statuses(
|
||||
&snapshot,
|
||||
&[
|
||||
(Path::new(""), Some(GitFileStatus::Conflict)),
|
||||
(Path::new("a"), Some(GitFileStatus::Modified)),
|
||||
(Path::new("a/b"), Some(GitFileStatus::Added)),
|
||||
(Path::new("a/b/c1.txt"), Some(GitFileStatus::Added)),
|
||||
(Path::new("a/b/c2.txt"), None),
|
||||
(Path::new("a/d"), Some(GitFileStatus::Modified)),
|
||||
(Path::new("a/d/e2.txt"), Some(GitFileStatus::Modified)),
|
||||
(Path::new("f"), None),
|
||||
(Path::new("f/no-status.txt"), None),
|
||||
(Path::new("g"), Some(GitFileStatus::Conflict)),
|
||||
(Path::new("g/h2.txt"), Some(GitFileStatus::Conflict)),
|
||||
(
|
||||
Path::new(""),
|
||||
GitSummary::CONFLICT + GitSummary::ADDED + GitSummary::MODIFIED,
|
||||
),
|
||||
(Path::new("a"), GitSummary::ADDED + GitSummary::MODIFIED),
|
||||
(Path::new("a/b"), GitSummary::ADDED),
|
||||
(Path::new("a/b/c1.txt"), GitSummary::ADDED),
|
||||
(Path::new("a/b/c2.txt"), GitSummary::UNCHANGED),
|
||||
(Path::new("a/d"), GitSummary::MODIFIED),
|
||||
(Path::new("a/d/e2.txt"), GitSummary::MODIFIED),
|
||||
(Path::new("f"), GitSummary::UNCHANGED),
|
||||
(Path::new("f/no-status.txt"), GitSummary::UNCHANGED),
|
||||
(Path::new("g"), GitSummary::CONFLICT),
|
||||
(Path::new("g/h2.txt"), GitSummary::CONFLICT),
|
||||
],
|
||||
);
|
||||
|
||||
check_git_statuses(
|
||||
&snapshot,
|
||||
&[
|
||||
(Path::new("a/b"), Some(GitFileStatus::Added)),
|
||||
(Path::new("a/b/c1.txt"), Some(GitFileStatus::Added)),
|
||||
(Path::new("a/b/c2.txt"), None),
|
||||
(Path::new("a/d"), Some(GitFileStatus::Modified)),
|
||||
(Path::new("a/d/e1.txt"), None),
|
||||
(Path::new("a/d/e2.txt"), Some(GitFileStatus::Modified)),
|
||||
(Path::new("f"), None),
|
||||
(Path::new("f/no-status.txt"), None),
|
||||
(Path::new("g"), Some(GitFileStatus::Conflict)),
|
||||
(Path::new("a/b"), GitSummary::ADDED),
|
||||
(Path::new("a/b/c1.txt"), GitSummary::ADDED),
|
||||
(Path::new("a/b/c2.txt"), GitSummary::UNCHANGED),
|
||||
(Path::new("a/d"), GitSummary::MODIFIED),
|
||||
(Path::new("a/d/e1.txt"), GitSummary::UNCHANGED),
|
||||
(Path::new("a/d/e2.txt"), GitSummary::MODIFIED),
|
||||
(Path::new("f"), GitSummary::UNCHANGED),
|
||||
(Path::new("f/no-status.txt"), GitSummary::UNCHANGED),
|
||||
(Path::new("g"), GitSummary::CONFLICT),
|
||||
],
|
||||
);
|
||||
|
||||
check_git_statuses(
|
||||
&snapshot,
|
||||
&[
|
||||
(Path::new("a/b/c1.txt"), Some(GitFileStatus::Added)),
|
||||
(Path::new("a/b/c2.txt"), None),
|
||||
(Path::new("a/d/e1.txt"), None),
|
||||
(Path::new("a/d/e2.txt"), Some(GitFileStatus::Modified)),
|
||||
(Path::new("f/no-status.txt"), None),
|
||||
(Path::new("a/b/c1.txt"), GitSummary::ADDED),
|
||||
(Path::new("a/b/c2.txt"), GitSummary::UNCHANGED),
|
||||
(Path::new("a/d/e1.txt"), GitSummary::UNCHANGED),
|
||||
(Path::new("a/d/e2.txt"), GitSummary::MODIFIED),
|
||||
(Path::new("f/no-status.txt"), GitSummary::UNCHANGED),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
@ -2937,18 +2977,24 @@ async fn test_propagate_statuses_for_repos_under_project(cx: &mut TestAppContext
|
|||
|
||||
fs.set_status_for_repo_via_git_operation(
|
||||
Path::new("/root/x/.git"),
|
||||
&[(Path::new("x1.txt"), GitFileStatus::Added)],
|
||||
&[(Path::new("x1.txt"), FileStatus::worktree(StatusCode::Added))],
|
||||
);
|
||||
fs.set_status_for_repo_via_git_operation(
|
||||
Path::new("/root/y/.git"),
|
||||
&[
|
||||
(Path::new("y1.txt"), GitFileStatus::Conflict),
|
||||
(Path::new("y2.txt"), GitFileStatus::Modified),
|
||||
(Path::new("y1.txt"), CONFLICT),
|
||||
(
|
||||
Path::new("y2.txt"),
|
||||
FileStatus::worktree(StatusCode::Modified),
|
||||
),
|
||||
],
|
||||
);
|
||||
fs.set_status_for_repo_via_git_operation(
|
||||
Path::new("/root/z/.git"),
|
||||
&[(Path::new("z2.txt"), GitFileStatus::Modified)],
|
||||
&[(
|
||||
Path::new("z2.txt"),
|
||||
FileStatus::worktree(StatusCode::Modified),
|
||||
)],
|
||||
);
|
||||
|
||||
let tree = Worktree::local(
|
||||
|
@ -2971,48 +3017,48 @@ async fn test_propagate_statuses_for_repos_under_project(cx: &mut TestAppContext
|
|||
check_git_statuses(
|
||||
&snapshot,
|
||||
&[
|
||||
(Path::new("x"), Some(GitFileStatus::Added)),
|
||||
(Path::new("x/x1.txt"), Some(GitFileStatus::Added)),
|
||||
(Path::new("x"), GitSummary::ADDED),
|
||||
(Path::new("x/x1.txt"), GitSummary::ADDED),
|
||||
],
|
||||
);
|
||||
|
||||
check_git_statuses(
|
||||
&snapshot,
|
||||
&[
|
||||
(Path::new("y"), Some(GitFileStatus::Conflict)),
|
||||
(Path::new("y/y1.txt"), Some(GitFileStatus::Conflict)),
|
||||
(Path::new("y/y2.txt"), Some(GitFileStatus::Modified)),
|
||||
(Path::new("y"), GitSummary::CONFLICT + GitSummary::MODIFIED),
|
||||
(Path::new("y/y1.txt"), GitSummary::CONFLICT),
|
||||
(Path::new("y/y2.txt"), GitSummary::MODIFIED),
|
||||
],
|
||||
);
|
||||
|
||||
check_git_statuses(
|
||||
&snapshot,
|
||||
&[
|
||||
(Path::new("z"), Some(GitFileStatus::Modified)),
|
||||
(Path::new("z/z2.txt"), Some(GitFileStatus::Modified)),
|
||||
(Path::new("z"), GitSummary::MODIFIED),
|
||||
(Path::new("z/z2.txt"), GitSummary::MODIFIED),
|
||||
],
|
||||
);
|
||||
|
||||
check_git_statuses(
|
||||
&snapshot,
|
||||
&[
|
||||
(Path::new("x"), Some(GitFileStatus::Added)),
|
||||
(Path::new("x/x1.txt"), Some(GitFileStatus::Added)),
|
||||
(Path::new("x"), GitSummary::ADDED),
|
||||
(Path::new("x/x1.txt"), GitSummary::ADDED),
|
||||
],
|
||||
);
|
||||
|
||||
check_git_statuses(
|
||||
&snapshot,
|
||||
&[
|
||||
(Path::new("x"), Some(GitFileStatus::Added)),
|
||||
(Path::new("x/x1.txt"), Some(GitFileStatus::Added)),
|
||||
(Path::new("x/x2.txt"), None),
|
||||
(Path::new("y"), Some(GitFileStatus::Conflict)),
|
||||
(Path::new("y/y1.txt"), Some(GitFileStatus::Conflict)),
|
||||
(Path::new("y/y2.txt"), Some(GitFileStatus::Modified)),
|
||||
(Path::new("z"), Some(GitFileStatus::Modified)),
|
||||
(Path::new("z/z1.txt"), None),
|
||||
(Path::new("z/z2.txt"), Some(GitFileStatus::Modified)),
|
||||
(Path::new("x"), GitSummary::ADDED),
|
||||
(Path::new("x/x1.txt"), GitSummary::ADDED),
|
||||
(Path::new("x/x2.txt"), GitSummary::UNCHANGED),
|
||||
(Path::new("y"), GitSummary::CONFLICT + GitSummary::MODIFIED),
|
||||
(Path::new("y/y1.txt"), GitSummary::CONFLICT),
|
||||
(Path::new("y/y2.txt"), GitSummary::MODIFIED),
|
||||
(Path::new("z"), GitSummary::MODIFIED),
|
||||
(Path::new("z/z1.txt"), GitSummary::UNCHANGED),
|
||||
(Path::new("z/z2.txt"), GitSummary::MODIFIED),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
@ -3047,18 +3093,21 @@ async fn test_propagate_statuses_for_nested_repos(cx: &mut TestAppContext) {
|
|||
fs.set_status_for_repo_via_git_operation(
|
||||
Path::new("/root/x/.git"),
|
||||
&[
|
||||
(Path::new("x2.txt"), GitFileStatus::Modified),
|
||||
(Path::new("z.txt"), GitFileStatus::Added),
|
||||
(
|
||||
Path::new("x2.txt"),
|
||||
FileStatus::worktree(StatusCode::Modified),
|
||||
),
|
||||
(Path::new("z.txt"), FileStatus::worktree(StatusCode::Added)),
|
||||
],
|
||||
);
|
||||
fs.set_status_for_repo_via_git_operation(
|
||||
Path::new("/root/x/y/.git"),
|
||||
&[(Path::new("y1.txt"), GitFileStatus::Conflict)],
|
||||
&[(Path::new("y1.txt"), CONFLICT)],
|
||||
);
|
||||
|
||||
fs.set_status_for_repo_via_git_operation(
|
||||
Path::new("/root/z/.git"),
|
||||
&[(Path::new("z2.txt"), GitFileStatus::Added)],
|
||||
&[(Path::new("z2.txt"), FileStatus::worktree(StatusCode::Added))],
|
||||
);
|
||||
|
||||
let tree = Worktree::local(
|
||||
|
@ -3082,17 +3131,17 @@ async fn test_propagate_statuses_for_nested_repos(cx: &mut TestAppContext) {
|
|||
check_git_statuses(
|
||||
&snapshot,
|
||||
&[
|
||||
(Path::new("x/y"), Some(GitFileStatus::Conflict)), // the y git repository has conflict file in it, and so should have a conflict status
|
||||
(Path::new("x/y/y1.txt"), Some(GitFileStatus::Conflict)),
|
||||
(Path::new("x/y/y2.txt"), None),
|
||||
(Path::new("x/y"), GitSummary::CONFLICT),
|
||||
(Path::new("x/y/y1.txt"), GitSummary::CONFLICT),
|
||||
(Path::new("x/y/y2.txt"), GitSummary::UNCHANGED),
|
||||
],
|
||||
);
|
||||
check_git_statuses(
|
||||
&snapshot,
|
||||
&[
|
||||
(Path::new("z"), Some(GitFileStatus::Added)),
|
||||
(Path::new("z/z1.txt"), None),
|
||||
(Path::new("z/z2.txt"), Some(GitFileStatus::Added)),
|
||||
(Path::new("z"), GitSummary::ADDED),
|
||||
(Path::new("z/z1.txt"), GitSummary::UNCHANGED),
|
||||
(Path::new("z/z2.txt"), GitSummary::ADDED),
|
||||
],
|
||||
);
|
||||
|
||||
|
@ -3100,9 +3149,9 @@ async fn test_propagate_statuses_for_nested_repos(cx: &mut TestAppContext) {
|
|||
check_git_statuses(
|
||||
&snapshot,
|
||||
&[
|
||||
(Path::new("x"), Some(GitFileStatus::Modified)),
|
||||
(Path::new("x/y"), Some(GitFileStatus::Conflict)),
|
||||
(Path::new("x/y/y1.txt"), Some(GitFileStatus::Conflict)),
|
||||
(Path::new("x"), GitSummary::MODIFIED + GitSummary::ADDED),
|
||||
(Path::new("x/y"), GitSummary::CONFLICT),
|
||||
(Path::new("x/y/y1.txt"), GitSummary::CONFLICT),
|
||||
],
|
||||
);
|
||||
|
||||
|
@ -3110,13 +3159,13 @@ async fn test_propagate_statuses_for_nested_repos(cx: &mut TestAppContext) {
|
|||
check_git_statuses(
|
||||
&snapshot,
|
||||
&[
|
||||
(Path::new("x"), Some(GitFileStatus::Modified)),
|
||||
(Path::new("x/x1.txt"), None),
|
||||
(Path::new("x/x2.txt"), Some(GitFileStatus::Modified)),
|
||||
(Path::new("x/y"), Some(GitFileStatus::Conflict)),
|
||||
(Path::new("x/y/y1.txt"), Some(GitFileStatus::Conflict)),
|
||||
(Path::new("x/y/y2.txt"), None),
|
||||
(Path::new("x/z.txt"), Some(GitFileStatus::Added)),
|
||||
(Path::new("x"), GitSummary::MODIFIED + GitSummary::ADDED),
|
||||
(Path::new("x/x1.txt"), GitSummary::UNCHANGED),
|
||||
(Path::new("x/x2.txt"), GitSummary::MODIFIED),
|
||||
(Path::new("x/y"), GitSummary::CONFLICT),
|
||||
(Path::new("x/y/y1.txt"), GitSummary::CONFLICT),
|
||||
(Path::new("x/y/y2.txt"), GitSummary::UNCHANGED),
|
||||
(Path::new("x/z.txt"), GitSummary::ADDED),
|
||||
],
|
||||
);
|
||||
|
||||
|
@ -3124,9 +3173,9 @@ async fn test_propagate_statuses_for_nested_repos(cx: &mut TestAppContext) {
|
|||
check_git_statuses(
|
||||
&snapshot,
|
||||
&[
|
||||
(Path::new(""), None),
|
||||
(Path::new("x"), Some(GitFileStatus::Modified)),
|
||||
(Path::new("x/x1.txt"), None),
|
||||
(Path::new(""), GitSummary::UNCHANGED),
|
||||
(Path::new("x"), GitSummary::MODIFIED + GitSummary::ADDED),
|
||||
(Path::new("x/x1.txt"), GitSummary::UNCHANGED),
|
||||
],
|
||||
);
|
||||
|
||||
|
@ -3134,17 +3183,17 @@ async fn test_propagate_statuses_for_nested_repos(cx: &mut TestAppContext) {
|
|||
check_git_statuses(
|
||||
&snapshot,
|
||||
&[
|
||||
(Path::new(""), None),
|
||||
(Path::new("x"), Some(GitFileStatus::Modified)),
|
||||
(Path::new("x/x1.txt"), None),
|
||||
(Path::new("x/x2.txt"), Some(GitFileStatus::Modified)),
|
||||
(Path::new("x/y"), Some(GitFileStatus::Conflict)),
|
||||
(Path::new("x/y/y1.txt"), Some(GitFileStatus::Conflict)),
|
||||
(Path::new("x/y/y2.txt"), None),
|
||||
(Path::new("x/z.txt"), Some(GitFileStatus::Added)),
|
||||
(Path::new("z"), Some(GitFileStatus::Added)),
|
||||
(Path::new("z/z1.txt"), None),
|
||||
(Path::new("z/z2.txt"), Some(GitFileStatus::Added)),
|
||||
(Path::new(""), GitSummary::UNCHANGED),
|
||||
(Path::new("x"), GitSummary::MODIFIED + GitSummary::ADDED),
|
||||
(Path::new("x/x1.txt"), GitSummary::UNCHANGED),
|
||||
(Path::new("x/x2.txt"), GitSummary::MODIFIED),
|
||||
(Path::new("x/y"), GitSummary::CONFLICT),
|
||||
(Path::new("x/y/y1.txt"), GitSummary::CONFLICT),
|
||||
(Path::new("x/y/y2.txt"), GitSummary::UNCHANGED),
|
||||
(Path::new("x/z.txt"), GitSummary::ADDED),
|
||||
(Path::new("z"), GitSummary::ADDED),
|
||||
(Path::new("z/z1.txt"), GitSummary::UNCHANGED),
|
||||
(Path::new("z/z2.txt"), GitSummary::ADDED),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
@ -3173,7 +3222,7 @@ async fn test_private_single_file_worktree(cx: &mut TestAppContext) {
|
|||
}
|
||||
|
||||
#[track_caller]
|
||||
fn check_git_statuses(snapshot: &Snapshot, expected_statuses: &[(&Path, Option<GitFileStatus>)]) {
|
||||
fn check_git_statuses(snapshot: &Snapshot, expected_statuses: &[(&Path, GitSummary)]) {
|
||||
let mut traversal = snapshot
|
||||
.traverse_from_path(true, true, false, "".as_ref())
|
||||
.with_git_statuses();
|
||||
|
@ -3182,8 +3231,8 @@ fn check_git_statuses(snapshot: &Snapshot, expected_statuses: &[(&Path, Option<G
|
|||
.map(|&(path, _)| {
|
||||
let git_entry = traversal
|
||||
.find(|git_entry| &*git_entry.path == path)
|
||||
.expect("Traversal has no entry for {path:?}");
|
||||
(path, git_entry.git_status)
|
||||
.unwrap_or_else(|| panic!("Traversal has no entry for {path:?}"));
|
||||
(path, git_entry.git_summary)
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
assert_eq!(found_statuses, expected_statuses);
|
||||
|
@ -3330,14 +3379,21 @@ fn init_test(cx: &mut gpui::TestAppContext) {
|
|||
fn assert_entry_git_state(
|
||||
tree: &Worktree,
|
||||
path: &str,
|
||||
git_status: Option<GitFileStatus>,
|
||||
worktree_status: Option<StatusCode>,
|
||||
is_ignored: bool,
|
||||
) {
|
||||
let entry = tree.entry_for_path(path).expect("entry {path} not found");
|
||||
let status = tree.status_for_file(Path::new(path));
|
||||
let expected = worktree_status.map(|worktree_status| {
|
||||
TrackedStatus {
|
||||
worktree_status,
|
||||
index_status: StatusCode::Unmodified,
|
||||
}
|
||||
.into()
|
||||
});
|
||||
assert_eq!(
|
||||
tree.status_for_file(Path::new(path)),
|
||||
git_status,
|
||||
"expected {path} to have git status: {git_status:?}"
|
||||
status, expected,
|
||||
"expected {path} to have git status: {expected:?}"
|
||||
);
|
||||
assert_eq!(
|
||||
entry.is_ignored, is_ignored,
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue