// This file is part of radicle-surf // // // Copyright (C) 2019-2020 The Radicle Team // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License version 3 or // later as published by the Free Software Foundation. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program. If not, see . //! Types that represent diff(s) in a Git repo. #![allow(dead_code, unused_variables, missing_docs)] use std::path::PathBuf; #[cfg(feature = "serde")] use serde::{ser, Serialize, Serializer}; pub mod git; /// The serializable representation of a `git diff`. /// /// A [`Diff`] can be retrieved by the following functions: /// * [`crate::Repository::diff`] /// * [`crate::Repository::diff_commit`] #[cfg_attr(feature = "serde", derive(Serialize), serde(rename_all = "camelCase"))] #[derive(Clone, Debug, Default, PartialEq, Eq)] pub struct Diff { pub added: Vec, pub deleted: Vec, pub moved: Vec, pub copied: Vec, pub modified: Vec, pub stats: Stats, } impl Diff { pub fn new() -> Self { Diff::default() } fn modified( &mut self, path: PathBuf, hunks: impl Into>, eof: Option, ) { self.modified.push(Modified { path, diff: FileDiff::Plain { hunks: hunks.into(), }, eof, }) } fn moved(&mut self, old_path: PathBuf, new_path: PathBuf) { self.moved.push(Moved { old_path, new_path }); } fn copied(&mut self, old_path: PathBuf, new_path: PathBuf) { self.copied.push(Copied { old_path, new_path }); } fn modified_binary(&mut self, path: PathBuf) { self.modified.push(Modified { path, diff: FileDiff::Binary, eof: None, }) } fn added(&mut self, path: PathBuf, diff: FileDiff) { self.added.push(Added { path, diff }) } fn deleted(&mut self, path: PathBuf, diff: FileDiff) { self.deleted.push(Deleted { path, diff }) } } /// A file that was added within a [`Diff`]. #[cfg_attr(feature = "serde", derive(Serialize))] #[derive(Clone, Debug, PartialEq, Eq)] pub struct Added { /// The path to this file, relative to the repository root. pub path: PathBuf, /// The set of [`Addition`]s to this file. pub diff: FileDiff, } /// A file that was deleted within a [`Diff`]. #[cfg_attr(feature = "serde", derive(Serialize))] #[derive(Clone, Debug, PartialEq, Eq)] pub struct Deleted { /// The path to this file, relative to the repository root. pub path: PathBuf, /// The set of [`Deletion`]s to this file. pub diff: FileDiff, } /// A file that was moved within a [`Diff`]. #[cfg_attr(feature = "serde", derive(Serialize), serde(rename_all = "camelCase"))] #[derive(Clone, Debug, PartialEq, Eq)] pub struct Moved { /// The old path to this file, relative to the repository root. pub old_path: PathBuf, /// The new path to this file, relative to the repository root. pub new_path: PathBuf, } /// A file that was copied within a [`Diff`]. #[cfg_attr(feature = "serde", derive(Serialize), serde(rename_all = "camelCase"))] #[derive(Clone, Debug, PartialEq, Eq)] pub struct Copied { /// The old path to this file, relative to the repository root. pub old_path: PathBuf, /// The new path to this file, relative to the repository root. pub new_path: PathBuf, } #[cfg_attr(feature = "serde", derive(Serialize), serde(rename_all = "camelCase"))] #[derive(Clone, Debug, PartialEq, Eq)] pub enum EofNewLine { OldMissing, NewMissing, BothMissing, } /// A file that was modified within a [`Diff`]. #[cfg_attr(feature = "serde", derive(Serialize), serde(rename_all = "camelCase"))] #[derive(Clone, Debug, PartialEq, Eq)] pub struct Modified { /// The path to this file, relative to the repository root. pub path: PathBuf, /// The set of [`Modification`]s to this file. pub diff: FileDiff, /// Was there an EOF newline present. pub eof: Option, } /// The set of changes for a given file. #[cfg_attr( feature = "serde", derive(Serialize), serde(tag = "type", rename_all = "camelCase") )] #[derive(Clone, Debug, PartialEq, Eq)] pub enum FileDiff { /// The file is a binary file and so no set of changes can be provided. Binary, /// The set of changes, as [`Hunks`] for a plaintext file. #[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))] Plain { hunks: Hunks }, } /// Statistics describing a particular [`Diff`]. #[cfg_attr(feature = "serde", derive(Serialize), serde(rename_all = "camelCase"))] #[derive(Clone, Copy, Debug, Default, Eq, PartialEq)] pub struct Stats { /// Get the total number of files changed in a [`Diff`] pub files_changed: usize, /// Get the total number of insertions in a [`Diff`]. pub insertions: usize, /// Get the total number of deletions in a [`Diff`]. pub deletions: usize, } /// A set of changes across multiple lines. /// /// The parameter `T` can be an [`Addition`], [`Deletion`], or /// [`Modification`]. #[cfg_attr(feature = "serde", derive(Serialize), serde(rename_all = "camelCase"))] #[derive(Clone, Debug, PartialEq, Eq)] pub struct Hunk { pub header: Line, pub lines: Vec, } /// A set of [`Hunk`] changes. #[cfg_attr(feature = "serde", derive(Serialize))] #[derive(Clone, Debug, PartialEq, Eq)] pub struct Hunks(pub Vec>); impl Default for Hunks { fn default() -> Self { Self(Default::default()) } } impl Hunks { pub fn iter(&self) -> impl Iterator> { self.0.iter() } } impl From>> for Hunks { fn from(hunks: Vec>) -> Self { Self(hunks) } } /// The content of a single line. #[derive(Clone, Debug, PartialEq, Eq)] pub struct Line(pub(crate) Vec); impl From> for Line { fn from(v: Vec) -> Self { Self(v) } } impl From for Line { fn from(s: String) -> Self { Self(s.into_bytes()) } } #[cfg(feature = "serde")] impl Serialize for Line { fn serialize(&self, serializer: S) -> Result where S: Serializer, { let s = std::str::from_utf8(&self.0).map_err(ser::Error::custom)?; serializer.serialize_str(s) } } /// Either the modification of a single [`Line`], or just contextual /// information. #[derive(Clone, Debug, PartialEq, Eq)] pub enum Modification { /// A lines is an addition in a file. Addition(Addition), /// A lines is a deletion in a file. Deletion(Deletion), /// A contextual line in a file, i.e. there were no changes to the line. Context { line: Line, line_no_old: u32, line_no_new: u32, }, } #[cfg(feature = "serde")] impl Serialize for Modification { fn serialize(&self, serializer: S) -> Result where S: Serializer, { use serde::ser::SerializeMap as _; const NAME: &str = "Modification"; const ADDITION: &str = "Addition"; const DELETION: &str = "Deletion"; const CONTEXT: &str = "Context"; match self { Modification::Addition(addition) => { let mut map = serializer.serialize_map(Some(3))?; map.serialize_entry("line", &addition.line)?; map.serialize_entry("lineNo", &addition.line_no)?; map.serialize_entry("type", "addition")?; map.end() }, Modification::Deletion(deletion) => { let mut map = serializer.serialize_map(Some(3))?; map.serialize_entry("line", &deletion.line)?; map.serialize_entry("lineNo", &deletion.line_no)?; map.serialize_entry("type", "deletion")?; map.end() }, Modification::Context { line, line_no_old, line_no_new, } => { let mut map = serializer.serialize_map(Some(4))?; map.serialize_entry("line", line)?; map.serialize_entry("lineNoOld", line_no_old)?; map.serialize_entry("lineNoNew", line_no_new)?; map.serialize_entry("type", "context")?; map.end() }, } } } /// A addition of a [`Line`] at the `line_no`. #[derive(Clone, Debug, PartialEq, Eq)] pub struct Addition { pub line: Line, pub line_no: u32, } #[cfg(feature = "serde")] impl Serialize for Addition { fn serialize(&self, serializer: S) -> Result where S: Serializer, { use serde::ser::SerializeStruct as _; let mut s = serializer.serialize_struct("Addition", 3)?; s.serialize_field("line", &self.line)?; s.serialize_field("lineNo", &self.line_no)?; s.serialize_field("type", "addition")?; s.end() } } /// A deletion of a [`Line`] at the `line_no`. #[derive(Clone, Debug, PartialEq, Eq)] pub struct Deletion { pub line: Line, pub line_no: u32, } #[cfg(feature = "serde")] impl Serialize for Deletion { fn serialize(&self, serializer: S) -> Result where S: Serializer, { use serde::ser::SerializeStruct as _; let mut s = serializer.serialize_struct("Deletion", 3)?; s.serialize_field("line", &self.line)?; s.serialize_field("lineNo", &self.line_no)?; s.serialize_field("type", "deletion")?; s.end() } } impl Modification { pub fn addition(line: impl Into, line_no: u32) -> Self { Self::Addition(Addition { line: line.into(), line_no, }) } pub fn deletion(line: impl Into, line_no: u32) -> Self { Self::Deletion(Deletion { line: line.into(), line_no, }) } pub fn context(line: impl Into, line_no_old: u32, line_no_new: u32) -> Self { Self::Context { line: line.into(), line_no_old, line_no_new, } } }