gitscope
← rgit / 0.1.5 / src/methods/repo/mod.rs
https://dev.kronoz.win/r/rgit.git
src/methods/repo/mod.rs · Rust
Blame Raw
  1mod about;
  2mod commit;
  3mod diff;
  4mod log;
  5mod refs;
  6mod smart_git;
  7mod snapshot;
  8mod summary;
  9mod tag;
 10mod tree;
 11
 12use std::{
 13    collections::BTreeMap,
 14    ops::Deref,
 15    path::{Path, PathBuf},
 16    sync::{Arc, LazyLock},
 17};
 18
 19use axum::{
 20    body::Body,
 21    handler::Handler,
 22    http::{Request, StatusCode},
 23    response::{IntoResponse, Response},
 24};
 25use path_clean::PathClean;
 26
 27use self::{
 28    about::handle as handle_about,
 29    commit::handle as handle_commit,
 30    diff::{handle as handle_diff, handle_plain as handle_patch},
 31    log::handle as handle_log,
 32    refs::handle as handle_refs,
 33    smart_git::handle as handle_smart_git,
 34    snapshot::handle as handle_snapshot,
 35    summary::handle as handle_summary,
 36    tag::handle as handle_tag,
 37    tree::handle as handle_tree,
 38};
 39use crate::database::schema::tag::YokedString;
 40use crate::database::schema::{commit::YokedCommit, tag::YokedTag};
 41
 42pub const DEFAULT_BRANCHES: [&str; 2] = ["refs/heads/master", "refs/heads/main"];
 43
 44// this is some wicked, wicked abuse of axum right here...
 45#[allow(clippy::trait_duplication_in_bounds)] // clippy seems a bit.. lost
 46pub async fn service(mut request: Request<Body>) -> Response {
 47    let scan_path = request
 48        .extensions()
 49        .get::<Arc<PathBuf>>()
 50        .expect("scan_path missing");
 51
 52    let ParsedUri {
 53        uri,
 54        child_path,
 55        action,
 56    } = parse_uri(request.uri().path().trim_matches('/'));
 57
 58    let uri = Path::new(uri).clean();
 59    let path = scan_path.join(&uri);
 60
 61    let db = request
 62        .extensions()
 63        .get::<Arc<rocksdb::DB>>()
 64        .expect("db extension missing");
 65    if path.as_os_str().is_empty()
 66        || !crate::database::schema::repository::Repository::exists(db, &uri).unwrap_or_default()
 67    {
 68        return RepositoryNotFound.into_response();
 69    }
 70
 71    request.extensions_mut().insert(ChildPath(child_path));
 72    request.extensions_mut().insert(Repository(uri));
 73    request.extensions_mut().insert(RepositoryPath(path));
 74
 75    match action {
 76        HandlerAction::About => handle_about.call(request, None::<()>).await,
 77        HandlerAction::SmartGit => handle_smart_git.call(request, None::<()>).await,
 78        HandlerAction::Refs => handle_refs.call(request, None::<()>).await,
 79        HandlerAction::Log => handle_log.call(request, None::<()>).await,
 80        HandlerAction::Tree => handle_tree.call(request, None::<()>).await,
 81        HandlerAction::Commit => handle_commit.call(request, None::<()>).await,
 82        HandlerAction::Diff => handle_diff.call(request, None::<()>).await,
 83        HandlerAction::Patch => handle_patch.call(request, None::<()>).await,
 84        HandlerAction::Tag => handle_tag.call(request, None::<()>).await,
 85        HandlerAction::Snapshot => handle_snapshot.call(request, None::<()>).await,
 86        HandlerAction::Summary => handle_summary.call(request, None::<()>).await,
 87    }
 88}
 89
 90#[derive(Debug, PartialEq, Eq)]
 91struct ParsedUri<'a> {
 92    action: HandlerAction,
 93    uri: &'a str,
 94    child_path: Option<PathBuf>,
 95}
 96
 97fn parse_uri(uri: &str) -> ParsedUri<'_> {
 98    let mut uri_parts = memchr::memchr_iter(b'/', uri.as_bytes());
 99
