cob: Fix recursion bug in TryFrom of Reference

This commit addresses the issue of recursion in the TryFrom
call of Reference by resolving the parent in a lazy way.

Due to constraints posed by the need to access `git2::Repository`
or `git2::Commit` to resolve the parent, this commit decides to
pull out the parent resolution logic from the `cob::object::Commit`
module and move it inside the `cob::store::Store` implementation by adding
a new method `parents_of`.

Suggested-by: Fintan Halpenny <fintan.halpenny@gmail.com>
Tested-by: Vincenzo Palazzo <vincenzopalazzodev@gmail.com>
Signed-off-by: Vincenzo Palazzo <vincenzopalazzodev@gmail.com>
This commit is contained in:
Vincenzo Palazzo 2023-05-04 17:13:26 +02:00 committed by Alexis Sellier
parent 1d9ee81b6d
commit b29321dbf7
No known key found for this signature in database
6 changed files with 53 additions and 24 deletions

View File

@ -139,6 +139,14 @@ impl change::Storage for git2::Repository {
}) })
} }
fn parents_of(&self, id: &Oid) -> Result<Vec<Oid>, Self::LoadError> {
Ok(self
.find_commit(**id)?
.parent_ids()
.map(Oid::from)
.collect::<Vec<_>>())
}
fn load(&self, id: Self::ObjectId) -> Result<Change, Self::LoadError> { fn load(&self, id: Self::ObjectId) -> Result<Change, Self::LoadError> {
let commit = Commit::read(self, id.into())?; let commit = Commit::read(self, id.into())?;
let timestamp = git2::Time::from(commit.committer().time).seconds() as u64; let timestamp = git2::Time::from(commit.committer().time).seconds() as u64;

View File

@ -3,6 +3,7 @@
use std::{error::Error, fmt}; use std::{error::Error, fmt};
use nonempty::NonEmpty; use nonempty::NonEmpty;
use radicle_git_ext::Oid;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use crate::{ use crate::{
@ -37,6 +38,9 @@ pub trait Storage {
&self, &self,
id: Self::ObjectId, id: Self::ObjectId,
) -> Result<Change<Self::Parent, Self::ObjectId, Self::Signatures>, Self::LoadError>; ) -> Result<Change<Self::Parent, Self::ObjectId, Self::Signatures>, Self::LoadError>;
/// Returns the parents of the object with the specified ID.
fn parents_of(&self, id: &Oid) -> Result<Vec<Oid>, Self::LoadError>;
} }
/// Change template, used to create a new change. /// Change template, used to create a new change.

View File

@ -32,15 +32,16 @@ impl ChangeGraph {
{ {
log::info!("loading object '{}' '{}'", typename, oid); log::info!("loading object '{}' '{}'", typename, oid);
let mut builder = GraphBuilder::default(); let mut builder = GraphBuilder::default();
let mut edges_to_process: Vec<(object::Commit, Oid)> = Vec::new(); let mut edges_to_process: Vec<(Oid, Oid)> = Vec::new();
// Populate the initial set of edges_to_process from the refs we have // Populate the initial set of edges_to_process from the refs we have
for reference in tip_refs { for reference in tip_refs {
log::trace!("loading object from reference '{}'", reference.name); log::trace!("loading object from reference '{}'", reference.name);
match storage.load(reference.target.id) { match storage.load(reference.target.id) {
Ok(change) => { Ok(change) => {
let commit = reference.target.clone(); let new_edges = builder
let new_edges = builder.add_change(commit, change); .add_change(storage, reference.target.id, change)
.ok()?;
edges_to_process.extend(new_edges); edges_to_process.extend(new_edges);
} }
Err(e) => { Err(e) => {
@ -55,23 +56,22 @@ impl ChangeGraph {
} }
// Process edges until we have no more to process // Process edges until we have no more to process
while let Some((parent_commit, child_commit_id)) = edges_to_process.pop() { while let Some((parent_commit_id, child_commit_id)) = edges_to_process.pop() {
log::trace!( log::trace!(
"loading change parent='{}', child='{}'", "loading change parent='{}', child='{}'",
parent_commit.id, parent_commit_id,
child_commit_id child_commit_id
); );
match storage.load(parent_commit.id) { match storage.load(parent_commit_id) {
Ok(change) => { Ok(change) => {
let parent_commit_id = parent_commit.id; let new_edges = builder.add_change(storage, parent_commit_id, change).ok()?;
let new_edges = builder.add_change(parent_commit, change);
edges_to_process.extend(new_edges); edges_to_process.extend(new_edges);
builder.add_edge(child_commit_id, parent_commit_id); builder.add_edge(child_commit_id, parent_commit_id);
} }
Err(e) => { Err(e) => {
log::warn!( log::warn!(
"unable to load changetree from commit '{}', error '{}'", "unable to load changetree from commit '{}', error '{}'",
parent_commit.id, parent_commit_id,
e e
); );
} }
@ -122,24 +122,32 @@ impl Default for GraphBuilder {
impl GraphBuilder { impl GraphBuilder {
/// Add a change to the graph which we are building up, returning any edges /// Add a change to the graph which we are building up, returning any edges
/// corresponding to the parents of this node in the change graph /// corresponding to the parents of this node in the change graph
fn add_change( fn add_change<S>(
&mut self, &mut self,
commit: object::Commit, storage: &S,
commit_id: Oid,
change: Change, change: Change,
) -> impl Iterator<Item = (object::Commit, Oid)> + '_ { ) -> Result<Vec<(Oid, Oid)>, S::LoadError>
where
S: change::Storage<ObjectId = Oid, Parent = Oid, Signatures = ExtendedSignature>,
{
let resource_commit = *change.resource(); let resource_commit = *change.resource();
let commit_id = commit.id;
if !self.graph.contains(&commit_id) { if !self.graph.contains(&commit_id) {
self.graph.node(commit_id, change); self.graph.node(commit_id, change);
} }
commit.parents.into_iter().filter_map(move |parent| {
if parent.id != resource_commit && !self.graph.has_dependency(&commit_id, &parent.id) { Ok(storage
Some((parent, commit_id)) .parents_of(&commit_id)?
} else { .into_iter()
None .filter_map(move |parent| {
} if parent != resource_commit && !self.graph.has_dependency(&commit_id, &parent) {
}) Some((parent, commit_id))
} else {
None
}
})
.collect::<Vec<(Oid, Oid)>>())
} }
fn add_edge(&mut self, child: Oid, parent: Oid) { fn add_edge(&mut self, child: Oid, parent: Oid) {

View File

@ -50,8 +50,6 @@ pub struct Reference {
pub struct Commit { pub struct Commit {
/// The content identifier of the commit. /// The content identifier of the commit.
pub id: Oid, pub id: Oid,
/// The parents of the commit.
pub parents: Vec<Commit>,
} }
pub trait Storage { pub trait Storage {
@ -129,10 +127,8 @@ pub mod convert {
impl<'a> From<git2::Commit<'a>> for Commit { impl<'a> From<git2::Commit<'a>> for Commit {
fn from(commit: git2::Commit<'a>) -> Self { fn from(commit: git2::Commit<'a>) -> Self {
let parents = commit.parents().map(Commit::from).collect();
Commit { Commit {
id: commit.id().into(), id: commit.id().into(),
parents,
} }
} }
} }

View File

@ -91,6 +91,15 @@ impl change::Storage for Storage {
> { > {
self.as_raw().load(id) self.as_raw().load(id)
} }
fn parents_of(&self, id: &git_ext::Oid) -> Result<Vec<git_ext::Oid>, Self::LoadError> {
Ok(self
.as_raw()
.find_commit(**id)?
.parent_ids()
.map(git_ext::Oid::from)
.collect::<Vec<_>>())
}
} }
impl object::Storage for Storage { impl object::Storage for Storage {

View File

@ -60,6 +60,10 @@ impl change::Storage for Repository {
fn load(&self, id: Self::ObjectId) -> Result<cob::Change, Self::LoadError> { fn load(&self, id: Self::ObjectId) -> Result<cob::Change, Self::LoadError> {
self.backend.load(id) self.backend.load(id)
} }
fn parents_of(&self, id: &Oid) -> Result<Vec<Oid>, Self::LoadError> {
self.backend.parents_of(id)
}
} }
impl cob::object::Storage for Repository { impl cob::object::Storage for Repository {