Merge remote-tracking branch 'origin/cloudhead/add-file-info-to-diff'

* origin/cloudhead/add-file-info-to-diff:
  Add file information to diff

Signed-off-by: Fintan Halpenny <fintan.halpenny@gmail.com>
This commit is contained in:
Fintan Halpenny
2023-05-26 08:58:45 +01:00
3 changed files with 185 additions and 31 deletions
+55 -6
View File
@@ -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,44 @@ 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,
}
impl From<FileMode> for u32 {
fn from(m: FileMode) -> Self {
git2::FileMode::from(m).into()
}
}
impl From<FileMode> for i32 {
fn from(m: FileMode) -> Self {
git2::FileMode::from(m).into()
}
}
/// 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),
+74 -9
View File
@@ -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,44 @@ pub mod error {
}
}
impl<'a> TryFrom<git2::DiffFile<'a>> for DiffFile {
type Error = error::FileMode;
fn try_from(value: git2::DiffFile) -> Result<Self, Self::Error> {
Ok(Self {
mode: value.mode().try_into()?,
oid: value.id().into(),
})
}
}
impl TryFrom<git2::FileMode> for FileMode {
type Error = error::FileMode;
fn try_from(value: git2::FileMode) -> Result<Self, Self::Error> {
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 From<FileMode> for git2::FileMode {
fn from(m: FileMode) -> Self {
match m {
FileMode::Blob => git2::FileMode::Blob,
FileMode::BlobExecutable => git2::FileMode::BlobExecutable,
FileMode::Tree => git2::FileMode::Tree,
FileMode::Link => git2::FileMode::Link,
FileMode::Commit => git2::FileMode::Commit,
}
}
}
impl TryFrom<git2::Patch<'_>> for DiffContent {
type Error = error::Hunk;
@@ -192,16 +250,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 +275,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 +305,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))
+56 -16
View File
@@ -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,