mirror of
https://github.com/zed-industries/zed.git
synced 2024-11-07 20:39:04 +03:00
WIP: Track live entry status in repository
co-authored-by: petros <petros@zed.dev>
This commit is contained in:
parent
7169f5c760
commit
67491632cb
@ -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
|
||||
}
|
||||
}
|
||||
|
@ -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 _;
|
||||
|
||||
|
@ -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,
|
||||
|
Loading…
Reference in New Issue
Block a user