Compare commits

...

7 Commits

Author SHA1 Message Date
Cole Miller
3d43ee72e3 Merge remote-tracking branch 'origin/main' into remove-repositories-when-removing-folders 2025-04-24 11:19:47 -04:00
Cole Miller
f80bccbf7d more clippy 2025-04-17 17:09:53 -04:00
Cole Miller
8d2f39f108 Clippy 2025-04-17 17:07:14 -04:00
Cole Miller
05490eb1ea Pick another repository when the active repo is removed 2025-04-17 17:04:35 -04:00
Cole Miller
63a3b1037f Remove strong repository references from git UI 2025-04-17 16:21:16 -04:00
Cole Miller
ecc7d82202 Clean up dangling weak references 2025-04-17 14:46:09 -04:00
Mikayla Maki
71645aedb6 Only retain repositories that are associated with an active worktreee
Co-authored-by: Cole Miller <m@cole-miller.net>
2025-04-17 10:04:25 -07:00
13 changed files with 322 additions and 121 deletions

View File

@@ -1949,13 +1949,12 @@ impl Thread {
.update(cx, |git_store, cx| {
git_store
.repositories()
.values()
.map(|(_, repository)| repository)
.find(|repo| {
repo.read(cx)
.abs_path_to_repo_path(&worktree.read(cx).abs_path())
.is_some()
})
.cloned()
})
.ok()
.flatten()

View File

@@ -2892,7 +2892,10 @@ async fn test_git_branch_name(
#[track_caller]
fn assert_branch(branch_name: Option<impl Into<String>>, project: &Project, cx: &App) {
let branch_name = branch_name.map(Into::into);
let repositories = project.repositories(cx).values().collect::<Vec<_>>();
let repositories = project
.repositories(cx)
.map(|(_, repo)| repo)
.collect::<Vec<_>>();
assert_eq!(repositories.len(), 1);
let repository = repositories[0].clone();
assert_eq!(
@@ -3026,8 +3029,7 @@ async fn test_git_status_sync(
let file = file.as_ref();
let repos = project
.repositories(cx)
.values()
.cloned()
.map(|(_, repository)| repository)
.collect::<Vec<_>>();
assert_eq!(repos.len(), 1);
let repo = repos.into_iter().next().unwrap();
@@ -6882,7 +6884,7 @@ async fn test_remote_git_branches(
project_a.update(cx, |project, cx| {
project
.repositories(cx)
.values()
.map(|(_, repository)| repository)
.next()
.unwrap()
.read(cx)
@@ -6920,7 +6922,7 @@ async fn test_remote_git_branches(
project_a.update(cx, |project, cx| {
project
.repositories(cx)
.values()
.map(|(_, repository)| repository)
.next()
.unwrap()
.read(cx)

View File

@@ -314,7 +314,7 @@ async fn test_ssh_collaboration_git_branches(
headless_project.git_store.update(cx, |git_store, cx| {
git_store
.repositories()
.values()
.map(|(_, repository)| repository)
.next()
.unwrap()
.read(cx)
@@ -354,7 +354,7 @@ async fn test_ssh_collaboration_git_branches(
headless_project.git_store.update(cx, |git_store, cx| {
git_store
.repositories()
.values()
.map(|(_, repository)| repository)
.next()
.unwrap()
.read(cx)

View File

@@ -100,7 +100,7 @@ impl BlameRenderer for GitBlameRenderer {
CommitTooltip::blame_entry(
&blame_entry,
details.clone(),
repository.clone(),
repository.downgrade(),
workspace.clone(),
cx,
)
@@ -149,7 +149,7 @@ impl BlameRenderer for GitBlameRenderer {
CommitTooltip::blame_entry(
&blame_entry,
details.clone(),
repository.clone(),
repository.downgrade(),
workspace.clone(),
cx,
)

View File

@@ -5,7 +5,7 @@ use git::repository::Branch;
use gpui::{
App, Context, DismissEvent, Entity, EventEmitter, FocusHandle, Focusable, InteractiveElement,
IntoElement, Modifiers, ModifiersChangedEvent, ParentElement, Render, SharedString, Styled,
Subscription, Task, Window, rems,
Subscription, Task, WeakEntity, Window, rems,
};
use picker::{Picker, PickerDelegate, PickerEditorPosition};
use project::git_store::Repository;
@@ -47,7 +47,11 @@ pub fn open(
window: &mut Window,
cx: &mut Context<Workspace>,
) {
let repository = workspace.project().read(cx).active_repository(cx).clone();
let repository = workspace
.project()
.read(cx)
.active_repository(cx)
.map(|repo| repo.downgrade());
let style = BranchListStyle::Modal;
workspace.toggle_modal(window, cx, |window, cx| {
BranchList::new(repository, style, rems(34.), window, cx)
@@ -55,7 +59,7 @@ pub fn open(
}
pub fn popover(
repository: Option<Entity<Repository>>,
repository: Option<WeakEntity<Repository>>,
window: &mut Window,
cx: &mut App,
) -> Entity<BranchList> {
@@ -80,7 +84,7 @@ pub struct BranchList {
impl BranchList {
fn new(
repository: Option<Entity<Repository>>,
repository: Option<WeakEntity<Repository>>,
style: BranchListStyle,
width: Rems,
window: &mut Window,
@@ -88,6 +92,7 @@ impl BranchList {
) -> Self {
let all_branches_request = repository
.clone()
.and_then(|repo| repo.upgrade())
.map(|repository| repository.update(cx, |repository, _| repository.branches()));
cx.spawn_in(window, async move |this, cx| {
@@ -172,7 +177,7 @@ struct BranchEntry {
pub struct BranchListDelegate {
matches: Vec<BranchEntry>,
all_branches: Option<Vec<Branch>>,
repo: Option<Entity<Repository>>,
repo: Option<WeakEntity<Repository>>,
style: BranchListStyle,
selected_index: usize,
last_query: String,
@@ -180,7 +185,7 @@ pub struct BranchListDelegate {
}
impl BranchListDelegate {
fn new(repo: Option<Entity<Repository>>, style: BranchListStyle) -> Self {
fn new(repo: Option<WeakEntity<Repository>>, style: BranchListStyle) -> Self {
Self {
matches: vec![],
repo,
@@ -339,10 +344,11 @@ impl PickerDelegate for BranchListDelegate {
return;
}
let current_branch = self.repo.as_ref().map(|repo| {
let current_branch = self.repo.as_ref().and_then(|repo| {
repo.update(cx, |repo, _| {
repo.branch.as_ref().map(|branch| branch.name.clone())
})
.ok()
});
if current_branch
@@ -470,7 +476,11 @@ impl PickerDelegate for BranchListDelegate {
let message = if entry.is_new {
if let Some(current_branch) =
self.repo.as_ref().and_then(|repo| {
repo.read(cx).branch.as_ref().map(|b| b.name.clone())
repo.upgrade()?
.read(cx)
.branch
.as_ref()
.map(|b| b.name.clone())
})
{
format!("based off {}", current_branch)

View File

@@ -320,7 +320,7 @@ impl CommitModal {
let branch = active_repo
.as_ref()
.and_then(|repo| repo.read(cx).branch.as_ref())
.and_then(|repo| repo.upgrade()?.read(cx).branch.as_ref())
.map(|b| b.name.clone())
.unwrap_or_else(|| "<no branch>".into());

View File

@@ -108,7 +108,7 @@ pub struct CommitTooltip {
commit: CommitDetails,
scroll_handle: ScrollHandle,
markdown: Entity<Markdown>,
repository: Entity<Repository>,
repository: WeakEntity<Repository>,
workspace: WeakEntity<Workspace>,
}
@@ -116,7 +116,7 @@ impl CommitTooltip {
pub fn blame_entry(
blame: &BlameEntry,
details: Option<ParsedCommitMessage>,
repository: Entity<Repository>,
repository: WeakEntity<Repository>,
workspace: WeakEntity<Workspace>,
cx: &mut Context<Self>,
) -> Self {
@@ -145,7 +145,7 @@ impl CommitTooltip {
pub fn new(
commit: CommitDetails,
repository: Entity<Repository>,
repository: WeakEntity<Repository>,
workspace: WeakEntity<Workspace>,
cx: &mut Context<Self>,
) -> Self {
@@ -314,7 +314,7 @@ impl Render for CommitTooltip {
move |_, window, cx| {
CommitView::open(
commit_summary.clone(),
repo.downgrade(),
repo.clone(),
workspace.clone(),
window,
cx,

View File

@@ -321,7 +321,7 @@ impl ScrollbarProperties {
}
pub struct GitPanel {
pub(crate) active_repository: Option<Entity<Repository>>,
pub(crate) active_repository: Option<WeakEntity<Repository>>,
pub(crate) commit_editor: Entity<Editor>,
conflicted_count: usize,
conflicted_staged_count: usize,
@@ -397,7 +397,10 @@ impl GitPanel {
) -> Self {
let fs = app_state.fs.clone();
let git_store = project.read(cx).git_store().clone();
let active_repository = project.read(cx).active_repository(cx);
let active_repository = project
.read(cx)
.active_repository(cx)
.map(|repo| repo.downgrade());
let workspace = workspace.downgrade();
let focus_handle = cx.focus_handle();
@@ -425,7 +428,10 @@ impl GitPanel {
window,
move |this, git_store, event, window, cx| match event {
GitStoreEvent::ActiveRepositoryChanged(_) => {
this.active_repository = git_store.read(cx).active_repository();
this.active_repository = git_store
.read(cx)
.active_repository()
.map(|repo| repo.downgrade());
this.schedule_update(true, window, cx);
}
GitStoreEvent::RepositoryUpdated(
@@ -644,7 +650,11 @@ impl GitPanel {
_: &mut Window,
cx: &mut Context<Self>,
) {
let Some(git_repo) = self.active_repository.as_ref() else {
let Some(git_repo) = self
.active_repository
.as_ref()
.and_then(|repo| repo.upgrade())
else {
return;
};
let Some(repo_path) = git_repo.read(cx).project_path_to_repo_path(&path, cx) else {
@@ -813,6 +823,7 @@ impl GitPanel {
let have_entries = self
.active_repository
.as_ref()
.and_then(|repo| repo.upgrade())
.map_or(false, |active_repository| {
active_repository.read(cx).status_summary().count > 0
});
@@ -843,7 +854,10 @@ impl GitPanel {
maybe!({
let entry = self.entries.get(self.selected_entry?)?.status_entry()?;
let workspace = self.workspace.upgrade()?;
let git_repo = self.active_repository.as_ref()?;
let git_repo = self
.active_repository
.as_ref()
.and_then(|repo| repo.upgrade())?;
if let Some(project_diff) = workspace.read(cx).active_item_as::<ProjectDiff>(cx) {
if let Some(project_path) = project_diff.read(cx).active_path(cx) {
@@ -879,7 +893,10 @@ impl GitPanel {
) {
maybe!({
let entry = self.entries.get(self.selected_entry?)?.status_entry()?;
let active_repo = self.active_repository.as_ref()?;
let active_repo = self
.active_repository
.as_ref()
.and_then(|repo| repo.upgrade())?;
let path = active_repo
.read(cx)
.repo_path_to_project_path(&entry.repo_path, cx)?;
@@ -955,7 +972,10 @@ impl GitPanel {
cx: &mut Context<Self>,
) {
maybe!({
let active_repo = self.active_repository.clone()?;
let active_repo = self
.active_repository
.as_ref()
.and_then(|repo| repo.upgrade())?;
let path = active_repo
.read(cx)
.repo_path_to_project_path(&entry.repo_path, cx)?;
@@ -998,7 +1018,11 @@ impl GitPanel {
fn perform_checkout(&mut self, entries: Vec<GitStatusEntry>, cx: &mut Context<Self>) {
let workspace = self.workspace.clone();
let Some(active_repository) = self.active_repository.clone() else {
let Some(active_repository) = self
.active_repository
.as_ref()
.and_then(|repo| repo.upgrade())
else {
return;
};
@@ -1137,7 +1161,11 @@ impl GitPanel {
fn clean_all(&mut self, _: &TrashUntrackedFiles, window: &mut Window, cx: &mut Context<Self>) {
let workspace = self.workspace.clone();
let Some(active_repo) = self.active_repository.clone() else {
let Some(active_repo) = self
.active_repository
.as_ref()
.and_then(|repo| repo.upgrade())
else {
return;
};
let to_delete = self
@@ -1233,7 +1261,11 @@ impl GitPanel {
_window: &mut Window,
cx: &mut Context<Self>,
) {
let Some(active_repository) = self.active_repository.as_ref() else {
let Some(active_repository) = self
.active_repository
.as_ref()
.and_then(|repo| repo.upgrade())
else {
return;
};
let (stage, repo_paths) = match entry {
@@ -1270,7 +1302,11 @@ impl GitPanel {
entries: Vec<GitStatusEntry>,
cx: &mut Context<Self>,
) {
let Some(active_repository) = self.active_repository.clone() else {
let Some(active_repository) = self
.active_repository
.as_ref()
.and_then(|repo| repo.upgrade())
else {
return;
};
let op_id = self.pending.iter().map(|p| p.op_id).max().unwrap_or(0) + 1;
@@ -1430,7 +1466,11 @@ impl GitPanel {
if !self.commit_editor.read(cx).is_empty(cx) {
return;
}
let Some(active_repository) = self.active_repository.as_ref() else {
let Some(active_repository) = self
.active_repository
.as_ref()
.and_then(|repo| repo.upgrade())
else {
return;
};
let Some(recent_sha) = active_repository
@@ -1480,7 +1520,11 @@ impl GitPanel {
window: &mut Window,
cx: &mut Context<Self>,
) {
let Some(active_repository) = self.active_repository.clone() else {
let Some(active_repository) = self
.active_repository
.as_ref()
.and_then(|repo| repo.upgrade())
else {
return;
};
let error_spawn = |message, window: &mut Window, cx: &mut App| {
@@ -1702,7 +1746,11 @@ impl GitPanel {
None => return,
};
let Some(repo) = self.active_repository.as_ref() else {
let Some(repo) = self
.active_repository
.as_ref()
.and_then(|repo| repo.upgrade())
else {
return;
};
@@ -1905,7 +1953,11 @@ impl GitPanel {
if !self.can_push_and_pull(cx) {
return;
}
let Some(repo) = self.active_repository.clone() else {
let Some(repo) = self
.active_repository
.as_ref()
.and_then(|repo| repo.upgrade())
else {
return;
};
let Some(branch) = repo.read(cx).branch.as_ref() else {
@@ -1957,7 +2009,11 @@ impl GitPanel {
if !self.can_push_and_pull(cx) {
return;
}
let Some(repo) = self.active_repository.clone() else {
let Some(repo) = self
.active_repository
.as_ref()
.and_then(|repo| repo.upgrade())
else {
return;
};
let Some(branch) = repo.read(cx).branch.as_ref() else {
@@ -2237,7 +2293,11 @@ impl GitPanel {
}
fn reopen_commit_buffer(&mut self, window: &mut Window, cx: &mut Context<Self>) {
let Some(active_repo) = self.active_repository.as_ref() else {
let Some(active_repo) = self
.active_repository
.as_ref()
.and_then(|repo| repo.upgrade())
else {
return;
};
let load_buffer = active_repo.update(cx, |active_repo, cx| {
@@ -2300,7 +2360,11 @@ impl GitPanel {
let mut staged_count = 0;
let mut max_width_item: Option<(RepoPath, usize)> = None;
let Some(repo) = self.active_repository.as_ref() else {
let Some(repo) = self
.active_repository
.as_ref()
.and_then(|repo| repo.upgrade())
else {
// Just clear entries if no repository is active.
cx.notify();
return;
@@ -2906,7 +2970,13 @@ impl GitPanel {
}
pub(crate) fn render_remote_button(&self, cx: &mut Context<Self>) -> Option<AnyElement> {
let branch = self.active_repository.as_ref()?.read(cx).branch.clone();
let branch = self
.active_repository
.as_ref()?
.upgrade()?
.read(cx)
.branch
.clone();
if !self.can_push_and_pull(cx) {
return None;
}
@@ -2933,7 +3003,10 @@ impl GitPanel {
window: &mut Window,
cx: &mut Context<Self>,
) -> Option<impl IntoElement> {
let active_repository = self.active_repository.clone()?;
let active_repository = self
.active_repository
.as_ref()
.and_then(|repo| repo.upgrade())?;
let (can_commit, tooltip) = self.configure_commit_button(cx);
let panel_editor_style = panel_editor_style(true, window, cx);
@@ -3248,7 +3321,10 @@ impl GitPanel {
}
fn render_previous_commit(&self, cx: &mut Context<Self>) -> Option<impl IntoElement> {
let active_repository = self.active_repository.as_ref()?;
let active_repository = self
.active_repository
.as_ref()
.and_then(|weak| weak.upgrade())?;
let branch = active_repository.read(cx).branch.as_ref()?;
let commit = branch.most_recent_commit.as_ref()?.clone();
let workspace = self.workspace.clone();
@@ -3293,7 +3369,7 @@ impl GitPanel {
GitPanelMessageTooltip::new(
this.clone(),
commit.sha.clone(),
repo.clone(),
repo.downgrade(),
window,
cx,
)
@@ -3328,6 +3404,10 @@ impl GitPanel {
}
fn render_empty_state(&self, cx: &mut Context<Self>) -> impl IntoElement {
let active_repository = self
.active_repository
.as_ref()
.and_then(|repo| repo.upgrade());
h_flex()
.h_full()
.flex_grow()
@@ -3337,29 +3417,50 @@ impl GitPanel {
v_flex()
.gap_2()
.child(h_flex().w_full().justify_around().child(
if self.active_repository.is_some() {
if active_repository.is_some() {
"No changes to commit"
} else {
"No Git repositories"
"No active repository"
},
))
.children({
let repo_count = self.project.read(cx).repositories(cx).count();
let worktree_count = self.project.read(cx).visible_worktrees(cx).count();
(worktree_count > 0 && self.active_repository.is_none()).then(|| {
h_flex().w_full().justify_around().child(
panel_filled_button("Initialize Repository")
.tooltip(Tooltip::for_action_title_in(
"git init",
&git::Init,
&self.focus_handle,
))
.on_click(move |_, _, cx| {
cx.defer(move |cx| {
cx.dispatch_action(&git::Init);
})
}),
if active_repository.is_none() && repo_count > 0 {
Some(
h_flex().w_full().justify_around().child(
panel_filled_button("Choose Repository")
.tooltip(Tooltip::for_action_title_in(
"Choose Repository",
&zed_actions::git::SelectRepo,
&self.focus_handle,
))
.on_click(move |_, _, cx| {
cx.defer(move |cx| {
cx.dispatch_action(&zed_actions::git::SelectRepo);
})
}),
),
)
})
} else if active_repository.is_none() && worktree_count > 0 {
Some(
h_flex().w_full().justify_around().child(
panel_filled_button("Initialize Repository")
.tooltip(Tooltip::for_action_title_in(
"git init",
&git::Init,
&self.focus_handle,
))
.on_click(move |_, _, cx| {
cx.defer(move |cx| {
cx.dispatch_action(&git::Init);
})
}),
),
)
} else {
None
}
})
.text_ui_sm(cx)
.mx_auto()
@@ -3478,7 +3579,7 @@ impl GitPanel {
_: &Window,
cx: &App,
) -> Option<AnyElement> {
let repo = self.active_repository.as_ref()?.read(cx);
let repo = self.active_repository.as_ref()?.upgrade()?.read(cx);
let project_path = (file.worktree_id(cx), file.path()).into();
let repo_path = repo.project_path_to_repo_path(&project_path, cx)?;
let ix = self.entry_by_path(&repo_path)?;
@@ -3715,7 +3816,11 @@ impl GitPanel {
sha: String,
cx: &mut Context<Self>,
) -> Task<anyhow::Result<CommitDetails>> {
let Some(repo) = self.active_repository.clone() else {
let Some(repo) = self
.active_repository
.as_ref()
.and_then(|repo| repo.upgrade())
else {
return Task::ready(Err(anyhow::anyhow!("no active repo")));
};
repo.update(cx, |repo, cx| {
@@ -4242,7 +4347,7 @@ impl GitPanelMessageTooltip {
fn new(
git_panel: Entity<GitPanel>,
sha: SharedString,
repository: Entity<Repository>,
repository: WeakEntity<Repository>,
window: &mut Window,
cx: &mut App,
) -> Entity<Self> {
@@ -4344,7 +4449,7 @@ impl RenderOnce for PanelRepoFooter {
let single_repo = project
.as_ref()
.map(|project| project.read(cx).git_store().read(cx).repositories().len() == 1)
.map(|project| project.read(cx).git_store().read(cx).repositories().count() == 1)
.unwrap_or(true);
const MAX_BRANCH_LEN: usize = 16;

View File

@@ -40,8 +40,7 @@ impl RepositorySelector {
let repository_entries = git_store.update(cx, |git_store, _cx| {
git_store
.repositories()
.values()
.cloned()
.map(|(_, repository)| repository)
.collect::<Vec<_>>()
});
let filtered_repositories = repository_entries.clone();

View File

@@ -46,6 +46,7 @@ use rpc::{
proto::{self, FromProto, SSH_PROJECT_ID, ToProto, git_reset, split_repository_update},
};
use serde::Deserialize;
use settings::WorktreeId;
use std::{
cmp::Ordering,
collections::{BTreeSet, VecDeque},
@@ -71,7 +72,9 @@ pub struct GitStore {
state: GitStoreState,
buffer_store: Entity<BufferStore>,
worktree_store: Entity<WorktreeStore>,
repositories: HashMap<RepositoryId, Entity<Repository>>,
repositories: HashMap<RepositoryId, WeakEntity<Repository>>,
repositories_for_worktree: HashMap<WorktreeId, Vec<Entity<Repository>>>,
shared_repositories: HashMap<RepositoryId, Entity<Repository>>,
active_repo_id: Option<RepositoryId>,
#[allow(clippy::type_complexity)]
loading_diffs:
@@ -403,12 +406,14 @@ impl GitStore {
state,
buffer_store,
worktree_store,
shared_repositories: HashMap::default(),
repositories: HashMap::default(),
active_repo_id: None,
_subscriptions,
loading_diffs: HashMap::default(),
shared_diffs: HashMap::default(),
diffs: HashMap::default(),
repositories_for_worktree: HashMap::default(),
}
}
@@ -452,7 +457,7 @@ impl GitStore {
downstream: downstream_client,
..
} => {
for repo in self.repositories.values() {
for repo in self.repositories.values().filter_map(|repo| repo.upgrade()) {
let update = repo.read(cx).snapshot.initial_update(project_id);
for update in split_repository_update(update) {
client.send(update).log_err();
@@ -467,6 +472,9 @@ impl GitStore {
let mut snapshots = HashMap::default();
let (updates_tx, mut updates_rx) = mpsc::unbounded();
for repo in self.repositories.values() {
let Some(repo) = repo.upgrade() else {
continue;
};
updates_tx
.unbounded_send(DownstreamUpdate::UpdateRepository(
repo.read(cx).snapshot.clone(),
@@ -558,7 +566,7 @@ impl GitStore {
pub fn active_repository(&self) -> Option<Entity<Repository>> {
self.active_repo_id
.as_ref()
.map(|id| self.repositories[&id].clone())
.and_then(|id| self.repositories[&id].upgrade())
}
pub fn open_unstaged_diff(
@@ -821,7 +829,7 @@ impl GitStore {
pub fn checkpoint(&self, cx: &mut App) -> Task<Result<GitStoreCheckpoint>> {
let mut work_directory_abs_paths = Vec::new();
let mut checkpoints = Vec::new();
for repository in self.repositories.values() {
for (_, repository) in self.repositories() {
repository.update(cx, |repository, _| {
work_directory_abs_paths.push(repository.snapshot.work_directory_abs_path.clone());
checkpoints.push(repository.checkpoint().map(|checkpoint| checkpoint?));
@@ -844,15 +852,14 @@ impl GitStore {
checkpoint: GitStoreCheckpoint,
cx: &mut App,
) -> Task<Result<()>> {
let repositories_by_work_dir_abs_path = self
.repositories
.values()
.map(|repo| (repo.read(cx).snapshot.work_directory_abs_path.clone(), repo))
.collect::<HashMap<_, _>>();
let repositories_by_work_directory_abs_path =
self.repositories_by_work_directory_abs_path(cx);
let mut tasks = Vec::new();
for (work_dir_abs_path, checkpoint) in checkpoint.checkpoints_by_work_dir_abs_path {
if let Some(repository) = repositories_by_work_dir_abs_path.get(&work_dir_abs_path) {
if let Some(repository) =
repositories_by_work_directory_abs_path.get(&work_dir_abs_path)
{
let restore = repository.update(cx, |repository, _| {
repository.restore_checkpoint(checkpoint)
});
@@ -872,11 +879,7 @@ impl GitStore {
mut right: GitStoreCheckpoint,
cx: &mut App,
) -> Task<Result<bool>> {
let repositories_by_work_dir_abs_path = self
.repositories
.values()
.map(|repo| (repo.read(cx).snapshot.work_directory_abs_path.clone(), repo))
.collect::<HashMap<_, _>>();
let repositories_by_work_dir_abs_path = self.repositories_by_work_directory_abs_path(cx);
let mut tasks = Vec::new();
for (work_dir_abs_path, left_checkpoint) in left.checkpoints_by_work_dir_abs_path {
@@ -1129,6 +1132,7 @@ impl GitStore {
return;
}
self.update_repositories_from_worktree(
*worktree_id,
project_environment.clone(),
next_repository_id.clone(),
downstream
@@ -1140,6 +1144,25 @@ impl GitStore {
);
self.local_worktree_git_repos_changed(worktree, changed_repos, cx);
}
WorktreeStoreEvent::WorktreeRemoved(_, worktree_id) => {
for repo in self
.repositories_for_worktree
.remove(worktree_id)
.unwrap_or_default()
{
let id = repo.read(cx).id;
if self.active_repo_id == Some(id) {
if !self
.repositories_for_worktree
.values()
.any(|repos| repos.iter().any(|repo| repo.read(cx).id == id))
{
self.repositories.remove(&id);
self.reset_active_repository(cx);
}
}
}
}
_ => {}
}
}
@@ -1194,6 +1217,7 @@ impl GitStore {
/// Update our list of repositories and schedule git scans in response to a notification from a worktree,
fn update_repositories_from_worktree(
&mut self,
worktree_id: WorktreeId,
project_environment: Entity<ProjectEnvironment>,
next_repository_id: Arc<AtomicU64>,
updates_tx: Option<mpsc::UnboundedSender<DownstreamUpdate>>,
@@ -1202,8 +1226,9 @@ impl GitStore {
cx: &mut Context<Self>,
) {
let mut removed_ids = Vec::new();
self.repositories.retain(|_, weak| weak.is_upgradable());
for update in updated_git_repositories.iter() {
if let Some((id, existing)) = self.repositories.iter().find(|(_, repo)| {
if let Some((id, existing)) = self.repositories().find(|(_, repo)| {
let existing_work_directory_abs_path =
repo.read(cx).work_directory_abs_path.clone();
Some(&existing_work_directory_abs_path)
@@ -1219,7 +1244,7 @@ impl GitStore {
existing.schedule_scan(updates_tx.clone(), cx);
});
} else {
removed_ids.push(*id);
removed_ids.push(id);
}
} else if let UpdatedGitRepository {
new_work_directory_abs_path: Some(work_directory_abs_path),
@@ -1250,21 +1275,32 @@ impl GitStore {
.push(cx.subscribe(&repo, Self::on_repository_event));
self._subscriptions
.push(cx.subscribe(&repo, Self::on_jobs_updated));
self.repositories.insert(id, repo);
self.repositories.insert(id, repo.downgrade());
self.repositories_for_worktree
.entry(worktree_id)
.or_default()
.push(repo);
cx.emit(GitStoreEvent::RepositoryAdded(id));
self.active_repo_id.get_or_insert_with(|| {
cx.emit(GitStoreEvent::ActiveRepositoryChanged(Some(id)));
id
});
if self.active_repo_id.is_none() {
self.reset_active_repository(cx);
}
}
}
for id in removed_ids {
if self.active_repo_id == Some(id) {
self.active_repo_id = None;
cx.emit(GitStoreEvent::ActiveRepositoryChanged(None));
}
// Remove all references to the repository, even if it's referenced by other worktrees,
// since they all point to the same FS state.
self.repositories.remove(&id);
self.repositories_for_worktree
.values_mut()
.for_each(|repos| {
repos.retain(|repo| repo.read(cx).id != id);
});
if self.active_repo_id == Some(id) {
self.reset_active_repository(cx);
}
if let Some(updates_tx) = updates_tx.as_ref() {
updates_tx
.unbounded_send(DownstreamUpdate::RemoveRepository(id))
@@ -1384,7 +1420,7 @@ impl GitStore {
log::debug!("local worktree repos changed");
debug_assert!(worktree.read(cx).is_local());
for repository in self.repositories.values() {
for (_, repository) in self.repositories() {
repository.update(cx, |repository, cx| {
let repo_abs_path = &repository.work_directory_abs_path;
if changed_repos.iter().any(|update| {
@@ -1397,8 +1433,10 @@ impl GitStore {
}
}
pub fn repositories(&self) -> &HashMap<RepositoryId, Entity<Repository>> {
&self.repositories
pub fn repositories(&self) -> impl Iterator<Item = (RepositoryId, Entity<Repository>)> {
self.repositories
.iter()
.filter_map(|(id, weak_repository)| Some((*id, weak_repository.upgrade()?)))
}
pub fn status_for_buffer_id(&self, buffer_id: BufferId, cx: &App) -> Option<FileStatus> {
@@ -1426,6 +1464,7 @@ impl GitStore {
self.repositories
.values()
.filter_map(|repo| {
let repo = repo.upgrade()?;
let repo_path = repo.read(cx).abs_path_to_repo_path(&abs_path)?;
Some((repo.clone(), repo_path))
})
@@ -1485,7 +1524,7 @@ impl GitStore {
.clone();
let mut is_new = false;
let repo = this.repositories.entry(id).or_insert_with(|| {
let repo = this.shared_repositories.entry(id).or_insert_with(|| {
is_new = true;
let git_store = cx.weak_entity();
cx.new(|cx| {
@@ -1499,6 +1538,10 @@ impl GitStore {
)
})
});
this.repositories
.entry(id)
.or_insert_with(|| repo.downgrade());
if is_new {
this._subscriptions
.push(cx.subscribe(&repo, Self::on_repository_event))
@@ -1509,10 +1552,9 @@ impl GitStore {
|repo, cx| repo.apply_remote_update(update, cx)
})?;
this.active_repo_id.get_or_insert_with(|| {
cx.emit(GitStoreEvent::ActiveRepositoryChanged(Some(id)));
id
});
if this.active_repo_id.is_none() {
this.reset_active_repository(cx);
}
if let Some((client, project_id)) = this.downstream_client() {
update.project_id = project_id.to_proto();
@@ -1531,13 +1573,13 @@ impl GitStore {
let mut update = envelope.payload;
let id = RepositoryId::from_proto(update.id);
this.repositories.remove(&id);
this.shared_repositories.remove(&id);
if let Some((client, project_id)) = this.downstream_client() {
update.project_id = project_id.to_proto();
client.send(update).log_err();
}
if this.active_repo_id == Some(id) {
this.active_repo_id = None;
cx.emit(GitStoreEvent::ActiveRepositoryChanged(None));
this.reset_active_repository(cx);
}
cx.emit(GitStoreEvent::RepositoryRemoved(id));
})
@@ -2190,15 +2232,23 @@ impl GitStore {
.get(&id)
.context("missing repository handle")
.cloned()
})?
})??
.upgrade()
.ok_or_else(|| anyhow!("Repository was released"))
}
pub fn repo_snapshots(&self, cx: &App) -> HashMap<RepositoryId, RepositorySnapshot> {
self.repositories
.iter()
.map(|(id, repo)| (*id, repo.read(cx).snapshot.clone()))
.filter_map(|(id, repo)| Some((*id, repo.upgrade()?.read(cx).snapshot.clone())))
.collect()
}
fn reset_active_repository(&mut self, cx: &mut Context<Self>) {
let id = self.repositories().map(|(id, _)| id).next();
self.active_repo_id = id;
cx.emit(GitStoreEvent::ActiveRepositoryChanged(id))
}
}
impl BufferGitState {

View File

@@ -4835,7 +4835,10 @@ impl Project {
join_all(scans_complete).await;
let barriers = this
.update(cx, |this, cx| {
let repos = this.repositories(cx).values().cloned().collect::<Vec<_>>();
let repos: Vec<Entity<Repository>> = this
.repositories(cx)
.map(|(_, repository)| repository)
.collect::<Vec<_>>();
repos
.into_iter()
.map(|repo| repo.update(cx, |repo, _| repo.barrier()))
@@ -4850,7 +4853,10 @@ impl Project {
self.git_store.read(cx).active_repository()
}
pub fn repositories<'a>(&self, cx: &'a App) -> &'a HashMap<RepositoryId, Entity<Repository>> {
pub fn repositories(
&self,
cx: &App,
) -> impl Iterator<Item = (RepositoryId, Entity<Repository>)> {
self.git_store.read(cx).repositories()
}

View File

@@ -7446,7 +7446,12 @@ async fn test_git_repository_status(cx: &mut gpui::TestAppContext) {
cx.executor().run_until_parked();
let repository = project.read_with(cx, |project, cx| {
project.repositories(cx).values().next().unwrap().clone()
project
.repositories(cx)
.map(|(_, repository)| repository)
.next()
.unwrap()
.clone()
});
// Check that the right git state is observed on startup
@@ -7571,7 +7576,7 @@ async fn test_git_status_postprocessing(cx: &mut gpui::TestAppContext) {
let repository = project.read_with(cx, |project, cx| {
project
.repositories(cx)
.values()
.map(|(_, repository)| repository)
.find(|repo| repo.read(cx).work_directory_abs_path.ends_with("project"))
.unwrap()
.clone()
@@ -7644,7 +7649,12 @@ async fn test_repository_subfolder_git_status(
cx.run_until_parked();
let repository = project.read_with(cx, |project, cx| {
project.repositories(cx).values().next().unwrap().clone()
project
.repositories(cx)
.map(|(_, repository)| repository)
.next()
.unwrap()
.clone()
});
// Ensure that the git status is loaded correctly
@@ -7786,7 +7796,12 @@ async fn test_update_gitignore(cx: &mut gpui::TestAppContext) {
cx.executor().run_until_parked();
let repository = project.read_with(cx, |project, cx| {
project.repositories(cx).values().next().unwrap().clone()
project
.repositories(cx)
.map(|(_, repository)| repository)
.next()
.unwrap()
.clone()
});
// One file is unmodified, the other is ignored.
@@ -7856,7 +7871,12 @@ async fn test_rename_work_directory(cx: &mut gpui::TestAppContext) {
cx.executor().run_until_parked();
let repository = project.read_with(cx, |project, cx| {
project.repositories(cx).values().next().unwrap().clone()
project
.repositories(cx)
.map(|(_, repository)| repository)
.next()
.unwrap()
.clone()
});
repository.read_with(cx, |repository, _| {
@@ -7956,7 +7976,12 @@ async fn test_file_status(cx: &mut gpui::TestAppContext) {
cx.executor().run_until_parked();
let repository = project.read_with(cx, |project, cx| {
project.repositories(cx).values().next().unwrap().clone()
project
.repositories(cx)
.map(|(_, repository)| repository)
.next()
.unwrap()
.clone()
});
// Check that the right git state is observed on startup
@@ -8123,7 +8148,7 @@ async fn test_repos_in_invisible_worktrees(
let repos = project.read_with(cx, |project, cx| {
project
.repositories(cx)
.values()
.map(|(_, repository)| repository)
.map(|repo| repo.read(cx).work_directory_abs_path.clone())
.collect::<Vec<_>>()
});
@@ -8144,7 +8169,7 @@ async fn test_repos_in_invisible_worktrees(
let repos = project.read_with(cx, |project, cx| {
project
.repositories(cx)
.values()
.map(|(_, repository)| repository)
.map(|repo| repo.read(cx).work_directory_abs_path.clone())
.collect::<Vec<_>>()
});
@@ -8197,7 +8222,12 @@ async fn test_rescan_with_gitignore(cx: &mut gpui::TestAppContext) {
cx.executor().run_until_parked();
let repository = project.read_with(cx, |project, cx| {
project.repositories(cx).values().next().unwrap().clone()
project
.repositories(cx)
.map(|(_, repository)| repository)
.next()
.unwrap()
.clone()
});
tree.read_with(cx, |tree, _| {
@@ -8345,7 +8375,7 @@ async fn test_git_worktrees_and_submodules(cx: &mut gpui::TestAppContext) {
let mut repositories = project.update(cx, |project, cx| {
project
.repositories(cx)
.values()
.map(|(_, repository)| repository)
.map(|repo| repo.read(cx).work_directory_abs_path.clone())
.collect::<Vec<_>>()
});
@@ -8481,7 +8511,7 @@ async fn test_repository_deduplication(cx: &mut gpui::TestAppContext) {
let repos = project.read_with(cx, |project, cx| {
project
.repositories(cx)
.values()
.map(|(_, repository)| repository)
.map(|repo| repo.read(cx).work_directory_abs_path.clone())
.collect::<Vec<_>>()
});

View File

@@ -1493,7 +1493,7 @@ async fn test_remote_git_branches(cx: &mut TestAppContext, server_cx: &mut TestA
headless_project.git_store.update(cx, |git_store, cx| {
git_store
.repositories()
.values()
.map(|(_, repository)| repository)
.next()
.unwrap()
.read(cx)
@@ -1533,7 +1533,7 @@ async fn test_remote_git_branches(cx: &mut TestAppContext, server_cx: &mut TestA
headless_project.git_store.update(cx, |git_store, cx| {
git_store
.repositories()
.values()
.map(|(_, repository)| repository)
.next()
.unwrap()
.read(cx)