Skip to content

Commit

Permalink
Replace tree-traversal with State
Browse files Browse the repository at this point in the history
  • Loading branch information
xJonathanLEI committed Jan 24, 2023
1 parent bb97f55 commit 7e9d5fc
Showing 1 changed file with 49 additions and 106 deletions.
155 changes: 49 additions & 106 deletions helix-vcs/src/git.rs
Expand Up @@ -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;
Expand All @@ -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 {
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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<GitFileMeta>,
submodules: &mut Vec<PathBuf>,
) -> 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,
Expand Down

0 comments on commit 7e9d5fc

Please sign in to comment.