Radish alpha
h
Radicle Heartwood Protocol & Stack
Radicle
Git (anonymous pull)
Log in to clone via SSH
surf: Testing
Lorenz Leutgeb committed 3 months ago
commit 57fda8713d247305002d804857441fb64e0287d4
parent 33b03aeac5a74ec6804e3a47555031ec9893c034
39 files changed +2190 -1755
modified Cargo.lock
@@ -3013,6 +3013,7 @@ dependencies = [
name = "radicle-git-metadata"
version = "0.1.0"
dependencies = [
+
 "git2",
 "thiserror 2.0.17",
]

@@ -3164,10 +3165,15 @@ dependencies = [
 "git2",
 "log",
 "nonempty",
+
 "pretty_assertions",
+
 "proptest",
+
 "radicle-git-metadata",
 "radicle-git-ref-format",
 "radicle-oid",
 "serde",
+
 "serde_json",
 "tar",
+
 "tempfile",
 "thiserror 1.0.69",
 "url",
]
modified crates/radicle-git-metadata/Cargo.toml
@@ -10,4 +10,5 @@ keywords = ["radicle", "git", "metadata"]
rust-version.workspace = true

[dependencies]
-
thiserror = { workspace = true, default-features = true }

\ No newline at end of file
+
thiserror = { workspace = true, default-features = true }
+
git2 = { workspace = true, optional = true }

\ No newline at end of file
modified crates/radicle-git-metadata/src/author.rs
@@ -123,3 +123,16 @@ impl FromStr for Author {
        })
    }
}
+

+
#[cfg(feature = "git2")]
+
impl TryFrom<&Author> for git2::Signature<'_> {
+
    type Error = git2::Error;
+

+
    fn try_from(author: &Author) -> Result<Self, Self::Error> {
+
        git2::Signature::new(
+
            &author.name,
+
            &author.email,
+
            &git2::Time::new(author.time.seconds(), author.time.offset()),
+
        )
+
    }
+
}
modified crates/radicle-surf/Cargo.toml
@@ -14,10 +14,6 @@ include = [
    "data/git-platinum.tgz",
]

-
[lib]
-
test = false
-
doctest = false
-

[features]
# NOTE: testing `test_submodule_failure` on GH actions
# is painful since it uses this specific repo and expects
@@ -38,7 +34,15 @@ serde = { workspace = true, optional = true, features = ["derive"] }
git2 = { workspace = true, features = ["vendored-libgit2"] }

radicle-oid = { workspace = true, features = ["git2", "sha1"] }
-
radicle-git-ref-format = { workspace = true, features = ["macro"]}
+
radicle-git-ref-format = { workspace = true, features = ["macro"] }
+
radicle-git-metadata = { workspace = true, features = ["git2"] }
+

+
[dev-dependencies]
+
pretty_assertions = "1.3.0"
+
proptest = "1"
+
serde_json = "1"
+
url = "2.5"
+
tempfile = { workspace = true }

[build-dependencies]
anyhow = "1.0"
modified crates/radicle-surf/src/lib.rs
@@ -60,3 +60,6 @@ mod error;
pub use error::Error;

mod ext;
+

+
#[cfg(test)]
+
pub mod test;
added crates/radicle-surf/src/test/branch.rs
@@ -0,0 +1,25 @@
+
use super::gen;
+
use proptest::prelude::*;
+
// use radicle_git_ext_test::git_ref_format::gen;
+
use super::roundtrip;
+
use crate::Branch;
+
use radicle_git_ref_format::{RefStr, RefString};
+

+
proptest! {
+
    #[test]
+
    fn prop_test_branch(branch in gen_branch()) {
+
        super::roundtrip::json(branch)
+
    }
+
}
+

+
fn gen_branch() -> impl Strategy<Value = Branch> {
+
    prop_oneof![
+
        gen::valid().prop_map(|name| Branch::local(RefString::try_from(name).unwrap())),
+
        (gen::valid(), gen::valid()).prop_map(|(remote, name): (String, String)| {
+
            let remote =
+
                RefStr::try_from_str(&remote).expect("BUG: reference strings should be valid");
+
            let name = RefStr::try_from_str(&name).expect("BUG: reference strings should be valid");
+
            Branch::remote(remote.head(), name)
+
        })
+
    ]
+
}
added crates/radicle-surf/src/test/code_browsing.rs
@@ -0,0 +1,100 @@
+
use std::path::Path;
+

+
use crate::{
+
    fs::{self, Directory},
+
    Branch, Repository,
+
};
+
use radicle_git_ref_format::refname;
+

+
use super::GIT_PLATINUM;
+

+
#[test]
+
fn iterate_root_dir_recursive() {
+
    let repo = Repository::open(GIT_PLATINUM).unwrap();
+

+
    let root_dir = repo.root_dir(Branch::local(refname!("master"))).unwrap();
+
    let count = println_dir(&root_dir, &repo);
+

+
    assert_eq!(count, 36); // Check total file count.
+

+
    /// Prints items in `dir` with `indent_level`.
+
    /// For sub-directories, will do Depth-First-Search and print
+
    /// recursively.
+
    /// Returns the number of items visited (i.e. printed)
+
    fn println_dir(dir: &Directory, repo: &Repository) -> i32 {
+
        dir.traverse::<fs::error::Directory, _, _>(
+
            repo,
+
            (0, 0),
+
            &mut |(count, indent_level), entry| {
+
                println!("> {}{}", " ".repeat(indent_level * 4), entry.name());
+
                match entry {
+
                    fs::Entry::File(_) => Ok((count + 1, indent_level)),
+
                    fs::Entry::Directory(_) => Ok((count + 1, indent_level + 1)),
+
                    fs::Entry::Submodule(_) => Ok((count + 1, indent_level)),
+
                }
+
            },
+
        )
+
        .unwrap()
+
        .0
+
    }
+
}
+

+
#[test]
+
fn browse_repo_lazily() {
+
    let repo = Repository::open(GIT_PLATINUM).unwrap();
+

+
    let root_dir = repo.root_dir(Branch::local(refname!("master"))).unwrap();
+
    let count = root_dir.entries(&repo).unwrap().entries().count();
+
    assert_eq!(count, 8);
+
    let count = traverse(&root_dir, &repo);
+
    assert_eq!(count, 36);
+

+
    fn traverse(dir: &Directory, repo: &Repository) -> i32 {
+
        dir.traverse::<fs::error::Directory, _, _>(repo, 0, &mut |count, _| Ok(count + 1))
+
            .unwrap()
+
    }
+
}
+

+
#[test]
+
fn test_file_history() {
+
    let repo = Repository::open(GIT_PLATINUM).unwrap();
+
    let history = repo.history(Branch::local(refname!("dev"))).unwrap();
+
    let path = Path::new("README.md");
+
    let mut file_history = history.by_path(&path);
+
    let commit = file_history.next().unwrap().unwrap();
+
    let file = repo.get_commit_file(&commit.id, &path).unwrap();
+
    assert_eq!(file.size(), 67);
+
}
+

+
#[test]
+
fn test_commit_history() {
+
    let repo = Repository::open(GIT_PLATINUM).unwrap();
+
    let head = "a0dd9122d33dff2a35f564d564db127152c88e02";
+

+
    // verify `&str` works.
+
    let h1 = repo.history(head).unwrap();
+

+
    // verify `&String` works.
+
    let head_string = head.to_string();
+
    let h2 = repo.history(&head_string).unwrap();
+

+
    assert_eq!(h1.head().id, h2.head().id);
+
}
+

+
#[test]
+
fn test_commit_signature() {
+
    let repo = Repository::open(GIT_PLATINUM).unwrap();
+
    let commit_with_signature = "e24124b7538658220b5aaf3b6ef53758f0a106dc";
+
    let signature = repo.extract_signature(commit_with_signature, None).unwrap();
+
    assert!(signature.is_some());
+

+
    let commit_without_signature = "80bacafba303bf0cdf6142921f430ff265f25095";
+
    let signature = repo
+
        .extract_signature(commit_without_signature, None)
+
        .unwrap();
+
    assert!(signature.is_none());
+

+
    let commit_nonexist = "8080808080";
+
    let signature = repo.extract_signature(commit_nonexist, None);
+
    assert!(signature.is_err());
+
}
added crates/radicle-surf/src/test/commit.rs
@@ -0,0 +1,32 @@
+
use std::str::FromStr;
+

+
use crate::{Author, Commit, Time};
+
use proptest::prelude::*;
+
use radicle_oid::Oid;
+

+
#[cfg(feature = "serde")]
+
proptest! {
+
    #[test]
+
    fn prop_test_commits(commit in commits_strategy()) {
+
        super::roundtrip::json(commit)
+
    }
+
}
+

+
fn commits_strategy() -> impl Strategy<Value = Commit> {
+
    ("[a-fA-F0-9]{40}", any::<String>(), any::<i64>()).prop_map(|(id, text, time)| Commit {
+
        id: Oid::from_str(&id).unwrap(),
+
        author: Author {
+
            name: text.clone(),
+
            email: text.clone(),
+
            time: Time::new(time, 0),
+
        },
+
        committer: Author {
+
            name: text.clone(),
+
            email: text.clone(),
+
            time: Time::new(time, 0),
+
        },
+
        message: text.clone(),
+
        summary: text,
+
        parents: vec![Oid::from_str(&id).unwrap(), Oid::from_str(&id).unwrap()],
+
    })
+
}
added crates/radicle-surf/src/test/diff.rs
@@ -0,0 +1,674 @@
+
use crate::{
+
    diff::{
+
        Added, Diff, DiffContent, DiffFile, EofNewLine, FileDiff, FileMode, FileStats, Hunk, Line,
+
        Modification, Modified, Stats,
+
    },
+
    Branch, Error, Repository,
+
};
+
use pretty_assertions::assert_eq;
+
use radicle_git_ref_format::refname;
+
use radicle_oid::Oid;
+
use std::{path::Path, str::FromStr};
+

+
use super::GIT_PLATINUM;
+

+
#[test]
+
fn test_initial_diff() -> Result<(), Error> {
+
    let repo = Repository::open(GIT_PLATINUM)?;
+
    let oid = Oid::from_str("d3464e33d75c75c99bfb90fa2e9d16efc0b7d0e3")?;
+
    let commit = repo.commit(oid).unwrap();
+
    assert!(commit.parents.is_empty());
+

+
    let diff = repo.diff_commit(oid)?;
+
    let diff_stats = *diff.stats();
+
    let diff_files = diff.into_files();
+

+
    let expected_files = vec![FileDiff::Added(Added {
+
        path: Path::new("README.md").to_path_buf(),
+
        diff: DiffContent::Plain {
+
            hunks: vec![Hunk {
+
                header: Line::from(b"@@ -0,0 +1 @@\n".to_vec()),
+
                lines: vec![Modification::addition(
+
                    b"This repository is a data source for the Upstream front-end tests.\n"
+
                        .to_vec(),
+
                    1,
+
                )],
+
                old: 0..0,
+
                new: 1..2,
+
            }]
+
            .into(),
+
            stats: FileStats {
+
                additions: 1,
+
                deletions: 0,
+
            },
+
            eof: EofNewLine::default(),
+
        },
+
        new: DiffFile {
+
            oid: Oid::from_str("7f48df0118b1674f4ab0ed1717c1368091a5dddc").unwrap(),
+
            mode: FileMode::Blob,
+
        },
+
    })];
+

+
    let expected_stats = Stats {
+
        files_changed: 1,
+
        insertions: 1,
+
        deletions: 0,
+
    };
+

+
    assert_eq!(expected_files, diff_files);
+
    assert_eq!(expected_stats, diff_stats);
+

+
    Ok(())
+
}
+

+
#[test]
+
fn test_diff_of_rev() -> Result<(), Error> {
+
    let repo = Repository::open(GIT_PLATINUM)?;
+
    let diff = repo.diff_commit("80bacafba303bf0cdf6142921f430ff265f25095")?;
+
    assert_eq!(diff.files().count(), 1);
+
    Ok(())
+
}
+

+
#[test]
+
fn test_diff_file() -> Result<(), Error> {
+
    let repo = Repository::open(GIT_PLATINUM)?;
+
    let path_buf = Path::new("README.md").to_path_buf();
+
    let diff = repo.diff_file(
+
        &path_buf,
+
        "d6880352fc7fda8f521ae9b7357668b17bb5bad5",
+
        "223aaf87d6ea62eef0014857640fd7c8dd0f80b5",
+
    )?;
+
    let expected_diff = FileDiff::Modified(Modified {
+
        path: 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),
+
                ],
+
                old: 1..2,
+
                new: 1..3,
+
            }]
+
            .into(),
+
            stats: FileStats {
+
                additions: 2,
+
                deletions: 1
+
            },
+
            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);
+

+
    Ok(())
+
}
+

+
#[test]
+
fn test_diff() -> Result<(), Error> {
+
    let repo = Repository::open(GIT_PLATINUM)?;
+
    let oid = "80bacafba303bf0cdf6142921f430ff265f25095";
+
    let commit = repo.commit(oid).unwrap();
+
    let parent_oid = commit.parents.first().unwrap();
+
    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),
+
                ],
+
                old: 1..2,
+
                new: 1..3,
+
            }]
+
            .into(),
+
            stats: FileStats {
+
                additions: 2,
+
                deletions: 1
+
            },
+
            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,
+
        deletions: 1,
+
    };
+
    let diff_stats = *diff.stats();
+
    let diff_files = diff.into_files();
+
    assert_eq!(expected_files, diff_files);
+
    assert_eq!(expected_stats, diff_stats);
+

+
    Ok(())
+
}
+

+
#[test]
+
fn test_branch_diff() -> Result<(), Error> {
+
    let repo = Repository::open(GIT_PLATINUM)?;
+
    let rev_from = Branch::local(refname!("master"));
+
    let rev_to = Branch::local(refname!("dev"));
+
    let diff = repo.diff(&rev_from, &rev_to)?;
+

+
    println!("Diff two branches: master -> dev");
+
    println!(
+
        "result: added {} deleted {} moved {} modified {}",
+
        diff.added().count(),
+
        diff.deleted().count(),
+
        diff.moved().count(),
+
        diff.modified().count()
+
    );
+
    assert_eq!(diff.added().count(), 1);
+
    assert_eq!(diff.deleted().count(), 11);
+
    assert_eq!(diff.moved().count(), 1);
+
    assert_eq!(diff.modified().count(), 2);
+
    for c in diff.added() {
+
        println!("added: {:?}", &c.path);
+
    }
+
    for d in diff.deleted() {
+
        println!("deleted: {:?}", &d.path);
+
    }
+
    for m in diff.moved() {
+
        println!("moved: {:?} -> {:?}", &m.old_path, &m.new_path);
+
    }
+
    for m in diff.modified() {
+
        println!("modified: {:?}", &m.path);
+
    }
+

+
    // Verify moved.
+
    let diff_moved = diff.moved().next().unwrap();
+

+
    // We can find a `FileDiff` for the old_path in a move.
+
    let file_diff = repo.diff_file(&diff_moved.old_path, &rev_from, &rev_to)?;
+
    println!("old path file diff: {:?}", &file_diff);
+

+
    // We can find a `FileDiff` for the new_path in a move.
+
    let file_diff = repo.diff_file(&diff_moved.new_path, &rev_from, &rev_to)?;
+
    println!("new path file diff: {:?}", &file_diff);
+

+
    // We can find a `FileDiff` if given a directory name.
+
    let dir_diff = repo.diff_file(&"special/", &rev_from, &rev_to)?;
+
    println!("dir diff: {dir_diff:?}");
+

+
    Ok(())
+
}
+

+
#[cfg(feature = "serde")]
+
#[test]
+
fn test_diff_serde() -> Result<(), Error> {
+
    let repo = Repository::open(GIT_PLATINUM)?;
+
    let rev_from = Branch::local(refname!("master"));
+
    let rev_to = Branch::local(refname!("diff-test"));
+
    let diff = repo.diff(rev_from, rev_to)?;
+

+
    let json = serde_json::json!({
+
        "files": [{
+
            "path": "LICENSE",
+
            "diff": {
+
                "type": "plain",
+
                "hunks": [{
+
                    "header": "@@ -0,0 +1,2 @@\n",
+
                    "lines": [{
+
                        "line": "This is a license file.\n",
+
                        "lineNo": 1,
+
                        "type": "addition",
+
                    },
+
                    {
+
                        "line": "\n",
+
                        "lineNo": 2,
+
                        "type": "addition",
+
                    }],
+
                    "old": { "start": 0, "end": 0 },
+
                    "new": { "start": 1, "end": 3 },
+
                }],
+
                "stats": {
+
                    "additions": 2,
+
                    "deletions": 0,
+
                },
+
                "eof": "noneMissing",
+
            },
+
            "new": {
+
                "mode": "blob",
+
                "oid": "02f70f56ec62396ceaf38804c37e169e875ab291",
+
            },
+
            "status": "added"
+
        },
+
        {
+
            "path": "README.md",
+
            "diff": {
+
                "type": "plain",
+
                "hunks": [{
+
                    "header": "@@ -1,2 +1,2 @@\n",
+
                    "lines": [
+
                        { "lineNo": 1,
+
                          "line": "This repository is a data source for the Upstream front-end tests and the\n",
+
                          "type": "deletion"
+
                        },
+
                        { "lineNo": 2,
+
                          "line": "[`radicle-surf`](https://github.com/radicle-dev/git-platinum) unit tests.\n",
+
                          "type": "deletion"
+
                        },
+
                        { "lineNo": 1,
+
                          "line": "This repository is a data source for the upstream front-end tests and the\n",
+
                          "type": "addition"
+
                        },
+
                        { "lineNo": 2,
+
                          "line": "[`radicle-surf`](https://github.com/radicle-dev/radicle-surf) unit tests.\n",
+
                          "type": "addition"
+
                        },
+
                    ],
+
                    "old": { "start": 1, "end": 3 },
+
                    "new": { "start": 1, "end": 3 },
+
                }],
+
                "stats": {
+
                    "additions": 2,
+
                    "deletions": 2
+
                },
+
                "eof": "noneMissing",
+
            },
+
            "new": {
+
                "mode": "blob",
+
                "oid": "b033ecf407a44922b28c942c696922a7d7daf06e",
+
            },
+
            "old": {
+
                "mode": "blob",
+
                "oid": "5e07534cd74a6a9b2ccd2729b181c4ef26173a5e",
+
            },
+
            "status": "modified",
+
        },
+
        {
+
            "current": {
+
                "mode": "blob",
+
                "oid": "1570277532948712fea9029d100a4208f9e34241",
+
            },
+
            "oldPath": "text/emoji.txt",
+
            "newPath": "emoji.txt",
+
            "status": "moved"
+
        },
+
        {
+
            "current": {
+
                "mode": "blob",
+
                "oid": "5e07534cd74a6a9b2ccd2729b181c4ef26173a5e",
+
            },
+
            "newPath": "file_operations/copied.md",
+
            "oldPath": "README.md",
+
            "status": "copied"
+
        },
+
        {
+
            "path": "text/arrows.txt",
+
            "status": "deleted",
+
            "old": {
+
                "mode": "blob",
+
                "oid": "95418c04010a3cc758fb3a37f9918465f147566f",
+
             },
+
            "diff": {
+
                "type": "plain",
+
                "hunks": [{
+
                    "header": "@@ -1,7 +0,0 @@\n",
+
                    "lines": [
+
                        {
+
                            "line": "  ;;;;;        ;;;;;        ;;;;;\n",
+
                            "lineNo": 1,
+
                            "type": "deletion",
+
                        },
+
                        {
+
                            "line": "  ;;;;;        ;;;;;        ;;;;;\n",
+
                            "lineNo": 2,
+
                            "type": "deletion",
+
                        },
+
                        {
+
                            "line": "  ;;;;;        ;;;;;        ;;;;;\n",
+
                            "lineNo": 3,
+
                            "type": "deletion",
+
                        },
+
                        {
+
                            "line": "  ;;;;;        ;;;;;        ;;;;;\n",
+
                            "lineNo": 4,
+
                            "type": "deletion",
+
                        },
+
                        {
+
                            "line": "..;;;;;..    ..;;;;;..    ..;;;;;..\n",
+
                            "lineNo": 5,
+
                            "type": "deletion",
+
                        },
+
                        {
+
                            "line": " ':::::'      ':::::'      ':::::'\n",
+
                            "lineNo": 6,
+
                            "type": "deletion",
+
                        },
+
                        {
+
                            "line": "   ':`          ':`          ':`\n",
+
                            "lineNo": 7,
+
                            "type": "deletion",
+
                        },
+
                    ],
+
                    "old": { "start": 1, "end": 8 },
+
                    "new": { "start": 0, "end": 0 },
+
                }],
+
                "stats": {
+
                    "additions": 0,
+
                    "deletions": 7,
+
                },
+
                "eof": "noneMissing",
+
            },
+
        }],
+
        "stats": {
+
            "deletions": 9,
+
            "filesChanged": 5,
+
            "insertions": 4,
+
        }
+
    });
+
    assert_eq!(serde_json::to_value(diff).unwrap(), json);
+

+
    Ok(())
+
}
+

