Detailed changes
@@ -478,13 +478,13 @@ impl Item for Editor {
})
}
- fn update_git(
+ fn git_diff_recalc(
&mut self,
_project: ModelHandle<Project>,
cx: &mut ViewContext<Self>,
) -> Task<Result<()>> {
self.buffer().update(cx, |multibuffer, cx| {
- multibuffer.update_git(cx);
+ multibuffer.git_diff_recalc(cx);
});
Task::ready(Ok(()))
}
@@ -312,13 +312,13 @@ impl MultiBuffer {
self.read(cx).symbols_containing(offset, theme)
}
- pub fn update_git(&mut self, cx: &mut ModelContext<Self>) {
+ pub fn git_diff_recalc(&mut self, cx: &mut ModelContext<Self>) {
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))
}
}
}
@@ -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<Self>) {
+ //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<Self>) {
+ pub fn git_diff_recalc(&mut self, cx: &mut ModelContext<Self>) {
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);
}
})
}
@@ -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<Box<dyn io::Read>>;
async fn load(&self, path: &Path) -> Result<String>;
- async fn load_head_text(&self, path: &Path) -> Option<String>;
async fn save(&self, path: &Path, text: &Rope, line_ending: LineEnding) -> Result<()>;
async fn canonicalize(&self, path: &Path) -> Result<PathBuf>;
async fn is_file(&self, path: &Path) -> bool;
@@ -48,7 +47,6 @@ pub trait Fs: Send + Sync {
path: &Path,
latency: Duration,
) -> Pin<Box<dyn Send + Stream<Item = Vec<fsevent::Event>>>>;
- fn open_git_repository(&self, abs_dotgit_path: &Path) -> Option<GitRepository>;
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<String> {
- fn logic(path: &Path) -> Result<Option<String>> {
- 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> {
- 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<String> {
- 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<GitRepository> {
- None
- }
-
fn is_fake(&self) -> bool {
true
}
@@ -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<Path>,
scan_id: usize,
- libgit_repository: Arc<Mutex<git2::Repository>>,
+ libgit_repository: Arc<Mutex<LibGitRepository>>,
}
impl GitRepository {
pub fn open(dotgit_path: &Path) -> Option<GitRepository> {
- 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<String> {
+ fn logic(repo: &LibGitRepository, file_path: &Path) -> Result<Option<String>> {
+ 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()
+ }
}
@@ -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<Self>,
+ ) {
+ 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<ProjectPath>, cx: &mut ModelContext<Self>) {
let new_active_entry = entry.and_then(|project_path| {
let worktree = self.worktree_for_id(project_path.worktree_id, cx)?;
@@ -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<GitRepository> {
+ 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<Output = ()> {
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);
}
}
@@ -317,7 +317,7 @@ pub trait Item: View {
project: ModelHandle<Project>,
cx: &mut ViewContext<Self>,
) -> Task<Result<()>>;
- fn update_git(
+ fn git_diff_recalc(
&mut self,
_project: ModelHandle<Project>,
_cx: &mut ViewContext<Self>,
@@ -539,7 +539,7 @@ pub trait ItemHandle: 'static + fmt::Debug {
) -> Task<Result<()>>;
fn reload(&self, project: ModelHandle<Project>, cx: &mut MutableAppContext)
-> Task<Result<()>>;
- fn update_git(
+ fn git_diff_recalc(
&self,
project: ModelHandle<Project>,
cx: &mut MutableAppContext,
@@ -753,7 +753,7 @@ impl<T: Item> ItemHandle for ViewHandle<T> {
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<T: Item> ItemHandle for ViewHandle<T> {
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<T: Item> ItemHandle for ViewHandle<T> {
self.update(cx, |item, cx| item.reload(project, cx))
}
- fn update_git(
+ fn git_diff_recalc(
&self,
project: ModelHandle<Project>,
cx: &mut MutableAppContext,
) -> Task<Result<()>> {
- 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<AnyViewHandle> {