From 02321aba7cf280c9ff3d6572488bb4d54cf61c57 Mon Sep 17 00:00:00 2001 From: Fintan Halpenny Date: Tue, 22 Nov 2022 12:37:54 +0000 Subject: [PATCH 1/5] radicle-surf: source module This is a straightforward move of all the code source browsing modules into a module called `source`. Signed-off-by: Fintan Halpenny X-Clacks-Overhead: GNU Terry Pratchett --- radicle-surf/src/lib.rs | 13 +---------- radicle-surf/src/source.rs | 23 ++++++++++++++++++++ radicle-surf/src/{ => source}/commit.rs | 3 +-- radicle-surf/src/{ => source}/object.rs | 2 +- radicle-surf/src/{ => source}/object/blob.rs | 8 ++++--- radicle-surf/src/{ => source}/object/tree.rs | 8 ++++--- radicle-surf/src/{ => source}/person.rs | 0 radicle-surf/src/{ => source}/revision.rs | 0 8 files changed, 36 insertions(+), 21 deletions(-) create mode 100644 radicle-surf/src/source.rs rename radicle-surf/src/{ => source}/commit.rs (99%) rename radicle-surf/src/{ => source}/object.rs (98%) rename radicle-surf/src/{ => source}/object/blob.rs (98%) rename radicle-surf/src/{ => source}/object/tree.rs (97%) rename radicle-surf/src/{ => source}/person.rs (100%) rename radicle-surf/src/{ => source}/revision.rs (100%) diff --git a/radicle-surf/src/lib.rs b/radicle-surf/src/lib.rs index 815fd75..3113878 100644 --- a/radicle-surf/src/lib.rs +++ b/radicle-surf/src/lib.rs @@ -82,18 +82,7 @@ extern crate radicle_git_ext as git_ext; pub mod diff; pub mod file_system; pub mod git; - -pub mod commit; -pub use commit::{commit, commits, Commit}; - -pub mod object; -pub use object::{blob, tree as objectTree, Blob, BlobContent, Info, ObjectType, Tree}; - -pub mod person; -pub use person::Person; - -pub mod revision; -pub use revision::Revision; +pub mod source; #[cfg(feature = "syntax")] pub mod syntax; diff --git a/radicle-surf/src/source.rs b/radicle-surf/src/source.rs new file mode 100644 index 0000000..457c9cb --- /dev/null +++ b/radicle-surf/src/source.rs @@ -0,0 +1,23 @@ +//! The `source` module provides a layer on top of the [`crate::git`] +//! functionality. +//! +//! It provides data types of [`Blob`]s, [`Tree`]s, and [`Commit`]s +//! (see [git objects][git-objects]). These types are analgous to +//! [`crate::file_system::File`], [`crate::file_system::Directory`], and +//! [`crate::git::Commit`]. However, they provide extra metadata and +//! can be serialized to serve to other applications. For example, +//! they could be used in an HTTP server for viewing a Git repository. +//! +//! [git-objects]: https://git-scm.com/book/en/v2/Git-Internals-Git-Objects + +pub mod object; +pub use object::{blob, tree, Blob, BlobContent, Info, ObjectType, Tree}; + +pub mod commit; +pub use commit::{commit, commits, Commit}; + +pub mod person; +pub use person::Person; + +pub mod revision; +pub use revision::Revision; diff --git a/radicle-surf/src/commit.rs b/radicle-surf/src/source/commit.rs similarity index 99% rename from radicle-surf/src/commit.rs rename to radicle-surf/src/source/commit.rs index d2b77cf..2b1483f 100644 --- a/radicle-surf/src/commit.rs +++ b/radicle-surf/src/source/commit.rs @@ -29,8 +29,7 @@ use serde::{ use crate::{ diff, git::{self, glob, Glob, Repository}, - person::Person, - revision::Revision, + source::{person::Person, revision::Revision}, }; use radicle_git_ext::Oid; diff --git a/radicle-surf/src/object.rs b/radicle-surf/src/source/object.rs similarity index 98% rename from radicle-surf/src/object.rs rename to radicle-surf/src/source/object.rs index 4ef08b3..39105ab 100644 --- a/radicle-surf/src/object.rs +++ b/radicle-surf/src/source/object.rs @@ -32,7 +32,7 @@ pub use blob::{Blob, BlobContent}; pub mod tree; pub use tree::{tree, Tree, TreeEntry}; -use crate::{commit, file_system::directory, git}; +use crate::{file_system::directory, git, source::commit}; /// Git object types. /// diff --git a/radicle-surf/src/object/blob.rs b/radicle-surf/src/source/object/blob.rs similarity index 98% rename from radicle-surf/src/object/blob.rs rename to radicle-surf/src/source/object/blob.rs index b8bef4d..65d8161 100644 --- a/radicle-surf/src/object/blob.rs +++ b/radicle-surf/src/source/object/blob.rs @@ -30,10 +30,12 @@ use serde::{ }; use crate::{ - commit, git::Repository, - object::{Error, Info, ObjectType}, - revision::Revision, + source::{ + commit, + object::{Error, Info, ObjectType}, + revision::Revision, + }, }; #[cfg(feature = "syntax")] diff --git a/radicle-surf/src/object/tree.rs b/radicle-surf/src/source/object/tree.rs similarity index 97% rename from radicle-surf/src/object/tree.rs rename to radicle-surf/src/source/object/tree.rs index e60c26c..03b8e0f 100644 --- a/radicle-surf/src/object/tree.rs +++ b/radicle-surf/src/source/object/tree.rs @@ -28,11 +28,13 @@ use serde::{ }; use crate::{ - commit, file_system::directory, git::Repository, - object::{Error, Info, ObjectType}, - revision::Revision, + source::{ + commit, + object::{Error, Info, ObjectType}, + revision::Revision, + }, }; /// Result of a directory listing, carries other trees and blobs. diff --git a/radicle-surf/src/person.rs b/radicle-surf/src/source/person.rs similarity index 100% rename from radicle-surf/src/person.rs rename to radicle-surf/src/source/person.rs diff --git a/radicle-surf/src/revision.rs b/radicle-surf/src/source/revision.rs similarity index 100% rename from radicle-surf/src/revision.rs rename to radicle-surf/src/source/revision.rs From b4d3bf69cff13986a8a6ac604cd55babbd45b5e6 Mon Sep 17 00:00:00 2001 From: Fintan Halpenny Date: Tue, 22 Nov 2022 16:52:23 +0000 Subject: [PATCH 2/5] radicle-surf: refactor source objects Refactor the source data types to reuse existing logic and update to the latest protocol. The `Blob` and `Tree` data types are updated to be backed by `File` and `Directory` respectively. This allows the construction of them to be much simpler and reuse any existing logic on those types. This also lead to the removal of `ObjectType` and `Info`. The `Revision` type was removed in favour of using the `Revision` trait. The `Revisions` type was renamed to `View`, while also renaming the `revision` module to `view`. A `View` is a simple encoding of what a peer's "view" would look like, i.e. what references do they have. This also required the introduction of getting free-form categories in the reference db, i.e. `refs/`, and so a `Categories` iterator was also introduced. Signed-off-by: Fintan Halpenny X-Clacks-Overhead: GNU Terry Pratchett --- radicle-surf/Cargo.toml | 10 ++ radicle-surf/examples/browsing.rs | 2 +- radicle-surf/src/file_system/directory.rs | 40 ++++- radicle-surf/src/git.rs | 34 +++- radicle-surf/src/git/glob.rs | 49 ++++++ radicle-surf/src/git/history.rs | 2 +- radicle-surf/src/git/repo.rs | 51 ++++-- radicle-surf/src/git/repo/iter.rs | 58 ++++++- radicle-surf/src/lib.rs | 5 - radicle-surf/src/source.rs | 13 +- radicle-surf/src/source/commit.rs | 21 +-- radicle-surf/src/source/object.rs | 62 +------ radicle-surf/src/source/object/blob.rs | 200 ++++++++++------------ radicle-surf/src/source/object/tree.rs | 195 +++++++++------------ radicle-surf/src/source/revision.rs | 189 -------------------- radicle-surf/src/{ => source}/syntax.rs | 19 +- radicle-surf/src/source/view.rs | 96 +++++++++++ radicle-surf/t/src/git/last_commit.rs | 12 +- radicle-surf/t/src/git/namespace.rs | 8 +- radicle-surf/t/src/git/rev.rs | 6 +- 20 files changed, 534 insertions(+), 538 deletions(-) delete mode 100644 radicle-surf/src/source/revision.rs rename radicle-surf/src/{ => source}/syntax.rs (89%) create mode 100644 radicle-surf/src/source/view.rs diff --git a/radicle-surf/Cargo.toml b/radicle-surf/Cargo.toml index fc8eece..a720eed 100644 --- a/radicle-surf/Cargo.toml +++ b/radicle-surf/Cargo.toml @@ -21,6 +21,7 @@ doctest = false [features] serialize = ["serde"] +syntax = ["syntect", "lazy_static"] # NOTE: testing `test_submodule_failure` on GH actions # is painful since it uses this specific repo and expects # certain branches to be setup. So we use this feature flag @@ -29,6 +30,7 @@ gh-actions = [] [dependencies] base64 = "0.13" +log = "0.4" nonempty = "0.5" serde = { features = ["serde_derive"], optional = true, version = "1" } thiserror = "1.0" @@ -43,6 +45,10 @@ version = "0.1.0" path = "../git-ref-format" features = ["macro", "serde"] +[dependencies.lazy_static] +version = "1" +optional = true + [dependencies.radicle-git-ext] version = "0.2.0" path = "../radicle-git-ext" @@ -52,6 +58,10 @@ features = ["serde"] version = "0.1.0" path = "../radicle-std-ext" +[dependencies.syntect] +version = "5" +optional = true + [build-dependencies] anyhow = "1.0" flate2 = "1" diff --git a/radicle-surf/examples/browsing.rs b/radicle-surf/examples/browsing.rs index 91c9c19..a1128db 100644 --- a/radicle-surf/examples/browsing.rs +++ b/radicle-surf/examples/browsing.rs @@ -41,7 +41,7 @@ fn main() { let repo = Repository::discover(&repo_path).unwrap(); let now = Instant::now(); let head = repo.head_oid().unwrap(); - let root = repo.root_dir(head).unwrap(); + let root = repo.root_dir(&head).unwrap(); print_directory(&root, &repo, 0); let elapsed_millis = now.elapsed().as_millis(); diff --git a/radicle-surf/src/file_system/directory.rs b/radicle-surf/src/file_system/directory.rs index 21b3d1b..fea75eb 100644 --- a/radicle-surf/src/file_system/directory.rs +++ b/radicle-surf/src/file_system/directory.rs @@ -21,6 +21,7 @@ //! See [`Directory`] for more information. use std::{ + cmp::Ordering, collections::BTreeMap, convert::{Infallible, Into as _}, path::{Path, PathBuf}, @@ -186,6 +187,23 @@ pub enum Entry { Directory(Directory), } +impl PartialOrd for Entry { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +impl Ord for Entry { + fn cmp(&self, other: &Self) -> std::cmp::Ordering { + match (self, other) { + (Entry::File(x), Entry::File(y)) => x.name().cmp(y.name()), + (Entry::File(_), Entry::Directory(_)) => Ordering::Less, + (Entry::Directory(_), Entry::File(_)) => Ordering::Greater, + (Entry::Directory(x), Entry::Directory(y)) => x.name().cmp(y.name()), + } + } +} + impl Entry { /// Get a label for the `Entriess`, either the name of the [`File`] /// or the name of the [`Directory`]. @@ -196,6 +214,20 @@ impl Entry { } } + pub fn path(&self) -> PathBuf { + match self { + Entry::File(file) => file.path(), + Entry::Directory(directory) => directory.path(), + } + } + + pub fn location(&self) -> &Path { + match self { + Entry::File(file) => file.location(), + Entry::Directory(directory) => directory.location(), + } + } + pub(crate) fn from_entry( entry: &git2::TreeEntry, path: PathBuf, @@ -329,7 +361,7 @@ impl Directory { &self, path: &P, repo: &Repository, - ) -> Result, crate::git::Error> + ) -> Result, error::Directory> where P: AsRef, { @@ -351,12 +383,12 @@ impl Directory { &self, path: &P, repo: &Repository, - ) -> Result, crate::git::Error> + ) -> Result, error::Directory> where P: AsRef, { Ok(match self.find_entry(path, repo)? { - Some(Entry::File(f)) => Some(f.id), + Some(Entry::File(file)) => Some(file), _ => None, }) } @@ -366,7 +398,7 @@ impl Directory { &self, path: P, repo: &Repository, - ) -> Result, crate::git::Error> + ) -> Result, error::Directory> where P: AsRef, { diff --git a/radicle-surf/src/git.rs b/radicle-surf/src/git.rs index 3da31f5..0a8296c 100644 --- a/radicle-surf/src/git.rs +++ b/radicle-surf/src/git.rs @@ -167,6 +167,14 @@ impl Revision for &str { } } +impl Revision for Branch { + type Error = Error; + + fn object_id(&self, repo: &Repository) -> Result { + (&self).object_id(repo) + } +} + impl Revision for &Branch { type Error = Error; @@ -176,6 +184,14 @@ impl Revision for &Branch { } } +impl Revision for Tag { + type Error = Infallible; + + fn object_id(&self, repo: &Repository) -> Result { + (&self).object_id(repo) + } +} + impl Revision for &Tag { type Error = Infallible; @@ -184,27 +200,35 @@ impl Revision for &Tag { } } +impl Revision for Box { + type Error = R::Error; + + fn object_id(&self, repo: &Repository) -> Result { + self.as_ref().object_id(repo) + } +} + /// A common trait for anything that can convert to a `Commit`. pub trait ToCommit { type Error: std::error::Error + Send + Sync + 'static; /// Converts to a commit in `repo`. - fn to_commit(self, repo: &Repository) -> Result; + fn to_commit(&self, repo: &Repository) -> Result; } impl ToCommit for Commit { type Error = Infallible; - fn to_commit(self, _repo: &Repository) -> Result { - Ok(self) + fn to_commit(&self, _repo: &Repository) -> Result { + Ok(self.clone()) } } impl ToCommit for R { type Error = Error; - fn to_commit(self, repo: &Repository) -> Result { - let oid = repo.object_id(&self)?; + fn to_commit(&self, repo: &Repository) -> Result { + let oid = repo.object_id(self)?; let commit = repo.git2_repo().find_commit(oid.into())?; Ok(Commit::try_from(commit)?) } diff --git a/radicle-surf/src/git/glob.rs b/radicle-surf/src/git/glob.rs index 22d87db..9536cc8 100644 --- a/radicle-surf/src/git/glob.rs +++ b/radicle-surf/src/git/glob.rs @@ -20,6 +20,8 @@ use std::marker::PhantomData; use git_ref_format::{ refname, refspec::{self, PatternString, QualifiedPattern}, + Qualified, + RefStr, RefString, }; use thiserror::Error; @@ -39,6 +41,15 @@ pub struct Glob { glob_type: PhantomData, // To support different methods for different T. } +impl Default for Glob { + fn default() -> Self { + Self { + globs: Default::default(), + glob_type: PhantomData, + } + } +} + impl Glob { /// Return the [`QualifiedPattern`] globs of this `Glob`. pub fn globs(&self) -> impl Iterator> { @@ -303,6 +314,44 @@ impl From> for Glob { } } +impl Glob> { + pub fn all_category>(category: R) -> Self { + Self { + globs: vec![Self::qualify_category(category, refspec::pattern!("*"))], + glob_type: PhantomData, + } + } + + /// Creates a `Glob` for `refs/`, starting with `glob`. + pub fn categories(category: R, glob: PatternString) -> Self + where + R: AsRef, + { + let globs = vec![Self::qualify_category(category, glob)]; + Self { + globs, + glob_type: PhantomData, + } + } + + /// Adds a `refs/` pattern to this `Glob`. + pub fn insert(mut self, category: R, glob: PatternString) -> Self + where + R: AsRef, + { + self.globs.push(Self::qualify_category(category, glob)); + self + } + + fn qualify_category(category: R, glob: PatternString) -> QualifiedPattern<'static> + where + R: AsRef, + { + let prefix = refname!("refs").and(category); + qualify(&prefix, glob).expect("BUG: pattern is qualified") + } +} + fn qualify(prefix: &RefString, glob: PatternString) -> Option> { prefix.to_pattern(glob).qualified().map(|q| q.into_owned()) } diff --git a/radicle-surf/src/git/history.rs b/radicle-surf/src/git/history.rs index 45d25dc..26e6d33 100644 --- a/radicle-surf/src/git/history.rs +++ b/radicle-surf/src/git/history.rs @@ -38,7 +38,7 @@ enum FilterBy { impl<'a> History<'a> { /// Creates a new history starting from `head`, in `repo`. - pub fn new(repo: &'a Repository, head: C) -> Result { + pub fn new(repo: &'a Repository, head: &C) -> Result { let head = head .to_commit(repo) .map_err(|err| Error::ToCommit(err.into()))?; diff --git a/radicle-surf/src/git/repo.rs b/radicle-surf/src/git/repo.rs index a1770ce..04bb7b6 100644 --- a/radicle-surf/src/git/repo.rs +++ b/radicle-surf/src/git/repo.rs @@ -22,10 +22,7 @@ use std::{ str, }; -use git_ref_format::{ - refspec::{self, QualifiedPattern}, - Qualified, -}; +use git_ref_format::{refspec::QualifiedPattern, Qualified, RefString}; use radicle_git_ext::Oid; use thiserror::Error; @@ -50,7 +47,7 @@ use crate::{ }; pub mod iter; -pub use iter::{Branches, Namespaces, Tags}; +pub use iter::{Branches, Categories, Namespaces, Tags}; use self::iter::{BranchNames, TagNames}; @@ -61,6 +58,8 @@ pub enum Error { #[error(transparent)] Branches(#[from] iter::error::Branch), #[error(transparent)] + Categories(#[from] iter::error::Category), + #[error(transparent)] Commit(#[from] commit::Error), /// An error that comes from performing a *diff* operations. #[error(transparent)] @@ -136,6 +135,16 @@ impl Repository { Ok(tags) } + pub fn categories(&self, pattern: &Glob>) -> Result { + let mut cats = Categories::default(); + for glob in pattern.globs() { + let namespaced = self.namespaced_pattern(glob)?; + let references = self.inner.references_glob(&namespaced)?; + cats.push(references); + } + Ok(cats) + } + /// Returns an iterator of namespaces that match `pattern`. pub fn namespaces(&self, pattern: &Glob) -> Result { let mut set = BTreeSet::new(); @@ -189,7 +198,7 @@ impl Repository { /// /// To visit inside any nested sub-directories, call `directory.get(&repo)` /// on the sub-directory. - pub fn root_dir(&self, commit: C) -> Result { + pub fn root_dir(&self, commit: &C) -> Result { let commit = commit .to_commit(self) .map_err(|err| Error::ToCommit(err.into()))?; @@ -200,7 +209,7 @@ impl Repository { /// Returns the last commit, if exists, for a `path` in the history of /// `rev`. - pub fn last_commit(&self, path: P, rev: C) -> Result, Error> + pub fn last_commit(&self, path: P, rev: &C) -> Result, Error> where P: AsRef, C: ToCommit, @@ -215,7 +224,7 @@ impl Repository { } /// Gets stats of `commit`. - pub fn get_commit_stats(&self, commit: C) -> Result { + pub fn get_commit_stats(&self, commit: &C) -> Result { let branches = self.branches(Glob::all_heads())?.count(); let history = self.history(commit)?; let mut commits = 0; @@ -237,12 +246,6 @@ impl Repository { }) } - /// Obtain the file content - pub(crate) fn file_content(&self, object_id: Oid) -> Result { - let blob = self.inner.find_blob(object_id.into())?; - Ok(FileContent::new(blob)) - } - /// Retrieves the file with `path` in this commit. pub fn get_commit_file(&self, rev: &R, path: &P) -> Result where @@ -270,8 +273,8 @@ impl Repository { } /// Lists tag names in the local RefScope. - pub fn tag_names(&self) -> Result { - Ok(self.tags(&Glob::tags(refspec::pattern!("*")))?.names()) + pub fn tag_names(&self, filter: &Glob) -> Result { + Ok(self.tags(filter)?.names()) } /// Returns the Oid of the current HEAD @@ -282,8 +285,18 @@ impl Repository { } /// Switch to a `namespace` - pub fn switch_namespace(&self, namespace: &str) -> Result<(), Error> { - Ok(self.inner.set_namespace(namespace)?) + pub fn switch_namespace(&self, namespace: &RefString) -> Result<(), Error> { + Ok(self.inner.set_namespace(namespace.as_str())?) + } + + pub fn with_namespace(&self, namespace: &RefString, f: F) -> Result + where + F: FnOnce() -> Result, + { + self.switch_namespace(namespace)?; + let res = f(); + self.inner.remove_namespace()?; + res } /// Returns a full reference name with namespace(s) included. @@ -413,7 +426,7 @@ impl Repository { } /// Returns the history with the `head` commit. - pub fn history(&self, head: C) -> Result { + pub fn history(&self, head: &C) -> Result { History::new(self, head) } diff --git a/radicle-surf/src/git/repo/iter.rs b/radicle-surf/src/git/repo/iter.rs index 36920ab..33ec758 100644 --- a/radicle-surf/src/git/repo/iter.rs +++ b/radicle-surf/src/git/repo/iter.rs @@ -6,9 +6,9 @@ use std::{ convert::TryFrom as _, }; -use git_ref_format::{lit, Qualified}; +use git_ref_format::{lit, Qualified, RefString}; -use crate::git::{tag, Branch, Namespace, Tag}; +use crate::git::{refstr_join, tag, Branch, Namespace, Tag}; /// Iterator over [`Tag`]s. #[derive(Default)] @@ -165,7 +165,49 @@ impl Iterator for Namespaces { } } +#[derive(Default)] +pub struct Categories<'a> { + references: Vec>, + current: usize, +} + +impl<'a> Categories<'a> { + pub(super) fn push(&mut self, references: git2::References<'a>) { + self.references.push(references) + } +} + +impl<'a> Iterator for Categories<'a> { + type Item = Result<(RefString, RefString), error::Category>; + + fn next(&mut self) -> Option { + while self.current < self.references.len() { + match self.references.get_mut(self.current) { + Some(refs) => match refs.next() { + Some(res) => { + return Some(res.map_err(error::Category::from).and_then(|r| { + let name = std::str::from_utf8(r.name_bytes())?; + let name = git_ref_format::RefStr::try_from_str(name)?; + let name = name.qualified().ok_or_else(|| { + error::Category::NotQualified(name.to_ref_string()) + })?; + let (_refs, category, c, cs) = name.non_empty_components(); + Ok((category.to_ref_string(), refstr_join(c, cs))) + })); + }, + None => self.current += 1, + }, + None => break, + } + } + None + } +} + pub mod error { + use std::str; + + use git_ref_format::RefString; use thiserror::Error; use crate::git::{branch, tag}; @@ -178,6 +220,18 @@ pub mod error { Branch(#[from] branch::error::Branch), } + #[derive(Debug, Error)] + pub enum Category { + #[error(transparent)] + Git(#[from] git2::Error), + #[error("the reference '{0}' was expected to be qualified, i.e. 'refs//'")] + NotQualified(RefString), + #[error(transparent)] + RefFormat(#[from] git_ref_format::Error), + #[error(transparent)] + Utf8(#[from] str::Utf8Error), + } + #[derive(Debug, Error)] pub enum Tag { #[error(transparent)] diff --git a/radicle-surf/src/lib.rs b/radicle-surf/src/lib.rs index 3113878..53e50e8 100644 --- a/radicle-surf/src/lib.rs +++ b/radicle-surf/src/lib.rs @@ -83,8 +83,3 @@ pub mod diff; pub mod file_system; pub mod git; pub mod source; - -#[cfg(feature = "syntax")] -pub mod syntax; -#[cfg(feature = "syntax")] -pub use syntax::SYNTAX_SET; diff --git a/radicle-surf/src/source.rs b/radicle-surf/src/source.rs index 457c9cb..396cc80 100644 --- a/radicle-surf/src/source.rs +++ b/radicle-surf/src/source.rs @@ -11,13 +11,18 @@ //! [git-objects]: https://git-scm.com/book/en/v2/Git-Internals-Git-Objects pub mod object; -pub use object::{blob, tree, Blob, BlobContent, Info, ObjectType, Tree}; +pub use object::{blob, tree, Blob, BlobContent, Tree, TreeEntry}; pub mod commit; pub use commit::{commit, commits, Commit}; -pub mod person; +mod person; pub use person::Person; -pub mod revision; -pub use revision::Revision; +mod view; +pub use view::{view, View}; + +#[cfg(feature = "syntax")] +pub mod syntax; +#[cfg(feature = "syntax")] +pub use syntax::SYNTAX_SET; diff --git a/radicle-surf/src/source/commit.rs b/radicle-surf/src/source/commit.rs index 2b1483f..109f285 100644 --- a/radicle-surf/src/source/commit.rs +++ b/radicle-surf/src/source/commit.rs @@ -29,7 +29,7 @@ use serde::{ use crate::{ diff, git::{self, glob, Glob, Repository}, - source::{person::Person, revision::Revision}, + source::person::Person, }; use radicle_git_ext::Oid; @@ -227,18 +227,13 @@ pub fn header(repo: &Repository, sha1: Oid) -> Result { /// /// Will return [`Error`] if the project doesn't exist or the surf interaction /// fails. -pub fn commits(repo: &Repository, maybe_revision: Option) -> Result { - let rev = match maybe_revision { - Some(revision) => revision, - None => Revision::Sha { - sha: repo.head_oid()?, - }, - }; - - let stats = repo.get_commit_stats(&rev)?; - let commits: Result, git::Error> = repo.history(&rev)?.collect(); - let headers = commits?.iter().map(Header::from).collect(); - +pub fn commits(repo: &Repository, revision: &R) -> Result +where + R: git::Revision, +{ + let stats = repo.get_commit_stats(revision)?; + let commits = repo.history(revision)?.collect::, _>>()?; + let headers = commits.into_iter().map(Header::from).collect(); Ok(Commits { headers, stats }) } diff --git a/radicle-surf/src/source/object.rs b/radicle-surf/src/source/object.rs index 39105ab..6c943b5 100644 --- a/radicle-surf/src/source/object.rs +++ b/radicle-surf/src/source/object.rs @@ -20,68 +20,13 @@ use std::path::PathBuf; -#[cfg(feature = "serialize")] -use serde::{ - ser::{SerializeStruct as _, Serializer}, - Serialize, -}; - pub mod blob; pub use blob::{Blob, BlobContent}; pub mod tree; -pub use tree::{tree, Tree, TreeEntry}; +pub use tree::{Tree, TreeEntry}; -use crate::{file_system::directory, git, source::commit}; - -/// Git object types. -/// -/// `shafiul.github.io/gitbook/1_the_git_object_model.html` -#[derive(Debug, Eq, Ord, PartialOrd, PartialEq)] -pub enum ObjectType { - /// References a list of other trees and blobs. - Tree, - /// Used to store file data. - Blob, -} - -#[cfg(feature = "serialize")] -impl Serialize for ObjectType { - fn serialize(&self, serializer: S) -> Result - where - S: Serializer, - { - match self { - Self::Blob => serializer.serialize_unit_variant("ObjectType", 0, "BLOB"), - Self::Tree => serializer.serialize_unit_variant("ObjectType", 1, "TREE"), - } - } -} - -/// Set of extra information we carry for blob and tree objects returned from -/// the API. -pub struct Info { - /// Name part of an object. - pub name: String, - /// The type of the object. - pub object_type: ObjectType, - /// The last commmit that touched this object. - pub last_commit: Option, -} - -#[cfg(feature = "serialize")] -impl Serialize for Info { - fn serialize(&self, serializer: S) -> Result - where - S: Serializer, - { - let mut state = serializer.serialize_struct("Info", 3)?; - state.serialize_field("name", &self.name)?; - state.serialize_field("objectType", &self.object_type)?; - state.serialize_field("lastCommit", &self.last_commit)?; - state.end() - } -} +use crate::{file_system::directory, git}; /// An error reported by object types. #[derive(Debug, thiserror::Error)] @@ -89,6 +34,9 @@ pub enum Error { #[error(transparent)] Directory(#[from] directory::error::Directory), + #[error(transparent)] + File(#[from] directory::error::File), + /// An error occurred during a git operation. #[error(transparent)] Git(#[from] git::Error), diff --git a/radicle-surf/src/source/object/blob.rs b/radicle-surf/src/source/object/blob.rs index 65d8161..24b165f 100644 --- a/radicle-surf/src/source/object/blob.rs +++ b/radicle-surf/src/source/object/blob.rs @@ -18,10 +18,7 @@ //! Represents git object type 'blob', i.e. actual file contents. //! See git [doc](https://git-scm.com/book/en/v2/Git-Internals-Git-Objects) for more details. -use std::{ - path::{Path, PathBuf}, - str, -}; +use std::{path::Path, str}; #[cfg(feature = "serialize")] use serde::{ @@ -30,28 +27,63 @@ use serde::{ }; use crate::{ - git::Repository, - source::{ - commit, - object::{Error, Info, ObjectType}, - revision::Revision, - }, + file_system::{File, FileContent}, + git::{self, Repository}, + source::{commit, object::Error}, }; -#[cfg(feature = "syntax")] -use crate::syntax; - /// File data abstraction. pub struct Blob { - /// Actual content of the file, if the content is ASCII. + pub file: File, pub content: BlobContent, - /// Extra info for the file. - pub info: Info, - /// Absolute path to the object from the root of the repo. - pub path: PathBuf, + pub commit: Option, } impl Blob { + /// Returns the [`Blob`] for a file at `revision` under `path`. + /// + /// # Errors + /// + /// Will return [`Error`] if the project doesn't exist or a surf interaction + /// fails. + pub fn new(repo: &Repository, revision: &R, path: &P) -> Result + where + P: AsRef, + R: git::Revision, + { + Self::make_blob(repo, revision, path, |c| BlobContent::from(c)) + } + + fn make_blob( + repo: &Repository, + revision: &R, + path: &P, + content: impl FnOnce(FileContent) -> BlobContent, + ) -> Result + where + P: AsRef, + R: git::Revision, + { + let path = path.as_ref(); + let root = repo.root_dir(revision)?; + + let file = root + .find_file(&path, repo)? + .ok_or_else(|| Error::PathNotFound(path.to_path_buf()))?; + + let last_commit = repo + .last_commit(path, revision)? + .map(|c| commit::Header::from(&c)); + + let content = content(file.content(repo)?); + + Ok(Blob { + file, + content, + commit: last_commit, + }) + } + /// Indicates if the content of the [`Blob`] is binary. #[must_use] pub fn is_binary(&self) -> bool { @@ -71,12 +103,14 @@ impl Serialize for Blob { where S: Serializer, { - let mut state = serializer.serialize_struct("Blob", 5)?; + const FIELDS: usize = 6; + let mut state = serializer.serialize_struct("Blob", FIELDS)?; state.serialize_field("binary", &self.is_binary())?; state.serialize_field("html", &self.is_html())?; state.serialize_field("content", &self.content)?; - state.serialize_field("info", &self.info)?; - state.serialize_field("path", &self.path)?; + state.serialize_field("lastCommit", &self.commit)?; + state.serialize_field("name", &self.file.name())?; + state.serialize_field("path", &self.file.location())?; state.end() } } @@ -88,9 +122,9 @@ pub enum BlobContent { Plain(String), /// Content is syntax-highlighted HTML. /// - /// Note that is necessary to enable the `syntax` feature flag for this - /// variant to be constructed. Use `highlighting::blob`, instead of - /// [`blob`] to get highlighted content. + /// Note that it is necessary to enable the `syntax` feature flag + /// for this variant to be constructed. Use `Blob::highlighted`, + /// instead of `Blob::new` to get highlighted content. Html(String), /// Content is binary and needs special treatment. Binary(Vec), @@ -112,98 +146,54 @@ impl Serialize for BlobContent { } } -/// Returns the [`Blob`] for a file at `revision` under `path`. -/// -/// # Errors -/// -/// Will return [`Error`] if the project doesn't exist or a surf interaction -/// fails. -pub fn blob

