mirror of
https://github.com/zed-industries/zed.git
synced 2025-01-01 09:40:41 +03:00
WIP: Convert old git repository vec to new treemap based approach.
co-authored-by: Nathan <nathan@zed.dev>
This commit is contained in:
parent
a58d3d8128
commit
563f13925f
@ -64,6 +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 _};
|
||||
@ -4696,7 +4697,7 @@ impl Project {
|
||||
fn update_local_worktree_buffers_git_repos(
|
||||
&mut self,
|
||||
worktree: ModelHandle<Worktree>,
|
||||
repos: &[LocalGitRepositoryEntry],
|
||||
repos: &TreeMap<RepositoryWorkDirectory, RepositoryEntry>,
|
||||
cx: &mut ModelContext<Self>,
|
||||
) {
|
||||
for (_, buffer) in &self.opened_buffers {
|
||||
@ -4711,14 +4712,17 @@ impl Project {
|
||||
|
||||
let path = file.path().clone();
|
||||
|
||||
let repo = match repos.iter().find(|repo| repo.manages(&path)) {
|
||||
Some(repo) => repo.clone(),
|
||||
let (work_directory, repo) = match repos
|
||||
.iter()
|
||||
.find(|(work_directory, _)| work_directory.contains(&path))
|
||||
{
|
||||
Some((work_directory, repo)) => (work_directory, repo.clone()),
|
||||
None => return,
|
||||
};
|
||||
|
||||
let relative_repo = match path.strip_prefix(repo.content_path) {
|
||||
Ok(relative_repo) => relative_repo.to_owned(),
|
||||
Err(_) => return,
|
||||
let relative_repo = match work_directory.relativize(&path) {
|
||||
Some(relative_repo) => relative_repo.to_owned(),
|
||||
None => return,
|
||||
};
|
||||
|
||||
let remote_id = self.remote_id();
|
||||
|
@ -102,7 +102,7 @@ pub struct Snapshot {
|
||||
root_char_bag: CharBag,
|
||||
entries_by_path: SumTree<Entry>,
|
||||
entries_by_id: SumTree<PathEntry>,
|
||||
repository_entries: TreeMap<RepositoryEntryKey, RepositoryEntry>,
|
||||
repository_entries: TreeMap<RepositoryWorkDirectory, RepositoryEntry>,
|
||||
|
||||
/// A number that increases every time the worktree begins scanning
|
||||
/// a set of paths from the filesystem. This scanning could be caused
|
||||
@ -136,39 +136,28 @@ impl RepositoryEntry {
|
||||
|
||||
/// This path corresponds to the 'content path' (the folder that contains the .git)
|
||||
#[derive(Clone, Debug, Ord, PartialOrd, Eq, PartialEq)]
|
||||
pub struct RepositoryEntryKey(Arc<Path>);
|
||||
pub struct RepositoryWorkDirectory(Arc<Path>);
|
||||
|
||||
impl Default for RepositoryEntryKey {
|
||||
fn default() -> Self {
|
||||
RepositoryEntryKey(Arc::from(Path::new("")))
|
||||
impl RepositoryWorkDirectory {
|
||||
// Note that these paths should be relative to the worktree root.
|
||||
pub(crate) fn contains(&self, path: &Path) -> bool {
|
||||
path.starts_with(self.0.as_ref())
|
||||
}
|
||||
|
||||
pub(crate) fn relativize(&self, path: &Path) -> Option<&Path> {
|
||||
path.strip_prefix(self.0.as_ref()).ok()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct LocalGitRepositoryEntry {
|
||||
pub(crate) repo: Arc<Mutex<dyn GitRepository>>,
|
||||
|
||||
pub(crate) scan_id: usize,
|
||||
// Path to folder containing the .git file or directory
|
||||
pub(crate) content_path: Arc<Path>,
|
||||
// Path to the actual .git folder.
|
||||
// Note: if .git is a file, this points to the folder indicated by the .git file
|
||||
pub(crate) git_dir_path: Arc<Path>,
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for LocalGitRepositoryEntry {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("GitRepositoryEntry")
|
||||
.field("content_path", &self.content_path)
|
||||
.field("git_dir_path", &self.git_dir_path)
|
||||
.finish()
|
||||
impl Default for RepositoryWorkDirectory {
|
||||
fn default() -> Self {
|
||||
RepositoryWorkDirectory(Arc::from(Path::new("")))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct LocalSnapshot {
|
||||
ignores_by_parent_abs_path: HashMap<Arc<Path>, (Arc<Gitignore>, usize)>,
|
||||
git_repositories_old: Vec<LocalGitRepositoryEntry>,
|
||||
// The ProjectEntryId corresponds to the entry for the .git dir
|
||||
git_repositories: TreeMap<ProjectEntryId, Arc<Mutex<dyn GitRepository>>>,
|
||||
removed_entry_ids: HashMap<u64, ProjectEntryId>,
|
||||
@ -209,7 +198,7 @@ struct ShareState {
|
||||
|
||||
pub enum Event {
|
||||
UpdatedEntries(HashMap<Arc<Path>, PathChange>),
|
||||
UpdatedGitRepositories(Vec<LocalGitRepositoryEntry>),
|
||||
UpdatedGitRepositories(Vec<RepositoryEntry>),
|
||||
}
|
||||
|
||||
impl Entity for Worktree {
|
||||
@ -240,7 +229,6 @@ impl Worktree {
|
||||
|
||||
let mut snapshot = LocalSnapshot {
|
||||
ignores_by_parent_abs_path: Default::default(),
|
||||
git_repositories_old: Default::default(),
|
||||
removed_entry_ids: Default::default(),
|
||||
git_repositories: Default::default(),
|
||||
next_entry_id,
|
||||
@ -620,8 +608,8 @@ impl LocalWorktree {
|
||||
|
||||
fn set_snapshot(&mut self, new_snapshot: LocalSnapshot, cx: &mut ModelContext<Worktree>) {
|
||||
let updated_repos = Self::changed_repos(
|
||||
&self.snapshot.git_repositories_old,
|
||||
&new_snapshot.git_repositories_old,
|
||||
&self.snapshot.repository_entries,
|
||||
&new_snapshot.repository_entries,
|
||||
);
|
||||
self.snapshot = new_snapshot;
|
||||
|
||||
@ -635,16 +623,16 @@ impl LocalWorktree {
|
||||
}
|
||||
|
||||
fn changed_repos(
|
||||
old_repos: &[LocalGitRepositoryEntry],
|
||||
new_repos: &[LocalGitRepositoryEntry],
|
||||
) -> Vec<LocalGitRepositoryEntry> {
|
||||
old_repos: &TreeMap<RepositoryWorkDirectory, RepositoryEntry>,
|
||||
new_repos: &TreeMap<RepositoryWorkDirectory, RepositoryEntry>,
|
||||
) -> Vec<RepositoryEntry> {
|
||||
fn diff<'a>(
|
||||
a: &'a [LocalGitRepositoryEntry],
|
||||
b: &'a [LocalGitRepositoryEntry],
|
||||
updated: &mut HashMap<&'a Path, LocalGitRepositoryEntry>,
|
||||
a: impl Iterator<Item = &'a RepositoryEntry>,
|
||||
b: impl Iterator<Item = &'a RepositoryEntry>,
|
||||
updated: &mut HashMap<&'a Path, RepositoryEntry>,
|
||||
) {
|
||||
for a_repo in a {
|
||||
let matched = b.iter().find(|b_repo| {
|
||||
let matched = b.find(|b_repo| {
|
||||
a_repo.git_dir_path == b_repo.git_dir_path && a_repo.scan_id == b_repo.scan_id
|
||||
});
|
||||
|
||||
@ -654,10 +642,10 @@ impl LocalWorktree {
|
||||
}
|
||||
}
|
||||
|
||||
let mut updated = HashMap::<&Path, LocalGitRepositoryEntry>::default();
|
||||
let mut updated = HashMap::<&Path, RepositoryEntry>::default();
|
||||
|
||||
diff(old_repos, new_repos, &mut updated);
|
||||
diff(new_repos, old_repos, &mut updated);
|
||||
diff(old_repos.values(), new_repos.values(), &mut updated);
|
||||
diff(new_repos.values(), old_repos.values(), &mut updated);
|
||||
|
||||
updated.into_values().collect()
|
||||
}
|
||||
@ -1023,7 +1011,6 @@ impl LocalWorktree {
|
||||
let mut share_tx = Some(share_tx);
|
||||
let mut prev_snapshot = LocalSnapshot {
|
||||
ignores_by_parent_abs_path: Default::default(),
|
||||
git_repositories_old: Default::default(),
|
||||
removed_entry_ids: Default::default(),
|
||||
next_entry_id: Default::default(),
|
||||
git_repositories: Default::default(),
|
||||
@ -1432,18 +1419,16 @@ impl Snapshot {
|
||||
|
||||
impl LocalSnapshot {
|
||||
// Gives the most specific git repository for a given path
|
||||
pub(crate) fn repo_for(&self, path: &Path) -> Option<LocalGitRepositoryEntry> {
|
||||
self.git_repositories_old
|
||||
.iter()
|
||||
.rev() //git_repository is ordered lexicographically
|
||||
.find(|repo| repo.manages(path))
|
||||
.cloned()
|
||||
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,
|
||||
path: &Path,
|
||||
) -> Option<&mut LocalGitRepositoryEntry> {
|
||||
) -> Option<&mut RepositoryEntry> {
|
||||
// Git repositories cannot be nested, so we don't need to reverse the order
|
||||
self.git_repositories_old
|
||||
.iter_mut()
|
||||
@ -1621,7 +1606,7 @@ impl LocalSnapshot {
|
||||
let abs_path = self.abs_path.join(&parent_path);
|
||||
let content_path: Arc<Path> = parent_path.parent().unwrap().into();
|
||||
|
||||
let key = RepositoryEntryKey(content_path.clone());
|
||||
let key = RepositoryWorkDirectory(content_path.clone());
|
||||
if self.repository_entries.get(&key).is_none() {
|
||||
if let Some(repo) = fs.open_repo(abs_path.as_path()) {
|
||||
self.repository_entries.insert(
|
||||
@ -1636,23 +1621,6 @@ impl LocalSnapshot {
|
||||
self.git_repositories.insert(parent_entry.id, repo)
|
||||
}
|
||||
}
|
||||
|
||||
if let Err(ix) = self
|
||||
.git_repositories_old
|
||||
.binary_search_by_key(&&content_path, |repo| &repo.content_path)
|
||||
{
|
||||
if let Some(repo) = fs.open_repo(abs_path.as_path()) {
|
||||
self.git_repositories_old.insert(
|
||||
ix,
|
||||
LocalGitRepositoryEntry {
|
||||
repo,
|
||||
scan_id: 0,
|
||||
content_path,
|
||||
git_dir_path: parent_path,
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mut entries_by_path_edits = vec![Edit::Insert(parent_entry)];
|
||||
@ -1712,18 +1680,10 @@ impl LocalSnapshot {
|
||||
*scan_id = self.snapshot.scan_id;
|
||||
}
|
||||
} else if path.file_name() == Some(&DOT_GIT) {
|
||||
let repo_entry_key = RepositoryEntryKey(path.parent().unwrap().into());
|
||||
let repo_entry_key = RepositoryWorkDirectory(path.parent().unwrap().into());
|
||||
self.snapshot
|
||||
.repository_entries
|
||||
.update(&repo_entry_key, |repo| repo.scan_id = self.snapshot.scan_id);
|
||||
|
||||
let parent_path = path.parent().unwrap();
|
||||
if let Ok(ix) = self
|
||||
.git_repositories_old
|
||||
.binary_search_by_key(&parent_path, |repo| repo.content_path.as_ref())
|
||||
{
|
||||
self.git_repositories_old[ix].scan_id = self.snapshot.scan_id;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1763,22 +1723,6 @@ impl LocalSnapshot {
|
||||
|
||||
ignore_stack
|
||||
}
|
||||
|
||||
pub fn git_repo_entries(&self) -> &[LocalGitRepositoryEntry] {
|
||||
&self.git_repositories_old
|
||||
}
|
||||
}
|
||||
|
||||
impl LocalGitRepositoryEntry {
|
||||
// Note that these paths should be relative to the worktree root.
|
||||
pub(crate) fn manages(&self, path: &Path) -> bool {
|
||||
path.starts_with(self.content_path.as_ref())
|
||||
}
|
||||
|
||||
// Note that this path should be relative to the worktree root.
|
||||
pub(crate) fn in_dot_git(&self, path: &Path) -> bool {
|
||||
path.starts_with(self.git_dir_path.as_ref())
|
||||
}
|
||||
}
|
||||
|
||||
async fn build_gitignore(abs_path: &Path, fs: &dyn Fs) -> Result<Gitignore> {
|
||||
@ -2364,10 +2308,6 @@ impl BackgroundScanner {
|
||||
|
||||
let mut snapshot = self.snapshot.lock();
|
||||
|
||||
let mut git_repositories = mem::take(&mut snapshot.git_repositories_old);
|
||||
git_repositories.retain(|repo| snapshot.entry_for_path(&repo.git_dir_path).is_some());
|
||||
snapshot.git_repositories_old = git_repositories;
|
||||
|
||||
let mut git_repositories = mem::take(&mut snapshot.git_repositories);
|
||||
git_repositories.retain(|project_entry_id, _| snapshot.contains_entry(*project_entry_id));
|
||||
snapshot.git_repositories = git_repositories;
|
||||
@ -3489,26 +3429,41 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_changed_repos() {
|
||||
fn fake_entry(git_dir_path: impl AsRef<Path>, scan_id: usize) -> LocalGitRepositoryEntry {
|
||||
LocalGitRepositoryEntry {
|
||||
repo: Arc::new(Mutex::new(FakeGitRepository::default())),
|
||||
fn fake_entry(git_dir_path: impl AsRef<Path>, scan_id: usize) -> RepositoryEntry {
|
||||
RepositoryEntry {
|
||||
scan_id,
|
||||
content_path: git_dir_path.as_ref().parent().unwrap().into(),
|
||||
git_dir_path: git_dir_path.as_ref().into(),
|
||||
git_dir_entry_id: ProjectEntryId(0),
|
||||
}
|
||||
}
|
||||
|
||||
let prev_repos: Vec<LocalGitRepositoryEntry> = vec![
|
||||
let mut prev_repos = TreeMap::<RepositoryWorkDirectory, RepositoryEntry>::default();
|
||||
prev_repos.insert(
|
||||
RepositoryWorkDirectory(Path::new("don't-care-1").into()),
|
||||
fake_entry("/.git", 0),
|
||||
);
|
||||
prev_repos.insert(
|
||||
RepositoryWorkDirectory(Path::new("don't-care-2").into()),
|
||||
fake_entry("/a/.git", 0),
|
||||
);
|
||||
prev_repos.insert(
|
||||
RepositoryWorkDirectory(Path::new("don't-care-3").into()),
|
||||
fake_entry("/a/b/.git", 0),
|
||||
];
|
||||
);
|
||||
|
||||
let new_repos: Vec<LocalGitRepositoryEntry> = vec![
|
||||
let mut new_repos = TreeMap::<RepositoryWorkDirectory, RepositoryEntry>::default();
|
||||
new_repos.insert(
|
||||
RepositoryWorkDirectory(Path::new("don't-care-4").into()),
|
||||
fake_entry("/a/.git", 1),
|
||||
);
|
||||
new_repos.insert(
|
||||
RepositoryWorkDirectory(Path::new("don't-care-5").into()),
|
||||
fake_entry("/a/b/.git", 0),
|
||||
);
|
||||
new_repos.insert(
|
||||
RepositoryWorkDirectory(Path::new("don't-care-6").into()),
|
||||
fake_entry("/a/c/.git", 0),
|
||||
];
|
||||
);
|
||||
|
||||
let res = LocalWorktree::changed_repos(&prev_repos, &new_repos);
|
||||
|
||||
|
@ -73,6 +73,15 @@ impl<K: Clone + Debug + Default + Ord, V: Clone + Debug> TreeMap<K, V> {
|
||||
removed
|
||||
}
|
||||
|
||||
/// Returns the key-value pair with the greatest key less than or equal to the given key.
|
||||
pub fn closest(&self, key: &K) -> Option<(&K, &V)> {
|
||||
let mut cursor = self.0.cursor::<MapKeyRef<'_, K>>();
|
||||
let key = MapKeyRef(Some(key));
|
||||
cursor.seek(&key, Bias::Right, &());
|
||||
cursor.prev(&());
|
||||
cursor.item().map(|item| (&item.key, &item.value))
|
||||
}
|
||||
|
||||
pub fn update<F, T>(&mut self, key: &K, f: F) -> Option<T>
|
||||
where
|
||||
F: FnOnce(&mut V) -> T,
|
||||
@ -112,6 +121,10 @@ impl<K: Clone + Debug + Default + Ord, V: Clone + Debug> TreeMap<K, V> {
|
||||
pub fn iter(&self) -> impl Iterator<Item = (&K, &V)> + '_ {
|
||||
self.0.iter().map(|entry| (&entry.key, &entry.value))
|
||||
}
|
||||
|
||||
pub fn values(&self) -> impl Iterator<Item = &V> + '_ {
|
||||
self.0.iter().map(|entry| &entry.value)
|
||||
}
|
||||
}
|
||||
|
||||
impl<K, V> Default for TreeMap<K, V>
|
||||
@ -235,10 +248,16 @@ mod tests {
|
||||
vec![(&1, &"a"), (&2, &"b"), (&3, &"c")]
|
||||
);
|
||||
|
||||
assert_eq!(map.closest(&0), None);
|
||||
assert_eq!(map.closest(&1), Some((&1, &"a")));
|
||||
assert_eq!(map.closest(&10), Some((&3, &"c")));
|
||||
|
||||
map.remove(&2);
|
||||
assert_eq!(map.get(&2), None);
|
||||
assert_eq!(map.iter().collect::<Vec<_>>(), vec![(&1, &"a"), (&3, &"c")]);
|
||||
|
||||
assert_eq!(map.closest(&2), Some((&1, &"a")));
|
||||
|
||||
map.remove(&3);
|
||||
assert_eq!(map.get(&3), None);
|
||||
assert_eq!(map.iter().collect::<Vec<_>>(), vec![(&1, &"a")]);
|
||||
|
Loading…
Reference in New Issue
Block a user