Skip to main content

gengo/file_source/
git.rs

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    /// Constructs a [`State`] for the repository and rev.
38    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            // NOTE If we cannot get overrides, simply don't return them.
142            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        // NOTE Unspecified attributes are None, so `state.is_set()` is
166        //      implicitly `!state.is_unset()`.
167        // TODO This is really repetitive. Refactor to iteration?
168        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}