src/log.rs
Ref: Size: 6.5 KiB
use git2::Repository;
use crate::dag;
use crate::error::Error;
use crate::event::{Action, Author};
/// A single entry in the collab log output.
#[derive(Debug, Clone)]
pub struct LogEntry {
pub timestamp: String,
pub event_type: String,
pub entity_kind: String,
pub entity_id: String,
pub author: Author,
pub summary: String,
}
/// Collect all events from every collab ref, sorted by timestamp.
/// If `limit` is provided, return at most that many entries (most recent last).
pub fn collect_events(repo: &Repository, limit: Option<usize>) -> Result<Vec<LogEntry>, Error> {
let mut entries = Vec::new();
// Walk issues
let issue_refs = repo.references_glob("refs/collab/issues/*")?;
for r in issue_refs {
let r = r?;
let ref_name = r.name().unwrap_or_default().to_string();
let id = ref_name
.strip_prefix("refs/collab/issues/")
.unwrap_or_default()
.to_string();
if let Ok(events) = dag::walk_events(repo, &ref_name) {
for (_oid, event) in events {
entries.push(LogEntry {
timestamp: event.timestamp.clone(),
event_type: action_type_name(&event.action),
entity_kind: "issue".to_string(),
entity_id: id.clone(),
author: event.author,
summary: action_summary(&event.action),
});
}
}
}
// Walk patches
let patch_refs = repo.references_glob("refs/collab/patches/*")?;
for r in patch_refs {
let r = r?;
let ref_name = r.name().unwrap_or_default().to_string();
let id = ref_name
.strip_prefix("refs/collab/patches/")
.unwrap_or_default()
.to_string();
if let Ok(events) = dag::walk_events(repo, &ref_name) {
for (_oid, event) in events {
entries.push(LogEntry {
timestamp: event.timestamp.clone(),
event_type: action_type_name(&event.action),
entity_kind: "patch".to_string(),
entity_id: id.clone(),
author: event.author,
summary: action_summary(&event.action),
});
}
}
}
// Sort by timestamp (chronological)
entries.sort_by(|a, b| a.timestamp.cmp(&b.timestamp));
if let Some(n) = limit {
entries.truncate(n);
}
Ok(entries)
}
/// Format log entries into a human-readable string.
pub fn format_log(entries: &[LogEntry]) -> String {
let mut out = String::new();
for entry in entries {
out.push_str(&format!(
"{} {} {:.8} {} <{}> {}\n",
entry.timestamp,
entry.event_type,
entry.entity_kind,
entry.entity_id.get(..8).unwrap_or(&entry.entity_id),
entry.author.name,
entry.summary,
));
}
out
}
/// Print the log to stdout.
pub fn print_log(repo: &Repository, limit: Option<usize>) -> Result<(), Error> {
let entries = collect_events(repo, limit)?;
if entries.is_empty() {
println!("No collab events found.");
return Ok(());
}
print!("{}", format_log(&entries));
Ok(())
}
fn action_type_name(action: &Action) -> String {
match action {
Action::IssueOpen { .. } => "IssueOpen".to_string(),
Action::IssueComment { .. } => "IssueComment".to_string(),
Action::IssueClose { .. } => "IssueClose".to_string(),
Action::IssueEdit { .. } => "IssueEdit".to_string(),
Action::IssueLabel { .. } => "IssueLabel".to_string(),
Action::IssueUnlabel { .. } => "IssueUnlabel".to_string(),
Action::IssueAssign { .. } => "IssueAssign".to_string(),
Action::IssueUnassign { .. } => "IssueUnassign".to_string(),
Action::IssueReopen => "IssueReopen".to_string(),
Action::IssueCommitLink { .. } => "IssueCommitLink".to_string(),
Action::PatchCreate { .. } => "PatchCreate".to_string(),
Action::PatchRevision { .. } => "PatchRevision".to_string(),
Action::PatchReview { .. } => "PatchReview".to_string(),
Action::PatchComment { .. } => "PatchComment".to_string(),
Action::PatchInlineComment { .. } => "PatchInlineComment".to_string(),
Action::PatchClose { .. } => "PatchClose".to_string(),
Action::PatchMerge => "PatchMerge".to_string(),
Action::Merge => "Merge".to_string(),
}
}
fn action_summary(action: &Action) -> String {
match action {
Action::IssueOpen { title, .. } => format!("open \"{}\"", title),
Action::IssueComment { body } => truncate(body, 60),
Action::IssueClose { reason } => match reason {
Some(r) => format!("close: {}", r),
None => "close".to_string(),
},
Action::IssueEdit { title, body } => {
let parts: Vec<&str> = [
title.as_ref().map(|_| "title"),
body.as_ref().map(|_| "body"),
]
.into_iter()
.flatten()
.collect();
format!("edit {}", parts.join(", "))
}
Action::IssueLabel { label } => format!("label \"{}\"", label),
Action::IssueUnlabel { label } => format!("unlabel \"{}\"", label),
Action::IssueAssign { assignee } => format!("assign \"{}\"", assignee),
Action::IssueUnassign { assignee } => format!("unassign \"{}\"", assignee),
Action::IssueReopen => "reopen".to_string(),
Action::IssueCommitLink { commit } => {
format!("commit link {}", &commit[..commit.len().min(7)])
}
Action::PatchCreate { title, .. } => format!("create \"{}\"", title),
Action::PatchRevision { body, .. } => match body {
Some(b) => format!("revision: {}", truncate(b, 50)),
None => "revision".to_string(),
},
Action::PatchReview { verdict, .. } => format!("review: {}", verdict),
Action::PatchComment { body } => truncate(body, 60),
Action::PatchInlineComment { file, line, .. } => format!("comment on {}:{}", file, line),
Action::PatchClose { reason } => match reason {
Some(r) => format!("close: {}", r),
None => "close".to_string(),
},
Action::PatchMerge => "merge".to_string(),
Action::Merge => "dag merge".to_string(),
}
}
fn truncate(s: &str, max: usize) -> String {
if s.len() <= max {
s.to_string()
} else {
format!("{}...", &s[..max])
}
}