Handle more worktree update cases
Co-Authored-By: Cole Miller <53574922+cole-miller@users.noreply.github.com>
This commit is contained in:
@@ -1222,7 +1222,7 @@ impl RandomizedTest for ProjectCollaborationTest {
|
||||
id,
|
||||
guest_project.remote_id(),
|
||||
);
|
||||
assert_eq!(guest_snapshot.repositories().collect::<Vec<_>>(), host_snapshot.repositories().collect::<Vec<_>>(),
|
||||
assert_eq!(guest_snapshot.repositories().iter().collect::<Vec<_>>(), host_snapshot.repositories().iter().collect::<Vec<_>>(),
|
||||
"{} has different repositories than the host for worktree {:?} and project {:?}",
|
||||
client.username,
|
||||
host_snapshot.abs_path(),
|
||||
|
||||
@@ -197,12 +197,10 @@ impl ProjectDiffEditor {
|
||||
let snapshot = worktree.read(cx).snapshot();
|
||||
let applicable_entries = snapshot
|
||||
.repositories()
|
||||
.iter()
|
||||
.flat_map(|entry| {
|
||||
entry.status().map(|git_entry| {
|
||||
(
|
||||
git_entry.combined_status(),
|
||||
entry.join(git_entry.repo_path),
|
||||
)
|
||||
(git_entry.combined_status(), entry.join(git_entry.repo_path))
|
||||
})
|
||||
})
|
||||
.filter_map(|(status, path)| {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use crate::first_worktree_repository;
|
||||
use crate::{first_repository_in_project, first_worktree_repository};
|
||||
use crate::{
|
||||
git_status_icon, settings::GitPanelSettings, CommitAllChanges, CommitChanges, GitState,
|
||||
GitViewMode, RevertAll, StageAll, ToggleStaged, UnstageAll,
|
||||
@@ -129,27 +129,97 @@ impl GitPanel {
|
||||
})
|
||||
.detach();
|
||||
cx.subscribe(&project, move |this, project, event, cx| {
|
||||
use project::Event;
|
||||
|
||||
let first_worktree_id =
|
||||
project.read(cx).worktrees(cx).next().and_then(|worktree| {
|
||||
let snapshot = worktree.read(cx).snapshot();
|
||||
Some(snapshot.id().clone())
|
||||
});
|
||||
let first_repo_in_project = first_repository_in_project(&project, cx);
|
||||
|
||||
// TODO: Don't get another git_state here
|
||||
// was running into a borrow issue
|
||||
let git_state = GitState::get_global(cx);
|
||||
|
||||
match event {
|
||||
project::Event::WorktreeRemoved(_) | project::Event::WorktreeOrderChanged => {
|
||||
// TODO pick a new worktree if ours was removed
|
||||
this.schedule_update();
|
||||
project::Event::WorktreeRemoved(id) => {
|
||||
git_state.update(cx, |state, _| {
|
||||
state.all_repositories.remove(id);
|
||||
let Some((worktree_id, _, _)) = state.active_repository.as_ref() else {
|
||||
return;
|
||||
};
|
||||
if worktree_id == id {
|
||||
state.active_repository = first_repo_in_project;
|
||||
this.schedule_update();
|
||||
}
|
||||
});
|
||||
}
|
||||
project::Event::WorktreeUpdatedEntries(id, _)
|
||||
| project::Event::WorktreeAdded(id)
|
||||
| project::Event::WorktreeUpdatedGitRepositories(id) => {
|
||||
let git_state = git_state.clone();
|
||||
if let Some((first_repo, git_repo)) =
|
||||
first_worktree_repository(&project, *id, cx)
|
||||
{
|
||||
git_state.update(cx, |state, _| {
|
||||
state.activate_repository(first_repo, git_repo)
|
||||
});
|
||||
}
|
||||
this.schedule_update();
|
||||
project::Event::WorktreeOrderChanged => {
|
||||
// activate the new first worktree if the first was moved
|
||||
let Some(first_id) = first_worktree_id else {
|
||||
return;
|
||||
};
|
||||
git_state.update(cx, |state, _| {
|
||||
if !state
|
||||
.active_repository
|
||||
.as_ref()
|
||||
.is_some_and(|(id, _, _)| id == &first_id)
|
||||
{
|
||||
state.active_repository = first_repo_in_project;
|
||||
this.schedule_update();
|
||||
}
|
||||
});
|
||||
}
|
||||
Event::WorktreeAdded(id) => {
|
||||
git_state.update(cx, |state, cx| {
|
||||
let Some(worktree) = project.read(cx).worktree_for_id(*id, cx) else {
|
||||
return;
|
||||
};
|
||||
let snapshot = worktree.read(cx).snapshot();
|
||||
state
|
||||
.all_repositories
|
||||
.insert(*id, snapshot.repositories().clone());
|
||||
});
|
||||
let Some(first_id) = first_worktree_id else {
|
||||
return;
|
||||
};
|
||||
git_state.update(cx, |state, _| {
|
||||
if !state
|
||||
.active_repository
|
||||
.as_ref()
|
||||
.is_some_and(|(id, _, _)| id == &first_id)
|
||||
{
|
||||
state.active_repository = first_repo_in_project;
|
||||
this.schedule_update();
|
||||
}
|
||||
});
|
||||
}
|
||||
project::Event::WorktreeUpdatedEntries(id, _) => {
|
||||
git_state.update(cx, |state, _| {
|
||||
if state
|
||||
.active_repository
|
||||
.as_ref()
|
||||
.is_some_and(|(active_id, _, _)| active_id == id)
|
||||
{
|
||||
this.schedule_update();
|
||||
}
|
||||
});
|
||||
}
|
||||
project::Event::WorktreeUpdatedGitRepositories(_) => {
|
||||
let Some(first) = first_repo_in_project else {
|
||||
return;
|
||||
};
|
||||
git_state.update(cx, |state, _| {
|
||||
if !state
|
||||
.active_repository
|
||||
.as_ref()
|
||||
.is_some_and(|(id, _, _)| id == &first.0)
|
||||
{
|
||||
state.active_repository = Some(first);
|
||||
}
|
||||
this.schedule_update();
|
||||
});
|
||||
}
|
||||
project::Event::Closed => {
|
||||
this.reveal_in_editor = Task::ready(());
|
||||
@@ -222,7 +292,7 @@ impl GitPanel {
|
||||
if let Some((repo, git_repo)) =
|
||||
first_worktree_repository(&project, snapshot.id(), cx)
|
||||
{
|
||||
state.activate_repository(repo, git_repo);
|
||||
state.activate_repository(snapshot.id().clone(), repo, git_repo);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -503,6 +573,38 @@ impl GitPanel {
|
||||
fn open_entry(&self, entry: &GitListEntry) {
|
||||
// TODO: Open entry or entry's changes.
|
||||
println!("Open {} triggered!", entry.repo_path);
|
||||
|
||||
// cx.emit(project_panel::Event::OpenedEntry {
|
||||
// entry_id,
|
||||
// focus_opened_item,
|
||||
// allow_preview,
|
||||
// });
|
||||
//
|
||||
// workspace
|
||||
// .open_path_preview(
|
||||
// ProjectPath {
|
||||
// worktree_id,
|
||||
// path: file_path.clone(),
|
||||
// },
|
||||
// None,
|
||||
// focus_opened_item,
|
||||
// allow_preview,
|
||||
// cx,
|
||||
// )
|
||||
// .detach_and_prompt_err("Failed to open file", cx, move |e, _| {
|
||||
// match e.error_code() {
|
||||
// ErrorCode::Disconnected => if is_via_ssh {
|
||||
// Some("Disconnected from SSH host".to_string())
|
||||
// } else {
|
||||
// Some("Disconnected from remote project".to_string())
|
||||
// },
|
||||
// ErrorCode::UnsharedItem => Some(format!(
|
||||
// "{} is not shared by the host. This could be because it has been marked as `private`",
|
||||
// file_path.display()
|
||||
// )),
|
||||
// _ => None,
|
||||
// }
|
||||
// });
|
||||
}
|
||||
|
||||
fn stage_all(&mut self, _: &StageAll, cx: &mut ViewContext<Self>) {
|
||||
@@ -599,7 +701,7 @@ impl GitPanel {
|
||||
|
||||
self.visible_entries.clear();
|
||||
|
||||
let Some((repo, _)) = git_state.active_repository().as_ref() else {
|
||||
let Some((_, repo, _)) = git_state.active_repository().as_ref() else {
|
||||
// Just clear entries if no repository is active.
|
||||
cx.notify();
|
||||
return;
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
use ::settings::Settings;
|
||||
use collections::HashMap;
|
||||
use futures::{future::FusedFuture, select, FutureExt};
|
||||
use git::{
|
||||
repository::{GitFileStatus, GitRepository, RepoPath},
|
||||
status::GitStatusPair,
|
||||
};
|
||||
use git::repository::{GitFileStatus, GitRepository, RepoPath};
|
||||
use gpui::{actions, AppContext, Context, Global, Hsla, Model, ModelContext};
|
||||
use project::{Project, WorktreeId};
|
||||
use settings::GitPanelSettings;
|
||||
@@ -13,7 +11,8 @@ use std::{
|
||||
sync::Arc,
|
||||
time::Duration,
|
||||
};
|
||||
use ui::{Color, Icon, IconName, IntoElement, SharedString, ToggleState};
|
||||
use sum_tree::SumTree;
|
||||
use ui::{Color, Icon, IconName, IntoElement, SharedString};
|
||||
use worktree::RepositoryEntry;
|
||||
|
||||
pub mod git_panel;
|
||||
@@ -68,10 +67,12 @@ pub struct GitState {
|
||||
|
||||
/// When a git repository is selected, this is used to track which repository's changes
|
||||
/// are currently being viewed or modified in the UI.
|
||||
active_repository: Option<(RepositoryEntry, Arc<dyn GitRepository>)>,
|
||||
active_repository: Option<(WorktreeId, RepositoryEntry, Arc<dyn GitRepository>)>,
|
||||
|
||||
updater_tx: mpsc::Sender<(Arc<dyn GitRepository>, Vec<RepoPath>, StatusAction)>,
|
||||
|
||||
all_repositories: HashMap<WorktreeId, SumTree<RepositoryEntry>>,
|
||||
|
||||
list_view_mode: GitViewMode,
|
||||
}
|
||||
|
||||
@@ -163,6 +164,7 @@ impl GitState {
|
||||
active_repository: None,
|
||||
updater_tx,
|
||||
list_view_mode: GitViewMode::default(),
|
||||
all_repositories: HashMap::default(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -172,13 +174,16 @@ impl GitState {
|
||||
|
||||
pub fn activate_repository(
|
||||
&mut self,
|
||||
worktree_id: WorktreeId,
|
||||
active_repository: RepositoryEntry,
|
||||
git_repo: Arc<dyn GitRepository>,
|
||||
) {
|
||||
self.active_repository = Some((active_repository, git_repo));
|
||||
self.active_repository = Some((worktree_id, active_repository, git_repo));
|
||||
}
|
||||
|
||||
pub fn active_repository(&self) -> Option<&(RepositoryEntry, Arc<dyn GitRepository>)> {
|
||||
pub fn active_repository(
|
||||
&self,
|
||||
) -> Option<&(WorktreeId, RepositoryEntry, Arc<dyn GitRepository>)> {
|
||||
self.active_repository.as_ref()
|
||||
}
|
||||
|
||||
@@ -191,7 +196,7 @@ impl GitState {
|
||||
}
|
||||
|
||||
pub fn stage_entry(&mut self, repo_path: RepoPath) {
|
||||
if let Some((_, git_repo)) = self.active_repository.as_ref() {
|
||||
if let Some((_, _, git_repo)) = self.active_repository.as_ref() {
|
||||
let _ = self
|
||||
.updater_tx
|
||||
.send((git_repo.clone(), vec![repo_path], StatusAction::Stage));
|
||||
@@ -199,7 +204,7 @@ impl GitState {
|
||||
}
|
||||
|
||||
pub fn unstage_entry(&mut self, repo_path: RepoPath) {
|
||||
if let Some((_, git_repo)) = self.active_repository.as_ref() {
|
||||
if let Some((_, _, git_repo)) = self.active_repository.as_ref() {
|
||||
let _ =
|
||||
self.updater_tx
|
||||
.send((git_repo.clone(), vec![repo_path], StatusAction::Unstage));
|
||||
@@ -207,7 +212,7 @@ impl GitState {
|
||||
}
|
||||
|
||||
pub fn stage_entries(&mut self, entries: Vec<RepoPath>) {
|
||||
if let Some((_, git_repo)) = self.active_repository.as_ref() {
|
||||
if let Some((_, _, git_repo)) = self.active_repository.as_ref() {
|
||||
let _ = self
|
||||
.updater_tx
|
||||
.send((git_repo.clone(), entries, StatusAction::Stage));
|
||||
@@ -215,7 +220,7 @@ impl GitState {
|
||||
}
|
||||
|
||||
fn act_on_all(&mut self, action: StatusAction) {
|
||||
if let Some((active_repository, git_repo)) = self.active_repository.as_ref() {
|
||||
if let Some((_, active_repository, git_repo)) = self.active_repository.as_ref() {
|
||||
let _ = self.updater_tx.send((
|
||||
git_repo.clone(),
|
||||
active_repository
|
||||
@@ -241,12 +246,37 @@ pub fn first_worktree_repository(
|
||||
worktree_id: WorktreeId,
|
||||
cx: &mut AppContext,
|
||||
) -> Option<(RepositoryEntry, Arc<dyn GitRepository>)> {
|
||||
let worktree = project.read(cx).worktree_for_id(worktree_id, cx)?;
|
||||
let snapshot = worktree.read(cx).snapshot();
|
||||
let repo = snapshot.repositories().next()?.clone();
|
||||
let local = worktree.read(cx).as_local()?;
|
||||
let git_repo = local.get_local_repo(&repo)?.repo().clone();
|
||||
Some((repo, git_repo))
|
||||
project
|
||||
.read(cx)
|
||||
.worktree_for_id(worktree_id, cx)
|
||||
.and_then(|worktree| {
|
||||
let snapshot = worktree.read(cx).snapshot();
|
||||
let repo = snapshot.repositories().iter().next()?.clone();
|
||||
let git_repo = worktree
|
||||
.read(cx)
|
||||
.as_local()?
|
||||
.get_local_repo(&repo)?
|
||||
.repo()
|
||||
.clone();
|
||||
Some((repo, git_repo))
|
||||
})
|
||||
}
|
||||
|
||||
pub fn first_repository_in_project(
|
||||
project: &Model<Project>,
|
||||
cx: &mut AppContext,
|
||||
) -> Option<(WorktreeId, RepositoryEntry, Arc<dyn GitRepository>)> {
|
||||
project.read(cx).worktrees(cx).next().and_then(|worktree| {
|
||||
let snapshot = worktree.read(cx).snapshot();
|
||||
let repo = snapshot.repositories().iter().next()?.clone();
|
||||
let git_repo = worktree
|
||||
.read(cx)
|
||||
.as_local()?
|
||||
.get_local_repo(&repo)?
|
||||
.repo()
|
||||
.clone();
|
||||
Some((snapshot.id(), repo, git_repo))
|
||||
})
|
||||
}
|
||||
|
||||
const ADDED_COLOR: Hsla = Hsla {
|
||||
|
||||
@@ -2581,8 +2581,8 @@ impl Snapshot {
|
||||
.map(|repo| repo.status().collect())
|
||||
}
|
||||
|
||||
pub fn repositories(&self) -> impl Iterator<Item = &RepositoryEntry> {
|
||||
self.repositories.iter()
|
||||
pub fn repositories(&self) -> &SumTree<RepositoryEntry> {
|
||||
&self.repositories
|
||||
}
|
||||
|
||||
/// Get the repository whose work directory corresponds to the given path.
|
||||
@@ -2616,7 +2616,7 @@ impl Snapshot {
|
||||
entries: impl 'a + Iterator<Item = &'a Entry>,
|
||||
) -> impl 'a + Iterator<Item = (&'a Entry, Option<&'a RepositoryEntry>)> {
|
||||
let mut containing_repos = Vec::<&RepositoryEntry>::new();
|
||||
let mut repositories = self.repositories().peekable();
|
||||
let mut repositories = self.repositories().iter().peekable();
|
||||
entries.map(move |entry| {
|
||||
while let Some(repository) = containing_repos.last() {
|
||||
if repository.directory_contains(&entry.path) {
|
||||
|
||||
@@ -2179,7 +2179,7 @@ async fn test_rename_work_directory(cx: &mut TestAppContext) {
|
||||
|
||||
cx.read(|cx| {
|
||||
let tree = tree.read(cx);
|
||||
let repo = tree.repositories().next().unwrap();
|
||||
let repo = tree.repositories().iter().next().unwrap();
|
||||
assert_eq!(repo.path.as_ref(), Path::new("projects/project1"));
|
||||
assert_eq!(
|
||||
tree.status_for_file(Path::new("projects/project1/a")),
|
||||
@@ -2200,7 +2200,7 @@ async fn test_rename_work_directory(cx: &mut TestAppContext) {
|
||||
|
||||
cx.read(|cx| {
|
||||
let tree = tree.read(cx);
|
||||
let repo = tree.repositories().next().unwrap();
|
||||
let repo = tree.repositories().iter().next().unwrap();
|
||||
assert_eq!(repo.path.as_ref(), Path::new("projects/project2"));
|
||||
assert_eq!(
|
||||
tree.status_for_file(Path::new("projects/project2/a")),
|
||||
@@ -2380,8 +2380,8 @@ async fn test_file_status(cx: &mut TestAppContext) {
|
||||
// Check that the right git state is observed on startup
|
||||
tree.read_with(cx, |tree, _cx| {
|
||||
let snapshot = tree.snapshot();
|
||||
assert_eq!(snapshot.repositories().count(), 1);
|
||||
let repo_entry = snapshot.repositories().next().unwrap();
|
||||
assert_eq!(snapshot.repositories().iter().count(), 1);
|
||||
let repo_entry = snapshot.repositories().iter().next().unwrap();
|
||||
assert_eq!(repo_entry.path.as_ref(), Path::new("project"));
|
||||
assert!(repo_entry.location_in_repo.is_none());
|
||||
|
||||
@@ -2554,7 +2554,7 @@ async fn test_git_repository_status(cx: &mut TestAppContext) {
|
||||
// Check that the right git state is observed on startup
|
||||
tree.read_with(cx, |tree, _cx| {
|
||||
let snapshot = tree.snapshot();
|
||||
let repo = snapshot.repositories().next().unwrap();
|
||||
let repo = snapshot.repositories().iter().next().unwrap();
|
||||
let entries = repo.status().collect::<Vec<_>>();
|
||||
|
||||
assert_eq!(entries.len(), 3);
|
||||
@@ -2576,7 +2576,7 @@ async fn test_git_repository_status(cx: &mut TestAppContext) {
|
||||
|
||||
tree.read_with(cx, |tree, _cx| {
|
||||
let snapshot = tree.snapshot();
|
||||
let repository = snapshot.repositories().next().unwrap();
|
||||
let repository = snapshot.repositories().iter().next().unwrap();
|
||||
let entries = repository.status().collect::<Vec<_>>();
|
||||
|
||||
std::assert_eq!(entries.len(), 4, "entries: {entries:?}");
|
||||
@@ -2609,7 +2609,7 @@ async fn test_git_repository_status(cx: &mut TestAppContext) {
|
||||
|
||||
tree.read_with(cx, |tree, _cx| {
|
||||
let snapshot = tree.snapshot();
|
||||
let repo = snapshot.repositories().next().unwrap();
|
||||
let repo = snapshot.repositories().iter().next().unwrap();
|
||||
let entries = repo.status().collect::<Vec<_>>();
|
||||
|
||||
// Deleting an untracked entry, b.txt, should leave no status
|
||||
@@ -2676,8 +2676,8 @@ async fn test_repository_subfolder_git_status(cx: &mut TestAppContext) {
|
||||
// Ensure that the git status is loaded correctly
|
||||
tree.read_with(cx, |tree, _cx| {
|
||||
let snapshot = tree.snapshot();
|
||||
assert_eq!(snapshot.repositories().count(), 1);
|
||||
let repo = snapshot.repositories().next().unwrap();
|
||||
assert_eq!(snapshot.repositories().iter().count(), 1);
|
||||
let repo = snapshot.repositories().iter().next().unwrap();
|
||||
// Path is blank because the working directory of
|
||||
// the git repository is located at the root of the project
|
||||
assert_eq!(repo.path.as_ref(), Path::new(""));
|
||||
@@ -2707,7 +2707,7 @@ async fn test_repository_subfolder_git_status(cx: &mut TestAppContext) {
|
||||
tree.read_with(cx, |tree, _cx| {
|
||||
let snapshot = tree.snapshot();
|
||||
|
||||
assert!(snapshot.repositories().next().is_some());
|
||||
assert!(snapshot.repositories().iter().next().is_some());
|
||||
|
||||
assert_eq!(snapshot.status_for_file("c.txt"), None);
|
||||
assert_eq!(snapshot.status_for_file("d/e.txt"), None);
|
||||
|
||||
Reference in New Issue
Block a user