src/server/http/repo/diff.rs
Ref: Size: 3.3 KiB
use std::sync::Arc;
use axum::extract::{Path, State};
use axum::response::{IntoResponse, Response};
use chrono::{TimeZone, Utc};
use super::{AppState, collab_counts, not_found, open_repo};
#[derive(Debug)]
pub struct DiffLine {
pub kind: String,
pub text: String,
}
#[derive(askama::Template, askama_web::WebTemplate)]
#[template(path = "diff.html")]
pub struct DiffTemplate {
pub site_title: String,
pub repo_name: String,
pub active_section: String,
pub open_patches: usize,
pub open_issues: usize,
pub short_id: String,
pub summary: String,
pub body: String,
pub author: String,
pub date: String,
pub diff_lines: Vec<DiffLine>,
}
fn compute_diff_lines(repo: &git2::Repository, oid: git2::Oid) -> Vec<DiffLine> {
let commit = match repo.find_commit(oid) {
Ok(c) => c,
Err(_) => return Vec::new(),
};
let new_tree = match commit.tree() {
Ok(t) => t,
Err(_) => return Vec::new(),
};
let old_tree = commit
.parent(0)
.ok()
.and_then(|p| p.tree().ok());
let diff = match repo.diff_tree_to_tree(old_tree.as_ref(), Some(&new_tree), None) {
Ok(d) => d,
Err(_) => return Vec::new(),
};
let mut lines: Vec<DiffLine> = Vec::new();
let _ = diff.print(git2::DiffFormat::Patch, |_delta, _hunk, line| {
let text = String::from_utf8_lossy(line.content()).into_owned();
let kind = match line.origin() {
'+' => "add",
'-' => "del",
'@' => "hunk",
'F' => "file",
_ => "ctx",
}
.to_string();
lines.push(DiffLine { kind, text });
true
});
lines
}
pub async fn diff(
Path((repo_name, oid_str)): Path<(String, String)>,
State(state): State<Arc<AppState>>,
) -> Response {
let (_entry, repo) = match open_repo(&state, &repo_name) {
Ok(r) => r,
Err(resp) => return resp,
};
let (open_patches, open_issues) = collab_counts(&repo);
let oid = match git2::Oid::from_str(&oid_str) {
Ok(o) => o,
Err(_) => return not_found(&state, format!("Invalid OID: {}", oid_str)),
};
let commit = match repo.find_commit(oid) {
Ok(c) => c,
Err(_) => return not_found(&state, format!("Commit '{}' not found.", oid_str)),
};
let id = oid.to_string();
let short_id = id[..8.min(id.len())].to_string();
let summary = commit.summary().unwrap_or("").to_string();
let author = commit.author().name().unwrap_or("").to_string();
let secs = commit.time().seconds();
let date = Utc
.timestamp_opt(secs, 0)
.single()
.map(|dt| dt.format("%Y-%m-%d %H:%M").to_string())
.unwrap_or_default();
let body = commit
.message()
.map(|m| {
let lines: Vec<&str> = m.splitn(2, '\n').collect();
if lines.len() > 1 { lines[1].trim_start_matches('\n').to_string() } else { String::new() }
})
.unwrap_or_default();
let diff_lines = compute_diff_lines(&repo, oid);
DiffTemplate {
site_title: state.site_title.clone(),
repo_name,
active_section: "commits".to_string(),
open_patches,
open_issues,
short_id,
summary,
body,
author,
date,
diff_lines,
}
.into_response()
}