diff --git a/helix-vcs/src/git.rs b/helix-vcs/src/git.rs index f095ddf47141..623794c4e690 100644 --- a/helix-vcs/src/git.rs +++ b/helix-vcs/src/git.rs @@ -5,9 +5,10 @@ use std::{ }; use anyhow::Result; +use git::index::{entry::Mode, State}; use git::objs::tree::EntryMode; -use git::sec::trust::DefaultForLevel; -use git::{Commit, ObjectId, Repository, ThreadSafeRepository, Tree}; +use git::{prelude::FindExt, sec::trust::DefaultForLevel}; +use git::{Commit, ObjectId, Repository, ThreadSafeRepository}; use git_repository as git; use ignore::WalkBuilder; use sha1::Digest; @@ -19,12 +20,6 @@ mod test; pub struct Git; -struct GitFileMeta { - entry_mode: FileEntryMode, - oid: ObjectId, - path: PathBuf, -} - /// A subset of `git_repository::objs::tree::EntryMode` that actually makes sense for tree nodes. #[derive(Hash, PartialEq, Eq)] enum FileEntryMode { @@ -118,65 +113,65 @@ impl Git { // TODO: allow diffing against another ref let head_tree = repo.head_commit()?.tree()?; - - let mut head_tree_files = vec![]; - let mut submodule_paths = vec![PathBuf::from(".git")]; - traverse_tree( - &head_tree, - repo, - PathBuf::new(), - &mut head_tree_files, - &mut submodule_paths, - )?; - - submodule_paths - .iter_mut() - .for_each(|path| *path = work_dir.join(&path)); + let head_state = State::from_tree(&head_tree.id, |oid, buf| { + repo.objects.find_tree_iter(oid, buf).ok() + })?; let mut head_tree_set = HashSet::new(); + let mut submodule_paths = vec![]; let mut raw_changes = RawChanges::default(); - // Looks for modified & deleted files by walking the head tree and probing the fs - for item in head_tree_files.into_iter() { - let full_path = work_dir.join(&item.path); + for item in head_state.entries() { + let full_path = work_dir.join(&PathBuf::from(item.path(&head_state).to_string())); - match git_meta_from_path(&full_path, autocrlf)? { - Some((new_entry_mode, new_oid)) => { - // On Windows, physical files are _always_ inferred as `Blob`. We simply don't - // compare the entry mode as it's pointless. - let entry_mode_changed = { - #[cfg(unix)] - { - new_entry_mode != item.entry_mode - } + if item.mode == Mode::COMMIT { + submodule_paths.push(full_path); + } else { + let old_entry_mode = match item.mode { + Mode::FILE => FileEntryMode::Blob, + Mode::FILE_EXECUTABLE => FileEntryMode::BlobExecutable, + Mode::SYMLINK => FileEntryMode::Link, + _ => anyhow::bail!("unexpected entry mode"), + }; - #[cfg(not(unix))] - { - false + match git_meta_from_path(&full_path, autocrlf)? { + Some((new_entry_mode, new_oid)) => { + // On Windows, physical files are _always_ inferred as `Blob`. We simply don't + // compare the entry mode as it's pointless. + let entry_mode_changed = { + #[cfg(unix)] + { + new_entry_mode != old_entry_mode + } + + #[cfg(not(unix))] + { + false + } + }; + + if entry_mode_changed || new_oid != item.id { + raw_changes.add_modification(RawModification { + previous_entry_mode: old_entry_mode, + previous_oid: item.id, + entry_mode: new_entry_mode, + oid: new_oid, + path: full_path.clone(), + }); } - }; - - if entry_mode_changed || new_oid != item.oid { - raw_changes.add_modification(RawModification { - previous_entry_mode: item.entry_mode, - previous_oid: item.oid, - entry_mode: new_entry_mode, - oid: new_oid, + } + None => { + raw_changes.add_deletion(RawDeletion { + entry_mode: old_entry_mode, + oid: item.id, path: full_path.clone(), }); } } - None => { - raw_changes.add_deletion(RawDeletion { - entry_mode: item.entry_mode, - oid: item.oid, - path: full_path.clone(), - }); - } - } - head_tree_set.insert(full_path); + head_tree_set.insert(full_path); + } } // Looks for untracked files by walking the fs and probing the (cached) head tree @@ -346,58 +341,6 @@ fn find_file_in_commit(repo: &Repository, commit: &Commit, file: &Path) -> Optio } } -/// Traverses a tree with recursion. -fn traverse_tree( - tree: &Tree, - repo: &Repository, - base: PathBuf, - all_files: &mut Vec, - submodules: &mut Vec, -) -> Result<()> { - for entry in tree.iter() { - let entry = entry?; - let mut new_base = base.clone(); - new_base.push(entry.filename().to_string()); - - match entry.mode() { - EntryMode::Tree => { - let obj = repo.find_object(entry.id())?; - let new_tree = obj.try_into_tree()?; - traverse_tree(&new_tree, repo, new_base, all_files, submodules)?; - } - EntryMode::Commit => { - // Submodules not supported yet. We return the path so that the fs walk can ignore - // them. - // TODO: support option for recursively looking into submodules - submodules.push(new_base); - } - EntryMode::Link => { - all_files.push(GitFileMeta { - entry_mode: FileEntryMode::Link, - oid: entry.oid(), - path: new_base, - }); - } - EntryMode::Blob => { - all_files.push(GitFileMeta { - entry_mode: FileEntryMode::Blob, - oid: entry.oid(), - path: new_base, - }); - } - EntryMode::BlobExecutable => { - all_files.push(GitFileMeta { - entry_mode: FileEntryMode::BlobExecutable, - oid: entry.oid(), - path: new_base, - }); - } - } - } - - Ok(()) -} - fn git_meta_from_path( path: &Path, autocrlf: bool,