- [x] Rewrite worktree git handling - [x] Fix tests - [x] Fix `test_propagate_statuses_for_repos_under_project` - [x] Replace `WorkDirectoryEntry` with `WorkDirectory` in `RepositoryEntry` - [x] Add a worktree event for capturing git status changes - [x] Confirm that the local repositories are correctly updating the new WorkDirectory field - [x] Implement the git statuses query as a join when pulling entries out of worktree - [x] Use this new join to implement the project panel and outline panel. - [x] Synchronize git statuses over the wire for collab and remote dev (use the existing `worktree_repository_statuses` table, adjust as needed) - [x] Only send changed statuses to collab Release Notes: - N/A --------- Co-authored-by: Cole Miller <cole@zed.dev> Co-authored-by: Mikayla <mikayla@zed.com> Co-authored-by: Nathan <nathan@zed.dev>
89 lines
2.8 KiB
Rust
89 lines
2.8 KiB
Rust
use crate::repository::{GitFileStatus, RepoPath};
|
|
use anyhow::{anyhow, Result};
|
|
use std::{path::Path, process::Stdio, sync::Arc};
|
|
|
|
#[derive(Clone)]
|
|
pub struct GitStatus {
|
|
pub entries: Arc<[(RepoPath, GitFileStatus)]>,
|
|
}
|
|
|
|
impl GitStatus {
|
|
pub(crate) fn new(
|
|
git_binary: &Path,
|
|
working_directory: &Path,
|
|
path_prefixes: &[RepoPath],
|
|
) -> Result<Self> {
|
|
let child = util::command::new_std_command(git_binary)
|
|
.current_dir(working_directory)
|
|
.args([
|
|
"--no-optional-locks",
|
|
"status",
|
|
"--porcelain=v1",
|
|
"--untracked-files=all",
|
|
"-z",
|
|
])
|
|
.args(path_prefixes.iter().map(|path_prefix| {
|
|
if path_prefix.0.as_ref() == Path::new("") {
|
|
Path::new(".")
|
|
} else {
|
|
path_prefix
|
|
}
|
|
}))
|
|
.stdin(Stdio::null())
|
|
.stdout(Stdio::piped())
|
|
.stderr(Stdio::piped())
|
|
.spawn()
|
|
.map_err(|e| anyhow!("Failed to start git status process: {}", e))?;
|
|
|
|
let output = child
|
|
.wait_with_output()
|
|
.map_err(|e| anyhow!("Failed to read git blame output: {}", e))?;
|
|
|
|
if !output.status.success() {
|
|
let stderr = String::from_utf8_lossy(&output.stderr);
|
|
return Err(anyhow!("git status process failed: {}", stderr));
|
|
}
|
|
let stdout = String::from_utf8_lossy(&output.stdout);
|
|
let mut entries = stdout
|
|
.split('\0')
|
|
.filter_map(|entry| {
|
|
if entry.is_char_boundary(3) {
|
|
let (status, path) = entry.split_at(3);
|
|
let status = status.trim();
|
|
Some((
|
|
RepoPath(Path::new(path).into()),
|
|
match status {
|
|
"A" => GitFileStatus::Added,
|
|
"M" => GitFileStatus::Modified,
|
|
"D" => GitFileStatus::Deleted,
|
|
"??" => GitFileStatus::Untracked,
|
|
_ => return None,
|
|
},
|
|
))
|
|
} else {
|
|
None
|
|
}
|
|
})
|
|
.collect::<Vec<_>>();
|
|
entries.sort_unstable_by(|a, b| a.0.cmp(&b.0));
|
|
Ok(Self {
|
|
entries: entries.into(),
|
|
})
|
|
}
|
|
|
|
pub fn get(&self, path: &Path) -> Option<GitFileStatus> {
|
|
self.entries
|
|
.binary_search_by(|(repo_path, _)| repo_path.0.as_ref().cmp(path))
|
|
.ok()
|
|
.map(|index| self.entries[index].1)
|
|
}
|
|
}
|
|
|
|
impl Default for GitStatus {
|
|
fn default() -> Self {
|
|
Self {
|
|
entries: Arc::new([]),
|
|
}
|
|
}
|
|
}
|