From f3142420736b58f814f89572e30a7ee8961cf059 Mon Sep 17 00:00:00 2001 From: Jordan Doyle Date: Sat, 09 Jul 2022 00:02:09 +0100 Subject: [PATCH] Cleanup git API --- Cargo.lock | 1 + Cargo.toml | 1 + src/git.rs | 240 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++------------------------ src/main.rs | 3 +-- src/methods/index.rs | 2 +- src/methods/repo.rs | 73 ++++++++++++++++++++++++++++++++++++++----------------------------------- 6 files changed, 179 insertions(+), 141 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c750fb4..fec37a7 100644 --- a/Cargo.lock +++ a/Cargo.lock @@ -1611,6 +1611,7 @@ "humantime", "md5", "moka", + "parking_lot", "path-clean", "serde", "syntect", diff --git a/Cargo.toml b/Cargo.toml index 2577af8..ce79190 100644 --- a/Cargo.toml +++ a/Cargo.toml @@ -18,6 +18,7 @@ md5 = "0.7" moka = { version = "0.9", features = ["future"] } path-clean = "0.1" +parking_lot = "0.12" serde = { version = "1.0", features = ["derive"] } syntect = "5" time = "0.3" diff --git a/src/git.rs b/src/git.rs index 27a6315..6551bf1 100644 --- a/src/git.rs +++ a/src/git.rs @@ -11,22 +11,25 @@ DiffFormat, DiffLineType, DiffOptions, DiffStatsFormat, ObjectType, Oid, Repository, Signature, }; use moka::future::Cache; +use parking_lot::Mutex; use syntect::html::{ClassStyle, ClassedHTMLGenerator}; use syntect::parsing::SyntaxSet; use time::OffsetDateTime; +use tracing::instrument; pub type RepositoryMetadataList = BTreeMap, Vec>; -#[derive(Clone)] pub struct Git { commits: Cache>, readme_cache: Cache>>, refs: Cache>, - repository_metadata: Arc>, + repository_metadata: ArcSwapOption, + syntax_set: SyntaxSet, } -impl Default for Git { - fn default() -> Self { +impl Git { + #[instrument(skip(syntax_set))] + pub fn new(syntax_set: SyntaxSet) -> Self { Self { commits: Cache::builder() .time_to_live(Duration::from_secs(10)) @@ -40,68 +43,100 @@ .time_to_live(Duration::from_secs(10)) .max_capacity(100) .build(), - repository_metadata: Arc::new(ArcSwapOption::default()), + repository_metadata: ArcSwapOption::default(), + syntax_set, } } } impl Git { - pub async fn get_commit<'a>( - &'a self, - repo: PathBuf, - commit: &str, - syntax_set: Arc, - ) -> Arc { - let commit = Oid::from_str(commit).unwrap(); + #[instrument(skip(self))] + pub async fn repo(self: Arc, repo_path: PathBuf) -> Arc { + let repo = tokio::task::spawn_blocking({ + let repo_path = repo_path.clone(); + move || git2::Repository::open(repo_path).unwrap() + }) + .await + .unwrap(); - self.commits - .get_with(commit, async { - tokio::task::spawn_blocking(move || { - let repo = Repository::open_bare(repo).unwrap(); - let commit = repo.find_commit(commit).unwrap(); - let (diff_output, diff_stats) = - fetch_diff_and_stats(&repo, &commit, &syntax_set); + Arc::new(OpenRepository { + git: self, + cache_key: repo_path, + repo: Mutex::new(repo), + }) + } - let mut commit = Commit::from(commit); - commit.diff_stats = diff_stats; - commit.diff = diff_output; + #[instrument(skip(self))] + pub async fn fetch_repository_metadata(&self) -> Arc { + if let Some(metadata) = self.repository_metadata.load().as_ref() { + return Arc::clone(metadata); + } - Arc::new(commit) - }) - .await - .unwrap() - }) - .await + let start = Path::new("../test-git").canonicalize().unwrap(); + + let repos = tokio::task::spawn_blocking(move || { + let mut repos: RepositoryMetadataList = RepositoryMetadataList::new(); + fetch_repository_metadata_impl(&start, &start, &mut repos); + repos + }) + .await + .unwrap(); + + let repos = Arc::new(repos); + self.repository_metadata.store(Some(repos.clone())); + + repos } +} - pub async fn get_tag(&self, repo: PathBuf, tag_name: &str) -> DetailedTag { - let repo = Repository::open_bare(repo).unwrap(); - let tag = repo - .find_reference(&format!("refs/tags/{tag_name}")) - .unwrap() - .peel_to_tag() - .unwrap(); - let tag_target = tag.target().unwrap(); - - let tagged_object = match tag_target.kind() { - Some(ObjectType::Commit) => Some(TaggedObject::Commit(tag_target.id().to_string())), - Some(ObjectType::Tree) => Some(TaggedObject::Tree(tag_target.id().to_string())), - None | Some(_) => None, - }; +pub struct OpenRepository { + git: Arc, + cache_key: PathBuf, + repo: Mutex, +} - DetailedTag { - name: tag_name.to_string(), - tagger: tag.tagger().map(Into::into), - message: tag.message().unwrap().to_string(), - tagged_object, - } +impl OpenRepository { + #[instrument(skip(self))] + pub async fn tag_info(self: Arc, tag_name: &str) -> DetailedTag { + let reference = format!("refs/tags/{tag_name}"); + let tag_name = tag_name.to_string(); + + tokio::task::spawn_blocking(move || { + let repo = self.repo.lock(); + + let tag = repo + .find_reference(&reference) + .unwrap() + .peel_to_tag() + .unwrap(); + let tag_target = tag.target().unwrap(); + + let tagged_object = match tag_target.kind() { + Some(ObjectType::Commit) => Some(TaggedObject::Commit(tag_target.id().to_string())), + Some(ObjectType::Tree) => Some(TaggedObject::Tree(tag_target.id().to_string())), + None | Some(_) => None, + }; + + DetailedTag { + name: tag_name, + tagger: tag.tagger().map(Into::into), + message: tag.message().unwrap().to_string(), + tagged_object, + } + }) + .await + .unwrap() } - pub async fn get_refs(&self, repo: PathBuf) -> Arc { - self.refs - .get_with(repo.clone(), async { + #[instrument(skip(self))] + pub async fn refs(self: Arc) -> Arc { + let git = self.git.clone(); + + git.refs + .get_with(self.cache_key.clone(), async move { tokio::task::spawn_blocking(move || { - let repo = git2::Repository::open_bare(repo).unwrap(); + let repo = self.repo.lock(); + let ref_iter = repo.references().unwrap(); let mut built_refs = Refs::default(); @@ -134,33 +169,34 @@ .await } - pub async fn get_readme(&self, repo: PathBuf) -> Option> { + #[instrument(skip(self))] + pub async fn readme(self: Arc) -> Option> { const README_FILES: &[&str] = &["README.md", "README", "README.txt"]; + + let git = self.git.clone(); - self.readme_cache - .get_with(repo.clone(), async { + git.readme_cache + .get_with(self.cache_key.clone(), async move { tokio::task::spawn_blocking(move || { - let repo = Repository::open_bare(repo).unwrap(); + let repo = self.repo.lock(); + let head = repo.head().unwrap(); let commit = head.peel_to_commit().unwrap(); let tree = commit.tree().unwrap(); - - for file in README_FILES { - let object = if let Some(o) = tree.get_name(file) { - o - } else { - continue; - }; - - let object = object.to_object(&repo).unwrap(); - let blob = object.into_blob().unwrap(); - - return Some(Arc::from( - String::from_utf8(blob.content().to_vec()).unwrap(), - )); - } - None + let blob = README_FILES + .iter() + .map(|file| tree.get_name(file)) + .next() + .flatten()? + .to_object(&repo) + .unwrap() + .into_blob() + .unwrap(); + + Some(Arc::from( + String::from_utf8(blob.content().to_vec()).unwrap(), + )) }) .await .unwrap() @@ -168,12 +204,15 @@ .await } - pub async fn get_latest_commit(&self, repo: PathBuf, syntax_set: Arc) -> Commit { + #[instrument(skip(self))] + pub async fn latest_commit(self: Arc) -> Commit { tokio::task::spawn_blocking(move || { - let repo = Repository::open_bare(repo).unwrap(); + let repo = self.repo.lock(); + let head = repo.head().unwrap(); let commit = head.peel_to_commit().unwrap(); - let (diff_output, diff_stats) = fetch_diff_and_stats(&repo, &commit, &syntax_set); + let (diff_output, diff_stats) = + fetch_diff_and_stats(&repo, &commit, &self.git.syntax_set); let mut commit = Commit::from(commit); commit.diff_stats = diff_stats; @@ -184,39 +223,45 @@ .unwrap() } - pub async fn fetch_repository_metadata(&self) -> Arc { - if let Some(metadata) = self.repository_metadata.load().as_ref() { - return Arc::clone(metadata); - } + #[instrument(skip(self))] + pub async fn commit(self: Arc, commit: &str) -> Arc { + let commit = Oid::from_str(commit).unwrap(); - let start = Path::new("../test-git").canonicalize().unwrap(); + let git = self.git.clone(); - let repos = tokio::task::spawn_blocking(move || { - let mut repos: RepositoryMetadataList = RepositoryMetadataList::new(); - fetch_repository_metadata_impl(&start, &start, &mut repos); - repos - }) - .await - .unwrap(); + git.commits + .get_with(commit, async move { + tokio::task::spawn_blocking(move || { + let repo = self.repo.lock(); - let repos = Arc::new(repos); - self.repository_metadata.store(Some(repos.clone())); + let commit = repo.find_commit(commit).unwrap(); + let (diff_output, diff_stats) = + fetch_diff_and_stats(&repo, &commit, &self.git.syntax_set); - repos + let mut commit = Commit::from(commit); + commit.diff_stats = diff_stats; + commit.diff = diff_output; + + Arc::new(commit) + }) + .await + .unwrap() + }) + .await } - pub async fn get_commits( - &self, - repo: PathBuf, + #[instrument(skip(self))] + pub async fn commits( + self: Arc, branch: Option<&str>, offset: usize, ) -> (Vec, Option) { - const AMOUNT: usize = 200; + const LIMIT: usize = 200; let ref_name = branch.map(|branch| format!("refs/heads/{}", branch)); tokio::task::spawn_blocking(move || { - let repo = Repository::open_bare(repo).unwrap(); + let repo = self.repo.lock(); let mut revs = repo.revwalk().unwrap(); if let Some(ref_name) = ref_name.as_deref() { @@ -227,7 +272,7 @@ let mut commits: Vec = revs .skip(offset) - .take(AMOUNT + 1) + .take(LIMIT + 1) .map(|rev| { let rev = rev.unwrap(); repo.find_commit(rev).unwrap().into() @@ -385,6 +430,7 @@ } } +#[instrument(skip(repo, commit, syntax_set))] fn fetch_diff_and_stats( repo: &git2::Repository, commit: &git2::Commit<'_>, @@ -413,6 +459,7 @@ (diff_output, diff_stats) } +#[instrument(skip(diff, syntax_set))] fn format_diff(diff: &git2::Diff<'_>, syntax_set: &SyntaxSet) -> String { let mut diff_output = String::new(); @@ -462,6 +509,7 @@ diff_output } +#[instrument(skip(repos))] fn fetch_repository_metadata_impl( start: &Path, current: &Path, diff --git a/src/main.rs b/src/main.rs index a6a4711..4892d22 100644 --- a/src/main.rs +++ a/src/main.rs @@ -43,8 +43,7 @@ .route("/highlight.css", get(static_css(css))) .fallback(methods::repo::service.into_service()) .layer(layer_fn(LoggingMiddleware)) - .layer(Extension(Git::default())) - .layer(Extension(Arc::new(syntax_set))); + .layer(Extension(Arc::new(Git::new(syntax_set)))); axum::Server::bind(&"127.0.0.1:3333".parse().unwrap()) .serve(app.into_make_service_with_connect_info::()) diff --git a/src/methods/index.rs b/src/methods/index.rs index 7711e4f..7281242 100644 --- a/src/methods/index.rs +++ a/src/methods/index.rs @@ -7,7 +7,7 @@ use crate::{git::RepositoryMetadataList, Git}; #[allow(clippy::unused_async)] -pub async fn handle(Extension(git): Extension) -> Html { +pub async fn handle(Extension(git): Extension>) -> Html { #[derive(Template)] #[template(path = "index.html")] pub struct View { diff --git a/src/methods/repo.rs b/src/methods/repo.rs index e3a62df..bbaf83c 100644 --- a/src/methods/repo.rs +++ a/src/methods/repo.rs @@ -14,7 +14,6 @@ }; use path_clean::PathClean; use serde::Deserialize; -use syntect::parsing::SyntaxSet; use tower::{util::BoxCloneService, Service}; use super::filters; @@ -100,7 +99,7 @@ pub async fn handle_tag( Extension(repo): Extension, Extension(RepositoryPath(repository_path)): Extension, - Extension(git): Extension, + Extension(git): Extension>, Query(query): Query, ) -> Html { #[derive(Template)] @@ -110,7 +109,8 @@ tag: DetailedTag, } - let tag = git.get_tag(repository_path, &query.name).await; + let open_repo = git.repo(repository_path).await; + let tag = open_repo.tag_info(&query.name).await; Html(View { repo, tag }.render().unwrap()) } @@ -127,7 +127,7 @@ pub async fn handle_log( Extension(repo): Extension, Extension(RepositoryPath(repository_path)): Extension, - Extension(git): Extension, + Extension(git): Extension>, Query(query): Query, ) -> Html { #[derive(Template)] @@ -139,12 +139,9 @@ branch: Option, } - let (commits, next_offset) = git - .get_commits( - repository_path, - query.branch.as_deref(), - query.offset.unwrap_or(0), - ) + let open_repo = git.repo(repository_path).await; + let (commits, next_offset) = open_repo + .commits(query.branch.as_deref(), query.offset.unwrap_or(0)) .await; Html( @@ -163,7 +160,7 @@ pub async fn handle_refs( Extension(repo): Extension, Extension(RepositoryPath(repository_path)): Extension, - Extension(git): Extension, + Extension(git): Extension>, ) -> Html { #[derive(Template)] #[template(path = "repo/refs.html")] @@ -172,7 +169,8 @@ refs: Arc, } - let refs = git.get_refs(repository_path).await; + let open_repo = git.repo(repository_path).await; + let refs = open_repo.refs().await; Html(View { repo, refs }.render().unwrap()) } @@ -181,7 +179,7 @@ pub async fn handle_about( Extension(repo): Extension, Extension(RepositoryPath(repository_path)): Extension, - Extension(git): Extension, + Extension(git): Extension>, ) -> Html { #[derive(Template)] #[template(path = "repo/about.html")] @@ -190,7 +188,8 @@ readme: Option>, } - let readme = git.get_readme(repository_path).await; + let open_repo = git.clone().repo(repository_path).await; + let readme = open_repo.readme().await; Html(View { repo, readme }.render().unwrap()) } @@ -204,8 +203,7 @@ pub async fn handle_commit( Extension(repo): Extension, Extension(RepositoryPath(repository_path)): Extension, - Extension(git): Extension, - Extension(syntax_set): Extension>, + Extension(git): Extension>, Query(query): Query, ) -> Html { #[derive(Template)] @@ -215,18 +213,14 @@ pub commit: Arc, } - Html( - View { - repo, - commit: if let Some(commit) = query.id { - git.get_commit(repository_path, &commit, syntax_set).await - } else { - Arc::new(git.get_latest_commit(repository_path, syntax_set).await) - }, - } - .render() - .unwrap(), - ) + let open_repo = git.repo(repository_path).await; + let commit = if let Some(commit) = query.id { + open_repo.commit(&commit).await + } else { + Arc::new(open_repo.latest_commit().await) + }; + + Html(View { repo, commit }.render().unwrap()) } #[allow(clippy::unused_async)] @@ -244,8 +238,7 @@ pub async fn handle_diff( Extension(repo): Extension, Extension(RepositoryPath(repository_path)): Extension, - Extension(git): Extension, - Extension(syntax_set): Extension>, + Extension(git): Extension>, Query(query): Query, ) -> Html { #[derive(Template)] @@ -255,16 +248,12 @@ pub commit: Arc, } - Html( - View { - repo, - commit: if let Some(commit) = query.id { - git.get_commit(repository_path, &commit, syntax_set).await - } else { - Arc::new(git.get_latest_commit(repository_path, syntax_set).await) - }, - } - .render() - .unwrap(), - ) + let open_repo = git.repo(repository_path).await; + let commit = if let Some(commit) = query.id { + open_repo.commit(&commit).await + } else { + Arc::new(open_repo.latest_commit().await) + }; + + Html(View { repo, commit }.render().unwrap()) } -- rgit 0.1.3