Use repository mutex more sparingly. Don't hold it while running git status. (#12489)
Previously, each git `Repository` object was held inside of a mutex. This was needed because libgit2's Repository object is (as one would expect) not thread safe. But now, the two longest-running git operations that Zed performs, (`status` and `blame`) do not use libgit2 - they invoke the `git` executable. For these operations, it's not necessary to hold a lock on the repository. In this PR, I've moved our mutex usage so that it only wraps the libgit2 calls, not our `git` subprocess spawns. The main user-facing impact of this is that the UI is much more responsive when initially opening a project with a very large git repository (e.g. `chromium`, `webkit`, `linux`). Release Notes: - Improved Zed's responsiveness when initially opening a project containing a very large git repository.
This commit is contained in:
parent
1ecd13ba50
commit
8f942bf647
6 changed files with 69 additions and 79 deletions
|
@ -14,8 +14,6 @@ use std::{
|
|||
use sum_tree::MapSeekTarget;
|
||||
use util::ResultExt;
|
||||
|
||||
pub use git2::Repository as LibGitRepository;
|
||||
|
||||
#[derive(Clone, Debug, Hash, PartialEq)]
|
||||
pub struct Branch {
|
||||
pub is_head: bool,
|
||||
|
@ -24,7 +22,7 @@ pub struct Branch {
|
|||
pub unix_timestamp: Option<i64>,
|
||||
}
|
||||
|
||||
pub trait GitRepository: Send {
|
||||
pub trait GitRepository: Send + Sync {
|
||||
fn reload_index(&self);
|
||||
|
||||
/// Loads a git repository entry's contents.
|
||||
|
@ -58,19 +56,19 @@ impl std::fmt::Debug for dyn GitRepository {
|
|||
}
|
||||
|
||||
pub struct RealGitRepository {
|
||||
pub repository: LibGitRepository,
|
||||
pub repository: Mutex<git2::Repository>,
|
||||
pub git_binary_path: PathBuf,
|
||||
hosting_provider_registry: Arc<GitHostingProviderRegistry>,
|
||||
}
|
||||
|
||||
impl RealGitRepository {
|
||||
pub fn new(
|
||||
repository: LibGitRepository,
|
||||
repository: git2::Repository,
|
||||
git_binary_path: Option<PathBuf>,
|
||||
hosting_provider_registry: Arc<GitHostingProviderRegistry>,
|
||||
) -> Self {
|
||||
Self {
|
||||
repository,
|
||||
repository: Mutex::new(repository),
|
||||
git_binary_path: git_binary_path.unwrap_or_else(|| PathBuf::from("git")),
|
||||
hosting_provider_registry,
|
||||
}
|
||||
|
@ -79,13 +77,13 @@ impl RealGitRepository {
|
|||
|
||||
impl GitRepository for RealGitRepository {
|
||||
fn reload_index(&self) {
|
||||
if let Ok(mut index) = self.repository.index() {
|
||||
if let Ok(mut index) = self.repository.lock().index() {
|
||||
_ = index.read(false);
|
||||
}
|
||||
}
|
||||
|
||||
fn load_index_text(&self, relative_file_path: &Path) -> Option<String> {
|
||||
fn logic(repo: &LibGitRepository, relative_file_path: &Path) -> Result<Option<String>> {
|
||||
fn logic(repo: &git2::Repository, relative_file_path: &Path) -> Result<Option<String>> {
|
||||
const STAGE_NORMAL: i32 = 0;
|
||||
let index = repo.index()?;
|
||||
|
||||
|
@ -101,7 +99,7 @@ impl GitRepository for RealGitRepository {
|
|||
Ok(Some(String::from_utf8(content)?))
|
||||
}
|
||||
|
||||
match logic(&self.repository, relative_file_path) {
|
||||
match logic(&self.repository.lock(), relative_file_path) {
|
||||
Ok(value) => return value,
|
||||
Err(err) => log::error!("Error loading head text: {:?}", err),
|
||||
}
|
||||
|
@ -109,31 +107,35 @@ impl GitRepository for RealGitRepository {
|
|||
}
|
||||
|
||||
fn remote_url(&self, name: &str) -> Option<String> {
|
||||
let remote = self.repository.find_remote(name).ok()?;
|
||||
let repo = self.repository.lock();
|
||||
let remote = repo.find_remote(name).ok()?;
|
||||
remote.url().map(|url| url.to_string())
|
||||
}
|
||||
|
||||
fn branch_name(&self) -> Option<String> {
|
||||
let head = self.repository.head().log_err()?;
|
||||
let repo = self.repository.lock();
|
||||
let head = repo.head().log_err()?;
|
||||
let branch = String::from_utf8_lossy(head.shorthand_bytes());
|
||||
Some(branch.to_string())
|
||||
}
|
||||
|
||||
fn head_sha(&self) -> Option<String> {
|
||||
let head = self.repository.head().ok()?;
|
||||
head.target().map(|oid| oid.to_string())
|
||||
Some(self.repository.lock().head().ok()?.target()?.to_string())
|
||||
}
|
||||
|
||||
fn statuses(&self, path_prefix: &Path) -> Result<GitStatus> {
|
||||
let working_directory = self
|
||||
.repository
|
||||
.lock()
|
||||
.workdir()
|
||||
.context("failed to read git work directory")?;
|
||||
GitStatus::new(&self.git_binary_path, working_directory, path_prefix)
|
||||
.context("failed to read git work directory")?
|
||||
.to_path_buf();
|
||||
GitStatus::new(&self.git_binary_path, &working_directory, path_prefix)
|
||||
}
|
||||
|
||||
fn branches(&self) -> Result<Vec<Branch>> {
|
||||
let local_branches = self.repository.branches(Some(BranchType::Local))?;
|
||||
let repo = self.repository.lock();
|
||||
let local_branches = repo.branches(Some(BranchType::Local))?;
|
||||
let valid_branches = local_branches
|
||||
.filter_map(|branch| {
|
||||
branch.ok().and_then(|(branch, _)| {
|
||||
|
@ -158,36 +160,40 @@ impl GitRepository for RealGitRepository {
|
|||
}
|
||||
|
||||
fn change_branch(&self, name: &str) -> Result<()> {
|
||||
let revision = self.repository.find_branch(name, BranchType::Local)?;
|
||||
let repo = self.repository.lock();
|
||||
let revision = repo.find_branch(name, BranchType::Local)?;
|
||||
let revision = revision.get();
|
||||
let as_tree = revision.peel_to_tree()?;
|
||||
self.repository.checkout_tree(as_tree.as_object(), None)?;
|
||||
self.repository.set_head(
|
||||
repo.checkout_tree(as_tree.as_object(), None)?;
|
||||
repo.set_head(
|
||||
revision
|
||||
.name()
|
||||
.ok_or_else(|| anyhow::anyhow!("Branch name could not be retrieved"))?,
|
||||
)?;
|
||||
Ok(())
|
||||
}
|
||||
fn create_branch(&self, name: &str) -> Result<()> {
|
||||
let current_commit = self.repository.head()?.peel_to_commit()?;
|
||||
self.repository.branch(name, ¤t_commit, false)?;
|
||||
|
||||
fn create_branch(&self, name: &str) -> Result<()> {
|
||||
let repo = self.repository.lock();
|
||||
let current_commit = repo.head()?.peel_to_commit()?;
|
||||
repo.branch(name, ¤t_commit, false)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn blame(&self, path: &Path, content: Rope) -> Result<crate::blame::Blame> {
|
||||
let working_directory = self
|
||||
.repository
|
||||
.lock()
|
||||
.workdir()
|
||||
.with_context(|| format!("failed to get git working directory for file {:?}", path))?;
|
||||
.with_context(|| format!("failed to get git working directory for file {:?}", path))?
|
||||
.to_path_buf();
|
||||
|
||||
const REMOTE_NAME: &str = "origin";
|
||||
let remote_url = self.remote_url(REMOTE_NAME);
|
||||
|
||||
crate::blame::Blame::for_path(
|
||||
&self.git_binary_path,
|
||||
working_directory,
|
||||
&working_directory,
|
||||
path,
|
||||
&content,
|
||||
remote_url,
|
||||
|
@ -210,8 +216,8 @@ pub struct FakeGitRepositoryState {
|
|||
}
|
||||
|
||||
impl FakeGitRepository {
|
||||
pub fn open(state: Arc<Mutex<FakeGitRepositoryState>>) -> Arc<Mutex<dyn GitRepository>> {
|
||||
Arc::new(Mutex::new(FakeGitRepository { state }))
|
||||
pub fn open(state: Arc<Mutex<FakeGitRepositoryState>>) -> Arc<dyn GitRepository> {
|
||||
Arc::new(FakeGitRepository { state })
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue