radicle: Introduce `cob::DraftStore`
This storage backend for COBs stores changes in a `draft/cobs/*` namespace, which allows for some of the features needed for code review. For example, users can draft comments and later decide to publish them.
This commit is contained in:
parent
c9162121b0
commit
ff7e9ded17
|
|
@ -276,7 +276,7 @@ pub fn run(options: Options, ctx: impl term::Context) -> anyhow::Result<()> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Operation::Delete => {
|
Operation::Delete => {
|
||||||
let name = git::refs::storage::review(profile.id(), &patch_id);
|
let name = git::refs::storage::draft::review(profile.id(), &patch_id);
|
||||||
|
|
||||||
match repository.backend.find_reference(&name) {
|
match repository.backend.find_reference(&name) {
|
||||||
Ok(mut r) => r.delete()?,
|
Ok(mut r) => r.delete()?,
|
||||||
|
|
|
||||||
|
|
@ -182,7 +182,7 @@ impl<'a> ReviewBuilder<'a> {
|
||||||
pub fn new(patch_id: PatchId, nid: NodeId, repo: &'a Repository) -> Self {
|
pub fn new(patch_id: PatchId, nid: NodeId, repo: &'a Repository) -> Self {
|
||||||
Self {
|
Self {
|
||||||
patch_id,
|
patch_id,
|
||||||
refname: git::refs::storage::review(&nid, &patch_id),
|
refname: git::refs::storage::draft::review(&nid, &patch_id),
|
||||||
repo,
|
repo,
|
||||||
hunk: None,
|
hunk: None,
|
||||||
verdict: None,
|
verdict: None,
|
||||||
|
|
|
||||||
|
|
@ -241,14 +241,59 @@ pub mod refs {
|
||||||
.with_namespace(remote.into())
|
.with_namespace(remote.into())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Review draft reference.
|
/// Draft references.
|
||||||
|
///
|
||||||
|
/// These references are not replicated or signed.
|
||||||
|
pub mod draft {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
/// Review draft reference. Points to the non-COB part of a patch review.
|
||||||
|
///
|
||||||
|
/// `refs/namespaces/<remote>/refs/drafts/reviews/<patch-id>`
|
||||||
///
|
///
|
||||||
/// When building a patch review, we store the intermediate state in this ref.
|
/// When building a patch review, we store the intermediate state in this ref.
|
||||||
pub fn review<'a>(remote: &RemoteId, patch: &cob::ObjectId) -> Namespaced<'a> {
|
pub fn review<'a>(remote: &RemoteId, patch: &cob::ObjectId) -> Namespaced<'a> {
|
||||||
Qualified::from_components(component!("reviews"), Component::from(patch), None)
|
Qualified::from_components(
|
||||||
|
component!("drafts"),
|
||||||
|
component!("reviews"),
|
||||||
|
Some(Component::from(patch)),
|
||||||
|
)
|
||||||
.with_namespace(remote.into())
|
.with_namespace(remote.into())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A draft collaborative object. This can also be a draft operation on an existing
|
||||||
|
/// object.
|
||||||
|
///
|
||||||
|
/// `refs/namespaces/<remote>/refs/drafts/cobs/<typename>/<object_id>`
|
||||||
|
///
|
||||||
|
pub fn cob<'a>(
|
||||||
|
remote: &RemoteId,
|
||||||
|
typename: &cob::TypeName,
|
||||||
|
object_id: &cob::ObjectId,
|
||||||
|
) -> Namespaced<'a> {
|
||||||
|
Qualified::from_components(
|
||||||
|
component!("drafts"),
|
||||||
|
component!("cobs"),
|
||||||
|
[Component::from(typename), object_id.into()],
|
||||||
|
)
|
||||||
|
.with_namespace(remote.into())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Draft collaborative objects of a type.
|
||||||
|
///
|
||||||
|
/// `refs/namespaces/<remote>/refs/drafts/cobs/<typename>/*`
|
||||||
|
///
|
||||||
|
pub fn cobs(remote: &RemoteId, typename: &cob::TypeName) -> PatternString {
|
||||||
|
Qualified::from_components(
|
||||||
|
component!("drafts"),
|
||||||
|
component!("cobs"),
|
||||||
|
Some(Component::from(typename)),
|
||||||
|
)
|
||||||
|
.with_namespace(remote.into())
|
||||||
|
.to_pattern(refspec::pattern!("*"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Staging/temporary references.
|
/// Staging/temporary references.
|
||||||
pub mod staging {
|
pub mod staging {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,13 @@
|
||||||
//! COB storage Git backend.
|
//! COB storage Git backend.
|
||||||
use std::collections::BTreeMap;
|
use std::collections::BTreeMap;
|
||||||
|
|
||||||
|
use cob::object::Objects;
|
||||||
use radicle_cob as cob;
|
use radicle_cob as cob;
|
||||||
use radicle_cob::change;
|
use radicle_cob::change;
|
||||||
|
|
||||||
use crate::git;
|
use crate::git;
|
||||||
use crate::storage::Error;
|
use crate::storage::Error;
|
||||||
|
use crate::storage::ReadRepository;
|
||||||
|
|
||||||
pub use crate::git::*;
|
pub use crate::git::*;
|
||||||
pub use cob::*;
|
pub use cob::*;
|
||||||
|
|
@ -161,3 +163,119 @@ impl cob::object::Storage for Repository {
|
||||||
reference.delete().map_err(Self::RemoveError::from)
|
reference.delete().map_err(Self::RemoveError::from)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Stores draft collaborative objects.
|
||||||
|
pub struct DraftStore {
|
||||||
|
remote: RemoteId,
|
||||||
|
repo: Repository,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl cob::Store for DraftStore {}
|
||||||
|
|
||||||
|
impl change::Storage for DraftStore {
|
||||||
|
type StoreError = <git2::Repository as change::Storage>::StoreError;
|
||||||
|
type LoadError = <git2::Repository as change::Storage>::LoadError;
|
||||||
|
|
||||||
|
type ObjectId = <git2::Repository as change::Storage>::ObjectId;
|
||||||
|
type Parent = <git2::Repository as change::Storage>::Parent;
|
||||||
|
type Signatures = <git2::Repository as change::Storage>::Signatures;
|
||||||
|
|
||||||
|
fn store<Signer>(
|
||||||
|
&self,
|
||||||
|
authority: Self::Parent,
|
||||||
|
parents: Vec<Self::Parent>,
|
||||||
|
signer: &Signer,
|
||||||
|
spec: change::Template<Self::ObjectId>,
|
||||||
|
) -> Result<cob::Change, Self::StoreError>
|
||||||
|
where
|
||||||
|
Signer: crypto::Signer,
|
||||||
|
{
|
||||||
|
self.repo.backend.store(authority, parents, signer, spec)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn load(&self, id: Self::ObjectId) -> Result<cob::Change, Self::LoadError> {
|
||||||
|
self.repo.backend.load(id)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parents_of(&self, id: &Oid) -> Result<Vec<Oid>, Self::LoadError> {
|
||||||
|
self.repo.backend.parents_of(id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl cob::object::Storage for DraftStore {
|
||||||
|
type ObjectsError = ObjectsError;
|
||||||
|
type TypesError = git::ext::Error;
|
||||||
|
type UpdateError = git2::Error;
|
||||||
|
type RemoveError = git2::Error;
|
||||||
|
|
||||||
|
type Identifier = RemoteId;
|
||||||
|
|
||||||
|
fn objects(
|
||||||
|
&self,
|
||||||
|
typename: &cob::TypeName,
|
||||||
|
object_id: &cob::ObjectId,
|
||||||
|
) -> Result<cob::object::Objects, Self::ObjectsError> {
|
||||||
|
// Nb. There can only be one draft per COB, per remote.
|
||||||
|
let Ok(r) = self.repo.backend.find_reference(
|
||||||
|
git::refs::storage::draft::cob(&self.remote, typename, object_id).as_str(),
|
||||||
|
) else {
|
||||||
|
return Ok(Objects::default());
|
||||||
|
};
|
||||||
|
let r = cob::object::Reference::try_from(r).map_err(Self::ObjectsError::from)?;
|
||||||
|
|
||||||
|
Ok(Objects::new(r))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn types(
|
||||||
|
&self,
|
||||||
|
typename: &cob::TypeName,
|
||||||
|
) -> Result<BTreeMap<cob::ObjectId, cob::object::Objects>, Self::TypesError> {
|
||||||
|
let glob = git::refs::storage::draft::cobs(&self.remote, typename);
|
||||||
|
let references = self.repo.references_glob(&glob)?;
|
||||||
|
let mut objs = BTreeMap::new();
|
||||||
|
|
||||||
|
for (name, id) in references {
|
||||||
|
let r = cob::object::Reference {
|
||||||
|
name: name.into_refstring(),
|
||||||
|
target: cob::object::Commit { id },
|
||||||
|
};
|
||||||
|
objs.insert(id.into(), Objects::new(r));
|
||||||
|
}
|
||||||
|
Ok(objs)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update(
|
||||||
|
&self,
|
||||||
|
identifier: &Self::Identifier,
|
||||||
|
typename: &cob::TypeName,
|
||||||
|
object_id: &cob::ObjectId,
|
||||||
|
change: &cob::Change,
|
||||||
|
) -> Result<(), Self::UpdateError> {
|
||||||
|
self.repo.backend.reference(
|
||||||
|
git::refs::storage::draft::cob(identifier, typename, object_id).as_str(),
|
||||||
|
(*change.id()).into(),
|
||||||
|
true,
|
||||||
|
&format!(
|
||||||
|
"Updating draft collaborative object '{}/{}' with new change {}",
|
||||||
|
typename,
|
||||||
|
object_id,
|
||||||
|
change.id()
|
||||||
|
),
|
||||||
|
)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn remove(
|
||||||
|
&self,
|
||||||
|
identifier: &Self::Identifier,
|
||||||
|
typename: &cob::TypeName,
|
||||||
|
object_id: &cob::ObjectId,
|
||||||
|
) -> Result<(), Self::RemoveError> {
|
||||||
|
let mut reference = self.repo.backend.find_reference(
|
||||||
|
git::refs::storage::draft::cob(identifier, typename, object_id).as_str(),
|
||||||
|
)?;
|
||||||
|
|
||||||
|
reference.delete().map_err(Self::RemoveError::from)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue