Add Git inspection forms, find pruning, and WASI tail stdin (T76)
This commit is contained in:
+47
-46
@@ -61,6 +61,7 @@ mod history;
|
||||
mod local;
|
||||
mod inspect;
|
||||
mod plumbing;
|
||||
mod pretty;
|
||||
mod stash;
|
||||
use clap::{Parser, Subcommand};
|
||||
#[derive(Parser)]
|
||||
@@ -78,6 +79,12 @@ pub struct Cli {
|
||||
#[derive(Subcommand)]
|
||||
enum Command {
|
||||
Show {
|
||||
#[arg(short = 's', long = "no-patch")]
|
||||
no_patch: bool,
|
||||
#[arg(long)]
|
||||
format: Option<String>,
|
||||
#[arg(long)]
|
||||
pretty: Option<String>,
|
||||
#[arg(long)]
|
||||
stat: bool,
|
||||
#[arg(long)]
|
||||
@@ -224,6 +231,8 @@ enum Command {
|
||||
no_gpg_sign: bool,
|
||||
},
|
||||
Log {
|
||||
#[arg(long, num_args = 0..=1, default_missing_value = "short", require_equals = true, value_parser = ["short"])]
|
||||
decorate: Option<String>,
|
||||
#[arg(long)]
|
||||
name_only: bool,
|
||||
#[arg(long)]
|
||||
@@ -988,6 +997,7 @@ pub fn run(cli: Cli) -> Result<i32> {
|
||||
}
|
||||
}
|
||||
Command::Log {
|
||||
decorate,
|
||||
name_only,
|
||||
no_renames,
|
||||
diff_merges,
|
||||
@@ -1063,49 +1073,7 @@ pub fn run(cli: Cli) -> Result<i32> {
|
||||
.as_deref()
|
||||
.map(|s| s.strip_prefix("format:").unwrap_or(s)))
|
||||
{
|
||||
let author = c.author().map_err(|e| error("log author", e))?;
|
||||
let date = author
|
||||
.time()
|
||||
.map_err(|e| error("log date", e))?
|
||||
.format_or_unix(gix::date::time::format::DEFAULT);
|
||||
let mut labels = Vec::new();
|
||||
for reference in repository
|
||||
.references()
|
||||
.map_err(|e| error("log refs", e))?
|
||||
.all()
|
||||
.map_err(|e| error("log refs", e))?
|
||||
{
|
||||
let reference = reference.map_err(|e| error("log refs", e))?;
|
||||
let name = reference.name().as_bstr().to_str_lossy();
|
||||
if repository
|
||||
.rev_parse_single(name.as_ref())
|
||||
.map(|id| id.to_string() == c.id.to_string())
|
||||
.unwrap_or(false)
|
||||
{
|
||||
let short = reference.name().shorten().to_string();
|
||||
labels.push(
|
||||
if head_info(&repository)?.branch.as_deref() == Some(name.as_ref())
|
||||
{
|
||||
format!("HEAD -> {short}")
|
||||
} else {
|
||||
short
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
let decorations = if labels.is_empty() {
|
||||
String::new()
|
||||
} else {
|
||||
format!(" ({})", labels.join(", "))
|
||||
};
|
||||
let text = plumbing::format(template, &message)
|
||||
.replace("%H", &c.id.to_string())
|
||||
.replace("%h", &c.id.to_string()[..7])
|
||||
.replace("%s", message.lines().next().unwrap_or(""))
|
||||
.replace("%an", &author.name.to_string())
|
||||
.replace("%ae", &author.email.to_string())
|
||||
.replace("%ad", &date)
|
||||
.replace("%d", &decorations);
|
||||
let text = pretty::format(&c, template)?;
|
||||
if pretty.is_some() {
|
||||
if !first {
|
||||
println!();
|
||||
@@ -1117,15 +1085,24 @@ pub fn run(cli: Cli) -> Result<i32> {
|
||||
first = false;
|
||||
} else if oneline {
|
||||
println!(
|
||||
"{} {}",
|
||||
"{}{} {}",
|
||||
&c.id.to_string()[..7],
|
||||
if decorate.is_some() {
|
||||
pretty::decorations(&c)?
|
||||
} else {
|
||||
String::new()
|
||||
},
|
||||
message.lines().next().unwrap_or("")
|
||||
);
|
||||
} else {
|
||||
if !first {
|
||||
println!();
|
||||
}
|
||||
output::commit(&c)?;
|
||||
if decorate.is_some() {
|
||||
output::decorated_commit(&c, &pretty::decorations(&c)?)?;
|
||||
} else {
|
||||
output::commit(&c)?;
|
||||
}
|
||||
first = false;
|
||||
}
|
||||
if name_only && !diffs.is_empty() {
|
||||
@@ -1548,6 +1525,9 @@ pub fn run(cli: Cli) -> Result<i32> {
|
||||
)?;
|
||||
}
|
||||
Command::Show {
|
||||
no_patch,
|
||||
format,
|
||||
pretty,
|
||||
revision,
|
||||
stat,
|
||||
oneline,
|
||||
@@ -1565,6 +1545,22 @@ pub fn run(cli: Cli) -> Result<i32> {
|
||||
.map_err(|e| error("show", e))?;
|
||||
} else {
|
||||
let commit = object.peel_to_commit().map_err(|e| error("show", e))?;
|
||||
if no_patch {
|
||||
if let Some(template) = format.as_deref().or(pretty
|
||||
.as_deref()
|
||||
.map(|s| s.strip_prefix("format:").unwrap_or(s)))
|
||||
{
|
||||
let text = pretty::format(&commit, template)?;
|
||||
if pretty.is_some() && format.is_none() {
|
||||
print!("{text}");
|
||||
} else {
|
||||
println!("{text}");
|
||||
}
|
||||
} else {
|
||||
output::commit(&commit)?;
|
||||
}
|
||||
return Ok(0);
|
||||
}
|
||||
let merge_commit = commit.parent_ids().count() > 1;
|
||||
if merge_commit && !stat {
|
||||
let mut common: Option<std::collections::BTreeSet<String>> = None;
|
||||
@@ -1592,7 +1588,12 @@ pub fn run(cli: Cli) -> Result<i32> {
|
||||
return Ok(129);
|
||||
}
|
||||
}
|
||||
if oneline {
|
||||
if let Some(template) = format.as_deref().or(pretty
|
||||
.as_deref()
|
||||
.map(|s| s.strip_prefix("format:").unwrap_or(s)))
|
||||
{
|
||||
println!("{}\n", pretty::format(&commit, template)?);
|
||||
} else if oneline {
|
||||
let decoded = commit.decode().map_err(|e| error("show", e))?;
|
||||
println!(
|
||||
"{} {}",
|
||||
|
||||
@@ -2,10 +2,13 @@
|
||||
use crate::changes::{FileDiff, Kind, Status};
|
||||
use gix::bstr::ByteSlice;
|
||||
pub(crate) fn commit(commit: &gix::Commit<'_>) -> crate::Result<()> {
|
||||
decorated_commit(commit, "")
|
||||
}
|
||||
pub(crate) fn decorated_commit(commit: &gix::Commit<'_>, decorations: &str) -> crate::Result<()> {
|
||||
let decoded = commit
|
||||
.decode()
|
||||
.map_err(|e| crate::repo::error("commit output", e))?;
|
||||
println!("commit {}", commit.id);
|
||||
println!("commit {}{}", commit.id, decorations);
|
||||
if decoded.parents.len() > 1 {
|
||||
println!(
|
||||
"Merge: {}",
|
||||
|
||||
@@ -0,0 +1,157 @@
|
||||
//! 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)
|
||||
}
|
||||
Reference in New Issue
Block a user