WIP: Refactor existing git code to use new representation.

co-authored-by: petros <petros@zed.dev>
This commit is contained in:
Mikayla Maki 2023-05-02 10:22:47 -07:00
parent 563f13925f
commit bcf608e9e9
No known key found for this signature in database
2 changed files with 117 additions and 51 deletions

View File

@ -64,7 +64,7 @@ use std::{
},
time::{Duration, Instant, SystemTime},
};
use sum_tree::TreeMap;
use terminals::Terminals;
use util::{debug_panic, defer, merge_json_value_into, post_inc, ResultExt, TryFutureExt as _};
@ -4697,7 +4697,7 @@ impl Project {
fn update_local_worktree_buffers_git_repos(
&mut self,
worktree: ModelHandle<Worktree>,
repos: &TreeMap<RepositoryWorkDirectory, RepositoryEntry>,
repos: &Vec<RepositoryEntry>,
cx: &mut ModelContext<Self>,
) {
for (_, buffer) in &self.opened_buffers {
@ -4712,27 +4712,30 @@ impl Project {
let path = file.path().clone();
let (work_directory, repo) = match repos
let repo = match repos
.iter()
.find(|(work_directory, _)| work_directory.contains(&path))
.find(|entry| entry.work_directory.contains(&path))
{
Some((work_directory, repo)) => (work_directory, repo.clone()),
Some(repo) => repo.clone(),
None => return,
};
let relative_repo = match work_directory.relativize(&path) {
let relative_repo = match repo.work_directory.relativize(&path) {
Some(relative_repo) => relative_repo.to_owned(),
None => return,
};
let remote_id = self.remote_id();
let client = self.client.clone();
let diff_base_task = worktree.update(cx, move |worktree, cx| {
worktree
.as_local()
.unwrap()
.load_index_text(repo, relative_repo, cx)
});
cx.spawn(|_, mut cx| async move {
let diff_base = cx
.background()
.spawn(async move { repo.repo.lock().load_index_text(&relative_repo) })
.await;
let diff_base = diff_base_task.await;
let buffer_id = buffer.update(&mut cx, |buffer, cx| {
buffer.set_diff_base(diff_base.clone(), cx);

View File

@ -123,6 +123,7 @@ pub struct RepositoryEntry {
// Note: if .git is a file, this points to the folder indicated by the .git file
pub(crate) git_dir_path: Arc<Path>,
pub(crate) git_dir_entry_id: ProjectEntryId,
pub(crate) work_directory: RepositoryWorkDirectory,
pub(crate) scan_id: usize,
// TODO: pub(crate) head_ref: Arc<str>,
}
@ -144,8 +145,41 @@ impl RepositoryWorkDirectory {
path.starts_with(self.0.as_ref())
}
pub(crate) fn relativize(&self, path: &Path) -> Option<&Path> {
path.strip_prefix(self.0.as_ref()).ok()
pub(crate) fn relativize(&self, path: &Path) -> Option<RepoPath> {
path.strip_prefix(self.0.as_ref())
.ok()
.map(move |path| RepoPath(path.to_owned()))
}
}
impl Deref for RepositoryWorkDirectory {
type Target = Path;
fn deref(&self) -> &Self::Target {
self.0.as_ref()
}
}
#[derive(Clone, Debug, Ord, PartialOrd, Eq, PartialEq)]
pub struct RepoPath(PathBuf);
impl AsRef<Path> for RepoPath {
fn as_ref(&self) -> &Path {
self.0.as_ref()
}
}
impl Deref for RepoPath {
type Target = PathBuf;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl AsRef<Path> for RepositoryWorkDirectory {
fn as_ref(&self) -> &Path {
self.0.as_ref()
}
}
@ -628,7 +662,7 @@ impl LocalWorktree {
) -> Vec<RepositoryEntry> {
fn diff<'a>(
a: impl Iterator<Item = &'a RepositoryEntry>,
b: impl Iterator<Item = &'a RepositoryEntry>,
mut b: impl Iterator<Item = &'a RepositoryEntry>,
updated: &mut HashMap<&'a Path, RepositoryEntry>,
) {
for a_repo in a {
@ -691,11 +725,12 @@ impl LocalWorktree {
cx.spawn(|this, mut cx| async move {
let text = fs.load(&abs_path).await?;
let diff_base = if let Some(repo) = snapshot.repo_for(&path) {
if let Ok(repo_relative) = path.strip_prefix(repo.content_path) {
let diff_base = if let Some((work_directory, repo)) = snapshot.repo_for_metadata(&path)
{
if let Ok(repo_relative) = path.strip_prefix(&work_directory) {
let repo_relative = repo_relative.to_owned();
cx.background()
.spawn(async move { repo.repo.lock().load_index_text(&repo_relative) })
.spawn(async move { repo.lock().load_index_text(&repo_relative) })
.await
} else {
None
@ -1076,6 +1111,19 @@ impl LocalWorktree {
pub fn is_shared(&self) -> bool {
self.share.is_some()
}
pub fn load_index_text(
&self,
repo: RepositoryEntry,
repo_path: RepoPath,
cx: &mut ModelContext<Worktree>,
) -> Task<Option<String>> {
let Some(git_ptr) = self.git_repositories.get(&repo.git_dir_entry_id).map(|git_ptr| git_ptr.to_owned()) else {
return Task::Ready(Some(None))
};
cx.background()
.spawn(async move { git_ptr.lock().load_index_text(&repo_path) })
}
}
impl RemoteWorktree {
@ -1418,21 +1466,22 @@ impl Snapshot {
}
impl LocalSnapshot {
// Gives the most specific git repository for a given path
pub(crate) fn repo_for(&self, path: &Path) -> Option<RepositoryEntry> {
self.repository_entries
.closest(&RepositoryWorkDirectory(path.into()))
.map(|(_, entry)| entry.to_owned())
}
pub(crate) fn repo_with_dot_git_containing(
&mut self,
pub(crate) fn repo_for_metadata(
&self,
path: &Path,
) -> Option<&mut RepositoryEntry> {
// Git repositories cannot be nested, so we don't need to reverse the order
self.git_repositories_old
.iter_mut()
.find(|repo| repo.in_dot_git(path))
) -> Option<(RepositoryWorkDirectory, Arc<Mutex<dyn GitRepository>>)> {
self.repository_entries
.iter()
.find(|(_, repo)| repo.in_dot_git(path))
.map(|(work_directory, entry)| {
(
work_directory.to_owned(),
self.git_repositories
.get(&entry.git_dir_entry_id)
.expect("These two data structures should be in sync")
.to_owned(),
)
})
}
#[cfg(test)]
@ -1610,10 +1659,11 @@ impl LocalSnapshot {
if self.repository_entries.get(&key).is_none() {
if let Some(repo) = fs.open_repo(abs_path.as_path()) {
self.repository_entries.insert(
key,
key.clone(),
RepositoryEntry {
git_dir_path: parent_path.clone(),
git_dir_entry_id: parent_entry.id,
work_directory: key,
scan_id: 0,
},
);
@ -2599,16 +2649,10 @@ impl BackgroundScanner {
let scan_id = snapshot.scan_id;
if let Some(repo) = snapshot.repo_with_dot_git_containing(&path) {
repo.repo.lock().reload_index();
repo.scan_id = scan_id;
}
let repo_with_path_in_dotgit = snapshot.repo_for_metadata(&path);
if let Some((key, repo)) = repo_with_path_in_dotgit {
repo.lock().reload_index();
let repo_with_path_in_dotgit = snapshot
.repository_entries
.iter()
.find_map(|(key, repo)| repo.in_dot_git(&path).then(|| key.clone()));
if let Some(key) = repo_with_path_in_dotgit {
snapshot
.repository_entries
.update(&key, |entry| entry.scan_id = scan_id);
@ -3123,7 +3167,6 @@ impl<'a> TryFrom<(&'a CharBag, proto::Entry)> for Entry {
#[cfg(test)]
mod tests {
use super::*;
use fs::repository::FakeGitRepository;
use fs::{FakeFs, RealFs};
use gpui::{executor::Deterministic, TestAppContext};
use pretty_assertions::assert_eq;
@ -3386,23 +3429,37 @@ mod tests {
.await;
tree.flush_fs_events(cx).await;
fn entry(key: &RepositoryWorkDirectory, tree: &LocalWorktree) -> RepositoryEntry {
tree.repository_entries.get(key).unwrap().to_owned()
}
tree.read_with(cx, |tree, _cx| {
let tree = tree.as_local().unwrap();
assert!(tree.repo_for("c.txt".as_ref()).is_none());
assert!(tree.repo_for_metadata("c.txt".as_ref()).is_none());
let repo = tree.repo_for("dir1/src/b.txt".as_ref()).unwrap();
assert_eq!(repo.content_path.as_ref(), Path::new("dir1"));
assert_eq!(repo.git_dir_path.as_ref(), Path::new("dir1/.git"));
let (work_directory, _repo_entry) =
tree.repo_for_metadata("dir1/src/b.txt".as_ref()).unwrap();
assert_eq!(work_directory.0.as_ref(), Path::new("dir1"));
assert_eq!(
entry(&work_directory, &tree).git_dir_path.as_ref(),
Path::new("dir1/.git")
);
let repo = tree.repo_for("dir1/deps/dep1/src/a.txt".as_ref()).unwrap();
assert_eq!(repo.content_path.as_ref(), Path::new("dir1/deps/dep1"));
assert_eq!(repo.git_dir_path.as_ref(), Path::new("dir1/deps/dep1/.git"),);
let _repo = tree
.repo_for_metadata("dir1/deps/dep1/src/a.txt".as_ref())
.unwrap();
assert_eq!(work_directory.deref(), Path::new("dir1/deps/dep1"));
assert_eq!(
entry(&work_directory, &tree).git_dir_path.as_ref(),
Path::new("dir1/deps/dep1/.git"),
);
});
let original_scan_id = tree.read_with(cx, |tree, _cx| {
let tree = tree.as_local().unwrap();
tree.repo_for("dir1/src/b.txt".as_ref()).unwrap().scan_id
let (key, _) = tree.repo_for_metadata("dir1/src/b.txt".as_ref()).unwrap();
entry(&key, &tree).scan_id
});
std::fs::write(root.path().join("dir1/.git/random_new_file"), "hello").unwrap();
@ -3410,7 +3467,10 @@ mod tests {
tree.read_with(cx, |tree, _cx| {
let tree = tree.as_local().unwrap();
let new_scan_id = tree.repo_for("dir1/src/b.txt".as_ref()).unwrap().scan_id;
let new_scan_id = {
let (key, _) = tree.repo_for_metadata("dir1/src/b.txt".as_ref()).unwrap();
entry(&key, &tree).scan_id
};
assert_ne!(
original_scan_id, new_scan_id,
"original {original_scan_id}, new {new_scan_id}"
@ -3423,7 +3483,7 @@ mod tests {
tree.read_with(cx, |tree, _cx| {
let tree = tree.as_local().unwrap();
assert!(tree.repo_for("dir1/src/b.txt".as_ref()).is_none());
assert!(tree.repo_for_metadata("dir1/src/b.txt".as_ref()).is_none());
});
}
@ -3434,6 +3494,9 @@ mod tests {
scan_id,
git_dir_path: git_dir_path.as_ref().into(),
git_dir_entry_id: ProjectEntryId(0),
work_directory: RepositoryWorkDirectory(
Path::new(&format!("don't-care-{}", scan_id)).into(),
),
}
}