1use super::{FileSource, Overrides};
2use crate::{Error, ErrorKind};
3use gix::{
4 Repository, ThreadSafeRepository,
5 attrs::StateRef,
6 attrs::search::Outcome as AttrOutcome,
7 bstr::ByteSlice,
8 discover::Error as DiscoverError,
9 index::{self, entry::Mode as EntryMode},
10 worktree::{Stack as WTStack, stack::state::attributes::Source as AttrSource},
11};
12use std::borrow::Cow;
13use std::path::Path;
14
15use std::slice;
16
17struct Builder {
18 repository: ThreadSafeRepository,
19 rev: String,
20}
21
22impl Builder {
23 fn new(path: impl AsRef<Path>, rev: &str) -> crate::Result<Self> {
24 let repository = match gix::discover(path) {
25 Ok(r) => r,
26 Err(DiscoverError::Discover(err)) => {
27 return Err(Box::new(Error::with_source(ErrorKind::NoRepository, err)));
28 }
29 Err(err) => return Err(err.into()),
30 };
31
32 let repository = repository.into_sync();
33 let rev = rev.to_string();
34 Ok(Self { repository, rev })
35 }
36
37 fn state(&self) -> crate::Result<(State, index::State)> {
39 let repo = self.repository.to_thread_local();
40 let tree_id = repo
41 .rev_parse_single(self.rev.as_str())?
42 .object()?
43 .peel_to_tree()?
44 .id;
45 let index = repo.index_from_tree(&tree_id)?;
46 let attr_stack = repo.attributes_only(&index, AttrSource::IdMapping)?;
47 let attr_matches = attr_stack.selected_attribute_matches(Git::OVERRIDE_ATTRS);
48 let (index_state, _) = index.into_parts();
49 let attr_stack = attr_stack.detach();
50 let state = State {
51 attr_stack,
52 attr_matches,
53 };
54 Ok((state, index_state))
55 }
56
57 fn build(self) -> crate::Result<Git> {
58 let (state, index_state) = self.state()?;
59 let git = Git {
60 repository: self.repository,
61 state,
62 index_state,
63 };
64 Ok(git)
65 }
66}
67
68pub struct Git {
69 repository: ThreadSafeRepository,
70 state: State,
71 index_state: index::State,
72}
73
74impl Git {
75 const OVERRIDE_ATTRS: [&'static str; 5] = [
76 "gengo-language",
77 "gengo-documentation",
78 "gengo-generated",
79 "gengo-vendored",
80 "gengo-detectable",
81 ];
82 const LANGUAGE_OVERRIDE: usize = 0;
83 const DOCUMENTATION_OVERRIDE: usize = 1;
84 const GENERATED_OVERRIDE: usize = 2;
85 const VENDORED_OVERRIDE: usize = 3;
86 const DETECTABLE_OVERRIDE: usize = 4;
87 pub fn new(path: impl AsRef<Path>, rev: &str) -> crate::Result<Self> {
88 Builder::new(path, rev)?.build()
89 }
90}
91
92impl<'repo> FileSource<'repo> for Git {
93 type Entry = &'repo index::Entry;
94 type Filepath = Cow<'repo, Path>;
95 type Contents = Vec<u8>;
96 type State = (State, Repository);
97 type Iter = Iter<'repo>;
98
99 fn entries(&'repo self) -> crate::Result<Self::Iter> {
100 let entries = self.index_state.entries().iter();
101 Ok(Iter { entries })
102 }
103
104 fn filepath(
105 &'repo self,
106 entry: &Self::Entry,
107 _state: &mut Self::State,
108 ) -> crate::Result<Self::Filepath> {
109 let path_storage = self.index_state.path_backing();
110 let path = entry.path_in(path_storage);
111 let path = gix::path::try_from_bstr(path)?;
112 Ok(path)
113 }
114
115 fn contents(
116 &'repo self,
117 entry: &Self::Entry,
118 (_, repository): &mut Self::State,
119 ) -> crate::Result<Self::Contents> {
120 let blob = repository.find_object(entry.id)?;
121 let blob = blob.detach();
122 let contents = blob.data;
123 Ok(contents)
124 }
125
126 fn state(&'repo self) -> crate::Result<Self::State> {
127 Ok((self.state.clone(), self.repository.to_thread_local()))
128 }
129
130 fn overrides(
131 &self,
132 path: impl AsRef<Path>,
133 (state, repository): &mut Self::State,
134 ) -> Overrides {
135 let path = path.as_ref();
136 let Ok(platform) = state.attr_stack.at_path(
137 path,
138 Some(EntryMode::FILE | EntryMode::FILE_EXECUTABLE),
139 &repository.objects,
140 ) else {
141 return Default::default();
143 };
144 platform.matching_attributes(&mut state.attr_matches);
145
146 let attrs = {
147 let mut attrs = [None, None, None, None, None];
148 state
149 .attr_matches
150 .iter_selected()
151 .zip(attrs.iter_mut())
152 .for_each(|(info, slot)| {
153 *slot = (info.assignment.state != StateRef::Unspecified).then_some(info);
154 });
155 attrs
156 };
157
158 let language =
159 attrs[Self::LANGUAGE_OVERRIDE]
160 .as_ref()
161 .and_then(|info| match info.assignment.state {
162 StateRef::Value(v) => v.as_bstr().to_str().ok().and_then(|s| s.parse().ok()),
163 _ => None,
164 });
165 let is_documentation = attrs[Self::DOCUMENTATION_OVERRIDE]
169 .as_ref()
170 .map(|info| info.assignment.state.is_set());
171 let is_generated = attrs[Self::GENERATED_OVERRIDE]
172 .as_ref()
173 .map(|info| info.assignment.state.is_set());
174 let is_vendored = attrs[Self::VENDORED_OVERRIDE]
175 .as_ref()
176 .map(|info| info.assignment.state.is_set());
177 let is_detectable = attrs[Self::DETECTABLE_OVERRIDE]
178 .as_ref()
179 .map(|info| info.assignment.state.is_set());
180
181 Overrides {
182 language,
183 is_documentation,
184 is_generated,
185 is_vendored,
186 is_detectable,
187 }
188 }
189}
190
191pub struct Iter<'repo> {
192 entries: slice::Iter<'repo, index::Entry>,
193}
194
195impl<'repo> Iterator for Iter<'repo> {
196 type Item = &'repo index::Entry;
197
198 fn next(&mut self) -> Option<Self::Item> {
199 let entry = loop {
200 let entry = self.entries.next()?;
201 if matches!(entry.mode, EntryMode::FILE | EntryMode::FILE_EXECUTABLE) {
202 break entry;
203 }
204 };
205 Some(entry)
206 }
207}
208
209#[derive(Clone)]
210pub struct State {
211 attr_stack: WTStack,
212 attr_matches: AttrOutcome,
213}