@@ -1,3 +1,4 @@
1+use anyhow::{bail, Context, Result};
12use arc_swap::ArcSwap;
23use std::path::Path;
34use std::sync::Arc;
@@ -14,7 +15,7 @@ mod test;
1415pub struct Git;
1516
1617impl Git {
17− fn open_repo(path: &Path, ceiling_dir: Option<&Path>) -> Option<ThreadSafeRepository> {
18+ fn open_repo(path: &Path, ceiling_dir: Option<&Path>) -> Result<ThreadSafeRepository> {
1819 // custom open options
1920 let mut git_open_opts_map = gix::sec::trust::Mapping::<gix::open::Options>::default();
2021
@@ -45,26 +46,31 @@ impl Git {
4546 open_options.ceiling_dirs = vec![ceiling_dir.to_owned()];
4647 }
4748
48− ThreadSafeRepository::discover_with_environment_overrides_opts(
49+ let res = ThreadSafeRepository::discover_with_environment_overrides_opts(
4950 path,
5051 open_options,
5152 git_open_opts_map,
52− )
53− .ok()
53+ )?;
54+
55+ Ok(res)
5456 }
5557}
5658
5759impl DiffProvider for Git {
58− fn get_diff_base(&self, file: &Path) -> Option<Vec<u8>> {
60+ fn get_diff_base(&self, file: &Path) -> Result<Vec<u8>> {
5961 debug_assert!(!file.exists() || file.is_file());
6062 debug_assert!(file.is_absolute());
6163
6264 // TODO cache repository lookup
63− let repo = Git::open_repo(file.parent()?, None)?.to_thread_local();
64− let head = repo.head_commit().ok()?;
65+
66+ let repo_dir = file.parent().context("file has no parent directory")?;
67+ let repo = Git::open_repo(repo_dir, None)
68+ .context("failed to open git repo")?
69+ .to_thread_local();
70+ let head = repo.head_commit()?;
6571 let file_oid = find_file_in_commit(&repo, &head, file)?;
6672
67− let file_object = repo.find_object(file_oid).ok()?;
73+ let file_object = repo.find_object(file_oid)?;
6874 let mut data = file_object.detach().data;
6975 // convert LF to CRLF if configured to avoid showing every line as changed
7076 if repo
@@ -87,35 +93,42 @@ impl DiffProvider for Git {
8793 }
8894 data = normalized_file
8995 }
90− Some(data)
96+ Ok(data)
9197 }
9298
93− fn get_current_head_name(&self, file: &Path) -> Option<Arc<ArcSwap<Box<str>>>> {
99+ fn get_current_head_name(&self, file: &Path) -> Result<Arc<ArcSwap<Box<str>>>> {
94100 debug_assert!(!file.exists() || file.is_file());
95101 debug_assert!(file.is_absolute());
96− let repo = Git::open_repo(file.parent()?, None)?.to_thread_local();
97− let head_ref = repo.head_ref().ok()?;
98− let head_commit = repo.head_commit().ok()?;
102+ let repo_dir = file.parent().context("file has no parent directory")?;
103+ let repo = Git::open_repo(repo_dir, None)
104+ .context("failed to open git repo")?
105+ .to_thread_local();
106+ let head_ref = repo.head_ref()?;
107+ let head_commit = repo.head_commit()?;
99108
100109 let name = match head_ref {
101110 Some(reference) => reference.name().shorten().to_string(),
102111 None => head_commit.id.to_hex_with_len(8).to_string(),
103112 };
104113
105− Some(Arc::new(ArcSwap::from_pointee(name.into_boxed_str())))
114+ Ok(Arc::new(ArcSwap::from_pointee(name.into_boxed_str())))
106115 }
107116}
108117
109118/// Finds the object that contains the contents of a file at a specific commit.
110−fn find_file_in_commit(repo: &Repository, commit: &Commit, file: &Path) -> Option<ObjectId> {
111− let repo_dir = repo.work_dir()?;
112− let rel_path = file.strip_prefix(repo_dir).ok()?;
113− let tree = commit.tree().ok()?;
114− let tree_entry = tree.lookup_entry_by_path(rel_path).ok()??;
119+fn find_file_in_commit(repo: &Repository, commit: &Commit, file: &Path) -> Result<ObjectId> {
120+ let repo_dir = repo.work_dir().context("repo has no worktree")?;
121+ let rel_path = file.strip_prefix(repo_dir)?;
122+ let tree = commit.tree()?;
123+ let tree_entry = tree
124+ .lookup_entry_by_path(rel_path)?
125+ .context("file is untracked")?;
115126 match tree_entry.mode() {
116127 // not a file, everything is new, do not show diff
117− EntryMode::Tree | EntryMode::Commit | EntryMode::Link => None,
128+ mode @ (EntryMode::Tree | EntryMode::Commit | EntryMode::Link) => {
129+ bail!("entry at {} is not a file but a {mode:?}", file.display())
130+ }
118131 // found a file
119− EntryMode::Blob | EntryMode::BlobExecutable => Some(tree_entry.object_id()),
132+ EntryMode::Blob | EntryMode::BlobExecutable => Ok(tree_entry.object_id()),
120133 }
121134}