100    let original_uri = uri;
101    let (action, mut uri) = if let Some(idx) = uri_parts.next_back() {
102        (uri.get(idx + 1..), &uri[..idx])
103    } else {
104        (None, uri)
105    };
106
107    match action {
108        Some("about") => ParsedUri {
109            action: HandlerAction::About,
110            uri,
111            child_path: None,
112        },
113        Some("git-upload-pack") => ParsedUri {
114            action: HandlerAction::SmartGit,
115            uri,
116            child_path: None,
117        },
118        Some("refs") => {
119            if let Some(idx) = uri_parts.next_back() {
120                if uri.get(idx + 1..) == Some("info") {
121                    ParsedUri {
122                        action: HandlerAction::SmartGit,
123                        uri: &uri[..idx],
124                        child_path: None,
125                    }
126                } else {
127                    ParsedUri {
128                        action: HandlerAction::Refs,
129                        uri,
130                        child_path: None,
131                    }
132                }
133            } else {
134                ParsedUri {
135                    action: HandlerAction::Refs,
136                    uri,
137                    child_path: None,
138                }
139            }
140        }
141        Some("log") => ParsedUri {
142            action: HandlerAction::Log,
143            uri,
144            child_path: None,
145        },
146        Some("tree") => ParsedUri {
147            action: HandlerAction::Tree,
148            uri,
149            child_path: None,
150        },
151        Some("commit") => ParsedUri {
152            action: HandlerAction::Commit,
153            uri,
154            child_path: None,
155        },
156        Some("diff") => ParsedUri {
157            action: HandlerAction::Diff,
158            uri,
159            child_path: None,
160        },
161        Some("patch") => ParsedUri {
162            action: HandlerAction::Patch,
163            uri,
164            child_path: None,
165        },
166        Some("tag") => ParsedUri {
167            action: HandlerAction::Tag,
168            uri,
169            child_path: None,
170        },
171        Some("snapshot") => ParsedUri {
172            action: HandlerAction::Snapshot,
173            uri,
174            child_path: None,
175        },
176        Some(_) => {
177            static TREE_FINDER: LazyLock<memchr::memmem::Finder> =
178                LazyLock::new(|| memchr::memmem::Finder::new(b"/tree/"));
179
180            uri = original_uri;
181
182            // match tree children
183            if let Some(idx) = TREE_FINDER.find(uri.as_bytes()) {
184                ParsedUri {
185                    action: HandlerAction::Tree,
186                    uri: &uri[..idx],
187                    // 6 is the length of /tree/
188                    child_path: Some(Path::new(&uri[idx + 6..]).clean()),
189                }
190            } else {
191                ParsedUri {
192                    action: HandlerAction::Summary,
193                    uri,
194                    child_path: None,
195                }
196            }
197        }
198        None => ParsedUri {
199            action: HandlerAction::Summary,
200            uri,
201            child_path: None,
202        },
203    }
204}
205
206#[derive(Copy, Clone, Debug, PartialEq, Eq)]
207enum HandlerAction {
208    About,
209    SmartGit,
210    Refs,
211    Log,
212    Tree,
213    Commit,
214    Diff,
215    Patch,
216    Tag,
217    Snapshot,
218    Summary,
219}
220
221#[derive(Clone)]
222pub struct Repository(pub PathBuf);
223
224impl Deref for Repository {
225    type Target = Path;
226
227    fn deref(&self) -> &Self::Target {
228        &self.0
229    }
230}
231
232#[derive(Clone)]
233pub struct RepositoryPath(pub PathBuf);
234
235#[derive(Clone)]
236pub struct ChildPath(pub Option<PathBuf>);
237
238impl Deref for RepositoryPath {
239    type Target = Path;
240
241    fn deref(&self) -> &Self::Target {
242        &self.0
243    }
244}
245
246pub type Result<T, E = Error> = std::result::Result<T, E>;
247
248pub struct InvalidRequest;
249
250impl IntoResponse for InvalidRequest {
251    fn into_response(self) -> Response {
252        (StatusCode::NOT_FOUND, "Invalid request").into_response()
253    }
254}
255
256pub struct RepositoryNotFound;
257
258impl IntoResponse for RepositoryNotFound {
259    fn into_response(self) -> Response {
260        (StatusCode::NOT_FOUND, "Repository not found").into_response()
261    }
262}
263
264pub struct Error(anyhow::Error);
265
266impl From<Arc<anyhow::Error>> for Error {
267    fn from(e: Arc<anyhow::Error>) -> Self {
268        Self(anyhow::Error::msg(format!("{e:?}")))
269    }
270}
271
272impl From<anyhow::Error> for Error {
273    fn from(e: anyhow::Error) -> Self {
274        Self(e)
275    }
276}
277
278impl IntoResponse for Error {
279    fn into_response(self) -> Response {
280        (StatusCode::INTERNAL_SERVER_ERROR, format!("{:?}", self.0)).into_response()
281    }
282}
283
284pub struct Refs {
285    heads: BTreeMap<String, YokedCommit>,
286    tags: Vec<(YokedString, YokedTag)>,
287}