(repo: &Repository, maybe_revision: Option, path: &P) -> Result -where - P: AsRef, -{ - make_blob(repo, maybe_revision, path, content) -} - -fn make_blob( - repo: &Repository, - maybe_revision: Option, - path: &P, - content: C, -) -> Result -where - P: AsRef, - C: FnOnce(&[u8]) -> BlobContent, -{ - let path = path.as_ref(); - let revision = maybe_revision.unwrap(); - let root = repo.root_dir(&revision)?; - - let file = root - .find_file(&path, repo)? - .ok_or_else(|| Error::PathNotFound(path.to_path_buf()))?; - - let last_commit = repo - .last_commit(path, &revision)? - .map(|c| commit::Header::from(&c)); - // TODO: fuck this - let name = path - .file_name() - .unwrap() - .to_os_string() - .into_string() - .ok() - .unwrap(); - - let file_content = repo.file_content(file)?; - let content = content(file_content.as_bytes()); - - Ok(Blob { - content, - info: Info { - name, - object_type: ObjectType::Blob, - last_commit, - }, - path: path.to_path_buf(), - }) -} - -/// Return a [`BlobContent`] given a byte slice. -fn content(content: &[u8]) -> BlobContent { - match str::from_utf8(content) { - Ok(utf8) => BlobContent::Plain(utf8.to_owned()), - Err(_) => BlobContent::Binary(content.to_owned()), +impl From> for BlobContent { + fn from(content: FileContent) -> Self { + let content = content.as_bytes(); + match str::from_utf8(content) { + Ok(utf8) => BlobContent::Plain(utf8.to_owned()), + Err(_) => BlobContent::Binary(content.to_owned()), + } } } #[cfg(feature = "syntax")] pub mod highlighting { + use crate::source::syntax; + use super::*; - /// Returns the [`Blob`] for a file at `revision` under `path`. - /// - /// # Errors - /// - /// Will return [`Error`] if the project doesn't exist or a surf interaction - /// fails. - pub fn blob

( - browser: &mut Browser, - maybe_revision: Option>, - path: &str, - theme: Option<&str>, - ) -> Result - where - P: ToString, - { - make_blob(browser, maybe_revision, path, |contents| { - content(path, contents, theme) - }) + impl Blob { + /// Returns the [`Blob`] for a file at `revision` under `path`. + /// + /// The content of the [`Blob`] will be highlighted, if possible. + /// + /// # Errors + /// + /// Will return [`Error`] if the project doesn't exist or a surf + /// interaction fails. + pub fn highlighted( + browser: &Repository, + revision: &R, + path: &P, + theme: Option<&str>, + ) -> Result + where + P: AsRef, + R: git::Revision, + { + Self::make_blob(browser, revision, path, |contents| { + content(path, &contents, theme) + }) + } } /// Return a [`BlobContent`] given a file path, content and theme. Attempts /// to perform syntax highlighting when the theme is `Some`. - fn content(path: &str, content: &[u8], theme_name: Option<&str>) -> BlobContent { + fn content

(path: P, content: &FileContent, theme_name: Option<&str>) -> BlobContent + where + P: AsRef, + { + let content = content.as_bytes(); let content = match str::from_utf8(content) { Ok(content) => content, Err(_) => return BlobContent::Binary(content.to_owned()), diff --git a/radicle-surf/src/source/object/tree.rs b/radicle-surf/src/source/object/tree.rs index 03b8e0f..3c83670 100644 --- a/radicle-surf/src/source/object/tree.rs +++ b/radicle-surf/src/source/object/tree.rs @@ -18,9 +18,8 @@ //! Represents git object type 'tree', i.e. like directory entries in Unix. //! See git [doc](https://git-scm.com/book/en/v2/Git-Internals-Git-Objects) for more details. -use std::path::{Path, PathBuf}; +use std::path::Path; -use git_ref_format::refname; #[cfg(feature = "serialize")] use serde::{ ser::{SerializeStruct as _, Serializer}, @@ -28,46 +27,95 @@ use serde::{ }; use crate::{ - file_system::directory, - git::Repository, - source::{ - commit, - object::{Error, Info, ObjectType}, - revision::Revision, - }, + file_system::{directory, Directory}, + git::{self, Repository}, + source::{commit, object::Error}, }; /// Result of a directory listing, carries other trees and blobs. pub struct Tree { - /// Absolute path to the tree object from the repo root. - pub path: PathBuf, + pub directory: Directory, + pub commit: Option, /// Entries listed in that tree result. pub entries: Vec, - /// Extra info for the tree object. - pub info: Info, } -#[cfg(feature = "serialize")] +impl Tree { + /// Retrieve the [`Tree`] for the given `revision` and directory `prefix`. + /// + /// # Errors + /// + /// Will return [`Error`] if any of the surf interactions fail. + pub fn new(repo: &Repository, revision: &R, prefix: Option<&P>) -> Result + where + P: AsRef, + R: git::Revision, + { + let prefix = prefix.map(|p| p.as_ref()); + + let prefix_dir = match prefix { + None => repo.root_dir(revision)?, + Some(path) => repo + .root_dir(revision)? + .find_directory(path, repo)? + .ok_or_else(|| Error::PathNotFound(path.to_path_buf()))?, + }; + + let mut entries = prefix_dir + .entries(repo)? + .entries() + .cloned() + .map(TreeEntry::from) + .collect::>(); + entries.sort(); + + let last_commit = if prefix.is_none() { + let history = repo.history(revision)?; + Some(commit::Header::from(history.head())) + } else { + None + }; + + Ok(Tree { + entries, + directory: prefix_dir, + commit: last_commit, + }) + } +} + +#[cfg(feature = "serde")] impl Serialize for Tree { fn serialize(&self, serializer: S) -> Result where S: Serializer, { - let mut state = serializer.serialize_struct("Tree", 3)?; - state.serialize_field("path", &self.path)?; + const FIELDS: usize = 4; + let mut state = serializer.serialize_struct("Tree", FIELDS)?; state.serialize_field("entries", &self.entries)?; - state.serialize_field("info", &self.info)?; + state.serialize_field("lastCommit", &self.commit)?; + state.serialize_field("name", &self.directory.name())?; + state.serialize_field("path", &self.directory.location())?; state.end() } } -// TODO(xla): Ensure correct by construction. /// Entry in a Tree result. +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] pub struct TreeEntry { - /// Extra info for the entry. - pub info: Info, - /// Absolute path to the object from the root of the repo. - pub path: PathBuf, + pub entry: directory::Entry, +} + +impl From for TreeEntry { + fn from(entry: directory::Entry) -> Self { + Self { entry } + } +} + +impl From for directory::Entry { + fn from(TreeEntry { entry }: TreeEntry) -> Self { + entry + } } #[cfg(feature = "serialize")] @@ -76,97 +124,18 @@ impl Serialize for TreeEntry { where S: Serializer, { - let mut state = serializer.serialize_struct("Tree", 2)?; - state.serialize_field("path", &self.path)?; - state.serialize_field("info", &self.info)?; + const FIELDS: usize = 4; + let mut state = serializer.serialize_struct("TreeEntry", FIELDS)?; + state.serialize_field("path", &self.entry.location())?; + state.serialize_field("name", &self.entry.name())?; + state.serialize_field("lastCommit", &None::)?; + state.serialize_field( + "kind", + match self.entry { + directory::Entry::File(_) => "blob", + directory::Entry::Directory(_) => "directory", + }, + )?; state.end() } } - -/// Retrieve the [`Tree`] for the given `revision` and directory `prefix`. -/// -/// # Errors -/// -/// Will return [`Error`] if any of the surf interactions fail. -pub fn tree

( - repo: &Repository, - maybe_revision: Option, - maybe_prefix: Option<&P>, -) -> Result -where - P: AsRef, -{ - let maybe_prefix = maybe_prefix.map(|p| p.as_ref()); - let rev = match maybe_revision { - Some(r) => r, - None => Revision::Branch { - name: refname!("main"), - remote: None, - }, - }; - - let prefix_dir = match maybe_prefix { - None => repo.root_dir(&rev)?, - Some(path) => repo - .root_dir(&rev)? - .find_directory(path, repo)? - .ok_or_else(|| Error::PathNotFound(path.to_path_buf()))?, - }; - - let mut entries = prefix_dir - .entries(repo)? - .entries() - .fold(Vec::new(), |mut entries, entry| { - let entry_path = match maybe_prefix { - Some(path) => { - let mut path = path.to_path_buf(); - path.push(entry.name()); - path - }, - None => PathBuf::new(), - }; - - let info = Info { - name: entry.name().clone(), - object_type: match entry { - directory::Entry::Directory(_) => ObjectType::Tree, - directory::Entry::File { .. } => ObjectType::Blob, - }, - last_commit: None, - }; - - entries.push(TreeEntry { - info, - path: entry_path, - }); - entries - }); - - // We want to ensure that in the response Tree entries come first. `Ord` being - // derived on the enum ensures Variant declaration order. - // - // https://doc.rust-lang.org/std/cmp/trait.Ord.html#derivable - entries.sort_by(|a, b| a.info.object_type.cmp(&b.info.object_type)); - - let last_commit = if maybe_prefix.is_none() { - let history = repo.history(&rev)?; - Some(commit::Header::from(history.head())) - } else { - None - }; - let name = match maybe_prefix { - None => "".into(), - Some(path) => path.file_name().unwrap().to_str().unwrap().to_string(), - }; - let info = Info { - name, - object_type: ObjectType::Tree, - last_commit, - }; - - Ok(Tree { - path: maybe_prefix.map_or(PathBuf::new(), |path| path.to_path_buf()), - entries, - info, - }) -} diff --git a/radicle-surf/src/source/revision.rs b/radicle-surf/src/source/revision.rs deleted file mode 100644 index 9651cf7..0000000 --- a/radicle-surf/src/source/revision.rs +++ /dev/null @@ -1,189 +0,0 @@ -// This file is part of radicle-surf -// -// -// Copyright (C) 2019-2020 The Radicle Team -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License version 3 or -// later as published by the Free Software Foundation. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . - -//! Represents revisions - -use std::collections::BTreeSet; - -use git_ref_format::{lit, refspec, Qualified, RefString}; - -#[cfg(feature = "serialize")] -use serde::{Deserialize, Serialize}; - -use radicle_git_ext::Oid; - -use crate::git::{self, Error, Glob, Repository}; - -/// Types of a peer. -pub enum Category { - /// Local peer. - Local { - /// Peer Id - peer_id: P, - /// User name - user: U, - }, - /// Remote peer. - Remote { - /// Peer Id - peer_id: P, - /// User name - user: U, - }, -} - -/// A revision selector for a `Browser`. -#[cfg_attr( - feature = "serialize", - derive(Deserialize, Serialize), - serde(rename_all = "camelCase", tag = "type") -)] -#[derive(Debug, Clone)] -pub enum Revision { - /// Select a tag under the name provided. - #[cfg_attr(feature = "serialize", serde(rename_all = "camelCase"))] - Tag { - /// Name of the tag. - name: RefString, - }, - /// Select a branch under the name provided. - #[cfg_attr(feature = "serialize", serde(rename_all = "camelCase"))] - Branch { - /// Name of the branch. - name: RefString, - /// The remote peer, if specified. - remote: Option, - }, - /// Select a SHA1 under the name provided. - #[cfg_attr(feature = "serialize", serde(rename_all = "camelCase"))] - Sha { - /// The SHA1 value. - sha: Oid, - }, -} - -impl git::Revision for &Revision { - type Error = git2::Error; - - fn object_id(&self, repo: &Repository) -> Result { - match self { - Revision::Tag { name } => match name.qualified() { - None => Qualified::from(lit::refs_tags(name)).object_id(repo), - Some(name) => name.object_id(repo), - }, - Revision::Branch { name, remote } => match remote { - Some(remote) => { - Qualified::from(lit::refs_remotes(remote.join(name))).object_id(repo) - }, - None => git::Branch::local(name).refname().object_id(repo), - }, - Revision::Sha { sha } => Ok(*sha), - } - } -} - -/// Bundled response to retrieve both branches and tags for -/// a user's repo. -#[derive(Clone, Debug, PartialEq, Eq)] -pub struct Revisions { - /// The peer peer_id for the user. - pub peer_id: P, - /// The user who owns these revisions. - pub user: U, - /// List of branch reference names. - pub branches: BTreeSet, - /// List of tag reference names. - pub tags: BTreeSet, -} - -/// Provide the [`Revisions`] for the given `peer_id`, looking for the -/// remote branches. -/// -/// If there are no branches then this returns `None`. -/// -/// # Errors -/// -/// * If we cannot get the branches from the `Browser` -pub fn remote( - repo: &Repository, - peer_id: RefString, - user: U, -) -> Result, Error> { - let pattern = peer_id.to_pattern(refspec::pattern!("*")); - let branches = repo - .branch_names(Glob::remotes(pattern))? - .map(|name| name.map(RefString::from)) - .collect::>()?; - Ok(Revisions { - peer_id, - user, - branches, - // TODO(rudolfs): implement remote peer tags once we decide how - // https://radicle.community/t/git-tags/214 - tags: BTreeSet::new(), - }) -} - -/// Provide the [`Revisions`] for the given `peer_id`, looking for the -/// local branches. -/// -/// If there are no branches then this returns `None`. -/// -/// # Errors -/// -/// * If we cannot get the branches from the `Browser` -pub fn local( - repo: &Repository, - peer_id: RefString, - user: U, -) -> Result, Error> { - let branches = repo - .branch_names(Glob::all_heads())? - .map(|name| name.map(RefString::from)) - .collect::>()?; - let tags = repo - .tag_names()? - .map(|name| name.map(RefString::from)) - .collect::>()?; - Ok(Revisions { - peer_id, - user, - branches, - tags, - }) -} - -/// Provide the [`Revisions`] of a peer. -/// -/// If the peer is [`Category::Local`], meaning that is the current person doing -/// the browsing and no remote is set for the reference. -/// -/// Othewise, the peer is [`Category::Remote`], meaning that we are looking into -/// a remote part of a reference. -/// -/// # Errors -/// -/// * If we cannot get the branches from the `Browser` -pub fn revisions( - repo: &Repository, - peer: Category, -) -> Result, Error> { - match peer { - Category::Local { peer_id, user } => local(repo, peer_id, user), - Category::Remote { peer_id, user } => remote(repo, peer_id, user), - } -} diff --git a/radicle-surf/src/syntax.rs b/radicle-surf/src/source/syntax.rs similarity index 89% rename from radicle-surf/src/syntax.rs rename to radicle-surf/src/source/syntax.rs index b572a35..b2f6c7a 100644 --- a/radicle-surf/src/syntax.rs +++ b/radicle-surf/src/source/syntax.rs @@ -15,7 +15,7 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -use std::path; +use std::path::Path; use syntect::{ easy::HighlightLines, @@ -56,10 +56,14 @@ lazy_static::lazy_static! { }; } -/// Return a [`BlobContent`] given a file path, content and theme. Attempts to -/// perform syntax highlighting when the theme is `Some`. -pub fn highlight(path: &str, content: &str, theme_name: &str) -> Option { - let syntax = path::Path::new(path) +/// Return highlighted text given a file path, the original text, and +/// a theme. +pub fn highlight

(path: P, content: &str, theme_name: &str) -> Option +where + P: AsRef, +{ + let syntax = path + .as_ref() .extension() .and_then(std::ffi::OsStr::to_str) .and_then(|ext| SYNTAX_SET.find_syntax_by_extension(ext)); @@ -73,12 +77,13 @@ pub fn highlight(path: &str, content: &str, theme_name: &str) -> Option let mut html = String::with_capacity(content.len()); for line in LinesWithEndings::from(content) { - let regions = highlighter.highlight(line, &SYNTAX_SET); + let regions = highlighter.highlight_line(line, &SYNTAX_SET).ok()?; syntect::html::append_highlighted_html_for_styled_line( ®ions[..], syntect::html::IncludeBackground::No, &mut html, - ); + ) + .ok(); } Some(html) }, diff --git a/radicle-surf/src/source/view.rs b/radicle-surf/src/source/view.rs new file mode 100644 index 0000000..e8554f9 --- /dev/null +++ b/radicle-surf/src/source/view.rs @@ -0,0 +1,96 @@ +// This file is part of radicle-surf +// +// +// Copyright (C) 2019-2020 The Radicle Team +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License version 3 or +// later as published by the Free Software Foundation. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +use std::collections::{BTreeMap, BTreeSet}; + +use git_ref_format::{RefStr, RefString}; + +use crate::git::{Error, Glob, Repository}; + +/// A `View` represents a logical view of peer's repository in the +/// [Heartwood protocol][heartwood]. +/// +/// The `Id` parameter allows the specification of what the identifier +/// for a peer is in Heartwood. +/// +/// The `P` parameter allows the specification of what a person looks +/// like in Heartwood, whether that be a DID document, website, etc. +/// +/// [heartwood]: https://github.com/radicle-dev/heartwood +pub struct View { + /// Identifier for the peer that this `View` is associated to. + pub identifier: Id, + /// Personal information for the peer that this `View` is associated to. + pub person: Option

, + /// All `refs/heads` and `refs/remotes` reference names. + pub branches: BTreeSet, + /// All `refs/tags` reference names. + pub tags: BTreeSet, + /// Any `refs/` reference names, where each key in the map is a + /// `` and the set is the references under that category. + pub references: BTreeMap>, +} + +/// Construct a [`View`] for the `identifier` and `person`. +/// +/// `identifier` is assumed to act as the namespace in the Heartwood storage. +/// +/// `categories` is the set of git non-standard reference categories, +/// i.e. `refs/heads`, `refs/remotes`, `refs/tags`, and `refs/notes`. +pub fn view( + repo: &Repository, + identifier: Id, + person: Option

, + categories: impl IntoIterator, +) -> Result, Error> +where + Id: Clone + Into, + R: AsRef, +{ + let namespace = identifier.clone().into(); + repo.with_namespace(&namespace.as_ref().to_ref_string(), move || { + let branches = repo + .branch_names(Glob::all_heads())? + .map(|name| name.map(RefString::from)) + .collect::>()?; + let tags = repo + .tag_names(&Glob::all_tags())? + .map(|name| name.map(RefString::from)) + .collect::>()?; + let categories = categories.into_iter().fold(Glob::default(), |globs, cat| { + globs.and(Glob::all_category(cat)) + }); + let references = + repo.categories(&categories)? + .try_fold(BTreeMap::new(), |mut map, r| { + let (cat, name) = r?; + map.entry(cat) + .and_modify(|names: &mut BTreeSet| { + names.insert(name.clone()); + }) + .or_insert_with(|| BTreeSet::from_iter(Some(name))); + Ok::<_, Error>(map) + })?; + Ok(View { + identifier, + person, + branches, + tags, + references, + }) + }) +} diff --git a/radicle-surf/t/src/git/last_commit.rs b/radicle-surf/t/src/git/last_commit.rs index d12dd18..335acf1 100644 --- a/radicle-surf/t/src/git/last_commit.rs +++ b/radicle-surf/t/src/git/last_commit.rs @@ -18,7 +18,7 @@ fn readme_missing_and_memory() { // memory.rs is commited later so it should not exist here. let memory_last_commit_oid = repo - .last_commit(Path::new("src/memory.rs"), oid) + .last_commit(Path::new("src/memory.rs"), &oid) .expect("Failed to get last commit") .map(|commit| commit.id); @@ -26,7 +26,7 @@ fn readme_missing_and_memory() { // README.md exists in this commit. let readme_last_commit = repo - .last_commit(Path::new("README.md"), oid) + .last_commit(Path::new("README.md"), &oid) .expect("Failed to get last commit") .map(|commit| commit.id); @@ -44,7 +44,7 @@ fn folder_svelte() { let expected_commit_id = Oid::from_str("f3a089488f4cfd1a240a9c01b3fcc4c34a4e97b2").unwrap(); let folder_svelte = repo - .last_commit(Path::new("examples/Folder.svelte"), oid) + .last_commit(Path::new("examples/Folder.svelte"), &oid) .expect("Failed to get last commit") .map(|commit| commit.id); @@ -64,7 +64,7 @@ fn nest_directory() { let nested_directory_tree_commit_id = repo .last_commit( Path::new("this/is/a/really/deeply/nested/directory/tree"), - oid, + &oid, ) .expect("Failed to get last commit") .map(|commit| commit.id); @@ -85,13 +85,13 @@ fn can_get_last_commit_for_special_filenames() { let expected_commit_id = Oid::from_str("a0dd9122d33dff2a35f564d564db127152c88e02").unwrap(); let backslash_commit_id = repo - .last_commit(Path::new(r"special/faux\\path"), oid) + .last_commit(Path::new(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(Path::new("special/👹👹👹"), oid) + .last_commit(Path::new("special/👹👹👹"), &oid) .expect("Failed to get last commit") .map(|commit| commit.id); assert_eq!(ogre_commit_id, Some(expected_commit_id)); diff --git a/radicle-surf/t/src/git/namespace.rs b/radicle-surf/t/src/git/namespace.rs index 4bc7a29..2fead75 100644 --- a/radicle-surf/t/src/git/namespace.rs +++ b/radicle-surf/t/src/git/namespace.rs @@ -8,7 +8,7 @@ use super::GIT_PLATINUM; fn switch_to_banana() -> Result<(), Error> { let repo = Repository::open(GIT_PLATINUM)?; let history_master = repo.history(&Branch::local(refname!("master")))?; - repo.switch_namespace("golden")?; + repo.switch_namespace(&refname!("golden"))?; let history_banana = repo.history(&Branch::local(refname!("banana")))?; assert_ne!(history_master.head(), history_banana.head()); @@ -23,7 +23,7 @@ fn me_namespace() -> Result<(), Error> { assert_eq!(repo.which_namespace().unwrap(), None); - repo.switch_namespace("me")?; + repo.switch_namespace(&refname!("me"))?; assert_eq!(repo.which_namespace().unwrap(), Some("me".parse()?)); let history_feature = repo.history(&Branch::local(refname!("feature/#1194")))?; @@ -58,7 +58,7 @@ fn golden_namespace() -> Result<(), Error> { assert_eq!(repo.which_namespace().unwrap(), None); - repo.switch_namespace("golden")?; + repo.switch_namespace(&refname!("golden"))?; assert_eq!(repo.which_namespace().unwrap(), Some("golden".parse()?)); @@ -101,7 +101,7 @@ fn silver_namespace() -> Result<(), Error> { assert_eq!(repo.which_namespace().unwrap(), None); - repo.switch_namespace("golden/silver")?; + repo.switch_namespace(&refname!("golden/silver"))?; assert_eq!( repo.which_namespace().unwrap(), Some("golden/silver".parse()?) diff --git a/radicle-surf/t/src/git/rev.rs b/radicle-surf/t/src/git/rev.rs index 2b7d1b8..ec25d33 100644 --- a/radicle-surf/t/src/git/rev.rs +++ b/radicle-surf/t/src/git/rev.rs @@ -44,7 +44,7 @@ fn _master() -> Result<(), Error> { fn commit() -> Result<(), Error> { let repo = Repository::open(GIT_PLATINUM)?; let rev = Oid::from_str("3873745c8f6ffb45c990eb23b491d4b4b6182f95")?; - let mut history = repo.history(rev)?; + let mut history = repo.history(&rev)?; let commit1 = Oid::from_str("3873745c8f6ffb45c990eb23b491d4b4b6182f95")?; assert!(history.any(|commit| commit.unwrap().id == commit1)); @@ -56,7 +56,7 @@ fn commit() -> Result<(), Error> { fn commit_parents() -> Result<(), Error> { let repo = Repository::open(GIT_PLATINUM)?; let rev = Oid::from_str("3873745c8f6ffb45c990eb23b491d4b4b6182f95")?; - let history = repo.history(rev)?; + let history = repo.history(&rev)?; let commit = history.head(); assert_eq!( @@ -71,7 +71,7 @@ fn commit_parents() -> Result<(), Error> { fn commit_short() -> Result<(), Error> { let repo = Repository::open(GIT_PLATINUM)?; let rev = repo.oid("3873745c8")?; - let mut history = repo.history(rev)?; + let mut history = repo.history(&rev)?; let commit1 = Oid::from_str("3873745c8f6ffb45c990eb23b491d4b4b6182f95")?; assert!(history.any(|commit| commit.unwrap().id == commit1)); From 3284149bd65b550ad4493dc388a0a17aba5fd663 Mon Sep 17 00:00:00 2001 From: Fintan Halpenny Date: Thu, 24 Nov 2022 15:02:25 +0000 Subject: [PATCH 3/5] radicle-surf: rename serialize to serde Instead of creating a named feature flag, opt for using the `serde` package name as the feature flag name. This makes it more clear that the feature is only enabling serde serialization and nothing else. This patch also cleans up the testing flags by removing the serialize flag for testing, as this should just always be tested, and also adds the syntax flag for future testing. Signed-off-by: Fintan Halpenny X-Clacks-Overhead: GNU Terry Pratchett --- radicle-surf/Cargo.toml | 7 ++- radicle-surf/src/diff.rs | 64 ++++++++------------------ radicle-surf/src/git/commit.rs | 14 +++--- radicle-surf/src/git/stats.rs | 8 +--- radicle-surf/src/source/commit.rs | 10 ++-- radicle-surf/src/source/object/blob.rs | 6 +-- radicle-surf/src/source/object/tree.rs | 4 +- radicle-surf/src/source/person.rs | 5 +- radicle-surf/t/Cargo.toml | 3 +- radicle-surf/t/src/git.rs | 2 - radicle-surf/t/src/git/commit.rs | 1 - radicle-surf/t/src/git/diff.rs | 1 - test/Cargo.toml | 2 +- 13 files changed, 45 insertions(+), 82 deletions(-) diff --git a/radicle-surf/Cargo.toml b/radicle-surf/Cargo.toml index a720eed..d32026e 100644 --- a/radicle-surf/Cargo.toml +++ b/radicle-surf/Cargo.toml @@ -20,7 +20,6 @@ test = false doctest = false [features] -serialize = ["serde"] syntax = ["syntect", "lazy_static"] # NOTE: testing `test_submodule_failure` on GH actions # is painful since it uses this specific repo and expects @@ -32,7 +31,6 @@ gh-actions = [] base64 = "0.13" log = "0.4" nonempty = "0.5" -serde = { features = ["serde_derive"], optional = true, version = "1" } thiserror = "1.0" [dependencies.git2] @@ -58,6 +56,11 @@ features = ["serde"] version = "0.1.0" path = "../radicle-std-ext" +[dependencies.serde] +version = "1" +features = ["serde_derive"] +optional = true + [dependencies.syntect] version = "5" optional = true diff --git a/radicle-surf/src/diff.rs b/radicle-surf/src/diff.rs index 855894c..8742734 100644 --- a/radicle-surf/src/diff.rs +++ b/radicle-surf/src/diff.rs @@ -19,16 +19,12 @@ use std::{convert::TryFrom, path::PathBuf, slice}; -#[cfg(feature = "serialize")] +#[cfg(feature = "serde")] use serde::{ser, Serialize, Serializer}; pub mod git; -#[cfg_attr( - feature = "serialize", - derive(Serialize), - serde(rename_all = "camelCase") -)] +#[cfg_attr(feature = "serde", derive(Serialize), serde(rename_all = "camelCase"))] #[derive(Clone, Debug, PartialEq, Eq)] pub struct Diff { pub created: Vec, @@ -44,47 +40,35 @@ impl Default for Diff { } } -#[cfg_attr(feature = "serialize", derive(Serialize))] +#[cfg_attr(feature = "serde", derive(Serialize))] #[derive(Clone, Debug, PartialEq, Eq)] pub struct CreateFile { pub path: PathBuf, pub diff: FileDiff, } -#[cfg_attr(feature = "serialize", derive(Serialize))] +#[cfg_attr(feature = "serde", derive(Serialize))] #[derive(Clone, Debug, PartialEq, Eq)] pub struct DeleteFile { pub path: PathBuf, pub diff: FileDiff, } -#[cfg_attr( - feature = "serialize", - derive(Serialize), - serde(rename_all = "camelCase") -)] +#[cfg_attr(feature = "serde", derive(Serialize), serde(rename_all = "camelCase"))] #[derive(Clone, Debug, PartialEq, Eq)] pub struct MoveFile { pub old_path: PathBuf, pub new_path: PathBuf, } -#[cfg_attr( - feature = "serialize", - derive(Serialize), - serde(rename_all = "camelCase") -)] +#[cfg_attr(feature = "serde", derive(Serialize), serde(rename_all = "camelCase"))] #[derive(Clone, Debug, PartialEq, Eq)] pub struct CopyFile { pub old_path: PathBuf, pub new_path: PathBuf, } -#[cfg_attr( - feature = "serialize", - derive(Serialize), - serde(rename_all = "camelCase") -)] +#[cfg_attr(feature = "serde", derive(Serialize), serde(rename_all = "camelCase"))] #[derive(Clone, Debug, PartialEq, Eq)] pub enum EofNewLine { OldMissing, @@ -92,11 +76,7 @@ pub enum EofNewLine { BothMissing, } -#[cfg_attr( - feature = "serialize", - derive(Serialize), - serde(rename_all = "camelCase") -)] +#[cfg_attr(feature = "serde", derive(Serialize), serde(rename_all = "camelCase"))] #[derive(Clone, Debug, PartialEq, Eq)] pub struct ModifiedFile { pub path: PathBuf, @@ -106,25 +86,21 @@ pub struct ModifiedFile { /// A set of changes belonging to one file. #[cfg_attr( - feature = "serialize", + feature = "serde", derive(Serialize), serde(tag = "type", rename_all = "camelCase") )] #[derive(Clone, Debug, PartialEq, Eq)] pub enum FileDiff { Binary, - #[cfg_attr(feature = "serialize", serde(rename_all = "camelCase"))] + #[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))] Plain { hunks: Hunks, }, } /// Statistics describing a particular [`Diff`]. -#[cfg_attr( - feature = "serialize", - derive(Serialize), - serde(rename_all = "camelCase") -)] +#[cfg_attr(feature = "serde", derive(Serialize), serde(rename_all = "camelCase"))] #[derive(Clone, Debug, Eq, PartialEq)] pub struct Stats { /// Get the total number of files changed in a diff. @@ -136,11 +112,7 @@ pub struct Stats { } /// A set of line changes. -#[cfg_attr( - feature = "serialize", - derive(Serialize), - serde(rename_all = "camelCase") -)] +#[cfg_attr(feature = "serde", derive(Serialize), serde(rename_all = "camelCase"))] #[derive(Clone, Debug, PartialEq, Eq)] pub struct Hunk { pub header: Line, @@ -148,7 +120,7 @@ pub struct Hunk { } /// A set of [`Hunk`]s. -#[cfg_attr(feature = "serialize", derive(Serialize))] +#[cfg_attr(feature = "serde", derive(Serialize))] #[derive(Clone, Debug, Default, PartialEq, Eq)] pub struct Hunks(pub Vec); @@ -215,7 +187,7 @@ impl From for Line { } } -#[cfg(feature = "serialize")] +#[cfg(feature = "serde")] impl Serialize for Line { fn serialize(&self, serializer: S) -> Result where @@ -230,22 +202,22 @@ impl Serialize for Line { /// Single line delta. Two of these are need to represented a modified line: one /// addition and one deletion. Context is also represented with this type. #[cfg_attr( - feature = "serialize", + feature = "serde", derive(Serialize), serde(tag = "type", rename_all = "camelCase") )] #[derive(Clone, Debug, PartialEq, Eq)] pub enum LineDiff { /// Line added. - #[cfg_attr(feature = "serialize", serde(rename_all = "camelCase"))] + #[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))] Addition { line: Line, line_num: u32 }, /// Line deleted. - #[cfg_attr(feature = "serialize", serde(rename_all = "camelCase"))] + #[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))] Deletion { line: Line, line_num: u32 }, /// Line context. - #[cfg_attr(feature = "serialize", serde(rename_all = "camelCase"))] + #[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))] Context { line: Line, line_num_old: u32, diff --git a/radicle-surf/src/git/commit.rs b/radicle-surf/src/git/commit.rs index 97ed8e7..272da9f 100644 --- a/radicle-surf/src/git/commit.rs +++ b/radicle-surf/src/git/commit.rs @@ -20,7 +20,7 @@ use std::{convert::TryFrom, str}; use git_ext::Oid; use thiserror::Error; -#[cfg(feature = "serialize")] +#[cfg(feature = "serde")] use serde::{ser::SerializeStruct, Deserialize, Deserializer, Serialize, Serializer}; #[derive(Debug, Error)] @@ -34,7 +34,7 @@ pub enum Error { } /// `Author` is the static information of a [`git2::Signature`]. -#[cfg_attr(feature = "serialize", derive(Deserialize, Serialize))] +#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))] #[derive(Clone, PartialEq, Eq, PartialOrd, Ord)] pub struct Author { /// Name of the author. @@ -43,7 +43,7 @@ pub struct Author { pub email: String, /// Time the action was taken, e.g. time of commit. #[cfg_attr( - feature = "serialize", + feature = "serde", serde( serialize_with = "serialize_time", deserialize_with = "deserialize_time" @@ -52,7 +52,7 @@ pub struct Author { pub time: git2::Time, } -#[cfg(feature = "serialize")] +#[cfg(feature = "serde")] fn deserialize_time<'de, D>(deserializer: D) -> Result where D: Deserializer<'de>, @@ -61,7 +61,7 @@ where Ok(git2::Time::new(seconds, 0)) } -#[cfg(feature = "serialize")] +#[cfg(feature = "serde")] fn serialize_time(t: &git2::Time, serializer: S) -> Result where S: Serializer, @@ -100,7 +100,7 @@ impl<'repo> TryFrom> for Author { /// `Commit` is the static information of a [`git2::Commit`]. To get back the /// original `Commit` in the repository we can use the [`Oid`] to retrieve /// it. -#[cfg_attr(feature = "serialize", derive(Deserialize))] +#[cfg_attr(feature = "serde", derive(Deserialize))] #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] pub struct Commit { /// Object Id @@ -129,7 +129,7 @@ impl Commit { } } -#[cfg(feature = "serialize")] +#[cfg(feature = "serde")] impl Serialize for Commit { fn serialize(&self, serializer: S) -> Result where diff --git a/radicle-surf/src/git/stats.rs b/radicle-surf/src/git/stats.rs index 5f0ad79..c2ac38b 100644 --- a/radicle-surf/src/git/stats.rs +++ b/radicle-surf/src/git/stats.rs @@ -17,15 +17,11 @@ pub use radicle_git_ext::Oid; -#[cfg(feature = "serialize")] +#[cfg(feature = "serde")] use serde::Serialize; /// Stats for a repository -#[cfg_attr( - feature = "serialize", - derive(Serialize), - serde(rename_all = "camelCase") -)] +#[cfg_attr(feature = "serde", derive(Serialize), serde(rename_all = "camelCase"))] pub struct Stats { /// Number of commits pub commits: usize, diff --git a/radicle-surf/src/source/commit.rs b/radicle-surf/src/source/commit.rs index 109f285..3bb3ce6 100644 --- a/radicle-surf/src/source/commit.rs +++ b/radicle-surf/src/source/commit.rs @@ -20,7 +20,7 @@ use std::path::PathBuf; use git_ref_format::RefString; -#[cfg(feature = "serialize")] +#[cfg(feature = "serde")] use serde::{ ser::{SerializeStruct as _, Serializer}, Serialize, @@ -35,7 +35,7 @@ use crate::{ use radicle_git_ext::Oid; /// Commit statistics. -#[cfg_attr(feature = "serialize", derive(Serialize))] +#[cfg_attr(feature = "serde", derive(Serialize))] #[derive(Clone)] pub struct Stats { /// Additions. @@ -45,7 +45,7 @@ pub struct Stats { } /// Representation of a changeset between two revs. -#[cfg_attr(feature = "serialize", derive(Serialize))] +#[cfg_attr(feature = "serde", derive(Serialize))] #[derive(Clone)] pub struct Commit { /// The commit header. @@ -114,7 +114,7 @@ impl From for Header { } } -#[cfg(feature = "serialize")] +#[cfg(feature = "serde")] impl Serialize for Header { fn serialize(&self, serializer: S) -> Result where @@ -132,7 +132,7 @@ impl Serialize for Header { } /// A selection of commit headers and their statistics. -#[cfg_attr(feature = "serialize", derive(Serialize))] +#[cfg_attr(feature = "serde", derive(Serialize))] pub struct Commits { /// The commit headers pub headers: Vec

, diff --git a/radicle-surf/src/source/object/blob.rs b/radicle-surf/src/source/object/blob.rs index 24b165f..9c89b4b 100644 --- a/radicle-surf/src/source/object/blob.rs +++ b/radicle-surf/src/source/object/blob.rs @@ -20,7 +20,7 @@ use std::{path::Path, str}; -#[cfg(feature = "serialize")] +#[cfg(feature = "serde")] use serde::{ ser::{SerializeStruct as _, Serializer}, Serialize, @@ -97,7 +97,7 @@ impl Blob { } } -#[cfg(feature = "serialize")] +#[cfg(feature = "serde")] impl Serialize for Blob { fn serialize(&self, serializer: S) -> Result where @@ -130,7 +130,7 @@ pub enum BlobContent { Binary(Vec), } -#[cfg(feature = "serialize")] +#[cfg(feature = "serde")] impl Serialize for BlobContent { fn serialize(&self, serializer: S) -> Result where diff --git a/radicle-surf/src/source/object/tree.rs b/radicle-surf/src/source/object/tree.rs index 3c83670..c06c416 100644 --- a/radicle-surf/src/source/object/tree.rs +++ b/radicle-surf/src/source/object/tree.rs @@ -20,7 +20,7 @@ use std::path::Path; -#[cfg(feature = "serialize")] +#[cfg(feature = "serde")] use serde::{ ser::{SerializeStruct as _, Serializer}, Serialize, @@ -118,7 +118,7 @@ impl From for directory::Entry { } } -#[cfg(feature = "serialize")] +#[cfg(feature = "serde")] impl Serialize for TreeEntry { fn serialize(&self, serializer: S) -> Result where diff --git a/radicle-surf/src/source/person.rs b/radicle-surf/src/source/person.rs index e200715..ad7ceec 100644 --- a/radicle-surf/src/source/person.rs +++ b/radicle-surf/src/source/person.rs @@ -17,12 +17,9 @@ //! Represents a person in a repo. -#[cfg(feature = "serialize")] -use serde::Serialize; - /// Representation of a person (e.g. committer, author, signer) from a /// repository. Usually extracted from a signature. -#[cfg_attr(feature = "serialize", derive(Serialize))] +#[cfg_attr(feature = "serde", derive(serde::Serialize))] #[derive(Clone, Debug)] pub struct Person { /// Name part of the commit signature. diff --git a/radicle-surf/t/Cargo.toml b/radicle-surf/t/Cargo.toml index efda92d..c9a737b 100644 --- a/radicle-surf/t/Cargo.toml +++ b/radicle-surf/t/Cargo.toml @@ -10,7 +10,6 @@ publish = false test = true [features] -serialize = [] test = [] [dev-dependencies] @@ -37,7 +36,7 @@ path = "../../radicle-git-ext" [dev-dependencies.radicle-surf] path = ".." -features = ["serialize"] +features = ["serde", "syntax"] [dev-dependencies.test-helpers] path = "../../test/test-helpers" diff --git a/radicle-surf/t/src/git.rs b/radicle-surf/t/src/git.rs index 513583a..1c989d1 100644 --- a/radicle-surf/t/src/git.rs +++ b/radicle-surf/t/src/git.rs @@ -5,12 +5,10 @@ const GIT_PLATINUM: &str = "../data/git-platinum"; -#[cfg(feature = "serialize")] #[cfg(test)] mod branch; #[cfg(test)] mod code_browsing; -#[cfg(feature = "serialize")] #[cfg(test)] mod commit; #[cfg(test)] diff --git a/radicle-surf/t/src/git/commit.rs b/radicle-surf/t/src/git/commit.rs index ef90cf5..5ad1bed 100644 --- a/radicle-surf/t/src/git/commit.rs +++ b/radicle-surf/t/src/git/commit.rs @@ -5,7 +5,6 @@ use radicle_git_ext::Oid; use radicle_surf::git::{Author, Commit}; use test_helpers::roundtrip; -#[cfg(feature = "serialize")] proptest! { #[test] fn prop_test_commits(commit in commits_strategy()) { diff --git a/radicle-surf/t/src/git/diff.rs b/radicle-surf/t/src/git/diff.rs index 1b89d5c..fa00741 100644 --- a/radicle-surf/t/src/git/diff.rs +++ b/radicle-surf/t/src/git/diff.rs @@ -122,7 +122,6 @@ fn test_branch_diff() -> Result<(), Error> { Ok(()) } -#[cfg(feature = "serialize")] #[test] fn test_diff_serde() { use radicle_surf::diff::{Hunks, MoveFile}; diff --git a/test/Cargo.toml b/test/Cargo.toml index 5e21e10..14761cb 100644 --- a/test/Cargo.toml +++ b/test/Cargo.toml @@ -33,7 +33,7 @@ features = ["test"] [dev-dependencies.radicle-surf-test] path = "../radicle-surf/t" -features = ["serialize", "test"] +features = ["test"] [dev-dependencies.git-storage-test] path = "../git-storage/t" From 59d25083922e34d1e9aa097589362ef19be0dda4 Mon Sep 17 00:00:00 2001 From: Fintan Halpenny Date: Mon, 28 Nov 2022 16:43:53 +0000 Subject: [PATCH 4/5] radicle-surf: remove syntax highlighting Remove the syntax feature flag and any of the syntax highlighting logic. This is not required any more from the clients implementation and so we minimise this codebase in favour of any downstream dependent to define how they highlight the blob's content. Signed-off-by: Fintan Halpenny X-Clacks-Overhead: GNU Terry Pratchett --- radicle-surf/Cargo.toml | 9 --- radicle-surf/src/source.rs | 5 -- radicle-surf/src/source/object/blob.rs | 68 +------------------ radicle-surf/src/source/syntax.rs | 92 -------------------------- radicle-surf/t/Cargo.toml | 2 +- 5 files changed, 3 insertions(+), 173 deletions(-) delete mode 100644 radicle-surf/src/source/syntax.rs diff --git a/radicle-surf/Cargo.toml b/radicle-surf/Cargo.toml index d32026e..f240586 100644 --- a/radicle-surf/Cargo.toml +++ b/radicle-surf/Cargo.toml @@ -20,7 +20,6 @@ test = false doctest = false [features] -syntax = ["syntect", "lazy_static"] # NOTE: testing `test_submodule_failure` on GH actions # is painful since it uses this specific repo and expects # certain branches to be setup. So we use this feature flag @@ -43,10 +42,6 @@ version = "0.1.0" path = "../git-ref-format" features = ["macro", "serde"] -[dependencies.lazy_static] -version = "1" -optional = true - [dependencies.radicle-git-ext] version = "0.2.0" path = "../radicle-git-ext" @@ -61,10 +56,6 @@ version = "1" features = ["serde_derive"] optional = true -[dependencies.syntect] -version = "5" -optional = true - [build-dependencies] anyhow = "1.0" flate2 = "1" diff --git a/radicle-surf/src/source.rs b/radicle-surf/src/source.rs index 396cc80..da54790 100644 --- a/radicle-surf/src/source.rs +++ b/radicle-surf/src/source.rs @@ -21,8 +21,3 @@ pub use person::Person; mod view; pub use view::{view, View}; - -#[cfg(feature = "syntax")] -pub mod syntax; -#[cfg(feature = "syntax")] -pub use syntax::SYNTAX_SET; diff --git a/radicle-surf/src/source/object/blob.rs b/radicle-surf/src/source/object/blob.rs index 9c89b4b..c3f95ad 100644 --- a/radicle-surf/src/source/object/blob.rs +++ b/radicle-surf/src/source/object/blob.rs @@ -89,12 +89,6 @@ impl Blob { pub fn is_binary(&self) -> bool { matches!(self.content, BlobContent::Binary(_)) } - - /// Indicates if the content of the [`Blob`] is HTML. - #[must_use] - pub const fn is_html(&self) -> bool { - matches!(self.content, BlobContent::Html(_)) - } } #[cfg(feature = "serde")] @@ -103,10 +97,9 @@ impl Serialize for Blob { where S: Serializer, { - const FIELDS: usize = 6; + const FIELDS: usize = 5; let mut state = serializer.serialize_struct("Blob", FIELDS)?; state.serialize_field("binary", &self.is_binary())?; - state.serialize_field("html", &self.is_html())?; state.serialize_field("content", &self.content)?; state.serialize_field("lastCommit", &self.commit)?; state.serialize_field("name", &self.file.name())?; @@ -120,12 +113,6 @@ impl Serialize for Blob { pub enum BlobContent { /// Content is plain text and can be passed as a string. Plain(String), - /// Content is syntax-highlighted HTML. - /// - /// Note that it is necessary to enable the `syntax` feature flag - /// for this variant to be constructed. Use `Blob::highlighted`, - /// instead of `Blob::new` to get highlighted content. - Html(String), /// Content is binary and needs special treatment. Binary(Vec), } @@ -137,7 +124,7 @@ impl Serialize for BlobContent { S: Serializer, { match self { - Self::Plain(content) | Self::Html(content) => serializer.serialize_str(content), + Self::Plain(content) => serializer.serialize_str(content), Self::Binary(bytes) => { let encoded = base64::encode(bytes); serializer.serialize_str(&encoded) @@ -155,54 +142,3 @@ impl From> for BlobContent { } } } - -#[cfg(feature = "syntax")] -pub mod highlighting { - use crate::source::syntax; - - use super::*; - - impl Blob { - /// Returns the [`Blob`] for a file at `revision` under `path`. - /// - /// The content of the [`Blob`] will be highlighted, if possible. - /// - /// # Errors - /// - /// Will return [`Error`] if the project doesn't exist or a surf - /// interaction fails. - pub fn highlighted( - browser: &Repository, - revision: &R, - path: &P, - theme: Option<&str>, - ) -> Result - where - P: AsRef, - R: git::Revision, - { - Self::make_blob(browser, revision, path, |contents| { - content(path, &contents, theme) - }) - } - } - - /// Return a [`BlobContent`] given a file path, content and theme. Attempts - /// to perform syntax highlighting when the theme is `Some`. - fn content

(path: P, content: &FileContent, theme_name: Option<&str>) -> BlobContent - where - P: AsRef, - { - let content = content.as_bytes(); - let content = match str::from_utf8(content) { - Ok(content) => content, - Err(_) => return BlobContent::Binary(content.to_owned()), - }; - - match theme_name { - None => BlobContent::Plain(content.to_owned()), - Some(theme) => syntax::highlight(path, content, theme) - .map_or_else(|| BlobContent::Plain(content.to_owned()), BlobContent::Html), - } - } -} diff --git a/radicle-surf/src/source/syntax.rs b/radicle-surf/src/source/syntax.rs deleted file mode 100644 index b2f6c7a..0000000 --- a/radicle-surf/src/source/syntax.rs +++ /dev/null @@ -1,92 +0,0 @@ -// This file is part of radicle-surf -// -// -// Copyright (C) 2019-2020 The Radicle Team -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License version 3 or -// later as published by the Free Software Foundation. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . - -use std::path::Path; - -use syntect::{ - easy::HighlightLines, - highlighting::ThemeSet, - parsing::SyntaxSet, - util::LinesWithEndings, -}; - -lazy_static::lazy_static! { - // The syntax set is slow to load (~30ms), so we make sure to only load it once. - // It _will_ affect the latency of the first request that uses syntax highlighting, - // but this is acceptable for now. - pub static ref SYNTAX_SET: SyntaxSet = { - let default_set = SyntaxSet::load_defaults_newlines(); - let mut builder = default_set.into_builder(); - - if cfg!(debug_assertions) { - // In development assets are relative to the proxy source. - // Don't crash if we aren't able to load additional syntaxes for some reason. - builder.add_from_folder("./assets", true).ok(); - } else { - // In production assets are relative to the proxy executable. - let exe_path = std::env::current_exe().expect("Can't get current exe path"); - let root_path = exe_path - .parent() - .expect("Could not get parent path of current executable"); - let mut tmp = root_path.to_path_buf(); - tmp.push("assets"); - let asset_path = tmp.to_str().expect("Couldn't convert pathbuf to str"); - - // Don't crash if we aren't able to load additional syntaxes for some reason. - match builder.add_from_folder(asset_path, true) { - Ok(_) => (), - Err(err) => log::warn!("Syntax builder error : {}", err), - }; - } - builder.build() - }; -} - -/// Return highlighted text given a file path, the original text, and -/// a theme. -pub fn highlight

(path: P, content: &str, theme_name: &str) -> Option -where - P: AsRef, -{ - let syntax = path - .as_ref() - .extension() - .and_then(std::ffi::OsStr::to_str) - .and_then(|ext| SYNTAX_SET.find_syntax_by_extension(ext)); - - let ts = ThemeSet::load_defaults(); - let theme = ts.themes.get(theme_name); - - match (syntax, theme) { - (Some(syntax), Some(theme)) => { - let mut highlighter = HighlightLines::new(syntax, theme); - let mut html = String::with_capacity(content.len()); - - for line in LinesWithEndings::from(content) { - let regions = highlighter.highlight_line(line, &SYNTAX_SET).ok()?; - syntect::html::append_highlighted_html_for_styled_line( - ®ions[..], - syntect::html::IncludeBackground::No, - &mut html, - ) - .ok(); - } - Some(html) - }, - _ => None, - } -} diff --git a/radicle-surf/t/Cargo.toml b/radicle-surf/t/Cargo.toml index c9a737b..c3d86f9 100644 --- a/radicle-surf/t/Cargo.toml +++ b/radicle-surf/t/Cargo.toml @@ -36,7 +36,7 @@ path = "../../radicle-git-ext" [dev-dependencies.radicle-surf] path = ".." -features = ["serde", "syntax"] +features = ["serde"] [dev-dependencies.test-helpers] path = "../../test/test-helpers" From c0545f4fac9403b3040f337db97769122e406647 Mon Sep 17 00:00:00 2001 From: Fintan Halpenny Date: Mon, 28 Nov 2022 16:45:52 +0000 Subject: [PATCH 5/5] radicle-surf: remove view The View type is specific to Heartwood and was preemptively defined when in fact it will not be used. YAGNI. Signed-off-by: Fintan Halpenny X-Clacks-Overhead: GNU Terry Pratchett --- radicle-surf/src/source.rs | 3 -- radicle-surf/src/source/view.rs | 96 --------------------------------- 2 files changed, 99 deletions(-) delete mode 100644 radicle-surf/src/source/view.rs diff --git a/radicle-surf/src/source.rs b/radicle-surf/src/source.rs index da54790..9e16e85 100644 --- a/radicle-surf/src/source.rs +++ b/radicle-surf/src/source.rs @@ -18,6 +18,3 @@ pub use commit::{commit, commits, Commit}; mod person; pub use person::Person; - -mod view; -pub use view::{view, View}; diff --git a/radicle-surf/src/source/view.rs b/radicle-surf/src/source/view.rs deleted file mode 100644 index e8554f9..0000000 --- a/radicle-surf/src/source/view.rs +++ /dev/null @@ -1,96 +0,0 @@ -// This file is part of radicle-surf -// -// -// Copyright (C) 2019-2020 The Radicle Team -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License version 3 or -// later as published by the Free Software Foundation. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . - -use std::collections::{BTreeMap, BTreeSet}; - -use git_ref_format::{RefStr, RefString}; - -use crate::git::{Error, Glob, Repository}; - -/// A `View` represents a logical view of peer's repository in the -/// [Heartwood protocol][heartwood]. -/// -/// The `Id` parameter allows the specification of what the identifier -/// for a peer is in Heartwood. -/// -/// The `P` parameter allows the specification of what a person looks -/// like in Heartwood, whether that be a DID document, website, etc. -/// -/// [heartwood]: https://github.com/radicle-dev/heartwood -pub struct View { - /// Identifier for the peer that this `View` is associated to. - pub identifier: Id, - /// Personal information for the peer that this `View` is associated to. - pub person: Option

, - /// All `refs/heads` and `refs/remotes` reference names. - pub branches: BTreeSet, - /// All `refs/tags` reference names. - pub tags: BTreeSet, - /// Any `refs/` reference names, where each key in the map is a - /// `` and the set is the references under that category. - pub references: BTreeMap>, -} - -/// Construct a [`View`] for the `identifier` and `person`. -/// -/// `identifier` is assumed to act as the namespace in the Heartwood storage. -/// -/// `categories` is the set of git non-standard reference categories, -/// i.e. `refs/heads`, `refs/remotes`, `refs/tags`, and `refs/notes`. -pub fn view( - repo: &Repository, - identifier: Id, - person: Option

, - categories: impl IntoIterator, -) -> Result, Error> -where - Id: Clone + Into, - R: AsRef, -{ - let namespace = identifier.clone().into(); - repo.with_namespace(&namespace.as_ref().to_ref_string(), move || { - let branches = repo - .branch_names(Glob::all_heads())? - .map(|name| name.map(RefString::from)) - .collect::>()?; - let tags = repo - .tag_names(&Glob::all_tags())? - .map(|name| name.map(RefString::from)) - .collect::>()?; - let categories = categories.into_iter().fold(Glob::default(), |globs, cat| { - globs.and(Glob::all_category(cat)) - }); - let references = - repo.categories(&categories)? - .try_fold(BTreeMap::new(), |mut map, r| { - let (cat, name) = r?; - map.entry(cat) - .and_modify(|names: &mut BTreeSet| { - names.insert(name.clone()); - }) - .or_insert_with(|| BTreeSet::from_iter(Some(name))); - Ok::<_, Error>(map) - })?; - Ok(View { - identifier, - person, - branches, - tags, - references, - }) - }) -}