|
|
|
@ -17,15 +17,64 @@ use gix::status::{
|
|
|
|
|
};
|
|
|
|
|
use gix::{Commit, ObjectId, Repository, ThreadSafeRepository};
|
|
|
|
|
|
|
|
|
|
use crate::{DiffProvider, FileChange};
|
|
|
|
|
use crate::FileChange;
|
|
|
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
|
mod test;
|
|
|
|
|
|
|
|
|
|
#[derive(Clone, Copy)]
|
|
|
|
|
pub struct Git;
|
|
|
|
|
pub fn get_diff_base(file: &Path) -> Result<Vec<u8>> {
|
|
|
|
|
debug_assert!(!file.exists() || file.is_file());
|
|
|
|
|
debug_assert!(file.is_absolute());
|
|
|
|
|
|
|
|
|
|
// TODO cache repository lookup
|
|
|
|
|
|
|
|
|
|
let repo_dir = file.parent().context("file has no parent directory")?;
|
|
|
|
|
let repo = open_repo(repo_dir, None)
|
|
|
|
|
.context("failed to open git repo")?
|
|
|
|
|
.to_thread_local();
|
|
|
|
|
let head = repo.head_commit()?;
|
|
|
|
|
let file_oid = find_file_in_commit(&repo, &head, file)?;
|
|
|
|
|
|
|
|
|
|
let file_object = repo.find_object(file_oid)?;
|
|
|
|
|
let data = file_object.detach().data;
|
|
|
|
|
// Get the actual data that git would make out of the git object.
|
|
|
|
|
// This will apply the user's git config or attributes like crlf conversions.
|
|
|
|
|
if let Some(work_dir) = repo.work_dir() {
|
|
|
|
|
let rela_path = file.strip_prefix(work_dir)?;
|
|
|
|
|
let rela_path = gix::path::try_into_bstr(rela_path)?;
|
|
|
|
|
let (mut pipeline, _) = repo.filter_pipeline(None)?;
|
|
|
|
|
let mut worktree_outcome =
|
|
|
|
|
pipeline.convert_to_worktree(&data, rela_path.as_ref(), Delay::Forbid)?;
|
|
|
|
|
let mut buf = Vec::with_capacity(data.len());
|
|
|
|
|
worktree_outcome.read_to_end(&mut buf)?;
|
|
|
|
|
Ok(buf)
|
|
|
|
|
} else {
|
|
|
|
|
Ok(data)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn get_current_head_name(file: &Path) -> Result<Arc<ArcSwap<Box<str>>>> {
|
|
|
|
|
debug_assert!(!file.exists() || file.is_file());
|
|
|
|
|
debug_assert!(file.is_absolute());
|
|
|
|
|
let repo_dir = file.parent().context("file has no parent directory")?;
|
|
|
|
|
let repo = open_repo(repo_dir, None)
|
|
|
|
|
.context("failed to open git repo")?
|
|
|
|
|
.to_thread_local();
|
|
|
|
|
let head_ref = repo.head_ref()?;
|
|
|
|
|
let head_commit = repo.head_commit()?;
|
|
|
|
|
|
|
|
|
|
let name = match head_ref {
|
|
|
|
|
Some(reference) => reference.name().shorten().to_string(),
|
|
|
|
|
None => head_commit.id.to_hex_with_len(8).to_string(),
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
Ok(Arc::new(ArcSwap::from_pointee(name.into_boxed_str())))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn for_each_changed_file(cwd: &Path, f: impl Fn(Result<FileChange>) -> bool) -> Result<()> {
|
|
|
|
|
status(&open_repo(cwd, None)?.to_thread_local(), f)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl Git {
|
|
|
|
|
fn open_repo(path: &Path, ceiling_dir: Option<&Path>) -> Result<ThreadSafeRepository> {
|
|
|
|
|
// custom open options
|
|
|
|
|
let mut git_open_opts_map = gix::sec::trust::Mapping::<gix::open::Options>::default();
|
|
|
|
@ -137,72 +186,6 @@ impl Git {
|
|
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl Git {
|
|
|
|
|
pub fn get_diff_base(&self, file: &Path) -> Result<Vec<u8>> {
|
|
|
|
|
debug_assert!(!file.exists() || file.is_file());
|
|
|
|
|
debug_assert!(file.is_absolute());
|
|
|
|
|
|
|
|
|
|
// TODO cache repository lookup
|
|
|
|
|
|
|
|
|
|
let repo_dir = file.parent().context("file has no parent directory")?;
|
|
|
|
|
let repo = Git::open_repo(repo_dir, None)
|
|
|
|
|
.context("failed to open git repo")?
|
|
|
|
|
.to_thread_local();
|
|
|
|
|
let head = repo.head_commit()?;
|
|
|
|
|
let file_oid = find_file_in_commit(&repo, &head, file)?;
|
|
|
|
|
|
|
|
|
|
let file_object = repo.find_object(file_oid)?;
|
|
|
|
|
let data = file_object.detach().data;
|
|
|
|
|
// Get the actual data that git would make out of the git object.
|
|
|
|
|
// This will apply the user's git config or attributes like crlf conversions.
|
|
|
|
|
if let Some(work_dir) = repo.work_dir() {
|
|
|
|
|
let rela_path = file.strip_prefix(work_dir)?;
|
|
|
|
|
let rela_path = gix::path::try_into_bstr(rela_path)?;
|
|
|
|
|
let (mut pipeline, _) = repo.filter_pipeline(None)?;
|
|
|
|
|
let mut worktree_outcome =
|
|
|
|
|
pipeline.convert_to_worktree(&data, rela_path.as_ref(), Delay::Forbid)?;
|
|
|
|
|
let mut buf = Vec::with_capacity(data.len());
|
|
|
|
|
worktree_outcome.read_to_end(&mut buf)?;
|
|
|
|
|
Ok(buf)
|
|
|
|
|
} else {
|
|
|
|
|
Ok(data)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn get_current_head_name(&self, file: &Path) -> Result<Arc<ArcSwap<Box<str>>>> {
|
|
|
|
|
debug_assert!(!file.exists() || file.is_file());
|
|
|
|
|
debug_assert!(file.is_absolute());
|
|
|
|
|
let repo_dir = file.parent().context("file has no parent directory")?;
|
|
|
|
|
let repo = Git::open_repo(repo_dir, None)
|
|
|
|
|
.context("failed to open git repo")?
|
|
|
|
|
.to_thread_local();
|
|
|
|
|
let head_ref = repo.head_ref()?;
|
|
|
|
|
let head_commit = repo.head_commit()?;
|
|
|
|
|
|
|
|
|
|
let name = match head_ref {
|
|
|
|
|
Some(reference) => reference.name().shorten().to_string(),
|
|
|
|
|
None => head_commit.id.to_hex_with_len(8).to_string(),
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
Ok(Arc::new(ArcSwap::from_pointee(name.into_boxed_str())))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn for_each_changed_file(
|
|
|
|
|
&self,
|
|
|
|
|
cwd: &Path,
|
|
|
|
|
f: impl Fn(Result<FileChange>) -> bool,
|
|
|
|
|
) -> Result<()> {
|
|
|
|
|
Self::status(&Self::open_repo(cwd, None)?.to_thread_local(), f)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl From<Git> for DiffProvider {
|
|
|
|
|
fn from(value: Git) -> Self {
|
|
|
|
|
DiffProvider::Git(value)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Finds the object that contains the contents of a file at a specific commit.
|
|
|
|
|
fn find_file_in_commit(repo: &Repository, commit: &Commit, file: &Path) -> Result<ObjectId> {
|
|
|
|
|