Implement repository refs page
Diff
Cargo.lock | 7 +++++++
Cargo.toml | 1 +
src/git.rs | 70 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
statics/style.css | 4 ++++
src/methods/repo.rs | 14 ++++++++++++--
templates/repo/refs.html | 49 +++++++++++++++++++++++++++++++++++++++++++++++++
6 files changed, 142 insertions(+), 3 deletions(-)
@@ -753,6 +753,12 @@
checksum = "60302e4db3a61da70c0cb7991976248362f30319e88850c487b9b95bbf059e00"
[[package]]
name = "md5"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "490cc448043f947bae3cbee9c203358d62dbee0db12107a74be5c30ccfd09771"
[[package]]
name = "memchr"
version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1071,6 +1077,7 @@
"git2",
"hex",
"humantime",
"md5",
"moka",
"path-clean",
"serde",
@@ -14,6 +14,7 @@
git2 = "0.14"
hex = "0.4"
humantime = "2.1"
md5 = "0.7"
moka = { version = "0.9", features = ["future"] }
path-clean = "0.1"
serde = { version = "1.0", features = ["derive"] }
@@ -16,6 +16,7 @@
pub struct Git {
commits: moka::future::Cache<Oid, Arc<Commit>>,
readme_cache: moka::future::Cache<PathBuf, Arc<str>>,
refs: moka::future::Cache<PathBuf, Arc<Refs>>,
repository_metadata: Arc<ArcSwapOption<RepositoryMetadataList>>,
}
@@ -24,6 +25,7 @@
Self {
commits: moka::future::Cache::new(100),
readme_cache: moka::future::Cache::new(100),
refs: moka::future::Cache::new(100),
repository_metadata: Arc::new(ArcSwapOption::default()),
}
}
@@ -47,6 +49,43 @@
.await
}
pub async fn get_refs<'a>(&'a self, repo: PathBuf) -> Arc<Refs> {
self.refs
.get_with(repo.clone(), async {
tokio::task::spawn_blocking(move || {
let repo = git2::Repository::open_bare(repo).unwrap();
let refs = repo.references().unwrap();
let mut built_refs = Refs::default();
for ref_ in refs {
let ref_ = ref_.unwrap();
if ref_.is_branch() {
let commit = ref_.peel_to_commit().unwrap();
built_refs.branch.push(Branch {
name: ref_.shorthand().unwrap().to_string(),
commit: commit.into(),
});
} else if ref_.is_tag() {
let commit = ref_.peel_to_commit().unwrap();
built_refs.tag.push(Tag {
name: ref_.shorthand().unwrap().to_string(),
commit: commit.into(),
});
}
}
Arc::new(built_refs)
})
.await
.unwrap()
})
.await
}
pub async fn get_readme(&self, repo: PathBuf) -> Arc<str> {
self.readme_cache
.get_with(repo.clone(), async {
@@ -99,6 +138,29 @@
repos
}
}
#[derive(Debug, Default)]
pub struct Refs {
pub branch: Vec<Branch>,
pub tag: Vec<Tag>,
}
#[derive(Debug)]
pub struct Branch {
pub name: String,
pub commit: Commit,
}
#[derive(Debug)]
pub struct Remote {
pub name: String,
}
#[derive(Debug)]
pub struct Tag {
pub name: String,
pub commit: Commit,
}
#[derive(Debug)]
@@ -109,9 +171,11 @@
pub last_modified: Duration,
}
#[derive(Debug)]
pub struct CommitUser {
name: String,
email: String,
email_md5: String,
time: String,
}
@@ -120,6 +184,7 @@
CommitUser {
name: v.name().unwrap().to_string(),
email: v.email().unwrap().to_string(),
email_md5: format!("{:x}", md5::compute(v.email_bytes())),
time: OffsetDateTime::from_unix_timestamp(v.when().seconds())
.unwrap()
.to_string(),
@@ -134,6 +199,10 @@
pub fn email(&self) -> &str {
&self.email
}
pub fn email_md5(&self) -> &str {
&self.email_md5
}
pub fn time(&self) -> &str {
@@ -141,6 +210,7 @@
}
}
#[derive(Debug)]
pub struct Commit {
author: CommitUser,
committer: CommitUser,
@@ -29,6 +29,10 @@
text-decoration: underline;
}
.mt-2 {
margin-top: 2rem;
}
table { border-collapse: collapse; }
table.repositories { width: 100%; }
table.repositories a { color: black; }
@@ -17,6 +17,7 @@
use tower::{util::BoxCloneService, Service};
use crate::{git::Commit, layers::UnwrapInfallible, Git};
use crate::git::Refs;
#[derive(Clone)]
pub struct Repository(pub PathBuf);
@@ -100,21 +101,28 @@
}
#[allow(clippy::unused_async)]
pub async fn handle_refs(Extension(repo): Extension<Repository>) -> Html<String> {
pub async fn handle_refs(
Extension(repo): Extension<Repository>,
Extension(RepositoryPath(repository_path)): Extension<RepositoryPath>,
Extension(git): Extension<Git>,
) -> Html<String> {
#[derive(Template)]
#[template(path = "repo/refs.html")]
pub struct View {
repo: Repository,
refs: Arc<Refs>,
}
Html(View { repo }.render().unwrap())
let refs = git.get_refs(repository_path).await;
Html(View { repo, refs }.render().unwrap())
}
#[allow(clippy::unused_async)]
pub async fn handle_about(
Extension(repo): Extension<Repository>,
Extension(RepositoryPath(repository_path)): Extension<RepositoryPath>,
Extension(git): Extension<Git>
Extension(git): Extension<Git>,
) -> Html<String> {
#[derive(Template)]
#[template(path = "repo/about.html")]
@@ -1,6 +1,55 @@
{% extends "repo/base.html" %}
{% block refs_nav_class %}active{% endblock %}
{% block content %}
<table class="repositories">
<thead>
<tr>
<th>Branch</th>
<th>Commit message</th>
<th>Author</th>
<th>Age</th>
</tr>
</thead>
<tbody>
{% for branch in refs.branch %}
<tr>
<td><a href="/{{ repo.display() }}/log/?h={{ branch.name }}">{{ branch.name }}</a></td>
<td><a href="/{{ repo.display() }}/commit/?id={{ branch.commit.oid() }}">{{ branch.commit.summary() }}</a></td>
<td>
<img src="https://www.gravatar.com/avatar/{{ branch.commit.author().email_md5() }}?s=13&d=retro" width="13" height="13">
{{ branch.commit.author().name() }}
</td>
<td>{{ branch.commit.author().time() }}</td>
</tr>
{% endfor %}
</tbody>
</table>
<table class="repositories mt-2">
<thead>
<tr>
<th>Tag</th>
<th>Download</th>
<th>Author</th>
<th>Age</th>
</tr>
</thead>
<tbody>
{% for tag in refs.tag %}
<tr>
<td><a href="/{{ repo.display() }}/tag/?h={{ tag.name }}">{{ tag.name }}</a></td>
<td></td>
<td>
<img src="https://www.gravatar.com/avatar/{{ tag.commit.author().email_md5() }}?s=13&d=retro" width="13" height="13">
{{ tag.commit.author().name() }}
</td>
<td>{{ tag.commit.author().time() }}</td>
</tr>
{% endfor %}
</tbody>
</table>
{% endblock %}