+
#[cfg(feature = "serde")]
+
#[test]
+
fn test_rename_with_changes() {
+
    let buf = r"
+
diff --git a/radicle/src/node/tracking/config.rs b/radicle-node/src/service/tracking.rs
+
similarity index 96%
+
rename from radicle/src/node/tracking/config.rs
+
rename to radicle-node/src/service/tracking.rs
+
index 3f69208f3..cbc843c82 100644
+
--- a/radicle/src/node/tracking/config.rs
+
+++ b/radicle-node/src/service/tracking.rs
+
@@ -5,15 +5,17 @@ use std::ops;
+
 use log::error;
+
 use thiserror::Error;
+

+
-use crate::crypto::PublicKey;
+
-use crate::identity::IdentityError;
+
+use radicle::crypto::PublicKey;
+
+use radicle::identity::IdentityError;
+
+use radicle::storage::{Namespaces, ReadRepository as _, ReadStorage};
+
+
+
+use crate::prelude::Id;
+
+use crate::service::NodeId;
+
+
+
 pub use crate::node::tracking::store;
+
 pub use crate::node::tracking::store::Config as Store;
+
 pub use crate::node::tracking::store::Error;
+
 pub use crate::node::tracking::{Alias, Node, Policy, Repo, Scope};
+
-use crate::node::NodeId;
+
-use crate::prelude::Id;
+
-use crate::storage::{Namespaces, ReadRepository as _, ReadStorage};
+

+
 #[derive(Debug, Error)]
+
 pub enum NamespacesError {
+
";
+
    let diff = git2::Diff::from_buffer(buf.as_bytes()).unwrap();
+
    let diff = Diff::try_from(diff).unwrap();
+
    let json = serde_json::json!(
+
    {
+
        "files": [
+
            {
+
                "diff": {
+
                    "eof": "noneMissing",
+
                    "hunks": [
+
                        {
+
                            "header": "@@ -5,15 +5,17 @@ use std::ops;\n",
+
                            "lines": [
+
                                {
+
                                    "line": "use log::error;\n",
+
                                    "lineNoNew": 5,
+
                                    "lineNoOld": 5,
+
                                    "type": "context",
+
                                },
+
                                {
+
                                    "line": "use thiserror::Error;\n",
+
                                    "lineNoNew": 6,
+
                                    "lineNoOld": 6,
+
                                    "type": "context",
+
                                },
+
                                {
+
                                    "line": "\n",
+
                                    "lineNoNew": 7,
+
                                    "lineNoOld": 7,
+
                                    "type": "context",
+
                                },
+
                                {
+
                                    "line": "use crate::crypto::PublicKey;\n",
+
                                    "lineNo": 8,
+
                                    "type": "deletion",
+
                                },
+
                                {
+
                                    "line": "use crate::identity::IdentityError;\n",
+
                                    "lineNo": 9,
+
                                    "type": "deletion",
+
                                },
+
                                {
+
                                    "line": "use radicle::crypto::PublicKey;\n",
+
                                    "lineNo": 8,
+
                                    "type": "addition",
+
                                },
+
                                {
+
                                    "line": "use radicle::identity::IdentityError;\n",
+
                                    "lineNo": 9,
+
                                    "type": "addition",
+
                                },
+
                                {
+
                                    "line": "use radicle::storage::{Namespaces, ReadRepository as _, ReadStorage};\n",
+
                                    "lineNo": 10,
+
                                    "type": "addition",
+
                                },
+
                                {
+
                                    "line": "\n",
+
                                    "lineNo": 11,
+
                                    "type": "addition",
+
                                },
+
                                {
+
                                    "line": "use crate::prelude::Id;\n",
+
                                    "lineNo": 12,
+
                                    "type": "addition",
+
                                },
+
                                {
+
                                    "line": "use crate::service::NodeId;\n",
+
                                    "lineNo": 13,
+
                                    "type": "addition",
+
                                },
+
                                {
+
                                    "line": "\n",
+
                                    "lineNo": 14,
+
                                    "type": "addition",
+
                                },
+
                                {
+
                                    "line": "pub use crate::node::tracking::store;\n",
+
                                    "lineNoNew": 15,
+
                                    "lineNoOld": 10,
+
                                    "type": "context",
+
                                },
+
                                {
+
                                    "line": "pub use crate::node::tracking::store::Config as Store;\n",
+
                                    "lineNoNew": 16,
+
                                    "lineNoOld": 11,
+
                                    "type": "context",
+
                                },
+
                                {
+
                                    "line": "pub use crate::node::tracking::store::Error;\n",
+
                                    "lineNoNew": 17,
+
                                    "lineNoOld": 12,
+
                                    "type": "context",
+
                                },
+
                                {
+
                                    "line": "pub use crate::node::tracking::{Alias, Node, Policy, Repo, Scope};\n",
+
                                    "lineNoNew": 18,
+
                                    "lineNoOld": 13,
+
                                    "type": "context",
+
                                },
+
                                {
+
                                    "line": "use crate::node::NodeId;\n",
+
                                    "lineNo": 14,
+
                                    "type": "deletion",
+
                                },
+
                                {
+
                                    "line": "use crate::prelude::Id;\n",
+
                                    "lineNo": 15,
+
                                    "type": "deletion",
+
                                },
+
                                {
+
                                    "line": "use crate::storage::{Namespaces, ReadRepository as _, ReadStorage};\n",
+
                                    "lineNo": 16,
+
                                    "type": "deletion",
+
                                },
+
                                {
+
                                    "line": "\n",
+
                                    "lineNoNew": 19,
+
                                    "lineNoOld": 17,
+
                                    "type": "context",
+
                                },
+
                                {
+
                                    "line": "#[derive(Debug, Error)]\n",
+
                                    "lineNoNew": 20,
+
                                    "lineNoOld": 18,
+
                                    "type": "context",
+
                                },
+
                                {
+
                                    "line": "pub enum NamespacesError {\n",
+
                                    "lineNoNew": 21,
+
                                    "lineNoOld": 19,
+
                                    "type": "context",
+
                                },
+
                            ],
+
                            "new": {
+
                                "end": 22,
+
                                "start": 5,
+
                            },
+
                            "old": {
+
                                "end": 20,
+
                                "start": 5,
+
                            },
+
                        },
+
                    ],
+
                    "stats": {
+
                        "additions": 7,
+
                        "deletions": 5
+
                    },
+
                    "type": "plain",
+
                },
+
                "new": {
+
                    "mode": "blob",
+
                    "oid": "cbc843c820000000000000000000000000000000",
+
                },
+
                "newPath": "radicle-node/src/service/tracking.rs",
+
                "old": {
+
                    "mode": "blob",
+
                    "oid": "3f69208f30000000000000000000000000000000",
+
                },
+
                "oldPath": "radicle/src/node/tracking/config.rs",
+
                "status": "moved"
+
            },
+
        ],
+
        "stats": {
+
            "deletions": 0,
+
            "filesChanged": 1,
+
            "insertions": 0,
+
        },
+
    });
+

+
    assert_eq!(serde_json::to_value(diff).unwrap(), json);
+
}
+

+
// A possible false positive is being hit here for this clippy
+
// warning. Tracking issue:
+
// https://github.com/rust-lang/rust-clippy/issues/11402
+
#[allow(clippy::needless_raw_string_hashes)]
+
#[test]
+
fn test_both_missing_eof_newline() {
+
    let buf = r#"
+
diff --git a/.env b/.env
+
index f89e4c0..7c56eb7 100644
+
--- a/.env
+
+++ b/.env
+
@@ -1 +1 @@
+
-hello=123
+
\ No newline at end of file
+
+hello=1234
+
\ No newline at end of file
+
"#;
+
    let diff = git2::Diff::from_buffer(buf.as_bytes()).unwrap();
+
    let diff = Diff::try_from(diff).unwrap();
+
    assert_eq!(
+
        diff.modified().next().unwrap().diff.eof(),
+
        Some(EofNewLine::BothMissing)
+
    );
+
}
+

+
#[test]
+
fn test_none_missing_eof_newline() {
+
    let buf = r#"
+
diff --git a/.env b/.env
+
index f89e4c0..7c56eb7 100644
+
--- a/.env
+
+++ b/.env
+
@@ -1 +1 @@
+
-hello=123
+
+hello=1234
+
"#;
+
    let diff = git2::Diff::from_buffer(buf.as_bytes()).unwrap();
+
    let diff = Diff::try_from(diff).unwrap();
+
    assert_eq!(
+
        diff.modified().next().unwrap().diff.eof(),
+
        Some(EofNewLine::NoneMissing)
+
    );
+
}
+

+
#[test]
+
fn test_old_missing_eof_newline() {
+
    let buf = r#"
+
diff --git a/.env b/.env
+
index f89e4c0..7c56eb7 100644
+
--- a/.env
+
+++ b/.env
+
@@ -1 +1 @@
+
-hello=123
+
\ No newline at end of file
+
+hello=1234
+
"#;
+
    let diff = git2::Diff::from_buffer(buf.as_bytes()).unwrap();
+
    let diff = Diff::try_from(diff).unwrap();
+
    assert_eq!(
+
        diff.modified().next().unwrap().diff.eof(),
+
        Some(EofNewLine::OldMissing)
+
    );
+
}
+

+
#[test]
+
fn test_new_missing_eof_newline() {
+
    let buf = r#"
+
diff --git a/.env b/.env
+
index f89e4c0..7c56eb7 100644
+
--- a/.env
+
+++ b/.env
+
@@ -1 +1 @@
+
-hello=123
+
+hello=1234
+
\ No newline at end of file
+
"#;
+
    let diff = git2::Diff::from_buffer(buf.as_bytes()).unwrap();
+
    let diff = Diff::try_from(diff).unwrap();
+
    assert_eq!(
+
        diff.modified().next().unwrap().diff.eof(),
+
        Some(EofNewLine::NewMissing)
+
    );
+
}
added crates/radicle-surf/src/test/file_system.rs
@@ -0,0 +1,140 @@
+
//! Unit tests for crate::file_system
+

+
pub mod directory {
+
    use crate::{
+
        fs::{self, Entry},
+
        Branch, Repository,
+
    };
+
    use radicle_git_ref_format::refname;
+
    use std::path::Path;
+

+
    const GIT_PLATINUM: &str = "../data/git-platinum";
+

+
    #[test]
+
    fn directory_find_entry() {
+
        let repo = Repository::open(GIT_PLATINUM).unwrap();
+
        let root = repo.root_dir(Branch::local(refname!("master"))).unwrap();
+

+
        // find_entry for a file.
+
        let path = Path::new("src/memory.rs");
+
        let entry = root.find_entry(&path, &repo).unwrap();
+
        assert!(matches!(entry, fs::Entry::File(_)));
+

+
        // find_entry for a directory.
+
        let path = Path::new("this/is/a/really/deeply/nested/directory/tree");
+
        let entry = root.find_entry(&path, &repo).unwrap();
+
        assert!(matches!(entry, fs::Entry::Directory(_)));
+

+
        // find_entry for a non-leaf directory and its relative path.
+
        let path = Path::new("text");
+
        let entry = root.find_entry(&path, &repo).unwrap();
+
        assert!(matches!(entry, fs::Entry::Directory(_)));
+
        if let fs::Entry::Directory(sub_dir) = entry {
+
            let inner_path = Path::new("garden.txt");
+
            let inner_entry = sub_dir.find_entry(&inner_path, &repo).unwrap();
+
            assert!(matches!(inner_entry, fs::Entry::File(_)));
+
        }
+

+
        // find_entry for non-existing file
+
        let path = Path::new("this/is/a/really/missing_file");
+
        let result = root.find_entry(&path, &repo);
+
        assert!(matches!(result, Err(fs::error::Directory::PathNotFound(_))));
+

+
        // find_entry for absolute path: fail.
+
        let path = Path::new("/src/memory.rs");
+
        let result = root.find_entry(&path, &repo);
+
        assert!(result.is_err());
+

+
        // find entry for an empty path
+
        let path = Path::new("");
+
        let result = root.find_entry(&path, &repo);
+
        assert!(result.is_err());
+
    }
+

+
    #[test]
+
    fn directory_find_file_and_directory() {
+
        let repo = Repository::open(GIT_PLATINUM).unwrap();
+
        // Get the snapshot of the directory for a given commit.
+
        let root = repo
+
            .root_dir("80ded66281a4de2889cc07293a8f10947c6d57fe")
+
            .unwrap();
+

+
        // Assert that we can find the memory.rs file!
+
        assert!(root.find_file(&Path::new("src/memory.rs"), &repo).is_ok());
+

+
        let root_contents: Vec<Entry> = root.entries(&repo).unwrap().collect();
+
        assert_eq!(root_contents.len(), 7);
+
        assert!(root_contents[0].is_file());
+
        assert!(root_contents[1].is_file());
+
        assert!(root_contents[2].is_file());
+
        assert_eq!(root_contents[0].name(), ".i-am-well-hidden");
+
        assert_eq!(root_contents[1].name(), ".i-too-am-hidden");
+
        assert_eq!(root_contents[2].name(), "README.md");
+

+
        assert!(root_contents[3].is_directory());
+
        assert!(root_contents[4].is_directory());
+
        assert!(root_contents[5].is_directory());
+
        assert!(root_contents[6].is_directory());
+
        assert_eq!(root_contents[3].name(), "bin");
+
        assert_eq!(root_contents[4].name(), "src");
+
        assert_eq!(root_contents[5].name(), "text");
+
        assert_eq!(root_contents[6].name(), "this");
+

+
        let src = root.find_directory(&Path::new("src"), &repo).unwrap();
+
        assert_eq!(src.path(), Path::new("src").to_path_buf());
+
        let src_contents: Vec<Entry> = src.entries(&repo).unwrap().collect();
+
        assert_eq!(src_contents.len(), 3);
+
        assert_eq!(src_contents[0].name(), "Eval.hs");
+
        assert_eq!(src_contents[1].name(), "Folder.svelte");
+
        assert_eq!(src_contents[2].name(), "memory.rs");
+
    }
+

+
    #[test]
+
    fn directory_size() {
+
        let repo = Repository::open(GIT_PLATINUM).unwrap();
+
        let root = repo.root_dir(Branch::local(refname!("master"))).unwrap();
+

+
        /*
+
        git-platinum (master) $ ls -l src
+
        -rw-r--r-- 1 pi pi 10044 Oct 31 11:32 Eval.hs
+
        -rw-r--r-- 1 pi pi  6253 Oct 31 11:27 memory.rs
+

+
        10044 + 6253 = 16297
+
         */
+

+
        let path = Path::new("src");
+
        let entry = root.find_entry(&path, &repo).unwrap();
+
        assert!(matches!(entry, fs::Entry::Directory(_)));
+
        if let fs::Entry::Directory(d) = entry {
+
            assert_eq!(16297, d.size(&repo).unwrap());
+
        }
+
    }
+

+
    #[test]
+
    fn directory_last_commit() {
+
        let repo = Repository::open(GIT_PLATINUM).unwrap();
+
        let branch = Branch::local(refname!("dev"));
+
        let root = repo.root_dir(&branch).unwrap();
+
        let dir = root.find_directory(&"this/is", &repo).unwrap();
+
        let last_commit = repo.last_commit(&dir.path(), &branch).unwrap().unwrap();
+
        assert_eq!(
+
            last_commit.id.to_string(),
+
            "2429f097664f9af0c5b7b389ab998b2199ffa977"
+
        );
+
    }
+

+
    #[test]
+
    fn file_last_commit() {
+
        let repo = Repository::open(GIT_PLATINUM).unwrap();
+
        let branch = Branch::local(refname!("master"));
+
        let root = repo.root_dir(&branch).unwrap();
+

+
        // Find a file with "\" in its name.
+
        let f = root.find_file(&"special/faux\\path", &repo).unwrap();
+
        let last_commit = repo.last_commit(&f.path(), &branch).unwrap().unwrap();
+
        assert_eq!(
+
            last_commit.id.to_string(),
+
            "a0dd9122d33dff2a35f564d564db127152c88e02"
+
        );
+
    }
+
}
added crates/radicle-surf/src/test/gen.rs
@@ -0,0 +1,108 @@
+
pub(crate) mod commit;
+

+
use proptest::prelude::*;
+

+
/// Any unicode "word" is trivially a valid refname.
+
pub fn trivial() -> impl Strategy<Value = String> {
+
    "\\w+"
+
}
+

+
pub fn valid() -> impl Strategy<Value = String> {
+
    prop::collection::vec(trivial(), 1..20).prop_map(|xs| xs.join("/"))
+
}
+

+
pub fn invalid_char() -> impl Strategy<Value = char> {
+
    prop_oneof![
+
        Just('\0'),
+
        Just('\\'),
+
        Just('~'),
+
        Just('^'),
+
        Just(':'),
+
        Just('?'),
+
        Just('[')
+
    ]
+
}
+

+
pub fn with_invalid_char() -> impl Strategy<Value = String> {
+
    ("\\w*", invalid_char(), "\\w*").prop_map(|(mut pre, invalid, suf)| {
+
        pre.push(invalid);
+
        pre.push_str(&suf);
+
        pre
+
    })
+
}
+

+
pub fn ends_with_dot_lock() -> impl Strategy<Value = String> {
+
    "\\w*\\.lock"
+
}
+

+
pub fn with_double_dot() -> impl Strategy<Value = String> {
+
    "\\w*\\.\\.\\w*"
+
}
+

+
pub fn starts_with_dot() -> impl Strategy<Value = String> {
+
    "\\.\\w*"
+
}
+

+
pub fn ends_with_dot() -> impl Strategy<Value = String> {
+
    "\\w+\\."
+
}
+

+
pub fn with_control_char() -> impl Strategy<Value = String> {
+
    "\\w*[\x01-\x1F\x7F]+\\w*"
+
}
+

+
pub fn with_space() -> impl Strategy<Value = String> {
+
    "\\w* +\\w*"
+
}
+

+
pub fn with_consecutive_slashes() -> impl Strategy<Value = String> {
+
    "\\w*//\\w*"
+
}
+

+
pub fn with_glob() -> impl Strategy<Value = String> {
+
    "\\w*\\*\\w*"
+
}
+

+
pub fn multi_glob() -> impl Strategy<Value = String> {
+
    (
+
        prop::collection::vec(with_glob(), 2..5),
+
        prop::collection::vec(trivial(), 0..5),
+
    )
+
        .prop_map(|(mut globs, mut valids)| {
+
            globs.append(&mut valids);
+
            globs
+
        })
+
        .prop_shuffle()
+
        .prop_map(|xs| xs.join("/"))
+
}
+

+
pub fn invalid() -> impl Strategy<Value = String> {
+
    fn path(s: impl Strategy<Value = String>) -> impl Strategy<Value = String> {
+
        prop::collection::vec(s, 1..20).prop_map(|xs| xs.join("/"))
+
    }
+

+
    prop_oneof![
+
        Just(String::from("")),
+
        Just(String::from("@")),
+
        path(with_invalid_char()),
+
        path(ends_with_dot_lock()),
+
        path(with_double_dot()),
+
        path(starts_with_dot()),
+
        path(ends_with_dot()),
+
        path(with_control_char()),
+
        path(with_space()),
+
        path(with_consecutive_slashes()),
+
        path(trivial()).prop_map(|mut p| {
+
            p.push('/');
+
            p
+
        }),
+
    ]
+
}
+

+
pub fn alphanumeric() -> impl Strategy<Value = String> {
+
    "[a-zA-Z0-9_]+"
+
}
+

+
pub fn alpha() -> impl Strategy<Value = String> {
+
    "[a-zA-Z]+"
+
}
added crates/radicle-surf/src/test/gen/commit.rs
@@ -0,0 +1,102 @@
+
use std::convert::Infallible;
+

+
use proptest::strategy::Strategy;
+
use radicle_git_metadata::commit::CommitData;
+

+
mod author;
+
mod headers;
+
mod trailers;
+

+
pub use author::author;
+
pub use headers::headers;
+
pub use trailers::trailers;
+

+
use super::alphanumeric;
+

+
pub fn commit() -> impl Strategy<Value = CommitData<TreeData, Infallible>> {
+
    (
+
        TreeData::gen(),
+
        author(),
+
        author(),
+
        headers(),
+
        alphanumeric(),
+
        trailers(3),
+
    )
+
        .prop_map(|(tree, author, committer, headers, message, trailers)| {
+
            CommitData::new(tree, vec![], author, committer, headers, message, trailers)
+
        })
+
}
+

+
pub fn write_commits(
+
    repo: &git2::Repository,
+
    linear: Vec<CommitData<TreeData, Infallible>>,
+
) -> Result<Vec<git2::Oid>, git2::Error> {
+
    let mut parent = None;
+
    let mut commits = Vec::new();
+
    for commit in linear {
+
        let commit = commit.map_tree(|tree| tree.write(repo))?;
+
        let commit = match parent {
+
            Some(parent) => commit
+
                .map_parents::<git2::Oid, Infallible, _>(|_| Ok(parent))
+
                .unwrap(),
+
            None => commit
+
                .map_parents::<git2::Oid, Infallible, _>(|_| unreachable!("no parents"))
+
                .unwrap(),
+
        };
+
        let tree = repo.find_tree(*commit.tree())?;
+
        let oid = repo.commit(
+
            None,
+
            &git2::Signature::try_from(commit.author()).unwrap(),
+
            &git2::Signature::try_from(commit.committer()).unwrap(),
+
            commit.message(),
+
            &tree,
+
            &[],
+
        )?;
+
        commits.push(oid);
+
        parent = Some(oid);
+
    }
+
    Ok(commits)
+
}
+

+
#[derive(Clone, Debug)]
+
pub enum TreeData {
+
    Blob { name: String, data: String },
+
    Tree { name: String, inner: Vec<TreeData> },
+
}
+

+
impl TreeData {
+
    fn gen() -> impl Strategy<Value = Self> {
+
        let leaf =
+
            (alphanumeric(), alphanumeric()).prop_map(|(name, data)| Self::Blob { name, data });
+
        leaf.prop_recursive(8, 16, 5, |inner| {
+
            (proptest::collection::vec(inner, 1..5), alphanumeric())
+
                .prop_map(|(inner, name)| Self::Tree { name, inner })
+
        })
+
    }
+

+
    fn write(&self, repo: &git2::Repository) -> Result<git2::Oid, git2::Error> {
+
        let mut builder = repo.treebuilder(None)?;
+
        self.write_(repo, &mut builder)?;
+
        builder.write()
+
    }
+

+
    fn write_(
+
        &self,
+
        repo: &git2::Repository,
+
        builder: &mut git2::TreeBuilder,
+
    ) -> Result<git2::Oid, git2::Error> {
+
        match self {
+
            Self::Blob { name, data } => {
+
                let oid = repo.blob(data.as_bytes())?;
+
                builder.insert(name, oid, git2::FileMode::Blob.into())?;
+
            }
+
            Self::Tree { name, inner } => {
+
                for data in inner {
+
                    let oid = data.write_(repo, builder)?;
+
                    builder.insert(name, oid, git2::FileMode::Tree.into())?;
+
                }
+
            }
+
        }
+
        builder.write()
+
    }
+
}
added crates/radicle-surf/src/test/gen/commit/author.rs
@@ -0,0 +1,19 @@
+
use proptest::strategy::{Just, Strategy};
+
use radicle_git_metadata::author::{Author, Time};
+

+
use crate::test::gen;
+

+
pub fn author() -> impl Strategy<Value = Author> {
+
    gen::alphanumeric().prop_flat_map(move |name| {
+
        (Just(name), gen::alphanumeric()).prop_flat_map(|(name, domain)| {
+
            (Just(name), Just(domain), (0..1000i64)).prop_map(move |(name, domain, time)| {
+
                let email = format!("{name}@{domain}");
+
                Author {
+
                    name,
+
                    email,
+
                    time: Time::new(time, 0),
+
                }
+
            })
+
        })
+
    })
+
}
added crates/radicle-surf/src/test/gen/commit/headers.rs
@@ -0,0 +1,30 @@
+
use proptest::{collection, prop_oneof, strategy::Strategy};
+
use radicle_git_metadata::commit::headers::Headers;
+

+
use crate::test::gen;
+

+
pub fn headers() -> impl Strategy<Value = Headers> {
+
    collection::vec(prop_oneof![header(), signature()], 0..5).prop_map(|hs| {
+
        let mut headers = Headers::new();
+
        for (k, v) in hs {
+
            headers.push(&k, &v);
+
        }
+
        headers
+
    })
+
}
+

+
fn header() -> impl Strategy<Value = (String, String)> {
+
    (prop_oneof!["test", "foo", "foobar"], gen::alphanumeric())
+
}
+

+
pub fn signature() -> impl Strategy<Value = (String, String)> {
+
    ("gpgsig", prop_oneof![pgp(), ssh()])
+
}
+

+
pub fn pgp() -> impl Strategy<Value = String> {
+
    "-----BEGIN PGP SIGNATURE-----\r?\n([A-Za-z0-9+/=\r\n]+)\r?\n-----END PGP SIGNATURE-----"
+
}
+

+
pub fn ssh() -> impl Strategy<Value = String> {
+
    "-----BEGIN SSH SIGNATURE-----\r?\n([A-Za-z0-9+/=\r\n]+)\r?\n-----END SSH SIGNATURE-----"
+
}
added crates/radicle-surf/src/test/gen/commit/trailers.rs
@@ -0,0 +1,18 @@
+
use proptest::{collection, strategy::Strategy};
+
use radicle_git_metadata::commit::trailers::{OwnedTrailer, Token, Trailer};
+

+
use crate::test::gen;
+

+
pub fn trailers(n: usize) -> impl Strategy<Value = Vec<OwnedTrailer>> {
+
    collection::vec(trailer(), 0..n)
+
}
+

+
pub fn trailer() -> impl Strategy<Value = OwnedTrailer> {
+
    (gen::alpha(), gen::alphanumeric()).prop_map(|(token, value)| {
+
        Trailer {
+
            token: Token::try_from(format!("X-{}", token).as_str()).unwrap(),
+
            value: value.into(),
+
        }
+
        .to_owned()
+
    })
+
}
added crates/radicle-surf/src/test/last_commit.rs
@@ -0,0 +1,120 @@
+
use std::{path::PathBuf, str::FromStr};
+

+
use crate::{Branch, Repository};
+
use radicle_git_ref_format::refname;
+
use radicle_oid::Oid;
+

+
use super::GIT_PLATINUM;
+

+
#[test]
+
fn readme_missing_and_memory() {
+
    let repo = Repository::open(GIT_PLATINUM)
+
        .expect("Could not retrieve ./data/git-platinum as git repository");
+
    let oid =
+
        Oid::from_str("d3464e33d75c75c99bfb90fa2e9d16efc0b7d0e3").expect("Failed to parse SHA");
+

+
    // memory.rs is commited later so it should not exist here.
+
    let memory_last_commit_oid = repo
+
        .last_commit(&"src/memory.rs", oid)
+
        .expect("Failed to get last commit")
+
        .map(|commit| commit.id);
+

+
    assert_eq!(memory_last_commit_oid, None);
+

+
    // README.md exists in this commit.
+
    let readme_last_commit = repo
+
        .last_commit(&"README.md", oid)
+
        .expect("Failed to get last commit")
+
        .map(|commit| commit.id);
+

+
    assert_eq!(readme_last_commit, Some(oid));
+
}
+

+
#[test]
+
fn folder_svelte() {
+
    let repo = Repository::open(GIT_PLATINUM)
+
        .expect("Could not retrieve ./data/git-platinum as git repository");
+
    // Check that last commit is the actual last commit even if head commit differs.
+
    let oid =
+
        Oid::from_str("19bec071db6474af89c866a1bd0e4b1ff76e2b97").expect("Could not parse SHA");
+

+
    let expected_commit_id = Oid::from_str("f3a089488f4cfd1a240a9c01b3fcc4c34a4e97b2").unwrap();
+

+
    let folder_svelte = repo
+
        .last_commit(&"examples/Folder.svelte", oid)
+
        .expect("Failed to get last commit")
+
        .map(|commit| commit.id);
+

+
    assert_eq!(folder_svelte, Some(expected_commit_id));
+
}
+

+
#[test]
+
fn nest_directory() {
+
    let repo = Repository::open(GIT_PLATINUM)
+
        .expect("Could not retrieve ./data/git-platinum as git repository");
+
    // Check that last commit is the actual last commit even if head commit differs.
+
    let oid =
+
        Oid::from_str("19bec071db6474af89c866a1bd0e4b1ff76e2b97").expect("Failed to parse SHA");
+

+
    let expected_commit_id = Oid::from_str("2429f097664f9af0c5b7b389ab998b2199ffa977").unwrap();
+

+
    let nested_directory_tree_commit_id = repo
+
        .last_commit(&"this/is/a/really/deeply/nested/directory/tree", oid)
+
        .expect("Failed to get last commit")
+
        .map(|commit| commit.id);
+

+
    assert_eq!(nested_directory_tree_commit_id, Some(expected_commit_id));
+
}
+

+
#[test]
+
#[cfg(not(windows))]
+
fn can_get_last_commit_for_special_filenames() {
+
    let repo = Repository::open(GIT_PLATINUM)
+
        .expect("Could not retrieve ./data/git-platinum as git repository");
+

+
    // Check that last commit is the actual last commit even if head commit differs.
+
    let oid =
+
        Oid::from_str("a0dd9122d33dff2a35f564d564db127152c88e02").expect("Failed to parse SHA");
+

+
    let expected_commit_id = Oid::from_str("a0dd9122d33dff2a35f564d564db127152c88e02").unwrap();
+

+
    let backslash_commit_id = repo
+
        .last_commit(&r"special/faux\\path", oid)
+
        .expect("Failed to get last commit")
+
        .map(|commit| commit.id);
+
    assert_eq!(backslash_commit_id, Some(expected_commit_id));
+

+
    let ogre_commit_id = repo
+
        .last_commit(&"special/👹👹👹", oid)
+
        .expect("Failed to get last commit")
+
        .map(|commit| commit.id);
+
    assert_eq!(ogre_commit_id, Some(expected_commit_id));
+
}
+

+
#[test]
+
fn root() {
+
    let repo = Repository::open(GIT_PLATINUM)
+
        .expect("Could not retrieve ./data/git-platinum as git repository");
+
    let rev = Branch::local(refname!("master"));
+
    let root_last_commit_id = repo
+
        .last_commit(&PathBuf::new(), rev)
+
        .expect("Failed to get last commit")
+
        .map(|commit| commit.id);
+

+
    let expected_oid = repo
+
        .history(Branch::local(refname!("master")))
+
        .unwrap()
+
        .head()
+
        .id;
+
    assert_eq!(root_last_commit_id, Some(expected_oid));
+
}
+

+
#[test]
+
fn binary_file() {
+
    let repo = Repository::open(GIT_PLATINUM)
+
        .expect("Could not retrieve ./data/git-platinum as git repository");
+
    let history = repo.history(Branch::local(refname!("dev"))).unwrap();
+
    let file_commit = history.by_path(&"bin/cat").next();
+
    assert!(file_commit.is_some());
+
    println!("file commit: {:?}", &file_commit);
+
}
added crates/radicle-surf/src/test/mod.rs
@@ -0,0 +1,44 @@
+
#[cfg(test)]
+
const GIT_PLATINUM: &str = "../data/git-platinum";
+

+
#[cfg(test)]
+
mod file_system;
+

+
#[cfg(all(test, feature = "serde"))]
+
mod source;
+

+
#[cfg(all(test, feature = "serde"))]
+
mod branch;
+

+
#[cfg(test)]
+
mod code_browsing;
+

+
#[cfg(test)]
+
mod commit;
+

+
#[cfg(test)]
+
mod diff;
+

+
#[cfg(test)]
+
mod last_commit;
+

+
#[cfg(test)]
+
mod namespace;
+

+
#[cfg(test)]
+
mod reference;
+

+
#[cfg(test)]
+
mod rev;
+

+
#[cfg(test)]
+
mod submodule;
+

+
#[cfg(test)]
+
mod threading;
+

+
mod roundtrip;
+

+
mod gen;
+

+
mod repository;
added crates/radicle-surf/src/test/namespace.rs
@@ -0,0 +1,121 @@
+
use crate::{Branch, Error, Glob, Repository};
+
use pretty_assertions::{assert_eq, assert_ne};
+
use radicle_git_ref_format::{component, pattern, refname};
+

+
use super::GIT_PLATINUM;
+

+
#[test]
+
fn switch_to_banana() -> Result<(), Error> {
+
    let repo = Repository::open(GIT_PLATINUM)?;
+
    let history_master = repo.history(Branch::local(refname!("master")))?;
+
    repo.switch_namespace(&refname!("golden"))?;
+
    let history_banana = repo.history(Branch::local(refname!("banana")))?;
+

+
    assert_ne!(history_master.head(), history_banana.head());
+

+
    Ok(())
+
}
+

+
#[test]
+
fn me_namespace() -> Result<(), Error> {
+
    let repo = Repository::open(GIT_PLATINUM)?;
+
    let history = repo.history(Branch::local(refname!("master")))?;
+

+
    assert_eq!(repo.which_namespace().unwrap(), None);
+

+
    repo.switch_namespace(&refname!("me"))?;
+
    assert_eq!(repo.which_namespace().unwrap(), Some("me".parse()?));
+

+
    let history_feature = repo.history(Branch::local(refname!("feature/#1194")))?;
+
    assert_eq!(history.head(), history_feature.head());
+

+
    let expected_branches: Vec<Branch> = vec![Branch::local(refname!("feature/#1194"))];
+
    let mut branches = repo
+
        .branches(Glob::all_heads())?
+
        .collect::<Result<Vec<_>, _>>()?;
+
    branches.sort();
+

+
    assert_eq!(expected_branches, branches);
+

+
    let expected_branches: Vec<Branch> = vec![Branch::remote(
+
        component!("fein"),
+
        refname!("heads/feature/#1194"),
+
    )];
+
    let mut branches = repo
+
        .branches(Glob::remotes(pattern!("fein/*")))?
+
        .collect::<Result<Vec<_>, _>>()?;
+
    branches.sort();
+

+
    assert_eq!(expected_branches, branches);
+

+
    Ok(())
+
}
+

+
#[test]
+
fn golden_namespace() -> Result<(), Error> {
+
    let repo = Repository::open(GIT_PLATINUM)?;
+
    let history = repo.history(Branch::local(refname!("master")))?;
+

+
    assert_eq!(repo.which_namespace().unwrap(), None);
+

+
    repo.switch_namespace(&refname!("golden"))?;
+

+
    assert_eq!(repo.which_namespace().unwrap(), Some("golden".parse()?));
+

+
    let golden_history = repo.history(Branch::local(refname!("master")))?;
+
    assert_eq!(history.head(), golden_history.head());
+

+
    let expected_branches: Vec<Branch> = vec![
+
        Branch::local(refname!("banana")),
+
        Branch::local(refname!("master")),
+
    ];
+
    let mut branches = repo
+
        .branches(Glob::all_heads())?
+
        .collect::<Result<Vec<_>, _>>()?;
+
    branches.sort();
+

+
    assert_eq!(expected_branches, branches);
+

+
    // NOTE: these tests used to remove the categories, i.e. heads & tags, but that
+
    // was specialised logic based on the radicle-link storage layout.
+
    let remote = component!("kickflip");
+
    let expected_branches: Vec<Branch> = vec![
+
        Branch::remote(remote.clone(), refname!("heads/fakie/bigspin")),
+
        Branch::remote(remote.clone(), refname!("heads/heelflip")),
+
        Branch::remote(remote, refname!("tags/v0.1.0")),
+
    ];
+
    let mut branches = repo
+
        .branches(Glob::remotes(pattern!("kickflip/*")))?
+
        .collect::<Result<Vec<_>, _>>()?;
+
    branches.sort();
+

+
    assert_eq!(expected_branches, branches);
+

+
    Ok(())
+
}
+

+
#[test]
+
fn silver_namespace() -> Result<(), Error> {
+
    let repo = Repository::open(GIT_PLATINUM)?;
+
    let history = repo.history(Branch::local(refname!("master")))?;
+

+
    assert_eq!(repo.which_namespace().unwrap(), None);
+

+
    repo.switch_namespace(&refname!("golden/silver"))?;
+
    assert_eq!(
+
        repo.which_namespace().unwrap(),
+
        Some("golden/silver".parse()?)
+
    );
+
    let silver_history = repo.history(Branch::local(refname!("master")))?;
+
    assert_ne!(history.head(), silver_history.head());
+

+
    let expected_branches: Vec<Branch> = vec![Branch::local(refname!("master"))];
+
    let mut branches = repo
+
        .branches(Glob::all_heads().branches().and(Glob::all_remotes()))?
+
        .collect::<Result<Vec<_>, _>>()?;
+
    branches.sort();
+

+
    assert_eq!(expected_branches, branches);
+

+
    Ok(())
+
}
added crates/radicle-surf/src/test/reference.rs
@@ -0,0 +1,49 @@
+
use crate::{Glob, Repository};
+
use radicle_git_ref_format::pattern;
+

+
use super::GIT_PLATINUM;
+

+
#[test]
+
fn test_branches() {
+
    let repo = Repository::open(GIT_PLATINUM).unwrap();
+
    let heads = Glob::all_heads();
+
    let branches = repo.branches(heads.clone()).unwrap();
+
    for b in branches {
+
        println!("{}", b.unwrap().refname());
+
    }
+
    let branches = repo
+
        .branches(heads.branches().and(Glob::remotes(pattern!("banana/*"))))
+
        .unwrap();
+
    for b in branches {
+
        println!("{}", b.unwrap().refname());
+
    }
+
}
+

+
#[test]
+
fn test_tag_snapshot() {
+
    let repo = Repository::open(GIT_PLATINUM).unwrap();
+
    let tags = repo
+
        .tags(&Glob::all_tags())
+
        .unwrap()
+
        .collect::<Result<Vec<_>, _>>()
+
        .unwrap();
+
    assert_eq!(tags.len(), 6);
+
    let root_dir = repo.root_dir(&tags[0]).unwrap();
+
    assert_eq!(root_dir.entries(&repo).unwrap().entries().count(), 1);
+
}
+

+
#[test]
+
fn test_namespaces() {
+
    let repo = Repository::open(GIT_PLATINUM).unwrap();
+

+
    let namespaces = repo.namespaces(&Glob::all_namespaces()).unwrap();
+
    assert_eq!(namespaces.count(), 3);
+
    let namespaces = repo
+
        .namespaces(&Glob::namespaces(pattern!("golden/*")))
+
        .unwrap();
+
    assert_eq!(namespaces.count(), 2);
+
    let namespaces = repo
+
        .namespaces(&Glob::namespaces(pattern!("golden/*")).insert(pattern!("me/*")))
+
        .unwrap();
+
    assert_eq!(namespaces.count(), 3);
+
}
added crates/radicle-surf/src/test/repository.rs
@@ -0,0 +1,93 @@
+
use std::{convert::Infallible, io, path::Path};
+

+
use git2::Oid;
+
use radicle_git_metadata::commit::CommitData;
+
use radicle_git_ref_format::RefString;
+

+
use crate::test::gen::commit::{self, TreeData};
+

+
pub struct Fixture {
+
    pub dir: tempfile::TempDir,
+
    pub repo: git2::Repository,
+
    pub head: Option<git2::Oid>,
+
}
+

+
/// Initialise a [`git2::Repository`] in a temporary directory.
+
///
+
/// The provided `commits` will be added to the repository, and the
+
/// head commit will be returned.
+
pub fn fixture(
+
    refname: &RefString,
+
    commits: Vec<CommitData<TreeData, Infallible>>,
+
) -> io::Result<Fixture> {
+
    let dir = tempfile::tempdir().unwrap();
+
    let repo = git2::Repository::init(dir.path()).map_err(io_other)?;
+
    let commits = commit::write_commits(&repo, commits).map_err(io_other)?;
+
    let head = commits.last().copied();
+

+
    if let Some(head) = head {
+
        repo.reference(refname.as_str(), head, false, "Initialise repository")
+
            .map_err(io_other)?;
+
    }
+

+
    Ok(Fixture { dir, repo, head })
+
}
+

+
pub fn bare_fixture(
+
    refname: &RefString,
+
    commits: Vec<CommitData<TreeData, Infallible>>,
+
) -> io::Result<Fixture> {
+
    let dir = tempfile::tempdir().unwrap();
+
    let repo = git2::Repository::init_bare(dir.path()).map_err(io_other)?;
+
    let commits = commit::write_commits(&repo, commits).map_err(io_other)?;
+
    let head = commits.last().copied();
+

+
    if let Some(head) = head {
+
        repo.reference(refname.as_str(), head, false, "Initialise repository")
+
            .map_err(io_other)?;
+
    }
+

+
    Ok(Fixture { dir, repo, head })
+
}
+

+
pub fn submodule<'a>(
+
    parent: &'a git2::Repository,
+
    child: &'a git2::Repository,
+
    refname: &RefString,
+
    head: Oid,
+
    author: &git2::Signature,
+
) -> io::Result<git2::Submodule<'a>> {
+
    let url = format!("file://{}", child.path().canonicalize()?.display());
