From 7e5d49487be16511f1246048b221997e9956d8f2 Mon Sep 17 00:00:00 2001 From: Julia Date: Tue, 27 Sep 2022 20:06:18 -0400 Subject: [PATCH] WIP Notifying buffers of head text change Co-Authored-By: Mikayla Maki --- crates/editor/src/items.rs | 4 +- crates/editor/src/multi_buffer.rs | 6 +-- crates/language/src/buffer.rs | 14 +++++-- crates/project/src/fs.rs | 46 ---------------------- crates/project/src/git_repository.rs | 44 +++++++++++++++++++-- crates/project/src/project.rs | 29 +++++++++++++- crates/project/src/worktree.rs | 57 +++++++++++++++++++++++++--- crates/workspace/src/workspace.rs | 12 +++--- 8 files changed, 142 insertions(+), 70 deletions(-) diff --git a/crates/editor/src/items.rs b/crates/editor/src/items.rs index 76e148018089913944fe0ca5379ce76f23658d28..c1082020e5f254cec81946cb62ec372f9651bd0d 100644 --- a/crates/editor/src/items.rs +++ b/crates/editor/src/items.rs @@ -478,13 +478,13 @@ impl Item for Editor { }) } - fn update_git( + fn git_diff_recalc( &mut self, _project: ModelHandle, cx: &mut ViewContext, ) -> Task> { self.buffer().update(cx, |multibuffer, cx| { - multibuffer.update_git(cx); + multibuffer.git_diff_recalc(cx); }); Task::ready(Ok(())) } diff --git a/crates/editor/src/multi_buffer.rs b/crates/editor/src/multi_buffer.rs index 2f93bc5b0930f9cd118dbe90a06f9c62b8668ba3..76093e0496c817d4ed1ce1b7d47ffe4366ae3e89 100644 --- a/crates/editor/src/multi_buffer.rs +++ b/crates/editor/src/multi_buffer.rs @@ -312,13 +312,13 @@ impl MultiBuffer { self.read(cx).symbols_containing(offset, theme) } - pub fn update_git(&mut self, cx: &mut ModelContext) { + pub fn git_diff_recalc(&mut self, cx: &mut ModelContext) { let buffers = self.buffers.borrow(); for buffer_state in buffers.values() { - if buffer_state.buffer.read(cx).needs_git_update() { + if buffer_state.buffer.read(cx).needs_git_diff_recalc() { buffer_state .buffer - .update(cx, |buffer, cx| buffer.update_git(cx)) + .update(cx, |buffer, cx| buffer.git_diff_recalc(cx)) } } } diff --git a/crates/language/src/buffer.rs b/crates/language/src/buffer.rs index 6ecfbc7e6293f75bad776817dbf5fc1c25f92ba8..d1dfb9ec22581f1e4390cff082adca2222edd1f5 100644 --- a/crates/language/src/buffer.rs +++ b/crates/language/src/buffer.rs @@ -613,6 +613,7 @@ impl Buffer { cx, ); } + self.update_git(cx); cx.emit(Event::Reloaded); cx.notify(); } @@ -661,12 +662,19 @@ impl Buffer { self.file = Some(new_file); task } + + pub fn update_git(&mut self, cx: &mut ModelContext) { + //Grab head text + + + self.git_diff_recalc(cx); + } - pub fn needs_git_update(&self) -> bool { + pub fn needs_git_diff_recalc(&self) -> bool { self.git_diff_status.diff.needs_update(self) } - pub fn update_git(&mut self, cx: &mut ModelContext) { + pub fn git_diff_recalc(&mut self, cx: &mut ModelContext) { if self.git_diff_status.update_in_progress { self.git_diff_status.update_requested = true; return; @@ -692,7 +700,7 @@ impl Buffer { this.git_diff_status.update_in_progress = false; if this.git_diff_status.update_requested { - this.update_git(cx); + this.git_diff_recalc(cx); } }) } diff --git a/crates/project/src/fs.rs b/crates/project/src/fs.rs index e675ddf8e58f67218c6526af73db9cd2acd4cd76..8542030cb7d646ee71d5afa81a1a27a09f8d36de 100644 --- a/crates/project/src/fs.rs +++ b/crates/project/src/fs.rs @@ -34,7 +34,6 @@ pub trait Fs: Send + Sync { async fn remove_file(&self, path: &Path, options: RemoveOptions) -> Result<()>; async fn open_sync(&self, path: &Path) -> Result>; async fn load(&self, path: &Path) -> Result; - async fn load_head_text(&self, path: &Path) -> Option; async fn save(&self, path: &Path, text: &Rope, line_ending: LineEnding) -> Result<()>; async fn canonicalize(&self, path: &Path) -> Result; async fn is_file(&self, path: &Path) -> bool; @@ -48,7 +47,6 @@ pub trait Fs: Send + Sync { path: &Path, latency: Duration, ) -> Pin>>>; - fn open_git_repository(&self, abs_dotgit_path: &Path) -> Option; fn is_fake(&self) -> bool; #[cfg(any(test, feature = "test-support"))] fn as_fake(&self) -> &FakeFs; @@ -168,38 +166,6 @@ impl Fs for RealFs { Ok(text) } - async fn load_head_text(&self, path: &Path) -> Option { - fn logic(path: &Path) -> Result> { - let repo = Repository::open_ext(path, RepositoryOpenFlags::empty(), &[OsStr::new("")])?; - assert!(repo.path().ends_with(".git")); - let repo_root_path = match repo.path().parent() { - Some(root) => root, - None => return Ok(None), - }; - - let relative_path = path.strip_prefix(repo_root_path)?; - let object = repo - .head()? - .peel_to_tree()? - .get_path(relative_path)? - .to_object(&repo)?; - - let content = match object.as_blob() { - Some(blob) => blob.content().to_owned(), - None => return Ok(None), - }; - - let head_text = String::from_utf8(content.to_owned())?; - Ok(Some(head_text)) - } - - match logic(path) { - Ok(value) => return value, - Err(err) => log::error!("Error loading head text: {:?}", err), - } - None - } - async fn save(&self, path: &Path, text: &Rope, line_ending: LineEnding) -> Result<()> { let buffer_size = text.summary().len.min(10 * 1024); let file = smol::fs::File::create(path).await?; @@ -274,10 +240,6 @@ impl Fs for RealFs { }))) } - fn open_git_repository(&self, abs_dotgit_path: &Path) -> Option { - GitRepository::open(abs_dotgit_path) - } - fn is_fake(&self) -> bool { false } @@ -791,10 +753,6 @@ impl Fs for FakeFs { entry.file_content(&path).cloned() } - async fn load_head_text(&self, _: &Path) -> Option { - None - } - async fn save(&self, path: &Path, text: &Rope, line_ending: LineEnding) -> Result<()> { self.simulate_random_delay().await; let path = normalize_path(path); @@ -893,10 +851,6 @@ impl Fs for FakeFs { })) } - fn open_git_repository(&self, _: &Path) -> Option { - None - } - fn is_fake(&self) -> bool { true } diff --git a/crates/project/src/git_repository.rs b/crates/project/src/git_repository.rs index eab031da17c0e8c84464c676de46d2d03eaeadf2..c27b1ba3859152996ae40d5f1da3f6cd8667408e 100644 --- a/crates/project/src/git_repository.rs +++ b/crates/project/src/git_repository.rs @@ -1,6 +1,7 @@ -use git2::Repository; +use anyhow::Result; +use git2::{Repository as LibGitRepository, RepositoryOpenFlags as LibGitRepositoryOpenFlags}; use parking_lot::Mutex; -use std::{path::Path, sync::Arc}; +use std::{path::Path, sync::Arc, ffi::OsStr}; use util::ResultExt; #[derive(Clone)] @@ -11,12 +12,12 @@ pub struct GitRepository { // Note: if .git is a file, this points to the folder indicated by the .git file git_dir_path: Arc, scan_id: usize, - libgit_repository: Arc>, + libgit_repository: Arc>, } impl GitRepository { pub fn open(dotgit_path: &Path) -> Option { - Repository::open(&dotgit_path) + LibGitRepository::open(&dotgit_path) .log_err() .and_then(|libgit_repository| { Some(Self { @@ -60,4 +61,39 @@ impl GitRepository { let mut git2 = self.libgit_repository.lock(); f(&mut git2) } + + pub async fn load_head_text(&self, file_path: &Path) -> Option { + fn logic(repo: &LibGitRepository, file_path: &Path) -> Result> { + let object = repo + .head()? + .peel_to_tree()? + .get_path(file_path)? + .to_object(&repo)?; + + let content = match object.as_blob() { + Some(blob) => blob.content().to_owned(), + None => return Ok(None), + }; + + let head_text = String::from_utf8(content.to_owned())?; + Ok(Some(head_text)) + } + + match logic(&self.libgit_repository.lock(), file_path) { + Ok(value) => return value, + Err(err) => log::error!("Error loading head text: {:?}", err), + } + None + } +} + +impl std::fmt::Debug for GitRepository { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("GitRepository") + .field("content_path", &self.content_path) + .field("git_dir_path", &self.git_dir_path) + .field("scan_id", &self.scan_id) + .field("libgit_repository", &"LibGitRepository") + .finish() + } } diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index 78a500585a3f2bbf997a5c4bdd98c05c5cc04446..4aa3a89d860fc77df056b7120103904e07c0a5bb 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -13,6 +13,7 @@ use client::{proto, Client, PeerId, TypedEnvelope, User, UserStore}; use clock::ReplicaId; use collections::{hash_map, BTreeMap, HashMap, HashSet}; use futures::{future::Shared, AsyncWriteExt, Future, FutureExt, StreamExt, TryFutureExt}; +use git_repository::GitRepository; use gpui::{ AnyModelHandle, AppContext, AsyncAppContext, Entity, ModelContext, ModelHandle, MutableAppContext, Task, UpgradeModelHandle, WeakModelHandle, @@ -4536,7 +4537,9 @@ impl Project { if worktree.read(cx).is_local() { cx.subscribe(worktree, |this, worktree, event, cx| match event { worktree::Event::UpdatedEntries => this.update_local_worktree_buffers(worktree, cx), - worktree::Event::UpdatedGitRepositories(_) => todo!(), + worktree::Event::UpdatedGitRepositories(updated_repos) => { + this.update_local_worktree_buffers_git_repos(updated_repos, cx) + } }) .detach(); } @@ -4644,6 +4647,30 @@ impl Project { } } + fn update_local_worktree_buffers_git_repos( + &mut self, + updated_repos: &[GitRepository], + cx: &mut ModelContext, + ) { + for (buffer_id, buffer) in &self.opened_buffers { + if let Some(buffer) = buffer.upgrade(cx) { + buffer.update(cx, |buffer, cx| { + let updated = updated_repos.iter().any(|repo| { + buffer + .file() + .and_then(|file| file.as_local()) + .map(|file| repo.manages(&file.abs_path(cx))) + .unwrap_or(false) + }); + + if updated { + buffer.update_git(cx); + } + }); + } + } + } + pub fn set_active_path(&mut self, entry: Option, cx: &mut ModelContext) { let new_active_entry = entry.and_then(|project_path| { let worktree = self.worktree_for_id(project_path.worktree_id, cx)?; diff --git a/crates/project/src/worktree.rs b/crates/project/src/worktree.rs index aead63102b218cd376612ac9942998a39e083453..beef854470ba5a808f129173d8f046270f8c9c72 100644 --- a/crates/project/src/worktree.rs +++ b/crates/project/src/worktree.rs @@ -467,7 +467,7 @@ impl LocalWorktree { .await?; Ok(cx.add_model(|cx| { let mut buffer = Buffer::from_file(0, contents, head_text, Arc::new(file), cx); - buffer.update_git(cx); + buffer.git_diff_recalc(cx); buffer })) }) @@ -522,16 +522,28 @@ impl LocalWorktree { match self.scan_state() { ScanState::Idle => { - self.snapshot = self.background_snapshot.lock().clone(); + let new_snapshot = self.background_snapshot.lock().clone(); + let updated_repos = self.list_updated_repos(&new_snapshot); + self.snapshot = new_snapshot; + if let Some(share) = self.share.as_mut() { *share.snapshots_tx.borrow_mut() = self.snapshot.clone(); } + cx.emit(Event::UpdatedEntries); + + if !updated_repos.is_empty() { + cx.emit(Event::UpdatedGitRepositories(updated_repos)); + } } ScanState::Initializing => { let is_fake_fs = self.fs.is_fake(); - self.snapshot = self.background_snapshot.lock().clone(); + + let new_snapshot = self.background_snapshot.lock().clone(); + let updated_repos = self.list_updated_repos(&new_snapshot); + self.snapshot = new_snapshot; + self.poll_task = Some(cx.spawn_weak(|this, mut cx| async move { if is_fake_fs { #[cfg(any(test, feature = "test-support"))] @@ -543,7 +555,12 @@ impl LocalWorktree { this.update(&mut cx, |this, cx| this.poll_snapshot(cx)); } })); + cx.emit(Event::UpdatedEntries); + + if !updated_repos.is_empty() { + cx.emit(Event::UpdatedGitRepositories(updated_repos)); + } } _ => { @@ -556,6 +573,34 @@ impl LocalWorktree { cx.notify(); } + fn list_updated_repos(&self, new_snapshot: &LocalSnapshot) -> Vec { + let old_snapshot = &self.snapshot; + + fn diff<'a>( + a: &'a LocalSnapshot, + b: &'a LocalSnapshot, + updated: &mut HashMap<&'a Path, GitRepository>, + ) { + for a_repo in &a.git_repositories { + let matched = b.git_repositories.iter().find(|b_repo| { + a_repo.git_dir_path() == b_repo.git_dir_path() + && a_repo.scan_id() == b_repo.scan_id() + }); + + if matched.is_some() { + updated.insert(a_repo.git_dir_path(), a_repo.clone()); + } + } + } + + let mut updated = HashMap::<&Path, GitRepository>::default(); + + diff(old_snapshot, new_snapshot, &mut updated); + diff(new_snapshot, old_snapshot, &mut updated); + + updated.into_values().collect() + } + pub fn scan_complete(&self) -> impl Future { let mut scan_state_rx = self.last_scan_state_rx.clone(); async move { @@ -606,9 +651,11 @@ impl LocalWorktree { files_included, settings::GitFilesIncluded::All | settings::GitFilesIncluded::OnlyTracked ) { + + let fs = fs.clone(); let abs_path = abs_path.clone(); - let task = async move { fs.load_head_text(&abs_path).await }; + let opt_future = async move { fs.load_head_text(&abs_path).await }; let results = cx.background().spawn(task).await; if files_included == settings::GitFilesIncluded::All { @@ -1495,7 +1542,7 @@ impl LocalSnapshot { .git_repositories .binary_search_by_key(&abs_path.as_path(), |repo| repo.git_dir_path()) { - if let Some(repository) = fs.open_git_repository(&abs_path) { + if let Some(repository) = GitRepository::open(&abs_path) { self.git_repositories.insert(ix, repository); } } diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 9e8338d2896294237642af9c8facf564c9af8d54..921fb2de201e4c1235a47239543de5d6f0d8bd71 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -317,7 +317,7 @@ pub trait Item: View { project: ModelHandle, cx: &mut ViewContext, ) -> Task>; - fn update_git( + fn git_diff_recalc( &mut self, _project: ModelHandle, _cx: &mut ViewContext, @@ -539,7 +539,7 @@ pub trait ItemHandle: 'static + fmt::Debug { ) -> Task>; fn reload(&self, project: ModelHandle, cx: &mut MutableAppContext) -> Task>; - fn update_git( + fn git_diff_recalc( &self, project: ModelHandle, cx: &mut MutableAppContext, @@ -753,7 +753,7 @@ impl ItemHandle for ViewHandle { workspace, cx, |project, mut cx| async move { - cx.update(|cx| item.update_git(project, cx)) + cx.update(|cx| item.git_diff_recalc(project, cx)) .await .log_err(); }, @@ -762,7 +762,7 @@ impl ItemHandle for ViewHandle { let project = workspace.project().downgrade(); cx.spawn_weak(|_, mut cx| async move { if let Some(project) = project.upgrade(&cx) { - cx.update(|cx| item.update_git(project, cx)) + cx.update(|cx| item.git_diff_recalc(project, cx)) .await .log_err(); } @@ -850,12 +850,12 @@ impl ItemHandle for ViewHandle { self.update(cx, |item, cx| item.reload(project, cx)) } - fn update_git( + fn git_diff_recalc( &self, project: ModelHandle, cx: &mut MutableAppContext, ) -> Task> { - self.update(cx, |item, cx| item.update_git(project, cx)) + self.update(cx, |item, cx| item.git_diff_recalc(project, cx)) } fn act_as_type(&self, type_id: TypeId, cx: &AppContext) -> Option {