From 248e23e6cfceb17a0d6b27e6b3700ee0ce542e70 Mon Sep 17 00:00:00 2001 From: Alexis Sellier Date: Wed, 24 May 2023 16:42:15 +0200 Subject: [PATCH] Add file information to diff Adds file mode and file OIDs to the file diff. --- radicle-surf/src/diff.rs | 49 +++++++++++++++++++++--- radicle-surf/src/diff/git.rs | 71 ++++++++++++++++++++++++++++++----- radicle-surf/t/src/diff.rs | 72 ++++++++++++++++++++++++++++-------- 3 files changed, 161 insertions(+), 31 deletions(-) diff --git a/radicle-surf/src/diff.rs b/radicle-surf/src/diff.rs index a28cf95..56566d9 100644 --- a/radicle-surf/src/diff.rs +++ b/radicle-surf/src/diff.rs @@ -22,6 +22,8 @@ use std::{borrow::Cow, path::PathBuf, string::FromUtf8Error}; #[cfg(feature = "serde")] use serde::{ser, ser::SerializeStruct, Serialize, Serializer}; +use git_ext::Oid; + pub mod git; /// The serializable representation of a `git diff`. @@ -90,8 +92,13 @@ impl Diff { &self.stats } - fn insert_modified(&mut self, path: PathBuf, diff: DiffContent) { - let diff = FileDiff::Modified(Modified { path, diff }); + fn insert_modified(&mut self, path: PathBuf, diff: DiffContent, old: DiffFile, new: DiffFile) { + let diff = FileDiff::Modified(Modified { + path, + diff, + old, + new, + }); self.files.push(diff) } @@ -113,13 +120,13 @@ impl Diff { self.files.push(diff); } - fn insert_added(&mut self, path: PathBuf, diff: DiffContent) { - let diff = FileDiff::Added(Added { path, diff }); + fn insert_added(&mut self, path: PathBuf, diff: DiffContent, new: DiffFile) { + let diff = FileDiff::Added(Added { path, diff, new }); self.files.push(diff); } - fn insert_deleted(&mut self, path: PathBuf, diff: DiffContent) { - let diff = FileDiff::Deleted(Deleted { path, diff }); + fn insert_deleted(&mut self, path: PathBuf, diff: DiffContent, old: DiffFile) { + let diff = FileDiff::Deleted(Deleted { path, diff, old }); self.files.push(diff); } } @@ -131,6 +138,7 @@ pub struct Added { /// The path to this file, relative to the repository root. pub path: PathBuf, pub diff: DiffContent, + pub new: DiffFile, } /// A file that was deleted within a [`Diff`]. @@ -140,6 +148,7 @@ pub struct Deleted { /// The path to this file, relative to the repository root. pub path: PathBuf, pub diff: DiffContent, + pub old: DiffFile, } /// A file that was moved within a [`Diff`]. @@ -199,6 +208,8 @@ impl Default for EofNewLine { pub struct Modified { pub path: PathBuf, pub diff: DiffContent, + pub old: DiffFile, + pub new: DiffFile, } /// The set of changes for a given file. @@ -229,6 +240,32 @@ impl DiffContent { } } +/// File mode in a diff. +#[derive(Clone, Debug, PartialEq, Eq)] +#[cfg_attr(feature = "serde", derive(Serialize), serde(rename_all = "camelCase"))] +pub enum FileMode { + /// For regular files. + Blob, + /// For regular files that are executable. + BlobExecutable, + /// For directories. + Tree, + /// For symbolic links. + Link, + /// Used for Git submodules. + Commit, +} + +/// A modified file. +#[derive(Clone, Debug, PartialEq, Eq)] +#[cfg_attr(feature = "serde", derive(Serialize), serde(rename_all = "camelCase"))] +pub struct DiffFile { + /// File blob id. + pub oid: Oid, + /// File mode. + pub mode: FileMode, +} + #[derive(Clone, Debug, PartialEq, Eq)] pub enum FileDiff { Added(Added), diff --git a/radicle-surf/src/diff/git.rs b/radicle-surf/src/diff/git.rs index 432fe3a..a65c2bd 100644 --- a/radicle-surf/src/diff/git.rs +++ b/radicle-surf/src/diff/git.rs @@ -17,7 +17,18 @@ use std::convert::TryFrom; -use super::{Diff, DiffContent, EofNewLine, Hunk, Hunks, Line, Modification, Stats}; +use super::{ + Diff, + DiffContent, + DiffFile, + EofNewLine, + FileMode, + Hunk, + Hunks, + Line, + Modification, + Stats, +}; pub mod error { use std::path::PathBuf; @@ -42,6 +53,13 @@ pub mod error { MissingOldLineNo, } + #[derive(Debug, Error)] + #[non_exhaustive] + pub enum FileMode { + #[error("unknown file mode `{0:?}`")] + Unknown(git2::FileMode), + } + #[derive(Debug, Error)] #[non_exhaustive] pub enum Modification { @@ -75,6 +93,8 @@ pub mod error { #[error(transparent)] Git(#[from] git2::Error), #[error(transparent)] + FileMode(#[from] FileMode), + #[error(transparent)] Hunk(#[from] Hunk), #[error(transparent)] Line(#[from] Modification), @@ -87,6 +107,32 @@ pub mod error { } } +impl<'a> TryFrom> for DiffFile { + type Error = error::FileMode; + + fn try_from(value: git2::DiffFile) -> Result { + Ok(Self { + mode: value.mode().try_into()?, + oid: value.id().into(), + }) + } +} + +impl TryFrom for FileMode { + type Error = error::FileMode; + + fn try_from(value: git2::FileMode) -> Result { + match value { + git2::FileMode::Blob => Ok(Self::Blob), + git2::FileMode::BlobExecutable => Ok(Self::BlobExecutable), + git2::FileMode::Commit => Ok(Self::Commit), + git2::FileMode::Tree => Ok(Self::Tree), + git2::FileMode::Link => Ok(Self::Link), + _ => Err(error::FileMode::Unknown(value)), + } + } +} + impl TryFrom> for DiffContent { type Error = error::Hunk; @@ -192,16 +238,18 @@ fn created( delta: &git2::DiffDelta<'_>, ) -> Result<(), error::Diff> { let diff_file = delta.new_file(); + let is_binary = diff_file.is_binary(); let path = diff_file .path() .ok_or(error::Diff::PathUnavailable)? .to_path_buf(); + let new = DiffFile::try_from(diff_file)?; let patch = git2::Patch::from_diff(git_diff, idx)?; if let Some(patch) = patch { - diff.insert_added(path, DiffContent::try_from(patch)?); - } else if diff_file.is_binary() { - diff.insert_added(path, DiffContent::Binary); + diff.insert_added(path, DiffContent::try_from(patch)?, new); + } else if is_binary { + diff.insert_added(path, DiffContent::Binary, new); } else { return Err(error::Diff::PatchUnavailable(path)); } @@ -215,15 +263,18 @@ fn deleted( delta: &git2::DiffDelta<'_>, ) -> Result<(), error::Diff> { let diff_file = delta.old_file(); + let is_binary = diff_file.is_binary(); let path = diff_file .path() .ok_or(error::Diff::PathUnavailable)? .to_path_buf(); let patch = git2::Patch::from_diff(git_diff, idx)?; + let old = DiffFile::try_from(diff_file)?; + if let Some(patch) = patch { - diff.insert_deleted(path, DiffContent::try_from(patch)?); - } else if diff_file.is_binary() { - diff.insert_deleted(path, DiffContent::Binary); + diff.insert_deleted(path, DiffContent::try_from(patch)?, old); + } else if is_binary { + diff.insert_deleted(path, DiffContent::Binary, old); } else { return Err(error::Diff::PatchUnavailable(path)); } @@ -242,12 +293,14 @@ fn modified( .ok_or(error::Diff::PathUnavailable)? .to_path_buf(); let patch = git2::Patch::from_diff(git_diff, idx)?; + let old = DiffFile::try_from(delta.old_file())?; + let new = DiffFile::try_from(delta.new_file())?; if let Some(patch) = patch { - diff.insert_modified(path, DiffContent::try_from(patch)?); + diff.insert_modified(path, DiffContent::try_from(patch)?, old, new); Ok(()) } else if diff_file.is_binary() { - diff.insert_modified(path, DiffContent::Binary); + diff.insert_modified(path, DiffContent::Binary, old, new); Ok(()) } else { Err(error::Diff::PatchUnavailable(path)) diff --git a/radicle-surf/t/src/diff.rs b/radicle-surf/t/src/diff.rs index 9922e0a..c5c9963 100644 --- a/radicle-surf/t/src/diff.rs +++ b/radicle-surf/t/src/diff.rs @@ -5,8 +5,10 @@ use radicle_surf::{ Added, Diff, DiffContent, + DiffFile, EofNewLine, FileDiff, + FileMode, Hunk, Line, Modification, @@ -46,6 +48,10 @@ fn test_initial_diff() -> Result<(), Error> { .into(), eof: EofNewLine::default(), }, + new: DiffFile { + oid: Oid::from_str("7f48df0118b1674f4ab0ed1717c1368091a5dddc").unwrap(), + mode: FileMode::Blob, + }, })]; let expected_stats = Stats { @@ -78,7 +84,7 @@ fn test_diff_file() -> Result<(), Error> { "223aaf87d6ea62eef0014857640fd7c8dd0f80b5", )?; let expected_diff = FileDiff::Modified(Modified { - path: path_buf, + path: path_buf, diff: DiffContent::Plain { hunks: vec![Hunk { header: Line::from(b"@@ -1 +1,2 @@\n".to_vec()), @@ -86,10 +92,19 @@ fn test_diff_file() -> Result<(), Error> { Modification::deletion(b"This repository is a data source for the Upstream front-end tests.\n".to_vec(), 1), Modification::addition(b"This repository is a data source for the Upstream front-end tests and the\n".to_vec(), 1), Modification::addition(b"[`radicle-surf`](https://github.com/radicle-dev/git-platinum) unit tests.\n".to_vec(), 2), - ] - }].into(), + ], + }] + .into(), eof: EofNewLine::default(), }, + old: DiffFile { + oid: Oid::from_str("7f48df0118b1674f4ab0ed1717c1368091a5dddc").unwrap(), + mode: FileMode::Blob, + }, + new: DiffFile { + oid: Oid::from_str("5e07534cd74a6a9b2ccd2729b181c4ef26173a5e").unwrap(), + mode: FileMode::Blob, + }, }); assert_eq!(expected_diff, diff); @@ -105,19 +120,28 @@ fn test_diff() -> Result<(), Error> { let diff = repo.diff(*parent_oid, oid)?; let expected_files = vec![FileDiff::Modified(Modified { - path: Path::new("README.md").to_path_buf(), - diff: DiffContent::Plain { - hunks: vec![Hunk { - header: Line::from(b"@@ -1 +1,2 @@\n".to_vec()), - lines: vec![ - Modification::deletion(b"This repository is a data source for the Upstream front-end tests.\n".to_vec(), 1), - Modification::addition(b"This repository is a data source for the Upstream front-end tests and the\n".to_vec(), 1), - Modification::addition(b"[`radicle-surf`](https://github.com/radicle-dev/git-platinum) unit tests.\n".to_vec(), 2), - ] - }].into(), - eof: EofNewLine::default(), - }, - })]; + path: Path::new("README.md").to_path_buf(), + diff: DiffContent::Plain { + hunks: vec![Hunk { + header: Line::from(b"@@ -1 +1,2 @@\n".to_vec()), + lines: vec![ + Modification::deletion(b"This repository is a data source for the Upstream front-end tests.\n".to_vec(), 1), + Modification::addition(b"This repository is a data source for the Upstream front-end tests and the\n".to_vec(), 1), + Modification::addition(b"[`radicle-surf`](https://github.com/radicle-dev/git-platinum) unit tests.\n".to_vec(), 2), + ], + }] + .into(), + eof: EofNewLine::default(), + }, + old: DiffFile { + oid: Oid::from_str("7f48df0118b1674f4ab0ed1717c1368091a5dddc").unwrap(), + mode: FileMode::Blob, + }, + new: DiffFile { + oid: Oid::from_str("5e07534cd74a6a9b2ccd2729b181c4ef26173a5e").unwrap(), + mode: FileMode::Blob, + }, + })]; let expected_stats = Stats { files_changed: 1, insertions: 2, @@ -208,9 +232,17 @@ fn test_diff_serde() -> Result<(), Error> { }], "eof": "noneMissing", }, + "new": { + "mode": "blob", + "oid": "02f70f56ec62396ceaf38804c37e169e875ab291", + }, }], "deleted": [{ "path": "text/arrows.txt", + "old": { + "mode": "blob", + "oid": "95418c04010a3cc758fb3a37f9918465f147566f", + }, "diff": { "type": "plain", "hunks": [{ @@ -288,6 +320,14 @@ fn test_diff_serde() -> Result<(), Error> { }], "eof": "noneMissing", }, + "new": { + "mode": "blob", + "oid": "b033ecf407a44922b28c942c696922a7d7daf06e", + }, + "old": { + "mode": "blob", + "oid": "5e07534cd74a6a9b2ccd2729b181c4ef26173a5e", + }, }], "stats": { "deletions": 9,