on 376c19e help sign in
104 lines 3.9 kB history raw
1use std::path::Path;
3use gix::objs::tree::EntryMode;
4use gix::sec::trust::DefaultForLevel;
5use gix::{Commit, ObjectId, Repository, ThreadSafeRepository};
7use crate::DiffProvider;
9#[cfg(test)]
10mod test;
12pub struct Git;
14impl Git {
15 fn open_repo(path: &Path, ceiling_dir: Option<&Path>) -> Option<ThreadSafeRepository> {
16 // custom open options
17 let mut git_open_opts_map = gix::sec::trust::Mapping::<gix::open::Options>::default();
19 // On windows various configuration options are bundled as part of the installations
20 // This path depends on the install location of git and therefore requires some overhead to lookup
21 // This is basically only used on windows and has some overhead hence it's disabled on other platforms.
22 // `gitoxide` doesn't use this as default
23 let config = gix::permissions::Config {
24 system: true,
25 git: true,
26 user: true,
27 env: true,
28 includes: true,
29 git_binary: cfg!(windows),
30 };
31 // change options for config permissions without touching anything else
32 git_open_opts_map.reduced = git_open_opts_map.reduced.permissions(gix::Permissions {
33 config,
34 ..gix::Permissions::default_for_level(gix::sec::Trust::Reduced)
35 });
36 git_open_opts_map.full = git_open_opts_map.full.permissions(gix::Permissions {
37 config,
38 ..gix::Permissions::default_for_level(gix::sec::Trust::Full)
39 });
41 let mut open_options = gix::discover::upwards::Options::default();
42 if let Some(ceiling_dir) = ceiling_dir {
43 open_options.ceiling_dirs = vec![ceiling_dir.to_owned()];
44 }
46 ThreadSafeRepository::discover_with_environment_overrides_opts(
47 path,
48 open_options,
49 git_open_opts_map,
50 )
51 .ok()
52 }
55impl DiffProvider for Git {
56 fn get_diff_base(&self, file: &Path) -> Option<Vec<u8>> {
57 debug_assert!(!file.exists() || file.is_file());
58 debug_assert!(file.is_absolute());
60 // TODO cache repository lookup
61 let repo = Git::open_repo(file.parent()?, None)?.to_thread_local();
62 let head = repo.head_commit().ok()?;
63 let file_oid = find_file_in_commit(&repo, &head, file)?;
65 let file_object = repo.find_object(file_oid).ok()?;
66 let mut data = file_object.detach().data;
67 // convert LF to CRLF if configured to avoid showing every line as changed
68 if repo
69 .config_snapshot()
70 .boolean("core.autocrlf")
71 .unwrap_or(false)
72 {
73 let mut normalized_file = Vec::with_capacity(data.len());
74 let mut at_cr = false;
75 for &byte in &data {
76 if byte == b'\n' {
77 // if this is a LF instead of a CRLF (last byte was not a CR)
78 // insert a new CR to generate a CRLF
79 if !at_cr {
80 normalized_file.push(b'\r');
81 }
82 }
83 at_cr = byte == b'\r';
84 normalized_file.push(byte)
85 }
86 data = normalized_file
87 }
88 Some(data)
89 }
92/// Finds the object that contains the contents of a file at a specific commit.
93fn find_file_in_commit(repo: &Repository, commit: &Commit, file: &Path) -> Option<ObjectId> {
94 let repo_dir = repo.work_dir()?;
95 let rel_path = file.strip_prefix(repo_dir).ok()?;
96 let tree = commit.tree().ok()?;
97 let tree_entry = tree.lookup_entry_by_path(rel_path).ok()??;
98 match tree_entry.mode() {
99 // not a file, everything is new, do not show diff
100 EntryMode::Tree | EntryMode::Commit | EntryMode::Link => None,
101 // found a file
102 EntryMode::Blob | EntryMode::BlobExecutable => Some(tree_entry.object_id()),
103 }