1use crate::{
4 Request, Response, route,
5 upload_pack::{self, PackOutput, advertisement},
6 v2,
7};
8use git_zero_core::{
9 Error, ObjectId, Repository, Result, io::Write, pack, reader::DEFAULT_READ_BUFFER_SIZE,
10};
11
12pub const HEAD: &[u8] = b"ref: refs/heads/main\n";
14
15pub fn serve(
19 repository: &impl Repository,
20 request: Request<'_>,
21 output: &mut impl Write,
22) -> Result<Response> {
23 serve_with_scratch(
24 repository,
25 request,
26 &mut [0; DEFAULT_READ_BUFFER_SIZE],
27 output,
28 )
29}
30
31pub fn serve_with_scratch(
35 repository: &impl Repository,
36 request: Request<'_>,
37 scratch: &mut [u8],
38 output: &mut impl Write,
39) -> Result<Response> {
40 serve_with_pack(repository, request, scratch, output, |scratch, output| {
41 pack::write_with_scratch(repository, scratch, output).map(|_| ())
42 })
43}
44
45pub fn serve_with_pack<W: Write>(
55 repository: &impl Repository,
56 request: Request<'_>,
57 scratch: &mut [u8],
58 output: &mut W,
59 write_pack: impl FnOnce(&mut [u8], &mut PackOutput<'_, W>) -> Result<()>,
60) -> Result<Response> {
61 match prepare(repository, request, output, false)? {
62 Plan::Buffered(response) => Ok(response),
63 Plan::Loose(index) => write_loose(repository, index, scratch, output),
64 Plan::Upload(version) => {
65 upload_pack::write_upload(repository, request, version, scratch, output, write_pack)?;
66 Ok(upload_pack::response())
67 }
68 }
69}
70
71pub(crate) fn write_loose(
72 repository: &impl Repository,
73 index: usize,
74 scratch: &mut [u8],
75 output: &mut impl Write,
76) -> Result<Response> {
77 pack::write_loose_with_scratch(repository, index, scratch, output)?;
78 Ok(Response {
79 status: 200,
80 content_type: "application/x-git-loose-object",
81 })
82}
83
84pub(crate) enum Plan {
86 Buffered(Response),
87 Loose(usize),
88 Upload(route::Version),
89}
90
91pub(crate) fn prepare(
92 repository: &impl Repository,
93 request: Request<'_>,
94 output: &mut impl Write,
95 selection: bool,
96) -> Result<Plan> {
97 prepare_with_refs(repository, request, output, |route, output| {
98 advertisement::serve_info_refs(repository, route, output, selection)
99 })
100}
101
102pub(crate) fn prepare_with_refs<W: Write>(
104 repository: &impl Repository,
105 request: Request<'_>,
106 output: &mut W,
107 advertise: impl FnOnce(&route::Route<'_>, &mut W) -> Result<Response>,
108) -> Result<Plan> {
109 let route = route::validate(request)?;
110 if let Some(response) = route.rejection {
111 return Ok(Plan::Buffered(response));
112 }
113 let status = match (request.method, route.path) {
114 ("GET", "HEAD") => {
115 write_head(repository, output)?;
116 200
117 }
118 ("GET", "info/refs") => {
119 return advertise(&route, output).map(Plan::Buffered);
120 }
121 ("GET", "objects/info/packs") => 200,
122 ("GET", path) if path.starts_with("refs/") => serve_reference(repository, path, output)?,
123 ("GET", path) if path.starts_with("objects/") => {
124 if let Some(index) = loose_index(repository, &path.as_bytes()[8..]) {
125 return Ok(Plan::Loose(index));
126 }
127 404
128 }
129 ("POST", "git-upload-pack") => {
130 if route.version == route::Version::V2
131 && v2::serve_refs(repository, request.body, output)?
132 {
133 return Ok(Plan::Buffered(upload_pack::response()));
134 }
135 return Ok(Plan::Upload(route.version));
136 }
137 ("GET" | "POST", _) => 404,
138 _ => 405,
139 };
140 Ok(Plan::Buffered(Response {
141 status,
142 content_type: "text/plain; charset=utf-8",
143 }))
144}
145
146fn write_head(repository: &impl Repository, output: &mut impl Write) -> Result<()> {
147 if let Some(target) = repository.head_state().target() {
148 output.write_all(b"ref: ")?;
149 output.write_all(target.as_bytes())?;
150 } else {
151 output.write_all(&repository.head().to_hex())?;
152 }
153 output.write_all(b"\n")
154}
155
156fn serve_reference(
157 repository: &impl Repository,
158 path: &str,
159 output: &mut impl Write,
160) -> Result<u16> {
161 let index = (0..repository.reference_count()).find(|&index| {
162 repository
163 .reference_name(index)
164 .is_some_and(|name| route::decoded(path).eq(name.bytes().map(Ok)))
165 });
166 let Some(index) = index else { return Ok(404) };
167 if let Some(target) = repository.reference_symbolic_target(index) {
168 output.write_all(b"ref: ")?;
169 output.write_all(target.as_bytes())?;
170 } else {
171 output.write_all(
172 &repository
173 .reference_id(index)
174 .ok_or(Error::InvalidObject)?
175 .to_hex(),
176 )?;
177 }
178 output.write_all(b"\n")?;
179 Ok(200)
180}
181
182fn loose_index(repository: &impl Repository, path: &[u8]) -> Option<usize> {
183 let width = repository.object_format().hex_length();
184 if path.len() != width + 1 || path[2] != b'/' {
185 return None;
186 }
187 let mut storage = [0; 64];
188 let hex = &mut storage[..width];
189 hex[..2].copy_from_slice(&path[..2]);
190 hex[2..].copy_from_slice(&path[3..]);
191 ObjectId::from_hex(hex)
192 .ok()
193 .and_then(|id| repository.find_index(id))
194}