+
    let mut sub = parent
+
        .submodule(url.as_str(), Path::new("submodule"), true)
+
        .map_err(io_other)?;
+
    sub.open().map_err(io_other)?;
+
    sub.clone(Some(&mut git2::SubmoduleUpdateOptions::default()))
+
        .map_err(io_other)?;
+
    sub.add_to_index(true).map_err(io_other)?;
+
    sub.add_finalize().map_err(io_other)?;
+
    {
+
        let mut ix = parent.index().map_err(io_other)?;
+
        let tree = ix.write_tree_to(parent).map_err(io_other)?;
+
        let tree = parent.find_tree(tree).map_err(io_other)?;
+
        let head = parent.find_commit(head).map_err(io_other)?;
+
        parent
+
            .commit(
+
                Some(refname.as_str()),
+
                author,
+
                author,
+
                "Commit submodule",
+
                &tree,
+
                &[&head],
+
            )
+
            .map_err(io_other)?;
+
    }
+
    Ok(sub)
+
}
+

+
fn io_other<E>(e: E) -> io::Error
+
where
+
    E: std::error::Error + Send + Sync + 'static,
+
{
+
    io::Error::other(e)
+
}
added crates/radicle-surf/src/test/rev.rs
@@ -0,0 +1,92 @@
+
use std::str::FromStr;
+

