Something went wrong. Try again.
This repository has no description
Something went wrong. Try again.
7.1 kB · 216 lines
Rust
at main
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217//! patch text rendering, copied from knot-xrpc/src/patchtext.rs.
use std::fmt::Write as _;
use knot_git::{ BinaryDiff, Commit, EntryKind, FilePatch, Hunk, LineCount, LineNumber, LineOp, PatchBody, PatchStatus, quote_path,};use super::wire::{entry_mode_octal, fold_subject, message_body, rfc2822};
const GRAPH_WIDTH: usize = 60;
fn entry_mode(kind: Option<EntryKind>) -> String { kind.map(entry_mode_octal).unwrap_or_default()}
fn span(start: LineNumber, lines: LineCount) -> String { let start = start.get(); match lines.get() { 1 => format!("{start}"), n => format!("{start},{n}"), }}
fn render_hunk(out: &mut String, hunk: &Hunk) { let _ = writeln!( out, "@@ -{} +{} @@", span(hunk.old_start, hunk.old_lines), span(hunk.new_start, hunk.new_lines) ); for line in &hunk.lines { out.push(match line.op { LineOp::Context => ' ', LineOp::Delete => '-', LineOp::Add => '+', }); out.push_str(&String::from_utf8_lossy(&line.text)); if !line.text.ends_with(b"\n") { out.push_str("\n\\ No newline at end of file\n"); } }}
fn render_file(out: &mut String, patch: &FilePatch) { let old_side = quote_path(&format!("a/{}", patch.path)); let new_side = quote_path(&format!("b/{}", patch.path)); let index = format!( "index {}..{}", patch.old_oid.to_hex(), patch.new_oid.to_hex() ); let _ = writeln!(out, "diff --git {old_side} {new_side}"); match patch.status { PatchStatus::Added => { let _ = writeln!(out, "new file mode {}\n{index}", entry_mode(patch.new_kind)); } PatchStatus::Deleted => { let _ = writeln!( out, "deleted file mode {}\n{index}", entry_mode(patch.old_kind) ); } PatchStatus::Modified if patch.old_kind == patch.new_kind => { let _ = writeln!(out, "{index} {}", entry_mode(patch.old_kind)); } PatchStatus::Modified => { let _ = writeln!( out, "old mode {}\nnew mode {}", entry_mode(patch.old_kind), entry_mode(patch.new_kind) ); if patch.old_oid != patch.new_oid { let _ = writeln!(out, "{index}"); } } } let old_label = if patch.status == PatchStatus::Added { "/dev/null" } else { &old_side }; let new_label = if patch.status == PatchStatus::Deleted { "/dev/null" } else { &new_side }; match &patch.body { PatchBody::Binary(BinaryDiff::Encoded { text, .. }) => out.push_str(text), PatchBody::Binary(BinaryDiff::Omitted(_)) => { let _ = writeln!(out, "Binary files {old_label} and {new_label} differ"); } PatchBody::Binary(BinaryDiff::Unchanged(_)) => {} PatchBody::Text(hunks) if hunks.is_empty() => {} PatchBody::Text(hunks) => { let _ = writeln!(out, "--- {old_label}\n+++ {new_label}"); for hunk in hunks { render_hunk(out, hunk); } } }}
pub fn render_patches(patches: &[FilePatch]) -> String { let mut out = String::new(); for patch in patches { render_file(&mut out, patch); } out}
fn stat_counts(patch: &FilePatch) -> (usize, usize) { patch.hunks().iter().fold((0, 0), |(added, deleted), hunk| { ( added + hunk.added().get() as usize, deleted + hunk.deleted().get() as usize, ) })}
fn graph(added: usize, deleted: usize) -> String { let total = added + deleted; let (added, deleted) = if total > GRAPH_WIDTH { (added * GRAPH_WIDTH / total, deleted * GRAPH_WIDTH / total) } else { (added, deleted) }; format!("{}{}", "+".repeat(added), "-".repeat(deleted))}
fn diffstat(patches: &[FilePatch]) -> String { let named: Vec<(String, &FilePatch)> = patches .iter() .map(|patch| (quote_path(patch.path.as_str()), patch)) .collect(); let width = named.iter().map(|(name, _)| name.len()).max().unwrap_or(0); let rows: String = named .iter() .map(|(name, patch)| match &patch.body { PatchBody::Binary(BinaryDiff::Unchanged(_)) => format!(" {name:<width$} | Bin\n"), PatchBody::Binary(binary) => { let sizes = binary.sizes(); format!( " {name:<width$} | Bin {} -> {} bytes\n", sizes.old, sizes.new ) } PatchBody::Text(_) => { let (added, deleted) = stat_counts(patch); let total = added + deleted; format!(" {name:<width$} | {total} {}\n", graph(added, deleted)) } }) .collect(); let (added, deleted) = patches.iter().fold((0, 0), |(a, d), patch| { let (pa, pd) = stat_counts(patch); (a + pa, d + pd) }); let files = patches.len(); let file_suffix = if files == 1 { "" } else { "s" }; let mut summary = format!(" {files} file{file_suffix} changed"); if added > 0 { let suffix = if added == 1 { "" } else { "s" }; let _ = write!(summary, ", {added} insertion{suffix}(+)"); } if deleted > 0 { let suffix = if deleted == 1 { "" } else { "s" }; let _ = write!(summary, ", {deleted} deletion{suffix}(-)"); } summary.push('\n'); let modes: String = named .iter() .filter_map(|(name, patch)| { let (old, new) = (entry_mode(patch.old_kind), entry_mode(patch.new_kind)); match patch.status { PatchStatus::Added => Some(format!(" create mode {new} {name}\n")), PatchStatus::Deleted => Some(format!(" delete mode {old} {name}\n")), PatchStatus::Modified if patch.old_kind != patch.new_kind => { Some(format!(" mode change {old} => {new} {name}\n")) } PatchStatus::Modified => None, } }) .collect(); format!("{rows}{summary}{modes}")}
pub fn render_format_patch(commit: &Commit, patches: &[FilePatch]) -> String { let subject = fold_subject(&commit.message); let body = message_body(&commit.message); let mut out = format!("From {} Mon Sep 17 00:00:00 2001\n", commit.id.to_hex()); let _ = write!( out, "From: {} <{}>\nDate: {}\nSubject: [PATCH] {subject}\n", commit.author.name, commit.author.email, rfc2822(commit.author.time.get(), commit.author.offset_seconds) ); if let Some(change_id) = commit.change_id() { let _ = writeln!(out, "Change-Id: {change_id}"); } out.push('\n'); if !body.is_empty() { out.push_str(&body); out.push('\n'); } let _ = write!( out, "---\n{}\n{}-- \nknot\n\n", diffstat(patches), render_patches(patches) ); out}