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