consolidate object::commit.rs with commit.rs

Signed-off-by: Han Xu <keepsimple@gmail.com>
This commit is contained in:
Han Xu
2023-01-07 13:05:32 -08:00
parent 2bb35ed996
commit 6bb9670abc
6 changed files with 73 additions and 249 deletions
-2
View File
@@ -23,5 +23,3 @@ pub use blob::{Blob, BlobContent};
pub mod tree;
pub use tree::{Tree, TreeEntry};
pub mod commit;
+4 -4
View File
@@ -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
}
}
-203
View File
@@ -1,203 +0,0 @@
// This file is part of radicle-surf
// <https://github.com/radicle-dev/radicle-surf>
//
// Copyright (C) 2019-2020 The Radicle Team <dev@radicle.xyz>
//
// 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 <https://www.gnu.org/licenses/>.
//! 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<RefString>,
}
/// 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<crate::Commit> for Header {
fn from(commit: crate::Commit) -> Self {
Self::from(&commit)
}
}
#[cfg(feature = "serde")]
impl Serialize for Header {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
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<Header>,
/// 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<R: Revision>(repo: &Repository, rev: R) -> Result<Commit, Error> {
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<Header, Error> {
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<R>(repo: &Repository, revision: &R) -> Result<Commits, Error>
where
R: Revision,
{
let stats = repo.stats_from(revision)?;
let commits = repo.history(revision)?.collect::<Result<Vec<_>, _>>()?;
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,
}
+7 -7
View File
@@ -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<TreeEntry>,
/// 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<TreeEntry>, commit: commit::Header) -> Self {
pub(crate) fn new(id: Oid, mut entries: Vec<TreeEntry>, 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
}
+14 -17
View File
@@ -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::<Result<Vec<TreeEntry>, 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<C: ToCommit>(&self, head: C) -> Result<History, Error> {
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<Branch>,
) -> Result<Vec<Branch>, 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::Blob<'_>, git2::Error> {
self.inner.find_blob(oid.into())
}
+48 -16
View File
@@ -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");
}