198 lines
5.8 KiB
Rust
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.");
|
|
}
|
|
}
|