Implement pagination on log view
Diff
src/git.rs | 53 +++++++++++++++++++++++++++++++++++++++++++++--------
statics/style.css | 9 +++++++++
src/methods/repo.rs | 22 ++++++++++++++++++++++
templates/repo/log.html | 6 ++++++
templates/repo/refs.html | 27 ++++++++++++++++++++++-----
5 files changed, 87 insertions(+), 30 deletions(-)
@@ -7,25 +7,35 @@
};
use arc_swap::ArcSwapOption;
use git2::{Oid, Repository, Signature, Sort};
use git2::{ObjectType, Oid, Repository, Signature};
use moka::future::Cache;
use time::OffsetDateTime;
pub type RepositoryMetadataList = BTreeMap<Option<String>, Vec<RepositoryMetadata>>;
#[derive(Clone)]
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>>,
commits: Cache<Oid, Arc<Commit>>,
readme_cache: Cache<PathBuf, Arc<str>>,
refs: Cache<PathBuf, Arc<Refs>>,
repository_metadata: Arc<ArcSwapOption<RepositoryMetadataList>>,
}
impl Default for Git {
fn default() -> Self {
Self {
commits: moka::future::Cache::new(100),
readme_cache: moka::future::Cache::new(100),
refs: moka::future::Cache::new(100),
commits: Cache::builder()
.time_to_live(Duration::from_secs(10))
.max_capacity(100)
.build(),
readme_cache: Cache::builder()
.time_to_live(Duration::from_secs(10))
.max_capacity(100)
.build(),
refs: Cache::builder()
.time_to_live(Duration::from_secs(10))
.max_capacity(100)
.build(),
repository_metadata: Arc::new(ArcSwapOption::default()),
}
}
@@ -69,11 +79,11 @@
commit: commit.into(),
});
} else if ref_.is_tag() {
let commit = ref_.peel_to_commit().unwrap();
let tag = ref_.peel_to_tag().unwrap();
built_refs.tag.push(Tag {
name: ref_.shorthand().unwrap().to_string(),
commit: commit.into(),
tagger: tag.tagger().map(Into::into),
});
}
}
@@ -143,22 +153,27 @@
repos
}
pub async fn get_commits(&self, repo: PathBuf) -> Vec<Commit> {
pub async fn get_commits(&self, repo: PathBuf, offset: usize) -> (Vec<Commit>, Option<usize>) {
const AMOUNT: usize = 200;
tokio::task::spawn_blocking(move || {
let repo = Repository::open_bare(repo).unwrap();
let mut revs = repo.revwalk().unwrap();
revs.set_sorting(Sort::TIME).unwrap();
revs.push_head().unwrap();
let mut commits = Vec::with_capacity(200);
let mut commits: Vec<Commit> = revs
.skip(offset)
.take(AMOUNT + 1)
.map(|rev| {
let rev = rev.unwrap();
repo.find_commit(rev).unwrap().into()
})
.collect();
for rev in revs.skip(0).take(200) {
let rev = rev.unwrap();
let commit = repo.find_commit(rev).unwrap();
commits.push(commit.into());
}
let next_offset = commits.pop().is_some().then(|| offset + commits.len());
commits
(commits, next_offset)
})
.await
.unwrap()
@@ -185,7 +200,7 @@
#[derive(Debug)]
pub struct Tag {
pub name: String,
pub commit: Commit,
pub tagger: Option<CommitUser>,
}
#[derive(Debug)]
@@ -33,6 +33,10 @@
margin-top: 2rem;
}
.text-center {
text-align: center;
}
.no-hover:hover {
text-decoration: none;
}
@@ -50,6 +54,11 @@
table tbody tr.has-parent td:first-of-type {
padding-left: 1rem;
}
tr.separator {
background: white !important;
height: 1rem;
}
table pre { margin: 0; }
@@ -89,22 +89,40 @@
Html(View { repo }.render().unwrap())
}
#[derive(Deserialize)]
pub struct LogQuery {
#[serde(rename = "ofs")]
offset: Option<usize>,
}
#[allow(clippy::unused_async)]
pub async fn handle_log(
Extension(repo): Extension<Repository>,
Extension(RepositoryPath(repository_path)): Extension<RepositoryPath>,
Extension(git): Extension<Git>,
Query(query): Query<LogQuery>,
) -> Html<String> {
#[derive(Template)]
#[template(path = "repo/log.html")]
pub struct View {
repo: Repository,
commits: Vec<Commit>,
next_offset: Option<usize>,
}
let commits = git.get_commits(repository_path).await;
let (commits, next_offset) = git
.get_commits(repository_path, query.offset.unwrap_or(0))
.await;
Html(View { repo, commits }.render().unwrap())
Html(
View {
repo,
commits,
next_offset,
}
.render()
.unwrap(),
)
}
#[allow(clippy::unused_async)]
@@ -25,4 +25,10 @@
{% endfor %}
</tbody>
</table>
{% if let Some(next_offset) = next_offset %}
<div class="mt-2 text-center">
<a href="?ofs={{ next_offset }}">[next]</a>
</div>
{% endif %}
{% endblock %}
@@ -26,28 +26,37 @@
</tr>
{% endfor %}
</tbody>
</table>
<table class="repositories mt-2">
<thead>
<tbody>
<tr class="separator">
<td></td>
<td></td>
<td></td>
<td></td>
</tr>
<tr>
<th>Tag</th>
<th>Download</th>
<th>Author</th>
<th>Age</th>
</tr>
</thead>
<tbody>
{% for tag in refs.tag %}
{% for tag in refs.tag.iter().rev() %}
<tr>
<td><a href="/{{ repo.display() }}/tag/?h={{ tag.name }}">{{ tag.name }}</a></td>
<td></td>
<td>
{% if let Some(tagger) = tag.tagger %}
<img src="https://www.gravatar.com/avatar/{{ tagger.email_md5() }}?s=13&d=retro" width="13" height="13">
{{ tagger.name() }}
{% endif %}
</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() }}
{% if let Some(tagger) = tag.tagger %}
{{ tagger.time() }}
{% endif %}
</td>
<td>{{ tag.commit.author().time() }}</td>
</tr>
{% endfor %}
</tbody>