Radish alpha
r
rad:z6cFWeWpnZNHh9rUW8phgA3b5yGt
Git libraries for Radicle
Radicle
Git
Merge remote-tracking branch 'origin/cloudhead/add-file-info-to-diff'
Fintan Halpenny committed 2 years ago
commit 6348e48e1f2ba9ae0f18b73ad899f2c0325cc22c
parent 5513ba4
3 files changed +185 -31
modified 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,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),
modified 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;
@@ -44,6 +55,13 @@ pub mod error {

    #[derive(Debug, Error)]
    #[non_exhaustive]
+
    pub enum FileMode {
+
        #[error("unknown file mode `{0:?}`")]
+
        Unknown(git2::FileMode),
+
    }
+

+
    #[derive(Debug, Error)]
+
    #[non_exhaustive]
    pub enum Modification {
        /// A Git `DiffLine` is invalid.
        #[error(
@@ -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))
modified 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,