mirror of
https://seed.radicle.dev/rad:z6cFWeWpnZNHh9rUW8phgA3b5yGt.git
synced 2026-10-24 05:40:38 +08:00
consolidate object::commit.rs with commit.rs
Signed-off-by: Han Xu <keepsimple@gmail.com>
This commit is contained in:
@@ -23,5 +23,3 @@ pub use blob::{Blob, BlobContent};
|
||||
|
||||
pub mod tree;
|
||||
pub use tree::{Tree, TreeEntry};
|
||||
|
||||
pub mod commit;
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
@@ -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
@@ -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())
|
||||
}
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user