diff --git a/Cargo.lock b/Cargo.lock
@@ -752,6 +752,12 @@ version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
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"
@@ -1071,6 +1077,7 @@ dependencies = [
"git2",
"hex",
"humantime",
+ "md5",
"moka",
"path-clean",
"serde",
diff --git a/Cargo.toml b/Cargo.toml
@@ -14,6 +14,7 @@ futures = "0.3"
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"] }
diff --git a/src/git.rs b/src/git.rs
@@ -16,6 +16,7 @@ pub type RepositoryMetadataList = BTreeMap<Option<String>, Vec<RepositoryMetadat
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 @@ impl Default for Git {
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 @@ impl Git {
.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 {
@@ -101,6 +140,29 @@ impl Git {
}
}
+#[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)]
pub struct RepositoryMetadata {
pub name: String,
@@ -109,9 +171,11 @@ pub struct RepositoryMetadata {
pub last_modified: Duration,
}
+#[derive(Debug)]
pub struct CommitUser {
name: String,
email: String,
+ email_md5: String,
time: String,
}
@@ -120,6 +184,7 @@ impl From<Signature<'_>> for CommitUser {
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(),
@@ -136,11 +201,16 @@ impl CommitUser {
&self.email
}
+ pub fn email_md5(&self) -> &str {
+ &self.email_md5
+ }
+
pub fn time(&self) -> &str {
&self.time
}
}
+#[derive(Debug)]
pub struct Commit {
author: CommitUser,
committer: CommitUser,
diff --git a/src/methods/repo.rs b/src/methods/repo.rs
@@ -17,6 +17,7 @@ use serde::Deserialize;
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 @@ pub async fn handle_log(Extension(repo): Extension<Repository>) -> Html<String>
}
#[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")]
diff --git a/statics/style.css b/statics/style.css
@@ -29,6 +29,10 @@ a:hover {
text-decoration: underline;
}
+.mt-2 {
+ margin-top: 2rem;
+}
+
table { border-collapse: collapse; }
table.repositories { width: 100%; }
table.repositories a { color: black; }
diff --git a/templates/repo/refs.html b/templates/repo/refs.html
@@ -3,4 +3,53 @@
{% 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 %}
\ No newline at end of file