+
use crate::{Branch, Error, Oid, Repository};
+
use radicle_git_ref_format::{component, refname};
+

+
use super::GIT_PLATINUM;
+

+
// **FIXME**: This seems to break occasionally on
+
// buildkite. For some reason the commit
+
// 3873745c8f6ffb45c990eb23b491d4b4b6182f95, which is on master
+
// (currently HEAD), is not found. It seems to load the history
+
// with d6880352fc7fda8f521ae9b7357668b17bb5bad5 as the HEAD.
+
//
+
// To temporarily fix this, we need to select "New Build" from the build kite
+
// build page that's failing.
+
// * Under "Message" put whatever you want.
+
// * Under "Branch" put in the branch you're working on.
+
// * Expand "Options" and select "clean checkout".
+
#[test]
+
fn _master() -> Result<(), Error> {
+
    let repo = Repository::open(GIT_PLATINUM)?;
+
    let mut history = repo.history(Branch::remote(component!("origin"), refname!("master")))?;
+

+
    let commit1 = Oid::from_str("3873745c8f6ffb45c990eb23b491d4b4b6182f95")?;
+
    assert!(
+
        history.any(|commit| commit.unwrap().id == commit1),
+
        "commit_id={}, history =\n{:#?}",
+
        commit1,
+
        &history
+
    );
+

+
    let commit2 = Oid::from_str("d6880352fc7fda8f521ae9b7357668b17bb5bad5")?;
+
    assert!(
+
        history.any(|commit| commit.unwrap().id == commit2),
+
        "commit_id={}, history =\n{:#?}",
+
        commit2,
+
        &history
+
    );
+

+
    Ok(())
+
}
+

+
#[test]
+
fn commit() -> Result<(), Error> {
+
    let repo = Repository::open(GIT_PLATINUM)?;
+
    let rev = Oid::from_str("3873745c8f6ffb45c990eb23b491d4b4b6182f95")?;
+
    let mut history = repo.history(rev)?;
+

+
    let commit1 = Oid::from_str("3873745c8f6ffb45c990eb23b491d4b4b6182f95")?;
+
    assert!(history.any(|commit| commit.unwrap().id == commit1));
+

+
    Ok(())
+
}
+

+
#[test]
+
fn commit_parents() -> Result<(), Error> {
+
    let repo = Repository::open(GIT_PLATINUM)?;
+
    let rev = Oid::from_str("3873745c8f6ffb45c990eb23b491d4b4b6182f95")?;
+
    let history = repo.history(rev)?;
+
    let commit = history.head();
+

+
    assert_eq!(
+
        commit.parents,
+
        vec![Oid::from_str("d6880352fc7fda8f521ae9b7357668b17bb5bad5")?]
+
    );
+

+
    Ok(())
+
}
+

+
#[test]
+
fn commit_short() -> Result<(), Error> {
+
    let repo = Repository::open(GIT_PLATINUM)?;
+
    let rev = repo.oid("3873745c8")?;
+
    let mut history = repo.history(rev)?;
+

+
    let commit1 = Oid::from_str("3873745c8f6ffb45c990eb23b491d4b4b6182f95")?;
+
    assert!(history.any(|commit| commit.unwrap().id == commit1));
+

+
    Ok(())
+
}
+

+
#[test]
+
fn tag() -> Result<(), Error> {
+
    let repo = Repository::open(GIT_PLATINUM)?;
+
    let rev = refname!("refs/tags/v0.2.0");
+
    let history = repo.history(&rev)?;
+

+
    let commit1 = Oid::from_str("2429f097664f9af0c5b7b389ab998b2199ffa977")?;
+
    assert_eq!(history.head().id, commit1);
+

+
    Ok(())
+
}
added crates/radicle-surf/src/test/roundtrip.rs
@@ -0,0 +1,44 @@
+
use std::{
+
    fmt::{Debug, Display},
+
    str::FromStr,
+
};
+

+
use pretty_assertions::assert_eq;
+

+
#[cfg(feature = "serde")]
+
pub fn json<A>(a: A)
+
where
+
    for<'de> A: Debug + PartialEq + serde::Serialize + serde::Deserialize<'de>,
+
{
+
    assert_eq!(
+
        a,
+
        serde_json::from_str(&serde_json::to_string(&a).unwrap()).unwrap()
+
    )
+
}
+

+
#[cfg(feature = "serde")]
+
pub fn json_value<A>(a: A)
+
where
+
    for<'de> A: Clone + Debug + PartialEq + serde::Serialize + serde::Deserialize<'de>,
+
{
+
    assert_eq!(
+
        a.clone(),
+
        serde_json::from_value(serde_json::to_value(a).unwrap()).unwrap()
+
    )
+
}
+

+
#[cfg(feature = "minicbor")]
+
pub fn cbor<A>(a: A)
+
where
+
    for<'de> A: Debug + PartialEq + minicbor::Encode + minicbor::Decode<'de>,
+
{
+
    assert_eq!(a, minicbor::decode(&minicbor::to_vec(&a).unwrap()).unwrap())
+
}
+

+
pub fn str<A>(a: A)
+
where
+
    A: Debug + PartialEq + Display + FromStr,
+
    <A as FromStr>::Err: Debug,
+
{
+
    assert_eq!(a, a.to_string().parse().unwrap())
+
}
added crates/radicle-surf/src/test/source.rs
@@ -0,0 +1,222 @@
+
use std::path::PathBuf;
+

+
use crate::{Branch, Glob, Repository};
+
use radicle_git_ref_format::refname;
+
use serde_json::json;
+

+
const GIT_PLATINUM: &str = "../data/git-platinum";
+

+
#[test]
+
fn tree_serialization() {
+
    let repo = Repository::open(GIT_PLATINUM).unwrap();
+
    let tree = repo.tree(refname!("refs/heads/master"), &"src").unwrap();
+

+
    let expected = json!({
+
      "oid": "ed52e9f8dfe1d8b374b2a118c25235349a743dd2",
+
      "entries": [
+
        {
+
          "name": "Eval.hs",
+
          "kind": "blob",
+
          "oid": "7d6240123a8d8ea8a8376610168a0a4bcb96afd0",
+
          "commit": "src/Eval.hs"
+
        },
+
        {
+
          "name": "memory.rs",
+
          "kind": "blob",
+
          "oid": "b84992d24be67536837f5ab45a943f1b3f501878",
+
          "commit": "src/memory.rs"
+
        }
+
      ],
+
      "commit": {
+
        "id": "a0dd9122d33dff2a35f564d564db127152c88e02",
+
        "author": {
+
          "name": "Rūdolfs Ošiņš",
+
          "email": "rudolfs@osins.org",
+
          "time": 1602778504
+
        },
+
        "committer": {
+
          "name": "GitHub",
+
          "email": "noreply@github.com",
+
          "time": 1602778504
+
        },
+
        "summary": "Add files with special characters in their filenames (#5)",
+
        "message": "Add files with special characters in their filenames (#5)\n\n",
+
        "description": "",
+
        "parents": [
+
          "223aaf87d6ea62eef0014857640fd7c8dd0f80b5"
+
        ]
+
      },
+
      "root": "src"
+
    });
+

+
    assert_eq!(
+
        serde_json::to_value(&tree).unwrap(),
+
        expected,
+
        "Got:\n{}",
+
        serde_json::to_string_pretty(&tree).unwrap()
+
    )
+
}
+

+
#[test]
+
fn test_tree_last_commit() {
+
    let repo = Repository::open(GIT_PLATINUM).unwrap();
+
    let tree = repo.tree(refname!("refs/heads/master"), &"src").unwrap();
+
    let last_commit = tree.last_commit(&repo).unwrap();
+
    assert_ne!(*tree.commit(), last_commit);
+
    assert_eq!(
+
        last_commit.id.to_string(),
+
        "a57846bbc8ced6587bf8329fc4bce970eb7b757e"
+
    )
+
}
+

+
#[test]
+
fn repo_tree_empty_branch() {
+
    let repo = Repository::open(GIT_PLATINUM).unwrap();
+
    let rev = Branch::local(refname!("empty-branch"));
+
    let tree = repo.tree(rev, &"").unwrap();
+
    assert_eq!(tree.entries().len(), 0);
+

+
    // Verify the last commit is the empty commit.
+
    assert_eq!(
+
        tree.commit().id.to_string(),
+
        "e972683fe8136bf8a5cb2378cf50303554008049"
+
    );
+
}
+

+
#[test]
+
fn repo_tree() {
+
    let repo = Repository::open(GIT_PLATINUM).unwrap();
+
    let tree = repo
+
        .tree("27acd68c7504755aa11023300890bb85bbd69d45", &"src")
+
        .unwrap();
+
    assert_eq!(tree.entries().len(), 3);
+

+
    let commit_header = tree.commit();
+
    assert_eq!(
+
        commit_header.id.to_string(),
+
        "27acd68c7504755aa11023300890bb85bbd69d45"
+
    );
+

+
    let tree_oid = tree.object_id();
+
    assert_eq!(
+
        tree_oid.to_string(),
+
        "dbd5d80c64a00969f521b96401a315e9481e9561"
+
    );
+

+
    let entries = tree.entries();
+
    assert_eq!(entries.len(), 3);
+
    let entry = &entries[0];
+
    assert!(!entry.is_tree());
+
    assert_eq!(entry.name(), "Eval.hs");
+
    assert_eq!(
+
        entry.object_id().to_string(),
+
        "8c7447d13b907aa994ac3a38317c1e9633bf0732"
+
    );
+
    let commit = entry.commit();
+
    assert_eq!(
+
        commit.id.to_string(),
+
        "27acd68c7504755aa11023300890bb85bbd69d45"
+
    );
+
    let last_commit = entry.last_commit(&repo).unwrap();
+
    assert_eq!(
+
        last_commit.id.to_string(),
+
        "e24124b7538658220b5aaf3b6ef53758f0a106dc"
+
    );
+

+
    // Verify that an empty path works for getting the root tree.
+
    let root_tree = repo
+
        .tree("27acd68c7504755aa11023300890bb85bbd69d45", &"")
+
        .unwrap();
+
    assert_eq!(root_tree.entries().len(), 8);
+
}
+

