1use std::{
4 borrow::Cow,
5 ops::Range,
6 path::{Path, PathBuf},
7 string::FromUtf8Error,
8};
9
10#[cfg(feature = "serde")]
11use serde::{ser, ser::SerializeStruct, Serialize, Serializer};
12
13use git_ext::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)]
261pub enum EofNewLine {
262 OldMissing,
263 NewMissing,
264 BothMissing,
265 NoneMissing,
266}
267
268impl Default for EofNewLine {
269 fn default() -> Self {
270 Self::NoneMissing
271 }
272}
273
274#[cfg_attr(feature = "serde", derive(Serialize), serde(rename_all = "camelCase"))]
276#[derive(Clone, Debug, PartialEq, Eq)]
277pub struct Modified {
278 pub path: PathBuf,
279 pub diff: DiffContent,
280 pub old: DiffFile,
281 pub new: DiffFile,
282}
283
284#[cfg_attr(
286 feature = "serde",
287 derive(Serialize),
288 serde(tag = "type", rename_all = "camelCase")
289)]
290#[derive(Clone, Debug, PartialEq, Eq)]
291pub enum DiffContent {
292 Binary,
294 #[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
296 Plain {
297 hunks: Hunks<Modification>,
298 stats: FileStats,
299 eof: EofNewLine,
300 },
301 Empty,
302}
303
304impl DiffContent {
305 pub fn eof(&self) -> Option<EofNewLine> {
306 match self {
307 Self::Plain { eof, .. } => Some(eof.clone()),
308 _ => None,
309 }
310 }
311
312 pub fn stats(&self) -> Option<&FileStats> {
313 match &self {
314 DiffContent::Plain { stats, .. } => Some(stats),
315 DiffContent::Empty => None,
316 DiffContent::Binary => None,
317 }
318 }
319}
320
321#[derive(Clone, Debug, PartialEq, Eq)]
323#[cfg_attr(feature = "serde", derive(Serialize), serde(rename_all = "camelCase"))]
324pub enum FileMode {
325 Blob,
327 BlobExecutable,
329 Tree,
331 Link,
333 Commit,
335}
336
337impl From<FileMode> for u32 {
338 fn from(m: FileMode) -> Self {
339 git2::FileMode::from(m).into()
340 }
341}
342
343impl From<FileMode> for i32 {
344 fn from(m: FileMode) -> Self {
345 git2::FileMode::from(m).into()
346 }
347}
348
349#[derive(Clone, Debug, PartialEq, Eq)]
351#[cfg_attr(feature = "serde", derive(Serialize), serde(rename_all = "camelCase"))]
352pub struct DiffFile {
353 pub oid: Oid,
355 pub mode: FileMode,
357}
358
359#[derive(Clone, Debug, PartialEq, Eq)]
360#[cfg_attr(
361 feature = "serde",
362 derive(Serialize),
363 serde(tag = "status", rename_all = "camelCase")
364)]
365pub enum FileDiff {
366 Added(Added),
367 Deleted(Deleted),
368 Modified(Modified),
369 Moved(Moved),
370 Copied(Copied),
371}
372
373impl FileDiff {
374 pub fn path(&self) -> &Path {
375 match self {
376 FileDiff::Added(x) => x.path.as_path(),
377 FileDiff::Deleted(x) => x.path.as_path(),
378 FileDiff::Modified(x) => x.path.as_path(),
379 FileDiff::Moved(x) => x.new_path.as_path(),
380 FileDiff::Copied(x) => x.new_path.as_path(),
381 }
382 }
383}
384
385#[cfg_attr(feature = "serde", derive(Serialize), serde(rename_all = "camelCase"))]
387#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
388pub struct FileStats {
389 pub additions: usize,
391 pub deletions: usize,
393}
394
395#[cfg_attr(feature = "serde", derive(Serialize), serde(rename_all = "camelCase"))]
397#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
398pub struct Stats {
399 pub files_changed: usize,
401 pub insertions: usize,
403 pub deletions: usize,
405}
406
407#[cfg_attr(feature = "serde", derive(Serialize), serde(rename_all = "camelCase"))]
412#[derive(Clone, Debug, PartialEq, Eq)]
413pub struct Hunk<T> {
414 pub header: Line,
415 pub lines: Vec<T>,
416 pub old: Range<u32>,
418 pub new: Range<u32>,
420}
421
422#[cfg_attr(feature = "serde", derive(Serialize))]
424#[derive(Clone, Debug, PartialEq, Eq)]
425pub struct Hunks<T>(pub Vec<Hunk<T>>);
426
427impl<T> Default for Hunks<T> {
428 fn default() -> Self {
429 Self(Default::default())
430 }
431}
432
433impl<T> Hunks<T> {
434 pub fn iter(&self) -> impl Iterator<Item = &Hunk<T>> {
435 self.0.iter()
436 }
437}
438
439impl<T> From<Vec<Hunk<T>>> for Hunks<T> {
440 fn from(hunks: Vec<Hunk<T>>) -> Self {
441 Self(hunks)
442 }
443}
444
445#[derive(Clone, Debug, PartialEq, Eq)]
447pub struct Line(pub(crate) Vec<u8>);
448
449impl Line {
450 pub fn as_bytes(&self) -> &[u8] {
451 self.0.as_slice()
452 }
453
454 pub fn from_utf8(self) -> Result<String, FromUtf8Error> {
455 String::from_utf8(self.0)
456 }
457
458 pub fn from_utf8_lossy<'a>(&'a self) -> Cow<'a, str> {
459 String::from_utf8_lossy(&self.0)
460 }
461}
462
463impl From<Vec<u8>> for Line {
464 fn from(v: Vec<u8>) -> Self {
465 Self(v)
466 }
467}
468
469impl From<String> for Line {
470 fn from(s: String) -> Self {
471 Self(s.into_bytes())
472 }
473}
474
475#[cfg(feature = "serde")]
476impl Serialize for Line {
477 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
478 where
479 S: Serializer,
480 {
481 let s = std::str::from_utf8(&self.0).map_err(ser::Error::custom)?;
482
483 serializer.serialize_str(s)
484 }
485}
486
487#[derive(Clone, Debug, PartialEq, Eq)]
490pub enum Modification {
491 Addition(Addition),
493
494 Deletion(Deletion),
496
497 Context {
499 line: Line,
500 line_no_old: u32,
501 line_no_new: u32,
502 },
503}
504
505#[cfg(feature = "serde")]
506impl Serialize for Modification {
507 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
508 where
509 S: Serializer,
510 {
511 use serde::ser::SerializeMap as _;
512
513 match self {
514 Modification::Addition(addition) => {
515 let mut map = serializer.serialize_map(Some(3))?;
516 map.serialize_entry("line", &addition.line)?;
517 map.serialize_entry("lineNo", &addition.line_no)?;
518 map.serialize_entry("type", "addition")?;
519 map.end()
520 }
521 Modification::Deletion(deletion) => {
522 let mut map = serializer.serialize_map(Some(3))?;
523 map.serialize_entry("line", &deletion.line)?;
524 map.serialize_entry("lineNo", &deletion.line_no)?;
525 map.serialize_entry("type", "deletion")?;
526 map.end()
527 }
528 Modification::Context {
529 line,
530 line_no_old,
531 line_no_new,
532 } => {
533 let mut map = serializer.serialize_map(Some(4))?;
534 map.serialize_entry("line", line)?;
535 map.serialize_entry("lineNoOld", line_no_old)?;
536 map.serialize_entry("lineNoNew", line_no_new)?;
537 map.serialize_entry("type", "context")?;
538 map.end()
539 }
540 }
541 }
542}
543
544#[derive(Clone, Debug, PartialEq, Eq)]
546pub struct Addition {
547 pub line: Line,
548 pub line_no: u32,
549}
550
551#[cfg(feature = "serde")]
552impl Serialize for Addition {
553 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
554 where
555 S: Serializer,
556 {
557 use serde::ser::SerializeStruct as _;
558
559 let mut s = serializer.serialize_struct("Addition", 3)?;
560 s.serialize_field("line", &self.line)?;
561 s.serialize_field("lineNo", &self.line_no)?;
562 s.serialize_field("type", "addition")?;
563 s.end()
564 }
565}
566
567#[derive(Clone, Debug, PartialEq, Eq)]
569pub struct Deletion {
570 pub line: Line,
571 pub line_no: u32,
572}
573
574#[cfg(feature = "serde")]
575impl Serialize for Deletion {
576 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
577 where
578 S: Serializer,
579 {
580 use serde::ser::SerializeStruct as _;
581
582 let mut s = serializer.serialize_struct("Deletion", 3)?;
583 s.serialize_field("line", &self.line)?;
584 s.serialize_field("lineNo", &self.line_no)?;
585 s.serialize_field("type", "deletion")?;
586 s.end()
587 }
588}
589
590impl Modification {
591 pub fn addition(line: impl Into<Line>, line_no: u32) -> Self {
592 Self::Addition(Addition {
593 line: line.into(),
594 line_no,
595 })
596 }
597
598 pub fn deletion(line: impl Into<Line>, line_no: u32) -> Self {
599 Self::Deletion(Deletion {
600 line: line.into(),
601 line_no,
602 })
603 }
604
605 pub fn context(line: impl Into<Line>, line_no_old: u32, line_no_new: u32) -> Self {
606 Self::Context {
607 line: line.into(),
608 line_no_old,
609 line_no_new,
610 }
611 }
612}