1use std::{
4 borrow::Cow,
5 ops::Range,
6 path::{Path, PathBuf},
7 string::FromUtf8Error,
8};
9
10#[cfg(feature = "serde")]
11use serde::{Serialize, Serializer, ser, ser::SerializeStruct};
12
13use radicle_oid::Oid;
14
15pub mod git;
16
17#[cfg_attr(feature = "serde", derive(Serialize))]
23#[derive(Clone, Debug, Default, PartialEq, Eq)]
24pub struct Diff {
25 files: Vec<FileDiff>,
26 stats: Stats,
27}
28
29impl Diff {
30 pub(crate) fn new() -> Self {
32 Diff::default()
33 }
34
35 pub fn files(&self) -> impl Iterator<Item = &FileDiff> {
37 self.files.iter()
38 }
39
40 pub fn into_files(self) -> Vec<FileDiff> {
42 self.files
43 }
44
45 pub fn added(&self) -> impl Iterator<Item = &Added> {
46 self.files().filter_map(|x| match x {
47 FileDiff::Added(a) => Some(a),
48 _ => None,
49 })
50 }
51
52 pub fn deleted(&self) -> impl Iterator<Item = &Deleted> {
53 self.files().filter_map(|x| match x {
54 FileDiff::Deleted(a) => Some(a),
55 _ => None,
56 })
57 }
58
59 pub fn moved(&self) -> impl Iterator<Item = &Moved> {
60 self.files().filter_map(|x| match x {
61 FileDiff::Moved(a) => Some(a),
62 _ => None,
63 })
64 }
65
66 pub fn modified(&self) -> impl Iterator<Item = &Modified> {
67 self.files().filter_map(|x| match x {
68 FileDiff::Modified(a) => Some(a),
69 _ => None,
70 })
71 }
72
73 pub fn copied(&self) -> impl Iterator<Item = &Copied> {
74 self.files().filter_map(|x| match x {
75 FileDiff::Copied(a) => Some(a),
76 _ => None,
77 })
78 }
79
80 pub fn stats(&self) -> &Stats {
81 &self.stats
82 }
83
84 fn update_stats(&mut self, diff: &DiffContent) {
85 self.stats.files_changed += 1;
86 if let DiffContent::Plain { hunks, .. } = diff {
87 for h in hunks.iter() {
88 for l in &h.lines {
89 match l {
90 Modification::Addition(_) => self.stats.insertions += 1,
91 Modification::Deletion(_) => self.stats.deletions += 1,
92 _ => (),
93 }
94 }
95 }
96 }
97 }
98
99 pub fn insert_modified(
100 &mut self,
101 path: PathBuf,
102 diff: DiffContent,
103 old: DiffFile,
104 new: DiffFile,
105 ) {
106 self.update_stats(&diff);
107 let diff = FileDiff::Modified(Modified {
108 path,
109 diff,
110 old,
111 new,
112 });
113 self.files.push(diff);
114 }
115
116 pub fn insert_moved(
117 &mut self,
118 old_path: PathBuf,
119 new_path: PathBuf,
120 old: DiffFile,
121 new: DiffFile,
122 content: DiffContent,
123 ) {
124 self.update_stats(&DiffContent::Empty);
125 let diff = FileDiff::Moved(Moved {
126 old_path,
127 new_path,
128 old,
129 new,
130 diff: content,
131 });
132 self.files.push(diff);
133 }
134
135 pub fn insert_copied(
136 &mut self,
137 old_path: PathBuf,
138 new_path: PathBuf,
139 old: DiffFile,
140 new: DiffFile,
141 content: DiffContent,
142 ) {
143 self.update_stats(&DiffContent::Empty);
144 let diff = FileDiff::Copied(Copied {
145 old_path,
146 new_path,
147 old,
148 new,
149 diff: content,
150 });
151 self.files.push(diff);
152 }
153
154 pub fn insert_added(&mut self, path: PathBuf, diff: DiffContent, new: DiffFile) {
155 self.update_stats(&diff);
156 let diff = FileDiff::Added(Added { path, diff, new });
157 self.files.push(diff);
158 }
159
160 pub fn insert_deleted(&mut self, path: PathBuf, diff: DiffContent, old: DiffFile) {
161 self.update_stats(&diff);
162 let diff = FileDiff::Deleted(Deleted { path, diff, old });
163 self.files.push(diff);
164 }
165}
166
167#[cfg_attr(feature = "serde", derive(Serialize))]
169#[derive(Clone, Debug, PartialEq, Eq)]
170pub struct Added {
171 pub path: PathBuf,
173 pub diff: DiffContent,
174 pub new: DiffFile,
175}
176
177#[cfg_attr(feature = "serde", derive(Serialize))]
179#[derive(Clone, Debug, PartialEq, Eq)]
180pub struct Deleted {
181 pub path: PathBuf,
183 pub diff: DiffContent,
184 pub old: DiffFile,
185}
186
187#[derive(Clone, Debug, PartialEq, Eq)]
189pub struct Moved {
190 pub old_path: PathBuf,
192 pub old: DiffFile,
193 pub new_path: PathBuf,
195 pub new: DiffFile,
196 pub diff: DiffContent,
197}
198
199#[cfg(feature = "serde")]
200impl Serialize for Moved {
201 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
202 where
203 S: Serializer,
204 {
205 if self.old == self.new {
206 let mut state = serializer.serialize_struct("Moved", 3)?;
207 state.serialize_field("oldPath", &self.old_path)?;
208 state.serialize_field("newPath", &self.new_path)?;
209 state.serialize_field("current", &self.new)?;
210 state.end()
211 } else {
212 let mut state = serializer.serialize_struct("Moved", 5)?;
213 state.serialize_field("oldPath", &self.old_path)?;
214 state.serialize_field("newPath", &self.new_path)?;
215 state.serialize_field("old", &self.old)?;
216 state.serialize_field("new", &self.new)?;
217 state.serialize_field("diff", &self.diff)?;
218 state.end()
219 }
220 }
221}
222
223#[derive(Clone, Debug, PartialEq, Eq)]
225pub struct Copied {
226 pub old_path: PathBuf,
228 pub new_path: PathBuf,
230 pub old: DiffFile,
231 pub new: DiffFile,
232 pub diff: DiffContent,
233}
234
235#[cfg(feature = "serde")]
236impl Serialize for Copied {
237 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
238 where
239 S: Serializer,
240 {
241 if self.old == self.new {
242 let mut state = serializer.serialize_struct("Copied", 3)?;
243 state.serialize_field("oldPath", &self.old_path)?;
244 state.serialize_field("newPath", &self.new_path)?;
245 state.serialize_field("current", &self.new)?;
246 state.end()
247 } else {
248 let mut state = serializer.serialize_struct("Copied", 5)?;
249 state.serialize_field("oldPath", &self.old_path)?;
250 state.serialize_field("newPath", &self.new_path)?;
251 state.serialize_field("old", &self.old)?;
252 state.serialize_field("new", &self.new)?;
253 state.serialize_field("diff", &self.diff)?;
254 state.end()
255 }
256 }
257}
258
259#[cfg_attr(feature = "serde", derive(Serialize), serde(rename_all = "camelCase"))]
260#[derive(Clone, Debug, PartialEq, Eq, Default)]
261pub enum EofNewLine {
262 OldMissing,
263 NewMissing,
264 BothMissing,
265 #[default]
266 NoneMissing,
267}
268
269#[cfg_attr(feature = "serde", derive(Serialize), serde(rename_all = "camelCase"))]
271#[derive(Clone, Debug, PartialEq, Eq)]
272pub struct Modified {
273 pub path: PathBuf,
274 pub diff: DiffContent,
275 pub old: DiffFile,
276 pub new: DiffFile,
277}
278
279#[cfg_attr(
281 feature = "serde",
282 derive(Serialize),
283 serde(tag = "type", rename_all = "camelCase")
284)]
285#[derive(Clone, Debug, PartialEq, Eq)]
286pub enum DiffContent {
287 Binary,
289 #[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
291 Plain {
292 hunks: Hunks<Modification>,
293 stats: FileStats,
294 eof: EofNewLine,
295 },
296 Empty,
297}
298
299impl DiffContent {
300 pub fn eof(&self) -> Option<EofNewLine> {
301 match self {
302 Self::Plain { eof, .. } => Some(eof.clone()),
303 _ => None,
304 }
305 }
306
307 pub fn stats(&self) -> Option<&FileStats> {
308 match &self {
309 DiffContent::Plain { stats, .. } => Some(stats),
310 DiffContent::Empty => None,
311 DiffContent::Binary => None,
312 }
313 }
314}
315
316#[derive(Clone, Debug, PartialEq, Eq)]
318#[cfg_attr(feature = "serde", derive(Serialize), serde(rename_all = "camelCase"))]
319pub enum FileMode {
320 Blob,
322 BlobExecutable,
324 Tree,
326 Link,
328 Commit,
330}
331
332impl From<FileMode> for u32 {
333 fn from(m: FileMode) -> Self {
334 git2::FileMode::from(m).into()
335 }
336}
337
338impl From<FileMode> for i32 {
339 fn from(m: FileMode) -> Self {
340 git2::FileMode::from(m).into()
341 }
342}
343
344#[derive(Clone, Debug, PartialEq, Eq)]
346#[cfg_attr(feature = "serde", derive(Serialize), serde(rename_all = "camelCase"))]
347pub struct DiffFile {
348 pub oid: Oid,
350 pub mode: FileMode,
352}
353
354#[derive(Clone, Debug, PartialEq, Eq)]
355#[cfg_attr(
356 feature = "serde",
357 derive(Serialize),
358 serde(tag = "status", rename_all = "camelCase")
359)]
360pub enum FileDiff {
361 Added(Added),
362 Deleted(Deleted),
363 Modified(Modified),
364 Moved(Moved),
365 Copied(Copied),
366}
367
368impl FileDiff {
369 pub fn path(&self) -> &Path {
370 match self {
371 FileDiff::Added(x) => x.path.as_path(),
372 FileDiff::Deleted(x) => x.path.as_path(),
373 FileDiff::Modified(x) => x.path.as_path(),
374 FileDiff::Moved(x) => x.new_path.as_path(),
375 FileDiff::Copied(x) => x.new_path.as_path(),
376 }
377 }
378}
379
380#[cfg_attr(feature = "serde", derive(Serialize), serde(rename_all = "camelCase"))]
382#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
383pub struct FileStats {
384 pub additions: usize,
386 pub deletions: usize,
388}
389
390#[cfg_attr(feature = "serde", derive(Serialize), serde(rename_all = "camelCase"))]
392#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
393pub struct Stats {
394 pub files_changed: usize,
396 pub insertions: usize,
398 pub deletions: usize,
400}
401
402#[cfg_attr(feature = "serde", derive(Serialize), serde(rename_all = "camelCase"))]
407#[derive(Clone, Debug, PartialEq, Eq)]
408pub struct Hunk<T> {
409 pub header: Line,
410 pub lines: Vec<T>,
411 pub old: Range<u32>,
413 pub new: Range<u32>,
415}
416
417#[cfg_attr(feature = "serde", derive(Serialize))]
419#[derive(Clone, Debug, PartialEq, Eq)]
420pub struct Hunks<T>(pub Vec<Hunk<T>>);
421
422impl<T> Default for Hunks<T> {
423 fn default() -> Self {
424 Self(Default::default())
425 }
426}
427
428impl<T> Hunks<T> {
429 pub fn iter(&self) -> impl Iterator<Item = &Hunk<T>> {
430 self.0.iter()
431 }
432}
433
434impl<T> From<Vec<Hunk<T>>> for Hunks<T> {
435 fn from(hunks: Vec<Hunk<T>>) -> Self {
436 Self(hunks)
437 }
438}
439
440#[derive(Clone, Debug, PartialEq, Eq)]
442pub struct Line(pub(crate) Vec<u8>);
443
444impl Line {
445 pub fn as_bytes(&self) -> &[u8] {
446 self.0.as_slice()
447 }
448
449 pub fn from_utf8(self) -> Result<String, FromUtf8Error> {
450 String::from_utf8(self.0)
451 }
452
453 pub fn from_utf8_lossy<'a>(&'a self) -> Cow<'a, str> {
454 String::from_utf8_lossy(&self.0)
455 }
456}
457
458impl From<Vec<u8>> for Line {
459 fn from(v: Vec<u8>) -> Self {
460 Self(v)
461 }
462}
463
464impl From<String> for Line {
465 fn from(s: String) -> Self {
466 Self(s.into_bytes())
467 }
468}
469
470#[cfg(feature = "serde")]
471impl Serialize for Line {
472 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
473 where
474 S: Serializer,
475 {
476 let s = std::str::from_utf8(&self.0).map_err(ser::Error::custom)?;
477
478 serializer.serialize_str(s)
479 }
480}
481
482#[derive(Clone, Debug, PartialEq, Eq)]
485pub enum Modification {
486 Addition(Addition),
488
489 Deletion(Deletion),
491
492 Context {
494 line: Line,
495 line_no_old: u32,
496 line_no_new: u32,
497 },
498}
499
500#[cfg(feature = "serde")]
501impl Serialize for Modification {
502 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
503 where
504 S: Serializer,
505 {
506 use serde::ser::SerializeMap as _;
507
508 match self {
509 Modification::Addition(addition) => {
510 let mut map = serializer.serialize_map(Some(3))?;
511 map.serialize_entry("line", &addition.line)?;
512 map.serialize_entry("lineNo", &addition.line_no)?;
513 map.serialize_entry("type", "addition")?;
514 map.end()
515 }
516 Modification::Deletion(deletion) => {
517 let mut map = serializer.serialize_map(Some(3))?;
518 map.serialize_entry("line", &deletion.line)?;
519 map.serialize_entry("lineNo", &deletion.line_no)?;
520 map.serialize_entry("type", "deletion")?;
521 map.end()
522 }
523 Modification::Context {
524 line,
525 line_no_old,
526 line_no_new,
527 } => {
528 let mut map = serializer.serialize_map(Some(4))?;
529 map.serialize_entry("line", line)?;
530 map.serialize_entry("lineNoOld", line_no_old)?;
531 map.serialize_entry("lineNoNew", line_no_new)?;
532 map.serialize_entry("type", "context")?;
533 map.end()
534 }
535 }
536 }
537}
538
539#[derive(Clone, Debug, PartialEq, Eq)]
541pub struct Addition {
542 pub line: Line,
543 pub line_no: u32,
544}
545
546#[cfg(feature = "serde")]
547impl Serialize for Addition {
548 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
549 where
550 S: Serializer,
551 {
552 use serde::ser::SerializeStruct as _;
553
554 let mut s = serializer.serialize_struct("Addition", 3)?;
555 s.serialize_field("line", &self.line)?;
556 s.serialize_field("lineNo", &self.line_no)?;
557 s.serialize_field("type", "addition")?;
558 s.end()
559 }
560}
561
562#[derive(Clone, Debug, PartialEq, Eq)]
564pub struct Deletion {
565 pub line: Line,
566 pub line_no: u32,
567}
568
569#[cfg(feature = "serde")]
570impl Serialize for Deletion {
571 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
572 where
573 S: Serializer,
574 {
575 use serde::ser::SerializeStruct as _;
576
577 let mut s = serializer.serialize_struct("Deletion", 3)?;
578 s.serialize_field("line", &self.line)?;
579 s.serialize_field("lineNo", &self.line_no)?;
580 s.serialize_field("type", "deletion")?;
581 s.end()
582 }
583}
584
585impl Modification {
586 pub fn addition(line: impl Into<Line>, line_no: u32) -> Self {
587 Self::Addition(Addition {
588 line: line.into(),
589 line_no,
590 })
591 }
592
593 pub fn deletion(line: impl Into<Line>, line_no: u32) -> Self {
594 Self::Deletion(Deletion {
595 line: line.into(),
596 line_no,
597 })
598 }
599
600 pub fn context(line: impl Into<Line>, line_no_old: u32, line_no_new: u32) -> Self {
601 Self::Context {
602 line: line.into(),
603 line_no_old,
604 line_no_new,
605 }
606 }
607}