diff --git a/radicle-surf/src/object.rs b/radicle-surf/src/object.rs index f95a15a..426f09c 100644 --- a/radicle-surf/src/object.rs +++ b/radicle-surf/src/object.rs @@ -23,5 +23,3 @@ pub use blob::{Blob, BlobContent}; pub mod tree; pub use tree::{Tree, TreeEntry}; - -pub mod commit; diff --git a/radicle-surf/src/object/blob.rs b/radicle-surf/src/object/blob.rs index 94d6095..f449dca 100644 --- a/radicle-surf/src/object/blob.rs +++ b/radicle-surf/src/object/blob.rs @@ -27,18 +27,18 @@ use serde::{ Serialize, }; -use crate::object::commit; +use crate::Commit; /// Represents a git blob object. pub struct Blob { id: Oid, content: BlobContent, - commit: commit::Header, + commit: Commit, } impl Blob { /// Returns the [`Blob`] for a file at `revision` under `path`. - pub(crate) fn new(id: Oid, content: &[u8], commit: commit::Header) -> Self { + pub(crate) fn new(id: Oid, content: &[u8], commit: Commit) -> Self { let content = BlobContent::from(content); Self { id, @@ -62,7 +62,7 @@ impl Blob { } /// Returns the commit that created this blob. - pub fn commit(&self) -> &commit::Header { + pub fn commit(&self) -> &Commit { &self.commit } } diff --git a/radicle-surf/src/object/commit.rs b/radicle-surf/src/object/commit.rs deleted file mode 100644 index 1253ad3..0000000 --- a/radicle-surf/src/object/commit.rs +++ /dev/null @@ -1,203 +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 a commit. - -use std::path::PathBuf; - -use git_ref_format::RefString; -#[cfg(feature = "serde")] -use serde::{ - ser::{SerializeStruct as _, Serializer}, - Serialize, -}; - -use crate::{diff, glob, Glob, Repository, Revision}; - -use radicle_git_ext::Oid; - -/// Representation of a changeset between two revs. -#[cfg_attr(feature = "serde", derive(Serialize))] -#[derive(Clone)] -pub struct Commit { - /// The commit header. - pub header: Header, - /// The changeset introduced by this commit. - pub diff: diff::Diff, - /// The list of branches this commit belongs to. - pub branches: Vec, -} - -/// Representation of a code commit. -#[derive(Clone, Debug)] -pub struct Header { - /// Identifier of the commit in the form of a sha1 hash. Often referred to - /// as oid or object id. - pub sha1: Oid, - /// The author of the commit. - pub author: Person, - /// The summary of the commit message body. - pub summary: String, - /// The entire commit message body. - pub message: String, - /// The committer of the commit. - pub committer: Person, - /// The recorded time of the committer signature. This is a convenience - /// alias until we expose the actual author and commiter signatures. - pub committer_time: git2::Time, -} - -impl Header { - /// Returns the commit description text. This is the text after the one-line - /// summary. - #[must_use] - pub fn description(&self) -> &str { - self.message - .strip_prefix(&self.summary) - .unwrap_or(&self.message) - .trim() - } -} - -impl From<&crate::Commit> for Header { - fn from(commit: &crate::Commit) -> Self { - Self { - sha1: commit.id, - author: Person { - name: commit.author.name.clone(), - email: commit.author.email.clone(), - }, - summary: commit.summary.clone(), - message: commit.message.clone(), - committer: Person { - name: commit.committer.name.clone(), - email: commit.committer.email.clone(), - }, - committer_time: commit.committer.time, - } - } -} - -impl From for Header { - fn from(commit: crate::Commit) -> Self { - Self::from(&commit) - } -} - -#[cfg(feature = "serde")] -impl Serialize for Header { - fn serialize(&self, serializer: S) -> Result - where - S: Serializer, - { - let mut state = serializer.serialize_struct("Header", 6)?; - state.serialize_field("sha1", &self.sha1.to_string())?; - state.serialize_field("author", &self.author)?; - state.serialize_field("summary", &self.summary)?; - state.serialize_field("description", &self.description())?; - state.serialize_field("committer", &self.committer)?; - state.serialize_field("committerTime", &self.committer_time.seconds())?; - state.end() - } -} - -/// A selection of commit headers and their statistics. -#[cfg_attr(feature = "serde", derive(Serialize))] -pub struct Commits { - /// The commit headers - pub headers: Vec
, - /// The statistics for the commit headers - pub stats: crate::Stats, -} - -/// Retrieves a [`Commit`]. -/// -/// # Errors -/// -/// Will return [`Error`] if the project doesn't exist or the surf interaction -/// fails. -pub fn commit(repo: &Repository, rev: R) -> Result { - let commit = repo.commit(rev)?; - let sha1 = commit.id; - let header = Header::from(&commit); - let diff = repo.diff_commit(commit)?; - - let branches = repo - .revision_branches(&sha1, Glob::all_heads().branches().and(Glob::all_remotes()))? - .into_iter() - .map(|b| b.refname().into()) - .collect(); - - Ok(Commit { - header, - diff, - branches, - }) -} - -/// Retrieves the [`Header`] for the given `sha1`. -/// -/// # Errors -/// -/// Will return [`Error`] if the project doesn't exist or the surf interaction -/// fails. -pub fn header(repo: &Repository, sha1: Oid) -> Result { - let commit = repo.commit(sha1)?; - Ok(Header::from(&commit)) -} - -/// Retrieves the [`Commit`] history for the given `revision`. -/// -/// # Errors -/// -/// Will return [`Error`] if the project doesn't exist or the surf interaction -/// fails. -pub fn commits(repo: &Repository, revision: &R) -> Result -where - R: Revision, -{ - let stats = repo.stats_from(revision)?; - let commits = repo.history(revision)?.collect::, _>>()?; - let headers = commits.into_iter().map(Header::from).collect(); - Ok(Commits { headers, stats }) -} - -/// An error reported by commit API. -#[derive(Debug, thiserror::Error)] -pub enum Error { - /// An error occurred during a git operation. - #[error(transparent)] - Git(#[from] crate::Error), - - #[error(transparent)] - Glob(#[from] glob::Error), - - /// Trying to find a file path which could not be found. - #[error("the path '{0}' was not found")] - PathNotFound(PathBuf), -} - -/// Representation of a person (e.g. committer, author, signer) from a -/// repository. Usually extracted from a signature. -#[cfg_attr(feature = "serde", derive(serde::Serialize))] -#[derive(Clone, Debug)] -pub struct Person { - /// Name part of the commit signature. - pub name: String, - /// Email part of the commit signature. - pub email: String, -} diff --git a/radicle-surf/src/object/tree.rs b/radicle-surf/src/object/tree.rs index 3378edd..fe46729 100644 --- a/radicle-surf/src/object/tree.rs +++ b/radicle-surf/src/object/tree.rs @@ -27,7 +27,7 @@ use serde::{ Serialize, }; -use crate::{fs, object::commit}; +use crate::{fs, Commit}; /// Represents a tree object as in git. It is essentially the content of /// one directory. Note that multiple directories can have the same content, @@ -39,12 +39,12 @@ pub struct Tree { /// The first descendant entries for this tree. entries: Vec, /// The commit object that created this tree object. - commit: commit::Header, + commit: Commit, } impl Tree { /// Creates a new tree, ensuring the `entries` are sorted. - pub(crate) fn new(id: Oid, mut entries: Vec, commit: commit::Header) -> Self { + pub(crate) fn new(id: Oid, mut entries: Vec, commit: Commit) -> Self { entries.sort(); Self { id, @@ -58,7 +58,7 @@ impl Tree { } /// Returns the commit that created this tree. - pub fn commit(&self) -> &commit::Header { + pub fn commit(&self) -> &Commit { &self.commit } @@ -148,11 +148,11 @@ pub struct TreeEntry { entry: Entry, /// The commit object that created this entry object. - commit: commit::Header, + commit: Commit, } impl TreeEntry { - pub(crate) fn new(name: String, entry: Entry, commit: commit::Header) -> Self { + pub(crate) fn new(name: String, entry: Entry, commit: Commit) -> Self { Self { name, entry, @@ -172,7 +172,7 @@ impl TreeEntry { matches!(self.entry, Entry::Tree(_)) } - pub fn commit(&self) -> &commit::Header { + pub fn commit(&self) -> &Commit { &self.commit } diff --git a/radicle-surf/src/repo.rs b/radicle-surf/src/repo.rs index dc70466..ca145df 100644 --- a/radicle-surf/src/repo.rs +++ b/radicle-surf/src/repo.rs @@ -32,7 +32,7 @@ use crate::{ fs::{self, Directory, File, FileContent}, glob, namespace, - object::{commit::Header, Blob, Tree, TreeEntry}, + object::{Blob, Tree, TreeEntry}, refs::{self, BranchNames, Branches, Categories, Namespaces, TagNames, Tags}, Branch, Commit, @@ -283,8 +283,7 @@ impl Repository { let commit = self .last_commit(&path, commit.id)? .ok_or(Error::PathNotFound(path))?; - let commit_header = Header::from(commit); - Ok(TreeEntry::new(name, en.into(), commit_header)) + Ok(TreeEntry::new(name, en.into(), commit)) }) .collect::, Error>>()?; entries.sort(); @@ -292,8 +291,7 @@ impl Repository { let last_commit = self .last_commit(path, commit)? .ok_or_else(|| Error::PathNotFound(path.as_ref().to_path_buf()))?; - let header = Header::from(last_commit); - Ok(Tree::new(dir.id(), entries, header)) + Ok(Tree::new(dir.id(), entries, last_commit)) } /// Returns a [`Blob`] for `path` in `commit`. @@ -305,10 +303,8 @@ impl Repository { let last_commit = self .last_commit(path, commit)? .ok_or_else(|| Error::PathNotFound(path.as_ref().to_path_buf()))?; - let header = Header::from(last_commit); - let content = file.content(self)?; - Ok(Blob::new(file.id(), content.as_bytes(), header)) + Ok(Blob::new(file.id(), content.as_bytes(), last_commit)) } /// Returns the last commit, if exists, for a `path` in the history of @@ -415,31 +411,32 @@ impl Repository { pub fn history(&self, head: C) -> Result { History::new(self, head) } -} -//////////////////////////////////////////////////////////// -// Private API, ONLY add `pub(crate) fn` or `fn` in here. // -//////////////////////////////////////////////////////////// -impl Repository { - /// Lists branches that are reachable from `oid`. - pub(crate) fn revision_branches( + /// Lists branches that are reachable from `rev`. + pub fn revision_branches( &self, - oid: &Oid, + rev: impl Revision, glob: Glob, ) -> Result, Error> { + let oid = self.object_id(&rev)?; let mut contained_branches = vec![]; for branch in self.branches(glob)? { let branch = branch?; let namespaced = self.namespaced_refname(&branch.refname())?; let reference = self.inner.find_reference(namespaced.as_str())?; - if self.reachable_from(&reference, oid)? { + if self.reachable_from(&reference, &oid)? { contained_branches.push(branch); } } Ok(contained_branches) } +} +//////////////////////////////////////////////////////////// +// Private API, ONLY add `pub(crate) fn` or `fn` in here. // +//////////////////////////////////////////////////////////// +impl Repository { pub(crate) fn find_blob(&self, oid: Oid) -> Result, git2::Error> { self.inner.find_blob(oid.into()) } diff --git a/radicle-surf/t/src/source.rs b/radicle-surf/t/src/source.rs index 3380f23..56cecdf 100644 --- a/radicle-surf/t/src/source.rs +++ b/radicle-surf/t/src/source.rs @@ -1,7 +1,7 @@ use std::path::PathBuf; use git_ref_format::refname; -use radicle_surf::Repository; +use radicle_surf::{Glob, Repository}; use serde_json::json; const GIT_PLATINUM: &str = "../data/git-platinum"; @@ -18,15 +18,18 @@ fn tree_serialization() { "lastCommit": { "author": { "email": "fintan.halpenny@gmail.com", - "name": "Fintan Halpenny" + "name": "Fintan Halpenny", + "time": 1578309972 }, "committer": { "email": "noreply@github.com", - "name": "GitHub" + "name": "GitHub", + "time": 1578309972 }, - "committerTime": 1578309972, "description": "I want to have files under src that have separate commits.\r\nThat way src's latest commit isn't the same as all its files, instead it's the file that was touched last.", - "sha1": "3873745c8f6ffb45c990eb23b491d4b4b6182f95", + "id": "3873745c8f6ffb45c990eb23b491d4b4b6182f95", + "message": "Extend the docs (#2)\n\nI want to have files under src that have separate commits.\r\nThat way src's latest commit isn't the same as all its files, instead it's the file that was touched last.", + "parents": ["d6880352fc7fda8f521ae9b7357668b17bb5bad5"], "summary": "Extend the docs (#2)" }, "name": "Eval.hs", @@ -37,15 +40,18 @@ fn tree_serialization() { "lastCommit": { "author": { "email": "rudolfs@osins.org", - "name": "Rūdolfs Ošiņš" + "name": "Rūdolfs Ošiņš", + "time": 1575283266 }, "committer": { "email": "rudolfs@osins.org", - "name": "Rūdolfs Ošiņš" + "name": "Rūdolfs Ošiņš", + "time": 1575283266 }, - "committerTime": 1575283266, "description": "", - "sha1": "e24124b7538658220b5aaf3b6ef53758f0a106dc", + "id": "e24124b7538658220b5aaf3b6ef53758f0a106dc", + "message": "Move examples to \"src\"\n", + "parents": ["19bec071db6474af89c866a1bd0e4b1ff76e2b97"], "summary": "Move examples to \"src\"" }, "name": "memory.rs", @@ -55,15 +61,18 @@ fn tree_serialization() { "lastCommit": { "author": { "email": "rudolfs@osins.org", - "name": "Rūdolfs Ošiņš" + "name": "Rūdolfs Ošiņš", + "time": 1582198877 }, "committer": { "email": "noreply@github.com", - "name": "GitHub" + "name": "GitHub", + "time": 1582198877 }, - "committerTime": 1582198877, "description": "It was a bad idea to have an actual source file which is used by\r\nradicle-upstream in the fixtures repository. It gets in the way of\r\nlinting and editors pick it up as a regular source file by accident.", - "sha1": "a57846bbc8ced6587bf8329fc4bce970eb7b757e", + "id": "a57846bbc8ced6587bf8329fc4bce970eb7b757e", + "message": "Remove src/Folder.svelte (#3)\n\nIt was a bad idea to have an actual source file which is used by\r\nradicle-upstream in the fixtures repository. It gets in the way of\r\nlinting and editors pick it up as a regular source file by accident.", + "parents": ["3873745c8f6ffb45c990eb23b491d4b4b6182f95"], "summary": "Remove src/Folder.svelte (#3)" }, "oid": "ed52e9f8dfe1d8b374b2a118c25235349a743dd2" @@ -81,7 +90,7 @@ fn repo_tree() { let commit_header = tree.commit(); assert_eq!( - commit_header.sha1.to_string(), + commit_header.id.to_string(), "e24124b7538658220b5aaf3b6ef53758f0a106dc" ); @@ -102,7 +111,7 @@ fn repo_tree() { ); let commit = entry.commit(); assert_eq!( - commit.sha1.to_string(), + commit.id.to_string(), "e24124b7538658220b5aaf3b6ef53758f0a106dc" ); @@ -128,7 +137,7 @@ fn repo_blob() { let commit_header = blob.commit(); assert_eq!( - commit_header.sha1.to_string(), + commit_header.id.to_string(), "e24124b7538658220b5aaf3b6ef53758f0a106dc" ); @@ -165,3 +174,26 @@ fn tree_ordering() { ] ); } + +#[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(), 7); + assert_eq!(branches[0].refname().as_str(), "refs/heads/dev"); + assert_eq!(branches[1].refname().as_str(), "refs/heads/master"); + assert_eq!( + branches[2].refname().as_str(), + "refs/remotes/banana/orange/pineapple" + ); + assert_eq!( + branches[3].refname().as_str(), + "refs/remotes/banana/pineapple" + ); + assert_eq!(branches[4].refname().as_str(), "refs/remotes/origin/HEAD"); + assert_eq!(branches[5].refname().as_str(), "refs/remotes/origin/dev"); + assert_eq!(branches[6].refname().as_str(), "refs/remotes/origin/master"); +}