git_zero_core/validation/
revision.rs1use super::*;
3
4mod date;
5mod state;
6use date::commit_date;
7use state::{BORDER, CANDIDATE, CLIENT_SHALLOW, DONE, EXCLUDED, PENDING, UNSHALLOW};
8pub use state::{RevisionLimits, RevisionOptions, RevisionState, ShallowChange};
9
10impl<R: Repository + ?Sized> ValidatedRepository<'_, '_, R> {
11 pub fn select_revision_fetch<'selected>(
23 &self,
24 roots: &[ObjectId],
25 history: (
26 impl IntoIterator<Item = Result<ObjectId>>,
27 impl IntoIterator<Item = Result<ObjectId>>,
28 impl IntoIterator<Item = Result<ObjectId>>,
29 ),
30 work: &mut [Option<ValidationMetadata>],
31 selected: &'selected mut [usize],
32 scratch: (&mut [u8], &mut [u8], &mut RevisionState<'_>),
33 options: (SelectionOptions, FetchOptions, RevisionOptions),
34 ) -> Result<&'selected [usize]> {
35 let (read, parse, revision) = scratch;
36 revision.count = 0;
37 let count = self.objects.len();
38 let (selection, fetch, options) = options;
39 if roots.is_empty() || fetch.depth.is_some() || options.since == Some(u64::MAX) {
40 return Err(Error::InvalidRequest);
41 }
42 if matches!(fetch.filter, BlobFilter::Type(_) | BlobFilter::TreeDepth(_)) {
43 return Err(Error::Unsupported);
44 }
45 if work.len() < count || selected.len() < count || revision.states.len() < count {
46 return Err(Error::BufferTooSmall);
47 }
48
49 let (common, shallow, excluded) = history;
50 let mut payload = Selection {
51 repository: self,
52 work: &mut work[..count],
53 scratch: (read, parse),
54 remaining: selection.graph_read_limit,
55 filter: BlobFilter::All,
56 };
57 payload.prepare(shallow)?;
58 for (state, object) in revision.states[..count].iter_mut().zip(payload.work.iter()) {
59 *state = u8::from(object.expect("validated metadata").client_shallow) * CLIENT_SHALLOW;
60 }
61 let mut walk = Walker {
62 repository: self,
63 objects: payload.work,
64 states: &mut revision.states[..count],
65 read: payload.scratch.0,
66 parse: payload.scratch.1,
67 bytes: payload.remaining,
68 };
69 walk.exclude(excluded, revision.limits.max_exclusions)?;
70 walk.candidates(roots, options.since)?;
71 walk.exclude_common(common)?;
72 walk.wanted_payload(roots)?;
73 filter_objects(walk.objects, fetch.filter);
74 if selection.include_tags {
75 walk.include_tags()?;
76 walk.payload(&[])?;
77 filter_objects(walk.objects, fetch.filter);
78 }
79 let length = selected_indices(walk.objects, selected);
80 revision.count = count;
81 Ok(&selected[..length])
82 }
83}
84
85#[derive(Clone, Copy)]
86enum Parents {
87 Pending,
88 Boundary,
89 Seed,
90}
91
92fn mark_parent(
93 action: Parents,
94 index: usize,
95 parent: usize,
96 objects: &mut [Option<ValidationMetadata>],
97 states: &mut [u8],
98) {
99 match action {
100 Parents::Pending => states[parent] |= PENDING,
101 Parents::Boundary if states[parent] & CANDIDATE == 0 => states[index] |= BORDER,
102 Parents::Seed => {
103 let object = metadata_mut(objects, parent);
104 if !object.excluded {
105 object.reached = true;
106 object.depth = 1;
107 }
108 }
109 _ => {}
110 }
111}
112
113struct Walker<'a, 'b, 'index, 'repo, R: Repository + ?Sized> {
114 repository: &'a ValidatedRepository<'index, 'repo, R>,
115 objects: &'b mut [Option<ValidationMetadata>],
116 states: &'b mut [u8],
117 read: &'b mut [u8],
118 parse: &'b mut [u8],
119 bytes: u64,
120}
121
122impl<R: Repository + ?Sized> Walker<'_, '_, '_, '_, R> {
123 fn exclude(
124 &mut self,
125 excluded: impl IntoIterator<Item = Result<ObjectId>>,
126 limit: usize,
127 ) -> Result<()> {
128 for (position, id) in excluded.into_iter().enumerate() {
129 if position >= limit {
130 return Err(Error::LimitExceeded);
131 }
132 let index = self.find(id?)?;
133 self.states[index] |= PENDING;
134 }
135 self.ancestry(None, EXCLUDED)
136 }
137
138 fn candidates(&mut self, roots: &[ObjectId], since: Option<u64>) -> Result<()> {
139 for &id in roots {
140 let index = self.find(id)?;
141 self.queue_commit(index)?;
142 }
143 self.ancestry(since.filter(|&value| value != 0), CANDIDATE)?;
144
145 if !self.states.iter().any(|state| state & CANDIDATE != 0) {
146 return Err(Error::InvalidRequest);
147 }
148 self.visit_parents(CANDIDATE, Parents::Boundary)?;
151 state::mark_unshallow(self.states);
152 Ok(())
153 }
154
155 fn exclude_common(&mut self, common: impl IntoIterator<Item = Result<ObjectId>>) -> Result<()> {
156 for id in common {
157 let index = self.find(id?)?;
158 if self.states[index] & CANDIDATE != 0 {
159 metadata_mut(self.objects, index).reached = true;
160 }
161 }
162 self.payload(&[])?;
163 for index in 0..self.objects.len() {
164 let object = metadata_mut(self.objects, index);
165 object.excluded = object.reached || self.states[index] & UNSHALLOW != 0;
166 object.reset_walk();
167 }
168 Ok(())
169 }
170
171 fn wanted_payload(&mut self, roots: &[ObjectId]) -> Result<()> {
172 self.visit_parents(UNSHALLOW, Parents::Seed)?;
173 self.payload(roots)
174 }
175
176 fn include_tags(&mut self) -> Result<()> {
177 Selection {
178 repository: self.repository,
179 work: self.objects,
180 scratch: (self.read, self.parse),
181 remaining: self.bytes,
182 filter: BlobFilter::All,
183 }
184 .include_matching_tags()
185 }
186
187 fn find(&self, id: ObjectId) -> Result<usize> {
188 find(self.objects, self.repository.by_id, id).ok_or(Error::ObjectNotFound)
189 }
190 fn peel(&mut self, mut index: usize) -> Result<usize> {
191 for _ in 0..self.objects.len() {
192 if metadata(self.objects, index).info.kind != ObjectKind::Tag {
193 return Ok(index);
194 }
195 let mut body =
196 Body::budgeted(self.repository, index, self.read, Some(&mut self.bytes))?;
197 let line = body.line(self.parse)?;
198 let id = link(line, b"object ", self.repository.object_format())?;
199 index = self.find(id)?;
200 }
201 Err(Error::InvalidObject)
202 }
203 fn queue_commit(&mut self, index: usize) -> Result<()> {
205 let index = self.peel(index)?;
206 if metadata(self.objects, index).info.kind == ObjectKind::Commit {
207 self.states[index] |= PENDING;
208 }
209 Ok(())
210 }
211 fn ancestry(&mut self, since: Option<u64>, flag: u8) -> Result<()> {
212 while let Some(index) = self
213 .states
214 .iter()
215 .position(|state| state & (PENDING | DONE) == PENDING)
216 {
217 self.states[index] |= DONE;
218 self.visit_ancestor(index, since, flag)?;
219 }
220
221 for state in self.states.iter_mut() {
222 *state &= !(PENDING | DONE);
223 }
224 Ok(())
225 }
226 fn visit_ancestor(&mut self, index: usize, since: Option<u64>, flag: u8) -> Result<()> {
227 if metadata(self.objects, index).info.kind != ObjectKind::Commit {
228 return self.queue_commit(index);
229 }
230 if flag == CANDIDATE && self.states[index] & EXCLUDED != 0 {
231 return Ok(());
232 }
233 if let Some(cut) = since {
234 if self.date(index)? < cut {
235 return Ok(());
236 }
237 }
238 self.states[index] |= flag;
239 self.commit(index, Parents::Pending)
240 }
241 fn date(&mut self, index: usize) -> Result<u64> {
242 let mut body = Body::budgeted(self.repository, index, self.read, Some(&mut self.bytes))?;
243 commit_tree(&mut body, self.parse)?;
244 let committer = commit_parents(&mut body, self.parse, |_| Ok(()))?;
245 Ok(commit_date(committer))
246 }
247
248 fn visit_parents(&mut self, flag: u8, action: Parents) -> Result<()> {
251 for index in 0..self.states.len() {
252 if self.states[index] & flag != 0 {
253 self.commit(index, action)?;
254 }
255 }
256 Ok(())
257 }
258
259 fn commit(&mut self, index: usize, action: Parents) -> Result<()> {
260 let mut body = Body::budgeted(self.repository, index, self.read, Some(&mut self.bytes))?;
261 commit_tree(&mut body, self.parse)?;
262 commit_parents(&mut body, self.parse, |id| {
263 let parent =
264 find(self.objects, self.repository.by_id, id).ok_or(Error::ObjectNotFound)?;
265 mark_parent(action, index, parent, self.objects, self.states);
266 Ok(())
267 })?;
268 Ok(())
269 }
270 fn payload(&mut self, roots: &[ObjectId]) -> Result<()> {
271 self.payload_boundaries(false);
274 let mut payload = Selection {
275 repository: self.repository,
276 work: self.objects,
277 scratch: (self.read, self.parse),
278 remaining: self.bytes,
279 filter: BlobFilter::All,
280 };
281 let result = payload.walk_wants(
282 roots,
283 History {
284 client: true,
285 ..History::default()
286 },
287 );
288 self.bytes = payload.remaining;
289 result?;
290 self.payload_boundaries(true);
291 Ok(())
292 }
293
294 fn payload_boundaries(&mut self, restore: bool) {
295 for (object, &state) in self.objects.iter_mut().zip(self.states.iter()) {
296 object.as_mut().expect("validated metadata").client_shallow =
297 state & CLIENT_SHALLOW != 0 || (!restore && state & BORDER != 0);
298 }
299 }
300}