1#[cfg(feature = "read")]
4use crate::http_backend;
5use crate::{Request, Response, route::Version};
6use git_zero_core::{Error, Repository, Result, io::Write, repository::uses_only_head};
7#[cfg(feature = "read")]
8use git_zero_core::{ObjectId, SelectionOptions, ValidatedRepository, pack};
9
10pub(crate) mod advertisement;
11pub(crate) mod extensions;
12pub(crate) mod negotiation;
13pub(crate) mod policy;
14#[cfg(feature = "read")]
15mod storage;
16
17#[cfg(feature = "read")]
18pub use negotiation::{Acknowledgments, ShallowUpdates};
19pub use negotiation::{PackOutput, write_upload_error};
20pub(crate) use negotiation::{UploadRequest, capabilities, id};
21pub use policy::{WantOptions, WantPolicy};
22#[cfg(feature = "read")]
23pub use storage::{RevisionBuffers, RevisionStorage, SelectionBuffers, SelectionStorage};
24
25#[cfg(feature = "read")]
54pub fn serve_with_selection<R: Repository + ?Sized>(
55 repository: &ValidatedRepository<'_, '_, R>,
56 request: Request<'_>,
57 storage: &mut SelectionStorage<'_>,
58 output: &mut impl Write,
59) -> Result<Response> {
60 serve_with_selection_policy(repository, request, storage, WantOptions::STRICT, output)
61}
62
63#[cfg(feature = "read")]
67pub fn serve_with_selection_policy<R: Repository + ?Sized>(
68 repository: &ValidatedRepository<'_, '_, R>,
69 request: Request<'_>,
70 storage: &mut SelectionStorage<'_>,
71 options: WantOptions,
72 output: &mut impl Write,
73) -> Result<Response> {
74 serve_with_selected_pack_policy(
75 repository,
76 request,
77 storage,
78 options,
79 output,
80 |indices, read, packed| {
81 pack::write_selected_with_scratch(repository, indices, read, packed).map(|_| ())
82 },
83 )
84}
85
86#[cfg(feature = "read")]
100pub fn serve_with_selected_pack<R: Repository + ?Sized, W: Write>(
101 repository: &ValidatedRepository<'_, '_, R>,
102 request: Request<'_>,
103 storage: &mut SelectionStorage<'_>,
104 output: &mut W,
105 write_selected: impl FnOnce(&[usize], &mut [u8], &mut PackOutput<'_, W>) -> Result<()>,
106) -> Result<Response> {
107 serve_with_selected_pack_policy(
108 repository,
109 request,
110 storage,
111 WantOptions::STRICT,
112 output,
113 write_selected,
114 )
115}
116
117#[cfg(feature = "read")]
122pub fn serve_with_selected_pack_policy<R: Repository + ?Sized, W: Write>(
123 repository: &ValidatedRepository<'_, '_, R>,
124 request: Request<'_>,
125 storage: &mut SelectionStorage<'_>,
126 options: WantOptions,
127 output: &mut W,
128 write_selected: impl FnOnce(&[usize], &mut [u8], &mut PackOutput<'_, W>) -> Result<()>,
129) -> Result<Response> {
130 serve_selected_request(
131 repository,
132 request,
133 storage.reborrow(),
134 options,
135 None,
136 output,
137 write_selected,
138 )
139}
140
141#[cfg(feature = "read")]
146pub fn serve_with_revisions<R: Repository + ?Sized>(
147 repository: &ValidatedRepository<'_, '_, R>,
148 request: Request<'_>,
149 storage: &mut RevisionStorage<'_>,
150 output: &mut impl Write,
151) -> Result<Response> {
152 let storage = storage.reborrow();
153 serve_selected_request(
154 repository,
155 request,
156 storage.selection,
157 WantOptions::STRICT,
158 Some((storage.state, storage.limits)),
159 output,
160 |indices, read, packed| {
161 pack::write_selected_with_scratch(repository, indices, read, packed).map(|_| ())
162 },
163 )
164}
165
166#[cfg(feature = "read")]
167fn serve_selected_request<R: Repository + ?Sized, W: Write>(
168 repository: &ValidatedRepository<'_, '_, R>,
169 request: Request<'_>,
170 storage: SelectionStorage<'_>,
171 options: WantOptions,
172 revision: Option<(&mut [u8], git_zero_core::RevisionLimits)>,
173 output: &mut W,
174 write_selected: impl FnOnce(&[usize], &mut [u8], &mut PackOutput<'_, W>) -> Result<()>,
175) -> Result<Response> {
176 let version = match prepare_route(repository, request, options, revision.is_some(), output)? {
177 http_backend::Plan::Buffered(response) => return Ok(response),
178 http_backend::Plan::Loose(index) => {
179 return http_backend::write_loose(repository, index, storage.read, output);
180 }
181 http_backend::Plan::Upload(version) => version,
182 };
183 let selected = prepare_fetch(
184 repository, request, version, storage, options, revision, output,
185 )?;
186 if let Some(indices) = selected.indices {
187 let mut packed = selected.upload.output(output);
188 write_selected(indices, selected.read, &mut packed)?;
189 packed.finish()?;
190 }
191 Ok(response())
192}
193
194#[cfg(feature = "read")]
195pub(crate) fn prepare_route(
196 repository: &impl Repository,
197 request: Request<'_>,
198 options: WantOptions,
199 revisions: bool,
200 output: &mut impl Write,
201) -> Result<http_backend::Plan> {
202 http_backend::prepare_with_refs(repository, request, output, |route, output| {
203 if revisions {
204 advertisement::serve_revision_info_refs(repository, route, output)
205 } else {
206 policy::info_refs(repository, route, output, options)
207 }
208 })
209}
210
211#[cfg(feature = "read")]
212pub(crate) struct SelectedFetch<'storage, 'body> {
213 pub(crate) upload: UploadRequest<'body>,
214 pub(crate) indices: Option<&'storage [usize]>,
215 read: &'storage mut [u8],
216}
217
218#[cfg(feature = "read")]
219fn select_fetch<'indices, R: Repository + ?Sized>(
220 repository: &ValidatedRepository<'_, '_, R>,
221 upload: &UploadRequest<'_>,
222 wants: &[ObjectId],
223 work: &mut [Option<git_zero_core::ValidationMetadata>],
224 indices: &'indices mut [usize],
225 scratch: (
226 &mut [u8],
227 &mut [u8],
228 Option<&mut git_zero_core::RevisionState<'_>>,
229 ),
230 options: SelectionOptions,
231) -> Result<&'indices [usize]> {
232 if upload.extensions.revision() {
233 repository.select_revision_fetch(
234 wants,
235 (
236 upload.common(repository),
237 upload.shallow(),
238 upload.exclusion_ids(repository),
239 ),
240 work,
241 indices,
242 (scratch.0, scratch.1, scratch.2.ok_or(Error::Unsupported)?),
243 (
244 options,
245 upload.extensions.options,
246 git_zero_core::RevisionOptions {
247 since: upload.extensions.since,
248 },
249 ),
250 )
251 } else {
252 repository.select_bounded_fetch(
253 wants,
254 (upload.common(repository), upload.shallow()),
255 work,
256 indices,
257 (scratch.0, scratch.1),
258 (options, upload.extensions.options),
259 )
260 }
261}
262
263#[cfg(feature = "read")]
264pub(crate) fn prepare_fetch<'storage, 'body, R: Repository + ?Sized>(
265 repository: &ValidatedRepository<'_, '_, R>,
266 request: Request<'body>,
267 version: Version,
268 storage: SelectionStorage<'storage>,
269 options: WantOptions,
270 revision: Option<(&mut [u8], git_zero_core::RevisionLimits)>,
271 output: &mut impl Write,
272) -> Result<SelectedFetch<'storage, 'body>> {
273 let (upload, count) = UploadRequest::parse_selected(
274 repository,
275 request.body,
276 version,
277 storage.wants,
278 options,
279 revision.is_some(),
280 )?;
281 let mut revision =
282 revision.map(|(state, limits)| git_zero_core::RevisionState::new(state, limits));
283 if upload.extensions.exclusions > revision.as_ref().map_or(0, |state| state.max_exclusions()) {
284 return Err(Error::LimitExceeded);
285 }
286 let boundary_probe = version != Version::V2 && !upload.done && upload.extensions.history();
287 let select_boundaries = boundary_probe
288 && (upload.extensions.revision()
289 || !upload.extensions.options.deepen_relative
290 || upload.shallow().next().is_some());
291 let indices = if upload.done || select_boundaries {
292 let options = SelectionOptions {
293 graph_read_limit: storage.graph_read_limit,
294 include_tags: upload.include_tags(),
295 };
296 Some(select_fetch(
297 repository,
298 &upload,
299 &storage.wants[..count],
300 storage.objects,
301 storage.indices,
302 (storage.read, storage.parse, revision.as_mut()),
303 options,
304 )?)
305 } else {
306 upload.validate_shallow(repository)?;
307 if upload.extensions.revision() {
308 for id in upload.exclusion_ids(repository) {
309 id?;
310 }
311 }
312 None
313 };
314 if indices.is_some() {
315 if upload.extensions.revision() {
316 upload.revision_info(
317 repository.object_format(),
318 revision
319 .as_ref()
320 .ok_or(Error::Unsupported)?
321 .changes(storage.objects),
322 output,
323 )?;
324 } else {
325 upload.shallow_info(
326 repository.object_format(),
327 &storage.objects[..repository.object_count()],
328 output,
329 )?;
330 }
331 } else if boundary_probe {
332 output.write_all(b"0000")?;
333 }
334 upload.acknowledge(repository, output)?;
335 Ok(SelectedFetch {
336 indices: indices.filter(|_| upload.done),
337 upload,
338 read: storage.read,
339 })
340}
341
342pub(crate) fn response() -> Response {
343 Response {
344 status: 200,
345 content_type: "application/x-git-upload-pack-result",
346 }
347}
348
349#[cfg(feature = "read")]
350pub(super) fn advertised(repository: &impl Repository, id: ObjectId) -> bool {
351 repository.head_state().id() == Some(id)
352 || (0..repository.reference_count()).any(|index| {
353 repository.reference_id(index) == Some(id)
354 || repository.reference_peeled(index) == Some(id)
355 })
356}
357
358pub(crate) fn prepare_complete<'a>(
360 repository: &impl Repository,
361 body: &'a [u8],
362 version: Version,
363) -> Result<UploadRequest<'a>> {
364 if !uses_only_head(repository) {
365 return Err(Error::Unsupported);
366 }
367 let head = repository.head_state().id().ok_or(Error::InvalidRequest)?;
368 let upload = UploadRequest::parse(body, repository.object_format(), version, |id| {
369 (id == head).then_some(()).ok_or(Error::InvalidRequest)
370 })?;
371 upload.require_complete()?;
372 Ok(upload)
373}
374
375pub(crate) fn write_upload<W: Write>(
376 repository: &impl Repository,
377 request: Request<'_>,
378 version: Version,
379 scratch: &mut [u8],
380 output: &mut W,
381 write_pack: impl FnOnce(&mut [u8], &mut PackOutput<'_, W>) -> Result<()>,
382) -> Result<()> {
383 let upload = prepare_complete(repository, request.body, version)?;
384 upload.acknowledge(repository, output)?;
385 if upload.done {
386 let mut packed = upload.output(output);
387 write_pack(scratch, &mut packed)?;
388 packed.finish()?;
389 }
390 Ok(())
391}