WIP: Track live entry status in repository

co-authored-by: petros <petros@zed.dev>
This commit is contained in:
Mikayla Maki 2023-05-09 10:02:58 -07:00 committed by Mikayla Maki
parent 7169f5c760
commit 67491632cb
No known key found for this signature in database
3 changed files with 165 additions and 86 deletions

View File

@ -1,11 +1,14 @@
use anyhow::Result;
use collections::HashMap;
use git2::Status;
use parking_lot::Mutex;
use sum_tree::TreeMap;
use std::{
ffi::OsStr,
os::unix::prelude::OsStrExt,
path::{Component, Path, PathBuf},
sync::Arc, ffi::OsStr, os::unix::prelude::OsStrExt,
sync::Arc,
};
use sum_tree::TreeMap;
use util::ResultExt;
pub use git2::Repository as LibGitRepository;
@ -19,6 +22,8 @@ pub trait GitRepository: Send {
fn branch_name(&self) -> Option<String>;
fn statuses(&self) -> Option<TreeMap<RepoPath, GitStatus>>;
fn file_status(&self, path: &RepoPath) -> Option<GitStatus>;
}
impl std::fmt::Debug for dyn GitRepository {
@ -70,72 +75,22 @@ impl GitRepository for LibGitRepository {
let mut map = TreeMap::default();
for status in statuses.iter() {
for status in statuses
.iter()
.filter(|status| !status.status().contains(git2::Status::IGNORED))
{
let path = RepoPath(PathBuf::from(OsStr::from_bytes(status.path_bytes())));
let status_data = status.status();
let status = if status_data.contains(git2::Status::CONFLICTED) {
GitStatus::Conflict
} else if status_data.intersects(git2::Status::INDEX_MODIFIED
| git2::Status::WT_MODIFIED
| git2::Status::INDEX_RENAMED
| git2::Status::WT_RENAMED) {
GitStatus::Modified
} else if status_data.intersects(git2::Status::INDEX_NEW | git2::Status::WT_NEW) {
GitStatus::Added
} else {
GitStatus::Untracked
};
map.insert(path, status)
map.insert(path, status.status().into())
}
Some(map)
}
}
#[derive(Debug, Clone, Default)]
pub enum GitStatus {
Added,
Modified,
Conflict,
#[default]
Untracked,
}
fn file_status(&self, path: &RepoPath) -> Option<GitStatus> {
let status = self.status_file(path).log_err()?;
#[derive(Clone, Debug, Ord, Hash, PartialOrd, Eq, PartialEq)]
pub struct RepoPath(PathBuf);
impl From<&Path> for RepoPath {
fn from(value: &Path) -> Self {
RepoPath(value.to_path_buf())
}
}
impl From<PathBuf> for RepoPath {
fn from(value: PathBuf) -> Self {
RepoPath(value)
}
}
impl Default for RepoPath {
fn default() -> Self {
RepoPath(PathBuf::new())
}
}
impl AsRef<Path> for RepoPath {
fn as_ref(&self) -> &Path {
self.0.as_ref()
}
}
impl std::ops::Deref for RepoPath {
type Target = PathBuf;
fn deref(&self) -> &Self::Target {
&self.0
Some(status.into())
}
}
@ -170,7 +125,11 @@ impl GitRepository for FakeGitRepository {
state.branch_name.clone()
}
fn statuses(&self) -> Option<TreeMap<RepoPath, GitStatus>>{
fn statuses(&self) -> Option<TreeMap<RepoPath, GitStatus>> {
todo!()
}
fn file_status(&self, _: &RepoPath) -> Option<GitStatus> {
todo!()
}
}
@ -203,3 +162,74 @@ fn check_path_to_repo_path_errors(relative_file_path: &Path) -> Result<()> {
_ => Ok(()),
}
}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub enum GitStatus {
Added,
Modified,
Conflict,
#[default]
Untracked,
}
impl From<Status> for GitStatus {
fn from(value: Status) -> Self {
if value.contains(git2::Status::CONFLICTED) {
GitStatus::Conflict
} else if value.intersects(
git2::Status::INDEX_MODIFIED
| git2::Status::WT_MODIFIED
| git2::Status::INDEX_RENAMED
| git2::Status::WT_RENAMED,
) {
GitStatus::Modified
} else if value.intersects(git2::Status::INDEX_NEW | git2::Status::WT_NEW) {
GitStatus::Added
} else {
GitStatus::Untracked
}
}
}
#[derive(Clone, Debug, Ord, Hash, PartialOrd, Eq, PartialEq)]
pub struct RepoPath(PathBuf);
impl RepoPath {
fn new(path: PathBuf) -> Self {
debug_assert!(path.is_relative(), "Repo paths must be relative");
RepoPath(path)
}
}
impl From<&Path> for RepoPath {
fn from(value: &Path) -> Self {
RepoPath::new(value.to_path_buf())
}
}
impl From<PathBuf> for RepoPath {
fn from(value: PathBuf) -> Self {
RepoPath::new(value)
}
}
impl Default for RepoPath {
fn default() -> Self {
RepoPath(PathBuf::new())
}
}
impl AsRef<Path> for RepoPath {
fn as_ref(&self) -> &Path {
self.0.as_ref()
}
}
impl std::ops::Deref for RepoPath {
type Target = PathBuf;
fn deref(&self) -> &Self::Target {
&self.0
}
}

View File

@ -6,7 +6,10 @@ use anyhow::{anyhow, Context, Result};
use client::{proto, Client};
use clock::ReplicaId;
use collections::{HashMap, VecDeque};
use fs::{repository::{GitRepository, RepoPath, GitStatus}, Fs, LineEnding};
use fs::{
repository::{GitRepository, GitStatus, RepoPath},
Fs, LineEnding,
};
use futures::{
channel::{
mpsc::{self, UnboundedSender},
@ -121,7 +124,7 @@ pub struct Snapshot {
pub struct RepositoryEntry {
pub(crate) work_directory: WorkDirectoryEntry,
pub(crate) branch: Option<Arc<str>>,
// pub(crate) statuses: TreeMap<RepoPath, GitStatus>
pub(crate) statuses: TreeMap<RepoPath, GitStatus>,
}
impl RepositoryEntry {
@ -169,7 +172,6 @@ impl AsRef<Path> for RepositoryWorkDirectory {
}
}
#[derive(Clone, Debug, Ord, PartialOrd, Eq, PartialEq)]
pub struct WorkDirectoryEntry(ProjectEntryId);
@ -219,6 +221,7 @@ pub struct LocalSnapshot {
#[derive(Debug, Clone)]
pub struct LocalRepositoryEntry {
pub(crate) scan_id: usize,
pub(crate) full_scan_id: usize,
pub(crate) repo_ptr: Arc<Mutex<dyn GitRepository>>,
/// Path to the actual .git folder.
/// Note: if .git is a file, this points to the folder indicated by the .git file
@ -1412,6 +1415,8 @@ impl Snapshot {
let repository = RepositoryEntry {
work_directory: ProjectEntryId::from_proto(repository.work_directory_id).into(),
branch: repository.branch.map(Into::into),
// TODO: status
statuses: Default::default(),
};
if let Some(entry) = self.entry_for_id(repository.work_directory_id()) {
self.repository_entries
@ -1572,6 +1577,10 @@ impl LocalSnapshot {
current_candidate.map(|entry| entry.to_owned())
}
pub(crate) fn get_local_repo(&self, repo: &RepositoryEntry) -> Option<&LocalRepositoryEntry> {
self.git_repositories.get(&repo.work_directory.0)
}
pub(crate) fn repo_for_metadata(
&self,
path: &Path,
@ -1842,6 +1851,7 @@ impl LocalSnapshot {
RepositoryEntry {
work_directory: work_dir_id.into(),
branch: repo_lock.branch_name().map(Into::into),
statuses: repo_lock.statuses().unwrap_or_default(),
},
);
drop(repo_lock);
@ -1850,6 +1860,7 @@ impl LocalSnapshot {
work_dir_id,
LocalRepositoryEntry {
scan_id,
full_scan_id: scan_id,
repo_ptr: repo,
git_dir_path: parent_path.clone(),
},
@ -2825,26 +2836,7 @@ impl BackgroundScanner {
fs_entry.is_ignored = ignore_stack.is_all();
snapshot.insert_entry(fs_entry, self.fs.as_ref());
let scan_id = snapshot.scan_id;
let repo_with_path_in_dotgit = snapshot.repo_for_metadata(&path);
if let Some((entry_id, repo)) = repo_with_path_in_dotgit {
let work_dir = snapshot
.entry_for_id(entry_id)
.map(|entry| RepositoryWorkDirectory(entry.path.clone()))?;
let repo = repo.lock();
repo.reload_index();
let branch = repo.branch_name();
snapshot.git_repositories.update(&entry_id, |entry| {
entry.scan_id = scan_id;
});
snapshot
.repository_entries
.update(&work_dir, |entry| entry.branch = branch.map(Into::into));
}
self.reload_repo_for_path(&path, &mut snapshot);
if let Some(scan_queue_tx) = &scan_queue_tx {
let mut ancestor_inodes = snapshot.ancestor_inodes_for_path(&path);
@ -2872,6 +2864,63 @@ impl BackgroundScanner {
Some(event_paths)
}
fn reload_repo_for_path(&self, path: &Path, snapshot: &mut LocalSnapshot) -> Option<()> {
let scan_id = snapshot.scan_id;
if path
.components()
.any(|component| component.as_os_str() == *DOT_GIT)
{
let (entry_id, repo) = snapshot.repo_for_metadata(&path)?;
let work_dir = snapshot
.entry_for_id(entry_id)
.map(|entry| RepositoryWorkDirectory(entry.path.clone()))?;
let repo = repo.lock();
repo.reload_index();
let branch = repo.branch_name();
let statuses = repo.statuses().unwrap_or_default();
snapshot.git_repositories.update(&entry_id, |entry| {
entry.scan_id = scan_id;
entry.full_scan_id = scan_id;
});
snapshot.repository_entries.update(&work_dir, |entry| {
entry.branch = branch.map(Into::into);
entry.statuses = statuses;
});
} else if let Some(repo) = snapshot.repo_for(&path) {
let status = {
let local_repo = snapshot.get_local_repo(&repo)?;
// Short circuit if we've already scanned everything
if local_repo.full_scan_id == scan_id {
return None;
}
let repo_path = repo.work_directory.relativize(&snapshot, &path)?;
let git_ptr = local_repo.repo_ptr.lock();
git_ptr.file_status(&repo_path)?
};
if status != GitStatus::Untracked {
let work_dir = repo.work_directory(snapshot)?;
let work_dir_id = repo.work_directory;
snapshot
.git_repositories
.update(&work_dir_id, |entry| entry.scan_id = scan_id);
snapshot
.repository_entries
.update(&work_dir, |entry| entry.statuses.insert(repo_path, status));
}
}
Some(())
}
async fn update_ignore_statuses(&self) {
use futures::FutureExt as _;

View File

@ -2,13 +2,13 @@ use std::{cmp::Ordering, fmt::Debug};
use crate::{Bias, Dimension, Item, KeyedItem, SeekTarget, SumTree, Summary};
#[derive(Clone, Debug)]
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct TreeMap<K, V>(SumTree<MapEntry<K, V>>)
where
K: Clone + Debug + Default + Ord,
V: Clone + Debug;
#[derive(Clone, Debug)]
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct MapEntry<K, V> {
key: K,
value: V,