+
#[test]
+
fn repo_blob() {
+
    let repo = Repository::open(GIT_PLATINUM).unwrap();
+
    let blob = repo
+
        .blob("27acd68c7504755aa11023300890bb85bbd69d45", &"src/memory.rs")
+
        .unwrap();
+

+
    let blob_oid = blob.object_id();
+
    assert_eq!(
+
        blob_oid.to_string(),
+
        "b84992d24be67536837f5ab45a943f1b3f501878"
+
    );
+

+
    let commit_header = blob.commit();
+
    assert_eq!(
+
        commit_header.id.to_string(),
+
        "e24124b7538658220b5aaf3b6ef53758f0a106dc"
+
    );
+

+
    assert!(!blob.is_binary());
+

+
    // Verify the blob content size matches with the file size of "memory.rs"
+
    let content = blob.content();
+
    assert_eq!(blob.size(), 6253);
+

+
    // Verify to_owned().
+
    let blob_owned = blob.to_owned();
+
    assert_eq!(blob_owned.size(), 6253);
+
    assert_eq!(blob.content(), blob_owned.content());
+

+
    // Verify JSON output is the same.
+
    let json_ref = json!({ "content": content }).to_string();
+
    let json_owned = json!( {
+
      "content": blob_owned.content()
+
    })
+
    .to_string();
+
    assert_eq!(json_ref, json_owned);
+
}
+

+
#[test]
+
fn tree_ordering() {
+
    let repo = Repository::open(GIT_PLATINUM).unwrap();
+
    let tree = repo
+
        .tree(refname!("refs/heads/master"), &PathBuf::new())
+
        .unwrap();
+
    assert_eq!(
+
        tree.entries()
+
            .iter()
+
            .map(|entry| entry.name().to_string())
+
            .collect::<Vec<_>>(),
+
        vec![
+
            "bin".to_string(),
+
            "special".to_string(),
+
            "src".to_string(),
+
            "text".to_string(),
+
            "this".to_string(),
+
            ".i-am-well-hidden".to_string(),
+
            ".i-too-am-hidden".to_string(),
+
            "README.md".to_string(),
+
        ]
+
    );
+
}
+

+
#[test]
+
fn commit_branches() {
+
    let repo = Repository::open(GIT_PLATINUM).unwrap();
+
    let init_commit = "d3464e33d75c75c99bfb90fa2e9d16efc0b7d0e3";
+
    let glob = Glob::all_heads().branches().and(Glob::all_remotes());
+
    let branches = repo.revision_branches(init_commit, glob).unwrap();
+

+
    assert_eq!(branches.len(), 11);
+

+
    let refnames: Vec<_> = branches.iter().map(|b| b.refname().to_string()).collect();
+
    assert_eq!(
+
        refnames,
+
        vec![
+
            "refs/heads/dev",
+
            "refs/heads/diff-test",
+
            "refs/heads/empty-branch",
+
            "refs/heads/master",
+
            "refs/remotes/banana/orange/pineapple",
+
            "refs/remotes/banana/pineapple",
+
            "refs/remotes/origin/HEAD",
+
            "refs/remotes/origin/dev",
+
            "refs/remotes/origin/diff-test",
+
            "refs/remotes/origin/empty-branch",
+
            "refs/remotes/origin/master"
+
        ]
+
    );
+
}
added crates/radicle-surf/src/test/submodule.rs
@@ -0,0 +1,87 @@
+
use std::{convert::Infallible, path::Path};
+

+
use super::gen;
+
use crate::tree::EntryKind;
+
use crate::{fs, Branch, Repository};
+
use proptest::{collection, proptest};
+
use radicle_git_metadata::commit::CommitData;
+
use radicle_git_ref_format::refname;
+

+
proptest! {
+
    #[test]
+
    fn test_submodule(
+
        initial in gen::commit::commit(),
+
        commits in collection::vec(gen::commit::commit(), 1..5)
+
    ) {
+
        prop::test_submodule(initial, commits)
+
    }
+

+
    #[ignore = "segfault"]
+
    #[test]
+
    fn test_submodule_bare(
+
        initial in gen::commit::commit(),
+
        commits in collection::vec(gen::commit::commit(), 1..5)
+
    ) {
+
        prop::test_submodule_bare(initial, commits)
+
    }
+

+
}
+

+
mod prop {
+
    use crate::test::gen::commit;
+
    use crate::test::repository;
+

+
    use super::*;
+

+
    pub fn test_submodule(
+
        initial: CommitData<commit::TreeData, Infallible>,
+
        commits: Vec<CommitData<commit::TreeData, Infallible>>,
+
    ) {
+
        let refname = refname!("refs/heads/master");
+
        let author = git2::Signature::try_from(initial.author()).unwrap();
+

+
        let submodule = repository::fixture(&refname, commits).unwrap();
+
        let parent = repository::fixture(&refname, vec![initial]).unwrap();
+

+
        let head = parent.head.expect("missing initial commit");
+
        let sub =
+
            repository::submodule(&parent.repo, &submodule.repo, &refname, head, &author).unwrap();
+

+
        let repo = Repository::open(parent.repo.path()).unwrap();
+
        let branch = Branch::local(refname);
+
        let dir = repo.root_dir(&branch).unwrap();
+

+
        let platinum = dir.find_entry(&sub.path(), &repo).unwrap();
+
        assert!(matches!(&platinum, fs::Entry::Submodule(module) if module.url().is_some()));
+

+
        let root = repo.tree(&branch, &Path::new("")).unwrap();
+
        let kind = EntryKind::from(platinum);
+
        assert!(root.entries().iter().any(|e| e.entry() == &kind));
+
    }
+

+
    pub fn test_submodule_bare(
+
        initial: CommitData<commit::TreeData, Infallible>,
+
        commits: Vec<CommitData<commit::TreeData, Infallible>>,
+
    ) {
+
        let refname = refname!("refs/heads/master");
+
        let author = git2::Signature::try_from(initial.author()).unwrap();
+

+
        let submodule = repository::fixture(&refname, commits).unwrap();
+
        let parent = repository::bare_fixture(&refname, vec![initial]).unwrap();
+

+
        let head = parent.head.expect("missing initial commit");
+
        let sub =
+
            repository::submodule(&parent.repo, &submodule.repo, &refname, head, &author).unwrap();
+

+
        let repo = Repository::open(parent.repo.path()).unwrap();
+
        let branch = Branch::local(refname);
+
        let dir = repo.root_dir(&branch).unwrap();
+

+
        let platinum = dir.find_entry(&sub.path(), &repo).unwrap();
+
        assert!(matches!(&platinum, fs::Entry::Submodule(module) if module.url().is_some()));
+

+
        let root = repo.tree(&branch, &Path::new("")).unwrap();
+
        let kind = EntryKind::from(platinum);
+
        assert!(root.entries().iter().any(|e| e.entry() == &kind));
+
    }
+
}
added crates/radicle-surf/src/test/threading.rs
@@ -0,0 +1,37 @@
+
use std::sync::{Mutex, MutexGuard};
+

+
use crate::{Branch, Error, Glob, Repository};
+
use radicle_git_ref_format::{component, refname};
+

+
use super::GIT_PLATINUM;
+

+
#[test]
+
fn basic_test() -> Result<(), Error> {
+
    let shared_repo = Mutex::new(Repository::open(GIT_PLATINUM)?);
+
    let locked_repo: MutexGuard<Repository> = shared_repo.lock().unwrap();
+
    let mut branches = locked_repo
+
        .branches(Glob::all_heads().branches().and(Glob::all_remotes()))?
+
        .collect::<Result<Vec<_>, _>>()?;
+
    branches.sort();
+

+
    let origin = component!("origin");
+
    let banana = component!("banana");
+
    assert_eq!(
+
        branches,
+
        vec![
+
            Branch::local(refname!("dev")),
+
            Branch::local(refname!("diff-test")),
+
            Branch::local(refname!("empty-branch")),
+
            Branch::local(refname!("master")),
+
            Branch::remote(banana.clone(), refname!("orange/pineapple")),
+
            Branch::remote(banana, refname!("pineapple")),
+
            Branch::remote(origin.clone(), refname!("HEAD")),
+
            Branch::remote(origin.clone(), refname!("dev")),
+
            Branch::remote(origin.clone(), refname!("diff-test")),
+
            Branch::remote(origin.clone(), refname!("empty-branch")),
+
            Branch::remote(origin, refname!("master")),
+
        ]
+
    );
+

+
    Ok(())
+
}
modified crates/radicle-surf/t/Cargo.toml
@@ -19,21 +19,5 @@ proptest = "1"
serde_json = "1"
url = "2.5"

-
[dev-dependencies.git2]
-
version = "0.19"
-
default-features = false
-
features = ["vendored-libgit2"]
-

-
[dev-dependencies.radicle-git-ext]
-
path = "../../radicle-git-ext"
-

-
[dev-dependencies.radicle-git-ext-test]
-
path = "../../radicle-git-ext/t"
-
features = ["test"]
-

-
[dev-dependencies.radicle-surf]
-
path = ".."
-
features = ["serde"]
-

[dev-dependencies.test-helpers]
path = "../../test/test-helpers"
deleted crates/radicle-surf/t/src/branch.rs
@@ -1,24 +0,0 @@
-
use proptest::prelude::*;
-
use radicle_git_ext_test::git_ref_format::gen;
-
use radicle_git_ref_format::{RefStr, RefString};
-
use radicle_surf::Branch;
-
use test_helpers::roundtrip;
-

-
proptest! {
-
    #[test]
-
    fn prop_test_branch(branch in gen_branch()) {
-
        roundtrip::json(branch)
-
    }
-
}
-

-
fn gen_branch() -> impl Strategy<Value = Branch> {
-
    prop_oneof![
-
        gen::valid().prop_map(|name| Branch::local(RefString::try_from(name).unwrap())),
-
        (gen::valid(), gen::valid()).prop_map(|(remote, name): (String, String)| {
-
            let remote =
-
                RefStr::try_from_str(&remote).expect("BUG: reference strings should be valid");
-
            let name = RefStr::try_from_str(&name).expect("BUG: reference strings should be valid");
-
            Branch::remote(remote.head(), name)
-
        })
-
    ]
-
}
deleted crates/radicle-surf/t/src/code_browsing.rs
@@ -1,100 +0,0 @@
-
use std::path::Path;
-

-
use radicle_git_ref_format::refname;
-
use radicle_surf::{
-
    fs::{self, Directory},
-
    Branch, Repository,
-
};
-

-
use super::GIT_PLATINUM;
-

-
#[test]
-
fn iterate_root_dir_recursive() {
-
    let repo = Repository::open(GIT_PLATINUM).unwrap();
-

-
    let root_dir = repo.root_dir(Branch::local(refname!("master"))).unwrap();
-
    let count = println_dir(&root_dir, &repo);
-

-
    assert_eq!(count, 36); // Check total file count.
-

-
    /// Prints items in `dir` with `indent_level`.
-
    /// For sub-directories, will do Depth-First-Search and print
-
    /// recursively.
-
    /// Returns the number of items visited (i.e. printed)
-
    fn println_dir(dir: &Directory, repo: &Repository) -> i32 {
-
        dir.traverse::<fs::error::Directory, _, _>(
-
            repo,
-
            (0, 0),
-
            &mut |(count, indent_level), entry| {
-
                println!("> {}{}", " ".repeat(indent_level * 4), entry.name());
-
                match entry {
-
                    fs::Entry::File(_) => Ok((count + 1, indent_level)),
-
                    fs::Entry::Directory(_) => Ok((count + 1, indent_level + 1)),
-
                    fs::Entry::Submodule(_) => Ok((count + 1, indent_level)),
-
                }
-
            },
-
        )
-
        .unwrap()
-
        .0
-
    }
-
}
-

-
#[test]
-
fn browse_repo_lazily() {
-
    let repo = Repository::open(GIT_PLATINUM).unwrap();
-

-
    let root_dir = repo.root_dir(Branch::local(refname!("master"))).unwrap();
-
    let count = root_dir.entries(&repo).unwrap().entries().count();
-
    assert_eq!(count, 8);
-
    let count = traverse(&root_dir, &repo);
-
    assert_eq!(count, 36);
-

-
    fn traverse(dir: &Directory, repo: &Repository) -> i32 {
-
        dir.traverse::<fs::error::Directory, _, _>(repo, 0, &mut |count, _| Ok(count + 1))
-
            .unwrap()
-
    }
-
}
-

-
#[test]
-
fn test_file_history() {
-
    let repo = Repository::open(GIT_PLATINUM).unwrap();
-
    let history = repo.history(Branch::local(refname!("dev"))).unwrap();
-
    let path = Path::new("README.md");
-
    let mut file_history = history.by_path(&path);
-
    let commit = file_history.next().unwrap().unwrap();
-
    let file = repo.get_commit_file(&commit.id, &path).unwrap();
-
    assert_eq!(file.size(), 67);
-
}
-

-
#[test]
-
fn test_commit_history() {
-
    let repo = Repository::open(GIT_PLATINUM).unwrap();
-
    let head = "a0dd9122d33dff2a35f564d564db127152c88e02";
-

-
    // verify `&str` works.
-
    let h1 = repo.history(head).unwrap();
-

-
    // verify `&String` works.
-
    let head_string = head.to_string();
-
    let h2 = repo.history(&head_string).unwrap();
-

-
    assert_eq!(h1.head().id, h2.head().id);
-
}
-

-
#[test]
-
fn test_commit_signature() {
-
    let repo = Repository::open(GIT_PLATINUM).unwrap();
-
    let commit_with_signature = "e24124b7538658220b5aaf3b6ef53758f0a106dc";
-
    let signature = repo.extract_signature(commit_with_signature, None).unwrap();
-
    assert!(signature.is_some());
-

-
    let commit_without_signature = "80bacafba303bf0cdf6142921f430ff265f25095";
-
    let signature = repo
-
        .extract_signature(commit_without_signature, None)
-
        .unwrap();
-
    assert!(signature.is_none());
-

-
    let commit_nonexist = "8080808080";
-
    let signature = repo.extract_signature(commit_nonexist, None);
-
    assert!(signature.is_err());
-
}
deleted crates/radicle-surf/t/src/commit.rs
@@ -1,32 +0,0 @@
-
use std::str::FromStr;
-

-
use proptest::prelude::*;
-
use radicle_oid::Oid;
-
use radicle_surf::{Author, Commit, Time};
-
use test_helpers::roundtrip;
-

-
proptest! {
-
    #[test]
-
    fn prop_test_commits(commit in commits_strategy()) {
-
        roundtrip::json(commit)
-
    }
-
}
-

-
fn commits_strategy() -> impl Strategy<Value = Commit> {
-
    ("[a-fA-F0-9]{40}", any::<String>(), any::<i64>()).prop_map(|(id, text, time)| Commit {
-
        id: Oid::from_str(&id).unwrap(),
-
        author: Author {
-
            name: text.clone(),
-
            email: text.clone(),
-
            time: Time::new(time, 0),
-
        },
-
        committer: Author {
-
            name: text.clone(),
-
            email: text.clone(),
-
            time: Time::new(time, 0),
-
        },
-
        message: text.clone(),
-
        summary: text,
-
        parents: vec![Oid::from_str(&id).unwrap(), Oid::from_str(&id).unwrap()],
-
    })
-
}
deleted crates/radicle-surf/t/src/diff.rs
@@ -1,672 +0,0 @@
-
use pretty_assertions::assert_eq;
-
use radicle_git_ref_format::refname;
-
use radicle_oid::Oid;
-
use radicle_surf::{
-
    diff::{
-
        Added, Diff, DiffContent, DiffFile, EofNewLine, FileDiff, FileMode, FileStats, Hunk, Line,
-
        Modification, Modified, Stats,
-
    },
-
    Branch, Error, Repository,
-
};
-
use std::{path::Path, str::FromStr};
-

-
use super::GIT_PLATINUM;
-

-
#[test]
-
fn test_initial_diff() -> Result<(), Error> {
-
    let repo = Repository::open(GIT_PLATINUM)?;
-
    let oid = Oid::from_str("d3464e33d75c75c99bfb90fa2e9d16efc0b7d0e3")?;
-
    let commit = repo.commit(oid).unwrap();
-
    assert!(commit.parents.is_empty());
-

-
    let diff = repo.diff_commit(oid)?;
-
    let diff_stats = *diff.stats();
-
    let diff_files = diff.into_files();
-

-
    let expected_files = vec![FileDiff::Added(Added {
-
        path: Path::new("README.md").to_path_buf(),
-
        diff: DiffContent::Plain {
-
            hunks: vec![Hunk {
-
                header: Line::from(b"@@ -0,0 +1 @@\n".to_vec()),
-
                lines: vec![Modification::addition(
-
                    b"This repository is a data source for the Upstream front-end tests.\n"
-
                        .to_vec(),
-
                    1,
-
                )],
-
                old: 0..0,
-
                new: 1..2,
-
            }]
-
            .into(),
-
            stats: FileStats {
-
                additions: 1,
-
                deletions: 0,
-
            },
-
            eof: EofNewLine::default(),
-
        },
-
        new: DiffFile {
-
            oid: Oid::from_str("7f48df0118b1674f4ab0ed1717c1368091a5dddc").unwrap(),
-
            mode: FileMode::Blob,
-
        },
-
    })];
-

-
    let expected_stats = Stats {
-
        files_changed: 1,
-
        insertions: 1,
-
        deletions: 0,
-
    };
-

-
    assert_eq!(expected_files, diff_files);
-
    assert_eq!(expected_stats, diff_stats);
-

-
    Ok(())
-
}
-

-
#[test]
-
fn test_diff_of_rev() -> Result<(), Error> {
-
    let repo = Repository::open(GIT_PLATINUM)?;
-
    let diff = repo.diff_commit("80bacafba303bf0cdf6142921f430ff265f25095")?;
-
    assert_eq!(diff.files().count(), 1);
-
    Ok(())
-
}
-

