158 lines
5.3 KiB
Rust
158 lines
5.3 KiB
Rust
//! Shared commit formats and short reference decorations for log and show.
|
|
use crate::{Result, error};
|
|
use gix::bstr::ByteSlice;
|
|
|
|
pub(crate) fn decorations(commit: &gix::Commit<'_>) -> Result<String> {
|
|
let repo = commit.repo;
|
|
let head = crate::head_info(repo)?;
|
|
let mut labels = Vec::new();
|
|
for reference in repo
|
|
.references()
|
|
.map_err(|e| error("refs", e))?
|
|
.all()
|
|
.map_err(|e| error("refs", e))?
|
|
{
|
|
let mut reference = reference.map_err(|e| error("refs", e))?;
|
|
let name = reference.name().as_bstr().to_str_lossy().into_owned();
|
|
if reference
|
|
.peel_to_id()
|
|
.is_ok_and(|id| id.detach() == commit.id)
|
|
{
|
|
let short = reference.name().shorten().to_string();
|
|
labels.push(if head.branch.as_deref() == Some(name.as_ref()) {
|
|
format!("HEAD -> {short}")
|
|
} else if name.starts_with("refs/tags/") {
|
|
format!("tag: {short}")
|
|
} else {
|
|
short
|
|
});
|
|
}
|
|
}
|
|
if head.branch.is_none()
|
|
&& repo
|
|
.rev_parse_single("HEAD")
|
|
.is_ok_and(|id| id.detach() == commit.id)
|
|
{
|
|
labels.push("HEAD".into());
|
|
}
|
|
labels.sort_by(|a, b| {
|
|
b.starts_with("HEAD")
|
|
.cmp(&a.starts_with("HEAD"))
|
|
.then_with(|| b.starts_with("tag: ").cmp(&a.starts_with("tag: ")))
|
|
.then_with(|| b.cmp(a))
|
|
});
|
|
Ok(if labels.is_empty() {
|
|
String::new()
|
|
} else {
|
|
format!(" ({})", labels.join(", "))
|
|
})
|
|
}
|
|
fn relative(seconds: i64) -> String {
|
|
let now = std::time::SystemTime::now()
|
|
.duration_since(std::time::UNIX_EPOCH)
|
|
.unwrap()
|
|
.as_secs() as i64;
|
|
let mut diff = now - seconds;
|
|
if diff < 0 {
|
|
return "in the future".into();
|
|
}
|
|
let (count, unit) = if diff < 90 {
|
|
(diff, "second")
|
|
} else {
|
|
diff = (diff + 30) / 60;
|
|
if diff < 90 {
|
|
(diff, "minute")
|
|
} else {
|
|
diff = (diff + 30) / 60;
|
|
if diff < 36 {
|
|
(diff, "hour")
|
|
} else {
|
|
diff = (diff + 12) / 24;
|
|
if diff < 14 {
|
|
(diff, "day")
|
|
} else if diff < 70 {
|
|
((diff + 3) / 7, "week")
|
|
} else if diff < 365 {
|
|
((diff + 15) / 30, "month")
|
|
} else if diff < 1825 {
|
|
let total_months = (diff * 12 + 183) / 365;
|
|
let years = total_months / 12;
|
|
let months = total_months % 12;
|
|
if months != 0 {
|
|
return format!(
|
|
"{years} year{}, {months} month{} ago",
|
|
if years == 1 { "" } else { "s" },
|
|
if months == 1 { "" } else { "s" }
|
|
);
|
|
}
|
|
(years, "year")
|
|
} else {
|
|
((diff + 183) / 365, "year")
|
|
}
|
|
}
|
|
}
|
|
};
|
|
format!("{count} {unit}{} ago", if count == 1 { "" } else { "s" })
|
|
}
|
|
pub(crate) fn format(commit: &gix::Commit<'_>, template: &str) -> Result<String> {
|
|
let decoded = commit.decode().map_err(|e| error("format", e))?;
|
|
let message = decoded.message.to_str_lossy();
|
|
let author = commit.author().map_err(|e| error("author", e))?;
|
|
let committer = commit.committer().map_err(|e| error("committer", e))?;
|
|
let at = author.time().map_err(|e| error("date", e))?;
|
|
let ct = committer.time().map_err(|e| error("date", e))?;
|
|
let parents = decoded
|
|
.parents
|
|
.iter()
|
|
.map(ToString::to_string)
|
|
.collect::<Vec<_>>();
|
|
let tree = decoded.tree.to_string();
|
|
let id = commit.id.to_string();
|
|
let fields = [
|
|
("%H", id.clone()),
|
|
("%h", id[..7].into()),
|
|
("%P", parents.join(" ")),
|
|
(
|
|
"%p",
|
|
parents
|
|
.iter()
|
|
.map(|p| &p[..7])
|
|
.collect::<Vec<_>>()
|
|
.join(" "),
|
|
),
|
|
("%T", tree.clone()),
|
|
("%t", tree[..7].into()),
|
|
("%an", author.name.to_string()),
|
|
("%ae", author.email.to_string()),
|
|
("%ad", at.format_or_unix(gix::date::time::format::DEFAULT)),
|
|
("%ar", relative(at.seconds)),
|
|
("%cn", committer.name.to_string()),
|
|
("%ce", committer.email.to_string()),
|
|
("%cd", ct.format_or_unix(gix::date::time::format::DEFAULT)),
|
|
("%cr", relative(ct.seconds)),
|
|
("%s", message.lines().next().unwrap_or("").into()),
|
|
(
|
|
"%b",
|
|
message.split_once("\n\n").map_or("", |(_, b)| b).into(),
|
|
),
|
|
("%B", message.to_string()),
|
|
("%d", decorations(commit)?),
|
|
("%n", "\n".into()),
|
|
("%%", "%".into()),
|
|
];
|
|
let expanded = crate::plumbing::format(template, &message);
|
|
let mut rest = expanded.as_str();
|
|
let mut text = String::new();
|
|
while !rest.is_empty() {
|
|
if let Some((key, value)) = fields.iter().find(|(key, _)| rest.starts_with(key)) {
|
|
text.push_str(value);
|
|
rest = &rest[key.len()..];
|
|
} else {
|
|
let ch = rest.chars().next().unwrap();
|
|
text.push(ch);
|
|
rest = &rest[ch.len_utf8()..];
|
|
}
|
|
}
|
|
Ok(text)
|
|
}
|