From 39bdc18d9982b21db2ca64e6173d495d37e793ed Mon Sep 17 00:00:00 2001 From: Jordan Doyle Date: Sun, 31 Dec 2023 01:57:53 +0000 Subject: [PATCH] Retain references across pages --- Cargo.toml | 2 +- src/database/indexer.rs | 11 ++++++++--- src/git.rs | 48 ++++++++++++++++++++++++++++++------------------ src/main.rs | 3 +-- src/methods/repo/about.rs | 22 +++++++++++++++++++--- src/methods/repo/commit.rs | 11 +++++++++-- src/methods/repo/diff.rs | 11 ++++++++--- src/methods/repo/log.rs | 19 +++++++++++++++---- src/methods/repo/refs.rs | 4 +++- src/methods/repo/smart_git.rs | 6 ++++-- src/methods/repo/summary.rs | 4 +++- src/methods/repo/tag.rs | 13 +++++++++---- src/methods/repo/tree.rs | 35 +++++++++++++++++++++++------------ src/syntax_highlight.rs | 3 +-- templates/repo/base.html | 11 ++++++----- templates/repo/commit.html | 7 ++++--- templates/repo/file.html | 3 ++- templates/repo/log.html | 7 ++----- templates/repo/macros/link.html | 3 +++ templates/repo/tag.html | 3 ++- 20 files changed, 153 insertions(+), 73 deletions(-) create mode 100644 templates/repo/macros/link.html diff --git a/Cargo.toml b/Cargo.toml index 31ecc41..37aabed 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -29,7 +29,7 @@ moka = { version = "0.12.0", features = ["future"] } once_cell = "1.18" path-clean = "1.0.1" parking_lot = "0.12" -serde = { version = "1.0", features = ["derive"] } +serde = { version = "1.0", features = ["derive", "rc"] } sha2 = "0.10" syntect = "5" sled = { version = "0.34", features = ["compression"] } diff --git a/src/database/indexer.rs b/src/database/indexer.rs index f66dbb8..11ba789 100644 --- a/src/database/indexer.rs +++ b/src/database/indexer.rs @@ -7,7 +7,7 @@ use std::{ use git2::Sort; use ini::Ini; use time::OffsetDateTime; -use tracing::{info, info_span}; +use tracing::{error, info, info_span}; use crate::database::schema::{ commit::Commit, @@ -89,7 +89,9 @@ fn update_repository_reflog(scan_path: &Path, db: &sled::Db) { let reference = reference.unwrap(); let reference_name = String::from_utf8_lossy(reference.name_bytes()); - if !reference_name.starts_with("refs/heads/") { + if !reference_name.starts_with("refs/heads/") + && !reference_name.starts_with("refs/tags/") + { continue; } @@ -119,7 +121,10 @@ fn update_repository_reflog(scan_path: &Path, db: &sled::Db) { // TODO: only scan revs from the last time we looked let mut revwalk = git_repository.revwalk().unwrap(); revwalk.set_sorting(Sort::REVERSE).unwrap(); - revwalk.push_ref(&reference_name).unwrap(); + if let Err(error) = revwalk.push_ref(&reference_name) { + error!(%error, "Failed to revwalk reference"); + continue; + } let mut i = 0; for rev in revwalk { diff --git a/src/git.rs b/src/git.rs index e9699d9..bbb1263 100644 --- a/src/git.rs +++ b/src/git.rs @@ -11,8 +11,8 @@ use anyhow::{Context, Result}; use bytes::{Bytes, BytesMut}; use comrak::{ComrakOptions, ComrakPlugins}; use git2::{ - BranchType, DiffFormat, DiffLineType, DiffOptions, DiffStatsFormat, Email, EmailCreateOptions, - ObjectType, Oid, Signature, + DiffFormat, DiffLineType, DiffOptions, DiffStatsFormat, Email, EmailCreateOptions, ObjectType, + Oid, Signature, }; use moka::future::Cache; use parking_lot::Mutex; @@ -51,7 +51,11 @@ impl Git { impl Git { #[instrument(skip(self))] - pub async fn repo(self: Arc, repo_path: PathBuf) -> Result> { + pub async fn repo( + self: Arc, + repo_path: PathBuf, + branch: Option>, + ) -> Result> { let repo = tokio::task::spawn_blocking({ let repo_path = repo_path.clone(); move || git2::Repository::open(repo_path) @@ -64,6 +68,7 @@ impl Git { git: self, cache_key: repo_path, repo: Mutex::new(repo), + branch, })) } } @@ -72,6 +77,7 @@ pub struct OpenRepository { git: Arc, cache_key: PathBuf, repo: Mutex, + branch: Option>, } impl OpenRepository { @@ -79,7 +85,6 @@ impl OpenRepository { self: Arc, path: Option, tree_id: Option<&str>, - branch: Option, formatted: bool, ) -> Result { let tree_id = tree_id @@ -93,12 +98,11 @@ impl OpenRepository { let mut tree = if let Some(tree_id) = tree_id { repo.find_tree(tree_id) .context("Couldn't find tree with given id")? - } else if let Some(branch) = branch { - let branch = repo.find_branch(&branch, BranchType::Local)?; - branch - .get() + } else if let Some(branch) = &self.branch { + let reference = repo.resolve_reference_from_short_name(branch)?; + reference .peel_to_tree() - .context("Couldn't find tree for branch")? + .context("Couldn't find tree for reference")? } else { let head = repo.head()?; head.peel_to_tree() @@ -175,16 +179,14 @@ impl OpenRepository { } #[instrument(skip(self))] - pub async fn tag_info(self: Arc, tag_name: &str) -> Result { - let reference = format!("refs/tags/{tag_name}"); - let tag_name = tag_name.to_string(); - + pub async fn tag_info(self: Arc) -> Result { tokio::task::spawn_blocking(move || { + let tag_name = self.branch.clone().context("no tag given")?; let repo = self.repo.lock(); let tag = repo - .find_reference(&reference) - .context("Given reference does not exist in repository")? + .find_reference(&format!("refs/tags/{tag_name}")) + .context("Given tag does not exist in repository")? .peel_to_tag() .context("Couldn't get to a tag from the given reference")?; let tag_target = tag.target().context("Couldn't find tagged object")?; @@ -222,7 +224,12 @@ impl OpenRepository { tokio::task::spawn_blocking(move || { let repo = self.repo.lock(); - let head = repo.head().context("Couldn't find HEAD of repository")?; + let head = if let Some(reference) = &self.branch { + repo.resolve_reference_from_short_name(reference)? + } else { + repo.head().context("Couldn't find HEAD of repository")? + }; + let commit = head.peel_to_commit().context( "Couldn't find the commit that the HEAD of the repository refers to", )?; @@ -268,7 +275,12 @@ impl OpenRepository { tokio::task::spawn_blocking(move || { let repo = self.repo.lock(); - let head = repo.head().context("Couldn't find HEAD of repository")?; + let head = if let Some(reference) = &self.branch { + repo.resolve_reference_from_short_name(reference)? + } else { + repo.head().context("Couldn't find HEAD of repository")? + }; + let commit = head .peel_to_commit() .context("Couldn't find commit HEAD of repository refers to")?; @@ -381,7 +393,7 @@ pub enum TaggedObject { #[derive(Debug)] pub struct DetailedTag { - pub name: String, + pub name: Arc, pub tagger: Option, pub message: String, pub tagged_object: Option, diff --git a/src/main.rs b/src/main.rs index dc0ee65..75f0f66 100644 --- a/src/main.rs +++ b/src/main.rs @@ -21,8 +21,7 @@ use axum::{ use bat::assets::HighlightingAssets; use clap::Parser; use once_cell::sync::{Lazy, OnceCell}; -use sha2::digest::FixedOutput; -use sha2::Digest; +use sha2::{digest::FixedOutput, Digest}; use sled::Db; use syntect::html::ClassStyle; use tokio::{ diff --git a/src/methods/repo/about.rs b/src/methods/repo/about.rs index 7dda1e1..f8e86cf 100644 --- a/src/methods/repo/about.rs +++ b/src/methods/repo/about.rs @@ -1,7 +1,8 @@ use std::sync::Arc; use askama::Template; -use axum::{response::Response, Extension}; +use axum::{extract::Query, response::Response, Extension}; +use serde::Deserialize; use crate::{ git::ReadmeFormat, @@ -10,20 +11,35 @@ use crate::{ Git, }; +#[derive(Deserialize)] +pub struct UriQuery { + #[serde(rename = "h")] + pub branch: Option>, +} + #[derive(Template)] #[template(path = "repo/about.html")] pub struct View { repo: Repository, readme: Option<(ReadmeFormat, Arc)>, + branch: Option>, } pub async fn handle( Extension(repo): Extension, Extension(RepositoryPath(repository_path)): Extension, Extension(git): Extension>, + Query(query): Query, ) -> Result { - let open_repo = git.clone().repo(repository_path).await?; + let open_repo = git + .clone() + .repo(repository_path, query.branch.clone()) + .await?; let readme = open_repo.readme().await?; - Ok(into_response(&View { repo, readme })) + Ok(into_response(&View { + repo, + readme, + branch: query.branch, + })) } diff --git a/src/methods/repo/commit.rs b/src/methods/repo/commit.rs index e3fea6d..046a6c5 100644 --- a/src/methods/repo/commit.rs +++ b/src/methods/repo/commit.rs @@ -16,11 +16,14 @@ use crate::{ pub struct View { pub repo: Repository, pub commit: Arc, + pub branch: Option>, } #[derive(Deserialize)] pub struct UriQuery { pub id: Option, + #[serde(rename = "h")] + pub branch: Option>, } pub async fn handle( @@ -29,12 +32,16 @@ pub async fn handle( Extension(git): Extension>, Query(query): Query, ) -> Result { - let open_repo = git.repo(repository_path).await?; + let open_repo = git.repo(repository_path, query.branch.clone()).await?; let commit = if let Some(commit) = query.id { open_repo.commit(&commit).await? } else { Arc::new(open_repo.latest_commit().await?) }; - Ok(into_response(&View { repo, commit })) + Ok(into_response(&View { + repo, + commit, + branch: query.branch, + })) } diff --git a/src/methods/repo/diff.rs b/src/methods/repo/diff.rs index 942bc3d..ebc7e25 100644 --- a/src/methods/repo/diff.rs +++ b/src/methods/repo/diff.rs @@ -20,6 +20,7 @@ use crate::{ pub struct View { pub repo: Repository, pub commit: Arc, + pub branch: Option>, } pub async fn handle( @@ -28,14 +29,18 @@ pub async fn handle( Extension(git): Extension>, Query(query): Query, ) -> Result { - let open_repo = git.repo(repository_path).await?; + let open_repo = git.repo(repository_path, query.branch.clone()).await?; let commit = if let Some(commit) = query.id { open_repo.commit(&commit).await? } else { Arc::new(open_repo.latest_commit().await?) }; - Ok(into_response(&View { repo, commit })) + Ok(into_response(&View { + repo, + commit, + branch: query.branch, + })) } pub async fn handle_plain( @@ -43,7 +48,7 @@ pub async fn handle_plain( Extension(git): Extension>, Query(query): Query, ) -> Result { - let open_repo = git.repo(repository_path).await?; + let open_repo = git.repo(repository_path, query.branch).await?; let commit = if let Some(commit) = query.id { open_repo.commit(&commit).await? } else { diff --git a/src/methods/repo/log.rs b/src/methods/repo/log.rs index 5429120..6b07a4c 100644 --- a/src/methods/repo/log.rs +++ b/src/methods/repo/log.rs @@ -66,11 +66,22 @@ pub async fn get_branch_commits( amount: usize, offset: usize, ) -> Result> { - let reference = branch.map(|branch| format!("refs/heads/{branch}")); + if let Some(reference) = branch { + let commit_tree = repository + .get() + .commit_tree(database, &format!("refs/heads/{reference}"))?; + let commit_tree = commit_tree.fetch_latest(amount, offset).await; - if let Some(reference) = reference { - let commit_tree = repository.get().commit_tree(database, &reference)?; - return Ok(commit_tree.fetch_latest(amount, offset).await); + if !commit_tree.is_empty() { + return Ok(commit_tree); + } + + let tag_tree = repository + .get() + .commit_tree(database, &format!("refs/tags/{reference}"))?; + let tag_tree = tag_tree.fetch_latest(amount, offset).await; + + return Ok(tag_tree); } for branch in DEFAULT_BRANCHES { diff --git a/src/methods/repo/refs.rs b/src/methods/repo/refs.rs index bf4f256..a085504 100644 --- a/src/methods/repo/refs.rs +++ b/src/methods/repo/refs.rs @@ -1,4 +1,4 @@ -use std::collections::BTreeMap; +use std::{collections::BTreeMap, sync::Arc}; use anyhow::Context; use askama::Template; @@ -17,6 +17,7 @@ use crate::{ pub struct View { repo: Repository, refs: Refs, + branch: Option>, } #[allow(clippy::unused_async)] @@ -46,5 +47,6 @@ pub async fn handle( Ok(into_response(&View { repo, refs: Refs { heads, tags }, + branch: None, })) } diff --git a/src/methods/repo/smart_git.rs b/src/methods/repo/smart_git.rs index 9791c5d..52d8fcb 100644 --- a/src/methods/repo/smart_git.rs +++ b/src/methods/repo/smart_git.rs @@ -14,8 +14,10 @@ use httparse::Status; use tokio_util::io::StreamReader; use tracing::warn; -use crate::methods::repo::{Repository, RepositoryPath, Result}; -use crate::StatusCode; +use crate::{ + methods::repo::{Repository, RepositoryPath, Result}, + StatusCode, +}; #[allow(clippy::unused_async)] pub async fn handle( diff --git a/src/methods/repo/summary.rs b/src/methods/repo/summary.rs index af88bce..4302c51 100644 --- a/src/methods/repo/summary.rs +++ b/src/methods/repo/summary.rs @@ -1,4 +1,4 @@ -use std::collections::BTreeMap; +use std::{collections::BTreeMap, sync::Arc}; use anyhow::Context; use askama::Template; @@ -20,6 +20,7 @@ pub struct View<'a> { repo: Repository, refs: Refs, commit_list: Vec<&'a crate::database::schema::commit::Commit<'a>>, + branch: Option>, } pub async fn handle( @@ -51,6 +52,7 @@ pub async fn handle( repo, refs: Refs { heads, tags }, commit_list, + branch: None, })) } diff --git a/src/methods/repo/tag.rs b/src/methods/repo/tag.rs index acdaf0c..e337d6b 100644 --- a/src/methods/repo/tag.rs +++ b/src/methods/repo/tag.rs @@ -14,7 +14,7 @@ use crate::{ #[derive(Deserialize)] pub struct UriQuery { #[serde(rename = "h")] - name: String, + name: Arc, } #[derive(Template)] @@ -22,6 +22,7 @@ pub struct UriQuery { pub struct View { repo: Repository, tag: DetailedTag, + branch: Option>, } pub async fn handle( @@ -30,8 +31,12 @@ pub async fn handle( Extension(git): Extension>, Query(query): Query, ) -> Result { - let open_repo = git.repo(repository_path).await?; - let tag = open_repo.tag_info(&query.name).await?; + let open_repo = git.repo(repository_path, Some(query.name.clone())).await?; + let tag = open_repo.tag_info().await?; - Ok(into_response(&View { repo, tag })) + Ok(into_response(&View { + repo, + tag, + branch: Some(query.name), + })) } diff --git a/src/methods/repo/tree.rs b/src/methods/repo/tree.rs index 06d2e3c..0fc5f7d 100644 --- a/src/methods/repo/tree.rs +++ b/src/methods/repo/tree.rs @@ -4,7 +4,12 @@ use std::{ }; use askama::Template; -use axum::{extract::Query, http, response::IntoResponse, response::Response, Extension}; +use axum::{ + extract::Query, + http, + response::{IntoResponse, Response}, + Extension, +}; use serde::Deserialize; use crate::{ @@ -20,10 +25,10 @@ use crate::{ #[derive(Deserialize)] pub struct UriQuery { id: Option, - #[serde(rename = "h")] - branch: Option, #[serde(default)] raw: bool, + #[serde(rename = "h")] + branch: Option>, } impl Display for UriQuery { @@ -50,6 +55,7 @@ pub struct TreeView { pub repo: Repository, pub items: Vec, pub query: UriQuery, + pub branch: Option>, } #[derive(Template)] @@ -57,6 +63,7 @@ pub struct TreeView { pub struct FileView { pub repo: Repository, pub file: FileWithContent, + pub branch: Option>, } pub async fn handle( @@ -66,19 +73,19 @@ pub async fn handle( Extension(git): Extension>, Query(query): Query, ) -> Result { - let open_repo = git.repo(repository_path).await?; + let open_repo = git.repo(repository_path, query.branch.clone()).await?; Ok( match open_repo - .path( - child_path, - query.id.as_deref(), - query.branch.clone(), - !query.raw, - ) + .path(child_path, query.id.as_deref(), !query.raw) .await? { - PathDestination::Tree(items) => into_response(&TreeView { repo, items, query }), + PathDestination::Tree(items) => into_response(&TreeView { + repo, + items, + branch: query.branch.clone(), + query, + }), PathDestination::File(file) if query.raw => { let headers = [( http::header::CONTENT_TYPE, @@ -87,7 +94,11 @@ pub async fn handle( (headers, file.content).into_response() } - PathDestination::File(file) => into_response(&FileView { repo, file }), + PathDestination::File(file) => into_response(&FileView { + repo, + file, + branch: query.branch, + }), }, ) } diff --git a/src/syntax_highlight.rs b/src/syntax_highlight.rs index 45d9a10..f197351 100644 --- a/src/syntax_highlight.rs +++ b/src/syntax_highlight.rs @@ -1,5 +1,4 @@ -use std::collections::HashMap; -use std::io::Write; +use std::{collections::HashMap, io::Write}; use comrak::adapters::SyntaxHighlighterAdapter; use syntect::{ diff --git a/templates/repo/base.html b/templates/repo/base.html index 30a7456..057217f 100644 --- a/templates/repo/base.html +++ b/templates/repo/base.html @@ -1,3 +1,4 @@ +{% import "macros/link.html" as link %} {% extends "../base.html" %} {% block title %}{{ repo.display() }}{% endblock %} @@ -9,13 +10,13 @@ {% block nav %}