-
#[test]
-
fn test_diff_file() -> Result<(), Error> {
-
    let repo = Repository::open(GIT_PLATINUM)?;
-
    let path_buf = Path::new("README.md").to_path_buf();
-
    let diff = repo.diff_file(
-
        &path_buf,
-
        "d6880352fc7fda8f521ae9b7357668b17bb5bad5",
-
        "223aaf87d6ea62eef0014857640fd7c8dd0f80b5",
-
    )?;
-
    let expected_diff = FileDiff::Modified(Modified {
-
        path: 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),
-
                ],
-
                old: 1..2,
-
                new: 1..3,
-
            }]
-
            .into(),
-
            stats: FileStats {
-
                additions: 2,
-
                deletions: 1
-
            },
-
            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);
-

-
    Ok(())
-
}
-

-
#[test]
-
fn test_diff() -> Result<(), Error> {
-
    let repo = Repository::open(GIT_PLATINUM)?;
-
    let oid = "80bacafba303bf0cdf6142921f430ff265f25095";
-
    let commit = repo.commit(oid).unwrap();
-
    let parent_oid = commit.parents.first().unwrap();
-
    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),
-
                ],
-
                old: 1..2,
-
                new: 1..3,
-
            }]
-
            .into(),
-
            stats: FileStats {
-
                additions: 2,
-
                deletions: 1
-
            },
-
            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,
-
        deletions: 1,
-
    };
-
    let diff_stats = *diff.stats();
-
    let diff_files = diff.into_files();
-
    assert_eq!(expected_files, diff_files);
-
    assert_eq!(expected_stats, diff_stats);
-

-
    Ok(())
-
}
-

-
#[test]
-
fn test_branch_diff() -> Result<(), Error> {
-
    let repo = Repository::open(GIT_PLATINUM)?;
-
    let rev_from = Branch::local(refname!("master"));
-
    let rev_to = Branch::local(refname!("dev"));
-
    let diff = repo.diff(&rev_from, &rev_to)?;
-

-
    println!("Diff two branches: master -> dev");
-
    println!(
-
        "result: added {} deleted {} moved {} modified {}",
-
        diff.added().count(),
-
        diff.deleted().count(),
-
        diff.moved().count(),
-
        diff.modified().count()
-
    );
-
    assert_eq!(diff.added().count(), 1);
-
    assert_eq!(diff.deleted().count(), 11);
-
    assert_eq!(diff.moved().count(), 1);
-
    assert_eq!(diff.modified().count(), 2);
-
    for c in diff.added() {
-
        println!("added: {:?}", &c.path);
-
    }
-
    for d in diff.deleted() {
-
        println!("deleted: {:?}", &d.path);
-
    }
-
    for m in diff.moved() {
-
        println!("moved: {:?} -> {:?}", &m.old_path, &m.new_path);
-
    }
-
    for m in diff.modified() {
-
        println!("modified: {:?}", &m.path);
-
    }
-

-
    // Verify moved.
-
    let diff_moved = diff.moved().next().unwrap();
-

-
    // We can find a `FileDiff` for the old_path in a move.
-
    let file_diff = repo.diff_file(&diff_moved.old_path, &rev_from, &rev_to)?;
-
    println!("old path file diff: {:?}", &file_diff);
-

-
    // We can find a `FileDiff` for the new_path in a move.
-
    let file_diff = repo.diff_file(&diff_moved.new_path, &rev_from, &rev_to)?;
-
    println!("new path file diff: {:?}", &file_diff);
-

-
    // We can find a `FileDiff` if given a directory name.
-
    let dir_diff = repo.diff_file(&"special/", &rev_from, &rev_to)?;
-
    println!("dir diff: {dir_diff:?}");
-

-
    Ok(())
-
}
-

-
#[test]
-
fn test_diff_serde() -> Result<(), Error> {
-
    let repo = Repository::open(GIT_PLATINUM)?;
-
    let rev_from = Branch::local(refname!("master"));
-
    let rev_to = Branch::local(refname!("diff-test"));
-
    let diff = repo.diff(rev_from, rev_to)?;
-

-
    let json = serde_json::json!({
-
        "files": [{
-
            "path": "LICENSE",
-
            "diff": {
-
                "type": "plain",
-
                "hunks": [{
-
                    "header": "@@ -0,0 +1,2 @@\n",
-
                    "lines": [{
-
                        "line": "This is a license file.\n",
-
                        "lineNo": 1,
-
                        "type": "addition",
-
                    },
-
                    {
-
                        "line": "\n",
-
                        "lineNo": 2,
-
                        "type": "addition",
-
                    }],
-
                    "old": { "start": 0, "end": 0 },
-
                    "new": { "start": 1, "end": 3 },
-
                }],
-
                "stats": {
-
                    "additions": 2,
-
                    "deletions": 0,
-
                },
-
                "eof": "noneMissing",
-
            },
-
            "new": {
-
                "mode": "blob",
-
                "oid": "02f70f56ec62396ceaf38804c37e169e875ab291",
-
            },
-
            "status": "added"
-
        },
-
        {
-
            "path": "README.md",
-
            "diff": {
-
                "type": "plain",
-
                "hunks": [{
-
                    "header": "@@ -1,2 +1,2 @@\n",
-
                    "lines": [
-
                        { "lineNo": 1,
-
                          "line": "This repository is a data source for the Upstream front-end tests and the\n",
-
                          "type": "deletion"
-
                        },
-
                        { "lineNo": 2,
-
                          "line": "[`radicle-surf`](https://github.com/radicle-dev/git-platinum) unit tests.\n",
-
                          "type": "deletion"
-
                        },
-
                        { "lineNo": 1,
-
                          "line": "This repository is a data source for the upstream front-end tests and the\n",
-
                          "type": "addition"
-
                        },
-
                        { "lineNo": 2,
-
                          "line": "[`radicle-surf`](https://github.com/radicle-dev/radicle-surf) unit tests.\n",
-
                          "type": "addition"
-
                        },
-
                    ],
-
                    "old": { "start": 1, "end": 3 },
-
                    "new": { "start": 1, "end": 3 },
-
                }],
-
                "stats": {
-
                    "additions": 2,
-
                    "deletions": 2
-
                },
-
                "eof": "noneMissing",
-
            },
-
            "new": {
-
                "mode": "blob",
-
                "oid": "b033ecf407a44922b28c942c696922a7d7daf06e",
-
            },
-
            "old": {
-
                "mode": "blob",
-
                "oid": "5e07534cd74a6a9b2ccd2729b181c4ef26173a5e",
-
            },
-
            "status": "modified",
-
        },
-
        {
-
            "current": {
-
                "mode": "blob",
-
                "oid": "1570277532948712fea9029d100a4208f9e34241",
-
            },
-
            "oldPath": "text/emoji.txt",
-
            "newPath": "emoji.txt",
-
            "status": "moved"
-
        },
-
        {
-
            "current": {
-
                "mode": "blob",
-
                "oid": "5e07534cd74a6a9b2ccd2729b181c4ef26173a5e",
-
            },
-
            "newPath": "file_operations/copied.md",
-
            "oldPath": "README.md",
-
            "status": "copied"
-
        },
-
        {
-
            "path": "text/arrows.txt",
-
            "status": "deleted",
-
            "old": {
-
                "mode": "blob",
-
                "oid": "95418c04010a3cc758fb3a37f9918465f147566f",
-
             },
-
            "diff": {
-
                "type": "plain",
-
                "hunks": [{
-
                    "header": "@@ -1,7 +0,0 @@\n",
-
                    "lines": [
-
                        {
-
                            "line": "  ;;;;;        ;;;;;        ;;;;;\n",
-
                            "lineNo": 1,
-
                            "type": "deletion",
-
                        },
-
                        {
-
                            "line": "  ;;;;;        ;;;;;        ;;;;;\n",
-
                            "lineNo": 2,
-
                            "type": "deletion",
-
                        },
-
                        {
-
                            "line": "  ;;;;;        ;;;;;        ;;;;;\n",
-
                            "lineNo": 3,
-
                            "type": "deletion",
-
                        },
-
                        {
-
                            "line": "  ;;;;;        ;;;;;        ;;;;;\n",
-
                            "lineNo": 4,
-
                            "type": "deletion",
-
                        },
-
                        {
-
                            "line": "..;;;;;..    ..;;;;;..    ..;;;;;..\n",
-
                            "lineNo": 5,
-
                            "type": "deletion",
-
                        },
-
                        {
-
                            "line": " ':::::'      ':::::'      ':::::'\n",
-
                            "lineNo": 6,
-
                            "type": "deletion",
-
                        },
-
                        {
-
                            "line": "   ':`          ':`          ':`\n",
-
                            "lineNo": 7,
-
                            "type": "deletion",
-
                        },
-
                    ],
-
                    "old": { "start": 1, "end": 8 },
-
                    "new": { "start": 0, "end": 0 },
-
                }],
-
                "stats": {
-
                    "additions": 0,
-
                    "deletions": 7,
-
                },
-
                "eof": "noneMissing",
-
            },
-
        }],
-
        "stats": {
-
            "deletions": 9,
-
            "filesChanged": 5,
-
            "insertions": 4,
-
        }
-
    });
-
    assert_eq!(serde_json::to_value(diff).unwrap(), json);
-

-
    Ok(())
-
}
-

