Files
sausage/userland/git/src/output.rs
T

198 lines
5.8 KiB
Rust

//! Git's text summaries for small, textual diffs.
use crate::changes::{FileDiff, Kind, Status};
use gix::bstr::ByteSlice;
pub(crate) fn commit(commit: &gix::Commit<'_>) -> crate::Result<()> {
let decoded = commit
.decode()
.map_err(|e| crate::repo::error("commit output", e))?;
println!("commit {}", commit.id);
if decoded.parents.len() > 1 {
println!(
"Merge: {}",
decoded
.parents
.iter()
.map(|id| id.to_string()[..7].to_owned())
.collect::<Vec<_>>()
.join(" ")
);
}
let author = commit
.author()
.map_err(|e| crate::repo::error("commit author", e))?;
let time = author
.time()
.map_err(|e| crate::repo::error("commit date", e))?;
println!(
"Author: {} <{}>\nDate: {}\n\n {}",
author.name,
author.email,
time.format_or_unix(gix::date::time::format::DEFAULT),
decoded
.message
.to_str_lossy()
.trim_end()
.replace('\n', "\n ")
);
Ok(())
}
pub(crate) fn author_date(commit: &gix::Commit<'_>) -> crate::Result<()> {
let author = commit
.author()
.map_err(|e| crate::repo::error("author", e))?;
println!(
" Date: {}",
author
.time()
.map_err(|e| crate::repo::error("date", e))?
.format_or_unix(gix::date::time::format::DEFAULT)
);
Ok(())
}
pub(crate) fn long_status(s: &Status) {
let branch = s
.head
.branch
.as_deref()
.unwrap_or("HEAD")
.trim_start_matches("refs/heads/");
if s.head.detached {
println!(
"HEAD detached at {}",
&s.head.id.as_deref().unwrap_or("HEAD")[..7]
);
} else {
println!("On branch {branch}");
}
if s.head.unborn {
println!("\nNo commits yet\n");
}
if !s.staged.is_empty() {
println!("Changes to be committed:");
println!(
" (use \"git {} <file>...\" to unstage)",
if s.head.unborn {
"rm --cached"
} else {
"restore --staged"
}
);
for c in &s.staged {
println!("\t{:12}{}", label(&c.status), c.path);
}
println!();
}
if !s.unstaged.is_empty() {
println!("Changes not staged for commit:");
println!(" (use \"git add <file>...\" to update what will be committed)");
println!(" (use \"git restore <file>...\" to discard changes in working directory)");
for c in &s.unstaged {
println!("\t{:12}{}", label(&c.status), c.path);
}
println!();
}
if !s.untracked.is_empty() {
println!(
"Untracked files:\n (use \"git add <file>...\" to include in what will be committed)"
);
for path in &s.untracked {
println!("\t{path}");
}
println!();
}
if s.staged.is_empty() {
if !s.unstaged.is_empty() {
println!("no changes added to commit (use \"git add\" and/or \"git commit -a\")");
} else if !s.untracked.is_empty() {
println!(
"nothing added to commit but untracked files present (use \"git add\" to track)"
);
} else if s.head.unborn {
println!("nothing to commit (create/copy files and use \"git add\" to track)");
} else {
println!("nothing to commit, working tree clean");
}
}
}
fn label(kind: &Kind) -> &'static str {
match kind {
Kind::Added => "new file:",
Kind::Modified => "modified:",
Kind::Deleted => "deleted:",
Kind::Renamed => "renamed:",
}
}
pub(crate) fn stat(diffs: &[FileDiff], summary: bool) {
summarize(diffs, true, summary);
}
pub(crate) fn commit_stat(diffs: &[FileDiff]) {
summarize(diffs, false, true);
}
fn summarize(diffs: &[FileDiff], names: bool, summary: bool) {
let width = diffs.iter().map(|d| d.path.len()).max().unwrap_or(0);
let mut insertions = 0;
let mut deletions = 0;
for diff in diffs {
let added = diff
.patch
.lines()
.filter(|l| l.starts_with('+') && !l.starts_with("+++"))
.count();
let removed = diff
.patch
.lines()
.filter(|l| l.starts_with('-') && !l.starts_with("---"))
.count();
insertions += added;
deletions += removed;
if names {
println!(
" {:width$} | {} {}{}",
diff.path,
added + removed,
"+".repeat(added),
"-".repeat(removed)
);
}
}
if diffs.is_empty() {
return;
}
print!(
" {} file{} changed",
diffs.len(),
if diffs.len() == 1 { "" } else { "s" }
);
if insertions > 0 {
print!(
", {insertions} insertion{}(+)",
if insertions == 1 { "" } else { "s" }
);
}
if deletions > 0 {
print!(
", {deletions} deletion{}(-)",
if deletions == 1 { "" } else { "s" }
);
}
println!();
if summary {
for d in diffs {
match d.status {
Kind::Added => println!(" create mode 100644 {}", d.path),
Kind::Deleted => println!(" delete mode 100644 {}", d.path),
_ => (),
}
}
}
}
pub(crate) fn conflicts(paths: &[String], merge: bool) {
for path in paths {
println!("Auto-merging {path}");
println!("CONFLICT (content): Merge conflict in {path}");
}
if merge && !paths.is_empty() {
println!("Automatic merge failed; fix conflicts and then commit the result.");
}
}