Directly parse .git when it's a file instead of using libgit2 (#27885)
Avoids building a whole git2 repository object at the worktree layer just to watch some additional paths. - [x] Tidy up names of the various paths - [x] Tests for worktrees and submodules Release Notes: - N/A
This commit is contained in:
parent
429d4580cf
commit
055df30757
7 changed files with 401 additions and 160 deletions
|
@ -384,12 +384,23 @@ struct LocalRepositoryEntry {
|
|||
work_directory: WorkDirectory,
|
||||
work_directory_abs_path: Arc<Path>,
|
||||
git_dir_scan_id: usize,
|
||||
original_dot_git_abs_path: Arc<Path>,
|
||||
/// Absolute path to the actual .git folder.
|
||||
/// Note: if .git is a file, this points to the folder indicated by the .git file
|
||||
dot_git_dir_abs_path: Arc<Path>,
|
||||
/// Absolute path to the .git file, if we're in a git worktree.
|
||||
dot_git_worktree_abs_path: Option<Arc<Path>>,
|
||||
/// Absolute path to the original .git entry that caused us to create this repository.
|
||||
///
|
||||
/// This is normally a directory, but may be a "gitfile" that points to a directory elsewhere
|
||||
/// (whose path we then store in `repository_dir_abs_path`).
|
||||
dot_git_abs_path: Arc<Path>,
|
||||
/// Absolute path to the "commondir" for this repository.
|
||||
///
|
||||
/// This is always a directory. For a normal repository, this is the same as dot_git_abs_path,
|
||||
/// but in the case of a submodule or a worktree it is the path to the "parent" .git directory
|
||||
/// from which the submodule/worktree was derived.
|
||||
common_dir_abs_path: Arc<Path>,
|
||||
/// Absolute path to the directory holding the repository's state.
|
||||
///
|
||||
/// For a normal repository, this is a directory and coincides with `dot_git_abs_path` and
|
||||
/// `common_dir_abs_path`. For a submodule or worktree, this is some subdirectory of the
|
||||
/// commondir like `/project/.git/modules/foo`.
|
||||
repository_dir_abs_path: Arc<Path>,
|
||||
}
|
||||
|
||||
impl sum_tree::Item for LocalRepositoryEntry {
|
||||
|
@ -1351,7 +1362,11 @@ impl LocalWorktree {
|
|||
new_work_directory_abs_path: Some(
|
||||
new_repo.work_directory_abs_path.clone(),
|
||||
),
|
||||
dot_git_abs_path: Some(new_repo.original_dot_git_abs_path.clone()),
|
||||
dot_git_abs_path: Some(new_repo.dot_git_abs_path.clone()),
|
||||
repository_dir_abs_path: Some(
|
||||
new_repo.repository_dir_abs_path.clone(),
|
||||
),
|
||||
common_dir_abs_path: Some(new_repo.common_dir_abs_path.clone()),
|
||||
});
|
||||
new_repos.next();
|
||||
}
|
||||
|
@ -1368,9 +1383,11 @@ impl LocalWorktree {
|
|||
new_work_directory_abs_path: Some(
|
||||
new_repo.work_directory_abs_path.clone(),
|
||||
),
|
||||
dot_git_abs_path: Some(
|
||||
new_repo.original_dot_git_abs_path.clone(),
|
||||
dot_git_abs_path: Some(new_repo.dot_git_abs_path.clone()),
|
||||
repository_dir_abs_path: Some(
|
||||
new_repo.repository_dir_abs_path.clone(),
|
||||
),
|
||||
common_dir_abs_path: Some(new_repo.common_dir_abs_path.clone()),
|
||||
});
|
||||
}
|
||||
new_repos.next();
|
||||
|
@ -1384,6 +1401,8 @@ impl LocalWorktree {
|
|||
),
|
||||
new_work_directory_abs_path: None,
|
||||
dot_git_abs_path: None,
|
||||
repository_dir_abs_path: None,
|
||||
common_dir_abs_path: None,
|
||||
});
|
||||
old_repos.next();
|
||||
}
|
||||
|
@ -1394,7 +1413,9 @@ impl LocalWorktree {
|
|||
work_directory_id: entry_id,
|
||||
old_work_directory_abs_path: None,
|
||||
new_work_directory_abs_path: Some(repo.work_directory_abs_path.clone()),
|
||||
dot_git_abs_path: Some(repo.original_dot_git_abs_path.clone()),
|
||||
dot_git_abs_path: Some(repo.dot_git_abs_path.clone()),
|
||||
repository_dir_abs_path: Some(repo.repository_dir_abs_path.clone()),
|
||||
common_dir_abs_path: Some(repo.common_dir_abs_path.clone()),
|
||||
});
|
||||
new_repos.next();
|
||||
}
|
||||
|
@ -1403,7 +1424,9 @@ impl LocalWorktree {
|
|||
work_directory_id: entry_id,
|
||||
old_work_directory_abs_path: Some(repo.work_directory_abs_path.clone()),
|
||||
new_work_directory_abs_path: None,
|
||||
dot_git_abs_path: Some(repo.original_dot_git_abs_path.clone()),
|
||||
dot_git_abs_path: Some(repo.dot_git_abs_path.clone()),
|
||||
repository_dir_abs_path: Some(repo.repository_dir_abs_path.clone()),
|
||||
common_dir_abs_path: Some(repo.common_dir_abs_path.clone()),
|
||||
});
|
||||
old_repos.next();
|
||||
}
|
||||
|
@ -3042,9 +3065,6 @@ impl BackgroundScannerState {
|
|||
);
|
||||
return;
|
||||
};
|
||||
log::debug!(
|
||||
"building git repository, `.git` path in the worktree: {dot_git_path:?}"
|
||||
);
|
||||
|
||||
parent_dir.into()
|
||||
}
|
||||
|
@ -3075,7 +3095,6 @@ impl BackgroundScannerState {
|
|||
fs: &dyn Fs,
|
||||
watcher: &dyn Watcher,
|
||||
) -> Option<LocalRepositoryEntry> {
|
||||
log::trace!("insert git repository for {dot_git_path:?}");
|
||||
let work_dir_entry = self.snapshot.entry_for_path(work_directory.path_key().0)?;
|
||||
let work_directory_abs_path = self
|
||||
.snapshot
|
||||
|
@ -3092,46 +3111,51 @@ impl BackgroundScannerState {
|
|||
return None;
|
||||
}
|
||||
|
||||
let dot_git_abs_path = self.snapshot.abs_path.as_path().join(&dot_git_path);
|
||||
let dot_git_abs_path: Arc<Path> = self
|
||||
.snapshot
|
||||
.abs_path
|
||||
.as_path()
|
||||
.join(&dot_git_path)
|
||||
.as_path()
|
||||
.into();
|
||||
|
||||
// TODO add these watchers without building a whole repository by parsing .git-with-indirection
|
||||
let t0 = Instant::now();
|
||||
let repository = fs.open_repo(&dot_git_abs_path)?;
|
||||
log::trace!("opened git repo for {dot_git_abs_path:?}");
|
||||
|
||||
let repository_path = repository.path();
|
||||
watcher.add(&repository_path).log_err()?;
|
||||
|
||||
let actual_dot_git_dir_abs_path = repository.main_repository_path();
|
||||
let dot_git_worktree_abs_path = if actual_dot_git_dir_abs_path == dot_git_abs_path {
|
||||
None
|
||||
} else {
|
||||
// The two paths could be different because we opened a git worktree.
|
||||
// When that happens:
|
||||
//
|
||||
// * `dot_git_abs_path` is a file that points to the worktree-subdirectory in the actual
|
||||
// .git directory.
|
||||
//
|
||||
// * `repository_path` is the worktree-subdirectory.
|
||||
//
|
||||
// * `actual_dot_git_dir_abs_path` is the path to the actual .git directory. In git
|
||||
// documentation this is called the "commondir".
|
||||
watcher.add(&dot_git_abs_path).log_err()?;
|
||||
Some(Arc::from(dot_git_abs_path.as_path()))
|
||||
let mut common_dir_abs_path = dot_git_abs_path.clone();
|
||||
let mut repository_dir_abs_path = dot_git_abs_path.clone();
|
||||
// Parse .git if it's a "gitfile" pointing to a repository directory elsewhere.
|
||||
if let Some(dot_git_contents) = smol::block_on(fs.load(&dot_git_abs_path)).ok() {
|
||||
if let Some(path) = dot_git_contents.strip_prefix("gitdir:") {
|
||||
let path = path.trim();
|
||||
let path = dot_git_abs_path
|
||||
.parent()
|
||||
.unwrap_or(Path::new(""))
|
||||
.join(path);
|
||||
if let Some(path) = smol::block_on(fs.canonicalize(&path)).log_err() {
|
||||
repository_dir_abs_path = Path::new(&path).into();
|
||||
common_dir_abs_path = repository_dir_abs_path.clone();
|
||||
if let Some(ancestor_dot_git) = path
|
||||
.ancestors()
|
||||
.skip(1)
|
||||
.find(|ancestor| smol::block_on(is_git_dir(ancestor, fs)))
|
||||
{
|
||||
common_dir_abs_path = ancestor_dot_git.into();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
log::error!("failed to parse contents of .git file: {dot_git_contents:?}");
|
||||
}
|
||||
};
|
||||
|
||||
log::trace!("constructed libgit2 repo in {:?}", t0.elapsed());
|
||||
watcher.add(&common_dir_abs_path).log_err();
|
||||
|
||||
let work_directory_id = work_dir_entry.id;
|
||||
|
||||
let local_repository = LocalRepositoryEntry {
|
||||
work_directory_id,
|
||||
work_directory,
|
||||
git_dir_scan_id: 0,
|
||||
original_dot_git_abs_path: dot_git_abs_path.as_path().into(),
|
||||
dot_git_dir_abs_path: actual_dot_git_dir_abs_path.into(),
|
||||
work_directory_abs_path: work_directory_abs_path.as_path().into(),
|
||||
dot_git_worktree_abs_path,
|
||||
git_dir_scan_id: 0,
|
||||
dot_git_abs_path,
|
||||
common_dir_abs_path,
|
||||
repository_dir_abs_path,
|
||||
};
|
||||
|
||||
self.snapshot
|
||||
|
@ -3454,6 +3478,8 @@ pub struct UpdatedGitRepository {
|
|||
/// For a normal git repository checkout, the absolute path to the .git directory.
|
||||
/// For a worktree, the absolute path to the worktree's subdirectory inside the .git directory.
|
||||
pub dot_git_abs_path: Option<Arc<Path>>,
|
||||
pub repository_dir_abs_path: Option<Arc<Path>>,
|
||||
pub common_dir_abs_path: Option<Arc<Path>>,
|
||||
}
|
||||
|
||||
pub type UpdatedEntriesSet = Arc<[(Arc<Path>, ProjectEntryId, PathChange)]>;
|
||||
|
@ -4010,8 +4036,8 @@ impl BackgroundScanner {
|
|||
|
||||
if abs_path.0.file_name() == Some(*GITIGNORE) {
|
||||
for (_, repo) in snapshot.git_repositories.iter().filter(|(_, repo)| repo.directory_contains(&relative_path)) {
|
||||
if !dot_git_abs_paths.iter().any(|dot_git_abs_path| dot_git_abs_path == repo.dot_git_dir_abs_path.as_ref()) {
|
||||
dot_git_abs_paths.push(repo.dot_git_dir_abs_path.to_path_buf());
|
||||
if !dot_git_abs_paths.iter().any(|dot_git_abs_path| dot_git_abs_path == repo.common_dir_abs_path.as_ref()) {
|
||||
dot_git_abs_paths.push(repo.common_dir_abs_path.to_path_buf());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -4070,7 +4096,6 @@ impl BackgroundScanner {
|
|||
}
|
||||
}
|
||||
self.send_status_update(false, SmallVec::new());
|
||||
// send_status_update_inner(phase, state, status_update_tx, false, SmallVec::new());
|
||||
}
|
||||
|
||||
async fn forcibly_load_paths(&self, paths: &[Arc<Path>]) -> bool {
|
||||
|
@ -4709,8 +4734,8 @@ impl BackgroundScanner {
|
|||
.git_repositories
|
||||
.iter()
|
||||
.find_map(|(_, repo)| {
|
||||
if repo.dot_git_dir_abs_path.as_ref() == &dot_git_dir
|
||||
|| repo.dot_git_worktree_abs_path.as_deref() == Some(&dot_git_dir)
|
||||
if repo.common_dir_abs_path.as_ref() == &dot_git_dir
|
||||
|| repo.repository_dir_abs_path.as_ref() == &dot_git_dir
|
||||
{
|
||||
Some(repo.clone())
|
||||
} else {
|
||||
|
@ -4752,7 +4777,7 @@ impl BackgroundScanner {
|
|||
|
||||
if exists_in_snapshot
|
||||
|| matches!(
|
||||
smol::block_on(self.fs.metadata(&entry.dot_git_dir_abs_path)),
|
||||
smol::block_on(self.fs.metadata(&entry.common_dir_abs_path)),
|
||||
Ok(Some(_))
|
||||
)
|
||||
{
|
||||
|
@ -5081,7 +5106,7 @@ impl WorktreeModelHandle for Entity<Worktree> {
|
|||
.unwrap();
|
||||
(
|
||||
tree.fs.clone(),
|
||||
local_repo_entry.dot_git_dir_abs_path.clone(),
|
||||
local_repo_entry.common_dir_abs_path.clone(),
|
||||
local_repo_entry.git_dir_scan_id,
|
||||
)
|
||||
});
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue