This repository has no description
0

Configure Feed

Select the types of activity you want to include in your feed.

core / knot2 / crates / knot-xrpc / src / patchtext.rs
7.0 kB 215 lines
1use knot_git::{Commit, FilePatch, Hunk, LineCount, LineNumber, LineOp, PatchStatus}; 2 3use crate::wire::{entry_mode_octal, fold_subject, message_body, rfc2822}; 4 5const GRAPH_WIDTH: usize = 60; 6 7fn span(start: LineNumber, lines: LineCount) -> String { 8 match lines.get() { 9 1 => format!("{}", start.get()), 10 _ => format!("{},{}", start.get(), lines.get()), 11 } 12} 13 14fn render_hunk(out: &mut String, hunk: &Hunk) { 15 out.push_str(&format!( 16 "@@ -{} +{} @@\n", 17 span(hunk.old_start, hunk.old_lines), 18 span(hunk.new_start, hunk.new_lines) 19 )); 20 hunk.lines.iter().for_each(|line| { 21 out.push(match line.op { 22 LineOp::Context => ' ', 23 LineOp::Delete => '-', 24 LineOp::Add => '+', 25 }); 26 out.push_str(&String::from_utf8_lossy(&line.text)); 27 if !line.text.ends_with(b"\n") { 28 out.push_str("\n\\ No newline at end of file\n"); 29 } 30 }); 31} 32 33fn render_file(out: &mut String, patch: &FilePatch) { 34 let (a, b) = (&patch.path, &patch.path); 35 out.push_str(&format!("diff --git a/{a} b/{b}\n")); 36 match patch.status { 37 PatchStatus::Added => { 38 let mode = patch.new_kind.map(entry_mode_octal).unwrap_or_default(); 39 out.push_str(&format!("new file mode {mode}\n")); 40 out.push_str(&format!( 41 "index {}..{}\n", 42 patch.old_oid.to_hex(), 43 patch.new_oid.to_hex() 44 )); 45 } 46 PatchStatus::Deleted => { 47 let mode = patch.old_kind.map(entry_mode_octal).unwrap_or_default(); 48 out.push_str(&format!("deleted file mode {mode}\n")); 49 out.push_str(&format!( 50 "index {}..{}\n", 51 patch.old_oid.to_hex(), 52 patch.new_oid.to_hex() 53 )); 54 } 55 PatchStatus::Modified => { 56 if patch.old_kind == patch.new_kind { 57 let mode = patch.old_kind.map(entry_mode_octal).unwrap_or_default(); 58 out.push_str(&format!( 59 "index {}..{} {mode}\n", 60 patch.old_oid.to_hex(), 61 patch.new_oid.to_hex() 62 )); 63 } else { 64 let old = patch.old_kind.map(entry_mode_octal).unwrap_or_default(); 65 let new = patch.new_kind.map(entry_mode_octal).unwrap_or_default(); 66 out.push_str(&format!("old mode {old}\nnew mode {new}\n")); 67 out.push_str(&format!( 68 "index {}..{}\n", 69 patch.old_oid.to_hex(), 70 patch.new_oid.to_hex() 71 )); 72 } 73 } 74 } 75 let old_label = match patch.status { 76 PatchStatus::Added => "/dev/null".to_string(), 77 _ => format!("a/{a}"), 78 }; 79 let new_label = match patch.status { 80 PatchStatus::Deleted => "/dev/null".to_string(), 81 _ => format!("b/{b}"), 82 }; 83 if patch.is_binary { 84 out.push_str(&format!( 85 "Binary files {old_label} and {new_label} differ\n" 86 )); 87 return; 88 } 89 if patch.hunks.is_empty() { 90 return; 91 } 92 out.push_str(&format!("--- {old_label}\n+++ {new_label}\n")); 93 patch.hunks.iter().for_each(|hunk| render_hunk(out, hunk)); 94} 95 96pub(crate) fn render_patches(patches: &[FilePatch]) -> String { 97 patches.iter().fold(String::new(), |mut out, patch| { 98 render_file(&mut out, patch); 99 out 100 }) 101} 102 103fn stat_counts(patch: &FilePatch) -> (usize, usize) { 104 patch.hunks.iter().fold((0, 0), |(added, deleted), hunk| { 105 ( 106 added + hunk.added().get() as usize, 107 deleted + hunk.deleted().get() as usize, 108 ) 109 }) 110} 111 112fn graph(added: usize, deleted: usize) -> String { 113 let total = added + deleted; 114 let (added, deleted) = if total > GRAPH_WIDTH { 115 (added * GRAPH_WIDTH / total, deleted * GRAPH_WIDTH / total) 116 } else { 117 (added, deleted) 118 }; 119 format!("{}{}", "+".repeat(added), "-".repeat(deleted)) 120} 121 122fn diffstat(patches: &[FilePatch]) -> String { 123 let width = patches 124 .iter() 125 .map(|patch| patch.path.as_str().len()) 126 .max() 127 .unwrap_or(0); 128 let rows: String = patches 129 .iter() 130 .map(|patch| { 131 if patch.is_binary { 132 format!(" {:<width$} | Bin\n", patch.path) 133 } else { 134 let (added, deleted) = stat_counts(patch); 135 format!( 136 " {:<width$} | {} {}\n", 137 patch.path, 138 added + deleted, 139 graph(added, deleted) 140 ) 141 } 142 }) 143 .collect(); 144 let (added, deleted) = patches.iter().fold((0, 0), |(a, d), patch| { 145 let (pa, pd) = stat_counts(patch); 146 (a + pa, d + pd) 147 }); 148 let files = patches.len(); 149 let mut summary = format!(" {files} file{} changed", if files == 1 { "" } else { "s" }); 150 if added > 0 { 151 summary.push_str(&format!( 152 ", {added} insertion{}(+)", 153 if added == 1 { "" } else { "s" } 154 )); 155 } 156 if deleted > 0 { 157 summary.push_str(&format!( 158 ", {deleted} deletion{}(-)", 159 if deleted == 1 { "" } else { "s" } 160 )); 161 } 162 summary.push('\n'); 163 let created: String = patches 164 .iter() 165 .filter(|patch| patch.status == PatchStatus::Added) 166 .map(|patch| { 167 format!( 168 " create mode {} {}\n", 169 patch.new_kind.map(entry_mode_octal).unwrap_or_default(), 170 patch.path 171 ) 172 }) 173 .collect(); 174 let deleted_rows: String = patches 175 .iter() 176 .filter(|patch| patch.status == PatchStatus::Deleted) 177 .map(|patch| { 178 format!( 179 " delete mode {} {}\n", 180 patch.old_kind.map(entry_mode_octal).unwrap_or_default(), 181 patch.path 182 ) 183 }) 184 .collect(); 185 format!("{rows}{summary}{created}{deleted_rows}") 186} 187 188pub(crate) fn render_format_patch(commit: &Commit, patches: &[FilePatch]) -> String { 189 let subject = fold_subject(&commit.message); 190 let body = message_body(&commit.message); 191 let mut out = format!("From {} Mon Sep 17 00:00:00 2001\n", commit.id.to_hex()); 192 out.push_str(&format!( 193 "From: {} <{}>\n", 194 commit.author.name, commit.author.email 195 )); 196 out.push_str(&format!( 197 "Date: {}\n", 198 rfc2822(commit.author.time.get(), commit.author.offset_seconds) 199 )); 200 out.push_str(&format!("Subject: [PATCH] {subject}\n")); 201 if let Some(change_id) = commit.change_id() { 202 out.push_str(&format!("Change-Id: {change_id}\n")); 203 } 204 out.push('\n'); 205 if !body.is_empty() { 206 out.push_str(&body); 207 out.push('\n'); 208 } 209 out.push_str("---\n"); 210 out.push_str(&diffstat(patches)); 211 out.push('\n'); 212 out.push_str(&render_patches(patches)); 213 out.push_str("-- \nknot\n\n"); 214 out 215}