branch: main
log.rs
7763 bytesRaw
use super::*;
struct LogPage {
route_path: String,
owner: String,
repo_name: String,
ref_name: String,
current_page: i64,
per_page: i64,
branches: Vec<String>,
commits: Vec<CommitListItem>,
has_next: bool,
}
impl LogPage {
fn previous_page(&self) -> Option<i64> {
(self.current_page > 1).then_some(self.current_page - 1)
}
fn next_page(&self) -> Option<i64> {
self.has_next.then_some(self.current_page + 1)
}
fn log_path(&self, ref_name: &str, page: i64) -> String {
let mut path = format!("{}?ref={}", self.route_path, ref_name);
if page > 1 {
path.push_str(&format!("&page={}", page));
}
path
}
fn current_path(&self) -> String {
self.log_path(&self.ref_name, self.current_page)
}
fn commit_path(&self, hash: &str) -> String {
format!("/{}/{}/commit/{}", self.owner, self.repo_name, hash)
}
fn home_path(&self) -> String {
format!("/{}/{}/?ref={}", self.owner, self.repo_name, self.ref_name)
}
fn search_path(&self) -> String {
format!("/{}/{}/search-ui?scope=commits", self.owner, self.repo_name)
}
fn json_log_path(&self) -> String {
let offset = (self.current_page - 1) * self.per_page;
format!(
"/{}/{}/log?ref={}&limit={}&offset={}",
self.owner, self.repo_name, self.ref_name, self.per_page, offset
)
}
}
fn build_log_page(sql: &SqlStorage, owner: &str, repo_name: &str, url: &Url) -> Result<LogPage> {
let ref_name = api::get_query(url, "ref").unwrap_or_else(|| {
resolve_default_branch(sql)
.map(|(name, _)| name)
.unwrap_or_else(|_| "main".to_string())
});
let current_page: i64 = api::get_query(url, "page")
.and_then(|v| v.parse().ok())
.unwrap_or(1)
.max(1);
let per_page: i64 = 30;
let offset = (current_page - 1) * per_page;
let branches = load_branches(sql)?;
let (commits, has_next) = match api::resolve_ref(sql, &ref_name)? {
Some(head) => {
let commits = walk_commits(sql, &head, per_page + 1, offset)?;
let has_next = commits.len() as i64 > per_page;
let commits = summarize_commits(commits.into_iter().take(per_page as usize).collect());
(commits, has_next)
}
None => (Vec::new(), false),
};
Ok(LogPage {
route_path: url.path().to_string(),
owner: owner.to_string(),
repo_name: repo_name.to_string(),
ref_name,
current_page,
per_page,
branches,
commits,
has_next,
})
}
fn render_log_branch_selector(page: &LogPage) -> String {
if page.branches.len() <= 1 {
return format!(
r#"<div class="branch-selector"><span class="branch-label">branch:</span> <strong>{}</strong></div>"#,
html_escape(&page.ref_name)
);
}
let mut html = String::from(
r#"<div class="branch-selector"><span class="branch-label">branch:</span> <select onchange="window.location.href=this.value">"#,
);
for branch in &page.branches {
let selected = if branch == &page.ref_name {
" selected"
} else {
""
};
html.push_str(&format!(
r#"<option value="{}"{}>{}</option>"#,
html_escape(&page.log_path(branch, 1)),
selected,
html_escape(branch)
));
}
html.push_str("</select></div>");
html
}
fn render_log_html(page: &LogPage, actor_name: Option<&str>) -> String {
let mut html = String::new();
html.push_str(&render_log_branch_selector(page));
html.push_str(&format!(
r#"<h1>Commits on <strong>{}</strong></h1>"#,
html_escape(&page.ref_name)
));
if page.commits.is_empty() {
html.push_str("<p>No commits yet.</p>");
} else {
html.push_str(&render_commit_list(
&page.commits,
&page.owner,
&page.repo_name,
true,
));
}
html.push_str(r#"<div class="pagination">"#);
if let Some(previous_page) = page.previous_page() {
html.push_str(&format!(
r#"<a href="{}">Previous</a>"#,
html_escape(&page.log_path(&page.ref_name, previous_page))
));
}
if let Some(next_page) = page.next_page() {
html.push_str(&format!(
r#"<a href="{}">Next</a>"#,
html_escape(&page.log_path(&page.ref_name, next_page))
));
}
html.push_str("</div>");
layout(
"Commits",
&page.owner,
&page.repo_name,
&page.ref_name,
actor_name,
&html,
)
}
fn render_log_markdown(page: &LogPage, selection: &NegotiatedRepresentation) -> String {
let mut markdown = format!(
"# {}/{} commits\n\nBranch: `{}`\nPage: `{}`\n",
page.owner, page.repo_name, page.ref_name, page.current_page
);
if page.commits.is_empty() {
markdown.push_str("\nNo commits yet.\n");
} else {
markdown.push_str("\n## Commits (GET paths)\n");
for commit in &page.commits {
markdown.push_str(&format!(
"- `{}` - {} - {} - {} - `{}`\n",
commit.short_hash,
commit.subject,
commit.author,
commit.relative_time,
page.commit_path(&commit.hash)
));
}
}
if !page.branches.is_empty() {
markdown.push_str("\n## Branches (GET paths)\n");
for branch in &page.branches {
let current = if branch == &page.ref_name {
" (current)"
} else {
""
};
markdown.push_str(&format!(
"- `{}`{} - `{}`\n",
branch,
current,
page.log_path(branch, 1)
));
}
}
let mut actions = vec![
Action::get(page.current_path(), "reload this commit log page"),
Action::get(page.home_path(), "browse the repository root at this ref"),
Action::get(page.search_path(), "search commit messages and authors"),
Action::get(page.json_log_path(), "fetch this page as structured JSON"),
];
if let Some(previous_page) = page.previous_page() {
actions.push(Action::get(
page.log_path(&page.ref_name, previous_page),
"view the previous page of commits",
));
}
if let Some(next_page) = page.next_page() {
actions.push(Action::get(
page.log_path(&page.ref_name, next_page),
"view the next page of commits",
));
}
let hints = vec![
presentation::text_navigation_hint(*selection),
Hint::new("Pagination uses `page=` here and `offset=` plus `limit=` on the JSON API."),
Hint::new("The `/log` and `/commits` routes now render the same commit-log page model."),
];
markdown.push_str(&presentation::render_actions_section(&actions));
markdown.push_str(&presentation::render_hints_section(&hints));
markdown
}
pub fn page_log(
sql: &SqlStorage,
owner: &str,
repo_name: &str,
url: &Url,
actor_name: Option<&str>,
) -> Result<Response> {
let page = build_log_page(sql, owner, repo_name, url)?;
html_response(&render_log_html(&page, actor_name))
}
pub fn page_log_markdown(
sql: &SqlStorage,
owner: &str,
repo_name: &str,
url: &Url,
selection: &NegotiatedRepresentation,
) -> Result<Response> {
let page = build_log_page(sql, owner, repo_name, url)?;
presentation::markdown_response(&render_log_markdown(&page, selection), selection)
}