-
#[test]
-
fn test_rename_with_changes() {
-
    let buf = r"
-
diff --git a/radicle/src/node/tracking/config.rs b/radicle-node/src/service/tracking.rs
-
similarity index 96%
-
rename from radicle/src/node/tracking/config.rs
-
rename to radicle-node/src/service/tracking.rs
-
index 3f69208f3..cbc843c82 100644
-
--- a/radicle/src/node/tracking/config.rs
-
+++ b/radicle-node/src/service/tracking.rs
-
@@ -5,15 +5,17 @@ use std::ops;
-
 use log::error;
-
 use thiserror::Error;
-

-
-use crate::crypto::PublicKey;
-
-use crate::identity::IdentityError;
-
+use radicle::crypto::PublicKey;
-
+use radicle::identity::IdentityError;
-
+use radicle::storage::{Namespaces, ReadRepository as _, ReadStorage};
-
+
-
+use crate::prelude::Id;
-
+use crate::service::NodeId;
-
+
-
 pub use crate::node::tracking::store;
-
 pub use crate::node::tracking::store::Config as Store;
-
 pub use crate::node::tracking::store::Error;
-
 pub use crate::node::tracking::{Alias, Node, Policy, Repo, Scope};
-
-use crate::node::NodeId;
-
-use crate::prelude::Id;
-
-use crate::storage::{Namespaces, ReadRepository as _, ReadStorage};
-

-
 #[derive(Debug, Error)]
-
 pub enum NamespacesError {
-
";
-
    let diff = git2::Diff::from_buffer(buf.as_bytes()).unwrap();
-
    let diff = Diff::try_from(diff).unwrap();
-
    let json = serde_json::json!(
-
    {
-
        "files": [
-
            {
-
                "diff": {
-
                    "eof": "noneMissing",
-
                    "hunks": [
-
                        {
-
                            "header": "@@ -5,15 +5,17 @@ use std::ops;\n",
-
                            "lines": [
-
                                {
-
                                    "line": "use log::error;\n",
-
                                    "lineNoNew": 5,
-
                                    "lineNoOld": 5,
-
                                    "type": "context",
-
                                },
-
                                {
-
                                    "line": "use thiserror::Error;\n",
-
                                    "lineNoNew": 6,
-
                                    "lineNoOld": 6,
-
                                    "type": "context",
-
                                },
-
                                {
-
                                    "line": "\n",
-
                                    "lineNoNew": 7,
-
                                    "lineNoOld": 7,
-
                                    "type": "context",
-
                                },
-
                                {
-
                                    "line": "use crate::crypto::PublicKey;\n",
-
                                    "lineNo": 8,
-
                                    "type": "deletion",
-
                                },
-
                                {
-
                                    "line": "use crate::identity::IdentityError;\n",
-
                                    "lineNo": 9,
-
                                    "type": "deletion",
-
                                },
-
                                {
-
                                    "line": "use radicle::crypto::PublicKey;\n",
-
                                    "lineNo": 8,
-
                                    "type": "addition",
-
                                },
-
                                {
-
                                    "line": "use radicle::identity::IdentityError;\n",
-
                                    "lineNo": 9,
-
                                    "type": "addition",
-
                                },
-
                                {
-
                                    "line": "use radicle::storage::{Namespaces, ReadRepository as _, ReadStorage};\n",
-
                                    "lineNo": 10,
-
                                    "type": "addition",
-
                                },
-
                                {
-
                                    "line": "\n",
-
                                    "lineNo": 11,
-
                                    "type": "addition",
-
                                },
-
                                {
-
                                    "line": "use crate::prelude::Id;\n",
-
                                    "lineNo": 12,
-
                                    "type": "addition",
-
                                },
-
                                {
-
                                    "line": "use crate::service::NodeId;\n",
-
                                    "lineNo": 13,
-
                                    "type": "addition",
-
                                },
-
                                {
-
                                    "line": "\n",
-
                                    "lineNo": 14,
-
                                    "type": "addition",
-
                                },
-
                                {
-
                                    "line": "pub use crate::node::tracking::store;\n",
-
                                    "lineNoNew": 15,
-
                                    "lineNoOld": 10,
-
                                    "type": "context",
-
                                },
-
                                {
-
                                    "line": "pub use crate::node::tracking::store::Config as Store;\n",
-
                                    "lineNoNew": 16,
-
                                    "lineNoOld": 11,
-
                                    "type": "context",
-
                                },
-
                                {
-
                                    "line": "pub use crate::node::tracking::store::Error;\n",
-
                                    "lineNoNew": 17,
-
                                    "lineNoOld": 12,
-
                                    "type": "context",
-
                                },
-
                                {
-
                                    "line": "pub use crate::node::tracking::{Alias, Node, Policy, Repo, Scope};\n",
-
                                    "lineNoNew": 18,
-
                                    "lineNoOld": 13,
-
                                    "type": "context",
-
                                },
-
                                {
-
                                    "line": "use crate::node::NodeId;\n",
-
                                    "lineNo": 14,
-
                                    "type": "deletion",
-
                                },
-
                                {
-
                                    "line": "use crate::prelude::Id;\n",
-
                                    "lineNo": 15,
-
                                    "type": "deletion",
-
                                },
-
                                {
-
                                    "line": "use crate::storage::{Namespaces, ReadRepository as _, ReadStorage};\n",
-
                                    "lineNo": 16,
-
                                    "type": "deletion",
-
                                },
-
                                {
-
                                    "line": "\n",
-
                                    "lineNoNew": 19,
-
                                    "lineNoOld": 17,
-
                                    "type": "context",
-
                                },
-
                                {
-
                                    "line": "#[derive(Debug, Error)]\n",
-
                                    "lineNoNew": 20,
-
                                    "lineNoOld": 18,
-
                                    "type": "context",
-
                                },
-
                                {
-
                                    "line": "pub enum NamespacesError {\n",
-
                                    "lineNoNew": 21,
-
                                    "lineNoOld": 19,
-
                                    "type": "context",
-
                                },
-
                            ],
-
                            "new": {
-
                                "end": 22,
-
                                "start": 5,
-
                            },
-
                            "old": {
-
                                "end": 20,
-
                                "start": 5,
-
                            },
-
                        },
-
                    ],
-
                    "stats": {
-
                        "additions": 7,
-
                        "deletions": 5
-
                    },
-
                    "type": "plain",
-
                },
-
                "new": {
-
                    "mode": "blob",
-
                    "oid": "cbc843c820000000000000000000000000000000",
-
                },
-
                "newPath": "radicle-node/src/service/tracking.rs",
-
                "old": {
-
                    "mode": "blob",
-
                    "oid": "3f69208f30000000000000000000000000000000",
-
                },
-
                "oldPath": "radicle/src/node/tracking/config.rs",
-
                "status": "moved"
-
            },
-
        ],
-
        "stats": {
-
            "deletions": 0,
-
            "filesChanged": 1,
-
            "insertions": 0,
-
        },
-
    });
-

-
    assert_eq!(serde_json::to_value(diff).unwrap(), json);
-
}
-

-
// A possible false positive is being hit here for this clippy
-
// warning. Tracking issue:
-
// https://github.com/rust-lang/rust-clippy/issues/11402
-
#[allow(clippy::needless_raw_string_hashes)]
-
#[test]
-
fn test_both_missing_eof_newline() {
-
    let buf = r#"
-
diff --git a/.env b/.env
-
index f89e4c0..7c56eb7 100644
-
--- a/.env
-
+++ b/.env
-
@@ -1 +1 @@
-
-hello=123
-
\ No newline at end of file
-
+hello=1234
-
\ No newline at end of file
-
"#;
-
    let diff = git2::Diff::from_buffer(buf.as_bytes()).unwrap();
-
    let diff = Diff::try_from(diff).unwrap();
-
    assert_eq!(
-
        diff.modified().next().unwrap().diff.eof(),
-
        Some(EofNewLine::BothMissing)
-
    );
-
}
-

-
#[test]
-
fn test_none_missing_eof_newline() {
-
    let buf = r#"
-
diff --git a/.env b/.env
-
index f89e4c0..7c56eb7 100644
-
--- a/.env
-
+++ b/.env
-
@@ -1 +1 @@
-
-hello=123
-
+hello=1234
-
"#;
-
    let diff = git2::Diff::from_buffer(buf.as_bytes()).unwrap();
-
    let diff = Diff::try_from(diff).unwrap();
-
    assert_eq!(
-
        diff.modified().next().unwrap().diff.eof(),
-
        Some(EofNewLine::NoneMissing)
-
    );
-
}
-

-
#[test]
-
fn test_old_missing_eof_newline() {
-
    let buf = r#"
-
diff --git a/.env b/.env
-
index f89e4c0..7c56eb7 100644
-
--- a/.env
-
+++ b/.env
-
@@ -1 +1 @@
-
-hello=123
-
\ No newline at end of file
-
+hello=1234
-
"#;
-
    let diff = git2::Diff::from_buffer(buf.as_bytes()).unwrap();
-
    let diff = Diff::try_from(diff).unwrap();
-
    assert_eq!(
-
        diff.modified().next().unwrap().diff.eof(),
-
        Some(EofNewLine::OldMissing)
-
    );
-
}
-

-
#[test]
-
fn test_new_missing_eof_newline() {
-
    let buf = r#"
-
diff --git a/.env b/.env
-
index f89e4c0..7c56eb7 100644
-
--- a/.env
-
+++ b/.env
-
@@ -1 +1 @@
-
-hello=123
-
+hello=1234
-
\ No newline at end of file
-
"#;
-
    let diff = git2::Diff::from_buffer(buf.as_bytes()).unwrap();
-
    let diff = Diff::try_from(diff).unwrap();
-
    assert_eq!(
-
        diff.modified().next().unwrap().diff.eof(),
-
        Some(EofNewLine::NewMissing)
-
    );
-
}
deleted crates/radicle-surf/t/src/file_system.rs
@@ -1,140 +0,0 @@
-
//! Unit tests for radicle_surf::file_system
-

-
mod directory {
-
    use radicle_git_ref_format::refname;
-
    use radicle_surf::{
-
        fs::{self, Entry},
-
        Branch, Repository,
-
    };
-
    use std::path::Path;
-

-
    const GIT_PLATINUM: &str = "../data/git-platinum";
-

-
    #[test]
-
    fn directory_find_entry() {
-
        let repo = Repository::open(GIT_PLATINUM).unwrap();
-
        let root = repo.root_dir(Branch::local(refname!("master"))).unwrap();
-

-
        // find_entry for a file.
-
        let path = Path::new("src/memory.rs");
-
        let entry = root.find_entry(&path, &repo).unwrap();
-
        assert!(matches!(entry, fs::Entry::File(_)));
-

-
        // find_entry for a directory.
-
        let path = Path::new("this/is/a/really/deeply/nested/directory/tree");
-
        let entry = root.find_entry(&path, &repo).unwrap();
-
        assert!(matches!(entry, fs::Entry::Directory(_)));
-

-
        // find_entry for a non-leaf directory and its relative path.
-
        let path = Path::new("text");
-
        let entry = root.find_entry(&path, &repo).unwrap();
-
        assert!(matches!(entry, fs::Entry::Directory(_)));
-
        if let fs::Entry::Directory(sub_dir) = entry {
-
            let inner_path = Path::new("garden.txt");
-
            let inner_entry = sub_dir.find_entry(&inner_path, &repo).unwrap();
-
            assert!(matches!(inner_entry, fs::Entry::File(_)));
-
        }
-

-
        // find_entry for non-existing file
-
        let path = Path::new("this/is/a/really/missing_file");
-
        let result = root.find_entry(&path, &repo);
-
        assert!(matches!(result, Err(fs::error::Directory::PathNotFound(_))));
-

-
        // find_entry for absolute path: fail.
-
        let path = Path::new("/src/memory.rs");
-
        let result = root.find_entry(&path, &repo);
-
        assert!(result.is_err());
-

-
        // find entry for an empty path
-
        let path = Path::new("");
-
        let result = root.find_entry(&path, &repo);
-
        assert!(result.is_err());
-
    }
-

-
    #[test]
-
    fn directory_find_file_and_directory() {
-
        let repo = Repository::open(GIT_PLATINUM).unwrap();
-
        // Get the snapshot of the directory for a given commit.
-
        let root = repo
-
            .root_dir("80ded66281a4de2889cc07293a8f10947c6d57fe")
-
            .unwrap();
-

-
        // Assert that we can find the memory.rs file!
-
        assert!(root.find_file(&Path::new("src/memory.rs"), &repo).is_ok());
-

-
        let root_contents: Vec<Entry> = root.entries(&repo).unwrap().collect();
-
        assert_eq!(root_contents.len(), 7);
-
        assert!(root_contents[0].is_file());
-
        assert!(root_contents[1].is_file());
-
        assert!(root_contents[2].is_file());
-
        assert_eq!(root_contents[0].name(), ".i-am-well-hidden");
-
        assert_eq!(root_contents[1].name(), ".i-too-am-hidden");
-
        assert_eq!(root_contents[2].name(), "README.md");
-

-
        assert!(root_contents[3].is_directory());
-
        assert!(root_contents[4].is_directory());
-
        assert!(root_contents[5].is_directory());
-
        assert!(root_contents[6].is_directory());
-
        assert_eq!(root_contents[3].name(), "bin");
-
        assert_eq!(root_contents[4].name(), "src");
-
        assert_eq!(root_contents[5].name(), "text");
-
        assert_eq!(root_contents[6].name(), "this");
-

-
        let src = root.find_directory(&Path::new("src"), &repo).unwrap();
-
        assert_eq!(src.path(), Path::new("src").to_path_buf());
-
        let src_contents: Vec<Entry> = src.entries(&repo).unwrap().collect();
-
        assert_eq!(src_contents.len(), 3);
-
        assert_eq!(src_contents[0].name(), "Eval.hs");
-
        assert_eq!(src_contents[1].name(), "Folder.svelte");
-
        assert_eq!(src_contents[2].name(), "memory.rs");
-
    }
-

-
    #[test]
-
    fn directory_size() {
-
        let repo = Repository::open(GIT_PLATINUM).unwrap();
-
        let root = repo.root_dir(Branch::local(refname!("master"))).unwrap();
-

-
        /*
-
        git-platinum (master) $ ls -l src
-
        -rw-r--r-- 1 pi pi 10044 Oct 31 11:32 Eval.hs
-
        -rw-r--r-- 1 pi pi  6253 Oct 31 11:27 memory.rs
-

-
        10044 + 6253 = 16297
-
         */
-

-
        let path = Path::new("src");
-
        let entry = root.find_entry(&path, &repo).unwrap();
-
        assert!(matches!(entry, fs::Entry::Directory(_)));
-
        if let fs::Entry::Directory(d) = entry {
-
            assert_eq!(16297, d.size(&repo).unwrap());
-
        }
-
    }
-

-
    #[test]
-
    fn directory_last_commit() {
-
        let repo = Repository::open(GIT_PLATINUM).unwrap();
-
        let branch = Branch::local(refname!("dev"));
-
        let root = repo.root_dir(&branch).unwrap();
-
        let dir = root.find_directory(&"this/is", &repo).unwrap();
-
        let last_commit = repo.last_commit(&dir.path(), &branch).unwrap().unwrap();
-
        assert_eq!(
-
            last_commit.id.to_string(),
-
            "2429f097664f9af0c5b7b389ab998b2199ffa977"
-
        );
-
    }
-

-
    #[test]
-
    fn file_last_commit() {
-
        let repo = Repository::open(GIT_PLATINUM).unwrap();
-
        let branch = Branch::local(refname!("master"));
-
        let root = repo.root_dir(&branch).unwrap();
-

-
        // Find a file with "\" in its name.
-
        let f = root.find_file(&"special/faux\\path", &repo).unwrap();
-
        let last_commit = repo.last_commit(&f.path(), &branch).unwrap().unwrap();
-
        assert_eq!(
-
            last_commit.id.to_string(),
-
            "a0dd9122d33dff2a35f564d564db127152c88e02"
-
        );
-
    }
-
}
deleted crates/radicle-surf/t/src/last_commit.rs
@@ -1,120 +0,0 @@
-
use std::{path::PathBuf, str::FromStr};
-

-
use radicle_oid::Oid;
-
use radicle_git_ref_format::refname;
-
use radicle_surf::{Branch, Repository};
-

-
use super::GIT_PLATINUM;
-

-
#[test]
-
fn readme_missing_and_memory() {
-
    let repo = Repository::open(GIT_PLATINUM)
-
        .expect("Could not retrieve ./data/git-platinum as git repository");
-
    let oid =
-
        Oid::from_str("d3464e33d75c75c99bfb90fa2e9d16efc0b7d0e3").expect("Failed to parse SHA");
-

-
    // memory.rs is commited later so it should not exist here.
-
    let memory_last_commit_oid = repo
-
        .last_commit(&"src/memory.rs", oid)
-
        .expect("Failed to get last commit")
-
        .map(|commit| commit.id);
-

-
    assert_eq!(memory_last_commit_oid, None);
-

-
    // README.md exists in this commit.
-
    let readme_last_commit = repo
-
        .last_commit(&"README.md", oid)
-
        .expect("Failed to get last commit")
-
        .map(|commit| commit.id);
-

-
    assert_eq!(readme_last_commit, Some(oid));
-
}
-

-
#[test]
-
fn folder_svelte() {
-
    let repo = Repository::open(GIT_PLATINUM)
-
        .expect("Could not retrieve ./data/git-platinum as git repository");
-
    // Check that last commit is the actual last commit even if head commit differs.
-
    let oid =
-
        Oid::from_str("19bec071db6474af89c866a1bd0e4b1ff76e2b97").expect("Could not parse SHA");
-

-
    let expected_commit_id = Oid::from_str("f3a089488f4cfd1a240a9c01b3fcc4c34a4e97b2").unwrap();
-

-
    let folder_svelte = repo
-
        .last_commit(&"examples/Folder.svelte", oid)
-
        .expect("Failed to get last commit")
-
        .map(|commit| commit.id);
-

-
    assert_eq!(folder_svelte, Some(expected_commit_id));
-
}
-

-
#[test]
-
fn nest_directory() {
-
    let repo = Repository::open(GIT_PLATINUM)
-
        .expect("Could not retrieve ./data/git-platinum as git repository");
-
    // Check that last commit is the actual last commit even if head commit differs.
-
    let oid =
-
        Oid::from_str("19bec071db6474af89c866a1bd0e4b1ff76e2b97").expect("Failed to parse SHA");
-

-
    let expected_commit_id = Oid::from_str("2429f097664f9af0c5b7b389ab998b2199ffa977").unwrap();
-

-
    let nested_directory_tree_commit_id = repo
-
        .last_commit(&"this/is/a/really/deeply/nested/directory/tree", oid)
-
        .expect("Failed to get last commit")
-
        .map(|commit| commit.id);
-

-
    assert_eq!(nested_directory_tree_commit_id, Some(expected_commit_id));
-
}
-

-
#[test]
-
#[cfg(not(windows))]
-
fn can_get_last_commit_for_special_filenames() {
-
    let repo = Repository::open(GIT_PLATINUM)
-
        .expect("Could not retrieve ./data/git-platinum as git repository");
-

-
    // Check that last commit is the actual last commit even if head commit differs.
-
    let oid =
-
        Oid::from_str("a0dd9122d33dff2a35f564d564db127152c88e02").expect("Failed to parse SHA");
-

-
    let expected_commit_id = Oid::from_str("a0dd9122d33dff2a35f564d564db127152c88e02").unwrap();
-

-
    let backslash_commit_id = repo
-
        .last_commit(&r"special/faux\\path", oid)
-
        .expect("Failed to get last commit")
-
        .map(|commit| commit.id);
-
    assert_eq!(backslash_commit_id, Some(expected_commit_id));
-

-
    let ogre_commit_id = repo
-
        .last_commit(&"special/👹👹👹", oid)
-
        .expect("Failed to get last commit")
-
        .map(|commit| commit.id);
-
    assert_eq!(ogre_commit_id, Some(expected_commit_id));
-
}
-

-
#[test]
-
fn root() {
-
    let repo = Repository::open(GIT_PLATINUM)
-
        .expect("Could not retrieve ./data/git-platinum as git repository");
-
    let rev = Branch::local(refname!("master"));
-
    let root_last_commit_id = repo
-
        .last_commit(&PathBuf::new(), rev)
-
        .expect("Failed to get last commit")
-
        .map(|commit| commit.id);
-

-
    let expected_oid = repo
-
        .history(Branch::local(refname!("master")))
-
        .unwrap()
-
        .head()
-
        .id;
-
    assert_eq!(root_last_commit_id, Some(expected_oid));
-
}
-

-
#[test]
-
fn binary_file() {
-
    let repo = Repository::open(GIT_PLATINUM)
-
        .expect("Could not retrieve ./data/git-platinum as git repository");
-
    let history = repo.history(Branch::local(refname!("dev"))).unwrap();
-
    let file_commit = history.by_path(&"bin/cat").next();
-
    assert!(file_commit.is_some());
-
    println!("file commit: {:?}", &file_commit);
-
}
deleted crates/radicle-surf/t/src/lib.rs
@@ -1,38 +0,0 @@
-
#[cfg(test)]
-
const GIT_PLATINUM: &str = "../data/git-platinum";
-

-
#[cfg(test)]
-
mod file_system;
-

-
#[cfg(test)]
-
mod source;
-

-
// #[cfg(test)]
-
// mod branch;
-

-
#[cfg(test)]
-
mod code_browsing;
-

-
#[cfg(test)]
-
mod commit;
-

-
#[cfg(test)]
-
mod diff;
-

-
// #[cfg(test)]
-
// mod last_commit;
-

-
#[cfg(test)]
-
mod namespace;
-

-
#[cfg(test)]
-
mod reference;
-

-
#[cfg(test)]
-
mod rev;
-

-
// #[cfg(test)]
-
// mod submodule;
-

-
#[cfg(test)]
-
mod threading;
deleted crates/radicle-surf/t/src/namespace.rs
@@ -1,121 +0,0 @@
-
use pretty_assertions::{assert_eq, assert_ne};
-
use radicle_git_ref_format::{name::component, refname, refspec};
-
use radicle_surf::{Branch, Error, Glob, Repository};
-

-
use super::GIT_PLATINUM;
-

-
#[test]
-
fn switch_to_banana() -> Result<(), Error> {
-
    let repo = Repository::open(GIT_PLATINUM)?;
-
    let history_master = repo.history(Branch::local(refname!("master")))?;
-
    repo.switch_namespace(&refname!("golden"))?;
-
    let history_banana = repo.history(Branch::local(refname!("banana")))?;
-

-
    assert_ne!(history_master.head(), history_banana.head());
-

-
    Ok(())
-
}
-

-
#[test]
-
fn me_namespace() -> Result<(), Error> {
-
    let repo = Repository::open(GIT_PLATINUM)?;
-
    let history = repo.history(Branch::local(refname!("master")))?;
-

-
    assert_eq!(repo.which_namespace().unwrap(), None);
-

-
    repo.switch_namespace(&refname!("me"))?;
-
    assert_eq!(repo.which_namespace().unwrap(), Some("me".parse()?));
-

-
    let history_feature = repo.history(Branch::local(refname!("feature/#1194")))?;
-
    assert_eq!(history.head(), history_feature.head());
-

-
    let expected_branches: Vec<Branch> = vec![Branch::local(refname!("feature/#1194"))];
-
    let mut branches = repo
-
        .branches(Glob::all_heads())?
-
        .collect::<Result<Vec<_>, _>>()?;
-
    branches.sort();
-

-
    assert_eq!(expected_branches, branches);
-

-
    let expected_branches: Vec<Branch> = vec![Branch::remote(
-
        component!("fein"),
-
        refname!("heads/feature/#1194"),
-
    )];
-
    let mut branches = repo
-
        .branches(Glob::remotes(pattern!("fein/*")))?
-
        .collect::<Result<Vec<_>, _>>()?;
-
    branches.sort();
-

-
    assert_eq!(expected_branches, branches);
-

-
    Ok(())
-
}
-

-
#[test]
-
fn golden_namespace() -> Result<(), Error> {
-
    let repo = Repository::open(GIT_PLATINUM)?;
-
    let history = repo.history(Branch::local(refname!("master")))?;
-

-
    assert_eq!(repo.which_namespace().unwrap(), None);
-

-
    repo.switch_namespace(&refname!("golden"))?;
-

-
    assert_eq!(repo.which_namespace().unwrap(), Some("golden".parse()?));
-

-
    let golden_history = repo.history(Branch::local(refname!("master")))?;
-
    assert_eq!(history.head(), golden_history.head());
-

-
    let expected_branches: Vec<Branch> = vec![
-
        Branch::local(refname!("banana")),
-
        Branch::local(refname!("master")),
-
    ];
-
    let mut branches = repo
-
        .branches(Glob::all_heads())?
-
        .collect::<Result<Vec<_>, _>>()?;
-
    branches.sort();
-

-
    assert_eq!(expected_branches, branches);
-

-
    // NOTE: these tests used to remove the categories, i.e. heads & tags, but that
-
    // was specialised logic based on the radicle-link storage layout.
-
    let remote = component!("kickflip");
-
    let expected_branches: Vec<Branch> = vec![
-
        Branch::remote(remote.clone(), refname!("heads/fakie/bigspin")),
-
        Branch::remote(remote.clone(), refname!("heads/heelflip")),
-
        Branch::remote(remote, refname!("tags/v0.1.0")),
-
    ];
-
    let mut branches = repo
-
        .branches(Glob::remotes(pattern!("kickflip/*")))?
-
        .collect::<Result<Vec<_>, _>>()?;
-
    branches.sort();
-

-
    assert_eq!(expected_branches, branches);
-

-
    Ok(())
-
}
-

-
#[test]
-
fn silver_namespace() -> Result<(), Error> {
-
    let repo = Repository::open(GIT_PLATINUM)?;
-
    let history = repo.history(Branch::local(refname!("master")))?;
-

-
    assert_eq!(repo.which_namespace().unwrap(), None);
-

-
    repo.switch_namespace(&refname!("golden/silver"))?;
-
    assert_eq!(
-
        repo.which_namespace().unwrap(),
-
        Some("golden/silver".parse()?)
-
    );
-
    let silver_history = repo.history(Branch::local(refname!("master")))?;
-
    assert_ne!(history.head(), silver_history.head());
-

-
    let expected_branches: Vec<Branch> = vec![Branch::local(refname!("master"))];
-
    let mut branches = repo
-
        .branches(Glob::all_heads().branches().and(Glob::all_remotes()))?
-
        .collect::<Result<Vec<_>, _>>()?;
-
    branches.sort();
-

-
    assert_eq!(expected_branches, branches);
-

-
    Ok(())
-
}
deleted crates/radicle-surf/t/src/reference.rs
@@ -1,49 +0,0 @@
-
use radicle_git_ref_format::refspec;
-
use radicle_surf::{Glob, Repository};
-

-
use super::GIT_PLATINUM;
-

-
#[test]
-
fn test_branches() {
-
    let repo = Repository::open(GIT_PLATINUM).unwrap();
-
    let heads = Glob::all_heads();
-
    let branches = repo.branches(heads.clone()).unwrap();
-
    for b in branches {
-
        println!("{}", b.unwrap().refname());
-
    }
-
    let branches = repo
-
        .branches(heads.branches().and(Glob::remotes(pattern!("banana/*"))))
-
        .unwrap();
-
    for b in branches {
-
        println!("{}", b.unwrap().refname());
-
    }
-
}
-

-
#[test]
-
fn test_tag_snapshot() {
-
    let repo = Repository::open(GIT_PLATINUM).unwrap();
-
    let tags = repo
-
        .tags(&Glob::all_tags())
-
        .unwrap()
-
        .collect::<Result<Vec<_>, _>>()
-
        .unwrap();
-
    assert_eq!(tags.len(), 6);
-
    let root_dir = repo.root_dir(&tags[0]).unwrap();
-
    assert_eq!(root_dir.entries(&repo).unwrap().entries().count(), 1);
-
}
-

-
#[test]
-
fn test_namespaces() {
-
    let repo = Repository::open(GIT_PLATINUM).unwrap();
-

-
    let namespaces = repo.namespaces(&Glob::all_namespaces()).unwrap();
-
    assert_eq!(namespaces.count(), 3);
-
    let namespaces = repo
-
        .namespaces(&Glob::namespaces(pattern!("golden/*")))
-
        .unwrap();
-
    assert_eq!(namespaces.count(), 2);
-
    let namespaces = repo
-
        .namespaces(&Glob::namespaces(pattern!("golden/*")).insert(pattern!("me/*")))
-
        .unwrap();
-
    assert_eq!(namespaces.count(), 3);
-
}
deleted crates/radicle-surf/t/src/rev.rs
@@ -1,92 +0,0 @@
-
use std::str::FromStr;
-

-
use radicle_git_ref_format::{name::component, refname};
-
use radicle_surf::{Branch, Error, Oid, Repository};
-

-
use super::GIT_PLATINUM;
-

-
// **FIXME**: This seems to break occasionally on
-
// buildkite. For some reason the commit
-
// 3873745c8f6ffb45c990eb23b491d4b4b6182f95, which is on master
-
// (currently HEAD), is not found. It seems to load the history
-
// with d6880352fc7fda8f521ae9b7357668b17bb5bad5 as the HEAD.
-
//
-
// To temporarily fix this, we need to select "New Build" from the build kite
-
// build page that's failing.
-
// * Under "Message" put whatever you want.
-
// * Under "Branch" put in the branch you're working on.
-
// * Expand "Options" and select "clean checkout".
-
#[test]
-
fn _master() -> Result<(), Error> {
-
    let repo = Repository::open(GIT_PLATINUM)?;
-
    let mut history = repo.history(Branch::remote(component!("origin"), refname!("master")))?;
-

-
    let commit1 = Oid::from_str("3873745c8f6ffb45c990eb23b491d4b4b6182f95")?;
-
    assert!(
-
        history.any(|commit| commit.unwrap().id == commit1),
-
        "commit_id={}, history =\n{:#?}",
-
        commit1,
-
        &history
-
    );
-

-
    let commit2 = Oid::from_str("d6880352fc7fda8f521ae9b7357668b17bb5bad5")?;
-
    assert!(
-
        history.any(|commit| commit.unwrap().id == commit2),
-
        "commit_id={}, history =\n{:#?}",
-
        commit2,
-
        &history
-
    );
-

-
    Ok(())
-
}
-

-
#[test]
-
fn commit() -> Result<(), Error> {
-
    let repo = Repository::open(GIT_PLATINUM)?;
-
    let rev = Oid::from_str("3873745c8f6ffb45c990eb23b491d4b4b6182f95")?;
-
    let mut history = repo.history(rev)?;
-

-
    let commit1 = Oid::from_str("3873745c8f6ffb45c990eb23b491d4b4b6182f95")?;
-
    assert!(history.any(|commit| commit.unwrap().id == commit1));
-

-
    Ok(())
-
}
-

-
#[test]
-
fn commit_parents() -> Result<(), Error> {
-
    let repo = Repository::open(GIT_PLATINUM)?;
-
    let rev = Oid::from_str("3873745c8f6ffb45c990eb23b491d4b4b6182f95")?;
-
    let history = repo.history(rev)?;
-
    let commit = history.head();
-

-
    assert_eq!(
-
        commit.parents,
-
        vec![Oid::from_str("d6880352fc7fda8f521ae9b7357668b17bb5bad5")?]
-
    );
-

-
    Ok(())
-
}
-

-
#[test]
-
fn commit_short() -> Result<(), Error> {
-
    let repo = Repository::open(GIT_PLATINUM)?;
-
    let rev = repo.oid("3873745c8")?;
-
    let mut history = repo.history(rev)?;
-

-
    let commit1 = Oid::from_str("3873745c8f6ffb45c990eb23b491d4b4b6182f95")?;
-
    assert!(history.any(|commit| commit.unwrap().id == commit1));
-

-
    Ok(())
-
}
-

-
#[test]
-
fn tag() -> Result<(), Error> {
-
    let repo = Repository::open(GIT_PLATINUM)?;
-
    let rev = refname!("refs/tags/v0.2.0");
-
    let history = repo.history(&rev)?;
-

-
    let commit1 = Oid::from_str("2429f097664f9af0c5b7b389ab998b2199ffa977")?;
-
    assert_eq!(history.head().id, commit1);
-

-
    Ok(())
-
}
deleted crates/radicle-surf/t/src/source.rs
@@ -1,222 +0,0 @@
-
use std::path::PathBuf;
-

-
use radicle_git_ref_format::refname;
-
use radicle_surf::{Branch, Glob, Repository};
-
use serde_json::json;
-

-
const GIT_PLATINUM: &str = "../data/git-platinum";
-

-
#[test]
-
fn tree_serialization() {
-
    let repo = Repository::open(GIT_PLATINUM).unwrap();
-
    let tree = repo.tree(refname!("refs/heads/master"), &"src").unwrap();
-

-
    let expected = json!({
-
      "oid": "ed52e9f8dfe1d8b374b2a118c25235349a743dd2",
-
      "entries": [
-
        {
-
          "name": "Eval.hs",
-
          "kind": "blob",
-
          "oid": "7d6240123a8d8ea8a8376610168a0a4bcb96afd0",
-
          "commit": "src/Eval.hs"
-
        },
-
        {
-
          "name": "memory.rs",
-
          "kind": "blob",
-
          "oid": "b84992d24be67536837f5ab45a943f1b3f501878",
-
          "commit": "src/memory.rs"
-
        }
-
      ],
-
      "commit": {
-
        "id": "a0dd9122d33dff2a35f564d564db127152c88e02",
-
        "author": {
-
          "name": "Rūdolfs Ošiņš",
-
          "email": "rudolfs@osins.org",
-
          "time": 1602778504
-
        },
-
        "committer": {
-
          "name": "GitHub",
-
          "email": "noreply@github.com",
-
          "time": 1602778504
-
        },
-
        "summary": "Add files with special characters in their filenames (#5)",
-
        "message": "Add files with special characters in their filenames (#5)\n\n",
-
        "description": "",
-
        "parents": [
-
          "223aaf87d6ea62eef0014857640fd7c8dd0f80b5"
-
        ]
-
      },
-
      "root": "src"
-
    });
-

-
    assert_eq!(
-
        serde_json::to_value(&tree).unwrap(),
-
        expected,
-
        "Got:\n{}",
-
        serde_json::to_string_pretty(&tree).unwrap()
-
    )
-
}
-

-
#[test]
-
fn test_tree_last_commit() {
-
    let repo = Repository::open(GIT_PLATINUM).unwrap();
-
    let tree = repo.tree(refname!("refs/heads/master"), &"src").unwrap();
-
    let last_commit = tree.last_commit(&repo).unwrap();
-
    assert_ne!(*tree.commit(), last_commit);
-
    assert_eq!(
-
        last_commit.id.to_string(),
-
        "a57846bbc8ced6587bf8329fc4bce970eb7b757e"
-
    )
-
}
-

-
#[test]
-
fn repo_tree_empty_branch() {
-
    let repo = Repository::open(GIT_PLATINUM).unwrap();
-
    let rev = Branch::local(refname!("empty-branch"));
-
    let tree = repo.tree(rev, &"").unwrap();
-
    assert_eq!(tree.entries().len(), 0);
-

-
    // Verify the last commit is the empty commit.
-
    assert_eq!(
-
        tree.commit().id.to_string(),
-
        "e972683fe8136bf8a5cb2378cf50303554008049"
-
    );
-
}
-

-
#[test]
-
fn repo_tree() {
-
    let repo = Repository::open(GIT_PLATINUM).unwrap();
-
    let tree = repo
-
        .tree("27acd68c7504755aa11023300890bb85bbd69d45", &"src")
-
        .unwrap();
-
    assert_eq!(tree.entries().len(), 3);
-

-
    let commit_header = tree.commit();
-
    assert_eq!(
-
        commit_header.id.to_string(),
-
        "27acd68c7504755aa11023300890bb85bbd69d45"
-
    );
-

-
    let tree_oid = tree.object_id();
-
    assert_eq!(
-
        tree_oid.to_string(),
-
        "dbd5d80c64a00969f521b96401a315e9481e9561"
-
    );
-

-
    let entries = tree.entries();
-
    assert_eq!(entries.len(), 3);
-
    let entry = &entries[0];
-
    assert!(!entry.is_tree());
-
    assert_eq!(entry.name(), "Eval.hs");
-
    assert_eq!(
-
        entry.object_id().to_string(),
-
        "8c7447d13b907aa994ac3a38317c1e9633bf0732"
-
    );
-
    let commit = entry.commit();
-
    assert_eq!(
-
        commit.id.to_string(),
-
        "27acd68c7504755aa11023300890bb85bbd69d45"
-
    );
-
    let last_commit = entry.last_commit(&repo).unwrap();
-
    assert_eq!(
-
        last_commit.id.to_string(),
-
        "e24124b7538658220b5aaf3b6ef53758f0a106dc"
-
    );
-

-
    // Verify that an empty path works for getting the root tree.
-
    let root_tree = repo
-
        .tree("27acd68c7504755aa11023300890bb85bbd69d45", &"")
-
        .unwrap();
-
    assert_eq!(root_tree.entries().len(), 8);
-
}
-

-
#[test]
-
fn repo_blob() {
-
    let repo = Repository::open(GIT_PLATINUM).unwrap();
-
    let blob = repo
-
        .blob("27acd68c7504755aa11023300890bb85bbd69d45", &"src/memory.rs")
-
        .unwrap();
-

-
    let blob_oid = blob.object_id();
-
    assert_eq!(
-
        blob_oid.to_string(),
-
        "b84992d24be67536837f5ab45a943f1b3f501878"
-
    );
-

-
    let commit_header = blob.commit();
-
    assert_eq!(
-
        commit_header.id.to_string(),
-
        "e24124b7538658220b5aaf3b6ef53758f0a106dc"
-
    );
-

-
    assert!(!blob.is_binary());
-

-
    // Verify the blob content size matches with the file size of "memory.rs"
-
    let content = blob.content();
-
    assert_eq!(blob.size(), 6253);
-

-
    // Verify to_owned().
-
    let blob_owned = blob.to_owned();
-
    assert_eq!(blob_owned.size(), 6253);
-
    assert_eq!(blob.content(), blob_owned.content());
-

-
    // Verify JSON output is the same.
-
    let json_ref = json!({ "content": content }).to_string();
-
    let json_owned = json!( {
-
      "content": blob_owned.content()
-
    })
-
    .to_string();
-
    assert_eq!(json_ref, json_owned);
-
}
-

-
#[test]
-
fn tree_ordering() {
-
    let repo = Repository::open(GIT_PLATINUM).unwrap();
-
    let tree = repo
-
        .tree(refname!("refs/heads/master"), &PathBuf::new())
-
        .unwrap();
-
    assert_eq!(
-
        tree.entries()
-
            .iter()
-
            .map(|entry| entry.name().to_string())
-
            .collect::<Vec<_>>(),
-
        vec![
-
            "bin".to_string(),
-
            "special".to_string(),
-
            "src".to_string(),
-
            "text".to_string(),
-
            "this".to_string(),
-
            ".i-am-well-hidden".to_string(),
-
            ".i-too-am-hidden".to_string(),
-
            "README.md".to_string(),
-
        ]
-
    );
-
}
-

-
#[test]
-
fn commit_branches() {
-
    let repo = Repository::open(GIT_PLATINUM).unwrap();
-
    let init_commit = "d3464e33d75c75c99bfb90fa2e9d16efc0b7d0e3";
-
    let glob = Glob::all_heads().branches().and(Glob::all_remotes());
-
    let branches = repo.revision_branches(init_commit, glob).unwrap();
-

-
    assert_eq!(branches.len(), 11);
-

-
    let refnames: Vec<_> = branches.iter().map(|b| b.refname().to_string()).collect();
-
    assert_eq!(
-
        refnames,
-
        vec![
-
            "refs/heads/dev",
-
            "refs/heads/diff-test",
-
            "refs/heads/empty-branch",
-
            "refs/heads/master",
-
            "refs/remotes/banana/orange/pineapple",
-
            "refs/remotes/banana/pineapple",
-
            "refs/remotes/origin/HEAD",
-
            "refs/remotes/origin/dev",
-
            "refs/remotes/origin/diff-test",
-
            "refs/remotes/origin/empty-branch",
-
            "refs/remotes/origin/master"
-
        ]
-
    );
-
}
deleted crates/radicle-surf/t/src/submodule.rs
@@ -1,86 +0,0 @@
-
use std::{convert::Infallible, path::Path};
-

-
use proptest::{collection, proptest};
-
use radicle_git_metadata::commit::CommitData;
-
use radicle_git_ext_test::gen;
-
use radicle_git_ref_format::refname;
-
use radicle_surf::tree::EntryKind;
-
use radicle_surf::{fs, Branch, Repository};
-

-
proptest! {
-
    #[test]
-
    fn test_submodule(
-
        initial in gen::commit::commit(),
-
        commits in collection::vec(gen::commit::commit(), 1..5)
-
    ) {
-
        prop::test_submodule(initial, commits)
-
    }
-

-
    #[ignore = "segfault"]
-
    #[test]
-
    fn test_submodule_bare(
-
        initial in gen::commit::commit(),
-
        commits in collection::vec(gen::commit::commit(), 1..5)
-
    ) {
-
        prop::test_submodule_bare(initial, commits)
-
    }
-

-
}
-

-
mod prop {
-
    use radicle_git_ext_test::{gen::commit, repository};
-

-
    use super::*;
-

-
    pub fn test_submodule(
-
        initial: CommitData<commit::TreeData, Infallible>,
-
        commits: Vec<CommitData<commit::TreeData, Infallible>>,
-
    ) {
-
        let refname = refname!("refs/heads/master");
-
        let author = git2::Signature::try_from(initial.author()).unwrap();
-

-
        let submodule = repository::fixture(&refname, commits).unwrap();
-
        let repo = repository::fixture(&refname, vec![initial]).unwrap();
-

-
        let head = repo.head.expect("missing initial commit");
-
        let sub =
-
            repository::submodule(&repo.inner, &submodule.inner, &refname, head, &author).unwrap();
-

-
        let repo = Repository::open(repo.inner.path()).unwrap();
-
        let branch = Branch::local(refname);
-
        let dir = repo.root_dir(&branch).unwrap();
-

-
        let platinum = dir.find_entry(&sub.path(), &repo).unwrap();
-
        assert!(matches!(&platinum, fs::Entry::Submodule(module) if module.url().is_some()));
-

-
        let root = repo.tree(&branch, &Path::new("")).unwrap();
-
        let kind = EntryKind::from(platinum);
-
        assert!(root.entries().iter().any(|e| e.entry() == &kind));
-
    }
-

-
    pub fn test_submodule_bare(
-
        initial: CommitData<commit::TreeData, Infallible>,
-
        commits: Vec<CommitData<commit::TreeData, Infallible>>,
-
    ) {
-
        let refname = refname!("refs/heads/master");
-
        let author = git2::Signature::try_from(initial.author()).unwrap();
-

-
        let submodule = repository::fixture(&refname, commits).unwrap();
-
        let repo = repository::bare_fixture(&refname, vec![initial]).unwrap();
-

-
        let head = repo.head.expect("missing initial commit");
-
        let sub =
-
            repository::submodule(&repo.inner, &submodule.inner, &refname, head, &author).unwrap();
-

-
        let repo = Repository::open(repo.inner.path()).unwrap();
-
        let branch = Branch::local(refname);
-
        let dir = repo.root_dir(&branch).unwrap();
-

-
        let platinum = dir.find_entry(&sub.path(), &repo).unwrap();
-
        assert!(matches!(&platinum, fs::Entry::Submodule(module) if module.url().is_some()));
-

-
        let root = repo.tree(&branch, &Path::new("")).unwrap();
-
        let kind = EntryKind::from(platinum);
-
        assert!(root.entries().iter().any(|e| e.entry() == &kind));
-
    }
-
}
deleted crates/radicle-surf/t/src/threading.rs
@@ -1,37 +0,0 @@
-
use std::sync::{Mutex, MutexGuard};
-

-
use radicle_git_ref_format::{name::component, refname};
-
use radicle_surf::{Branch, Error, Glob, Repository};
-

-
use super::GIT_PLATINUM;
-

-
#[test]
-
fn basic_test() -> Result<(), Error> {
-
    let shared_repo = Mutex::new(Repository::open(GIT_PLATINUM)?);
-
    let locked_repo: MutexGuard<Repository> = shared_repo.lock().unwrap();
-
    let mut branches = locked_repo
-
        .branches(Glob::all_heads().branches().and(Glob::all_remotes()))?
-
        .collect::<Result<Vec<_>, _>>()?;
-
    branches.sort();
-

-
    let origin = component!("origin");
-
    let banana = component!("banana");
-
    assert_eq!(
-
        branches,
-
        vec![
-
            Branch::local(refname!("dev")),
-
            Branch::local(refname!("diff-test")),
-
            Branch::local(refname!("empty-branch")),
-
            Branch::local(refname!("master")),
-
            Branch::remote(banana.clone(), refname!("orange/pineapple")),
-
            Branch::remote(banana, refname!("pineapple")),
-
            Branch::remote(origin.clone(), refname!("HEAD")),
-
            Branch::remote(origin.clone(), refname!("dev")),
-
            Branch::remote(origin.clone(), refname!("diff-test")),
-
            Branch::remote(origin.clone(), refname!("empty-branch")),
-
            Branch::remote(origin, refname!("master")),
-
        ]
-
    );
-

-
    Ok(())
-
}